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