@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,231 @@
|
|
|
1
|
+
// props.ts — typed props-as-signals (plan §5).
|
|
2
|
+
//
|
|
3
|
+
// Two halves: (1) the COMPILE-TIME typing contract — `PropType`/`PropConfig`/`prop.*`/`ReactiveProps`
|
|
4
|
+
// — that proves the headline TS bet (an `as const` enum prop declare-merges to its literal union,
|
|
5
|
+
// never widened to `string`; goals.md G2 DoD1, proven in `props-typing.test.ts`); and (2) the RUNTIME
|
|
6
|
+
// install, `finalize()`, which turns that contract into per-instance signal-backed prototype accessors
|
|
7
|
+
// (goals.md G2 DoD2). `finalize` is the FIRST dom module code to touch the `../reactive` kernel —
|
|
8
|
+
// the one allowed direction (dom → reactive). No decorators / enum / namespace (erasableSyntaxOnly).
|
|
9
|
+
//
|
|
10
|
+
// The runtime half also owns the attribute↔value REFLECTION BOUNDARY (goals.md G2 DoD2; rubric D3).
|
|
11
|
+
// The string↔typed boundary lives at EXACTLY TWO functions: `coerceAttribute` (inbound, string→typed
|
|
12
|
+
// via `PropType.from`) and `reflectOut` (outbound, typed→string via `PropType.to`). A per-instance
|
|
13
|
+
// DIRECTIONAL LOCK guards each against the other so the platform's `attributeChangedCallback` echo
|
|
14
|
+
// cannot loop: while a value→attribute reflect is in flight the inbound echo it triggers is suppressed,
|
|
15
|
+
// and while an attribute→value coercion is in flight the setter does not reflect it back out.
|
|
16
|
+
import { signal } from "../reactive/index.js";
|
|
17
|
+
const stringType = {
|
|
18
|
+
from(attr) {
|
|
19
|
+
return attr ?? '';
|
|
20
|
+
},
|
|
21
|
+
to(value) {
|
|
22
|
+
return value;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const numberType = {
|
|
26
|
+
from(attr) {
|
|
27
|
+
return attr === null || attr === '' ? null : Number(attr);
|
|
28
|
+
},
|
|
29
|
+
to(value) {
|
|
30
|
+
return value === null ? null : String(value);
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const booleanType = {
|
|
34
|
+
from(attr) {
|
|
35
|
+
return attr !== null; // presence semantics
|
|
36
|
+
},
|
|
37
|
+
to(value) {
|
|
38
|
+
return value ? '' : null;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
/** A codec for a fixed string-literal set; `from` snaps an unknown attribute back to the first member. */
|
|
42
|
+
export function enumType(values) {
|
|
43
|
+
return {
|
|
44
|
+
from(attr) {
|
|
45
|
+
return attr !== null && values.includes(attr) ? attr : values[0];
|
|
46
|
+
},
|
|
47
|
+
to(value) {
|
|
48
|
+
return value;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** A codec for an arbitrary JSON-serializable value of type `T` (round-trips via JSON). */
|
|
53
|
+
export function jsonType() {
|
|
54
|
+
return {
|
|
55
|
+
from(attr) {
|
|
56
|
+
return attr === null ? null : JSON.parse(attr);
|
|
57
|
+
},
|
|
58
|
+
to(value) {
|
|
59
|
+
return JSON.stringify(value);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** The three fixed codecs (the parametric `enumType`/`jsonType` are factories above). */
|
|
64
|
+
export const Types = {
|
|
65
|
+
string: stringType,
|
|
66
|
+
number: numberType,
|
|
67
|
+
boolean: booleanType,
|
|
68
|
+
};
|
|
69
|
+
export const prop = {
|
|
70
|
+
string(def = '') {
|
|
71
|
+
return { type: Types.string, default: def };
|
|
72
|
+
},
|
|
73
|
+
number(def = null) {
|
|
74
|
+
return { type: Types.number, default: def };
|
|
75
|
+
},
|
|
76
|
+
boolean(def = false) {
|
|
77
|
+
return { type: Types.boolean, default: def };
|
|
78
|
+
},
|
|
79
|
+
enum(values, def) {
|
|
80
|
+
return { type: enumType(values), default: def };
|
|
81
|
+
},
|
|
82
|
+
json(def) {
|
|
83
|
+
return { type: jsonType(), default: def };
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
const STORE = new WeakMap();
|
|
87
|
+
const FINALIZED = new WeakSet();
|
|
88
|
+
function signalFor(instance, name, config) {
|
|
89
|
+
let signals = STORE.get(instance);
|
|
90
|
+
if (!signals) {
|
|
91
|
+
signals = new Map();
|
|
92
|
+
STORE.set(instance, signals);
|
|
93
|
+
}
|
|
94
|
+
let sig = signals.get(name);
|
|
95
|
+
if (!sig) {
|
|
96
|
+
sig = signal(config.default); // lazy init to the declared default
|
|
97
|
+
signals.set(name, sig);
|
|
98
|
+
}
|
|
99
|
+
return sig;
|
|
100
|
+
}
|
|
101
|
+
const GUARD = new WeakMap();
|
|
102
|
+
function guardOf(instance) {
|
|
103
|
+
let g = GUARD.get(instance);
|
|
104
|
+
if (!g) {
|
|
105
|
+
g = { outbound: new Set(), inbound: new Set() };
|
|
106
|
+
GUARD.set(instance, g);
|
|
107
|
+
}
|
|
108
|
+
return g;
|
|
109
|
+
}
|
|
110
|
+
function isCrossing(instance, dir, name) {
|
|
111
|
+
return GUARD.get(instance)?.[dir].has(name) ?? false;
|
|
112
|
+
}
|
|
113
|
+
/** Resolve a prop's attribute name: `attribute: false` ⇒ no attribute (property-only); else the override or the prop name. */
|
|
114
|
+
function attrNameOf(config, name) {
|
|
115
|
+
return config.attribute === false ? null : (config.attribute ?? name);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Outbound seam (the second of the two boundary functions): cross a TYPED value → its attribute string
|
|
119
|
+
* via the prop's `PropType.to` codec, and write it to the host attribute exactly once. Holds the
|
|
120
|
+
* `outbound` directional lock across the write so the synchronous `attributeChangedCallback` echo the
|
|
121
|
+
* platform fires re-enters `coerceAttribute` LOCKED and is suppressed — the loop is broken at the gate,
|
|
122
|
+
* not after a redundant round-trip. A `null` from `to` (boolean-false / number-null / property-only)
|
|
123
|
+
* removes the attribute. Called only from the reflect setter, only when `config.reflect` is set.
|
|
124
|
+
*/
|
|
125
|
+
function reflectOut(instance, name, config, value) {
|
|
126
|
+
const attr = attrNameOf(config, name);
|
|
127
|
+
if (attr === null)
|
|
128
|
+
return; // property-only prop: nothing to reflect to
|
|
129
|
+
const host = instance; // a reflect prop's host is an Element; setAttribute is a DOM API, not an import
|
|
130
|
+
const guard = guardOf(instance);
|
|
131
|
+
guard.outbound.add(name);
|
|
132
|
+
try {
|
|
133
|
+
const serialized = config.type.to(value); // OUTBOUND boundary: typed → string
|
|
134
|
+
if (serialized === null)
|
|
135
|
+
host.removeAttribute(attr);
|
|
136
|
+
else
|
|
137
|
+
host.setAttribute(attr, serialized);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
guard.outbound.delete(name);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Install signal-backed prototype accessors from `Ctor.props`. Idempotent (safe to call once per class). */
|
|
144
|
+
export function finalize(ctor) {
|
|
145
|
+
if (FINALIZED.has(ctor))
|
|
146
|
+
return;
|
|
147
|
+
FINALIZED.add(ctor);
|
|
148
|
+
const props = ctor.props;
|
|
149
|
+
if (!props)
|
|
150
|
+
return;
|
|
151
|
+
for (const name of Object.keys(props)) {
|
|
152
|
+
const config = props[name];
|
|
153
|
+
Object.defineProperty(ctor.prototype, name, {
|
|
154
|
+
configurable: true,
|
|
155
|
+
enumerable: true,
|
|
156
|
+
get() {
|
|
157
|
+
return signalFor(this, name, config).value;
|
|
158
|
+
},
|
|
159
|
+
set(next) {
|
|
160
|
+
signalFor(this, name, config).value = next;
|
|
161
|
+
// Reflect value→attribute exactly once, unless this write IS an inbound coercion (the
|
|
162
|
+
// directional lock): an attribute→value crossing must not bounce straight back out.
|
|
163
|
+
if (config.reflect && !isCrossing(this, 'inbound', name)) {
|
|
164
|
+
reflectOut(this, name, config, next);
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Inbound seam (the first of the two boundary functions): cross an attribute STRING (or `null` for
|
|
172
|
+
* absence) into the typed prop signal via the prop's `PropType.from` codec, and return the coerced
|
|
173
|
+
* value. This is the primitive the element slice's `attributeChangedCallback` (e-attrs, later) composes
|
|
174
|
+
* on — p-install owns the per-instance store + the string→typed crossing; it does NOT own the platform
|
|
175
|
+
* callback. A no-op for an unknown prop name.
|
|
176
|
+
*
|
|
177
|
+
* Two directional-lock interactions (rubric D3): (1) if an outbound reflect for this prop is in flight,
|
|
178
|
+
* this call is that reflect's own platform echo — suppress it (return `undefined`) so it cannot loop.
|
|
179
|
+
* (2) Otherwise write through the installed accessor under the `inbound` lock, so the setter applies the
|
|
180
|
+
* value but does NOT reflect it straight back out. The write goes through the setter (not the raw signal)
|
|
181
|
+
* precisely so that symmetric guard is load-bearing: one write path, the lock decides whether it reflects.
|
|
182
|
+
*/
|
|
183
|
+
export function coerceAttribute(instance, ctor, name, attr) {
|
|
184
|
+
const config = ctor.props?.[name];
|
|
185
|
+
if (!config)
|
|
186
|
+
return undefined;
|
|
187
|
+
if (isCrossing(instance, 'outbound', name))
|
|
188
|
+
return undefined; // our own reflect's echo → suppress (no loop)
|
|
189
|
+
const value = config.type.from(attr); // INBOUND boundary: string → typed
|
|
190
|
+
const guard = guardOf(instance);
|
|
191
|
+
guard.inbound.add(name);
|
|
192
|
+
try {
|
|
193
|
+
;
|
|
194
|
+
instance[name] = value; // through the accessor; the inbound lock stops a reflect-back
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
guard.inbound.delete(name);
|
|
198
|
+
}
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
// ── attribute↔prop NAME mapping (the e-attrs seam) ───────────────────────────
|
|
202
|
+
//
|
|
203
|
+
// The platform's `observedAttributes` / `attributeChangedCallback` speak ATTRIBUTE names; `coerceAttribute`
|
|
204
|
+
// speaks PROP names. These two functions own the attribute↔prop NAME mapping (via the same `attrNameOf`
|
|
205
|
+
// the reflect path uses), so the string↔typed boundary AND the name mapping stay single-sourced in
|
|
206
|
+
// props.ts; element.ts (e-attrs) is a thin platform-callback adapter over them. Internal seam — used by
|
|
207
|
+
// element.ts, NOT re-exported from the dom barrel.
|
|
208
|
+
/** The attribute names to observe for a finalized ctor: each prop's attribute name, minus property-only props. */
|
|
209
|
+
export function observedAttributesFor(ctor) {
|
|
210
|
+
const props = ctor.props;
|
|
211
|
+
if (!props)
|
|
212
|
+
return [];
|
|
213
|
+
const names = [];
|
|
214
|
+
for (const name of Object.keys(props)) {
|
|
215
|
+
const attr = attrNameOf(props[name], name);
|
|
216
|
+
if (attr !== null)
|
|
217
|
+
names.push(attr);
|
|
218
|
+
}
|
|
219
|
+
return names;
|
|
220
|
+
}
|
|
221
|
+
/** Reverse the mapping: the prop name owning a given attribute name (respecting `attribute` overrides), or `undefined`. */
|
|
222
|
+
export function propForAttribute(ctor, attr) {
|
|
223
|
+
const props = ctor.props;
|
|
224
|
+
if (!props)
|
|
225
|
+
return undefined;
|
|
226
|
+
for (const name of Object.keys(props)) {
|
|
227
|
+
if (attrNameOf(props[name], name) === attr)
|
|
228
|
+
return name;
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type DirectiveResult } from './template.js';
|
|
2
|
+
/**
|
|
3
|
+
* `repeat(items, keyFn, template)` — render a keyed list into a child hole, reusing DOM by key across data
|
|
4
|
+
* changes. `keyFn(item, index)` must return a value UNIQUE per item (a duplicate throws); `template(item,
|
|
5
|
+
* index)` renders each item (text, a `TemplateResult`, …). A reorder MOVES surviving items by identity
|
|
6
|
+
* (node identity preserved always; focus / selection preserved where native `moveBefore` is supported,
|
|
7
|
+
* identity-only fallback otherwise — ADR-0022), while append / remove-tail / a stable prefix cost zero
|
|
8
|
+
* moves. Prefer `repeat` over a positional array hole when items carry identity or interactive state.
|
|
9
|
+
*/
|
|
10
|
+
export declare function repeat<T>(items: Iterable<T>, keyFn: (item: T, index: number) => unknown, template: (item: T, index: number) => unknown): DirectiveResult;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// repeat.ts — the `repeat` directive (plan §6; G3 slice 1, the keyed list reconcile).
|
|
2
|
+
//
|
|
3
|
+
// `repeat(items, keyFn, template)` is a DOM-owning child directive (returns `NO_COMMIT`): it drives one
|
|
4
|
+
// sub-`ChildPart` per item and reconciles the keyed list itself, REUSING the part engine rather than any
|
|
5
|
+
// kernel/binding primitive — list reconciliation is pure DOM + key bookkeeping, it owns no effect (so it
|
|
6
|
+
// needs no scope_seam / `ctx`). The reconcile is a head/tail two-pointer trim plus a key→old-index map for
|
|
7
|
+
// the disordered middle: a surviving key REUSES its sub-part (commit the new template value → the part's
|
|
8
|
+
// `Object.is` skip), a moved key relocates its sub-part by IDENTITY via `moveBefore`, a new key creates a
|
|
9
|
+
// sub-part, a removed key disposes one, and a duplicate key THROWS. Append / remove-tail / a stable prefix
|
|
10
|
+
// perform ZERO moves of existing nodes (the head/tail fast paths never reach the move/map branches).
|
|
11
|
+
//
|
|
12
|
+
// Reorder guarantee — TWO-TIER (ADR-0022, the `moveBefore` seam): DOM node IDENTITY is preserved across a
|
|
13
|
+
// reorder ALWAYS, on every engine (the same node objects move, never re-created — what the keyed reconcile
|
|
14
|
+
// guarantees). ELEMENT STATE — focus / selection / transitions — additionally survives where the platform's
|
|
15
|
+
// native `Node.prototype.moveBefore` is supported (an atomic move); on engines without it the seam falls back
|
|
16
|
+
// to detach+reinsert: identity-only, focus NOT preserved (graceful degradation, no regression).
|
|
17
|
+
//
|
|
18
|
+
// SCOPE LIMIT (from the seam's `moveBefore`): a sub-part's move relocates its owned nodes + anchor —
|
|
19
|
+
// complete for an item rendering text or a SINGLE template (the norm). An item whose template renders an
|
|
20
|
+
// ARRAY or a nested directive keeps deeper content in sub-anchors `moveBefore` does not track; multi-node
|
|
21
|
+
// item moves are out of this slice's scope.
|
|
22
|
+
//
|
|
23
|
+
// Imports only the directive seam from `./template.ts` (same dom layer) — inward-only layering; not
|
|
24
|
+
// re-exported from this file (the dom barrel re-exports `repeat`).
|
|
25
|
+
import { Directive, directive, NO_COMMIT } from "./template.js";
|
|
26
|
+
/** Render a key for a clear duplicate-key error without throwing on a symbol / object key. */
|
|
27
|
+
function describeKey(key) {
|
|
28
|
+
if (typeof key === 'string')
|
|
29
|
+
return JSON.stringify(key);
|
|
30
|
+
if (typeof key === 'symbol')
|
|
31
|
+
return key.toString();
|
|
32
|
+
return String(key);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The `repeat` directive instance. Holds the committed list as two aligned arrays — `#keys` (the committed
|
|
36
|
+
* keys, in DOM order) and `#parts` (their sub-parts) — and reconciles them against the new keys on each
|
|
37
|
+
* commit of the SAME hole (state threads across commits). Owns no effect; ignores `ctx`.
|
|
38
|
+
*/
|
|
39
|
+
class RepeatDirective extends Directive {
|
|
40
|
+
#keys = [];
|
|
41
|
+
#parts = [];
|
|
42
|
+
update(args) {
|
|
43
|
+
const items = args[0];
|
|
44
|
+
const keyFn = args[1];
|
|
45
|
+
const template = args[2];
|
|
46
|
+
// Pass 1 — materialize the new keys + values, throwing on a DUPLICATE key BEFORE any DOM mutation, so a
|
|
47
|
+
// bad input never leaves the list half-reconciled. Keys must be unique for the reconcile to be a bijection.
|
|
48
|
+
const newKeys = [];
|
|
49
|
+
const newValues = [];
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
let index = 0;
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
const key = keyFn(item, index);
|
|
54
|
+
if (seen.has(key)) {
|
|
55
|
+
throw new Error(`repeat: duplicate key ${describeKey(key)} — every item's key must be unique within one render.`);
|
|
56
|
+
}
|
|
57
|
+
seen.add(key);
|
|
58
|
+
newKeys.push(key);
|
|
59
|
+
newValues.push(template(item, index));
|
|
60
|
+
index++;
|
|
61
|
+
}
|
|
62
|
+
this.#reconcile(newKeys, newValues);
|
|
63
|
+
return NO_COMMIT; // repeat owns its DOM via the per-item sub-parts → the host part commits nothing
|
|
64
|
+
}
|
|
65
|
+
/** Tear down every sub-part (each removes its content + anchor). The seam isolates a throwing disposer. */
|
|
66
|
+
dispose() {
|
|
67
|
+
for (const part of this.#parts)
|
|
68
|
+
part.dispose();
|
|
69
|
+
this.#keys = [];
|
|
70
|
+
this.#parts = [];
|
|
71
|
+
}
|
|
72
|
+
/** A move reference: the START node of the part a relocation lands before, or the host hole's end anchor. */
|
|
73
|
+
#refNode(part) {
|
|
74
|
+
return part ? part.startNode : this.endNode;
|
|
75
|
+
}
|
|
76
|
+
/** Create a sub-part, commit its value, and position it before `before` (or leave it at the tail). */
|
|
77
|
+
#mount(value, before) {
|
|
78
|
+
const part = this.createPart(); // its anchor lands just before the host anchor → the tail
|
|
79
|
+
part.commit(value);
|
|
80
|
+
if (before)
|
|
81
|
+
part.moveBefore(before.startNode); // a middle insert moves the (new) node into place
|
|
82
|
+
return part;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The keyed reconcile (Levitin/`lit-html` style): a head/tail two-pointer trims the unchanged prefix and
|
|
86
|
+
* suffix in place (committing each surviving key's new value → the part's `Object.is` skip, ZERO moves),
|
|
87
|
+
* the two cross checks catch a head↔tail swap, and a key→old-index map resolves the disordered middle —
|
|
88
|
+
* reusing a surviving key's sub-part (moved by IDENTITY), creating one for a new key, disposing one for a
|
|
89
|
+
* removed key. Append / remove-tail / a stable prefix never leave the head/tail fast paths → zero moves.
|
|
90
|
+
*/
|
|
91
|
+
#reconcile(newKeys, newValues) {
|
|
92
|
+
const oldKeys = this.#keys;
|
|
93
|
+
const old = this.#parts.slice(); // working copy; entries are nulled as consumed
|
|
94
|
+
const newParts = new Array(newKeys.length).fill(null);
|
|
95
|
+
let oldHead = 0;
|
|
96
|
+
let oldTail = old.length - 1;
|
|
97
|
+
let newHead = 0;
|
|
98
|
+
let newTail = newKeys.length - 1;
|
|
99
|
+
// The maps over the disordered windows, built lazily on the FIRST middle case and reused thereafter
|
|
100
|
+
// (the windows only shrink, so the first window is a superset of every later one).
|
|
101
|
+
let oldKeyToIndex = null;
|
|
102
|
+
let newKeySet = null;
|
|
103
|
+
while (oldHead <= oldTail && newHead <= newTail) {
|
|
104
|
+
if (old[oldHead] === null) {
|
|
105
|
+
oldHead++; // a part consumed out of order by the map branch — skip its hole
|
|
106
|
+
}
|
|
107
|
+
else if (old[oldTail] === null) {
|
|
108
|
+
oldTail--;
|
|
109
|
+
}
|
|
110
|
+
else if (oldKeys[oldHead] === newKeys[newHead]) {
|
|
111
|
+
// Heads match — update in place, no move.
|
|
112
|
+
old[oldHead].commit(newValues[newHead]);
|
|
113
|
+
newParts[newHead] = old[oldHead];
|
|
114
|
+
oldHead++;
|
|
115
|
+
newHead++;
|
|
116
|
+
}
|
|
117
|
+
else if (oldKeys[oldTail] === newKeys[newTail]) {
|
|
118
|
+
// Tails match — update in place, no move.
|
|
119
|
+
old[oldTail].commit(newValues[newTail]);
|
|
120
|
+
newParts[newTail] = old[oldTail];
|
|
121
|
+
oldTail--;
|
|
122
|
+
newTail--;
|
|
123
|
+
}
|
|
124
|
+
else if (oldKeys[oldHead] === newKeys[newTail]) {
|
|
125
|
+
// Old head became the new tail — move it before the already-placed part after the new tail.
|
|
126
|
+
const part = old[oldHead];
|
|
127
|
+
part.commit(newValues[newTail]);
|
|
128
|
+
part.moveBefore(this.#refNode(newParts[newTail + 1]));
|
|
129
|
+
newParts[newTail] = part;
|
|
130
|
+
oldHead++;
|
|
131
|
+
newTail--;
|
|
132
|
+
}
|
|
133
|
+
else if (oldKeys[oldTail] === newKeys[newHead]) {
|
|
134
|
+
// Old tail became the new head — move it before the current head part.
|
|
135
|
+
const part = old[oldTail];
|
|
136
|
+
part.commit(newValues[newHead]);
|
|
137
|
+
part.moveBefore(this.#refNode(old[oldHead]));
|
|
138
|
+
newParts[newHead] = part;
|
|
139
|
+
oldTail--;
|
|
140
|
+
newHead++;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
if (oldKeyToIndex === null) {
|
|
144
|
+
oldKeyToIndex = new Map();
|
|
145
|
+
for (let i = oldHead; i <= oldTail; i++)
|
|
146
|
+
oldKeyToIndex.set(oldKeys[i], i);
|
|
147
|
+
newKeySet = new Set(newKeys.slice(newHead, newTail + 1));
|
|
148
|
+
}
|
|
149
|
+
if (!newKeySet.has(oldKeys[oldHead])) {
|
|
150
|
+
old[oldHead].dispose(); // the head key is gone → remove it
|
|
151
|
+
oldHead++;
|
|
152
|
+
}
|
|
153
|
+
else if (!newKeySet.has(oldKeys[oldTail])) {
|
|
154
|
+
old[oldTail].dispose();
|
|
155
|
+
oldTail--;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// The new head's key survives somewhere in the old middle — reuse that sub-part (or create one
|
|
159
|
+
// if it is genuinely new), moving it before the current head part.
|
|
160
|
+
const wantKey = newKeys[newHead];
|
|
161
|
+
const oldIndex = oldKeyToIndex.has(wantKey) ? oldKeyToIndex.get(wantKey) : -1;
|
|
162
|
+
const reusable = oldIndex >= 0 ? old[oldIndex] : null;
|
|
163
|
+
if (reusable === null) {
|
|
164
|
+
newParts[newHead] = this.#mount(newValues[newHead], old[oldHead]);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
reusable.commit(newValues[newHead]);
|
|
168
|
+
reusable.moveBefore(this.#refNode(old[oldHead]));
|
|
169
|
+
old[oldIndex] = null; // consumed — its old hole is now a skip
|
|
170
|
+
newParts[newHead] = reusable;
|
|
171
|
+
}
|
|
172
|
+
newHead++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Any new keys left are appended/inserted before the part after the new tail (the tail run lands before
|
|
177
|
+
// the host anchor in ascending order; a middle run moves before its fixed successor).
|
|
178
|
+
while (newHead <= newTail) {
|
|
179
|
+
newParts[newHead] = this.#mount(newValues[newHead], newParts[newTail + 1] ?? null);
|
|
180
|
+
newHead++;
|
|
181
|
+
}
|
|
182
|
+
// Any old parts left are removed (their keys are gone).
|
|
183
|
+
while (oldHead <= oldTail) {
|
|
184
|
+
old[oldHead]?.dispose();
|
|
185
|
+
oldHead++;
|
|
186
|
+
}
|
|
187
|
+
this.#keys = newKeys;
|
|
188
|
+
this.#parts = newParts; // every slot was filled by the reconcile above
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const repeatDirective = directive(RepeatDirective);
|
|
192
|
+
/**
|
|
193
|
+
* `repeat(items, keyFn, template)` — render a keyed list into a child hole, reusing DOM by key across data
|
|
194
|
+
* changes. `keyFn(item, index)` must return a value UNIQUE per item (a duplicate throws); `template(item,
|
|
195
|
+
* index)` renders each item (text, a `TemplateResult`, …). A reorder MOVES surviving items by identity
|
|
196
|
+
* (node identity preserved always; focus / selection preserved where native `moveBefore` is supported,
|
|
197
|
+
* identity-only fallback otherwise — ADR-0022), while append / remove-tail / a stable prefix cost zero
|
|
198
|
+
* moves. Prefer `repeat` over a positional array hole when items carry identity or interactive state.
|
|
199
|
+
*/
|
|
200
|
+
export function repeat(items, keyFn, template) {
|
|
201
|
+
return repeatDirective(items, keyFn, template);
|
|
202
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inert product of `html\`…\`` / `svg\`…\``: the call site's frozen `strings`, this render's dynamic
|
|
3
|
+
* `values`, and the parse MODE (`svg` fragments parse in the SVG namespace). No DOM work happens at the
|
|
4
|
+
* call site — preparation is lazy, at render.
|
|
5
|
+
*/
|
|
6
|
+
export declare class TemplateResult {
|
|
7
|
+
readonly strings: TemplateStringsArray;
|
|
8
|
+
readonly values: readonly unknown[];
|
|
9
|
+
readonly svg: boolean;
|
|
10
|
+
constructor(strings: TemplateStringsArray, values: readonly unknown[], svg?: boolean);
|
|
11
|
+
}
|
|
12
|
+
/** Tagged-template tag → an inert HTML `TemplateResult`. */
|
|
13
|
+
export declare function html(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult;
|
|
14
|
+
/** Like `html`, but the fragment parses in the SVG NAMESPACE — `<circle>`/`<path>`/… become real SVGElements. */
|
|
15
|
+
export declare function svg(strings: TemplateStringsArray, ...values: unknown[]): TemplateResult;
|
|
16
|
+
/**
|
|
17
|
+
* Where one hole binds. A child position (the marker comment), or one of the attribute-position kinds
|
|
18
|
+
* routed by binding sigil: `attr` (string attribute), `bool` (`?name`), `prop` (`.name`, a DOM property),
|
|
19
|
+
* `event` (`@name`). `nodeIndex` is the host node's pre-order index; `name` is the SOURCE name (sigil
|
|
20
|
+
* stripped) — taken from the template strings, NOT the parsed DOM, so a `.prop`'s camelCase survives the
|
|
21
|
+
* HTML parser's attribute-name lowercasing.
|
|
22
|
+
*/
|
|
23
|
+
type PartInfo = {
|
|
24
|
+
kind: 'child';
|
|
25
|
+
nodeIndex: number;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'attr';
|
|
28
|
+
nodeIndex: number;
|
|
29
|
+
name: string;
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'bool';
|
|
32
|
+
nodeIndex: number;
|
|
33
|
+
name: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'prop';
|
|
36
|
+
nodeIndex: number;
|
|
37
|
+
name: string;
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'event';
|
|
40
|
+
nodeIndex: number;
|
|
41
|
+
name: string;
|
|
42
|
+
};
|
|
43
|
+
interface PreparedTemplate {
|
|
44
|
+
template: HTMLTemplateElement;
|
|
45
|
+
parts: PartInfo[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Prepare a call site's `strings`: build the marker HTML, parse it into one `<template>`, and walk it
|
|
49
|
+
* once to build the parts manifest (cleaning attribute markers). Memoized by `strings` IDENTITY — a
|
|
50
|
+
* re-render of the same call site hits the cache and re-parses nothing. Exported for the cache probes
|
|
51
|
+
* (an internal seam; NOT re-exported from the dom barrel).
|
|
52
|
+
*/
|
|
53
|
+
export declare function prepare(strings: TemplateStringsArray, svg?: boolean): PreparedTemplate;
|
|
54
|
+
declare const DIRECTIVE_BRAND: unique symbol;
|
|
55
|
+
/**
|
|
56
|
+
* The render-side handle a directive uses to reach the host's CONNECTION SCOPE — the scope_seam. A host
|
|
57
|
+
* (`UIElement`) satisfies it structurally via its scope-owned `effect`. Threaded read-only into the render
|
|
58
|
+
* path so a per-hole directive effect (e.g. `watch`) is owned by the connection scope, NOT the transient
|
|
59
|
+
* render effect — which re-runs (scheduler flush) with no active owner, so a bare `effect()` created there
|
|
60
|
+
* would leak. `ctx.effect` re-establishes scope ownership explicitly on every render.
|
|
61
|
+
*
|
|
62
|
+
* Install-once invariant for an effect-owning directive: gate the install on a flag, but re-assert that flag
|
|
63
|
+
* at the TOP of the effect body — NOT once after `ctx.effect`. The kernel runs an effect's cleanup before
|
|
64
|
+
* EVERY re-run (not only on disposal), so a cleanup that clears the flag clears it on normal re-runs too;
|
|
65
|
+
* re-asserting in the body nets `true` on a re-run and `false` only on disposal, so a later host re-render
|
|
66
|
+
* won't double-install. (See `watch.ts`; the `scope_seam` probe in template-directives.test.ts pins it.)
|
|
67
|
+
*/
|
|
68
|
+
export interface RenderContext {
|
|
69
|
+
effect(fn: () => void | (() => void)): () => void;
|
|
70
|
+
}
|
|
71
|
+
/** A directive's `update` returns this when it manages its OWN DOM (via sub-parts) — the host part commits nothing. */
|
|
72
|
+
export declare const NO_COMMIT: unique symbol;
|
|
73
|
+
/**
|
|
74
|
+
* A directive instance: `update` runs on every commit of its hole (threading state across commits of the
|
|
75
|
+
* SAME hole), returning a value the host part commits through its normal child path OR `NO_COMMIT` when the
|
|
76
|
+
* directive drives its own DOM; `dispose` tears it down. The host `ChildPart` is handed to the constructor
|
|
77
|
+
* so a DOM-owning directive can drive sub-parts via the `protected` primitives below. Subclassed by the
|
|
78
|
+
* rider files (repeat.ts/watch.ts) — never instantiated by consumers; reached only through `directive()`.
|
|
79
|
+
*/
|
|
80
|
+
export declare abstract class Directive {
|
|
81
|
+
#private;
|
|
82
|
+
constructor(part: ChildPart);
|
|
83
|
+
/**
|
|
84
|
+
* Create a sub-`ChildPart` rendering into THIS directive's hole (its anchor sits just before the host
|
|
85
|
+
* hole's anchor). `watch` drives one inner part; `repeat` drives one per item. The returned part's
|
|
86
|
+
* `commit` / `dispose` / `moveBefore` / `startNode` are the directive's DOM primitives.
|
|
87
|
+
*/
|
|
88
|
+
protected createPart(): ChildPart;
|
|
89
|
+
/** The host hole's end boundary — sub-part content orders BEFORE this node (a `repeat` reorder reference). */
|
|
90
|
+
protected get endNode(): ChildNode;
|
|
91
|
+
/** Recognize the directive on each commit of the hole; return a value to commit, or `NO_COMMIT`. */
|
|
92
|
+
abstract update(args: readonly unknown[], ctx?: RenderContext): unknown;
|
|
93
|
+
/** Tear down owned DOM/effects. Default no-op; the part isolates a throw so sibling teardown still runs. */
|
|
94
|
+
dispose(): void;
|
|
95
|
+
}
|
|
96
|
+
type DirectiveConstructor = new (part: ChildPart) => Directive;
|
|
97
|
+
/** The inert product of a directive factory call — like `TemplateResult`, no DOM work at the call site. */
|
|
98
|
+
export interface DirectiveResult {
|
|
99
|
+
readonly [DIRECTIVE_BRAND]: true;
|
|
100
|
+
readonly ctor: DirectiveConstructor;
|
|
101
|
+
readonly args: readonly unknown[];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Wrap a `Directive` subclass into a factory: `const repeat = directive(RepeatDirective)`. Calling the
|
|
105
|
+
* factory returns an inert branded `DirectiveResult` that a child hole routes to its (per-hole, state-
|
|
106
|
+
* threading) instance. Rider files import this from template.ts — the one extension point.
|
|
107
|
+
*/
|
|
108
|
+
export declare function directive(ctor: DirectiveConstructor): (...args: unknown[]) => DirectiveResult;
|
|
109
|
+
interface Part {
|
|
110
|
+
commit(value: unknown, ctx?: RenderContext): void;
|
|
111
|
+
}
|
|
112
|
+
/** A child hole: commits a primitive (text, updated in place), a nested `TemplateResult`, an array, or a directive. */
|
|
113
|
+
declare class ChildPart implements Part {
|
|
114
|
+
#private;
|
|
115
|
+
constructor(anchor: Comment);
|
|
116
|
+
commit(value: unknown, ctx?: RenderContext): void;
|
|
117
|
+
/** Remove this part's content + its directive AND its own anchor — for an array sub-part being dropped. */
|
|
118
|
+
dispose(): void;
|
|
119
|
+
/** Create a sub-part whose anchor sits just before THIS part's anchor — a directive's DOM primitive. */
|
|
120
|
+
createChild(): ChildPart;
|
|
121
|
+
/** This part's end boundary — a directive orders sub-part content before it. */
|
|
122
|
+
get endNode(): ChildNode;
|
|
123
|
+
/** The first DOM node of this part's range (its first owned node, else its anchor) — a reorder reference. */
|
|
124
|
+
get startNode(): ChildNode;
|
|
125
|
+
/**
|
|
126
|
+
* Relocate this part's owned nodes + anchor to just before `ref`. TWO-TIER guarantee (ADR-0022): NODE
|
|
127
|
+
* IDENTITY is preserved always, on every engine (the same node objects move, never re-created). ELEMENT
|
|
128
|
+
* STATE — focus, selection, transitions, `<iframe>`/media — survives ONLY on the native path, where the
|
|
129
|
+
* parent supports `Node.prototype.moveBefore` (an atomic move that never detaches the node from the
|
|
130
|
+
* document); on the `before()` fallback (native absent) the nodes are detached + re-inserted, so identity
|
|
131
|
+
* is kept but focus/state are NOT (graceful degradation — the reconcile stays correct, only the focus
|
|
132
|
+
* nicety is absent). Covers a part holding text or a single nested template (the `repeat` item norm); a
|
|
133
|
+
* part holding an array/nested directive keeps deeper content in sub-anchors `#nodes` does not track.
|
|
134
|
+
*
|
|
135
|
+
* Caller invariant (repeat): `ref` and the moving `#nodes`/`#anchor` always share the same connected
|
|
136
|
+
* parent (the directive's container) — native `moveBefore`'s happy path, so no `try/catch` is needed; a
|
|
137
|
+
* future caller that violated same-parent/connected would get a loud `HierarchyRequestError`, not a
|
|
138
|
+
* silent mis-move.
|
|
139
|
+
*/
|
|
140
|
+
moveBefore(ref: ChildNode): void;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Commit `result` into `container`. The first render of a given `strings` at a container instantiates the
|
|
144
|
+
* prepared template; a re-render with the SAME `strings` reuses that instance and writes only the changed
|
|
145
|
+
* holes (per-part `Object.is`). A different `strings` replaces the container's content. The optional `ctx`
|
|
146
|
+
* (the host's scope-owned `effect`) threads to directives so a per-hole effect (`watch`) is scope-owned;
|
|
147
|
+
* 2-arg calls keep working (`ctx` undefined → directives that need it simply have none).
|
|
148
|
+
*/
|
|
149
|
+
export declare function render(result: TemplateResult, container: Node, ctx?: RenderContext): void;
|
|
150
|
+
/**
|
|
151
|
+
* Mount a kernel directive IMPERATIVELY (ADR-0023): commit `result` into `container` and return a teardown
|
|
152
|
+
* that disposes the directive (+ its sub-parts), clears its content, and removes the mount anchor. Reuses
|
|
153
|
+
* the SAME `ChildPart` engine `render()` rides — no parallel commit path: an anchor comment is appended to
|
|
154
|
+
* `container` (the directive's content commits as the anchor's previous siblings → inside `container`,
|
|
155
|
+
* leaving any pre-existing content untouched), a `ChildPart` is bound to it, and `result` is committed
|
|
156
|
+
* through the normal directive route. `ctx` threads the connection scope, so a per-hole effect (the `watch`
|
|
157
|
+
* pattern) stays scope-owned exactly as under `render()`. The public host for imperative consumers (the a2ui
|
|
158
|
+
* renderer) that need a kernel directive — e.g. `repeat` — WITHOUT the private `html\`\`` template entry.
|
|
159
|
+
*/
|
|
160
|
+
export declare function mount(result: DirectiveResult, container: Node, ctx?: RenderContext): () => void;
|
|
161
|
+
export {};
|