@agent-ui-kit/components 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base-styles.css +8 -0
- package/dist/component-styles.css +147 -0
- package/dist/controls/_base/index.d.ts +3 -0
- package/dist/controls/_base/index.js +9 -0
- package/dist/controls/_base/indicator-element.d.ts +132 -0
- package/dist/controls/_base/indicator-element.js +158 -0
- package/dist/controls/_base/listbox-element.d.ts +70 -0
- package/dist/controls/_base/listbox-element.js +74 -0
- package/dist/controls/_base/range-element.d.ts +120 -0
- package/dist/controls/_base/range-element.js +158 -0
- package/dist/controls/_surface/container-box.css +301 -0
- package/dist/controls/_surface/container.css +156 -0
- package/dist/controls/_token-surface/token-surface.d.ts +35 -0
- package/dist/controls/_token-surface/token-surface.js +97 -0
- package/dist/controls/attachment/attachment-meta.d.ts +8 -0
- package/dist/controls/attachment/attachment-meta.js +84 -0
- package/dist/controls/attachment/attachment.css +91 -0
- package/dist/controls/attachment/attachment.d.ts +40 -0
- package/dist/controls/attachment/attachment.js +70 -0
- package/dist/controls/avatar/avatar-initials.d.ts +4 -0
- package/dist/controls/avatar/avatar-initials.js +30 -0
- package/dist/controls/avatar/avatar.css +87 -0
- package/dist/controls/avatar/avatar.d.ts +40 -0
- package/dist/controls/avatar/avatar.js +77 -0
- package/dist/controls/badge/badge.css +175 -0
- package/dist/controls/badge/badge.d.ts +36 -0
- package/dist/controls/badge/badge.js +71 -0
- package/dist/controls/bar-chart/bar-chart.css +110 -0
- package/dist/controls/bar-chart/bar-chart.d.ts +26 -0
- package/dist/controls/bar-chart/bar-chart.js +80 -0
- package/dist/controls/bar-chart/bar-math.d.ts +31 -0
- package/dist/controls/bar-chart/bar-math.js +106 -0
- package/dist/controls/button/button.css +263 -0
- package/dist/controls/button/button.d.ts +61 -0
- package/dist/controls/button/button.js +115 -0
- package/dist/controls/calendar/calendar.css +495 -0
- package/dist/controls/calendar/calendar.d.ts +161 -0
- package/dist/controls/calendar/calendar.js +1004 -0
- package/dist/controls/card/card-content.d.ts +24 -0
- package/dist/controls/card/card-content.js +133 -0
- package/dist/controls/card/card-footer.d.ts +3 -0
- package/dist/controls/card/card-footer.js +16 -0
- package/dist/controls/card/card-header.d.ts +3 -0
- package/dist/controls/card/card-header.js +18 -0
- package/dist/controls/card/card.css +445 -0
- package/dist/controls/card/card.d.ts +51 -0
- package/dist/controls/card/card.js +61 -0
- package/dist/controls/checkbox/checkbox.css +204 -0
- package/dist/controls/checkbox/checkbox.d.ts +114 -0
- package/dist/controls/checkbox/checkbox.js +130 -0
- package/dist/controls/code/code.css +72 -0
- package/dist/controls/code/code.d.ts +23 -0
- package/dist/controls/code/code.js +40 -0
- package/dist/controls/color-picker/color-picker.css +209 -0
- package/dist/controls/color-picker/color-picker.d.ts +84 -0
- package/dist/controls/color-picker/color-picker.js +545 -0
- package/dist/controls/color-picker/color.d.ts +40 -0
- package/dist/controls/color-picker/color.js +159 -0
- package/dist/controls/column/column.css +151 -0
- package/dist/controls/column/column.d.ts +107 -0
- package/dist/controls/column/column.js +57 -0
- package/dist/controls/combo-box/combo-box.css +397 -0
- package/dist/controls/combo-box/combo-box.d.ts +133 -0
- package/dist/controls/combo-box/combo-box.js +680 -0
- package/dist/controls/command-modal/command-modal.css +226 -0
- package/dist/controls/command-modal/command-modal.d.ts +42 -0
- package/dist/controls/command-modal/command-modal.js +315 -0
- package/dist/controls/disclosure/disclosure.css +162 -0
- package/dist/controls/disclosure/disclosure.d.ts +37 -0
- package/dist/controls/disclosure/disclosure.js +207 -0
- package/dist/controls/field/field.css +140 -0
- package/dist/controls/field/field.d.ts +27 -0
- package/dist/controls/field/field.js +228 -0
- package/dist/controls/form-provider/form-provider.css +40 -0
- package/dist/controls/form-provider/form-provider.d.ts +42 -0
- package/dist/controls/form-provider/form-provider.js +105 -0
- package/dist/controls/grid/grid.css +72 -0
- package/dist/controls/grid/grid.d.ts +60 -0
- package/dist/controls/grid/grid.js +56 -0
- package/dist/controls/icon/icon.css +37 -0
- package/dist/controls/icon/icon.d.ts +25 -0
- package/dist/controls/icon/icon.js +55 -0
- package/dist/controls/index.d.ts +57 -0
- package/dist/controls/index.js +136 -0
- package/dist/controls/ladder/ladder.css +89 -0
- package/dist/controls/ladder/ladder.d.ts +27 -0
- package/dist/controls/ladder/ladder.js +78 -0
- package/dist/controls/list/list.css +118 -0
- package/dist/controls/list/list.d.ts +84 -0
- package/dist/controls/list/list.js +48 -0
- package/dist/controls/menu/menu.css +195 -0
- package/dist/controls/menu/menu.d.ts +44 -0
- package/dist/controls/menu/menu.js +248 -0
- package/dist/controls/modal/modal.css +99 -0
- package/dist/controls/modal/modal.d.ts +61 -0
- package/dist/controls/modal/modal.js +155 -0
- package/dist/controls/popover/popover.css +83 -0
- package/dist/controls/popover/popover.d.ts +44 -0
- package/dist/controls/popover/popover.js +164 -0
- package/dist/controls/progress/progress.css +106 -0
- package/dist/controls/progress/progress.d.ts +28 -0
- package/dist/controls/progress/progress.js +80 -0
- package/dist/controls/radio/radio-group.css +84 -0
- package/dist/controls/radio/radio-group.d.ts +152 -0
- package/dist/controls/radio/radio-group.js +350 -0
- package/dist/controls/radio/radio.css +219 -0
- package/dist/controls/radio/radio.d.ts +19 -0
- package/dist/controls/radio/radio.js +75 -0
- package/dist/controls/ramp/ramp.css +75 -0
- package/dist/controls/ramp/ramp.d.ts +29 -0
- package/dist/controls/ramp/ramp.js +72 -0
- package/dist/controls/row/row.css +152 -0
- package/dist/controls/row/row.d.ts +95 -0
- package/dist/controls/row/row.js +45 -0
- package/dist/controls/segment/segment.css +66 -0
- package/dist/controls/segment/segment.d.ts +3 -0
- package/dist/controls/segment/segment.js +26 -0
- package/dist/controls/segmented-control/segmented-control.css +189 -0
- package/dist/controls/segmented-control/segmented-control.d.ts +20 -0
- package/dist/controls/segmented-control/segmented-control.js +64 -0
- package/dist/controls/select/select.css +467 -0
- package/dist/controls/select/select.d.ts +147 -0
- package/dist/controls/select/select.js +538 -0
- package/dist/controls/slider/slider.css +231 -0
- package/dist/controls/slider/slider.d.ts +111 -0
- package/dist/controls/slider/slider.js +60 -0
- package/dist/controls/slider-multi/slider-multi.css +197 -0
- package/dist/controls/slider-multi/slider-multi.d.ts +149 -0
- package/dist/controls/slider-multi/slider-multi.js +380 -0
- package/dist/controls/sparkline/sparkline-math.d.ts +23 -0
- package/dist/controls/sparkline/sparkline-math.js +79 -0
- package/dist/controls/sparkline/sparkline.css +61 -0
- package/dist/controls/sparkline/sparkline.d.ts +27 -0
- package/dist/controls/sparkline/sparkline.js +74 -0
- package/dist/controls/split/constrain.d.ts +44 -0
- package/dist/controls/split/constrain.js +126 -0
- package/dist/controls/split/split-pane.css +63 -0
- package/dist/controls/split/split-pane.d.ts +49 -0
- package/dist/controls/split/split-pane.js +42 -0
- package/dist/controls/split/split.css +127 -0
- package/dist/controls/split/split.d.ts +42 -0
- package/dist/controls/split/split.js +364 -0
- package/dist/controls/stat/stat-model.d.ts +22 -0
- package/dist/controls/stat/stat-model.js +69 -0
- package/dist/controls/stat/stat.css +101 -0
- package/dist/controls/stat/stat.d.ts +30 -0
- package/dist/controls/stat/stat.js +73 -0
- package/dist/controls/status-stream/status-stream.css +151 -0
- package/dist/controls/status-stream/status-stream.d.ts +108 -0
- package/dist/controls/status-stream/status-stream.js +417 -0
- package/dist/controls/swatch/swatch.css +66 -0
- package/dist/controls/swatch/swatch.d.ts +28 -0
- package/dist/controls/swatch/swatch.js +74 -0
- package/dist/controls/swiper/swiper-item.css +17 -0
- package/dist/controls/swiper/swiper-item.d.ts +31 -0
- package/dist/controls/swiper/swiper-item.js +47 -0
- package/dist/controls/swiper/swiper-label.css +10 -0
- package/dist/controls/swiper/swiper-label.d.ts +4 -0
- package/dist/controls/swiper/swiper-label.js +20 -0
- package/dist/controls/swiper/swiper-paddles.css +26 -0
- package/dist/controls/swiper/swiper-paddles.d.ts +10 -0
- package/dist/controls/swiper/swiper-paddles.js +62 -0
- package/dist/controls/swiper/swiper-pagination.css +60 -0
- package/dist/controls/swiper/swiper-pagination.d.ts +31 -0
- package/dist/controls/swiper/swiper-pagination.js +78 -0
- package/dist/controls/swiper/swiper.css +153 -0
- package/dist/controls/swiper/swiper.d.ts +158 -0
- package/dist/controls/swiper/swiper.js +659 -0
- package/dist/controls/switch/switch.css +194 -0
- package/dist/controls/switch/switch.d.ts +5 -0
- package/dist/controls/switch/switch.js +18 -0
- package/dist/controls/table/table-model.d.ts +43 -0
- package/dist/controls/table/table-model.js +124 -0
- package/dist/controls/table/table.css +90 -0
- package/dist/controls/table/table.d.ts +29 -0
- package/dist/controls/table/table.js +178 -0
- package/dist/controls/tabs/tab-panel.d.ts +10 -0
- package/dist/controls/tabs/tab-panel.js +89 -0
- package/dist/controls/tabs/tab.d.ts +26 -0
- package/dist/controls/tabs/tab.js +67 -0
- package/dist/controls/tabs/tabs.css +181 -0
- package/dist/controls/tabs/tabs.d.ts +61 -0
- package/dist/controls/tabs/tabs.js +176 -0
- package/dist/controls/text/href.d.ts +26 -0
- package/dist/controls/text/href.js +48 -0
- package/dist/controls/text/text.css +328 -0
- package/dist/controls/text/text.d.ts +85 -0
- package/dist/controls/text/text.js +273 -0
- package/dist/controls/text-field/text-field.css +498 -0
- package/dist/controls/text-field/text-field.d.ts +226 -0
- package/dist/controls/text-field/text-field.js +905 -0
- package/dist/controls/textarea/textarea.css +251 -0
- package/dist/controls/textarea/textarea.d.ts +136 -0
- package/dist/controls/textarea/textarea.js +312 -0
- package/dist/controls/theme-provider/theme-provider.css +48 -0
- package/dist/controls/theme-provider/theme-provider.d.ts +59 -0
- package/dist/controls/theme-provider/theme-provider.js +40 -0
- package/dist/controls/timeline/timeline.css +33 -0
- package/dist/controls/timeline/timeline.d.ts +39 -0
- package/dist/controls/timeline/timeline.js +52 -0
- package/dist/controls/timeline-item/timeline-item.css +495 -0
- package/dist/controls/timeline-item/timeline-item.d.ts +79 -0
- package/dist/controls/timeline-item/timeline-item.js +360 -0
- package/dist/controls/toast/toast-region.css +56 -0
- package/dist/controls/toast/toast-region.d.ts +20 -0
- package/dist/controls/toast/toast-region.js +113 -0
- package/dist/controls/toast/toast.css +96 -0
- package/dist/controls/toast/toast.d.ts +28 -0
- package/dist/controls/toast/toast.js +191 -0
- package/dist/controls/toolbar/toolbar.css +125 -0
- package/dist/controls/toolbar/toolbar.d.ts +108 -0
- package/dist/controls/toolbar/toolbar.js +68 -0
- package/dist/controls/tooltip/tooltip.css +80 -0
- package/dist/controls/tooltip/tooltip.d.ts +57 -0
- package/dist/controls/tooltip/tooltip.js +205 -0
- package/dist/descriptor/component-descriptor.d.ts +112 -0
- package/dist/descriptor/component-descriptor.js +492 -0
- package/dist/descriptor/index.d.ts +1 -0
- package/dist/descriptor/index.js +5 -0
- package/dist/dom/container.d.ts +45 -0
- package/dist/dom/container.js +60 -0
- package/dist/dom/element.d.ts +98 -0
- package/dist/dom/element.js +221 -0
- package/dist/dom/form.d.ts +240 -0
- package/dist/dom/form.js +399 -0
- package/dist/dom/index.d.ts +10 -0
- package/dist/dom/index.js +31 -0
- package/dist/dom/props.d.ts +56 -0
- package/dist/dom/props.js +231 -0
- package/dist/dom/repeat.d.ts +10 -0
- package/dist/dom/repeat.js +202 -0
- package/dist/dom/template.d.ts +161 -0
- package/dist/dom/template.js +634 -0
- package/dist/dom/watch.d.ts +11 -0
- package/dist/dom/watch.js +82 -0
- package/dist/foundation-styles.css +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/reactive/graph.d.ts +65 -0
- package/dist/reactive/graph.js +310 -0
- package/dist/reactive/index.d.ts +3 -0
- package/dist/reactive/index.js +4 -0
- package/dist/reactive/scheduler.d.ts +13 -0
- package/dist/reactive/scheduler.js +49 -0
- package/dist/traits/area-drag.d.ts +12 -0
- package/dist/traits/area-drag.js +70 -0
- package/dist/traits/form-registry.d.ts +36 -0
- package/dist/traits/form-registry.js +108 -0
- package/dist/traits/index.d.ts +24 -0
- package/dist/traits/index.js +19 -0
- package/dist/traits/overlay.d.ts +50 -0
- package/dist/traits/overlay.js +277 -0
- package/dist/traits/pane-resize.d.ts +26 -0
- package/dist/traits/pane-resize.js +103 -0
- package/dist/traits/press-activation.d.ts +11 -0
- package/dist/traits/press-activation.js +55 -0
- package/dist/traits/roving-focus.d.ts +52 -0
- package/dist/traits/roving-focus.js +214 -0
- package/dist/traits/scroll-fade.d.ts +28 -0
- package/dist/traits/scroll-fade.js +120 -0
- package/dist/traits/selection-commit.d.ts +30 -0
- package/dist/traits/selection-commit.js +146 -0
- package/dist/traits/tabbable.d.ts +12 -0
- package/dist/traits/tabbable.js +59 -0
- package/dist/traits/track-user-invalid.d.ts +36 -0
- package/dist/traits/track-user-invalid.js +52 -0
- package/dist/traits/value-codec.d.ts +120 -0
- package/dist/traits/value-codec.js +497 -0
- package/dist/traits/value-drag.d.ts +18 -0
- package/dist/traits/value-drag.js +84 -0
- package/package.json +263 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* avatar.css — ui-avatar, the Indicator-class compact identity mark (LLD-C3, feed-family.lld.md §3;
|
|
2
|
+
* SPEC-R4/R5/R6/R7/R19/R20; ADR-0112 cl.3, fork F3). Two sectioned blocks:
|
|
3
|
+
*
|
|
4
|
+
* [1] TOKEN BLOCK — `:where(ui-avatar)` declares `--ui-avatar-*`: the widget box off the widget-box
|
|
5
|
+
* ramp `--md-sys-compact-{sm,md,lg}` (ADR-0041 — fork F3, no new register minted; `[size]` repoints the
|
|
6
|
+
* tier, `[scale]` on an ancestor re-tables the ramp for free, no avatar-local [scale] rule needed)
|
|
7
|
+
* and ONE neutral plane + on-surface ink pair (SPEC-R7 — no per-identity hue).
|
|
8
|
+
* [2] STYLES BLOCK — `@scope (ui-avatar)` consumes ONLY `--ui-avatar-*`. The host is a circle-masked
|
|
9
|
+
* widget box (`inline-grid`, `place-items: center`, `border-radius: 50%`); exactly one child paints
|
|
10
|
+
* at a time (avatar.ts) — an `<img>` (object-fit: cover), the `[data-part=initials]` text, or a
|
|
11
|
+
* `<ui-icon>` glyph, each sized off the box.
|
|
12
|
+
*
|
|
13
|
+
* No hue anywhere (SPEC-R7 AC1): the fallback plane/ink pair is identical for every `name` — a
|
|
14
|
+
* computed-style equality probe, not a visual comparison. Identity is carried by the initials/name text,
|
|
15
|
+
* never by color (ADR-0057).
|
|
16
|
+
*
|
|
17
|
+
* Forced colors (SPEC-R19): a background-drawn circle plane can visually merge with the page under WHCM
|
|
18
|
+
* — an explicit `forced-colors` block adds a system-ink border so the circle boundary stays visible (the
|
|
19
|
+
* badge/checkbox box-boundary precedent); initials are real text and survive untouched.
|
|
20
|
+
*
|
|
21
|
+
* No interaction states (SPEC-R4 — non-interactive, no focus/hover/active, no post-connect motion gate:
|
|
22
|
+
* there is nothing to transition). Standalone stylesheet — linked via the host page / component-styles
|
|
23
|
+
* barrel; NEVER injected from avatar.ts (behaviour-only `.ts`, no runtime style injection — plan §2).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-avatar) declares --ui-avatar-* ════════════════ */
|
|
27
|
+
|
|
28
|
+
:where(ui-avatar) {
|
|
29
|
+
/* Widget-box size (ADR-0041 fork F3) — defaults to the compact-realm `md` tier; a page override for
|
|
30
|
+
larger chrome (a profile header) is the token's job, not a new fleet register (F3's named trigger). */
|
|
31
|
+
--ui-avatar-size: var(--md-sys-compact-md);
|
|
32
|
+
|
|
33
|
+
/* ONE neutral pair (SPEC-R7) — AA-verifiable once; every `name` computes the identical plane/ink. */
|
|
34
|
+
--ui-avatar-plane: var(--md-sys-color-neutral-surface-high);
|
|
35
|
+
--ui-avatar-ink: var(--md-sys-color-neutral-on-surface);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* [size] → widget-box tier (the checkbox/badge precedent: sm/lg repoint, md is the base default above). */
|
|
39
|
+
:where(ui-avatar[size='sm']) {
|
|
40
|
+
--ui-avatar-size: var(--md-sys-compact-sm);
|
|
41
|
+
}
|
|
42
|
+
:where(ui-avatar[size='lg']) {
|
|
43
|
+
--ui-avatar-size: var(--md-sys-compact-lg);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-avatar) consumes ONLY --ui-avatar-* ════════════════ */
|
|
47
|
+
|
|
48
|
+
@scope (ui-avatar) {
|
|
49
|
+
:scope {
|
|
50
|
+
display: inline-grid;
|
|
51
|
+
place-items: center;
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
border-radius: 50%; /* the circle mask (SPEC-R7) */
|
|
54
|
+
inline-size: var(--ui-avatar-size);
|
|
55
|
+
block-size: var(--ui-avatar-size);
|
|
56
|
+
background: var(--ui-avatar-plane);
|
|
57
|
+
color: var(--ui-avatar-ink);
|
|
58
|
+
vertical-align: middle; /* an inline-grid box sitting beside text (SPEC-R6's "beside a name" case) */
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
:scope img {
|
|
62
|
+
inline-size: 100%;
|
|
63
|
+
block-size: 100%;
|
|
64
|
+
object-fit: cover;
|
|
65
|
+
display: block;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
:scope [data-part='initials'] {
|
|
69
|
+
font-size: calc(var(--ui-avatar-size) * 0.42);
|
|
70
|
+
font-weight: var(--md-sys-typescale-label-medium-weight);
|
|
71
|
+
line-height: 1; /* single-line, centers like a glyph (geometry.md's control line-height law) */
|
|
72
|
+
user-select: none;
|
|
73
|
+
text-transform: uppercase;
|
|
74
|
+
letter-spacing: 0.02em;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
:scope ui-icon {
|
|
78
|
+
/* the content-icon register (geometry.md taxonomy) — the glyph derives from the box, not the font */
|
|
79
|
+
font-size: calc(var(--ui-avatar-size) * 0.6);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@media (forced-colors: active) {
|
|
83
|
+
:scope {
|
|
84
|
+
border: 1px solid CanvasText; /* the circle boundary stays visible under WHCM (SPEC-R19) */
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { UIElement, type ReactiveProps } from '../../dom/index.js';
|
|
2
|
+
declare const props: {
|
|
3
|
+
src: import("../../index.js").PropConfig<string>;
|
|
4
|
+
identity: import("../../index.js").PropConfig<string>;
|
|
5
|
+
label: {
|
|
6
|
+
reflect: true;
|
|
7
|
+
type: import("../../index.js").PropType<string>;
|
|
8
|
+
default: string;
|
|
9
|
+
attribute?: string | false;
|
|
10
|
+
};
|
|
11
|
+
size: {
|
|
12
|
+
reflect: true;
|
|
13
|
+
type: import("../../index.js").PropType<"sm" | "md" | "lg">;
|
|
14
|
+
default: "sm" | "md" | "lg";
|
|
15
|
+
attribute?: string | false;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export interface UIAvatarElement extends ReactiveProps<typeof props> {
|
|
19
|
+
}
|
|
20
|
+
export declare class UIAvatarElement extends UIElement {
|
|
21
|
+
#private;
|
|
22
|
+
static props: {
|
|
23
|
+
src: import("../../index.js").PropConfig<string>;
|
|
24
|
+
identity: import("../../index.js").PropConfig<string>;
|
|
25
|
+
label: {
|
|
26
|
+
reflect: true;
|
|
27
|
+
type: import("../../index.js").PropType<string>;
|
|
28
|
+
default: string;
|
|
29
|
+
attribute?: string | false;
|
|
30
|
+
};
|
|
31
|
+
size: {
|
|
32
|
+
reflect: true;
|
|
33
|
+
type: import("../../index.js").PropType<"sm" | "md" | "lg">;
|
|
34
|
+
default: "sm" | "md" | "lg";
|
|
35
|
+
attribute?: string | false;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
protected connected(): void;
|
|
39
|
+
}
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// avatar.ts — UIAvatarElement, the Indicator-class compact identity mark (LLD-C3, feed-family.lld.md §3;
|
|
2
|
+
// SPEC-R4…R7; ADR-0112 cl.3). Non-interactive, non-form-associated leaf: no events, no keyboard contract,
|
|
3
|
+
// no focus. The fallback chain (SPEC-R5) never renders a broken-image box and never renders empty —
|
|
4
|
+
// exactly one of `src` image / initials / person glyph paints at a time.
|
|
5
|
+
//
|
|
6
|
+
// ONE render effect (reads src/identity/#failedSrc) walks the chain in order:
|
|
7
|
+
// 1. `src` non-empty AND not the failed src ⇒ an <img alt=""> (empty-alt — the Option/MenuItem sanction;
|
|
8
|
+
// host ARIA stays on internals) with an `error` listener that records the src into `#failedSrc`. The
|
|
9
|
+
// effect re-runs on that write and falls through to initials/glyph — no broken-image final state. A
|
|
10
|
+
// NEW `src` no longer equals `#failedSrc`, so it re-attempts (SPEC-R5's re-attempt transition falls
|
|
11
|
+
// out of the equality check, no extra state machine); clearing `src` falls back immediately.
|
|
12
|
+
// 2. else `initialsFrom(identity)` non-empty ⇒ a `<span data-part="initials">`.
|
|
13
|
+
// 3. else ⇒ `<ui-icon glyph="user">` — decorative by its own default; the icon control module is
|
|
14
|
+
// statically imported so the tag is defined before use (the sanctioned sibling-control import).
|
|
15
|
+
//
|
|
16
|
+
// A second effect owns ARIA (SPEC-R6) — the `ui-icon` contract shape verbatim: decorative by default
|
|
17
|
+
// (`internals.ariaHidden = 'true'`, no role — a feed avatar beside a visible name would otherwise
|
|
18
|
+
// duplicate it); non-empty `label` opts into `role=img` + `ariaLabel` + clears `ariaHidden`. A label-less
|
|
19
|
+
// avatar beside no name announces nothing — that is a contract-level author error, not a defect here
|
|
20
|
+
// (the descriptor states it explicitly).
|
|
21
|
+
import { signal } from "../../reactive/index.js";
|
|
22
|
+
import { UIElement, prop } from "../../dom/index.js";
|
|
23
|
+
import { initialsFrom } from "./avatar-initials.js";
|
|
24
|
+
import { UIIconElement } from "../icon/icon.js"; // sanctioned sibling-control import — self-defines ui-icon
|
|
25
|
+
const props = {
|
|
26
|
+
src: prop.string(''), // image URL; a load error falls back without ever painting a broken-image box
|
|
27
|
+
identity: prop.string(''), // the identity the initials derive from; NOT announced by default (SPEC-R6). Renamed from `name` (TKT-0069 item 1 ruling: `name` = the FORM name, reserved; the A2UI catalog keeps wire `name`, mapped in its bespoke factory)
|
|
28
|
+
label: { ...prop.string(''), reflect: true }, // the a11y escape hatch — non-empty makes the avatar itself the accessible name
|
|
29
|
+
size: { ...prop.enum(['sm', 'md', 'lg'], 'md'), reflect: true }, // reflected — the CSS [size] hook
|
|
30
|
+
};
|
|
31
|
+
export class UIAvatarElement extends UIElement {
|
|
32
|
+
static props = props;
|
|
33
|
+
// The most recent `src` that failed to load — an equality check, not a boolean flag, so a NEW src
|
|
34
|
+
// (which no longer equals this value) re-attempts for free (SPEC-R5's re-attempt transition).
|
|
35
|
+
#failedSrc = signal('');
|
|
36
|
+
connected() {
|
|
37
|
+
this.effect(() => {
|
|
38
|
+
const src = this.src;
|
|
39
|
+
const name = this.identity;
|
|
40
|
+
const failedSrc = this.#failedSrc.value;
|
|
41
|
+
if (src !== '' && src !== failedSrc) {
|
|
42
|
+
const img = document.createElement('img');
|
|
43
|
+
img.alt = ''; // empty-alt semantics on an interior node — host ARIA stays on internals
|
|
44
|
+
img.addEventListener('error', () => { this.#failedSrc.value = src; }, { once: true });
|
|
45
|
+
img.src = src;
|
|
46
|
+
this.replaceChildren(img);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const initials = initialsFrom(name);
|
|
50
|
+
if (initials !== '') {
|
|
51
|
+
const span = document.createElement('span');
|
|
52
|
+
span.dataset.part = 'initials';
|
|
53
|
+
span.textContent = initials;
|
|
54
|
+
this.replaceChildren(span);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const icon = document.createElement('ui-icon');
|
|
58
|
+
icon.glyph = 'user';
|
|
59
|
+
this.replaceChildren(icon);
|
|
60
|
+
});
|
|
61
|
+
// ARIA effect (SPEC-R6): decorative by default; `label` opts into role=img + accessible name.
|
|
62
|
+
this.effect(() => {
|
|
63
|
+
if (this.label) {
|
|
64
|
+
this.internals.role = 'img';
|
|
65
|
+
this.internals.ariaLabel = this.label;
|
|
66
|
+
this.internals.ariaHidden = null;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
this.internals.role = null;
|
|
70
|
+
this.internals.ariaLabel = null;
|
|
71
|
+
this.internals.ariaHidden = 'true';
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!customElements.get('ui-avatar'))
|
|
77
|
+
customElements.define('ui-avatar', UIAvatarElement); // idempotent self-define
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/* badge.css — ui-badge, the compact realm's first shipped non-interactive widget (report-family.lld.md
|
|
2
|
+
* LLD-C8 · SPEC-R12/R13/R14/R15/R16/R17 · ADR-0041 · ADR-0057 · ADR-0111 cl.5).
|
|
3
|
+
*
|
|
4
|
+
* [1] TOKEN BLOCK — `:where(ui-badge)` (specificity 0,0,0) declares the --ui-badge-* chain from the
|
|
5
|
+
* colour roles (--md-sys-color-{family}-{role}) and the compact-realm widget-box ramp
|
|
6
|
+
* (--md-sys-compact-lg, ADR-0041 — box = compact-realm law, NOT h/2; SPEC-R13). `[intent]` repoints the
|
|
7
|
+
* colour channel (fill/ink/outline) here — the SAME technique checkbox.css uses for `[size]`/
|
|
8
|
+
* `[disabled]`: every attribute-conditional token repoint stays inside `:where()`, never inside
|
|
9
|
+
* `@scope` (component-packaging.md/tokens.md — "@scope consumes ONLY --ui-{name}-*"). No `[size]`
|
|
10
|
+
* attribute in v1 (SPEC-R13 AC3) — the box is fixed to `--md-sys-compact-lg` regardless of content.
|
|
11
|
+
* [2] STYLES BLOCK — `@scope (ui-badge)` consumes ONLY --ui-badge-*. The host is an inline-flex pill:
|
|
12
|
+
* fixed square block-size (the compact-realm box), the compact pad law `2px + box·ratio·density`
|
|
13
|
+
* (never `h/2` — geometry.md's compact-realm exception), radius = box/2 (the realm's "count pill"
|
|
14
|
+
* case). The glyph is a real, constant `[data-part=glyph]` node; its SHAPE differs per `[intent]`
|
|
15
|
+
* via `clip-path` (the checkbox tick precedent, ADR-0057's non-color signifier — shape is the
|
|
16
|
+
* meaning channel, colour is the redundant one) — `neutral` shows no glyph at all (absence IS its
|
|
17
|
+
* signifier). Forced-colors maps the host to Canvas/CanvasText and the glyph to CanvasText so both
|
|
18
|
+
* survive WHCM (the bar-chart fill lesson: a background-drawn clip-path glyph is otherwise flattened
|
|
19
|
+
* to `Canvas` and vanishes).
|
|
20
|
+
*
|
|
21
|
+
* No interaction states at all (SPEC-R11 — non-interactive, no focus/hover/active, no post-connect motion
|
|
22
|
+
* gate: there is nothing to transition). Standalone stylesheet — linked via the host page / component-
|
|
23
|
+
* styles barrel; NEVER injected from badge.ts (behaviour-only `.ts`, no runtime style injection — plan §2).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-badge) declares --ui-badge-* ════════════════ */
|
|
27
|
+
|
|
28
|
+
:where(ui-badge) {
|
|
29
|
+
/* Compact-realm widget-box (ADR-0041; SPEC-R13) — fixed regardless of content/label length; [scale] on
|
|
30
|
+
an ancestor re-tables --md-sys-compact-lg for its subtree for free (no badge-local [scale] rule needed). */
|
|
31
|
+
--ui-badge-box: var(--md-sys-compact-lg); /* 18px @ default ui-md scale */
|
|
32
|
+
|
|
33
|
+
/* The compact pad law (dimensional-standard.md §2.4; geometry.md "the compact realm") — 2px + box ×
|
|
34
|
+
0.375 × density. Density rides the PAD only; the box above stays density-invariant (geometry.md). */
|
|
35
|
+
--ui-badge-pad: calc(2px + var(--ui-badge-box) * 0.375 * var(--md-sys-density, 1));
|
|
36
|
+
|
|
37
|
+
/* Text rides the fleet font ramp (NOT the compact box) — ADR-0111 cl.5's dual-listing resolution: box =
|
|
38
|
+
compact-realm law, text = --md-sys-font-*. line-height:1 in the styles block centers it like a glyph. */
|
|
39
|
+
--ui-badge-font: var(--md-sys-font-sm);
|
|
40
|
+
|
|
41
|
+
/* Rhythm — the glyph↔label gap (density-responsive for free, geometry.md "rhythm ∝ font"). */
|
|
42
|
+
--ui-badge-gap: var(--md-sys-gap-sm);
|
|
43
|
+
|
|
44
|
+
/* The glyph fills half the box — a content-mark, not an inline affordance (anatomy.md's frame family). */
|
|
45
|
+
--ui-badge-glyph: calc(var(--ui-badge-box) * 0.5);
|
|
46
|
+
|
|
47
|
+
/* Colour channel — neutral default (the "tag" reading of the one component, ADR-0111 fork F3). */
|
|
48
|
+
--ui-badge-fill: var(--md-sys-color-neutral-container);
|
|
49
|
+
--ui-badge-ink: var(--md-sys-color-neutral-on-surface);
|
|
50
|
+
--ui-badge-outline: var(--md-sys-color-neutral-outline-variant);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* [intent] → the colour channel repoint (SPEC-R14 AC3 defaults, build-time AA-probe-gated per intent ×
|
|
54
|
+
light/dark — the -on-surface-variant / -container pairing was flagged as a likely AA casualty at design
|
|
55
|
+
time). MEASURED (badge.browser.test.ts, both engines): 7.10–7.62:1 across all four intents × both
|
|
56
|
+
colour schemes — clears 4.5:1 comfortably; the predicted casualty did not materialize, no repoint owed.
|
|
57
|
+
A failing pairing would be a token-default repoint here, never a mechanism change (SPEC-R14/LLD-C8
|
|
58
|
+
failure-mode #13). */
|
|
59
|
+
:where(ui-badge[intent='info']) {
|
|
60
|
+
--ui-badge-fill: var(--md-sys-color-info-container);
|
|
61
|
+
--ui-badge-ink: var(--md-sys-color-info-on-surface-variant);
|
|
62
|
+
--ui-badge-outline: var(--md-sys-color-info-outline-variant);
|
|
63
|
+
}
|
|
64
|
+
:where(ui-badge[intent='success']) {
|
|
65
|
+
--ui-badge-fill: var(--md-sys-color-success-container);
|
|
66
|
+
--ui-badge-ink: var(--md-sys-color-success-on-surface-variant);
|
|
67
|
+
--ui-badge-outline: var(--md-sys-color-success-outline-variant);
|
|
68
|
+
}
|
|
69
|
+
:where(ui-badge[intent='warning']) {
|
|
70
|
+
--ui-badge-fill: var(--md-sys-color-warning-container);
|
|
71
|
+
--ui-badge-ink: var(--md-sys-color-warning-on-surface-variant);
|
|
72
|
+
--ui-badge-outline: var(--md-sys-color-warning-outline-variant);
|
|
73
|
+
}
|
|
74
|
+
:where(ui-badge[intent='danger']) {
|
|
75
|
+
--ui-badge-fill: var(--md-sys-color-danger-container);
|
|
76
|
+
--ui-badge-ink: var(--md-sys-color-danger-on-surface-variant);
|
|
77
|
+
--ui-badge-outline: var(--md-sys-color-danger-outline-variant);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-badge) consumes ONLY --ui-badge-* ════════════════ */
|
|
81
|
+
|
|
82
|
+
@scope (ui-badge) {
|
|
83
|
+
:scope {
|
|
84
|
+
display: inline-flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
gap: var(--ui-badge-gap);
|
|
87
|
+
box-sizing: border-box;
|
|
88
|
+
block-size: var(--ui-badge-box);
|
|
89
|
+
min-inline-size: var(--ui-badge-box); /* empty-label floor → a filled pill/dot (SPEC-R13 AC2) */
|
|
90
|
+
padding-inline: var(--ui-badge-pad);
|
|
91
|
+
padding-block: 0; /* the frame law — block-size is the vertical lever, never block-padding */
|
|
92
|
+
border-radius: calc(var(--ui-badge-box) / 2); /* the pill = box/2, the realm's "count pill" case */
|
|
93
|
+
border: 1px solid var(--ui-badge-outline);
|
|
94
|
+
background: var(--ui-badge-fill);
|
|
95
|
+
color: var(--ui-badge-ink);
|
|
96
|
+
font-size: var(--ui-badge-font);
|
|
97
|
+
line-height: 1; /* single-line token centers like a glyph (geometry.md's control line-height law) */
|
|
98
|
+
white-space: nowrap; /* anonymous host-as-content label text — the SAME no-wrapper gap ui-button had
|
|
99
|
+
(button.css, fixed by ADR-0133) also exists here: no overflow/text-overflow, so
|
|
100
|
+
a label an ancestor constrains would clip/overlap with no ellipsis affordance.
|
|
101
|
+
NAMED DEFERRAL, not fixed here (ADR-0133 §Decision cl.3, TKT-0042): no evidence
|
|
102
|
+
has been submitted that a badge label actually overflows in practice (the
|
|
103
|
+
compact-realm box is meant for short tags/counts), and ADR-0106's Alternatives
|
|
104
|
+
already rejected a generic fleet-wide truncate fix — per-control adoption stays
|
|
105
|
+
per-control evidence. A future report against this control reopens ADR-0133's
|
|
106
|
+
Alternatives with its own evidence, rather than starting cold. */
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Glyph visibility is DEFAULT-OFF, opted in per the four known intents below — never default-on with a
|
|
110
|
+
neutral/unknown opt-out. This is deliberately robust to an out-of-enum [intent] attribute value that
|
|
111
|
+
the property-level hardening effect (badge.ts) cannot reach: the effect snaps the PROPERTY back to
|
|
112
|
+
'neutral', but a raw/reflected attribute mismatch (e.g. a plain-author typo, or a window where the
|
|
113
|
+
attribute briefly differs from the hardened property) must still degrade to "no glyph," never a
|
|
114
|
+
shapeless solid square (component-review finding — verified reachable on a literal out-of-enum
|
|
115
|
+
attribute in both engines before this fix). */
|
|
116
|
+
:scope [data-part='glyph'] {
|
|
117
|
+
display: none;
|
|
118
|
+
flex: none;
|
|
119
|
+
background-color: currentColor;
|
|
120
|
+
inline-size: var(--ui-badge-glyph);
|
|
121
|
+
block-size: var(--ui-badge-glyph);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Per-intent glyph SHAPES — pairwise-distinct clip-paths (SPEC-R12 AC1/AC2; the checkbox tick
|
|
125
|
+
precedent). Shape is the meaning channel; the [intent] colour repoints above are the REDUNDANT hue
|
|
126
|
+
channel (ADR-0057) — removing colour (e.g. grayscale) must never collapse two intents together.
|
|
127
|
+
`neutral` (and any unrecognized [intent]) inherits the base rule's `display: none` — absence IS
|
|
128
|
+
neutral's signifier, and it is also the safe fallback for anything not in this closed set. */
|
|
129
|
+
:scope[intent='success'] [data-part='glyph'] {
|
|
130
|
+
/* tick — the checkbox ✓ clip-path polygon, reused verbatim as the shared fleet "affirmative" mark */
|
|
131
|
+
display: inline-block;
|
|
132
|
+
clip-path: polygon(14% 44%, 0% 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
|
133
|
+
}
|
|
134
|
+
:scope[intent='danger'] [data-part='glyph'] {
|
|
135
|
+
/* cross — an X polygon, distinct from the tick and the triangle at every corner */
|
|
136
|
+
display: inline-block;
|
|
137
|
+
clip-path: polygon(
|
|
138
|
+
20% 8%,
|
|
139
|
+
8% 20%,
|
|
140
|
+
38% 50%,
|
|
141
|
+
8% 80%,
|
|
142
|
+
20% 92%,
|
|
143
|
+
50% 62%,
|
|
144
|
+
80% 92%,
|
|
145
|
+
92% 80%,
|
|
146
|
+
62% 50%,
|
|
147
|
+
92% 20%,
|
|
148
|
+
80% 8%,
|
|
149
|
+
50% 38%
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
:scope[intent='warning'] [data-part='glyph'] {
|
|
153
|
+
display: inline-block;
|
|
154
|
+
clip-path: polygon(50% 0, 100% 100%, 0 100%); /* triangle */
|
|
155
|
+
}
|
|
156
|
+
:scope[intent='info'] [data-part='glyph'] {
|
|
157
|
+
display: inline-block;
|
|
158
|
+
clip-path: circle(50%); /* disc */
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Forced-colors (SPEC-R15) — the badge keeps its boxed identity via a system-ink border, and the glyph
|
|
162
|
+
paints in CanvasText: a background-drawn clip-path glyph is otherwise forced to `Canvas` and vanishes
|
|
163
|
+
(the bar-chart fill lesson). forced-color-adjust:none preserves these explicit system-colour mappings. */
|
|
164
|
+
@media (forced-colors: active) {
|
|
165
|
+
:scope {
|
|
166
|
+
background-color: Canvas;
|
|
167
|
+
color: CanvasText;
|
|
168
|
+
border-color: CanvasText;
|
|
169
|
+
forced-color-adjust: none;
|
|
170
|
+
}
|
|
171
|
+
:scope [data-part='glyph'] {
|
|
172
|
+
background-color: CanvasText;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { UIElement, type ReactiveProps } from '../../dom/index.js';
|
|
2
|
+
declare const props: {
|
|
3
|
+
label: {
|
|
4
|
+
reflect: true;
|
|
5
|
+
type: import("../../index.js").PropType<string>;
|
|
6
|
+
default: string;
|
|
7
|
+
attribute?: string | false;
|
|
8
|
+
};
|
|
9
|
+
intent: {
|
|
10
|
+
reflect: true;
|
|
11
|
+
type: import("../../index.js").PropType<"neutral" | "info" | "success" | "warning" | "danger">;
|
|
12
|
+
default: "neutral" | "info" | "success" | "warning" | "danger";
|
|
13
|
+
attribute?: string | false;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export interface UIBadgeElement extends ReactiveProps<typeof props> {
|
|
17
|
+
}
|
|
18
|
+
export declare class UIBadgeElement extends UIElement {
|
|
19
|
+
#private;
|
|
20
|
+
static props: {
|
|
21
|
+
label: {
|
|
22
|
+
reflect: true;
|
|
23
|
+
type: import("../../index.js").PropType<string>;
|
|
24
|
+
default: string;
|
|
25
|
+
attribute?: string | false;
|
|
26
|
+
};
|
|
27
|
+
intent: {
|
|
28
|
+
reflect: true;
|
|
29
|
+
type: import("../../index.js").PropType<"neutral" | "info" | "success" | "warning" | "danger">;
|
|
30
|
+
default: "neutral" | "info" | "success" | "warning" | "danger";
|
|
31
|
+
attribute?: string | false;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
protected connected(): void;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// badge.ts — UIBadgeElement, the compact-realm's first shipped non-interactive status/label token
|
|
2
|
+
// (report-family.lld.md LLD-C7; SPEC-R11…R13; ADR-0111 cl.1/2/3/4/5, fork F3; ADR-0057). Light-DOM
|
|
3
|
+
// display leaf — extends UIElement directly (no form participation, no focus, no keyboard contract, no
|
|
4
|
+
// events; SPEC-R11): a badge announces via its own real text, nothing else.
|
|
5
|
+
//
|
|
6
|
+
// Two props: `label` (plain string text) and `intent` (bindable STATUS DATA, unlike Sparkline's
|
|
7
|
+
// structural `variant` — ADR-0111 cl.2) — the enum snaps an unknown ATTRIBUTE string back to 'neutral'
|
|
8
|
+
// for free (`prop.enum`'s codec, the attribute↔value crossing in dom/props.ts). That codec guard fires
|
|
9
|
+
// only on the attribute-string path, though: a raw PROPERTY write (`el.intent = 'bogus'`, the shape a
|
|
10
|
+
// `{path}` DATA-MODEL bind actually takes at runtime — ADR-0098's validator sees literals at parse time,
|
|
11
|
+
// never a bound value) goes straight through `finalize()`'s generic accessor with no codec involved at
|
|
12
|
+
// all. SPEC-R11 AC2 requires the STORED value itself to read back 'neutral' after such a write (not just
|
|
13
|
+
// what a render effect happens to clean on the way out) — so the render-boundary hardening idiom
|
|
14
|
+
// (sparkline's `cleanSeries`, table's `cleanColumns`/`cleanRows`) is not enough on its own here; this file
|
|
15
|
+
// adds one self-correcting `effect` (below) that re-validates `intent` on every change and writes the
|
|
16
|
+
// snapped value BACK through the normal reactive setter (which also re-reflects the `[intent]` attribute
|
|
17
|
+
// — AC2's second half). It converges in at most one extra microtask (bad → 'neutral' → no further write)
|
|
18
|
+
// and uses only the public `this.effect`/`this.intent` surface — no reach into the props.ts internals.
|
|
19
|
+
//
|
|
20
|
+
// The glyph node is CONSTANT DOM — built once in connected(), never replaced. Every per-intent visual
|
|
21
|
+
// difference (shape, color, visibility) is CSS keyed off the reflected `[intent]` host attribute, so an
|
|
22
|
+
// intent change (a bound update) is a zero-DOM-churn attribute flip, never a re-render.
|
|
23
|
+
import { UIElement, prop } from "../../dom/index.js";
|
|
24
|
+
/** The closed intent set (SPEC-R11) — shared between the enum prop and the runtime hardening guard below. */
|
|
25
|
+
const INTENTS = ['neutral', 'info', 'success', 'warning', 'danger'];
|
|
26
|
+
const props = {
|
|
27
|
+
label: { ...prop.string(''), reflect: true },
|
|
28
|
+
// Bindable status data (fork F3): `prop.enum`'s codec snaps an unknown ATTRIBUTE string to 'neutral'
|
|
29
|
+
// for free; the property-write path is hardened separately (see the connected() effect). REFLECTS so
|
|
30
|
+
// `[intent]` CSS keys on JS-set/bound values (the ui-text `variant`/`size` precedent).
|
|
31
|
+
intent: { ...prop.enum(INTENTS, 'neutral'), reflect: true },
|
|
32
|
+
};
|
|
33
|
+
export class UIBadgeElement extends UIElement {
|
|
34
|
+
static props = props;
|
|
35
|
+
// The label part — held so the build below is genuinely once-EVER (TKT-0067): light-DOM children
|
|
36
|
+
// persist across a disconnect, so a reconnect reuses the SAME nodes instead of re-minting them.
|
|
37
|
+
#label;
|
|
38
|
+
connected() {
|
|
39
|
+
// Component-side hardening (SPEC-R11 AC2, ADR-0111 cl.2): a bound-garbage `intent` value snaps back
|
|
40
|
+
// to 'neutral' — re-checked on every change, written back through the normal reactive setter so BOTH
|
|
41
|
+
// `el.intent` and the reflected `[intent]` attribute read the sanitized value. Self-converging: the
|
|
42
|
+
// corrective write changes the signal from the bad value to 'neutral', which re-runs this same effect
|
|
43
|
+
// once more, finds 'neutral' valid, and stops (never a tight loop — `intent`'s own change is what
|
|
44
|
+
// re-triggers it, and the second pass is a no-op).
|
|
45
|
+
this.effect(() => {
|
|
46
|
+
if (!INTENTS.includes(this.intent))
|
|
47
|
+
this.intent = 'neutral';
|
|
48
|
+
});
|
|
49
|
+
// One-time DOM build (LLD-C7), now genuinely once-EVER behind an idempotent guard (TKT-0067: the
|
|
50
|
+
// prior code re-minted both nodes on every connect despite this comment's own "neither node is ever
|
|
51
|
+
// replaced" claim — true within a connection, false across reconnect; the parts-once canon): the
|
|
52
|
+
// glyph is decorative-only (aria-hidden; shape/visibility ride CSS off `[intent]`), the label
|
|
53
|
+
// carries the announced text. Only the label's textContent updates; only the host's `[intent]` flips.
|
|
54
|
+
if (this.#label === undefined) {
|
|
55
|
+
const glyph = document.createElement('span');
|
|
56
|
+
glyph.dataset.part = 'glyph';
|
|
57
|
+
glyph.setAttribute('aria-hidden', 'true');
|
|
58
|
+
this.#label = document.createElement('span');
|
|
59
|
+
this.#label.dataset.part = 'label';
|
|
60
|
+
this.replaceChildren(glyph, this.#label);
|
|
61
|
+
}
|
|
62
|
+
const label = this.#label;
|
|
63
|
+
// The one render effect: real text is the whole announcement (SPEC-R12 AC3) — no internals ARIA is
|
|
64
|
+
// minted at all (no role, no aria-label); the host's accessible name is its own text content.
|
|
65
|
+
this.effect(() => {
|
|
66
|
+
label.textContent = this.label;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (!customElements.get('ui-badge'))
|
|
71
|
+
customElements.define('ui-badge', UIBadgeElement);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* bar-chart.css — ui-bar-chart, the Display-class magnitude-comparison bar list (LLD-C6,
|
|
2
|
+
* chart-family.lld.md §3; SPEC-R6/R9/R10/R11/R12). Two clearly-SECTIONED blocks:
|
|
3
|
+
*
|
|
4
|
+
* [1] TOKEN BLOCK — `:where(ui-bar-chart)` (specificity 0,0,0) declares the `--ui-bar-chart-*` chain:
|
|
5
|
+
* the whole-shape floor (SPEC-R9 AC1 — a bare, unstyled chart in a flex row must still paint a
|
|
6
|
+
* visible, non-collapsed, proportionally-correct box), density-INVARIANT mark geometry (bar
|
|
7
|
+
* thickness/radius — SPEC-R12: mark geometry never rides `[scale]`), the data-bearing fill ink (a
|
|
8
|
+
* ≥3:1-vs-surface fleet colour role, WCAG 1.4.11 — the fill is the graphic, the track is
|
|
9
|
+
* decorative) and the density-RESPONSIVE row/column rhythm (rides `--md-sys-space` × `[density]` for
|
|
10
|
+
* free — the ADR-0103 radio-group precedent, mirroring `ui-list`'s gap).
|
|
11
|
+
* [2] STYLES BLOCK — `@scope (ui-bar-chart)` consumes ONLY `--ui-bar-chart-*` (∪ the shared
|
|
12
|
+
* `--md-sys-color-*`/`--md-sys-typescale-*` namespaces the token block itself reads to build that
|
|
13
|
+
* chain). `ui-bar-chart` is Display-class, TEXT-BEARING (SPEC-R12): labels/values read the
|
|
14
|
+
* `--md-sys-typescale-body-medium-*` row directly (no `[size]`/`[scale]` lever — a chart takes
|
|
15
|
+
* neither, SPEC-R12 AC2); no control height, no `padding-block` law.
|
|
16
|
+
*
|
|
17
|
+
* Row model: `fit-content(40%) 1fr auto` — labels cap at 40% and WRAP (`overflow-wrap: anywhere`, SPEC-R6
|
|
18
|
+
* AC3 — never starve the track); the track takes the remainder; values right-align with tabular numerals
|
|
19
|
+
* so magnitudes scan vertically. Each `[role='listitem']` row is a REAL `subgrid` child sharing the three
|
|
20
|
+
* columns (not `display: contents`, whose role-semantics engine-bug history the fleet doesn't need — LLD
|
|
21
|
+
* §3 note). The fill's inset/length ride two row-scoped custom properties SET IMPERATIVELY by bar-chart.ts
|
|
22
|
+
* (`--_bar-start`/`--_bar-length` — a non-`--ui-*` namespace, the slider/slider-multi `--value-pct`
|
|
23
|
+
* precedent the family-coherence token gate already exempts) — this sheet owns every paint decision, the
|
|
24
|
+
* behaviour file never writes a `width`.
|
|
25
|
+
*
|
|
26
|
+
* RTL is free (SPEC-R11 AC1): `inset-inline-start` + logical grid flow mirror under `dir="rtl"` — no
|
|
27
|
+
* physical property anywhere in this file. WHCM (SPEC-R10 AC1): the fill MUST render in a system ink (a
|
|
28
|
+
* background-drawn fill is otherwise forced to `Canvas` and vanishes) — an explicit `forced-colors` block
|
|
29
|
+
* paints it `CanvasText`, with the track kept visible + distinguishable via a `Canvas` fill + `CanvasText`
|
|
30
|
+
* border. Non-color signifier (SPEC-R8, ADR-0057): magnitude travels by length + the printed number; no
|
|
31
|
+
* intent-role is consumed anywhere in this sheet — nothing to co-carry.
|
|
32
|
+
*
|
|
33
|
+
* Standalone stylesheet — linked via the component-styles barrel (wave M1-b); NEVER injected from
|
|
34
|
+
* bar-chart.ts (behaviour-only `.ts`, no runtime style injection — plan §2).
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-bar-chart) declares --ui-bar-chart-* ════════════════ */
|
|
38
|
+
|
|
39
|
+
:where(ui-bar-chart) {
|
|
40
|
+
--ui-bar-chart-min-inline-size: 16em; /* the whole-shape floor (SPEC-R9 AC1) — never a collapsed sliver */
|
|
41
|
+
--ui-bar-chart-bar-size: 0.5rem; /* bar/track thickness — density-invariant (SPEC-R12) */
|
|
42
|
+
--ui-bar-chart-bar-radius: 2px;
|
|
43
|
+
--ui-bar-chart-bar-ink: var(--md-sys-color-primary); /* the data-bearing fill — >=3:1 vs surface (WCAG 1.4.11) */
|
|
44
|
+
--ui-bar-chart-track-ink: var(--md-sys-color-neutral-container); /* the decorative rail */
|
|
45
|
+
--ui-bar-chart-text-ink: var(--md-sys-color-neutral-on-surface); /* label/value ink — the ONE role reference (role-pure) */
|
|
46
|
+
--ui-bar-chart-row-gap: var(--md-sys-space-sm); /* row rhythm — rides [density] for free (ADR-0103) */
|
|
47
|
+
--ui-bar-chart-col-gap: var(--md-sys-space-sm);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-bar-chart) consumes ONLY --ui-bar-chart-* ════════════════ */
|
|
51
|
+
|
|
52
|
+
@scope (ui-bar-chart) {
|
|
53
|
+
:scope {
|
|
54
|
+
display: grid;
|
|
55
|
+
grid-template-columns: fit-content(40%) 1fr auto;
|
|
56
|
+
column-gap: var(--ui-bar-chart-col-gap);
|
|
57
|
+
row-gap: var(--ui-bar-chart-row-gap);
|
|
58
|
+
align-items: center;
|
|
59
|
+
min-inline-size: var(--ui-bar-chart-min-inline-size);
|
|
60
|
+
box-sizing: border-box;
|
|
61
|
+
/* text-bearing Display (SPEC-R12) — the type matrix is the lever, not [size]/[scale]/--md-sys-height-* */
|
|
62
|
+
font-size: var(--md-sys-typescale-body-medium-size);
|
|
63
|
+
line-height: var(--md-sys-typescale-body-medium-line-height);
|
|
64
|
+
color: var(--ui-bar-chart-text-ink);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:scope [role='listitem'] {
|
|
68
|
+
display: grid;
|
|
69
|
+
grid-template-columns: subgrid;
|
|
70
|
+
grid-column: 1 / -1;
|
|
71
|
+
align-items: center;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
:scope [data-part='label'] {
|
|
75
|
+
overflow-wrap: anywhere; /* long labels wrap at the 40% cap (SPEC-R6 AC3) instead of starving the track */
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
:scope [data-part='track'] {
|
|
79
|
+
position: relative;
|
|
80
|
+
display: block;
|
|
81
|
+
block-size: var(--ui-bar-chart-bar-size);
|
|
82
|
+
border-radius: var(--ui-bar-chart-bar-radius);
|
|
83
|
+
background: var(--ui-bar-chart-track-ink);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
:scope [data-part='fill'] {
|
|
87
|
+
position: absolute;
|
|
88
|
+
inset-block: 0;
|
|
89
|
+
inset-inline-start: calc(var(--_bar-start) * 1%); /* row-scoped, imperatively set (bar-chart.ts) */
|
|
90
|
+
inline-size: calc(var(--_bar-length) * 1%);
|
|
91
|
+
display: block;
|
|
92
|
+
background: var(--ui-bar-chart-bar-ink);
|
|
93
|
+
border-radius: inherit;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
:scope [data-part='value'] {
|
|
97
|
+
text-align: end;
|
|
98
|
+
font-variant-numeric: tabular-nums;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@media (forced-colors: active) {
|
|
102
|
+
:scope [data-part='fill'] {
|
|
103
|
+
background: CanvasText; /* a background-drawn fill is otherwise forced to Canvas and vanishes */
|
|
104
|
+
}
|
|
105
|
+
:scope [data-part='track'] {
|
|
106
|
+
background: Canvas;
|
|
107
|
+
border: 1px solid CanvasText; /* keeps the track distinguishable from the fill under WHCM */
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { UIElement, type ReactiveProps } from '../../dom/index.js';
|
|
2
|
+
declare const props: {
|
|
3
|
+
data: import("../../index.js").PropConfig<import("./bar-math.js").BarDatum[]>;
|
|
4
|
+
label: {
|
|
5
|
+
reflect: true;
|
|
6
|
+
type: import("../../index.js").PropType<string>;
|
|
7
|
+
default: string;
|
|
8
|
+
attribute?: string | false;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export interface UIBarChartElement extends ReactiveProps<typeof props> {
|
|
12
|
+
}
|
|
13
|
+
export declare class UIBarChartElement extends UIElement {
|
|
14
|
+
#private;
|
|
15
|
+
static props: {
|
|
16
|
+
data: import("../../index.js").PropConfig<import("./bar-math.js").BarDatum[]>;
|
|
17
|
+
label: {
|
|
18
|
+
reflect: true;
|
|
19
|
+
type: import("../../index.js").PropType<string>;
|
|
20
|
+
default: string;
|
|
21
|
+
attribute?: string | false;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
protected connected(): void;
|
|
25
|
+
}
|
|
26
|
+
export {};
|