@adia-ai/web-components 0.8.4 → 0.8.5
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 +18 -1
- package/USAGE.md +60 -25
- package/components/adia-mark/adia-mark.a2ui.json +123 -0
- package/components/adia-mark/adia-mark.class.js +64 -0
- package/components/adia-mark/adia-mark.css +48 -0
- package/components/adia-mark/adia-mark.d.ts +20 -0
- package/components/adia-mark/adia-mark.examples.md +26 -0
- package/components/adia-mark/adia-mark.js +17 -0
- package/components/adia-mark/adia-mark.test.js +69 -0
- package/components/adia-mark/adia-mark.yaml +121 -0
- package/components/agent-questions/agent-questions.a2ui.json +2 -1
- package/components/agent-questions/agent-questions.class.js +96 -53
- package/components/agent-questions/agent-questions.css +27 -106
- package/components/agent-questions/agent-questions.yaml +1 -0
- package/components/button/button.class.js +5 -1
- package/components/calendar-grid/calendar-grid.a2ui.json +3 -1
- package/components/calendar-grid/calendar-grid.class.js +11 -6
- package/components/calendar-grid/calendar-grid.css +42 -20
- package/components/calendar-grid/calendar-grid.yaml +5 -0
- package/components/calendar-picker/calendar-picker.a2ui.json +3 -1
- package/components/calendar-picker/calendar-picker.class.js +11 -6
- package/components/calendar-picker/calendar-picker.css +39 -19
- package/components/calendar-picker/calendar-picker.yaml +5 -0
- package/components/chart/chart.class.js +17 -14
- package/components/color-input/color-input.class.js +8 -5
- package/components/color-picker/color-picker.class.js +6 -3
- package/components/field/field.class.js +10 -7
- package/components/fields/fields.class.js +15 -12
- package/components/icon/icon.class.js +9 -5
- package/components/index.js +1 -0
- package/components/nav/nav.class.js +2 -1
- package/components/noodles/noodles.class.js +14 -10
- package/components/page/page.class.js +3 -2
- package/components/select/select.class.js +3 -2
- package/components/swatch/swatch.class.js +27 -24
- package/components/swiper/swiper.class.js +4 -1
- package/components/swiper/swiper.css +13 -4
- package/components/toolbar/toolbar.class.js +11 -6
- package/components/tree/tree.class.js +5 -2
- package/core/data-stream.js +21 -15
- package/core/element.js +36 -1
- package/core/element.test.js +67 -0
- package/core/icons-phosphor.js +86 -22
- package/core/icons.js +22 -8
- package/dist/theme-provider.min.js +2 -2
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +107 -102
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -5
- package/styles/components.css +1 -0
- package/traits/error-shake/error-shake.js +23 -17
- package/traits/fade-presence/fade-presence.js +19 -15
- package/traits/success-checkmark/success-checkmark.js +21 -16
|
@@ -232,14 +232,22 @@ calendar-picker-ui [data-cal-title] {
|
|
|
232
232
|
color: var(--calendar-picker-title-fg);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
/* Stamped <button-ui variant="ghost" size="xs" icon="caret-…"> (gh issue 276
|
|
236
|
+
wave 4) — icon-only (no [text], no child text) so button-ui's own
|
|
237
|
+
[data-icon-only] rule already squares it to --button-height; pointing
|
|
238
|
+
--button-height at the nav size (not just literal height below) avoids
|
|
239
|
+
the min-width/min-height floor trap. Literal background/color below
|
|
240
|
+
out-specify button.css's own via the `button-ui` type-selector bump
|
|
241
|
+
(same technique as swiper's dots). */
|
|
242
|
+
calendar-picker-ui button-ui[data-cal-prev],
|
|
243
|
+
calendar-picker-ui button-ui[data-cal-next] {
|
|
244
|
+
--button-height: var(--calendar-picker-nav-size);
|
|
245
|
+
--a-icon-size: var(--calendar-picker-nav-icon-size);
|
|
246
|
+
|
|
237
247
|
display: inline-flex;
|
|
238
248
|
align-items: center;
|
|
239
249
|
justify-content: center;
|
|
240
|
-
width: var(--calendar-picker-nav-size);
|
|
241
250
|
height: var(--calendar-picker-nav-size);
|
|
242
|
-
padding: 0;
|
|
243
251
|
border: none;
|
|
244
252
|
border-radius: var(--calendar-picker-nav-radius);
|
|
245
253
|
background: var(--calendar-picker-nav-bg);
|
|
@@ -248,10 +256,9 @@ calendar-picker-ui [data-cal-next] {
|
|
|
248
256
|
transition:
|
|
249
257
|
background var(--calendar-picker-duration) var(--calendar-picker-easing),
|
|
250
258
|
color var(--calendar-picker-duration) var(--calendar-picker-easing);
|
|
251
|
-
--a-icon-size: var(--calendar-picker-nav-icon-size);
|
|
252
259
|
}
|
|
253
|
-
calendar-picker-ui [data-cal-prev]:hover,
|
|
254
|
-
calendar-picker-ui [data-cal-next]:hover {
|
|
260
|
+
calendar-picker-ui button-ui[data-cal-prev]:hover,
|
|
261
|
+
calendar-picker-ui button-ui[data-cal-next]:hover {
|
|
255
262
|
background: var(--calendar-picker-nav-bg-hover);
|
|
256
263
|
color: var(--calendar-picker-nav-fg-hover);
|
|
257
264
|
}
|
|
@@ -281,8 +288,18 @@ calendar-picker-ui [data-cal-grid] {
|
|
|
281
288
|
gap: var(--calendar-picker-day-grid-gap);
|
|
282
289
|
}
|
|
283
290
|
|
|
284
|
-
/* Day buttons
|
|
285
|
-
|
|
291
|
+
/* Day buttons — stamped <button-ui variant="ghost" text="…"> (gh issue 276
|
|
292
|
+
wave 4). button-ui floors min-width/min-height at --button-height — point
|
|
293
|
+
it at the day-cell size (not just the literal width/height below) so the
|
|
294
|
+
floor never clamps a smaller [size] tier. The literal geometry/color
|
|
295
|
+
declarations below out-specify button-ui's own equivalents via the
|
|
296
|
+
`button-ui` type-selector bump (same technique as swiper's dots).
|
|
297
|
+
button-ui renders its label via `::after { content: attr(text) }` — the
|
|
298
|
+
today-indicator dot below therefore uses `::before` (kept free), not
|
|
299
|
+
`::after`, to avoid the two fighting over the same pseudo-element. */
|
|
300
|
+
calendar-picker-ui button-ui[data-cal-day] {
|
|
301
|
+
--button-height: var(--calendar-picker-day-size);
|
|
302
|
+
|
|
286
303
|
display: flex;
|
|
287
304
|
align-items: center;
|
|
288
305
|
justify-content: center;
|
|
@@ -304,25 +321,25 @@ calendar-picker-ui [data-cal-day] {
|
|
|
304
321
|
}
|
|
305
322
|
|
|
306
323
|
/* ── State: hover ── */
|
|
307
|
-
calendar-picker-ui [data-cal-day]:not([disabled]):not([data-outside]):hover {
|
|
324
|
+
calendar-picker-ui button-ui[data-cal-day]:not([disabled]):not([data-outside]):hover {
|
|
308
325
|
background: var(--calendar-picker-day-bg-hover);
|
|
309
326
|
color: var(--calendar-picker-day-fg-hover);
|
|
310
327
|
}
|
|
311
328
|
|
|
312
329
|
/* ── State: focus ── */
|
|
313
|
-
calendar-picker-ui [data-cal-day]:not([disabled]):not([data-outside]):focus-visible,
|
|
314
|
-
calendar-picker-ui [data-cal-day][data-focused] {
|
|
330
|
+
calendar-picker-ui button-ui[data-cal-day]:not([disabled]):not([data-outside]):focus-visible,
|
|
331
|
+
calendar-picker-ui button-ui[data-cal-day][data-focused] {
|
|
315
332
|
outline: none;
|
|
316
333
|
box-shadow: var(--calendar-picker-day-focus-ring);
|
|
317
334
|
}
|
|
318
335
|
|
|
319
336
|
/* ── State: selected ── */
|
|
320
|
-
calendar-picker-ui [data-cal-day][data-selected] {
|
|
337
|
+
calendar-picker-ui button-ui[data-cal-day][data-selected] {
|
|
321
338
|
background: var(--calendar-picker-day-bg-selected);
|
|
322
339
|
color: var(--calendar-picker-day-fg-selected);
|
|
323
340
|
font-weight: var(--calendar-picker-selected-weight);
|
|
324
341
|
}
|
|
325
|
-
calendar-picker-ui [data-cal-day][data-selected]:not([disabled]):hover {
|
|
342
|
+
calendar-picker-ui button-ui[data-cal-day][data-selected]:not([disabled]):hover {
|
|
326
343
|
background: var(--calendar-picker-day-bg-selected-hover);
|
|
327
344
|
color: var(--calendar-picker-day-fg-selected);
|
|
328
345
|
}
|
|
@@ -330,12 +347,12 @@ calendar-picker-ui [data-cal-day][data-selected]:not([disabled]):hover {
|
|
|
330
347
|
/* ── State: today (unselected) ──
|
|
331
348
|
Use a small bottom dot so it does not compete with the rounded
|
|
332
349
|
focus-ring box-shadow (that looked like a double outline). */
|
|
333
|
-
calendar-picker-ui [data-cal-day][data-today]:not([data-selected]) {
|
|
350
|
+
calendar-picker-ui button-ui[data-cal-day][data-today]:not([data-selected]) {
|
|
334
351
|
position: relative;
|
|
335
352
|
color: var(--calendar-picker-day-today-border);
|
|
336
353
|
font-weight: var(--calendar-picker-today-weight);
|
|
337
354
|
}
|
|
338
|
-
calendar-picker-ui [data-cal-day][data-today]:not([data-selected])::
|
|
355
|
+
calendar-picker-ui button-ui[data-cal-day][data-today]:not([data-selected])::before {
|
|
339
356
|
content: "";
|
|
340
357
|
position: absolute;
|
|
341
358
|
left: 50%;
|
|
@@ -349,13 +366,16 @@ calendar-picker-ui [data-cal-day][data-today]:not([data-selected])::after {
|
|
|
349
366
|
}
|
|
350
367
|
|
|
351
368
|
/* ── State: outside month ── */
|
|
352
|
-
calendar-picker-ui [data-cal-day][data-outside] {
|
|
369
|
+
calendar-picker-ui button-ui[data-cal-day][data-outside] {
|
|
353
370
|
color: var(--calendar-picker-day-fg-outside);
|
|
354
371
|
cursor: default;
|
|
355
372
|
}
|
|
356
373
|
|
|
357
|
-
/* ── State: disabled ──
|
|
358
|
-
|
|
374
|
+
/* ── State: disabled ──
|
|
375
|
+
`[disabled]` — NOT the `:disabled` pseudo-class, which only matches
|
|
376
|
+
native form controls; button-ui is a custom element, so `:disabled`
|
|
377
|
+
silently never matches it. */
|
|
378
|
+
calendar-picker-ui button-ui[data-cal-day][disabled]:not([data-outside]) {
|
|
359
379
|
color: var(--calendar-picker-day-fg-disabled);
|
|
360
380
|
cursor: not-allowed;
|
|
361
381
|
}
|
|
@@ -11,6 +11,11 @@ description: >-
|
|
|
11
11
|
supports min/max and disabled-date masks. Use for single-date input in
|
|
12
12
|
forms; for date ranges compose two pickers or use a dedicated range
|
|
13
13
|
component.
|
|
14
|
+
# Per ADR-0027 — primitives that programmatically create other primitives
|
|
15
|
+
# in their render() do NOT auto-import them. Consumer (or demo shell) must
|
|
16
|
+
# explicitly import each composed primitive.
|
|
17
|
+
composes:
|
|
18
|
+
- button-ui # day cells + month nav (gh issue 276 wave 4)
|
|
14
19
|
props:
|
|
15
20
|
name:
|
|
16
21
|
description: Form field name
|
|
@@ -341,21 +341,24 @@ export class UIChart extends UIElement {
|
|
|
341
341
|
this.addEventListener('pointerdown', this.#onPointerDown);
|
|
342
342
|
this.addEventListener('click', this.#onClick);
|
|
343
343
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
this.#resizeRaf
|
|
355
|
-
this.#
|
|
344
|
+
// gh#285 — SSR DOM shims (linkedom) have no ResizeObserver global.
|
|
345
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
346
|
+
this.#resizeObs = new ResizeObserver((entries) => {
|
|
347
|
+
const { inlineSize: w, blockSize: h } = entries[0].contentBoxSize[0];
|
|
348
|
+
if (!this.#data.length) return;
|
|
349
|
+
const rw = Math.round(w);
|
|
350
|
+
const rh = Math.round(h);
|
|
351
|
+
if (rw === this.#lastW && (!this.hasAttribute('resize') || rh === this.#lastH)) return;
|
|
352
|
+
this.#lastW = rw;
|
|
353
|
+
this.#lastH = rh;
|
|
354
|
+
if (this.#resizeRaf) return;
|
|
355
|
+
this.#resizeRaf = requestAnimationFrame(() => {
|
|
356
|
+
this.#resizeRaf = null;
|
|
357
|
+
this.#renderChart();
|
|
358
|
+
});
|
|
356
359
|
});
|
|
357
|
-
|
|
358
|
-
|
|
360
|
+
this.#resizeObs.observe(this);
|
|
361
|
+
}
|
|
359
362
|
}
|
|
360
363
|
|
|
361
364
|
render() {
|
|
@@ -169,11 +169,14 @@ export class UIColorInput extends UIFormElement {
|
|
|
169
169
|
// Store the observer in a #field so disconnected() can clean it up
|
|
170
170
|
// — without the explicit reference, the observer survives the host
|
|
171
171
|
// element across mount/unmount cycles (audit-lifecycle-leak).
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
172
|
+
// gh#285 — SSR DOM shims (linkedom) have no MutationObserver global.
|
|
173
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
174
|
+
this.#popoverObserver = new MutationObserver(sync);
|
|
175
|
+
this.#popoverObserver.observe(this.#popover, {
|
|
176
|
+
attributes: true,
|
|
177
|
+
attributeFilter: ['open'],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
#onPickerChange = (e) => {
|
|
@@ -362,9 +362,12 @@ export class UIColorPicker extends UIFormElement {
|
|
|
362
362
|
|
|
363
363
|
this.#hueTrack.addEventListener('keydown', this.#onHueKeydown);
|
|
364
364
|
|
|
365
|
-
// Observe area resize to redraw canvas
|
|
366
|
-
|
|
367
|
-
|
|
365
|
+
// Observe area resize to redraw canvas.
|
|
366
|
+
// gh#285 — SSR DOM shims (linkedom) have no ResizeObserver global.
|
|
367
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
368
|
+
this.#areaResizeObs = new ResizeObserver(() => this.#drawArea());
|
|
369
|
+
this.#areaResizeObs.observe(this.#areaEl);
|
|
370
|
+
}
|
|
368
371
|
}
|
|
369
372
|
|
|
370
373
|
disconnected() {
|
|
@@ -95,13 +95,16 @@ export class UIField extends UIElement {
|
|
|
95
95
|
this.#bindForToControl();
|
|
96
96
|
this.#bindErrorListener();
|
|
97
97
|
this.#labelEl?.addEventListener('click', this.#onLabelClick);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.#
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
// gh#285 — SSR DOM shims (linkedom) have no MutationObserver global.
|
|
99
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
100
|
+
this.#mo = new MutationObserver(() => {
|
|
101
|
+
this.#bindForToControl();
|
|
102
|
+
this.#bindErrorListener();
|
|
103
|
+
this.#wireAriaDescribedBy();
|
|
104
|
+
this.#syncErrorMessage();
|
|
105
|
+
});
|
|
106
|
+
this.#mo.observe(this, { childList: true });
|
|
107
|
+
}
|
|
105
108
|
this.#syncErrorMessage();
|
|
106
109
|
}
|
|
107
110
|
|
|
@@ -51,19 +51,22 @@ export class UIFields extends UIElement {
|
|
|
51
51
|
|
|
52
52
|
connected() {
|
|
53
53
|
this.#syncInline();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
// gh#285 — SSR DOM shims (linkedom) have no MutationObserver global.
|
|
55
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
56
|
+
this.#mo = new MutationObserver((records) => {
|
|
57
|
+
// Only re-sync on childList changes that involve direct <field-ui>
|
|
58
|
+
// children — attribute mutations on grandchildren aren't ours to
|
|
59
|
+
// care about.
|
|
60
|
+
for (const r of records) {
|
|
61
|
+
if (r.type !== 'childList') continue;
|
|
62
|
+
if (this.#hasFieldChild(r.addedNodes) || this.#hasFieldChild(r.removedNodes)) {
|
|
63
|
+
this.#syncInline();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
63
66
|
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
});
|
|
68
|
+
this.#mo.observe(this, { childList: true });
|
|
69
|
+
}
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
disconnected() {
|
|
@@ -30,13 +30,17 @@ function warnIfIconRegistryUnwired() {
|
|
|
30
30
|
// eslint-disable-next-line no-console
|
|
31
31
|
console.error(
|
|
32
32
|
`[icon-ui] No icons are registered and no icon loaders were installed — every <icon-ui> renders blank.\n` +
|
|
33
|
-
`
|
|
34
|
-
`
|
|
33
|
+
` Two known causes (gh#287): (1) Vite's dev-server optimizeDeps pre-bundled this package, which empties\n` +
|
|
34
|
+
` the auto-discovery glob — add to your Vite config:\n` +
|
|
35
|
+
` optimizeDeps: { exclude: ['@adia-ai/web-components', '@adia-ai/web-modules'] }\n` +
|
|
36
|
+
` (2) @phosphor-icons/core is missing or this file was re-nested by a packaging step (Yarn PnP, a custom\n` +
|
|
37
|
+
` bundler). If neither applies, install loaders manually with plain imports from your APP entry:\n` +
|
|
35
38
|
` npm i @phosphor-icons/core\n` +
|
|
36
39
|
` import { installIconLoaders } from '@adia-ai/web-components/core/icons';\n` +
|
|
37
|
-
`
|
|
38
|
-
`
|
|
39
|
-
`
|
|
40
|
+
` import caretRight from '@phosphor-icons/core/assets/regular/caret-right.svg?raw';\n` +
|
|
41
|
+
` installIconLoaders({ regular: {\n` +
|
|
42
|
+
` '/node_modules/@phosphor-icons/core/assets/regular/caret-right.svg': caretRight,\n` +
|
|
43
|
+
` } });\n` +
|
|
40
44
|
` See USAGE.md § Icons.`,
|
|
41
45
|
);
|
|
42
46
|
}, 0);
|
package/components/index.js
CHANGED
|
@@ -58,6 +58,7 @@ export { UISwiper } from './swiper/swiper.js';
|
|
|
58
58
|
export { UIUpload } from './upload/upload.js';
|
|
59
59
|
export { UICard } from './card/card.js';
|
|
60
60
|
export { UIAvatar, UIAvatarGroup } from './avatar/avatar.js';
|
|
61
|
+
export { UIAdiaMark } from './adia-mark/adia-mark.js';
|
|
61
62
|
export { UIProgress } from './progress/progress.js';
|
|
62
63
|
export { UIStepProgress } from './step-progress/step-progress.js';
|
|
63
64
|
export { UISkeleton } from './skeleton/skeleton.js';
|
|
@@ -61,7 +61,8 @@ export class UINav extends UIElement {
|
|
|
61
61
|
this.addEventListener('click', this.#onClick);
|
|
62
62
|
|
|
63
63
|
// ResizeObserver only meaningful for primary variant — section is static.
|
|
64
|
-
|
|
64
|
+
// gh#285 — SSR DOM shims (linkedom) have no ResizeObserver global.
|
|
65
|
+
if (this.variant !== 'section' && typeof ResizeObserver !== 'undefined') {
|
|
65
66
|
this.#ro = new ResizeObserver(() => this.#updateTooltips());
|
|
66
67
|
this.#ro.observe(this);
|
|
67
68
|
}
|
|
@@ -108,19 +108,23 @@ export class UINoodles extends UIElement {
|
|
|
108
108
|
this.#svg.setAttribute('data-noodle-svg', '');
|
|
109
109
|
this.prepend(this.#svg);
|
|
110
110
|
|
|
111
|
-
// Observers for child layout changes
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
this.#
|
|
115
|
-
this.#
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
111
|
+
// Observers for child layout changes.
|
|
112
|
+
// gh#285 — SSR DOM shims (linkedom) have neither observer global.
|
|
113
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
114
|
+
this.#resizeObs = new ResizeObserver(() => this.#scheduleUpdate());
|
|
115
|
+
this.#resizeObs.observe(this);
|
|
116
|
+
}
|
|
117
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
118
|
+
this.#mutationObs = new MutationObserver(() => {
|
|
119
|
+
this.#rebuildPortIndicators();
|
|
120
|
+
this.#scheduleUpdate();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
119
123
|
this.#observeMutations();
|
|
120
124
|
|
|
121
125
|
// Observe each port-bearing child
|
|
122
126
|
for (const child of this.#getPortChildren()) {
|
|
123
|
-
this.#resizeObs
|
|
127
|
+
this.#resizeObs?.observe(child);
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
this.#rebuildPortIndicators();
|
|
@@ -181,7 +185,7 @@ export class UINoodles extends UIElement {
|
|
|
181
185
|
// Sync resize observer with current port children
|
|
182
186
|
const portChildren = this.#getPortChildren();
|
|
183
187
|
for (const child of portChildren) {
|
|
184
|
-
this.#resizeObs
|
|
188
|
+
this.#resizeObs?.observe(child);
|
|
185
189
|
}
|
|
186
190
|
|
|
187
191
|
// Update port indicator positions
|
|
@@ -78,13 +78,14 @@ export class UIPage extends UIElement {
|
|
|
78
78
|
header.insertAdjacentElement('beforebegin', this.#sentinel);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
// gh#285 — SSR DOM shims (linkedom) have no IntersectionObserver global.
|
|
82
|
+
if (!this.#observer && typeof IntersectionObserver !== 'undefined') {
|
|
82
83
|
this.#observer = new IntersectionObserver((entries) => {
|
|
83
84
|
this.toggleAttribute('data-header-stuck', !entries[0].isIntersecting);
|
|
84
85
|
}, { threshold: 0 });
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
this.#observer
|
|
88
|
+
this.#observer?.observe(this.#sentinel);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
#teardownSticky() {
|
|
@@ -381,7 +381,8 @@ export class UISelect extends UIFormElement {
|
|
|
381
381
|
// options are already parsed/set, and skip unless there's a real
|
|
382
382
|
// <option>/<optgroup> to parse — so the component's own listbox/empty-state
|
|
383
383
|
// renders (and #parseOptions's own option removals) don't re-enter or loop.
|
|
384
|
-
|
|
384
|
+
// gh#285 — SSR DOM shims (linkedom) have no MutationObserver global.
|
|
385
|
+
if (!this.#optionObserver && typeof MutationObserver !== 'undefined') {
|
|
385
386
|
this.#optionObserver = new MutationObserver(() => {
|
|
386
387
|
if (this.#options.length) return;
|
|
387
388
|
const kids = this.#logicalOptionChildren();
|
|
@@ -394,7 +395,7 @@ export class UISelect extends UIFormElement {
|
|
|
394
395
|
}
|
|
395
396
|
});
|
|
396
397
|
}
|
|
397
|
-
this.#optionObserver
|
|
398
|
+
this.#optionObserver?.observe(this, { childList: true, subtree: true });
|
|
398
399
|
}
|
|
399
400
|
|
|
400
401
|
render() {
|
|
@@ -208,31 +208,34 @@ export class UISwatch extends UIElement {
|
|
|
208
208
|
// the canonical post-tile region. MutationObserver closes this gap for
|
|
209
209
|
// every arrival path: single-template ordering skew, separate render
|
|
210
210
|
// cycle, or imperative host.appendChild().
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
n.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
211
|
+
// gh#285 — SSR DOM shims (linkedom) have no MutationObserver global.
|
|
212
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
213
|
+
this.#chromeObserver = new MutationObserver((mutations) => {
|
|
214
|
+
let hasChrome = false;
|
|
215
|
+
for (const m of mutations) {
|
|
216
|
+
for (const n of m.addedNodes) {
|
|
217
|
+
if (n.nodeType !== 1) continue;
|
|
218
|
+
// Direct chrome child OR a display:contents wrapper-span carrying chrome.
|
|
219
|
+
if (n.getAttribute?.('slot') === 'chrome') { hasChrome = true; break; }
|
|
220
|
+
if (
|
|
221
|
+
n.tagName === 'SPAN' &&
|
|
222
|
+
n.style?.display === 'contents' &&
|
|
223
|
+
n.querySelector?.('[slot="chrome"]')
|
|
224
|
+
) { hasChrome = true; break; }
|
|
225
|
+
}
|
|
226
|
+
if (hasChrome) break;
|
|
223
227
|
}
|
|
224
|
-
if (hasChrome)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
this.#chromeObserver.
|
|
234
|
-
}
|
|
235
|
-
this.#chromeObserver.observe(this, { childList: true });
|
|
228
|
+
if (!hasChrome) return;
|
|
229
|
+
// Re-entrancy guard: #absorbChromeSlot() calls insertBefore() which itself
|
|
230
|
+
// generates childList mutations on the host. Without this guard, every
|
|
231
|
+
// absorb pass would re-trigger the observer indefinitely. takeRecords()
|
|
232
|
+
// drains the queue without dispatch so the next observation cycle starts
|
|
233
|
+
// clean.
|
|
234
|
+
this.#absorbChromeSlot();
|
|
235
|
+
this.#chromeObserver.takeRecords();
|
|
236
|
+
});
|
|
237
|
+
this.#chromeObserver.observe(this, { childList: true });
|
|
238
|
+
}
|
|
236
239
|
}
|
|
237
240
|
|
|
238
241
|
render() {
|
|
@@ -436,7 +436,10 @@ export class UISwiper extends UIElement {
|
|
|
436
436
|
if (dots.children.length !== desired) {
|
|
437
437
|
while (dots.firstChild) dots.removeChild(dots.firstChild);
|
|
438
438
|
for (let i = 0; i < desired; i++) {
|
|
439
|
-
|
|
439
|
+
// button-ui, not a native <button> (gh issue 276 wave 4) — it
|
|
440
|
+
// respects the consumer-set role, so the tab semantics survive.
|
|
441
|
+
const dot = document.createElement('button-ui');
|
|
442
|
+
dot.setAttribute('variant', 'ghost');
|
|
440
443
|
dot.setAttribute('role', 'tab');
|
|
441
444
|
dot.setAttribute('aria-label', `Page ${i + 1}`);
|
|
442
445
|
dot.addEventListener('click', () => this.goTo(this.#pageToSlideIndex(i)));
|
|
@@ -191,7 +191,16 @@
|
|
|
191
191
|
`--swiper-dot-size` for idle dots and `--swiper-dot-size-active`
|
|
192
192
|
for the active one (one space token larger). Padding is computed
|
|
193
193
|
from the size so the dot stays centered in the hitbox. */
|
|
194
|
-
:scope [data-swiper-dots] > button {
|
|
194
|
+
:scope [data-swiper-dots] > button-ui {
|
|
195
|
+
/* Stamped <button-ui variant="ghost" role="tab"> (gh issue 276 wave 4).
|
|
196
|
+
The dot look is bespoke (content-box clip keeps a 1rem hitbox around
|
|
197
|
+
a --swiper-dot-size circle) — these host declarations out-specify
|
|
198
|
+
button-ui's token-driven chrome; the ghost variant keeps its own
|
|
199
|
+
hover fill out of the way. */
|
|
200
|
+
/* button-ui floors min-width/height at --button-height — point it at
|
|
201
|
+
the VISIBLE circle so the padding (not the min-floor) builds the
|
|
202
|
+
1rem hitbox, exactly like the native dot did. */
|
|
203
|
+
--button-height: var(--swiper-dot-size);
|
|
195
204
|
width: var(--swiper-dot-size);
|
|
196
205
|
height: var(--swiper-dot-size);
|
|
197
206
|
border-radius: var(--swiper-dot-radius);
|
|
@@ -208,18 +217,18 @@
|
|
|
208
217
|
`background-clip: content-box` from the base rule isn't reset to
|
|
209
218
|
its initial `border-box` value — the colored area would otherwise
|
|
210
219
|
fill the full 1rem hitbox and turn into a huge circle. */
|
|
211
|
-
:scope [data-swiper-dots] > button:hover {
|
|
220
|
+
:scope [data-swiper-dots] > button-ui:hover {
|
|
212
221
|
background-color: var(--swiper-dot-bg-active);
|
|
213
222
|
}
|
|
214
223
|
|
|
215
224
|
/* Focus ring is rendered INSIDE the padding area (inset shadow), so
|
|
216
225
|
the dot's visual footprint stays the size of every other dot. */
|
|
217
|
-
:scope [data-swiper-dots] > button:focus-visible {
|
|
226
|
+
:scope [data-swiper-dots] > button-ui:focus-visible {
|
|
218
227
|
outline: none;
|
|
219
228
|
box-shadow: inset 0 0 0 var(--a-focus-width) var(--a-focus-color);
|
|
220
229
|
}
|
|
221
230
|
|
|
222
|
-
:scope [data-swiper-dots] > button[aria-current="true"] {
|
|
231
|
+
:scope [data-swiper-dots] > button-ui[aria-current="true"] {
|
|
223
232
|
background-color: var(--swiper-dot-bg-active);
|
|
224
233
|
width: var(--swiper-dot-size-active);
|
|
225
234
|
height: var(--swiper-dot-size-active);
|
|
@@ -114,12 +114,17 @@ export class UIToolbar extends UIElement {
|
|
|
114
114
|
connected() {
|
|
115
115
|
this.setAttribute('role', 'toolbar');
|
|
116
116
|
this.#ensureSpillover();
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
// gh#285 — SSR DOM shims (linkedom) have neither observer global.
|
|
118
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
119
|
+
this.#ro = new ResizeObserver(() => this.#queueReflow());
|
|
120
|
+
this.#ro.observe(this);
|
|
121
|
+
}
|
|
122
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
123
|
+
this.#mo = new MutationObserver(() => {
|
|
124
|
+
if (this.#measuring) return;
|
|
125
|
+
this.#queueReflow();
|
|
126
|
+
});
|
|
127
|
+
}
|
|
123
128
|
this.#moObserve();
|
|
124
129
|
this.#queueReflow();
|
|
125
130
|
}
|
|
@@ -324,8 +324,11 @@ export class UITreeItem extends UIElement {
|
|
|
324
324
|
// role=button child of the group/tree (axe `aria-required-children`). Adopt
|
|
325
325
|
// what's present now, then observe for the late arrivals.
|
|
326
326
|
this.#adoptSlotted();
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
// gh#285 — SSR DOM shims (linkedom) have no MutationObserver global.
|
|
328
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
329
|
+
this.#slotObserver = new MutationObserver(() => this.#adoptSlotted());
|
|
330
|
+
this.#slotObserver.observe(this, { childList: true, subtree: true });
|
|
331
|
+
}
|
|
329
332
|
}
|
|
330
333
|
|
|
331
334
|
#stamp() {
|
package/core/data-stream.js
CHANGED
|
@@ -453,24 +453,30 @@ function visitSubtree(root, fn) {
|
|
|
453
453
|
}
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
456
|
+
// gh#285 — SSR DOM shims (linkedom) have no MutationObserver global.
|
|
457
|
+
// Constructed lazily so import-time evaluation never throws; bootstrap()
|
|
458
|
+
// no-ops the document-level watch when the global is missing (existing
|
|
459
|
+
// stream elements found via querySelectorAll still get started once).
|
|
460
|
+
const observer = typeof MutationObserver !== 'undefined'
|
|
461
|
+
? new MutationObserver((mutations) => {
|
|
462
|
+
for (const m of mutations) {
|
|
463
|
+
if (m.type === 'attributes' && ATTR_FILTER.includes(m.attributeName)) {
|
|
464
|
+
const el = m.target;
|
|
465
|
+
if (isStreamingEl(el)) start(el);
|
|
466
|
+
else stop(el);
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (m.type === 'childList') {
|
|
470
|
+
m.addedNodes.forEach(n => visitSubtree(n, start));
|
|
471
|
+
m.removedNodes.forEach(n => visitSubtree(n, stop));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
})
|
|
475
|
+
: null;
|
|
470
476
|
|
|
471
477
|
function bootstrap() {
|
|
472
478
|
if (typeof document === 'undefined') return;
|
|
473
|
-
observer
|
|
479
|
+
observer?.observe(document.documentElement, {
|
|
474
480
|
childList: true, subtree: true,
|
|
475
481
|
attributes: true, attributeFilter: ATTR_FILTER,
|
|
476
482
|
});
|