@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,801 @@
|
|
|
1
|
+
// APG Listbox, rearrangeable options —
|
|
2
|
+
// https://www.w3.org/WAI/ARIA/apg/patterns/listbox/examples/listbox-rearrangeable/
|
|
3
|
+
// (and the pattern page https://www.w3.org/WAI/ARIA/apg/patterns/listbox/)
|
|
4
|
+
//
|
|
5
|
+
// Two listboxes, A and B, share four options (ids 0..3); every option sits in
|
|
6
|
+
// exactly one list. Initially A holds all four in order. The machine covers
|
|
7
|
+
// both examples of the page:
|
|
8
|
+
// example 1 (single-select, multi = false) Up/Down reorder the focused option
|
|
9
|
+
// inside its list (Alt+Up / Alt+Down); "Not Important"/"Important"
|
|
10
|
+
// move it to the other list (Delete); selection follows focus.
|
|
11
|
+
// example 2 (multi-select, multi = true) Space toggles; "Add"/"Remove" move
|
|
12
|
+
// every selected option of the focused list to the other list
|
|
13
|
+
// (Enter / Delete); arrows move focus only.
|
|
14
|
+
// The same transfer rule serves both: it moves the SELECTED options of the
|
|
15
|
+
// focused list, and in single-select mode "the selected option" is the focused
|
|
16
|
+
// one because selection follows focus.
|
|
17
|
+
//
|
|
18
|
+
// Keyboard facts modelled from the example's "Accessibility Features" and
|
|
19
|
+
// keyboard tables:
|
|
20
|
+
// - Tab entry: the first selected option, else the first option
|
|
21
|
+
// - Down/Up/Home/End move focus (and select it in the single-select example)
|
|
22
|
+
// - after a reorder, focus stays on the moved option
|
|
23
|
+
// - after a transfer, focus in the source list lands on the first remaining
|
|
24
|
+
// option after the removed one; if none follows, on the last remaining one
|
|
25
|
+
// - the options keep their order when they move to the other list (appended)
|
|
26
|
+
//
|
|
27
|
+
// State: the option ids in each list's four slots (4 = empty slot, lists are
|
|
28
|
+
// packed to the left), the active option of each list, which list is focused,
|
|
29
|
+
// the selection as a bitmask over option ids (selection is a property of the
|
|
30
|
+
// option, so it survives reordering without any remapping), and the mode.
|
|
31
|
+
//
|
|
32
|
+
// Request vs commit (map ticket rt1p). Every event a user would see as a
|
|
33
|
+
// selection change — focus_a/focus_b (the single-select entry select), the plain
|
|
34
|
+
// moves, Space, transfer (which deselects the moved options) and set_multi (the
|
|
35
|
+
// switch back to single-select) — is a *request* carrying the intended selection
|
|
36
|
+
// mask. It fires onValueChange (`requests` counts the calls) when the intent
|
|
37
|
+
// differs from the current selection, and the selection commits through
|
|
38
|
+
// `landed(holds, sel, intent)`:
|
|
39
|
+
// uncontrolled accepted: the selection commits the intent
|
|
40
|
+
// controlled, holds rejected: the selection is untouched
|
|
41
|
+
// controlled, accepts accepted through the controlled value (hostWrites)
|
|
42
|
+
// The controlled value is `valueProp0..valueProp3` (one bool per option id), the
|
|
43
|
+
// only thing that changes the selection while `controlled`; `setValue` /
|
|
44
|
+
// `hostToggle` are the host writing it. `control` / `acceptRequests` are
|
|
45
|
+
// configuration events (init is literal-only). A rejected transfer moves nothing
|
|
46
|
+
// at all (its list surgery is gated on the request landing), so a rejected
|
|
47
|
+
// request leaves the whole widget consistent.
|
|
48
|
+
//
|
|
49
|
+
// Choices (ponytail): a transferred option arrives deselected (selection is a
|
|
50
|
+
// per-list concept); toggling the mode to single-select collapses each list's
|
|
51
|
+
// selection to its active option; Alt+Up/Down are only handled in single-select
|
|
52
|
+
// mode (example 2 has no reorder buttons); the toolbar buttons are modelled by
|
|
53
|
+
// their keyboard shortcuts on the focused list, so a transfer with nothing
|
|
54
|
+
// selected or no focused list is a no-op.
|
|
55
|
+
//
|
|
56
|
+
// Deliberate omissions: the live-region "Last change" announcements, the
|
|
57
|
+
// toolbar roving tabindex, aria-keyshortcuts, typeahead (listbox-typeahead.qnt),
|
|
58
|
+
// scrolling, disabled buttons, and add/remove of brand-new options (the option
|
|
59
|
+
// set is fixed at four).
|
|
60
|
+
//
|
|
61
|
+
// Generator notes: no parameterized actions and no `let`, so a request spells
|
|
62
|
+
// `landed(...)` out per assignment and every action assigns every var.
|
|
63
|
+
module apg_listbox_rearrangeable {
|
|
64
|
+
import aria.* from "../../shared/aria"
|
|
65
|
+
|
|
66
|
+
var a0: int // list A, slot 0..3: option id (noItem = empty)
|
|
67
|
+
var a1: int // list A, slot 1
|
|
68
|
+
var a2: int // list A, slot 2
|
|
69
|
+
var a3: int // list A, slot 3
|
|
70
|
+
var b0: int // list B, slot 0..3: option id (noItem = empty)
|
|
71
|
+
var b1: int // list B, slot 1
|
|
72
|
+
var b2: int // list B, slot 2
|
|
73
|
+
var b3: int // list B, slot 3
|
|
74
|
+
var activeA: int // active option of list A (noItem = list empty)
|
|
75
|
+
var activeB: int // active option of list B (noItem = list empty)
|
|
76
|
+
var focusList: str // "none" | "a" | "b": the listbox holding focus
|
|
77
|
+
var sel: int // selection bitmask over option ids
|
|
78
|
+
var multi: bool // mode: multi-select (example 2) vs single-select (example 1)
|
|
79
|
+
var controlled: bool // the host owns the value (`value` prop present)
|
|
80
|
+
var accepting: bool // controlled host that answers every request with its intent
|
|
81
|
+
var valueProp0: bool // the controlled value prop, one bool per option id
|
|
82
|
+
var valueProp1: bool
|
|
83
|
+
var valueProp2: bool
|
|
84
|
+
var valueProp3: bool
|
|
85
|
+
var requested: int // intent of the last request (selection mask)
|
|
86
|
+
var outcome: str // "none" | "accepted" | "rejected"
|
|
87
|
+
var base: int // selection when the last request was received
|
|
88
|
+
var requests: int // number of requests that fired onValueChange
|
|
89
|
+
|
|
90
|
+
def itemCount: int = 4
|
|
91
|
+
// "no option" / "empty slot" / "not found" sentinel
|
|
92
|
+
def noItem: int = 4
|
|
93
|
+
|
|
94
|
+
def pow2(i: int): int =
|
|
95
|
+
if (i == 0) 1
|
|
96
|
+
else if (i == 1) 2
|
|
97
|
+
else if (i == 2) 4
|
|
98
|
+
else if (i == 3) 8
|
|
99
|
+
else 16
|
|
100
|
+
// bit i of a selection mask: option i is selected
|
|
101
|
+
def bit(mask: int, i: int): bool = (mask / pow2(i)) % 2 == 1
|
|
102
|
+
def popcount(mask: int): int =
|
|
103
|
+
(if (bit(mask, 0)) 1 else 0)
|
|
104
|
+
+ (if (bit(mask, 1)) 1 else 0)
|
|
105
|
+
+ (if (bit(mask, 2)) 1 else 0)
|
|
106
|
+
+ (if (bit(mask, 3)) 1 else 0)
|
|
107
|
+
def toggle(mask: int, i: int): int =
|
|
108
|
+
if (bit(mask, i)) mask - pow2(i) else mask + pow2(i)
|
|
109
|
+
def focusBit(opt: int): int = if (opt == noItem) 0 else pow2(opt)
|
|
110
|
+
|
|
111
|
+
// --- one list, as four slots ----------------------------------------------
|
|
112
|
+
def slotAt(s0: int, s1: int, s2: int, s3: int, k: int): int =
|
|
113
|
+
if (k == 0) s0
|
|
114
|
+
else if (k == 1) s1
|
|
115
|
+
else if (k == 2) s2
|
|
116
|
+
else if (k == 3) s3
|
|
117
|
+
else noItem
|
|
118
|
+
def lenOf(s0: int, s1: int, s2: int, s3: int): int =
|
|
119
|
+
(if (s0 != noItem) 1 else 0)
|
|
120
|
+
+ (if (s1 != noItem) 1 else 0)
|
|
121
|
+
+ (if (s2 != noItem) 1 else 0)
|
|
122
|
+
+ (if (s3 != noItem) 1 else 0)
|
|
123
|
+
def posOf(s0: int, s1: int, s2: int, s3: int, opt: int): int =
|
|
124
|
+
if (opt == noItem) noItem
|
|
125
|
+
else if (s0 == opt) 0
|
|
126
|
+
else if (s1 == opt) 1
|
|
127
|
+
else if (s2 == opt) 2
|
|
128
|
+
else if (s3 == opt) 3
|
|
129
|
+
else noItem
|
|
130
|
+
def holds(s0: int, s1: int, s2: int, s3: int, opt: int): bool =
|
|
131
|
+
and(opt != noItem, or(s0 == opt, s1 == opt, s2 == opt, s3 == opt))
|
|
132
|
+
def selOf(mask: int, opt: int): int = if (opt == noItem) 0 else (if (bit(mask, opt)) pow2(opt) else 0)
|
|
133
|
+
// the selected options that are in this list, as a mask
|
|
134
|
+
def selIn(mask: int, s0: int, s1: int, s2: int, s3: int): int =
|
|
135
|
+
selOf(mask, s0) + selOf(mask, s1) + selOf(mask, s2) + selOf(mask, s3)
|
|
136
|
+
// Tab entry: the first selected option of the list, else its first option
|
|
137
|
+
def entryItem(mask: int, s0: int, s1: int, s2: int, s3: int): int =
|
|
138
|
+
if (selOf(mask, s0) != 0) s0
|
|
139
|
+
else if (selOf(mask, s1) != 0) s1
|
|
140
|
+
else if (selOf(mask, s2) != 0) s2
|
|
141
|
+
else if (selOf(mask, s3) != 0) s3
|
|
142
|
+
else s0
|
|
143
|
+
|
|
144
|
+
// --- keyboard targets inside a list -------------------------------------
|
|
145
|
+
def nextItem(s0: int, s1: int, s2: int, s3: int, act: int): int =
|
|
146
|
+
if (posOf(s0, s1, s2, s3, act) == noItem) noItem
|
|
147
|
+
else slotAt(s0, s1, s2, s3, if (posOf(s0, s1, s2, s3, act) + 1 < lenOf(s0, s1, s2, s3)) posOf(s0, s1, s2, s3, act) + 1 else posOf(s0, s1, s2, s3, act))
|
|
148
|
+
def prevItem(s0: int, s1: int, s2: int, s3: int, act: int): int =
|
|
149
|
+
if (posOf(s0, s1, s2, s3, act) == noItem) noItem
|
|
150
|
+
else slotAt(s0, s1, s2, s3, if (posOf(s0, s1, s2, s3, act) > 0) posOf(s0, s1, s2, s3, act) - 1 else posOf(s0, s1, s2, s3, act))
|
|
151
|
+
def lastItem(s0: int, s1: int, s2: int, s3: int): int = slotAt(s0, s1, s2, s3, lenOf(s0, s1, s2, s3) - 1)
|
|
152
|
+
|
|
153
|
+
// --- reordering (Alt+Up / Alt+Down) ---------------------------------------
|
|
154
|
+
def canUp(s0: int, s1: int, s2: int, s3: int, act: int): bool = and(act != noItem, posOf(s0, s1, s2, s3, act) > 0)
|
|
155
|
+
def canDown(s0: int, s1: int, s2: int, s3: int, act: int): bool =
|
|
156
|
+
and(act != noItem, posOf(s0, s1, s2, s3, act) + 1 < lenOf(s0, s1, s2, s3))
|
|
157
|
+
def swapUpSlot(s0: int, s1: int, s2: int, s3: int, act: int, k: int): int =
|
|
158
|
+
if (not(canUp(s0, s1, s2, s3, act))) slotAt(s0, s1, s2, s3, k)
|
|
159
|
+
else if (k == posOf(s0, s1, s2, s3, act) - 1) slotAt(s0, s1, s2, s3, posOf(s0, s1, s2, s3, act))
|
|
160
|
+
else if (k == posOf(s0, s1, s2, s3, act)) slotAt(s0, s1, s2, s3, posOf(s0, s1, s2, s3, act) - 1)
|
|
161
|
+
else slotAt(s0, s1, s2, s3, k)
|
|
162
|
+
def swapDownSlot(s0: int, s1: int, s2: int, s3: int, act: int, k: int): int =
|
|
163
|
+
if (not(canDown(s0, s1, s2, s3, act))) slotAt(s0, s1, s2, s3, k)
|
|
164
|
+
else if (k == posOf(s0, s1, s2, s3, act)) slotAt(s0, s1, s2, s3, posOf(s0, s1, s2, s3, act) + 1)
|
|
165
|
+
else if (k == posOf(s0, s1, s2, s3, act) + 1) slotAt(s0, s1, s2, s3, posOf(s0, s1, s2, s3, act))
|
|
166
|
+
else slotAt(s0, s1, s2, s3, k)
|
|
167
|
+
|
|
168
|
+
// --- transferring the selected options to the other list -------------------
|
|
169
|
+
def keeps(mask: int, opt: int): bool = and(opt != noItem, not(bit(mask, opt)))
|
|
170
|
+
def moves(mask: int, opt: int): bool = and(opt != noItem, bit(mask, opt))
|
|
171
|
+
def keepsN(mask: int, opt: int): int = if (keeps(mask, opt)) 1 else 0
|
|
172
|
+
def movesN(mask: int, opt: int): int = if (moves(mask, opt)) 1 else 0
|
|
173
|
+
// how many kept / moved options sit in slots before slot j
|
|
174
|
+
def keptBefore(mask: int, s0: int, s1: int, s2: int, s3: int, j: int): int =
|
|
175
|
+
if (j == 0) 0
|
|
176
|
+
else if (j == 1) keepsN(mask, s0)
|
|
177
|
+
else if (j == 2) keepsN(mask, s0) + keepsN(mask, s1)
|
|
178
|
+
else keepsN(mask, s0) + keepsN(mask, s1) + keepsN(mask, s2)
|
|
179
|
+
def movedBefore(mask: int, s0: int, s1: int, s2: int, s3: int, j: int): int =
|
|
180
|
+
if (j == 0) 0
|
|
181
|
+
else if (j == 1) movesN(mask, s0)
|
|
182
|
+
else if (j == 2) movesN(mask, s0) + movesN(mask, s1)
|
|
183
|
+
else movesN(mask, s0) + movesN(mask, s1) + movesN(mask, s2)
|
|
184
|
+
// the k-th option that stays / leaves, in slot order (noItem = none)
|
|
185
|
+
def keptAt(mask: int, s0: int, s1: int, s2: int, s3: int, k: int): int =
|
|
186
|
+
if (and(keeps(mask, s0), keptBefore(mask, s0, s1, s2, s3, 0) == k)) s0
|
|
187
|
+
else if (and(keeps(mask, s1), keptBefore(mask, s0, s1, s2, s3, 1) == k)) s1
|
|
188
|
+
else if (and(keeps(mask, s2), keptBefore(mask, s0, s1, s2, s3, 2) == k)) s2
|
|
189
|
+
else if (and(keeps(mask, s3), keptBefore(mask, s0, s1, s2, s3, 3) == k)) s3
|
|
190
|
+
else noItem
|
|
191
|
+
def movedAt(mask: int, s0: int, s1: int, s2: int, s3: int, k: int): int =
|
|
192
|
+
if (and(moves(mask, s0), movedBefore(mask, s0, s1, s2, s3, 0) == k)) s0
|
|
193
|
+
else if (and(moves(mask, s1), movedBefore(mask, s0, s1, s2, s3, 1) == k)) s1
|
|
194
|
+
else if (and(moves(mask, s2), movedBefore(mask, s0, s1, s2, s3, 2) == k)) s2
|
|
195
|
+
else if (and(moves(mask, s3), movedBefore(mask, s0, s1, s2, s3, 3) == k)) s3
|
|
196
|
+
else noItem
|
|
197
|
+
def movedCount(mask: int, s0: int, s1: int, s2: int, s3: int): int = popcount(selIn(mask, s0, s1, s2, s3))
|
|
198
|
+
// the destination list: its options, then the moved ones in source order
|
|
199
|
+
def appendAt(mask: int, t0: int, t1: int, t2: int, t3: int, s0: int, s1: int, s2: int, s3: int, k: int): int =
|
|
200
|
+
if (k < lenOf(t0, t1, t2, t3)) slotAt(t0, t1, t2, t3, k)
|
|
201
|
+
else movedAt(mask, s0, s1, s2, s3, k - lenOf(t0, t1, t2, t3))
|
|
202
|
+
// the source list's focus after its selected options left: stay if the
|
|
203
|
+
// focused option stays, else the first kept option after it, else the last
|
|
204
|
+
// kept option before it
|
|
205
|
+
def focusAfterRemoval(mask: int, s0: int, s1: int, s2: int, s3: int, act: int): int =
|
|
206
|
+
if (act == noItem) noItem
|
|
207
|
+
else if (not(moves(mask, act))) act
|
|
208
|
+
else if (and(posOf(s0, s1, s2, s3, act) < 1, keeps(mask, s1))) s1
|
|
209
|
+
else if (and(posOf(s0, s1, s2, s3, act) < 2, keeps(mask, s2))) s2
|
|
210
|
+
else if (and(posOf(s0, s1, s2, s3, act) < 3, keeps(mask, s3))) s3
|
|
211
|
+
else if (and(posOf(s0, s1, s2, s3, act) > 2, keeps(mask, s2))) s2
|
|
212
|
+
else if (and(posOf(s0, s1, s2, s3, act) > 1, keeps(mask, s1))) s1
|
|
213
|
+
else if (and(posOf(s0, s1, s2, s3, act) > 0, keeps(mask, s0))) s0
|
|
214
|
+
else noItem
|
|
215
|
+
|
|
216
|
+
// --- request / commit (map ticket rt1p) ---------------------------------
|
|
217
|
+
def b2i(b: bool): int = if (b) 1 else 0
|
|
218
|
+
// the controlled value prop, packed into a mask
|
|
219
|
+
def propsMask: int =
|
|
220
|
+
b2i(valueProp0) + 2 * b2i(valueProp1) + 4 * b2i(valueProp2) + 8 * b2i(valueProp3)
|
|
221
|
+
// the host flips one selected option of its own copy of the value
|
|
222
|
+
def flipAt(n: int, i: int): int = if (bit(n, i)) n - pow2(i) else n + pow2(i)
|
|
223
|
+
// controlled host that keeps the old value: the request is rejected
|
|
224
|
+
def rejects: bool = and(controlled, not(accepting))
|
|
225
|
+
// controlled host that writes the intent back through the value prop
|
|
226
|
+
def hostWrites: bool = and(controlled, accepting)
|
|
227
|
+
def verdict: str = if (rejects) "rejected" else "accepted"
|
|
228
|
+
// the value after a request for `want`
|
|
229
|
+
def landed(reject: bool, current: int, want: int): int = if (reject) current else want
|
|
230
|
+
// one value-prop member after a request for `want`
|
|
231
|
+
def propBit(writes: bool, current: bool, want: int, i: int): bool =
|
|
232
|
+
if (writes) bit(want, i) else current
|
|
233
|
+
// the intended selection of each request
|
|
234
|
+
def focusAIntent: int =
|
|
235
|
+
if (and(not(multi), not(focusList == "a"))) selIn(sel, b0, b1, b2, b3) + focusBit(entryItem(sel, a0, a1, a2, a3)) else sel
|
|
236
|
+
def focusBIntent: int =
|
|
237
|
+
if (and(not(multi), not(focusList == "b"))) selIn(sel, a0, a1, a2, a3) + focusBit(entryItem(sel, b0, b1, b2, b3)) else sel
|
|
238
|
+
def downIntent: int =
|
|
239
|
+
if (and(not(multi), focusList == "a")) selIn(sel, b0, b1, b2, b3) + focusBit(nextItem(a0, a1, a2, a3, activeA))
|
|
240
|
+
else if (and(not(multi), focusList == "b")) selIn(sel, a0, a1, a2, a3) + focusBit(nextItem(b0, b1, b2, b3, activeB))
|
|
241
|
+
else sel
|
|
242
|
+
def upIntent: int =
|
|
243
|
+
if (and(not(multi), focusList == "a")) selIn(sel, b0, b1, b2, b3) + focusBit(prevItem(a0, a1, a2, a3, activeA))
|
|
244
|
+
else if (and(not(multi), focusList == "b")) selIn(sel, a0, a1, a2, a3) + focusBit(prevItem(b0, b1, b2, b3, activeB))
|
|
245
|
+
else sel
|
|
246
|
+
def homeIntent: int =
|
|
247
|
+
if (and(not(multi), focusList == "a")) selIn(sel, b0, b1, b2, b3) + focusBit(a0)
|
|
248
|
+
else if (and(not(multi), focusList == "b")) selIn(sel, a0, a1, a2, a3) + focusBit(b0)
|
|
249
|
+
else sel
|
|
250
|
+
def endIntent: int =
|
|
251
|
+
if (and(not(multi), focusList == "a")) selIn(sel, b0, b1, b2, b3) + focusBit(lastItem(a0, a1, a2, a3))
|
|
252
|
+
else if (and(not(multi), focusList == "b")) selIn(sel, a0, a1, a2, a3) + focusBit(lastItem(b0, b1, b2, b3))
|
|
253
|
+
else sel
|
|
254
|
+
def spaceIntent: int =
|
|
255
|
+
if (and(multi, focusList == "a", activeA != noItem)) toggle(sel, activeA)
|
|
256
|
+
else if (and(multi, focusList == "b", activeB != noItem)) toggle(sel, activeB)
|
|
257
|
+
else sel
|
|
258
|
+
def transferIntent: int =
|
|
259
|
+
if (and(focusList == "a", movedCount(sel, a0, a1, a2, a3) > 0)) selIn(sel, b0, b1, b2, b3) + (if (multi) 0 else focusBit(focusAfterRemoval(sel, a0, a1, a2, a3, activeA)))
|
|
260
|
+
else if (and(focusList == "b", movedCount(sel, b0, b1, b2, b3) > 0)) selIn(sel, a0, a1, a2, a3) + (if (multi) 0 else focusBit(focusAfterRemoval(sel, b0, b1, b2, b3, activeB)))
|
|
261
|
+
else sel
|
|
262
|
+
def setMultiIntent(flag: bool): int =
|
|
263
|
+
if (flag) sel else focusBit(activeA) + focusBit(activeB)
|
|
264
|
+
|
|
265
|
+
// --- ARIA contract, derived from the state ------------------------------
|
|
266
|
+
def ariaSelected(mask: int, opt: int): bool = bit(mask, opt)
|
|
267
|
+
def ariaActiveDescendant(focusedList: str, which: str, act: int): int =
|
|
268
|
+
if (focusedList == which) act else noItem
|
|
269
|
+
def listboxTabIndex: int = 0
|
|
270
|
+
|
|
271
|
+
// --- invariant helpers ----------------------------------------------------
|
|
272
|
+
def occurrences(opt: int): int =
|
|
273
|
+
(if (a0 == opt) 1 else 0)
|
|
274
|
+
+ (if (a1 == opt) 1 else 0)
|
|
275
|
+
+ (if (a2 == opt) 1 else 0)
|
|
276
|
+
+ (if (a3 == opt) 1 else 0)
|
|
277
|
+
+ (if (b0 == opt) 1 else 0)
|
|
278
|
+
+ (if (b1 == opt) 1 else 0)
|
|
279
|
+
+ (if (b2 == opt) 1 else 0)
|
|
280
|
+
+ (if (b3 == opt) 1 else 0)
|
|
281
|
+
def packed(s0: int, s1: int, s2: int, s3: int): bool =
|
|
282
|
+
and(or(s1 == noItem, s0 != noItem), or(s2 == noItem, s1 != noItem), or(s3 == noItem, s2 != noItem))
|
|
283
|
+
def activeValid(s0: int, s1: int, s2: int, s3: int, act: int): bool =
|
|
284
|
+
if (s0 == noItem) act == noItem else holds(s0, s1, s2, s3, act)
|
|
285
|
+
|
|
286
|
+
action init = all {
|
|
287
|
+
a0' = 0,
|
|
288
|
+
a1' = 1,
|
|
289
|
+
a2' = 2,
|
|
290
|
+
a3' = 3,
|
|
291
|
+
b0' = noItem,
|
|
292
|
+
b1' = noItem,
|
|
293
|
+
b2' = noItem,
|
|
294
|
+
b3' = noItem,
|
|
295
|
+
activeA' = 0,
|
|
296
|
+
activeB' = noItem,
|
|
297
|
+
focusList' = "none",
|
|
298
|
+
sel' = 0,
|
|
299
|
+
multi' = false,
|
|
300
|
+
controlled' = false,
|
|
301
|
+
accepting' = false,
|
|
302
|
+
valueProp0' = false,
|
|
303
|
+
valueProp1' = false,
|
|
304
|
+
valueProp2' = false,
|
|
305
|
+
valueProp3' = false,
|
|
306
|
+
requested' = 0,
|
|
307
|
+
outcome' = "none",
|
|
308
|
+
base' = 0,
|
|
309
|
+
requests' = 0
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Tab into list A (first selected option, else first option).
|
|
313
|
+
action focus_a = all {
|
|
314
|
+
a0' = a0,
|
|
315
|
+
a1' = a1,
|
|
316
|
+
a2' = a2,
|
|
317
|
+
a3' = a3,
|
|
318
|
+
b0' = b0,
|
|
319
|
+
b1' = b1,
|
|
320
|
+
b2' = b2,
|
|
321
|
+
b3' = b3,
|
|
322
|
+
activeA' = if (focusList == "a") activeA else entryItem(sel, a0, a1, a2, a3),
|
|
323
|
+
activeB' = activeB,
|
|
324
|
+
focusList' = "a",
|
|
325
|
+
sel' = landed(rejects, sel, focusAIntent),
|
|
326
|
+
multi' = multi,
|
|
327
|
+
controlled' = controlled,
|
|
328
|
+
accepting' = accepting,
|
|
329
|
+
valueProp0' = propBit(hostWrites, valueProp0, focusAIntent, 0),
|
|
330
|
+
valueProp1' = propBit(hostWrites, valueProp1, focusAIntent, 1),
|
|
331
|
+
valueProp2' = propBit(hostWrites, valueProp2, focusAIntent, 2),
|
|
332
|
+
valueProp3' = propBit(hostWrites, valueProp3, focusAIntent, 3),
|
|
333
|
+
requested' = if (focusAIntent != sel) focusAIntent else requested,
|
|
334
|
+
outcome' = if (focusAIntent != sel) verdict else outcome,
|
|
335
|
+
base' = if (focusAIntent != sel) sel else base,
|
|
336
|
+
requests' = if (focusAIntent != sel) requests + 1 else requests
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Tab into list B.
|
|
340
|
+
action focus_b = all {
|
|
341
|
+
a0' = a0,
|
|
342
|
+
a1' = a1,
|
|
343
|
+
a2' = a2,
|
|
344
|
+
a3' = a3,
|
|
345
|
+
b0' = b0,
|
|
346
|
+
b1' = b1,
|
|
347
|
+
b2' = b2,
|
|
348
|
+
b3' = b3,
|
|
349
|
+
activeA' = activeA,
|
|
350
|
+
activeB' = if (focusList == "b") activeB else entryItem(sel, b0, b1, b2, b3),
|
|
351
|
+
focusList' = "b",
|
|
352
|
+
sel' = landed(rejects, sel, focusBIntent),
|
|
353
|
+
multi' = multi,
|
|
354
|
+
controlled' = controlled,
|
|
355
|
+
accepting' = accepting,
|
|
356
|
+
valueProp0' = propBit(hostWrites, valueProp0, focusBIntent, 0),
|
|
357
|
+
valueProp1' = propBit(hostWrites, valueProp1, focusBIntent, 1),
|
|
358
|
+
valueProp2' = propBit(hostWrites, valueProp2, focusBIntent, 2),
|
|
359
|
+
valueProp3' = propBit(hostWrites, valueProp3, focusBIntent, 3),
|
|
360
|
+
requested' = if (focusBIntent != sel) focusBIntent else requested,
|
|
361
|
+
outcome' = if (focusBIntent != sel) verdict else outcome,
|
|
362
|
+
base' = if (focusBIntent != sel) sel else base,
|
|
363
|
+
requests' = if (focusBIntent != sel) requests + 1 else requests
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Tab out of whichever list holds focus.
|
|
367
|
+
action blur = all {
|
|
368
|
+
a0' = a0,
|
|
369
|
+
a1' = a1,
|
|
370
|
+
a2' = a2,
|
|
371
|
+
a3' = a3,
|
|
372
|
+
b0' = b0,
|
|
373
|
+
b1' = b1,
|
|
374
|
+
b2' = b2,
|
|
375
|
+
b3' = b3,
|
|
376
|
+
activeA' = activeA,
|
|
377
|
+
activeB' = activeB,
|
|
378
|
+
focusList' = "none",
|
|
379
|
+
sel' = sel,
|
|
380
|
+
multi' = multi,
|
|
381
|
+
controlled' = controlled,
|
|
382
|
+
accepting' = accepting,
|
|
383
|
+
valueProp0' = valueProp0,
|
|
384
|
+
valueProp1' = valueProp1,
|
|
385
|
+
valueProp2' = valueProp2,
|
|
386
|
+
valueProp3' = valueProp3,
|
|
387
|
+
requested' = requested,
|
|
388
|
+
outcome' = outcome,
|
|
389
|
+
base' = base,
|
|
390
|
+
requests' = requests
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Move focus inside the focused list (and select it in single-select mode).
|
|
394
|
+
action arrow_down = all {
|
|
395
|
+
a0' = a0,
|
|
396
|
+
a1' = a1,
|
|
397
|
+
a2' = a2,
|
|
398
|
+
a3' = a3,
|
|
399
|
+
b0' = b0,
|
|
400
|
+
b1' = b1,
|
|
401
|
+
b2' = b2,
|
|
402
|
+
b3' = b3,
|
|
403
|
+
activeA' = if (focusList == "a") nextItem(a0, a1, a2, a3, activeA) else activeA,
|
|
404
|
+
activeB' = if (focusList == "b") nextItem(b0, b1, b2, b3, activeB) else activeB,
|
|
405
|
+
focusList' = focusList,
|
|
406
|
+
sel' = landed(rejects, sel, downIntent),
|
|
407
|
+
multi' = multi,
|
|
408
|
+
controlled' = controlled,
|
|
409
|
+
accepting' = accepting,
|
|
410
|
+
valueProp0' = propBit(hostWrites, valueProp0, downIntent, 0),
|
|
411
|
+
valueProp1' = propBit(hostWrites, valueProp1, downIntent, 1),
|
|
412
|
+
valueProp2' = propBit(hostWrites, valueProp2, downIntent, 2),
|
|
413
|
+
valueProp3' = propBit(hostWrites, valueProp3, downIntent, 3),
|
|
414
|
+
requested' = if (downIntent != sel) downIntent else requested,
|
|
415
|
+
outcome' = if (downIntent != sel) verdict else outcome,
|
|
416
|
+
base' = if (downIntent != sel) sel else base,
|
|
417
|
+
requests' = if (downIntent != sel) requests + 1 else requests
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
action arrow_up = all {
|
|
421
|
+
a0' = a0,
|
|
422
|
+
a1' = a1,
|
|
423
|
+
a2' = a2,
|
|
424
|
+
a3' = a3,
|
|
425
|
+
b0' = b0,
|
|
426
|
+
b1' = b1,
|
|
427
|
+
b2' = b2,
|
|
428
|
+
b3' = b3,
|
|
429
|
+
activeA' = if (focusList == "a") prevItem(a0, a1, a2, a3, activeA) else activeA,
|
|
430
|
+
activeB' = if (focusList == "b") prevItem(b0, b1, b2, b3, activeB) else activeB,
|
|
431
|
+
focusList' = focusList,
|
|
432
|
+
sel' = landed(rejects, sel, upIntent),
|
|
433
|
+
multi' = multi,
|
|
434
|
+
controlled' = controlled,
|
|
435
|
+
accepting' = accepting,
|
|
436
|
+
valueProp0' = propBit(hostWrites, valueProp0, upIntent, 0),
|
|
437
|
+
valueProp1' = propBit(hostWrites, valueProp1, upIntent, 1),
|
|
438
|
+
valueProp2' = propBit(hostWrites, valueProp2, upIntent, 2),
|
|
439
|
+
valueProp3' = propBit(hostWrites, valueProp3, upIntent, 3),
|
|
440
|
+
requested' = if (upIntent != sel) upIntent else requested,
|
|
441
|
+
outcome' = if (upIntent != sel) verdict else outcome,
|
|
442
|
+
base' = if (upIntent != sel) sel else base,
|
|
443
|
+
requests' = if (upIntent != sel) requests + 1 else requests
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
action home = all {
|
|
447
|
+
a0' = a0,
|
|
448
|
+
a1' = a1,
|
|
449
|
+
a2' = a2,
|
|
450
|
+
a3' = a3,
|
|
451
|
+
b0' = b0,
|
|
452
|
+
b1' = b1,
|
|
453
|
+
b2' = b2,
|
|
454
|
+
b3' = b3,
|
|
455
|
+
activeA' = if (focusList == "a") a0 else activeA,
|
|
456
|
+
activeB' = if (focusList == "b") b0 else activeB,
|
|
457
|
+
focusList' = focusList,
|
|
458
|
+
sel' = landed(rejects, sel, homeIntent),
|
|
459
|
+
multi' = multi,
|
|
460
|
+
controlled' = controlled,
|
|
461
|
+
accepting' = accepting,
|
|
462
|
+
valueProp0' = propBit(hostWrites, valueProp0, homeIntent, 0),
|
|
463
|
+
valueProp1' = propBit(hostWrites, valueProp1, homeIntent, 1),
|
|
464
|
+
valueProp2' = propBit(hostWrites, valueProp2, homeIntent, 2),
|
|
465
|
+
valueProp3' = propBit(hostWrites, valueProp3, homeIntent, 3),
|
|
466
|
+
requested' = if (homeIntent != sel) homeIntent else requested,
|
|
467
|
+
outcome' = if (homeIntent != sel) verdict else outcome,
|
|
468
|
+
base' = if (homeIntent != sel) sel else base,
|
|
469
|
+
requests' = if (homeIntent != sel) requests + 1 else requests
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
action end = all {
|
|
473
|
+
a0' = a0,
|
|
474
|
+
a1' = a1,
|
|
475
|
+
a2' = a2,
|
|
476
|
+
a3' = a3,
|
|
477
|
+
b0' = b0,
|
|
478
|
+
b1' = b1,
|
|
479
|
+
b2' = b2,
|
|
480
|
+
b3' = b3,
|
|
481
|
+
activeA' = if (focusList == "a") lastItem(a0, a1, a2, a3) else activeA,
|
|
482
|
+
activeB' = if (focusList == "b") lastItem(b0, b1, b2, b3) else activeB,
|
|
483
|
+
focusList' = focusList,
|
|
484
|
+
sel' = landed(rejects, sel, endIntent),
|
|
485
|
+
multi' = multi,
|
|
486
|
+
controlled' = controlled,
|
|
487
|
+
accepting' = accepting,
|
|
488
|
+
valueProp0' = propBit(hostWrites, valueProp0, endIntent, 0),
|
|
489
|
+
valueProp1' = propBit(hostWrites, valueProp1, endIntent, 1),
|
|
490
|
+
valueProp2' = propBit(hostWrites, valueProp2, endIntent, 2),
|
|
491
|
+
valueProp3' = propBit(hostWrites, valueProp3, endIntent, 3),
|
|
492
|
+
requested' = if (endIntent != sel) endIntent else requested,
|
|
493
|
+
outcome' = if (endIntent != sel) verdict else outcome,
|
|
494
|
+
base' = if (endIntent != sel) sel else base,
|
|
495
|
+
requests' = if (endIntent != sel) requests + 1 else requests
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Multi-select: toggle the active option. Single-select: selection already
|
|
499
|
+
// follows focus, nothing to do.
|
|
500
|
+
action space = all {
|
|
501
|
+
a0' = a0,
|
|
502
|
+
a1' = a1,
|
|
503
|
+
a2' = a2,
|
|
504
|
+
a3' = a3,
|
|
505
|
+
b0' = b0,
|
|
506
|
+
b1' = b1,
|
|
507
|
+
b2' = b2,
|
|
508
|
+
b3' = b3,
|
|
509
|
+
activeA' = activeA,
|
|
510
|
+
activeB' = activeB,
|
|
511
|
+
focusList' = focusList,
|
|
512
|
+
sel' = landed(rejects, sel, spaceIntent),
|
|
513
|
+
multi' = multi,
|
|
514
|
+
controlled' = controlled,
|
|
515
|
+
accepting' = accepting,
|
|
516
|
+
valueProp0' = propBit(hostWrites, valueProp0, spaceIntent, 0),
|
|
517
|
+
valueProp1' = propBit(hostWrites, valueProp1, spaceIntent, 1),
|
|
518
|
+
valueProp2' = propBit(hostWrites, valueProp2, spaceIntent, 2),
|
|
519
|
+
valueProp3' = propBit(hostWrites, valueProp3, spaceIntent, 3),
|
|
520
|
+
requested' = if (spaceIntent != sel) spaceIntent else requested,
|
|
521
|
+
outcome' = if (spaceIntent != sel) verdict else outcome,
|
|
522
|
+
base' = if (spaceIntent != sel) sel else base,
|
|
523
|
+
requests' = if (spaceIntent != sel) requests + 1 else requests
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Alt+Up (single-select): the focused option swaps with its predecessor;
|
|
527
|
+
// focus stays on it.
|
|
528
|
+
action move_up = all {
|
|
529
|
+
a0' = if (and(not(multi), focusList == "a")) swapUpSlot(a0, a1, a2, a3, activeA, 0) else a0,
|
|
530
|
+
a1' = if (and(not(multi), focusList == "a")) swapUpSlot(a0, a1, a2, a3, activeA, 1) else a1,
|
|
531
|
+
a2' = if (and(not(multi), focusList == "a")) swapUpSlot(a0, a1, a2, a3, activeA, 2) else a2,
|
|
532
|
+
a3' = if (and(not(multi), focusList == "a")) swapUpSlot(a0, a1, a2, a3, activeA, 3) else a3,
|
|
533
|
+
b0' = if (and(not(multi), focusList == "b")) swapUpSlot(b0, b1, b2, b3, activeB, 0) else b0,
|
|
534
|
+
b1' = if (and(not(multi), focusList == "b")) swapUpSlot(b0, b1, b2, b3, activeB, 1) else b1,
|
|
535
|
+
b2' = if (and(not(multi), focusList == "b")) swapUpSlot(b0, b1, b2, b3, activeB, 2) else b2,
|
|
536
|
+
b3' = if (and(not(multi), focusList == "b")) swapUpSlot(b0, b1, b2, b3, activeB, 3) else b3,
|
|
537
|
+
activeA' = activeA,
|
|
538
|
+
activeB' = activeB,
|
|
539
|
+
focusList' = focusList,
|
|
540
|
+
sel' = sel,
|
|
541
|
+
multi' = multi,
|
|
542
|
+
controlled' = controlled,
|
|
543
|
+
accepting' = accepting,
|
|
544
|
+
valueProp0' = valueProp0,
|
|
545
|
+
valueProp1' = valueProp1,
|
|
546
|
+
valueProp2' = valueProp2,
|
|
547
|
+
valueProp3' = valueProp3,
|
|
548
|
+
requested' = requested,
|
|
549
|
+
outcome' = outcome,
|
|
550
|
+
base' = base,
|
|
551
|
+
requests' = requests
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Alt+Down (single-select).
|
|
555
|
+
action move_down = all {
|
|
556
|
+
a0' = if (and(not(multi), focusList == "a")) swapDownSlot(a0, a1, a2, a3, activeA, 0) else a0,
|
|
557
|
+
a1' = if (and(not(multi), focusList == "a")) swapDownSlot(a0, a1, a2, a3, activeA, 1) else a1,
|
|
558
|
+
a2' = if (and(not(multi), focusList == "a")) swapDownSlot(a0, a1, a2, a3, activeA, 2) else a2,
|
|
559
|
+
a3' = if (and(not(multi), focusList == "a")) swapDownSlot(a0, a1, a2, a3, activeA, 3) else a3,
|
|
560
|
+
b0' = if (and(not(multi), focusList == "b")) swapDownSlot(b0, b1, b2, b3, activeB, 0) else b0,
|
|
561
|
+
b1' = if (and(not(multi), focusList == "b")) swapDownSlot(b0, b1, b2, b3, activeB, 1) else b1,
|
|
562
|
+
b2' = if (and(not(multi), focusList == "b")) swapDownSlot(b0, b1, b2, b3, activeB, 2) else b2,
|
|
563
|
+
b3' = if (and(not(multi), focusList == "b")) swapDownSlot(b0, b1, b2, b3, activeB, 3) else b3,
|
|
564
|
+
activeA' = activeA,
|
|
565
|
+
activeB' = activeB,
|
|
566
|
+
focusList' = focusList,
|
|
567
|
+
sel' = sel,
|
|
568
|
+
multi' = multi,
|
|
569
|
+
controlled' = controlled,
|
|
570
|
+
accepting' = accepting,
|
|
571
|
+
valueProp0' = valueProp0,
|
|
572
|
+
valueProp1' = valueProp1,
|
|
573
|
+
valueProp2' = valueProp2,
|
|
574
|
+
valueProp3' = valueProp3,
|
|
575
|
+
requested' = requested,
|
|
576
|
+
outcome' = outcome,
|
|
577
|
+
base' = base,
|
|
578
|
+
requests' = requests
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Delete/Enter: move the selected options of the focused list to the other
|
|
582
|
+
// list; they arrive deselected, source focus lands on the next remaining option.
|
|
583
|
+
action transfer = all {
|
|
584
|
+
a0' = if (rejects) a0 else (if (and(focusList == "a", movedCount(sel, a0, a1, a2, a3) > 0)) keptAt(sel, a0, a1, a2, a3, 0) else if (and(focusList == "b", movedCount(sel, b0, b1, b2, b3) > 0)) appendAt(sel, a0, a1, a2, a3, b0, b1, b2, b3, 0) else a0),
|
|
585
|
+
a1' = if (rejects) a1 else (if (and(focusList == "a", movedCount(sel, a0, a1, a2, a3) > 0)) keptAt(sel, a0, a1, a2, a3, 1) else if (and(focusList == "b", movedCount(sel, b0, b1, b2, b3) > 0)) appendAt(sel, a0, a1, a2, a3, b0, b1, b2, b3, 1) else a1),
|
|
586
|
+
a2' = if (rejects) a2 else (if (and(focusList == "a", movedCount(sel, a0, a1, a2, a3) > 0)) keptAt(sel, a0, a1, a2, a3, 2) else if (and(focusList == "b", movedCount(sel, b0, b1, b2, b3) > 0)) appendAt(sel, a0, a1, a2, a3, b0, b1, b2, b3, 2) else a2),
|
|
587
|
+
a3' = if (rejects) a3 else (if (and(focusList == "a", movedCount(sel, a0, a1, a2, a3) > 0)) keptAt(sel, a0, a1, a2, a3, 3) else if (and(focusList == "b", movedCount(sel, b0, b1, b2, b3) > 0)) appendAt(sel, a0, a1, a2, a3, b0, b1, b2, b3, 3) else a3),
|
|
588
|
+
b0' = if (rejects) b0 else (if (and(focusList == "b", movedCount(sel, b0, b1, b2, b3) > 0)) keptAt(sel, b0, b1, b2, b3, 0) else if (and(focusList == "a", movedCount(sel, a0, a1, a2, a3) > 0)) appendAt(sel, b0, b1, b2, b3, a0, a1, a2, a3, 0) else b0),
|
|
589
|
+
b1' = if (rejects) b1 else (if (and(focusList == "b", movedCount(sel, b0, b1, b2, b3) > 0)) keptAt(sel, b0, b1, b2, b3, 1) else if (and(focusList == "a", movedCount(sel, a0, a1, a2, a3) > 0)) appendAt(sel, b0, b1, b2, b3, a0, a1, a2, a3, 1) else b1),
|
|
590
|
+
b2' = if (rejects) b2 else (if (and(focusList == "b", movedCount(sel, b0, b1, b2, b3) > 0)) keptAt(sel, b0, b1, b2, b3, 2) else if (and(focusList == "a", movedCount(sel, a0, a1, a2, a3) > 0)) appendAt(sel, b0, b1, b2, b3, a0, a1, a2, a3, 2) else b2),
|
|
591
|
+
b3' = if (rejects) b3 else (if (and(focusList == "b", movedCount(sel, b0, b1, b2, b3) > 0)) keptAt(sel, b0, b1, b2, b3, 3) else if (and(focusList == "a", movedCount(sel, a0, a1, a2, a3) > 0)) appendAt(sel, b0, b1, b2, b3, a0, a1, a2, a3, 3) else b3),
|
|
592
|
+
activeA' = if (rejects) activeA else (if (and(focusList == "a", movedCount(sel, a0, a1, a2, a3) > 0)) focusAfterRemoval(sel, a0, a1, a2, a3, activeA) else if (and(focusList == "b", movedCount(sel, b0, b1, b2, b3) > 0)) (if (activeA == noItem) movedAt(sel, b0, b1, b2, b3, 0) else activeA) else activeA),
|
|
593
|
+
activeB' = if (rejects) activeB else (if (and(focusList == "b", movedCount(sel, b0, b1, b2, b3) > 0)) focusAfterRemoval(sel, b0, b1, b2, b3, activeB) else if (and(focusList == "a", movedCount(sel, a0, a1, a2, a3) > 0)) (if (activeB == noItem) movedAt(sel, a0, a1, a2, a3, 0) else activeB) else activeB),
|
|
594
|
+
focusList' = focusList,
|
|
595
|
+
sel' = landed(rejects, sel, transferIntent),
|
|
596
|
+
multi' = multi,
|
|
597
|
+
controlled' = controlled,
|
|
598
|
+
accepting' = accepting,
|
|
599
|
+
valueProp0' = propBit(hostWrites, valueProp0, transferIntent, 0),
|
|
600
|
+
valueProp1' = propBit(hostWrites, valueProp1, transferIntent, 1),
|
|
601
|
+
valueProp2' = propBit(hostWrites, valueProp2, transferIntent, 2),
|
|
602
|
+
valueProp3' = propBit(hostWrites, valueProp3, transferIntent, 3),
|
|
603
|
+
requested' = if (transferIntent != sel) transferIntent else requested,
|
|
604
|
+
outcome' = if (transferIntent != sel) verdict else outcome,
|
|
605
|
+
base' = if (transferIntent != sel) sel else base,
|
|
606
|
+
requests' = if (transferIntent != sel) requests + 1 else requests
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Mode prop; switching to single-select collapses each list to its active
|
|
610
|
+
// option.
|
|
611
|
+
action set_multi = {
|
|
612
|
+
nondet flag = Set(true, false).oneOf()
|
|
613
|
+
all {
|
|
614
|
+
a0' = a0,
|
|
615
|
+
a1' = a1,
|
|
616
|
+
a2' = a2,
|
|
617
|
+
a3' = a3,
|
|
618
|
+
b0' = b0,
|
|
619
|
+
b1' = b1,
|
|
620
|
+
b2' = b2,
|
|
621
|
+
b3' = b3,
|
|
622
|
+
activeA' = activeA,
|
|
623
|
+
activeB' = activeB,
|
|
624
|
+
focusList' = focusList,
|
|
625
|
+
sel' = landed(rejects, sel, setMultiIntent(flag)),
|
|
626
|
+
multi' = flag,
|
|
627
|
+
controlled' = controlled,
|
|
628
|
+
accepting' = accepting,
|
|
629
|
+
valueProp0' = propBit(hostWrites, valueProp0, setMultiIntent(flag), 0),
|
|
630
|
+
valueProp1' = propBit(hostWrites, valueProp1, setMultiIntent(flag), 1),
|
|
631
|
+
valueProp2' = propBit(hostWrites, valueProp2, setMultiIntent(flag), 2),
|
|
632
|
+
valueProp3' = propBit(hostWrites, valueProp3, setMultiIntent(flag), 3),
|
|
633
|
+
requested' = if (setMultiIntent(flag) != sel) setMultiIntent(flag) else requested,
|
|
634
|
+
outcome' = if (setMultiIntent(flag) != sel) verdict else outcome,
|
|
635
|
+
base' = if (setMultiIntent(flag) != sel) sel else base,
|
|
636
|
+
requests' = if (setMultiIntent(flag) != sel) requests + 1 else requests
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// commit: the host writes the controlled value (a no-op while uncontrolled)
|
|
641
|
+
action setValue = {
|
|
642
|
+
nondet v = Set(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15).oneOf()
|
|
643
|
+
all {
|
|
644
|
+
a0' = a0,
|
|
645
|
+
a1' = a1,
|
|
646
|
+
a2' = a2,
|
|
647
|
+
a3' = a3,
|
|
648
|
+
b0' = b0,
|
|
649
|
+
b1' = b1,
|
|
650
|
+
b2' = b2,
|
|
651
|
+
b3' = b3,
|
|
652
|
+
activeA' = activeA,
|
|
653
|
+
activeB' = activeB,
|
|
654
|
+
focusList' = focusList,
|
|
655
|
+
sel' = if (controlled) v else sel,
|
|
656
|
+
multi' = multi,
|
|
657
|
+
controlled' = controlled,
|
|
658
|
+
accepting' = accepting,
|
|
659
|
+
valueProp0' = if (controlled) bit(v, 0) else valueProp0,
|
|
660
|
+
valueProp1' = if (controlled) bit(v, 1) else valueProp1,
|
|
661
|
+
valueProp2' = if (controlled) bit(v, 2) else valueProp2,
|
|
662
|
+
valueProp3' = if (controlled) bit(v, 3) else valueProp3,
|
|
663
|
+
requested' = requested,
|
|
664
|
+
outcome' = if (controlled) "none" else outcome,
|
|
665
|
+
base' = base,
|
|
666
|
+
requests' = requests
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// commit: the host flips one option of its own copy of the value
|
|
671
|
+
action hostToggle = {
|
|
672
|
+
nondet panel = Set(0, 1, 2, 3).oneOf()
|
|
673
|
+
all {
|
|
674
|
+
a0' = a0,
|
|
675
|
+
a1' = a1,
|
|
676
|
+
a2' = a2,
|
|
677
|
+
a3' = a3,
|
|
678
|
+
b0' = b0,
|
|
679
|
+
b1' = b1,
|
|
680
|
+
b2' = b2,
|
|
681
|
+
b3' = b3,
|
|
682
|
+
activeA' = activeA,
|
|
683
|
+
activeB' = activeB,
|
|
684
|
+
focusList' = focusList,
|
|
685
|
+
sel' = if (controlled) flipAt(propsMask, panel) else sel,
|
|
686
|
+
multi' = multi,
|
|
687
|
+
controlled' = controlled,
|
|
688
|
+
accepting' = accepting,
|
|
689
|
+
valueProp0' = if (controlled) bit(flipAt(propsMask, panel), 0) else valueProp0,
|
|
690
|
+
valueProp1' = if (controlled) bit(flipAt(propsMask, panel), 1) else valueProp1,
|
|
691
|
+
valueProp2' = if (controlled) bit(flipAt(propsMask, panel), 2) else valueProp2,
|
|
692
|
+
valueProp3' = if (controlled) bit(flipAt(propsMask, panel), 3) else valueProp3,
|
|
693
|
+
requested' = requested,
|
|
694
|
+
outcome' = if (controlled) "none" else outcome,
|
|
695
|
+
base' = base,
|
|
696
|
+
requests' = requests
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// configuration: the host starts owning the value, adopting the current one
|
|
701
|
+
action control = all {
|
|
702
|
+
a0' = a0,
|
|
703
|
+
a1' = a1,
|
|
704
|
+
a2' = a2,
|
|
705
|
+
a3' = a3,
|
|
706
|
+
b0' = b0,
|
|
707
|
+
b1' = b1,
|
|
708
|
+
b2' = b2,
|
|
709
|
+
b3' = b3,
|
|
710
|
+
activeA' = activeA,
|
|
711
|
+
activeB' = activeB,
|
|
712
|
+
focusList' = focusList,
|
|
713
|
+
sel' = sel,
|
|
714
|
+
multi' = multi,
|
|
715
|
+
controlled' = true,
|
|
716
|
+
accepting' = accepting,
|
|
717
|
+
valueProp0' = bit(sel, 0),
|
|
718
|
+
valueProp1' = bit(sel, 1),
|
|
719
|
+
valueProp2' = bit(sel, 2),
|
|
720
|
+
valueProp3' = bit(sel, 3),
|
|
721
|
+
requested' = requested,
|
|
722
|
+
outcome' = outcome,
|
|
723
|
+
base' = base,
|
|
724
|
+
requests' = requests
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// configuration: the controlled host answers every request with its intent
|
|
728
|
+
action acceptRequests = all {
|
|
729
|
+
a0' = a0,
|
|
730
|
+
a1' = a1,
|
|
731
|
+
a2' = a2,
|
|
732
|
+
a3' = a3,
|
|
733
|
+
b0' = b0,
|
|
734
|
+
b1' = b1,
|
|
735
|
+
b2' = b2,
|
|
736
|
+
b3' = b3,
|
|
737
|
+
activeA' = activeA,
|
|
738
|
+
activeB' = activeB,
|
|
739
|
+
focusList' = focusList,
|
|
740
|
+
sel' = sel,
|
|
741
|
+
multi' = multi,
|
|
742
|
+
controlled' = controlled,
|
|
743
|
+
accepting' = true,
|
|
744
|
+
valueProp0' = valueProp0,
|
|
745
|
+
valueProp1' = valueProp1,
|
|
746
|
+
valueProp2' = valueProp2,
|
|
747
|
+
valueProp3' = valueProp3,
|
|
748
|
+
requested' = requested,
|
|
749
|
+
outcome' = outcome,
|
|
750
|
+
base' = base,
|
|
751
|
+
requests' = requests
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
action step = any {
|
|
755
|
+
init, focus_a, focus_b, blur, arrow_down, arrow_up, home, end, space,
|
|
756
|
+
move_up, move_down, transfer, set_multi, setValue, hostToggle, control,
|
|
757
|
+
acceptRequests
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// every option is in exactly one list, exactly once
|
|
761
|
+
val OptionsPartitioned: bool =
|
|
762
|
+
and(occurrences(0) == 1, occurrences(1) == 1, occurrences(2) == 1, occurrences(3) == 1)
|
|
763
|
+
// lists are packed to the left: no hole before an option
|
|
764
|
+
val ListsPacked: bool = and(packed(a0, a1, a2, a3), packed(b0, b1, b2, b3))
|
|
765
|
+
// a list's active option is one of its own options (noItem exactly when it is empty)
|
|
766
|
+
val ActiveInOwnList: bool = and(activeValid(a0, a1, a2, a3, activeA), activeValid(b0, b1, b2, b3, activeB))
|
|
767
|
+
// the mask only ever names real options
|
|
768
|
+
val SelectionInRange: bool = and(sel >= 0, sel < pow2(itemCount))
|
|
769
|
+
val FocusListKnown: bool = or(focusList == "none", focusList == "a", focusList == "b")
|
|
770
|
+
// APG: no more than one option selected without aria-multiselectable (per list)
|
|
771
|
+
// (uncontrolled: a controlled host owns the value and may hand us any set)
|
|
772
|
+
val SingleSelectOnePerList: bool =
|
|
773
|
+
and(
|
|
774
|
+
ariaSingleHoldsAtMostOne(controlled, multi, popcount(selIn(sel, a0, a1, a2, a3))),
|
|
775
|
+
ariaSingleHoldsAtMostOne(controlled, multi, popcount(selIn(sel, b0, b1, b2, b3)))
|
|
776
|
+
)
|
|
777
|
+
// single-select: the focused list's selection is its active option
|
|
778
|
+
val SingleSelectFollowsFocus: bool =
|
|
779
|
+
or(
|
|
780
|
+
controlled,
|
|
781
|
+
multi,
|
|
782
|
+
and(
|
|
783
|
+
or(focusList != "a", selIn(sel, a0, a1, a2, a3) == focusBit(activeA)),
|
|
784
|
+
or(focusList != "b", selIn(sel, b0, b1, b2, b3) == focusBit(activeB))
|
|
785
|
+
)
|
|
786
|
+
)
|
|
787
|
+
// aria-activedescendant is set on exactly the focused list
|
|
788
|
+
val ActiveDescendantOnFocusedListOnly: bool =
|
|
789
|
+
and(
|
|
790
|
+
(ariaActiveDescendant(focusList, "a", activeA) != noItem) == and(focusList == "a", activeA != noItem),
|
|
791
|
+
(ariaActiveDescendant(focusList, "b", activeB) != noItem) == and(focusList == "b", activeB != noItem)
|
|
792
|
+
)
|
|
793
|
+
// callbacks only ever count up from zero
|
|
794
|
+
val RequestsNonNegative: bool = requests >= 0
|
|
795
|
+
// controlled: only the value prop sets the state, so no event commits
|
|
796
|
+
val ControlledFollowsProp: bool = or(not(controlled), sel == propsMask)
|
|
797
|
+
// a rejected request leaves the state as it found it
|
|
798
|
+
val RejectedRequestUntouched: bool = or(not(outcome == "rejected"), sel == base)
|
|
799
|
+
// an accepted request committed exactly its intent
|
|
800
|
+
val AcceptedRequestCommits: bool = or(not(outcome == "accepted"), sel == requested)
|
|
801
|
+
}
|