@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.
- package/dist/base-styles.css +8 -0
- package/dist/component-styles.css +147 -0
- package/dist/controls/_base/index.d.ts +3 -0
- package/dist/controls/_base/index.js +9 -0
- package/dist/controls/_base/indicator-element.d.ts +132 -0
- package/dist/controls/_base/indicator-element.js +158 -0
- package/dist/controls/_base/listbox-element.d.ts +70 -0
- package/dist/controls/_base/listbox-element.js +74 -0
- package/dist/controls/_base/range-element.d.ts +120 -0
- package/dist/controls/_base/range-element.js +158 -0
- package/dist/controls/_surface/container-box.css +301 -0
- package/dist/controls/_surface/container.css +156 -0
- package/dist/controls/_token-surface/token-surface.d.ts +35 -0
- package/dist/controls/_token-surface/token-surface.js +97 -0
- package/dist/controls/attachment/attachment-meta.d.ts +8 -0
- package/dist/controls/attachment/attachment-meta.js +84 -0
- package/dist/controls/attachment/attachment.css +91 -0
- package/dist/controls/attachment/attachment.d.ts +40 -0
- package/dist/controls/attachment/attachment.js +70 -0
- package/dist/controls/avatar/avatar-initials.d.ts +4 -0
- package/dist/controls/avatar/avatar-initials.js +30 -0
- package/dist/controls/avatar/avatar.css +87 -0
- package/dist/controls/avatar/avatar.d.ts +40 -0
- package/dist/controls/avatar/avatar.js +77 -0
- package/dist/controls/badge/badge.css +175 -0
- package/dist/controls/badge/badge.d.ts +36 -0
- package/dist/controls/badge/badge.js +71 -0
- package/dist/controls/bar-chart/bar-chart.css +110 -0
- package/dist/controls/bar-chart/bar-chart.d.ts +26 -0
- package/dist/controls/bar-chart/bar-chart.js +80 -0
- package/dist/controls/bar-chart/bar-math.d.ts +31 -0
- package/dist/controls/bar-chart/bar-math.js +106 -0
- package/dist/controls/button/button.css +263 -0
- package/dist/controls/button/button.d.ts +61 -0
- package/dist/controls/button/button.js +115 -0
- package/dist/controls/calendar/calendar.css +495 -0
- package/dist/controls/calendar/calendar.d.ts +161 -0
- package/dist/controls/calendar/calendar.js +1004 -0
- package/dist/controls/card/card-content.d.ts +24 -0
- package/dist/controls/card/card-content.js +133 -0
- package/dist/controls/card/card-footer.d.ts +3 -0
- package/dist/controls/card/card-footer.js +16 -0
- package/dist/controls/card/card-header.d.ts +3 -0
- package/dist/controls/card/card-header.js +18 -0
- package/dist/controls/card/card.css +445 -0
- package/dist/controls/card/card.d.ts +51 -0
- package/dist/controls/card/card.js +61 -0
- package/dist/controls/checkbox/checkbox.css +204 -0
- package/dist/controls/checkbox/checkbox.d.ts +114 -0
- package/dist/controls/checkbox/checkbox.js +130 -0
- package/dist/controls/code/code.css +72 -0
- package/dist/controls/code/code.d.ts +23 -0
- package/dist/controls/code/code.js +40 -0
- package/dist/controls/color-picker/color-picker.css +209 -0
- package/dist/controls/color-picker/color-picker.d.ts +84 -0
- package/dist/controls/color-picker/color-picker.js +545 -0
- package/dist/controls/color-picker/color.d.ts +40 -0
- package/dist/controls/color-picker/color.js +159 -0
- package/dist/controls/column/column.css +151 -0
- package/dist/controls/column/column.d.ts +107 -0
- package/dist/controls/column/column.js +57 -0
- package/dist/controls/combo-box/combo-box.css +397 -0
- package/dist/controls/combo-box/combo-box.d.ts +133 -0
- package/dist/controls/combo-box/combo-box.js +680 -0
- package/dist/controls/command-modal/command-modal.css +226 -0
- package/dist/controls/command-modal/command-modal.d.ts +42 -0
- package/dist/controls/command-modal/command-modal.js +315 -0
- package/dist/controls/disclosure/disclosure.css +162 -0
- package/dist/controls/disclosure/disclosure.d.ts +37 -0
- package/dist/controls/disclosure/disclosure.js +207 -0
- package/dist/controls/field/field.css +140 -0
- package/dist/controls/field/field.d.ts +27 -0
- package/dist/controls/field/field.js +228 -0
- package/dist/controls/form-provider/form-provider.css +40 -0
- package/dist/controls/form-provider/form-provider.d.ts +42 -0
- package/dist/controls/form-provider/form-provider.js +105 -0
- package/dist/controls/grid/grid.css +72 -0
- package/dist/controls/grid/grid.d.ts +60 -0
- package/dist/controls/grid/grid.js +56 -0
- package/dist/controls/icon/icon.css +37 -0
- package/dist/controls/icon/icon.d.ts +25 -0
- package/dist/controls/icon/icon.js +55 -0
- package/dist/controls/index.d.ts +57 -0
- package/dist/controls/index.js +136 -0
- package/dist/controls/ladder/ladder.css +89 -0
- package/dist/controls/ladder/ladder.d.ts +27 -0
- package/dist/controls/ladder/ladder.js +78 -0
- package/dist/controls/list/list.css +118 -0
- package/dist/controls/list/list.d.ts +84 -0
- package/dist/controls/list/list.js +48 -0
- package/dist/controls/menu/menu.css +195 -0
- package/dist/controls/menu/menu.d.ts +44 -0
- package/dist/controls/menu/menu.js +248 -0
- package/dist/controls/modal/modal.css +99 -0
- package/dist/controls/modal/modal.d.ts +61 -0
- package/dist/controls/modal/modal.js +155 -0
- package/dist/controls/popover/popover.css +83 -0
- package/dist/controls/popover/popover.d.ts +44 -0
- package/dist/controls/popover/popover.js +164 -0
- package/dist/controls/progress/progress.css +106 -0
- package/dist/controls/progress/progress.d.ts +28 -0
- package/dist/controls/progress/progress.js +80 -0
- package/dist/controls/radio/radio-group.css +84 -0
- package/dist/controls/radio/radio-group.d.ts +152 -0
- package/dist/controls/radio/radio-group.js +350 -0
- package/dist/controls/radio/radio.css +219 -0
- package/dist/controls/radio/radio.d.ts +19 -0
- package/dist/controls/radio/radio.js +75 -0
- package/dist/controls/ramp/ramp.css +75 -0
- package/dist/controls/ramp/ramp.d.ts +29 -0
- package/dist/controls/ramp/ramp.js +72 -0
- package/dist/controls/row/row.css +152 -0
- package/dist/controls/row/row.d.ts +95 -0
- package/dist/controls/row/row.js +45 -0
- package/dist/controls/segment/segment.css +66 -0
- package/dist/controls/segment/segment.d.ts +3 -0
- package/dist/controls/segment/segment.js +26 -0
- package/dist/controls/segmented-control/segmented-control.css +189 -0
- package/dist/controls/segmented-control/segmented-control.d.ts +20 -0
- package/dist/controls/segmented-control/segmented-control.js +64 -0
- package/dist/controls/select/select.css +467 -0
- package/dist/controls/select/select.d.ts +147 -0
- package/dist/controls/select/select.js +538 -0
- package/dist/controls/slider/slider.css +231 -0
- package/dist/controls/slider/slider.d.ts +111 -0
- package/dist/controls/slider/slider.js +60 -0
- package/dist/controls/slider-multi/slider-multi.css +197 -0
- package/dist/controls/slider-multi/slider-multi.d.ts +149 -0
- package/dist/controls/slider-multi/slider-multi.js +380 -0
- package/dist/controls/sparkline/sparkline-math.d.ts +23 -0
- package/dist/controls/sparkline/sparkline-math.js +79 -0
- package/dist/controls/sparkline/sparkline.css +61 -0
- package/dist/controls/sparkline/sparkline.d.ts +27 -0
- package/dist/controls/sparkline/sparkline.js +74 -0
- package/dist/controls/split/constrain.d.ts +44 -0
- package/dist/controls/split/constrain.js +126 -0
- package/dist/controls/split/split-pane.css +63 -0
- package/dist/controls/split/split-pane.d.ts +49 -0
- package/dist/controls/split/split-pane.js +42 -0
- package/dist/controls/split/split.css +127 -0
- package/dist/controls/split/split.d.ts +42 -0
- package/dist/controls/split/split.js +364 -0
- package/dist/controls/stat/stat-model.d.ts +22 -0
- package/dist/controls/stat/stat-model.js +69 -0
- package/dist/controls/stat/stat.css +101 -0
- package/dist/controls/stat/stat.d.ts +30 -0
- package/dist/controls/stat/stat.js +73 -0
- package/dist/controls/status-stream/status-stream.css +151 -0
- package/dist/controls/status-stream/status-stream.d.ts +108 -0
- package/dist/controls/status-stream/status-stream.js +417 -0
- package/dist/controls/swatch/swatch.css +66 -0
- package/dist/controls/swatch/swatch.d.ts +28 -0
- package/dist/controls/swatch/swatch.js +74 -0
- package/dist/controls/swiper/swiper-item.css +17 -0
- package/dist/controls/swiper/swiper-item.d.ts +31 -0
- package/dist/controls/swiper/swiper-item.js +47 -0
- package/dist/controls/swiper/swiper-label.css +10 -0
- package/dist/controls/swiper/swiper-label.d.ts +4 -0
- package/dist/controls/swiper/swiper-label.js +20 -0
- package/dist/controls/swiper/swiper-paddles.css +26 -0
- package/dist/controls/swiper/swiper-paddles.d.ts +10 -0
- package/dist/controls/swiper/swiper-paddles.js +62 -0
- package/dist/controls/swiper/swiper-pagination.css +60 -0
- package/dist/controls/swiper/swiper-pagination.d.ts +31 -0
- package/dist/controls/swiper/swiper-pagination.js +78 -0
- package/dist/controls/swiper/swiper.css +153 -0
- package/dist/controls/swiper/swiper.d.ts +158 -0
- package/dist/controls/swiper/swiper.js +659 -0
- package/dist/controls/switch/switch.css +194 -0
- package/dist/controls/switch/switch.d.ts +5 -0
- package/dist/controls/switch/switch.js +18 -0
- package/dist/controls/table/table-model.d.ts +43 -0
- package/dist/controls/table/table-model.js +124 -0
- package/dist/controls/table/table.css +90 -0
- package/dist/controls/table/table.d.ts +29 -0
- package/dist/controls/table/table.js +178 -0
- package/dist/controls/tabs/tab-panel.d.ts +10 -0
- package/dist/controls/tabs/tab-panel.js +89 -0
- package/dist/controls/tabs/tab.d.ts +26 -0
- package/dist/controls/tabs/tab.js +67 -0
- package/dist/controls/tabs/tabs.css +181 -0
- package/dist/controls/tabs/tabs.d.ts +61 -0
- package/dist/controls/tabs/tabs.js +176 -0
- package/dist/controls/text/href.d.ts +26 -0
- package/dist/controls/text/href.js +48 -0
- package/dist/controls/text/text.css +328 -0
- package/dist/controls/text/text.d.ts +85 -0
- package/dist/controls/text/text.js +273 -0
- package/dist/controls/text-field/text-field.css +498 -0
- package/dist/controls/text-field/text-field.d.ts +226 -0
- package/dist/controls/text-field/text-field.js +905 -0
- package/dist/controls/textarea/textarea.css +251 -0
- package/dist/controls/textarea/textarea.d.ts +136 -0
- package/dist/controls/textarea/textarea.js +312 -0
- package/dist/controls/theme-provider/theme-provider.css +48 -0
- package/dist/controls/theme-provider/theme-provider.d.ts +59 -0
- package/dist/controls/theme-provider/theme-provider.js +40 -0
- package/dist/controls/timeline/timeline.css +33 -0
- package/dist/controls/timeline/timeline.d.ts +39 -0
- package/dist/controls/timeline/timeline.js +52 -0
- package/dist/controls/timeline-item/timeline-item.css +495 -0
- package/dist/controls/timeline-item/timeline-item.d.ts +79 -0
- package/dist/controls/timeline-item/timeline-item.js +360 -0
- package/dist/controls/toast/toast-region.css +56 -0
- package/dist/controls/toast/toast-region.d.ts +20 -0
- package/dist/controls/toast/toast-region.js +113 -0
- package/dist/controls/toast/toast.css +96 -0
- package/dist/controls/toast/toast.d.ts +28 -0
- package/dist/controls/toast/toast.js +191 -0
- package/dist/controls/toolbar/toolbar.css +125 -0
- package/dist/controls/toolbar/toolbar.d.ts +108 -0
- package/dist/controls/toolbar/toolbar.js +68 -0
- package/dist/controls/tooltip/tooltip.css +80 -0
- package/dist/controls/tooltip/tooltip.d.ts +57 -0
- package/dist/controls/tooltip/tooltip.js +205 -0
- package/dist/descriptor/component-descriptor.d.ts +112 -0
- package/dist/descriptor/component-descriptor.js +492 -0
- package/dist/descriptor/index.d.ts +1 -0
- package/dist/descriptor/index.js +5 -0
- package/dist/dom/container.d.ts +45 -0
- package/dist/dom/container.js +60 -0
- package/dist/dom/element.d.ts +98 -0
- package/dist/dom/element.js +221 -0
- package/dist/dom/form.d.ts +240 -0
- package/dist/dom/form.js +399 -0
- package/dist/dom/index.d.ts +10 -0
- package/dist/dom/index.js +31 -0
- package/dist/dom/props.d.ts +56 -0
- package/dist/dom/props.js +231 -0
- package/dist/dom/repeat.d.ts +10 -0
- package/dist/dom/repeat.js +202 -0
- package/dist/dom/template.d.ts +161 -0
- package/dist/dom/template.js +634 -0
- package/dist/dom/watch.d.ts +11 -0
- package/dist/dom/watch.js +82 -0
- package/dist/foundation-styles.css +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/reactive/graph.d.ts +65 -0
- package/dist/reactive/graph.js +310 -0
- package/dist/reactive/index.d.ts +3 -0
- package/dist/reactive/index.js +4 -0
- package/dist/reactive/scheduler.d.ts +13 -0
- package/dist/reactive/scheduler.js +49 -0
- package/dist/traits/area-drag.d.ts +12 -0
- package/dist/traits/area-drag.js +70 -0
- package/dist/traits/form-registry.d.ts +36 -0
- package/dist/traits/form-registry.js +108 -0
- package/dist/traits/index.d.ts +24 -0
- package/dist/traits/index.js +19 -0
- package/dist/traits/overlay.d.ts +50 -0
- package/dist/traits/overlay.js +277 -0
- package/dist/traits/pane-resize.d.ts +26 -0
- package/dist/traits/pane-resize.js +103 -0
- package/dist/traits/press-activation.d.ts +11 -0
- package/dist/traits/press-activation.js +55 -0
- package/dist/traits/roving-focus.d.ts +52 -0
- package/dist/traits/roving-focus.js +214 -0
- package/dist/traits/scroll-fade.d.ts +28 -0
- package/dist/traits/scroll-fade.js +120 -0
- package/dist/traits/selection-commit.d.ts +30 -0
- package/dist/traits/selection-commit.js +146 -0
- package/dist/traits/tabbable.d.ts +12 -0
- package/dist/traits/tabbable.js +59 -0
- package/dist/traits/track-user-invalid.d.ts +36 -0
- package/dist/traits/track-user-invalid.js +52 -0
- package/dist/traits/value-codec.d.ts +120 -0
- package/dist/traits/value-codec.js +497 -0
- package/dist/traits/value-drag.d.ts +18 -0
- package/dist/traits/value-drag.js +84 -0
- package/package.json +263 -0
package/dist/dom/form.js
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
// form.ts — `UIFormElement`, the FACE form-associated base (ADR-0013; decomp g4-g6 slice s1).
|
|
2
|
+
//
|
|
3
|
+
// The base every value-carrying FACE control (`ui-text-field`, `ui-checkbox`, `ui-switch`, `ui-select`,
|
|
4
|
+
// `ui-listbox`) extends. It turns a `UIElement` into a form participant: `static formAssociated = true`
|
|
5
|
+
// activates the platform's form lifecycle, and the inherited `ElementInternals` — acquired ONCE in the
|
|
6
|
+
// `UIElement` constructor, reused here and NEVER re-`attachInternals()`d (a second acquisition throws) —
|
|
7
|
+
// carries the value + validity to the owning `<form>`. Two scope-owned effects publish the control's
|
|
8
|
+
// `formValue()` / `formValidity()` hooks to `internals.setFormValue` / `setValidity`, so a control declares
|
|
9
|
+
// WHAT its value + validity are and the base wires the reactive plumbing. The base owns the three universal
|
|
10
|
+
// form attributes via the SPREADABLE `formProps` (props.ts has no static-props prototype merge — the
|
|
11
|
+
// subclass folds them into its own `static props`); it deliberately does NOT own a typed `value` — the
|
|
12
|
+
// value's type + codec are the subclass's (string for text-field; a `checked` boolean for checkbox; …).
|
|
13
|
+
//
|
|
14
|
+
// Super-wrapped wiring keeps subclass authoring identical to a plain `UIElement` control: a subclass still
|
|
15
|
+
// overrides the clean, no-`super` `connected()` hook; this base wraps `connectedCallback` to add its own
|
|
16
|
+
// scope-owned effects after `super` has opened the scope.
|
|
17
|
+
//
|
|
18
|
+
// G7 growth (ADR-0050 + ADR-0051, the fleet's first context/provider primitive + its labelling seam):
|
|
19
|
+
// • ADR-0050 — at the END of `connectedCallback` (scope + effects all live) the base dispatches the
|
|
20
|
+
// `ui-form-connect` protocol event: a composed, bubbling, NOT-cancelable event (`dispatchEvent`, not
|
|
21
|
+
// `this.emit` — `emit`'s cancelable/public shape is for consumer events) whose detail is the control's
|
|
22
|
+
// PUBLIC reactive surface, closing over `formValue()`/the merged validity/`formUserInvalid()`, plus the
|
|
23
|
+
// live connection `AbortSignal` (the teardown handle a registry subscribes `abort` on — a disconnected
|
|
24
|
+
// control cannot dispatch, so deregistration rides the handle, never a second event). `formResetCallback`
|
|
25
|
+
// grows a matching `ui-form-reset` dispatch (no detail — the target IS the resetting control).
|
|
26
|
+
// • ADR-0051 — a small signal-backed field-labelling seam: `setFieldLabelling`/`fieldLabelling` are the
|
|
27
|
+
// handoff surface a `ui-field` calls (elements, not id strings — the internals path consumes elements);
|
|
28
|
+
// a base scope-owned effect forwards the current labelling through the overridable `applyFieldLabelling`
|
|
29
|
+
// hook, whose GUARDED default reflects via `ElementInternals` ARIA-element accessors ONLY when
|
|
30
|
+
// `internals.role` is set (feature-detected — the tabs `reflectAriaElements` precedent) — a part-role
|
|
31
|
+
// control (text-field) overrides it. `formUserInvalid()` is the parallel hook feeding the connect
|
|
32
|
+
// detail's `userInvalid` read; a control composing `trackUserInvalid` overrides it with the tracker gate.
|
|
33
|
+
// Both are additive: provider-less/field-less usage shows ZERO behavioral drift (the events bubble to
|
|
34
|
+
// nobody; `fieldLabelling` stays `null`, so `applyFieldLabelling`'s default no-op — jsdom has no
|
|
35
|
+
// `internals.role` reflection surface either — never fires a visible change).
|
|
36
|
+
// • F1 (review-caught upgrade-order hole): the one connect dispatch above assumes a listening
|
|
37
|
+
// provider/field is ALREADY live — true for insertion order, but NOT custom-element UPGRADE order
|
|
38
|
+
// (pre-existing DOM whose control module upgrades before the provider/field module does ⇒ the dispatch
|
|
39
|
+
// fires into the void). `announceFormConnect()` (public) re-dispatches the SAME event with a fresh
|
|
40
|
+
// detail on demand — a provider/field's one-shot catch-up scan calls it per already-connected control
|
|
41
|
+
// it discovers late. Blanket-safe: the registry/field acceptance guards are already idempotent.
|
|
42
|
+
// • Nested-member guard (defect repair — A2UI patterns-page-caught): a `UIFormElement` never announces
|
|
43
|
+
// when it has a `UIFormElement` ANCESTOR — it is an INTERNAL PART (e.g. text-field type=date's own
|
|
44
|
+
// `<ui-calendar>`; a radio inside its radio-group), never a registry/field member. Both dispatch sites
|
|
45
|
+
// route through the one `#dispatchFormConnect` choke point so the guard can never be bypassed by one
|
|
46
|
+
// of the two. See `#dispatchFormConnect`'s own doc for the write-loop this closes.
|
|
47
|
+
//
|
|
48
|
+
// Imports only `../reactive` (the kernel) + same-layer `./element.ts` / `./props.ts` (the layering holds).
|
|
49
|
+
// `ElementInternals` / `ValidityState` / `ValidityStateFlags` / `HTMLFormElement` / `HTMLElement` / `File` /
|
|
50
|
+
// `FormData` / `AbortSignal` / `CustomEvent` are ambient DOM globals, not imports.
|
|
51
|
+
var _a;
|
|
52
|
+
import { signal } from "../reactive/index.js";
|
|
53
|
+
import { UIElement } from "./element.js";
|
|
54
|
+
import { prop } from "./props.js";
|
|
55
|
+
// ── ADR-0050 — the protocol events ──────────────────────────────────────────────
|
|
56
|
+
// The `ui-` namespace: base↔provider/field plumbing, OUTSIDE the public component-event vocab
|
|
57
|
+
// (change·input·select·open·close·toggle). Both composed + bubbling, NOT cancelable (dispatched directly
|
|
58
|
+
// with `dispatchEvent`, not `this.emit` — `emit`'s cancelable/public shape is for consumer events).
|
|
59
|
+
/** Fired once, at the end of `connectedCallback` — the control announcing full connection to the nearest
|
|
60
|
+
* provider/field ancestor. */
|
|
61
|
+
export const FORM_CONNECT_EVENT = 'ui-form-connect';
|
|
62
|
+
/** Fired from `formResetCallback`, after `formReset()` — detail: none (the target IS the resetting
|
|
63
|
+
* control). Covers BOTH reset paths: native `form.reset()` and a provider's direct `formResetCallback()` call. */
|
|
64
|
+
export const FORM_RESET_EVENT = 'ui-form-reset';
|
|
65
|
+
// Reflect an ARIA element-list relation through `internals` (feature-detected — the reflection accessors
|
|
66
|
+
// landed in modern Chromium/WebKit but NOT jsdom, so this is a no-op under the jsdom inner loop and live in
|
|
67
|
+
// the real engines, verified by the s11 browser smokes; the tabs `reflectAriaElements` precedent — tab.ts /
|
|
68
|
+
// tab-panel.ts carry their own local copies too, the folder ships no shared module for this one-liner).
|
|
69
|
+
// `null` clears the relation.
|
|
70
|
+
function reflectAriaElements(internals, name, elements) {
|
|
71
|
+
if (name in internals)
|
|
72
|
+
internals[name] = elements;
|
|
73
|
+
}
|
|
74
|
+
// The three universal form attributes every value-carrying control shares (clause 2). All three REFLECT:
|
|
75
|
+
// `name` reflects for native parity (the HTML `name` IDL attribute reflects its content attribute) AND
|
|
76
|
+
// because FACE submission keys the entry by the `name` CONTENT attribute — so the imperative `el.name = …`
|
|
77
|
+
// path must update the attribute or the value submits unkeyed; `disabled` / `required` reflect so
|
|
78
|
+
// attribute-selector styling (`[disabled]` / `[required]`) applies to JS-set values too. `value` is ABSENT —
|
|
79
|
+
// its type/codec belong to the subclass. A subclass spreads this into its own `static props`:
|
|
80
|
+
// `static props = { ...UIFormElement.formProps, value: prop.string(), … }`.
|
|
81
|
+
const formProps = {
|
|
82
|
+
name: { ...prop.string(), reflect: true },
|
|
83
|
+
disabled: { ...prop.boolean(), reflect: true },
|
|
84
|
+
required: { ...prop.boolean(), reflect: true },
|
|
85
|
+
};
|
|
86
|
+
export class UIFormElement extends UIElement {
|
|
87
|
+
/** Activates platform form association — `setFormValue`/`setValidity` + the form lifecycle callbacks (clause 1). */
|
|
88
|
+
static formAssociated = true;
|
|
89
|
+
/** The spreadable universal form attributes — the no-static-props-inheritance workaround (clause 2). */
|
|
90
|
+
static formProps = formProps;
|
|
91
|
+
// Fieldset / `<form disabled>` state, pushed in by `formDisabledCallback`. A signal so `effectiveDisabled()`
|
|
92
|
+
// is reactive — a subclass effect reading it re-runs when an ancestor fieldset toggles disabled.
|
|
93
|
+
#formDisabled = signal(false);
|
|
94
|
+
// Renderer-driven custom validity (ADR-0029 §5 — the A2UI `checks` controller seam). A signal so the
|
|
95
|
+
// merged validity effect below re-runs when the renderer calls `setCustomValidity` (live validation).
|
|
96
|
+
// Empty string = no custom message = no custom-error contribution (native-parity clear, like `input.setCustomValidity('')`).
|
|
97
|
+
#customValidity = signal('');
|
|
98
|
+
// ADR-0051 — the field-labelling handoff, written by `setFieldLabelling`, read by the base's forwarding
|
|
99
|
+
// effect (below) + `fieldLabelling`. `null` = unassociated (the default; every existing control's behavior).
|
|
100
|
+
#fieldLabelling = signal(null);
|
|
101
|
+
/**
|
|
102
|
+
* Super-wrapped wiring (clause 4). `super.connectedCallback()` opens the connection scope + AbortController,
|
|
103
|
+
* runs the no-`super` `connected()` hook, and installs the render effect; the scope is now live, so the
|
|
104
|
+
* effects below are scope-owned and disposed on disconnect (zero residue). They install AFTER the render
|
|
105
|
+
* effect — harmless: the form family's `render()` is the inherited void. The subclass keeps the clean,
|
|
106
|
+
* no-`super` `connected()` hook — it never sees this wrapper.
|
|
107
|
+
*/
|
|
108
|
+
connectedCallback() {
|
|
109
|
+
super.connectedCallback();
|
|
110
|
+
// Publish the control's value to the form, reactively: reading `formValue()` tracks whatever signals the
|
|
111
|
+
// subclass's hook reads (its `value` prop), so a value change re-runs ONLY this effect → one setFormValue.
|
|
112
|
+
this.effect(() => {
|
|
113
|
+
this.internals.setFormValue(this.formValue());
|
|
114
|
+
});
|
|
115
|
+
// Publish the control's MERGED validity verdict to the platform, reactively (same tracking discipline;
|
|
116
|
+
// `#mergedValidity` is the single source — also fed to the ADR-0050 connect detail below).
|
|
117
|
+
this.effect(() => {
|
|
118
|
+
this.#applyValidity(this.#mergedValidity());
|
|
119
|
+
});
|
|
120
|
+
// ADR-0051 — forward the current field labelling through the overridable hook. Base scope-owned, so
|
|
121
|
+
// association (a `ui-field`'s `setFieldLabelling` call) / clear re-apply reactively. Installed BEFORE
|
|
122
|
+
// the connect dispatch below (LLD-C2) — nothing can call `setFieldLabelling` before this control has
|
|
123
|
+
// even announced itself, so there is no real race, but the ordering is the documented contract.
|
|
124
|
+
this.effect(() => {
|
|
125
|
+
this.applyFieldLabelling(this.#fieldLabelling.value);
|
|
126
|
+
});
|
|
127
|
+
// ADR-0050 — announce full connection to the nearest provider/field ancestor. Dispatched LAST — the
|
|
128
|
+
// scope is open, `connected()` has run, and all three effects above are installed, so a listener sees
|
|
129
|
+
// a fully-wired control.
|
|
130
|
+
this.#dispatchFormConnect();
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The ONE dispatch choke point — both the initial connect dispatch above and `announceFormConnect`
|
|
134
|
+
* (F1, below) route through this, so the nested-member guard (bug repair, below) and the dispatch shape
|
|
135
|
+
* itself never drift apart between the two call sites. `dispatchEvent` directly (not `this.emit`):
|
|
136
|
+
* composed + bubbling, NOT cancelable — this is base↔provider/field plumbing, not a consumer-facing
|
|
137
|
+
* semantic event.
|
|
138
|
+
*
|
|
139
|
+
* GUARD (defect repair — a nested `UIFormElement` is an INTERNAL PART, never a registry/field MEMBER):
|
|
140
|
+
* native form controls do not nest as members of each other (there is no such thing as an `<input>`
|
|
141
|
+
* inside an `<input>`'s own submission graph) — ADR-0050's intent was always to discover form-owning
|
|
142
|
+
* MEMBERS, not every `UIFormElement`-shaped node regardless of where it sits. `ui-text-field type=date`
|
|
143
|
+
* creates its OWN internal `<ui-calendar>` (itself a `UIFormElement`, for its own unrelated internal
|
|
144
|
+
* value-picking UI) and appends it inside itself — undetected, a wrapping `ui-form-provider`'s catch-up
|
|
145
|
+
* scan (`querySelectorAll('*')` + `instanceof`) discovers the calendar as a SECOND, phantom member,
|
|
146
|
+
* whose aggregate reads feed back into the field's own effects → an unbounded write-loop (the kernel's
|
|
147
|
+
* ~100-wave budget throws). The fix: a control with a `UIFormElement` ANCESTOR never announces at all —
|
|
148
|
+
* neither the initial dispatch nor a catch-up re-announce reaches a listener, so it can never register
|
|
149
|
+
* anywhere. `#hasFormElementAncestor` walks `parentElement` (O(depth), stops at the first hit) — cheap,
|
|
150
|
+
* connect-time-only, no observer. This also correctly covers `ui-radio` nested in `ui-radio-group`
|
|
151
|
+
* (`UIRadioGroupElement extends UIFormElement`) — the group is the aggregation owner of its own radios,
|
|
152
|
+
* which are internal parts of ITS submission graph the same way the calendar is the text-field's; each
|
|
153
|
+
* radio's own `formValidity()` was always trivially valid, so no outer aggregate observably changes.
|
|
154
|
+
*/
|
|
155
|
+
#dispatchFormConnect() {
|
|
156
|
+
if (this.#hasFormElementAncestor())
|
|
157
|
+
return;
|
|
158
|
+
this.dispatchEvent(new CustomEvent(FORM_CONNECT_EVENT, {
|
|
159
|
+
bubbles: true,
|
|
160
|
+
composed: true,
|
|
161
|
+
detail: this.#connectDetail(),
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
/** Walk the parentElement chain for the nearest `UIFormElement` ancestor (O(depth), stops at the first
|
|
165
|
+
* hit) — the nested-member guard `#dispatchFormConnect` applies. Ancestors only: `this` itself is
|
|
166
|
+
* always a `UIFormElement` and is deliberately excluded from the walk. */
|
|
167
|
+
#hasFormElementAncestor() {
|
|
168
|
+
let node = this.parentElement;
|
|
169
|
+
while (node !== null) {
|
|
170
|
+
if (node instanceof _a)
|
|
171
|
+
return true;
|
|
172
|
+
node = node.parentElement;
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Mint a FRESH `FormConnectDetail` off the CURRENT connection signal — the single source
|
|
178
|
+
* `#dispatchFormConnect` constructs from (both the initial dispatch and `announceFormConnect` route
|
|
179
|
+
* through it), so the two never drift apart. Only meaningful while connected (`connectionSignal` is
|
|
180
|
+
* live); the non-null assertion holds because both call sites already guard on `connectionSignal !==
|
|
181
|
+
* null` before reaching here (not `isConnected` — see `announceFormConnect`'s doc for why that guard is
|
|
182
|
+
* the wrong one).
|
|
183
|
+
*/
|
|
184
|
+
#connectDetail() {
|
|
185
|
+
return {
|
|
186
|
+
control: this,
|
|
187
|
+
signal: this.connectionSignal,
|
|
188
|
+
value: () => this.formValue(),
|
|
189
|
+
validity: () => this.#mergedValidity(),
|
|
190
|
+
userInvalid: () => this.formUserInvalid(),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Re-announce full connection (F1 — the review-caught upgrade-order hole). "Ancestors connect before
|
|
195
|
+
* descendants" holds for INSERTION order but NOT custom-element UPGRADE order: pre-existing DOM whose
|
|
196
|
+
* control module registers/upgrades before the provider/field module does means the ONE connect dispatch
|
|
197
|
+
* in `connectedCallback` fires into the void — no listener is live yet — and registration would be lost
|
|
198
|
+
* forever without a catch-up path. Public so a provider/field's one-shot catch-up scan (built at their
|
|
199
|
+
* own build slice) can call it on every already-connected `UIFormElement` it discovers late, re-dispatching
|
|
200
|
+
* `ui-form-connect` with a FRESH detail (the same minting `#connectDetail()` uses, so a reconnect since
|
|
201
|
+
* the original dispatch is reflected — a NEW connection signal, current prop values).
|
|
202
|
+
*
|
|
203
|
+
* Guard: `connectionSignal === null`, NOT `isConnected` (a real bug an s9 repro caught — the bulk-insert
|
|
204
|
+
* hazard). `isConnected` reflects DOM tree membership, set the instant a subtree is spliced in; a custom
|
|
205
|
+
* element's OWN `connectedCallback` (which opens the connection scope + mints `#ac`) fires ASYNC to that,
|
|
206
|
+
* in a separate reaction-queue pass, ancestor-first. So on a bulk insert of a pre-built offline subtree
|
|
207
|
+
* (assemble, then one `append()` — the house test idiom), every descendant is already `isConnected` the
|
|
208
|
+
* moment the PARENT's `connectedCallback` runs (and could catch-up-scan its already-`isConnected`
|
|
209
|
+
* children) even though THEIR OWN `connectedCallback` — and so `connectionSignal` — hasn't fired yet.
|
|
210
|
+
* NOT a jsdom accommodation: atomic-subtree-connect ordering (ancestor-`connectedCallback`-before-
|
|
211
|
+
* descendant, all descendants already `isConnected` throughout) is SPEC-MANDATED and holds identically in
|
|
212
|
+
* every real engine (s8 confirmed with a plain-HTMLElement probe) — this is a real-browser correctness
|
|
213
|
+
* fix, not a test-environment workaround. `connectionSignal === null` is the correct predicate for "has
|
|
214
|
+
* this control's OWN connect wiring run yet": it is null before that control's first `connectedCallback`
|
|
215
|
+
* (nothing to announce — that `connectedCallback` will dispatch for itself momentarily) AND after its
|
|
216
|
+
* `disconnectedCallback` (`#ac` is nulled there too, element.ts) — one guard covers both the not-yet-mine
|
|
217
|
+
* and no-longer-mine legs; `isConnected` is redundant once this holds and is dropped to avoid it masking
|
|
218
|
+
* the real invariant.
|
|
219
|
+
*
|
|
220
|
+
* This guard closes TWO bugs, not one. The obvious one: the pre-fix crash (a `null` `signal` in the
|
|
221
|
+
* minted detail, `detail.signal.aborted` throwing in a registry/field listener). The SILENT one (s8-found):
|
|
222
|
+
* in the same-cascade race, `ui-field`'s association still applies `aria-labelledby` (the labelling signal
|
|
223
|
+
* write does not ride the connection scope), but the affected control's OWN `connectedCallback` — and so
|
|
224
|
+
* its abort listener — had not yet run, so `this.listen(detail.signal, 'abort', …)` in `#associate` would
|
|
225
|
+
* have wired against a signal that was never live, and a later removal would never dissociate (stale
|
|
226
|
+
* labelling, forever). This guard skips the same-cascade announce entirely and lets the descendant's OWN
|
|
227
|
+
* end-of-`connectedCallback` dispatch (below the guard here — see that dispatch's own signal, always live
|
|
228
|
+
* by construction) carry the association instead, once the field/provider is already listening — so the
|
|
229
|
+
* abort listener installs correctly, every time.
|
|
230
|
+
*
|
|
231
|
+
* Also routes through `#dispatchFormConnect`'s nested-member guard — a catch-up scan's
|
|
232
|
+
* `querySelectorAll('*')` finds every `UIFormElement` descendant indiscriminately (it cannot itself
|
|
233
|
+
* distinguish an internal part like a text-field's calendar from a real member), so the guard MUST live
|
|
234
|
+
* here too, not only on the initial dispatch, or a late-discovered internal part would still register.
|
|
235
|
+
*/
|
|
236
|
+
announceFormConnect() {
|
|
237
|
+
if (this.connectionSignal === null)
|
|
238
|
+
return;
|
|
239
|
+
this.#dispatchFormConnect();
|
|
240
|
+
}
|
|
241
|
+
/** Map a `ValidityResult` onto `internals.setValidity`: valid clears it; invalid sets flags + message (+ anchor). */
|
|
242
|
+
#applyValidity(result) {
|
|
243
|
+
if (result.valid)
|
|
244
|
+
this.internals.setValidity({});
|
|
245
|
+
else
|
|
246
|
+
this.internals.setValidity(result.flags, result.message, result.anchor);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* The MERGED validity verdict — `formValidity()` ⊕ the renderer-driven `#customValidity` signal (ADR-0029
|
|
250
|
+
* §5): native wins when already invalid; a non-empty custom message produces `{valid:false,
|
|
251
|
+
* flags:{customError:true}, message}`; both empty → valid (clear). The SINGLE source feeding both the
|
|
252
|
+
* internals-publishing effect above and `FormConnectDetail.validity` (ADR-0050 — extracted so the two
|
|
253
|
+
* never drift apart).
|
|
254
|
+
*/
|
|
255
|
+
#mergedValidity() {
|
|
256
|
+
const native = this.formValidity();
|
|
257
|
+
const custom = this.#customValidity.value;
|
|
258
|
+
if (!native.valid)
|
|
259
|
+
return native; // native wins (e.g. valueMissing overrides the custom message)
|
|
260
|
+
if (custom !== '')
|
|
261
|
+
return { valid: false, flags: { customError: true }, message: custom };
|
|
262
|
+
return native; // both valid: clear
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Set a renderer-driven custom validity message (ADR-0029 §5 — the A2UI `checks` seam, native-parity name).
|
|
266
|
+
* An empty string clears the custom contribution (native parity: `el.setCustomValidity('')` = valid).
|
|
267
|
+
* A non-empty message drives `{valid:false, flags:{customError:true}, message}` unless `formValidity()`
|
|
268
|
+
* is already invalid (native wins — e.g. `valueMissing` takes the display slot over a custom message).
|
|
269
|
+
* The reactive effect in `connectedCallback` picks up the `#customValidity` signal change and re-publishes
|
|
270
|
+
* through `#applyValidity` without any direct caller → `internals.setValidity` call here.
|
|
271
|
+
*/
|
|
272
|
+
setCustomValidity(message) {
|
|
273
|
+
this.#customValidity.value = message;
|
|
274
|
+
}
|
|
275
|
+
// ── overridable control hooks (clause 3) ──────────────────────────────────────
|
|
276
|
+
/** The value this control contributes to its form. Default: nothing (`null`); the subclass returns its value. */
|
|
277
|
+
formValue() {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* This control's validity verdict, published to `internals.setValidity` by a scope-owned effect. Default:
|
|
282
|
+
* always valid; the subclass computes it from its props (e.g. `required && value === '' → valueMissing`).
|
|
283
|
+
*
|
|
284
|
+
* NAMED `formValidity`, NOT `validity` (parallel to `formValue`): the public IDL `validity` GETTER below
|
|
285
|
+
* returns the platform's `ValidityState`, so the verdict hook cannot share that name. ADR-0013 clauses 3/6
|
|
286
|
+
* both wrote `validity`; this base resolves the collision in favour of native IDL parity for `validity`.
|
|
287
|
+
*/
|
|
288
|
+
formValidity() {
|
|
289
|
+
return { valid: true };
|
|
290
|
+
}
|
|
291
|
+
// ── effective-disabled (clause 5) ─────────────────────────────────────────────
|
|
292
|
+
/**
|
|
293
|
+
* The disabled state a control should obey: its OWN `disabled` prop OR an ancestor `<fieldset disabled>` /
|
|
294
|
+
* form-disabled state (`formDisabledCallback`). Reactive — read it inside an effect and it re-runs when
|
|
295
|
+
* either source flips. (The form-disabled channel is the ADR-0013 divergence from ADR-0010's `ariaDisabled`
|
|
296
|
+
* for non-form controls.)
|
|
297
|
+
*/
|
|
298
|
+
effectiveDisabled() {
|
|
299
|
+
return this.disabled || this.#formDisabled.value;
|
|
300
|
+
}
|
|
301
|
+
// ── platform form lifecycle callbacks → overridable hooks (clause 5) ──────────
|
|
302
|
+
/**
|
|
303
|
+
* Platform reset → the `formReset()` hook (the subclass restores value ← its defaultValue; clause 2), THEN
|
|
304
|
+
* the ADR-0051 `ui-form-reset` protocol dispatch (composed, bubbling, no detail — the target IS the
|
|
305
|
+
* resetting control) so an observing `ui-field` re-suppresses its visible error. Covers BOTH reset paths:
|
|
306
|
+
* native `form.reset()` (the platform walks its FACE members' `formResetCallback`s) and a form-less
|
|
307
|
+
* provider's direct call to this same public platform callback.
|
|
308
|
+
*/
|
|
309
|
+
formResetCallback() {
|
|
310
|
+
this.formReset();
|
|
311
|
+
this.dispatchEvent(new CustomEvent(FORM_RESET_EVENT, { bubbles: true, composed: true }));
|
|
312
|
+
}
|
|
313
|
+
/** Platform fieldset/form-disabled change → the reactive `#formDisabled` signal behind `effectiveDisabled()`. */
|
|
314
|
+
formDisabledCallback(disabled) {
|
|
315
|
+
this.#formDisabled.value = disabled;
|
|
316
|
+
}
|
|
317
|
+
/** Platform state restore (navigation/autofill) → the `formStateRestore(state)` hook. */
|
|
318
|
+
formStateRestoreCallback(state, _mode) {
|
|
319
|
+
this.formStateRestore(state);
|
|
320
|
+
}
|
|
321
|
+
/** Platform association change → the no-op `formAssociated(form)` hook (an extension point). */
|
|
322
|
+
formAssociatedCallback(form) {
|
|
323
|
+
this.formAssociated(form);
|
|
324
|
+
}
|
|
325
|
+
/** Restore value ← defaultValue on form reset. Default no-op — the subclass owns `value` + its defaultValue. */
|
|
326
|
+
formReset() { }
|
|
327
|
+
/** Restore value from a previously-submitted/autofilled form state. Default no-op — the subclass decodes `state`. */
|
|
328
|
+
formStateRestore(_state) { }
|
|
329
|
+
/** Called when the element associates with / disassociates from a form. Default no-op extension point. */
|
|
330
|
+
formAssociated(_form) { }
|
|
331
|
+
// ── IDL delegators (clause 6) — native form-control parity, via the inherited internals ───────
|
|
332
|
+
// `name` is NOT delegated here: it is the reactive `name` prop (spread from `formProps`), the same surface
|
|
333
|
+
// a consumer reads as `el.name` — and `internals` has no `name` member to delegate to.
|
|
334
|
+
/** The owning `<form>`, or null. */
|
|
335
|
+
get form() {
|
|
336
|
+
return this.internals.form;
|
|
337
|
+
}
|
|
338
|
+
/** The platform's live `ValidityState` (the IDL object; distinct from the `formValidity()` verdict hook). */
|
|
339
|
+
get validity() {
|
|
340
|
+
return this.internals.validity;
|
|
341
|
+
}
|
|
342
|
+
/** The platform validation message (the `setValidity` message; empty when valid). */
|
|
343
|
+
get validationMessage() {
|
|
344
|
+
return this.internals.validationMessage;
|
|
345
|
+
}
|
|
346
|
+
/** Whether this control is a candidate for constraint validation. */
|
|
347
|
+
get willValidate() {
|
|
348
|
+
return this.internals.willValidate;
|
|
349
|
+
}
|
|
350
|
+
/** Run constraint validation, firing an `invalid` event when invalid. */
|
|
351
|
+
checkValidity() {
|
|
352
|
+
return this.internals.checkValidity();
|
|
353
|
+
}
|
|
354
|
+
/** Like `checkValidity`, and additionally reports the problem to the user (focuses the anchor). */
|
|
355
|
+
reportValidity() {
|
|
356
|
+
return this.internals.reportValidity();
|
|
357
|
+
}
|
|
358
|
+
// ── ADR-0051 — the field-labelling seam ───────────────────────────────────────
|
|
359
|
+
/**
|
|
360
|
+
* Public — a `ui-field` calls this with its label/description/error part elements; `null` clears the
|
|
361
|
+
* association (control removed from its field, or the field itself torn down). Signal-backed (the
|
|
362
|
+
* `setCustomValidity` precedent, ADR-0029 §5) — deliberately NOT a prop: elements can't ride attributes,
|
|
363
|
+
* so this stays off the attributes-as-API descriptor surface. Safe on a disconnected control (a plain
|
|
364
|
+
* signal write); the base effect applies it on next connect.
|
|
365
|
+
*/
|
|
366
|
+
setFieldLabelling(refs) {
|
|
367
|
+
this.#fieldLabelling.value = refs;
|
|
368
|
+
}
|
|
369
|
+
/** Protected reactive read — an override's forwarding effect (or a subclass's own label/message effects,
|
|
370
|
+
* e.g. text-field's wire) re-runs on association/clear. */
|
|
371
|
+
get fieldLabelling() {
|
|
372
|
+
return this.#fieldLabelling.value;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Protected forwarding hook (ADR-0051 cl.2), applied by the base scope-owned effect installed in
|
|
376
|
+
* `connectedCallback`. Default: internals ARIA element reflection, GUARDED on `internals.role != null` —
|
|
377
|
+
* every internals-role control (checkbox/switch/radio/slider/calendar) wires for FREE, while a role-less
|
|
378
|
+
* host deliberately no-ops (a half-attached name on a role-less AX node is silent lossiness that can fool
|
|
379
|
+
* a probe — a part-role control, e.g. text-field, MUST override instead). Contract for an override:
|
|
380
|
+
* idempotent; handle `null` (clear); guard a not-yet-created part.
|
|
381
|
+
*/
|
|
382
|
+
applyFieldLabelling(refs) {
|
|
383
|
+
if (this.internals.role == null)
|
|
384
|
+
return; // role-less host — a part-role control MUST override (ADR-0051)
|
|
385
|
+
reflectAriaElements(this.internals, 'ariaLabelledByElements', refs?.label ? [refs.label] : null);
|
|
386
|
+
const described = refs ? [refs.description, refs.error].filter((el) => el !== null) : null;
|
|
387
|
+
reflectAriaElements(this.internals, 'ariaDescribedByElements', described && described.length > 0 ? described : null);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Protected user-invalid gate, feeding `FormConnectDetail.userInvalid` (ADR-0050). Default `false`. A
|
|
391
|
+
* control composing `trackUserInvalid` overrides it with its tracker's gate (`() =>
|
|
392
|
+
* tracker.userInvalid()`), keeping ONE timing source — the field's error observation rides this read
|
|
393
|
+
* (jsdom cannot match `:state(user-invalid)`; the browser gate asserts the equivalence).
|
|
394
|
+
*/
|
|
395
|
+
formUserInvalid() {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
_a = UIFormElement;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { UIElement } from './element.js';
|
|
2
|
+
export { UIFormElement, FORM_CONNECT_EVENT, FORM_RESET_EVENT } from './form.js';
|
|
3
|
+
export type { FormValue, ValidityResult, FormConnectDetail, FieldLabelling } from './form.js';
|
|
4
|
+
export { UIContainerElement } from './container.js';
|
|
5
|
+
export { prop, Types } from './props.js';
|
|
6
|
+
export type { PropType, PropConfig, PropsSchema, ReactiveProps } from './props.js';
|
|
7
|
+
export { repeat } from './repeat.js';
|
|
8
|
+
export { watch } from './watch.js';
|
|
9
|
+
export { Directive, directive, NO_COMMIT, mount } from './template.js';
|
|
10
|
+
export type { RenderContext, DirectiveResult } from './template.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// dom — the element hosts (UIElement, the FACE form base UIFormElement, and the FACE container surface base
|
|
2
|
+
// UIContainerElement) + the typed-prop authoring API + the G3 child directives (repeat/watch). Imports only
|
|
3
|
+
// ../reactive (and same-layer ./element.ts / ./props.ts / ./form.ts / ./container.ts / ./repeat.ts / ./watch.ts).
|
|
4
|
+
//
|
|
5
|
+
// The PUBLIC dom surface (rubric D7): the element hosts and the typed-prop schema API a control author uses,
|
|
6
|
+
// the two child directives a template author drives, PLUS the directive-AUTHORING trio + the imperative
|
|
7
|
+
// `mount` host (ADR-0023). The wiring seams that connect them stay module-private and are deliberately NOT
|
|
8
|
+
// re-exported here:
|
|
9
|
+
// • props.ts/element.ts plumbing — `finalize`, `coerceAttribute`, `observedAttributesFor`, `propForAttribute`,
|
|
10
|
+
// and the codec factories behind `prop.*` (`enumType`/`jsonType`).
|
|
11
|
+
// • the template ENTRY — `render` / `html` / `svg` / `TemplateResult` / `ChildPart` / `prepare`. The template
|
|
12
|
+
// machinery is internal cross-module plumbing; a directive is the only PUBLIC render unit.
|
|
13
|
+
// PUBLIC since ADR-0023: directive-AUTHORING (`Directive` / `directive` / `NO_COMMIT`, plus the `RenderContext`
|
|
14
|
+
// and `DirectiveResult` types) and the imperative `mount(result, container, ctx?)` host that commits a kernel
|
|
15
|
+
// directive (e.g. `repeat`) into a container WITHOUT the private `html\`\`` entry — what an imperative consumer
|
|
16
|
+
// (the a2ui renderer) needs to invoke AND author directives. `repeat` / `watch` remain the shipped directives.
|
|
17
|
+
export { UIElement } from "./element.js";
|
|
18
|
+
// The ADR-0050/0051 protocol surface (G7): the `ui-form-connect`/`ui-form-reset` event names + the connect
|
|
19
|
+
// detail shape are PUBLIC so a registry controller (traits/form-registry.ts) and a consuming control
|
|
20
|
+
// (controls/field/) can listen/type against them without reaching into `./form.ts` directly.
|
|
21
|
+
export { UIFormElement, FORM_CONNECT_EVENT, FORM_RESET_EVENT } from "./form.js";
|
|
22
|
+
// The FACE container surface base (G9, ADR-0015/0016) — NOT form-associated. The shared spreadable surface +
|
|
23
|
+
// flex prop schemas ride its statics (`UIContainerElement.surfaceProps` / `.flexProps`), the ADR-0013 formProps
|
|
24
|
+
// precedent: a subclass folds them into its OWN `static props`, so there is no standalone schema value to
|
|
25
|
+
// re-export — the class IS the surface (matching how `UIFormElement` carries `formProps`).
|
|
26
|
+
export { UIContainerElement } from "./container.js";
|
|
27
|
+
export { prop, Types } from "./props.js";
|
|
28
|
+
export { repeat } from "./repeat.js";
|
|
29
|
+
export { watch } from "./watch.js";
|
|
30
|
+
// The directive-authoring trio + the imperative mount host (ADR-0023). `render`/`html`/`ChildPart` stay private.
|
|
31
|
+
export { Directive, directive, NO_COMMIT, mount } from "./template.js";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface PropType<T> {
|
|
2
|
+
from(attr: string | null): T;
|
|
3
|
+
to(value: T): string | null;
|
|
4
|
+
}
|
|
5
|
+
/** A codec for a fixed string-literal set; `from` snaps an unknown attribute back to the first member. */
|
|
6
|
+
export declare function enumType<T extends readonly string[]>(values: T): PropType<T[number]>;
|
|
7
|
+
/** A codec for an arbitrary JSON-serializable value of type `T` (round-trips via JSON). */
|
|
8
|
+
export declare function jsonType<T>(): PropType<T>;
|
|
9
|
+
/** The three fixed codecs (the parametric `enumType`/`jsonType` are factories above). */
|
|
10
|
+
export declare const Types: {
|
|
11
|
+
string: PropType<string>;
|
|
12
|
+
number: PropType<number | null>;
|
|
13
|
+
boolean: PropType<boolean>;
|
|
14
|
+
};
|
|
15
|
+
export interface PropConfig<T> {
|
|
16
|
+
type: PropType<T>;
|
|
17
|
+
default: T;
|
|
18
|
+
attribute?: string | false;
|
|
19
|
+
reflect?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare const prop: {
|
|
22
|
+
string(def?: string): PropConfig<string>;
|
|
23
|
+
number(def?: number | null): PropConfig<number | null>;
|
|
24
|
+
boolean(def?: boolean): PropConfig<boolean>;
|
|
25
|
+
enum<const T extends readonly string[]>(values: T, def: T[number]): PropConfig<T[number]>;
|
|
26
|
+
json<T>(def: T): PropConfig<T>;
|
|
27
|
+
};
|
|
28
|
+
export type PropsSchema = Record<string, PropConfig<unknown>>;
|
|
29
|
+
export type ReactiveProps<S extends PropsSchema> = {
|
|
30
|
+
[K in keyof S]: S[K] extends PropConfig<infer T> ? T : never;
|
|
31
|
+
};
|
|
32
|
+
/** A constructor-like with a `static props` schema — what `finalize` installs accessors onto. */
|
|
33
|
+
export interface Finalizable {
|
|
34
|
+
prototype: object;
|
|
35
|
+
props?: PropsSchema;
|
|
36
|
+
}
|
|
37
|
+
/** Install signal-backed prototype accessors from `Ctor.props`. Idempotent (safe to call once per class). */
|
|
38
|
+
export declare function finalize(ctor: Finalizable): void;
|
|
39
|
+
/**
|
|
40
|
+
* Inbound seam (the first of the two boundary functions): cross an attribute STRING (or `null` for
|
|
41
|
+
* absence) into the typed prop signal via the prop's `PropType.from` codec, and return the coerced
|
|
42
|
+
* value. This is the primitive the element slice's `attributeChangedCallback` (e-attrs, later) composes
|
|
43
|
+
* on — p-install owns the per-instance store + the string→typed crossing; it does NOT own the platform
|
|
44
|
+
* callback. A no-op for an unknown prop name.
|
|
45
|
+
*
|
|
46
|
+
* Two directional-lock interactions (rubric D3): (1) if an outbound reflect for this prop is in flight,
|
|
47
|
+
* this call is that reflect's own platform echo — suppress it (return `undefined`) so it cannot loop.
|
|
48
|
+
* (2) Otherwise write through the installed accessor under the `inbound` lock, so the setter applies the
|
|
49
|
+
* value but does NOT reflect it straight back out. The write goes through the setter (not the raw signal)
|
|
50
|
+
* precisely so that symmetric guard is load-bearing: one write path, the lock decides whether it reflects.
|
|
51
|
+
*/
|
|
52
|
+
export declare function coerceAttribute(instance: object, ctor: Finalizable, name: string, attr: string | null): unknown;
|
|
53
|
+
/** The attribute names to observe for a finalized ctor: each prop's attribute name, minus property-only props. */
|
|
54
|
+
export declare function observedAttributesFor(ctor: Finalizable): string[];
|
|
55
|
+
/** Reverse the mapping: the prop name owning a given attribute name (respecting `attribute` overrides), or `undefined`. */
|
|
56
|
+
export declare function propForAttribute(ctor: Finalizable, attr: string): string | undefined;
|