@adia-ai/web-components 0.8.44 → 0.8.45
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/CHANGELOG.md +24 -0
- package/components/context-menu/context-menu.a2ui.json +8 -3
- package/components/context-menu/context-menu.class.js +46 -5
- package/components/context-menu/context-menu.d.ts +6 -3
- package/components/context-menu/context-menu.examples.md +2 -2
- package/components/context-menu/context-menu.yaml +22 -5
- package/components/nav/nav.a2ui.json +2 -2
- package/components/nav/nav.css +1 -1
- package/components/nav/nav.d.ts +1 -1
- package/components/nav/nav.yaml +14 -3
- package/components/nav-group/nav-group.css +37 -3
- package/components/pagination/pagination.class.js +52 -22
- package/components/search/search.class.js +39 -5
- package/components/select/select.a2ui.json +5 -0
- package/components/select/select.class.js +20 -0
- package/components/select/select.css +25 -0
- package/components/select/select.d.ts +2 -0
- package/components/select/select.yaml +12 -0
- package/components/table/cell-types.js +9 -0
- package/components/table/table.class.js +247 -33
- package/components/table/table.css +7 -4
- package/components/table/table.yaml +6 -1
- package/components/table-toolbar/table-toolbar.a2ui.json +15 -0
- package/components/table-toolbar/table-toolbar.class.js +61 -17
- package/components/table-toolbar/table-toolbar.css +34 -0
- package/components/table-toolbar/table-toolbar.d.ts +10 -0
- package/components/table-toolbar/table-toolbar.yaml +41 -9
- package/core/data-stream.js +37 -2
- package/core/index.d.ts +1 -0
- package/core/index.js +1 -0
- package/core/provider.d.ts +9 -13
- package/core/provider.js +9 -113
- package/core/store.d.ts +46 -0
- package/core/store.js +89 -0
- package/custom-elements.json +54 -4
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/theme-provider.min.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +93 -93
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -1
- package/styles/api/sizing.css +46 -0
|
@@ -29,11 +29,22 @@
|
|
|
29
29
|
* Popovers use the platform Popover API + core/anchor.js, the same primitives
|
|
30
30
|
* that menu-ui / popover-ui / toolbar-ui already use in this package.
|
|
31
31
|
*
|
|
32
|
-
* State flow
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
32
|
+
* State flow — events-only interaction contract (gh#1764/#1780, ADR-0079;
|
|
33
|
+
* mirrors chart-legend-ui↔chart-ui's own bubbling-CustomEvent model). The
|
|
34
|
+
* toolbar never writes a property or calls a method on the bound table
|
|
35
|
+
* directly — every command is a `toolbar-*` CustomEvent dispatched AT the
|
|
36
|
+
* resolved target, which table.class.js listens for in its own
|
|
37
|
+
* connected()/disconnected() and applies via its existing public surface:
|
|
38
|
+
* search → `toolbar-search` { value } → table.search =
|
|
39
|
+
* filters → `toolbar-filter-set` { key, value, op } → table.setFilter()
|
|
40
|
+
* filters clear → `toolbar-filter-clear` {} → table.clearFilters()
|
|
41
|
+
* sort → simulated click on table's [data-sort-key] header (already
|
|
42
|
+
* event-shaped — a real DOM event dispatched at the target,
|
|
43
|
+
* not a property write; left as-is, see gh#1780's PR notes)
|
|
44
|
+
* column hidden → `toolbar-columns-set` { columns } → table.columns =
|
|
45
|
+
* page size → `toolbar-paginate` { pageSize } → table.paginate =
|
|
46
|
+
* The table→toolbar direction (`sort`, `filter-change` listened on #target)
|
|
47
|
+
* was already events-only and is unchanged.
|
|
37
48
|
*/
|
|
38
49
|
|
|
39
50
|
import { UIElement } from '../../core/element.js';
|
|
@@ -846,11 +857,16 @@ export class UITableToolbar extends UIElement {
|
|
|
846
857
|
#onPageSizeChange = (e) => {
|
|
847
858
|
const pageSize = Number(e.detail?.value ?? this.querySelector(':scope [data-page-size]')?.value) || 0;
|
|
848
859
|
this.pageSize = pageSize;
|
|
849
|
-
//
|
|
850
|
-
//
|
|
851
|
-
//
|
|
852
|
-
//
|
|
853
|
-
if (this.#target)
|
|
860
|
+
// gh#1764/#1780 (events-only interaction contract, ADR-0079) — the
|
|
861
|
+
// bound table applies its own [paginate] through the `toolbar-paginate`
|
|
862
|
+
// command event rather than a direct property write; table.class.js
|
|
863
|
+
// listens for it in connected() and sets `this.paginate` itself.
|
|
864
|
+
if (this.#target) {
|
|
865
|
+
this.#target.dispatchEvent(new CustomEvent('toolbar-paginate', {
|
|
866
|
+
bubbles: true,
|
|
867
|
+
detail: { pageSize },
|
|
868
|
+
}));
|
|
869
|
+
}
|
|
854
870
|
this.dispatchEvent(new CustomEvent('page-size-change', {
|
|
855
871
|
bubbles: true,
|
|
856
872
|
detail: { pageSize },
|
|
@@ -902,7 +918,15 @@ export class UITableToolbar extends UIElement {
|
|
|
902
918
|
|
|
903
919
|
#onSearch = (e) => {
|
|
904
920
|
const value = e.detail?.value ?? '';
|
|
905
|
-
|
|
921
|
+
// gh#1764/#1780 (events-only interaction contract, ADR-0079) —
|
|
922
|
+
// `toolbar-search` replaces the direct `.search =` write; table.class.js
|
|
923
|
+
// listens for it in connected() and sets `this.search` itself.
|
|
924
|
+
if (this.#target) {
|
|
925
|
+
this.#target.dispatchEvent(new CustomEvent('toolbar-search', {
|
|
926
|
+
bubbles: true,
|
|
927
|
+
detail: { value },
|
|
928
|
+
}));
|
|
929
|
+
}
|
|
906
930
|
this.dispatchEvent(new CustomEvent('search', {
|
|
907
931
|
bubbles: true,
|
|
908
932
|
detail: { value },
|
|
@@ -1044,8 +1068,14 @@ export class UITableToolbar extends UIElement {
|
|
|
1044
1068
|
}
|
|
1045
1069
|
sel.addEventListener('change', () => {
|
|
1046
1070
|
const v = sel.value || '';
|
|
1047
|
-
|
|
1048
|
-
|
|
1071
|
+
// gh#1764/#1780 (events-only interaction contract, ADR-0079) —
|
|
1072
|
+
// `toolbar-filter-set` replaces the direct target.setFilter()
|
|
1073
|
+
// call; table.class.js applies it synchronously in its own
|
|
1074
|
+
// listener, so `target.filters` below already reflects it.
|
|
1075
|
+
target.dispatchEvent(new CustomEvent('toolbar-filter-set', {
|
|
1076
|
+
bubbles: true,
|
|
1077
|
+
detail: { key: col.key, value: v || null, op: 'select' },
|
|
1078
|
+
}));
|
|
1049
1079
|
this.dispatchEvent(new CustomEvent('filter-change', {
|
|
1050
1080
|
bubbles: true,
|
|
1051
1081
|
detail: { filters: target.filters },
|
|
@@ -1061,8 +1091,12 @@ export class UITableToolbar extends UIElement {
|
|
|
1061
1091
|
if (current?.op === 'contains') input.value = current.value ?? '';
|
|
1062
1092
|
input.addEventListener('input', () => {
|
|
1063
1093
|
const v = input.value;
|
|
1064
|
-
|
|
1065
|
-
|
|
1094
|
+
// gh#1764/#1780 (events-only interaction contract, ADR-0079) —
|
|
1095
|
+
// see the select-filter handler above for the same normalization.
|
|
1096
|
+
target.dispatchEvent(new CustomEvent('toolbar-filter-set', {
|
|
1097
|
+
bubbles: true,
|
|
1098
|
+
detail: { key: col.key, value: v || null, op: 'contains' },
|
|
1099
|
+
}));
|
|
1066
1100
|
this.dispatchEvent(new CustomEvent('filter-change', {
|
|
1067
1101
|
bubbles: true,
|
|
1068
1102
|
detail: { filters: target.filters },
|
|
@@ -1078,7 +1112,11 @@ export class UITableToolbar extends UIElement {
|
|
|
1078
1112
|
|
|
1079
1113
|
if (Object.keys(filters).length) {
|
|
1080
1114
|
const clear = this.#mkPopoverAction('Clear all filters', () => {
|
|
1081
|
-
|
|
1115
|
+
// gh#1764/#1780 (events-only interaction contract, ADR-0079) —
|
|
1116
|
+
// `toolbar-filter-clear` replaces the direct target.clearFilters()
|
|
1117
|
+
// call; table.class.js applies it synchronously in its own
|
|
1118
|
+
// listener before #refreshFilterPanel() re-reads target.filters.
|
|
1119
|
+
target.dispatchEvent(new CustomEvent('toolbar-filter-clear', { bubbles: true }));
|
|
1082
1120
|
this.dispatchEvent(new CustomEvent('filter-change', {
|
|
1083
1121
|
bubbles: true,
|
|
1084
1122
|
detail: { filters: {} },
|
|
@@ -1227,7 +1265,13 @@ export class UITableToolbar extends UIElement {
|
|
|
1227
1265
|
const next = target.columns.map((c) => (
|
|
1228
1266
|
c.key === col.key ? { ...c, hidden: !check.hasAttribute('checked') } : { ...c }
|
|
1229
1267
|
));
|
|
1230
|
-
|
|
1268
|
+
// gh#1764/#1780 (events-only interaction contract, ADR-0079) —
|
|
1269
|
+
// `toolbar-columns-set` replaces the direct target.columns= write;
|
|
1270
|
+
// table.class.js applies it synchronously in its own listener.
|
|
1271
|
+
target.dispatchEvent(new CustomEvent('toolbar-columns-set', {
|
|
1272
|
+
bubbles: true,
|
|
1273
|
+
detail: { columns: next },
|
|
1274
|
+
}));
|
|
1231
1275
|
this.dispatchEvent(new CustomEvent('columns-change', {
|
|
1232
1276
|
bubbles: true,
|
|
1233
1277
|
detail: { hiddenColumns: next.filter((c) => c.hidden).map((c) => c.key) },
|
|
@@ -163,6 +163,40 @@
|
|
|
163
163
|
width: var(--button-height);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
/* ═══════ Actions-leading icon-only reduction ═══════ (gh#1748) — extends
|
|
167
|
+
the SAME icon-only collapse above to a plain <button-ui> a consumer
|
|
168
|
+
slots into [slot="actions-leading"] (gh#1649, e.g. the app-owned
|
|
169
|
+
Filter/Columns triggers on the ratified adiav2 row), so it reads as
|
|
170
|
+
part of the same toolbar affordance cluster rather than staying
|
|
171
|
+
full-width while its native siblings compact. A separate rule block
|
|
172
|
+
(rather than folding into [data-toolbar-btn]'s own selector list above)
|
|
173
|
+
keeps that block's literal text byte-identical for its own
|
|
174
|
+
source-assertion tests. Same breakpoint/pin pair, same
|
|
175
|
+
aria-label-survives guarantee (button-ui's own REQ-X-001 stamp,
|
|
176
|
+
unaffected here) — light-DOM real insertion point (no shadow
|
|
177
|
+
boundary), so a plain descendant tag selector reaches it directly, no
|
|
178
|
+
`::slotted()` needed. Literal `40rem` bound to
|
|
179
|
+
--table-toolbar-bp-icon-only's default, same as the native block. */
|
|
180
|
+
@container table-toolbar (max-width: 40rem) {
|
|
181
|
+
:scope:is(:not([stage]), [stage=""]) [data-actions-leading] button-ui[text]::after {
|
|
182
|
+
content: none;
|
|
183
|
+
}
|
|
184
|
+
:scope:is(:not([stage]), [stage=""]) [data-actions-leading] button-ui {
|
|
185
|
+
--button-px: 0;
|
|
186
|
+
|
|
187
|
+
width: var(--button-height);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
:scope:is([stage="icon-only"], [stage="overflow"]) [data-actions-leading] button-ui[text]::after {
|
|
192
|
+
content: none;
|
|
193
|
+
}
|
|
194
|
+
:scope:is([stage="icon-only"], [stage="overflow"]) [data-actions-leading] button-ui {
|
|
195
|
+
--button-px: 0;
|
|
196
|
+
|
|
197
|
+
width: var(--button-height);
|
|
198
|
+
}
|
|
199
|
+
|
|
166
200
|
/* ═══════ Range-summary conditional compact/hide ═══════ (ADR-0076
|
|
167
201
|
REQ-S-003, step 4) — at the SAME icon-only breakpoint as above:
|
|
168
202
|
- [data-page-size] present AND visible (no [hidden]) → its own
|
|
@@ -17,6 +17,11 @@ export type TableToolbarFilterChangeEvent = CustomEvent<unknown>;
|
|
|
17
17
|
export type TableToolbarPageSizeChangeEvent = CustomEvent<unknown>;
|
|
18
18
|
export type TableToolbarSearchEvent = CustomEvent<unknown>;
|
|
19
19
|
export type TableToolbarSortChangeEvent = CustomEvent<unknown>;
|
|
20
|
+
export type TableToolbarToolbarColumnsSetEvent = CustomEvent<unknown>;
|
|
21
|
+
export type TableToolbarToolbarFilterClearEvent = CustomEvent<unknown>;
|
|
22
|
+
export type TableToolbarToolbarFilterSetEvent = CustomEvent<unknown>;
|
|
23
|
+
export type TableToolbarToolbarPaginateEvent = CustomEvent<unknown>;
|
|
24
|
+
export type TableToolbarToolbarSearchEvent = CustomEvent<unknown>;
|
|
20
25
|
|
|
21
26
|
export class UITableToolbar extends UIElement {
|
|
22
27
|
/** Suppress all four native controls/search at once (filter, sort, columns, search) as additive sugar over noFilter/noSort/noColumns/ noSearch (ADR-0076, ADIA2-9123). Precedence is pure, absolute OR — while set, all four stay off regardless of any individual no-* attribute's own value, with no partial re-enable path; to re-enable one control, remove chrome-only entirely and set the other three no-* attributes explicitly instead. The four granular attributes are not deprecated or removed — they remain the independently-addressable shipped API; chrome-only never replaces them, it's a convenience preset on top. */
|
|
@@ -57,6 +62,11 @@ export class UITableToolbar extends UIElement {
|
|
|
57
62
|
addEventListener(type: 'page-size-change', listener: (ev: TableToolbarPageSizeChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
58
63
|
addEventListener(type: 'search', listener: (ev: TableToolbarSearchEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
59
64
|
addEventListener(type: 'sort-change', listener: (ev: TableToolbarSortChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
65
|
+
addEventListener(type: 'toolbar-columns-set', listener: (ev: TableToolbarToolbarColumnsSetEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
66
|
+
addEventListener(type: 'toolbar-filter-clear', listener: (ev: TableToolbarToolbarFilterClearEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
67
|
+
addEventListener(type: 'toolbar-filter-set', listener: (ev: TableToolbarToolbarFilterSetEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
68
|
+
addEventListener(type: 'toolbar-paginate', listener: (ev: TableToolbarToolbarPaginateEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
69
|
+
addEventListener(type: 'toolbar-search', listener: (ev: TableToolbarToolbarSearchEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
60
70
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
61
71
|
type: K,
|
|
62
72
|
listener: (this: UITableToolbar, ev: HTMLElementEventMap[K]) => unknown,
|
|
@@ -199,6 +199,33 @@ events:
|
|
|
199
199
|
description: "Column visibility changed. Detail: { hiddenColumns }."
|
|
200
200
|
page-size-change:
|
|
201
201
|
description: "Page-size select changed. Detail: { pageSize }."
|
|
202
|
+
toolbar-search:
|
|
203
|
+
description: >-
|
|
204
|
+
gh#1764/#1780, ADR-0079 (events-only interaction contract) — dispatched
|
|
205
|
+
directly at the resolved [for] target (not bubbled from this element)
|
|
206
|
+
in place of the pre-#1780 direct `.search =` write. table-ui listens
|
|
207
|
+
for this on itself. Detail: { value }.
|
|
208
|
+
toolbar-filter-set:
|
|
209
|
+
description: >-
|
|
210
|
+
gh#1764/#1780, ADR-0079 — dispatched directly at the resolved [for]
|
|
211
|
+
target in place of the pre-#1780 direct `target.setFilter()` call.
|
|
212
|
+
table-ui listens for this on itself. Detail: { key, value, op }; a
|
|
213
|
+
null `value` clears that one column's filter.
|
|
214
|
+
toolbar-filter-clear:
|
|
215
|
+
description: >-
|
|
216
|
+
gh#1764/#1780, ADR-0079 — dispatched directly at the resolved [for]
|
|
217
|
+
target in place of the pre-#1780 direct `target.clearFilters()` call.
|
|
218
|
+
table-ui listens for this on itself. No detail.
|
|
219
|
+
toolbar-columns-set:
|
|
220
|
+
description: >-
|
|
221
|
+
gh#1764/#1780, ADR-0079 — dispatched directly at the resolved [for]
|
|
222
|
+
target in place of the pre-#1780 direct `target.columns =` write.
|
|
223
|
+
table-ui listens for this on itself. Detail: { columns }.
|
|
224
|
+
toolbar-paginate:
|
|
225
|
+
description: >-
|
|
226
|
+
gh#1764/#1780, ADR-0079 — dispatched directly at the resolved [for]
|
|
227
|
+
target in place of the pre-#1780 direct `target.paginate =` write.
|
|
228
|
+
table-ui listens for this on itself. Detail: { pageSize }.
|
|
202
229
|
slots:
|
|
203
230
|
scope:
|
|
204
231
|
description: Leading region rendered BEFORE the [text]/[count] title cluster — e.g. a scope/view-switcher menu (an org's teams, a saved view). Positioning is CSS by DOM order, mirroring how [slot="actions"] is a real, author-fillable insertion point rather than template-stamped content.
|
|
@@ -322,15 +349,20 @@ a2ui:
|
|
|
322
349
|
EXPLICIT range-total="0" — omitting range-* entirely (loading) never
|
|
323
350
|
shows it.
|
|
324
351
|
- >-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
352
|
+
A plain <button-ui> slotted into [slot="actions-leading"] (e.g. the
|
|
353
|
+
app-owned Filter/Columns triggers above) already collapses to
|
|
354
|
+
icon-only at the SAME breakpoint as the native Filter/Sort/Columns
|
|
355
|
+
buttons (gh#1748) — no consumer JS/CSS required. Read the host's
|
|
356
|
+
[data-stage-resolved] attribute (ADR-0076 REQ-M-005, ADIA2-9123
|
|
357
|
+
S4-ii) only for compaction BEYOND plain icon-only reduction — e.g.
|
|
358
|
+
hiding a slotted button entirely at the overflow stage, or reacting
|
|
359
|
+
to the stage in a consumer's own drawer/menu — without re-deriving
|
|
360
|
+
the same breakpoints yourself. Informational only — mirrors the
|
|
361
|
+
CSS-decided compaction stage (full | search-tight | icon-only |
|
|
362
|
+
overflow), never drives table-toolbar's own rendering. Reports the
|
|
363
|
+
pinned [stage] value when set, the live width classification
|
|
364
|
+
otherwise; updates on both a live resize and a [stage] attribute
|
|
365
|
+
change.
|
|
334
366
|
anti_patterns: []
|
|
335
367
|
examples:
|
|
336
368
|
- name: members-toolbar
|
package/core/data-stream.js
CHANGED
|
@@ -37,6 +37,14 @@
|
|
|
37
37
|
* stream-load — first signal value received for this element
|
|
38
38
|
* stream-update — each subsequent value, detail.data = the new value
|
|
39
39
|
* stream-error — transport-level error, detail.error = message
|
|
40
|
+
*
|
|
41
|
+
* Opting out (gh#1760):
|
|
42
|
+
* `data-stream-managed="false"` tells the document-level observer to
|
|
43
|
+
* never claim this element, even though it carries `data-stream-src`.
|
|
44
|
+
* For elements that own their own imperative fetch (e.g. the billing
|
|
45
|
+
* composites' `refresh()`) and must not also be claimed by this module —
|
|
46
|
+
* never mix an imperative fetch with an unmanaged `data-stream-src` on
|
|
47
|
+
* the same element (data-stream-protocol.md §10.1).
|
|
40
48
|
*/
|
|
41
49
|
|
|
42
50
|
import { signal, effect, untracked } from './signals.js';
|
|
@@ -54,6 +62,11 @@ const ATTRS = {
|
|
|
54
62
|
merge: 'data-stream-merge',
|
|
55
63
|
format: 'data-stream-format',
|
|
56
64
|
id: 'data-stream-id',
|
|
65
|
+
// gh#1760 — opt-out for elements that carry `data-stream-src` but manage
|
|
66
|
+
// their own fetch (e.g. the billing composites' hand-rolled `refresh()`).
|
|
67
|
+
// `"false"` tells the document-level observer to never claim this element,
|
|
68
|
+
// so exactly one owner (the element's own imperative fetch) ever runs.
|
|
69
|
+
managed: 'data-stream-managed',
|
|
57
70
|
};
|
|
58
71
|
|
|
59
72
|
const STREAMS = new Map(); /* streamId → { signal, refs, transport, opts } */
|
|
@@ -386,6 +399,7 @@ function applyData(el, raw, opts) {
|
|
|
386
399
|
export function start(el) {
|
|
387
400
|
stop(el);
|
|
388
401
|
if (!el.isConnected) return;
|
|
402
|
+
if (attr(el, 'managed') === 'false') return; // gh#1760 — self-managed opt-out
|
|
389
403
|
const src = attr(el, 'src');
|
|
390
404
|
if (!src) return;
|
|
391
405
|
|
|
@@ -442,7 +456,8 @@ export function stop(el) {
|
|
|
442
456
|
const ATTR_FILTER = Object.values(ATTRS);
|
|
443
457
|
|
|
444
458
|
function isStreamingEl(node) {
|
|
445
|
-
return node && node.nodeType === 1 && node.hasAttribute && node.hasAttribute(ATTRS.src)
|
|
459
|
+
return !!(node && node.nodeType === 1 && node.hasAttribute && node.hasAttribute(ATTRS.src)
|
|
460
|
+
&& node.getAttribute(ATTRS.managed) !== 'false');
|
|
446
461
|
}
|
|
447
462
|
|
|
448
463
|
function visitSubtree(root, fn) {
|
|
@@ -476,11 +491,31 @@ const observer = typeof MutationObserver !== 'undefined'
|
|
|
476
491
|
|
|
477
492
|
function bootstrap() {
|
|
478
493
|
if (typeof document === 'undefined') return;
|
|
494
|
+
// Register the observer synchronously — no live mutation is missed.
|
|
479
495
|
observer?.observe(document.documentElement, {
|
|
480
496
|
childList: true, subtree: true,
|
|
481
497
|
attributes: true, attributeFilter: ATTR_FILTER,
|
|
482
498
|
});
|
|
483
|
-
|
|
499
|
+
// gh#1760 — defer the INITIAL sweep of already-present markup by one
|
|
500
|
+
// microtask. Without this, a page whose entry module imports the core
|
|
501
|
+
// barrel (this module) BEFORE a self-managed composite's own module
|
|
502
|
+
// (which sets `data-stream-managed="false"` synchronously from its
|
|
503
|
+
// upgrade-triggered `connectedCallback`) races: this sweep would claim
|
|
504
|
+
// pre-existing SSR markup before the composite ever gets a chance to
|
|
505
|
+
// opt out. Deferring to a microtask lets the rest of the SAME
|
|
506
|
+
// synchronous module-evaluation phase — including every sibling
|
|
507
|
+
// module's own top-level side effects — finish first, so by the time
|
|
508
|
+
// this sweep runs, every already-loaded composite has already set its
|
|
509
|
+
// marker. (A composite loaded asynchronously — a dynamic `import()` —
|
|
510
|
+
// after this microtask has already fired is not covered by this
|
|
511
|
+
// deferral alone; its own `connected()` marker-set plus the observer's
|
|
512
|
+
// live attribute-mutation handling still tears down a wrongly-started
|
|
513
|
+
// stream, bounding the damage to at most one extra fetch.)
|
|
514
|
+
queueMicrotask(() => {
|
|
515
|
+
document.querySelectorAll(`[${ATTRS.src}]`).forEach((el) => {
|
|
516
|
+
if (isStreamingEl(el)) start(el);
|
|
517
|
+
});
|
|
518
|
+
});
|
|
484
519
|
}
|
|
485
520
|
|
|
486
521
|
if (typeof document !== 'undefined') {
|
package/core/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from './template.js';
|
|
|
9
9
|
export * from './element.js';
|
|
10
10
|
export * from './form.js';
|
|
11
11
|
export * from './register.js';
|
|
12
|
+
export * from './store.js';
|
|
12
13
|
// Mirror the explicit re-export in core/index.js — `streams` + `whenStream`
|
|
13
14
|
// (the public surface of the data-stream attribute-driven ingestion module).
|
|
14
15
|
// `export *` from data-stream.js would also expose internals; keep the
|
package/core/index.js
CHANGED
package/core/provider.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `<router-ui>` provider — declarative + imperative client-side
|
|
3
|
-
* routing built on {@link RouteController} (re-
|
|
4
|
-
*
|
|
3
|
+
* routing built on {@link RouteController} (re-exported here for
|
|
4
|
+
* back-compat — gh#1766 — the canonical implementation lives in
|
|
5
|
+
* `core/controller.js`).
|
|
5
6
|
*
|
|
6
7
|
* Two consumer paths:
|
|
7
8
|
*
|
|
@@ -25,22 +26,17 @@
|
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
28
|
import { UIElement } from './element.js';
|
|
28
|
-
import type { Route
|
|
29
|
-
import {
|
|
29
|
+
import type { Route } from './controller.js';
|
|
30
|
+
import { RouteController } from './controller.js';
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
|
-
* Routing-state controller
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* Routing-state controller, re-exported from `core/controller.js`
|
|
34
|
+
* (gh#1766) for back-compat — consumers can import either path.
|
|
35
|
+
* New code should prefer
|
|
35
36
|
* `import { RouteController } from '@adia-ai/web-components/core/controller'`
|
|
36
37
|
* for the canonical path.
|
|
37
38
|
*/
|
|
38
|
-
export
|
|
39
|
-
static schema: ControllerSchema;
|
|
40
|
-
constructor(options?: RouteControllerOptions);
|
|
41
|
-
getState(): RouteState;
|
|
42
|
-
commands: RouteCommands;
|
|
43
|
-
}
|
|
39
|
+
export { RouteController };
|
|
44
40
|
|
|
45
41
|
/**
|
|
46
42
|
* Optional async transform applied to fetched route content before
|
package/core/provider.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AdiaUI Router — Self-contained vanilla JS bundle.
|
|
3
|
-
* Includes:
|
|
3
|
+
* Includes: router-ui component; re-exports `RouteController` for
|
|
4
|
+
* back-compat (gh#1766 — the canonical definition lives in
|
|
5
|
+
* `core/controller.js`, alongside its `BaseController` superclass).
|
|
4
6
|
*
|
|
5
|
-
* Depends on:
|
|
7
|
+
* Depends on: element.js (UIElement), controller.js (RouteController)
|
|
6
8
|
*
|
|
7
9
|
* Usage:
|
|
8
10
|
* import { UIElement } from './core.js';
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
|
|
25
27
|
import { UIElement } from './element.js';
|
|
26
28
|
import { defineIfFree } from './register.js';
|
|
27
|
-
import {
|
|
29
|
+
import { RouteController } from './controller.js';
|
|
28
30
|
import { viewTransition } from '../traits/view-transition/view-transition.js';
|
|
29
31
|
|
|
30
32
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -46,118 +48,12 @@ class UIProvider extends UIElement {
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
// ═══════════════════════════════════════════════════════════════
|
|
49
|
-
// ROUTE CONTROLLER
|
|
51
|
+
// ROUTE CONTROLLER — re-exported for back-compat (gh#1766); the
|
|
52
|
+
// canonical definition lives in `core/controller.js`, which is
|
|
53
|
+
// where `BaseController` (its own superclass) already lives.
|
|
50
54
|
// ═══════════════════════════════════════════════════════════════
|
|
51
55
|
|
|
52
|
-
export
|
|
53
|
-
static schema = {
|
|
54
|
-
name: 'route',
|
|
55
|
-
state: { path: 'string', params: 'object', route: 'object', previous: 'string' },
|
|
56
|
-
commands: ['navigate', 'replace', 'back', 'forward', 'setRoutes'],
|
|
57
|
-
attributes: ['data-route-path'],
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
#routes = [];
|
|
61
|
-
#path = '';
|
|
62
|
-
#previous = '';
|
|
63
|
-
#params = {};
|
|
64
|
-
#route = null;
|
|
65
|
-
#historySync = true;
|
|
66
|
-
#boundPopState = null;
|
|
67
|
-
|
|
68
|
-
constructor({ routes = [], initial, historySync = true } = {}) {
|
|
69
|
-
super();
|
|
70
|
-
this.#routes = routes;
|
|
71
|
-
this.#historySync = historySync;
|
|
72
|
-
this.#path = initial ?? location.pathname;
|
|
73
|
-
this.#match();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
getState() {
|
|
77
|
-
return {
|
|
78
|
-
path: this.#path,
|
|
79
|
-
params: { ...this.#params },
|
|
80
|
-
route: this.#route,
|
|
81
|
-
previous: this.#previous,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
reflect() {
|
|
86
|
-
const host = this.host;
|
|
87
|
-
if (!host) return;
|
|
88
|
-
host.setAttribute('data-route-path', this.#path);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
onConnect(host) {
|
|
92
|
-
if (this.#historySync) {
|
|
93
|
-
this.#boundPopState = () => {
|
|
94
|
-
this.#previous = this.#path;
|
|
95
|
-
this.#path = location.pathname;
|
|
96
|
-
this.#match();
|
|
97
|
-
this.notify();
|
|
98
|
-
};
|
|
99
|
-
window.addEventListener('popstate', this.#boundPopState);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
onDisconnect() {
|
|
104
|
-
if (this.#boundPopState) {
|
|
105
|
-
window.removeEventListener('popstate', this.#boundPopState);
|
|
106
|
-
this.#boundPopState = null;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
#match() {
|
|
111
|
-
this.#params = {};
|
|
112
|
-
this.#route = null;
|
|
113
|
-
for (const route of this.#routes) {
|
|
114
|
-
const match = this.#matchPath(route.path, this.#path);
|
|
115
|
-
if (match) {
|
|
116
|
-
this.#params = match.params;
|
|
117
|
-
this.#route = route;
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
#matchPath(pattern, path) {
|
|
124
|
-
const patternParts = pattern.split('/').filter(Boolean);
|
|
125
|
-
const pathParts = path.split('/').filter(Boolean);
|
|
126
|
-
if (!pattern.includes(':')) return pattern === path ? { params: {} } : null;
|
|
127
|
-
if (patternParts.length !== pathParts.length) return null;
|
|
128
|
-
const params = {};
|
|
129
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
130
|
-
if (patternParts[i].startsWith(':')) params[patternParts[i].slice(1)] = pathParts[i];
|
|
131
|
-
else if (patternParts[i] !== pathParts[i]) return null;
|
|
132
|
-
}
|
|
133
|
-
return { params };
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
commands = {
|
|
137
|
-
navigate: (path) => {
|
|
138
|
-
if (path === this.#path) return;
|
|
139
|
-
this.#previous = this.#path;
|
|
140
|
-
this.#path = path;
|
|
141
|
-
this.#match();
|
|
142
|
-
if (this.#historySync) history.pushState(null, '', path);
|
|
143
|
-
this.notify();
|
|
144
|
-
},
|
|
145
|
-
replace: (path) => {
|
|
146
|
-
this.#previous = this.#path;
|
|
147
|
-
this.#path = path;
|
|
148
|
-
this.#match();
|
|
149
|
-
if (this.#historySync) history.replaceState(null, '', path);
|
|
150
|
-
this.notify();
|
|
151
|
-
},
|
|
152
|
-
back: () => { if (this.#historySync) history.back(); },
|
|
153
|
-
forward: () => { if (this.#historySync) history.forward(); },
|
|
154
|
-
setRoutes: (routes) => {
|
|
155
|
-
this.#routes = routes;
|
|
156
|
-
this.#match();
|
|
157
|
-
this.notify();
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
56
|
+
export { RouteController };
|
|
161
57
|
|
|
162
58
|
// ═══════════════════════════════════════════════════════════════
|
|
163
59
|
// ROUTER-UI — content fragment renderer
|
package/core/store.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared app store — the blessed R2 primitive. Signal-backed, with a
|
|
3
|
+
* Set-of-listeners-compatible `subscribe()` channel for imperative
|
|
4
|
+
* (non-`effect()`) consumers.
|
|
5
|
+
*
|
|
6
|
+
* @see ./store.js (runtime SoT)
|
|
7
|
+
* @see ../USAGE.md
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A `signal()`-backed store. `.value` composes with `computed()`/`effect()`
|
|
12
|
+
* exactly like a plain signal; `subscribe()` is the extra imperative
|
|
13
|
+
* channel the seven hand-rolled app-layer stores already share (see
|
|
14
|
+
* `store.js`'s header for the full rationale and the rejected
|
|
15
|
+
* BaseController alternative).
|
|
16
|
+
*/
|
|
17
|
+
export interface Store<T> {
|
|
18
|
+
/** Tracked read/write, same semantics as `Signal<T>.value`. */
|
|
19
|
+
value: T;
|
|
20
|
+
/** Read without subscribing the surrounding `effect()`. */
|
|
21
|
+
peek(): T;
|
|
22
|
+
/**
|
|
23
|
+
* Subscribe to changes. `cb` receives the new value on every write that
|
|
24
|
+
* doesn't fail the `Object.is` no-notify guard. Returns an unsubscribe
|
|
25
|
+
* function — safe to call more than once. Compatible with
|
|
26
|
+
* `UIElement`'s `controller` setter (`element.controller = store`).
|
|
27
|
+
*/
|
|
28
|
+
subscribe(cb: (value: T) => void): () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a shared app store.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* const count = createStore(0);
|
|
36
|
+
* const stop = count.subscribe((v) => console.log('count is', v));
|
|
37
|
+
* count.value = 1; // logs "count is 1"
|
|
38
|
+
* count.value = 1; // no-op — Object.is guard, no log
|
|
39
|
+
* stop();
|
|
40
|
+
*
|
|
41
|
+
* @example Controller-setter interop
|
|
42
|
+
* class MyEl extends UIElement {}
|
|
43
|
+
* const el = new MyEl();
|
|
44
|
+
* el.controller = createStore({ ready: false }); // re-renders on change
|
|
45
|
+
*/
|
|
46
|
+
export function createStore<T>(initial: T): Store<T>;
|