@agent-ui-kit/components 0.0.3

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 (271) hide show
  1. package/dist/base-styles.css +8 -0
  2. package/dist/component-styles.css +147 -0
  3. package/dist/controls/_base/index.d.ts +3 -0
  4. package/dist/controls/_base/index.js +9 -0
  5. package/dist/controls/_base/indicator-element.d.ts +132 -0
  6. package/dist/controls/_base/indicator-element.js +158 -0
  7. package/dist/controls/_base/listbox-element.d.ts +70 -0
  8. package/dist/controls/_base/listbox-element.js +74 -0
  9. package/dist/controls/_base/range-element.d.ts +120 -0
  10. package/dist/controls/_base/range-element.js +158 -0
  11. package/dist/controls/_surface/container-box.css +301 -0
  12. package/dist/controls/_surface/container.css +156 -0
  13. package/dist/controls/_token-surface/token-surface.d.ts +35 -0
  14. package/dist/controls/_token-surface/token-surface.js +97 -0
  15. package/dist/controls/attachment/attachment-meta.d.ts +8 -0
  16. package/dist/controls/attachment/attachment-meta.js +84 -0
  17. package/dist/controls/attachment/attachment.css +91 -0
  18. package/dist/controls/attachment/attachment.d.ts +40 -0
  19. package/dist/controls/attachment/attachment.js +70 -0
  20. package/dist/controls/avatar/avatar-initials.d.ts +4 -0
  21. package/dist/controls/avatar/avatar-initials.js +30 -0
  22. package/dist/controls/avatar/avatar.css +87 -0
  23. package/dist/controls/avatar/avatar.d.ts +40 -0
  24. package/dist/controls/avatar/avatar.js +77 -0
  25. package/dist/controls/badge/badge.css +175 -0
  26. package/dist/controls/badge/badge.d.ts +36 -0
  27. package/dist/controls/badge/badge.js +71 -0
  28. package/dist/controls/bar-chart/bar-chart.css +110 -0
  29. package/dist/controls/bar-chart/bar-chart.d.ts +26 -0
  30. package/dist/controls/bar-chart/bar-chart.js +80 -0
  31. package/dist/controls/bar-chart/bar-math.d.ts +31 -0
  32. package/dist/controls/bar-chart/bar-math.js +106 -0
  33. package/dist/controls/button/button.css +263 -0
  34. package/dist/controls/button/button.d.ts +61 -0
  35. package/dist/controls/button/button.js +115 -0
  36. package/dist/controls/calendar/calendar.css +495 -0
  37. package/dist/controls/calendar/calendar.d.ts +161 -0
  38. package/dist/controls/calendar/calendar.js +1004 -0
  39. package/dist/controls/card/card-content.d.ts +24 -0
  40. package/dist/controls/card/card-content.js +133 -0
  41. package/dist/controls/card/card-footer.d.ts +3 -0
  42. package/dist/controls/card/card-footer.js +16 -0
  43. package/dist/controls/card/card-header.d.ts +3 -0
  44. package/dist/controls/card/card-header.js +18 -0
  45. package/dist/controls/card/card.css +445 -0
  46. package/dist/controls/card/card.d.ts +51 -0
  47. package/dist/controls/card/card.js +61 -0
  48. package/dist/controls/checkbox/checkbox.css +204 -0
  49. package/dist/controls/checkbox/checkbox.d.ts +114 -0
  50. package/dist/controls/checkbox/checkbox.js +130 -0
  51. package/dist/controls/code/code.css +72 -0
  52. package/dist/controls/code/code.d.ts +23 -0
  53. package/dist/controls/code/code.js +40 -0
  54. package/dist/controls/color-picker/color-picker.css +209 -0
  55. package/dist/controls/color-picker/color-picker.d.ts +84 -0
  56. package/dist/controls/color-picker/color-picker.js +545 -0
  57. package/dist/controls/color-picker/color.d.ts +40 -0
  58. package/dist/controls/color-picker/color.js +159 -0
  59. package/dist/controls/column/column.css +151 -0
  60. package/dist/controls/column/column.d.ts +107 -0
  61. package/dist/controls/column/column.js +57 -0
  62. package/dist/controls/combo-box/combo-box.css +397 -0
  63. package/dist/controls/combo-box/combo-box.d.ts +133 -0
  64. package/dist/controls/combo-box/combo-box.js +680 -0
  65. package/dist/controls/command-modal/command-modal.css +226 -0
  66. package/dist/controls/command-modal/command-modal.d.ts +42 -0
  67. package/dist/controls/command-modal/command-modal.js +315 -0
  68. package/dist/controls/disclosure/disclosure.css +162 -0
  69. package/dist/controls/disclosure/disclosure.d.ts +37 -0
  70. package/dist/controls/disclosure/disclosure.js +207 -0
  71. package/dist/controls/field/field.css +140 -0
  72. package/dist/controls/field/field.d.ts +27 -0
  73. package/dist/controls/field/field.js +228 -0
  74. package/dist/controls/form-provider/form-provider.css +40 -0
  75. package/dist/controls/form-provider/form-provider.d.ts +42 -0
  76. package/dist/controls/form-provider/form-provider.js +105 -0
  77. package/dist/controls/grid/grid.css +72 -0
  78. package/dist/controls/grid/grid.d.ts +60 -0
  79. package/dist/controls/grid/grid.js +56 -0
  80. package/dist/controls/icon/icon.css +37 -0
  81. package/dist/controls/icon/icon.d.ts +25 -0
  82. package/dist/controls/icon/icon.js +55 -0
  83. package/dist/controls/index.d.ts +57 -0
  84. package/dist/controls/index.js +136 -0
  85. package/dist/controls/ladder/ladder.css +89 -0
  86. package/dist/controls/ladder/ladder.d.ts +27 -0
  87. package/dist/controls/ladder/ladder.js +78 -0
  88. package/dist/controls/list/list.css +118 -0
  89. package/dist/controls/list/list.d.ts +84 -0
  90. package/dist/controls/list/list.js +48 -0
  91. package/dist/controls/menu/menu.css +195 -0
  92. package/dist/controls/menu/menu.d.ts +44 -0
  93. package/dist/controls/menu/menu.js +248 -0
  94. package/dist/controls/modal/modal.css +99 -0
  95. package/dist/controls/modal/modal.d.ts +61 -0
  96. package/dist/controls/modal/modal.js +155 -0
  97. package/dist/controls/popover/popover.css +83 -0
  98. package/dist/controls/popover/popover.d.ts +44 -0
  99. package/dist/controls/popover/popover.js +164 -0
  100. package/dist/controls/progress/progress.css +106 -0
  101. package/dist/controls/progress/progress.d.ts +28 -0
  102. package/dist/controls/progress/progress.js +80 -0
  103. package/dist/controls/radio/radio-group.css +84 -0
  104. package/dist/controls/radio/radio-group.d.ts +152 -0
  105. package/dist/controls/radio/radio-group.js +350 -0
  106. package/dist/controls/radio/radio.css +219 -0
  107. package/dist/controls/radio/radio.d.ts +19 -0
  108. package/dist/controls/radio/radio.js +75 -0
  109. package/dist/controls/ramp/ramp.css +75 -0
  110. package/dist/controls/ramp/ramp.d.ts +29 -0
  111. package/dist/controls/ramp/ramp.js +72 -0
  112. package/dist/controls/row/row.css +152 -0
  113. package/dist/controls/row/row.d.ts +95 -0
  114. package/dist/controls/row/row.js +45 -0
  115. package/dist/controls/segment/segment.css +66 -0
  116. package/dist/controls/segment/segment.d.ts +3 -0
  117. package/dist/controls/segment/segment.js +26 -0
  118. package/dist/controls/segmented-control/segmented-control.css +189 -0
  119. package/dist/controls/segmented-control/segmented-control.d.ts +20 -0
  120. package/dist/controls/segmented-control/segmented-control.js +64 -0
  121. package/dist/controls/select/select.css +467 -0
  122. package/dist/controls/select/select.d.ts +147 -0
  123. package/dist/controls/select/select.js +538 -0
  124. package/dist/controls/slider/slider.css +231 -0
  125. package/dist/controls/slider/slider.d.ts +111 -0
  126. package/dist/controls/slider/slider.js +60 -0
  127. package/dist/controls/slider-multi/slider-multi.css +197 -0
  128. package/dist/controls/slider-multi/slider-multi.d.ts +149 -0
  129. package/dist/controls/slider-multi/slider-multi.js +380 -0
  130. package/dist/controls/sparkline/sparkline-math.d.ts +23 -0
  131. package/dist/controls/sparkline/sparkline-math.js +79 -0
  132. package/dist/controls/sparkline/sparkline.css +61 -0
  133. package/dist/controls/sparkline/sparkline.d.ts +27 -0
  134. package/dist/controls/sparkline/sparkline.js +74 -0
  135. package/dist/controls/split/constrain.d.ts +44 -0
  136. package/dist/controls/split/constrain.js +126 -0
  137. package/dist/controls/split/split-pane.css +63 -0
  138. package/dist/controls/split/split-pane.d.ts +49 -0
  139. package/dist/controls/split/split-pane.js +42 -0
  140. package/dist/controls/split/split.css +127 -0
  141. package/dist/controls/split/split.d.ts +42 -0
  142. package/dist/controls/split/split.js +364 -0
  143. package/dist/controls/stat/stat-model.d.ts +22 -0
  144. package/dist/controls/stat/stat-model.js +69 -0
  145. package/dist/controls/stat/stat.css +101 -0
  146. package/dist/controls/stat/stat.d.ts +30 -0
  147. package/dist/controls/stat/stat.js +73 -0
  148. package/dist/controls/status-stream/status-stream.css +151 -0
  149. package/dist/controls/status-stream/status-stream.d.ts +108 -0
  150. package/dist/controls/status-stream/status-stream.js +417 -0
  151. package/dist/controls/swatch/swatch.css +66 -0
  152. package/dist/controls/swatch/swatch.d.ts +28 -0
  153. package/dist/controls/swatch/swatch.js +74 -0
  154. package/dist/controls/swiper/swiper-item.css +17 -0
  155. package/dist/controls/swiper/swiper-item.d.ts +31 -0
  156. package/dist/controls/swiper/swiper-item.js +47 -0
  157. package/dist/controls/swiper/swiper-label.css +10 -0
  158. package/dist/controls/swiper/swiper-label.d.ts +4 -0
  159. package/dist/controls/swiper/swiper-label.js +20 -0
  160. package/dist/controls/swiper/swiper-paddles.css +26 -0
  161. package/dist/controls/swiper/swiper-paddles.d.ts +10 -0
  162. package/dist/controls/swiper/swiper-paddles.js +62 -0
  163. package/dist/controls/swiper/swiper-pagination.css +60 -0
  164. package/dist/controls/swiper/swiper-pagination.d.ts +31 -0
  165. package/dist/controls/swiper/swiper-pagination.js +78 -0
  166. package/dist/controls/swiper/swiper.css +153 -0
  167. package/dist/controls/swiper/swiper.d.ts +158 -0
  168. package/dist/controls/swiper/swiper.js +659 -0
  169. package/dist/controls/switch/switch.css +194 -0
  170. package/dist/controls/switch/switch.d.ts +5 -0
  171. package/dist/controls/switch/switch.js +18 -0
  172. package/dist/controls/table/table-model.d.ts +43 -0
  173. package/dist/controls/table/table-model.js +124 -0
  174. package/dist/controls/table/table.css +90 -0
  175. package/dist/controls/table/table.d.ts +29 -0
  176. package/dist/controls/table/table.js +178 -0
  177. package/dist/controls/tabs/tab-panel.d.ts +10 -0
  178. package/dist/controls/tabs/tab-panel.js +89 -0
  179. package/dist/controls/tabs/tab.d.ts +26 -0
  180. package/dist/controls/tabs/tab.js +67 -0
  181. package/dist/controls/tabs/tabs.css +181 -0
  182. package/dist/controls/tabs/tabs.d.ts +61 -0
  183. package/dist/controls/tabs/tabs.js +176 -0
  184. package/dist/controls/text/href.d.ts +26 -0
  185. package/dist/controls/text/href.js +48 -0
  186. package/dist/controls/text/text.css +328 -0
  187. package/dist/controls/text/text.d.ts +85 -0
  188. package/dist/controls/text/text.js +273 -0
  189. package/dist/controls/text-field/text-field.css +498 -0
  190. package/dist/controls/text-field/text-field.d.ts +226 -0
  191. package/dist/controls/text-field/text-field.js +905 -0
  192. package/dist/controls/textarea/textarea.css +251 -0
  193. package/dist/controls/textarea/textarea.d.ts +136 -0
  194. package/dist/controls/textarea/textarea.js +312 -0
  195. package/dist/controls/theme-provider/theme-provider.css +48 -0
  196. package/dist/controls/theme-provider/theme-provider.d.ts +59 -0
  197. package/dist/controls/theme-provider/theme-provider.js +40 -0
  198. package/dist/controls/timeline/timeline.css +33 -0
  199. package/dist/controls/timeline/timeline.d.ts +39 -0
  200. package/dist/controls/timeline/timeline.js +52 -0
  201. package/dist/controls/timeline-item/timeline-item.css +495 -0
  202. package/dist/controls/timeline-item/timeline-item.d.ts +79 -0
  203. package/dist/controls/timeline-item/timeline-item.js +360 -0
  204. package/dist/controls/toast/toast-region.css +56 -0
  205. package/dist/controls/toast/toast-region.d.ts +20 -0
  206. package/dist/controls/toast/toast-region.js +113 -0
  207. package/dist/controls/toast/toast.css +96 -0
  208. package/dist/controls/toast/toast.d.ts +28 -0
  209. package/dist/controls/toast/toast.js +191 -0
  210. package/dist/controls/toolbar/toolbar.css +125 -0
  211. package/dist/controls/toolbar/toolbar.d.ts +108 -0
  212. package/dist/controls/toolbar/toolbar.js +68 -0
  213. package/dist/controls/tooltip/tooltip.css +80 -0
  214. package/dist/controls/tooltip/tooltip.d.ts +57 -0
  215. package/dist/controls/tooltip/tooltip.js +205 -0
  216. package/dist/descriptor/component-descriptor.d.ts +112 -0
  217. package/dist/descriptor/component-descriptor.js +492 -0
  218. package/dist/descriptor/index.d.ts +1 -0
  219. package/dist/descriptor/index.js +5 -0
  220. package/dist/dom/container.d.ts +45 -0
  221. package/dist/dom/container.js +60 -0
  222. package/dist/dom/element.d.ts +98 -0
  223. package/dist/dom/element.js +221 -0
  224. package/dist/dom/form.d.ts +240 -0
  225. package/dist/dom/form.js +399 -0
  226. package/dist/dom/index.d.ts +10 -0
  227. package/dist/dom/index.js +31 -0
  228. package/dist/dom/props.d.ts +56 -0
  229. package/dist/dom/props.js +231 -0
  230. package/dist/dom/repeat.d.ts +10 -0
  231. package/dist/dom/repeat.js +202 -0
  232. package/dist/dom/template.d.ts +161 -0
  233. package/dist/dom/template.js +634 -0
  234. package/dist/dom/watch.d.ts +11 -0
  235. package/dist/dom/watch.js +82 -0
  236. package/dist/foundation-styles.css +9 -0
  237. package/dist/index.d.ts +2 -0
  238. package/dist/index.js +3 -0
  239. package/dist/reactive/graph.d.ts +65 -0
  240. package/dist/reactive/graph.js +310 -0
  241. package/dist/reactive/index.d.ts +3 -0
  242. package/dist/reactive/index.js +4 -0
  243. package/dist/reactive/scheduler.d.ts +13 -0
  244. package/dist/reactive/scheduler.js +49 -0
  245. package/dist/traits/area-drag.d.ts +12 -0
  246. package/dist/traits/area-drag.js +70 -0
  247. package/dist/traits/form-registry.d.ts +36 -0
  248. package/dist/traits/form-registry.js +108 -0
  249. package/dist/traits/index.d.ts +24 -0
  250. package/dist/traits/index.js +19 -0
  251. package/dist/traits/overlay.d.ts +50 -0
  252. package/dist/traits/overlay.js +277 -0
  253. package/dist/traits/pane-resize.d.ts +26 -0
  254. package/dist/traits/pane-resize.js +103 -0
  255. package/dist/traits/press-activation.d.ts +11 -0
  256. package/dist/traits/press-activation.js +55 -0
  257. package/dist/traits/roving-focus.d.ts +52 -0
  258. package/dist/traits/roving-focus.js +214 -0
  259. package/dist/traits/scroll-fade.d.ts +28 -0
  260. package/dist/traits/scroll-fade.js +120 -0
  261. package/dist/traits/selection-commit.d.ts +30 -0
  262. package/dist/traits/selection-commit.js +146 -0
  263. package/dist/traits/tabbable.d.ts +12 -0
  264. package/dist/traits/tabbable.js +59 -0
  265. package/dist/traits/track-user-invalid.d.ts +36 -0
  266. package/dist/traits/track-user-invalid.js +52 -0
  267. package/dist/traits/value-codec.d.ts +120 -0
  268. package/dist/traits/value-codec.js +497 -0
  269. package/dist/traits/value-drag.d.ts +18 -0
  270. package/dist/traits/value-drag.js +84 -0
  271. package/package.json +263 -0
@@ -0,0 +1,12 @@
1
+ import type { UIElement } from '../dom/index.js';
2
+ export interface TabbableOptions {
3
+ /** True ⇒ the control is disabled and leaves the tab order. Read reactively inside the effect. */
4
+ disabled: () => boolean;
5
+ }
6
+ /**
7
+ * Make a `UIElement` keyboard-focusable (`tabindex=0`) while enabled and remove it from the tab order while
8
+ * disabled, native-button style. Invoke from the control's `connected()` (where the connection scope is
9
+ * live). Returns `release()` for early teardown (idempotent); otherwise the effect is disposed when the host
10
+ * disconnects.
11
+ */
12
+ export declare function tabbable(host: UIElement, opts: TabbableOptions): () => void;
@@ -0,0 +1,59 @@
1
+ // tabbable.ts — the focusability trait (ADR-0010). A light-DOM custom element inherits NONE of a native
2
+ // button's focus behaviour, so an interactive control opts into tab participation by calling this from
3
+ // `connected()`.
4
+ //
5
+ // Semantics (native-button parity):
6
+ // · enabled — `host.tabIndex = 0` ⇒ keyboard-focusable (role=button parity; lets the focus ring show).
7
+ // · disabled — `removeAttribute('tabindex')` ⇒ out of the tab order, matching native `<button disabled>`.
8
+ // · reactive — the rule runs in a scope-owned `host.effect`, so toggling the `disabled` signal re-applies it.
9
+ //
10
+ // THE ROVING-MARKER CONTRACT (ADR-0121 amendment, post-toolbar): while `disabled` is false, this trait
11
+ // DEFERS the `tabIndex = 0` write whenever the host carries `ROVING_ITEM_ATTR` (`data-roving`) —
12
+ // traits/roving-focus.ts's ownership marker, stamped on every item a roving-focus host manages (see that
13
+ // module's own banner for the full ordering story: a roving PARENT's synchronous init runs before this
14
+ // trait's own `connected()`-installed effect for items connected in the same subtree, so without this
15
+ // deferral a `ui-button`/`ui-radio`/any tabbable item inside a roving host would silently reclaim a second
16
+ // tab stop the instant it connects — `ui-toolbar` → `ui-button` is what surfaced the bug; `ui-radio-group`
17
+ // → `ui-radio` carried it too, latent). The check runs on EVERY effect re-run (not just the first), so a
18
+ // later re-enable while still roving-owned correctly stays deferred (no "two tab stops" regression) —
19
+ // `disabled`/`aria-disabled` semantics are entirely UNCHANGED by this; only the tabIndex=0 WRITE yields.
20
+ // Absent the marker (every non-roving-hosted tabbable control — the overwhelming majority), this trait's
21
+ // behavior is byte-identical to the pre-ADR-0121 rule (tabbable.test.ts's own identity-style probe pins it).
22
+ //
23
+ // Where `press-activation` only GUARDS at event time (no reactivity), `tabbable` must ACTIVELY change a DOM
24
+ // attribute when `disabled` toggles — hence an effect, not listeners. Both are scope/abort-owned + leak-free:
25
+ // the effect is created under the host's connection scope, so it dies on disconnect and re-installs on
26
+ // reconnect (`connected()` re-runs). `release()` is an idempotent early-teardown escape hatch.
27
+ //
28
+ // `traits → dom` is the one allowed cross-layer direction (reactive ← dom ← traits); the host type only.
29
+ // `ROVING_ITEM_ATTR` is imported from the sibling `roving-focus.ts` trait — a same-layer (traits → traits)
30
+ // import, permitted by layering.test.ts (only an upward import is a violation).
31
+ import { ROVING_ITEM_ATTR } from "./roving-focus.js";
32
+ /**
33
+ * Make a `UIElement` keyboard-focusable (`tabindex=0`) while enabled and remove it from the tab order while
34
+ * disabled, native-button style. Invoke from the control's `connected()` (where the connection scope is
35
+ * live). Returns `release()` for early teardown (idempotent); otherwise the effect is disposed when the host
36
+ * disconnects.
37
+ */
38
+ export function tabbable(host, opts) {
39
+ let released = false;
40
+ const dispose = host.effect(() => {
41
+ if (released)
42
+ return;
43
+ if (opts.disabled()) {
44
+ host.removeAttribute('tabindex'); // disabled → out of the tab order (native parity)
45
+ return;
46
+ }
47
+ // ROVING-MARKER CONTRACT (see the file banner) — an external roving-focus host already owns this
48
+ // item's tab-stop; yield the write rather than reclaiming a second tabindex=0.
49
+ if (host.hasAttribute(ROVING_ITEM_ATTR))
50
+ return;
51
+ host.tabIndex = 0; // enabled, not roving-owned → focusable, role=button parity
52
+ });
53
+ return () => {
54
+ if (released)
55
+ return;
56
+ released = true;
57
+ dispose();
58
+ };
59
+ }
@@ -0,0 +1,36 @@
1
+ import type { ReadonlySignal } from '../reactive/index.js';
2
+ import type { UIElement } from '../dom/index.js';
3
+ export interface TrackUserInvalidOptions {
4
+ /** True ⇒ the control is currently invalid. Read lazily by `userInvalid()`, never to drive the timing. */
5
+ invalid: () => boolean;
6
+ }
7
+ export interface TrackUserInvalidController {
8
+ /**
9
+ * The TIMING signal: false until the first blur/change on the host, then true. The control reads this
10
+ * (directly or via `userInvalid()`) to gate the user-invalid treatment. Read-only to consumers.
11
+ */
12
+ readonly interacted: ReadonlySignal<boolean>;
13
+ /**
14
+ * The composed gate: `interacted.value && invalid()`. The control's apply effect reads this and reflects
15
+ * it onto `internals` (`states.add/delete('user-invalid')` + the editor `aria-invalid`). Reading it inside
16
+ * an effect tracks `interacted` (always) and `invalid`'s deps (only once interacted — `&&` short-circuits,
17
+ * so validity is untracked until the user has interacted, which is the whole point).
18
+ */
19
+ userInvalid: () => boolean;
20
+ /**
21
+ * Clear the touched state back to its first-paint suppression: flips `interacted` to false so the
22
+ * user-invalid treatment is gated off again until the next blur/change. The control calls this from its
23
+ * `formReset()` so a form reset does not leave a required-empty field showing `:state(user-invalid)` (native
24
+ * parity). Idempotent (a no-op when already false — an Object.is-equal set); does not touch the listeners.
25
+ */
26
+ reset: () => void;
27
+ /** Idempotent early teardown: stops the behaviour. Otherwise the listeners die with the connection. */
28
+ release: () => void;
29
+ }
30
+ /**
31
+ * Track first-interaction TIMING for a form control's user-invalid treatment. Invoke from the control's
32
+ * `connected()` (where the connection scope + `AbortSignal` are live). Returns the `interacted` signal +
33
+ * the composed `userInvalid()` gate + an idempotent `release()`; the listeners auto-remove on disconnect
34
+ * and re-arm on reconnect.
35
+ */
36
+ export declare function trackUserInvalid(host: UIElement, opts: TrackUserInvalidOptions): TrackUserInvalidController;
@@ -0,0 +1,52 @@
1
+ // track-user-invalid.ts — the user-invalid TIMING controller (ADR-0014 clause 2c, G4).
2
+ //
3
+ // A form control must NOT flash `aria-invalid` / `:state(user-invalid)` on first paint — the danger
4
+ // treatment surfaces only AFTER the user has interacted (the first blur or change). This controller owns
5
+ // that TIMING: it watches the host for the first interaction and flips a reactive `interacted` cell true,
6
+ // exposing it so the CONTROL gates the danger state. The control APPLIES the AX/custom state via its
7
+ // protected `internals` (a controller cannot reach `host.internals` — the same `tabbable`/`ariaDisabled`
8
+ // split ADR-0010 forced); this supplies the TIMING, never the application.
9
+ //
10
+ // Stateless trait vs stateful controller (plan §7 'traits + controllers'): `tabbable`/`press-activation`
11
+ // are stateless traits; this OWNS a signal, so it is a CONTROLLER — same `(host, opts) => …` seam, but its
12
+ // return carries the `interacted` signal (+ the composed `userInvalid()` convenience) alongside `release()`.
13
+ //
14
+ // Lifetime (mirrors `press-activation`): the blur/change listeners ride `host.listen` → the connection
15
+ // `AbortSignal`, so they auto-remove on disconnect — zero residue, and re-armed on reconnect when
16
+ // `connected()` re-runs the controller. `release()` is an idempotent early-teardown guard the handlers
17
+ // check; like `press-activation` it stops the BEHAVIOUR (the listeners themselves die with the connection).
18
+ //
19
+ // Layering: `traits → reactive` (the `signal`/`ReadonlySignal` cell) and `traits → dom` (the host type) are
20
+ // both DOWNWARD imports (reactive L0 ← dom L1 ← traits L2), allowed by the import-layering trip-wire — the
21
+ // signal-OWNING controller needs the kernel that the stateless `tabbable`/`press-activation` traits do not.
22
+ import { signal } from "../reactive/index.js";
23
+ /**
24
+ * Track first-interaction TIMING for a form control's user-invalid treatment. Invoke from the control's
25
+ * `connected()` (where the connection scope + `AbortSignal` are live). Returns the `interacted` signal +
26
+ * the composed `userInvalid()` gate + an idempotent `release()`; the listeners auto-remove on disconnect
27
+ * and re-arm on reconnect.
28
+ */
29
+ export function trackUserInvalid(host, opts) {
30
+ let released = false;
31
+ const interacted = signal(false);
32
+ const onInteract = () => {
33
+ if (released)
34
+ return;
35
+ interacted.value = true; // first interaction flips it; later sets are Object.is no-ops (idempotent)
36
+ };
37
+ // `blur` does NOT bubble, so a host listener captures it — the capture phase reaches the host before a
38
+ // focusable descendant (e.g. the text-field editor part), catching its blur. `change` bubbles (native +
39
+ // the control's host-emitted `change`), so it needs no capture.
40
+ host.listen(host, 'blur', onInteract, { capture: true });
41
+ host.listen(host, 'change', onInteract);
42
+ return {
43
+ interacted,
44
+ userInvalid: () => interacted.value && opts.invalid(),
45
+ reset: () => {
46
+ interacted.value = false; // back to first-paint suppression; equal-set is an Object.is no-op (idempotent)
47
+ },
48
+ release: () => {
49
+ released = true;
50
+ },
51
+ };
52
+ }
@@ -0,0 +1,120 @@
1
+ import type { ReadonlySignal } from '../reactive/index.js';
2
+ import type { UIElement } from '../dom/index.js';
3
+ export interface ValueCodecOptions {
4
+ /** Parse display string → canonical form value (e.g. "1,234.56" → "1234.56"). null = parse error. */
5
+ parse(display: string): string | null;
6
+ /** Format canonical form value → display string (e.g. "1234.56" → "1,234.56"). */
7
+ format(value: string): string;
8
+ /** Validation message to expose when parse returns null; the control uses it in formValidity(). */
9
+ errorMessage: string;
10
+ }
11
+ export interface ValueCodecController {
12
+ /**
13
+ * The last successfully parsed canonical form value. The control MUST return `canonical.value` from
14
+ * its formValue() override when the codec is active — host.value holds the formatted display after
15
+ * blur, not the canonical submission value. Seeded from host.value at attach time.
16
+ */
17
+ readonly canonical: ReadonlySignal<string>;
18
+ /**
19
+ * True when the most recent blur attempt failed to parse. The control drives its formValidity()
20
+ * `customError` flag from this signal.
21
+ */
22
+ readonly hasError: ReadonlySignal<boolean>;
23
+ /** The error message string (mirrors opts.errorMessage). */
24
+ readonly errorMessage: string;
25
+ /**
26
+ * Force-update the canonical when the control changes the value programmatically outside the blur
27
+ * path (e.g. clear button, steppers). Also clears hasError, since the programmatic value is known-valid.
28
+ */
29
+ setCanonical(value: string): void;
30
+ /**
31
+ * Idempotent early teardown — stops the behaviour on the next event. Listeners auto-die with the
32
+ * connection scope regardless; call release() from the type-change effect cleanup or disconnected().
33
+ */
34
+ release(): void;
35
+ }
36
+ interface CodecHost extends UIElement {
37
+ value: string;
38
+ }
39
+ /**
40
+ * Attach the display-codec behaviour to a form control. Invoke from connected(), AFTER the control
41
+ * has created its [data-part="editor"] child (so the editor is present for listener registration).
42
+ *
43
+ * @param host The control — must have a `value` reactive prop and a `[data-part="editor"]` child.
44
+ * @param opts The codec: parse / format / errorMessage.
45
+ * @param typeSignal Optional per-type-run `AbortSignal` (from the type-effect's `AbortController`). When
46
+ * provided, both the focus (capture on host) and blur (on editor) listeners are tied
47
+ * to THIS signal, so the type-effect's `typeAc.abort()` removes them immediately on
48
+ * type change. Without the signal, they fall back to `host.listen()` (connection scope)
49
+ * and live until disconnect — which leaks N−1 inert listener pairs after N type-switches.
50
+ * Wave 5B date/time codecs MUST pass `typeAc.signal` on the same seam.
51
+ * @returns Controller: canonical + hasError + errorMessage + setCanonical + release.
52
+ */
53
+ export declare function valueCodec(host: CodecHost, opts: ValueCodecOptions, typeSignal?: AbortSignal): ValueCodecController;
54
+ /**
55
+ * Codec options for type=number. Parses by stripping grouping separators (accepts "1,234.56" → 1234.56);
56
+ * formats with Intl.NumberFormat (locale-aware grouping). Accepts both raw and formatted display strings.
57
+ *
58
+ * @param locale BCP 47 locale (default: runtime locale). Pin to 'en-US' in tests for determinism.
59
+ */
60
+ export declare function numberCodecOptions(locale?: string): ValueCodecOptions;
61
+ /**
62
+ * Codec options for type=currency. Parses like numberCodecOptions; formats with the per-currency
63
+ * fraction-digit count resolved from Intl (USD 2 · JPY 0 · BHD 3). The currency symbol is displayed
64
+ * as the control's leading adornment (not embedded in the formatted number string) — use
65
+ * `currencySymbol()` to resolve the glyph separately.
66
+ *
67
+ * @param currency ISO 4217 currency code (e.g. 'USD', 'JPY', 'BHD'). Falls back to 2 fraction digits
68
+ * on an invalid code (Intl throws a RangeError — caught defensively).
69
+ * @param locale BCP 47 locale (default: runtime locale).
70
+ */
71
+ export declare function currencyCodecOptions(currency: string, locale?: string): ValueCodecOptions;
72
+ /**
73
+ * Codec options for type=unit. Delegates to the numeric core today; the `unit` param is RESERVED for
74
+ * future per-unit precision (e.g. integer-only for °C vs. many decimals for currency ratios). The
75
+ * unit-specificity that matters today is the trailing adornment label, not the parse/format — so there
76
+ * is zero distinct numeric behaviour, and this is an honest reserved seam (ADR-0047 alt.2).
77
+ *
78
+ * @param unit CLDR unit identifier (reserved — no numeric effect today).
79
+ * @param locale BCP 47 locale (default: runtime locale).
80
+ */
81
+ export declare function unitCodecOptions(unit: string, locale?: string): ValueCodecOptions;
82
+ /**
83
+ * Extract the narrow currency symbol for the given ISO 4217 currency code (e.g. 'USD' → '$',
84
+ * 'JPY' → '¥', 'EUR' → '€'). Uses `currencyDisplay: 'narrowSymbol'` for the compact glyph.
85
+ * Falls back to the currency string itself on Intl error (invalid code or unsupported locale feature).
86
+ *
87
+ * @param currency ISO 4217 currency code.
88
+ * @param locale BCP 47 locale (default: runtime locale). Affects symbol variants in some locales.
89
+ */
90
+ export declare function currencySymbol(currency: string, locale?: string): string;
91
+ /**
92
+ * Resolve the short localized unit label for a CLDR unit identifier (e.g. 'kilogram' → 'kg',
93
+ * 'mile-per-hour' → 'mph'). Falls back to the raw `unit` string if Intl rejects it (an invalid CLDR
94
+ * id raises a RangeError — caught defensively). The trimmed `unit` part of `formatToParts(1)` is used
95
+ * to extract just the label without the numeric value.
96
+ *
97
+ * @param unit CLDR unit identifier (e.g. 'kilogram', 'mile-per-hour').
98
+ * @param locale BCP 47 locale (default: runtime locale).
99
+ */
100
+ export declare function unitLabel(unit: string, locale?: string): string;
101
+ /**
102
+ * Codec options for type=date. Canonical form: ISO `YYYY-MM-DD`. Format: localized medium
103
+ * date string via Intl.DateTimeFormat `{dateStyle:'medium'}`. Parse: ISO always accepted
104
+ * (timezone-safe local-time constructor); locale heuristic for numeric inputs (e.g. "03/04/2024"
105
+ * in en-US → 2024-03-04); last-resort new Date() for named-month forms; null on invalid.
106
+ * Error message: 'Please enter a valid date.'
107
+ *
108
+ * @param locale BCP 47 locale (default: runtime locale). Pin to 'en-US' in tests for determinism.
109
+ */
110
+ export declare function dateCodecOptions(locale?: string): ValueCodecOptions;
111
+ /**
112
+ * Codec options for type=time. Canonical form: `HH:MM` (24h, zero-padded). Format: localized
113
+ * short time string via Intl.DateTimeFormat `{timeStyle:'short'}`. Parse: accepts HH:MM (24h)
114
+ * and H:MM AM/PM (best-effort 12h → 24h conversion, handles en-US Intl output including
115
+ * narrow no-break space variants); null on invalid. Error message: 'Please enter a valid time.'
116
+ *
117
+ * @param locale BCP 47 locale (default: runtime locale). Pin to 'en-US' in tests for determinism.
118
+ */
119
+ export declare function timeCodecOptions(locale?: string): ValueCodecOptions;
120
+ export {};