npm package: @netlisian/dynamic-functions
Description: A **Blender-inspired visual programming interface** for creating dynamic data transformations. Build complex transformation pipelines using an intuitive node-based editor powered by React Flow.
Latest version: 0.0.0

# Dynamic Function Package

A **Blender-inspired visual programming interface** for creating dynamic data transformations. Build complex transformation pipelines using an intuitive node-based editor powered by React Flow.

> **🎨 NEW: React Flow Visual Programming System**
> - **[Quick Start Guide](./QUICKSTART.md)** - Get started in 5 minutes
> - **[Visual Programming Docs](./VISUAL_PROGRAMMING.md)** - Complete feature guide
> - **[Implementation Summary](./IMPLEMENTATION_SUMMARY.md)** - Architecture details
> - **[Simple Example](./EXAMPLE_SIMPLE.md)** - Step-by-step tutorial

> **📚 Legacy Rete.js Documentation:**
> - [Quick Reference](./QUICK_REFERENCE.md) - Essential patterns and code snippets
> - [React Integration Guide](./REACT_INTEGRATION_GUIDE.md) - Complete setup walkthrough
> - [Setup Summary](./REACT_SETUP_SUMMARY.md) - What we've implemented so far

## Features

### Visual Programming (React Flow)
- **🎨 Blender-Inspired Design**: Professional dark theme with color-coded sockets
- **🔌 Smart Connections**: Type-safe socket connections with visual feedback
- **📦 50+ Pre-built Operations**: Arithmetic, logical, string, array, and object operations
- **🎯 Drag & Drop**: Intuitive node creation from sidebar
- **💾 Serializable Graphs**: Save and load complete graph state
- **⚡ Runtime Execution**: Convert visual graphs to executable functions
- **🔍 Input Fields**: Edit values directly in unconnected input sockets
- **🎭 Three Node Types**:
  - **Source Nodes**: Data input with configurable paths
  - **Transform Nodes**: Operation nodes for data transformation
  - **Destination Nodes**: Data output targets

### Legacy Rete.js Support
- **Visual Node Editor**: Built with Rete.js for intuitive visual programming
- **Three Node Types**:
  - **From Nodes**: Input fields that provide values to the transform function
  - **Operator Nodes**: Processing nodes for transformations (math, logic, string operations)
  - **To Nodes**: Output fields that receive transformed values
- **Built-in Operators**:
  - Math: Add, Subtract, Multiply, Divide, Modulo, Power
  - Logic: AND, OR, NOT, Equals, Not Equals, Greater/Less Than
  - String: Concat, Uppercase, Lowercase, Trim, Split, Join, Replace, Substring
- **Real-time Validation**: Detects circular dependencies and validates connections
- **TypeScript Support**: Fully typed API with Puck integration

## Installation

```bash
pnpm add @netlisian/dynamic-functions
```

## Quick Start (React Flow)

```tsx
import { DynamicFunctionModal } from '@netlisian/dynamic-functions';
import '@xyflow/react/dist/style.css';

function MyComponent() {
  const [isOpen, setIsOpen] = useState(false);
  const [mappings, setMappings] = useState([]);

  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Open Visual Editor
      </button>

      <DynamicFunctionModal
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        fromOptions={[
          { label: 'First Name', value: 'user.firstName' },
          { label: 'Last Name', value: 'user.lastName' },
        ]}
        toOptions={[
          { label: 'Full Name', value: 'profile.fullName' },
        ]}
        value={mappings}
        onChange={setMappings}
      />
    </>
  );
}
```

See the [Quick Start Guide](./QUICKSTART.md) for a complete tutorial.

## Usage (Legacy Rete.js)

### Simple Editor (Getting Started)

For learning and testing, use the SimpleEditor which follows official Rete.js patterns:

```tsx
import { SimpleEditorTest } from "@netlisian/dynamic-functions";
import "@netlisian/dynamic-functions/dist/styles.css";

function App() {
  return (
    <div>
      <h1>Simple Rete.js Editor</h1>
      <SimpleEditorTest />
    </div>
  );
}
```

Or create your own editor programmatically:

```tsx
import { SimpleEditor, createNumberNode, createAddNode } from "@netlisian/dynamic-functions";
import { ClassicPreset } from "rete";

function MyCustomEditor() {
  const containerRef = useRef<HTMLDivElement>(null);
  const editorRef = useRef<SimpleEditor | null>(null);

  useEffect(() => {
    if (!containerRef.current) return;

    const initEditor = async () => {
      const editor = new SimpleEditor(containerRef.current!);
      
      // Add nodes
      const num1 = createNumberNode(5);
      const num2 = createNumberNode(10);
      const add = createAddNode();
      
      await editor.addNode(num1, { x: 50, y: 100 });
      await editor.addNode(num2, { x: 50, y: 250 });
      await editor.addNode(add, { x: 350, y: 170 });
      
      // Connect nodes
      await editor.addConnection(
        new ClassicPreset.Connection(num1, "value", add, "left")
      );
      
      await editor.zoomToFit();
      editorRef.current = editor;
    };

    initEditor();

    return () => {
      if (editorRef.current) {
        editorRef.current.destroy();
      }
    };
  }, []);

  return <div ref={containerRef} style={{ width: "100%", height: "600px" }} />;
}
```

### Basic Example

```tsx
import { DynamicFunctionModal } from "@netlisian/dynamic-functions/puck";
import "@netlisian/dynamic-functions/dist/styles.css";

function MyComponent() {
  const [isOpen, setIsOpen] = useState(false);
  const [mappings, setMappings] = useState([]);

  const fromOptions = [
    { label: "First Name", value: "firstName", type: "text" },
    { label: "Last Name", value: "lastName", type: "text" },
    { label: "Age", value: "age", type: "number" },
  ];

  const toOptions = [
    { label: "Full Name", value: "fullName", type: "text" },
    { label: "Display Name", value: "displayName", type: "text" },
  ];

  return (
    <>
      <button onClick={() => setIsOpen(true)}>
        Open Function Editor
      </button>

      <DynamicFunctionModal
        isOpen={isOpen}
        onClose={() => setIsOpen(false)}
        fromOptions={fromOptions}
        toOptions={toOptions}
        value={mappings}
        onChange={setMappings}
      />
    </>
  );
}
```

### With Puck (SoftConfig)

The package integrates seamlessly with the SoftConfig system:

```tsx
import { SoftConfigProvider } from "@netlisian/softconfig/puck";
import { DynamicFunctionModal } from "@netlisian/dynamic-functions/puck";

<SoftConfigProvider
  hardConfig={config}
  softComponents={softComponents}
  overrides={{
    map: (props) => {
      const [isModalOpen, setIsModalOpen] = useState(false);

      return (
        <div>
          <button onClick={() => setIsModalOpen(true)}>
            Configure Transform
          </button>

          <DynamicFunctionModal
            isOpen={isModalOpen}
            onClose={() => setIsModalOpen(false)}
            fromOptions={props.fromOptions}
            toOptions={props.toOptions}
            value={props.value || []}
            onChange={props.onChange}
          />
        </div>
      );
    },
  }}
>
  {/* Your Puck editor */}
</SoftConfigProvider>
```

## API Reference

### DynamicFunctionModal

The main component for the visual function editor.

#### Props

- `isOpen: boolean` - Controls modal visibility
- `onClose: () => void` - Callback when modal closes
- `fromOptions: Array<{ label: string; value: string; type: FieldType }>` - Available input fields
- `toOptions: Array<{ label: string; value: string; type: FieldType }>` - Available output fields
- `value?: MapConfig[]` - Current transformation mappings
- `onChange: (value: MapConfig[]) => void` - Callback when mappings change
- `currentIndex?: number` - Index of current mapping being edited (default: 0)

### MapConfig

Configuration for a single transformation:

```typescript
type MapConfig = {
  from: string | string[];
  to: string | string[];
  transform?: TransformFunction;
};

type TransformFunction = (values: any[], props: any) => any;
```

### FieldType

Supported Puck field types:

```typescript
type FieldType =
  | "text"
  | "textarea"
  | "number"
  | "select"
  | "radio"
  | "external"
  | "custom"
  | "array";
```

## How It Works

1. **Add Input Nodes**: Click fields from the "Input Fields" sidebar to add "From" nodes
2. **Add Operator Nodes**: Choose operators from Math, Logic, or String categories
3. **Add Output Nodes**: Add "To" nodes from the "Output Fields" sidebar
4. **Connect Nodes**: Drag from output ports to input ports to create connections
5. **Save Transform**: Click "Save Transform" to generate the transformation function

The editor automatically:
- Validates connections between nodes
- Generates executable transformation functions
- Handles multiple inputs/outputs
- Detects circular dependencies
- Orders execution using topological sort

## Operator Categories

### Math Operators
- **Add (+)**: Adds two numbers
- **Subtract (-)**: Subtracts second from first
- **Multiply (×)**: Multiplies two numbers
- **Divide (÷)**: Divides first by second
- **Modulo (%)**: Returns remainder
- **Power (^)**: Raises first to power of second

### Logic Operators
- **AND**: Logical AND of two booleans
- **OR**: Logical OR of two booleans
- **NOT**: Logical NOT of a boolean
- **Equals (==)**: Checks equality
- **Not Equals (!=)**: Checks inequality
- **Greater Than (>)**: Numeric comparison
- **Less Than (<)**: Numeric comparison
- **Greater or Equal (≥)**: Numeric comparison
- **Less or Equal (≤)**: Numeric comparison

### String Operators
- **Concatenate**: Joins two strings
- **Uppercase**: Converts to uppercase
- **Lowercase**: Converts to lowercase
- **Trim**: Removes whitespace
- **Split**: Splits string by separator
- **Join**: Joins array with separator
- **Replace**: Replaces text in string
- **Substring**: Extracts substring

## Architecture

```
dynamic-function/
├── src/
│   ├── types.ts              # TypeScript type definitions
│   ├── operators.ts          # Operator definitions and implementations
│   ├── nodes.ts              # Node class definitions
│   ├── editor.ts             # Core editor logic and function generation
│   ├── components/
│   │   ├── Modal.tsx         # Generic modal component
│   │   └── DynamicFunctionModal.tsx  # Main editor modal
│   ├── puck/
│   │   └── index.tsx         # Puck-specific exports
│   └── styles.css            # Component styles
```

## Advanced Usage

### Custom Operators

You can extend the operator set by modifying `operators.ts`:

```typescript
export const operators: Record<OperatorType, OperatorDefinition> = {
  // ... existing operators
  customOp: {
    type: "customOp",
    label: "Custom Operation",
    inputs: 2,
    category: "math",
    execute: (a: number, b: number) => {
      // Your custom logic
      return a * 2 + b;
    },
  },
};
```

### Programmatic Editor Access

```typescript
import { createDynamicFunctionEditor } from "@netlisian/dynamic-functions";

const editor = await createDynamicFunctionEditor(containerElement);

// Add nodes programmatically
await editor.addNode(someNode, { x: 100, y: 100 });

// Generate config
const config = editor.generateMapConfig();

// Clean up
editor.destroy();
```

## Browser Compatibility

- Chrome/Edge: ✅ Full support
- Firefox: ✅ Full support
- Safari: ✅ Full support
- IE11: ❌ Not supported

## Dependencies

- `rete`: Node editor framework
- `rete-area-plugin`: Area management
- `rete-connection-plugin`: Connection handling
- `rete-react-plugin`: React rendering
- `lucide-react`: Icons
- `@measured/puck`: Puck integration (peer dependency)

## License

MIT