@cssints/apg-specs 0.0.0-stage → 0.5.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/LICENSE +21 -0
- package/README.md +65 -2
- package/dist/apg/accordion.bridge.d.mts +35 -0
- package/dist/apg/accordion.bridge.mjs +326 -0
- package/dist/apg/accordion.model.json +8397 -0
- package/dist/apg/alert.bridge.d.mts +22 -0
- package/dist/apg/alert.bridge.mjs +86 -0
- package/dist/apg/alert.model.json +871 -0
- package/dist/apg/alertdialog.bridge.d.mts +21 -0
- package/dist/apg/alertdialog.bridge.mjs +221 -0
- package/dist/apg/alertdialog.model.json +5232 -0
- package/dist/apg/breadcrumb.bridge.d.mts +26 -0
- package/dist/apg/breadcrumb.bridge.mjs +120 -0
- package/dist/apg/breadcrumb.model.json +574 -0
- package/dist/apg/button.bridge.d.mts +35 -0
- package/dist/apg/button.bridge.mjs +208 -0
- package/dist/apg/button.model.json +5814 -0
- package/dist/apg/carousel.bridge.d.mts +44 -0
- package/dist/apg/carousel.bridge.mjs +262 -0
- package/dist/apg/carousel.model.json +8963 -0
- package/dist/apg/checkbox-group.bridge.d.mts +25 -0
- package/dist/apg/checkbox-group.bridge.mjs +276 -0
- package/dist/apg/checkbox-group.model.json +3863 -0
- package/dist/apg/checkbox.bridge.d.mts +34 -0
- package/dist/apg/checkbox.bridge.mjs +253 -0
- package/dist/apg/checkbox.model.json +3158 -0
- package/dist/apg/combobox-autocomplete.bridge.d.mts +14 -0
- package/dist/apg/combobox-autocomplete.bridge.mjs +326 -0
- package/dist/apg/combobox-autocomplete.model.json +11586 -0
- package/dist/apg/combobox-dialog.bridge.d.mts +17 -0
- package/dist/apg/combobox-dialog.bridge.mjs +250 -0
- package/dist/apg/combobox-dialog.model.json +8981 -0
- package/dist/apg/combobox-grid.bridge.d.mts +17 -0
- package/dist/apg/combobox-grid.bridge.mjs +227 -0
- package/dist/apg/combobox-grid.model.json +8554 -0
- package/dist/apg/combobox-select-only.bridge.d.mts +24 -0
- package/dist/apg/combobox-select-only.bridge.mjs +304 -0
- package/dist/apg/combobox-select-only.model.json +7846 -0
- package/dist/apg/combobox-tree.bridge.d.mts +17 -0
- package/dist/apg/combobox-tree.bridge.mjs +211 -0
- package/dist/apg/combobox-tree.model.json +9345 -0
- package/dist/apg/dialog-modal.bridge.d.mts +35 -0
- package/dist/apg/dialog-modal.bridge.mjs +186 -0
- package/dist/apg/dialog-modal.model.json +7580 -0
- package/dist/apg/disclosure.bridge.d.mts +25 -0
- package/dist/apg/disclosure.bridge.mjs +202 -0
- package/dist/apg/disclosure.model.json +4703 -0
- package/dist/apg/feed.bridge.d.mts +31 -0
- package/dist/apg/feed.bridge.mjs +246 -0
- package/dist/apg/feed.model.json +4523 -0
- package/dist/apg/grid-selection.bridge.d.mts +13 -0
- package/dist/apg/grid-selection.bridge.mjs +160 -0
- package/dist/apg/grid-selection.model.json +6974 -0
- package/dist/apg/grid-sort.bridge.d.mts +12 -0
- package/dist/apg/grid-sort.bridge.mjs +138 -0
- package/dist/apg/grid-sort.model.json +3799 -0
- package/dist/apg/grid.bridge.d.mts +54 -0
- package/dist/apg/grid.bridge.mjs +441 -0
- package/dist/apg/grid.model.json +3802 -0
- package/dist/apg/link.bridge.d.mts +24 -0
- package/dist/apg/link.bridge.mjs +126 -0
- package/dist/apg/link.model.json +694 -0
- package/dist/apg/listbox-multi.bridge.d.mts +10 -0
- package/dist/apg/listbox-multi.bridge.mjs +261 -0
- package/dist/apg/listbox-multi.model.json +10893 -0
- package/dist/apg/listbox-rearrangeable.bridge.d.mts +20 -0
- package/dist/apg/listbox-rearrangeable.bridge.mjs +237 -0
- package/dist/apg/listbox-rearrangeable.model.json +17592 -0
- package/dist/apg/listbox-shared.d.mts +58 -0
- package/dist/apg/listbox-shared.mjs +171 -0
- package/dist/apg/listbox-single.bridge.d.mts +10 -0
- package/dist/apg/listbox-single.bridge.mjs +196 -0
- package/dist/apg/listbox-single.model.json +8024 -0
- package/dist/apg/listbox-typeahead.bridge.d.mts +10 -0
- package/dist/apg/listbox-typeahead.bridge.mjs +153 -0
- package/dist/apg/listbox-typeahead.model.json +5615 -0
- package/dist/apg/menu-button.bridge.d.mts +29 -0
- package/dist/apg/menu-button.bridge.mjs +259 -0
- package/dist/apg/menu-button.model.json +5706 -0
- package/dist/apg/menu-checkable.bridge.d.mts +25 -0
- package/dist/apg/menu-checkable.bridge.mjs +226 -0
- package/dist/apg/menu-checkable.model.json +4277 -0
- package/dist/apg/menu-menubar.bridge.d.mts +27 -0
- package/dist/apg/menu-menubar.bridge.mjs +273 -0
- package/dist/apg/menu-menubar.model.json +9540 -0
- package/dist/apg/meter.bridge.d.mts +28 -0
- package/dist/apg/meter.bridge.mjs +159 -0
- package/dist/apg/meter.model.json +1185 -0
- package/dist/apg/player.bridge.d.mts +24 -0
- package/dist/apg/player.bridge.mjs +342 -0
- package/dist/apg/player.model.json +11013 -0
- package/dist/apg/radio.bridge.d.mts +35 -0
- package/dist/apg/radio.bridge.mjs +371 -0
- package/dist/apg/radio.model.json +5011 -0
- package/dist/apg/slider-multithumb.bridge.d.mts +34 -0
- package/dist/apg/slider-multithumb.bridge.mjs +365 -0
- package/dist/apg/slider-multithumb.model.json +5774 -0
- package/dist/apg/slider.bridge.d.mts +54 -0
- package/dist/apg/slider.bridge.mjs +359 -0
- package/dist/apg/slider.model.json +4881 -0
- package/dist/apg/spinbutton.bridge.d.mts +41 -0
- package/dist/apg/spinbutton.bridge.mjs +327 -0
- package/dist/apg/spinbutton.model.json +7571 -0
- package/dist/apg/switch.bridge.d.mts +30 -0
- package/dist/apg/switch.bridge.mjs +243 -0
- package/dist/apg/switch.model.json +3159 -0
- package/dist/apg/tabs.bridge.d.mts +41 -0
- package/dist/apg/tabs.bridge.mjs +294 -0
- package/dist/apg/tabs.model.json +7659 -0
- package/dist/apg/timeline.bridge.d.mts +36 -0
- package/dist/apg/timeline.bridge.mjs +265 -0
- package/dist/apg/timeline.model.json +6189 -0
- package/dist/apg/toolbar.bridge.d.mts +25 -0
- package/dist/apg/toolbar.bridge.mjs +307 -0
- package/dist/apg/toolbar.model.json +6616 -0
- package/dist/apg/tooltip.bridge.d.mts +36 -0
- package/dist/apg/tooltip.bridge.mjs +208 -0
- package/dist/apg/tooltip.model.json +8295 -0
- package/dist/apg/treegrid.bridge.d.mts +28 -0
- package/dist/apg/treegrid.bridge.mjs +311 -0
- package/dist/apg/treegrid.model.json +17358 -0
- package/dist/apg/treeview.bridge.d.mts +26 -0
- package/dist/apg/treeview.bridge.mjs +396 -0
- package/dist/apg/treeview.model.json +13745 -0
- package/dist/apg/windowsplitter.bridge.d.mts +36 -0
- package/dist/apg/windowsplitter.bridge.mjs +242 -0
- package/dist/apg/windowsplitter.model.json +6253 -0
- package/dist/canvas/camera.model.json +6568 -0
- package/dist/canvas/canvas.bridge.d.mts +190 -0
- package/dist/canvas/canvas.bridge.mjs +729 -0
- package/dist/canvas/pointer.model.json +17095 -0
- package/dist/scenario.d.mts +233 -0
- package/dist/scenario.mjs +313 -0
- package/package.json +57 -4
- package/scenarios/apg/README.md +221 -0
- package/scenarios/apg/accordion.base-ui.json +1005 -0
- package/scenarios/apg/accordion.react-aria.json +929 -0
- package/scenarios/apg/alert.base-ui.json +81 -0
- package/scenarios/apg/alert.react-aria.json +98 -0
- package/scenarios/apg/alertdialog.base-ui.json +331 -0
- package/scenarios/apg/alertdialog.react-aria.json +225 -0
- package/scenarios/apg/breadcrumb.react-aria.json +312 -0
- package/scenarios/apg/button.ariakit.json +136 -0
- package/scenarios/apg/button.base-ui.json +747 -0
- package/scenarios/apg/button.react-aria.json +443 -0
- package/scenarios/apg/carousel.apg.json +909 -0
- package/scenarios/apg/checkbox.ariakit.json +299 -0
- package/scenarios/apg/checkbox.base-ui.json +1126 -0
- package/scenarios/apg/checkbox.react-aria.json +518 -0
- package/scenarios/apg/combobox.ariakit.json +945 -0
- package/scenarios/apg/combobox.base-ui.json +2444 -0
- package/scenarios/apg/combobox.react-aria.json +845 -0
- package/scenarios/apg/dialog-modal.ariakit.json +1102 -0
- package/scenarios/apg/dialog-modal.base-ui.json +775 -0
- package/scenarios/apg/dialog-modal.react-aria.json +638 -0
- package/scenarios/apg/disclosure.apg.json +349 -0
- package/scenarios/apg/disclosure.ariakit.json +163 -0
- package/scenarios/apg/disclosure.base-ui.json +451 -0
- package/scenarios/apg/disclosure.react-aria.json +643 -0
- package/scenarios/apg/feed.apg.json +442 -0
- package/scenarios/apg/grid.ariakit.json +197 -0
- package/scenarios/apg/grid.base-ui.json +720 -0
- package/scenarios/apg/grid.react-aria.json +969 -0
- package/scenarios/apg/landmarks.ariakit.json +93 -0
- package/scenarios/apg/landmarks.react-aria.json +330 -0
- package/scenarios/apg/link.ariakit.json +83 -0
- package/scenarios/apg/link.base-ui.json +88 -0
- package/scenarios/apg/link.react-aria.json +186 -0
- package/scenarios/apg/listbox.ariakit.json +78 -0
- package/scenarios/apg/listbox.base-ui.json +163 -0
- package/scenarios/apg/listbox.react-aria.json +1118 -0
- package/scenarios/apg/menu-button.ariakit.json +924 -0
- package/scenarios/apg/menu-button.base-ui.json +1155 -0
- package/scenarios/apg/menu-button.react-aria.json +1080 -0
- package/scenarios/apg/menubar.ariakit.json +758 -0
- package/scenarios/apg/menubar.base-ui.json +673 -0
- package/scenarios/apg/meter.base-ui.json +243 -0
- package/scenarios/apg/meter.react-aria.json +201 -0
- package/scenarios/apg/radio.ariakit.json +459 -0
- package/scenarios/apg/radio.base-ui.json +581 -0
- package/scenarios/apg/radio.react-aria.json +576 -0
- package/scenarios/apg/slider-multithumb.base-ui.json +316 -0
- package/scenarios/apg/slider-multithumb.react-aria.json +363 -0
- package/scenarios/apg/slider.base-ui.json +544 -0
- package/scenarios/apg/slider.react-aria.json +456 -0
- package/scenarios/apg/spinbutton.base-ui.json +1688 -0
- package/scenarios/apg/spinbutton.react-aria.json +426 -0
- package/scenarios/apg/switch.base-ui.json +456 -0
- package/scenarios/apg/switch.react-aria.json +303 -0
- package/scenarios/apg/table.ariakit.json +65 -0
- package/scenarios/apg/table.react-aria.json +221 -0
- package/scenarios/apg/tabs.ariakit.json +329 -0
- package/scenarios/apg/tabs.base-ui.json +875 -0
- package/scenarios/apg/tabs.react-aria.json +644 -0
- package/scenarios/apg/toolbar.ariakit.json +595 -0
- package/scenarios/apg/toolbar.base-ui.json +1189 -0
- package/scenarios/apg/toolbar.react-aria.json +383 -0
- package/scenarios/apg/tooltip.ariakit.json +441 -0
- package/scenarios/apg/tooltip.base-ui.json +411 -0
- package/scenarios/apg/tooltip.react-aria.json +454 -0
- package/scenarios/apg/treegrid.apg.json +2558 -0
- package/scenarios/apg/treegrid.react-aria.json +5094 -0
- package/scenarios/apg/treeview.apg.json +928 -0
- package/scenarios/apg/windowsplitter.apg.json +557 -0
- package/scenarios/canvas/README.md +69 -0
- package/scenarios/canvas/camera.contract.json +237 -0
- package/scenarios/canvas/camera.excalidraw.json +57 -0
- package/scenarios/canvas/marquee.excalidraw.json +122 -0
- package/scenarios/canvas/pan.excalidraw.json +46 -0
- package/scenarios/canvas/pointer.contract.json +528 -0
- package/scenarios/parts.json +291 -0
- package/shared/aria.qnt +186 -0
- package/shared/details-group.qnt +50 -0
- package/shared/focusgroup.qnt +152 -0
- package/shared/invokers.qnt +81 -0
- package/shared/popover.qnt +71 -0
- package/specs/apg/STATIC.md +103 -0
- package/specs/apg/accordion.qnt +578 -0
- package/specs/apg/alert.qnt +131 -0
- package/specs/apg/alertdialog.qnt +455 -0
- package/specs/apg/breadcrumb.qnt +88 -0
- package/specs/apg/button.qnt +445 -0
- package/specs/apg/carousel.qnt +732 -0
- package/specs/apg/checkbox-group.qnt +388 -0
- package/specs/apg/checkbox.qnt +394 -0
- package/specs/apg/combobox-autocomplete.qnt +764 -0
- package/specs/apg/combobox-dialog.qnt +627 -0
- package/specs/apg/combobox-grid.qnt +632 -0
- package/specs/apg/combobox-select-only.qnt +626 -0
- package/specs/apg/combobox-tree.qnt +633 -0
- package/specs/apg/dialog-modal.qnt +499 -0
- package/specs/apg/disclosure.qnt +309 -0
- package/specs/apg/feed.qnt +485 -0
- package/specs/apg/grid-selection.qnt +512 -0
- package/specs/apg/grid-sort.qnt +330 -0
- package/specs/apg/grid.qnt +360 -0
- package/specs/apg/link.qnt +119 -0
- package/specs/apg/listbox-multi.qnt +799 -0
- package/specs/apg/listbox-rearrangeable.qnt +801 -0
- package/specs/apg/listbox-single.qnt +511 -0
- package/specs/apg/listbox-typeahead.qnt +396 -0
- package/specs/apg/menu-button.qnt +363 -0
- package/specs/apg/menu-checkable.qnt +304 -0
- package/specs/apg/menu-menubar.qnt +531 -0
- package/specs/apg/meter.qnt +129 -0
- package/specs/apg/player.qnt +1081 -0
- package/specs/apg/radio.qnt +394 -0
- package/specs/apg/slider-multithumb.qnt +441 -0
- package/specs/apg/slider.qnt +396 -0
- package/specs/apg/spinbutton.qnt +494 -0
- package/specs/apg/switch.qnt +389 -0
- package/specs/apg/tabs.qnt +412 -0
- package/specs/apg/timeline.qnt +612 -0
- package/specs/apg/toolbar.qnt +364 -0
- package/specs/apg/tooltip.qnt +553 -0
- package/specs/apg/treegrid.qnt +983 -0
- package/specs/apg/treeview.qnt +1004 -0
- package/specs/apg/windowsplitter.qnt +487 -0
- package/specs/canvas/camera.qnt +407 -0
- package/specs/canvas/pointer.qnt +841 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ActionStep, Probe, ScenarioConfig, SpecBridge } from "../scenario.mjs";
|
|
2
|
+
//#region src/apg/treeview.bridge.d.ts
|
|
3
|
+
/** The `data-part` target of a node id (e.g. `treeitem:0/0/1`). */
|
|
4
|
+
export declare const nodeToPath: (node: number) => string;
|
|
5
|
+
/** Resolve a scenario path (`treeitem:0/0/1`, `0/1`, ...) to a node id. */
|
|
6
|
+
export declare const pathToNode: (path: string) => number;
|
|
7
|
+
export interface TreeviewConfig {
|
|
8
|
+
readonly multi: boolean;
|
|
9
|
+
readonly expanded: number;
|
|
10
|
+
readonly selected: number;
|
|
11
|
+
readonly focus: number;
|
|
12
|
+
readonly hasFocus: boolean;
|
|
13
|
+
/** Controlled: the host owns the selection and this is the only thing that changes it. */
|
|
14
|
+
readonly controlled: boolean;
|
|
15
|
+
/** Controlled only: the host writes every request's intent back as the value. */
|
|
16
|
+
readonly accepts: boolean;
|
|
17
|
+
readonly label?: string;
|
|
18
|
+
readonly orientation?: "vertical";
|
|
19
|
+
}
|
|
20
|
+
/** Scenario `config` -> what both targets are mounted with. */
|
|
21
|
+
export declare const treeviewConfig: (config: ScenarioConfig) => TreeviewConfig;
|
|
22
|
+
export declare const treeviewBridge: SpecBridge;
|
|
23
|
+
/** The user-level steps a differential walk draws from. */
|
|
24
|
+
export declare const treeviewSteps: ReadonlyArray<ActionStep>;
|
|
25
|
+
export declare const treeviewProbe: Probe;
|
|
26
|
+
//#endregion
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import { Unsupported } from "../scenario.mjs";
|
|
2
|
+
//#region src/apg/treeview.bridge.ts
|
|
3
|
+
const specEvent = (tag, params = {}) => ({
|
|
4
|
+
_tag: tag,
|
|
5
|
+
...params
|
|
6
|
+
});
|
|
7
|
+
/**
|
|
8
|
+
* Treeview vocabulary <-> `specs/apg/treeview.qnt`, shared by the spec driver
|
|
9
|
+
* and the DOM driver so both gate scenarios identically.
|
|
10
|
+
*
|
|
11
|
+
* The tree is the fixed 7-node pre-order tree of the spec (ids 1..7); scenario
|
|
12
|
+
* targets carry the slash path the fixture renders as `data-part`, so
|
|
13
|
+
* `treeitem:0/1` is Notes. `expanded` / `selected` arrive as path arrays;
|
|
14
|
+
* `focused` maps to `hasFocus`, `multiSelectable` to `multi`.
|
|
15
|
+
*/
|
|
16
|
+
const PARENT = /* @__PURE__ */ new Map([
|
|
17
|
+
[1, 0],
|
|
18
|
+
[2, 1],
|
|
19
|
+
[3, 2],
|
|
20
|
+
[4, 2],
|
|
21
|
+
[5, 1],
|
|
22
|
+
[6, 0],
|
|
23
|
+
[7, 6]
|
|
24
|
+
]);
|
|
25
|
+
const CHILDREN = /* @__PURE__ */ new Map([
|
|
26
|
+
[1, [2, 5]],
|
|
27
|
+
[2, [3, 4]],
|
|
28
|
+
[6, [7]]
|
|
29
|
+
]);
|
|
30
|
+
const ROOTS = [1, 6];
|
|
31
|
+
const IDS = [
|
|
32
|
+
1,
|
|
33
|
+
2,
|
|
34
|
+
3,
|
|
35
|
+
4,
|
|
36
|
+
5,
|
|
37
|
+
6,
|
|
38
|
+
7
|
|
39
|
+
];
|
|
40
|
+
const PREFIX = "treeitem:";
|
|
41
|
+
/** The `data-part` target of a node id (e.g. `treeitem:0/0/1`). */
|
|
42
|
+
const nodeToPath = (node) => {
|
|
43
|
+
const segments = [];
|
|
44
|
+
let current = node;
|
|
45
|
+
while (current !== 0) {
|
|
46
|
+
const parent = PARENT.get(current) ?? 0;
|
|
47
|
+
const index = (parent === 0 ? ROOTS : CHILDREN.get(parent) ?? []).indexOf(current);
|
|
48
|
+
if (index < 0) throw new Error(`unknown treeview node ${current}`);
|
|
49
|
+
segments.unshift(index);
|
|
50
|
+
current = parent;
|
|
51
|
+
}
|
|
52
|
+
return `${PREFIX}${segments.join("/")}`;
|
|
53
|
+
};
|
|
54
|
+
/** Resolve a scenario path (`treeitem:0/0/1`, `0/1`, ...) to a node id. */
|
|
55
|
+
const pathToNode = (path) => {
|
|
56
|
+
const parts = (path.startsWith(PREFIX) ? path.slice(9) : path).split("/");
|
|
57
|
+
const root = Number(parts[0]);
|
|
58
|
+
let node = 0;
|
|
59
|
+
if (root === 0) node = 1;
|
|
60
|
+
else if (root === 1) node = 6;
|
|
61
|
+
if (node === 0) throw new Unsupported(`treeitem path ${path}`);
|
|
62
|
+
for (const part of parts.slice(1)) {
|
|
63
|
+
const next = (CHILDREN.get(node) ?? [])[Number(part)];
|
|
64
|
+
if (next === void 0) throw new Unsupported(`treeitem path ${path}`);
|
|
65
|
+
node = next;
|
|
66
|
+
}
|
|
67
|
+
return node;
|
|
68
|
+
};
|
|
69
|
+
const parentOf = (node) => PARENT.get(node) ?? 0;
|
|
70
|
+
const childrenOf = (node) => node === 0 ? ROOTS : CHILDREN.get(node) ?? [];
|
|
71
|
+
const isParent = (node) => childrenOf(node).length > 0;
|
|
72
|
+
const weight = (node) => 1 << node - 1;
|
|
73
|
+
const bit = (mask, node) => (mask & weight(node)) !== 0;
|
|
74
|
+
const setBit = (mask, node, on) => on ? mask | weight(node) : mask & ~weight(node);
|
|
75
|
+
const maskOfPaths = (value) => {
|
|
76
|
+
if (!Array.isArray(value)) throw new Unsupported(`expected a path array, got ${JSON.stringify(value)}`);
|
|
77
|
+
let mask = 0;
|
|
78
|
+
for (const path of value) {
|
|
79
|
+
if (typeof path !== "string") throw new Unsupported(`treeitem path ${JSON.stringify(path)}`);
|
|
80
|
+
mask = setBit(mask, pathToNode(path), true);
|
|
81
|
+
}
|
|
82
|
+
return mask;
|
|
83
|
+
};
|
|
84
|
+
/** Scenario `config` -> what both targets are mounted with. */
|
|
85
|
+
const treeviewConfig = (config) => {
|
|
86
|
+
let multi = false;
|
|
87
|
+
let hasFocus = false;
|
|
88
|
+
let expanded = 0;
|
|
89
|
+
let selected = 0;
|
|
90
|
+
let focus = 1;
|
|
91
|
+
let controlled = false;
|
|
92
|
+
let accepts = false;
|
|
93
|
+
let label;
|
|
94
|
+
let orientation;
|
|
95
|
+
for (const [key, value] of Object.entries(config)) switch (key) {
|
|
96
|
+
case "focused":
|
|
97
|
+
if (typeof value !== "boolean") throw new Unsupported(`focused=${JSON.stringify(value)}`);
|
|
98
|
+
hasFocus = value;
|
|
99
|
+
break;
|
|
100
|
+
case "multiSelectable":
|
|
101
|
+
if (typeof value !== "boolean") throw new Unsupported(`multiSelectable=${JSON.stringify(value)}`);
|
|
102
|
+
multi = value;
|
|
103
|
+
break;
|
|
104
|
+
case "expanded":
|
|
105
|
+
expanded = maskOfPaths(value);
|
|
106
|
+
break;
|
|
107
|
+
case "selected":
|
|
108
|
+
selected = maskOfPaths(value);
|
|
109
|
+
break;
|
|
110
|
+
case "controlled":
|
|
111
|
+
if (typeof value !== "boolean") throw new Unsupported(`controlled=${JSON.stringify(value)}`);
|
|
112
|
+
controlled = value;
|
|
113
|
+
accepts = value;
|
|
114
|
+
break;
|
|
115
|
+
case "accepts":
|
|
116
|
+
if (typeof value !== "boolean") throw new Unsupported(`accepts=${JSON.stringify(value)}`);
|
|
117
|
+
controlled = true;
|
|
118
|
+
accepts = value;
|
|
119
|
+
break;
|
|
120
|
+
case "focus":
|
|
121
|
+
if (typeof value !== "string") throw new Unsupported(`focus=${JSON.stringify(value)}`);
|
|
122
|
+
focus = pathToNode(value);
|
|
123
|
+
break;
|
|
124
|
+
case "label":
|
|
125
|
+
if (typeof value !== "string") throw new Unsupported(`label=${JSON.stringify(value)}`);
|
|
126
|
+
label = value;
|
|
127
|
+
break;
|
|
128
|
+
case "orientation":
|
|
129
|
+
if (value !== "vertical") throw new Unsupported(`orientation ${JSON.stringify(value)}`);
|
|
130
|
+
orientation = "vertical";
|
|
131
|
+
break;
|
|
132
|
+
default: throw new Unsupported(`config ${key}=${JSON.stringify(value)}`);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
multi,
|
|
136
|
+
expanded,
|
|
137
|
+
selected,
|
|
138
|
+
focus,
|
|
139
|
+
hasFocus,
|
|
140
|
+
controlled,
|
|
141
|
+
accepts,
|
|
142
|
+
...label === void 0 ? {} : { label },
|
|
143
|
+
...orientation === void 0 ? {} : { orientation }
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
const visibleIn = (node, expanded) => {
|
|
147
|
+
let current = node;
|
|
148
|
+
while (current !== 0) {
|
|
149
|
+
const parent = parentOf(current);
|
|
150
|
+
if (parent !== 0 && !bit(expanded, parent)) return false;
|
|
151
|
+
current = parent;
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
};
|
|
155
|
+
const orderVisible = (expanded) => IDS.filter((node) => visibleIn(node, expanded));
|
|
156
|
+
/**
|
|
157
|
+
* Events that bring a fresh model to the scenario config: switch the select
|
|
158
|
+
* mode, focus the tree, open the requested parents, set the selection, move the
|
|
159
|
+
* roving node, then drop DOM focus if the config says so.
|
|
160
|
+
*/
|
|
161
|
+
const setupEvents = (config) => {
|
|
162
|
+
const events = [specEvent("configure", { m: config.multi }), specEvent("focusTree")];
|
|
163
|
+
let focus = 1;
|
|
164
|
+
let expanded = 0;
|
|
165
|
+
const moveTo = (target) => {
|
|
166
|
+
let guard = 0;
|
|
167
|
+
while (focus !== target) {
|
|
168
|
+
if (guard++ > IDS.length) throw new Unsupported(`focus is not reachable at ${target}`);
|
|
169
|
+
const visible = orderVisible(expanded);
|
|
170
|
+
const from = visible.indexOf(focus);
|
|
171
|
+
const to = visible.indexOf(target);
|
|
172
|
+
if (from < 0 || to < 0) throw new Unsupported(`node ${target} is not visible`);
|
|
173
|
+
events.push(specEvent(from < to ? "pressDown" : "pressUp"));
|
|
174
|
+
focus = from < to ? visible[from + 1] ?? focus : visible[from - 1] ?? focus;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
for (const node of IDS) if (isParent(node) && bit(config.expanded, node)) {
|
|
178
|
+
moveTo(node);
|
|
179
|
+
events.push(specEvent("pressRight"));
|
|
180
|
+
expanded = setBit(expanded, node, true);
|
|
181
|
+
}
|
|
182
|
+
if (config.controlled) {
|
|
183
|
+
events.push(specEvent("control"));
|
|
184
|
+
if (config.accepts) events.push(specEvent("acceptRequests"));
|
|
185
|
+
if (config.selected !== 0) events.push(specEvent("setValue", { v: BigInt(config.selected) }));
|
|
186
|
+
} else for (const node of IDS.filter((n) => bit(config.selected, n))) {
|
|
187
|
+
moveTo(node);
|
|
188
|
+
events.push(specEvent("pressSpace"));
|
|
189
|
+
}
|
|
190
|
+
moveTo(config.focus);
|
|
191
|
+
if (!config.hasFocus) events.push(specEvent("blurTree"));
|
|
192
|
+
return events;
|
|
193
|
+
};
|
|
194
|
+
const KEY_EVENTS = /* @__PURE__ */ new Map([
|
|
195
|
+
["ArrowDown", "pressDown"],
|
|
196
|
+
["ArrowUp", "pressUp"],
|
|
197
|
+
["ArrowRight", "pressRight"],
|
|
198
|
+
["ArrowLeft", "pressLeft"],
|
|
199
|
+
["Home", "pressHome"],
|
|
200
|
+
["End", "pressEnd"],
|
|
201
|
+
["Enter", "pressEnter"],
|
|
202
|
+
["Space", "pressSpace"],
|
|
203
|
+
[" ", "pressSpace"],
|
|
204
|
+
["*", "pressStar"]
|
|
205
|
+
]);
|
|
206
|
+
const treeviewBridge = {
|
|
207
|
+
setup: (config) => setupEvents(treeviewConfig(config)),
|
|
208
|
+
event: (step) => {
|
|
209
|
+
switch (step.event) {
|
|
210
|
+
case "focus": return [specEvent("focusTree")];
|
|
211
|
+
case "blur": return [specEvent("blurTree")];
|
|
212
|
+
case "click": {
|
|
213
|
+
const node = pathToNode(step.target ?? "");
|
|
214
|
+
return [specEvent("click", { node: BigInt(node) })];
|
|
215
|
+
}
|
|
216
|
+
case "key": {
|
|
217
|
+
const action = KEY_EVENTS.get(step.key ?? "");
|
|
218
|
+
if (action === void 0) throw new Unsupported(`key ${step.key ?? ""}`);
|
|
219
|
+
return [specEvent("focusTree"), specEvent(action)];
|
|
220
|
+
}
|
|
221
|
+
case "type": {
|
|
222
|
+
const text = step.text ?? "";
|
|
223
|
+
if ([...text].length !== 1) throw new Unsupported(`type ${JSON.stringify(text)}`);
|
|
224
|
+
return [specEvent("focusTree"), specEvent("typeChar", { ch: text })];
|
|
225
|
+
}
|
|
226
|
+
case "typeaheadReset": return [specEvent("typeaheadReset")];
|
|
227
|
+
case "prop": switch (step.prop) {
|
|
228
|
+
case "selected":
|
|
229
|
+
case "setValue": return [specEvent("setValue", { v: BigInt(maskOfPaths(step.value)) })];
|
|
230
|
+
case "hostToggle": return [specEvent("hostToggle", { n: 1n })];
|
|
231
|
+
default: throw new Unsupported(`prop ${step.prop ?? ""}`);
|
|
232
|
+
}
|
|
233
|
+
case "hover":
|
|
234
|
+
case "unhover": return [];
|
|
235
|
+
default: throw new Unsupported(`event ${step.event}`);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
focus: (state) => state["hasFocus"] === true ? nodeToPath(Number(state["focus"])) : null,
|
|
239
|
+
attr: (state, target, name) => {
|
|
240
|
+
const expanded = Number(state["expanded"]);
|
|
241
|
+
const selected = Number(state["selected"]);
|
|
242
|
+
const focus = Number(state["focus"]);
|
|
243
|
+
if (target === "tree") {
|
|
244
|
+
if (name === "aria-multiselectable") return state["multi"] === true ? "true" : "false";
|
|
245
|
+
if (name === "aria-orientation") return null;
|
|
246
|
+
throw new Unsupported(`attribute ${name} on tree`);
|
|
247
|
+
}
|
|
248
|
+
const node = pathToNode(target);
|
|
249
|
+
switch (name) {
|
|
250
|
+
case "aria-expanded": return isParent(node) ? bit(expanded, node) ? "true" : "false" : null;
|
|
251
|
+
case "aria-selected": return bit(selected, node) ? "true" : "false";
|
|
252
|
+
case "aria-level": {
|
|
253
|
+
let level = 1;
|
|
254
|
+
let parent = parentOf(node);
|
|
255
|
+
while (parent !== 0) {
|
|
256
|
+
level += 1;
|
|
257
|
+
parent = parentOf(parent);
|
|
258
|
+
}
|
|
259
|
+
return String(level);
|
|
260
|
+
}
|
|
261
|
+
case "aria-setsize": return String(childrenOf(parentOf(node)).length);
|
|
262
|
+
case "aria-posinset": return String(childrenOf(parentOf(node)).indexOf(node) + 1);
|
|
263
|
+
case "tabindex": return focus === node ? "0" : "-1";
|
|
264
|
+
default: throw new Unsupported(`attribute ${name} on ${target}`);
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
visible: (state) => orderVisible(Number(state["expanded"])).map(nodeToPath),
|
|
268
|
+
events: (before, after) => {
|
|
269
|
+
const out = [];
|
|
270
|
+
const expandedBefore = Number(before["expanded"]);
|
|
271
|
+
const expandedAfter = Number(after["expanded"]);
|
|
272
|
+
for (const node of IDS) if (bit(expandedBefore, node) !== bit(expandedAfter, node)) out.push(["onExpandedChange", nodeToPath(node)]);
|
|
273
|
+
if (before["requests"] !== after["requests"]) {
|
|
274
|
+
const requested = Number(after["requested"]);
|
|
275
|
+
const base = Number(after["base"]);
|
|
276
|
+
for (const node of IDS) if (bit(requested, node) && !bit(base, node)) out.push(["onSelectionChange", nodeToPath(node)]);
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
/** The user-level steps a differential walk draws from. */
|
|
282
|
+
const treeviewSteps = [
|
|
283
|
+
{
|
|
284
|
+
event: "focus",
|
|
285
|
+
target: "tree"
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
event: "blur",
|
|
289
|
+
target: "tree"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
event: "key",
|
|
293
|
+
key: "ArrowDown",
|
|
294
|
+
target: "tree"
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
event: "key",
|
|
298
|
+
key: "ArrowUp",
|
|
299
|
+
target: "tree"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
event: "key",
|
|
303
|
+
key: "ArrowRight",
|
|
304
|
+
target: "tree"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
event: "key",
|
|
308
|
+
key: "ArrowLeft",
|
|
309
|
+
target: "tree"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
event: "key",
|
|
313
|
+
key: "Home",
|
|
314
|
+
target: "tree"
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
event: "key",
|
|
318
|
+
key: "End",
|
|
319
|
+
target: "tree"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
event: "key",
|
|
323
|
+
key: "Enter",
|
|
324
|
+
target: "tree"
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
event: "key",
|
|
328
|
+
key: " ",
|
|
329
|
+
target: "tree"
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
event: "key",
|
|
333
|
+
key: "*",
|
|
334
|
+
target: "tree"
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
event: "type",
|
|
338
|
+
text: "p",
|
|
339
|
+
target: "tree"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
event: "type",
|
|
343
|
+
text: "r",
|
|
344
|
+
target: "tree"
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
event: "type",
|
|
348
|
+
text: "o",
|
|
349
|
+
target: "tree"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
event: "type",
|
|
353
|
+
text: "s",
|
|
354
|
+
target: "tree"
|
|
355
|
+
},
|
|
356
|
+
{ event: "typeaheadReset" },
|
|
357
|
+
...IDS.map((node) => ({
|
|
358
|
+
event: "click",
|
|
359
|
+
target: nodeToPath(node)
|
|
360
|
+
})),
|
|
361
|
+
{
|
|
362
|
+
event: "prop",
|
|
363
|
+
prop: "hostToggle"
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
event: "prop",
|
|
367
|
+
prop: "selected",
|
|
368
|
+
value: ["treeitem:0"]
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
event: "prop",
|
|
372
|
+
prop: "selected",
|
|
373
|
+
value: ["treeitem:1"]
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
event: "prop",
|
|
377
|
+
prop: "selected",
|
|
378
|
+
value: []
|
|
379
|
+
}
|
|
380
|
+
];
|
|
381
|
+
const treeviewProbe = {
|
|
382
|
+
attrs: [["tree", "aria-multiselectable"], ...IDS.flatMap((node) => {
|
|
383
|
+
const path = nodeToPath(node);
|
|
384
|
+
return [
|
|
385
|
+
[path, "aria-expanded"],
|
|
386
|
+
[path, "aria-selected"],
|
|
387
|
+
[path, "aria-level"],
|
|
388
|
+
[path, "aria-setsize"],
|
|
389
|
+
[path, "aria-posinset"],
|
|
390
|
+
[path, "tabindex"]
|
|
391
|
+
];
|
|
392
|
+
})],
|
|
393
|
+
events: ["onExpandedChange", "onSelectionChange"]
|
|
394
|
+
};
|
|
395
|
+
//#endregion
|
|
396
|
+
export { nodeToPath, pathToNode, treeviewBridge, treeviewConfig, treeviewProbe, treeviewSteps };
|