@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.
Files changed (260) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -2
  3. package/dist/apg/accordion.bridge.d.mts +35 -0
  4. package/dist/apg/accordion.bridge.mjs +326 -0
  5. package/dist/apg/accordion.model.json +8397 -0
  6. package/dist/apg/alert.bridge.d.mts +22 -0
  7. package/dist/apg/alert.bridge.mjs +86 -0
  8. package/dist/apg/alert.model.json +871 -0
  9. package/dist/apg/alertdialog.bridge.d.mts +21 -0
  10. package/dist/apg/alertdialog.bridge.mjs +221 -0
  11. package/dist/apg/alertdialog.model.json +5232 -0
  12. package/dist/apg/breadcrumb.bridge.d.mts +26 -0
  13. package/dist/apg/breadcrumb.bridge.mjs +120 -0
  14. package/dist/apg/breadcrumb.model.json +574 -0
  15. package/dist/apg/button.bridge.d.mts +35 -0
  16. package/dist/apg/button.bridge.mjs +208 -0
  17. package/dist/apg/button.model.json +5814 -0
  18. package/dist/apg/carousel.bridge.d.mts +44 -0
  19. package/dist/apg/carousel.bridge.mjs +262 -0
  20. package/dist/apg/carousel.model.json +8963 -0
  21. package/dist/apg/checkbox-group.bridge.d.mts +25 -0
  22. package/dist/apg/checkbox-group.bridge.mjs +276 -0
  23. package/dist/apg/checkbox-group.model.json +3863 -0
  24. package/dist/apg/checkbox.bridge.d.mts +34 -0
  25. package/dist/apg/checkbox.bridge.mjs +253 -0
  26. package/dist/apg/checkbox.model.json +3158 -0
  27. package/dist/apg/combobox-autocomplete.bridge.d.mts +14 -0
  28. package/dist/apg/combobox-autocomplete.bridge.mjs +326 -0
  29. package/dist/apg/combobox-autocomplete.model.json +11586 -0
  30. package/dist/apg/combobox-dialog.bridge.d.mts +17 -0
  31. package/dist/apg/combobox-dialog.bridge.mjs +250 -0
  32. package/dist/apg/combobox-dialog.model.json +8981 -0
  33. package/dist/apg/combobox-grid.bridge.d.mts +17 -0
  34. package/dist/apg/combobox-grid.bridge.mjs +227 -0
  35. package/dist/apg/combobox-grid.model.json +8554 -0
  36. package/dist/apg/combobox-select-only.bridge.d.mts +24 -0
  37. package/dist/apg/combobox-select-only.bridge.mjs +304 -0
  38. package/dist/apg/combobox-select-only.model.json +7846 -0
  39. package/dist/apg/combobox-tree.bridge.d.mts +17 -0
  40. package/dist/apg/combobox-tree.bridge.mjs +211 -0
  41. package/dist/apg/combobox-tree.model.json +9345 -0
  42. package/dist/apg/dialog-modal.bridge.d.mts +35 -0
  43. package/dist/apg/dialog-modal.bridge.mjs +186 -0
  44. package/dist/apg/dialog-modal.model.json +7580 -0
  45. package/dist/apg/disclosure.bridge.d.mts +25 -0
  46. package/dist/apg/disclosure.bridge.mjs +202 -0
  47. package/dist/apg/disclosure.model.json +4703 -0
  48. package/dist/apg/feed.bridge.d.mts +31 -0
  49. package/dist/apg/feed.bridge.mjs +246 -0
  50. package/dist/apg/feed.model.json +4523 -0
  51. package/dist/apg/grid-selection.bridge.d.mts +13 -0
  52. package/dist/apg/grid-selection.bridge.mjs +160 -0
  53. package/dist/apg/grid-selection.model.json +6974 -0
  54. package/dist/apg/grid-sort.bridge.d.mts +12 -0
  55. package/dist/apg/grid-sort.bridge.mjs +138 -0
  56. package/dist/apg/grid-sort.model.json +3799 -0
  57. package/dist/apg/grid.bridge.d.mts +54 -0
  58. package/dist/apg/grid.bridge.mjs +441 -0
  59. package/dist/apg/grid.model.json +3802 -0
  60. package/dist/apg/link.bridge.d.mts +24 -0
  61. package/dist/apg/link.bridge.mjs +126 -0
  62. package/dist/apg/link.model.json +694 -0
  63. package/dist/apg/listbox-multi.bridge.d.mts +10 -0
  64. package/dist/apg/listbox-multi.bridge.mjs +261 -0
  65. package/dist/apg/listbox-multi.model.json +10893 -0
  66. package/dist/apg/listbox-rearrangeable.bridge.d.mts +20 -0
  67. package/dist/apg/listbox-rearrangeable.bridge.mjs +237 -0
  68. package/dist/apg/listbox-rearrangeable.model.json +17592 -0
  69. package/dist/apg/listbox-shared.d.mts +58 -0
  70. package/dist/apg/listbox-shared.mjs +171 -0
  71. package/dist/apg/listbox-single.bridge.d.mts +10 -0
  72. package/dist/apg/listbox-single.bridge.mjs +196 -0
  73. package/dist/apg/listbox-single.model.json +8024 -0
  74. package/dist/apg/listbox-typeahead.bridge.d.mts +10 -0
  75. package/dist/apg/listbox-typeahead.bridge.mjs +153 -0
  76. package/dist/apg/listbox-typeahead.model.json +5615 -0
  77. package/dist/apg/menu-button.bridge.d.mts +29 -0
  78. package/dist/apg/menu-button.bridge.mjs +259 -0
  79. package/dist/apg/menu-button.model.json +5706 -0
  80. package/dist/apg/menu-checkable.bridge.d.mts +25 -0
  81. package/dist/apg/menu-checkable.bridge.mjs +226 -0
  82. package/dist/apg/menu-checkable.model.json +4277 -0
  83. package/dist/apg/menu-menubar.bridge.d.mts +27 -0
  84. package/dist/apg/menu-menubar.bridge.mjs +273 -0
  85. package/dist/apg/menu-menubar.model.json +9540 -0
  86. package/dist/apg/meter.bridge.d.mts +28 -0
  87. package/dist/apg/meter.bridge.mjs +159 -0
  88. package/dist/apg/meter.model.json +1185 -0
  89. package/dist/apg/player.bridge.d.mts +24 -0
  90. package/dist/apg/player.bridge.mjs +342 -0
  91. package/dist/apg/player.model.json +11013 -0
  92. package/dist/apg/radio.bridge.d.mts +35 -0
  93. package/dist/apg/radio.bridge.mjs +371 -0
  94. package/dist/apg/radio.model.json +5011 -0
  95. package/dist/apg/slider-multithumb.bridge.d.mts +34 -0
  96. package/dist/apg/slider-multithumb.bridge.mjs +365 -0
  97. package/dist/apg/slider-multithumb.model.json +5774 -0
  98. package/dist/apg/slider.bridge.d.mts +54 -0
  99. package/dist/apg/slider.bridge.mjs +359 -0
  100. package/dist/apg/slider.model.json +4881 -0
  101. package/dist/apg/spinbutton.bridge.d.mts +41 -0
  102. package/dist/apg/spinbutton.bridge.mjs +327 -0
  103. package/dist/apg/spinbutton.model.json +7571 -0
  104. package/dist/apg/switch.bridge.d.mts +30 -0
  105. package/dist/apg/switch.bridge.mjs +243 -0
  106. package/dist/apg/switch.model.json +3159 -0
  107. package/dist/apg/tabs.bridge.d.mts +41 -0
  108. package/dist/apg/tabs.bridge.mjs +294 -0
  109. package/dist/apg/tabs.model.json +7659 -0
  110. package/dist/apg/timeline.bridge.d.mts +36 -0
  111. package/dist/apg/timeline.bridge.mjs +265 -0
  112. package/dist/apg/timeline.model.json +6189 -0
  113. package/dist/apg/toolbar.bridge.d.mts +25 -0
  114. package/dist/apg/toolbar.bridge.mjs +307 -0
  115. package/dist/apg/toolbar.model.json +6616 -0
  116. package/dist/apg/tooltip.bridge.d.mts +36 -0
  117. package/dist/apg/tooltip.bridge.mjs +208 -0
  118. package/dist/apg/tooltip.model.json +8295 -0
  119. package/dist/apg/treegrid.bridge.d.mts +28 -0
  120. package/dist/apg/treegrid.bridge.mjs +311 -0
  121. package/dist/apg/treegrid.model.json +17358 -0
  122. package/dist/apg/treeview.bridge.d.mts +26 -0
  123. package/dist/apg/treeview.bridge.mjs +396 -0
  124. package/dist/apg/treeview.model.json +13745 -0
  125. package/dist/apg/windowsplitter.bridge.d.mts +36 -0
  126. package/dist/apg/windowsplitter.bridge.mjs +242 -0
  127. package/dist/apg/windowsplitter.model.json +6253 -0
  128. package/dist/canvas/camera.model.json +6568 -0
  129. package/dist/canvas/canvas.bridge.d.mts +190 -0
  130. package/dist/canvas/canvas.bridge.mjs +729 -0
  131. package/dist/canvas/pointer.model.json +17095 -0
  132. package/dist/scenario.d.mts +233 -0
  133. package/dist/scenario.mjs +313 -0
  134. package/package.json +57 -4
  135. package/scenarios/apg/README.md +221 -0
  136. package/scenarios/apg/accordion.base-ui.json +1005 -0
  137. package/scenarios/apg/accordion.react-aria.json +929 -0
  138. package/scenarios/apg/alert.base-ui.json +81 -0
  139. package/scenarios/apg/alert.react-aria.json +98 -0
  140. package/scenarios/apg/alertdialog.base-ui.json +331 -0
  141. package/scenarios/apg/alertdialog.react-aria.json +225 -0
  142. package/scenarios/apg/breadcrumb.react-aria.json +312 -0
  143. package/scenarios/apg/button.ariakit.json +136 -0
  144. package/scenarios/apg/button.base-ui.json +747 -0
  145. package/scenarios/apg/button.react-aria.json +443 -0
  146. package/scenarios/apg/carousel.apg.json +909 -0
  147. package/scenarios/apg/checkbox.ariakit.json +299 -0
  148. package/scenarios/apg/checkbox.base-ui.json +1126 -0
  149. package/scenarios/apg/checkbox.react-aria.json +518 -0
  150. package/scenarios/apg/combobox.ariakit.json +945 -0
  151. package/scenarios/apg/combobox.base-ui.json +2444 -0
  152. package/scenarios/apg/combobox.react-aria.json +845 -0
  153. package/scenarios/apg/dialog-modal.ariakit.json +1102 -0
  154. package/scenarios/apg/dialog-modal.base-ui.json +775 -0
  155. package/scenarios/apg/dialog-modal.react-aria.json +638 -0
  156. package/scenarios/apg/disclosure.apg.json +349 -0
  157. package/scenarios/apg/disclosure.ariakit.json +163 -0
  158. package/scenarios/apg/disclosure.base-ui.json +451 -0
  159. package/scenarios/apg/disclosure.react-aria.json +643 -0
  160. package/scenarios/apg/feed.apg.json +442 -0
  161. package/scenarios/apg/grid.ariakit.json +197 -0
  162. package/scenarios/apg/grid.base-ui.json +720 -0
  163. package/scenarios/apg/grid.react-aria.json +969 -0
  164. package/scenarios/apg/landmarks.ariakit.json +93 -0
  165. package/scenarios/apg/landmarks.react-aria.json +330 -0
  166. package/scenarios/apg/link.ariakit.json +83 -0
  167. package/scenarios/apg/link.base-ui.json +88 -0
  168. package/scenarios/apg/link.react-aria.json +186 -0
  169. package/scenarios/apg/listbox.ariakit.json +78 -0
  170. package/scenarios/apg/listbox.base-ui.json +163 -0
  171. package/scenarios/apg/listbox.react-aria.json +1118 -0
  172. package/scenarios/apg/menu-button.ariakit.json +924 -0
  173. package/scenarios/apg/menu-button.base-ui.json +1155 -0
  174. package/scenarios/apg/menu-button.react-aria.json +1080 -0
  175. package/scenarios/apg/menubar.ariakit.json +758 -0
  176. package/scenarios/apg/menubar.base-ui.json +673 -0
  177. package/scenarios/apg/meter.base-ui.json +243 -0
  178. package/scenarios/apg/meter.react-aria.json +201 -0
  179. package/scenarios/apg/radio.ariakit.json +459 -0
  180. package/scenarios/apg/radio.base-ui.json +581 -0
  181. package/scenarios/apg/radio.react-aria.json +576 -0
  182. package/scenarios/apg/slider-multithumb.base-ui.json +316 -0
  183. package/scenarios/apg/slider-multithumb.react-aria.json +363 -0
  184. package/scenarios/apg/slider.base-ui.json +544 -0
  185. package/scenarios/apg/slider.react-aria.json +456 -0
  186. package/scenarios/apg/spinbutton.base-ui.json +1688 -0
  187. package/scenarios/apg/spinbutton.react-aria.json +426 -0
  188. package/scenarios/apg/switch.base-ui.json +456 -0
  189. package/scenarios/apg/switch.react-aria.json +303 -0
  190. package/scenarios/apg/table.ariakit.json +65 -0
  191. package/scenarios/apg/table.react-aria.json +221 -0
  192. package/scenarios/apg/tabs.ariakit.json +329 -0
  193. package/scenarios/apg/tabs.base-ui.json +875 -0
  194. package/scenarios/apg/tabs.react-aria.json +644 -0
  195. package/scenarios/apg/toolbar.ariakit.json +595 -0
  196. package/scenarios/apg/toolbar.base-ui.json +1189 -0
  197. package/scenarios/apg/toolbar.react-aria.json +383 -0
  198. package/scenarios/apg/tooltip.ariakit.json +441 -0
  199. package/scenarios/apg/tooltip.base-ui.json +411 -0
  200. package/scenarios/apg/tooltip.react-aria.json +454 -0
  201. package/scenarios/apg/treegrid.apg.json +2558 -0
  202. package/scenarios/apg/treegrid.react-aria.json +5094 -0
  203. package/scenarios/apg/treeview.apg.json +928 -0
  204. package/scenarios/apg/windowsplitter.apg.json +557 -0
  205. package/scenarios/canvas/README.md +69 -0
  206. package/scenarios/canvas/camera.contract.json +237 -0
  207. package/scenarios/canvas/camera.excalidraw.json +57 -0
  208. package/scenarios/canvas/marquee.excalidraw.json +122 -0
  209. package/scenarios/canvas/pan.excalidraw.json +46 -0
  210. package/scenarios/canvas/pointer.contract.json +528 -0
  211. package/scenarios/parts.json +291 -0
  212. package/shared/aria.qnt +186 -0
  213. package/shared/details-group.qnt +50 -0
  214. package/shared/focusgroup.qnt +152 -0
  215. package/shared/invokers.qnt +81 -0
  216. package/shared/popover.qnt +71 -0
  217. package/specs/apg/STATIC.md +103 -0
  218. package/specs/apg/accordion.qnt +578 -0
  219. package/specs/apg/alert.qnt +131 -0
  220. package/specs/apg/alertdialog.qnt +455 -0
  221. package/specs/apg/breadcrumb.qnt +88 -0
  222. package/specs/apg/button.qnt +445 -0
  223. package/specs/apg/carousel.qnt +732 -0
  224. package/specs/apg/checkbox-group.qnt +388 -0
  225. package/specs/apg/checkbox.qnt +394 -0
  226. package/specs/apg/combobox-autocomplete.qnt +764 -0
  227. package/specs/apg/combobox-dialog.qnt +627 -0
  228. package/specs/apg/combobox-grid.qnt +632 -0
  229. package/specs/apg/combobox-select-only.qnt +626 -0
  230. package/specs/apg/combobox-tree.qnt +633 -0
  231. package/specs/apg/dialog-modal.qnt +499 -0
  232. package/specs/apg/disclosure.qnt +309 -0
  233. package/specs/apg/feed.qnt +485 -0
  234. package/specs/apg/grid-selection.qnt +512 -0
  235. package/specs/apg/grid-sort.qnt +330 -0
  236. package/specs/apg/grid.qnt +360 -0
  237. package/specs/apg/link.qnt +119 -0
  238. package/specs/apg/listbox-multi.qnt +799 -0
  239. package/specs/apg/listbox-rearrangeable.qnt +801 -0
  240. package/specs/apg/listbox-single.qnt +511 -0
  241. package/specs/apg/listbox-typeahead.qnt +396 -0
  242. package/specs/apg/menu-button.qnt +363 -0
  243. package/specs/apg/menu-checkable.qnt +304 -0
  244. package/specs/apg/menu-menubar.qnt +531 -0
  245. package/specs/apg/meter.qnt +129 -0
  246. package/specs/apg/player.qnt +1081 -0
  247. package/specs/apg/radio.qnt +394 -0
  248. package/specs/apg/slider-multithumb.qnt +441 -0
  249. package/specs/apg/slider.qnt +396 -0
  250. package/specs/apg/spinbutton.qnt +494 -0
  251. package/specs/apg/switch.qnt +389 -0
  252. package/specs/apg/tabs.qnt +412 -0
  253. package/specs/apg/timeline.qnt +612 -0
  254. package/specs/apg/toolbar.qnt +364 -0
  255. package/specs/apg/tooltip.qnt +553 -0
  256. package/specs/apg/treegrid.qnt +983 -0
  257. package/specs/apg/treeview.qnt +1004 -0
  258. package/specs/apg/windowsplitter.qnt +487 -0
  259. package/specs/canvas/camera.qnt +407 -0
  260. package/specs/canvas/pointer.qnt +841 -0
@@ -0,0 +1,237 @@
1
+ {
2
+ "pattern": "camera",
3
+ "source": "contract",
4
+ "license": "authored in this repository (no upstream); numbers derived from specs/canvas/camera.qnt",
5
+ "scenarios": [
6
+ {
7
+ "id": "camera.contract.zoom-about-the-centre-keeps-the-anchor",
8
+ "area": "zoom",
9
+ "config": {},
10
+ "steps": [
11
+ {
12
+ "event": "prop",
13
+ "prop": "zoom",
14
+ "value": { "factor": 2, "anchor": "centre" }
15
+ },
16
+ {
17
+ "expect": {
18
+ "attrs": {
19
+ "layer": {
20
+ "--canvas-x": "-200",
21
+ "--canvas-y": "-150",
22
+ "--canvas-zoom": "2"
23
+ }
24
+ }
25
+ }
26
+ }
27
+ ],
28
+ "notes": "800 x 600 viewport, anchor (400, 300): screen = (world + t) * 2 maps world (400, 300) to the same pixel only with t = (-200, -150)."
29
+ },
30
+ {
31
+ "id": "camera.contract.a-limit-clamp-re-anchors",
32
+ "area": "zoom",
33
+ "config": {},
34
+ "steps": [
35
+ {
36
+ "event": "prop",
37
+ "prop": "zoom",
38
+ "value": { "factor": 16, "anchor": "centre" }
39
+ },
40
+ {
41
+ "expect": {
42
+ "attrs": {
43
+ "layer": {
44
+ "--canvas-x": "-350",
45
+ "--canvas-y": "-262.5",
46
+ "--canvas-zoom": "8"
47
+ }
48
+ }
49
+ }
50
+ },
51
+ {
52
+ "event": "prop",
53
+ "prop": "zoom",
54
+ "value": { "factor": 2, "anchor": "centre" }
55
+ },
56
+ {
57
+ "expect": {
58
+ "attrs": {
59
+ "layer": {
60
+ "--canvas-x": "-350",
61
+ "--canvas-y": "-262.5",
62
+ "--canvas-zoom": "8"
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ],
68
+ "notes": "x16 is clamped to ZMAX = 8, and the translation is solved for the clamped zoom (not the requested one), so the anchor stays put; a further zoom-in at the limit changes nothing. The y offset is a whole 1/256 fraction: -262.5 units."
69
+ },
70
+ {
71
+ "id": "camera.contract.pan-clamps-the-viewport-origin-at-the-world-bound",
72
+ "area": "pan",
73
+ "config": {},
74
+ "steps": [
75
+ { "event": "prop", "prop": "pan", "value": [9000000, 0] },
76
+ {
77
+ "expect": {
78
+ "attrs": { "layer": { "--canvas-x": "1000000", "--canvas-y": "0" } }
79
+ }
80
+ }
81
+ ],
82
+ "notes": "A 9e6 px pan is far beyond the +-1e6 unit bound: the move is clamped, not refused."
83
+ },
84
+ {
85
+ "id": "camera.contract.zoom-limit-min-about-the-centre",
86
+ "area": "zoom",
87
+ "config": {},
88
+ "steps": [
89
+ {
90
+ "event": "prop",
91
+ "prop": "zoomLimit",
92
+ "value": { "limit": "min", "anchor": "centre" }
93
+ },
94
+ {
95
+ "expect": {
96
+ "attrs": {
97
+ "layer": {
98
+ "--canvas-x": "6000",
99
+ "--canvas-y": "4500",
100
+ "--canvas-zoom": "0.0625"
101
+ }
102
+ }
103
+ }
104
+ }
105
+ ],
106
+ "notes": "ZMIN = 1/16 about (400, 300): the world point under the centre stays there, so the camera offset grows to (6000, 4500)."
107
+ },
108
+ {
109
+ "id": "camera.contract.reduced-motion-jumps-to-the-tween-target",
110
+ "area": "tween",
111
+ "config": { "reducedMotion": true },
112
+ "steps": [
113
+ {
114
+ "event": "prop",
115
+ "prop": "tween",
116
+ "value": { "centre": [1000, 500] }
117
+ },
118
+ {
119
+ "expect": {
120
+ "attrs": {
121
+ "layer": { "--canvas-x": "-600", "--canvas-y": "-200" },
122
+ "state": { "tweening": "false", "reducedMotion": "true" }
123
+ }
124
+ }
125
+ }
126
+ ],
127
+ "notes": "Centring world (1000, 500) in an 800 x 600 viewport at zoom 1 needs t = (-600, -200); with reduced motion the camera is there at once."
128
+ },
129
+ {
130
+ "id": "camera.contract.a-tween-ends-where-the-jump-lands",
131
+ "area": "tween",
132
+ "config": {},
133
+ "steps": [
134
+ {
135
+ "event": "prop",
136
+ "prop": "tween",
137
+ "value": { "centre": [1000, 500] }
138
+ },
139
+ {
140
+ "expect": {
141
+ "attrs": {
142
+ "layer": { "--canvas-x": "0", "--canvas-y": "0" },
143
+ "state": { "tweening": "true" }
144
+ }
145
+ }
146
+ },
147
+ { "event": "prop", "prop": "tick" },
148
+ {
149
+ "expect": {
150
+ "attrs": { "layer": { "--canvas-x": "-150", "--canvas-y": "-50" } }
151
+ }
152
+ },
153
+ { "event": "prop", "prop": "tick" },
154
+ { "event": "prop", "prop": "tick" },
155
+ {
156
+ "expect": {
157
+ "attrs": {
158
+ "layer": { "--canvas-x": "-450", "--canvas-y": "-150" },
159
+ "state": { "tweening": "true" }
160
+ }
161
+ }
162
+ },
163
+ { "event": "prop", "prop": "tick" },
164
+ {
165
+ "expect": {
166
+ "attrs": {
167
+ "layer": { "--canvas-x": "-600", "--canvas-y": "-200" },
168
+ "state": { "tweening": "false" }
169
+ }
170
+ }
171
+ },
172
+ { "event": "prop", "prop": "tick" },
173
+ {
174
+ "expect": {
175
+ "attrs": { "layer": { "--canvas-x": "-600", "--canvas-y": "-200" } }
176
+ }
177
+ }
178
+ ],
179
+ "notes": "Four linear ticks, the last exact; a tick at rest is a no-op. The end state equals the reduced-motion jump of the previous scenario."
180
+ },
181
+ {
182
+ "id": "camera.contract.a-gesture-stops-a-tween-in-place",
183
+ "area": "tween",
184
+ "config": {},
185
+ "steps": [
186
+ {
187
+ "event": "prop",
188
+ "prop": "tween",
189
+ "value": { "centre": [1000, 500] }
190
+ },
191
+ { "event": "prop", "prop": "tick" },
192
+ { "event": "prop", "prop": "pan", "value": [5, 0] },
193
+ {
194
+ "expect": {
195
+ "attrs": {
196
+ "layer": { "--canvas-x": "-145", "--canvas-y": "-50" },
197
+ "state": { "tweening": "false" }
198
+ }
199
+ }
200
+ },
201
+ { "event": "prop", "prop": "tick" },
202
+ {
203
+ "expect": {
204
+ "attrs": { "layer": { "--canvas-x": "-145", "--canvas-y": "-50" } }
205
+ }
206
+ }
207
+ ],
208
+ "notes": "Any pan or zoom cancels a running tween where the camera is; the interrupted tween never resumes."
209
+ },
210
+ {
211
+ "id": "camera.contract.resize-keeps-the-top-left",
212
+ "area": "resize",
213
+ "config": {},
214
+ "steps": [
215
+ {
216
+ "event": "prop",
217
+ "prop": "zoom",
218
+ "value": { "factor": 2, "anchor": "centre" }
219
+ },
220
+ { "event": "prop", "prop": "resize", "value": 3 },
221
+ {
222
+ "expect": {
223
+ "attrs": {
224
+ "layer": {
225
+ "--canvas-x": "-200",
226
+ "--canvas-y": "-150",
227
+ "--canvas-zoom": "2"
228
+ },
229
+ "state": { "viewport": "{\"width\":1920,\"height\":1080}" }
230
+ }
231
+ }
232
+ }
233
+ ],
234
+ "notes": "Resizing changes only the viewport size; the camera (and so the world point at the top-left corner) is untouched."
235
+ }
236
+ ]
237
+ }
@@ -0,0 +1,57 @@
1
+ {
2
+ "pattern": "camera",
3
+ "source": "excalidraw",
4
+ "commit": "ed10ac7dca7e40f3f4a31269b4bfba980d0db41e",
5
+ "license": "MIT",
6
+ "copyright": "Copyright (c) 2020 Excalidraw",
7
+ "scenarios": [
8
+ {
9
+ "id": "camera.excalidraw.centre-target-keeps-zoom",
10
+ "upstream": "https://github.com/excalidraw/excalidraw/blob/ed10ac7dca7e40f3f4a31269b4bfba980d0db41e/packages/excalidraw/tests/fitToContent.test.tsx#L200",
11
+ "upstreamTest": "none > should keep the current zoom and only center the target",
12
+ "area": "extension",
13
+ "config": {
14
+ "viewport": { "w": 100, "h": 100 },
15
+ "camera": { "x": 0, "y": 0, "zoom": 0.5 },
16
+ "nodes": [{ "id": "rect", "x": 1000, "y": 1000, "w": 50, "h": 50 }]
17
+ },
18
+ "steps": [
19
+ {
20
+ "event": "tween",
21
+ "goal": { "centreOn": [1025, 1025] },
22
+ "animation": false
23
+ },
24
+ { "expect": { "camera": { "x": -925, "y": -925, "zoom": 0.5 } } }
25
+ ],
26
+ "notes": "Excalidraw scroll is world-offset, the same convention as camera.qnt (screen = (world + t) * zoom), so scrollX maps to tx. scroll = viewport / 2 / zoom - centre = 100 / 2 / 0.5 - 1025 = -925. Upstream uses toBeCloseTo on doubles; the fixed-point camera (zoom / 4096, t / 256) is exact here. fit 'none' is the centre-on-a-point goal of request_tween."
27
+ },
28
+ {
29
+ "id": "camera.excalidraw.animation-settles-on-target-and-stops",
30
+ "upstream": "https://github.com/excalidraw/excalidraw/blob/ed10ac7dca7e40f3f4a31269b4bfba980d0db41e/packages/excalidraw/tests/fitToContent.test.tsx#L368",
31
+ "upstreamTest": "scale-down animated > should stop ticking and settle on the target once complete",
32
+ "area": "extension",
33
+ "config": {
34
+ "viewport": { "w": 10, "h": 10 },
35
+ "camera": { "x": 0, "y": 0, "zoom": 1 },
36
+ "nodes": [{ "id": "rect", "x": -100, "y": -100, "w": 100, "h": 100 }]
37
+ },
38
+ "steps": [
39
+ {
40
+ "event": "tween",
41
+ "goal": { "fit": "scale-down", "target": "rect" },
42
+ "animation": true
43
+ },
44
+ { "event": "tick", "until": "settled" },
45
+ { "expect": { "tweenActive": false, "cameraMoved": true } },
46
+ { "event": "tick", "count": 3 },
47
+ {
48
+ "expect": {
49
+ "tweenActive": false,
50
+ "cameraUnchangedSinceSettled": true
51
+ }
52
+ }
53
+ ],
54
+ "notes": "Ported by intent: a finished tween removes itself and leaves the camera ON the target; further frames never move it (camera.qnt: RestIsOnTarget, a tick at rest is a no-op). The same goal with reduced motion (animation: false) must end in the identical camera. Fit-to-bounds itself is not modelled in camera.qnt (adapter policy); the target is whatever the host computes."
55
+ }
56
+ ]
57
+ }
@@ -0,0 +1,122 @@
1
+ {
2
+ "pattern": "marquee",
3
+ "source": "excalidraw",
4
+ "commit": "ed10ac7dca7e40f3f4a31269b4bfba980d0db41e",
5
+ "license": "MIT",
6
+ "copyright": "Copyright (c) 2020 Excalidraw",
7
+ "scenarios": [
8
+ {
9
+ "id": "marquee.excalidraw.contain-shift-additive",
10
+ "upstream": "https://github.com/excalidraw/excalidraw/blob/ed10ac7dca7e40f3f4a31269b4bfba980d0db41e/packages/excalidraw/tests/selection.test.tsx#L67",
11
+ "upstreamTest": "box-selection > should allow adding to selection via box-select when holding shift",
12
+ "area": "selection",
13
+ "config": {
14
+ "boxSelection": "contain",
15
+ "camera": { "x": 0, "y": 0, "zoom": 1 },
16
+ "nodes": [
17
+ { "id": "rect1", "x": 0, "y": 0, "w": 50, "h": 50 },
18
+ { "id": "rect2", "x": 100, "y": 0, "w": 50, "h": 50 }
19
+ ]
20
+ },
21
+ "steps": [
22
+ { "event": "pointerdown", "at": [175, -20] },
23
+ { "event": "pointermove", "at": [85, 70] },
24
+ { "event": "pointerup" },
25
+ { "expect": { "selection": ["rect2"] } },
26
+ { "event": "modifier", "shift": true },
27
+ { "event": "pointerdown", "at": [75, -20] },
28
+ { "event": "pointermove", "at": [-15, 70] },
29
+ { "event": "pointerup" },
30
+ {
31
+ "expect": {
32
+ "selection": ["rect2", "rect1"],
33
+ "selectionOrder": "unordered"
34
+ }
35
+ }
36
+ ],
37
+ "notes": "Upstream first throws the pointer to (-1000, -1000) to get past the drag threshold; the port moves straight to the final point. Both boxes sit on empty canvas and fully enclose their node. Shift keeps the pointerdown selection and adds the marquee hits."
38
+ },
39
+ {
40
+ "id": "marquee.excalidraw.live-deselect-when-leaving",
41
+ "upstream": "https://github.com/excalidraw/excalidraw/blob/ed10ac7dca7e40f3f4a31269b4bfba980d0db41e/packages/excalidraw/tests/selection.test.tsx#L104",
42
+ "upstreamTest": "box-selection > should (de)select element when box-selecting over and out while not holding shift",
43
+ "area": "selection",
44
+ "config": {
45
+ "boxSelection": "contain",
46
+ "camera": { "x": 0, "y": 0, "zoom": 1 },
47
+ "nodes": [{ "id": "rect1", "x": 0, "y": 0, "w": 50, "h": 50 }]
48
+ },
49
+ "steps": [
50
+ { "event": "pointerdown", "at": [75, -20] },
51
+ { "event": "pointermove", "at": [-15, 70] },
52
+ { "expect": { "selection": ["rect1"] } },
53
+ { "event": "pointermove", "at": [100, -100] },
54
+ { "expect": { "selection": [] } },
55
+ { "event": "pointerup" },
56
+ { "expect": { "selection": [] } }
57
+ ],
58
+ "notes": "The marquee selection is live: it is recomputed from the rectangle on every move, so shrinking the rectangle away from a node deselects it before release."
59
+ },
60
+ {
61
+ "id": "marquee.excalidraw.contain-partial-overlap-selects-nothing",
62
+ "upstream": "https://github.com/excalidraw/excalidraw/blob/ed10ac7dca7e40f3f4a31269b4bfba980d0db41e/packages/excalidraw/tests/selection.test.tsx#L132",
63
+ "upstreamTest": "box-selection > should not select an element when the selection box only partially overlaps it",
64
+ "area": "selection",
65
+ "config": {
66
+ "boxSelection": "contain",
67
+ "camera": { "x": 0, "y": 0, "zoom": 1 },
68
+ "nodes": [{ "id": "rect1", "x": 0, "y": 0, "w": 50, "h": 50 }]
69
+ },
70
+ "steps": [
71
+ { "event": "pointerdown", "at": [25, -20] },
72
+ { "event": "pointermove", "at": [75, 70] },
73
+ { "event": "pointerup" },
74
+ { "expect": { "selection": [] } }
75
+ ],
76
+ "notes": "Excalidraw's default mode is contain, as in pointer.qnt. The node is a filled rectangle, so geometry-aware hit rules (hollow shapes) do not enter."
77
+ },
78
+ {
79
+ "id": "marquee.excalidraw.overlap-partial-selects",
80
+ "upstream": "https://github.com/excalidraw/excalidraw/blob/ed10ac7dca7e40f3f4a31269b4bfba980d0db41e/packages/excalidraw/tests/selection.test.tsx#L261",
81
+ "upstreamTest": "box-selection overlap mode > should select an element when the selection box partially overlaps it",
82
+ "area": "selection",
83
+ "config": {
84
+ "boxSelection": "overlap",
85
+ "camera": { "x": 0, "y": 0, "zoom": 1 },
86
+ "nodes": [{ "id": "rect1", "x": 0, "y": 0, "w": 50, "h": 50 }]
87
+ },
88
+ "steps": [
89
+ { "event": "pointerdown", "at": [25, -20] },
90
+ { "event": "pointermove", "at": [75, 70] },
91
+ { "event": "pointerup" },
92
+ { "expect": { "selection": ["rect1"] } }
93
+ ],
94
+ "notes": "The same drag as contain-partial-overlap-selects-nothing: contain selects a subset of overlap (pointer.qnt ContainSubsetOfOverlap). Upstream starts the app with boxSelectionMode 'overlap' in appState; the port states it as config."
95
+ },
96
+ {
97
+ "id": "marquee.excalidraw.unchanged-selection-does-not-churn",
98
+ "upstream": "https://github.com/excalidraw/excalidraw/blob/ed10ac7dca7e40f3f4a31269b4bfba980d0db41e/packages/excalidraw/tests/selection.test.tsx#L1407",
99
+ "upstreamTest": "selectedElementIds stability > box-selection should be stable when not changing selection",
100
+ "area": "extension",
101
+ "config": {
102
+ "boxSelection": "contain",
103
+ "camera": { "x": 0, "y": 0, "zoom": 1 },
104
+ "nodes": [{ "id": "rect", "x": 0, "y": 0, "w": 10, "h": 10 }]
105
+ },
106
+ "steps": [
107
+ { "event": "pointerdown", "at": [-100, -100] },
108
+ { "event": "pointermove", "at": [-50, -50] },
109
+ { "event": "pointerup" },
110
+ { "expect": { "selection": [], "selectionChanged": false } },
111
+ { "event": "pointerdown", "at": [-50, -50] },
112
+ { "event": "pointermove", "at": [50, 50] },
113
+ { "expect": { "selection": ["rect"], "selectionChanged": true } },
114
+ { "event": "pointermove", "at": [60, 60] },
115
+ { "expect": { "selection": ["rect"], "selectionChanged": false } },
116
+ { "event": "pointerup" },
117
+ { "expect": { "selection": ["rect"], "selectionChanged": false } }
118
+ ],
119
+ "notes": "Upstream asserts object identity of selectedElementIds (React state stability). The engine-neutral reading is selectionChanged: a step that leaves the selection set equal must not emit a selection change (a Solid signal must not fire; pointer.qnt announces only when a gesture ends with a changed selection)."
120
+ }
121
+ ]
122
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "pattern": "pan",
3
+ "source": "excalidraw",
4
+ "commit": "ed10ac7dca7e40f3f4a31269b4bfba980d0db41e",
5
+ "license": "MIT",
6
+ "copyright": "Copyright (c) 2020 Excalidraw",
7
+ "scenarios": [
8
+ {
9
+ "id": "pan.excalidraw.secondary-button-threshold",
10
+ "upstream": "https://github.com/excalidraw/excalidraw/blob/ed10ac7dca7e40f3f4a31269b4bfba980d0db41e/packages/excalidraw/tests/pan.test.tsx#L46",
11
+ "upstreamTest": "secondary-button pan > pans past five pixels with no context menu",
12
+ "area": "extension",
13
+ "config": {
14
+ "dragThresholdPx": 5,
15
+ "button": "secondary",
16
+ "camera": { "x": 0, "y": 0, "zoom": 1 }
17
+ },
18
+ "steps": [
19
+ { "event": "pointerdown", "at": [100, 100] },
20
+ { "event": "pointermove", "at": [103, 104] },
21
+ {
22
+ "expect": {
23
+ "camera": { "x": 0, "y": 0, "zoom": 1 },
24
+ "panEngaged": false
25
+ }
26
+ },
27
+ { "event": "pointermove", "at": [106, 100] },
28
+ {
29
+ "expect": {
30
+ "camera": { "x": 0, "y": 0, "zoom": 1 },
31
+ "panEngaged": true
32
+ }
33
+ },
34
+ { "event": "pointermove", "at": [108, 103] },
35
+ {
36
+ "expect": {
37
+ "camera": { "x": 2, "y": 3, "zoom": 1 },
38
+ "panEngaged": true
39
+ }
40
+ },
41
+ { "event": "pointerup" }
42
+ ],
43
+ "notes": "The threshold is a SCREEN quantity: 5 px away (dx 3, dy 4) is still a click, 6 px engages the pan. Upstream then pans incrementally from the engage point (the engaging move itself does not move the view; the next move shifts the camera by delta / zoom). pointer.qnt differs on purpose: its hand-tool pan starts on the press with no threshold and keeps the grab world point under the pointer (PanKeepsGrabUnderPointer), and its threshold is DRAG_PX = 4 (squared distance > 16). Replay the threshold expectations (config.dragThresholdPx) against the pointing -> gesture edge; the camera expectations after engagement are an adapter-policy divergence. Context-menu and pointer-broadcast parts of the upstream test are host concerns and are not ported."
44
+ }
45
+ ]
46
+ }