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["
"] + subgraph Form[" Provider"] FI["FormInstance (useRef)"] FIC["FormInstanceContext.Provider"] FOC["FormOptionsContext.Provider"] end - subgraph FormItem1[""] - 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