@cssints/apg-specs 0.0.0-stage → 0.5.2
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,983 @@
|
|
|
1
|
+
// APG Treegrid — https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/
|
|
2
|
+
// and its example, the E-mail Inbox treegrid:
|
|
3
|
+
// https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/examples/treegrid-1/
|
|
4
|
+
//
|
|
5
|
+
// Modelled (APG sections: About This Pattern, Keyboard Interaction, WAI-ARIA
|
|
6
|
+
// Roles/States/Properties):
|
|
7
|
+
// - a fixed tree of 7 rows in document order x 3 columns, the parent map being
|
|
8
|
+
// the only tree data (Inbox-style: A > {A.1, A.2 > {A.2.1}}, B > {B.1, B.2}):
|
|
9
|
+
// row 0 A parent - row 4 B parent -
|
|
10
|
+
// row 1 A.1 parent 0 row 5 B.1 parent 4
|
|
11
|
+
// row 2 A.2 parent 0 row 6 B.2 parent 4
|
|
12
|
+
// row 3 A.2.1 parent 2
|
|
13
|
+
// every ARIA fact is DERIVED from that map: hasChildren, aria-level,
|
|
14
|
+
// aria-setsize, aria-posinset, aria-expanded (only on parent rows), and row
|
|
15
|
+
// visibility (a row is rendered only while all its ancestors are expanded).
|
|
16
|
+
// - the three navigation models of the example: `rowsFirst` (Tab lands on the
|
|
17
|
+
// row, Right enters the cells), `cellsFirst` (Tab lands on the first cell,
|
|
18
|
+
// Left from the first cell still reaches the row) and `cellsOnly` (rows are
|
|
19
|
+
// never focused; Enter on the first cell of a parent row toggles it).
|
|
20
|
+
// - roving tabindex: exactly one rendered row-or-cell is the tab stop, and the
|
|
21
|
+
// tab stop is remembered while focus is outside the grid.
|
|
22
|
+
// - keys: Right / Left (expand / collapse a focused row, otherwise move between
|
|
23
|
+
// cells), Down / Up over VISIBLE rows (same column in cell mode), Home / End
|
|
24
|
+
// (first / last visible row in row mode, first / last cell in cell mode),
|
|
25
|
+
// Ctrl+Home / Ctrl+End (first / last visible row; same column in cell mode),
|
|
26
|
+
// Enter (cellsOnly toggle), plus Tab in / out and pointer clicks (cell focus,
|
|
27
|
+
// expander toggle - which relocates focus to the toggled row when it would
|
|
28
|
+
// hide the focused row).
|
|
29
|
+
// - selection (rows): `single` selection follows focus (aria-selected on one
|
|
30
|
+
// row only, absent elsewhere); `multi` selection is independent of focus
|
|
31
|
+
// (aria-multiselectable, aria-selected true/false on every row): Shift+Space
|
|
32
|
+
// toggles the focused row, Ctrl+A selects all rows, Shift+Down / Shift+Up on
|
|
33
|
+
// a focused row extend the selection to the neighbouring visible row.
|
|
34
|
+
//
|
|
35
|
+
// Request vs commit (map ticket rt1p). The controlled value of a treegrid is the
|
|
36
|
+
// selected-row set; expansion stays uncontrolled (like a menu's open state). Every
|
|
37
|
+
// user-intent event that changes the selection - Tab in (single follows focus),
|
|
38
|
+
// Down / Up / Home / End / Ctrl+Home / Ctrl+End (single follows focus), a cell
|
|
39
|
+
// click, an expander toggle that relocates focus, Shift+Space, Ctrl+A and the
|
|
40
|
+
// Shift+arrow extensions - is a *request* carrying the intended `selMask`
|
|
41
|
+
// (packed into `requested`). It fires onValueChange (`requests` counts the calls)
|
|
42
|
+
// when the intent differs from the current selection, and then:
|
|
43
|
+
// uncontrolled the request is accepted: the rows commit the intent
|
|
44
|
+
// controlled, holds the host ignores it ("rejected"): the selection is untouched
|
|
45
|
+
// controlled, accepts the host answers with the same value (`accepting`): the
|
|
46
|
+
// rows commit through the controlled value, not the event
|
|
47
|
+
// The controlled value `selMask` is spelled out per member (`valueProp0..6`, bit
|
|
48
|
+
// r = row r) because the generator has no collections; it is the only thing that
|
|
49
|
+
// changes the selection while `controlled`, and `setValue` / `hostToggle` are the
|
|
50
|
+
// host writing it. `control` and `acceptRequests` are configuration events (init
|
|
51
|
+
// is literal-only). Configuration events (`configureNav` / `configureSel`) reset
|
|
52
|
+
// the uncontrolled expansion and focus but leave a controlled selection alone.
|
|
53
|
+
// Expansion keys (Right / Left / Enter) and focus movement do not change the
|
|
54
|
+
// controlled value, so they commit unconditionally.
|
|
55
|
+
//
|
|
56
|
+
// Invariants for the model: ControlledFollowsProp (in controlled mode no event
|
|
57
|
+
// commits the selection), RejectedRequestUntouched, AcceptedRequestCommits. The
|
|
58
|
+
// single-select invariants are scoped to the uncontrolled path: a controlled host
|
|
59
|
+
// owns the selection and may hand the grid any set.
|
|
60
|
+
//
|
|
61
|
+
// `outcome` is "none" | "accepted" | "rejected" for the last request; a host
|
|
62
|
+
// write settles it back to "none". `base` is the packed selection when the last
|
|
63
|
+
// request was received.
|
|
64
|
+
//
|
|
65
|
+
// Deliberately omitted (recorded gaps, see the bean summary):
|
|
66
|
+
// - Page Up / Page Down: the row count is "author-determined".
|
|
67
|
+
// - Tab to focusable elements inside cells and editing inside cells (grid
|
|
68
|
+
// "Editing and Navigating Inside a Cell"); cells are the focus targets.
|
|
69
|
+
// - cell / column selection (Ctrl+Space, Shift+Left/Right, cell-level Shift+Up/
|
|
70
|
+
// Down), aria-sort, aria-readonly, aria-rowcount / aria-colcount / *index.
|
|
71
|
+
// - row type headers (columnheader / rowheader), aria-label(ledby).
|
|
72
|
+
// - dynamic trees: the parent map is constant. The generator has no
|
|
73
|
+
// collections (str / int / bool vars only), so per-parent expansion is three
|
|
74
|
+
// bool vars and the controlled selected-row set is one bool per row.
|
|
75
|
+
// - Left on a collapsed / leaf row does NOT move to the parent: that is what
|
|
76
|
+
// APG says ("focus does not move"); implementations that jump to the parent
|
|
77
|
+
// diverge from APG.
|
|
78
|
+
module apg_treegrid {
|
|
79
|
+
import aria.* from "../../shared/aria"
|
|
80
|
+
|
|
81
|
+
var navModel: str // "rowsFirst" | "cellsFirst" | "cellsOnly"
|
|
82
|
+
var selMode: str // "single" | "multi"
|
|
83
|
+
var exp0: bool // row 0 expanded
|
|
84
|
+
var exp2: bool // row 2 expanded
|
|
85
|
+
var exp4: bool // row 4 expanded
|
|
86
|
+
var inGrid: bool // DOM focus is inside the treegrid
|
|
87
|
+
var kind: str // the roving item is a "row" or a "cell"
|
|
88
|
+
var frow: int // roving / focused row (0..6)
|
|
89
|
+
var fcol: int // roving / focused column (0..2); always 0 on a row
|
|
90
|
+
var selMask: int // selected rows, bit r set = row r selected (0..127)
|
|
91
|
+
var controlled: bool // the host owns the selection (`value` prop present)
|
|
92
|
+
var accepting: bool // controlled host that answers every request with its intent
|
|
93
|
+
var valueProp0: bool // controlled selection member 0 (row 0), ...
|
|
94
|
+
var valueProp1: bool
|
|
95
|
+
var valueProp2: bool
|
|
96
|
+
var valueProp3: bool
|
|
97
|
+
var valueProp4: bool
|
|
98
|
+
var valueProp5: bool
|
|
99
|
+
var valueProp6: bool
|
|
100
|
+
var requested: int // intent of the last request, packed selection
|
|
101
|
+
var outcome: str // "none" | "accepted" | "rejected"
|
|
102
|
+
var base: int // packed selection when the last request was received
|
|
103
|
+
var requests: int // number of requests that fired onSelectionChange
|
|
104
|
+
|
|
105
|
+
// ---- constants as nullary vals --------------------------------------------
|
|
106
|
+
val ROWS: int = 7
|
|
107
|
+
val COLS: int = 3
|
|
108
|
+
|
|
109
|
+
// ---- small arithmetic -----------------------------------------------------
|
|
110
|
+
def b(x: bool): int = if (x) 1 else 0
|
|
111
|
+
def minI(x: int, y: int): int = if (x < y) x else y
|
|
112
|
+
|
|
113
|
+
def bit(r: int): int =
|
|
114
|
+
if (r == 0) 1 else if (r == 1) 2 else if (r == 2) 4 else if (r == 3) 8
|
|
115
|
+
else if (r == 4) 16 else if (r == 5) 32 else if (r == 6) 64 else 0
|
|
116
|
+
def bitSet(mask: int, r: int): bool =
|
|
117
|
+
if (bit(r) == 0) false else (mask / bit(r)) % 2 == 1
|
|
118
|
+
def popcount(mask: int): int =
|
|
119
|
+
b(bitSet(mask, 0)) + b(bitSet(mask, 1)) + b(bitSet(mask, 2)) + b(bitSet(mask, 3))
|
|
120
|
+
+ b(bitSet(mask, 4)) + b(bitSet(mask, 5)) + b(bitSet(mask, 6))
|
|
121
|
+
def withBit(mask: int, r: int): int = if (bitSet(mask, r)) mask else mask + bit(r)
|
|
122
|
+
def flipBit(mask: int, r: int): int =
|
|
123
|
+
if (bitSet(mask, r)) mask - bit(r) else mask + bit(r)
|
|
124
|
+
|
|
125
|
+
// ---- the tree: ONE parent map, everything else derived --------------------
|
|
126
|
+
def parentOf(r: int): int =
|
|
127
|
+
if (r == 1) 0 else if (r == 2) 0 else if (r == 3) 2
|
|
128
|
+
else if (r == 5) 4 else if (r == 6) 4 else -1
|
|
129
|
+
def childCount(p: int): int =
|
|
130
|
+
b(parentOf(0) == p) + b(parentOf(1) == p) + b(parentOf(2) == p) + b(parentOf(3) == p)
|
|
131
|
+
+ b(parentOf(4) == p) + b(parentOf(5) == p) + b(parentOf(6) == p)
|
|
132
|
+
def hasChildren(r: int): bool = childCount(r) > 0
|
|
133
|
+
|
|
134
|
+
// ---- ARIA contract derived from the tree ----------------------------------
|
|
135
|
+
def ariaLevel(r: int): int =
|
|
136
|
+
if (parentOf(r) < 0) 1 else if (parentOf(parentOf(r)) < 0) 2 else 3
|
|
137
|
+
// siblings share a parent; the roots (parent -1) form the top-level set
|
|
138
|
+
def ariaSetSize(r: int): int = childCount(parentOf(r))
|
|
139
|
+
def ariaPosInSet(r: int): int =
|
|
140
|
+
1 + b(0 < r and parentOf(0) == parentOf(r)) + b(1 < r and parentOf(1) == parentOf(r))
|
|
141
|
+
+ b(2 < r and parentOf(2) == parentOf(r)) + b(3 < r and parentOf(3) == parentOf(r))
|
|
142
|
+
+ b(4 < r and parentOf(4) == parentOf(r)) + b(5 < r and parentOf(5) == parentOf(r))
|
|
143
|
+
|
|
144
|
+
// ---- expansion and visibility ---------------------------------------------
|
|
145
|
+
// expansion of row p under a hypothetical (a, c, d) = (exp0, exp2, exp4)
|
|
146
|
+
def expW(p: int, a: bool, c: bool, d: bool): bool =
|
|
147
|
+
if (p == 0) a else if (p == 2) c else if (p == 4) d else false
|
|
148
|
+
def isExp(p: int): bool = expW(p, exp0, exp2, exp4)
|
|
149
|
+
// the children of p are rendered: p and every ancestor of p are expanded
|
|
150
|
+
// (depth is at most 3, so one grandparent step covers it)
|
|
151
|
+
def openW(p: int, a: bool, c: bool, d: bool): bool =
|
|
152
|
+
if (p < 0) true
|
|
153
|
+
else if (not(expW(p, a, c, d))) false
|
|
154
|
+
else if (parentOf(p) < 0) true
|
|
155
|
+
else expW(parentOf(p), a, c, d)
|
|
156
|
+
def visibleW(r: int, a: bool, c: bool, d: bool): bool =
|
|
157
|
+
if (r < 0 or r >= ROWS) false else openW(parentOf(r), a, c, d)
|
|
158
|
+
def visible(r: int): bool = visibleW(r, exp0, exp2, exp4)
|
|
159
|
+
|
|
160
|
+
// aria-expanded exists only on parent rows
|
|
161
|
+
def ariaExpandedOf(r: int): str =
|
|
162
|
+
if (hasChildren(r)) ariaBoolToken(isExp(r)) else "absent"
|
|
163
|
+
def canExpand(r: int): bool = hasChildren(r) and not(isExp(r))
|
|
164
|
+
def canCollapse(r: int): bool = hasChildren(r) and isExp(r)
|
|
165
|
+
def toggleIf(target: int, r: int, cur: bool): bool = if (target == r) not(cur) else cur
|
|
166
|
+
|
|
167
|
+
// ---- selection ARIA --------------------------------------------------------
|
|
168
|
+
def multiselectable(sel: str): bool = ariaSelIsMulti(sel)
|
|
169
|
+
def ariaSelectedOf(sel: str, mask: int, r: int): str =
|
|
170
|
+
if (bitSet(mask, r)) "true" else if (sel == "multi") "false" else "absent"
|
|
171
|
+
// single selection follows focus; multi leaves the selection alone
|
|
172
|
+
def follow(sel: str, mask: int, r: int): int = if (sel == "single") bit(r) else mask
|
|
173
|
+
|
|
174
|
+
// ---- roving tabindex -------------------------------------------------------
|
|
175
|
+
def rowFocusSupported(nav: str): bool = nav != "cellsOnly"
|
|
176
|
+
def isTabRow(r: int, k: str, fr: int): bool = k == "row" and fr == r
|
|
177
|
+
def isTabCell(r: int, c: int, k: str, fr: int, fc: int): bool =
|
|
178
|
+
k == "cell" and fr == r and fc == c
|
|
179
|
+
def rowTabindex(r: int, k: str, fr: int): int = if (isTabRow(r, k, fr)) 0 else -1
|
|
180
|
+
def cellTabindex(r: int, c: int, k: str, fr: int, fc: int): int =
|
|
181
|
+
if (isTabCell(r, c, k, fr, fc)) 0 else -1
|
|
182
|
+
def tabStopsIn(r: int, k: str, fr: int, fc: int): int =
|
|
183
|
+
b(isTabRow(r, k, fr)) + b(isTabCell(r, 0, k, fr, fc)) + b(isTabCell(r, 1, k, fr, fc))
|
|
184
|
+
+ b(isTabCell(r, 2, k, fr, fc))
|
|
185
|
+
// tab stops among the RENDERED rows only
|
|
186
|
+
val tabStops: int =
|
|
187
|
+
b(visible(0)) * tabStopsIn(0, kind, frow, fcol) + b(visible(1)) * tabStopsIn(1, kind, frow, fcol)
|
|
188
|
+
+ b(visible(2)) * tabStopsIn(2, kind, frow, fcol) + b(visible(3)) * tabStopsIn(3, kind, frow, fcol)
|
|
189
|
+
+ b(visible(4)) * tabStopsIn(4, kind, frow, fcol) + b(visible(5)) * tabStopsIn(5, kind, frow, fcol)
|
|
190
|
+
+ b(visible(6)) * tabStopsIn(6, kind, frow, fcol)
|
|
191
|
+
|
|
192
|
+
// ---- navigation over VISIBLE rows ------------------------------------------
|
|
193
|
+
def nextVis(r: int): int =
|
|
194
|
+
if (visible(r + 1)) r + 1 else if (visible(r + 2)) r + 2 else if (visible(r + 3)) r + 3
|
|
195
|
+
else if (visible(r + 4)) r + 4 else if (visible(r + 5)) r + 5 else if (visible(r + 6)) r + 6
|
|
196
|
+
else r
|
|
197
|
+
def prevVis(r: int): int =
|
|
198
|
+
if (visible(r - 1)) r - 1 else if (visible(r - 2)) r - 2 else if (visible(r - 3)) r - 3
|
|
199
|
+
else if (visible(r - 4)) r - 4 else if (visible(r - 5)) r - 5 else if (visible(r - 6)) r - 6
|
|
200
|
+
else r
|
|
201
|
+
val lastVis: int = prevVis(ROWS)
|
|
202
|
+
// target rows of the jump keys (the cell-mode forms keep the row)
|
|
203
|
+
val homeRow: int = if (kind == "row") 0 else frow
|
|
204
|
+
val endRow: int = if (kind == "row") lastVis else frow
|
|
205
|
+
|
|
206
|
+
// a click on the expander of row r is ignored unless that row is rendered
|
|
207
|
+
def clickTarget(r: int): int = if (visible(r)) r else -1
|
|
208
|
+
// collapsing may hide the focused row: focus relocates to the toggled row
|
|
209
|
+
def rowAfterToggle(r: int): int =
|
|
210
|
+
if (visibleW(frow, toggleIf(clickTarget(r), 0, exp0), toggleIf(clickTarget(r), 2, exp2),
|
|
211
|
+
toggleIf(clickTarget(r), 4, exp4))) frow else r
|
|
212
|
+
|
|
213
|
+
// ---- request / commit (map ticket rt1p) -----------------------------------
|
|
214
|
+
def holds: bool = and(controlled, not(accepting))
|
|
215
|
+
def follows: bool = and(controlled, accepting)
|
|
216
|
+
def verdict: str = if (holds) "rejected" else "accepted"
|
|
217
|
+
// the selection after a request for `want`
|
|
218
|
+
def landed(h: bool, current: int, want: int): int = if (h) current else want
|
|
219
|
+
// a controlled member after a request for `want`
|
|
220
|
+
def propAfter(f: bool, p: bool, want: bool): bool = if (f) want else p
|
|
221
|
+
// the packed controlled value, spelled out one member at a time
|
|
222
|
+
def propMask: int =
|
|
223
|
+
b(valueProp0) + 2 * b(valueProp1) + 4 * b(valueProp2) + 8 * b(valueProp3)
|
|
224
|
+
+ 16 * b(valueProp4) + 32 * b(valueProp5) + 64 * b(valueProp6)
|
|
225
|
+
// a request is real only when its intent differs from the current selection
|
|
226
|
+
def reqMoves(want: int): bool = want != selMask
|
|
227
|
+
// the intended selection of each request action
|
|
228
|
+
def focusWant: int = follow(selMode, selMask, frow)
|
|
229
|
+
def downWant: int = if (inGrid) follow(selMode, selMask, nextVis(frow)) else selMask
|
|
230
|
+
def upWant: int = if (inGrid) follow(selMode, selMask, prevVis(frow)) else selMask
|
|
231
|
+
def homeWant: int = if (inGrid) follow(selMode, selMask, homeRow) else selMask
|
|
232
|
+
def endWant: int = if (inGrid) follow(selMode, selMask, endRow) else selMask
|
|
233
|
+
def ctrlHomeWant: int = if (inGrid) follow(selMode, selMask, 0) else selMask
|
|
234
|
+
def ctrlEndWant: int = if (inGrid) follow(selMode, selMask, lastVis) else selMask
|
|
235
|
+
def cellWant(cell: int): int =
|
|
236
|
+
if (visible(cell / COLS)) follow(selMode, selMask, cell / COLS) else selMask
|
|
237
|
+
def toggleRowWant(r: int): int =
|
|
238
|
+
if (and(selMode == "single", and(selMask != 0, visible(r))))
|
|
239
|
+
bit(rowAfterToggle(r)) else selMask
|
|
240
|
+
def toggleSelectWant: int =
|
|
241
|
+
if (inGrid and selMode == "multi") flipBit(selMask, frow) else selMask
|
|
242
|
+
def selectAllWant: int = if (inGrid and selMode == "multi") 127 else selMask
|
|
243
|
+
def extendDownWant: int =
|
|
244
|
+
if (inGrid and selMode == "multi" and kind == "row")
|
|
245
|
+
withBit(withBit(selMask, frow), nextVis(frow)) else selMask
|
|
246
|
+
def extendUpWant: int =
|
|
247
|
+
if (inGrid and selMode == "multi" and kind == "row")
|
|
248
|
+
withBit(withBit(selMask, frow), prevVis(frow)) else selMask
|
|
249
|
+
|
|
250
|
+
// ---- actions ---------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
action init = all {
|
|
253
|
+
navModel' = "rowsFirst",
|
|
254
|
+
selMode' = "single",
|
|
255
|
+
exp0' = false,
|
|
256
|
+
exp2' = false,
|
|
257
|
+
exp4' = false,
|
|
258
|
+
inGrid' = false,
|
|
259
|
+
kind' = "row",
|
|
260
|
+
frow' = 0,
|
|
261
|
+
fcol' = 0,
|
|
262
|
+
selMask' = 0,
|
|
263
|
+
controlled' = false,
|
|
264
|
+
accepting' = false,
|
|
265
|
+
valueProp0' = false,
|
|
266
|
+
valueProp1' = false,
|
|
267
|
+
valueProp2' = false,
|
|
268
|
+
valueProp3' = false,
|
|
269
|
+
valueProp4' = false,
|
|
270
|
+
valueProp5' = false,
|
|
271
|
+
valueProp6' = false,
|
|
272
|
+
requested' = 0,
|
|
273
|
+
outcome' = "none",
|
|
274
|
+
base' = 0,
|
|
275
|
+
requests' = 0
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// pick the navigation model; the grid restarts collapsed with the model's entry
|
|
279
|
+
// point (a controlled selection is owned by the host and stays put)
|
|
280
|
+
action configureNav = {
|
|
281
|
+
nondet nav = Set("rowsFirst", "cellsFirst", "cellsOnly").oneOf()
|
|
282
|
+
all {
|
|
283
|
+
navModel' = nav,
|
|
284
|
+
selMode' = selMode,
|
|
285
|
+
exp0' = false,
|
|
286
|
+
exp2' = false,
|
|
287
|
+
exp4' = false,
|
|
288
|
+
inGrid' = false,
|
|
289
|
+
kind' = if (nav == "rowsFirst") "row" else "cell",
|
|
290
|
+
frow' = 0,
|
|
291
|
+
fcol' = 0,
|
|
292
|
+
selMask' = if (controlled) selMask else 0,
|
|
293
|
+
controlled' = controlled,
|
|
294
|
+
accepting' = accepting,
|
|
295
|
+
valueProp0' = valueProp0,
|
|
296
|
+
valueProp1' = valueProp1,
|
|
297
|
+
valueProp2' = valueProp2,
|
|
298
|
+
valueProp3' = valueProp3,
|
|
299
|
+
valueProp4' = valueProp4,
|
|
300
|
+
valueProp5' = valueProp5,
|
|
301
|
+
valueProp6' = valueProp6,
|
|
302
|
+
requested' = 0,
|
|
303
|
+
outcome' = "none",
|
|
304
|
+
base' = 0,
|
|
305
|
+
requests' = requests
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// pick the selection variant; the grid restarts with the navigation model kept
|
|
310
|
+
action configureSel = {
|
|
311
|
+
nondet sel = Set("single", "multi").oneOf()
|
|
312
|
+
all {
|
|
313
|
+
navModel' = navModel,
|
|
314
|
+
selMode' = sel,
|
|
315
|
+
exp0' = false,
|
|
316
|
+
exp2' = false,
|
|
317
|
+
exp4' = false,
|
|
318
|
+
inGrid' = false,
|
|
319
|
+
kind' = if (navModel == "rowsFirst") "row" else "cell",
|
|
320
|
+
frow' = 0,
|
|
321
|
+
fcol' = 0,
|
|
322
|
+
selMask' = if (controlled) selMask else 0,
|
|
323
|
+
controlled' = controlled,
|
|
324
|
+
accepting' = accepting,
|
|
325
|
+
valueProp0' = valueProp0,
|
|
326
|
+
valueProp1' = valueProp1,
|
|
327
|
+
valueProp2' = valueProp2,
|
|
328
|
+
valueProp3' = valueProp3,
|
|
329
|
+
valueProp4' = valueProp4,
|
|
330
|
+
valueProp5' = valueProp5,
|
|
331
|
+
valueProp6' = valueProp6,
|
|
332
|
+
requested' = 0,
|
|
333
|
+
outcome' = "none",
|
|
334
|
+
base' = 0,
|
|
335
|
+
requests' = requests
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// request: Tab into the grid (single selection follows the remembered tab stop)
|
|
340
|
+
action focusIn = all {
|
|
341
|
+
navModel' = navModel,
|
|
342
|
+
selMode' = selMode,
|
|
343
|
+
exp0' = exp0,
|
|
344
|
+
exp2' = exp2,
|
|
345
|
+
exp4' = exp4,
|
|
346
|
+
inGrid' = true,
|
|
347
|
+
kind' = kind,
|
|
348
|
+
frow' = frow,
|
|
349
|
+
fcol' = fcol,
|
|
350
|
+
selMask' = landed(holds, selMask, focusWant),
|
|
351
|
+
controlled' = controlled,
|
|
352
|
+
accepting' = accepting,
|
|
353
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(focusWant, 0)),
|
|
354
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(focusWant, 1)),
|
|
355
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(focusWant, 2)),
|
|
356
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(focusWant, 3)),
|
|
357
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(focusWant, 4)),
|
|
358
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(focusWant, 5)),
|
|
359
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(focusWant, 6)),
|
|
360
|
+
requested' = if (reqMoves(focusWant)) focusWant else requested,
|
|
361
|
+
outcome' = if (reqMoves(focusWant)) verdict else outcome,
|
|
362
|
+
base' = if (reqMoves(focusWant)) selMask else base,
|
|
363
|
+
requests' = if (reqMoves(focusWant)) requests + 1 else requests
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Tab out of the grid: the tab stop is remembered
|
|
367
|
+
action blur = all {
|
|
368
|
+
navModel' = navModel,
|
|
369
|
+
selMode' = selMode,
|
|
370
|
+
exp0' = exp0,
|
|
371
|
+
exp2' = exp2,
|
|
372
|
+
exp4' = exp4,
|
|
373
|
+
inGrid' = false,
|
|
374
|
+
kind' = kind,
|
|
375
|
+
frow' = frow,
|
|
376
|
+
fcol' = fcol,
|
|
377
|
+
selMask' = selMask,
|
|
378
|
+
controlled' = controlled,
|
|
379
|
+
accepting' = accepting,
|
|
380
|
+
valueProp0' = valueProp0,
|
|
381
|
+
valueProp1' = valueProp1,
|
|
382
|
+
valueProp2' = valueProp2,
|
|
383
|
+
valueProp3' = valueProp3,
|
|
384
|
+
valueProp4' = valueProp4,
|
|
385
|
+
valueProp5' = valueProp5,
|
|
386
|
+
valueProp6' = valueProp6,
|
|
387
|
+
requested' = requested,
|
|
388
|
+
outcome' = outcome,
|
|
389
|
+
base' = base,
|
|
390
|
+
requests' = requests
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Right: collapsed row expands; expanded / leaf row -> first cell; cell -> next cell
|
|
394
|
+
action right = all {
|
|
395
|
+
navModel' = navModel,
|
|
396
|
+
selMode' = selMode,
|
|
397
|
+
exp0' = toggleIf(if (inGrid and kind == "row" and canExpand(frow)) frow else -1, 0, exp0),
|
|
398
|
+
exp2' = toggleIf(if (inGrid and kind == "row" and canExpand(frow)) frow else -1, 2, exp2),
|
|
399
|
+
exp4' = toggleIf(if (inGrid and kind == "row" and canExpand(frow)) frow else -1, 4, exp4),
|
|
400
|
+
inGrid' = inGrid,
|
|
401
|
+
kind' = if (inGrid and kind == "row" and not(canExpand(frow))) "cell" else kind,
|
|
402
|
+
frow' = frow,
|
|
403
|
+
fcol' = if (inGrid and kind == "cell") minI(fcol + 1, COLS - 1) else fcol,
|
|
404
|
+
selMask' = selMask,
|
|
405
|
+
controlled' = controlled,
|
|
406
|
+
accepting' = accepting,
|
|
407
|
+
valueProp0' = valueProp0,
|
|
408
|
+
valueProp1' = valueProp1,
|
|
409
|
+
valueProp2' = valueProp2,
|
|
410
|
+
valueProp3' = valueProp3,
|
|
411
|
+
valueProp4' = valueProp4,
|
|
412
|
+
valueProp5' = valueProp5,
|
|
413
|
+
valueProp6' = valueProp6,
|
|
414
|
+
requested' = requested,
|
|
415
|
+
outcome' = outcome,
|
|
416
|
+
base' = base,
|
|
417
|
+
requests' = requests
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Left: expanded row collapses; first cell -> row (when rows are focusable);
|
|
421
|
+
// other cell -> previous cell; collapsed / leaf row stays
|
|
422
|
+
action left = all {
|
|
423
|
+
navModel' = navModel,
|
|
424
|
+
selMode' = selMode,
|
|
425
|
+
exp0' = toggleIf(if (inGrid and kind == "row" and canCollapse(frow)) frow else -1, 0, exp0),
|
|
426
|
+
exp2' = toggleIf(if (inGrid and kind == "row" and canCollapse(frow)) frow else -1, 2, exp2),
|
|
427
|
+
exp4' = toggleIf(if (inGrid and kind == "row" and canCollapse(frow)) frow else -1, 4, exp4),
|
|
428
|
+
inGrid' = inGrid,
|
|
429
|
+
kind' = if (inGrid and kind == "cell" and fcol == 0 and rowFocusSupported(navModel)) "row" else kind,
|
|
430
|
+
frow' = frow,
|
|
431
|
+
fcol' = if (inGrid and kind == "cell" and fcol > 0) fcol - 1 else fcol,
|
|
432
|
+
selMask' = selMask,
|
|
433
|
+
controlled' = controlled,
|
|
434
|
+
accepting' = accepting,
|
|
435
|
+
valueProp0' = valueProp0,
|
|
436
|
+
valueProp1' = valueProp1,
|
|
437
|
+
valueProp2' = valueProp2,
|
|
438
|
+
valueProp3' = valueProp3,
|
|
439
|
+
valueProp4' = valueProp4,
|
|
440
|
+
valueProp5' = valueProp5,
|
|
441
|
+
valueProp6' = valueProp6,
|
|
442
|
+
requested' = requested,
|
|
443
|
+
outcome' = outcome,
|
|
444
|
+
base' = base,
|
|
445
|
+
requests' = requests
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// request: Down, next VISIBLE row (same column in cell mode)
|
|
449
|
+
action down = all {
|
|
450
|
+
navModel' = navModel,
|
|
451
|
+
selMode' = selMode,
|
|
452
|
+
exp0' = exp0,
|
|
453
|
+
exp2' = exp2,
|
|
454
|
+
exp4' = exp4,
|
|
455
|
+
inGrid' = inGrid,
|
|
456
|
+
kind' = kind,
|
|
457
|
+
frow' = if (inGrid) nextVis(frow) else frow,
|
|
458
|
+
fcol' = fcol,
|
|
459
|
+
selMask' = landed(holds, selMask, downWant),
|
|
460
|
+
controlled' = controlled,
|
|
461
|
+
accepting' = accepting,
|
|
462
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(downWant, 0)),
|
|
463
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(downWant, 1)),
|
|
464
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(downWant, 2)),
|
|
465
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(downWant, 3)),
|
|
466
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(downWant, 4)),
|
|
467
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(downWant, 5)),
|
|
468
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(downWant, 6)),
|
|
469
|
+
requested' = if (reqMoves(downWant)) downWant else requested,
|
|
470
|
+
outcome' = if (reqMoves(downWant)) verdict else outcome,
|
|
471
|
+
base' = if (reqMoves(downWant)) selMask else base,
|
|
472
|
+
requests' = if (reqMoves(downWant)) requests + 1 else requests
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// request: Up, previous VISIBLE row (same column in cell mode)
|
|
476
|
+
action up = all {
|
|
477
|
+
navModel' = navModel,
|
|
478
|
+
selMode' = selMode,
|
|
479
|
+
exp0' = exp0,
|
|
480
|
+
exp2' = exp2,
|
|
481
|
+
exp4' = exp4,
|
|
482
|
+
inGrid' = inGrid,
|
|
483
|
+
kind' = kind,
|
|
484
|
+
frow' = if (inGrid) prevVis(frow) else frow,
|
|
485
|
+
fcol' = fcol,
|
|
486
|
+
selMask' = landed(holds, selMask, upWant),
|
|
487
|
+
controlled' = controlled,
|
|
488
|
+
accepting' = accepting,
|
|
489
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(upWant, 0)),
|
|
490
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(upWant, 1)),
|
|
491
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(upWant, 2)),
|
|
492
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(upWant, 3)),
|
|
493
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(upWant, 4)),
|
|
494
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(upWant, 5)),
|
|
495
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(upWant, 6)),
|
|
496
|
+
requested' = if (reqMoves(upWant)) upWant else requested,
|
|
497
|
+
outcome' = if (reqMoves(upWant)) verdict else outcome,
|
|
498
|
+
base' = if (reqMoves(upWant)) selMask else base,
|
|
499
|
+
requests' = if (reqMoves(upWant)) requests + 1 else requests
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// request: Home; row -> first row, cell -> first cell of the row
|
|
503
|
+
action home = all {
|
|
504
|
+
navModel' = navModel,
|
|
505
|
+
selMode' = selMode,
|
|
506
|
+
exp0' = exp0,
|
|
507
|
+
exp2' = exp2,
|
|
508
|
+
exp4' = exp4,
|
|
509
|
+
inGrid' = inGrid,
|
|
510
|
+
kind' = kind,
|
|
511
|
+
frow' = if (inGrid) homeRow else frow,
|
|
512
|
+
fcol' = if (inGrid and kind == "cell") 0 else fcol,
|
|
513
|
+
selMask' = landed(holds, selMask, homeWant),
|
|
514
|
+
controlled' = controlled,
|
|
515
|
+
accepting' = accepting,
|
|
516
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(homeWant, 0)),
|
|
517
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(homeWant, 1)),
|
|
518
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(homeWant, 2)),
|
|
519
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(homeWant, 3)),
|
|
520
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(homeWant, 4)),
|
|
521
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(homeWant, 5)),
|
|
522
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(homeWant, 6)),
|
|
523
|
+
requested' = if (reqMoves(homeWant)) homeWant else requested,
|
|
524
|
+
outcome' = if (reqMoves(homeWant)) verdict else outcome,
|
|
525
|
+
base' = if (reqMoves(homeWant)) selMask else base,
|
|
526
|
+
requests' = if (reqMoves(homeWant)) requests + 1 else requests
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// request: End; row -> last visible row, cell -> last cell of the row
|
|
530
|
+
action end = all {
|
|
531
|
+
navModel' = navModel,
|
|
532
|
+
selMode' = selMode,
|
|
533
|
+
exp0' = exp0,
|
|
534
|
+
exp2' = exp2,
|
|
535
|
+
exp4' = exp4,
|
|
536
|
+
inGrid' = inGrid,
|
|
537
|
+
kind' = kind,
|
|
538
|
+
frow' = if (inGrid) endRow else frow,
|
|
539
|
+
fcol' = if (inGrid and kind == "cell") COLS - 1 else fcol,
|
|
540
|
+
selMask' = landed(holds, selMask, endWant),
|
|
541
|
+
controlled' = controlled,
|
|
542
|
+
accepting' = accepting,
|
|
543
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(endWant, 0)),
|
|
544
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(endWant, 1)),
|
|
545
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(endWant, 2)),
|
|
546
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(endWant, 3)),
|
|
547
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(endWant, 4)),
|
|
548
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(endWant, 5)),
|
|
549
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(endWant, 6)),
|
|
550
|
+
requested' = if (reqMoves(endWant)) endWant else requested,
|
|
551
|
+
outcome' = if (reqMoves(endWant)) verdict else outcome,
|
|
552
|
+
base' = if (reqMoves(endWant)) selMask else base,
|
|
553
|
+
requests' = if (reqMoves(endWant)) requests + 1 else requests
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// request: Ctrl+Home, first row (cell mode keeps the column)
|
|
557
|
+
action ctrlHome = all {
|
|
558
|
+
navModel' = navModel,
|
|
559
|
+
selMode' = selMode,
|
|
560
|
+
exp0' = exp0,
|
|
561
|
+
exp2' = exp2,
|
|
562
|
+
exp4' = exp4,
|
|
563
|
+
inGrid' = inGrid,
|
|
564
|
+
kind' = kind,
|
|
565
|
+
frow' = if (inGrid) 0 else frow,
|
|
566
|
+
fcol' = fcol,
|
|
567
|
+
selMask' = landed(holds, selMask, ctrlHomeWant),
|
|
568
|
+
controlled' = controlled,
|
|
569
|
+
accepting' = accepting,
|
|
570
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(ctrlHomeWant, 0)),
|
|
571
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(ctrlHomeWant, 1)),
|
|
572
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(ctrlHomeWant, 2)),
|
|
573
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(ctrlHomeWant, 3)),
|
|
574
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(ctrlHomeWant, 4)),
|
|
575
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(ctrlHomeWant, 5)),
|
|
576
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(ctrlHomeWant, 6)),
|
|
577
|
+
requested' = if (reqMoves(ctrlHomeWant)) ctrlHomeWant else requested,
|
|
578
|
+
outcome' = if (reqMoves(ctrlHomeWant)) verdict else outcome,
|
|
579
|
+
base' = if (reqMoves(ctrlHomeWant)) selMask else base,
|
|
580
|
+
requests' = if (reqMoves(ctrlHomeWant)) requests + 1 else requests
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// request: Ctrl+End, last visible row (cell mode keeps the column)
|
|
584
|
+
action ctrlEnd = all {
|
|
585
|
+
navModel' = navModel,
|
|
586
|
+
selMode' = selMode,
|
|
587
|
+
exp0' = exp0,
|
|
588
|
+
exp2' = exp2,
|
|
589
|
+
exp4' = exp4,
|
|
590
|
+
inGrid' = inGrid,
|
|
591
|
+
kind' = kind,
|
|
592
|
+
frow' = if (inGrid) lastVis else frow,
|
|
593
|
+
fcol' = fcol,
|
|
594
|
+
selMask' = landed(holds, selMask, ctrlEndWant),
|
|
595
|
+
controlled' = controlled,
|
|
596
|
+
accepting' = accepting,
|
|
597
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(ctrlEndWant, 0)),
|
|
598
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(ctrlEndWant, 1)),
|
|
599
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(ctrlEndWant, 2)),
|
|
600
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(ctrlEndWant, 3)),
|
|
601
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(ctrlEndWant, 4)),
|
|
602
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(ctrlEndWant, 5)),
|
|
603
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(ctrlEndWant, 6)),
|
|
604
|
+
requested' = if (reqMoves(ctrlEndWant)) ctrlEndWant else requested,
|
|
605
|
+
outcome' = if (reqMoves(ctrlEndWant)) verdict else outcome,
|
|
606
|
+
base' = if (reqMoves(ctrlEndWant)) selMask else base,
|
|
607
|
+
requests' = if (reqMoves(ctrlEndWant)) requests + 1 else requests
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Enter: with cells-only focus, on the first cell of a parent row, toggles its children
|
|
611
|
+
action enter = all {
|
|
612
|
+
navModel' = navModel,
|
|
613
|
+
selMode' = selMode,
|
|
614
|
+
exp0' = toggleIf(if (inGrid and navModel == "cellsOnly" and kind == "cell" and fcol == 0 and hasChildren(frow)) frow else -1, 0, exp0),
|
|
615
|
+
exp2' = toggleIf(if (inGrid and navModel == "cellsOnly" and kind == "cell" and fcol == 0 and hasChildren(frow)) frow else -1, 2, exp2),
|
|
616
|
+
exp4' = toggleIf(if (inGrid and navModel == "cellsOnly" and kind == "cell" and fcol == 0 and hasChildren(frow)) frow else -1, 4, exp4),
|
|
617
|
+
inGrid' = inGrid,
|
|
618
|
+
kind' = kind,
|
|
619
|
+
frow' = frow,
|
|
620
|
+
fcol' = fcol,
|
|
621
|
+
selMask' = selMask,
|
|
622
|
+
controlled' = controlled,
|
|
623
|
+
accepting' = accepting,
|
|
624
|
+
valueProp0' = valueProp0,
|
|
625
|
+
valueProp1' = valueProp1,
|
|
626
|
+
valueProp2' = valueProp2,
|
|
627
|
+
valueProp3' = valueProp3,
|
|
628
|
+
valueProp4' = valueProp4,
|
|
629
|
+
valueProp5' = valueProp5,
|
|
630
|
+
valueProp6' = valueProp6,
|
|
631
|
+
requested' = requested,
|
|
632
|
+
outcome' = outcome,
|
|
633
|
+
base' = base,
|
|
634
|
+
requests' = requests
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// request: click the expander of parent row r; single selection follows the
|
|
638
|
+
// relocated focus
|
|
639
|
+
action toggleRow = {
|
|
640
|
+
nondet r = Set(0, 2, 4).oneOf()
|
|
641
|
+
all {
|
|
642
|
+
navModel' = navModel,
|
|
643
|
+
selMode' = selMode,
|
|
644
|
+
exp0' = toggleIf(clickTarget(r), 0, exp0),
|
|
645
|
+
exp2' = toggleIf(clickTarget(r), 2, exp2),
|
|
646
|
+
exp4' = toggleIf(clickTarget(r), 4, exp4),
|
|
647
|
+
inGrid' = inGrid,
|
|
648
|
+
kind' = kind,
|
|
649
|
+
frow' = rowAfterToggle(r),
|
|
650
|
+
fcol' = fcol,
|
|
651
|
+
selMask' = landed(holds, selMask, toggleRowWant(r)),
|
|
652
|
+
controlled' = controlled,
|
|
653
|
+
accepting' = accepting,
|
|
654
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(toggleRowWant(r), 0)),
|
|
655
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(toggleRowWant(r), 1)),
|
|
656
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(toggleRowWant(r), 2)),
|
|
657
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(toggleRowWant(r), 3)),
|
|
658
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(toggleRowWant(r), 4)),
|
|
659
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(toggleRowWant(r), 5)),
|
|
660
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(toggleRowWant(r), 6)),
|
|
661
|
+
requested' = if (reqMoves(toggleRowWant(r))) toggleRowWant(r) else requested,
|
|
662
|
+
outcome' = if (reqMoves(toggleRowWant(r))) verdict else outcome,
|
|
663
|
+
base' = if (reqMoves(toggleRowWant(r))) selMask else base,
|
|
664
|
+
requests' = if (reqMoves(toggleRowWant(r))) requests + 1 else requests
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// request: click cell number (row * COLS + col); an unrendered cell cannot be clicked
|
|
669
|
+
action clickCell = {
|
|
670
|
+
nondet cell = Set(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20).oneOf()
|
|
671
|
+
all {
|
|
672
|
+
navModel' = navModel,
|
|
673
|
+
selMode' = selMode,
|
|
674
|
+
exp0' = exp0,
|
|
675
|
+
exp2' = exp2,
|
|
676
|
+
exp4' = exp4,
|
|
677
|
+
inGrid' = if (visible(cell / COLS)) true else inGrid,
|
|
678
|
+
kind' = if (visible(cell / COLS)) "cell" else kind,
|
|
679
|
+
frow' = if (visible(cell / COLS)) cell / COLS else frow,
|
|
680
|
+
fcol' = if (visible(cell / COLS)) cell % COLS else fcol,
|
|
681
|
+
selMask' = landed(holds, selMask, cellWant(cell)),
|
|
682
|
+
controlled' = controlled,
|
|
683
|
+
accepting' = accepting,
|
|
684
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(cellWant(cell), 0)),
|
|
685
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(cellWant(cell), 1)),
|
|
686
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(cellWant(cell), 2)),
|
|
687
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(cellWant(cell), 3)),
|
|
688
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(cellWant(cell), 4)),
|
|
689
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(cellWant(cell), 5)),
|
|
690
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(cellWant(cell), 6)),
|
|
691
|
+
requested' = if (reqMoves(cellWant(cell))) cellWant(cell) else requested,
|
|
692
|
+
outcome' = if (reqMoves(cellWant(cell))) verdict else outcome,
|
|
693
|
+
base' = if (reqMoves(cellWant(cell))) selMask else base,
|
|
694
|
+
requests' = if (reqMoves(cellWant(cell))) requests + 1 else requests
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// request: Shift+Space, multi toggles the focused row's selection
|
|
699
|
+
action toggleSelect = all {
|
|
700
|
+
navModel' = navModel,
|
|
701
|
+
selMode' = selMode,
|
|
702
|
+
exp0' = exp0,
|
|
703
|
+
exp2' = exp2,
|
|
704
|
+
exp4' = exp4,
|
|
705
|
+
inGrid' = inGrid,
|
|
706
|
+
kind' = kind,
|
|
707
|
+
frow' = frow,
|
|
708
|
+
fcol' = fcol,
|
|
709
|
+
selMask' = landed(holds, selMask, toggleSelectWant),
|
|
710
|
+
controlled' = controlled,
|
|
711
|
+
accepting' = accepting,
|
|
712
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(toggleSelectWant, 0)),
|
|
713
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(toggleSelectWant, 1)),
|
|
714
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(toggleSelectWant, 2)),
|
|
715
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(toggleSelectWant, 3)),
|
|
716
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(toggleSelectWant, 4)),
|
|
717
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(toggleSelectWant, 5)),
|
|
718
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(toggleSelectWant, 6)),
|
|
719
|
+
requested' = if (reqMoves(toggleSelectWant)) toggleSelectWant else requested,
|
|
720
|
+
outcome' = if (reqMoves(toggleSelectWant)) verdict else outcome,
|
|
721
|
+
base' = if (reqMoves(toggleSelectWant)) selMask else base,
|
|
722
|
+
requests' = if (reqMoves(toggleSelectWant)) requests + 1 else requests
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// request: Ctrl+A, multi selects every row
|
|
726
|
+
action selectAll = all {
|
|
727
|
+
navModel' = navModel,
|
|
728
|
+
selMode' = selMode,
|
|
729
|
+
exp0' = exp0,
|
|
730
|
+
exp2' = exp2,
|
|
731
|
+
exp4' = exp4,
|
|
732
|
+
inGrid' = inGrid,
|
|
733
|
+
kind' = kind,
|
|
734
|
+
frow' = frow,
|
|
735
|
+
fcol' = fcol,
|
|
736
|
+
selMask' = landed(holds, selMask, selectAllWant),
|
|
737
|
+
controlled' = controlled,
|
|
738
|
+
accepting' = accepting,
|
|
739
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(selectAllWant, 0)),
|
|
740
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(selectAllWant, 1)),
|
|
741
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(selectAllWant, 2)),
|
|
742
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(selectAllWant, 3)),
|
|
743
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(selectAllWant, 4)),
|
|
744
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(selectAllWant, 5)),
|
|
745
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(selectAllWant, 6)),
|
|
746
|
+
requested' = if (reqMoves(selectAllWant)) selectAllWant else requested,
|
|
747
|
+
outcome' = if (reqMoves(selectAllWant)) verdict else outcome,
|
|
748
|
+
base' = if (reqMoves(selectAllWant)) selMask else base,
|
|
749
|
+
requests' = if (reqMoves(selectAllWant)) requests + 1 else requests
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// request: Shift+Down on a focused row (multi), move down and extend the selection
|
|
753
|
+
action extendDown = all {
|
|
754
|
+
navModel' = navModel,
|
|
755
|
+
selMode' = selMode,
|
|
756
|
+
exp0' = exp0,
|
|
757
|
+
exp2' = exp2,
|
|
758
|
+
exp4' = exp4,
|
|
759
|
+
inGrid' = inGrid,
|
|
760
|
+
kind' = kind,
|
|
761
|
+
frow' = if (inGrid and selMode == "multi" and kind == "row") nextVis(frow) else frow,
|
|
762
|
+
fcol' = fcol,
|
|
763
|
+
selMask' = landed(holds, selMask, extendDownWant),
|
|
764
|
+
controlled' = controlled,
|
|
765
|
+
accepting' = accepting,
|
|
766
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(extendDownWant, 0)),
|
|
767
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(extendDownWant, 1)),
|
|
768
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(extendDownWant, 2)),
|
|
769
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(extendDownWant, 3)),
|
|
770
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(extendDownWant, 4)),
|
|
771
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(extendDownWant, 5)),
|
|
772
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(extendDownWant, 6)),
|
|
773
|
+
requested' = if (reqMoves(extendDownWant)) extendDownWant else requested,
|
|
774
|
+
outcome' = if (reqMoves(extendDownWant)) verdict else outcome,
|
|
775
|
+
base' = if (reqMoves(extendDownWant)) selMask else base,
|
|
776
|
+
requests' = if (reqMoves(extendDownWant)) requests + 1 else requests
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// request: Shift+Up on a focused row (multi), move up and extend the selection
|
|
780
|
+
action extendUp = all {
|
|
781
|
+
navModel' = navModel,
|
|
782
|
+
selMode' = selMode,
|
|
783
|
+
exp0' = exp0,
|
|
784
|
+
exp2' = exp2,
|
|
785
|
+
exp4' = exp4,
|
|
786
|
+
inGrid' = inGrid,
|
|
787
|
+
kind' = kind,
|
|
788
|
+
frow' = if (inGrid and selMode == "multi" and kind == "row") prevVis(frow) else frow,
|
|
789
|
+
fcol' = fcol,
|
|
790
|
+
selMask' = landed(holds, selMask, extendUpWant),
|
|
791
|
+
controlled' = controlled,
|
|
792
|
+
accepting' = accepting,
|
|
793
|
+
valueProp0' = propAfter(follows, valueProp0, bitSet(extendUpWant, 0)),
|
|
794
|
+
valueProp1' = propAfter(follows, valueProp1, bitSet(extendUpWant, 1)),
|
|
795
|
+
valueProp2' = propAfter(follows, valueProp2, bitSet(extendUpWant, 2)),
|
|
796
|
+
valueProp3' = propAfter(follows, valueProp3, bitSet(extendUpWant, 3)),
|
|
797
|
+
valueProp4' = propAfter(follows, valueProp4, bitSet(extendUpWant, 4)),
|
|
798
|
+
valueProp5' = propAfter(follows, valueProp5, bitSet(extendUpWant, 5)),
|
|
799
|
+
valueProp6' = propAfter(follows, valueProp6, bitSet(extendUpWant, 6)),
|
|
800
|
+
requested' = if (reqMoves(extendUpWant)) extendUpWant else requested,
|
|
801
|
+
outcome' = if (reqMoves(extendUpWant)) verdict else outcome,
|
|
802
|
+
base' = if (reqMoves(extendUpWant)) selMask else base,
|
|
803
|
+
requests' = if (reqMoves(extendUpWant)) requests + 1 else requests
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// commit: the host writes the controlled selection (a no-op while uncontrolled).
|
|
807
|
+
// The pool is one row at a time: a host sets a single row or clears.
|
|
808
|
+
action setValue = {
|
|
809
|
+
nondet v = Set(0, 1, 2, 4, 8, 16, 32, 64).oneOf()
|
|
810
|
+
all {
|
|
811
|
+
navModel' = navModel,
|
|
812
|
+
selMode' = selMode,
|
|
813
|
+
exp0' = exp0,
|
|
814
|
+
exp2' = exp2,
|
|
815
|
+
exp4' = exp4,
|
|
816
|
+
inGrid' = inGrid,
|
|
817
|
+
kind' = kind,
|
|
818
|
+
frow' = frow,
|
|
819
|
+
fcol' = fcol,
|
|
820
|
+
selMask' = if (controlled) v else selMask,
|
|
821
|
+
controlled' = controlled,
|
|
822
|
+
accepting' = accepting,
|
|
823
|
+
valueProp0' = if (controlled) bitSet(v, 0) else valueProp0,
|
|
824
|
+
valueProp1' = if (controlled) bitSet(v, 1) else valueProp1,
|
|
825
|
+
valueProp2' = if (controlled) bitSet(v, 2) else valueProp2,
|
|
826
|
+
valueProp3' = if (controlled) bitSet(v, 3) else valueProp3,
|
|
827
|
+
valueProp4' = if (controlled) bitSet(v, 4) else valueProp4,
|
|
828
|
+
valueProp5' = if (controlled) bitSet(v, 5) else valueProp5,
|
|
829
|
+
valueProp6' = if (controlled) bitSet(v, 6) else valueProp6,
|
|
830
|
+
requested' = requested,
|
|
831
|
+
outcome' = if (controlled) "none" else outcome,
|
|
832
|
+
base' = base,
|
|
833
|
+
requests' = requests
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// commit: the host flips one row of its own copy of the selection
|
|
838
|
+
action hostToggle = {
|
|
839
|
+
nondet r = Set(0, 1, 2, 3, 4, 5, 6).oneOf()
|
|
840
|
+
all {
|
|
841
|
+
navModel' = navModel,
|
|
842
|
+
selMode' = selMode,
|
|
843
|
+
exp0' = exp0,
|
|
844
|
+
exp2' = exp2,
|
|
845
|
+
exp4' = exp4,
|
|
846
|
+
inGrid' = inGrid,
|
|
847
|
+
kind' = kind,
|
|
848
|
+
frow' = frow,
|
|
849
|
+
fcol' = fcol,
|
|
850
|
+
selMask' = if (controlled) flipBit(propMask, r) else selMask,
|
|
851
|
+
controlled' = controlled,
|
|
852
|
+
accepting' = accepting,
|
|
853
|
+
valueProp0' = if (and(controlled, r == 0)) not(valueProp0) else valueProp0,
|
|
854
|
+
valueProp1' = if (and(controlled, r == 1)) not(valueProp1) else valueProp1,
|
|
855
|
+
valueProp2' = if (and(controlled, r == 2)) not(valueProp2) else valueProp2,
|
|
856
|
+
valueProp3' = if (and(controlled, r == 3)) not(valueProp3) else valueProp3,
|
|
857
|
+
valueProp4' = if (and(controlled, r == 4)) not(valueProp4) else valueProp4,
|
|
858
|
+
valueProp5' = if (and(controlled, r == 5)) not(valueProp5) else valueProp5,
|
|
859
|
+
valueProp6' = if (and(controlled, r == 6)) not(valueProp6) else valueProp6,
|
|
860
|
+
requested' = requested,
|
|
861
|
+
outcome' = if (controlled) "none" else outcome,
|
|
862
|
+
base' = base,
|
|
863
|
+
requests' = requests
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// configuration: the host starts owning the selection, adopting the current rows
|
|
868
|
+
action control = all {
|
|
869
|
+
navModel' = navModel,
|
|
870
|
+
selMode' = selMode,
|
|
871
|
+
exp0' = exp0,
|
|
872
|
+
exp2' = exp2,
|
|
873
|
+
exp4' = exp4,
|
|
874
|
+
inGrid' = inGrid,
|
|
875
|
+
kind' = kind,
|
|
876
|
+
frow' = frow,
|
|
877
|
+
fcol' = fcol,
|
|
878
|
+
selMask' = selMask,
|
|
879
|
+
controlled' = true,
|
|
880
|
+
accepting' = accepting,
|
|
881
|
+
valueProp0' = bitSet(selMask, 0),
|
|
882
|
+
valueProp1' = bitSet(selMask, 1),
|
|
883
|
+
valueProp2' = bitSet(selMask, 2),
|
|
884
|
+
valueProp3' = bitSet(selMask, 3),
|
|
885
|
+
valueProp4' = bitSet(selMask, 4),
|
|
886
|
+
valueProp5' = bitSet(selMask, 5),
|
|
887
|
+
valueProp6' = bitSet(selMask, 6),
|
|
888
|
+
requested' = requested,
|
|
889
|
+
outcome' = outcome,
|
|
890
|
+
base' = base,
|
|
891
|
+
requests' = requests
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// configuration: the controlled host answers every request with its intent
|
|
895
|
+
action acceptRequests = all {
|
|
896
|
+
navModel' = navModel,
|
|
897
|
+
selMode' = selMode,
|
|
898
|
+
exp0' = exp0,
|
|
899
|
+
exp2' = exp2,
|
|
900
|
+
exp4' = exp4,
|
|
901
|
+
inGrid' = inGrid,
|
|
902
|
+
kind' = kind,
|
|
903
|
+
frow' = frow,
|
|
904
|
+
fcol' = fcol,
|
|
905
|
+
selMask' = selMask,
|
|
906
|
+
controlled' = controlled,
|
|
907
|
+
accepting' = true,
|
|
908
|
+
valueProp0' = valueProp0,
|
|
909
|
+
valueProp1' = valueProp1,
|
|
910
|
+
valueProp2' = valueProp2,
|
|
911
|
+
valueProp3' = valueProp3,
|
|
912
|
+
valueProp4' = valueProp4,
|
|
913
|
+
valueProp5' = valueProp5,
|
|
914
|
+
valueProp6' = valueProp6,
|
|
915
|
+
requested' = requested,
|
|
916
|
+
outcome' = outcome,
|
|
917
|
+
base' = base,
|
|
918
|
+
requests' = requests
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
action step = any {
|
|
922
|
+
init, configureNav, configureSel, focusIn, blur,
|
|
923
|
+
right, left, down, up, home, end, ctrlHome, ctrlEnd, enter,
|
|
924
|
+
toggleRow, clickCell, toggleSelect, selectAll, extendDown, extendUp,
|
|
925
|
+
setValue, hostToggle, control, acceptRequests
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// ---- invariant helpers -----------------------------------------------------
|
|
929
|
+
def childMirror(k: int): bool =
|
|
930
|
+
parentOf(k) < 0 or not(visible(parentOf(k))) or visible(k) == isExp(parentOf(k))
|
|
931
|
+
def rowContractOk(r: int): bool =
|
|
932
|
+
ariaLevel(r) >= 1 and ariaLevel(r) <= 3
|
|
933
|
+
and ariaPosInSet(r) >= 1 and ariaPosInSet(r) <= ariaSetSize(r)
|
|
934
|
+
and ariaLevel(r) == (if (parentOf(r) < 0) 1 else ariaLevel(parentOf(r)) + 1)
|
|
935
|
+
and (ariaExpandedOf(r) == "absent") == not(hasChildren(r))
|
|
936
|
+
|
|
937
|
+
// ---- invariants ------------------------------------------------------------
|
|
938
|
+
// the focused / tab-stop row is always rendered (never inside a collapsed subtree)
|
|
939
|
+
val FocusedRowVisible: bool = visible(frow)
|
|
940
|
+
// roving tabindex: exactly one rendered row-or-cell has tabindex=0
|
|
941
|
+
val ExactlyOneTabStop: bool = tabStops == 1
|
|
942
|
+
// the roving position stays inside the grid
|
|
943
|
+
val PositionInRange: bool = frow >= 0 and frow < ROWS and fcol >= 0 and fcol < COLS
|
|
944
|
+
// a focused row has no column
|
|
945
|
+
val RowFocusHasNoColumn: bool = kind != "row" or fcol == 0
|
|
946
|
+
// kind is one of the two modelled item types
|
|
947
|
+
val KindKnown: bool = kind == "row" or kind == "cell"
|
|
948
|
+
// cells-only models never focus a row
|
|
949
|
+
val CellsOnlyNeverFocusesRow: bool = navModel != "cellsOnly" or kind == "cell"
|
|
950
|
+
// top-level rows are always rendered
|
|
951
|
+
val TopLevelAlwaysVisible: bool = visible(0) and visible(4)
|
|
952
|
+
// aria-expanded mirrors child visibility: children of a rendered parent row are
|
|
953
|
+
// rendered exactly when it is expanded
|
|
954
|
+
val AriaExpandedMirrorsChildren: bool =
|
|
955
|
+
childMirror(0) and childMirror(1) and childMirror(2) and childMirror(3)
|
|
956
|
+
and childMirror(4) and childMirror(5) and childMirror(6)
|
|
957
|
+
// level / setsize / posinset / aria-expanded are well-formed for every row
|
|
958
|
+
val AriaContractWellFormed: bool =
|
|
959
|
+
rowContractOk(0) and rowContractOk(1) and rowContractOk(2) and rowContractOk(3)
|
|
960
|
+
and rowContractOk(4) and rowContractOk(5) and rowContractOk(6)
|
|
961
|
+
// the selection bitmask stays inside the 7 rows
|
|
962
|
+
val SelectionInRange: bool = selMask >= 0 and selMask < 128
|
|
963
|
+
// single-select: aria-selected on at most one row, and it follows the focus
|
|
964
|
+
// (uncontrolled: a controlled host owns the selection)
|
|
965
|
+
val SingleSelectAtMostOne: bool =
|
|
966
|
+
ariaSingleHoldsAtMostOne(controlled, selMode != "single", popcount(selMask))
|
|
967
|
+
val SingleSelectFollowsFocus: bool =
|
|
968
|
+
or(controlled, selMode != "single" or selMask == 0 or selMask == bit(frow))
|
|
969
|
+
val SingleSelectedWhenFocused: bool =
|
|
970
|
+
or(controlled, selMode != "single" or not(inGrid) or selMask == bit(frow))
|
|
971
|
+
// models and variants are the modelled ones
|
|
972
|
+
val ModesKnown: bool =
|
|
973
|
+
(navModel == "rowsFirst" or navModel == "cellsFirst" or navModel == "cellsOnly")
|
|
974
|
+
and ariaSelModeKnown(selMode)
|
|
975
|
+
// callbacks only ever count up from zero
|
|
976
|
+
val RequestsNonNegative: bool = requests >= 0
|
|
977
|
+
// controlled: only the value prop sets the selection, so no event commits
|
|
978
|
+
val ControlledFollowsProp: bool = or(not(controlled), selMask == propMask)
|
|
979
|
+
// a rejected request leaves the selection as it found it
|
|
980
|
+
val RejectedRequestUntouched: bool = or(not(outcome == "rejected"), selMask == base)
|
|
981
|
+
// an accepted request committed exactly its intent
|
|
982
|
+
val AcceptedRequestCommits: bool = or(not(outcome == "accepted"), selMask == requested)
|
|
983
|
+
}
|