@cssints/headless 0.5.2 → 0.6.1

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/README.md CHANGED
@@ -65,7 +65,7 @@ A primitive that has a seam honours an optional `prefs` entry (a `Partial<Prefs>
65
65
  - **menu / menubar** — `typeaheadDelayMs` (the typeahead buffer timeout).
66
66
  - **tooltip** — `tooltipShowDelayMs` / `tooltipHideDelayMs`, exposed as `showDelayMs()` / `hideDelayMs()` for the host to wire its `setTimeout` from (the spec keeps timers as events).
67
67
 
68
- Everything else **ignores** prefs: accordion, alert, alertdialog, breadcrumb, button, carousel, checkbox, checkbox-group, combobox, dialog-modal, disclosure, feed, grid, link, listbox, meter, player, radio, slider, slider-multithumb, spinbutton, switch, tabs, timeline, toolbar, treegrid, treeview, windowsplitter — they expose no timing / motion / live-region / camera seam. `listbox` / `treeview` / `timeline` typeahead timeouts are host-driven via their reset methods, so they take no delay here either.
68
+ Everything else **ignores** prefs: accordion, alert, alertdialog, breadcrumb, button, carousel, checkbox, checkbox-group, combobox, dialog-modal, disclosure, feed, grid, link, listbox, meter, player, radio, resizable-panels, slider, slider-multithumb, spinbutton, switch, tabs, timeline, toolbar, treegrid, treeview, windowsplitter — they expose no timing / motion / live-region / camera seam. `listbox` / `treeview` / `timeline` typeahead timeouts are host-driven via their reset methods, so they take no delay here either.
69
69
 
70
70
  `motionDurationMs` is **not consumed**: canvas tweens are tick-driven, not millisecond-driven, and the look layer owns durations. It stays in `Prefs` only so a host's preference record fits the type.
71
71
 
@@ -89,12 +89,46 @@ Membership, sizes and z-order are **not modelled** in `pointer.qnt` (its node se
89
89
 
90
90
  **Nested canvases** are two instances the host composes: a `#canvas` node holds a second `createCanvas`, and the inner viewport stops propagation of its own pointer, wheel and key events. The innermost canvas under the pointer owns the event, so a press or zoom inside it never starts an outer gesture or moves the outer camera. A nested canvas is a single tab stop in its parent.
91
91
 
92
+ ## Resizable panels
93
+
94
+ `createResizablePanels` is a group of N panes and N-1 separators: react-resizable-panels' layout on the APG window splitter's keyboard. Its behaviour is `packages/apg-specs/specs/patterns/resizable-panels.qnt` — not an APG pattern, so it lives under `patterns/` — and the DOM is checked against it by the differential walk. The spec is the three-pane instance (10/80 bounds, 10% steps, pane 0 collapsible at 0); the primitive takes N panes and any bounds.
95
+
96
+ **The contract.** Sizes are percentages of the group and always sum to 100. Each pane has `min` / `max`; a `collapsible` pane sits at `collapsedSize` (0) while collapsed. A move — an arrow, Home/End, a drag — changes only the two panes around the separator, clamped by both bounds. Collapsing hands the pane's space to the nearest pane that can take it and cascades outward; restoring takes it back in the same order, so a restore always returns the remembered size.
97
+
98
+ **The separator is the widget**: `role=separator`, `tabindex=0`, `aria-orientation`, `aria-valuenow` = the size of the pane it resizes, `aria-valuemin` / `-max` that pane's bounds, `aria-controls` it. Arrows move by `step` (10%), Home/End jump the pair to its bounds, Enter collapses and restores. A separator next to a collapsed pane is inert (only Enter restores).
99
+
100
+ **The drag is snapshot + delta**: pointerdown remembers the layout, every move re-solves from it, so dragging back past a clamp recovers exactly. It uses pointer capture and ends on pointerup / pointercancel; a host write of `sizes` ends it too. Keyboard input is a no-op while a drag runs.
101
+
102
+ ```tsx
103
+ const panels = createResizablePanels({
104
+ panes: [{ label: "Sidebar", collapsible: true }, { label: "Content" }, { label: "Inspector" }],
105
+ defaultSizes: [20, 40, 40],
106
+ storageKey: "app-layout",
107
+ onSizesChange: (sizes) => save(sizes), // ignored while uncontrolled
108
+ });
109
+
110
+ <div {...panels.groupProps} style={{ display: "flex" }}>
111
+ {[0, 1, 2].map((index) => (
112
+ <>
113
+ <div {...panels.paneProps(index)} style={{ flex: `${panels.sizes()[index]}%` }}>
114
+ …
115
+ </div>
116
+ {index < 2 && <div {...panels.separatorProps(index)} />}
117
+ </>
118
+ ))}
119
+ </div>;
120
+ ```
121
+
122
+ Controlled is `sizes` + `onSizesChange` (the request), like every other primitive. `storageKey` persists the layout in localStorage — host storage, so the key belongs to the host's namespace.
123
+
124
+ Left to the host: pane bounds in pixels (percent only), nested groups, and F6 (with three panes a Tab is enough). Not modelled: react-resizable-panels' collapse-on-drag threshold (a pane collapses on Enter or a host write, never by dragging through MIN) and restoring a pane that was mounted already collapsed (it returns to its own `min`).
125
+
92
126
  ## Commands
93
127
 
94
128
  ```sh
95
129
  cd packages/headless
96
130
  bun run test # vp test run: vitest browser mode, real Chromium (playwright; needs the browser installed: bunx playwright install chromium)
97
- bun run typecheck # tsc-rs
131
+ bun run typecheck # effect-tsrs
98
132
  bun run build # vp pack: dist/ (ESM and declarations; solid-js is a peer)
99
133
  ```
100
134
 
@@ -104,7 +138,7 @@ The specs, bridges and scenarios the tests replay are [`@cssints/apg-specs`](../
104
138
 
105
139
  ## Status
106
140
 
107
- Not on npm yet: the package is in the Changesets `ignore` list until it joins the `fixed` group (RELEASING.md). Its manifest follows the other packages (`development` condition on `src`, `dist` for consumers, `publishConfig.directory`).
141
+ Published with the Changesets `fixed` group (one version with the other packages, RELEASING.md). Its manifest follows the other packages (`development` condition on `src`, `dist` for consumers, `publishConfig.directory`).
108
142
 
109
143
  Solid 2 reminders: setters apply on the microtask flush (the DOM driver waits a frame after each step); pass props objects/getters, never destructure options.
110
144
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Accessor } from "solid-js";
2
- //#region src/canvas-math.d.ts
3
2
  /**
4
3
  * The fixed-point math of the canvas, copied from the Quint specs. Integers only, power-of-two
5
4
  * denominators, every intermediate below 2^53, so doubles are exact and the DOM implementation can
@@ -49,8 +48,6 @@ interface SnapContext extends SnapNode {
49
48
  type SnapCandidates = (context: SnapContext) => readonly number[];
50
49
  /** The default policy and the one `specs/canvas/pointer.qnt` models: every `GRID` units. */
51
50
  export declare const gridCandidates: SnapCandidates;
52
- //#endregion
53
- //#region src/prefs.d.ts
54
51
  /**
55
52
  * The user preferences a primitive honours. The host resolves them from
56
53
  * wherever it keeps them and injects a `Partial<Prefs>` per primitive;
@@ -74,8 +71,6 @@ export interface Prefs {
74
71
  export declare const DEFAULT_PREFS: Prefs;
75
72
  /** Merge an injected partial with the defaults; never mutates the input. */
76
73
  export declare const withPrefs: (partial?: Partial<Prefs>) => Prefs;
77
- //#endregion
78
- //#region src/canvas.d.ts
79
74
  /**
80
75
  * Headless infinite canvas: camera, selection and node-move state plus attribute / prop objects
81
76
  * for a viewport, a camera layer, the nodes and a live region. No elements, no styling beyond the
@@ -376,8 +371,6 @@ interface Canvas {
376
371
  readonly removeNodes: (ids: readonly string[]) => void;
377
372
  }
378
373
  export declare const createCanvas: (options: CanvasOptions) => Canvas;
379
- //#endregion
380
- //#region src/disclosure.d.ts
381
374
  /**
382
375
  * Options are read lazily: pass a component's `props` (or an object with
383
376
  * getters), never destructured values, so `disabled` stays reactive.
@@ -431,8 +424,6 @@ interface Disclosure {
431
424
  * pass `value` + `onValueChange` to own the state.
432
425
  */
433
426
  export declare const createDisclosure: (options?: DisclosureOptions) => Disclosure;
434
- //#endregion
435
- //#region src/player.d.ts
436
427
  /**
437
428
  * A playback engine the primitive drives. Mediabunny is one; the primitive
438
429
  * never imports a media library (`@cssints/headless` stays `solid-js`-only).
@@ -556,8 +547,6 @@ interface Player {
556
547
  * no elements live here.
557
548
  */
558
549
  export declare const createPlayer: (options: PlayerOptions) => Player;
559
- //#endregion
560
- //#region src/accordion.d.ts
561
550
  /**
562
551
  * Options are read lazily (getters) so `multiple` / `disabled` stay reactive;
563
552
  * pass a component's `props`, never destructured values.
@@ -641,8 +630,6 @@ export interface Accordion {
641
630
  * while uncontrolled.
642
631
  */
643
632
  export declare const createAccordion: (options?: AccordionOptions) => Accordion;
644
- //#endregion
645
- //#region src/alert.d.ts
646
633
  export interface AlertOptions {
647
634
  /** Rendered at mount; `undefined` starts with no live region. */
648
635
  readonly message?: string | undefined;
@@ -675,8 +662,6 @@ export interface Alert {
675
662
  * region is live; the primitive keeps the region mounted and unhides it.
676
663
  */
677
664
  export declare const createAlert: (options?: AlertOptions) => Alert;
678
- //#endregion
679
- //#region src/alertdialog.d.ts
680
665
  export interface AlertDialogOptions {
681
666
  /** Controlled: the host owns the open state and this is the only thing that changes it. */
682
667
  readonly value?: boolean | undefined;
@@ -765,8 +750,6 @@ declare const YES_POS = 2;
765
750
  * open state through an effect, so a rejected request leaves them untouched.
766
751
  */
767
752
  export declare const createAlertDialog: (options?: AlertDialogOptions) => AlertDialog;
768
- //#endregion
769
- //#region src/breadcrumb.d.ts
770
753
  export interface BreadcrumbItem {
771
754
  readonly label: string;
772
755
  readonly href?: string | undefined;
@@ -813,8 +796,6 @@ export interface Breadcrumb {
813
796
  * markup; the navigation landmark is a UA/AT concern).
814
797
  */
815
798
  export declare const createBreadcrumb: (options: BreadcrumbOptions) => Breadcrumb;
816
- //#endregion
817
- //#region src/button.d.ts
818
799
  export interface ButtonOptions {
819
800
  /** A toggle button carries `aria-pressed`; a command button does not. */
820
801
  readonly isToggle?: boolean;
@@ -862,8 +843,6 @@ export interface Button {
862
843
  * uncontrolled. A command button's activation never carries a value.
863
844
  */
864
845
  export declare const createButton: (options?: ButtonOptions) => Button;
865
- //#endregion
866
- //#region src/carousel.d.ts
867
846
  export type CarouselRole = "region" | "group";
868
847
  /**
869
848
  * Options are read lazily (getters); pass a component's `props`.
@@ -995,8 +974,6 @@ export interface Carousel {
995
974
  * (and `prefers-reduced-motion`) in the consumer.
996
975
  */
997
976
  export declare const createCarousel: (options?: CarouselOptions) => Carousel;
998
- //#endregion
999
- //#region src/checkbox.d.ts
1000
977
  export type CheckboxState = "true" | "false" | "mixed";
1001
978
  export interface CheckboxOptions {
1002
979
  /** Controlled: the host owns the value and this is the only thing that changes it. */
@@ -1053,8 +1030,6 @@ export interface Checkbox {
1053
1030
  * pass `value` + `onValueChange` to own the state.
1054
1031
  */
1055
1032
  export declare const createCheckbox: (options?: CheckboxOptions) => Checkbox;
1056
- //#endregion
1057
- //#region src/checkbox-group.d.ts
1058
1033
  export type CheckboxGroupFocus = "none" | "parent" | "c0" | "c1" | "c2";
1059
1034
  export type ParentChecked = "true" | "false" | "mixed";
1060
1035
  export interface CheckboxGroupOptions<T = unknown> {
@@ -1131,8 +1106,6 @@ export interface CheckboxGroup<T = unknown> {
1131
1106
  * Behaviour is checked against `specs/apg/checkbox-group.qnt` by the bridge.
1132
1107
  */
1133
1108
  export declare const createCheckboxGroup: <T = unknown>(options: CheckboxGroupOptions<T>) => CheckboxGroup<T>;
1134
- //#endregion
1135
- //#region src/combobox.d.ts
1136
1109
  /**
1137
1110
  * APG combobox (https://www.w3.org/WAI/ARIA/apg/patterns/combobox/): state plus
1138
1111
  * getter-backed attribute objects for a listbox popup. No elements, no styling.
@@ -1253,8 +1226,6 @@ export interface ComboboxOptions {
1253
1226
  readonly onFocusRelease?: () => void;
1254
1227
  }
1255
1228
  export declare const createCombobox: (options: ComboboxOptions) => Combobox;
1256
- //#endregion
1257
- //#region src/dialog-modal.d.ts
1258
1229
  export type DialogRole = "dialog" | "alertdialog";
1259
1230
  export interface DialogModalOptions {
1260
1231
  /** Controlled: the host owns the open state and this is the only thing that changes it. */
@@ -1323,8 +1294,6 @@ export interface DialogModal {
1323
1294
  * are requests the host may ignore.
1324
1295
  */
1325
1296
  export declare const createDialogModal: (options?: DialogModalOptions) => DialogModal;
1326
- //#endregion
1327
- //#region src/feed.d.ts
1328
1297
  /**
1329
1298
  * Options are read lazily (getters); pass a component's `props`.
1330
1299
  */
@@ -1412,8 +1381,6 @@ export interface Feed {
1412
1381
  * request; `loadStart` / `loadFail` are adapter lifecycle events.
1413
1382
  */
1414
1383
  export declare const createFeed: (options?: FeedOptions) => Feed;
1415
- //#endregion
1416
- //#region src/grid.d.ts
1417
1384
  /**
1418
1385
  * APG grid primitive on Solid 2: roving focus, 2-D navigation, edit mode, a
1419
1386
  * cell/row/column selection rectangle and sortable column headers. State plus
@@ -1584,8 +1551,6 @@ export declare const packSort: (column: number, dir: GridSortDirection) => numbe
1584
1551
  * snapshot); `rows`/`cols`/`page` are captured once.
1585
1552
  */
1586
1553
  export declare const createGrid: (options?: GridOptions) => Grid;
1587
- //#endregion
1588
- //#region src/link.d.ts
1589
1554
  export interface LinkOptions {
1590
1555
  readonly href?: string | undefined;
1591
1556
  /** Render a native `<a href>` (the default when `href` is set). */
@@ -1622,8 +1587,6 @@ export interface Link {
1622
1587
  * cause browsers to enhance the element". Platform: native.
1623
1588
  */
1624
1589
  export declare const createLink: (options?: LinkOptions) => Link;
1625
- //#endregion
1626
- //#region src/listbox.d.ts
1627
1590
  export type ListboxSelectionMode = "single" | "multiple" | "none";
1628
1591
  export interface ListboxItem {
1629
1592
  readonly id: string;
@@ -1729,8 +1692,6 @@ export interface Listbox {
1729
1692
  * move is a request the host may ignore.
1730
1693
  */
1731
1694
  export declare const createListbox: (options: ListboxOptions) => Listbox;
1732
- //#endregion
1733
- //#region src/menu.d.ts
1734
1695
  /**
1735
1696
  * APG menu family on Solid 2: `createMenu` (menu button + the checkable /
1736
1697
  * menuitemcheckbox / menuitemradio capability) and `createMenubar` (roving
@@ -1953,8 +1914,6 @@ export interface Menubar {
1953
1914
  };
1954
1915
  }
1955
1916
  export declare const createMenubar: (options: MenubarOptions) => Menubar;
1956
- //#endregion
1957
- //#region src/meter.d.ts
1958
1917
  /**
1959
1918
  * A controlled numeric display; the consumer feeds `value`/`min`/`max`.
1960
1919
  * `valueText` overrides the default percentage `aria-valuetext`.
@@ -1992,8 +1951,6 @@ export interface Meter {
1992
1951
  * this primitive only derives the ARIA contract; no state machine runs here.
1993
1952
  */
1994
1953
  export declare const createMeter: (options?: MeterOptions) => Meter;
1995
- //#endregion
1996
- //#region src/radio.d.ts
1997
1954
  export type RadioFlavor = "roving" | "activedescendant";
1998
1955
  export interface RadioGroupOptions<T = unknown> {
1999
1956
  /** Radio values, one per radio; the observable `value` is the checked one. */
@@ -2075,8 +2032,99 @@ export interface RadioGroup<T = unknown> {
2075
2032
  * callback, which `createControllable` does not expose.
2076
2033
  */
2077
2034
  export declare const createRadioGroup: <T = unknown>(options: RadioGroupOptions<T>) => RadioGroup<T>;
2078
- //#endregion
2079
- //#region src/slider.d.ts
2035
+ export type PanelsOrientation = "vertical" | "horizontal";
2036
+ /** One pane of the group. Sizes are percentages of the group (0..100). */
2037
+ export interface ResizablePaneOptions {
2038
+ readonly id?: string;
2039
+ /** Smallest expanded size, in percent. */
2040
+ readonly min?: number;
2041
+ /** Largest size, in percent. */
2042
+ readonly max?: number;
2043
+ /** The uncontrolled start; defaults to an equal split. */
2044
+ readonly defaultSize?: number;
2045
+ /** Collapses to `collapsedSize` on Enter and restores the remembered size. */
2046
+ readonly collapsible?: boolean;
2047
+ readonly collapsedSize?: number;
2048
+ /** Accessible name fragment: the separator becomes "Resize <label>". */
2049
+ readonly label?: string;
2050
+ }
2051
+ /** Options are read lazily: pass an object with getters, never destructured values. */
2052
+ export interface ResizablePanelsOptions {
2053
+ readonly panes: ReadonlyArray<ResizablePaneOptions>;
2054
+ readonly orientation?: PanelsOrientation;
2055
+ /** Controlled: the host owns the layout. A new array per change. */
2056
+ readonly sizes?: ReadonlyArray<number> | undefined;
2057
+ /** The uncontrolled start; ignored while `sizes` is set. */
2058
+ readonly defaultSizes?: ReadonlyArray<number>;
2059
+ /** Keyboard step, in percent. */
2060
+ readonly step?: number;
2061
+ /** Base for the generated group / pane / separator ids. */
2062
+ readonly id?: string;
2063
+ /** Accessible name of the group. */
2064
+ readonly label?: string | undefined;
2065
+ /** Persist the layout in localStorage under this key (host storage, not modelled). */
2066
+ readonly storageKey?: string | undefined;
2067
+ /** A request to change the layout the host may ignore. */
2068
+ readonly onSizesChange?: (sizes: ReadonlyArray<number>) => void;
2069
+ }
2070
+ export interface ResizablePanelsGroupProps {
2071
+ readonly role: "group";
2072
+ readonly "aria-label": string | undefined;
2073
+ }
2074
+ export interface ResizablePaneProps {
2075
+ readonly id: string;
2076
+ }
2077
+ /** Spread onto the focusable `role=separator` bar between pane `index` and `index + 1`. */
2078
+ export interface ResizableSeparatorProps {
2079
+ readonly id: string;
2080
+ readonly role: "separator";
2081
+ readonly "aria-orientation": PanelsOrientation;
2082
+ readonly "aria-valuenow": number;
2083
+ readonly "aria-valuemin": number;
2084
+ readonly "aria-valuemax": number;
2085
+ /** The pane this separator resizes (the one on its left). */
2086
+ readonly "aria-controls": string;
2087
+ readonly "aria-label": string;
2088
+ readonly tabindex: 0;
2089
+ readonly onKeyDown: (event: KeyboardEvent) => void;
2090
+ readonly onFocus: () => void;
2091
+ readonly onBlur: () => void;
2092
+ readonly onPointerDown: (event: PointerEvent) => void;
2093
+ readonly onPointerMove: (event: PointerEvent) => void;
2094
+ readonly onPointerUp: (event: PointerEvent) => void;
2095
+ readonly onPointerCancel: (event: PointerEvent) => void;
2096
+ }
2097
+ export interface ResizablePanels {
2098
+ /** The committed layout, in percent; always sums to 100. */
2099
+ readonly sizes: Accessor<ReadonlyArray<number>>;
2100
+ readonly collapsed: (index: number) => boolean;
2101
+ /** The separator being dragged, or `null`. */
2102
+ readonly resizing: Accessor<number | null>;
2103
+ readonly focusSeparator: Accessor<number | null>;
2104
+ readonly setFocusSeparator: (index: number | null) => void;
2105
+ readonly keyDown: (index: number, key: string) => void;
2106
+ readonly groupProps: ResizablePanelsGroupProps;
2107
+ readonly paneProps: (index: number) => ResizablePaneProps;
2108
+ readonly separatorProps: (index: number) => ResizableSeparatorProps;
2109
+ }
2110
+ /**
2111
+ * A group of N panes and N-1 separators, react-resizable-panels' model on the
2112
+ * APG window splitter's keyboard: sizes in percent of the group, always summing
2113
+ * to 100, each pane within `min..max`, a collapsible pane landing on
2114
+ * `collapsedSize`. The separator is the focusable widget (`role=separator`,
2115
+ * its `aria-valuenow` is the pane on its left); arrows/Home/End move the pair
2116
+ * around a separator and Enter collapses / restores, all as requests.
2117
+ *
2118
+ * A move only ever changes the separator's two neighbours, clamped by both
2119
+ * bounds. Collapsing hands the pane's space to the nearest pane that can take
2120
+ * it and cascades outward; restoring takes it back in the same order. The drag
2121
+ * is snapshot + delta: pointerdown remembers the layout, every move re-solves
2122
+ * from it, so dragging back past a clamp recovers exactly.
2123
+ *
2124
+ * Behaviour is `packages/apg-specs/specs/patterns/resizable-panels.qnt`; the
2125
+ * DOM is checked against it by the differential walk.
2126
+ */
2127
+ export declare const createResizablePanels: (options: ResizablePanelsOptions) => ResizablePanels;
2080
2128
  export type SliderOrientation = "horizontal" | "vertical";
2081
2129
  export type SliderDirection = "ltr" | "rtl";
2082
2130
  export interface SliderOptions {
@@ -2148,8 +2196,6 @@ export interface Slider {
2148
2196
  * deliberately only fixes a discrete rail press.
2149
2197
  */
2150
2198
  export declare const createSlider: (options?: SliderOptions) => Slider;
2151
- //#endregion
2152
- //#region src/slider-multithumb.d.ts
2153
2199
  export type SliderMultiThumbIndex = 0 | 1;
2154
2200
  export interface SliderMultiThumbOptions {
2155
2201
  readonly min?: number;
@@ -2217,8 +2263,6 @@ export interface SliderMultiThumb {
2217
2263
  * omits them (the event names the thumb).
2218
2264
  */
2219
2265
  export declare const createSliderMultiThumb: (options?: SliderMultiThumbOptions) => SliderMultiThumb;
2220
- //#endregion
2221
- //#region src/spinbutton.d.ts
2222
2266
  export interface SpinbuttonOptions {
2223
2267
  readonly min?: number;
2224
2268
  readonly max?: number;
@@ -2307,8 +2351,6 @@ export interface Spinbutton {
2307
2351
  * APG text.
2308
2352
  */
2309
2353
  export declare const createSpinbutton: (options?: SpinbuttonOptions) => Spinbutton;
2310
- //#endregion
2311
- //#region src/switch.d.ts
2312
2354
  export interface SwitchOptions {
2313
2355
  /** Controlled: the host owns the state and this is the only thing that changes it. */
2314
2356
  readonly value?: boolean | undefined;
@@ -2363,8 +2405,6 @@ export interface Switch {
2363
2405
  * pass `value` + `onValueChange` to own the state.
2364
2406
  */
2365
2407
  export declare const createSwitch: (options?: SwitchOptions) => Switch;
2366
- //#endregion
2367
- //#region src/tabs.d.ts
2368
2408
  export type TabsOrientation = "horizontal" | "vertical";
2369
2409
  export type TabsActivation = "automatic" | "manual";
2370
2410
  /**
@@ -2449,8 +2489,6 @@ export interface Tabs {
2449
2489
  * Programmatic methods (`select`, `setSelected`, `keyDown`) are requests too.
2450
2490
  */
2451
2491
  export declare const createTabs: (options?: TabsOptions) => Tabs;
2452
- //#endregion
2453
- //#region src/timeline.d.ts
2454
2492
  /**
2455
2493
  * APG has no waterfall pattern. This is the span timeline the o11y dashboard
2456
2494
  * needs: spans laid out on a shared tick axis with nesting/indentation, a
@@ -2558,8 +2596,6 @@ export interface Timeline {
2558
2596
  * reactive.
2559
2597
  */
2560
2598
  export declare const createTimeline: (options?: TimelineOptions) => Timeline;
2561
- //#endregion
2562
- //#region src/toolbar.d.ts
2563
2599
  /**
2564
2600
  * APG Toolbar on Solid 2: state plus attribute objects, no elements, no
2565
2601
  * styling. A faithful transcription of `specs/apg/toolbar.qnt`.
@@ -2631,8 +2667,6 @@ export interface Toolbar {
2631
2667
  readonly itemProps: (index: number) => ToolbarItemProps;
2632
2668
  }
2633
2669
  export declare const createToolbar: (options: ToolbarOptions) => Toolbar;
2634
- //#endregion
2635
- //#region src/tooltip.d.ts
2636
2670
  export interface TooltipTriggerInput {
2637
2671
  readonly id?: string;
2638
2672
  /** Id of this trigger's tooltip element (needed for `aria-describedby`). */
@@ -2731,8 +2765,6 @@ export interface TooltipGroup {
2731
2765
  * gated, so a rejected request leaves the visible tooltip untouched.
2732
2766
  */
2733
2767
  export declare const createTooltipGroup: (options: TooltipGroupOptions) => TooltipGroup;
2734
- //#endregion
2735
- //#region src/treegrid.d.ts
2736
2768
  /**
2737
2769
  * APG treegrid primitive on Solid 2: a fixed tree of rows, row/cell roving focus
2738
2770
  * across the three APG navigation models, expansion and row selection. State plus
@@ -2824,8 +2856,6 @@ export declare const treegridCellPart: (row: number, col: number) => string;
2824
2856
  export declare const createTreegrid: (options?: TreegridOptions) => Treegrid;
2825
2857
  /** The `data-part` target the treegrid roving tab stop should focus. */
2826
2858
  export declare const treegridFocusTargetPart: (grid: Treegrid) => string | null;
2827
- //#endregion
2828
- //#region src/treeview.d.ts
2829
2859
  /** A tree node: `parent` is 0 for a root; order in `nodes` is document order. */
2830
2860
  export interface TreeviewNode {
2831
2861
  readonly id: number;
@@ -2920,8 +2950,6 @@ export interface Treeview {
2920
2950
  * explicit `resetTypeahead` method; add a timer when a consumer wants one.
2921
2951
  */
2922
2952
  export declare const createTreeview: (options?: TreeviewOptions) => Treeview;
2923
- //#endregion
2924
- //#region src/windowsplitter.d.ts
2925
2953
  export type SplitterOrientation = "vertical" | "horizontal";
2926
2954
  export type SplitterFocus = "none" | "splitter" | "primary" | "secondary";
2927
2955
  /** Options are read lazily: pass an object with getters, never destructured values. */
@@ -2999,8 +3027,6 @@ export interface WindowSplitter {
2999
3027
  * `collapsed` is derived from the controlled value plus the last request.
3000
3028
  */
3001
3029
  export declare const createWindowSplitter: (options?: WindowSplitterOptions) => WindowSplitter;
3002
- //#endregion
3003
- //#region src/listbox-rearrangeable.d.ts
3004
3030
  export type RearrangeableList = "a" | "b";
3005
3031
  export interface RearrangeableListboxOptions {
3006
3032
  /** Option labels; the option id is the index, and every option starts in list A. */
@@ -3064,5 +3090,4 @@ export interface RearrangeableListbox {
3064
3090
  * host that holds the value moves nothing.
3065
3091
  */
3066
3092
  export declare const createRearrangeableListbox: (options: RearrangeableListboxOptions) => RearrangeableListbox;
3067
- //#endregion
3068
3093
  export { type Canvas, type CanvasGoal, type CanvasLayerProps, type CanvasLiveRegionProps, type CanvasMessages, type CanvasMode, type CanvasMove, type CanvasNodeGeometry, type CanvasNodeInput, type CanvasNodeProps, type CanvasNodeRole, type CanvasOptions, type CanvasSnapshot, type CanvasViewportProps, type Disclosure, type DisclosureOptions, type DisclosurePanelProps, type DisclosureTriggerProps, type MarqueeMode, NO_POS, type PlaybackAdapter, type PlaybackAdapterEvent, type Player, type PlayerButtonProps, type PlayerOptions, type PlayerPlayProps, type PlayerSeekProps, type PlayerStage, type PlayerSurfaceProps, type PlayerToggleProps, type PointerInput, type SnapAxis, type SnapCandidates, type SnapContext, YES_POS };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createEffect, createMemo, createRoot, createSignal, createUniqueId, getOwner, onCleanup, onSettled, runWithOwner, untrack } from "solid-js";
2
- //#region src/canvas-math.ts
2
+ // src/canvas-math.ts
3
3
  /**
4
4
  * The fixed-point math of the canvas, copied from the Quint specs. Integers only, power-of-two
5
5
  * denominators, every intermediate below 2^53, so doubles are exact and the DOM implementation can
@@ -108,8 +108,7 @@ const snapNudge = (v, on, zf, nearest) => (snapTarget(v, on, zf, nearest) ?? v)
108
108
  /** Live drag delta on one axis; `base` is the dragged node's committed position on it. */
109
109
  const liveDD = (w, g, base, on, zf, nearest) => rawD(w, g) + snapNudge(base + rawD(w, g), on, zf, nearest);
110
110
  const clampPos = (value, extent) => clampI(value, -1e6, WORLD_BOUND - extent);
111
- //#endregion
112
- //#region src/prefs.ts
111
+ // src/prefs.ts
113
112
  /** Today's behaviour, used for every pref the host does not inject. */
114
113
  const DEFAULT_PREFS = {
115
114
  motion: "full",
@@ -125,13 +124,12 @@ const withPrefs = (partial) => ({
125
124
  ...DEFAULT_PREFS,
126
125
  ...partial
127
126
  });
128
- //#endregion
129
- //#region src/canvas.ts
127
+ // src/canvas.ts
130
128
  const DEFAULT_VIEWPORT = {
131
129
  width: 800,
132
130
  height: 600
133
131
  };
134
- const DEFAULT_STEP = 10;
132
+ const DEFAULT_STEP$1 = 10;
135
133
  const DEFAULT_SHIFT_MULTIPLIER = 5;
136
134
  const PRIMARY_BUTTON = 0;
137
135
  const DEFAULT_MESSAGES = {
@@ -761,7 +759,7 @@ const createCanvas = (options) => {
761
759
  const arrow = ARROWS.get(event.key);
762
760
  if (arrow !== void 0) {
763
761
  event.preventDefault();
764
- const step = (options.keyboardStep ?? DEFAULT_STEP) * (event.shiftKey ? options.keyboardShiftMultiplier ?? DEFAULT_SHIFT_MULTIPLIER : 1);
762
+ const step = (options.keyboardStep ?? DEFAULT_STEP$1) * (event.shiftKey ? options.keyboardShiftMultiplier ?? DEFAULT_SHIFT_MULTIPLIER : 1);
765
763
  moveFrom(index, arrow[0] * step, arrow[1] * step, arrow[2]);
766
764
  } else if (event.key === "Enter") {
767
765
  event.preventDefault();
@@ -967,8 +965,7 @@ const createCanvas = (options) => {
967
965
  removeNodes
968
966
  };
969
967
  };
970
- //#endregion
971
- //#region src/controllable.ts
968
+ // src/controllable.ts
972
969
  /**
973
970
  * The request/commit split (map ticket `rt1p`): `request` is what a user
974
971
  * event does. A change of intent calls `onChange`; uncontrolled it also
@@ -982,8 +979,9 @@ const createControllable = (options) => {
982
979
  version();
983
980
  return options.value() ?? local;
984
981
  };
982
+ const same = options.same ?? Object.is;
985
983
  const request = (next) => {
986
- if (next === current()) return;
984
+ if (same(next, current())) return;
987
985
  if (options.value() === void 0) {
988
986
  local = next;
989
987
  setVersion((count) => count + 1);
@@ -992,8 +990,7 @@ const createControllable = (options) => {
992
990
  };
993
991
  return [current, request];
994
992
  };
995
- //#endregion
996
- //#region src/disclosure.ts
993
+ // src/disclosure.ts
997
994
  /**
998
995
  * APG Disclosure (https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/):
999
996
  * state plus attribute objects for a trigger and a panel. No styling, no
@@ -1041,8 +1038,7 @@ const createDisclosure = (options = {}) => {
1041
1038
  }
1042
1039
  };
1043
1040
  };
1044
- //#endregion
1045
- //#region src/player.ts
1041
+ // src/player.ts
1046
1042
  const RATE_PERCENTS = [
1047
1043
  50,
1048
1044
  100,
@@ -1052,7 +1048,7 @@ const RATE_PERCENTS = [
1052
1048
  const SURFACE_SEEK_SECONDS = 5;
1053
1049
  const SEEK_STEP_SECONDS = 1;
1054
1050
  const SEEK_PAGE_SECONDS = 15;
1055
- const clamp$1 = (value, max) => value < 0 ? 0 : value > max ? max : value;
1051
+ const clamp$2 = (value, max) => value < 0 ? 0 : value > max ? max : value;
1056
1052
  const mss = (seconds) => `${Math.floor(seconds / 60)}:${String(Math.floor(seconds) % 60).padStart(2, "0")}`;
1057
1053
  /**
1058
1054
  * APG media player primitive on Solid 2: playback state + attribute objects.
@@ -1089,7 +1085,7 @@ const createPlayer = (options) => {
1089
1085
  const isActive = () => stage() === "playing" || stage() === "buffering";
1090
1086
  const canPlay = () => stage() === "ready" || stage() === "paused" || stage() === "ended" || stage() === "buffering";
1091
1087
  const moveTo = (seconds) => {
1092
- setPosition(clamp$1(seconds, duration()));
1088
+ setPosition(clamp$2(seconds, duration()));
1093
1089
  };
1094
1090
  const play = () => {
1095
1091
  if (!canPlay()) return;
@@ -1110,14 +1106,14 @@ const createPlayer = (options) => {
1110
1106
  };
1111
1107
  const commit = (seconds) => {
1112
1108
  if (!known()) return;
1113
- const target = clamp$1(seconds, duration());
1109
+ const target = clamp$2(seconds, duration());
1114
1110
  if (stage() === "ended") setStage("paused");
1115
1111
  moveTo(target);
1116
1112
  options.onSeek?.(target);
1117
1113
  options.adapter.seek(target);
1118
1114
  };
1119
1115
  const setVolume = (value) => {
1120
- requestVolume(clamp$1(value, 100));
1116
+ requestVolume(clamp$2(value, 100));
1121
1117
  };
1122
1118
  const toggleMute = () => {
1123
1119
  requestMuted(!muted());
@@ -1296,8 +1292,7 @@ const createPlayer = (options) => {
1296
1292
  }
1297
1293
  };
1298
1294
  };
1299
- //#endregion
1300
- //#region src/accordion.ts
1295
+ // src/accordion.ts
1301
1296
  const HEADING_LEVELS = 6;
1302
1297
  const count = (expanded) => expanded.filter(Boolean).length;
1303
1298
  /** Re-establish the mode's constraints, exactly as the spec's `normExpanded`. */
@@ -1408,8 +1403,7 @@ const createAccordion = (options = {}) => {
1408
1403
  })
1409
1404
  };
1410
1405
  };
1411
- //#endregion
1412
- //#region src/alert.ts
1406
+ // src/alert.ts
1413
1407
  /**
1414
1408
  * APG Alert (https://www.w3.org/WAI/ARIA/apg/patterns/alert/): a brief message
1415
1409
  * in a `role=alert` live region. Platform: native live-region markup
@@ -1449,8 +1443,7 @@ const createAlert = (options = {}) => {
1449
1443
  }
1450
1444
  };
1451
1445
  };
1452
- //#endregion
1453
- //#region src/alertdialog.ts
1446
+ // src/alertdialog.ts
1454
1447
  const NO_POS = 1;
1455
1448
  const YES_POS = 2;
1456
1449
  /**
@@ -1683,8 +1676,7 @@ const createAlertDialog = (options = {}) => {
1683
1676
  }
1684
1677
  };
1685
1678
  };
1686
- //#endregion
1687
- //#region src/breadcrumb.ts
1679
+ // src/breadcrumb.ts
1688
1680
  /**
1689
1681
  * APG Breadcrumb (https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/): a trail
1690
1682
  * of links in hierarchical order, the current page LAST and marked
@@ -1730,8 +1722,7 @@ const createBreadcrumb = (options) => {
1730
1722
  })
1731
1723
  };
1732
1724
  };
1733
- //#endregion
1734
- //#region src/button.ts
1725
+ // src/button.ts
1735
1726
  /**
1736
1727
  * APG Button (https://www.w3.org/WAI/ARIA/apg/patterns/button/): a command
1737
1728
  * button and (with `isToggle`) a toggle button exposing `aria-pressed`.
@@ -1775,8 +1766,7 @@ const createButton = (options = {}) => {
1775
1766
  }
1776
1767
  };
1777
1768
  };
1778
- //#endregion
1779
- //#region src/carousel.ts
1769
+ // src/carousel.ts
1780
1770
  /**
1781
1771
  * APG Carousel (https://www.w3.org/WAI/ARIA/apg/patterns/carousel/): state plus
1782
1772
  * prop objects for the rotation/prev/next controls, slides and the optional
@@ -1966,8 +1956,7 @@ const createCarousel = (options = {}) => {
1966
1956
  })
1967
1957
  };
1968
1958
  };
1969
- //#endregion
1970
- //#region src/checkbox.ts
1959
+ // src/checkbox.ts
1971
1960
  /**
1972
1961
  * APG Checkbox (https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/): one
1973
1962
  * dual-state, optionally tri-state, checkbox. Platform choice (packages/headless/docs/
@@ -2062,8 +2051,7 @@ const createCheckbox = (options = {}) => {
2062
2051
  }
2063
2052
  };
2064
2053
  };
2065
- //#endregion
2066
- //#region src/checkbox-group.ts
2054
+ // src/checkbox-group.ts
2067
2055
  const CHILDREN = [
2068
2056
  "c0",
2069
2057
  "c1",
@@ -2231,8 +2219,7 @@ const createCheckboxGroup = (options) => {
2231
2219
  })
2232
2220
  };
2233
2221
  };
2234
- //#endregion
2235
- //#region src/combobox.ts
2222
+ // src/combobox.ts
2236
2223
  const PAGE_SIZE = 4;
2237
2224
  const toOption = (input) => typeof input === "string" ? {
2238
2225
  value: input,
@@ -2520,8 +2507,7 @@ const createCombobox = (options) => {
2520
2507
  }
2521
2508
  };
2522
2509
  };
2523
- //#endregion
2524
- //#region src/dialog-modal.ts
2510
+ // src/dialog-modal.ts
2525
2511
  /**
2526
2512
  * APG Dialog (Modal) on Solid 2. `modal` adds `aria-modal`. Focus moves into
2527
2513
  * the dialog on open and is the host's to restore on close (the fixture does,
@@ -2598,8 +2584,7 @@ const createDialogModal = (options = {}) => {
2598
2584
  }
2599
2585
  };
2600
2586
  };
2601
- //#endregion
2602
- //#region src/feed.ts
2587
+ // src/feed.ts
2603
2588
  /**
2604
2589
  * APG Feed (https://www.w3.org/WAI/ARIA/apg/patterns/feed/): state plus prop
2605
2590
  * objects for the feed container, its articles and the two outside focus
@@ -2734,8 +2719,7 @@ const createFeed = (options = {}) => {
2734
2719
  }
2735
2720
  };
2736
2721
  };
2737
- //#endregion
2738
- //#region src/grid.ts
2722
+ // src/grid.ts
2739
2723
  const GRID_KEYS = /* @__PURE__ */ new Set([
2740
2724
  "ArrowRight",
2741
2725
  "ArrowLeft",
@@ -3144,8 +3128,7 @@ const createGrid = (options = {}) => {
3144
3128
  }
3145
3129
  };
3146
3130
  };
3147
- //#endregion
3148
- //#region src/link.ts
3131
+ // src/link.ts
3149
3132
  /**
3150
3133
  * APG Link (https://www.w3.org/WAI/ARIA/apg/patterns/link/): a native `<a href>`
3151
3134
  * brings focus and activation for free; a custom element takes `role=link` and
@@ -3194,8 +3177,7 @@ const createLink = (options = {}) => {
3194
3177
  }
3195
3178
  };
3196
3179
  };
3197
- //#endregion
3198
- //#region src/listbox.ts
3180
+ // src/listbox.ts
3199
3181
  const normalizeSelected = (indices, count) => [...new Set(indices)].filter((index) => index >= 0 && index < count).sort((a, b) => a - b);
3200
3182
  const sameSelection = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
3201
3183
  /**
@@ -3542,8 +3524,7 @@ const createListbox = (options) => {
3542
3524
  })
3543
3525
  };
3544
3526
  };
3545
- //#endregion
3546
- //#region src/menu.ts
3527
+ // src/menu.ts
3547
3528
  const toItem = (input, index, base) => {
3548
3529
  if (typeof input === "string") return {
3549
3530
  label: input,
@@ -4247,9 +4228,8 @@ const createMenubar = (options) => {
4247
4228
  })
4248
4229
  };
4249
4230
  };
4250
- //#endregion
4251
- //#region src/meter.ts
4252
- const clamp = (value, lo, hi) => value < lo ? lo : value > hi ? hi : value;
4231
+ // src/meter.ts
4232
+ const clamp$1 = (value, lo, hi) => value < lo ? lo : value > hi ? hi : value;
4253
4233
  /**
4254
4234
  * APG Meter (https://www.w3.org/WAI/ARIA/apg/patterns/meter/): a graphical
4255
4235
  * display of a numeric value within a range. Platform: native (`<meter>`), so
@@ -4262,7 +4242,7 @@ const createMeter = (options = {}) => {
4262
4242
  const value = () => {
4263
4243
  const lo = min();
4264
4244
  const hi = max();
4265
- return hi <= lo ? lo : clamp(options.value ?? 0, lo, hi);
4245
+ return hi <= lo ? lo : clamp$1(options.value ?? 0, lo, hi);
4266
4246
  };
4267
4247
  const percent = () => {
4268
4248
  const width = span();
@@ -4296,8 +4276,7 @@ const createMeter = (options = {}) => {
4296
4276
  }
4297
4277
  };
4298
4278
  };
4299
- //#endregion
4300
- //#region src/radio.ts
4279
+ // src/radio.ts
4301
4280
  /**
4302
4281
  * APG Radio Group (https://www.w3.org/WAI/ARIA/apg/patterns/radio/): a
4303
4282
  * `radiogroup` with `role=radio` items. Platform choice (packages/headless/docs/
@@ -4483,8 +4462,289 @@ const createRadioGroup = (options) => {
4483
4462
  })
4484
4463
  };
4485
4464
  };
4486
- //#endregion
4487
- //#region src/slider.ts
4465
+ // src/resizable-panels.ts
4466
+ const DEFAULT_MIN = 10;
4467
+ const DEFAULT_MAX = 80;
4468
+ const DEFAULT_STEP = 10;
4469
+ const FULL = 100;
4470
+ const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
4471
+ /** Percentages are reported at a hundredth: a pointer delta is never exact. */
4472
+ const round2 = (value) => Math.round(value * 100) / 100;
4473
+ const sameSizes = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
4474
+ const KEY_TAGS$1 = /* @__PURE__ */ new Set([
4475
+ "ArrowLeft",
4476
+ "ArrowRight",
4477
+ "ArrowUp",
4478
+ "ArrowDown",
4479
+ "Home",
4480
+ "End",
4481
+ "Enter"
4482
+ ]);
4483
+ const capture = (element, pointerId) => {
4484
+ try {
4485
+ element.setPointerCapture(pointerId);
4486
+ } catch {}
4487
+ };
4488
+ const release = (element, pointerId) => {
4489
+ try {
4490
+ element.releasePointerCapture(pointerId);
4491
+ } catch {}
4492
+ };
4493
+ const readStored = (key, count) => {
4494
+ if (typeof localStorage === "undefined") return;
4495
+ try {
4496
+ const raw = localStorage.getItem(key);
4497
+ if (raw === null) return;
4498
+ const parsed = JSON.parse(raw);
4499
+ if (Array.isArray(parsed) && parsed.length === count && parsed.every((value) => typeof value === "number" && Number.isFinite(value))) return parsed;
4500
+ } catch {}
4501
+ };
4502
+ const writeStored = (key, sizes) => {
4503
+ if (typeof localStorage === "undefined") return;
4504
+ try {
4505
+ localStorage.setItem(key, JSON.stringify(sizes));
4506
+ } catch {}
4507
+ };
4508
+ /**
4509
+ * A group of N panes and N-1 separators, react-resizable-panels' model on the
4510
+ * APG window splitter's keyboard: sizes in percent of the group, always summing
4511
+ * to 100, each pane within `min..max`, a collapsible pane landing on
4512
+ * `collapsedSize`. The separator is the focusable widget (`role=separator`,
4513
+ * its `aria-valuenow` is the pane on its left); arrows/Home/End move the pair
4514
+ * around a separator and Enter collapses / restores, all as requests.
4515
+ *
4516
+ * A move only ever changes the separator's two neighbours, clamped by both
4517
+ * bounds. Collapsing hands the pane's space to the nearest pane that can take
4518
+ * it and cascades outward; restoring takes it back in the same order. The drag
4519
+ * is snapshot + delta: pointerdown remembers the layout, every move re-solves
4520
+ * from it, so dragging back past a clamp recovers exactly.
4521
+ *
4522
+ * Behaviour is `packages/apg-specs/specs/patterns/resizable-panels.qnt`; the
4523
+ * DOM is checked against it by the differential walk.
4524
+ */
4525
+ const createResizablePanels = (options) => {
4526
+ const panes = () => options.panes;
4527
+ const count = () => panes().length;
4528
+ const orientation = () => options.orientation ?? "vertical";
4529
+ const step = () => options.step ?? DEFAULT_STEP;
4530
+ const base = options.id ?? createUniqueId();
4531
+ const paneId = (index) => panes()[index]?.id ?? `${base}-pane-${index}`;
4532
+ const collapsedSize = (index) => panes()[index]?.collapsible === true ? panes()[index]?.collapsedSize ?? 0 : 0;
4533
+ /** The smallest expanded size (a move never collapses through it). */
4534
+ const minOf = (index) => panes()[index]?.min ?? DEFAULT_MIN;
4535
+ /** The smallest committed size (a collapsible pane may sit at collapsedSize). */
4536
+ const lowOf = (index) => panes()[index]?.collapsible === true ? collapsedSize(index) : minOf(index);
4537
+ const highOf = (index) => panes()[index]?.max ?? DEFAULT_MAX;
4538
+ /** Clamp every pane, then push the residual into the panes in order. */
4539
+ const fit = (raw) => {
4540
+ const next = panes().map((_, index) => clamp(round2(raw[index] ?? FULL / Math.max(count(), 1)), lowOf(index), highOf(index)));
4541
+ let residual = round2(FULL - next.reduce((sum, value) => sum + value, 0));
4542
+ for (let index = 0; index < next.length && residual !== 0; index += 1) {
4543
+ const room = residual > 0 ? highOf(index) - (next[index] ?? 0) : (next[index] ?? 0) - lowOf(index);
4544
+ const move = residual > 0 ? Math.min(residual, room) : Math.max(residual, -room);
4545
+ next[index] = round2((next[index] ?? 0) + move);
4546
+ residual = round2(residual - move);
4547
+ }
4548
+ return next;
4549
+ };
4550
+ /** Move `amount` percent into pane `index` (negative: out of it), taking from or
4551
+ * giving to its neighbours nearest-first (right, then left), each within bounds. */
4552
+ const shift = (sizes, index, amount) => {
4553
+ const next = [...sizes];
4554
+ let left = round2(amount);
4555
+ const order = [];
4556
+ for (let other = index + 1; other < next.length; other += 1) order.push(other);
4557
+ for (let other = index - 1; other >= 0; other -= 1) order.push(other);
4558
+ for (const other of order) {
4559
+ if (left === 0) break;
4560
+ const value = next[other] ?? 0;
4561
+ const room = left > 0 ? Math.max(0, value - minOf(other)) : Math.max(0, highOf(other) - value);
4562
+ const move = left > 0 ? Math.min(left, room) : Math.max(left, -room);
4563
+ next[other] = round2(value - move);
4564
+ left = round2(left - move);
4565
+ }
4566
+ next[index] = round2((next[index] ?? 0) + (round2(amount) - left));
4567
+ return next;
4568
+ };
4569
+ /** Move the boundary between pane `index` and `index + 1` by `delta`. */
4570
+ const movePair = (sizes, index, delta) => {
4571
+ const a = sizes[index] ?? 0;
4572
+ const b = sizes[index + 1] ?? 0;
4573
+ const low = Math.max(minOf(index), round2(a + b - highOf(index + 1)));
4574
+ const high = Math.min(highOf(index), round2(a + b - minOf(index + 1)));
4575
+ const nextA = round2(clamp(a + delta, low, high));
4576
+ const next = [...sizes];
4577
+ next[index] = nextA;
4578
+ next[index + 1] = round2(a + b - nextA);
4579
+ return next;
4580
+ };
4581
+ const initialSizes = () => {
4582
+ const stored = options.storageKey === void 0 ? void 0 : readStored(options.storageKey, count());
4583
+ return fit(stored ?? options.defaultSizes ?? panes().map((pane) => pane.defaultSize ?? round2(FULL / Math.max(count(), 1))));
4584
+ };
4585
+ const start = initialSizes();
4586
+ const [sizes, request] = createControllable({
4587
+ value: () => options.sizes,
4588
+ initial: start,
4589
+ onChange: () => options.onSizesChange,
4590
+ same: sameSizes
4591
+ });
4592
+ const [focusSeparator, setFocusSeparator] = createSignal(null);
4593
+ const [resizing, setResizing] = createSignal(null);
4594
+ const lastExpanded = start.map((value, index) => Math.max(value, minOf(index)));
4595
+ createEffect(() => sizes(), (now) => {
4596
+ now.forEach((value, index) => {
4597
+ if (value > collapsedSize(index)) lastExpanded[index] = value;
4598
+ });
4599
+ });
4600
+ const collapsed = (index) => panes()[index]?.collapsible === true && (sizes()[index] ?? 0) <= collapsedSize(index);
4601
+ const movable = (index) => index >= 0 && index + 1 < count() && !collapsed(index) && !collapsed(index + 1);
4602
+ let asked;
4603
+ const commit = (next) => {
4604
+ const fitted = fit(next);
4605
+ asked = fitted;
4606
+ request(fitted);
4607
+ };
4608
+ const move = (index, delta) => {
4609
+ if (drag !== void 0 || !movable(index)) return;
4610
+ commit(movePair(sizes(), index, delta));
4611
+ };
4612
+ const toggle = (index) => {
4613
+ const pane = panes()[index];
4614
+ if (drag !== void 0 || pane?.collapsible !== true) return;
4615
+ if (collapsed(index)) commit(shift(sizes(), index, (lastExpanded[index] ?? lowOf(index)) - (sizes()[index] ?? 0)));
4616
+ else commit(shift(sizes(), index, collapsedSize(index) - (sizes()[index] ?? 0)));
4617
+ };
4618
+ const keyDown = (index, key) => {
4619
+ if (!KEY_TAGS$1.has(key)) return;
4620
+ const vertical = orientation() === "vertical";
4621
+ switch (key) {
4622
+ case "ArrowLeft":
4623
+ if (vertical) move(index, 0 - step());
4624
+ break;
4625
+ case "ArrowRight":
4626
+ if (vertical) move(index, step());
4627
+ break;
4628
+ case "ArrowUp":
4629
+ if (!vertical) move(index, 0 - step());
4630
+ break;
4631
+ case "ArrowDown":
4632
+ if (!vertical) move(index, step());
4633
+ break;
4634
+ case "Home":
4635
+ move(index, -100);
4636
+ break;
4637
+ case "End":
4638
+ move(index, FULL);
4639
+ break;
4640
+ default: toggle(index);
4641
+ }
4642
+ };
4643
+ let drag;
4644
+ const axisOf = (event, axis) => axis === "x" ? event.clientX : event.clientY;
4645
+ const lengthOf = (element) => {
4646
+ const group = element.parentElement;
4647
+ if (group === null) return 0;
4648
+ const rect = group.getBoundingClientRect();
4649
+ return orientation() === "vertical" ? rect.width : rect.height;
4650
+ };
4651
+ const onPointerDown = (index, event) => {
4652
+ if (event.button !== 0) return;
4653
+ if (drag !== void 0) {
4654
+ release(drag.element, drag.pointerId);
4655
+ drag = void 0;
4656
+ setResizing(null);
4657
+ }
4658
+ setFocusSeparator(index);
4659
+ if (!movable(index)) return;
4660
+ const length = lengthOf(event.currentTarget);
4661
+ if (length <= 0) return;
4662
+ const axis = orientation() === "vertical" ? "x" : "y";
4663
+ drag = {
4664
+ separator: index,
4665
+ element: event.currentTarget,
4666
+ pointerId: event.pointerId,
4667
+ start: axisOf(event, axis),
4668
+ axis,
4669
+ from: sizes(),
4670
+ length
4671
+ };
4672
+ setResizing(index);
4673
+ capture(event.currentTarget, event.pointerId);
4674
+ event.preventDefault();
4675
+ };
4676
+ const onPointerMove = (event) => {
4677
+ if (drag === void 0 || drag.pointerId !== event.pointerId) return;
4678
+ const delta = (axisOf(event, drag.axis) - drag.start) / drag.length * FULL;
4679
+ commit(movePair(drag.from, drag.separator, delta));
4680
+ };
4681
+ const onPointerEnd = (event) => {
4682
+ if (drag === void 0 || drag.pointerId !== event.pointerId) return;
4683
+ drag = void 0;
4684
+ setResizing(null);
4685
+ release(event.currentTarget, event.pointerId);
4686
+ };
4687
+ createEffect(() => options.sizes, (now) => {
4688
+ if (now === void 0 || asked !== void 0 && sameSizes(now, asked)) return;
4689
+ drag = void 0;
4690
+ setResizing(null);
4691
+ });
4692
+ if (options.storageKey !== void 0) {
4693
+ const storageKey = options.storageKey;
4694
+ createEffect(() => sizes(), (now) => writeStored(storageKey, now));
4695
+ }
4696
+ const separatorProps = (index) => ({
4697
+ id: `${base}-separator-${index}`,
4698
+ role: "separator",
4699
+ get "aria-orientation"() {
4700
+ return orientation();
4701
+ },
4702
+ get "aria-valuenow"() {
4703
+ return round2(sizes()[index] ?? 0);
4704
+ },
4705
+ get "aria-valuemin"() {
4706
+ return panes()[index]?.min ?? DEFAULT_MIN;
4707
+ },
4708
+ get "aria-valuemax"() {
4709
+ return highOf(index);
4710
+ },
4711
+ get "aria-controls"() {
4712
+ return paneId(index);
4713
+ },
4714
+ get "aria-label"() {
4715
+ const label = panes()[index]?.label;
4716
+ return label === void 0 ? "Resize" : `Resize ${label}`;
4717
+ },
4718
+ tabindex: 0,
4719
+ onKeyDown: (event) => {
4720
+ if (KEY_TAGS$1.has(event.key)) event.preventDefault();
4721
+ keyDown(index, event.key);
4722
+ },
4723
+ onFocus: () => setFocusSeparator(index),
4724
+ onBlur: () => setFocusSeparator(null),
4725
+ onPointerDown: (event) => onPointerDown(index, event),
4726
+ onPointerMove,
4727
+ onPointerUp: onPointerEnd,
4728
+ onPointerCancel: onPointerEnd
4729
+ });
4730
+ return {
4731
+ sizes,
4732
+ collapsed,
4733
+ resizing,
4734
+ focusSeparator,
4735
+ setFocusSeparator,
4736
+ keyDown,
4737
+ groupProps: {
4738
+ role: "group",
4739
+ get "aria-label"() {
4740
+ return options.label;
4741
+ }
4742
+ },
4743
+ paneProps: (index) => ({ id: paneId(index) }),
4744
+ separatorProps
4745
+ };
4746
+ };
4747
+ // src/slider.ts
4488
4748
  const valueTextFor$1 = (value) => value <= 3 ? "low" : value <= 7 ? "medium" : "high";
4489
4749
  /**
4490
4750
  * APG Slider (https://www.w3.org/WAI/ARIA/apg/patterns/slider/): a single
@@ -4590,8 +4850,7 @@ const createSlider = (options = {}) => {
4590
4850
  } }
4591
4851
  };
4592
4852
  };
4593
- //#endregion
4594
- //#region src/slider-multithumb.ts
4853
+ // src/slider-multithumb.ts
4595
4854
  const valueTextFor = (value) => value <= 3 ? "low" : value <= 7 ? "medium" : "high";
4596
4855
  const keyOf = (pair) => `${pair[0]},${pair[1]}`;
4597
4856
  const pairOf = (key) => {
@@ -4721,8 +4980,7 @@ const createSliderMultiThumb = (options = {}) => {
4721
4980
  })
4722
4981
  };
4723
4982
  };
4724
- //#endregion
4725
- //#region src/spinbutton.ts
4983
+ // src/spinbutton.ts
4726
4984
  /**
4727
4985
  * APG Spinbutton (https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/): a text
4728
4986
  * field plus increase / decrease buttons. Plain Solid signals, no elements, no
@@ -4926,8 +5184,7 @@ const createSpinbutton = (options = {}) => {
4926
5184
  }
4927
5185
  };
4928
5186
  };
4929
- //#endregion
4930
- //#region src/switch.ts
5187
+ // src/switch.ts
4931
5188
  /**
4932
5189
  * APG Switch (https://www.w3.org/WAI/ARIA/apg/patterns/switch/): a binary on/off
4933
5190
  * input. Platform choice (packages/headless/docs/apg-platform-features.md): **native
@@ -5016,8 +5273,7 @@ const createSwitch = (options = {}) => {
5016
5273
  }
5017
5274
  };
5018
5275
  };
5019
- //#endregion
5020
- //#region src/tabs.ts
5276
+ // src/tabs.ts
5021
5277
  /**
5022
5278
  * APG Tabs (https://www.w3.org/WAI/ARIA/apg/patterns/tabs/): state plus prop
5023
5279
  * objects for a tablist, tabs and panels. Roving tabindex, wrapping arrows,
@@ -5154,8 +5410,7 @@ const createTabs = (options = {}) => {
5154
5410
  })
5155
5411
  };
5156
5412
  };
5157
- //#endregion
5158
- //#region src/timeline.ts
5413
+ // src/timeline.ts
5159
5414
  const LAST_NODE = 5;
5160
5415
  const DOMAIN_MIN = 0;
5161
5416
  const DOMAIN_MAX = 100;
@@ -5391,8 +5646,7 @@ const createTimeline = (options = {}) => {
5391
5646
  }
5392
5647
  };
5393
5648
  };
5394
- //#endregion
5395
- //#region src/toolbar.ts
5649
+ // src/toolbar.ts
5396
5650
  const AXIS = (o) => o === "horizontal" ? "inline" : "block";
5397
5651
  const delta = (axis, key) => {
5398
5652
  if (axis === "inline") {
@@ -5572,8 +5826,7 @@ const createToolbar = (options) => {
5572
5826
  })
5573
5827
  };
5574
5828
  };
5575
- //#endregion
5576
- //#region src/tooltip.ts
5829
+ // src/tooltip.ts
5577
5830
  const letters = ["a", "b"];
5578
5831
  const letterOf = (index) => letters[index] ?? "a";
5579
5832
  const shownOf = (vis) => [vis === 1, vis === 2];
@@ -5861,8 +6114,7 @@ const createTooltipGroup = (options) => {
5861
6114
  })
5862
6115
  };
5863
6116
  };
5864
- //#endregion
5865
- //#region src/treegrid.ts
6117
+ // src/treegrid.ts
5866
6118
  const PARENTS = [
5867
6119
  -1,
5868
6120
  0,
@@ -6241,8 +6493,7 @@ const createTreegrid = (options = {}) => {
6241
6493
  };
6242
6494
  /** The `data-part` target the treegrid roving tab stop should focus. */
6243
6495
  const treegridFocusTargetPart = (grid) => grid.focusTargetPart();
6244
- //#endregion
6245
- //#region src/treeview.ts
6496
+ // src/treeview.ts
6246
6497
  /**
6247
6498
  * The fixed tree of `specs/apg/treeview.qnt` (ids are pre-order = document
6248
6499
  * order): 1 Projects > {2 Reports > {3 Roadmap, 4 Summary}, 5 Notes}, 6 Photos
@@ -6578,8 +6829,7 @@ const createTreeview = (options = {}) => {
6578
6829
  })
6579
6830
  };
6580
6831
  };
6581
- //#endregion
6582
- //#region src/windowsplitter.ts
6832
+ // src/windowsplitter.ts
6583
6833
  const KEY_TAGS = /* @__PURE__ */ new Set([
6584
6834
  "ArrowLeft",
6585
6835
  "ArrowRight",
@@ -6740,8 +6990,7 @@ const createWindowSplitter = (options = {}) => {
6740
6990
  secondaryProps: paneProps("secondary")
6741
6991
  };
6742
6992
  };
6743
- //#endregion
6744
- //#region src/listbox-rearrangeable.ts
6993
+ // src/listbox-rearrangeable.ts
6745
6994
  const other = (list) => list === "a" ? "b" : "a";
6746
6995
  const normalize = (indices, count) => [...new Set(indices)].filter((index) => index >= 0 && index < count).sort((x, y) => x - y);
6747
6996
  const same = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
@@ -6990,5 +7239,4 @@ const createRearrangeableListbox = (options) => {
6990
7239
  })
6991
7240
  };
6992
7241
  };
6993
- //#endregion
6994
- export { DEFAULT_PREFS, DRAG_PX, GRID, NO_POS, SNAP_PX, TONE, TREEVIEW_NODES, TWEEN_TICKS, WORLD_BOUND, YES_POS, ZMAX, ZMIN, ZONE, cellPart, createAccordion, createAlert, createAlertDialog, createBreadcrumb, createButton, createCanvas, createCarousel, createCheckbox, createCheckboxGroup, createCombobox, createDialogModal, createDisclosure, createFeed, createGrid, createLink, createListbox, createMenu, createMenubar, createMeter, createPlayer, createRadioGroup, createRearrangeableListbox, createSlider, createSliderMultiThumb, createSpinbutton, createSwitch, createTabs, createTimeline, createToolbar, createTooltipGroup, createTreegrid, createTreeview, createWindowSplitter, gridCandidates, headerPart, packSelection, packSort, rowPart, treegridCellPart, treegridFocusTargetPart, treegridRowPart, withPrefs };
7242
+ export { DEFAULT_PREFS, DRAG_PX, GRID, NO_POS, SNAP_PX, TONE, TREEVIEW_NODES, TWEEN_TICKS, WORLD_BOUND, YES_POS, ZMAX, ZMIN, ZONE, cellPart, createAccordion, createAlert, createAlertDialog, createBreadcrumb, createButton, createCanvas, createCarousel, createCheckbox, createCheckboxGroup, createCombobox, createDialogModal, createDisclosure, createFeed, createGrid, createLink, createListbox, createMenu, createMenubar, createMeter, createPlayer, createRadioGroup, createRearrangeableListbox, createResizablePanels, createSlider, createSliderMultiThumb, createSpinbutton, createSwitch, createTabs, createTimeline, createToolbar, createTooltipGroup, createTreegrid, createTreeview, createWindowSplitter, gridCandidates, headerPart, packSelection, packSort, rowPart, treegridCellPart, treegridFocusTargetPart, treegridRowPart, withPrefs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cssints/headless",
3
- "version": "0.5.2",
3
+ "version": "0.6.1",
4
4
  "description": "Headless APG widgets on Solid 2: state plus attribute objects, no elements, no styling, each conformance-checked against its Quint spec",
5
5
  "license": "MIT",
6
6
  "repository": {