@adia-ai/web-components 0.8.47 → 0.8.50
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 +50 -0
- package/MIGRATION.md +136 -0
- package/components/card/card.css +25 -2
- package/components/chart/chart.a2ui.json +1 -1
- package/components/chart/chart.class.js +41 -10
- package/components/chart/chart.d.ts +1 -1
- package/components/chart/chart.yaml +6 -3
- package/components/input/input.a2ui.json +2 -2
- package/components/input/input.css +45 -16
- package/components/input/input.yaml +11 -9
- package/components/link/link.css +3 -2
- package/components/richtext/richtext.css +2 -2
- package/components/search/search.a2ui.json +1 -1
- package/components/search/search.class.js +43 -0
- package/components/search/search.css +28 -0
- package/components/search/search.yaml +4 -1
- package/components/select/select.class.js +4 -1
- package/components/select/select.css +13 -0
- package/components/stat/stat.a2ui.json +30 -3
- package/components/stat/stat.css +88 -0
- package/components/stat/stat.d.ts +2 -2
- package/components/stat/stat.yaml +99 -6
- package/components/table/table.a2ui.json +5 -2
- package/components/table/table.class.js +36 -1
- package/components/table/table.d.ts +2 -2
- package/components/table/table.yaml +17 -1
- package/components/table-footer/table-footer.a2ui.json +20 -2
- package/components/table-footer/table-footer.class.js +94 -8
- package/components/table-footer/table-footer.d.ts +6 -2
- package/components/table-footer/table-footer.yaml +57 -6
- package/components/table-toolbar/table-toolbar.a2ui.json +28 -2
- package/components/table-toolbar/table-toolbar.class.js +69 -2
- package/components/table-toolbar/table-toolbar.css +36 -2
- package/components/table-toolbar/table-toolbar.d.ts +8 -2
- package/components/table-toolbar/table-toolbar.examples.md +6 -4
- package/components/table-toolbar/table-toolbar.yaml +140 -26
- package/components/theme-provider/theme-provider.a2ui.json +3 -5
- package/components/theme-provider/theme-provider.class.js +9 -30
- package/components/theme-provider/theme-provider.d.ts +3 -5
- package/components/theme-provider/theme-provider.yaml +4 -8
- package/core/element.js +16 -3
- package/custom-elements.json +52 -10
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/theme-provider.min.js +8 -8
- package/dist/themes.min.css +1 -1
- package/dist/themes.sheet.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +70 -69
- package/dist/web-components.sheet.js +1 -1
- package/index.css +1 -2
- package/package.json +1 -1
- package/styles/README.md +1 -1
- package/styles/api/sizing.css +1 -1
- package/styles/colors/material-static.css +34 -12
- package/styles/host.css +1 -4
- package/styles/index.css +2 -2
- package/styles/scale.css +19 -26
- package/styles/themes.css +2678 -2
- package/styles/type/elements.css +1 -0
- package/styles/type/roles.css +3 -3
- package/styles/typography.css +6 -5
- package/dist/prose.min.css +0 -1
- package/dist/prose.sheet.js +0 -11
- package/dist/verse.min.css +0 -1
- package/dist/verse.sheet.js +0 -11
- package/styles/prose.css +0 -211
- package/styles/verse.css +0 -151
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
## Wiring
|
|
4
4
|
|
|
5
5
|
```html
|
|
6
|
-
<table-toolbar-ui for="members" text="All employees" count="32"></table-toolbar-ui>
|
|
7
6
|
<card-ui>
|
|
8
|
-
<
|
|
9
|
-
<table-ui
|
|
10
|
-
</
|
|
7
|
+
<header>
|
|
8
|
+
<table-toolbar-ui for="members" text="All employees" count="32"></table-toolbar-ui>
|
|
9
|
+
</header>
|
|
10
|
+
<section bleed>
|
|
11
|
+
<table-ui id="members" sortable>…</table-ui>
|
|
12
|
+
</section>
|
|
11
13
|
</card-ui>
|
|
12
14
|
```
|
|
13
15
|
|
|
@@ -88,11 +88,45 @@ props:
|
|
|
88
88
|
in "Showing 1–25 of 320"). Distinct from pagination-ui's `total`
|
|
89
89
|
(total PAGES) — this is total ROWS; deliberately not spelled `total`
|
|
90
90
|
to avoid that cross-sibling name collision (ADR-0063 B5). See
|
|
91
|
-
rangeStart.
|
|
92
|
-
|
|
91
|
+
rangeStart. Explicit range-total="?" (gh#1877, ADR-0082 Amendment) is
|
|
92
|
+
the OPEN/unproven-total state — cursor/hasMore server paging with no
|
|
93
|
+
known row count yet: the summary reads "Showing X–Y" with no "of N",
|
|
94
|
+
requiring only [range-start]/[range-end] (> 0) to render, not
|
|
95
|
+
rangeTotal too. Resolves to the normal finite summary the moment a
|
|
96
|
+
real number replaces the "?". Named identically to table-footer-ui's
|
|
97
|
+
own [range-total] (ADR-0082 §"same name on both companions").
|
|
98
|
+
type: [number, string]
|
|
93
99
|
default: 0
|
|
94
100
|
reflect: true
|
|
95
101
|
attribute: range-total
|
|
102
|
+
rangeOf:
|
|
103
|
+
description: >-
|
|
104
|
+
gh#1877, ADR-0082 Amendment — secondary whole-set total for the
|
|
105
|
+
"filtered-of-whole" shape: a client-side facet narrows [rangeTotal]
|
|
106
|
+
(a filtered count) while rangeOf names the unfiltered set size, e.g.
|
|
107
|
+
"Showing 1–4 of 4 traversals (6 total)". Appends "(of <rangeOf>
|
|
108
|
+
total)" onto the normal finite range summary; unset (the default)
|
|
109
|
+
changes nothing. Never applies while the summary is in its open
|
|
110
|
+
("?") or opted-out (all-zero) state. Named identically to
|
|
111
|
+
table-footer-ui's own [range-of] (ADR-0082 §"same name on both
|
|
112
|
+
companions").
|
|
113
|
+
type: number
|
|
114
|
+
default: 0
|
|
115
|
+
reflect: true
|
|
116
|
+
attribute: range-of
|
|
117
|
+
rangeNoun:
|
|
118
|
+
description: >-
|
|
119
|
+
gh#1877, ADR-0082 Amendment — optional per-slice noun appended after
|
|
120
|
+
the range total ("of 80 users") so consumers stop hand-painting the
|
|
121
|
+
noun themselves. Applies in both the normal finite range summary and
|
|
122
|
+
the open/unproven-total summary ("Showing 51–100 users" with no "of
|
|
123
|
+
N"). Unset (the default) changes nothing. Named identically to
|
|
124
|
+
table-footer-ui's own [range-noun] (ADR-0082 §"same name on both
|
|
125
|
+
companions").
|
|
126
|
+
type: string
|
|
127
|
+
default: ""
|
|
128
|
+
reflect: true
|
|
129
|
+
attribute: range-noun
|
|
96
130
|
pageSize:
|
|
97
131
|
description: >-
|
|
98
132
|
Current rows-per-page value, shown as the selected option in the
|
|
@@ -190,6 +224,19 @@ props:
|
|
|
190
224
|
default: false
|
|
191
225
|
reflect: true
|
|
192
226
|
attribute: chrome-only
|
|
227
|
+
titleAlways:
|
|
228
|
+
description: >-
|
|
229
|
+
Opt out of the scope-reconciliation title hide: by default, a filled
|
|
230
|
+
[slot="scope"] region (a select/menu whose own displayed value already
|
|
231
|
+
names the entity being listed) hides the separate [text]/[count]
|
|
232
|
+
title cluster next to it, since together they'd say the same thing
|
|
233
|
+
twice. Set [title-always] to keep the title cluster visible alongside
|
|
234
|
+
a filled scope slot — e.g. a scope select PLUS an explicit
|
|
235
|
+
text/count title on the same row (gh#1879).
|
|
236
|
+
type: boolean
|
|
237
|
+
default: false
|
|
238
|
+
reflect: true
|
|
239
|
+
attribute: title-always
|
|
193
240
|
placeholder:
|
|
194
241
|
description: Placeholder text for the search input.
|
|
195
242
|
type: string
|
|
@@ -270,6 +317,26 @@ slots:
|
|
|
270
317
|
stamp. Empty (the default) has zero footprint on existing consumers.
|
|
271
318
|
actions:
|
|
272
319
|
description: Trailing action area — primary buttons (e.g. "New row") rendered after the search input, [slot="actions-leading"], and the page-size select.
|
|
320
|
+
summary:
|
|
321
|
+
description: >-
|
|
322
|
+
gh#1883 — trailing, text-tier cluster for an aggregate datum that
|
|
323
|
+
belongs with the table but has no home in the range summary or the
|
|
324
|
+
actions area (e.g. batch-detail's line-items Subtotal:
|
|
325
|
+
`<span slot="summary">Subtotal <strong>$1,240.00</strong></span>`).
|
|
326
|
+
Rendered in the same flex row, after [slot="actions-leading"] and
|
|
327
|
+
before the page-size select / [slot="actions"] — closer to the
|
|
328
|
+
trailing edge than [slot="scope"]/[range-*], since it reads as a
|
|
329
|
+
result/total rather than pagination or view-switching context. Text
|
|
330
|
+
sized via [--table-toolbar-summary-size] (shared with the
|
|
331
|
+
"Showing X–Y of N" range summary — both are the same text tier).
|
|
332
|
+
Plain author-supplied markup, same real-insertion-point pattern as
|
|
333
|
+
every other table-toolbar slot — no forced typography beyond the
|
|
334
|
+
font-size token. Stays visible at every ADR-0076 compaction stage: it
|
|
335
|
+
is DATA (an aggregate figure the consumer chose to show), not chrome,
|
|
336
|
+
so it is never dropped or compacted the way the range summary/
|
|
337
|
+
controls are — a consumer needing narrower-container behavior owns
|
|
338
|
+
that via its own slotted content, not a component-side compaction
|
|
339
|
+
rule. Empty (the default) has zero footprint on existing consumers.
|
|
273
340
|
states:
|
|
274
341
|
- name: idle
|
|
275
342
|
description: Default, ready for interaction.
|
|
@@ -307,29 +374,40 @@ a2ui:
|
|
|
307
374
|
- >-
|
|
308
375
|
Pair <table-toolbar-ui> with <table-ui> via [for="<table-id>"]
|
|
309
376
|
(or rely on first-sibling fallback when both are inside the
|
|
310
|
-
same parent). One toolbar per table.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
chrome
|
|
318
|
-
|
|
377
|
+
same parent). One toolbar per table. <table-toolbar-ui> MAY sit
|
|
378
|
+
as <card-ui>'s <header>'s sole child — verified in a rendered
|
|
379
|
+
browser to lay out full-width identically to the
|
|
380
|
+
sibling-above-card pattern (gh#1807/#1884, ADR-0080) — but do
|
|
381
|
+
NOT also give that same <header> a slot="heading"/slot="action"
|
|
382
|
+
child: card.css's grid mode then shrinks the toolbar to its own
|
|
383
|
+
content width and collapses its flex-row layout. The
|
|
384
|
+
doubled-chrome hazard is a TITLE COLLISION, not a blanket
|
|
385
|
+
card-header ban (SPEC REQ-C-004): a toolbar carrying [text] must
|
|
386
|
+
not share a card with a separately heading-slotted <card-ui>
|
|
387
|
+
<header> — that pairing doubles the title row. As the header's
|
|
388
|
+
sole child, the toolbar's own [text]/[count] cluster IS the
|
|
389
|
+
card's title; there is no separate heading to collide with.
|
|
319
390
|
- >-
|
|
320
391
|
All four affordances (search, filter, sort, columns) default ON.
|
|
321
392
|
Opt out individually via [no-search] / [no-filter] / [no-sort]
|
|
322
393
|
/ [no-columns]. The previous [searchable] / [filterable]
|
|
323
394
|
attributes are deprecated — do NOT emit them.
|
|
324
395
|
- >-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
the
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
396
|
+
Two legitimate in-card placements, chosen by whether the card
|
|
397
|
+
header needs content beyond the toolbar: ABOVE the <card-ui>
|
|
398
|
+
(sibling, typically wrapped with the table in a <col-ui>) when
|
|
399
|
+
the header carries its own slot="heading"/slot="action" content
|
|
400
|
+
alongside the toolbar; or AS <card-ui>'s <header> sole child —
|
|
401
|
+
the canonical in-card shape (gh#1807/#1884, ADR-0080) — when the
|
|
402
|
+
toolbar IS the header and no separate title/action row is
|
|
403
|
+
needed. A toolbar can also stand alone outside any card-ui
|
|
404
|
+
parent with [variant="card"], which wraps it in card-style
|
|
405
|
+
chrome. The previous plain (non-bleed) <section> placement
|
|
406
|
+
between the card header and the bleed table section — a
|
|
407
|
+
title-less toolbar leaving the title to a separately-slotted
|
|
408
|
+
header — is retired: no live demo or pattern documents it, and
|
|
409
|
+
the header-child shape supersedes it as the canonical in-card
|
|
410
|
+
composition.
|
|
333
411
|
- >-
|
|
334
412
|
Use slot="action" (or [slot="actions"]) for trailing primary
|
|
335
413
|
buttons (Invite, Export, +New). Use [text] / [count] props for
|
|
@@ -343,13 +421,15 @@ a2ui:
|
|
|
343
421
|
update the table.
|
|
344
422
|
- >-
|
|
345
423
|
Use [slot="scope"] for a leading scope/view switcher (an org's
|
|
346
|
-
teams, a saved view) rendered before [text]/[count].
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
424
|
+
teams, a saved view) rendered before [text]/[count]. By default, a
|
|
425
|
+
filled scope slot hides the [text]/[count] title cluster (they'd
|
|
426
|
+
otherwise repeat the same entity name) — set [title-always] to keep
|
|
427
|
+
both visible together. Use [range-start] / [range-end] /
|
|
428
|
+
[range-total] together for a "Showing X–Y of N" summary — it renders
|
|
429
|
+
independently of, and alongside, [text]/[count], never in place of
|
|
430
|
+
them. Use [page-size] + [page-size-options] for a rows-per-page
|
|
431
|
+
select; it applies directly to the bound table's [paginate] prop the
|
|
432
|
+
same way filter/sort/columns changes apply directly to the target.
|
|
353
433
|
- >-
|
|
354
434
|
Use [slot="actions-leading"] (gh#1649) for app-owned trigger
|
|
355
435
|
buttons that must land BEFORE the page-size select — e.g. custom
|
|
@@ -373,6 +453,26 @@ a2ui:
|
|
|
373
453
|
normal "Showing X-Y of N" text (ADR-0076, REQ-E). Only fires on an
|
|
374
454
|
EXPLICIT range-total="0" — omitting range-* entirely (loading) never
|
|
375
455
|
shows it.
|
|
456
|
+
- >-
|
|
457
|
+
gh#1877, ADR-0082 Amendment — cursor/hasMore server paging (no
|
|
458
|
+
server-reported row count) sets [range-total="?"] instead of a real
|
|
459
|
+
number: the summary reads "Showing 51–100" with no "of N" (needs only
|
|
460
|
+
[range-start]/[range-end] > 0 to render), resolving to the normal
|
|
461
|
+
finite summary once a real [range-total] arrives. Use [range-of] for
|
|
462
|
+
the separate "filtered-of-whole" shape (a filtered range-total plus a
|
|
463
|
+
secondary unfiltered-set total, e.g. "of 4 traversals (6 total)"),
|
|
464
|
+
and [range-noun] to stop hand-painting the per-slice noun ("of 80
|
|
465
|
+
users"). Same three attributes, same names, on table-footer-ui
|
|
466
|
+
(ADR-0082 §"same name on both companions").
|
|
467
|
+
- >-
|
|
468
|
+
Use [slot="summary"] (gh#1883) for a trailing aggregate/total datum
|
|
469
|
+
that belongs with the table but isn't pagination context — e.g.
|
|
470
|
+
batch-detail's line-items Subtotal:
|
|
471
|
+
`<span slot="summary">Subtotal <strong>$1,240.00</strong></span>`.
|
|
472
|
+
Renders after [slot="actions-leading"] and before the page-size
|
|
473
|
+
select / [slot="actions"]. Always visible — it is data, never
|
|
474
|
+
collapsed or hidden by the compaction stages that reduce
|
|
475
|
+
Filter/Sort/Columns/search or the range summary.
|
|
376
476
|
- >-
|
|
377
477
|
A plain <button-ui> slotted into [slot="actions-leading"] (e.g. the
|
|
378
478
|
app-owned Filter/Columns triggers above) already collapses to
|
|
@@ -420,6 +520,20 @@ examples:
|
|
|
420
520
|
{"id": "sec", "component": "Section", "bleed": true, "children": ["tbl"]},
|
|
421
521
|
{"id": "tbl", "component": "Table", "id": "accounts", "sortable": true, "raw": true}
|
|
422
522
|
]
|
|
523
|
+
- name: table-toolbar-open-total
|
|
524
|
+
description: >-
|
|
525
|
+
gh#1877, ADR-0082 Amendment — cursor/hasMore server paging with no
|
|
526
|
+
known row count yet: [range-total="?"] renders "Showing 1–50
|
|
527
|
+
subscriptions" (via [range-noun]) with no "of N", resolving to the
|
|
528
|
+
normal finite summary once a real total arrives.
|
|
529
|
+
a2ui: >-
|
|
530
|
+
[
|
|
531
|
+
{"id": "root", "component": "Column", "gap": "3", "children": ["bar", "card"]},
|
|
532
|
+
{"id": "bar", "component": "TableToolbar", "for": "subs", "range-start": 1, "range-end": 50, "range-total": "?", "range-noun": "subscriptions"},
|
|
533
|
+
{"id": "card", "component": "Card", "children": ["sec"]},
|
|
534
|
+
{"id": "sec", "component": "Section", "bleed": true, "children": ["tbl"]},
|
|
535
|
+
{"id": "tbl", "component": "Table", "id": "subs", "raw": true}
|
|
536
|
+
]
|
|
423
537
|
keywords:
|
|
424
538
|
- table-toolbar
|
|
425
539
|
- data-grid
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://adiaui.dev/a2ui/v0_9/components/ThemeProvider.json",
|
|
4
4
|
"title": "ThemeProvider",
|
|
5
|
-
"description": "Foundation-providing wrapper — adopts the AdiaUI foundation (design tokens +\nresets + page-frame + every primitive's CSS) into the document from anywhere\nin the DOM, so a surface renders fully-styled WITHOUT a hand-wired\n<link rel=\"stylesheet\"> in <head>. Layout-transparent (display: contents): the\nelement owns no box; children lay out as if it weren't there.\n\nMechanism: it imports the constructable-stylesheet twin of web-components.min.css\n(byte-identical to the CDN bundle, emitted from the same build buffer) and\nadopts it once into document.adoptedStyleSheets, deduped — adoption fires at\nmodule load, before paint. Coexists with the render-blocking <link> path; both\ndeliver the same bytes. Use a <link> (-> the CDN web-components.min.css) for\nmulti-page / top-level surfaces (cacheable across navigations, zero flash); use\n<theme-provider> for SPA roots, embedded apps, and dynamic mounts where you do\nnot control <head> (e.g. <embed-shell>, an A2UI surface, a micro-frontend).\n\nTheming: the base foundation is OS light/dark via light-dark() tokens. Two opt-in\nattributes adopt their layer on demand — theme=\"ocean|forest|slate|…\" applies a\nnamed preset (adopts the themes layer; matches the [theme] hook) and\nscale=\"ui-sm|ui-md|ui-lg|content-sm|content-md|content-lg\" sets the sizing\nregister (adopts the matching register layer).
|
|
5
|
+
"description": "Foundation-providing wrapper — adopts the AdiaUI foundation (design tokens +\nresets + page-frame + every primitive's CSS) into the document from anywhere\nin the DOM, so a surface renders fully-styled WITHOUT a hand-wired\n<link rel=\"stylesheet\"> in <head>. Layout-transparent (display: contents): the\nelement owns no box; children lay out as if it weren't there.\n\nMechanism: it imports the constructable-stylesheet twin of web-components.min.css\n(byte-identical to the CDN bundle, emitted from the same build buffer) and\nadopts it once into document.adoptedStyleSheets, deduped — adoption fires at\nmodule load, before paint. Coexists with the render-blocking <link> path; both\ndeliver the same bytes. Use a <link> (-> the CDN web-components.min.css) for\nmulti-page / top-level surfaces (cacheable across navigations, zero flash); use\n<theme-provider> for SPA roots, embedded apps, and dynamic mounts where you do\nnot control <head> (e.g. <embed-shell>, an A2UI surface, a micro-frontend).\n\nTheming: the base foundation is OS light/dark via light-dark() tokens. Two opt-in\nattributes adopt their layer on demand — theme=\"ocean|forest|slate|…\" applies a\nnamed preset (adopts the themes layer; matches the [theme] hook) and\nscale=\"ui-sm|ui-md|ui-lg|content-sm|content-md|content-lg\" sets the sizing\nregister (adopts the matching register layer). Each layer is fetched only\nwhen its attribute is set, so a bare provider stays lean.\nOpt-in infra: NOT in the all-in-one @adia-ai/web-components barrel (it carries\nthe whole foundation); import @adia-ai/web-components/components/theme-provider\nexplicitly. Distinct from <frame-ui> (a layout skeleton, owns no CSS delivery)\nand the page shells (chrome over the same foundation).\n",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"allOf": [
|
|
8
8
|
{
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"const": "ThemeProvider"
|
|
23
23
|
},
|
|
24
24
|
"scale": {
|
|
25
|
-
"description": "Sizing register for the subtree — one of the six [scale] tiers: ui-sm (dense product UI), ui-md (base), ui-lg (touch/presentation), content-sm (compact article), content-md (long-form), content-lg (hero/marketing). Adopts the scale layer on demand; the reflected attribute is the [scale=\"…\"] CSS hook.
|
|
25
|
+
"description": "Sizing register for the subtree — one of the six [scale] tiers: ui-sm (dense product UI), ui-md (base), ui-lg (touch/presentation), content-sm (compact article), content-md (long-form), content-lg (hero/marketing). Adopts the scale layer on demand; the reflected attribute is the [scale=\"…\"] CSS hook.",
|
|
26
26
|
"type": "string",
|
|
27
27
|
"enum": [
|
|
28
28
|
"ui-sm",
|
|
@@ -30,9 +30,7 @@
|
|
|
30
30
|
"ui-lg",
|
|
31
31
|
"content-sm",
|
|
32
32
|
"content-md",
|
|
33
|
-
"content-lg"
|
|
34
|
-
"verse",
|
|
35
|
-
"prose"
|
|
33
|
+
"content-lg"
|
|
36
34
|
],
|
|
37
35
|
"default": ""
|
|
38
36
|
}
|
|
@@ -28,12 +28,11 @@
|
|
|
28
28
|
*
|
|
29
29
|
* Scope (v1): the provider supplies the FOUNDATION only — design tokens, resets,
|
|
30
30
|
* page-frame, and every primitive's CSS. OS light/dark resolves automatically
|
|
31
|
-
* (the tokens are `light-dark()`-based). Named themes (`[theme]`)
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* `theme
|
|
35
|
-
*
|
|
36
|
-
* is pre-wired for it; see .brain/notes/theme-provider-design-2026-06-04.md).
|
|
31
|
+
* (the tokens are `light-dark()`-based). Named themes (`[theme]`) live in an
|
|
32
|
+
* OPT-IN layer (themes.css) that is NOT in the foundation bundle, so setting
|
|
33
|
+
* `theme` on the wrapper does nothing until that layer is also adopted — a
|
|
34
|
+
* planned `theme=` provider option (the token-root selector is pre-wired for
|
|
35
|
+
* it; see .brain/notes/theme-provider-design-2026-06-04.md).
|
|
37
36
|
*
|
|
38
37
|
* Deliberately NOT in the all-in-one `@adia-ai/web-components` barrel — it's
|
|
39
38
|
* opt-in infra (it carries the whole foundation). Import it explicitly:
|
|
@@ -58,19 +57,17 @@ function provideFoundation() {
|
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
// Opt-in context registers — themes (named `[theme]` presets)
|
|
62
|
-
//
|
|
60
|
+
// Opt-in context registers — themes (named `[theme]` presets) and the `scale`
|
|
61
|
+
// register. Each is its own constructable-sheet twin (built by
|
|
63
62
|
// scripts/build/bundle-css.mjs, byte-identical to its .min.css). Adopted on demand
|
|
64
63
|
// the first time a provider needs one, deduped by sheet identity. Dynamic-imported
|
|
65
64
|
// (static paths, so bundlers can code-split) so the element stays lean — a provider
|
|
66
65
|
// that does no theming never pulls these in.
|
|
67
|
-
const TWIN_LOADED = { themes: false,
|
|
66
|
+
const TWIN_LOADED = { themes: false, scale: false };
|
|
68
67
|
function loadTwin(name) {
|
|
69
68
|
switch (name) {
|
|
70
69
|
case 'themes': return import('../../dist/themes.sheet.js');
|
|
71
70
|
case 'scale': return import('../../dist/scale.sheet.js');
|
|
72
|
-
case 'verse': return import('../../dist/verse.sheet.js');
|
|
73
|
-
case 'prose': return import('../../dist/prose.sheet.js');
|
|
74
71
|
default: return null;
|
|
75
72
|
}
|
|
76
73
|
}
|
|
@@ -100,8 +97,6 @@ export class UIThemeProvider extends UIElement {
|
|
|
100
97
|
// Sizing register — one of the six [scale] tiers (ui-sm | ui-md | ui-lg |
|
|
101
98
|
// content-sm | content-md | content-lg). Reflected, so the attribute IS the
|
|
102
99
|
// `[scale="…"]` CSS hook in scale.css (the `theme` prop's mechanism).
|
|
103
|
-
// DEPRECATED aliases: "verse" (→ ui-sm) and "prose" (→ content-md) still
|
|
104
|
-
// map onto the legacy [verse]/[prose] attributes; removed at v1.0.
|
|
105
100
|
scale: { type: String, reflect: true },
|
|
106
101
|
};
|
|
107
102
|
static template = () => null;
|
|
@@ -128,15 +123,7 @@ export class UIThemeProvider extends UIElement {
|
|
|
128
123
|
if (this.theme) ensureTwin('themes');
|
|
129
124
|
|
|
130
125
|
const scale = this.scale;
|
|
131
|
-
|
|
132
|
-
this.toggleAttribute('prose', scale === 'prose');
|
|
133
|
-
if (scale === 'verse' || scale === 'prose') {
|
|
134
|
-
// Legacy register aliases — the reflected scale="verse|prose" attribute
|
|
135
|
-
// matches no scale.css tier selector, so only the toggled [verse]/[prose]
|
|
136
|
-
// attribute has CSS effect. Removed at v1.0.
|
|
137
|
-
warnDeprecatedScaleAlias(scale);
|
|
138
|
-
ensureTwin(scale);
|
|
139
|
-
} else if (SCALE_TIERS.has(scale)) {
|
|
126
|
+
if (SCALE_TIERS.has(scale)) {
|
|
140
127
|
// Tier names match [scale="…"] in scale.css directly via reflection.
|
|
141
128
|
ensureTwin('scale');
|
|
142
129
|
}
|
|
@@ -144,11 +131,3 @@ export class UIThemeProvider extends UIElement {
|
|
|
144
131
|
}
|
|
145
132
|
|
|
146
133
|
const SCALE_TIERS = new Set(['ui-sm', 'ui-md', 'ui-lg', 'content-sm', 'content-md', 'content-lg']);
|
|
147
|
-
|
|
148
|
-
const warnedAliases = new Set();
|
|
149
|
-
function warnDeprecatedScaleAlias(alias) {
|
|
150
|
-
if (warnedAliases.has(alias)) return;
|
|
151
|
-
warnedAliases.add(alias);
|
|
152
|
-
const tier = alias === 'verse' ? 'ui-sm' : 'content-md';
|
|
153
|
-
console.warn(`[theme-provider] scale="${alias}" is deprecated — use scale="${tier}" (removed at v1.0).`);
|
|
154
|
-
}
|
|
@@ -18,9 +18,7 @@ Theming: the base foundation is OS light/dark via light-dark() tokens. Two opt-i
|
|
|
18
18
|
attributes adopt their layer on demand — theme="ocean|forest|slate|…" applies a
|
|
19
19
|
named preset (adopts the themes layer; matches the [theme] hook) and
|
|
20
20
|
scale="ui-sm|ui-md|ui-lg|content-sm|content-md|content-lg" sets the sizing
|
|
21
|
-
register (adopts the matching register layer).
|
|
22
|
-
(→ ui-sm) and "prose" (→ content-md) still map onto the legacy
|
|
23
|
-
[verse]/[prose] attributes; removed at v1.0. Each layer is fetched only
|
|
21
|
+
register (adopts the matching register layer). Each layer is fetched only
|
|
24
22
|
when its attribute is set, so a bare provider stays lean.
|
|
25
23
|
Opt-in infra: NOT in the all-in-one @adia-ai/web-components barrel (it carries
|
|
26
24
|
the whole foundation); import @adia-ai/web-components/components/theme-provider
|
|
@@ -42,6 +40,6 @@ import { UIElement } from '../../core/element.js';
|
|
|
42
40
|
export class UIThemeProvider extends UIElement {
|
|
43
41
|
/** Named theme preset for the wrapped subtree (default, ocean, forest, sunset, lavender, rose, slate, midnight). Adopts the themes layer on demand + matches the [theme="…"] hook. */
|
|
44
42
|
theme: string;
|
|
45
|
-
/** Sizing register for the subtree — one of the six [scale] tiers: ui-sm (dense product UI), ui-md (base), ui-lg (touch/presentation), content-sm (compact article), content-md (long-form), content-lg (hero/marketing). Adopts the scale layer on demand; the reflected attribute is the [scale="…"] CSS hook.
|
|
46
|
-
scale: 'ui-sm' | 'ui-md' | 'ui-lg' | 'content-sm' | 'content-md' | 'content-lg'
|
|
43
|
+
/** Sizing register for the subtree — one of the six [scale] tiers: ui-sm (dense product UI), ui-md (base), ui-lg (touch/presentation), content-sm (compact article), content-md (long-form), content-lg (hero/marketing). Adopts the scale layer on demand; the reflected attribute is the [scale="…"] CSS hook. */
|
|
44
|
+
scale: 'ui-sm' | 'ui-md' | 'ui-lg' | 'content-sm' | 'content-md' | 'content-lg';
|
|
47
45
|
}
|
|
@@ -26,9 +26,7 @@ description: |
|
|
|
26
26
|
attributes adopt their layer on demand — theme="ocean|forest|slate|…" applies a
|
|
27
27
|
named preset (adopts the themes layer; matches the [theme] hook) and
|
|
28
28
|
scale="ui-sm|ui-md|ui-lg|content-sm|content-md|content-lg" sets the sizing
|
|
29
|
-
register (adopts the matching register layer).
|
|
30
|
-
(→ ui-sm) and "prose" (→ content-md) still map onto the legacy
|
|
31
|
-
[verse]/[prose] attributes; removed at v1.0. Each layer is fetched only
|
|
29
|
+
register (adopts the matching register layer). Each layer is fetched only
|
|
32
30
|
when its attribute is set, so a bare provider stays lean.
|
|
33
31
|
Opt-in infra: NOT in the all-in-one @adia-ai/web-components barrel (it carries
|
|
34
32
|
the whole foundation); import @adia-ai/web-components/components/theme-provider
|
|
@@ -41,7 +39,7 @@ props:
|
|
|
41
39
|
default: ""
|
|
42
40
|
reflect: true
|
|
43
41
|
scale:
|
|
44
|
-
description: 'Sizing register for the subtree — one of the six [scale] tiers: ui-sm (dense product UI), ui-md (base), ui-lg (touch/presentation), content-sm (compact article), content-md (long-form), content-lg (hero/marketing). Adopts the scale layer on demand; the reflected attribute is the [scale="…"] CSS hook.
|
|
42
|
+
description: 'Sizing register for the subtree — one of the six [scale] tiers: ui-sm (dense product UI), ui-md (base), ui-lg (touch/presentation), content-sm (compact article), content-md (long-form), content-lg (hero/marketing). Adopts the scale layer on demand; the reflected attribute is the [scale="…"] CSS hook.'
|
|
45
43
|
type: string
|
|
46
44
|
default: ""
|
|
47
45
|
enum:
|
|
@@ -51,8 +49,6 @@ props:
|
|
|
51
49
|
- content-sm
|
|
52
50
|
- content-md
|
|
53
51
|
- content-lg
|
|
54
|
-
- verse
|
|
55
|
-
- prose
|
|
56
52
|
reflect: true
|
|
57
53
|
events: {}
|
|
58
54
|
slots: {}
|
|
@@ -67,8 +63,8 @@ a2ui:
|
|
|
67
63
|
reason: 'Adopts the foundation bundle into the document from anywhere in the tree (document.adoptedStyleSheets).'
|
|
68
64
|
- rule: 'For multi-page / top-level pages, prefer a render-blocking <link rel="stylesheet" href=".../web-components.min.css"> in <head> instead — cacheable across navigations, styled on the first frame. <theme-provider> and the link deliver byte-identical CSS, so they coexist.'
|
|
69
65
|
reason: 'Link for multi-page (no flash); provider for SPA/embedded (no head access).'
|
|
70
|
-
- rule: 'Theme the wrapped subtree with theme="ocean" (a named preset) and/or scale="
|
|
71
|
-
reason: 'theme/scale each fetch + adopt their layer (themes.css /
|
|
66
|
+
- rule: 'Theme the wrapped subtree with theme="ocean" (a named preset) and/or scale="content-md" (a sizing register tier) — the provider adopts the matching opt-in layer on demand. The base foundation is OS light/dark automatically. Keep <theme-provider> at/near the mount root — display:contents means an ancestor "> " child selector will not reach its children.'
|
|
67
|
+
reason: 'theme/scale each fetch + adopt their layer (themes.css / scale.css) on demand; display:contents preserves custom-property inheritance but not child-combinator reach (Light DOM, ADR-0033).'
|
|
72
68
|
anti_patterns: []
|
|
73
69
|
examples:
|
|
74
70
|
- name: spa-root
|
package/core/element.js
CHANGED
|
@@ -85,9 +85,22 @@ const NOOP_INTERNALS = Object.freeze({
|
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
function reflect(el, a, v, t) {
|
|
88
|
-
t === Boolean
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
if (t === Boolean) {
|
|
89
|
+
v ? el.setAttribute(a, '') : el.removeAttribute(a);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// gh#1895 — a Number-typed reflected prop set from an unparseable sentinel
|
|
93
|
+
// attribute (e.g. `range-total="?"`, ADR-0082 Amendment's open/unproven-
|
|
94
|
+
// total grammar) coerces to NaN. Writing `String(NaN)` back would stomp
|
|
95
|
+
// the author's sentinel with the literal string "NaN" on the very next
|
|
96
|
+
// render, so a non-finite Number value is deliberately left unreflected —
|
|
97
|
+
// the attribute keeps whatever the author wrote (or stays absent, for a
|
|
98
|
+
// NaN assigned straight from JS with no attribute yet). This mirrors how
|
|
99
|
+
// `null` already skips straight to removeAttribute below instead of
|
|
100
|
+
// stringifying: NaN gets its own no-write branch rather than one more
|
|
101
|
+
// Number.isFinite check duplicated at every "?"-sentinel call site.
|
|
102
|
+
if (t === Number && v != null && !Number.isFinite(v)) return;
|
|
103
|
+
v == null ? el.removeAttribute(a) : el.setAttribute(a, String(v));
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
const parseAttr = (v, t) =>
|