@abuzareal/contour-ui 0.3.0 → 0.5.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.
Files changed (147) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/COMPOSITION.md +28 -28
  3. package/CONTRACTS.md +9 -3
  4. package/FORMS.md +94 -0
  5. package/NEXTJS.md +6 -2
  6. package/OVERLAYS.md +52 -0
  7. package/PUBLIC-API.md +181 -162
  8. package/README.md +44 -28
  9. package/RELEASING.md +1 -1
  10. package/SIZE.md +45 -0
  11. package/STYLING.md +68 -0
  12. package/dist/actions.css +7 -0
  13. package/dist/components/hero/Sculpture.js +2 -1
  14. package/dist/components/three/ObjectStudy.js +2 -1
  15. package/dist/components/ui/Alert.d.ts +2 -1
  16. package/dist/components/ui/Alert.js +3 -3
  17. package/dist/components/ui/Avatar.d.ts +3 -1
  18. package/dist/components/ui/Avatar.js +3 -3
  19. package/dist/components/ui/Badge.js +2 -2
  20. package/dist/components/ui/BrandMark.js +2 -1
  21. package/dist/components/ui/Breadcrumbs.d.ts +2 -1
  22. package/dist/components/ui/Breadcrumbs.js +3 -2
  23. package/dist/components/ui/Button.d.ts +2 -0
  24. package/dist/components/ui/Button.js +3 -2
  25. package/dist/components/ui/Card.js +2 -2
  26. package/dist/components/ui/Checkbox.d.ts +2 -2
  27. package/dist/components/ui/Checkbox.js +14 -5
  28. package/dist/components/ui/CountUp.d.ts +2 -1
  29. package/dist/components/ui/CountUp.js +6 -3
  30. package/dist/components/ui/DataTable.d.ts +1 -0
  31. package/dist/components/ui/DataTable.js +12 -8
  32. package/dist/components/ui/DepthButton.js +2 -2
  33. package/dist/components/ui/Dialog.d.ts +5 -1
  34. package/dist/components/ui/Dialog.js +49 -18
  35. package/dist/components/ui/Disclosure.js +2 -1
  36. package/dist/components/ui/Divider.js +3 -2
  37. package/dist/components/ui/DropdownMenu.d.ts +11 -4
  38. package/dist/components/ui/DropdownMenu.js +109 -66
  39. package/dist/components/ui/EmptyState.js +2 -1
  40. package/dist/components/ui/Field.js +2 -2
  41. package/dist/components/ui/FlipCard.js +2 -2
  42. package/dist/components/ui/FloatingPortal.d.ts +10 -0
  43. package/dist/components/ui/FloatingPortal.js +51 -0
  44. package/dist/components/ui/IconButton.js +2 -2
  45. package/dist/components/ui/Kbd.js +2 -1
  46. package/dist/components/ui/Magnetic.js +2 -1
  47. package/dist/components/ui/Marquee.js +3 -2
  48. package/dist/components/ui/OverlayTrigger.d.ts +11 -0
  49. package/dist/components/ui/OverlayTrigger.js +5 -0
  50. package/dist/components/ui/Pagination.d.ts +5 -1
  51. package/dist/components/ui/Pagination.js +3 -2
  52. package/dist/components/ui/Popover.d.ts +18 -8
  53. package/dist/components/ui/Popover.js +58 -17
  54. package/dist/components/ui/ProgressBar.js +2 -2
  55. package/dist/components/ui/RadioGroup.d.ts +13 -5
  56. package/dist/components/ui/RadioGroup.js +36 -9
  57. package/dist/components/ui/Reveal.d.ts +0 -1
  58. package/dist/components/ui/Reveal.js +4 -3
  59. package/dist/components/ui/RevealHeading.js +2 -1
  60. package/dist/components/ui/ScrollProgress.js +2 -1
  61. package/dist/components/ui/SectionLabel.js +2 -1
  62. package/dist/components/ui/SegmentedControl.js +6 -3
  63. package/dist/components/ui/SelectField.js +18 -3
  64. package/dist/components/ui/Skeleton.js +2 -2
  65. package/dist/components/ui/Slider.d.ts +23 -7
  66. package/dist/components/ui/Slider.js +43 -6
  67. package/dist/components/ui/Spinner.js +2 -2
  68. package/dist/components/ui/Stat.js +2 -1
  69. package/dist/components/ui/Stepper.js +3 -3
  70. package/dist/components/ui/Switch.d.ts +27 -5
  71. package/dist/components/ui/Switch.js +51 -5
  72. package/dist/components/ui/Tabs.js +7 -5
  73. package/dist/components/ui/Tag.d.ts +2 -1
  74. package/dist/components/ui/Tag.js +4 -3
  75. package/dist/components/ui/TextArea.js +4 -1
  76. package/dist/components/ui/TextField.js +8 -3
  77. package/dist/components/ui/ThemeScope.d.ts +13 -0
  78. package/dist/components/ui/ThemeScope.js +10 -0
  79. package/dist/components/ui/ThemeToggle.d.ts +3 -0
  80. package/dist/components/ui/ThemeToggle.js +4 -3
  81. package/dist/components/ui/TiltCard.js +3 -2
  82. package/dist/components/ui/Timeline.js +2 -2
  83. package/dist/components/ui/ToastProvider.d.ts +5 -2
  84. package/dist/components/ui/ToastProvider.js +5 -5
  85. package/dist/components/ui/Tooltip.d.ts +4 -4
  86. package/dist/components/ui/Tooltip.js +70 -10
  87. package/dist/data-display.css +5 -0
  88. package/dist/effects.css +6 -0
  89. package/dist/feedback.css +5 -0
  90. package/dist/fonts-latin.css +10 -4
  91. package/dist/fonts.css +8 -0
  92. package/dist/forms.css +5 -0
  93. package/dist/hooks/useDismiss.d.ts +12 -3
  94. package/dist/hooks/useDismiss.js +5 -21
  95. package/dist/hooks/useFloatingPanel.d.ts +14 -0
  96. package/dist/hooks/useFloatingPanel.js +157 -0
  97. package/dist/hooks/useFormReset.d.ts +1 -1
  98. package/dist/hooks/useLayerDismiss.d.ts +4 -0
  99. package/dist/hooks/useLayerDismiss.js +48 -0
  100. package/dist/hooks/useMobileMenu.d.ts +0 -1
  101. package/dist/hooks/useMobileMenu.js +11 -16
  102. package/dist/hooks/usePanelFocus.d.ts +10 -0
  103. package/dist/hooks/usePanelFocus.js +60 -0
  104. package/dist/hooks/useTheme.d.ts +1 -0
  105. package/dist/hooks/useTheme.js +18 -5
  106. package/dist/index.d.ts +5 -0
  107. package/dist/index.js +1 -0
  108. package/dist/kit/forms.css +11 -2
  109. package/dist/kit/navigation.css +4 -0
  110. package/dist/kit/overlays.css +8 -4
  111. package/dist/lib/classNames.d.ts +2 -0
  112. package/dist/lib/classNames.js +16 -1
  113. package/dist/lib/componentClasses.d.ts +1 -0
  114. package/dist/lib/componentClasses.js +2 -0
  115. package/dist/lib/keyboard.d.ts +1 -1
  116. package/dist/lib/keyboard.js +5 -2
  117. package/dist/lib/layers.d.ts +18 -0
  118. package/dist/lib/layers.js +128 -0
  119. package/dist/lib/themeScope.d.ts +5 -0
  120. package/dist/lib/themeScope.js +3 -0
  121. package/dist/minimal-reset.css +6 -0
  122. package/dist/navigation.css +5 -0
  123. package/dist/overlays.css +5 -0
  124. package/dist/scoped/base.css +183 -0
  125. package/dist/scoped/kit/actions.css +209 -0
  126. package/dist/scoped/kit/data-display.css +492 -0
  127. package/dist/scoped/kit/effects.css +140 -0
  128. package/dist/scoped/kit/feedback.css +309 -0
  129. package/dist/scoped/kit/forms.css +370 -0
  130. package/dist/scoped/kit/navigation.css +266 -0
  131. package/dist/scoped/kit/overlays.css +299 -0
  132. package/dist/scoped/marquee.css +58 -0
  133. package/dist/scoped/spinner.css +20 -0
  134. package/dist/scoped/theme-toggle.css +38 -0
  135. package/dist/scoped/tokens.css +119 -0
  136. package/dist/scoped/ui.css +127 -0
  137. package/dist/scoped/utilities.css +17 -0
  138. package/dist/scoped-tokens.css +1 -0
  139. package/dist/scoped.css +12 -0
  140. package/dist/theme-toggle.css +2 -2
  141. package/dist/tokens.json +710 -0
  142. package/examples/forms/HookFormSettingsForm.tsx +176 -0
  143. package/examples/forms/ManualSettingsForm-0.3.0.tsx +102 -0
  144. package/examples/forms/NativeSettingsForm.tsx +119 -0
  145. package/examples/forms/settings.ts +61 -0
  146. package/examples/forms/useSubmission.ts +77 -0
  147. package/package.json +30 -6
package/CHANGELOG.md CHANGED
@@ -6,6 +6,29 @@ Changes to the published `@abuzareal/contour-ui` package. Version headings refer
6
6
 
7
7
  No changes recorded.
8
8
 
9
+ ## 0.5.0
10
+
11
+ Combined overlay, styling and size work. This heading records source work, not npm publication.
12
+
13
+ - Shared owner-document topmost dismissal and matching paint order for nested/sibling/portaled layers; cancellation, outside focus, ancestor visibility, native backdrop priority and cleanup are covered by regressions. Mobile-menu dismissal cooperates with the same registry.
14
+ - Popover/Menu/Tooltip gain collision-aware placement, bounded scrolling, RTL and CSS-zoom handling via Floating UI DOM 1.8.0. Default body/native-modal portals escape clipping and carry theme values/direction. Default DOM placement changes ancestor selectors; use direct part hooks or portal=false for a deliberate local integration. Public refs/state callbacks and void useDismiss remain compatible.
15
+ - Hoverable/persistent supplemental tooltips retain descriptions/names and Escape suppression. Public button renderTrigger and focus policies avoid nested controls; native Dialog preserves focus/scroll/lifecycle behavior and accepts explicit initial focus.
16
+ - Add isolated scoped.css, prefixed classes/tokens, controlled ThemeScope, minimal-reset.css, token JSON generation, forced-color states, logical RTL layouts and configurable internal labels/formatters. Legacy globals, branded fonts and application-layout compatibility remain; no consumer migration is performed. Compact density is deferred pending a concrete need.
17
+ - Add measured Vite/Next Button/settings/table fixtures, selected-icon comparison, actual network/install/archive categories, selective CSS entries and reviewed regression budgets. System fonts make no font requests; Fontsource/Lucide install costs are retained and disclosed. Three.js/Lenis remain optional and absent from core fixtures.
18
+
19
+ See OVERLAYS.md, STYLING.md and SIZE.md for migration, behavior, measurements and evidence boundaries. Independent applications/site versions are not upgraded by this package release.
20
+
21
+ ## 0.4.0
22
+
23
+ Native forms and tested application recipes. This heading records source work, not npm publication.
24
+
25
+ - 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.
26
+ - 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.
27
+ - 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.
28
+ - 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.
29
+
30
+ 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.
31
+
9
32
  ## 0.3.0
10
33
 
11
34
  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.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.
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 | Target and use |
8
- | -------------------------------------------- | -------------------------------------------------------------------------------------------------- |
9
- | Button, IconButton, DepthButton, ThemeToggle | 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 |
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 Popover, the caller handler on the same owned element runs first. preventDefault vetoes internal tab click/arrow selection, popover trigger toggle, 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.
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 | Required number value / boolean checked, onChange(value); no new name/form/default/reset API |
143
- | RadioGroup / SegmentedControl | Required string value, onChange(value); generated radio name / 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. Switch/Slider/RadioGroup form integration remains FORM scope.
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.0**, reviewed 12 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.
3
+ Contract version **0.5.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 retains its generated ID and internal indeterminate behavior alongside the forwarded input ref; caller ARIA validity and descriptions remain composed. Switch/Slider/RadioGroup lack caller-named form contracts; serialize state deliberately.
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.
@@ -91,3 +91,9 @@ Formal deprecation identifies replacement/affected versions, marks the API/docs
91
91
  Releases choose an unused version, align manifest/lock/tag/changelog, validate a candidate archive and state material limits. Consumers keep exact pins until the released version is installed, their lockfile updated and relevant checks pass. Package publication does not deploy an application.
92
92
 
93
93
  1.0 requires settled supported contracts, appropriate docs/evidence, successful outside-use workflows and no known critical/high-impact defect in the promoted scope. Complete stabilization, then rerun final release gates. Deferred component backlog completion is not a prerequisite.
94
+
95
+ ## 0.5 overlay/style/size contract
96
+
97
+ Default floating portals render in the owner body or active native modal, carrying scoped values/direction. Topmost dismissal, focus customization, closed-ancestor suspension and hover persistence follow OVERLAYS.md. Default portal DOM placement is an explicit 0.x migration; local placement remains available. Stable ref targets and the public void useDismiss return remain unchanged.
98
+
99
+ Legacy aggregate/reset/tokens/classes remain compatibility mode. Isolated CSS, prefixed schema, controlled ThemeScope, optional minimal reset and measured family entries follow STYLING.md. Product/layout tokens remain legacy/app-owned. Scope persistence is caller-owned; document hooks retain aa-theme behavior. Compact density and a font-install packaging migration were evaluated and deferred with reasons in STYLING.md/SIZE.md. No zero-dependency, universal RTL/AT or production performance claim is added.
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.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.
3
+ This guide describes **Contour 0.5.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,8 @@ 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.3.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.
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.
56
+
57
+ For embedded first use, substitute scoped.css, omit fonts/bootstrap/reset, and wrap client controls in controlled ThemeScope. The size fixture separately measures Button/settings/table routes with system fonts on the same pinned webpack configuration. Existing document-owned/branded integration above remains compatible; new portal migration and family imports are described in OVERLAYS.md and SIZE.md.
package/OVERLAYS.md ADDED
@@ -0,0 +1,52 @@
1
+ # Floating layers and focus
2
+
3
+ 0.5 adds shared dismissal, collision-aware placement and portals to Popover, DropdownMenu and Tooltip. Dialog remains a native modal/drawer. Contour uses MIT-licensed Floating UI DOM 1.8.0 for geometry and observers; it does not replace the public components with another component library.
4
+
5
+ ## Dismissal
6
+
7
+ One Escape reaches the deepest active layer; independent siblings use most-recent activation. Paint order follows that dismissal order. One outside primary-pointer press dismisses at most one layer. Presses in portaled descendants are inside their owned layer. Explicit buttons can still perform their own actions. A native modal backdrop does not also dismiss its modal when that press already belongs to a child layer.
8
+
9
+ Listeners attach to the layer's owner document, rebind on document moves and release on inactivity/unmount. They never stop event propagation. Handled Escape prevents its native default so a child dismissal does not also cancel its native parent dialog. Other keys and ordinary outside focus remain available. `onDismiss(reason, nativeEvent)` on Popover/Menu can call `preventDefault()` to veto dismissal. Dialog uses its existing `onCancel` and `onClick` cancellation contracts. `useDismiss(ref, active, callback, options)` uses the same registry; additional `branches` include a caller's trigger/portal. `layerElement` opts a custom floating element into owned stack order; its previous inline z-index is restored on release.
10
+
11
+ A closed ancestor suspends descendant layers without clearing their controlled/uncontrolled state. Reopening resumes that state; reset child state in the parent's application callback when a different policy is wanted. Modal/native close buttons and controlled parent updates are application-owned.
12
+
13
+ ## Placement and containers
14
+
15
+ Popover/Menu accept `side` (bottom by default), `align` (start by default; center/end supported), `sideOffset` (8 px) and `collisionPadding` (8 px). Tooltip retains `placement` (top), with center alignment and a 10 px offset. Alignment is logical in RTL. Placement flips, shifts on both axes and constrains scrollable content to the clipping boundary; scroll/resize/layout/element observers update it while open and clean up on close. Nonfinite offsets/padding fall back to defaults; negative collision padding clamps to zero. A geometry failure retains a viewport fallback and emits a warning rather than leaving an unhandled promise. Geometry properties and stack order are owned; use part props for appearance/content, not competing position/transform declarations. `--contour-z-overlay` configures the nonmodal stacking base (30); native modal top-layer ordering is browser-owned.
16
+
17
+ Default `portal` is true. Content renders in the trigger's owner-document body, or **inside its open native dialog**. A small portal wrapper carries inherited variables, font, text color and direction and follows ancestor theme/style mutations. This escapes a clipped or transformed theme root without changing the document's theme. `container` selects an explicit element in the same document; outside/wrong-document containers inside an active modal fall back to that modal. Keep custom containers unclipped and provide enough space. `portal={false}` preserves local DOM placement for a deliberate integration; clipping ancestors may then clip it.
18
+
19
+ **Migration from 0.4:** default portal DOM placement changes ancestor selectors and DOM-based tests. Target `panelProps.className` directly rather than `.host-root .host-panel`; use `portal={false}` when local placement is required. Root refs still target the wrapper, trigger refs target buttons and panel refs target panels, and may detach/reattach when the portal container changes. No deep import is needed.
20
+
21
+ ## Triggers and focus
22
+
23
+ `trigger` still supplies button content. `renderTrigger` supplies a supported ref-forwarding button component. Spread the provided props so refs, caller handlers and owned type/expanded/controls/haspopup relationships survive; do not nest a button inside the default trigger. An ordinary native button or Contour Button works. Link/non-button triggers are not supported by this contract.
24
+
25
+ ```tsx
26
+ import { Button, Popover, TextField } from "@abuzareal/contour-ui";
27
+ export function HelpPanel() {
28
+ return (
29
+ <Popover
30
+ title="Help"
31
+ trigger="Help"
32
+ renderTrigger={(props) => <Button {...props} variant="secondary" />}
33
+ >
34
+ <TextField label="Question" />
35
+ </Popover>
36
+ );
37
+ }
38
+ ```
39
+
40
+ Popover focuses its panel on entry; `initialFocus={ref}` selects a mounted descendant and `initialFocus={false}` keeps the trigger focused. Cancelable `onOpenAutoFocus` / `onCloseAutoFocus` events allow caller control. Escape returns to a connected trigger by default, including controlled dismissal requests. Outside press does not steal focus from its destination. Programmatic close/unmount returns focus when the layer or a descendant owned it. A missing/removed trigger receives no forced substitute; the application owns its workflow fallback. Popover is nonmodal: it does not trap focus or make the background inert. Portal DOM order affects Tab order; choose inline placement or explicit focus customization for an application-specific sequence.
41
+
42
+ Menu enters the first enabled action; ArrowUp from its trigger enters the last. ArrowUp/Down, Home/End and typeahead skip disabled entries. Selection/Escape return to the trigger; Tab dismisses without consuming Tab. Empty/all-disabled menus focus their panel. Labels/content are caller-owned. Dialog uses native focus confinement and focus return; its new `initialFocus` chooses a mounted modal descendant. `onOpenAutoFocus` can cancel that explicit override; it does not undo native showModal autofocus. Existing nested locks, drawer placement, long content and cleanup remain supported.
43
+
44
+ ## Supplemental hints
45
+
46
+ Tooltip content is a noninteractive string. Hover and keyboard focus cooperate; leaving the pointer does not erase a focused hint. Trigger-to-content travel stays available through a bounded pointer corridor; the hint remains while pointer/focus applies. Escape suppresses it until the current hover/focus conditions end and a new session begins. Existing `aria-describedby` values and the child's accessible name/ref/events are retained. Pointer content is hoverable; tooltip content is never a substitute for a visible label, essential instruction, validation error or interactive panel. Touch users must have the same essential information visibly available.
47
+
48
+ Production checks cover nested/sibling/portaled layers, native modal portals, touch outside press, RTL, narrow/resized viewports, long scrolling panels and CSS zoom. These are automated browser checks, not physical mobile-keyboard or assistive-technology certification. See [W3C hover/focus guidance](https://www.w3.org/WAI/WCAG21/Techniques/client-side-script/SCR39) and [Floating UI observer lifecycle](https://floating-ui.com/docs/autoupdate).
49
+
50
+ Keep a portal container stable while open. Changing its DOM target can remount the portaled subtree; keep unsaved child state in the owner when container changes are required.
51
+
52
+ Default-open children choose an enclosing native dialog even before its open attribute is set. When Dialog calls showModal in the same commit, these children enter its top layer without an additional click or render.