@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,394 @@
|
|
|
1
|
+
// WAI-ARIA APG — Checkbox pattern: one checkbox (dual-state, optionally tri-state).
|
|
2
|
+
//
|
|
3
|
+
// Modelled from https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/ :
|
|
4
|
+
// - "About This Pattern": dual-state (checked / not checked) and tri-state
|
|
5
|
+
// (adds partially checked) checkboxes
|
|
6
|
+
// - "Keyboard Interaction": Space changes the state; Enter does nothing
|
|
7
|
+
// - "WAI-ARIA Roles, States, and Properties": role=checkbox; aria-checked is
|
|
8
|
+
// "true" / "false" / "mixed" (mixed only for a tri-state checkbox)
|
|
9
|
+
// and the Checkbox Examples (https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/examples/checkbox/):
|
|
10
|
+
// Space is armed on keydown and flips on keyup (the native checkbox behaviour).
|
|
11
|
+
// The group controller that derives "mixed" from its children is the separate
|
|
12
|
+
// machine specs/apg/checkbox-group.qnt.
|
|
13
|
+
//
|
|
14
|
+
// Attribute contract (derived from state, never stored):
|
|
15
|
+
// role = "checkbox"
|
|
16
|
+
// aria-checked = "true" | "false" | "mixed"; "mixed" only when `allowMixed`
|
|
17
|
+
// Configuration variant: `allowMixed` (dual-state vs tri-state display).
|
|
18
|
+
// Activating a mixed checkbox checks it ("mixed" -> "true"), as the group
|
|
19
|
+
// controller does ("Checking the overall checkbox checks all options").
|
|
20
|
+
//
|
|
21
|
+
// Request vs commit (map ticket rt1p). A user activation — Space, a pointer
|
|
22
|
+
// click — is a *request* carrying the new state (`requested`); it fires
|
|
23
|
+
// onCheckedChange (`requests` counts the calls) and then:
|
|
24
|
+
// uncontrolled the request is accepted: the state commits the intent
|
|
25
|
+
// controlled, holds the host ignores it ("rejected"): the state is untouched
|
|
26
|
+
// controlled, accepts the host answers with the same value (`accepting`): the
|
|
27
|
+
// state commits through the controlled value, not the event
|
|
28
|
+
// The controlled value (`valueProp`, possibly "mixed") is the only thing that
|
|
29
|
+
// changes state while `controlled`; `setValue` / `hostToggle` are the host
|
|
30
|
+
// writing it. `control` / `acceptRequests` are configuration events, `seed`
|
|
31
|
+
// plants an initial value (including "mixed") without counting as a request.
|
|
32
|
+
//
|
|
33
|
+
// Invariants for the model: ControlledFollowsProp (in controlled mode no event
|
|
34
|
+
// commits), RejectedRequestUntouched, AcceptedRequestCommits.
|
|
35
|
+
//
|
|
36
|
+
// `outcome` is "none" | "accepted" | "rejected" for the last request; a host
|
|
37
|
+
// write settles it back to "none". `base` is the value when it was received.
|
|
38
|
+
//
|
|
39
|
+
// Deliberate omissions:
|
|
40
|
+
// - accessible name, aria-labelledby/aria-describedby, group wiring
|
|
41
|
+
// - Enter key (the pattern lists Space only; modelled as a no-op)
|
|
42
|
+
// - required / invalid / read-only (not part of the APG checkbox pattern)
|
|
43
|
+
// - pointer-capture details
|
|
44
|
+
module apg_checkbox {
|
|
45
|
+
var focused: bool // the checkbox has keyboard focus
|
|
46
|
+
var disabled: bool // aria-disabled: focusable, never changes by user input
|
|
47
|
+
var state: str // "true" | "false" | "mixed"
|
|
48
|
+
var allowMixed: bool // config: tri-state checkbox (may display "mixed")
|
|
49
|
+
var spaceArmed: bool // Space went down on the focused checkbox, awaiting keyup
|
|
50
|
+
var pointerArmed: bool // pointer went down on the checkbox, awaiting release
|
|
51
|
+
var requests: int // onChange callbacks fired by user input (not by prop changes)
|
|
52
|
+
var controlled: bool // the host owns the value (`value` prop present)
|
|
53
|
+
var accepting: bool // controlled host that answers every request with its intent
|
|
54
|
+
var valueProp: str // the controlled value prop (meaningful while controlled)
|
|
55
|
+
var requested: str // intent of the last request
|
|
56
|
+
var outcome: str // "none" | "accepted" | "rejected"
|
|
57
|
+
var base: str // value when the last request was received
|
|
58
|
+
|
|
59
|
+
// false -> true, true -> false, mixed -> true
|
|
60
|
+
def nextState(state: str): str = if (state == "true") "false" else "true"
|
|
61
|
+
|
|
62
|
+
def spaceArms(focused: bool, disabled: bool, key: str): bool =
|
|
63
|
+
and(focused, not(disabled), key == "Space")
|
|
64
|
+
def spaceReleaseFires(armed: bool, disabled: bool, key: str): bool =
|
|
65
|
+
and(armed, not(disabled), key == "Space")
|
|
66
|
+
def pointerReleaseFires(armed: bool, disabled: bool): bool =
|
|
67
|
+
and(armed, not(disabled))
|
|
68
|
+
|
|
69
|
+
// ARIA attribute contract: "mixed" is only exposed by a tri-state checkbox
|
|
70
|
+
def ariaChecked(state: str, allowMixed: bool): str =
|
|
71
|
+
if (state == "true") "true"
|
|
72
|
+
else if (and(state == "mixed", allowMixed)) "mixed"
|
|
73
|
+
else "false"
|
|
74
|
+
|
|
75
|
+
// request / commit (map ticket rt1p)
|
|
76
|
+
def holds: bool = and(controlled, not(accepting))
|
|
77
|
+
def follows: bool = and(controlled, accepting)
|
|
78
|
+
def verdict: str = if (holds) "rejected" else "accepted"
|
|
79
|
+
// the value after a request for `want`
|
|
80
|
+
def landed(h: bool, c: str, want: str): str = if (h) c else want
|
|
81
|
+
// the value prop after a request for `want`
|
|
82
|
+
def propAfter(f: bool, p: str, want: str): str = if (f) want else p
|
|
83
|
+
|
|
84
|
+
action init = all {
|
|
85
|
+
focused' = false,
|
|
86
|
+
disabled' = false,
|
|
87
|
+
state' = "false",
|
|
88
|
+
allowMixed' = false,
|
|
89
|
+
spaceArmed' = false,
|
|
90
|
+
pointerArmed' = false,
|
|
91
|
+
requests' = 0,
|
|
92
|
+
controlled' = false,
|
|
93
|
+
accepting' = false,
|
|
94
|
+
valueProp' = "false",
|
|
95
|
+
requested' = "false",
|
|
96
|
+
outcome' = "none",
|
|
97
|
+
base' = "false"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
action focus = all {
|
|
101
|
+
focused' = true,
|
|
102
|
+
disabled' = disabled,
|
|
103
|
+
state' = state,
|
|
104
|
+
allowMixed' = allowMixed,
|
|
105
|
+
spaceArmed' = spaceArmed,
|
|
106
|
+
pointerArmed' = pointerArmed,
|
|
107
|
+
requests' = requests,
|
|
108
|
+
controlled' = controlled,
|
|
109
|
+
accepting' = accepting,
|
|
110
|
+
valueProp' = valueProp,
|
|
111
|
+
requested' = requested,
|
|
112
|
+
outcome' = outcome,
|
|
113
|
+
base' = base
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// blur cancels a pending Space press
|
|
117
|
+
action blur = all {
|
|
118
|
+
focused' = false,
|
|
119
|
+
disabled' = disabled,
|
|
120
|
+
state' = state,
|
|
121
|
+
allowMixed' = allowMixed,
|
|
122
|
+
spaceArmed' = false,
|
|
123
|
+
pointerArmed' = pointerArmed,
|
|
124
|
+
requests' = requests,
|
|
125
|
+
controlled' = controlled,
|
|
126
|
+
accepting' = accepting,
|
|
127
|
+
valueProp' = valueProp,
|
|
128
|
+
requested' = requested,
|
|
129
|
+
outcome' = outcome,
|
|
130
|
+
base' = base
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// only Space is handled on keydown (it arms); Enter and other keys do nothing
|
|
134
|
+
action key_down = {
|
|
135
|
+
nondet key = Set("Space", "Enter", "Other").oneOf()
|
|
136
|
+
all {
|
|
137
|
+
focused' = focused,
|
|
138
|
+
disabled' = disabled,
|
|
139
|
+
state' = state,
|
|
140
|
+
allowMixed' = allowMixed,
|
|
141
|
+
spaceArmed' = if (spaceArms(focused, disabled, key)) true else spaceArmed,
|
|
142
|
+
pointerArmed' = pointerArmed,
|
|
143
|
+
requests' = requests,
|
|
144
|
+
controlled' = controlled,
|
|
145
|
+
accepting' = accepting,
|
|
146
|
+
valueProp' = valueProp,
|
|
147
|
+
requested' = requested,
|
|
148
|
+
outcome' = outcome,
|
|
149
|
+
base' = base
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// request: Space released on the focused, enabled checkbox
|
|
154
|
+
action key_up = {
|
|
155
|
+
nondet key = Set("Space", "Enter", "Other").oneOf()
|
|
156
|
+
all {
|
|
157
|
+
focused' = focused,
|
|
158
|
+
disabled' = disabled,
|
|
159
|
+
state' = if (spaceReleaseFires(spaceArmed, disabled, key)) landed(holds, state, nextState(state)) else state,
|
|
160
|
+
allowMixed' = allowMixed,
|
|
161
|
+
spaceArmed' = if (key == "Space") false else spaceArmed,
|
|
162
|
+
pointerArmed' = pointerArmed,
|
|
163
|
+
requests' = if (spaceReleaseFires(spaceArmed, disabled, key)) requests + 1 else requests,
|
|
164
|
+
controlled' = controlled,
|
|
165
|
+
accepting' = accepting,
|
|
166
|
+
valueProp' = if (spaceReleaseFires(spaceArmed, disabled, key)) propAfter(follows, valueProp, nextState(state)) else valueProp,
|
|
167
|
+
requested' = if (spaceReleaseFires(spaceArmed, disabled, key)) nextState(state) else requested,
|
|
168
|
+
outcome' = if (spaceReleaseFires(spaceArmed, disabled, key)) verdict else outcome,
|
|
169
|
+
base' = if (spaceReleaseFires(spaceArmed, disabled, key)) state else base
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
action pointer_down = all {
|
|
174
|
+
focused' = true,
|
|
175
|
+
disabled' = disabled,
|
|
176
|
+
state' = state,
|
|
177
|
+
allowMixed' = allowMixed,
|
|
178
|
+
spaceArmed' = spaceArmed,
|
|
179
|
+
pointerArmed' = not(disabled),
|
|
180
|
+
requests' = requests,
|
|
181
|
+
controlled' = controlled,
|
|
182
|
+
accepting' = accepting,
|
|
183
|
+
valueProp' = valueProp,
|
|
184
|
+
requested' = requested,
|
|
185
|
+
outcome' = outcome,
|
|
186
|
+
base' = base
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// request: pointer released over the checkbox
|
|
190
|
+
action pointer_up = all {
|
|
191
|
+
focused' = focused,
|
|
192
|
+
disabled' = disabled,
|
|
193
|
+
state' = if (pointerReleaseFires(pointerArmed, disabled)) landed(holds, state, nextState(state)) else state,
|
|
194
|
+
allowMixed' = allowMixed,
|
|
195
|
+
spaceArmed' = spaceArmed,
|
|
196
|
+
pointerArmed' = false,
|
|
197
|
+
requests' = if (pointerReleaseFires(pointerArmed, disabled)) requests + 1 else requests,
|
|
198
|
+
controlled' = controlled,
|
|
199
|
+
accepting' = accepting,
|
|
200
|
+
valueProp' = if (pointerReleaseFires(pointerArmed, disabled)) propAfter(follows, valueProp, nextState(state)) else valueProp,
|
|
201
|
+
requested' = if (pointerReleaseFires(pointerArmed, disabled)) nextState(state) else requested,
|
|
202
|
+
outcome' = if (pointerReleaseFires(pointerArmed, disabled)) verdict else outcome,
|
|
203
|
+
base' = if (pointerReleaseFires(pointerArmed, disabled)) state else base
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
action pointer_cancel = all {
|
|
207
|
+
focused' = focused,
|
|
208
|
+
disabled' = disabled,
|
|
209
|
+
state' = state,
|
|
210
|
+
allowMixed' = allowMixed,
|
|
211
|
+
spaceArmed' = spaceArmed,
|
|
212
|
+
pointerArmed' = false,
|
|
213
|
+
requests' = requests,
|
|
214
|
+
controlled' = controlled,
|
|
215
|
+
accepting' = accepting,
|
|
216
|
+
valueProp' = valueProp,
|
|
217
|
+
requested' = requested,
|
|
218
|
+
outcome' = outcome,
|
|
219
|
+
base' = base
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// commit: the host writes the controlled value (a no-op while uncontrolled).
|
|
223
|
+
// A dual-state checkbox rejects "mixed".
|
|
224
|
+
action setValue = {
|
|
225
|
+
nondet incoming = Set("true", "false", "mixed").oneOf()
|
|
226
|
+
all {
|
|
227
|
+
focused' = focused,
|
|
228
|
+
disabled' = disabled,
|
|
229
|
+
state' = if (and(controlled, or(not(incoming == "mixed"), allowMixed))) incoming else state,
|
|
230
|
+
allowMixed' = allowMixed,
|
|
231
|
+
spaceArmed' = spaceArmed,
|
|
232
|
+
pointerArmed' = pointerArmed,
|
|
233
|
+
requests' = requests,
|
|
234
|
+
controlled' = controlled,
|
|
235
|
+
accepting' = accepting,
|
|
236
|
+
valueProp' = if (and(controlled, or(not(incoming == "mixed"), allowMixed))) incoming else valueProp,
|
|
237
|
+
requested' = requested,
|
|
238
|
+
outcome' = if (controlled) "none" else outcome,
|
|
239
|
+
base' = base
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// commit: the host flips its own copy of the value (an outside checkbox)
|
|
244
|
+
action hostToggle = all {
|
|
245
|
+
focused' = focused,
|
|
246
|
+
disabled' = disabled,
|
|
247
|
+
state' = if (controlled) nextState(valueProp) else state,
|
|
248
|
+
allowMixed' = allowMixed,
|
|
249
|
+
spaceArmed' = spaceArmed,
|
|
250
|
+
pointerArmed' = pointerArmed,
|
|
251
|
+
requests' = requests,
|
|
252
|
+
controlled' = controlled,
|
|
253
|
+
accepting' = accepting,
|
|
254
|
+
valueProp' = if (controlled) nextState(valueProp) else valueProp,
|
|
255
|
+
requested' = requested,
|
|
256
|
+
outcome' = if (controlled) "none" else outcome,
|
|
257
|
+
base' = base
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// configuration: the host starts owning the value, adopting the current one
|
|
261
|
+
action control = all {
|
|
262
|
+
focused' = focused,
|
|
263
|
+
disabled' = disabled,
|
|
264
|
+
state' = state,
|
|
265
|
+
allowMixed' = allowMixed,
|
|
266
|
+
spaceArmed' = spaceArmed,
|
|
267
|
+
pointerArmed' = pointerArmed,
|
|
268
|
+
requests' = requests,
|
|
269
|
+
controlled' = true,
|
|
270
|
+
accepting' = accepting,
|
|
271
|
+
valueProp' = state,
|
|
272
|
+
requested' = requested,
|
|
273
|
+
outcome' = outcome,
|
|
274
|
+
base' = base
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// configuration: the controlled host answers every request with its intent
|
|
278
|
+
action acceptRequests = all {
|
|
279
|
+
focused' = focused,
|
|
280
|
+
disabled' = disabled,
|
|
281
|
+
state' = state,
|
|
282
|
+
allowMixed' = allowMixed,
|
|
283
|
+
spaceArmed' = spaceArmed,
|
|
284
|
+
pointerArmed' = pointerArmed,
|
|
285
|
+
requests' = requests,
|
|
286
|
+
controlled' = controlled,
|
|
287
|
+
accepting' = true,
|
|
288
|
+
valueProp' = valueProp,
|
|
289
|
+
requested' = requested,
|
|
290
|
+
outcome' = outcome,
|
|
291
|
+
base' = base
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// configuration: plant an initial value (including "mixed") without counting
|
|
295
|
+
// as a request. Inert while controlled: only the value prop may move the state.
|
|
296
|
+
action seed = {
|
|
297
|
+
nondet incoming = Set("true", "false", "mixed").oneOf()
|
|
298
|
+
all {
|
|
299
|
+
focused' = focused,
|
|
300
|
+
disabled' = disabled,
|
|
301
|
+
state' = if (controlled) state else (if (and(incoming == "mixed", not(allowMixed))) state else incoming),
|
|
302
|
+
allowMixed' = allowMixed,
|
|
303
|
+
spaceArmed' = spaceArmed,
|
|
304
|
+
pointerArmed' = pointerArmed,
|
|
305
|
+
requests' = requests,
|
|
306
|
+
controlled' = controlled,
|
|
307
|
+
accepting' = accepting,
|
|
308
|
+
valueProp' = valueProp,
|
|
309
|
+
requested' = requested,
|
|
310
|
+
outcome' = "none",
|
|
311
|
+
base' = base
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// prop change: aria-disabled. Disabling drops any pending press.
|
|
316
|
+
action set_disabled = {
|
|
317
|
+
nondet value = Set(true, false).oneOf()
|
|
318
|
+
all {
|
|
319
|
+
focused' = focused,
|
|
320
|
+
disabled' = value,
|
|
321
|
+
state' = state,
|
|
322
|
+
allowMixed' = allowMixed,
|
|
323
|
+
spaceArmed' = if (value) false else spaceArmed,
|
|
324
|
+
pointerArmed' = if (value) false else pointerArmed,
|
|
325
|
+
requests' = requests,
|
|
326
|
+
controlled' = controlled,
|
|
327
|
+
accepting' = accepting,
|
|
328
|
+
valueProp' = valueProp,
|
|
329
|
+
requested' = requested,
|
|
330
|
+
outcome' = outcome,
|
|
331
|
+
base' = base
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// prop change: dual-state <-> tri-state. Going dual-state resolves "mixed".
|
|
336
|
+
action set_allow_mixed = {
|
|
337
|
+
nondet value = Set(true, false).oneOf()
|
|
338
|
+
all {
|
|
339
|
+
focused' = focused,
|
|
340
|
+
disabled' = disabled,
|
|
341
|
+
state' = if (and(not(value), state == "mixed")) "false" else state,
|
|
342
|
+
allowMixed' = value,
|
|
343
|
+
spaceArmed' = spaceArmed,
|
|
344
|
+
pointerArmed' = pointerArmed,
|
|
345
|
+
requests' = requests,
|
|
346
|
+
controlled' = controlled,
|
|
347
|
+
accepting' = accepting,
|
|
348
|
+
valueProp' = if (controlled) (if (and(not(value), valueProp == "mixed")) "false" else valueProp) else valueProp,
|
|
349
|
+
requested' = requested,
|
|
350
|
+
outcome' = "none",
|
|
351
|
+
base' = base
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
action step = any {
|
|
356
|
+
init,
|
|
357
|
+
focus,
|
|
358
|
+
blur,
|
|
359
|
+
key_down,
|
|
360
|
+
key_up,
|
|
361
|
+
pointer_down,
|
|
362
|
+
pointer_up,
|
|
363
|
+
pointer_cancel,
|
|
364
|
+
setValue,
|
|
365
|
+
hostToggle,
|
|
366
|
+
control,
|
|
367
|
+
acceptRequests,
|
|
368
|
+
seed,
|
|
369
|
+
set_disabled,
|
|
370
|
+
set_allow_mixed
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
val StateValid: bool = or(state == "true", or(state == "false", state == "mixed"))
|
|
374
|
+
// a dual-state checkbox is never mixed
|
|
375
|
+
val DualStateNeverMixed: bool = or(allowMixed, state != "mixed")
|
|
376
|
+
// aria-checked is "mixed" exactly when the tri-state checkbox is partially checked
|
|
377
|
+
val AriaCheckedContract: bool = and(
|
|
378
|
+
(ariaChecked(state, allowMixed) == "mixed") == and(allowMixed, state == "mixed"),
|
|
379
|
+
(ariaChecked(state, allowMixed) == "true") == (state == "true"),
|
|
380
|
+
(ariaChecked(state, allowMixed) == "false") == (state == "false")
|
|
381
|
+
)
|
|
382
|
+
// a pending Space press needs a focused, enabled checkbox
|
|
383
|
+
val SpaceArmedImpliesFocusedEnabled: bool =
|
|
384
|
+
or(not(spaceArmed), and(focused, not(disabled)))
|
|
385
|
+
// a pending pointer press never survives disabling
|
|
386
|
+
val PointerArmedImpliesEnabled: bool =
|
|
387
|
+
or(not(pointerArmed), not(disabled))
|
|
388
|
+
// controlled: only the value prop sets the state, so no event commits
|
|
389
|
+
val ControlledFollowsProp: bool = or(not(controlled), state == valueProp)
|
|
390
|
+
// a rejected request leaves the state as it found it
|
|
391
|
+
val RejectedRequestUntouched: bool = or(not(outcome == "rejected"), state == base)
|
|
392
|
+
// an accepted request committed exactly its intent
|
|
393
|
+
val AcceptedRequestCommits: bool = or(not(outcome == "accepted"), state == requested)
|
|
394
|
+
}
|