@cssints/apg-specs 0.0.0-stage → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +65 -2
- package/dist/apg/accordion.bridge.d.mts +35 -0
- package/dist/apg/accordion.bridge.mjs +326 -0
- package/dist/apg/accordion.model.json +8397 -0
- package/dist/apg/alert.bridge.d.mts +22 -0
- package/dist/apg/alert.bridge.mjs +86 -0
- package/dist/apg/alert.model.json +871 -0
- package/dist/apg/alertdialog.bridge.d.mts +21 -0
- package/dist/apg/alertdialog.bridge.mjs +221 -0
- package/dist/apg/alertdialog.model.json +5232 -0
- package/dist/apg/breadcrumb.bridge.d.mts +26 -0
- package/dist/apg/breadcrumb.bridge.mjs +120 -0
- package/dist/apg/breadcrumb.model.json +574 -0
- package/dist/apg/button.bridge.d.mts +35 -0
- package/dist/apg/button.bridge.mjs +208 -0
- package/dist/apg/button.model.json +5814 -0
- package/dist/apg/carousel.bridge.d.mts +44 -0
- package/dist/apg/carousel.bridge.mjs +262 -0
- package/dist/apg/carousel.model.json +8963 -0
- package/dist/apg/checkbox-group.bridge.d.mts +25 -0
- package/dist/apg/checkbox-group.bridge.mjs +276 -0
- package/dist/apg/checkbox-group.model.json +3863 -0
- package/dist/apg/checkbox.bridge.d.mts +34 -0
- package/dist/apg/checkbox.bridge.mjs +253 -0
- package/dist/apg/checkbox.model.json +3158 -0
- package/dist/apg/combobox-autocomplete.bridge.d.mts +14 -0
- package/dist/apg/combobox-autocomplete.bridge.mjs +326 -0
- package/dist/apg/combobox-autocomplete.model.json +11586 -0
- package/dist/apg/combobox-dialog.bridge.d.mts +17 -0
- package/dist/apg/combobox-dialog.bridge.mjs +250 -0
- package/dist/apg/combobox-dialog.model.json +8981 -0
- package/dist/apg/combobox-grid.bridge.d.mts +17 -0
- package/dist/apg/combobox-grid.bridge.mjs +227 -0
- package/dist/apg/combobox-grid.model.json +8554 -0
- package/dist/apg/combobox-select-only.bridge.d.mts +24 -0
- package/dist/apg/combobox-select-only.bridge.mjs +304 -0
- package/dist/apg/combobox-select-only.model.json +7846 -0
- package/dist/apg/combobox-tree.bridge.d.mts +17 -0
- package/dist/apg/combobox-tree.bridge.mjs +211 -0
- package/dist/apg/combobox-tree.model.json +9345 -0
- package/dist/apg/dialog-modal.bridge.d.mts +35 -0
- package/dist/apg/dialog-modal.bridge.mjs +186 -0
- package/dist/apg/dialog-modal.model.json +7580 -0
- package/dist/apg/disclosure.bridge.d.mts +25 -0
- package/dist/apg/disclosure.bridge.mjs +202 -0
- package/dist/apg/disclosure.model.json +4703 -0
- package/dist/apg/feed.bridge.d.mts +31 -0
- package/dist/apg/feed.bridge.mjs +246 -0
- package/dist/apg/feed.model.json +4523 -0
- package/dist/apg/grid-selection.bridge.d.mts +13 -0
- package/dist/apg/grid-selection.bridge.mjs +160 -0
- package/dist/apg/grid-selection.model.json +6974 -0
- package/dist/apg/grid-sort.bridge.d.mts +12 -0
- package/dist/apg/grid-sort.bridge.mjs +138 -0
- package/dist/apg/grid-sort.model.json +3799 -0
- package/dist/apg/grid.bridge.d.mts +54 -0
- package/dist/apg/grid.bridge.mjs +441 -0
- package/dist/apg/grid.model.json +3802 -0
- package/dist/apg/link.bridge.d.mts +24 -0
- package/dist/apg/link.bridge.mjs +126 -0
- package/dist/apg/link.model.json +694 -0
- package/dist/apg/listbox-multi.bridge.d.mts +10 -0
- package/dist/apg/listbox-multi.bridge.mjs +261 -0
- package/dist/apg/listbox-multi.model.json +10893 -0
- package/dist/apg/listbox-rearrangeable.bridge.d.mts +20 -0
- package/dist/apg/listbox-rearrangeable.bridge.mjs +237 -0
- package/dist/apg/listbox-rearrangeable.model.json +17592 -0
- package/dist/apg/listbox-shared.d.mts +58 -0
- package/dist/apg/listbox-shared.mjs +171 -0
- package/dist/apg/listbox-single.bridge.d.mts +10 -0
- package/dist/apg/listbox-single.bridge.mjs +196 -0
- package/dist/apg/listbox-single.model.json +8024 -0
- package/dist/apg/listbox-typeahead.bridge.d.mts +10 -0
- package/dist/apg/listbox-typeahead.bridge.mjs +153 -0
- package/dist/apg/listbox-typeahead.model.json +5615 -0
- package/dist/apg/menu-button.bridge.d.mts +29 -0
- package/dist/apg/menu-button.bridge.mjs +259 -0
- package/dist/apg/menu-button.model.json +5706 -0
- package/dist/apg/menu-checkable.bridge.d.mts +25 -0
- package/dist/apg/menu-checkable.bridge.mjs +226 -0
- package/dist/apg/menu-checkable.model.json +4277 -0
- package/dist/apg/menu-menubar.bridge.d.mts +27 -0
- package/dist/apg/menu-menubar.bridge.mjs +273 -0
- package/dist/apg/menu-menubar.model.json +9540 -0
- package/dist/apg/meter.bridge.d.mts +28 -0
- package/dist/apg/meter.bridge.mjs +159 -0
- package/dist/apg/meter.model.json +1185 -0
- package/dist/apg/player.bridge.d.mts +24 -0
- package/dist/apg/player.bridge.mjs +342 -0
- package/dist/apg/player.model.json +11013 -0
- package/dist/apg/radio.bridge.d.mts +35 -0
- package/dist/apg/radio.bridge.mjs +371 -0
- package/dist/apg/radio.model.json +5011 -0
- package/dist/apg/slider-multithumb.bridge.d.mts +34 -0
- package/dist/apg/slider-multithumb.bridge.mjs +365 -0
- package/dist/apg/slider-multithumb.model.json +5774 -0
- package/dist/apg/slider.bridge.d.mts +54 -0
- package/dist/apg/slider.bridge.mjs +359 -0
- package/dist/apg/slider.model.json +4881 -0
- package/dist/apg/spinbutton.bridge.d.mts +41 -0
- package/dist/apg/spinbutton.bridge.mjs +327 -0
- package/dist/apg/spinbutton.model.json +7571 -0
- package/dist/apg/switch.bridge.d.mts +30 -0
- package/dist/apg/switch.bridge.mjs +243 -0
- package/dist/apg/switch.model.json +3159 -0
- package/dist/apg/tabs.bridge.d.mts +41 -0
- package/dist/apg/tabs.bridge.mjs +294 -0
- package/dist/apg/tabs.model.json +7659 -0
- package/dist/apg/timeline.bridge.d.mts +36 -0
- package/dist/apg/timeline.bridge.mjs +265 -0
- package/dist/apg/timeline.model.json +6189 -0
- package/dist/apg/toolbar.bridge.d.mts +25 -0
- package/dist/apg/toolbar.bridge.mjs +307 -0
- package/dist/apg/toolbar.model.json +6616 -0
- package/dist/apg/tooltip.bridge.d.mts +36 -0
- package/dist/apg/tooltip.bridge.mjs +208 -0
- package/dist/apg/tooltip.model.json +8295 -0
- package/dist/apg/treegrid.bridge.d.mts +28 -0
- package/dist/apg/treegrid.bridge.mjs +311 -0
- package/dist/apg/treegrid.model.json +17358 -0
- package/dist/apg/treeview.bridge.d.mts +26 -0
- package/dist/apg/treeview.bridge.mjs +396 -0
- package/dist/apg/treeview.model.json +13745 -0
- package/dist/apg/windowsplitter.bridge.d.mts +36 -0
- package/dist/apg/windowsplitter.bridge.mjs +242 -0
- package/dist/apg/windowsplitter.model.json +6253 -0
- package/dist/canvas/camera.model.json +6568 -0
- package/dist/canvas/canvas.bridge.d.mts +190 -0
- package/dist/canvas/canvas.bridge.mjs +729 -0
- package/dist/canvas/pointer.model.json +17095 -0
- package/dist/scenario.d.mts +233 -0
- package/dist/scenario.mjs +313 -0
- package/package.json +57 -4
- package/scenarios/apg/README.md +221 -0
- package/scenarios/apg/accordion.base-ui.json +1005 -0
- package/scenarios/apg/accordion.react-aria.json +929 -0
- package/scenarios/apg/alert.base-ui.json +81 -0
- package/scenarios/apg/alert.react-aria.json +98 -0
- package/scenarios/apg/alertdialog.base-ui.json +331 -0
- package/scenarios/apg/alertdialog.react-aria.json +225 -0
- package/scenarios/apg/breadcrumb.react-aria.json +312 -0
- package/scenarios/apg/button.ariakit.json +136 -0
- package/scenarios/apg/button.base-ui.json +747 -0
- package/scenarios/apg/button.react-aria.json +443 -0
- package/scenarios/apg/carousel.apg.json +909 -0
- package/scenarios/apg/checkbox.ariakit.json +299 -0
- package/scenarios/apg/checkbox.base-ui.json +1126 -0
- package/scenarios/apg/checkbox.react-aria.json +518 -0
- package/scenarios/apg/combobox.ariakit.json +945 -0
- package/scenarios/apg/combobox.base-ui.json +2444 -0
- package/scenarios/apg/combobox.react-aria.json +845 -0
- package/scenarios/apg/dialog-modal.ariakit.json +1102 -0
- package/scenarios/apg/dialog-modal.base-ui.json +775 -0
- package/scenarios/apg/dialog-modal.react-aria.json +638 -0
- package/scenarios/apg/disclosure.apg.json +349 -0
- package/scenarios/apg/disclosure.ariakit.json +163 -0
- package/scenarios/apg/disclosure.base-ui.json +451 -0
- package/scenarios/apg/disclosure.react-aria.json +643 -0
- package/scenarios/apg/feed.apg.json +442 -0
- package/scenarios/apg/grid.ariakit.json +197 -0
- package/scenarios/apg/grid.base-ui.json +720 -0
- package/scenarios/apg/grid.react-aria.json +969 -0
- package/scenarios/apg/landmarks.ariakit.json +93 -0
- package/scenarios/apg/landmarks.react-aria.json +330 -0
- package/scenarios/apg/link.ariakit.json +83 -0
- package/scenarios/apg/link.base-ui.json +88 -0
- package/scenarios/apg/link.react-aria.json +186 -0
- package/scenarios/apg/listbox.ariakit.json +78 -0
- package/scenarios/apg/listbox.base-ui.json +163 -0
- package/scenarios/apg/listbox.react-aria.json +1118 -0
- package/scenarios/apg/menu-button.ariakit.json +924 -0
- package/scenarios/apg/menu-button.base-ui.json +1155 -0
- package/scenarios/apg/menu-button.react-aria.json +1080 -0
- package/scenarios/apg/menubar.ariakit.json +758 -0
- package/scenarios/apg/menubar.base-ui.json +673 -0
- package/scenarios/apg/meter.base-ui.json +243 -0
- package/scenarios/apg/meter.react-aria.json +201 -0
- package/scenarios/apg/radio.ariakit.json +459 -0
- package/scenarios/apg/radio.base-ui.json +581 -0
- package/scenarios/apg/radio.react-aria.json +576 -0
- package/scenarios/apg/slider-multithumb.base-ui.json +316 -0
- package/scenarios/apg/slider-multithumb.react-aria.json +363 -0
- package/scenarios/apg/slider.base-ui.json +544 -0
- package/scenarios/apg/slider.react-aria.json +456 -0
- package/scenarios/apg/spinbutton.base-ui.json +1688 -0
- package/scenarios/apg/spinbutton.react-aria.json +426 -0
- package/scenarios/apg/switch.base-ui.json +456 -0
- package/scenarios/apg/switch.react-aria.json +303 -0
- package/scenarios/apg/table.ariakit.json +65 -0
- package/scenarios/apg/table.react-aria.json +221 -0
- package/scenarios/apg/tabs.ariakit.json +329 -0
- package/scenarios/apg/tabs.base-ui.json +875 -0
- package/scenarios/apg/tabs.react-aria.json +644 -0
- package/scenarios/apg/toolbar.ariakit.json +595 -0
- package/scenarios/apg/toolbar.base-ui.json +1189 -0
- package/scenarios/apg/toolbar.react-aria.json +383 -0
- package/scenarios/apg/tooltip.ariakit.json +441 -0
- package/scenarios/apg/tooltip.base-ui.json +411 -0
- package/scenarios/apg/tooltip.react-aria.json +454 -0
- package/scenarios/apg/treegrid.apg.json +2558 -0
- package/scenarios/apg/treegrid.react-aria.json +5094 -0
- package/scenarios/apg/treeview.apg.json +928 -0
- package/scenarios/apg/windowsplitter.apg.json +557 -0
- package/scenarios/canvas/README.md +69 -0
- package/scenarios/canvas/camera.contract.json +237 -0
- package/scenarios/canvas/camera.excalidraw.json +57 -0
- package/scenarios/canvas/marquee.excalidraw.json +122 -0
- package/scenarios/canvas/pan.excalidraw.json +46 -0
- package/scenarios/canvas/pointer.contract.json +528 -0
- package/scenarios/parts.json +291 -0
- package/shared/aria.qnt +186 -0
- package/shared/details-group.qnt +50 -0
- package/shared/focusgroup.qnt +152 -0
- package/shared/invokers.qnt +81 -0
- package/shared/popover.qnt +71 -0
- package/specs/apg/STATIC.md +103 -0
- package/specs/apg/accordion.qnt +578 -0
- package/specs/apg/alert.qnt +131 -0
- package/specs/apg/alertdialog.qnt +455 -0
- package/specs/apg/breadcrumb.qnt +88 -0
- package/specs/apg/button.qnt +445 -0
- package/specs/apg/carousel.qnt +732 -0
- package/specs/apg/checkbox-group.qnt +388 -0
- package/specs/apg/checkbox.qnt +394 -0
- package/specs/apg/combobox-autocomplete.qnt +764 -0
- package/specs/apg/combobox-dialog.qnt +627 -0
- package/specs/apg/combobox-grid.qnt +632 -0
- package/specs/apg/combobox-select-only.qnt +626 -0
- package/specs/apg/combobox-tree.qnt +633 -0
- package/specs/apg/dialog-modal.qnt +499 -0
- package/specs/apg/disclosure.qnt +309 -0
- package/specs/apg/feed.qnt +485 -0
- package/specs/apg/grid-selection.qnt +512 -0
- package/specs/apg/grid-sort.qnt +330 -0
- package/specs/apg/grid.qnt +360 -0
- package/specs/apg/link.qnt +119 -0
- package/specs/apg/listbox-multi.qnt +799 -0
- package/specs/apg/listbox-rearrangeable.qnt +801 -0
- package/specs/apg/listbox-single.qnt +511 -0
- package/specs/apg/listbox-typeahead.qnt +396 -0
- package/specs/apg/menu-button.qnt +363 -0
- package/specs/apg/menu-checkable.qnt +304 -0
- package/specs/apg/menu-menubar.qnt +531 -0
- package/specs/apg/meter.qnt +129 -0
- package/specs/apg/player.qnt +1081 -0
- package/specs/apg/radio.qnt +394 -0
- package/specs/apg/slider-multithumb.qnt +441 -0
- package/specs/apg/slider.qnt +396 -0
- package/specs/apg/spinbutton.qnt +494 -0
- package/specs/apg/switch.qnt +389 -0
- package/specs/apg/tabs.qnt +412 -0
- package/specs/apg/timeline.qnt +612 -0
- package/specs/apg/toolbar.qnt +364 -0
- package/specs/apg/tooltip.qnt +553 -0
- package/specs/apg/treegrid.qnt +983 -0
- package/specs/apg/treeview.qnt +1004 -0
- package/specs/apg/windowsplitter.qnt +487 -0
- package/specs/canvas/camera.qnt +407 -0
- package/specs/canvas/pointer.qnt +841 -0
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
// APG Tooltip — https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/ (work in
|
|
2
|
+
// progress upstream) plus WCAG 1.4.13 Content on Hover or Focus.
|
|
3
|
+
//
|
|
4
|
+
// Two triggers ("a", "b") each own one role=tooltip element. The tooltip shows
|
|
5
|
+
// on pointer hover or keyboard focus after a delay, hides after a grace period
|
|
6
|
+
// once neither the trigger nor the tooltip is engaged, and is:
|
|
7
|
+
// dismissable Escape hides it without moving pointer or focus, and it stays
|
|
8
|
+
// hidden until the trigger is left (no hover, no focus) and
|
|
9
|
+
// re-entered
|
|
10
|
+
// hoverable the pointer may travel onto the tooltip; hovering it keeps it
|
|
11
|
+
// open (the grace period is cancelled)
|
|
12
|
+
// persistent it never hides by itself while engaged: only the hide timer
|
|
13
|
+
// after disengagement, or Escape, removes it
|
|
14
|
+
//
|
|
15
|
+
// Timers are events, not clocks: `showTimer` and `hideTimer` are the delay
|
|
16
|
+
// and grace expiries, firing when the matching timer is armed. "Armed" is state:
|
|
17
|
+
// show timer `pendA`/`pendB`, set when a trigger is entered
|
|
18
|
+
// hide timer derived: a tooltip is visible and neither its trigger nor the
|
|
19
|
+
// tooltip is engaged (see `hidable`)
|
|
20
|
+
//
|
|
21
|
+
// Variant — global warm-up (`warmEnabled`): while another tooltip is visible
|
|
22
|
+
// or one was hidden within the warm window (`warm`, cleared by `warmExpire`), a
|
|
23
|
+
// newly entered trigger opens immediately and skips the show delay.
|
|
24
|
+
//
|
|
25
|
+
// Rendered attributes are state too: `describedA`/`describedB` mirror
|
|
26
|
+
// aria-describedby on each trigger. This spec removes the attribute while the
|
|
27
|
+
// tooltip is hidden; keeping it permanently is also valid per APG.
|
|
28
|
+
//
|
|
29
|
+
// Request vs commit (map ticket rt1p). The visible tooltip is the controlled
|
|
30
|
+
// value, packed as 0 = none, 1 = a, 2 = b. Every user-intent event (focusIn,
|
|
31
|
+
// focusOut, pointerEnter, pointerLeave, showTimer, hideTimer, escape) is a
|
|
32
|
+
// request carrying the visible value it wants (`requested`). It fires
|
|
33
|
+
// onValueChange (`requests` counts the calls) when the intent differs from the
|
|
34
|
+
// current value, and then:
|
|
35
|
+
// uncontrolled the request is accepted: the tooltip commits the intent
|
|
36
|
+
// controlled, holds the host ignores it ("rejected"): `shown` is untouched
|
|
37
|
+
// controlled, accepts the host writes the intent back (`accepting`)
|
|
38
|
+
// `valueProp` is the host's copy (meaningful while `controlled`); `setValue` /
|
|
39
|
+
// `hostToggle` are the host writing it, the only commit while controlled.
|
|
40
|
+
// `control` / `acceptRequests` configure the host (init is literal-only). The
|
|
41
|
+
// non-visible state (focus, pointer, pending timers) still tracks the event, so
|
|
42
|
+
// only the visible commit is gated.
|
|
43
|
+
//
|
|
44
|
+
// Invariants for the model: ControlledFollowsProp (in controlled mode no event
|
|
45
|
+
// commits), RejectedRequestUntouched, AcceptedRequestCommits.
|
|
46
|
+
//
|
|
47
|
+
// Events (user-level alphabet): focusIn(t) / focusOut, pointerEnter(t) /
|
|
48
|
+
// pointerLeave, pointerEnterTip, showTimer(t), hideTimer, escape, warmExpire,
|
|
49
|
+
// setWarmup(on).
|
|
50
|
+
//
|
|
51
|
+
// What interest invokers (`interestfor`, popover=hint) would provide natively:
|
|
52
|
+
// - show/hide on hover and focus with `interest-delay-start`/`-end`
|
|
53
|
+
// (the show delay and hide grace period above)
|
|
54
|
+
// - hoverable content: interest stays while the target is hovered
|
|
55
|
+
// - popover=hint: Escape light dismiss, and a hint replaces another open hint
|
|
56
|
+
// (at most one visible)
|
|
57
|
+
// Not provided natively: the global warm-up variant, the "stay dismissed until
|
|
58
|
+
// the trigger is left and re-entered" memory, and the aria-describedby wiring.
|
|
59
|
+
//
|
|
60
|
+
// ponytail: two triggers stand in for N; a tooltip with interactive content
|
|
61
|
+
// and tooltip-owned focus are out of scope (APG says tooltips never take focus).
|
|
62
|
+
module apg_tooltip {
|
|
63
|
+
var focus: str // "none" | "a" | "b"
|
|
64
|
+
var pointer: str // "none" | "a" | "b" | "tip" (over the visible tooltip)
|
|
65
|
+
var shownA: bool // tooltip a is visible
|
|
66
|
+
var shownB: bool // tooltip b is visible
|
|
67
|
+
var pendA: bool // show timer armed for a
|
|
68
|
+
var pendB: bool // show timer armed for b
|
|
69
|
+
var dismA: bool // a was dismissed with Escape and is still engaged
|
|
70
|
+
var dismB: bool // b was dismissed with Escape and is still engaged
|
|
71
|
+
var warm: bool // a tooltip was hidden recently (warm-up window open)
|
|
72
|
+
var warmEnabled: bool // variant switch: global warm-up on
|
|
73
|
+
var describedA: bool // aria-describedby on trigger a
|
|
74
|
+
var describedB: bool // aria-describedby on trigger b
|
|
75
|
+
var requests: int // number of requests that fired onValueChange
|
|
76
|
+
var controlled: bool // the host owns the visible tooltip (`value` prop present)
|
|
77
|
+
var accepting: bool // controlled host that answers every request with its intent
|
|
78
|
+
var valueProp: int // the controlled value prop (0 none, 1 a, 2 b)
|
|
79
|
+
var requested: int // visible value of the last request
|
|
80
|
+
var outcome: str // "none" | "accepted" | "rejected"
|
|
81
|
+
var base: int // visible value when the last request was received
|
|
82
|
+
|
|
83
|
+
val visibleT: str = if (shownA) "a" else if (shownB) "b" else "none"
|
|
84
|
+
|
|
85
|
+
def isShown(t: str): bool = if (t == "a") shownA else shownB
|
|
86
|
+
def isPend(t: str): bool = if (t == "a") pendA else pendB
|
|
87
|
+
def isDism(t: str): bool = if (t == "a") dismA else dismB
|
|
88
|
+
def otherT(t: str): str = if (t == "a") "b" else "a"
|
|
89
|
+
def entryOf(current: str, t: str): str = if (current == t) "none" else t
|
|
90
|
+
def quickOf(on: bool, w: bool, a: bool, b: bool): bool = and(on, or(w, or(a, b)))
|
|
91
|
+
|
|
92
|
+
// the trigger itself has pointer hover or focus
|
|
93
|
+
def trigEng(t: str, f: str, p: str): bool = or(f == t, p == t)
|
|
94
|
+
// the trigger or its visible tooltip is engaged
|
|
95
|
+
def engaged(t: str, f: str, p: str): bool =
|
|
96
|
+
or(trigEng(t, f, p), and(p == "tip", visibleT == t))
|
|
97
|
+
// Escape memory survives only while the trigger stays engaged
|
|
98
|
+
def dismNext(t: str, f: str, p: str): bool = and(isDism(t), trigEng(t, f, p))
|
|
99
|
+
|
|
100
|
+
// entering `e` with the warm-up variant on opens it with no delay
|
|
101
|
+
def immediateShow(t: str, f: str, p: str, e: str): bool =
|
|
102
|
+
and(
|
|
103
|
+
and(e == t, quickOf(warmEnabled, warm, shownA, shownB)),
|
|
104
|
+
and(not(dismNext(t, f, p)), not(isShown(t)))
|
|
105
|
+
)
|
|
106
|
+
def shownNext(t: str, f: str, p: str, e: str): bool =
|
|
107
|
+
or(immediateShow(t, f, p, e), and(isShown(t), not(immediateShow(otherT(t), f, p, e))))
|
|
108
|
+
def visNext(f: str, p: str, e: str): str =
|
|
109
|
+
if (shownNext("a", f, p, e)) "a" else if (shownNext("b", f, p, e)) "b" else "none"
|
|
110
|
+
def pendNext(t: str, f: str, p: str, e: str): bool =
|
|
111
|
+
and(
|
|
112
|
+
trigEng(t, f, p),
|
|
113
|
+
and(
|
|
114
|
+
not(shownNext(t, f, p, e)),
|
|
115
|
+
and(not(dismNext(t, f, p)), or(isPend(t), e == t))
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
// the pointer rests on a tooltip only while that same tooltip stays visible
|
|
119
|
+
def ptrNext(p: str, f: str, e: str): str =
|
|
120
|
+
if (and(p == "tip", visNext(f, p, e) != visibleT)) "none" else p
|
|
121
|
+
|
|
122
|
+
// the visible tooltip lost trigger and tooltip engagement: grace is running
|
|
123
|
+
def hidable(f: str, p: str): bool =
|
|
124
|
+
and(visibleT != "none", not(engaged(visibleT, f, p)))
|
|
125
|
+
|
|
126
|
+
// --- request / commit (map ticket rt1p) ----------------------------------
|
|
127
|
+
def visOf(a: bool, b: bool): int = if (a) 1 else if (b) 2 else 0
|
|
128
|
+
def visNow: int = visOf(shownA, shownB)
|
|
129
|
+
def moved(a: int, b: int): int = if (a == b) 0 else 1
|
|
130
|
+
def holds: bool = and(controlled, not(accepting))
|
|
131
|
+
def follows: bool = and(controlled, accepting)
|
|
132
|
+
def verdict: str = if (holds) "rejected" else "accepted"
|
|
133
|
+
def landed(h: bool, v: int, want: int): int = if (h) v else want
|
|
134
|
+
def propAfter(f: bool, p: int, want: int): int = if (f) want else p
|
|
135
|
+
// the visible value an event without a host would commit
|
|
136
|
+
def wantVis(f: str, p: str, e: str): int =
|
|
137
|
+
if (shownNext("a", f, p, e)) 1 else if (shownNext("b", f, p, e)) 2 else 0
|
|
138
|
+
// an Escape that would hide a visible tooltip is the request a host can veto
|
|
139
|
+
def rejectHide: bool = and(holds, visNow != 0)
|
|
140
|
+
// the host's own flip: none <-> tooltip a
|
|
141
|
+
def toggleVis: int = if (valueProp == 0) 1 else 0
|
|
142
|
+
|
|
143
|
+
action init = all {
|
|
144
|
+
focus' = "none",
|
|
145
|
+
pointer' = "none",
|
|
146
|
+
shownA' = false,
|
|
147
|
+
shownB' = false,
|
|
148
|
+
pendA' = false,
|
|
149
|
+
pendB' = false,
|
|
150
|
+
dismA' = false,
|
|
151
|
+
dismB' = false,
|
|
152
|
+
warm' = false,
|
|
153
|
+
warmEnabled' = false,
|
|
154
|
+
describedA' = false,
|
|
155
|
+
describedB' = false,
|
|
156
|
+
requests' = 0,
|
|
157
|
+
controlled' = false,
|
|
158
|
+
accepting' = false,
|
|
159
|
+
valueProp' = 0,
|
|
160
|
+
requested' = 0,
|
|
161
|
+
outcome' = "none",
|
|
162
|
+
base' = 0
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
action focusIn = {
|
|
166
|
+
nondet t = Set("a", "b").oneOf()
|
|
167
|
+
all {
|
|
168
|
+
focus' = t,
|
|
169
|
+
pointer' = ptrNext(pointer, t, entryOf(focus, t)),
|
|
170
|
+
shownA' = if (holds) shownA else (wantVis(t, pointer, entryOf(focus, t)) == 1),
|
|
171
|
+
shownB' = if (holds) shownB else (wantVis(t, pointer, entryOf(focus, t)) == 2),
|
|
172
|
+
pendA' = pendNext("a", t, pointer, entryOf(focus, t)),
|
|
173
|
+
pendB' = pendNext("b", t, pointer, entryOf(focus, t)),
|
|
174
|
+
dismA' = dismNext("a", t, pointer),
|
|
175
|
+
dismB' = dismNext("b", t, pointer),
|
|
176
|
+
warm' = warm,
|
|
177
|
+
warmEnabled' = warmEnabled,
|
|
178
|
+
describedA' = if (holds) describedA else (wantVis(t, pointer, entryOf(focus, t)) == 1),
|
|
179
|
+
describedB' = if (holds) describedB else (wantVis(t, pointer, entryOf(focus, t)) == 2),
|
|
180
|
+
requests' = requests + moved(visNow, wantVis(t, pointer, entryOf(focus, t))),
|
|
181
|
+
controlled' = controlled,
|
|
182
|
+
accepting' = accepting,
|
|
183
|
+
valueProp' = propAfter(follows, valueProp, wantVis(t, pointer, entryOf(focus, t))),
|
|
184
|
+
requested' = if (visNow == wantVis(t, pointer, entryOf(focus, t))) requested else wantVis(t, pointer, entryOf(focus, t)),
|
|
185
|
+
outcome' = if (visNow == wantVis(t, pointer, entryOf(focus, t))) outcome else verdict,
|
|
186
|
+
base' = if (visNow == wantVis(t, pointer, entryOf(focus, t))) base else visNow
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
action focusOut = all {
|
|
191
|
+
focus' = "none",
|
|
192
|
+
pointer' = ptrNext(pointer, "none", "none"),
|
|
193
|
+
shownA' = if (holds) shownA else (wantVis("none", pointer, "none") == 1),
|
|
194
|
+
shownB' = if (holds) shownB else (wantVis("none", pointer, "none") == 2),
|
|
195
|
+
pendA' = pendNext("a", "none", pointer, "none"),
|
|
196
|
+
pendB' = pendNext("b", "none", pointer, "none"),
|
|
197
|
+
dismA' = dismNext("a", "none", pointer),
|
|
198
|
+
dismB' = dismNext("b", "none", pointer),
|
|
199
|
+
warm' = warm,
|
|
200
|
+
warmEnabled' = warmEnabled,
|
|
201
|
+
describedA' = if (holds) describedA else (wantVis("none", pointer, "none") == 1),
|
|
202
|
+
describedB' = if (holds) describedB else (wantVis("none", pointer, "none") == 2),
|
|
203
|
+
requests' = requests + moved(visNow, wantVis("none", pointer, "none")),
|
|
204
|
+
controlled' = controlled,
|
|
205
|
+
accepting' = accepting,
|
|
206
|
+
valueProp' = propAfter(follows, valueProp, wantVis("none", pointer, "none")),
|
|
207
|
+
requested' = if (visNow == wantVis("none", pointer, "none")) requested else wantVis("none", pointer, "none"),
|
|
208
|
+
outcome' = if (visNow == wantVis("none", pointer, "none")) outcome else verdict,
|
|
209
|
+
base' = if (visNow == wantVis("none", pointer, "none")) base else visNow
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
action pointerEnter = {
|
|
213
|
+
nondet t = Set("a", "b").oneOf()
|
|
214
|
+
all {
|
|
215
|
+
focus' = focus,
|
|
216
|
+
pointer' = ptrNext(t, focus, entryOf(pointer, t)),
|
|
217
|
+
shownA' = if (holds) shownA else (wantVis(focus, t, entryOf(pointer, t)) == 1),
|
|
218
|
+
shownB' = if (holds) shownB else (wantVis(focus, t, entryOf(pointer, t)) == 2),
|
|
219
|
+
pendA' = pendNext("a", focus, t, entryOf(pointer, t)),
|
|
220
|
+
pendB' = pendNext("b", focus, t, entryOf(pointer, t)),
|
|
221
|
+
dismA' = dismNext("a", focus, t),
|
|
222
|
+
dismB' = dismNext("b", focus, t),
|
|
223
|
+
warm' = warm,
|
|
224
|
+
warmEnabled' = warmEnabled,
|
|
225
|
+
describedA' = if (holds) describedA else (wantVis(focus, t, entryOf(pointer, t)) == 1),
|
|
226
|
+
describedB' = if (holds) describedB else (wantVis(focus, t, entryOf(pointer, t)) == 2),
|
|
227
|
+
requests' = requests + moved(visNow, wantVis(focus, t, entryOf(pointer, t))),
|
|
228
|
+
controlled' = controlled,
|
|
229
|
+
accepting' = accepting,
|
|
230
|
+
valueProp' = propAfter(follows, valueProp, wantVis(focus, t, entryOf(pointer, t))),
|
|
231
|
+
requested' = if (visNow == wantVis(focus, t, entryOf(pointer, t))) requested else wantVis(focus, t, entryOf(pointer, t)),
|
|
232
|
+
outcome' = if (visNow == wantVis(focus, t, entryOf(pointer, t))) outcome else verdict,
|
|
233
|
+
base' = if (visNow == wantVis(focus, t, entryOf(pointer, t))) base else visNow
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
action pointerLeave = all {
|
|
238
|
+
focus' = focus,
|
|
239
|
+
pointer' = ptrNext("none", focus, "none"),
|
|
240
|
+
shownA' = if (holds) shownA else (wantVis(focus, "none", "none") == 1),
|
|
241
|
+
shownB' = if (holds) shownB else (wantVis(focus, "none", "none") == 2),
|
|
242
|
+
pendA' = pendNext("a", focus, "none", "none"),
|
|
243
|
+
pendB' = pendNext("b", focus, "none", "none"),
|
|
244
|
+
dismA' = dismNext("a", focus, "none"),
|
|
245
|
+
dismB' = dismNext("b", focus, "none"),
|
|
246
|
+
warm' = warm,
|
|
247
|
+
warmEnabled' = warmEnabled,
|
|
248
|
+
describedA' = if (holds) describedA else (wantVis(focus, "none", "none") == 1),
|
|
249
|
+
describedB' = if (holds) describedB else (wantVis(focus, "none", "none") == 2),
|
|
250
|
+
requests' = requests + moved(visNow, wantVis(focus, "none", "none")),
|
|
251
|
+
controlled' = controlled,
|
|
252
|
+
accepting' = accepting,
|
|
253
|
+
valueProp' = propAfter(follows, valueProp, wantVis(focus, "none", "none")),
|
|
254
|
+
requested' = if (visNow == wantVis(focus, "none", "none")) requested else wantVis(focus, "none", "none"),
|
|
255
|
+
outcome' = if (visNow == wantVis(focus, "none", "none")) outcome else verdict,
|
|
256
|
+
base' = if (visNow == wantVis(focus, "none", "none")) base else visNow
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// the pointer moves onto the visible tooltip: hoverable content (no commit)
|
|
260
|
+
action pointerEnterTip = all {
|
|
261
|
+
focus' = focus,
|
|
262
|
+
pointer' = if (visibleT != "none") "tip" else pointer,
|
|
263
|
+
shownA' = shownA,
|
|
264
|
+
shownB' = shownB,
|
|
265
|
+
pendA' = pendNext("a", focus, if (visibleT != "none") "tip" else pointer, "none"),
|
|
266
|
+
pendB' = pendNext("b", focus, if (visibleT != "none") "tip" else pointer, "none"),
|
|
267
|
+
dismA' = dismNext("a", focus, if (visibleT != "none") "tip" else pointer),
|
|
268
|
+
dismB' = dismNext("b", focus, if (visibleT != "none") "tip" else pointer),
|
|
269
|
+
warm' = warm,
|
|
270
|
+
warmEnabled' = warmEnabled,
|
|
271
|
+
describedA' = describedA,
|
|
272
|
+
describedB' = describedB,
|
|
273
|
+
requests' = requests,
|
|
274
|
+
controlled' = controlled,
|
|
275
|
+
accepting' = accepting,
|
|
276
|
+
valueProp' = valueProp,
|
|
277
|
+
requested' = requested,
|
|
278
|
+
outcome' = outcome,
|
|
279
|
+
base' = base
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// request: the show delay for t elapsed; it replaces any other visible tooltip
|
|
283
|
+
action showTimer = {
|
|
284
|
+
nondet t = Set("a", "b").oneOf()
|
|
285
|
+
all {
|
|
286
|
+
focus' = focus,
|
|
287
|
+
pointer' = if (and(isPend(t), pointer == "tip")) "none" else pointer,
|
|
288
|
+
shownA' = if (holds) shownA
|
|
289
|
+
else if (isPend(t)) t == "a"
|
|
290
|
+
else shownA,
|
|
291
|
+
shownB' = if (holds) shownB
|
|
292
|
+
else if (isPend(t)) t == "b"
|
|
293
|
+
else shownB,
|
|
294
|
+
pendA' = if (and(isPend(t), t == "a")) false else pendA,
|
|
295
|
+
pendB' = if (and(isPend(t), t == "b")) false else pendB,
|
|
296
|
+
dismA' = dismA,
|
|
297
|
+
dismB' = dismB,
|
|
298
|
+
warm' = warm,
|
|
299
|
+
warmEnabled' = warmEnabled,
|
|
300
|
+
describedA' = if (holds) describedA
|
|
301
|
+
else if (isPend(t)) t == "a"
|
|
302
|
+
else describedA,
|
|
303
|
+
describedB' = if (holds) describedB
|
|
304
|
+
else if (isPend(t)) t == "b"
|
|
305
|
+
else describedB,
|
|
306
|
+
requests' = requests + moved(visNow, if (isPend(t)) (if (t == "a") 1 else 2) else visNow),
|
|
307
|
+
controlled' = controlled,
|
|
308
|
+
accepting' = accepting,
|
|
309
|
+
valueProp' = propAfter(follows, valueProp, if (isPend(t)) (if (t == "a") 1 else 2) else visNow),
|
|
310
|
+
requested' = if (visNow == (if (isPend(t)) (if (t == "a") 1 else 2) else visNow)) requested
|
|
311
|
+
else if (isPend(t)) (if (t == "a") 1 else 2) else visNow,
|
|
312
|
+
outcome' = if (visNow == (if (isPend(t)) (if (t == "a") 1 else 2) else visNow)) outcome else verdict,
|
|
313
|
+
base' = if (visNow == (if (isPend(t)) (if (t == "a") 1 else 2) else visNow)) base else visNow
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// request: the grace period elapsed with nothing engaged
|
|
318
|
+
action hideTimer = all {
|
|
319
|
+
focus' = focus,
|
|
320
|
+
pointer' = pointer,
|
|
321
|
+
shownA' = if (holds) shownA else if (hidable(focus, pointer)) false else shownA,
|
|
322
|
+
shownB' = if (holds) shownB else if (hidable(focus, pointer)) false else shownB,
|
|
323
|
+
pendA' = pendA,
|
|
324
|
+
pendB' = pendB,
|
|
325
|
+
dismA' = dismA,
|
|
326
|
+
dismB' = dismB,
|
|
327
|
+
warm' = if (and(holds, hidable(focus, pointer))) warm
|
|
328
|
+
else if (hidable(focus, pointer)) true else warm,
|
|
329
|
+
warmEnabled' = warmEnabled,
|
|
330
|
+
describedA' = if (holds) describedA
|
|
331
|
+
else if (hidable(focus, pointer)) false else describedA,
|
|
332
|
+
describedB' = if (holds) describedB
|
|
333
|
+
else if (hidable(focus, pointer)) false else describedB,
|
|
334
|
+
requests' = requests + moved(visNow, if (hidable(focus, pointer)) 0 else visNow),
|
|
335
|
+
controlled' = controlled,
|
|
336
|
+
accepting' = accepting,
|
|
337
|
+
valueProp' = propAfter(follows, valueProp, if (hidable(focus, pointer)) 0 else visNow),
|
|
338
|
+
requested' = if (visNow == (if (hidable(focus, pointer)) 0 else visNow)) requested
|
|
339
|
+
else if (hidable(focus, pointer)) 0 else visNow,
|
|
340
|
+
outcome' = if (visNow == (if (hidable(focus, pointer)) 0 else visNow)) outcome else verdict,
|
|
341
|
+
base' = if (visNow == (if (hidable(focus, pointer)) 0 else visNow)) base else visNow
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// request: WCAG dismissable — hides the tooltip (and cancels armed show
|
|
345
|
+
// timers) without moving focus or pointer; the engaged trigger stays dismissed
|
|
346
|
+
action escape = all {
|
|
347
|
+
focus' = focus,
|
|
348
|
+
pointer' = if (pointer == "tip") "none" else pointer,
|
|
349
|
+
shownA' = if (holds) shownA else false,
|
|
350
|
+
shownB' = if (holds) shownB else false,
|
|
351
|
+
pendA' = false,
|
|
352
|
+
pendB' = false,
|
|
353
|
+
dismA' = if (rejectHide) dismA
|
|
354
|
+
else or(dismA, and(trigEng("a", focus, pointer), or(shownA, pendA))),
|
|
355
|
+
dismB' = if (rejectHide) dismB
|
|
356
|
+
else or(dismB, and(trigEng("b", focus, pointer), or(shownB, pendB))),
|
|
357
|
+
warm' = if (rejectHide) warm else or(warm, or(shownA, shownB)),
|
|
358
|
+
warmEnabled' = warmEnabled,
|
|
359
|
+
describedA' = if (holds) describedA else false,
|
|
360
|
+
describedB' = if (holds) describedB else false,
|
|
361
|
+
requests' = requests + moved(visNow, 0),
|
|
362
|
+
controlled' = controlled,
|
|
363
|
+
accepting' = accepting,
|
|
364
|
+
valueProp' = propAfter(follows, valueProp, 0),
|
|
365
|
+
requested' = if (visNow == 0) requested else 0,
|
|
366
|
+
outcome' = if (visNow == 0) outcome else verdict,
|
|
367
|
+
base' = if (visNow == 0) base else visNow
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// commit: the host writes the controlled visible tooltip (0 none, 1 a, 2 b)
|
|
371
|
+
action setValue = {
|
|
372
|
+
nondet v = Set(0, 1, 2).oneOf()
|
|
373
|
+
all {
|
|
374
|
+
focus' = focus,
|
|
375
|
+
pointer' = if (and(controlled, and(v == 0, pointer == "tip"))) "none" else pointer,
|
|
376
|
+
shownA' = if (controlled) v == 1 else shownA,
|
|
377
|
+
shownB' = if (controlled) v == 2 else shownB,
|
|
378
|
+
pendA' = if (and(controlled, v == 1)) false else pendA,
|
|
379
|
+
pendB' = if (and(controlled, v == 2)) false else pendB,
|
|
380
|
+
dismA' = if (and(controlled, v == 1)) false else dismA,
|
|
381
|
+
dismB' = if (and(controlled, v == 2)) false else dismB,
|
|
382
|
+
warm' = warm,
|
|
383
|
+
warmEnabled' = warmEnabled,
|
|
384
|
+
describedA' = if (controlled) v == 1 else describedA,
|
|
385
|
+
describedB' = if (controlled) v == 2 else describedB,
|
|
386
|
+
requests' = requests,
|
|
387
|
+
controlled' = controlled,
|
|
388
|
+
accepting' = accepting,
|
|
389
|
+
valueProp' = if (controlled) v else valueProp,
|
|
390
|
+
requested' = requested,
|
|
391
|
+
outcome' = if (controlled) "none" else outcome,
|
|
392
|
+
base' = base
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// commit: the host flips its own copy between none and tooltip a
|
|
397
|
+
action hostToggle = all {
|
|
398
|
+
focus' = focus,
|
|
399
|
+
pointer' = if (and(controlled, and(toggleVis == 0, pointer == "tip"))) "none" else pointer,
|
|
400
|
+
shownA' = if (controlled) toggleVis == 1 else shownA,
|
|
401
|
+
shownB' = if (controlled) false else shownB,
|
|
402
|
+
pendA' = if (and(controlled, toggleVis == 1)) false else pendA,
|
|
403
|
+
pendB' = if (controlled) false else pendB,
|
|
404
|
+
dismA' = if (and(controlled, toggleVis == 1)) false else dismA,
|
|
405
|
+
dismB' = if (controlled) false else dismB,
|
|
406
|
+
warm' = warm,
|
|
407
|
+
warmEnabled' = warmEnabled,
|
|
408
|
+
describedA' = if (controlled) toggleVis == 1 else describedA,
|
|
409
|
+
describedB' = if (controlled) false else describedB,
|
|
410
|
+
requests' = requests,
|
|
411
|
+
controlled' = controlled,
|
|
412
|
+
accepting' = accepting,
|
|
413
|
+
valueProp' = if (controlled) toggleVis else valueProp,
|
|
414
|
+
requested' = requested,
|
|
415
|
+
outcome' = if (controlled) "none" else outcome,
|
|
416
|
+
base' = base
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// configuration: the host starts owning the visible tooltip, adopting it
|
|
420
|
+
action control = all {
|
|
421
|
+
focus' = focus,
|
|
422
|
+
pointer' = pointer,
|
|
423
|
+
shownA' = shownA,
|
|
424
|
+
shownB' = shownB,
|
|
425
|
+
pendA' = pendA,
|
|
426
|
+
pendB' = pendB,
|
|
427
|
+
dismA' = dismA,
|
|
428
|
+
dismB' = dismB,
|
|
429
|
+
warm' = warm,
|
|
430
|
+
warmEnabled' = warmEnabled,
|
|
431
|
+
describedA' = describedA,
|
|
432
|
+
describedB' = describedB,
|
|
433
|
+
requests' = requests,
|
|
434
|
+
controlled' = true,
|
|
435
|
+
accepting' = accepting,
|
|
436
|
+
valueProp' = visNow,
|
|
437
|
+
requested' = requested,
|
|
438
|
+
outcome' = outcome,
|
|
439
|
+
base' = base
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// configuration: the controlled host answers every request with its intent
|
|
443
|
+
action acceptRequests = all {
|
|
444
|
+
focus' = focus,
|
|
445
|
+
pointer' = pointer,
|
|
446
|
+
shownA' = shownA,
|
|
447
|
+
shownB' = shownB,
|
|
448
|
+
pendA' = pendA,
|
|
449
|
+
pendB' = pendB,
|
|
450
|
+
dismA' = dismA,
|
|
451
|
+
dismB' = dismB,
|
|
452
|
+
warm' = warm,
|
|
453
|
+
warmEnabled' = warmEnabled,
|
|
454
|
+
describedA' = describedA,
|
|
455
|
+
describedB' = describedB,
|
|
456
|
+
requests' = requests,
|
|
457
|
+
controlled' = controlled,
|
|
458
|
+
accepting' = true,
|
|
459
|
+
valueProp' = valueProp,
|
|
460
|
+
requested' = requested,
|
|
461
|
+
outcome' = outcome,
|
|
462
|
+
base' = base
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
action warmExpire = all {
|
|
466
|
+
focus' = focus,
|
|
467
|
+
pointer' = pointer,
|
|
468
|
+
shownA' = shownA,
|
|
469
|
+
shownB' = shownB,
|
|
470
|
+
pendA' = pendA,
|
|
471
|
+
pendB' = pendB,
|
|
472
|
+
dismA' = dismA,
|
|
473
|
+
dismB' = dismB,
|
|
474
|
+
warm' = false,
|
|
475
|
+
warmEnabled' = warmEnabled,
|
|
476
|
+
describedA' = describedA,
|
|
477
|
+
describedB' = describedB,
|
|
478
|
+
requests' = requests,
|
|
479
|
+
controlled' = controlled,
|
|
480
|
+
accepting' = accepting,
|
|
481
|
+
valueProp' = valueProp,
|
|
482
|
+
requested' = requested,
|
|
483
|
+
outcome' = outcome,
|
|
484
|
+
base' = base
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
action setWarmup = {
|
|
488
|
+
nondet on = Set(true, false).oneOf()
|
|
489
|
+
all {
|
|
490
|
+
focus' = focus,
|
|
491
|
+
pointer' = pointer,
|
|
492
|
+
shownA' = shownA,
|
|
493
|
+
shownB' = shownB,
|
|
494
|
+
pendA' = pendA,
|
|
495
|
+
pendB' = pendB,
|
|
496
|
+
dismA' = dismA,
|
|
497
|
+
dismB' = dismB,
|
|
498
|
+
warm' = warm,
|
|
499
|
+
warmEnabled' = on,
|
|
500
|
+
describedA' = describedA,
|
|
501
|
+
describedB' = describedB,
|
|
502
|
+
requests' = requests,
|
|
503
|
+
controlled' = controlled,
|
|
504
|
+
accepting' = accepting,
|
|
505
|
+
valueProp' = valueProp,
|
|
506
|
+
requested' = requested,
|
|
507
|
+
outcome' = outcome,
|
|
508
|
+
base' = base
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
action step = any {
|
|
513
|
+
init, focusIn, focusOut, pointerEnter, pointerLeave, pointerEnterTip,
|
|
514
|
+
showTimer, hideTimer, escape, warmExpire, setWarmup, setValue, hostToggle,
|
|
515
|
+
control, acceptRequests
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// at most one tooltip is visible at a time
|
|
519
|
+
val AtMostOneVisible: bool = not(and(shownA, shownB))
|
|
520
|
+
// aria-describedby is present on a trigger exactly while its tooltip exists
|
|
521
|
+
val DescribedByIffShown: bool = and(describedA == shownA, describedB == shownB)
|
|
522
|
+
// an Escape-dismissed tooltip stays hidden (and is never queued to show)
|
|
523
|
+
val DismissedStaysHidden: bool =
|
|
524
|
+
and(
|
|
525
|
+
and(not(and(dismA, shownA)), not(and(dismB, shownB))),
|
|
526
|
+
and(not(and(dismA, pendA)), not(and(dismB, pendB)))
|
|
527
|
+
)
|
|
528
|
+
// the dismissal memory is dropped once the trigger is left
|
|
529
|
+
val DismissedImpliesEngaged: bool =
|
|
530
|
+
and(or(not(dismA), trigEng("a", focus, pointer)), or(not(dismB), trigEng("b", focus, pointer)))
|
|
531
|
+
// a show timer only runs for an engaged trigger whose tooltip is hidden
|
|
532
|
+
val PendingImpliesEngagedHidden: bool =
|
|
533
|
+
and(
|
|
534
|
+
or(not(pendA), and(trigEng("a", focus, pointer), not(shownA))),
|
|
535
|
+
or(not(pendB), and(trigEng("b", focus, pointer), not(shownB)))
|
|
536
|
+
)
|
|
537
|
+
// hoverable: the pointer only rests on a tooltip that is visible
|
|
538
|
+
val TipHoverNeedsTooltip: bool = or(pointer != "tip", visibleT != "none")
|
|
539
|
+
// positions are one of the modelled values
|
|
540
|
+
val PositionsKnown: bool =
|
|
541
|
+
and(
|
|
542
|
+
or(focus == "none", or(focus == "a", focus == "b")),
|
|
543
|
+
or(pointer == "none", or(pointer == "a", or(pointer == "b", pointer == "tip")))
|
|
544
|
+
)
|
|
545
|
+
// callbacks only ever count up from zero
|
|
546
|
+
val RequestsNonNegative: bool = requests >= 0
|
|
547
|
+
// controlled: only the value prop sets the visible tooltip, so no event commits
|
|
548
|
+
val ControlledFollowsProp: bool = or(not(controlled), visNow == valueProp)
|
|
549
|
+
// a rejected request leaves the visible tooltip as it found it
|
|
550
|
+
val RejectedRequestUntouched: bool = or(not(outcome == "rejected"), visNow == base)
|
|
551
|
+
// an accepted request committed exactly its intent
|
|
552
|
+
val AcceptedRequestCommits: bool = or(not(outcome == "accepted"), visNow == requested)
|
|
553
|
+
}
|