@adia-ai/web-components 0.8.33 → 0.8.34
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/agent-reasoning/agent-reasoning.a2ui.json +1 -1
- package/components/agent-reasoning/agent-reasoning.yaml +3 -3
- package/components/badge/badge.css +6 -4
- package/components/button/button.css +12 -0
- package/components/combobox/combobox.a2ui.json +1 -1
- package/components/combobox/combobox.yaml +3 -0
- package/components/command/command.a2ui.json +4 -0
- package/components/command/command.class.js +54 -4
- package/components/command/command.d.ts +2 -0
- package/components/command/command.yaml +25 -3
- package/components/date-range-picker/date-range-picker.a2ui.json +1 -1
- package/components/date-range-picker/date-range-picker.class.js +42 -43
- package/components/date-range-picker/date-range-picker.css +11 -0
- package/components/date-range-picker/date-range-picker.yaml +6 -3
- package/components/description-list/description-list.css +16 -4
- package/components/modal/modal.class.js +43 -2
- package/components/modal/modal.css +23 -0
- package/components/nav-group/nav-group.css +15 -4
- package/components/nav-item/nav-item.css +11 -3
- package/components/richtext/richtext.css +5 -1
- package/components/segment/segment.css +10 -0
- package/components/step-progress/step-progress.css +16 -3
- package/components/table/cell-types.js +8 -0
- package/components/table/table.a2ui.json +1 -1
- package/components/table/table.class.js +11 -0
- package/components/table/table.css +9 -0
- package/components/table/table.d.ts +1 -1
- package/components/table/table.yaml +1 -1
- package/components/tabs/tabs.css +3 -1
- package/components/tag/tag.css +7 -4
- package/core/element.js +31 -0
- package/core/signals.js +3 -1
- package/dist/theme-provider.min.js +2 -2
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +40 -40
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -1
- package/patterns/responsive-shell-sidebar/responsive-shell-sidebar.examples.html +135 -147
- package/styles/colors/semantics/core.css +22 -2
|
@@ -10,13 +10,22 @@
|
|
|
10
10
|
--nav-group-font-size-sm: var(--a-ui-sm);
|
|
11
11
|
--nav-group-font-size-lg: var(--a-ui-lg);
|
|
12
12
|
--nav-group-font-weight: var(--a-weight-normal);
|
|
13
|
-
|
|
13
|
+
/* State color spec (gh#968, operator ruling 2026-08-11) — one
|
|
14
|
+
language with nav-item-ui:
|
|
15
|
+
idle — bg transparent · icon NEUTRAL · text NEUTRAL-HIGH
|
|
16
|
+
hover — bg container-low · icon + text ON-SURFACE
|
|
17
|
+
selected — bg container · icon + text ON-SURFACE
|
|
18
|
+
The selected icon no longer takes primary (supersedes gh#522's
|
|
19
|
+
glyph ruling); Figma's neutral/surfaceBright was considered for the
|
|
20
|
+
selected fill and RULED OUT — container stays (gh#795's mirror). */
|
|
21
|
+
--nav-group-fg: var(--md-sys-color-neutral-high);
|
|
14
22
|
--nav-group-fg-hover: var(--a-fg-strong);
|
|
15
23
|
--nav-group-fg-muted: var(--a-fg-muted);
|
|
16
24
|
--nav-group-fg-selected: var(--a-fg-strong);
|
|
17
|
-
--nav-group-icon-fg
|
|
25
|
+
--nav-group-icon-fg: var(--md-sys-color-neutral);
|
|
26
|
+
--nav-group-icon-fg-selected: var(--a-fg-strong);
|
|
18
27
|
--nav-group-bg-hover: var(--a-bg-muted);
|
|
19
|
-
--nav-group-bg-selected: var(--a-bg-selected); /* mirrors --nav-item-bg-selected
|
|
28
|
+
--nav-group-bg-selected: var(--a-bg-selected); /* mirrors --nav-item-bg-selected (gh#795) */
|
|
20
29
|
--nav-group-icon-size: calc(var(--nav-group-row-height) - var(--a-space-2));
|
|
21
30
|
--nav-group-badge-size: var(--a-ui-sm);
|
|
22
31
|
|
|
@@ -119,7 +128,9 @@
|
|
|
119
128
|
width: var(--nav-group-icon-size);
|
|
120
129
|
height: var(--nav-group-icon-size);
|
|
121
130
|
flex-shrink: 0;
|
|
122
|
-
|
|
131
|
+
/* idle icon = NEUTRAL (gh#968 state spec), one step quieter than the
|
|
132
|
+
header's neutral-high text. */
|
|
133
|
+
color: var(--nav-group-icon-fg);
|
|
123
134
|
transition: color var(--nav-duration-fast) var(--nav-easing);
|
|
124
135
|
display: flex;
|
|
125
136
|
align-items: center;
|
|
@@ -40,11 +40,19 @@ nav-item-ui[selected] [slot="icon"]:empty::before {
|
|
|
40
40
|
--nav-item-font-size-sm: var(--a-ui-sm);
|
|
41
41
|
--nav-item-font-size-lg: var(--a-ui-lg);
|
|
42
42
|
--nav-item-font-weight: var(--a-weight-normal);
|
|
43
|
-
|
|
43
|
+
/* State color spec (gh#968, operator ruling 2026-08-11):
|
|
44
|
+
idle — bg transparent · icon NEUTRAL · text NEUTRAL-HIGH
|
|
45
|
+
hover — bg container-low · icon + text ON-SURFACE
|
|
46
|
+
selected — bg container · icon + text ON-SURFACE
|
|
47
|
+
Idle sits two quiet steps down (icon quieter than text); hover and
|
|
48
|
+
selected converge on on-surface. The selected ICON no longer takes
|
|
49
|
+
the primary accent (supersedes gh#522 for the glyph) — the accent
|
|
50
|
+
BAR remains the primary selection marker. */
|
|
51
|
+
--nav-item-fg: var(--md-sys-color-neutral-high);
|
|
44
52
|
--nav-item-fg-hover: var(--a-ui-text-hover);
|
|
45
53
|
--nav-item-fg-selected: var(--a-ui-text-selected);
|
|
46
|
-
--nav-item-icon-fg: var(--
|
|
47
|
-
--nav-item-icon-fg-selected: var(--
|
|
54
|
+
--nav-item-icon-fg: var(--md-sys-color-neutral);
|
|
55
|
+
--nav-item-icon-fg-selected: var(--a-ui-text-selected);
|
|
48
56
|
/* Two real container roles, one mechanism — no opacity synth.
|
|
49
57
|
hover = --a-bg-muted (--md-sys-color-neutral-container-low, 10%),
|
|
50
58
|
matching nav-group-ui's own --nav-group-bg-hover exactly.
|
|
@@ -15,7 +15,11 @@
|
|
|
15
15
|
--richtext-fg: var(--md-sys-color-neutral-on-surface);
|
|
16
16
|
--richtext-fg-muted: var(--a-fg-muted);
|
|
17
17
|
--richtext-fg-subtle: var(--md-sys-color-neutral-on-surface-variant);
|
|
18
|
-
|
|
18
|
+
/* Container-low (10% alpha), not an opaque canvas tier (gh#972): the
|
|
19
|
+
inline chip must RECEDE behind prose and composite consistently on
|
|
20
|
+
any stacked surface — an opaque canvas-2 chip read heavy on the
|
|
21
|
+
page and inverted inside cards. Same tier as --a-bg-muted/hover. */
|
|
22
|
+
--richtext-code-bg: var(--md-sys-color-neutral-container-low);
|
|
19
23
|
--richtext-border: var(--md-sys-color-neutral-outline-variant);
|
|
20
24
|
--richtext-link: var(--a-primary-bg);
|
|
21
25
|
--richtext-link-hover: var(--md-sys-color-primary);
|
|
@@ -19,6 +19,13 @@ segment-ui[selected] {
|
|
|
19
19
|
--segment-font-size: var(--a-ui-size);
|
|
20
20
|
--segment-font-weight: var(--a-ui-weight);
|
|
21
21
|
--segment-font-family: var(--a-font-family-ui);
|
|
22
|
+
/* Leading for the [text] label box ONLY — never the host. The host's
|
|
23
|
+
`line-height: 1` is deliberate, but the label's ::after clips to its
|
|
24
|
+
own line box, so inheriting 1 makes half-leading negative and shears
|
|
25
|
+
descenders (g/j/p/q/y, parentheses) by ~0.93px at the default size —
|
|
26
|
+
gh#906. Snug (1.3) contains the font's content box at every [size];
|
|
27
|
+
mirrors --button-label-line-height. */
|
|
28
|
+
--segment-label-line-height: var(--a-leading-snug);
|
|
22
29
|
|
|
23
30
|
/* ── Colors ── */
|
|
24
31
|
--segment-fg: var(--a-ui-text-subtle);
|
|
@@ -92,6 +99,9 @@ segment-ui[selected] {
|
|
|
92
99
|
overflow: hidden;
|
|
93
100
|
text-overflow: ellipsis;
|
|
94
101
|
white-space: nowrap;
|
|
102
|
+
/* `overflow: hidden` (needed for the ellipsis) makes this line box the
|
|
103
|
+
clip box — give it leading tall enough to contain the descenders. */
|
|
104
|
+
line-height: var(--segment-label-line-height);
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
/* States — hover + [selected] rules moved outside @scope; see Safari 17.x bug note at top. */
|
|
@@ -14,9 +14,15 @@
|
|
|
14
14
|
--step-progress-caption-fg: var(--a-fg-muted);
|
|
15
15
|
--step-progress-caption-size: var(--a-ui-sm);
|
|
16
16
|
|
|
17
|
-
/* Sizing —
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
/* Sizing — 18rem of track width, shrinkable to 10rem, matching the
|
|
18
|
+
bespoke shape in registration + onboarding. Expressed as inline-size
|
|
19
|
+
rather than flex-basis (gh#942): a flex-basis of 18rem is
|
|
20
|
+
direction-fragile — inside a COLUMN-direction flex parent (col-ui,
|
|
21
|
+
any stacked wizard) the same basis constrains HEIGHT, inflating the
|
|
22
|
+
~30px component to 288px. inline-size constrains width in both flex
|
|
23
|
+
directions. Consumers can override. */
|
|
24
|
+
--step-progress-flex: 0 1 auto;
|
|
25
|
+
--step-progress-inline-size: 18rem;
|
|
20
26
|
--step-progress-min-width: 10rem;
|
|
21
27
|
}
|
|
22
28
|
|
|
@@ -25,6 +31,13 @@
|
|
|
25
31
|
flex-direction: column;
|
|
26
32
|
gap: var(--step-progress-gap);
|
|
27
33
|
flex: var(--step-progress-flex);
|
|
34
|
+
/* Deliberate SCOPE-EXTENT exception: this component has always shipped
|
|
35
|
+
a default extent (previously flex-basis 18rem + min-width 10rem —
|
|
36
|
+
extent by another name, just direction-fragile). Both remain
|
|
37
|
+
token-overridable; going fully size-agnostic would silently reflow
|
|
38
|
+
the registration/onboarding row-header consumers. */
|
|
39
|
+
inline-size: var(--step-progress-inline-size);
|
|
40
|
+
max-inline-size: 100%;
|
|
28
41
|
min-width: var(--step-progress-min-width);
|
|
29
42
|
}
|
|
30
43
|
|
|
@@ -282,10 +282,18 @@ registerCellType('progress', {
|
|
|
282
282
|
wrapper = document.createElement('row-ui');
|
|
283
283
|
wrapper.setAttribute('gap', '2');
|
|
284
284
|
wrapper.setAttribute('align', 'center');
|
|
285
|
+
// The gridcell is a flex container; without an explicit flex the
|
|
286
|
+
// wrapper shrinks to content — and a progress bar has no intrinsic
|
|
287
|
+
// width, so the whole cell collapsed to ~33px and the value span
|
|
288
|
+
// ellipsized to "7.." (gh#940). Fill the cell; keep the numeric
|
|
289
|
+
// label un-shrinkable so the BAR absorbs the flexing instead.
|
|
290
|
+
wrapper.style.flex = '1';
|
|
291
|
+
wrapper.style.minWidth = '0';
|
|
285
292
|
const prog = document.createElement('progress-ui');
|
|
286
293
|
prog.setAttribute('size', 'sm');
|
|
287
294
|
prog.style.flex = '1';
|
|
288
295
|
const span = document.createElement('span');
|
|
296
|
+
span.style.flexShrink = '0';
|
|
289
297
|
wrapper.appendChild(prog);
|
|
290
298
|
wrapper.appendChild(span);
|
|
291
299
|
cell.appendChild(wrapper);
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"properties": {
|
|
16
16
|
"columns": {
|
|
17
|
-
"description": "Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign=\"top\">).",
|
|
17
|
+
"description": "Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign=\"top\">); col-def also accepts `meta` as a JSON attribute (gh#925) — e.g. <col-def type=\"badge\" meta='{\"variants\":{\"Online\":\"success\"}}'> — so cell-type meta (badge variant maps, currency codes) is expressible in static HTML.",
|
|
18
18
|
"$ref": "common_types.json#/$defs/DynamicStringList"
|
|
19
19
|
},
|
|
20
20
|
"component": {
|
|
@@ -432,6 +432,17 @@ export class UITable extends UIElement {
|
|
|
432
432
|
const flex = el.getAttribute('flex');
|
|
433
433
|
if (flex) col.flex = Number(flex);
|
|
434
434
|
|
|
435
|
+
/* gh#925 — cell-type meta (e.g. the badge type's per-value variant
|
|
436
|
+
map, currency's currency code) had no declarative form: columns[]
|
|
437
|
+
carries `meta` but col-def never parsed it, so a static-HTML badge
|
|
438
|
+
column was structurally stuck at the unmapped tone="muted"
|
|
439
|
+
fallback. JSON attribute, same silent-on-malformed discipline as
|
|
440
|
+
the host's `data` attribute (gh#288). */
|
|
441
|
+
const meta = el.getAttribute('meta');
|
|
442
|
+
if (meta) {
|
|
443
|
+
try { col.meta = JSON.parse(meta); } catch (_) { /* malformed JSON — ignored */ }
|
|
444
|
+
}
|
|
445
|
+
|
|
435
446
|
cols.push(col);
|
|
436
447
|
el.remove();
|
|
437
448
|
}
|
|
@@ -313,8 +313,17 @@
|
|
|
313
313
|
white-space: nowrap;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
/* Must match the span/a rule above: without flex:1, row-ui (a flex
|
|
317
|
+
child with default flex:0 1 auto) sizes to its own shrink-to-fit
|
|
318
|
+
content instead of filling the cell — avatar/progress composite
|
|
319
|
+
types then split that tiny intrinsic width between their
|
|
320
|
+
progress-ui/avatar-ui and label span, clipping the label (gh#940:
|
|
321
|
+
stock "71%" rendered as "7.."). Generalizes the per-renderer inline
|
|
322
|
+
flex fix from PR #945 to every composite cell type; adopted from
|
|
323
|
+
PR #943's analysis. */
|
|
316
324
|
[data-body] [role="gridcell"] > row-ui {
|
|
317
325
|
min-width: 0;
|
|
326
|
+
flex: 1;
|
|
318
327
|
overflow: hidden;
|
|
319
328
|
}
|
|
320
329
|
|
|
@@ -86,7 +86,7 @@ export interface TableSortEventDetail {
|
|
|
86
86
|
export type TableSortEvent = CustomEvent<TableSortEventDetail>;
|
|
87
87
|
|
|
88
88
|
export class UITable extends UIElement {
|
|
89
|
-
/** Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign="top">). */
|
|
89
|
+
/** Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign="top">); col-def also accepts `meta` as a JSON attribute (gh#925) — e.g. <col-def type="badge" meta='{"variants":{"Online":"success"}}'> — so cell-type meta (badge variant maps, currency codes) is expressible in static HTML. */
|
|
90
90
|
columns: string;
|
|
91
91
|
/** JS property (set programmatically — `el.data = [...]`) — row records, an array of plain objects keyed to columns[].key. May also be supplied declaratively as a JSON-array `data="[…]"` attribute, hydrated once at connect (gh#288) — the same pattern chart-ui's `data` attribute uses. Custom accessor on the element class, not a reflected attribute. */
|
|
92
92
|
data: string;
|
|
@@ -20,7 +20,7 @@ composes:
|
|
|
20
20
|
- badge-ui
|
|
21
21
|
props:
|
|
22
22
|
columns:
|
|
23
|
-
description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign="top">).
|
|
23
|
+
description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign="top">); col-def also accepts `meta` as a JSON attribute (gh#925) — e.g. <col-def type="badge" meta='{"variants":{"Online":"success"}}'> — so cell-type meta (badge variant maps, currency codes) is expressible in static HTML.
|
|
24
24
|
type: array
|
|
25
25
|
default: []
|
|
26
26
|
dynamic: true # JS-set collection prop with a custom setter — deliberately not in static properties
|
package/components/tabs/tabs.css
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
--tabs-bg: transparent;
|
|
6
6
|
--tabs-radius: var(--a-radius-md);
|
|
7
7
|
--tabs-button-height: var(--a-size);
|
|
8
|
-
--
|
|
8
|
+
/* One rung UP the --a-ui-* scale (gh#965): tab labels are primary
|
|
9
|
+
navigation, not control chrome. */
|
|
10
|
+
--tabs-font-size: var(--a-ui-lg);
|
|
9
11
|
--tabs-font-family: var(--a-font-family-ui);
|
|
10
12
|
/* Strip owns the button-to-button spacing — 4px base gap + one
|
|
11
13
|
padding unit (8px) folded in, since tab-button carries no
|
package/components/tag/tag.css
CHANGED
|
@@ -30,7 +30,10 @@ tag-ui[removable]:not([disabled]):hover {
|
|
|
30
30
|
--tag-px: var(--a-space-1-5);
|
|
31
31
|
--tag-py: var(--a-space-0-5);
|
|
32
32
|
--tag-gap: var(--a-space-1);
|
|
33
|
-
--
|
|
33
|
+
/* One rung down the --a-ui-* scale (gh#965): tags read as metadata
|
|
34
|
+
chrome, not body copy. Whole tier ladder shifts with it (sm=2xs,
|
|
35
|
+
lg=sm) so the tiers stay one rung apart. */
|
|
36
|
+
--tag-font: var(--a-ui-xs);
|
|
34
37
|
|
|
35
38
|
/* ── Size tiers (gh#779) — md (above) is the pixel-identical default;
|
|
36
39
|
[size="sm"|"lg"] below re-point the active tokens. Component-owned
|
|
@@ -45,8 +48,8 @@ tag-ui[removable]:not([disabled]):hover {
|
|
|
45
48
|
--tag-px-lg: var(--a-space-2);
|
|
46
49
|
--tag-py-sm: var(--a-space-px);
|
|
47
50
|
--tag-py-lg: var(--a-space-1);
|
|
48
|
-
--tag-font-sm: var(--a-ui-
|
|
49
|
-
--tag-font-lg: var(--a-ui-
|
|
51
|
+
--tag-font-sm: var(--a-ui-2xs);
|
|
52
|
+
--tag-font-lg: var(--a-ui-sm);
|
|
50
53
|
--tag-font-family: var(--a-font-family-ui);
|
|
51
54
|
--tag-dismiss-size: 1rem;
|
|
52
55
|
--tag-bg-hover: var(--a-bg-hover);
|
|
@@ -292,7 +295,7 @@ tag-ui[removable]:not([disabled]):hover {
|
|
|
292
295
|
:scope[size="md"] {
|
|
293
296
|
--tag-px: var(--a-space-1-5);
|
|
294
297
|
--tag-py: var(--a-space-0-5);
|
|
295
|
-
--tag-font: var(--a-ui-
|
|
298
|
+
--tag-font: var(--a-ui-xs);
|
|
296
299
|
}
|
|
297
300
|
:scope[size="lg"] {
|
|
298
301
|
--tag-px: var(--tag-px-lg);
|
package/core/element.js
CHANGED
|
@@ -176,12 +176,43 @@ export class UIElement extends HTMLElement {
|
|
|
176
176
|
// compliant environments: the value already matches (the property
|
|
177
177
|
// setter's Object.is check short-circuits), so this never causes an
|
|
178
178
|
// extra render pass on browsers/environments that already got it right.
|
|
179
|
+
// The whole pre-sync loop runs UNTRACKED: connectedCallback commonly
|
|
180
|
+
// fires synchronously inside a PARENT's render effect (template-created
|
|
181
|
+
// children connect during stamp()), and the gh#961 branch below READS
|
|
182
|
+
// `this[key]` — a tracked read there subscribes the parent's effect to
|
|
183
|
+
// this child's signals, so any later child-prop write (a `.checked`
|
|
184
|
+
// template apply) re-dirties the parent, which re-stamps fresh
|
|
185
|
+
// children, whose own mount-reads re-subscribe: the unbounded
|
|
186
|
+
// parent↔child oscillation the signals drain-loop guard traps (the
|
|
187
|
+
// admin-settings/switch-ui/button-ui loop on gh#961). Same rationale
|
|
188
|
+
// as the untracked(connected()) wrapper below.
|
|
189
|
+
untracked(() => {
|
|
179
190
|
for (const [key, cfg] of Object.entries(ctor.properties)) {
|
|
180
191
|
const attr = cfg.attribute ?? key.toLowerCase();
|
|
181
192
|
if (this.hasAttribute(attr)) {
|
|
182
193
|
this[key] = parseAttr(this.getAttribute(attr), cfg.type ?? String);
|
|
194
|
+
} else if (cfg.reflect && cfg.type === Boolean && this[key] === true) {
|
|
195
|
+
// gh#961 — stamp the initial attribute for a TRUE-default reflected
|
|
196
|
+
// boolean. installProps seeds the signal with the default but can't
|
|
197
|
+
// touch attributes (constructor restriction), and the property
|
|
198
|
+
// setter's Object.is short-circuit means a later same-value set
|
|
199
|
+
// (e.g. chat-thread's #syncEmptyFromChildren recomputing `empty:
|
|
200
|
+
// true` on first connect) never reflects either — so CSS gates like
|
|
201
|
+
// `chat-thread:not([empty]) > chat-empty` and
|
|
202
|
+
// `editor-canvas:not([empty]) > editor-canvas-empty` read the
|
|
203
|
+
// wrong state forever. Deliberately scoped to Boolean-true ONLY:
|
|
204
|
+
// reflecting string/number defaults here stamps `text=""`-shaped
|
|
205
|
+
// attributes framework-wide, flipping `[attr]` CSS gates and
|
|
206
|
+
// cascading re-renders into the signals drain-loop guard (the
|
|
207
|
+
// full-reflect attempt documented on gh#961). A false default is
|
|
208
|
+
// already a DOM no-op (removeAttribute on an absent attribute).
|
|
209
|
+
// The setAttribute replays attributeChangedCallback → parseAttr →
|
|
210
|
+
// same value → Object.is short-circuit: no signal write, no
|
|
211
|
+
// render pass.
|
|
212
|
+
this.setAttribute(attr, '');
|
|
183
213
|
}
|
|
184
214
|
}
|
|
215
|
+
});
|
|
185
216
|
// connected() commonly reads this element's reactive properties via
|
|
186
217
|
// template-literal interpolation (`${this.label}` etc.) to stamp its
|
|
187
218
|
// inner DOM. If the outer call path is inside another effect (e.g.
|
package/core/signals.js
CHANGED
|
@@ -32,7 +32,9 @@ function flushSigs() {
|
|
|
32
32
|
while ((q = pending)) {
|
|
33
33
|
if (++loops > 100) {
|
|
34
34
|
pending = null;
|
|
35
|
-
|
|
35
|
+
// Name the still-dirty effect hosts — a bare count is undiagnosable
|
|
36
|
+
// (gh#961's oscillation took host attribution to even locate).
|
|
37
|
+
console.error(`signals: drain loop exceeded 100 iterations`, JSON.stringify([...q].map(s => s.host || '?')));
|
|
36
38
|
break;
|
|
37
39
|
}
|
|
38
40
|
pending = null;
|