@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,61 @@
|
|
|
1
|
+
/* sparkline.css — ui-sparkline, the Display-class series-shape mark (LLD-C3, chart-family.lld.md §2;
|
|
2
|
+
* SPEC-R2/R9/R10/R11; ADR-0107). Two sectioned blocks:
|
|
3
|
+
*
|
|
4
|
+
* [1] TOKEN BLOCK — `:where(ui-sparkline)` declares the mark geometry as `--ui-sparkline-*`:
|
|
5
|
+
* density-invariant px/em quantities (ADR-0107 cl.5) — no [size]/[scale] lookup (Display class,
|
|
6
|
+
* geometry.md; SPEC-R12: no control height, no h/2 law).
|
|
7
|
+
* [2] STYLES BLOCK — `@scope (ui-sparkline)` consumes ONLY `--ui-sparkline-*`.
|
|
8
|
+
*
|
|
9
|
+
* The sizing ruling (SPEC-R9 AC1): the host takes an EXPLICIT `inline-size` token default, not an
|
|
10
|
+
* `auto + min-floor` pair — `auto` is non-deterministic here (a percentage-sized SVG inside a shrink-to-fit
|
|
11
|
+
* flex item falls back to the replaced-element intrinsic, 300px). `8em x 1lh` paints identically everywhere
|
|
12
|
+
* (whole-shape law, test-the-whole-shape) and rides the ambient type context (the `ui-icon` `1em` posture,
|
|
13
|
+
* widened to a mark box) — the two tokens are the page-author override freedom (ADR-0102 Lane A).
|
|
14
|
+
*
|
|
15
|
+
* `overflow: visible` on the svg: with `vector-effect: non-scaling-stroke` the stroke is px-sized, so at
|
|
16
|
+
* y=0/100 half the stroke falls outside the viewBox; visible overflow shows it instead of clipping the
|
|
17
|
+
* series extremes flat.
|
|
18
|
+
*
|
|
19
|
+
* Forced colors: NO dedicated block (SPEC-R10) — the mark is `currentColor` stroke/fill, which resolves to
|
|
20
|
+
* the consuming context's forced ink (the `ui-icon` precedent); the area wash is that same ink at token
|
|
21
|
+
* opacity (opacity is not a color property — WHCM never flattens it).
|
|
22
|
+
*
|
|
23
|
+
* RTL: NO mirroring rule (SPEC-R11) — SVG viewBox coordinates are physical by design; the series reads
|
|
24
|
+
* left-to-right in both directions (chronology is data order, not a bidi concern). Documented in sparkline.md.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-sparkline) declares --ui-sparkline-* ════════════════ */
|
|
28
|
+
|
|
29
|
+
:where(ui-sparkline) {
|
|
30
|
+
--ui-sparkline-inline-size: 8em; /* the deterministic default box (see the sizing ruling above) */
|
|
31
|
+
--ui-sparkline-block-size: 1lh; /* rides the ambient line box — inline-in-text fits by construction */
|
|
32
|
+
--ui-sparkline-stroke-width: 1.5px; /* density-invariant px (ADR-0107 cl.5) */
|
|
33
|
+
--ui-sparkline-area-opacity: 0.15;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-sparkline) consumes ONLY --ui-sparkline-* ════════════════ */
|
|
37
|
+
|
|
38
|
+
@scope (ui-sparkline) {
|
|
39
|
+
:scope {
|
|
40
|
+
display: inline-block;
|
|
41
|
+
inline-size: var(--ui-sparkline-inline-size);
|
|
42
|
+
block-size: var(--ui-sparkline-block-size);
|
|
43
|
+
vertical-align: text-bottom;
|
|
44
|
+
color: inherit;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
:scope svg {
|
|
48
|
+
display: block;
|
|
49
|
+
inline-size: 100%;
|
|
50
|
+
block-size: 100%;
|
|
51
|
+
overflow: visible;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
:scope svg [data-part='line'] {
|
|
55
|
+
stroke-width: var(--ui-sparkline-stroke-width);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
:scope svg [data-part='area'] {
|
|
59
|
+
opacity: var(--ui-sparkline-area-opacity);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { UIElement, type ReactiveProps } from '../../dom/index.js';
|
|
2
|
+
declare const props: {
|
|
3
|
+
values: import("../../index.js").PropConfig<number[]>;
|
|
4
|
+
label: {
|
|
5
|
+
reflect: true;
|
|
6
|
+
type: import("../../index.js").PropType<string>;
|
|
7
|
+
default: string;
|
|
8
|
+
attribute?: string | false;
|
|
9
|
+
};
|
|
10
|
+
variant: import("../../index.js").PropConfig<"line" | "area">;
|
|
11
|
+
};
|
|
12
|
+
export interface UISparklineElement extends ReactiveProps<typeof props> {
|
|
13
|
+
}
|
|
14
|
+
export declare class UISparklineElement extends UIElement {
|
|
15
|
+
static props: {
|
|
16
|
+
values: import("../../index.js").PropConfig<number[]>;
|
|
17
|
+
label: {
|
|
18
|
+
reflect: true;
|
|
19
|
+
type: import("../../index.js").PropType<string>;
|
|
20
|
+
default: string;
|
|
21
|
+
attribute?: string | false;
|
|
22
|
+
};
|
|
23
|
+
variant: import("../../index.js").PropConfig<"line" | "area">;
|
|
24
|
+
};
|
|
25
|
+
protected connected(): void;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// sparkline.ts — UISparklineElement, the Display-class series-shape mark (LLD-C2, chart-family.lld.md §2;
|
|
2
|
+
// SPEC-R1..R4; ADR-0107). Axis-free: no ticks, no legend, no interaction, no keyboard contract, no form
|
|
3
|
+
// participation. Light DOM; the only child is the component-built `<svg>` (the `ui-icon` imperative-
|
|
4
|
+
// injection idiom) — `render()` stays the inherited no-op.
|
|
5
|
+
//
|
|
6
|
+
// Three props, two connected() effects:
|
|
7
|
+
// • `values` (number[], the safe JSON codec from sparkline-math.ts) + `variant` ('line'|'area') drive the
|
|
8
|
+
// MARK effect: a whole-array swap rebuilds the <svg> via `replaceChildren` — no incremental patching.
|
|
9
|
+
// • `label` (accessible context) + `values` drive the ARIA effect: `internals.ariaLabel` is the SPEC-R4
|
|
10
|
+
// generated summary, recomputed on either input — never null, never aria-hidden (no silent state).
|
|
11
|
+
// `internals.role = 'img'` is a CONSTANT set directly in connected() (the list.ts `role=list` precedent) —
|
|
12
|
+
// it never varies, so it is not inside an effect.
|
|
13
|
+
//
|
|
14
|
+
// `cleanSeries` runs again at the render boundary (not just inside the codec): a PROPERTY write of garbage
|
|
15
|
+
// (e.g. `el.values = [1, null, 'x']`) never reaches the math either (SPEC-R3 AC2) — the codec only guards
|
|
16
|
+
// the ATTRIBUTE path.
|
|
17
|
+
import { UIElement, prop } from "../../dom/index.js";
|
|
18
|
+
import { cleanSeries, sparklineGeometry, sparklineSummary, sparklineValuesProp } from "./sparkline-math.js";
|
|
19
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
20
|
+
const props = {
|
|
21
|
+
values: sparklineValuesProp, // number[] · safe JSON codec (sparkline-math.ts)
|
|
22
|
+
label: { ...prop.string(''), reflect: true }, // accessible context (SPEC-R4), e.g. "Revenue trend"
|
|
23
|
+
variant: prop.enum(['line', 'area'], 'line'), // structural — enumType snaps unknowns to 'line'
|
|
24
|
+
};
|
|
25
|
+
export class UISparklineElement extends UIElement {
|
|
26
|
+
static props = props;
|
|
27
|
+
connected() {
|
|
28
|
+
// A chart is data, not decoration (ADR-0107 cl.4): role=img is CONSTANT — set directly, once, the
|
|
29
|
+
// list.ts `role=list` precedent — never through an effect (there is nothing to toggle it away).
|
|
30
|
+
this.internals.role = 'img';
|
|
31
|
+
// Mark effect (reads values, variant): rebuild the <svg> on any change. `g === null` (empty rendered
|
|
32
|
+
// set) clears the host — the box still paints via the CSS floors (SPEC-R9).
|
|
33
|
+
this.effect(() => {
|
|
34
|
+
const g = sparklineGeometry(cleanSeries(this.values));
|
|
35
|
+
if (g === null) {
|
|
36
|
+
this.replaceChildren();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const svg = document.createElementNS(SVG_NS, 'svg');
|
|
40
|
+
svg.setAttribute('viewBox', '0 0 100 100');
|
|
41
|
+
svg.setAttribute('preserveAspectRatio', 'none');
|
|
42
|
+
// aria-hidden on the SVG — the HOST carries role=img; the svg must never double-announce.
|
|
43
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
44
|
+
svg.setAttribute('focusable', 'false');
|
|
45
|
+
if (this.variant === 'area' && g.area !== null) {
|
|
46
|
+
const area = document.createElementNS(SVG_NS, 'polygon');
|
|
47
|
+
area.setAttribute('data-part', 'area');
|
|
48
|
+
area.setAttribute('points', g.area);
|
|
49
|
+
area.setAttribute('fill', 'currentColor');
|
|
50
|
+
area.setAttribute('stroke', 'none');
|
|
51
|
+
svg.append(area);
|
|
52
|
+
}
|
|
53
|
+
const line = document.createElementNS(SVG_NS, 'polyline');
|
|
54
|
+
line.setAttribute('data-part', 'line');
|
|
55
|
+
line.setAttribute('points', g.points);
|
|
56
|
+
line.setAttribute('fill', 'none');
|
|
57
|
+
line.setAttribute('stroke', 'currentColor');
|
|
58
|
+
line.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
59
|
+
line.setAttribute('stroke-linecap', 'round');
|
|
60
|
+
line.setAttribute('stroke-linejoin', 'round');
|
|
61
|
+
svg.append(line);
|
|
62
|
+
// One replaceChildren(svg) per change — whole-array swap semantics (SPEC-R2; no incremental
|
|
63
|
+
// patching of a 2-node tree).
|
|
64
|
+
this.replaceChildren(svg);
|
|
65
|
+
});
|
|
66
|
+
// ARIA effect (reads label, values): the generated accessible name — recomputed on either input.
|
|
67
|
+
// Never null, never aria-hidden (SPEC-R4 AC2: no silent state, with or without a label).
|
|
68
|
+
this.effect(() => {
|
|
69
|
+
this.internals.ariaLabel = sparklineSummary(this.label, sparklineGeometry(cleanSeries(this.values)));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!customElements.get('ui-sparkline'))
|
|
74
|
+
customElements.define('ui-sparkline', UISparklineElement); // idempotent self-define
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** A pane's [min, max] extent, in RATIO space (already resolved against the live container extent). */
|
|
2
|
+
export type Bounds = [number, number];
|
|
3
|
+
/**
|
|
4
|
+
* Two-neighbor local redistribution (SPEC-R2 §2, the core contract). Dragging/stepping separator `sepIndex`
|
|
5
|
+
* by `deltaRatio` grows pane `i = sepIndex` and shrinks pane `j = i + 1` by the SAME amount — sum-invariant
|
|
6
|
+
* by construction (adding `d` to one and subtracting `d` from the other never changes their sum) — clamped
|
|
7
|
+
* to the tightest of both neighbors' `[min, max]` windows. The residual (whatever `deltaRatio` couldn't fit)
|
|
8
|
+
* is DROPPED — push-through to non-adjacent panes is reserved (LLD §8 fork F5, not v1). Never throws: an
|
|
9
|
+
* out-of-range `sepIndex` (0/1-pane degenerate case, or a stale index) returns `ratios` unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export declare function redistribute(ratios: number[], sepIndex: number, deltaRatio: number, bounds: Bounds[]): number[];
|
|
12
|
+
/** Scale a ratio vector to sum to 1. A zero-sum, negative-sum, or non-finite vector (degenerate input)
|
|
13
|
+
* falls back to an EQUAL split rather than dividing by zero / propagating NaN. Empty stays empty. */
|
|
14
|
+
export declare function normalizeRatios(ratios: number[]): number[];
|
|
15
|
+
/**
|
|
16
|
+
* Reconcile a ratio vector to a target pane COUNT (SPEC-R2 AC4 — a `sizes` length mismatch; also the
|
|
17
|
+
* SHRINK leg of AC5's dynamic panes): extra entries are DROPPED (truncate), missing entries are EQUAL-
|
|
18
|
+
* FILLED, then the whole vector is normalized to sum 1 — so a removed pane's share is absorbed by the
|
|
19
|
+
* survivors automatically (normalizing after truncation redistributes proportionally). Never throws;
|
|
20
|
+
* `count <= 0` returns `[]`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function reconcileRatios(ratios: number[], count: number): number[];
|
|
23
|
+
/** A pane's declared `size` seed, or `null`/`undefined` when unset (the "equal-fill the rest" case). */
|
|
24
|
+
export type SizeSeed = number | null | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* The CONNECT-time seed (SPEC-R2 §2, uncontrolled mode): each pane with a declared, positive, finite
|
|
27
|
+
* `size` keeps it as its seed; panes with none equally split whatever ratio remains. The whole vector is
|
|
28
|
+
* then normalized to sum 1 regardless (so an over-declared set of sizes, e.g. summing > 1, still resolves
|
|
29
|
+
* to a valid ratio vector rather than an invalid one). Empty input ⇒ `[]`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function seedRatios(sizes: SizeSeed[]): number[];
|
|
32
|
+
/**
|
|
33
|
+
* Re-derive an UNCONTROLLED ratio vector when the pane COUNT changes after connect (SPEC-R2 AC5, the
|
|
34
|
+
* dynamic-panes contract — load-bearing: the app-tier consumers add/remove panes at runtime).
|
|
35
|
+
* • Shrink (`nextCount <= prevCount`) — delegates to `reconcileRatios` (truncate + renormalize; the
|
|
36
|
+
* survivors absorb the removed panes' share).
|
|
37
|
+
* • No prior panes (`prevCount === 0`) — a pure seed from the new full `sizes` vector.
|
|
38
|
+
* • Growth — the NEW panes (the tail of `sizes`, one per added slot) take their own declared `size` or
|
|
39
|
+
* an equal share of a carved-out "reserved" portion (declared-sum, or `addedCount/nextCount` when
|
|
40
|
+
* none declare — whichever is larger, capped at 95% so survivors never lose their entire share); the
|
|
41
|
+
* SURVIVING panes' ratios shrink proportionally (their RELATIVE proportions among themselves are
|
|
42
|
+
* preserved) to make room. Always normalizes to sum 1; never throws.
|
|
43
|
+
*/
|
|
44
|
+
export declare function rederiveRatios(prevRatios: number[], sizes: SizeSeed[]): number[];
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// constrain.ts — the pure N-pane ratio math for ui-split (LLD-C3, app-surfaces-m4.lld.md §2.3; SPEC-R2).
|
|
2
|
+
// DOM-free by construction (the value-codec / bar-math.ts precedent: the hard math isolated from the
|
|
3
|
+
// element so it is independently unit-testable and the element stays a thin DOM/ARIA/event adapter over
|
|
4
|
+
// it). Every function here operates purely on ratio vectors (numbers summing to ~1) and [min,max] ratio
|
|
5
|
+
// windows — resolving a CSS length (px/%/rem) to a ratio, and measuring the live container extent, is the
|
|
6
|
+
// ELEMENT's job (split.ts), which then calls in here with already-resolved ratio-space numbers.
|
|
7
|
+
//
|
|
8
|
+
// Four functions, each SPEC-R2-traceable:
|
|
9
|
+
// • redistribute — two-neighbor LOCAL resize (the core drag/keyboard-step math, SPEC-R2 §2 + AC1/AC2).
|
|
10
|
+
// • normalizeRatios — scale a vector to sum 1 (degenerate/zero-sum ⇒ equal-fill, never NaN/throw).
|
|
11
|
+
// • reconcileRatios — a `sizes` length MISMATCH repair: truncate extra, equal-fill missing, normalize
|
|
12
|
+
// (SPEC-R2 AC4 — the controlled-`sizes` path, and the shrink leg of dynamic panes AC5).
|
|
13
|
+
// • seedRatios — the CONNECT-time seed from each pane's own `size` prop (declared panes keep their
|
|
14
|
+
// seed; undeclared panes equally split what's left), normalized (SPEC-R2 §2 "uncontrolled ... seeded").
|
|
15
|
+
// • rederiveRatios — the GROWTH leg of dynamic panes (SPEC-R2 AC5): survivors shrink proportionally to
|
|
16
|
+
// make room; new panes take their own `size` seed or an equal share of the carved-out remainder.
|
|
17
|
+
/** Normalize a possibly-inverted bounds pair: `min > max` clamps to `[min, min]` (SPEC §2.3 "contradictory
|
|
18
|
+
* bounds ⇒ clamp to min" — the pane is pinned at its own min, never a negative-width window). */
|
|
19
|
+
function normalizeBounds([min, max]) {
|
|
20
|
+
return min > max ? [min, min] : [min, max];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Two-neighbor local redistribution (SPEC-R2 §2, the core contract). Dragging/stepping separator `sepIndex`
|
|
24
|
+
* by `deltaRatio` grows pane `i = sepIndex` and shrinks pane `j = i + 1` by the SAME amount — sum-invariant
|
|
25
|
+
* by construction (adding `d` to one and subtracting `d` from the other never changes their sum) — clamped
|
|
26
|
+
* to the tightest of both neighbors' `[min, max]` windows. The residual (whatever `deltaRatio` couldn't fit)
|
|
27
|
+
* is DROPPED — push-through to non-adjacent panes is reserved (LLD §8 fork F5, not v1). Never throws: an
|
|
28
|
+
* out-of-range `sepIndex` (0/1-pane degenerate case, or a stale index) returns `ratios` unchanged.
|
|
29
|
+
*/
|
|
30
|
+
export function redistribute(ratios, sepIndex, deltaRatio, bounds) {
|
|
31
|
+
const i = sepIndex;
|
|
32
|
+
const j = i + 1;
|
|
33
|
+
if (!Number.isInteger(i) || i < 0 || j >= ratios.length)
|
|
34
|
+
return ratios.slice();
|
|
35
|
+
const [minI, maxI] = normalizeBounds(bounds[i] ?? [0, 1]);
|
|
36
|
+
const [minJ, maxJ] = normalizeBounds(bounds[j] ?? [0, 1]);
|
|
37
|
+
// The achievable delta window: pane i's own [min,max] bounds it directly; pane j's [min,max] bounds it
|
|
38
|
+
// INVERTED (j SHRINKS as delta grows). The combined window is the intersection of both — the tightest
|
|
39
|
+
// constraint wins, and a delta outside it clamps to the nearest edge (the residual silently drops).
|
|
40
|
+
const lo = Math.max(minI - ratios[i], ratios[j] - maxJ);
|
|
41
|
+
const hi = Math.min(maxI - ratios[i], ratios[j] - minJ);
|
|
42
|
+
const clamped = lo > hi ? 0 : Math.max(lo, Math.min(hi, deltaRatio)); // lo>hi: a degenerate/contradictory window — hold in place
|
|
43
|
+
const next = ratios.slice();
|
|
44
|
+
next[i] = ratios[i] + clamped;
|
|
45
|
+
next[j] = ratios[j] - clamped;
|
|
46
|
+
return next;
|
|
47
|
+
}
|
|
48
|
+
/** Scale a ratio vector to sum to 1. A zero-sum, negative-sum, or non-finite vector (degenerate input)
|
|
49
|
+
* falls back to an EQUAL split rather than dividing by zero / propagating NaN. Empty stays empty. */
|
|
50
|
+
export function normalizeRatios(ratios) {
|
|
51
|
+
const n = ratios.length;
|
|
52
|
+
if (n === 0)
|
|
53
|
+
return [];
|
|
54
|
+
const sum = ratios.reduce((a, b) => a + b, 0);
|
|
55
|
+
if (!Number.isFinite(sum) || sum <= 0)
|
|
56
|
+
return ratios.map(() => 1 / n);
|
|
57
|
+
return ratios.map((r) => r / sum);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Reconcile a ratio vector to a target pane COUNT (SPEC-R2 AC4 — a `sizes` length mismatch; also the
|
|
61
|
+
* SHRINK leg of AC5's dynamic panes): extra entries are DROPPED (truncate), missing entries are EQUAL-
|
|
62
|
+
* FILLED, then the whole vector is normalized to sum 1 — so a removed pane's share is absorbed by the
|
|
63
|
+
* survivors automatically (normalizing after truncation redistributes proportionally). Never throws;
|
|
64
|
+
* `count <= 0` returns `[]`.
|
|
65
|
+
*/
|
|
66
|
+
export function reconcileRatios(ratios, count) {
|
|
67
|
+
if (count <= 0)
|
|
68
|
+
return [];
|
|
69
|
+
const next = ratios.slice(0, count);
|
|
70
|
+
while (next.length < count)
|
|
71
|
+
next.push(0);
|
|
72
|
+
if (next.every((r) => r === 0))
|
|
73
|
+
return next.map(() => 1 / count);
|
|
74
|
+
return normalizeRatios(next);
|
|
75
|
+
}
|
|
76
|
+
const isDeclared = (s) => typeof s === 'number' && Number.isFinite(s) && s > 0;
|
|
77
|
+
/**
|
|
78
|
+
* The CONNECT-time seed (SPEC-R2 §2, uncontrolled mode): each pane with a declared, positive, finite
|
|
79
|
+
* `size` keeps it as its seed; panes with none equally split whatever ratio remains. The whole vector is
|
|
80
|
+
* then normalized to sum 1 regardless (so an over-declared set of sizes, e.g. summing > 1, still resolves
|
|
81
|
+
* to a valid ratio vector rather than an invalid one). Empty input ⇒ `[]`.
|
|
82
|
+
*/
|
|
83
|
+
export function seedRatios(sizes) {
|
|
84
|
+
const n = sizes.length;
|
|
85
|
+
if (n === 0)
|
|
86
|
+
return [];
|
|
87
|
+
const declaredSum = sizes.reduce((a, s) => a + (isDeclared(s) ? s : 0), 0);
|
|
88
|
+
const undeclaredCount = sizes.filter((s) => !isDeclared(s)).length;
|
|
89
|
+
if (undeclaredCount === n)
|
|
90
|
+
return sizes.map(() => 1 / n); // nobody declared a size — equal split
|
|
91
|
+
const remaining = Math.max(0, 1 - Math.min(1, declaredSum));
|
|
92
|
+
const equalShare = undeclaredCount > 0 ? remaining / undeclaredCount : 0;
|
|
93
|
+
return normalizeRatios(sizes.map((s) => (isDeclared(s) ? s : equalShare)));
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Re-derive an UNCONTROLLED ratio vector when the pane COUNT changes after connect (SPEC-R2 AC5, the
|
|
97
|
+
* dynamic-panes contract — load-bearing: the app-tier consumers add/remove panes at runtime).
|
|
98
|
+
* • Shrink (`nextCount <= prevCount`) — delegates to `reconcileRatios` (truncate + renormalize; the
|
|
99
|
+
* survivors absorb the removed panes' share).
|
|
100
|
+
* • No prior panes (`prevCount === 0`) — a pure seed from the new full `sizes` vector.
|
|
101
|
+
* • Growth — the NEW panes (the tail of `sizes`, one per added slot) take their own declared `size` or
|
|
102
|
+
* an equal share of a carved-out "reserved" portion (declared-sum, or `addedCount/nextCount` when
|
|
103
|
+
* none declare — whichever is larger, capped at 95% so survivors never lose their entire share); the
|
|
104
|
+
* SURVIVING panes' ratios shrink proportionally (their RELATIVE proportions among themselves are
|
|
105
|
+
* preserved) to make room. Always normalizes to sum 1; never throws.
|
|
106
|
+
*/
|
|
107
|
+
export function rederiveRatios(prevRatios, sizes) {
|
|
108
|
+
const prevCount = prevRatios.length;
|
|
109
|
+
const nextCount = sizes.length;
|
|
110
|
+
if (nextCount === 0)
|
|
111
|
+
return [];
|
|
112
|
+
if (nextCount <= prevCount)
|
|
113
|
+
return reconcileRatios(prevRatios, nextCount);
|
|
114
|
+
if (prevCount === 0)
|
|
115
|
+
return seedRatios(sizes);
|
|
116
|
+
const addedSizes = sizes.slice(prevCount);
|
|
117
|
+
const addedDeclaredSum = addedSizes.reduce((a, s) => a + (isDeclared(s) ? s : 0), 0);
|
|
118
|
+
const addedUndeclaredCount = addedSizes.filter((s) => !isDeclared(s)).length;
|
|
119
|
+
const equalShareGuess = addedSizes.length / nextCount;
|
|
120
|
+
const reservedTotal = Math.min(0.95, Math.max(addedDeclaredSum, equalShareGuess));
|
|
121
|
+
const survivorScale = 1 - reservedTotal;
|
|
122
|
+
const survivors = prevRatios.map((r) => r * survivorScale);
|
|
123
|
+
const addedEqualShare = addedUndeclaredCount > 0 ? Math.max(0, reservedTotal - addedDeclaredSum) / addedUndeclaredCount : 0;
|
|
124
|
+
const added = addedSizes.map((s) => (isDeclared(s) ? s : addedEqualShare));
|
|
125
|
+
return normalizeRatios([...survivors, ...added]);
|
|
126
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* split-pane.css — ui-split-pane, the generic pane child of ui-split (ADR-0003 single-file convention).
|
|
2
|
+
* Two sectioned blocks:
|
|
3
|
+
*
|
|
4
|
+
* [1] TOKEN BLOCK — `:where(ui-split-pane)` declares ONLY `--ui-split-pane-*`: the bare-pane floor
|
|
5
|
+
* (`--ui-split-pane-min`, the LLD's "min unset ⇒ this floor" default, SPEC-R2 §2). Owned HERE (not
|
|
6
|
+
* on `ui-split`) because it textually IS this control's own token (`--ui-split-pane-{...}`) even
|
|
7
|
+
* though it's consumed via the PARENT-written geometry seam below — the same token-ownership law any
|
|
8
|
+
* `--ui-{name}-*` prefix earns regardless of which element's CSS ends up reading it.
|
|
9
|
+
* [2] STYLES BLOCK — `@scope (ui-split-pane)` is AXIS-OBLIQUE by construction: `ui-split` (the parent)
|
|
10
|
+
* writes three imperative custom properties per pane — `--_pane-flex` (a unitless flex-grow ratio),
|
|
11
|
+
* `--_pane-min` / `--_pane-max` (the resolved CSS-length floor/ceiling) — PLUS a `[data-axis-vertical]`
|
|
12
|
+
* presence marker mirroring its own `axis` prop (the slider-multi `--value-pct-lo/-hi` JS-geometry-seam
|
|
13
|
+
* precedent: the parent computes, the child's CSS purely consumes). Physical `width`/`height`
|
|
14
|
+
* properties (never logical `inline-size`) are used deliberately — `axis` selects a PHYSICAL
|
|
15
|
+
* dimension (which the flex row/column lays out), NOT a writing-mode-relative one; RTL only inverts
|
|
16
|
+
* the DRAG SENSE within the horizontal axis (SPEC-R3 AC3), it never changes which physical dimension
|
|
17
|
+
* is constrained.
|
|
18
|
+
*
|
|
19
|
+
* NO control height (geometry.md's Container/layout-adjacent size-class): a pane has no frame of its own —
|
|
20
|
+
* `overflow: auto` lets whatever CONTENT the author places (a `ui-card`, a nav tree, prose) manage its own
|
|
21
|
+
* scrolling; the pane itself is a pure flex-distribution box.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-split-pane) declares --ui-split-pane-* ════════════════ */
|
|
25
|
+
|
|
26
|
+
:where(ui-split-pane) {
|
|
27
|
+
/* The bare-pane floor (SPEC-R2 §2 "default min = a floor"). A small, sane minimum so an unset-`min`
|
|
28
|
+
pane never collapses to zero-width during a hard drag/clamp — the entry-control `min-inline-size`
|
|
29
|
+
floor's spirit (geometry.md), sized for a generic content region rather than a typing surface. */
|
|
30
|
+
--ui-split-pane-min: 4rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-split-pane) consumes the parent-written seam ═══════ */
|
|
34
|
+
|
|
35
|
+
@scope (ui-split-pane) {
|
|
36
|
+
:scope {
|
|
37
|
+
display: block;
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
overflow: auto; /* the pane's own content manages its scroll; the pane itself never clips silently */
|
|
40
|
+
/* Consumer-INHERITED scrollbar-visibility seam (the ui-container-bg seam shape, NOT a token-block
|
|
41
|
+
declaration): a composing surface hides the pane scroller for an app-chrome look by setting
|
|
42
|
+
--ui-split-pane-scrollbar-width on ITSELF (ui-agent-admin sets `none`; the value inherits down to
|
|
43
|
+
every descendant pane). Deliberately NOT declared in this file's :where() block — an own
|
|
44
|
+
declaration would BEAT the consumer's inherited value (the TKT-0065 alias-resolution lesson's
|
|
45
|
+
sibling), so the default rides the var() fallback. Scrolling stays live; scrollbar-width is
|
|
46
|
+
supported by both shipped engines (Chromium 121+/Safari 18.2+), no ::-webkit-scrollbar leg. */
|
|
47
|
+
scrollbar-width: var(--ui-split-pane-scrollbar-width, auto);
|
|
48
|
+
|
|
49
|
+
/* horizontal axis (default) — width is the constrained dimension */
|
|
50
|
+
flex: var(--_pane-flex, 1) 0 0%;
|
|
51
|
+
min-width: var(--_pane-min, var(--ui-split-pane-min));
|
|
52
|
+
max-width: var(--_pane-max, none);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* vertical axis — the parent mirrors `axis="vertical"` onto each pane via [data-axis-vertical]; height
|
|
56
|
+
becomes the constrained dimension instead (the min-width floor above is released to auto). */
|
|
57
|
+
:scope[data-axis-vertical] {
|
|
58
|
+
min-width: auto;
|
|
59
|
+
max-width: none;
|
|
60
|
+
min-height: var(--_pane-min, var(--ui-split-pane-min));
|
|
61
|
+
max-height: var(--_pane-max, none);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { UIContainerElement } from '../../dom/container.js';
|
|
2
|
+
import { type ReactiveProps } from '../../dom/index.js';
|
|
3
|
+
declare const splitPaneProps: {
|
|
4
|
+
initial: import("../../index.js").PropConfig<number | null>;
|
|
5
|
+
min: {
|
|
6
|
+
reflect: true;
|
|
7
|
+
type: import("../../index.js").PropType<string>;
|
|
8
|
+
default: string;
|
|
9
|
+
attribute?: string | false;
|
|
10
|
+
};
|
|
11
|
+
max: {
|
|
12
|
+
reflect: true;
|
|
13
|
+
type: import("../../index.js").PropType<string>;
|
|
14
|
+
default: string;
|
|
15
|
+
attribute?: string | false;
|
|
16
|
+
};
|
|
17
|
+
collapsible: {
|
|
18
|
+
reflect: true;
|
|
19
|
+
type: import("../../index.js").PropType<boolean>;
|
|
20
|
+
default: boolean;
|
|
21
|
+
attribute?: string | false;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export interface UISplitPaneElement extends ReactiveProps<typeof splitPaneProps> {
|
|
25
|
+
}
|
|
26
|
+
export declare class UISplitPaneElement extends UIContainerElement {
|
|
27
|
+
static props: {
|
|
28
|
+
initial: import("../../index.js").PropConfig<number | null>;
|
|
29
|
+
min: {
|
|
30
|
+
reflect: true;
|
|
31
|
+
type: import("../../index.js").PropType<string>;
|
|
32
|
+
default: string;
|
|
33
|
+
attribute?: string | false;
|
|
34
|
+
};
|
|
35
|
+
max: {
|
|
36
|
+
reflect: true;
|
|
37
|
+
type: import("../../index.js").PropType<string>;
|
|
38
|
+
default: string;
|
|
39
|
+
attribute?: string | false;
|
|
40
|
+
};
|
|
41
|
+
collapsible: {
|
|
42
|
+
reflect: true;
|
|
43
|
+
type: import("../../index.js").PropType<boolean>;
|
|
44
|
+
default: boolean;
|
|
45
|
+
attribute?: string | false;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// split-pane.ts — UISplitPaneElement, the generic pane child of `ui-split` (app-surfaces-m4.lld.md LLD-C1,
|
|
2
|
+
// SPEC-R1/R2). A structural, non-form, non-interactive content region — the `ui-card-header`/`-content`
|
|
3
|
+
// regions precedent (extends `UIContainerElement` for the family base; carries no ARIA of its own; the
|
|
4
|
+
// AUTHOR's light-DOM children are the content, `render()` stays the inherited void). One folder, TWO real
|
|
5
|
+
// components (the `radio`/`radio-group` precedent, ADR-0080's radio-group special case): `ui-split-pane`
|
|
6
|
+
// gets its OWN `.ts`/`.css`/`.md` triple rather than being a side-effect-imported sub-element of split.ts —
|
|
7
|
+
// each is independently fleet-discoverable (family-coherence.test.ts keys discovery off `{name}.md`), and
|
|
8
|
+
// `ui-split-pane`'s per-pane props (`initial`/`min`/`max`/`collapsible`) earn their own descriptor + contract
|
|
9
|
+
// trip-wire (LLD-C6) exactly as `radio-group.md` does inside `radio/`.
|
|
10
|
+
//
|
|
11
|
+
// Props (LLD §2.1's code sample names this `size`; RENAMED here to `initial` — `family-coherence.test.ts`'s
|
|
12
|
+
// A2 invariant treats ANY attribute literally named `size` fleet-wide as the `[sm,md,lg]` sizing enum
|
|
13
|
+
// (ADR-0081), and this prop is a semantically UNRELATED ratio seed, not a widget-box size step. Same value,
|
|
14
|
+
// same behavior, a build-mechanics naming fix — not a design deviation):
|
|
15
|
+
// • `initial` — an UNREFLECTED optional ratio seed (`prop.number()`, default null) — read ONCE by the
|
|
16
|
+
// parent `ui-split` at connect / pane-count growth (`constrain.ts`'s `seedRatios`/`rederiveRatios`),
|
|
17
|
+
// never reflected (an internal seed, not a live authored dimension — the progress.md/stat.md optional-
|
|
18
|
+
// number precedent).
|
|
19
|
+
// • `min`/`max` — REFLECTED CSS-length strings (default `''` ⇒ the parent applies the
|
|
20
|
+
// `--ui-split-pane-min` floor / `none`). The PARENT (ui-split) resolves these into physical
|
|
21
|
+
// `min-width`/`max-width` (horizontal axis) or `min-height`/`max-height` (vertical axis) via the
|
|
22
|
+
// `--_pane-min`/`--_pane-max` JS geometry seam it writes onto each pane (the slider-multi
|
|
23
|
+
// `--value-pct-lo/hi` precedent) — split-pane.css is axis-oblique by construction; the parent decides
|
|
24
|
+
// which physical dimension the pane's floor/ceiling binds to.
|
|
25
|
+
// • `collapsible` — a REFLECTED boolean gating the parent's Enter-key collapse-to-last affordance
|
|
26
|
+
// (SPEC-R4) when this pane is a separator's LEADING pane. `ui-split-pane` itself does nothing with it —
|
|
27
|
+
// read only by the parent.
|
|
28
|
+
//
|
|
29
|
+
// `controls → dom` is the allowed import direction (no traits needed — this leaf has no behaviour).
|
|
30
|
+
import { UIContainerElement } from "../../dom/container.js";
|
|
31
|
+
import { prop } from "../../dom/index.js";
|
|
32
|
+
const splitPaneProps = {
|
|
33
|
+
initial: prop.number(), // undefined ratio seed (uncontrolled) — a positive finite value; unset/invalid ⇒ equal-fill (constrain.ts)
|
|
34
|
+
min: { ...prop.string(''), reflect: true }, // CSS length; '' ⇒ the --ui-split-pane-min floor (parent-applied)
|
|
35
|
+
max: { ...prop.string(''), reflect: true }, // CSS length; '' ⇒ unbounded (parent-applied)
|
|
36
|
+
collapsible: { ...prop.boolean(false), reflect: true }, // gates the parent's Enter-key collapse-to-last (SPEC-R4)
|
|
37
|
+
};
|
|
38
|
+
export class UISplitPaneElement extends UIContainerElement {
|
|
39
|
+
static props = splitPaneProps;
|
|
40
|
+
}
|
|
41
|
+
if (!customElements.get('ui-split-pane'))
|
|
42
|
+
customElements.define('ui-split-pane', UISplitPaneElement);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/* split.css — ui-split, the single-file component stylesheet (ADR-0003). Two sectioned blocks:
|
|
2
|
+
*
|
|
3
|
+
* [1] TOKEN BLOCK — `:where(ui-split)` declares ONLY `--ui-split-*`: the divider thickness, the
|
|
4
|
+
* invisible hit-slop width, the keyboard step ratio, and the divider ink role. Geometry is
|
|
5
|
+
* Container/layout size-class (geometry.md "the five size-classes"): NO control height, no
|
|
6
|
+
* `--md-sys-height-*` — the divider + hit-slop are layout dimensions off the space/dimension scale.
|
|
7
|
+
* [2] STYLES BLOCK — `@scope (ui-split)` consumes ONLY `--ui-split-*` for the host + the
|
|
8
|
+
* control-rendered `[data-separator]` divider (a light-DOM `<div>`, not its own custom element —
|
|
9
|
+
* styled here via a descendant selector, the `bar-chart.css` `[data-part]` precedent). The pane
|
|
10
|
+
* CHILDREN's own geometry (flex-grow ratio, min/max clamp) lives in split-pane.css, consuming the
|
|
11
|
+
* `--_pane-*` seam this control's `.ts` writes onto each pane (JS geometry seam, never a raw layout
|
|
12
|
+
* style property — the slider-multi precedent).
|
|
13
|
+
*
|
|
14
|
+
* RTL: the axis mapping is PHYSICAL (`flex-direction: row`/`column`, [axis=vertical] repoints it) — under
|
|
15
|
+
* `direction: rtl`, `flex-direction: row` visually reverses item order NATIVELY (no bespoke RTL layout rule
|
|
16
|
+
* needed here); only the JS DRAG SENSE inverts (traits/pane-resize.ts, SPEC-R3 AC3).
|
|
17
|
+
*
|
|
18
|
+
* Motion: NO transitions are declared anywhere in this sheet — a resize (pointer or keyboard) is always
|
|
19
|
+
* instant (the geometry-law "a [scale]/[size]/[density] change must be instant" + the slider "no position
|
|
20
|
+
* transition" precedent), so `prefers-reduced-motion` has nothing to gate here (vacuously satisfied). TKT-0015
|
|
21
|
+
* traced the reported "animation" to split.ts's drag MATH (a since-press delta double-applied against the
|
|
22
|
+
* live ratio vector, compounding every pointermove) — never a CSS transition; this claim needed no repair.
|
|
23
|
+
*
|
|
24
|
+
* Selection (TKT-0015 pt.2): the `dragging` custom state suspends `user-select` across the WHOLE host (not
|
|
25
|
+
* just the separator, which already carries its own `user-select: none` below) while a drag is active, so a
|
|
26
|
+
* fast sweep never selects pane content underneath the pointer. The state is set/cleared ENTIRELY in split.ts
|
|
27
|
+
* around the drag's live-move↔commit span — never an authored attribute (button.css/combo-box.css's own
|
|
28
|
+
* internal, non-authorable custom-state motion/validity gates are the same shape). Host-level scope is
|
|
29
|
+
* sufficient — `setPointerCapture` on the separator keeps every `pointermove` routed to it regardless of
|
|
30
|
+
* where the cursor sweeps (SPEC-R3 AC2 capture-continuity), so a `document`-level rule buys nothing extra.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-split) declares --ui-split-* ════════════════ */
|
|
34
|
+
|
|
35
|
+
:where(ui-split) {
|
|
36
|
+
--ui-split-divider: 1px; /* the visible divider thickness */
|
|
37
|
+
--ui-split-hit-slop: 24px; /* the invisible pointer hit target (WCAG 2.5.8, SPEC-R5 AC1) */
|
|
38
|
+
--ui-split-key-step: 5%; /* the keyboard Arrow step, as a PERCENTAGE of the two-neighbor pair (SPEC-R4) */
|
|
39
|
+
/* Kim-directed (2026-07-16): a resting DECORATIVE outline, not the solid ADR-0059 track role — the
|
|
40
|
+
divider is a layout boundary/drag handle, not a state-bearing part carrying a value a user must
|
|
41
|
+
perceive (unlike the switch off-track / slider rail ADR-0059 actually gates); its operability
|
|
42
|
+
already comes from the pointer cursor + the ≥24px hit-slop (SPEC-R5 AC1) + `role=separator`
|
|
43
|
+
regardless of visual weight, so the softer translucent role is legal here. Primary on hover/drag
|
|
44
|
+
is the clear "you're interacting with this" cue. */
|
|
45
|
+
--ui-split-divider-ink: var(--md-sys-color-neutral-outline-variant);
|
|
46
|
+
--ui-split-divider-ink-hover: var(--md-sys-color-primary);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-split) consumes ONLY --ui-split-* ════════════════ */
|
|
50
|
+
|
|
51
|
+
@scope (ui-split) {
|
|
52
|
+
:scope {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: row; /* the horizontal identity — [axis=vertical] repoints it below */
|
|
55
|
+
align-items: stretch;
|
|
56
|
+
box-sizing: border-box;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
:scope[axis='vertical'] {
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Selection suspension during an active drag (TKT-0015 pt.2) — see the file header for the full rationale. */
|
|
64
|
+
:scope:state(dragging) {
|
|
65
|
+
-webkit-user-select: none;
|
|
66
|
+
user-select: none;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* The separator — a control-rendered `<div data-separator role="separator" tabindex="0">` between each
|
|
70
|
+
adjacent pane pair (never authored — the slider-thumb precedent). Fixed-size flex item (never grows/
|
|
71
|
+
shrinks): its OWN box is the thin `--ui-split-divider`; the `::before` below expands the INVISIBLE
|
|
72
|
+
pointer hit area without displacing the pane tracks (absolutely positioned, zero layout contribution). */
|
|
73
|
+
[data-separator] {
|
|
74
|
+
position: relative;
|
|
75
|
+
flex: 0 0 var(--ui-split-divider);
|
|
76
|
+
background: var(--ui-split-divider-ink);
|
|
77
|
+
cursor: col-resize;
|
|
78
|
+
touch-action: none; /* give pointer events exclusively to the drag gesture, no scroll-conflict */
|
|
79
|
+
-webkit-user-select: none;
|
|
80
|
+
user-select: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
:scope[axis='vertical'] [data-separator] {
|
|
84
|
+
cursor: row-resize;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Hover repoints to the primary cue; `:state(dragging)` (set host-wide in split.ts around the drag's
|
|
88
|
+
live-move↔commit span, see the file header) keeps the SAME cue live for the whole gesture even after
|
|
89
|
+
the pointer sweeps off the thin divider box — `setPointerCapture` keeps the drag routed to this
|
|
90
|
+
element regardless, but a plain `:hover` alone would drop the highlight the moment the cursor left
|
|
91
|
+
the 1px track, which reads as the divider "letting go" mid-drag. */
|
|
92
|
+
[data-separator]:hover,
|
|
93
|
+
:scope:state(dragging) [data-separator] {
|
|
94
|
+
background: var(--ui-split-divider-ink-hover);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* The hit-slop — an invisible `::before`, centred on the divider, expanding the pointer-interactive area
|
|
98
|
+
to `--ui-split-hit-slop` on the CROSS axis (SPEC-R5 AC1: ≥24px even when the visible divider is 1-2px).
|
|
99
|
+
`position: absolute` means it contributes NOTHING to flex sizing — the pane tracks are never displaced. */
|
|
100
|
+
[data-separator]::before {
|
|
101
|
+
content: '';
|
|
102
|
+
position: absolute;
|
|
103
|
+
inset-block: 0;
|
|
104
|
+
inset-inline: calc((var(--ui-split-divider) - var(--ui-split-hit-slop)) / 2);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
:scope[axis='vertical'] [data-separator]::before {
|
|
108
|
+
inset-inline: 0;
|
|
109
|
+
inset-block: calc((var(--ui-split-divider) - var(--ui-split-hit-slop)) / 2);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Focus ring (ADR-0009) — the shared fleet ring on the focused separator (keyboard-only). */
|
|
113
|
+
[data-separator]:focus-visible {
|
|
114
|
+
outline: var(--md-sys-state-focus-ring-width) solid var(--md-sys-color-focus-ring);
|
|
115
|
+
outline-offset: var(--md-sys-state-focus-ring-offset);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Forced-colors — the divider stays a real, visible system colour under `forced-colors: active` (SPEC-R5
|
|
119
|
+
AC2; the slider.css `forced-color-adjust: none` + explicit system-colour precedent). The focus ring is
|
|
120
|
+
already free via `--md-sys-color-focus-ring`'s own `→ Highlight` token-layer mapping (ADR-0009). */
|
|
121
|
+
@media (forced-colors: active) {
|
|
122
|
+
[data-separator] {
|
|
123
|
+
background: ButtonText;
|
|
124
|
+
forced-color-adjust: none;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|