@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,1004 @@
|
|
|
1
|
+
// APG Tree View — https://www.w3.org/WAI/ARIA/apg/patterns/treeview/
|
|
2
|
+
// (and the File Directory Treeview example, single/multi-select variants).
|
|
3
|
+
//
|
|
4
|
+
// Modelled APG sections:
|
|
5
|
+
// * "Keyboard Interaction": Down/Up (next/previous *visible* node, collapsed
|
|
6
|
+
// subtrees skipped), Right (open a closed parent, else move to its first
|
|
7
|
+
// child, else nothing), Left (close an open parent, else move to the
|
|
8
|
+
// parent, else nothing), Home/End (first / last visible node), Enter
|
|
9
|
+
// (activate; opens/closes a parent), type-ahead (1-2 characters), `*`
|
|
10
|
+
// (open every sibling of the focused node), and the multi-select keys
|
|
11
|
+
// Space (toggle), Shift+Down / Shift+Up (move + toggle the arrived-at
|
|
12
|
+
// node), Ctrl+A (select all; a second press clears).
|
|
13
|
+
// * "WAI-ARIA Roles, States, and Properties": aria-expanded only on parent
|
|
14
|
+
// nodes (absent on end nodes), aria-selected on every node,
|
|
15
|
+
// aria-multiselectable on the tree, aria-level / aria-setsize /
|
|
16
|
+
// aria-posinset on every treeitem, roving tabindex (exactly one tabbable
|
|
17
|
+
// treeitem). These are derived defs (`ariaExpanded`, `ariaSelected`,
|
|
18
|
+
// `ariaLevel`, `ariaSetsize`, `ariaPosinset`, `tabIndex`,
|
|
19
|
+
// `ariaMultiselectable`), never stored vars, so they cannot drift from
|
|
20
|
+
// the widget state.
|
|
21
|
+
//
|
|
22
|
+
// Request vs commit (map ticket rt1p). The controlled value of a tree view is the
|
|
23
|
+
// selected-node set; expansion stays uncontrolled (a `defaultExpanded` /
|
|
24
|
+
// `onExpandedChange` pair, like a menu's open state). Every user-intent event that
|
|
25
|
+
// changes the selection - Space, Enter (single-select), a click, Shift+Down /
|
|
26
|
+
// Shift+Up (multi), Ctrl+A (multi) - is a *request* carrying the intended
|
|
27
|
+
// `selected` mask (packed into `requested`). It fires onSelectionChange
|
|
28
|
+
// (`requests` counts the calls) when the intent differs from the current
|
|
29
|
+
// selection, and then:
|
|
30
|
+
// uncontrolled the request is accepted: the nodes commit the intent
|
|
31
|
+
// controlled, holds the host ignores it ("rejected"): the selection is untouched
|
|
32
|
+
// controlled, accepts the host answers with the same value (`accepting`): the
|
|
33
|
+
// nodes commit through the controlled value, not the event
|
|
34
|
+
// The controlled value `selected` is spelled out per member (`valueProp0..6`, bit
|
|
35
|
+
// n-1 = node n) because the generator has no collections; it is the only thing
|
|
36
|
+
// that changes the selection while `controlled`, and `setValue` / `hostToggle` are
|
|
37
|
+
// the host writing it. `control` and `acceptRequests` are configuration events
|
|
38
|
+
// (init is literal-only). `configure` (the multi-select switch) resets the
|
|
39
|
+
// uncontrolled selection only. Expansion keys move focus and toggle expansion but
|
|
40
|
+
// do not change the controlled value, so they commit unconditionally.
|
|
41
|
+
//
|
|
42
|
+
// Invariants for the model: ControlledFollowsProp (in controlled mode no event
|
|
43
|
+
// commits the selection), RejectedRequestUntouched, AcceptedRequestCommits. The
|
|
44
|
+
// single-select invariant is scoped to the uncontrolled path: a controlled host
|
|
45
|
+
// owns the selection and may hand the tree any set.
|
|
46
|
+
//
|
|
47
|
+
// `outcome` is "none" | "accepted" | "rejected" for the last request; a host
|
|
48
|
+
// write settles it back to "none". `base` is the selection when the last request
|
|
49
|
+
// was received.
|
|
50
|
+
//
|
|
51
|
+
// Tree shape (a fixed constant, ids are pre-order = document order):
|
|
52
|
+
//
|
|
53
|
+
// 1 Projects level 1, parent
|
|
54
|
+
// 2 Reports level 2, parent
|
|
55
|
+
// 3 Roadmap level 3, end node
|
|
56
|
+
// 4 Summary level 3, end node
|
|
57
|
+
// 5 Notes level 2, end node
|
|
58
|
+
// 6 Photos level 1, parent
|
|
59
|
+
// 7 Trip level 2, end node
|
|
60
|
+
//
|
|
61
|
+
// Encoding (the generator only supports str/int/bool vars): `expanded` and
|
|
62
|
+
// `selected` are bitmasks over node ids (bit n-1, weight 2^(n-1)); `focus` is
|
|
63
|
+
// the roving-tabindex node (always one of 1..7, even while the tree is not
|
|
64
|
+
// focused); `hasFocus` says whether DOM focus is inside the tree. Key events
|
|
65
|
+
// are no-ops while `hasFocus` is false. `ta1`/`ta2` are the type-ahead buffer
|
|
66
|
+
// ("" = empty). `activated` is the last node whose activation callback fired
|
|
67
|
+
// (0 = none).
|
|
68
|
+
//
|
|
69
|
+
// Deliberate omissions (ponytail):
|
|
70
|
+
// * selection-follows-focus (single-select here selects on Enter/Space/click
|
|
71
|
+
// only), Shift+Space / Ctrl+Shift+Home/End range selection (needs an
|
|
72
|
+
// anchor), Ctrl+Arrow, Ctrl+click, and the "focus the first selected node
|
|
73
|
+
// when the tree receives focus" rule (Tab returns to the roving node).
|
|
74
|
+
// * type-ahead is capped at two characters (a third character restarts the
|
|
75
|
+
// buffer), a repeated identical character does not cycle, and the timeout
|
|
76
|
+
// is an explicit `typeaheadReset` event.
|
|
77
|
+
// * trees deeper than 3 levels (visibility unrolls two ancestor checks),
|
|
78
|
+
// dynamic/lazy children, the Navigation Treeview example (aria-current
|
|
79
|
+
// links), aria-disabled nodes, and aria-owns/group wrappers.
|
|
80
|
+
// * Ctrl+A selects every node (as the APG text says), including nodes inside
|
|
81
|
+
// collapsed subtrees.
|
|
82
|
+
module apg_treeview {
|
|
83
|
+
import aria.* from "../../shared/aria"
|
|
84
|
+
|
|
85
|
+
var multi: bool // aria-multiselectable
|
|
86
|
+
var expanded: int // bitmask: parent nodes whose children are shown
|
|
87
|
+
var selected: int // bitmask: aria-selected nodes
|
|
88
|
+
var focus: int // roving-tabindex node, 1..7
|
|
89
|
+
var hasFocus: bool // DOM focus is inside the tree
|
|
90
|
+
var ta1: str // type-ahead buffer, first character ("" = empty)
|
|
91
|
+
var ta2: str // type-ahead buffer, second character
|
|
92
|
+
var activated: int // last activated node, 0 = none
|
|
93
|
+
var controlled: bool // the host owns the selection (`value` prop present)
|
|
94
|
+
var accepting: bool // controlled host that answers every request with its intent
|
|
95
|
+
var valueProp0: bool // controlled selection member node 1, ...
|
|
96
|
+
var valueProp1: bool
|
|
97
|
+
var valueProp2: bool
|
|
98
|
+
var valueProp3: bool
|
|
99
|
+
var valueProp4: bool
|
|
100
|
+
var valueProp5: bool
|
|
101
|
+
var valueProp6: bool
|
|
102
|
+
var requested: int // intent of the last request, packed selection
|
|
103
|
+
var outcome: str // "none" | "accepted" | "rejected"
|
|
104
|
+
var base: int // selection when the last request was received
|
|
105
|
+
var requests: int // number of requests that fired onSelectionChange
|
|
106
|
+
|
|
107
|
+
// ---- the constant tree --------------------------------------------------
|
|
108
|
+
|
|
109
|
+
val lastNode: int = 7
|
|
110
|
+
val allMask: int = 127
|
|
111
|
+
|
|
112
|
+
def parentOf(n: int): int =
|
|
113
|
+
if (n == 2) 1
|
|
114
|
+
else if (n == 3) 2
|
|
115
|
+
else if (n == 4) 2
|
|
116
|
+
else if (n == 5) 1
|
|
117
|
+
else if (n == 7) 6
|
|
118
|
+
else 0
|
|
119
|
+
|
|
120
|
+
def firstChild(n: int): int =
|
|
121
|
+
if (n == 1) 2
|
|
122
|
+
else if (n == 2) 3
|
|
123
|
+
else if (n == 6) 7
|
|
124
|
+
else 0
|
|
125
|
+
|
|
126
|
+
def isParent(n: int): bool = firstChild(n) != 0
|
|
127
|
+
|
|
128
|
+
// first two characters of each node's name, lowercased (for type-ahead)
|
|
129
|
+
def label1(n: int): str =
|
|
130
|
+
if (n == 1) "p"
|
|
131
|
+
else if (n == 2) "r"
|
|
132
|
+
else if (n == 3) "r"
|
|
133
|
+
else if (n == 4) "s"
|
|
134
|
+
else if (n == 5) "n"
|
|
135
|
+
else if (n == 6) "p"
|
|
136
|
+
else "t"
|
|
137
|
+
|
|
138
|
+
def label2(n: int): str =
|
|
139
|
+
if (n == 1) "r"
|
|
140
|
+
else if (n == 2) "e"
|
|
141
|
+
else if (n == 3) "o"
|
|
142
|
+
else if (n == 4) "u"
|
|
143
|
+
else if (n == 5) "o"
|
|
144
|
+
else if (n == 6) "h"
|
|
145
|
+
else "r"
|
|
146
|
+
|
|
147
|
+
// ---- bitmask helpers ----------------------------------------------------
|
|
148
|
+
|
|
149
|
+
def weight(n: int): int =
|
|
150
|
+
if (n == 1) 1
|
|
151
|
+
else if (n == 2) 2
|
|
152
|
+
else if (n == 3) 4
|
|
153
|
+
else if (n == 4) 8
|
|
154
|
+
else if (n == 5) 16
|
|
155
|
+
else if (n == 6) 32
|
|
156
|
+
else if (n == 7) 64
|
|
157
|
+
else 0
|
|
158
|
+
|
|
159
|
+
def bit(mask: int, n: int): bool =
|
|
160
|
+
if (and(n >= 1, n <= 7)) ((mask / weight(n)) % 2) == 1 else false
|
|
161
|
+
|
|
162
|
+
def setBit(mask: int, n: int, on: bool): int =
|
|
163
|
+
if (bit(mask, n) == on) mask
|
|
164
|
+
else if (on) mask + weight(n)
|
|
165
|
+
else mask - weight(n)
|
|
166
|
+
|
|
167
|
+
def toggleBit(mask: int, n: int): int = setBit(mask, n, not(bit(mask, n)))
|
|
168
|
+
|
|
169
|
+
def bitCount(mask: int, n: int): int = if (bit(mask, n)) 1 else 0
|
|
170
|
+
|
|
171
|
+
def popcount(mask: int): int =
|
|
172
|
+
bitCount(mask, 1) + bitCount(mask, 2) + bitCount(mask, 3) + bitCount(mask, 4)
|
|
173
|
+
+ bitCount(mask, 5) + bitCount(mask, 6) + bitCount(mask, 7)
|
|
174
|
+
|
|
175
|
+
// ---- visibility and navigation -----------------------------------------
|
|
176
|
+
|
|
177
|
+
// the node's parent is shown-open (roots, and the pseudo-parent 0, always are)
|
|
178
|
+
def parentOpen(n: int, e: int): bool = or(parentOf(n) == 0, bit(e, parentOf(n)))
|
|
179
|
+
|
|
180
|
+
// focusable: every ancestor is expanded (depth <= 3 => two ancestor checks)
|
|
181
|
+
def visible(n: int, e: int): bool =
|
|
182
|
+
and(n >= 1, n <= 7, parentOpen(n, e), parentOpen(parentOf(n), e))
|
|
183
|
+
|
|
184
|
+
def visibleAfter(n: int, k: int, e: int): bool = and(n + k <= 7, visible(n + k, e))
|
|
185
|
+
def visibleBefore(n: int, k: int, e: int): bool = and(n - k >= 1, visible(n - k, e))
|
|
186
|
+
|
|
187
|
+
// Down: the next visible node in document order; the last node stays put
|
|
188
|
+
def nextVisible(n: int, e: int): int =
|
|
189
|
+
if (visibleAfter(n, 1, e)) n + 1
|
|
190
|
+
else if (visibleAfter(n, 2, e)) n + 2
|
|
191
|
+
else if (visibleAfter(n, 3, e)) n + 3
|
|
192
|
+
else if (visibleAfter(n, 4, e)) n + 4
|
|
193
|
+
else if (visibleAfter(n, 5, e)) n + 5
|
|
194
|
+
else if (visibleAfter(n, 6, e)) n + 6
|
|
195
|
+
else n
|
|
196
|
+
|
|
197
|
+
// Up: the previous visible node; the first node stays put
|
|
198
|
+
def prevVisible(n: int, e: int): int =
|
|
199
|
+
if (visibleBefore(n, 1, e)) n - 1
|
|
200
|
+
else if (visibleBefore(n, 2, e)) n - 2
|
|
201
|
+
else if (visibleBefore(n, 3, e)) n - 3
|
|
202
|
+
else if (visibleBefore(n, 4, e)) n - 4
|
|
203
|
+
else if (visibleBefore(n, 5, e)) n - 5
|
|
204
|
+
else if (visibleBefore(n, 6, e)) n - 6
|
|
205
|
+
else n
|
|
206
|
+
|
|
207
|
+
// End: the last node focusable without opening anything (node 1 is a root)
|
|
208
|
+
def lastVisible(e: int): int =
|
|
209
|
+
if (visible(7, e)) 7
|
|
210
|
+
else if (visible(6, e)) 6
|
|
211
|
+
else if (visible(5, e)) 5
|
|
212
|
+
else if (visible(4, e)) 4
|
|
213
|
+
else if (visible(3, e)) 3
|
|
214
|
+
else if (visible(2, e)) 2
|
|
215
|
+
else 1
|
|
216
|
+
|
|
217
|
+
// Right on a closed parent opens it (focus stays); on an open parent focus
|
|
218
|
+
// moves to its first child; on an end node nothing happens
|
|
219
|
+
def rightExpands(f: int, e: int): bool = and(isParent(f), not(bit(e, f)))
|
|
220
|
+
def rightTarget(f: int, e: int): int =
|
|
221
|
+
if (and(isParent(f), bit(e, f))) firstChild(f) else f
|
|
222
|
+
|
|
223
|
+
// Left on an open parent closes it (focus stays); otherwise focus moves to
|
|
224
|
+
// the parent; on a root nothing happens
|
|
225
|
+
def leftCollapses(f: int, e: int): bool = and(isParent(f), bit(e, f))
|
|
226
|
+
def leftTarget(f: int, e: int): int =
|
|
227
|
+
if (leftCollapses(f, e)) f
|
|
228
|
+
else if (parentOf(f) == 0) f
|
|
229
|
+
else parentOf(f)
|
|
230
|
+
|
|
231
|
+
// `*`: open every parent that shares the focused node's parent
|
|
232
|
+
def expandIfSibling(e: int, m: int, n: int): int =
|
|
233
|
+
if (and(isParent(m), parentOf(m) == parentOf(n))) setBit(e, m, true) else e
|
|
234
|
+
|
|
235
|
+
def expandSiblings(n: int, e: int): int =
|
|
236
|
+
expandIfSibling(
|
|
237
|
+
expandIfSibling(
|
|
238
|
+
expandIfSibling(
|
|
239
|
+
expandIfSibling(
|
|
240
|
+
expandIfSibling(
|
|
241
|
+
expandIfSibling(expandIfSibling(e, 1, n), 2, n), 3, n),
|
|
242
|
+
4, n),
|
|
243
|
+
5, n),
|
|
244
|
+
6, n),
|
|
245
|
+
7, n)
|
|
246
|
+
|
|
247
|
+
// ---- type-ahead ---------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
// append `ch` to the buffer; a full buffer restarts with `ch`
|
|
250
|
+
def nextC1(a: str, b: str, ch: str): str =
|
|
251
|
+
if (a == "") ch else if (b == "") a else ch
|
|
252
|
+
def nextC2(a: str, b: str, ch: str): str =
|
|
253
|
+
if (a == "") "" else if (b == "") ch else ""
|
|
254
|
+
|
|
255
|
+
def matches(n: int, c1: str, c2: str, e: int): bool =
|
|
256
|
+
and(visible(n, e), label1(n) == c1, or(c2 == "", label2(n) == c2))
|
|
257
|
+
|
|
258
|
+
// k-th candidate in cyclic id order starting at s (shared/aria.qnt)
|
|
259
|
+
def cand(s: int, k: int): int = ariaCycle1(7, s, k)
|
|
260
|
+
|
|
261
|
+
def firstMatch(s: int, c1: str, c2: str, e: int): int =
|
|
262
|
+
if (matches(cand(s, 0), c1, c2, e)) cand(s, 0)
|
|
263
|
+
else if (matches(cand(s, 1), c1, c2, e)) cand(s, 1)
|
|
264
|
+
else if (matches(cand(s, 2), c1, c2, e)) cand(s, 2)
|
|
265
|
+
else if (matches(cand(s, 3), c1, c2, e)) cand(s, 3)
|
|
266
|
+
else if (matches(cand(s, 4), c1, c2, e)) cand(s, 4)
|
|
267
|
+
else if (matches(cand(s, 5), c1, c2, e)) cand(s, 5)
|
|
268
|
+
else if (matches(cand(s, 6), c1, c2, e)) cand(s, 6)
|
|
269
|
+
else 0
|
|
270
|
+
|
|
271
|
+
// one character searches from the node after focus; more characters search
|
|
272
|
+
// from the focused node itself (so refining a match keeps focus on it)
|
|
273
|
+
def typeStart(f: int, c2: str): int = ariaTaStart1(7, f, c2 == "")
|
|
274
|
+
|
|
275
|
+
// 0 = no visible node matches (focus stays)
|
|
276
|
+
def typeTarget(f: int, c1: str, c2: str, e: int): int =
|
|
277
|
+
firstMatch(typeStart(f, c2), c1, c2, e)
|
|
278
|
+
|
|
279
|
+
// ---- selection ----------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
def shiftMoves(f: int, e: int): bool = nextVisible(f, e) != f
|
|
282
|
+
def shiftUpMoves(f: int, e: int): bool = prevVisible(f, e) != f
|
|
283
|
+
|
|
284
|
+
// ---- request / commit (map ticket rt1p) ---------------------------------
|
|
285
|
+
|
|
286
|
+
def holds: bool = and(controlled, not(accepting))
|
|
287
|
+
def follows: bool = and(controlled, accepting)
|
|
288
|
+
def verdict: str = if (holds) "rejected" else "accepted"
|
|
289
|
+
// the selection after a request for `want`
|
|
290
|
+
def landed(h: bool, current: int, want: int): int = if (h) current else want
|
|
291
|
+
// a controlled member after a request for `want`
|
|
292
|
+
def propAfter(f: bool, p: bool, want: bool): bool = if (f) want else p
|
|
293
|
+
// the packed controlled value, spelled out one member at a time (node i+1)
|
|
294
|
+
def propMask: int =
|
|
295
|
+
(if (valueProp0) 1 else 0) + (if (valueProp1) 2 else 0)
|
|
296
|
+
+ (if (valueProp2) 4 else 0) + (if (valueProp3) 8 else 0)
|
|
297
|
+
+ (if (valueProp4) 16 else 0) + (if (valueProp5) 32 else 0)
|
|
298
|
+
+ (if (valueProp6) 64 else 0)
|
|
299
|
+
// the selection component `i` (0..6) of a mask
|
|
300
|
+
def at(mask: int, i: int): bool = bit(mask, i + 1)
|
|
301
|
+
// a request is real only when its intent differs from the current selection
|
|
302
|
+
def reqMoves(want: int): bool = want != selected
|
|
303
|
+
// the intended selection of each request action
|
|
304
|
+
def spaceWant: int =
|
|
305
|
+
if (hasFocus) (if (multi) toggleBit(selected, focus) else weight(focus)) else selected
|
|
306
|
+
def enterWant: int =
|
|
307
|
+
if (and(hasFocus, not(multi))) weight(focus) else selected
|
|
308
|
+
def shiftDownWant: int =
|
|
309
|
+
if (and(hasFocus, multi, shiftMoves(focus, expanded)))
|
|
310
|
+
toggleBit(selected, nextVisible(focus, expanded)) else selected
|
|
311
|
+
def shiftUpWant: int =
|
|
312
|
+
if (and(hasFocus, multi, shiftUpMoves(focus, expanded)))
|
|
313
|
+
toggleBit(selected, prevVisible(focus, expanded)) else selected
|
|
314
|
+
def ctrlAWant: int =
|
|
315
|
+
if (and(hasFocus, multi)) (if (selected == allMask) 0 else allMask) else selected
|
|
316
|
+
def clickWant(node: int): int =
|
|
317
|
+
if (visible(node, expanded)) weight(node) else selected
|
|
318
|
+
|
|
319
|
+
// ---- the ARIA contract (derived) ----------------------------------------
|
|
320
|
+
|
|
321
|
+
// "none" means the attribute is absent (end nodes never carry aria-expanded)
|
|
322
|
+
def ariaExpanded(n: int, e: int): str =
|
|
323
|
+
if (not(isParent(n))) "none"
|
|
324
|
+
else ariaBoolToken(bit(e, n))
|
|
325
|
+
|
|
326
|
+
def ariaSelected(n: int, s: int): str = ariaBoolToken(bit(s, n))
|
|
327
|
+
def ariaMultiselectable(m: bool): str = ariaBoolToken(m)
|
|
328
|
+
|
|
329
|
+
def ariaLevel(n: int): int =
|
|
330
|
+
if (parentOf(n) == 0) 1
|
|
331
|
+
else if (parentOf(parentOf(n)) == 0) 2
|
|
332
|
+
else 3
|
|
333
|
+
|
|
334
|
+
def sibling(m: int, n: int): int = if (parentOf(m) == parentOf(n)) 1 else 0
|
|
335
|
+
def siblingBefore(m: int, n: int): int = if (m < n) sibling(m, n) else 0
|
|
336
|
+
|
|
337
|
+
def ariaSetsize(n: int): int =
|
|
338
|
+
sibling(1, n) + sibling(2, n) + sibling(3, n) + sibling(4, n)
|
|
339
|
+
+ sibling(5, n) + sibling(6, n) + sibling(7, n)
|
|
340
|
+
|
|
341
|
+
def ariaPosinset(n: int): int =
|
|
342
|
+
1 + siblingBefore(1, n) + siblingBefore(2, n) + siblingBefore(3, n)
|
|
343
|
+
+ siblingBefore(4, n) + siblingBefore(5, n) + siblingBefore(6, n)
|
|
344
|
+
+ siblingBefore(7, n)
|
|
345
|
+
|
|
346
|
+
// roving tabindex: 0 on the focus node, -1 elsewhere
|
|
347
|
+
def tabIndex(n: int, f: int): int = if (n == f) 0 else -1
|
|
348
|
+
def tabbable(n: int, f: int): int = if (tabIndex(n, f) == 0) 1 else 0
|
|
349
|
+
def tabbableCount(f: int): int =
|
|
350
|
+
tabbable(1, f) + tabbable(2, f) + tabbable(3, f) + tabbable(4, f)
|
|
351
|
+
+ tabbable(5, f) + tabbable(6, f) + tabbable(7, f)
|
|
352
|
+
|
|
353
|
+
// ---- actions ------------------------------------------------------------
|
|
354
|
+
|
|
355
|
+
action init = all {
|
|
356
|
+
multi' = false,
|
|
357
|
+
expanded' = 0,
|
|
358
|
+
selected' = 0,
|
|
359
|
+
focus' = 1,
|
|
360
|
+
hasFocus' = false,
|
|
361
|
+
ta1' = "",
|
|
362
|
+
ta2' = "",
|
|
363
|
+
activated' = 0,
|
|
364
|
+
controlled' = false,
|
|
365
|
+
accepting' = false,
|
|
366
|
+
valueProp0' = false,
|
|
367
|
+
valueProp1' = false,
|
|
368
|
+
valueProp2' = false,
|
|
369
|
+
valueProp3' = false,
|
|
370
|
+
valueProp4' = false,
|
|
371
|
+
valueProp5' = false,
|
|
372
|
+
valueProp6' = false,
|
|
373
|
+
requested' = 0,
|
|
374
|
+
outcome' = "none",
|
|
375
|
+
base' = 0,
|
|
376
|
+
requests' = 0
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// switch between single- and multi-select; the uncontrolled selection resets
|
|
380
|
+
action configure = {
|
|
381
|
+
nondet m = Set(true, false).oneOf()
|
|
382
|
+
all {
|
|
383
|
+
multi' = m,
|
|
384
|
+
expanded' = expanded,
|
|
385
|
+
selected' = if (controlled) selected else (if (m == multi) selected else 0),
|
|
386
|
+
focus' = focus,
|
|
387
|
+
hasFocus' = hasFocus,
|
|
388
|
+
ta1' = "",
|
|
389
|
+
ta2' = "",
|
|
390
|
+
activated' = activated,
|
|
391
|
+
controlled' = controlled,
|
|
392
|
+
accepting' = accepting,
|
|
393
|
+
valueProp0' = valueProp0,
|
|
394
|
+
valueProp1' = valueProp1,
|
|
395
|
+
valueProp2' = valueProp2,
|
|
396
|
+
valueProp3' = valueProp3,
|
|
397
|
+
valueProp4' = valueProp4,
|
|
398
|
+
valueProp5' = valueProp5,
|
|
399
|
+
valueProp6' = valueProp6,
|
|
400
|
+
requested' = 0,
|
|
401
|
+
outcome' = "none",
|
|
402
|
+
base' = 0,
|
|
403
|
+
requests' = requests
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Tab into the tree: the roving node receives focus
|
|
408
|
+
action focusTree = all {
|
|
409
|
+
multi' = multi,
|
|
410
|
+
expanded' = expanded,
|
|
411
|
+
selected' = selected,
|
|
412
|
+
focus' = focus,
|
|
413
|
+
hasFocus' = true,
|
|
414
|
+
ta1' = ta1,
|
|
415
|
+
ta2' = ta2,
|
|
416
|
+
activated' = activated,
|
|
417
|
+
controlled' = controlled,
|
|
418
|
+
accepting' = accepting,
|
|
419
|
+
valueProp0' = valueProp0,
|
|
420
|
+
valueProp1' = valueProp1,
|
|
421
|
+
valueProp2' = valueProp2,
|
|
422
|
+
valueProp3' = valueProp3,
|
|
423
|
+
valueProp4' = valueProp4,
|
|
424
|
+
valueProp5' = valueProp5,
|
|
425
|
+
valueProp6' = valueProp6,
|
|
426
|
+
requested' = requested,
|
|
427
|
+
outcome' = outcome,
|
|
428
|
+
base' = base,
|
|
429
|
+
requests' = requests
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
action blurTree = all {
|
|
433
|
+
multi' = multi,
|
|
434
|
+
expanded' = expanded,
|
|
435
|
+
selected' = selected,
|
|
436
|
+
focus' = focus,
|
|
437
|
+
hasFocus' = false,
|
|
438
|
+
ta1' = "",
|
|
439
|
+
ta2' = "",
|
|
440
|
+
activated' = activated,
|
|
441
|
+
controlled' = controlled,
|
|
442
|
+
accepting' = accepting,
|
|
443
|
+
valueProp0' = valueProp0,
|
|
444
|
+
valueProp1' = valueProp1,
|
|
445
|
+
valueProp2' = valueProp2,
|
|
446
|
+
valueProp3' = valueProp3,
|
|
447
|
+
valueProp4' = valueProp4,
|
|
448
|
+
valueProp5' = valueProp5,
|
|
449
|
+
valueProp6' = valueProp6,
|
|
450
|
+
requested' = requested,
|
|
451
|
+
outcome' = outcome,
|
|
452
|
+
base' = base,
|
|
453
|
+
requests' = requests
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
action pressDown = all {
|
|
457
|
+
multi' = multi,
|
|
458
|
+
expanded' = expanded,
|
|
459
|
+
selected' = selected,
|
|
460
|
+
focus' = if (hasFocus) nextVisible(focus, expanded) else focus,
|
|
461
|
+
hasFocus' = hasFocus,
|
|
462
|
+
ta1' = "",
|
|
463
|
+
ta2' = "",
|
|
464
|
+
activated' = activated,
|
|
465
|
+
controlled' = controlled,
|
|
466
|
+
accepting' = accepting,
|
|
467
|
+
valueProp0' = valueProp0,
|
|
468
|
+
valueProp1' = valueProp1,
|
|
469
|
+
valueProp2' = valueProp2,
|
|
470
|
+
valueProp3' = valueProp3,
|
|
471
|
+
valueProp4' = valueProp4,
|
|
472
|
+
valueProp5' = valueProp5,
|
|
473
|
+
valueProp6' = valueProp6,
|
|
474
|
+
requested' = requested,
|
|
475
|
+
outcome' = outcome,
|
|
476
|
+
base' = base,
|
|
477
|
+
requests' = requests
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
action pressUp = all {
|
|
481
|
+
multi' = multi,
|
|
482
|
+
expanded' = expanded,
|
|
483
|
+
selected' = selected,
|
|
484
|
+
focus' = if (hasFocus) prevVisible(focus, expanded) else focus,
|
|
485
|
+
hasFocus' = hasFocus,
|
|
486
|
+
ta1' = "",
|
|
487
|
+
ta2' = "",
|
|
488
|
+
activated' = activated,
|
|
489
|
+
controlled' = controlled,
|
|
490
|
+
accepting' = accepting,
|
|
491
|
+
valueProp0' = valueProp0,
|
|
492
|
+
valueProp1' = valueProp1,
|
|
493
|
+
valueProp2' = valueProp2,
|
|
494
|
+
valueProp3' = valueProp3,
|
|
495
|
+
valueProp4' = valueProp4,
|
|
496
|
+
valueProp5' = valueProp5,
|
|
497
|
+
valueProp6' = valueProp6,
|
|
498
|
+
requested' = requested,
|
|
499
|
+
outcome' = outcome,
|
|
500
|
+
base' = base,
|
|
501
|
+
requests' = requests
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
action pressRight = all {
|
|
505
|
+
multi' = multi,
|
|
506
|
+
expanded' = if (and(hasFocus, rightExpands(focus, expanded))) setBit(expanded, focus, true) else expanded,
|
|
507
|
+
selected' = selected,
|
|
508
|
+
focus' = if (hasFocus) rightTarget(focus, expanded) else focus,
|
|
509
|
+
hasFocus' = hasFocus,
|
|
510
|
+
ta1' = "",
|
|
511
|
+
ta2' = "",
|
|
512
|
+
activated' = activated,
|
|
513
|
+
controlled' = controlled,
|
|
514
|
+
accepting' = accepting,
|
|
515
|
+
valueProp0' = valueProp0,
|
|
516
|
+
valueProp1' = valueProp1,
|
|
517
|
+
valueProp2' = valueProp2,
|
|
518
|
+
valueProp3' = valueProp3,
|
|
519
|
+
valueProp4' = valueProp4,
|
|
520
|
+
valueProp5' = valueProp5,
|
|
521
|
+
valueProp6' = valueProp6,
|
|
522
|
+
requested' = requested,
|
|
523
|
+
outcome' = outcome,
|
|
524
|
+
base' = base,
|
|
525
|
+
requests' = requests
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
action pressLeft = all {
|
|
529
|
+
multi' = multi,
|
|
530
|
+
expanded' = if (and(hasFocus, leftCollapses(focus, expanded))) setBit(expanded, focus, false) else expanded,
|
|
531
|
+
selected' = selected,
|
|
532
|
+
focus' = if (hasFocus) leftTarget(focus, expanded) else focus,
|
|
533
|
+
hasFocus' = hasFocus,
|
|
534
|
+
ta1' = "",
|
|
535
|
+
ta2' = "",
|
|
536
|
+
activated' = activated,
|
|
537
|
+
controlled' = controlled,
|
|
538
|
+
accepting' = accepting,
|
|
539
|
+
valueProp0' = valueProp0,
|
|
540
|
+
valueProp1' = valueProp1,
|
|
541
|
+
valueProp2' = valueProp2,
|
|
542
|
+
valueProp3' = valueProp3,
|
|
543
|
+
valueProp4' = valueProp4,
|
|
544
|
+
valueProp5' = valueProp5,
|
|
545
|
+
valueProp6' = valueProp6,
|
|
546
|
+
requested' = requested,
|
|
547
|
+
outcome' = outcome,
|
|
548
|
+
base' = base,
|
|
549
|
+
requests' = requests
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
action pressHome = all {
|
|
553
|
+
multi' = multi,
|
|
554
|
+
expanded' = expanded,
|
|
555
|
+
selected' = selected,
|
|
556
|
+
focus' = if (hasFocus) 1 else focus,
|
|
557
|
+
hasFocus' = hasFocus,
|
|
558
|
+
ta1' = "",
|
|
559
|
+
ta2' = "",
|
|
560
|
+
activated' = activated,
|
|
561
|
+
controlled' = controlled,
|
|
562
|
+
accepting' = accepting,
|
|
563
|
+
valueProp0' = valueProp0,
|
|
564
|
+
valueProp1' = valueProp1,
|
|
565
|
+
valueProp2' = valueProp2,
|
|
566
|
+
valueProp3' = valueProp3,
|
|
567
|
+
valueProp4' = valueProp4,
|
|
568
|
+
valueProp5' = valueProp5,
|
|
569
|
+
valueProp6' = valueProp6,
|
|
570
|
+
requested' = requested,
|
|
571
|
+
outcome' = outcome,
|
|
572
|
+
base' = base,
|
|
573
|
+
requests' = requests
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
action pressEnd = all {
|
|
577
|
+
multi' = multi,
|
|
578
|
+
expanded' = expanded,
|
|
579
|
+
selected' = selected,
|
|
580
|
+
focus' = if (hasFocus) lastVisible(expanded) else focus,
|
|
581
|
+
hasFocus' = hasFocus,
|
|
582
|
+
ta1' = "",
|
|
583
|
+
ta2' = "",
|
|
584
|
+
activated' = activated,
|
|
585
|
+
controlled' = controlled,
|
|
586
|
+
accepting' = accepting,
|
|
587
|
+
valueProp0' = valueProp0,
|
|
588
|
+
valueProp1' = valueProp1,
|
|
589
|
+
valueProp2' = valueProp2,
|
|
590
|
+
valueProp3' = valueProp3,
|
|
591
|
+
valueProp4' = valueProp4,
|
|
592
|
+
valueProp5' = valueProp5,
|
|
593
|
+
valueProp6' = valueProp6,
|
|
594
|
+
requested' = requested,
|
|
595
|
+
outcome' = outcome,
|
|
596
|
+
base' = base,
|
|
597
|
+
requests' = requests
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// request: Enter activates the focused node. A parent also opens/closes
|
|
601
|
+
// (uncontrolled); in single-select the activated node becomes the selection.
|
|
602
|
+
action pressEnter = all {
|
|
603
|
+
multi' = multi,
|
|
604
|
+
expanded' = if (and(hasFocus, isParent(focus))) toggleBit(expanded, focus) else expanded,
|
|
605
|
+
selected' = landed(holds, selected, enterWant),
|
|
606
|
+
focus' = focus,
|
|
607
|
+
hasFocus' = hasFocus,
|
|
608
|
+
ta1' = "",
|
|
609
|
+
ta2' = "",
|
|
610
|
+
activated' = if (hasFocus) focus else activated,
|
|
611
|
+
controlled' = controlled,
|
|
612
|
+
accepting' = accepting,
|
|
613
|
+
valueProp0' = propAfter(follows, valueProp0, at(enterWant, 0)),
|
|
614
|
+
valueProp1' = propAfter(follows, valueProp1, at(enterWant, 1)),
|
|
615
|
+
valueProp2' = propAfter(follows, valueProp2, at(enterWant, 2)),
|
|
616
|
+
valueProp3' = propAfter(follows, valueProp3, at(enterWant, 3)),
|
|
617
|
+
valueProp4' = propAfter(follows, valueProp4, at(enterWant, 4)),
|
|
618
|
+
valueProp5' = propAfter(follows, valueProp5, at(enterWant, 5)),
|
|
619
|
+
valueProp6' = propAfter(follows, valueProp6, at(enterWant, 6)),
|
|
620
|
+
requested' = if (reqMoves(enterWant)) enterWant else requested,
|
|
621
|
+
outcome' = if (reqMoves(enterWant)) verdict else outcome,
|
|
622
|
+
base' = if (reqMoves(enterWant)) selected else base,
|
|
623
|
+
requests' = if (reqMoves(enterWant)) requests + 1 else requests
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// request: Space - single-select selects the focused node, multi-select toggles it
|
|
627
|
+
action pressSpace = all {
|
|
628
|
+
multi' = multi,
|
|
629
|
+
expanded' = expanded,
|
|
630
|
+
selected' = landed(holds, selected, spaceWant),
|
|
631
|
+
focus' = focus,
|
|
632
|
+
hasFocus' = hasFocus,
|
|
633
|
+
ta1' = "",
|
|
634
|
+
ta2' = "",
|
|
635
|
+
activated' = activated,
|
|
636
|
+
controlled' = controlled,
|
|
637
|
+
accepting' = accepting,
|
|
638
|
+
valueProp0' = propAfter(follows, valueProp0, at(spaceWant, 0)),
|
|
639
|
+
valueProp1' = propAfter(follows, valueProp1, at(spaceWant, 1)),
|
|
640
|
+
valueProp2' = propAfter(follows, valueProp2, at(spaceWant, 2)),
|
|
641
|
+
valueProp3' = propAfter(follows, valueProp3, at(spaceWant, 3)),
|
|
642
|
+
valueProp4' = propAfter(follows, valueProp4, at(spaceWant, 4)),
|
|
643
|
+
valueProp5' = propAfter(follows, valueProp5, at(spaceWant, 5)),
|
|
644
|
+
valueProp6' = propAfter(follows, valueProp6, at(spaceWant, 6)),
|
|
645
|
+
requested' = if (reqMoves(spaceWant)) spaceWant else requested,
|
|
646
|
+
outcome' = if (reqMoves(spaceWant)) verdict else outcome,
|
|
647
|
+
base' = if (reqMoves(spaceWant)) selected else base,
|
|
648
|
+
requests' = if (reqMoves(spaceWant)) requests + 1 else requests
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// `*`: open all siblings of the focused node (uncontrolled expansion)
|
|
652
|
+
action pressStar = all {
|
|
653
|
+
multi' = multi,
|
|
654
|
+
expanded' = if (hasFocus) expandSiblings(focus, expanded) else expanded,
|
|
655
|
+
selected' = selected,
|
|
656
|
+
focus' = focus,
|
|
657
|
+
hasFocus' = hasFocus,
|
|
658
|
+
ta1' = "",
|
|
659
|
+
ta2' = "",
|
|
660
|
+
activated' = activated,
|
|
661
|
+
controlled' = controlled,
|
|
662
|
+
accepting' = accepting,
|
|
663
|
+
valueProp0' = valueProp0,
|
|
664
|
+
valueProp1' = valueProp1,
|
|
665
|
+
valueProp2' = valueProp2,
|
|
666
|
+
valueProp3' = valueProp3,
|
|
667
|
+
valueProp4' = valueProp4,
|
|
668
|
+
valueProp5' = valueProp5,
|
|
669
|
+
valueProp6' = valueProp6,
|
|
670
|
+
requested' = requested,
|
|
671
|
+
outcome' = outcome,
|
|
672
|
+
base' = base,
|
|
673
|
+
requests' = requests
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// a printable character: type-ahead over the visible nodes
|
|
677
|
+
action typeChar = {
|
|
678
|
+
nondet ch = Set("p", "r", "n", "s", "t", "h", "e", "o", "u", "x").oneOf()
|
|
679
|
+
all {
|
|
680
|
+
multi' = multi,
|
|
681
|
+
expanded' = expanded,
|
|
682
|
+
selected' = selected,
|
|
683
|
+
focus' = if (and(hasFocus, typeTarget(focus, nextC1(ta1, ta2, ch), nextC2(ta1, ta2, ch), expanded) != 0)) typeTarget(focus, nextC1(ta1, ta2, ch), nextC2(ta1, ta2, ch), expanded) else focus,
|
|
684
|
+
hasFocus' = hasFocus,
|
|
685
|
+
ta1' = if (hasFocus) nextC1(ta1, ta2, ch) else ta1,
|
|
686
|
+
ta2' = if (hasFocus) nextC2(ta1, ta2, ch) else ta2,
|
|
687
|
+
activated' = activated,
|
|
688
|
+
controlled' = controlled,
|
|
689
|
+
accepting' = accepting,
|
|
690
|
+
valueProp0' = valueProp0,
|
|
691
|
+
valueProp1' = valueProp1,
|
|
692
|
+
valueProp2' = valueProp2,
|
|
693
|
+
valueProp3' = valueProp3,
|
|
694
|
+
valueProp4' = valueProp4,
|
|
695
|
+
valueProp5' = valueProp5,
|
|
696
|
+
valueProp6' = valueProp6,
|
|
697
|
+
requested' = requested,
|
|
698
|
+
outcome' = outcome,
|
|
699
|
+
base' = base,
|
|
700
|
+
requests' = requests
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// the type-ahead timeout elapsed
|
|
705
|
+
action typeaheadReset = all {
|
|
706
|
+
multi' = multi,
|
|
707
|
+
expanded' = expanded,
|
|
708
|
+
selected' = selected,
|
|
709
|
+
focus' = focus,
|
|
710
|
+
hasFocus' = hasFocus,
|
|
711
|
+
ta1' = "",
|
|
712
|
+
ta2' = "",
|
|
713
|
+
activated' = activated,
|
|
714
|
+
controlled' = controlled,
|
|
715
|
+
accepting' = accepting,
|
|
716
|
+
valueProp0' = valueProp0,
|
|
717
|
+
valueProp1' = valueProp1,
|
|
718
|
+
valueProp2' = valueProp2,
|
|
719
|
+
valueProp3' = valueProp3,
|
|
720
|
+
valueProp4' = valueProp4,
|
|
721
|
+
valueProp5' = valueProp5,
|
|
722
|
+
valueProp6' = valueProp6,
|
|
723
|
+
requested' = requested,
|
|
724
|
+
outcome' = outcome,
|
|
725
|
+
base' = base,
|
|
726
|
+
requests' = requests
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// request: Shift+Down - move to the next node and (multi) toggle its selection
|
|
730
|
+
action pressShiftDown = all {
|
|
731
|
+
multi' = multi,
|
|
732
|
+
expanded' = expanded,
|
|
733
|
+
selected' = landed(holds, selected, shiftDownWant),
|
|
734
|
+
focus' = if (hasFocus) nextVisible(focus, expanded) else focus,
|
|
735
|
+
hasFocus' = hasFocus,
|
|
736
|
+
ta1' = "",
|
|
737
|
+
ta2' = "",
|
|
738
|
+
activated' = activated,
|
|
739
|
+
controlled' = controlled,
|
|
740
|
+
accepting' = accepting,
|
|
741
|
+
valueProp0' = propAfter(follows, valueProp0, at(shiftDownWant, 0)),
|
|
742
|
+
valueProp1' = propAfter(follows, valueProp1, at(shiftDownWant, 1)),
|
|
743
|
+
valueProp2' = propAfter(follows, valueProp2, at(shiftDownWant, 2)),
|
|
744
|
+
valueProp3' = propAfter(follows, valueProp3, at(shiftDownWant, 3)),
|
|
745
|
+
valueProp4' = propAfter(follows, valueProp4, at(shiftDownWant, 4)),
|
|
746
|
+
valueProp5' = propAfter(follows, valueProp5, at(shiftDownWant, 5)),
|
|
747
|
+
valueProp6' = propAfter(follows, valueProp6, at(shiftDownWant, 6)),
|
|
748
|
+
requested' = if (reqMoves(shiftDownWant)) shiftDownWant else requested,
|
|
749
|
+
outcome' = if (reqMoves(shiftDownWant)) verdict else outcome,
|
|
750
|
+
base' = if (reqMoves(shiftDownWant)) selected else base,
|
|
751
|
+
requests' = if (reqMoves(shiftDownWant)) requests + 1 else requests
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// request: Shift+Up - move to the previous node and (multi) toggle its selection
|
|
755
|
+
action pressShiftUp = all {
|
|
756
|
+
multi' = multi,
|
|
757
|
+
expanded' = expanded,
|
|
758
|
+
selected' = landed(holds, selected, shiftUpWant),
|
|
759
|
+
focus' = if (hasFocus) prevVisible(focus, expanded) else focus,
|
|
760
|
+
hasFocus' = hasFocus,
|
|
761
|
+
ta1' = "",
|
|
762
|
+
ta2' = "",
|
|
763
|
+
activated' = activated,
|
|
764
|
+
controlled' = controlled,
|
|
765
|
+
accepting' = accepting,
|
|
766
|
+
valueProp0' = propAfter(follows, valueProp0, at(shiftUpWant, 0)),
|
|
767
|
+
valueProp1' = propAfter(follows, valueProp1, at(shiftUpWant, 1)),
|
|
768
|
+
valueProp2' = propAfter(follows, valueProp2, at(shiftUpWant, 2)),
|
|
769
|
+
valueProp3' = propAfter(follows, valueProp3, at(shiftUpWant, 3)),
|
|
770
|
+
valueProp4' = propAfter(follows, valueProp4, at(shiftUpWant, 4)),
|
|
771
|
+
valueProp5' = propAfter(follows, valueProp5, at(shiftUpWant, 5)),
|
|
772
|
+
valueProp6' = propAfter(follows, valueProp6, at(shiftUpWant, 6)),
|
|
773
|
+
requested' = if (reqMoves(shiftUpWant)) shiftUpWant else requested,
|
|
774
|
+
outcome' = if (reqMoves(shiftUpWant)) verdict else outcome,
|
|
775
|
+
base' = if (reqMoves(shiftUpWant)) selected else base,
|
|
776
|
+
requests' = if (reqMoves(shiftUpWant)) requests + 1 else requests
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// request: Ctrl+A (multi) selects every node; pressed again, clear
|
|
780
|
+
action pressCtrlA = all {
|
|
781
|
+
multi' = multi,
|
|
782
|
+
expanded' = expanded,
|
|
783
|
+
selected' = landed(holds, selected, ctrlAWant),
|
|
784
|
+
focus' = focus,
|
|
785
|
+
hasFocus' = hasFocus,
|
|
786
|
+
ta1' = "",
|
|
787
|
+
ta2' = "",
|
|
788
|
+
activated' = activated,
|
|
789
|
+
controlled' = controlled,
|
|
790
|
+
accepting' = accepting,
|
|
791
|
+
valueProp0' = propAfter(follows, valueProp0, at(ctrlAWant, 0)),
|
|
792
|
+
valueProp1' = propAfter(follows, valueProp1, at(ctrlAWant, 1)),
|
|
793
|
+
valueProp2' = propAfter(follows, valueProp2, at(ctrlAWant, 2)),
|
|
794
|
+
valueProp3' = propAfter(follows, valueProp3, at(ctrlAWant, 3)),
|
|
795
|
+
valueProp4' = propAfter(follows, valueProp4, at(ctrlAWant, 4)),
|
|
796
|
+
valueProp5' = propAfter(follows, valueProp5, at(ctrlAWant, 5)),
|
|
797
|
+
valueProp6' = propAfter(follows, valueProp6, at(ctrlAWant, 6)),
|
|
798
|
+
requested' = if (reqMoves(ctrlAWant)) ctrlAWant else requested,
|
|
799
|
+
outcome' = if (reqMoves(ctrlAWant)) verdict else outcome,
|
|
800
|
+
base' = if (reqMoves(ctrlAWant)) selected else base,
|
|
801
|
+
requests' = if (reqMoves(ctrlAWant)) requests + 1 else requests
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// request: pointer click on a node - only a visible node can be hit. It takes
|
|
805
|
+
// focus, becomes the sole selection, is activated, and a parent toggles.
|
|
806
|
+
action click = {
|
|
807
|
+
nondet node = Set(1, 2, 3, 4, 5, 6, 7).oneOf()
|
|
808
|
+
all {
|
|
809
|
+
multi' = multi,
|
|
810
|
+
expanded' = if (and(visible(node, expanded), isParent(node))) toggleBit(expanded, node) else expanded,
|
|
811
|
+
selected' = landed(holds, selected, clickWant(node)),
|
|
812
|
+
focus' = if (visible(node, expanded)) node else focus,
|
|
813
|
+
hasFocus' = if (visible(node, expanded)) true else hasFocus,
|
|
814
|
+
ta1' = "",
|
|
815
|
+
ta2' = "",
|
|
816
|
+
activated' = if (visible(node, expanded)) node else activated,
|
|
817
|
+
controlled' = controlled,
|
|
818
|
+
accepting' = accepting,
|
|
819
|
+
valueProp0' = propAfter(follows, valueProp0, at(clickWant(node), 0)),
|
|
820
|
+
valueProp1' = propAfter(follows, valueProp1, at(clickWant(node), 1)),
|
|
821
|
+
valueProp2' = propAfter(follows, valueProp2, at(clickWant(node), 2)),
|
|
822
|
+
valueProp3' = propAfter(follows, valueProp3, at(clickWant(node), 3)),
|
|
823
|
+
valueProp4' = propAfter(follows, valueProp4, at(clickWant(node), 4)),
|
|
824
|
+
valueProp5' = propAfter(follows, valueProp5, at(clickWant(node), 5)),
|
|
825
|
+
valueProp6' = propAfter(follows, valueProp6, at(clickWant(node), 6)),
|
|
826
|
+
requested' = if (reqMoves(clickWant(node))) clickWant(node) else requested,
|
|
827
|
+
outcome' = if (reqMoves(clickWant(node))) verdict else outcome,
|
|
828
|
+
base' = if (reqMoves(clickWant(node))) selected else base,
|
|
829
|
+
requests' = if (reqMoves(clickWant(node))) requests + 1 else requests
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// commit: the host writes the controlled selection (a no-op while uncontrolled).
|
|
834
|
+
// The pool is one node at a time: a host sets a single node or clears.
|
|
835
|
+
action setValue = {
|
|
836
|
+
nondet v = Set(0, 1, 2, 4, 8, 16, 32, 64).oneOf()
|
|
837
|
+
all {
|
|
838
|
+
multi' = multi,
|
|
839
|
+
expanded' = expanded,
|
|
840
|
+
selected' = if (controlled) v else selected,
|
|
841
|
+
focus' = focus,
|
|
842
|
+
hasFocus' = hasFocus,
|
|
843
|
+
ta1' = ta1,
|
|
844
|
+
ta2' = ta2,
|
|
845
|
+
activated' = activated,
|
|
846
|
+
controlled' = controlled,
|
|
847
|
+
accepting' = accepting,
|
|
848
|
+
valueProp0' = if (controlled) at(v, 0) else valueProp0,
|
|
849
|
+
valueProp1' = if (controlled) at(v, 1) else valueProp1,
|
|
850
|
+
valueProp2' = if (controlled) at(v, 2) else valueProp2,
|
|
851
|
+
valueProp3' = if (controlled) at(v, 3) else valueProp3,
|
|
852
|
+
valueProp4' = if (controlled) at(v, 4) else valueProp4,
|
|
853
|
+
valueProp5' = if (controlled) at(v, 5) else valueProp5,
|
|
854
|
+
valueProp6' = if (controlled) at(v, 6) else valueProp6,
|
|
855
|
+
requested' = requested,
|
|
856
|
+
outcome' = if (controlled) "none" else outcome,
|
|
857
|
+
base' = base,
|
|
858
|
+
requests' = requests
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// commit: the host flips one node of its own copy of the selection
|
|
863
|
+
action hostToggle = {
|
|
864
|
+
nondet n = Set(1, 2, 3, 4, 5, 6, 7).oneOf()
|
|
865
|
+
all {
|
|
866
|
+
multi' = multi,
|
|
867
|
+
expanded' = expanded,
|
|
868
|
+
selected' = if (controlled) toggleBit(propMask, n) else selected,
|
|
869
|
+
focus' = focus,
|
|
870
|
+
hasFocus' = hasFocus,
|
|
871
|
+
ta1' = ta1,
|
|
872
|
+
ta2' = ta2,
|
|
873
|
+
activated' = activated,
|
|
874
|
+
controlled' = controlled,
|
|
875
|
+
accepting' = accepting,
|
|
876
|
+
valueProp0' = if (and(controlled, n == 1)) not(valueProp0) else valueProp0,
|
|
877
|
+
valueProp1' = if (and(controlled, n == 2)) not(valueProp1) else valueProp1,
|
|
878
|
+
valueProp2' = if (and(controlled, n == 3)) not(valueProp2) else valueProp2,
|
|
879
|
+
valueProp3' = if (and(controlled, n == 4)) not(valueProp3) else valueProp3,
|
|
880
|
+
valueProp4' = if (and(controlled, n == 5)) not(valueProp4) else valueProp4,
|
|
881
|
+
valueProp5' = if (and(controlled, n == 6)) not(valueProp5) else valueProp5,
|
|
882
|
+
valueProp6' = if (and(controlled, n == 7)) not(valueProp6) else valueProp6,
|
|
883
|
+
requested' = requested,
|
|
884
|
+
outcome' = if (controlled) "none" else outcome,
|
|
885
|
+
base' = base,
|
|
886
|
+
requests' = requests
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// configuration: the host starts owning the selection, adopting the current nodes
|
|
891
|
+
action control = all {
|
|
892
|
+
multi' = multi,
|
|
893
|
+
expanded' = expanded,
|
|
894
|
+
selected' = selected,
|
|
895
|
+
focus' = focus,
|
|
896
|
+
hasFocus' = hasFocus,
|
|
897
|
+
ta1' = ta1,
|
|
898
|
+
ta2' = ta2,
|
|
899
|
+
activated' = activated,
|
|
900
|
+
controlled' = true,
|
|
901
|
+
accepting' = accepting,
|
|
902
|
+
valueProp0' = at(selected, 0),
|
|
903
|
+
valueProp1' = at(selected, 1),
|
|
904
|
+
valueProp2' = at(selected, 2),
|
|
905
|
+
valueProp3' = at(selected, 3),
|
|
906
|
+
valueProp4' = at(selected, 4),
|
|
907
|
+
valueProp5' = at(selected, 5),
|
|
908
|
+
valueProp6' = at(selected, 6),
|
|
909
|
+
requested' = requested,
|
|
910
|
+
outcome' = outcome,
|
|
911
|
+
base' = base,
|
|
912
|
+
requests' = requests
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// configuration: the controlled host answers every request with its intent
|
|
916
|
+
action acceptRequests = all {
|
|
917
|
+
multi' = multi,
|
|
918
|
+
expanded' = expanded,
|
|
919
|
+
selected' = selected,
|
|
920
|
+
focus' = focus,
|
|
921
|
+
hasFocus' = hasFocus,
|
|
922
|
+
ta1' = ta1,
|
|
923
|
+
ta2' = ta2,
|
|
924
|
+
activated' = activated,
|
|
925
|
+
controlled' = controlled,
|
|
926
|
+
accepting' = true,
|
|
927
|
+
valueProp0' = valueProp0,
|
|
928
|
+
valueProp1' = valueProp1,
|
|
929
|
+
valueProp2' = valueProp2,
|
|
930
|
+
valueProp3' = valueProp3,
|
|
931
|
+
valueProp4' = valueProp4,
|
|
932
|
+
valueProp5' = valueProp5,
|
|
933
|
+
valueProp6' = valueProp6,
|
|
934
|
+
requested' = requested,
|
|
935
|
+
outcome' = outcome,
|
|
936
|
+
base' = base,
|
|
937
|
+
requests' = requests
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
action step = any {
|
|
941
|
+
init, configure, focusTree, blurTree,
|
|
942
|
+
pressDown, pressUp, pressRight, pressLeft, pressHome, pressEnd,
|
|
943
|
+
pressEnter, pressSpace, pressStar, typeChar, typeaheadReset,
|
|
944
|
+
pressShiftDown, pressShiftUp, pressCtrlA, click,
|
|
945
|
+
setValue, hostToggle, control, acceptRequests
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// ---- invariants ---------------------------------------------------------
|
|
949
|
+
|
|
950
|
+
// the roving node exists
|
|
951
|
+
val FocusKnown: bool = and(focus >= 1, focus <= 7)
|
|
952
|
+
// APG: focus only ever rests on a focusable node — every ancestor is open
|
|
953
|
+
val FocusVisible: bool = visible(focus, expanded)
|
|
954
|
+
// roving tabindex: exactly one treeitem has tabindex=0
|
|
955
|
+
val ExactlyOneTabbable: bool = tabbableCount(focus) == 1
|
|
956
|
+
// masks stay within the node set
|
|
957
|
+
val MasksInRange: bool = and(expanded >= 0, expanded <= allMask, selected >= 0, selected <= allMask)
|
|
958
|
+
|
|
959
|
+
def leafNeverOpen(n: int, e: int): bool = or(isParent(n), not(bit(e, n)))
|
|
960
|
+
// end nodes are never recorded as expanded
|
|
961
|
+
val LeafNeverExpanded: bool = and(
|
|
962
|
+
leafNeverOpen(1, expanded), leafNeverOpen(2, expanded), leafNeverOpen(3, expanded),
|
|
963
|
+
leafNeverOpen(4, expanded), leafNeverOpen(5, expanded), leafNeverOpen(6, expanded),
|
|
964
|
+
leafNeverOpen(7, expanded)
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
// aria-expanded is rendered on exactly the parent nodes
|
|
968
|
+
def ariaExpandedOnlyOnParent(n: int, e: int): bool =
|
|
969
|
+
isParent(n) == (ariaExpanded(n, e) != "none")
|
|
970
|
+
val AriaExpandedOnlyOnParents: bool = and(
|
|
971
|
+
ariaExpandedOnlyOnParent(1, expanded), ariaExpandedOnlyOnParent(2, expanded),
|
|
972
|
+
ariaExpandedOnlyOnParent(3, expanded), ariaExpandedOnlyOnParent(4, expanded),
|
|
973
|
+
ariaExpandedOnlyOnParent(5, expanded), ariaExpandedOnlyOnParent(6, expanded),
|
|
974
|
+
ariaExpandedOnlyOnParent(7, expanded)
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
// aria-level is one deeper than the parent's; posinset lies in 1..setsize
|
|
978
|
+
def ariaPositionOk(n: int): bool =
|
|
979
|
+
and(
|
|
980
|
+
ariaLevel(n) == (if (parentOf(n) == 0) 1 else ariaLevel(parentOf(n)) + 1),
|
|
981
|
+
ariaPosinset(n) >= 1,
|
|
982
|
+
ariaPosinset(n) <= ariaSetsize(n)
|
|
983
|
+
)
|
|
984
|
+
val AriaPositionConsistent: bool = and(
|
|
985
|
+
ariaPositionOk(1), ariaPositionOk(2), ariaPositionOk(3), ariaPositionOk(4),
|
|
986
|
+
ariaPositionOk(5), ariaPositionOk(6), ariaPositionOk(7)
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
// a single-select tree never has more than one aria-selected node (uncontrolled:
|
|
990
|
+
// a controlled host owns the selection and may hand us any set)
|
|
991
|
+
val SingleSelectAtMostOne: bool = ariaSingleHoldsAtMostOne(controlled, multi, popcount(selected))
|
|
992
|
+
// the type-ahead buffer fills left to right
|
|
993
|
+
val TypeaheadBufferShape: bool = or(ta1 != "", ta2 == "")
|
|
994
|
+
// activation only ever reports a real node
|
|
995
|
+
val ActivatedKnown: bool = and(activated >= 0, activated <= 7)
|
|
996
|
+
// callbacks only ever count up from zero
|
|
997
|
+
val RequestsNonNegative: bool = requests >= 0
|
|
998
|
+
// controlled: only the value prop sets the selection, so no event commits
|
|
999
|
+
val ControlledFollowsProp: bool = or(not(controlled), selected == propMask)
|
|
1000
|
+
// a rejected request leaves the selection as it found it
|
|
1001
|
+
val RejectedRequestUntouched: bool = or(not(outcome == "rejected"), selected == base)
|
|
1002
|
+
// an accepted request committed exactly its intent
|
|
1003
|
+
val AcceptedRequestCommits: bool = or(not(outcome == "accepted"), selected == requested)
|
|
1004
|
+
}
|