@abuzareal/contour-ui 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/COMPOSITION.md +28 -28
- package/CONTRACTS.md +3 -3
- package/FORMS.md +94 -0
- package/NEXTJS.md +4 -2
- package/PUBLIC-API.md +157 -155
- package/README.md +7 -5
- package/dist/components/ui/Checkbox.d.ts +2 -2
- package/dist/components/ui/Checkbox.js +11 -2
- package/dist/components/ui/RadioGroup.d.ts +13 -5
- package/dist/components/ui/RadioGroup.js +35 -8
- package/dist/components/ui/SelectField.js +16 -2
- package/dist/components/ui/Slider.d.ts +23 -7
- package/dist/components/ui/Slider.js +43 -6
- package/dist/components/ui/Switch.d.ts +25 -5
- package/dist/components/ui/Switch.js +52 -5
- package/dist/components/ui/TextArea.js +2 -0
- package/dist/components/ui/TextField.js +5 -1
- package/dist/hooks/useFormReset.d.ts +1 -1
- package/examples/forms/HookFormSettingsForm.tsx +176 -0
- package/examples/forms/ManualSettingsForm-0.3.0.tsx +102 -0
- package/examples/forms/NativeSettingsForm.tsx +119 -0
- package/examples/forms/settings.ts +61 -0
- package/examples/forms/useSubmission.ts +77 -0
- package/package.json +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,17 @@ Changes to the published `@abuzareal/contour-ui` package. Version headings refer
|
|
|
6
6
|
|
|
7
7
|
No changes recorded.
|
|
8
8
|
|
|
9
|
+
## 0.4.0
|
|
10
|
+
|
|
11
|
+
Native forms and tested application recipes. This heading records source work, not npm publication.
|
|
12
|
+
|
|
13
|
+
- Switch supports name/form/value, defaultChecked and native required validation through a checkbox backing; checked submits, unchecked/disabled omit. The native ref remains a button and onChange remains boolean.
|
|
14
|
+
- Slider supports native name/form/defaultValue, reset, input attributes and formatted native sanitized values; onChange remains numeric. RadioGroup supports caller name/form/defaultValue, required/disabled and per-option native input props/refs; onChange remains a string value.
|
|
15
|
+
- Native reset and cancellation work with external owners. Controlled TextField/TextArea/SelectField/Checkbox DOM values now remain aligned with their parent state after native reset, including the select-first-option regression. Controlled values remain parent-owned; defaults for these controls are captured on mount. Checkbox accepts caller IDs and preserves descriptions/validity/indeterminate/ref behavior.
|
|
16
|
+
- Ship version-qualified 0.3.0 manual submission guidance and copyable native/React Hook Form recipes. Tested field/service errors, real pending/success states, retry, duplicate-submit prevention, reset/cancel/unmount and stale-result cleanup. RHF is example-only; no new runtime dependency, CSS/theme change or overlay rewrite.
|
|
17
|
+
|
|
18
|
+
Existing controlled JSX calls remain compatible. Add names only when native submission is intended; unchecked Switch values are omitted rather than serialized as false. Use Controller for value callbacks; native registered fields retain native events. See [FORMS.md](FORMS.md) for copyable sources, reset/registration details and service responsibilities. Independent consumer updates require an exact version/lockfile change and validation.
|
|
19
|
+
|
|
9
20
|
## 0.3.0
|
|
10
21
|
|
|
11
22
|
Native refs and focused composition APIs for existing components. This heading records source work, not npm publication.
|
package/COMPOSITION.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# Refs, parts, state and links
|
|
2
2
|
|
|
3
|
-
Contract version **0.
|
|
3
|
+
Contract version **0.4.0**. These additions are absent from npm 0.2.1; a source heading does not establish publication. Use the version named by the release notes. Import components directly and load CSS once. This guide describes existing DOM parts; no copying step or polymorphic Button system is required.
|
|
4
4
|
|
|
5
5
|
## Native ref targets
|
|
6
6
|
|
|
7
|
-
| Export
|
|
8
|
-
|
|
|
9
|
-
| Button, IconButton, DepthButton, ThemeToggle | HTMLButtonElement: focus, measurement, external trigger adapter |
|
|
10
|
-
| ExternalLink
|
|
11
|
-
| TextField, Checkbox, Slider
|
|
12
|
-
| TextArea / SelectField
|
|
13
|
-
| Dialog
|
|
14
|
-
| Tabs
|
|
15
|
-
| Popover
|
|
7
|
+
| Export | Target and use |
|
|
8
|
+
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| Button, IconButton, DepthButton, ThemeToggle, Switch | HTMLButtonElement: focus, measurement, external trigger adapter |
|
|
10
|
+
| ExternalLink | HTMLAnchorElement: focus and native-link integration |
|
|
11
|
+
| TextField, Checkbox, Slider | HTMLInputElement: focus/measurement; Checkbox retains indeterminate behavior |
|
|
12
|
+
| TextArea / SelectField | HTMLTextAreaElement / HTMLSelectElement; existing native reset/validation preserved |
|
|
13
|
+
| Dialog | HTMLDialogElement: focus/measurement; open stays prop-owned, do not bypass it with showModal/close |
|
|
14
|
+
| Tabs | Root HTMLDivElement; list/button/panel parts have separate native refs |
|
|
15
|
+
| Popover | Root HTMLDivElement; triggerProps.ref is its owned button, panelProps.ref its local panel |
|
|
16
16
|
|
|
17
17
|
Object refs become null on unmount/ref replacement. Callback refs receive the node then null; on React 19 they may return cleanup instead. Internal refs coexist with caller refs. Stable callback identities avoid deliberate ref replacement. Targets remain mounted while a dialog/panel is closed/hidden.
|
|
18
18
|
|
|
19
|
-
RadioGroup, SegmentedControl, Pagination and DropdownMenu contain multiple controls; no singular input/action ref is claimed. Field is a frame, Tooltip describes a caller-owned child, Disclosure has native details semantics, and FlipCard/Tag have conditional controls. Presentation/scene wrappers receive no arbitrary native control handle. Part refs for those require a demonstrated reusable need.
|
|
19
|
+
RadioGroup, SegmentedControl, Pagination and DropdownMenu contain multiple controls; no singular input/action ref is claimed. RadioGroup now exposes per-option native input refs through inputProps for form controllers. Field is a frame, Tooltip describes a caller-owned child, Disclosure has native details semantics, and FlipCard/Tag have conditional controls. Presentation/scene wrappers receive no arbitrary native control handle. Part refs for those require a demonstrated reusable need.
|
|
20
20
|
|
|
21
21
|
React 18 compatibility uses forwardRef; React 19 uses the same JSX API. Render through React; do not call exports as plain functions or depend on typeof being function. [React ref guidance](https://react.dev/reference/react/forwardRef) explains why low-level controls expose DOM nodes.
|
|
22
22
|
|
|
@@ -112,7 +112,7 @@ Popover still has local absolute placement and the known nested-dismissal limit.
|
|
|
112
112
|
|
|
113
113
|
## Event composition
|
|
114
114
|
|
|
115
|
-
For the composed action handlers on Dialog, Tabs and
|
|
115
|
+
For the composed action handlers on Dialog, Tabs, Popover and the Switch button, the caller handler on the same owned element runs first. preventDefault vetoes internal tab click/arrow selection, popover/switch toggling, and dialog close-button/backdrop dismissal. stopPropagation controls propagation, not this cancellation contract. Root bubbling handlers run after descendant handlers; intercept through the relevant part or capture phase.
|
|
116
116
|
|
|
117
117
|
Dialog onCancel receives the native event. Caller preventDefault vetoes onClose; Contour always prevents the browser from closing independently of open. An allowed cancel requests onClose and the parent must update open. Native disabled buttons cannot activate; Button loading also disables it. Native field counter/resize bookkeeping runs before its caller input/change handler and remains intact; preventDefault on those notifications is not a counter-reset API.
|
|
118
118
|
|
|
@@ -135,22 +135,22 @@ If dismissal is suppressed, provide a usable cancel/recovery path. Do not disabl
|
|
|
135
135
|
|
|
136
136
|
## State and callback audit
|
|
137
137
|
|
|
138
|
-
| Family | Existing ownership/callback contract
|
|
139
|
-
| ------------------------------------------ |
|
|
140
|
-
| Button/IconButton/DepthButton/ExternalLink | Native events; supported button disabled/loading; navigation remains browser/router-owned
|
|
141
|
-
| TextField/TextArea/SelectField/Checkbox | Native event callbacks; controlled value/checked or native defaults; refs, validity/reset remain intact
|
|
142
|
-
| Slider / Switch |
|
|
143
|
-
| RadioGroup / SegmentedControl |
|
|
144
|
-
| Tabs | activeTabId + onTabChange(id), or internal defaultTabId; invalid/removed IDs fall back to first available
|
|
145
|
-
| Popover / DropdownMenu | open + onOpenChange(boolean), or internal defaultOpen; unchanged values do not notify
|
|
146
|
-
| Dialog | Required open and onClose() request; no independent imperative ownership
|
|
147
|
-
| Disclosure | defaultOpen on native details; no controlled open/onOpenChange
|
|
148
|
-
| Pagination | page/pageCount and onChange(page); app owns URL/fetch/bounds
|
|
149
|
-
| FlipCard / Tooltip | Internal flipped / hover-focus state; no new controlled API
|
|
150
|
-
| ThemeToggle/useTheme | Shared document theme/preference, not scoped provider
|
|
151
|
-
| Tag, EmptyState, Alert, toast APIs | Caller removal/actions or supplied feedback/library toast commands; no business operation/persistence implied
|
|
152
|
-
|
|
153
|
-
Do not switch controlled/uncontrolled ownership mid-mount. A later default does not reset established state. Controlled callbacks never silently overwrite parent state. Native event callbacks are not cosmetically renamed into value callbacks.
|
|
138
|
+
| Family | Existing ownership/callback contract |
|
|
139
|
+
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
140
|
+
| Button/IconButton/DepthButton/ExternalLink | Native events; supported button disabled/loading; navigation remains browser/router-owned |
|
|
141
|
+
| TextField/TextArea/SelectField/Checkbox | Native event callbacks; controlled value/checked or native defaults; refs, validity/reset remain intact |
|
|
142
|
+
| Slider / Switch | Number value / boolean checked or captured default; value callbacks; named form/reset support in 0.4.0 |
|
|
143
|
+
| RadioGroup / SegmentedControl | RadioGroup has controlled/default string selection and native form names; SegmentedControl remains a required value/button group |
|
|
144
|
+
| Tabs | activeTabId + onTabChange(id), or internal defaultTabId; invalid/removed IDs fall back to first available |
|
|
145
|
+
| Popover / DropdownMenu | open + onOpenChange(boolean), or internal defaultOpen; unchanged values do not notify |
|
|
146
|
+
| Dialog | Required open and onClose() request; no independent imperative ownership |
|
|
147
|
+
| Disclosure | defaultOpen on native details; no controlled open/onOpenChange |
|
|
148
|
+
| Pagination | page/pageCount and onChange(page); app owns URL/fetch/bounds |
|
|
149
|
+
| FlipCard / Tooltip | Internal flipped / hover-focus state; no new controlled API |
|
|
150
|
+
| ThemeToggle/useTheme | Shared document theme/preference, not scoped provider |
|
|
151
|
+
| Tag, EmptyState, Alert, toast APIs | Caller removal/actions or supplied feedback/library toast commands; no business operation/persistence implied |
|
|
152
|
+
|
|
153
|
+
Do not switch controlled/uncontrolled ownership mid-mount. A later default does not reset established state. Controlled callbacks never silently overwrite parent state. Native event callbacks are not cosmetically renamed into value callbacks. See [the form guide](FORMS.md) for 0.4.0 named submission, defaults, validation and reset. SegmentedControl remains application-state submission.
|
|
154
154
|
|
|
155
155
|
## Native and router links
|
|
156
156
|
|
package/CONTRACTS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Architecture, scope and compatibility
|
|
2
2
|
|
|
3
|
-
Contract version **0.
|
|
3
|
+
Contract version **0.4.0**, reviewed 13 October 2026. Contour is an early 0.x React library with typed controls, paper/ink surfaces, a lime accent, light/dark themes, restrained motion and separate optional depth/scene APIs. The [public API and maturity register](PUBLIC-API.md) lists every exported value, type and stylesheet, its maintainer, status, use and limits.
|
|
4
4
|
|
|
5
5
|
## Intended adoption scope
|
|
6
6
|
|
|
@@ -37,7 +37,7 @@ React is the UI runtime. Strict TypeScript/TSX source compiles through `tsc` int
|
|
|
37
37
|
| Optional peers | three: `^0.186.1`; lenis: `^1.3.26` | Core works without either. Install explicitly for the matching optional entry. |
|
|
38
38
|
| Development dependencies | TypeScript, Vite, Oxlint, Testing Library, jsdom, Playwright, React/types and optional peer dev copies | Source authoring/tests/build; not downstream Contour production dependencies. |
|
|
39
39
|
|
|
40
|
-
Use explicit public paths; deep imports into dist internals are unsupported. CSS is marked as side-effectful to retain imported styles. The archive contains built modules/declarations/assets and release documents, with no consumer install scripts or private research.
|
|
40
|
+
Use explicit public paths; deep imports into dist internals are unsupported. CSS is marked as side-effectful to retain imported styles. The archive contains built modules/declarations/assets and release documents, plus copyable form recipe sources, with no consumer install scripts or private research. React Hook Form is an example/application dependency, not a runtime dependency.
|
|
41
41
|
|
|
42
42
|
Archive bytes, installed dependency bytes, browser JS/CSS, emitted fonts and actual requested/cached network assets are different measurements. An installed icon collection is not proof all icons download. JavaScript tree shaking does not remove unused aggregate CSS rules automatically. Size claims require version, fixture, tool versions, compression and exclusions; independent component gzip samples cannot be added as an application total.
|
|
43
43
|
|
|
@@ -71,7 +71,7 @@ Respect system reduced motion and app pause. CSS depth is core; WebGL is separat
|
|
|
71
71
|
Preserve native input/select/details/dialog behavior where appropriate. Future replacements require a written rationale and equivalent labels/descriptions, submission/disabled omission, validation/reset timing, keyboard/focus, modal scroll locking and cleanup, with relevant component/browser checks.
|
|
72
72
|
|
|
73
73
|
- Actions, ExternalLink, native fields/choices, Dialog and the documented Tabs/Popover parts forward their native targets on React 18/19. Multi-control/presentation wrappers do not imply a singular input ref; see [the composition contract](COMPOSITION.md).
|
|
74
|
-
- Checkbox
|
|
74
|
+
- Checkbox accepts caller IDs while retaining a generated fallback and internal indeterminate behavior alongside the forwarded input ref; caller ARIA validity and descriptions remain composed. Switch/Slider/RadioGroup support named/external form submission and controlled/default/reset behavior; see [the form guide](FORMS.md). Switch refs remain buttons and its checkbox backing submits only while checked; a range has no required constraint.
|
|
75
75
|
- Popover/DropdownMenu use local absolute panels without portal/collision/submenu/custom-trigger contracts. Independent document listeners close both nested Popovers on one Escape: a reproduced defect.
|
|
76
76
|
- Tooltip closes on pointer leave and CSS prevents content hover: a source finding/accessibility risk requiring correction and pointer/focus verification, not a formal conformance audit. Hints must be supplemental and noninteractive.
|
|
77
77
|
- Dialog/Tabs/Popover expose typed root and owned-part props/refs; state and ARIA wiring stay protected and caller cancellation composes with internal behavior. Arbitrary trigger replacement, portals/collision and general polymorphism remain outside this API.
|
package/FORMS.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Native form and form-library integration
|
|
2
|
+
|
|
3
|
+
Contract version **0.4.0**. This source heading does not establish npm publication; new contracts are absent from 0.3.0. Use the version named by the release notes. Existing controlled boolean/number/string callbacks and native ref targets remain compatible.
|
|
4
|
+
|
|
5
|
+
## Existing 0.3.0 installations
|
|
6
|
+
|
|
7
|
+
TextField, TextArea, SelectField and Checkbox accept native name/form/disabled/default/required props and can be read through FormData. Checkbox generates its own ID in 0.3.0. Switch and Slider in 0.3.0 require checked/value and value callbacks; they do not accept a submission name or external form contract. RadioGroup in 0.3.0 uses its own generated radio name; applications should serialize its selected value under their own stable field name. Construct a payload from native fields plus application state rather than guessing the generated name. Keep parent-owned reset explicit.
|
|
8
|
+
|
|
9
|
+
[ManualSettingsForm-0.3.0.tsx](examples/forms/ManualSettingsForm-0.3.0.tsx) is a complete old-release recipe. It is compiled against the actual npm 0.3.0 package. It deliberately whitelists the service payload; generated radio names are not sent to the service.
|
|
10
|
+
|
|
11
|
+
## 0.4.0 control contracts
|
|
12
|
+
|
|
13
|
+
| Control | Submission and defaults | Ownership, validation and ref |
|
|
14
|
+
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| Switch | name + optional form; checked submits name=value (default "on"); unchecked/disabled omits it. defaultChecked defaults to false. | checked controls state; absent checked uses internal state. onChange(boolean), not a native input event. Ref remains HTMLButtonElement. Native checkbox backing is noninteractive; required means checked, invalid feedback focuses the visible switch and supplies a linked message. onInvalid receives that checkbox's native event; preventDefault supplies your own feedback/focus. |
|
|
16
|
+
| Slider | Native range name/form; numeric value is serialized as a string. defaultValue defaults to the native range midpoint. Disabled omits it. | value controls state; absent value uses native uncontrolled input. onChange(number) remains a value callback. Ref is HTMLInputElement. Browser range clamping/stepping governs value and formatted readout. required is not supported because a native range cannot be empty. |
|
|
17
|
+
| RadioGroup | Caller name/form on every radio; selected enabled option submits its string value. Without name the generated name remains compatible but is unsuitable for a service field. Without defaultValue/value no option is selected. | value controls selection; absent value is native uncontrolled selection. onChange(value) stays a string callback. Group disabled and option disabled omit affected radios; required uses native radio validation. No singular group input ref: option.inputProps.ref targets that native radio. |
|
|
18
|
+
| TextField/TextArea/SelectField/Checkbox | Native names/owners/defaults/disabled/required and native event callbacks remain. Checkbox now accepts caller id. | Native input/textarea/select refs, explicit labels, caller ARIA descriptions/validity plus component errors are preserved. Text counters support maxLength=0; textarea auto-resize follows reset. |
|
|
19
|
+
|
|
20
|
+
Set unique IDs and stable radio option values. Equal names in the same form intentionally form one native radio group; unrelated groups need distinct names. Radio option inputProps allows ref/id/blur/focus/style/ARIA/native handlers; type/name/form/value/checked/default/required/disabled/selection remain group-owned. Extra description IDs must point to nodes you provide. Switch change notification is its boolean callback; do not rely on delegated native form onChange to observe its noninteractive backing checkbox. Switch/Slider native props target their visible native control, except Switch name/value/required belong to its backing input; existing styles/classes are retained. Button type and switch/range semantics remain owned.
|
|
21
|
+
|
|
22
|
+
Custom defaults for these three controls are captured on mount. Changing a default prop does not reset current state or replace the captured baseline; remount or use controlled values to replace that baseline. Do not switch ownership mid-mount. Supplying a controlled value wins over an initial default. Numeric props should be finite; native range sanitization is not service/domain validation. Missing selections in an enabled group use native required validation. Disabled groups/options omit values; validate availability and missing fields in the application/service.
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { Slider, Switch, RadioGroup } from "@abuzareal/contour-ui";
|
|
26
|
+
export function PreferencesFields() {
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<form id="preferences" />
|
|
30
|
+
<Switch
|
|
31
|
+
label="Updates"
|
|
32
|
+
name="updates"
|
|
33
|
+
form="preferences"
|
|
34
|
+
defaultChecked
|
|
35
|
+
/>
|
|
36
|
+
<Slider
|
|
37
|
+
label="Level"
|
|
38
|
+
name="level"
|
|
39
|
+
form="preferences"
|
|
40
|
+
defaultValue={20}
|
|
41
|
+
min={0}
|
|
42
|
+
max={100}
|
|
43
|
+
step={5}
|
|
44
|
+
/>
|
|
45
|
+
<RadioGroup
|
|
46
|
+
legend="Contact"
|
|
47
|
+
name="contact"
|
|
48
|
+
form="preferences"
|
|
49
|
+
defaultValue="email"
|
|
50
|
+
required
|
|
51
|
+
options={[
|
|
52
|
+
{ value: "email", label: "Email" },
|
|
53
|
+
{ value: "phone", label: "Phone" },
|
|
54
|
+
]}
|
|
55
|
+
/>
|
|
56
|
+
</>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
JavaScript uses the same imports/JSX without type arguments. RadioGroup does not require a wrapper/provider. Native form owners also work when the controls are rendered outside the form and when a form prop changes. Disabled fieldsets retain native disabled omission.
|
|
62
|
+
|
|
63
|
+
## Reset and controlled responsibilities
|
|
64
|
+
|
|
65
|
+
Native reset fires before the browser restores defaults. Uncontrolled choices/range and supporting readouts/counters synchronize after that default action (a browser task). A canceled reset preserves state/readouts. Reset does not generate a change callback. Listener/timer cleanup uses the owner document and current form owner, including external forms and unmount.
|
|
66
|
+
|
|
67
|
+
For controlled values, the application must update its own state in onReset or a dedicated reset command. The library restores the controlled visible/submitted value after the native reset task and never changes the parent state. Do not read a just-reset controlled form synchronously and assume React has committed; await the state update/task. React Hook Form Controller values need useForm defaultValues and reset(defaults). A later default prop is not a controlled reset command. A submission cancel stops client waiting; it cannot prove the server rolled back a request already received.
|
|
68
|
+
|
|
69
|
+
## Copyable native recipe
|
|
70
|
+
|
|
71
|
+
Copy [NativeSettingsForm.tsx](examples/forms/NativeSettingsForm.tsx), [settings.ts](examples/forms/settings.ts) and [useSubmission.ts](examples/forms/useSubmission.ts) into your application. Their relative .js imports resolve to the TypeScript files under NodeNext; use your bundler's matching source-import convention. Load Contour CSS once and add application layout styles. Pass a real SaveSettings adapter; no service endpoint, delay or fabricated success is built into the recipe.
|
|
72
|
+
|
|
73
|
+
The recipe reads an exact payload through FormData, validates known fields, displays field/service failures, prevents a second submission while waiting for the current request, disables editing while pending, preserves inputs on failure, permits retry/stop-waiting/reset and aborts/ignores obsolete/unmounted results. Stopping waiting abandons client ownership; it does not guarantee server work has stopped. Success is displayed only when your adapter returns ok:true, and a new invalid submission replaces previous success feedback. Submission announcements stay outside the busy fieldset. The example email/step policy is intentionally small; it is not universal address validation. Accessibility/status/keyboard behaviors are verified in the fixture. Service error strings should be meaningful to the user and safe to display.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { NativeSettingsForm } from "./NativeSettingsForm.js";
|
|
77
|
+
import type { SaveSettings } from "./settings.js";
|
|
78
|
+
// Your adapter validates unknown response data and confirms the server result.
|
|
79
|
+
export function PreferencesPage({ save }: { save: SaveSettings }) {
|
|
80
|
+
return <NativeSettingsForm save={save} />;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Do not substitute an always-resolving callback for a real service and imply data was persisted. The browser fixture uses an explicitly labeled local test service to verify pending/error/success, not a production backend.
|
|
85
|
+
|
|
86
|
+
## React Hook Form recipe
|
|
87
|
+
|
|
88
|
+
Copy [HookFormSettingsForm.tsx](examples/forms/HookFormSettingsForm.tsx) and the same shared files. Install react-hook-form in the **application**, not as a Contour peer. Fixtures pin 7.90.0 with React 18.3.1 and 19.3.0; other versions are separate compatibility questions.
|
|
89
|
+
|
|
90
|
+
Native TextField registration forwards name/onChange/onBlur/ref. RHF submits its field model rather than FormData; use RHF disabled field options when omission is required. A disabled fieldset prevents editing but is not a replacement for that model policy. Switch/Slider/RadioGroup use Controller because onChange carries a boolean/number/string. Switch still exposes a button ref; radio field.ref goes on an enabled option's inputProps.ref. Blur handlers go on the visible control or radio options. The shown RHF recipe uses field errors rather than shouldUseNativeValidation; the Switch button ref has no input validity methods. Native form validity still includes its backing checkbox. Service errors focus after the disabled pending fieldset becomes enabled. Do not register and Controller the same field or spread a radio group's single controller ref onto every option.
|
|
91
|
+
|
|
92
|
+
The recipe uses useForm defaultValues, RHF field validation/server errors, handleSubmit, controller refs and reset. Its reset command performs native form.reset() first so registered field counters/auto-resize observe the reset, then reset(defaults) updates RHF state. For arbitrary programmatic RHF setValue calls on fields with counters/auto-resize, use Controller and pass the controlled value; direct DOM writes through a registered ref do not dispatch native input events. Its shared submission lifecycle ignores late results after reset/cancel/unmount even if the service disregards AbortSignal. Real services must enforce validation, authentication, authorization, CSRF policy and idempotency/overlap handling at the server boundary. Treat parsed responses as unknown until your adapter validates them; TypeScript alone does not validate network data.
|
|
93
|
+
|
|
94
|
+
References: [React native input ownership](https://react.dev/reference/react-dom/components/input), [native range semantics](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/range), [RHF Controller](https://react-hook-form.com/docs/usecontroller/controller), [RHF reset](https://react-hook-form.com/docs/useform/reset), [Radix Switch form contract](https://www.radix-ui.com/primitives/docs/components/switch). These inform bounded contracts; Contour does not add a second interaction foundation or copy their implementation. Native-form checks are scoped Chromium evidence, not cross-browser/screen-reader certification.
|
package/NEXTJS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Next.js App Router integration
|
|
2
2
|
|
|
3
|
-
This guide describes **Contour 0.
|
|
3
|
+
This guide describes **Contour 0.4.0**. npm 0.1.2 does not include the client directives, native refs or relative Latin font files described here. Check npm for release availability before installing. The fixture pins Next 16.4.0 with React/React DOM 19.3.0, uses the webpack production build, and consumes an npm archive. It does not establish every Next version, Turbopack, Pages Router, scroll entries or hosting platform. A separate optional mode checks a lazy Three.js scene with SSR disabled and disposal on navigation.
|
|
4
4
|
|
|
5
5
|
In `app/layout.tsx`, load CSS once:
|
|
6
6
|
|
|
@@ -50,4 +50,6 @@ Provide real submission behavior in the application. Toast consumers need ToastP
|
|
|
50
50
|
|
|
51
51
|
Run `npm run test:next` to rebuild and consume the candidate archive. The fixture checks server imports/prerendering, hydration, CSS/fonts, refs, native resets, dialog dismissal/focus, toast, tabs, theme persistence and client navigation without Three.js/Lenis installed. Run `CONTOUR_NEXT_OPTIONAL=1 node scripts/test-next.mjs` for the separate lazy `/three` route; ordinary consumers still require neither optional peer. Compatibility evidence applies to the fixture's exact pair and local runtime, not deployment.
|
|
52
52
|
|
|
53
|
-
The 0.
|
|
53
|
+
The 0.4.0 fixture also verifies Button native-ref focus and styled Next Link ordinary/canceled/middle-click navigation. Use [the composition guide](COMPOSITION.md#native-and-router-links) for the Link recipe; Button remains an action rather than a polymorphic navigation component.
|
|
54
|
+
|
|
55
|
+
Named form/reset APIs and copyable form recipes are described in [FORMS.md](FORMS.md). They remain client interactions; server-side validation and persistence belong to your application.
|