@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,312 @@
|
|
|
1
|
+
// textarea.ts — UITextareaElement, the FACE multi-line text primitive (ADR-0134; a SIBLING of ui-text-field,
|
|
2
|
+
// NOT a text-field mode). BEHAVIOUR + props + the contenteditable editor part + self-define ONLY;
|
|
3
|
+
// geometry/colour live in textarea.css (its OWN multi-line law, not the single-line (scale×size)→§1-row
|
|
4
|
+
// lookup), the public contract in textarea.md.
|
|
5
|
+
//
|
|
6
|
+
// Editable surface — the ADR-0014 contenteditable PATTERN reused verbatim: a stable, control-created
|
|
7
|
+
// light-DOM `<div data-part="editor" contenteditable="plaintext-only" role="textbox" aria-multiline="true">`,
|
|
8
|
+
// appended ONCE (an idempotent guard) and NEVER re-rendered. `value`↔surface is the same two scope-owned
|
|
9
|
+
// wires as ui-text-field (surface→model on `input`, model→surface under the CARET GUARD), both suppressed
|
|
10
|
+
// during IME composition.
|
|
11
|
+
//
|
|
12
|
+
// The INVERSION from ui-text-field (ADR-0134's whole reason to be a sibling, not a mode): `aria-multiline`
|
|
13
|
+
// is `true`, and `Enter` is NOT intercepted — it inserts a newline exactly like a native `<textarea>`, and
|
|
14
|
+
// commit is BLUR-with-change ONLY (never Enter). Geometry inverts too: `rows` sets a GROWABLE
|
|
15
|
+
// `min-block-size`, real `padding-block`, `align-items: start`, prose `line-height`, `resize: vertical` —
|
|
16
|
+
// see textarea.css for the law. No `type` prop, no adornment/overlay/codec machinery — none of that applies
|
|
17
|
+
// to plain multi-line text (ADR-0134 "Decision").
|
|
18
|
+
//
|
|
19
|
+
// The HOST carries no `role`/`aria-*` attribute — form semantics ride `internals` (ADR-0013) and the editor
|
|
20
|
+
// PART carries `role=textbox`. ARIA + the custom states are set THROUGH the protected `internals`. Disabled
|
|
21
|
+
// rides the editor + the platform form-disabled channel (`effectiveDisabled = own || form`), NOT host
|
|
22
|
+
// `ariaDisabled`; `readonly` is editable=false but focusable (still submits). `user-invalid` timing comes
|
|
23
|
+
// from the `trackUserInvalid` controller, reinforced by a non-colour `aria-describedby` message node (WCAG
|
|
24
|
+
// 1.4.1). `controls → dom + traits` is the allowed import direction.
|
|
25
|
+
import { prop } from "../../dom/index.js";
|
|
26
|
+
import { UIFormElement } from "../../dom/form.js";
|
|
27
|
+
import { trackUserInvalid } from "../../traits/track-user-invalid.js";
|
|
28
|
+
// The editor's editable mode (ADR-0014 cl.1, reused) and a per-instance id seed for aria-describedby.
|
|
29
|
+
const EDITABLE = 'plaintext-only';
|
|
30
|
+
let messageSeq = 0;
|
|
31
|
+
// ── props ─────────────────────────────────────────────────────────────────────
|
|
32
|
+
const props = {
|
|
33
|
+
// The universal form attributes (name / disabled(reflect) / required(reflect)) are SPREAD, not inherited —
|
|
34
|
+
// props.ts has no static-props prototype merge, so UIFormElement exposes them as a spreadable bag (ADR-0013).
|
|
35
|
+
...UIFormElement.formProps,
|
|
36
|
+
// `value` is OBSERVED (its initial attribute seeds the reset baseline) but NOT reflected — the live value
|
|
37
|
+
// rides the editor surface, never a host attribute.
|
|
38
|
+
value: prop.string(),
|
|
39
|
+
label: { ...prop.string(), reflect: true }, // → the editor's aria-label (the labelling SEAM; yields under a ui-field association)
|
|
40
|
+
placeholder: prop.string(), // shown via [data-empty]::before { content: attr(data-placeholder) } when empty
|
|
41
|
+
// rows: native <textarea rows> parity — the MIN-height lever (ADR-0134), not a fixed control height. reflects
|
|
42
|
+
// so [rows] attribute-selector / attr-based CSS repoints apply to JS-set values too.
|
|
43
|
+
rows: { ...prop.number(3), reflect: true },
|
|
44
|
+
// size repoints font + block-padding + the per-row line-box in textarea.css (NOT a fixed height lever —
|
|
45
|
+
// the single-line (scale×size)→§1-row lookup does not apply here, ADR-0134).
|
|
46
|
+
size: { ...prop.enum(['sm', 'md', 'lg'], 'md'), reflect: true },
|
|
47
|
+
readonly: { ...prop.boolean(false), reflect: true },
|
|
48
|
+
};
|
|
49
|
+
export class UITextareaElement extends UIFormElement {
|
|
50
|
+
static props = props;
|
|
51
|
+
// The stable contenteditable editor PART + the aria-describedby message node. Light-DOM children, so they
|
|
52
|
+
// persist across disconnect/reconnect — created ONCE and never re-appended (the idempotent guard below).
|
|
53
|
+
#editor = null;
|
|
54
|
+
#message = null;
|
|
55
|
+
// The native-parity reset baseline — seeded ONCE from the initial `value` attribute (native `defaultValue`).
|
|
56
|
+
#defaultValue = '';
|
|
57
|
+
#defaultCaptured = false;
|
|
58
|
+
// The `change`-on-commit baseline (value at the last focus/commit) + the IME-composition guard.
|
|
59
|
+
#committed = '';
|
|
60
|
+
#composing = false;
|
|
61
|
+
// The user-invalid TIMING controller, created per connection (re-arms on reconnect; released on disconnect).
|
|
62
|
+
#userInvalid = null;
|
|
63
|
+
connected() {
|
|
64
|
+
// Seed the reset baseline ONCE from the INITIAL `value` attribute (native `defaultValue` — the value
|
|
65
|
+
// attribute is never reflected, so it stays the markup value and survives later property writes).
|
|
66
|
+
if (!this.#defaultCaptured) {
|
|
67
|
+
this.#defaultValue = this.getAttribute('value') ?? '';
|
|
68
|
+
this.#defaultCaptured = true;
|
|
69
|
+
}
|
|
70
|
+
const editor = this.#ensureParts();
|
|
71
|
+
const message = this.#message;
|
|
72
|
+
// ── surface → model (ADR-0014 cl.1, reused) — the editor's edits flow into `value`, IME-guarded ──
|
|
73
|
+
this.listen(editor, 'input', (event) => {
|
|
74
|
+
if (this.#composing)
|
|
75
|
+
return; // never mid-composition — compositionend commits the final composed text
|
|
76
|
+
event.stopPropagation(); // suppress the raw editor input; the host re-emits ONE composed `input` (target = host)
|
|
77
|
+
this.value = editor.textContent ?? ''; // model ← surface (the caret guard below then skips the echo write)
|
|
78
|
+
this.emit('input');
|
|
79
|
+
});
|
|
80
|
+
this.listen(editor, 'compositionstart', () => {
|
|
81
|
+
this.#composing = true;
|
|
82
|
+
});
|
|
83
|
+
this.listen(editor, 'compositionend', () => {
|
|
84
|
+
this.#composing = false;
|
|
85
|
+
this.value = editor.textContent ?? ''; // catch the model up to the composed result (the suppressed inputs)
|
|
86
|
+
});
|
|
87
|
+
// ── change on commit — blur-with-change ONLY (ADR-0134: the Enter-commits law INVERTS — Enter inserts a
|
|
88
|
+
// newline here, never commits; no keydown interception at all, unlike ui-text-field) ──
|
|
89
|
+
this.listen(editor, 'focus', () => {
|
|
90
|
+
this.#committed = this.value;
|
|
91
|
+
});
|
|
92
|
+
this.listen(editor, 'blur', () => {
|
|
93
|
+
if (this.value === this.#committed)
|
|
94
|
+
return;
|
|
95
|
+
this.#committed = this.value;
|
|
96
|
+
this.emit('change');
|
|
97
|
+
});
|
|
98
|
+
// ── the user-invalid TIMING controller — gates the danger treatment until the first blur/change ──
|
|
99
|
+
const controller = trackUserInvalid(this, { invalid: () => !this.formValidity().valid });
|
|
100
|
+
this.#userInvalid = controller;
|
|
101
|
+
// ── model → surface (ADR-0014 cl.1: the CARET GUARD) + the placeholder presence flag ──
|
|
102
|
+
this.effect(() => {
|
|
103
|
+
const value = this.value; // tracked — re-runs on every value change (typed OR programmatic/reset/restore)
|
|
104
|
+
if (this.#composing)
|
|
105
|
+
return; // never write mid-composition
|
|
106
|
+
// CARET GUARD: rewrite the editor ONLY when the model diverges from the surface, so a keystroke (which
|
|
107
|
+
// already updated textContent) never resets the caret; a programmatic write/reset/restore DOES flow.
|
|
108
|
+
if (editor.textContent !== value)
|
|
109
|
+
editor.textContent = value;
|
|
110
|
+
editor.toggleAttribute('data-empty', value === ''); // keys the CSS placeholder (not :empty — see ADR cl.1)
|
|
111
|
+
});
|
|
112
|
+
// ── editor attribute mirror — the label seam, the placeholder text, the required mirror ──
|
|
113
|
+
this.effect(() => {
|
|
114
|
+
// Yield: aria-label is the BARE stand-in only — a `ui-field` association carries the name via
|
|
115
|
+
// aria-labelledby (applyFieldLabelling below), which beats aria-label in accname resolution anyway;
|
|
116
|
+
// clearing it here keeps the editor's AX tree clean under association (the ADR-0051 pattern).
|
|
117
|
+
if (this.label && this.fieldLabelling === null)
|
|
118
|
+
editor.setAttribute('aria-label', this.label);
|
|
119
|
+
else
|
|
120
|
+
editor.removeAttribute('aria-label');
|
|
121
|
+
editor.setAttribute('data-placeholder', this.placeholder); // the CSS placeholder reads attr(data-placeholder)
|
|
122
|
+
if (this.required)
|
|
123
|
+
editor.setAttribute('aria-required', 'true');
|
|
124
|
+
else
|
|
125
|
+
editor.removeAttribute('aria-required');
|
|
126
|
+
});
|
|
127
|
+
// ── the disabled / readonly channel (ADR-0014 dev#b, reused) — effectiveDisabled = own || form-disabled ──
|
|
128
|
+
this.effect(() => {
|
|
129
|
+
if (this.effectiveDisabled()) {
|
|
130
|
+
editor.setAttribute('contenteditable', 'false');
|
|
131
|
+
editor.removeAttribute('tabindex'); // not focusable (out of the tab order, like <textarea disabled>)
|
|
132
|
+
editor.setAttribute('aria-disabled', 'true');
|
|
133
|
+
editor.removeAttribute('aria-readonly');
|
|
134
|
+
this.internals.states?.add('disabled'); // the :state(disabled) CSS hook (NOT host ariaDisabled)
|
|
135
|
+
}
|
|
136
|
+
else if (this.readonly) {
|
|
137
|
+
editor.setAttribute('contenteditable', 'false'); // not editable …
|
|
138
|
+
editor.setAttribute('tabindex', '0'); // … but still focusable / selectable (and still submits)
|
|
139
|
+
editor.removeAttribute('aria-disabled');
|
|
140
|
+
editor.setAttribute('aria-readonly', 'true');
|
|
141
|
+
this.internals.states?.delete('disabled');
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
editor.setAttribute('contenteditable', EDITABLE); // editable; a contenteditable region is intrinsically focusable
|
|
145
|
+
editor.removeAttribute('tabindex');
|
|
146
|
+
editor.removeAttribute('aria-disabled');
|
|
147
|
+
editor.removeAttribute('aria-readonly');
|
|
148
|
+
this.internals.states?.delete('disabled');
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
// ── user-invalid → aria-invalid + the non-colour message cue + :state(user-invalid) (ADR-0014 cl.2c/4) ──
|
|
152
|
+
// Reused verbatim from ui-text-field: the message node is VISIBLE when carrying a message (ADR-0029 A1);
|
|
153
|
+
// under a `ui-field` association the internal message yields — empty, hidden, dropped from
|
|
154
|
+
// aria-describedby — so the field's own error part is the ONE AT-announced error (ADR-0051 cl.4).
|
|
155
|
+
this.effect(() => {
|
|
156
|
+
const fielded = this.fieldLabelling !== null;
|
|
157
|
+
if (controller.userInvalid()) {
|
|
158
|
+
const verdict = this.formValidity();
|
|
159
|
+
const text = verdict.valid ? '' : verdict.message;
|
|
160
|
+
editor.setAttribute('aria-invalid', 'true');
|
|
161
|
+
if (fielded) {
|
|
162
|
+
message.textContent = '';
|
|
163
|
+
message.hidden = true;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
editor.setAttribute('aria-describedby', message.id);
|
|
167
|
+
message.textContent = text;
|
|
168
|
+
message.hidden = text === '';
|
|
169
|
+
}
|
|
170
|
+
this.internals.states?.add('user-invalid');
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
editor.removeAttribute('aria-invalid');
|
|
174
|
+
if (!fielded)
|
|
175
|
+
editor.removeAttribute('aria-describedby'); // never touch it while fielded (F3 precedent)
|
|
176
|
+
message.textContent = '';
|
|
177
|
+
message.hidden = true;
|
|
178
|
+
this.internals.states?.delete('user-invalid');
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
// ── rows → the CSS min-block-size lever (ADR-0134: rows is a MIN, not a fixed height) ──
|
|
182
|
+
this.effect(() => {
|
|
183
|
+
this.style.setProperty('--ui-textarea-rows', String(this.rows ?? 3));
|
|
184
|
+
});
|
|
185
|
+
// Motion gate (interaction-states standard) — arm `ready` ONE frame past first paint so the upgrade/first
|
|
186
|
+
// paint SNAPS and only subsequent state changes animate. `states` optional-chained — jsdom has no
|
|
187
|
+
// CustomStateSet (the real motion is the browser smoke).
|
|
188
|
+
requestAnimationFrame(() => this.internals.states?.add('ready'));
|
|
189
|
+
}
|
|
190
|
+
disconnected() {
|
|
191
|
+
this.#userInvalid?.release(); // idempotent — the controller's listeners already die with the connection scope
|
|
192
|
+
this.#userInvalid = null;
|
|
193
|
+
}
|
|
194
|
+
/** Forward host focus to the editor PART (label-association + native `.focus()` parity). */
|
|
195
|
+
focus(options) {
|
|
196
|
+
if (this.#editor)
|
|
197
|
+
this.#editor.focus(options);
|
|
198
|
+
else
|
|
199
|
+
super.focus(options);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Focus the editor and move the caret to the END of its text (a `ui-textarea`-friendly equivalent of the
|
|
203
|
+
* native `<textarea>.setSelectionRange(len, len)` a contenteditable host does not expose — the ADR-0134
|
|
204
|
+
* migration seam `entry-list.ts` uses to restore an in-progress edit after a re-render). A no-op before
|
|
205
|
+
* the editor part exists (guards a not-yet-connected control, mirroring `focus()`'s own guard above).
|
|
206
|
+
*/
|
|
207
|
+
selectToEnd() {
|
|
208
|
+
const editor = this.#editor;
|
|
209
|
+
if (!editor)
|
|
210
|
+
return;
|
|
211
|
+
editor.focus();
|
|
212
|
+
const selection = editor.ownerDocument.defaultView?.getSelection() ?? window.getSelection();
|
|
213
|
+
if (!selection)
|
|
214
|
+
return;
|
|
215
|
+
const range = editor.ownerDocument.createRange();
|
|
216
|
+
range.selectNodeContents(editor);
|
|
217
|
+
range.collapse(false); // collapse to the END
|
|
218
|
+
selection.removeAllRanges();
|
|
219
|
+
selection.addRange(range);
|
|
220
|
+
}
|
|
221
|
+
// ── form hooks (overrides of the UIFormElement seams) ─────────────────────────
|
|
222
|
+
/** The value contributed to the owning form (FACE — the base publishes it via internals.setFormValue). */
|
|
223
|
+
formValue() {
|
|
224
|
+
return this.value;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The validity verdict: `required && value === ''` → `valueMissing`; valid otherwise. No codec/type
|
|
228
|
+
* validation — plain multi-line text has no value-shape to mismatch (ADR-0134). A disabled field is barred
|
|
229
|
+
* from constraint validation (native parity).
|
|
230
|
+
*/
|
|
231
|
+
formValidity() {
|
|
232
|
+
if (this.effectiveDisabled())
|
|
233
|
+
return { valid: true };
|
|
234
|
+
if (this.required && this.value === '') {
|
|
235
|
+
return {
|
|
236
|
+
valid: false,
|
|
237
|
+
flags: { valueMissing: true },
|
|
238
|
+
message: 'Please fill out this field.',
|
|
239
|
+
anchor: this.#editor ?? undefined,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return { valid: true };
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Form reset → value ← the initial `value` attribute (native-parity defaultValue) + clear the touched
|
|
246
|
+
* state (so a required-empty field does not show `:state(user-invalid)` until the user re-interacts).
|
|
247
|
+
*/
|
|
248
|
+
formReset() {
|
|
249
|
+
this.value = this.#defaultValue;
|
|
250
|
+
this.#userInvalid?.reset();
|
|
251
|
+
}
|
|
252
|
+
/** Restore the value after navigation/autofill (FACE state restore). */
|
|
253
|
+
formStateRestore(state) {
|
|
254
|
+
if (typeof state === 'string')
|
|
255
|
+
this.value = state;
|
|
256
|
+
}
|
|
257
|
+
// ── ADR-0051 — the field-labelling seam wire (the ui-text-field part-role override, reused verbatim) ──────
|
|
258
|
+
/**
|
|
259
|
+
* The part-role override (ADR-0051 cl.2) — the editor's `role=textbox` rides a light-DOM PART, not
|
|
260
|
+
* `internals.role`, so the base's guarded internals-reflection default never fires here; id-reference the
|
|
261
|
+
* editor directly instead. See text-field.ts's own doc for the full ordering rationale (reused verbatim —
|
|
262
|
+
* `aria-describedby` is only WRITTEN here on association; on dissociation the user-invalid/message effect
|
|
263
|
+
* above stays the exclusive owner, avoiding a two-effect write race).
|
|
264
|
+
*/
|
|
265
|
+
applyFieldLabelling(refs) {
|
|
266
|
+
const editor = this.#editor;
|
|
267
|
+
if (!editor)
|
|
268
|
+
return;
|
|
269
|
+
if (refs === null) {
|
|
270
|
+
editor.removeAttribute('aria-labelledby');
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (refs.label)
|
|
274
|
+
editor.setAttribute('aria-labelledby', refs.label.id);
|
|
275
|
+
else
|
|
276
|
+
editor.removeAttribute('aria-labelledby');
|
|
277
|
+
const described = [refs.description, refs.error].filter((el) => el !== null);
|
|
278
|
+
if (described.length > 0)
|
|
279
|
+
editor.setAttribute('aria-describedby', described.map((el) => el.id).join(' '));
|
|
280
|
+
else
|
|
281
|
+
editor.removeAttribute('aria-describedby');
|
|
282
|
+
}
|
|
283
|
+
/** Feeds `FormConnectDetail.userInvalid` (ADR-0050) — the `trackUserInvalid` tracker IS the timing source. */
|
|
284
|
+
formUserInvalid() {
|
|
285
|
+
return this.#userInvalid?.userInvalid() ?? false;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Create the editor PART + the aria message node ONCE (idempotent across reconnect — both are light-DOM
|
|
289
|
+
* children that persist through disconnect), returning the editor. The message node is NOT a public part
|
|
290
|
+
* (the descriptor lists only `editor` under `parts`) — it is `hidden`, carrying `validity().message` only
|
|
291
|
+
* under `user-invalid`.
|
|
292
|
+
*/
|
|
293
|
+
#ensureParts() {
|
|
294
|
+
if (this.#editor)
|
|
295
|
+
return this.#editor;
|
|
296
|
+
const editor = document.createElement('div');
|
|
297
|
+
editor.setAttribute('data-part', 'editor');
|
|
298
|
+
editor.setAttribute('contenteditable', EDITABLE);
|
|
299
|
+
editor.setAttribute('role', 'textbox'); // the role rides the PART — the host carries NO role/aria-* attribute
|
|
300
|
+
editor.setAttribute('aria-multiline', 'true'); // the ADR-0134 inversion from ui-text-field's aria-multiline="false"
|
|
301
|
+
this.#editor = editor;
|
|
302
|
+
const message = document.createElement('div');
|
|
303
|
+
message.className = 'ui-textarea-message'; // a queryable hook, NOT a [data-part] (it is not a public part)
|
|
304
|
+
message.id = `ui-textarea-message-${++messageSeq}`;
|
|
305
|
+
message.hidden = true;
|
|
306
|
+
this.#message = message;
|
|
307
|
+
this.append(editor, message);
|
|
308
|
+
return editor;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (!customElements.get('ui-textarea'))
|
|
312
|
+
customElements.define('ui-textarea', UITextareaElement);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* theme-provider.css — ui-theme-provider, the single-file component stylesheet (ADR-0003). The degenerate
|
|
2
|
+
* minimal case of the fleet's two-block shape (text-field.css / column.css), the SAME shape
|
|
3
|
+
* form-provider.css already establishes for a pure coordination/carrier element —
|
|
4
|
+
*
|
|
5
|
+
* [1] TOKEN BLOCK — `:where(ui-theme-provider)` declares ONE thing: the scheme-boundary INK RE-ROOT
|
|
6
|
+
* (ADR-0148, amending SPEC-R6's original "declares nothing"). The provider maps `scheme` to its own
|
|
7
|
+
* `color-scheme` (LLD-C1/C2), but `color-scheme` alone does not re-resolve an INHERITED `color` —
|
|
8
|
+
* `light-dark()` only re-resolves where a property containing it is declared, so ink computed ONCE
|
|
9
|
+
* at an outer scheme leaks into a forced subtree as the WRONG channel values (white text on a light
|
|
10
|
+
* surface; issue #31, first hit by the gallery as the TKT-0002 class). Re-declaring `color` here, at
|
|
11
|
+
* the boundary, makes inherited ink re-resolve against the provider's own scheme. Zero specificity
|
|
12
|
+
* (`:where()`) is load-bearing: any consumer declaration on any real selector outranks it (and
|
|
13
|
+
* `color: inherit` restores the leak-through on purpose). Ink ONLY — no background, no surface: the
|
|
14
|
+
* invariant is that every BACKGROUND reachable under a provider is itself scheme-declared (all fleet
|
|
15
|
+
* surfaces are), so ink and ground re-resolve in step. Still no `--ui-theme-provider-*` chain — the
|
|
16
|
+
* rule consumes the shared, universally-consumable neutral ink role directly (family-coherence
|
|
17
|
+
* B-group's sanctioned shape); nothing to theme remains true (form-provider.css LLD-C8 precedent).
|
|
18
|
+
* [2] STYLES BLOCK — `@scope (ui-theme-provider)` sets exactly one declaration: `display: block`. An
|
|
19
|
+
* unstyled custom element defaults to `inline`, which would lay its slotted block-level descendants
|
|
20
|
+
* out inline against the host — a first-paint layout defect. No further rule: no spacing, no
|
|
21
|
+
* geometry, no surface — the coordination host paints nothing, the slotted subtree owns every box.
|
|
22
|
+
* (Deliberately NOT the home of the ink re-root: a scoped `:scope` rule carries (0,1,0) specificity
|
|
23
|
+
* plus scoping proximity and would silently BEAT a consumer's class-selector ink — the TKT-0001 trap.)
|
|
24
|
+
*
|
|
25
|
+
* Standalone stylesheet — linked via the component-styles barrel; NEVER injected from theme-provider.ts
|
|
26
|
+
* (behaviour-only `.ts`, no runtime style injection — plan §2). No forced-colors block: the component
|
|
27
|
+
* paints no surface of its own (SPEC-R6).
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-theme-provider): the ink re-root, nothing else ════════════════ */
|
|
31
|
+
/* Still no --ui-theme-provider-* token chain (SPEC-R6 as amended by ADR-0148): a pure coordination/carrier
|
|
32
|
+
element has no visual voice of its own. `scale`/`density` ride the ambient [scale]/[density] selectors in
|
|
33
|
+
dimensions.css, unmodified by this sheet (SPEC-R4); `theme` is wired but inert (SPEC-R5). The single
|
|
34
|
+
declaration below is the SCHEME-BOUNDARY INK RE-ROOT (banner §[1]) — inherited-ink re-resolution at the
|
|
35
|
+
provider's own color-scheme, not a painted surface. */
|
|
36
|
+
:where(ui-theme-provider) {
|
|
37
|
+
color: var(--md-sys-color-neutral-on-surface);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-theme-provider) consumes NOTHING ════════════════ */
|
|
41
|
+
|
|
42
|
+
@scope (ui-theme-provider) {
|
|
43
|
+
:scope {
|
|
44
|
+
display: block; /* the host's only rule: an unstyled custom element defaults to `inline`, which would
|
|
45
|
+
lay its slotted block-level descendants out inline against it — `block` is the coordination host's
|
|
46
|
+
structural default, not a styling opinion. */
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { UIElement, type ReactiveProps } from '../../dom/index.js';
|
|
2
|
+
declare const props: {
|
|
3
|
+
scheme: {
|
|
4
|
+
reflect: true;
|
|
5
|
+
type: import("../../index.js").PropType<"" | "light" | "dark">;
|
|
6
|
+
default: "" | "light" | "dark";
|
|
7
|
+
attribute?: string | false;
|
|
8
|
+
};
|
|
9
|
+
scale: {
|
|
10
|
+
reflect: true;
|
|
11
|
+
type: import("../../index.js").PropType<"" | "ui-sm" | "ui-md" | "ui-lg" | "content-sm" | "content-md" | "content-lg">;
|
|
12
|
+
default: "" | "ui-sm" | "ui-md" | "ui-lg" | "content-sm" | "content-md" | "content-lg";
|
|
13
|
+
attribute?: string | false;
|
|
14
|
+
};
|
|
15
|
+
density: {
|
|
16
|
+
reflect: true;
|
|
17
|
+
type: import("../../index.js").PropType<"" | "compact" | "comfortable" | "spacious">;
|
|
18
|
+
default: "" | "compact" | "comfortable" | "spacious";
|
|
19
|
+
attribute?: string | false;
|
|
20
|
+
};
|
|
21
|
+
theme: {
|
|
22
|
+
reflect: true;
|
|
23
|
+
type: import("../../index.js").PropType<string>;
|
|
24
|
+
default: string;
|
|
25
|
+
attribute?: string | false;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export interface UIThemeProviderElement extends ReactiveProps<typeof props> {
|
|
29
|
+
}
|
|
30
|
+
export declare class UIThemeProviderElement extends UIElement {
|
|
31
|
+
static props: {
|
|
32
|
+
scheme: {
|
|
33
|
+
reflect: true;
|
|
34
|
+
type: import("../../index.js").PropType<"" | "light" | "dark">;
|
|
35
|
+
default: "" | "light" | "dark";
|
|
36
|
+
attribute?: string | false;
|
|
37
|
+
};
|
|
38
|
+
scale: {
|
|
39
|
+
reflect: true;
|
|
40
|
+
type: import("../../index.js").PropType<"" | "ui-sm" | "ui-md" | "ui-lg" | "content-sm" | "content-md" | "content-lg">;
|
|
41
|
+
default: "" | "ui-sm" | "ui-md" | "ui-lg" | "content-sm" | "content-md" | "content-lg";
|
|
42
|
+
attribute?: string | false;
|
|
43
|
+
};
|
|
44
|
+
density: {
|
|
45
|
+
reflect: true;
|
|
46
|
+
type: import("../../index.js").PropType<"" | "compact" | "comfortable" | "spacious">;
|
|
47
|
+
default: "" | "compact" | "comfortable" | "spacious";
|
|
48
|
+
attribute?: string | false;
|
|
49
|
+
};
|
|
50
|
+
theme: {
|
|
51
|
+
reflect: true;
|
|
52
|
+
type: import("../../index.js").PropType<string>;
|
|
53
|
+
default: string;
|
|
54
|
+
attribute?: string | false;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
protected connected(): void;
|
|
58
|
+
}
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// theme-provider.ts — UIThemeProviderElement, the fleet's second UIElement-based pure-coordination/carrier
|
|
2
|
+
// primitive (ADR-0117; SPEC theme-provider.spec.md; LLD theme-provider.lld.md LLD-C1/C2). BEHAVIOUR +
|
|
3
|
+
// self-define ONLY — geometry lives in theme-provider.css (LLD-C3), the public contract in
|
|
4
|
+
// theme-provider.md (LLD-C4).
|
|
5
|
+
//
|
|
6
|
+
// Promotes site/lib/theme-provider.ts's CONTRACT (not its file) to a shipped `@agent-ui-kit/components`
|
|
7
|
+
// control, with one deliberate behavioral fix (SPEC-R3): `scheme` unset no longer collapses to light — it
|
|
8
|
+
// clears any inline override and lets `color-scheme` inherit (the page default, or an ancestor provider's,
|
|
9
|
+
// if nested). `scale`/`density`/`theme` are pure attribute carriers with zero JS-side effect (SPEC-R4/R5) —
|
|
10
|
+
// `dimensions.css`'s `[scale]`/`[density]` selectors and a future `[theme='<name>']` package layer read them
|
|
11
|
+
// directly; this class does nothing beyond reflecting them.
|
|
12
|
+
//
|
|
13
|
+
// `controls → dom` is the allowed import direction (this control needs no trait).
|
|
14
|
+
import { UIElement, prop } from "../../dom/index.js";
|
|
15
|
+
const SCHEMES = ['', 'light', 'dark'];
|
|
16
|
+
const SCALES = ['', 'ui-sm', 'ui-md', 'ui-lg', 'content-sm', 'content-md', 'content-lg']; // ADR-0032
|
|
17
|
+
const DENSITIES = ['', 'compact', 'comfortable', 'spacious'];
|
|
18
|
+
const props = {
|
|
19
|
+
// The ONE prop with a JS-side effect (below) — SPEC-R3.
|
|
20
|
+
scheme: { ...prop.enum(SCHEMES, ''), reflect: true },
|
|
21
|
+
// Pure carriers — reflected, zero effect; dimensions.css [scale]/[density] key off the attribute (SPEC-R4).
|
|
22
|
+
scale: { ...prop.enum(SCALES, ''), reflect: true },
|
|
23
|
+
density: { ...prop.enum(DENSITIES, ''), reflect: true },
|
|
24
|
+
// The reserved package seam (ADR-0079 cl.3 / SPEC-R5) — free string, inert until a future [theme=] layer ships.
|
|
25
|
+
theme: { ...prop.string(''), reflect: true },
|
|
26
|
+
};
|
|
27
|
+
export class UIThemeProviderElement extends UIElement {
|
|
28
|
+
static props = props;
|
|
29
|
+
connected() {
|
|
30
|
+
// SPEC-R3 — '' clears any inline color-scheme (never coerces to 'light'), so an unset provider imposes
|
|
31
|
+
// NO override and its subtree inherits the ambient color-scheme: the page's own, or an ANCESTOR
|
|
32
|
+
// provider's if this one nests inside another (SPEC-R3 AC4). This is the load-bearing fix over the
|
|
33
|
+
// site-local predecessor, which collapsed any non-'dark' value — including genuinely unset — to 'light'.
|
|
34
|
+
this.effect(() => {
|
|
35
|
+
this.style.colorScheme = this.scheme === '' ? '' : this.scheme;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!customElements.get('ui-theme-provider'))
|
|
40
|
+
customElements.define('ui-theme-provider', UIThemeProviderElement);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* timeline.css — ui-timeline, the single-file component stylesheet (ADR-0003; timeline-family.lld.md §3 ·
|
|
2
|
+
* SPEC-R6/R7/R14/R15). ui-timeline is a STRUCTURAL container (SPEC-R7: "MAY declare nothing else
|
|
3
|
+
* value-bearing — it is a structural container") — the rail RHYTHM (row-gap) and MARKER geometry are
|
|
4
|
+
* owned entirely by each authored `ui-timeline-item` child (its own `margin-block-end` + marker-system
|
|
5
|
+
* table, timeline-item.css); this host contributes ONLY the vertical flex stack + its accessible-name
|
|
6
|
+
* wiring (`label` → `internals.ariaLabel`, timeline.ts). No `[size]` CSS selector — `size` is a
|
|
7
|
+
* first-class prop for family/catalog symmetry (ADR-0122 F2) but this structural container itself has no
|
|
8
|
+
* geometry of its own to repoint (family-coherence A2b: a CSS `[size` selector is never present WITHOUT a
|
|
9
|
+
* declared attribute — the inverse holds trivially here, same shape as toast.css's "no [size] axis" note).
|
|
10
|
+
*
|
|
11
|
+
* [1] TOKEN BLOCK — `:where(ui-timeline)` declares the one dimensional quantity this host owns: the
|
|
12
|
+
* bare-host `min-inline-size` floor (the ui-slider whole-shape lesson — SPEC-R14 AC1).
|
|
13
|
+
* [2] STYLES BLOCK — `@scope (ui-timeline)` lays out the authored `ui-timeline-item` children as a
|
|
14
|
+
* vertical stack, DOM order, no auto-sort (the adia rule) — page/container-scrolled, static.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-timeline) declares --ui-timeline-* ════════════════ */
|
|
18
|
+
|
|
19
|
+
:where(ui-timeline) {
|
|
20
|
+
--ui-timeline-min-inline-size: 12rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-timeline) consumes ONLY --ui-timeline-* ════════════════ */
|
|
24
|
+
|
|
25
|
+
@scope (ui-timeline) {
|
|
26
|
+
:scope {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
/* min-inline-size floor — the ui-slider whole-shape lesson: a bare/unsized host must not collapse to a
|
|
30
|
+
0-width sliver (SPEC-R14 AC1). Above the floor, width stays the layout's job. */
|
|
31
|
+
min-inline-size: var(--ui-timeline-min-inline-size);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { UIContainerElement, type ReactiveProps } from '../../dom/index.js';
|
|
2
|
+
import '../timeline-item/timeline-item.js';
|
|
3
|
+
declare const props: {
|
|
4
|
+
size: {
|
|
5
|
+
reflect: true;
|
|
6
|
+
type: import("../../index.js").PropType<"sm" | "md" | "lg">;
|
|
7
|
+
default: "sm" | "md" | "lg";
|
|
8
|
+
attribute?: string | false;
|
|
9
|
+
};
|
|
10
|
+
label: {
|
|
11
|
+
reflect: true;
|
|
12
|
+
type: import("../../index.js").PropType<string>;
|
|
13
|
+
default: string;
|
|
14
|
+
attribute?: string | false;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export interface UITimelineElement extends ReactiveProps<typeof props> {
|
|
18
|
+
}
|
|
19
|
+
export declare class UITimelineElement extends UIContainerElement {
|
|
20
|
+
#private;
|
|
21
|
+
static props: {
|
|
22
|
+
size: {
|
|
23
|
+
reflect: true;
|
|
24
|
+
type: import("../../index.js").PropType<"sm" | "md" | "lg">;
|
|
25
|
+
default: "sm" | "md" | "lg";
|
|
26
|
+
attribute?: string | false;
|
|
27
|
+
};
|
|
28
|
+
label: {
|
|
29
|
+
reflect: true;
|
|
30
|
+
type: import("../../index.js").PropType<string>;
|
|
31
|
+
default: string;
|
|
32
|
+
attribute?: string | false;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
constructor();
|
|
36
|
+
protected connected(): void;
|
|
37
|
+
protected disconnected(): void;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// timeline.ts — UITimelineElement, the timeline family's DURABLE host (timeline-family.lld.md §3 ·
|
|
2
|
+
// SPEC-R6/R7 · ADR-0122 F1/F2/F6). BEHAVIOUR + props + the terminal-connector marking + self-define ONLY.
|
|
3
|
+
// Anatomy/geometry per the LLD; styling lives in timeline.css, the public contract in timeline.md.
|
|
4
|
+
//
|
|
5
|
+
// Authored-children ingress, DOM order, NO auto-sort (the adia rule) — the consumer's `ui-timeline-item`
|
|
6
|
+
// light-DOM children are the chronology, read back in the order authored. `internals.role = 'list'` (the
|
|
7
|
+
// `ui-list` precedent — list.ts:50), never a host `role` attribute; items are `role="listitem"` (their
|
|
8
|
+
// own contract). STATIC — no imperative append/update/finalize API, no MutationObserver tail-follow, no
|
|
9
|
+
// live-region role: the negative control separating this from `ui-status-stream` (SPEC-R6 AC3). The ONE
|
|
10
|
+
// observer this host owns re-marks the terminal item's connector suppressed (`data-last`) whenever the
|
|
11
|
+
// child list changes — the `ui-toast-region` childList-observer precedent (toast-region.ts:55-57),
|
|
12
|
+
// repurposed for a STRUCTURAL fact (which item is last), not liveness.
|
|
13
|
+
//
|
|
14
|
+
// `controls → dom + controls/timeline-item/timeline-item.ts` — the allowed import direction (cross-folder
|
|
15
|
+
// sibling, the toast→button precedent direction).
|
|
16
|
+
import { UIContainerElement, prop } from "../../dom/index.js";
|
|
17
|
+
import "../timeline-item/timeline-item.js"; // registers the item child (the cross-folder sibling edge — the LLD's './timeline-item.ts' is a typo for this path, since timeline-item/ is a SIBLING folder per §1's layout, not nested under timeline/)
|
|
18
|
+
const SIZE = ['sm', 'md', 'lg'];
|
|
19
|
+
const props = {
|
|
20
|
+
size: { ...prop.enum(SIZE, 'md'), reflect: true }, // first-class geometry (F2); NO variant/data/orientation prop in v1
|
|
21
|
+
label: { ...prop.string(''), reflect: true }, // author accessible name → internals.ariaLabel (the toolbar.ts precedent)
|
|
22
|
+
};
|
|
23
|
+
export class UITimelineElement extends UIContainerElement {
|
|
24
|
+
static props = props; // deliberately does NOT spread surfaceProps/flexProps — a timeline owns no elevation axis
|
|
25
|
+
#observer = null;
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
this.internals.role = 'list'; // role VALUE = the ui-list precedent (list.ts:50); constructor PLACEMENT
|
|
29
|
+
// (semantics before insertion) = the toast precedent (toast.ts:68)
|
|
30
|
+
}
|
|
31
|
+
connected() {
|
|
32
|
+
this.effect(() => {
|
|
33
|
+
this.internals.ariaLabel = this.label === '' ? null : this.label;
|
|
34
|
+
});
|
|
35
|
+
this.#markLastItem(); // seed from any children already present (declarative markup)
|
|
36
|
+
this.#observer = new MutationObserver(() => this.#markLastItem());
|
|
37
|
+
this.#observer.observe(this, { childList: true });
|
|
38
|
+
}
|
|
39
|
+
disconnected() {
|
|
40
|
+
this.#observer?.disconnect();
|
|
41
|
+
this.#observer = null;
|
|
42
|
+
}
|
|
43
|
+
/** Clear `data-last` on every authored item, then set it on the last — the CSS suppresses that item's
|
|
44
|
+
* own terminal connector (SPEC-R6 AC2). Re-run on every childList mutation so a late-appended durable
|
|
45
|
+
* item re-marks the terminal correctly (the toast-region observer precedent). */
|
|
46
|
+
#markLastItem() {
|
|
47
|
+
const items = this.querySelectorAll(':scope > ui-timeline-item');
|
|
48
|
+
items.forEach((item, i) => item.toggleAttribute('data-last', i === items.length - 1));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!customElements.get('ui-timeline'))
|
|
52
|
+
customElements.define('ui-timeline', UITimelineElement);
|