diff --git a/apps/docs/src/components/demo-block/demo-block.scss b/apps/docs/src/components/demo-block/demo-block.scss
index d4f6f31d..4637cec9 100644
--- a/apps/docs/src/components/demo-block/demo-block.scss
+++ b/apps/docs/src/components/demo-block/demo-block.scss
@@ -32,7 +32,8 @@
&__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;
@@ -40,7 +41,6 @@
}
&__editor-wrapper {
- max-width: 800px;
width: 100%;
}
@@ -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;
}
}
@@ -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 {
diff --git a/packages/react/src/form/ARCHITECTURE.md b/packages/react/src/form/ARCHITECTURE.md
index c0f7af26..a1bea7a6 100644
--- a/packages/react/src/form/ARCHITECTURE.md
+++ b/packages/react/src/form/ARCHITECTURE.md
@@ -68,56 +68,113 @@ The `validate` function checks a value against a `Rule` supporting: `required`,
```mermaid
graph TD
- subgraph Form["
"]
- 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[""]
- 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
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"
→ 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
```
@@ -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
@@ -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
→ setValue() + setError() → re-render
+```
+
+### Controlled Component Pattern
+
+```mermaid
+graph TD
+ subgraph FormItem["Form.Item"]
+ STATE["useState
value, error"]
+ ONCHANGE["onChange(...args)
form.setFieldValue()
form.validateField()"]
+ CLONE["React.cloneElement
{ value, onChange, onBlur }"]
+ end
+
+ subgraph Child["Child Input"]
+ PROP["value prop
(controlled)"]
+ INTERNAL["no internal state used"]
+ end
+
+ STATE -->|"on mount"| CLONE
+ ONCHANGE -->|"on input"| CLONE
+ CLONE -->|"props"| PROP
+ PROP -->|"user types"| ONCHANGE
```
### Validation Rules
diff --git a/packages/react/src/form/index.md b/packages/react/src/form/index.md
index 8cac5b54..91c66956 100644
--- a/packages/react/src/form/index.md
+++ b/packages/react/src/form/index.md
@@ -115,12 +115,15 @@ Simulate an async form submission with loading state.
+
### Other Form Controls
A versatile example.
+
+
## API
### Form