Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions apps/docs/src/components/demo-block/demo-block.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@
&__editor-container {
background-color: var(--ty-color-fill);
border-top: solid 1px var(--ty-color-border-secondary);
overflow-x: auto;
// Wrap editor text instead of forcing horizontal scrolling.
overflow-x: hidden;
font-family: Menlo, Consolas, "Droid Sans Mono", monospace;
font-size: 13px;
line-height: 1.6;
tab-size: 2;
}

&__editor-wrapper {
max-width: 800px;
width: 100%;
}

Expand All @@ -50,6 +50,10 @@
pre {
margin: 0;
pointer-events: none;
// Ensure long lines wrap in the highlighted background layer.
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: anywhere;
}
}

Expand All @@ -73,7 +77,10 @@
font-size: inherit;
line-height: inherit;
tab-size: inherit;
white-space: pre;
// Keep caret + selection aligned with the highlighted background layer.
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: anywhere;
box-sizing: border-box;

&::selection {
Expand Down
130 changes: 104 additions & 26 deletions packages/react/src/form/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,56 +68,113 @@ The `validate` function checks a value against a `Rule` supporting: `required`,

```mermaid
graph TD
subgraph Form["<Form>"]
subgraph Form["<Form> Provider"]
FI["FormInstance (useRef)"]
FIC["FormInstanceContext.Provider"]
FOC["FormOptionsContext.Provider"]
end

subgraph FormItem1["<Form.Item name='username'>"]
SUB1["subscribe(listener)"]
CE1["cloneElement(child, {value, onChange, onBlur})"]
ERR1["Error display with Transition"]
subgraph FormItem1["Form.Item (name='username')"]
CE1["cloneElement → {value, onChange, onBlur}"]
end

subgraph FormItem2["<Form.Item name='password'>"]
SUB2["subscribe(listener)"]
CE2["cloneElement(child, {value, onChange, onBlur})"]
ERR2["Error display with Transition"]
subgraph FormItem2["Form.Item (name='password')"]
CE2["cloneElement → {value, onChange, onBlur}"]
end

subgraph Input1["Input (controlled by value prop)"]
V1["value: from useState"]
end

subgraph Input2["Input (controlled by value prop)"]
V2["value: from useState"]
end

FI --> FIC
FI --> FOC
FIC --> FormItem1
FIC --> FormItem2
FOC --> FormItem1
FOC --> FormItem2

FormItem1 --> CE1 --> V1
FormItem2 --> CE2 --> V2
```

### Subscribe/Notify Pattern (Pub/Sub)

```mermaid
graph LR
subgraph FormInstance["FormInstance (Central Store)"]
VALUES["values: { username: '', password: '' }"]
ERRORS["errors: { email: ['Invalid email'] }"]
RULES["rules: { username: [...], email: [...] }"]
LISTENERS["listeners: [listener1, listener2, ...]"]
NOTIFY["notify(name)"]
end

subgraph FormItem_A["Form.Item(name='username')"]
SUB_A["subscribe(callback)"]
CALLBACK_A["callback(n) { if n==='username' setValue() }"]
end

subgraph FormItem_B["Form.Item(name='email')"]
SUB_B["subscribe(callback)"]
CALLBACK_B["callback(n) { if n==='email' setValue() }"]
end

subgraph FormItem_C["Form.Item(name='password')"]
SUB_C["subscribe(callback)"]
CALLBACK_C["callback(n) { if n==='password' setValue() }"]
end

NOTIFY -->|"notify('email')"| LISTENERS
LISTENERS -->|"→ listener_A"| CALLBACK_A
LISTENERS -->|"→ listener_B"| CALLBACK_B
LISTENERS -->|"→ listener_C"| CALLBACK_C

SUB_A -.->|"adds listener_A"| LISTENERS
SUB_B -.->|"adds listener_B"| LISTENERS
SUB_C -.->|"adds listener_C"| LISTENERS

style CALLBACK_A fill:#ffcccc
style CALLBACK_B fill:#ccffcc
style CALLBACK_C fill:#ffcccc
```

### Data Flow: User Input
### Data Flow: User Input → UI Update (Controlled)

```mermaid
sequenceDiagram
participant User
participant Input as Child Input
participant Input as <Input />
participant Item as Form.Item
participant Store as FormInstance
participant Item2 as Form.Item (other)

Note over Item: On mount: subscribe + setFieldRules
Note over Item: Mount: subscribe(listener) + setFieldRules(name, rules)

User->>Input: Types a character
Input->>Item: onChange fires
Item->>Store: setFieldValue(name, value)
Store->>Store: notify(name)
Store->>Item: Listener callback fires
Item->>Item: setValue → re-render
Item->>Input: cloneElement with new value
User->>Input: Types "a"
Input->>Item: onChange("a")
Item->>Store: setFieldValue("field", "a")
Store->>Store: values["field"] = "a"
Store->>Store: notify("field")
Store->>Item: listener("field") fires
Store->>Item2: listener("field") fires

Note over Item2: n === "field" but name !== "field"<br/>→ ignore, no update

Item->>Item: setValue("a") → re-render
Item->>Input: cloneElement injects new value="a"

Note over Input: Controlled: displays "a"

alt validateTrigger === "onChange"
Item->>Store: validateField(name)
Store->>Store: run rules via validate()
Store->>Store: setFieldError + notify
Store->>Item: Listener fires again
Item->>Item: setError → show/hide error
Item->>Store: validateField("field")
Store->>Store: run validate() on rules
Store->>Store: setFieldError + notify("field")
Store->>Item: listener fires again
Item->>Item: setError() → show/hide error
end
```

Expand All @@ -133,7 +190,7 @@ sequenceDiagram
User->>Form: Submit
Form->>Form: e.preventDefault()
Form->>Store: validateFields()
Store->>Store: validateField() for each rule set
Store->>Store: validateField() for each field with rules
Store->>Items: notify each field name
Items->>Items: Update error states

Expand Down Expand Up @@ -161,7 +218,28 @@ sequenceDiagram
Store->>Store: errors = {}
Store->>Store: values = deepCopy(initValues)
Store->>Items: notify("*")
Items->>Items: All items re-read value and error → re-render
Items->>Items: All items: name === "*" or n === "*" → true<br/>→ setValue() + setError() → re-render
```

### Controlled Component Pattern

```mermaid
graph TD
subgraph FormItem["Form.Item"]
STATE["useState<br/>value, error"]
ONCHANGE["onChange(...args)<br/>form.setFieldValue()<br/>form.validateField()"]
CLONE["React.cloneElement<br/>{ value, onChange, onBlur }"]
end

subgraph Child["Child Input"]
PROP["value prop<br/>(controlled)"]
INTERNAL["no internal state used"]
end

STATE -->|"on mount"| CLONE
ONCHANGE -->|"on input"| CLONE
CLONE -->|"props"| PROP
PROP -->|"user types"| ONCHANGE
```

### Validation Rules
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/form/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,15 @@ Simulate an async form submission with loading state.
</Column>
</Layout>

<Demo>
### Other Form Controls

A versatile example.

<DemoBlock component={OtherControlsDemo} source={OtherControlsSource} />

</Demo>

## API

### Form
Expand Down
Loading