@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
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { TemplateResult } from './template.js';
|
|
2
|
+
export declare class UIElement extends HTMLElement {
|
|
3
|
+
#private;
|
|
4
|
+
/** Light DOM by default; a subclass sets `static shadow = true` to render into a shadow root instead. */
|
|
5
|
+
static shadow: boolean;
|
|
6
|
+
static get observedAttributes(): string[];
|
|
7
|
+
constructor();
|
|
8
|
+
connectedCallback(): void;
|
|
9
|
+
disconnectedCallback(): void;
|
|
10
|
+
/**
|
|
11
|
+
* Inbound attribute → typed prop crossing. Maps the platform's ATTRIBUTE name to its PROP name, then
|
|
12
|
+
* hands off to the shipped `coerceAttribute` (string→typed via the prop's codec). The directional lock
|
|
13
|
+
* inside `coerceAttribute` owns loop suppression — both ways — so this adapter adds NO lock logic:
|
|
14
|
+
* a `reflect` write's own echo arrives here and is suppressed (outbound lock); an external change
|
|
15
|
+
* crosses to the value without reflecting back (inbound lock).
|
|
16
|
+
*/
|
|
17
|
+
attributeChangedCallback(attr: string, _old: string | null, next: string | null): void;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve one pre-upgrade property shadow. A `.prop=` set BEFORE the element upgraded (before `finalize`
|
|
20
|
+
* installed the accessor) created an OWN data property that MASKS the accessor — reads/writes hit the
|
|
21
|
+
* dead own property, never the signal. Replay the value THROUGH the accessor: prefer the value the
|
|
22
|
+
* CONSTRUCTOR captured (property-wins, ADR-0005 — it predates any initial-attribute write to the shadow);
|
|
23
|
+
* fall back to the current own-property value when nothing was captured (e.g. a manual array/object
|
|
24
|
+
* accessor a control upgrades by hand, or a shadow created after construction). No-op with neither.
|
|
25
|
+
*/
|
|
26
|
+
protected upgradeProperty(name: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Replay every declared prop's pre-upgrade shadow. Run at the START of `connectedCallback`. Ordering
|
|
29
|
+
* note (the lazy-upgrade ↔ attributeChanged seam): an INITIAL observed attribute is applied during
|
|
30
|
+
* upgrade — before connect — while the shadow is still present, so its inbound write lands on the
|
|
31
|
+
* shadow; but the constructor already CAPTURED the pre-upgrade property value, so the replay restores
|
|
32
|
+
* the property over the attribute (property-wins, ADR-0005). See element-upgrade.test.ts.
|
|
33
|
+
*/
|
|
34
|
+
protected upgradeProps(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Register a connection-scoped effect: it runs now and re-runs on dependency change, and is disposed
|
|
37
|
+
* when the host disconnects (the scope owns it). This is the one effect-install primitive —
|
|
38
|
+
* `connectedCallback` routes the render effect through it too. Returns the effect's disposer (call it
|
|
39
|
+
* to end the effect early; otherwise disconnect ends it). Throws if called outside the connected
|
|
40
|
+
* lifetime, where there is no scope to own it.
|
|
41
|
+
*/
|
|
42
|
+
effect(fn: () => void | (() => void)): () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Add a platform event listener that rides the connection `AbortSignal`, so it is removed
|
|
45
|
+
* automatically on disconnect (`ac.abort()`) — zero live listeners after removal, no manual teardown.
|
|
46
|
+
* `type` stays an open string so custom event names are accepted; a control narrows at its call site.
|
|
47
|
+
* Throws if called outside the connected lifetime, where there is no signal to bind to.
|
|
48
|
+
*/
|
|
49
|
+
listen(target: EventTarget, type: string, handler: (event: Event) => void, opts?: AddEventListenerOptions): void;
|
|
50
|
+
/**
|
|
51
|
+
* Dispatch a composed, bubbling, cancelable `CustomEvent`. Typed generically over the detail `D`, so a
|
|
52
|
+
* caller that pins `D` (today explicitly; via an event-detail map / `HTMLElementEventMap` augmentation
|
|
53
|
+
* once controls land) gets the detail type-checked. Returns `false` if a listener called
|
|
54
|
+
* `preventDefault()`, mirroring the platform's `dispatchEvent`.
|
|
55
|
+
*/
|
|
56
|
+
emit<D = undefined>(type: string, detail?: D): boolean;
|
|
57
|
+
/** Resolves after the next render flush settles — `await el.updateComplete` waits for a pending render. */
|
|
58
|
+
get updateComplete(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* The render target: the shadow root when `static shadow` opted one in, else the host itself (light DOM
|
|
61
|
+
* is the default). G3's render effect commits into this — the seam lands now so G3 + the barrel compose.
|
|
62
|
+
*/
|
|
63
|
+
get renderRoot(): ShadowRoot | HTMLElement;
|
|
64
|
+
/**
|
|
65
|
+
* The single `ElementInternals` handle (acquired once in the constructor). ARIA is set THROUGH this —
|
|
66
|
+
* `this.internals.role = …`, `this.internals.ariaChecked = …` — NEVER host attributes, so the host
|
|
67
|
+
* stays free of `role`/`aria-*`. The protected seam the G4 form subclass + traits reuse without
|
|
68
|
+
* re-acquiring (a second `attachInternals()` throws).
|
|
69
|
+
*/
|
|
70
|
+
protected get internals(): ElementInternals;
|
|
71
|
+
/**
|
|
72
|
+
* The connection `AbortSignal`: connection-scoped platform listeners ride it, so disconnect
|
|
73
|
+
* (`abort()`) frees them in one shot. Null while disconnected. The minimal protected seam by which
|
|
74
|
+
* subclasses / traits observe the connection lifetime; `this.listen` (e-helpers) rides it.
|
|
75
|
+
*/
|
|
76
|
+
protected get connectionSignal(): AbortSignal | null;
|
|
77
|
+
/**
|
|
78
|
+
* Overridable connect hook. Runs on connect with the connection scope + `AbortSignal` live, so a
|
|
79
|
+
* `this.effect`/`this.listen` registered here is scope-owned and auto-removed on disconnect. It runs
|
|
80
|
+
* BEFORE the first render (the ordering: upgradeProps → scope/ac → `connected()` → render effect), so a
|
|
81
|
+
* control's host-level setup (traits, signals) is in place before the first commit. Override instead of
|
|
82
|
+
* `connectedCallback` — no `super` call needed. (Post-render DOM work belongs in `updateComplete`.)
|
|
83
|
+
*/
|
|
84
|
+
protected connected(): void;
|
|
85
|
+
/**
|
|
86
|
+
* Overridable disconnect hook. Runs on disconnect BEFORE the connection scope is disposed / the signal
|
|
87
|
+
* aborted, so the control can act while its connected resources are still live. Override instead of
|
|
88
|
+
* `disconnectedCallback` — no `super` call needed.
|
|
89
|
+
*/
|
|
90
|
+
protected disconnected(): void;
|
|
91
|
+
/**
|
|
92
|
+
* Overridable render hook. Return a `TemplateResult` (`html\`…\``) and the scope-owned render effect
|
|
93
|
+
* commits it into `renderRoot`; return nothing to render imperatively (or not at all). Runs inside the
|
|
94
|
+
* connection-scoped render effect, so any signal it reads re-runs only this render, committing only the
|
|
95
|
+
* holes whose values changed (the template engine's per-part `Object.is` skip).
|
|
96
|
+
*/
|
|
97
|
+
protected render(): TemplateResult | void;
|
|
98
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// element.ts — `UIElement`, the FACE custom-element host (plan §5).
|
|
2
|
+
//
|
|
3
|
+
// Maps the platform's connect/disconnect callbacks onto the kernel's two lifetimes: a connection
|
|
4
|
+
// **scope** (every computed/effect created under it dies on disconnect) and an **AbortController**
|
|
5
|
+
// (every platform listener riding its signal dies on disconnect). "Zero residue after removal" is then
|
|
6
|
+
// provable, not aspirational (goals.md G2 DoD3; rubric D4). Built so far: the connection scope/abort
|
|
7
|
+
// wiring + the ONE render effect (e-lifecycle), and the public helper surface — `this.effect` (scope-
|
|
8
|
+
// owned), `this.listen` (rides the abort signal → auto-removed on disconnect), `this.emit` (typed
|
|
9
|
+
// `CustomEvent`), `updateComplete` (e-helpers), and the attribute inbound crossing —
|
|
10
|
+
// `observedAttributes` + `attributeChangedCallback` → `coerceAttribute` (e-attrs), the lazy-property
|
|
11
|
+
// upgrade dance — `upgradeProps`/`upgradeProperty` at connect (e-upgrade), and internals-only ARIA + the
|
|
12
|
+
// light-DOM-default render root (e-internals). Only the dom barrel remains to close G2; `render()` stays
|
|
13
|
+
// a no-op hook until the template layer (G3).
|
|
14
|
+
//
|
|
15
|
+
// Imports only `../reactive` (the kernel) + `./props.ts` (same dom layer). `HTMLElement` /
|
|
16
|
+
// `AbortController` / `AbortSignal` / `CustomEvent` / `ElementInternals` are ambient DOM globals, not imports.
|
|
17
|
+
import { createScope, effect as createEffect, whenFlushed } from "../reactive/index.js";
|
|
18
|
+
import { finalize, coerceAttribute, observedAttributesFor, propForAttribute } from "./props.js";
|
|
19
|
+
import { render as commitTemplate, TemplateResult } from "./template.js";
|
|
20
|
+
export class UIElement extends HTMLElement {
|
|
21
|
+
// The connection lifetimes. Null while disconnected; opened on connect, torn down + nulled on
|
|
22
|
+
// disconnect. `#`-private (real JS privacy) so nothing outside the host can hold or revive them.
|
|
23
|
+
#scope = null;
|
|
24
|
+
#ac = null;
|
|
25
|
+
// The single `ElementInternals` handle, acquired ONCE here (a second `attachInternals()` throws).
|
|
26
|
+
// Surfaced to subclasses via the protected `internals` getter — a `#private` field can't cross to one.
|
|
27
|
+
#internals = this.attachInternals();
|
|
28
|
+
// Pre-upgrade `.prop=` values captured in the constructor (property-wins, ADR-0005). See the constructor.
|
|
29
|
+
#captured = new Map();
|
|
30
|
+
/** Light DOM by default; a subclass sets `static shadow = true` to render into a shadow root instead. */
|
|
31
|
+
static shadow = false;
|
|
32
|
+
// The attribute names the platform watches, derived from `static props` (property-only props excluded;
|
|
33
|
+
// `attribute` overrides honoured). Read once by `customElements.define`, before any instance exists —
|
|
34
|
+
// so it reads the schema directly, not the finalize-installed accessors.
|
|
35
|
+
static get observedAttributes() {
|
|
36
|
+
return observedAttributesFor(this);
|
|
37
|
+
}
|
|
38
|
+
constructor() {
|
|
39
|
+
super();
|
|
40
|
+
// Opt-in shadow root (light DOM is the default), attached once here so `renderRoot` is a pure getter.
|
|
41
|
+
if (this.constructor.shadow)
|
|
42
|
+
this.attachShadow({ mode: 'open' });
|
|
43
|
+
// Light the props-as-signals subsystem on this host: install the signal-backed prototype accessors
|
|
44
|
+
// declared by `static props`. Idempotent per class (props.ts' FINALIZED set), so every instance
|
|
45
|
+
// calls it but only the first instance of a class finalizes it — no props.ts change needed.
|
|
46
|
+
const ctor = this.constructor;
|
|
47
|
+
finalize(ctor);
|
|
48
|
+
// Property-wins (ADR-0005): snapshot each declared prop's pre-upgrade own-property value HERE — the
|
|
49
|
+
// constructor runs during upgrade BEFORE attributeChangedCallback can overwrite the shadow — so an
|
|
50
|
+
// imperatively-set `.prop=` beats an initial attribute. `upgradeProperty` replays the captured value.
|
|
51
|
+
if (ctor.props) {
|
|
52
|
+
for (const name of Object.keys(ctor.props)) {
|
|
53
|
+
if (Object.hasOwn(this, name))
|
|
54
|
+
this.#captured.set(name, this[name]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
connectedCallback() {
|
|
59
|
+
// FIRST: replay any pre-upgrade `.prop=` shadow into its signal — before the render effect installs
|
|
60
|
+
// and before anything reads a prop, so first render sees the assigned value, not the default.
|
|
61
|
+
this.upgradeProps();
|
|
62
|
+
this.#scope = createScope();
|
|
63
|
+
this.#ac = new AbortController();
|
|
64
|
+
// The connection scope + AbortController are now live, so a `this.effect`/`this.listen` registered in
|
|
65
|
+
// `connected()` is scope-owned + rides the abort signal. `connected()` runs BEFORE the first render so
|
|
66
|
+
// host-level setup (traits, signals) is in place before the first commit — see the ordering note below.
|
|
67
|
+
this.connected();
|
|
68
|
+
// The ONE render effect, installed through `this.effect` so there is a SINGLE scope-owned-effect
|
|
69
|
+
// path (no duplication). It is created inside the scope (the kernel's `activeOwner.add`) and
|
|
70
|
+
// disposed with it at disconnect — every render pass runs under the scope, so a directive attaching
|
|
71
|
+
// on a later conditional re-render is scope-owned too. The disposer is intentionally discarded.
|
|
72
|
+
// `render()` returns a `TemplateResult` (or nothing); when it returns one, commit it into `renderRoot`.
|
|
73
|
+
this.effect(() => {
|
|
74
|
+
const result = this.render();
|
|
75
|
+
// Pass `this` as the RenderContext (the scope_seam): `UIElement` structurally satisfies it via the
|
|
76
|
+
// scope-owned `effect`, so a per-hole directive (`watch`) installs its effect under the connection
|
|
77
|
+
// scope — surviving unrelated re-renders, dying on disconnect — not under this transient render effect.
|
|
78
|
+
if (result instanceof TemplateResult)
|
|
79
|
+
commitTemplate(result, this.renderRoot, this);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
disconnectedCallback() {
|
|
83
|
+
// Let the control act while its connected resources are STILL live (scope/effects alive, listeners
|
|
84
|
+
// unaborted) — `disconnected()` runs BEFORE the teardown below.
|
|
85
|
+
this.disconnected();
|
|
86
|
+
this.#scope?.dispose(); // every computed/effect created under the scope dies → zero subscribers
|
|
87
|
+
this.#ac?.abort(); // every listener riding the connection signal dies → zero live listeners
|
|
88
|
+
this.#scope = null;
|
|
89
|
+
this.#ac = null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Inbound attribute → typed prop crossing. Maps the platform's ATTRIBUTE name to its PROP name, then
|
|
93
|
+
* hands off to the shipped `coerceAttribute` (string→typed via the prop's codec). The directional lock
|
|
94
|
+
* inside `coerceAttribute` owns loop suppression — both ways — so this adapter adds NO lock logic:
|
|
95
|
+
* a `reflect` write's own echo arrives here and is suppressed (outbound lock); an external change
|
|
96
|
+
* crosses to the value without reflecting back (inbound lock).
|
|
97
|
+
*/
|
|
98
|
+
attributeChangedCallback(attr, _old, next) {
|
|
99
|
+
const ctor = this.constructor;
|
|
100
|
+
const name = propForAttribute(ctor, attr);
|
|
101
|
+
if (name !== undefined)
|
|
102
|
+
coerceAttribute(this, ctor, name, next);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolve one pre-upgrade property shadow. A `.prop=` set BEFORE the element upgraded (before `finalize`
|
|
106
|
+
* installed the accessor) created an OWN data property that MASKS the accessor — reads/writes hit the
|
|
107
|
+
* dead own property, never the signal. Replay the value THROUGH the accessor: prefer the value the
|
|
108
|
+
* CONSTRUCTOR captured (property-wins, ADR-0005 — it predates any initial-attribute write to the shadow);
|
|
109
|
+
* fall back to the current own-property value when nothing was captured (e.g. a manual array/object
|
|
110
|
+
* accessor a control upgrades by hand, or a shadow created after construction). No-op with neither.
|
|
111
|
+
*/
|
|
112
|
+
upgradeProperty(name) {
|
|
113
|
+
const hasCaptured = this.#captured.has(name);
|
|
114
|
+
const hasOwn = Object.hasOwn(this, name);
|
|
115
|
+
if (!hasCaptured && !hasOwn)
|
|
116
|
+
return;
|
|
117
|
+
const value = hasCaptured ? this.#captured.get(name) : this[name];
|
|
118
|
+
this.#captured.delete(name);
|
|
119
|
+
if (hasOwn)
|
|
120
|
+
delete this[name] // dissolve the shadow → reveal the accessor
|
|
121
|
+
;
|
|
122
|
+
this[name] = value; // replay through the accessor → the signal (+ reflect once)
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Replay every declared prop's pre-upgrade shadow. Run at the START of `connectedCallback`. Ordering
|
|
126
|
+
* note (the lazy-upgrade ↔ attributeChanged seam): an INITIAL observed attribute is applied during
|
|
127
|
+
* upgrade — before connect — while the shadow is still present, so its inbound write lands on the
|
|
128
|
+
* shadow; but the constructor already CAPTURED the pre-upgrade property value, so the replay restores
|
|
129
|
+
* the property over the attribute (property-wins, ADR-0005). See element-upgrade.test.ts.
|
|
130
|
+
*/
|
|
131
|
+
upgradeProps() {
|
|
132
|
+
const props = this.constructor.props;
|
|
133
|
+
if (!props)
|
|
134
|
+
return;
|
|
135
|
+
for (const name of Object.keys(props))
|
|
136
|
+
this.upgradeProperty(name);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Register a connection-scoped effect: it runs now and re-runs on dependency change, and is disposed
|
|
140
|
+
* when the host disconnects (the scope owns it). This is the one effect-install primitive —
|
|
141
|
+
* `connectedCallback` routes the render effect through it too. Returns the effect's disposer (call it
|
|
142
|
+
* to end the effect early; otherwise disconnect ends it). Throws if called outside the connected
|
|
143
|
+
* lifetime, where there is no scope to own it.
|
|
144
|
+
*/
|
|
145
|
+
effect(fn) {
|
|
146
|
+
const scope = this.#scope;
|
|
147
|
+
if (!scope)
|
|
148
|
+
throw new Error('UIElement.effect: no connection scope — call it during the connected lifetime');
|
|
149
|
+
return scope.run(() => createEffect(fn));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Add a platform event listener that rides the connection `AbortSignal`, so it is removed
|
|
153
|
+
* automatically on disconnect (`ac.abort()`) — zero live listeners after removal, no manual teardown.
|
|
154
|
+
* `type` stays an open string so custom event names are accepted; a control narrows at its call site.
|
|
155
|
+
* Throws if called outside the connected lifetime, where there is no signal to bind to.
|
|
156
|
+
*/
|
|
157
|
+
listen(target, type, handler, opts) {
|
|
158
|
+
const ac = this.#ac;
|
|
159
|
+
if (!ac)
|
|
160
|
+
throw new Error('UIElement.listen: no connection signal — call it during the connected lifetime');
|
|
161
|
+
target.addEventListener(type, handler, { ...opts, signal: ac.signal });
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Dispatch a composed, bubbling, cancelable `CustomEvent`. Typed generically over the detail `D`, so a
|
|
165
|
+
* caller that pins `D` (today explicitly; via an event-detail map / `HTMLElementEventMap` augmentation
|
|
166
|
+
* once controls land) gets the detail type-checked. Returns `false` if a listener called
|
|
167
|
+
* `preventDefault()`, mirroring the platform's `dispatchEvent`.
|
|
168
|
+
*/
|
|
169
|
+
emit(type, detail) {
|
|
170
|
+
return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true }));
|
|
171
|
+
}
|
|
172
|
+
/** Resolves after the next render flush settles — `await el.updateComplete` waits for a pending render. */
|
|
173
|
+
get updateComplete() {
|
|
174
|
+
return whenFlushed();
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The render target: the shadow root when `static shadow` opted one in, else the host itself (light DOM
|
|
178
|
+
* is the default). G3's render effect commits into this — the seam lands now so G3 + the barrel compose.
|
|
179
|
+
*/
|
|
180
|
+
get renderRoot() {
|
|
181
|
+
return this.shadowRoot ?? this;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The single `ElementInternals` handle (acquired once in the constructor). ARIA is set THROUGH this —
|
|
185
|
+
* `this.internals.role = …`, `this.internals.ariaChecked = …` — NEVER host attributes, so the host
|
|
186
|
+
* stays free of `role`/`aria-*`. The protected seam the G4 form subclass + traits reuse without
|
|
187
|
+
* re-acquiring (a second `attachInternals()` throws).
|
|
188
|
+
*/
|
|
189
|
+
get internals() {
|
|
190
|
+
return this.#internals;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The connection `AbortSignal`: connection-scoped platform listeners ride it, so disconnect
|
|
194
|
+
* (`abort()`) frees them in one shot. Null while disconnected. The minimal protected seam by which
|
|
195
|
+
* subclasses / traits observe the connection lifetime; `this.listen` (e-helpers) rides it.
|
|
196
|
+
*/
|
|
197
|
+
get connectionSignal() {
|
|
198
|
+
return this.#ac?.signal ?? null;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Overridable connect hook. Runs on connect with the connection scope + `AbortSignal` live, so a
|
|
202
|
+
* `this.effect`/`this.listen` registered here is scope-owned and auto-removed on disconnect. It runs
|
|
203
|
+
* BEFORE the first render (the ordering: upgradeProps → scope/ac → `connected()` → render effect), so a
|
|
204
|
+
* control's host-level setup (traits, signals) is in place before the first commit. Override instead of
|
|
205
|
+
* `connectedCallback` — no `super` call needed. (Post-render DOM work belongs in `updateComplete`.)
|
|
206
|
+
*/
|
|
207
|
+
connected() { }
|
|
208
|
+
/**
|
|
209
|
+
* Overridable disconnect hook. Runs on disconnect BEFORE the connection scope is disposed / the signal
|
|
210
|
+
* aborted, so the control can act while its connected resources are still live. Override instead of
|
|
211
|
+
* `disconnectedCallback` — no `super` call needed.
|
|
212
|
+
*/
|
|
213
|
+
disconnected() { }
|
|
214
|
+
/**
|
|
215
|
+
* Overridable render hook. Return a `TemplateResult` (`html\`…\``) and the scope-owned render effect
|
|
216
|
+
* commits it into `renderRoot`; return nothing to render imperatively (or not at all). Runs inside the
|
|
217
|
+
* connection-scoped render effect, so any signal it reads re-runs only this render, committing only the
|
|
218
|
+
* holes whose values changed (the template engine's per-part `Object.is` skip).
|
|
219
|
+
*/
|
|
220
|
+
render() { }
|
|
221
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { UIElement } from './element.js';
|
|
2
|
+
import { type ReactiveProps } from './props.js';
|
|
3
|
+
/**
|
|
4
|
+
* The value a form control contributes to its owning form — exactly the platform's `setFormValue` value
|
|
5
|
+
* type. `null` contributes no entry (the base's default). The subclass's `formValue()` returns its typed
|
|
6
|
+
* value coerced into this shape (a string for text-field; a `File` / `FormData` for richer controls).
|
|
7
|
+
*/
|
|
8
|
+
export type FormValue = File | string | FormData | null;
|
|
9
|
+
/**
|
|
10
|
+
* A control's validity VERDICT — a discriminated union the base maps onto `internals.setValidity`: valid
|
|
11
|
+
* clears it; invalid publishes the platform `flags` + the `message` (+ an optional `anchor` element the UA
|
|
12
|
+
* focuses on `reportValidity`). Distinct from the platform's `ValidityState` (the live IDL object the
|
|
13
|
+
* `validity` getter returns): this is the control's per-render assertion that DRIVES it.
|
|
14
|
+
*/
|
|
15
|
+
export type ValidityResult = {
|
|
16
|
+
valid: true;
|
|
17
|
+
} | {
|
|
18
|
+
valid: false;
|
|
19
|
+
flags: ValidityStateFlags;
|
|
20
|
+
message: string;
|
|
21
|
+
anchor?: HTMLElement;
|
|
22
|
+
};
|
|
23
|
+
/** Fired once, at the end of `connectedCallback` — the control announcing full connection to the nearest
|
|
24
|
+
* provider/field ancestor. */
|
|
25
|
+
export declare const FORM_CONNECT_EVENT = "ui-form-connect";
|
|
26
|
+
/** Fired from `formResetCallback`, after `formReset()` — detail: none (the target IS the resetting
|
|
27
|
+
* control). Covers BOTH reset paths: native `form.reset()` and a provider's direct `formResetCallback()` call. */
|
|
28
|
+
export declare const FORM_RESET_EVENT = "ui-form-reset";
|
|
29
|
+
/** The `ui-form-connect` detail. The closures are the control's PUBLIC reactive surface, minted by the base
|
|
30
|
+
* (ADR-0050 §4 realized without widening the class API): each read, inside a computed/effect, tracks
|
|
31
|
+
* exactly the signals the underlying hook reads. */
|
|
32
|
+
export interface FormConnectDetail {
|
|
33
|
+
control: UIFormElement;
|
|
34
|
+
/** The connection-scoped teardown handle — the control's live connection AbortSignal (ADR-0050 §3).
|
|
35
|
+
* Aborts on disconnect; a NEW signal is minted per connection (reconnect ⇒ fresh handle). */
|
|
36
|
+
signal: AbortSignal;
|
|
37
|
+
/** Reactive read of the submission value — closes over the protected `formValue()`. */
|
|
38
|
+
value: () => FormValue;
|
|
39
|
+
/** Reactive read of the MERGED validity verdict — `formValidity()` ⊕ `setCustomValidity` (the same merge
|
|
40
|
+
* the base publishes to internals; `#mergedValidity` is the single source feeding both). */
|
|
41
|
+
validity: () => ValidityResult;
|
|
42
|
+
/** Reactive read of the user-invalid gate — closes over the `formUserInvalid()` hook (below). jsdom
|
|
43
|
+
* cannot match `:state(user-invalid)` (no CustomStateSet), so the field's error gate rides THIS read;
|
|
44
|
+
* the browser smokes assert its equivalence with `:state(user-invalid)` (same tracker source). */
|
|
45
|
+
userInvalid: () => boolean;
|
|
46
|
+
}
|
|
47
|
+
/** The field-labelling handoff (ADR-0051 cl.1). ELEMENTS, not id strings — the internals path consumes
|
|
48
|
+
* elements; the id path reads `.id` off the node (the field seeds ids before handing over). */
|
|
49
|
+
export interface FieldLabelling {
|
|
50
|
+
label: HTMLElement | null;
|
|
51
|
+
description: HTMLElement | null;
|
|
52
|
+
error: HTMLElement | null;
|
|
53
|
+
}
|
|
54
|
+
declare const formProps: {
|
|
55
|
+
name: {
|
|
56
|
+
reflect: true;
|
|
57
|
+
type: import("./props.js").PropType<string>;
|
|
58
|
+
default: string;
|
|
59
|
+
attribute?: string | false;
|
|
60
|
+
};
|
|
61
|
+
disabled: {
|
|
62
|
+
reflect: true;
|
|
63
|
+
type: import("./props.js").PropType<boolean>;
|
|
64
|
+
default: boolean;
|
|
65
|
+
attribute?: string | false;
|
|
66
|
+
};
|
|
67
|
+
required: {
|
|
68
|
+
reflect: true;
|
|
69
|
+
type: import("./props.js").PropType<boolean>;
|
|
70
|
+
default: boolean;
|
|
71
|
+
attribute?: string | false;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
export interface UIFormElement extends ReactiveProps<typeof formProps> {
|
|
75
|
+
}
|
|
76
|
+
export declare class UIFormElement extends UIElement {
|
|
77
|
+
#private;
|
|
78
|
+
/** Activates platform form association — `setFormValue`/`setValidity` + the form lifecycle callbacks (clause 1). */
|
|
79
|
+
static formAssociated: boolean;
|
|
80
|
+
/** The spreadable universal form attributes — the no-static-props-inheritance workaround (clause 2). */
|
|
81
|
+
static formProps: {
|
|
82
|
+
name: {
|
|
83
|
+
reflect: true;
|
|
84
|
+
type: import("./props.js").PropType<string>;
|
|
85
|
+
default: string;
|
|
86
|
+
attribute?: string | false;
|
|
87
|
+
};
|
|
88
|
+
disabled: {
|
|
89
|
+
reflect: true;
|
|
90
|
+
type: import("./props.js").PropType<boolean>;
|
|
91
|
+
default: boolean;
|
|
92
|
+
attribute?: string | false;
|
|
93
|
+
};
|
|
94
|
+
required: {
|
|
95
|
+
reflect: true;
|
|
96
|
+
type: import("./props.js").PropType<boolean>;
|
|
97
|
+
default: boolean;
|
|
98
|
+
attribute?: string | false;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
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(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Re-announce full connection (F1 — the review-caught upgrade-order hole). "Ancestors connect before
|
|
111
|
+
* descendants" holds for INSERTION order but NOT custom-element UPGRADE order: pre-existing DOM whose
|
|
112
|
+
* control module registers/upgrades before the provider/field module does means the ONE connect dispatch
|
|
113
|
+
* in `connectedCallback` fires into the void — no listener is live yet — and registration would be lost
|
|
114
|
+
* forever without a catch-up path. Public so a provider/field's one-shot catch-up scan (built at their
|
|
115
|
+
* own build slice) can call it on every already-connected `UIFormElement` it discovers late, re-dispatching
|
|
116
|
+
* `ui-form-connect` with a FRESH detail (the same minting `#connectDetail()` uses, so a reconnect since
|
|
117
|
+
* the original dispatch is reflected — a NEW connection signal, current prop values).
|
|
118
|
+
*
|
|
119
|
+
* Guard: `connectionSignal === null`, NOT `isConnected` (a real bug an s9 repro caught — the bulk-insert
|
|
120
|
+
* hazard). `isConnected` reflects DOM tree membership, set the instant a subtree is spliced in; a custom
|
|
121
|
+
* element's OWN `connectedCallback` (which opens the connection scope + mints `#ac`) fires ASYNC to that,
|
|
122
|
+
* in a separate reaction-queue pass, ancestor-first. So on a bulk insert of a pre-built offline subtree
|
|
123
|
+
* (assemble, then one `append()` — the house test idiom), every descendant is already `isConnected` the
|
|
124
|
+
* moment the PARENT's `connectedCallback` runs (and could catch-up-scan its already-`isConnected`
|
|
125
|
+
* children) even though THEIR OWN `connectedCallback` — and so `connectionSignal` — hasn't fired yet.
|
|
126
|
+
* NOT a jsdom accommodation: atomic-subtree-connect ordering (ancestor-`connectedCallback`-before-
|
|
127
|
+
* descendant, all descendants already `isConnected` throughout) is SPEC-MANDATED and holds identically in
|
|
128
|
+
* every real engine (s8 confirmed with a plain-HTMLElement probe) — this is a real-browser correctness
|
|
129
|
+
* fix, not a test-environment workaround. `connectionSignal === null` is the correct predicate for "has
|
|
130
|
+
* this control's OWN connect wiring run yet": it is null before that control's first `connectedCallback`
|
|
131
|
+
* (nothing to announce — that `connectedCallback` will dispatch for itself momentarily) AND after its
|
|
132
|
+
* `disconnectedCallback` (`#ac` is nulled there too, element.ts) — one guard covers both the not-yet-mine
|
|
133
|
+
* and no-longer-mine legs; `isConnected` is redundant once this holds and is dropped to avoid it masking
|
|
134
|
+
* the real invariant.
|
|
135
|
+
*
|
|
136
|
+
* This guard closes TWO bugs, not one. The obvious one: the pre-fix crash (a `null` `signal` in the
|
|
137
|
+
* minted detail, `detail.signal.aborted` throwing in a registry/field listener). The SILENT one (s8-found):
|
|
138
|
+
* in the same-cascade race, `ui-field`'s association still applies `aria-labelledby` (the labelling signal
|
|
139
|
+
* write does not ride the connection scope), but the affected control's OWN `connectedCallback` — and so
|
|
140
|
+
* its abort listener — had not yet run, so `this.listen(detail.signal, 'abort', …)` in `#associate` would
|
|
141
|
+
* have wired against a signal that was never live, and a later removal would never dissociate (stale
|
|
142
|
+
* labelling, forever). This guard skips the same-cascade announce entirely and lets the descendant's OWN
|
|
143
|
+
* end-of-`connectedCallback` dispatch (below the guard here — see that dispatch's own signal, always live
|
|
144
|
+
* by construction) carry the association instead, once the field/provider is already listening — so the
|
|
145
|
+
* abort listener installs correctly, every time.
|
|
146
|
+
*
|
|
147
|
+
* Also routes through `#dispatchFormConnect`'s nested-member guard — a catch-up scan's
|
|
148
|
+
* `querySelectorAll('*')` finds every `UIFormElement` descendant indiscriminately (it cannot itself
|
|
149
|
+
* distinguish an internal part like a text-field's calendar from a real member), so the guard MUST live
|
|
150
|
+
* here too, not only on the initial dispatch, or a late-discovered internal part would still register.
|
|
151
|
+
*/
|
|
152
|
+
announceFormConnect(): void;
|
|
153
|
+
/**
|
|
154
|
+
* Set a renderer-driven custom validity message (ADR-0029 §5 — the A2UI `checks` seam, native-parity name).
|
|
155
|
+
* An empty string clears the custom contribution (native parity: `el.setCustomValidity('')` = valid).
|
|
156
|
+
* A non-empty message drives `{valid:false, flags:{customError:true}, message}` unless `formValidity()`
|
|
157
|
+
* is already invalid (native wins — e.g. `valueMissing` takes the display slot over a custom message).
|
|
158
|
+
* The reactive effect in `connectedCallback` picks up the `#customValidity` signal change and re-publishes
|
|
159
|
+
* through `#applyValidity` without any direct caller → `internals.setValidity` call here.
|
|
160
|
+
*/
|
|
161
|
+
setCustomValidity(message: string): void;
|
|
162
|
+
/** The value this control contributes to its form. Default: nothing (`null`); the subclass returns its value. */
|
|
163
|
+
protected formValue(): FormValue;
|
|
164
|
+
/**
|
|
165
|
+
* This control's validity verdict, published to `internals.setValidity` by a scope-owned effect. Default:
|
|
166
|
+
* always valid; the subclass computes it from its props (e.g. `required && value === '' → valueMissing`).
|
|
167
|
+
*
|
|
168
|
+
* NAMED `formValidity`, NOT `validity` (parallel to `formValue`): the public IDL `validity` GETTER below
|
|
169
|
+
* returns the platform's `ValidityState`, so the verdict hook cannot share that name. ADR-0013 clauses 3/6
|
|
170
|
+
* both wrote `validity`; this base resolves the collision in favour of native IDL parity for `validity`.
|
|
171
|
+
*/
|
|
172
|
+
protected formValidity(): ValidityResult;
|
|
173
|
+
/**
|
|
174
|
+
* The disabled state a control should obey: its OWN `disabled` prop OR an ancestor `<fieldset disabled>` /
|
|
175
|
+
* form-disabled state (`formDisabledCallback`). Reactive — read it inside an effect and it re-runs when
|
|
176
|
+
* either source flips. (The form-disabled channel is the ADR-0013 divergence from ADR-0010's `ariaDisabled`
|
|
177
|
+
* for non-form controls.)
|
|
178
|
+
*/
|
|
179
|
+
effectiveDisabled(): boolean;
|
|
180
|
+
/**
|
|
181
|
+
* Platform reset → the `formReset()` hook (the subclass restores value ← its defaultValue; clause 2), THEN
|
|
182
|
+
* the ADR-0051 `ui-form-reset` protocol dispatch (composed, bubbling, no detail — the target IS the
|
|
183
|
+
* resetting control) so an observing `ui-field` re-suppresses its visible error. Covers BOTH reset paths:
|
|
184
|
+
* native `form.reset()` (the platform walks its FACE members' `formResetCallback`s) and a form-less
|
|
185
|
+
* provider's direct call to this same public platform callback.
|
|
186
|
+
*/
|
|
187
|
+
formResetCallback(): void;
|
|
188
|
+
/** Platform fieldset/form-disabled change → the reactive `#formDisabled` signal behind `effectiveDisabled()`. */
|
|
189
|
+
formDisabledCallback(disabled: boolean): void;
|
|
190
|
+
/** Platform state restore (navigation/autofill) → the `formStateRestore(state)` hook. */
|
|
191
|
+
formStateRestoreCallback(state: File | string | FormData | null, _mode: 'restore' | 'autocomplete'): void;
|
|
192
|
+
/** Platform association change → the no-op `formAssociated(form)` hook (an extension point). */
|
|
193
|
+
formAssociatedCallback(form: HTMLFormElement | null): void;
|
|
194
|
+
/** Restore value ← defaultValue on form reset. Default no-op — the subclass owns `value` + its defaultValue. */
|
|
195
|
+
protected formReset(): void;
|
|
196
|
+
/** Restore value from a previously-submitted/autofilled form state. Default no-op — the subclass decodes `state`. */
|
|
197
|
+
protected formStateRestore(_state: File | string | FormData | null): void;
|
|
198
|
+
/** Called when the element associates with / disassociates from a form. Default no-op extension point. */
|
|
199
|
+
protected formAssociated(_form: HTMLFormElement | null): void;
|
|
200
|
+
/** The owning `<form>`, or null. */
|
|
201
|
+
get form(): HTMLFormElement | null;
|
|
202
|
+
/** The platform's live `ValidityState` (the IDL object; distinct from the `formValidity()` verdict hook). */
|
|
203
|
+
get validity(): ValidityState;
|
|
204
|
+
/** The platform validation message (the `setValidity` message; empty when valid). */
|
|
205
|
+
get validationMessage(): string;
|
|
206
|
+
/** Whether this control is a candidate for constraint validation. */
|
|
207
|
+
get willValidate(): boolean;
|
|
208
|
+
/** Run constraint validation, firing an `invalid` event when invalid. */
|
|
209
|
+
checkValidity(): boolean;
|
|
210
|
+
/** Like `checkValidity`, and additionally reports the problem to the user (focuses the anchor). */
|
|
211
|
+
reportValidity(): boolean;
|
|
212
|
+
/**
|
|
213
|
+
* Public — a `ui-field` calls this with its label/description/error part elements; `null` clears the
|
|
214
|
+
* association (control removed from its field, or the field itself torn down). Signal-backed (the
|
|
215
|
+
* `setCustomValidity` precedent, ADR-0029 §5) — deliberately NOT a prop: elements can't ride attributes,
|
|
216
|
+
* so this stays off the attributes-as-API descriptor surface. Safe on a disconnected control (a plain
|
|
217
|
+
* signal write); the base effect applies it on next connect.
|
|
218
|
+
*/
|
|
219
|
+
setFieldLabelling(refs: FieldLabelling | null): void;
|
|
220
|
+
/** Protected reactive read — an override's forwarding effect (or a subclass's own label/message effects,
|
|
221
|
+
* e.g. text-field's wire) re-runs on association/clear. */
|
|
222
|
+
protected get fieldLabelling(): FieldLabelling | null;
|
|
223
|
+
/**
|
|
224
|
+
* Protected forwarding hook (ADR-0051 cl.2), applied by the base scope-owned effect installed in
|
|
225
|
+
* `connectedCallback`. Default: internals ARIA element reflection, GUARDED on `internals.role != null` —
|
|
226
|
+
* every internals-role control (checkbox/switch/radio/slider/calendar) wires for FREE, while a role-less
|
|
227
|
+
* host deliberately no-ops (a half-attached name on a role-less AX node is silent lossiness that can fool
|
|
228
|
+
* a probe — a part-role control, e.g. text-field, MUST override instead). Contract for an override:
|
|
229
|
+
* idempotent; handle `null` (clear); guard a not-yet-created part.
|
|
230
|
+
*/
|
|
231
|
+
protected applyFieldLabelling(refs: FieldLabelling | null): void;
|
|
232
|
+
/**
|
|
233
|
+
* Protected user-invalid gate, feeding `FormConnectDetail.userInvalid` (ADR-0050). Default `false`. A
|
|
234
|
+
* control composing `trackUserInvalid` overrides it with its tracker's gate (`() =>
|
|
235
|
+
* tracker.userInvalid()`), keeping ONE timing source — the field's error observation rides this read
|
|
236
|
+
* (jsdom cannot match `:state(user-invalid)`; the browser gate asserts the equivalence).
|
|
237
|
+
*/
|
|
238
|
+
protected formUserInvalid(): boolean;
|
|
239
|
+
}
|
|
240
|
+
export {};
|