@dreamboard-games/sdk 0.5.0-alpha.2 → 0.5.0-alpha.21

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 (150) hide show
  1. package/README.md +462 -53
  2. package/dist/board-element-CqtxU5VZ.d.ts +13 -0
  3. package/dist/chunk-3RFGPBES.js +1792 -0
  4. package/dist/chunk-3RFGPBES.js.map +1 -0
  5. package/dist/chunk-CZRPUWCT.js +1602 -0
  6. package/dist/chunk-CZRPUWCT.js.map +1 -0
  7. package/dist/chunk-DMSXVMYY.js +7199 -0
  8. package/dist/chunk-DMSXVMYY.js.map +1 -0
  9. package/dist/chunk-U6R7FAH4.js +630 -0
  10. package/dist/chunk-U6R7FAH4.js.map +1 -0
  11. package/dist/chunk-YT3RPO5J.js +635 -0
  12. package/dist/chunk-YT3RPO5J.js.map +1 -0
  13. package/dist/diagnostics-ZAPEBlJP.d.ts +64 -0
  14. package/dist/extract-CwJWDrSD.d.ts +6850 -0
  15. package/dist/index.d.ts +5177 -1
  16. package/dist/index.js +1990 -6
  17. package/dist/index.js.map +1 -1
  18. package/dist/react.d.ts +158 -0
  19. package/dist/react.js +1119 -0
  20. package/dist/react.js.map +1 -0
  21. package/dist/reducer.d.ts +1028 -97
  22. package/dist/reducer.js +4892 -125
  23. package/dist/reducer.js.map +1 -1
  24. package/dist/shortcuts-B_RrufOM.d.ts +822 -0
  25. package/dist/testing.d.ts +529 -569
  26. package/dist/testing.js +2588 -934
  27. package/dist/testing.js.map +1 -1
  28. package/dist/types-BxXRINRF.d.ts +118 -0
  29. package/package.json +39 -144
  30. package/dist/HandView-Bi2lxzQP.d.ts +0 -79
  31. package/dist/ResourceCounter-BFxJknqp.d.ts +0 -98
  32. package/dist/ThemeProvider-CElaL2ik.d.ts +0 -99
  33. package/dist/attributes-DbvyMbXw.d.ts +0 -68
  34. package/dist/browser-interaction.d.ts +0 -910
  35. package/dist/browser-interaction.js +0 -119
  36. package/dist/browser-interaction.js.map +0 -1
  37. package/dist/chunk-32PFKDV7.js +0 -11705
  38. package/dist/chunk-32PFKDV7.js.map +0 -1
  39. package/dist/chunk-3BAD4VC2.js +0 -263
  40. package/dist/chunk-3BAD4VC2.js.map +0 -1
  41. package/dist/chunk-3SKDNDPG.js +0 -104
  42. package/dist/chunk-3SKDNDPG.js.map +0 -1
  43. package/dist/chunk-5YV2WHJ4.js +0 -25
  44. package/dist/chunk-5YV2WHJ4.js.map +0 -1
  45. package/dist/chunk-ANRB4XKA.js +0 -90
  46. package/dist/chunk-ANRB4XKA.js.map +0 -1
  47. package/dist/chunk-BI4G3PM2.js +0 -1864
  48. package/dist/chunk-BI4G3PM2.js.map +0 -1
  49. package/dist/chunk-EVCLTY4Q.js +0 -935
  50. package/dist/chunk-EVCLTY4Q.js.map +0 -1
  51. package/dist/chunk-GDPKTBUR.js +0 -564
  52. package/dist/chunk-GDPKTBUR.js.map +0 -1
  53. package/dist/chunk-H6VDGFL5.js +0 -2903
  54. package/dist/chunk-H6VDGFL5.js.map +0 -1
  55. package/dist/chunk-LR3ZTWQF.js +0 -5960
  56. package/dist/chunk-LR3ZTWQF.js.map +0 -1
  57. package/dist/chunk-PF7L4BMG.js +0 -117
  58. package/dist/chunk-PF7L4BMG.js.map +0 -1
  59. package/dist/chunk-PN5O6GG2.js +0 -32
  60. package/dist/chunk-PN5O6GG2.js.map +0 -1
  61. package/dist/chunk-PZ5AY32C.js +0 -10
  62. package/dist/chunk-PZ5AY32C.js.map +0 -1
  63. package/dist/chunk-S2JEDVWH.js +0 -7165
  64. package/dist/chunk-S2JEDVWH.js.map +0 -1
  65. package/dist/chunk-T3ZKNUZ7.js +0 -1
  66. package/dist/chunk-T3ZKNUZ7.js.map +0 -1
  67. package/dist/chunk-UGH54WZ2.js +0 -223
  68. package/dist/chunk-UGH54WZ2.js.map +0 -1
  69. package/dist/chunk-VCDWP5VL.js +0 -746
  70. package/dist/chunk-VCDWP5VL.js.map +0 -1
  71. package/dist/chunk-VDXOF4FW.js +0 -69
  72. package/dist/chunk-VDXOF4FW.js.map +0 -1
  73. package/dist/chunk-WHR5UW3F.js +0 -1988
  74. package/dist/chunk-WHR5UW3F.js.map +0 -1
  75. package/dist/chunk-Y75CFE77.js +0 -17
  76. package/dist/chunk-Y75CFE77.js.map +0 -1
  77. package/dist/chunk-Z7QCREWI.js +0 -3435
  78. package/dist/chunk-Z7QCREWI.js.map +0 -1
  79. package/dist/chunk-ZZGN2ATS.js +0 -1941
  80. package/dist/chunk-ZZGN2ATS.js.map +0 -1
  81. package/dist/components-gAbIz7hK.d.ts +0 -1284
  82. package/dist/definitions-BgmV_GhC.d.ts +0 -300
  83. package/dist/diagnostics-DIi3ina7.d.ts +0 -50
  84. package/dist/digest.d.ts +0 -8
  85. package/dist/game-CsZScpcn.d.ts +0 -763
  86. package/dist/hex-board-view-aKkblDp6.d.ts +0 -1228
  87. package/dist/index-CcT9Q7N7.d.ts +0 -193
  88. package/dist/index.d-DjzoK7zn.d.ts +0 -2101
  89. package/dist/package-set.d.ts +0 -13
  90. package/dist/package-set.js +0 -12
  91. package/dist/package-set.js.map +0 -1
  92. package/dist/player-state-Cqpyeql0.d.ts +0 -371
  93. package/dist/plugin-runtime-contract.d.ts +0 -17
  94. package/dist/plugin-runtime-contract.js +0 -92
  95. package/dist/plugin-runtime-contract.js.map +0 -1
  96. package/dist/primitive-props-BNHDkgd7.d.ts +0 -16
  97. package/dist/protocol-dYgafTYY.d.ts +0 -314
  98. package/dist/reducer/advanced.d.ts +0 -82
  99. package/dist/reducer/advanced.js +0 -51
  100. package/dist/reducer/advanced.js.map +0 -1
  101. package/dist/reducer-contract.d.ts +0 -11
  102. package/dist/reducer-contract.js +0 -16
  103. package/dist/reducer-contract.js.map +0 -1
  104. package/dist/reference-games/index.d.ts +0 -31
  105. package/dist/reference-games/index.js +0 -48
  106. package/dist/reference-games/index.js.map +0 -1
  107. package/dist/runtime/primitives.d.ts +0 -250
  108. package/dist/runtime/primitives.js +0 -189
  109. package/dist/runtime/primitives.js.map +0 -1
  110. package/dist/runtime/runtime-api.d.ts +0 -2
  111. package/dist/runtime/runtime-api.js +0 -1
  112. package/dist/runtime/runtime-api.js.map +0 -1
  113. package/dist/runtime/workspace-contract.d.ts +0 -392
  114. package/dist/runtime/workspace-contract.js +0 -28
  115. package/dist/runtime/workspace-contract.js.map +0 -1
  116. package/dist/runtime-RJ5orDJU.d.ts +0 -1699
  117. package/dist/runtime-api-Bz5pwNU_.d.ts +0 -296
  118. package/dist/runtime-json-CQ9QbLZ5.d.ts +0 -5
  119. package/dist/runtime.d.ts +0 -83
  120. package/dist/runtime.js +0 -242
  121. package/dist/runtime.js.map +0 -1
  122. package/dist/schema.d.ts +0 -379
  123. package/dist/stale-contract-artifact-error-XLaweZtF.d.ts +0 -18
  124. package/dist/testing-compiler.d.ts +0 -37
  125. package/dist/testing-compiler.js +0 -272
  126. package/dist/testing-compiler.js.map +0 -1
  127. package/dist/testing-runtime.d.ts +0 -119
  128. package/dist/testing-runtime.js +0 -212
  129. package/dist/testing-runtime.js.map +0 -1
  130. package/dist/types-DJj5MJkl.d.ts +0 -256
  131. package/dist/types-DR7DoB1x.d.ts +0 -28
  132. package/dist/types-JWCYHmu7.d.ts +0 -122
  133. package/dist/types.d.ts +0 -1958
  134. package/dist/types.js +0 -14
  135. package/dist/types.js.map +0 -1
  136. package/dist/ui/components.d.ts +0 -17
  137. package/dist/ui/components.js +0 -216
  138. package/dist/ui/components.js.map +0 -1
  139. package/dist/ui/defaults.d.ts +0 -19
  140. package/dist/ui/defaults.js +0 -104
  141. package/dist/ui/defaults.js.map +0 -1
  142. package/dist/ui/player-state.d.ts +0 -2
  143. package/dist/ui/player-state.js +0 -1
  144. package/dist/ui/player-state.js.map +0 -1
  145. package/dist/ui/plugin-styles.css +0 -2
  146. package/dist/ui-contract-GTUPkf8z.d.ts +0 -1168
  147. package/dist/ui.d.ts +0 -317
  148. package/dist/ui.js +0 -277
  149. package/dist/ui.js.map +0 -1
  150. package/dist/views-BpuWcOyN.d.ts +0 -1641
package/dist/react.js ADDED
@@ -0,0 +1,1119 @@
1
+ import {
2
+ createGameInstance,
3
+ createGestureRecognizer,
4
+ isSameDropTarget,
5
+ magneticDropPoint
6
+ } from "./chunk-3RFGPBES.js";
7
+ import {
8
+ inputValueKey
9
+ } from "./chunk-CZRPUWCT.js";
10
+
11
+ // src/react/create-game-hook.tsx
12
+ import { createStore as createStore2 } from "@tanstack/store";
13
+
14
+ // src/react/shortcuts.ts
15
+ import { useLayoutEffect, useRef } from "react";
16
+ function canHandle(event) {
17
+ const target = event.target;
18
+ return !event.defaultPrevented && !event.repeat && !event.isComposing && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey && !(target instanceof Element && target.closest(
19
+ "input:not([type='radio']), textarea, select, [contenteditable]:not([contenteditable='false']), [role='textbox']"
20
+ )) && !Array.from(
21
+ document.querySelectorAll(
22
+ "[aria-modal='true'], dialog[open], [role='dialog'], [role='menu'], [role='listbox']"
23
+ )
24
+ ).some(
25
+ (surface) => !surface.closest("[aria-hidden='true']") && surface.checkVisibility({
26
+ visibilityProperty: true,
27
+ opacityProperty: true
28
+ })
29
+ );
30
+ }
31
+ function useShortcutsAdapter(game, controller, session, options, onResult) {
32
+ const latest = useRef({ options, onResult });
33
+ const registration = useRef(null);
34
+ useLayoutEffect(() => {
35
+ latest.current = { options, onResult };
36
+ });
37
+ useLayoutEffect(() => {
38
+ let mounted = true;
39
+ const owned = controller.register(latest.current.options);
40
+ registration.current = owned;
41
+ let source = game.getOptions().source;
42
+ let seat = game.snapshot?.me;
43
+ let lifetime = 0;
44
+ const unsubscribe = game.subscribe(() => {
45
+ const nextSource = game.getOptions().source;
46
+ const nextSeat = game.snapshot?.me;
47
+ if (source === nextSource && seat === nextSeat) return;
48
+ source = nextSource;
49
+ seat = nextSeat;
50
+ ++lifetime;
51
+ });
52
+ function keydown(event) {
53
+ if (!canHandle(event)) return;
54
+ const started = lifetime;
55
+ const active = () => mounted && lifetime === started;
56
+ const pending = controller.handle(
57
+ event.key,
58
+ session.getActiveTarget(),
59
+ session.getShortcutActivity()
60
+ );
61
+ if (!pending) return;
62
+ event.preventDefault();
63
+ void pending.then(
64
+ (result) => {
65
+ if (active()) latest.current.onResult(result);
66
+ },
67
+ (cause) => {
68
+ if (active())
69
+ latest.current.onResult(
70
+ cause instanceof Error ? cause : new Error(String(cause))
71
+ );
72
+ }
73
+ );
74
+ }
75
+ window.addEventListener("keydown", keydown);
76
+ return () => {
77
+ mounted = false;
78
+ unsubscribe();
79
+ window.removeEventListener("keydown", keydown);
80
+ registration.current = null;
81
+ owned.dispose();
82
+ };
83
+ }, [game, controller, session]);
84
+ useLayoutEffect(() => {
85
+ registration.current?.update(options);
86
+ });
87
+ }
88
+
89
+ // src/react/gesture.tsx
90
+ import { createStore } from "@tanstack/store";
91
+ import { useSelector } from "@tanstack/react-store";
92
+ import {
93
+ createContext,
94
+ useContext
95
+ } from "react";
96
+ var CARD_STYLE = Object.freeze({
97
+ // Sideways movement stays native scrolling; everything else reaches the recognizer.
98
+ touchAction: "pan-x",
99
+ userSelect: "none",
100
+ WebkitUserSelect: "none",
101
+ WebkitTouchCallout: "none"
102
+ });
103
+ var INSPECTION_STYLE = Object.freeze({
104
+ ...CARD_STYLE,
105
+ // Inspection-only cards should not block their table's native scrolling.
106
+ touchAction: "manipulation"
107
+ });
108
+ var ROW_STYLE = Object.freeze({
109
+ // A row that fits never scrolls; its cards' sideways movement scrubs instead.
110
+ touchAction: "none"
111
+ });
112
+ var px = (value) => `${value}px`;
113
+ function resolveDropArea(drag, binding) {
114
+ if (!drag || !binding) return null;
115
+ if ("interactionKey" in binding)
116
+ return drag.getIsDropTarget(binding) ? binding : null;
117
+ const { position } = binding;
118
+ const matches = drag.getDropTargets().filter(
119
+ (target2) => target2.kind === (position ? "position" : "interaction") && target2.interactionKey === binding.interaction && (!binding.input || target2.cardInputKey === binding.input) && (!position || target2.kind === "position" && inputValueKey(target2.value) === inputValueKey(position))
120
+ );
121
+ if (position)
122
+ return matches.length === 1 && drag.getIsDropTarget(matches[0]) ? matches[0] : null;
123
+ if (matches.length !== 1) return null;
124
+ const target = {
125
+ ...matches[0],
126
+ ...binding.params === void 0 ? {} : { params: binding.params }
127
+ };
128
+ return drag.getIsDropTarget(target) ? target : null;
129
+ }
130
+ var sameDropTarget = (left, right) => left === right || !!left && !!right && isSameDropTarget(left, right);
131
+ var sessions = 0;
132
+ function createGestureSession(game) {
133
+ const id = `g${++sessions}`;
134
+ const store = createStore({
135
+ activeTarget: null,
136
+ focusedTarget: null,
137
+ pointerActive: false,
138
+ drag: null,
139
+ inspect: null
140
+ });
141
+ const targets = /* @__PURE__ */ new Map();
142
+ const sameTarget = (left, right) => inputValueKey(left) === inputValueKey(right);
143
+ const areas = /* @__PURE__ */ new Map();
144
+ const presentations = /* @__PURE__ */ new Map();
145
+ const rows = /* @__PURE__ */ new Map();
146
+ const controls = /* @__PURE__ */ new Map();
147
+ const followers = /* @__PURE__ */ new Set();
148
+ let press = null;
149
+ let hover = null;
150
+ let hoverPoint = null;
151
+ let focused = null;
152
+ let domFocused = null;
153
+ let alt = false;
154
+ let pointer = { x: 0, y: 0 };
155
+ let swallowing = false;
156
+ let guardingClicks = false;
157
+ let disposed = false;
158
+ const set = (patch) => store.setState((previous) => {
159
+ const next = { ...previous, ...patch };
160
+ return {
161
+ ...next,
162
+ focusedTarget: domFocused,
163
+ pointerActive: press !== null || next.drag !== null
164
+ };
165
+ });
166
+ let snapshot = game.snapshot;
167
+ let source = game.getOptions().source;
168
+ function admitted(target) {
169
+ if (!target || !game.snapshot) return null;
170
+ const zones = game.snapshot.frame.zones;
171
+ if (target.kind === "zone")
172
+ return zones[target.zoneId]?.[target.hostId] ? target : null;
173
+ if (target.kind === "card")
174
+ return Object.entries(snapshot?.frame.zones ?? {}).some(
175
+ ([zoneId, hosts]) => Object.entries(hosts).some(
176
+ ([hostId, zone]) => zone.cardIds.includes(target.value) && zones[zoneId]?.[hostId]?.cardIds.includes(target.value)
177
+ )
178
+ ) ? target : null;
179
+ return [...targets.values()].some((read) => sameTarget(read(), target)) ? target : null;
180
+ }
181
+ function admittedFocus(target) {
182
+ if (!target || !sameTarget(target, targetOf(document.activeElement)))
183
+ return null;
184
+ if (target.kind !== "card") return admitted(target);
185
+ return Object.values(game.snapshot?.frame.zones ?? {}).some(
186
+ (hosts) => Object.values(hosts).some((zone) => zone.cardIds.includes(target.value))
187
+ ) ? target : null;
188
+ }
189
+ const unsubscribe = game.subscribe(() => {
190
+ const nextSource = game.getOptions().source;
191
+ const changedLifetime = source !== nextSource || snapshot?.me !== game.snapshot?.me;
192
+ const changedSnapshot = snapshot !== game.snapshot;
193
+ if (changedLifetime || snapshot !== game.snapshot) {
194
+ press?.recognizer.cancel();
195
+ const nextHover = changedLifetime ? null : admitted(hover);
196
+ const nextFocused = changedLifetime ? null : admitted(focused);
197
+ const nextDomFocused = changedLifetime ? null : admittedFocus(domFocused);
198
+ source = nextSource;
199
+ snapshot = game.snapshot;
200
+ alt = false;
201
+ hover = nextHover;
202
+ focused = nextFocused;
203
+ domFocused = nextDomFocused;
204
+ if (changedLifetime) hoverPoint = null;
205
+ set({ activeTarget: hover ?? focused, inspect: null });
206
+ }
207
+ if (press?.dragging && !game.drag?.active) press.recognizer.cancel();
208
+ const drag = store.get().drag;
209
+ if (drag?.settling && (game.request === null || changedSnapshot || changedLifetime))
210
+ set({ drag: null });
211
+ });
212
+ function hitArea(at) {
213
+ for (const element of document.elementsFromPoint(at.x, at.y)) {
214
+ if (element.closest("[data-drag-overlay]")) continue;
215
+ const area = element.closest("[data-drop-area]");
216
+ if (area) return area.getAttribute("data-drop-area");
217
+ }
218
+ return null;
219
+ }
220
+ function retarget(at) {
221
+ if (!press?.dragging) return at;
222
+ let area = hitArea(at);
223
+ const read = area === null ? void 0 : areas.get(area);
224
+ let target = read ? resolveDropArea(game.drag, read(at)) : null;
225
+ let point = at;
226
+ if (!target) {
227
+ const candidates = [...areas].sort(
228
+ ([left], [right]) => left === press?.area ? -1 : right === press?.area ? 1 : 0
229
+ );
230
+ let distance = Infinity;
231
+ for (const [id2, binding] of candidates) {
232
+ const resolved = resolveDropArea(game.drag, binding(at));
233
+ if (!resolved || !game.drag?.getDropTargets().some((item) => sameDropTarget(item, resolved)))
234
+ continue;
235
+ const element = document.querySelector(
236
+ `[data-drop-area="${CSS.escape(id2)}"]`
237
+ );
238
+ if (!(element instanceof HTMLElement)) continue;
239
+ const snapped = magneticDropPoint(
240
+ at,
241
+ element.getBoundingClientRect(),
242
+ id2 === press.area,
243
+ press.pointerType === "touch"
244
+ );
245
+ if (!snapped) continue;
246
+ const nextDistance = Math.hypot(at.x - snapped.x, at.y - snapped.y);
247
+ if (nextDistance >= distance) continue;
248
+ area = id2;
249
+ target = resolved;
250
+ point = snapped;
251
+ distance = nextDistance;
252
+ if (id2 === press.area) break;
253
+ }
254
+ }
255
+ if (area !== press.area || !sameDropTarget(target, press.target)) {
256
+ press.area = area;
257
+ press.target = target;
258
+ game.drag?.setDropTarget(target);
259
+ const drag = store.get().drag;
260
+ if (drag) set({ drag: { ...drag, area, target } });
261
+ }
262
+ return point;
263
+ }
264
+ function swallow(event) {
265
+ if (!swallowing || event.detail === 0) return;
266
+ swallowing = false;
267
+ event.stopPropagation();
268
+ event.preventDefault();
269
+ }
270
+ function newPress() {
271
+ swallowing = false;
272
+ }
273
+ function swallowNextClick() {
274
+ swallowing = true;
275
+ if (guardingClicks) return;
276
+ guardingClicks = true;
277
+ addEventListener("click", swallow, true);
278
+ addEventListener("pointerdown", newPress, true);
279
+ }
280
+ function clearHover() {
281
+ hover = null;
282
+ }
283
+ function inspectWithAlt() {
284
+ const target = hover ?? focused;
285
+ const cardId = target?.kind === "card" ? target.value : null;
286
+ const pending = store.get().drag;
287
+ set({
288
+ activeTarget: target,
289
+ inspect: alt && cardId && !press && !(pending?.settling && pending.cardIds.includes(cardId)) ? { cardId, via: "hover" } : null
290
+ });
291
+ }
292
+ function targetOf(element) {
293
+ const hit = element?.closest("[data-card-gesture], [data-shortcut-target]");
294
+ const control = hit?.getAttribute("data-gesture-session") === id ? hit : null;
295
+ const targetId = control?.getAttribute("data-shortcut-target");
296
+ const cardId = control?.getAttribute("data-card-gesture");
297
+ return targetId ? targets.get(targetId)?.() ?? null : cardId ? { kind: "card", value: cardId } : null;
298
+ }
299
+ function hoverAt(at) {
300
+ hoverPoint = at;
301
+ const wasFocused = focused !== null;
302
+ focused = null;
303
+ const next = targetOf(document.elementFromPoint(at.x, at.y));
304
+ if (sameTarget(hover, next) && !wasFocused) return;
305
+ hover = next;
306
+ inspectWithAlt();
307
+ }
308
+ const browse = (event) => {
309
+ if (event.pointerType !== "mouse" || press) return;
310
+ if (hoverPoint?.x === event.clientX && hoverPoint.y === event.clientY)
311
+ return;
312
+ alt = event.altKey;
313
+ hoverAt({ x: event.clientX, y: event.clientY });
314
+ };
315
+ const key = (event) => {
316
+ if (event.key === "Escape") {
317
+ press?.recognizer.cancel();
318
+ alt = false;
319
+ set({ inspect: null });
320
+ } else if (event.key === "Alt") {
321
+ alt = event.type === "keydown";
322
+ inspectWithAlt();
323
+ }
324
+ };
325
+ const loseFocus = () => {
326
+ alt = false;
327
+ clearHover();
328
+ hoverPoint = null;
329
+ focused = null;
330
+ domFocused = null;
331
+ set({ activeTarget: null, inspect: null });
332
+ };
333
+ addEventListener("keydown", key);
334
+ addEventListener("keyup", key);
335
+ addEventListener("blur", loseFocus);
336
+ const leaveWindow = (event) => {
337
+ if (event.pointerType !== "mouse" || event.relatedTarget !== null) return;
338
+ clearHover();
339
+ hoverPoint = null;
340
+ focused = null;
341
+ alt = false;
342
+ inspectWithAlt();
343
+ };
344
+ addEventListener("pointermove", browse);
345
+ addEventListener("pointerout", leaveWindow);
346
+ function release() {
347
+ press?.detach();
348
+ press = null;
349
+ set({});
350
+ }
351
+ function start(event, cardId, element, options) {
352
+ if (disposed || press || event.button !== 0) return;
353
+ const pending = store.get().drag;
354
+ if (pending?.settling && pending.cardIds.includes(cardId)) return;
355
+ let card = { cardId, element, options };
356
+ const row = event.pointerType === "mouse" ? null : element.closest("[data-card-row]");
357
+ const cardAt = rows.get(row?.getAttribute("data-card-row") ?? "");
358
+ const scrubbing = cardAt !== void 0;
359
+ const activate = (next) => {
360
+ if ((hover?.kind === "card" ? hover.value : null) === next) return;
361
+ hover = next === null ? null : { kind: "card", value: next };
362
+ inspectWithAlt();
363
+ };
364
+ function scrubTo(at) {
365
+ const next = cardAt(at);
366
+ const control = next === null ? null : row.querySelector(`[data-card-gesture="${CSS.escape(next)}"]`);
367
+ const routes = controls.get(
368
+ control?.getAttribute("data-card-control") ?? ""
369
+ );
370
+ card = next === null || !control || !routes ? null : { cardId: next, element: control, options: routes() };
371
+ activate(card?.cardId ?? null);
372
+ }
373
+ const recognizer = createGestureRecognizer(
374
+ event,
375
+ {
376
+ // A hold comes before any movement, so the pressed card is still chosen.
377
+ hold() {
378
+ set({ inspect: { cardId, via: "hold" } });
379
+ },
380
+ dragStart(origin) {
381
+ if (!card) return false;
382
+ const { cardId: cardId2, element: element2, options: options2 } = card;
383
+ if (options2 === false || !game.drag?.begin(cardId2, options2))
384
+ return false;
385
+ const box = element2.getBoundingClientRect();
386
+ clearHover();
387
+ focused = null;
388
+ press.dragging = true;
389
+ set({
390
+ drag: {
391
+ cardIds: game.drag?.active?.cardIds ?? [cardId2],
392
+ cardId: cardId2,
393
+ grab: { x: origin.x - box.left, y: origin.y - box.top },
394
+ size: { width: box.width, height: box.height },
395
+ settling: false,
396
+ area: null,
397
+ target: null,
398
+ landing: null
399
+ },
400
+ inspect: null,
401
+ activeTarget: null
402
+ });
403
+ return true;
404
+ },
405
+ dragMove(at) {
406
+ pointer = retarget(at);
407
+ for (const follow of followers) follow(pointer);
408
+ },
409
+ browse: scrubTo,
410
+ end(kind, at) {
411
+ const dragging = press.dragging;
412
+ const pointerType = press.pointerType;
413
+ const chosen = scrubbing && kind === "browse" ? card?.element : null;
414
+ if (dragging) retarget(at);
415
+ release();
416
+ if (scrubbing) activate(null);
417
+ if (chosen instanceof HTMLElement) chosen.click();
418
+ if (kind !== "tap") swallowNextClick();
419
+ if (kind === "hold") set({ inspect: null });
420
+ if (!dragging) {
421
+ if (kind === "browse" && pointerType === "mouse") hoverAt(at);
422
+ return;
423
+ }
424
+ const before = game.request;
425
+ const drag = store.get().drag;
426
+ const areaPresentation = drag?.area ? presentations.get(drag.area)?.() : void 0;
427
+ const position = drag?.target?.kind === "position" ? drag.target.value : null;
428
+ const zone = position ?? areaPresentation?.zone;
429
+ const presentation = zone ? [
430
+ areaPresentation,
431
+ ...Array.from(presentations.values(), (read) => read())
432
+ ].find(
433
+ (candidate) => candidate?.zone.zoneId === zone.zoneId && candidate.zone.hostId === zone.hostId
434
+ ) : void 0;
435
+ const destination = zone && game.zones.find(zone.zoneId, zone.hostId);
436
+ const cards = drag?.cardIds.flatMap((id2) => {
437
+ const card2 = game.cards.find(id2);
438
+ return card2 ? [card2] : [];
439
+ }) ?? [];
440
+ const landing = destination && zone && cards.length ? Object.freeze({
441
+ zoneId: zone.zoneId,
442
+ hostId: zone.hostId,
443
+ index: position?.index ?? presentation?.index ?? destination.getCards().length,
444
+ concealed: presentation ? presentation.visibility === "hidden" || presentation.visibility === "ownerOnly" && zone.hostId !== game.me?.id : cards.some(
445
+ (card2) => card2.zone !== zone.zoneId || card2.hostId !== zone.hostId || card2.hidden
446
+ ),
447
+ cards: Object.freeze(cards),
448
+ snapshot: game.snapshot
449
+ }) : null;
450
+ game.drag?.drop();
451
+ if (drag && game.request !== null && game.request !== before)
452
+ set({ drag: { ...drag, settling: true, landing } });
453
+ else set({ drag: null });
454
+ },
455
+ cancel(kind) {
456
+ const dragging = press.dragging;
457
+ release();
458
+ if (scrubbing) activate(null);
459
+ if (kind !== "tap") swallowNextClick();
460
+ if (kind === "hold") set({ inspect: null });
461
+ if (dragging) {
462
+ game.drag?.cancel();
463
+ set({ drag: null });
464
+ }
465
+ }
466
+ },
467
+ { scrub: scrubbing }
468
+ );
469
+ const move = (next) => recognizer.move(next);
470
+ const up = (next) => recognizer.up(next);
471
+ const cancel = (next) => recognizer.cancel(next);
472
+ const blur = () => recognizer.cancel();
473
+ addEventListener("pointermove", move);
474
+ addEventListener("pointerup", up);
475
+ addEventListener("pointercancel", cancel);
476
+ addEventListener("blur", blur);
477
+ if (event.pointerType === "mouse") {
478
+ try {
479
+ element.setPointerCapture(event.pointerId);
480
+ } catch {
481
+ }
482
+ }
483
+ press = {
484
+ recognizer,
485
+ pointerType: event.pointerType,
486
+ dragging: false,
487
+ area: null,
488
+ target: null,
489
+ detach() {
490
+ removeEventListener("pointermove", move);
491
+ removeEventListener("pointerup", up);
492
+ removeEventListener("pointercancel", cancel);
493
+ removeEventListener("blur", blur);
494
+ }
495
+ };
496
+ set({});
497
+ }
498
+ function cardProps(cardId, control, options, flags) {
499
+ return {
500
+ onPointerDown(event) {
501
+ if (event.pointerType === "mouse") {
502
+ hover = { kind: "card", value: cardId };
503
+ alt = event.altKey;
504
+ inspectWithAlt();
505
+ }
506
+ start(event.nativeEvent, cardId, event.currentTarget, options);
507
+ },
508
+ onFocus(event) {
509
+ focused = event.currentTarget.matches(":focus-visible") ? { kind: "card", value: cardId } : null;
510
+ domFocused = { kind: "card", value: cardId };
511
+ if (focused) hover = null;
512
+ inspectWithAlt();
513
+ },
514
+ onBlur() {
515
+ if (focused?.kind === "card" && focused.value === cardId)
516
+ focused = null;
517
+ if (domFocused?.kind === "card" && domFocused.value === cardId)
518
+ domFocused = null;
519
+ inspectWithAlt();
520
+ },
521
+ onContextMenu(event) {
522
+ if (press && press.pointerType !== "mouse" || swallowing)
523
+ event.preventDefault();
524
+ },
525
+ onDragStart: (event) => event.preventDefault(),
526
+ style: options === false ? INSPECTION_STYLE : CARD_STYLE,
527
+ "data-dragging": flags.dragging || void 0,
528
+ "data-inspecting": flags.inspecting ?? void 0,
529
+ "data-card-gesture": cardId,
530
+ "data-card-control": control,
531
+ "data-gesture-session": id
532
+ };
533
+ }
534
+ function overlayRef(element) {
535
+ const drag = store.get().drag;
536
+ if (!element || !drag) return;
537
+ element.setAttribute("data-drag-overlay", "");
538
+ Object.assign(element.style, {
539
+ position: "fixed",
540
+ margin: "0",
541
+ pointerEvents: "none",
542
+ width: px(drag.size.width),
543
+ height: px(drag.size.height)
544
+ });
545
+ const follow = (at) => {
546
+ element.style.left = px(at.x - drag.grab.x);
547
+ element.style.top = px(at.y - drag.grab.y);
548
+ };
549
+ follow(pointer);
550
+ followers.add(follow);
551
+ return () => {
552
+ followers.delete(follow);
553
+ };
554
+ }
555
+ return {
556
+ id,
557
+ store,
558
+ cardProps,
559
+ getActiveCardId() {
560
+ const target = store.get().activeTarget;
561
+ return target?.kind === "card" ? target.value : null;
562
+ },
563
+ getActiveTarget: () => store.get().activeTarget,
564
+ getShortcutActivity: () => ({
565
+ // Removal can change DOM focus without emitting blur. Admit the current mounted control.
566
+ focusedTarget: sameTarget(domFocused, targetOf(document.activeElement)) ? domFocused : null,
567
+ pointerActive: store.get().pointerActive
568
+ }),
569
+ registerTarget(targetId, read) {
570
+ const target = read();
571
+ targets.set(targetId, read);
572
+ return () => {
573
+ targets.delete(targetId);
574
+ if (sameTarget(hover, target)) hover = null;
575
+ if (sameTarget(focused, target)) focused = null;
576
+ if (sameTarget(domFocused, target)) domFocused = null;
577
+ inspectWithAlt();
578
+ };
579
+ },
580
+ targetProps(targetId) {
581
+ return {
582
+ "data-shortcut-target": targetId,
583
+ "data-gesture-session": id,
584
+ onPointerDown(event) {
585
+ if (event.pointerType !== "mouse") return;
586
+ hover = targets.get(targetId)?.() ?? null;
587
+ alt = event.altKey;
588
+ inspectWithAlt();
589
+ },
590
+ onFocus(event) {
591
+ focused = event.currentTarget.matches(":focus-visible") ? targets.get(targetId)?.() ?? null : null;
592
+ domFocused = targets.get(targetId)?.() ?? null;
593
+ if (focused) hover = null;
594
+ inspectWithAlt();
595
+ },
596
+ onBlur() {
597
+ const target = targets.get(targetId)?.() ?? null;
598
+ if (sameTarget(focused, target)) focused = null;
599
+ if (sameTarget(domFocused, target)) domFocused = null;
600
+ inspectWithAlt();
601
+ }
602
+ };
603
+ },
604
+ overlayRef,
605
+ /** `read` returns the area's current binding; targets resolve when hit. */
606
+ registerArea(area, read, presentation) {
607
+ areas.set(area, read);
608
+ presentations.set(area, presentation);
609
+ return () => {
610
+ areas.delete(area);
611
+ presentations.delete(area);
612
+ };
613
+ },
614
+ /** `read` returns a mounted card control's current drag routes. */
615
+ registerControl(control, read) {
616
+ controls.set(control, read);
617
+ return () => {
618
+ controls.delete(control);
619
+ };
620
+ },
621
+ registerRow(row, cardAt) {
622
+ rows.set(row, cardAt);
623
+ return () => {
624
+ rows.delete(row);
625
+ };
626
+ },
627
+ rowProps(row) {
628
+ return { "data-card-row": row, style: ROW_STYLE };
629
+ },
630
+ dispose() {
631
+ disposed = true;
632
+ press?.recognizer.cancel();
633
+ clearHover();
634
+ removeEventListener("keydown", key);
635
+ removeEventListener("keyup", key);
636
+ removeEventListener("blur", loseFocus);
637
+ removeEventListener("pointermove", browse);
638
+ removeEventListener("pointerout", leaveWindow);
639
+ unsubscribe();
640
+ followers.clear();
641
+ targets.clear();
642
+ areas.clear();
643
+ presentations.clear();
644
+ rows.clear();
645
+ controls.clear();
646
+ if (!guardingClicks) return;
647
+ removeEventListener("click", swallow, true);
648
+ removeEventListener("pointerdown", newPress, true);
649
+ }
650
+ };
651
+ }
652
+ var GestureContext = createContext(null);
653
+ function useGestureSession() {
654
+ const session = useContext(GestureContext);
655
+ if (!session) throw new Error("Gesture hooks require their GameProvider.");
656
+ return session;
657
+ }
658
+ function useGestureState(session, select) {
659
+ return useSelector(session.store, select);
660
+ }
661
+
662
+ // src/react/create-game-hook.tsx
663
+ import {
664
+ createContext as createContext2,
665
+ useContext as useContext2,
666
+ useId,
667
+ useLayoutEffect as useLayoutEffect2,
668
+ useRef as useRef2,
669
+ useState,
670
+ useMemo
671
+ } from "react";
672
+ import { useSelector as useSelector2 } from "@tanstack/react-store";
673
+ import { jsx } from "react/jsx-runtime";
674
+ function createGameHook() {
675
+ return function(defaults) {
676
+ const Context = createContext2(null);
677
+ function GameProvider({ children, ...overrides }) {
678
+ const [owned, setOwned] = useState(null);
679
+ const lifetime = useRef2(null);
680
+ const options = {
681
+ ...defaults,
682
+ ...overrides
683
+ };
684
+ useLayoutEffect2(() => {
685
+ let current = lifetime.current;
686
+ if (!current) {
687
+ const instance = createGameInstance()(options);
688
+ current = {
689
+ instance,
690
+ // One bound game crosses into the framework adapter's erased runtime.
691
+ session: createGestureSession(instance),
692
+ generation: 0
693
+ };
694
+ lifetime.current = current;
695
+ setOwned({ instance, session: current.session });
696
+ }
697
+ const owned2 = current;
698
+ ++owned2.generation;
699
+ return () => {
700
+ const generation = ++owned2.generation;
701
+ queueMicrotask(() => {
702
+ if (owned2.generation !== generation) return;
703
+ owned2.session.dispose();
704
+ owned2.instance.dispose();
705
+ if (lifetime.current === owned2) lifetime.current = null;
706
+ });
707
+ };
708
+ }, []);
709
+ useLayoutEffect2(() => {
710
+ lifetime.current?.instance.setOptions(options);
711
+ });
712
+ return owned ? /* @__PURE__ */ jsx(Context.Provider, { value: owned.instance, children: /* @__PURE__ */ jsx(GestureContext.Provider, { value: owned.session, children }) }) : null;
713
+ }
714
+ function useInstance() {
715
+ const instance = useContext2(Context);
716
+ if (!instance) throw new Error("useGame requires its GameProvider.");
717
+ return instance;
718
+ }
719
+ function useGame(selector, options) {
720
+ const instance = useInstance();
721
+ const selected = useSelector2(
722
+ instance.store,
723
+ (snapshot) => selector ? { value: selector(snapshot) } : { snapshot },
724
+ {
725
+ compare: (previous, next) => "value" in previous && "value" in next ? (options?.compare ?? Object.is)(previous.value, next.value) : "snapshot" in previous && "snapshot" in next && Object.is(previous.snapshot, next.snapshot)
726
+ }
727
+ );
728
+ return "value" in selected ? selected.value : instance;
729
+ }
730
+ function Subscribe({
731
+ selector,
732
+ compare,
733
+ children
734
+ }) {
735
+ return children(useGame(selector, { compare }));
736
+ }
737
+ const dragOf = (snapshot) => snapshot.drag;
738
+ function useCardGesture(cardId, options) {
739
+ const session = useGestureSession();
740
+ const control = `${session.id}${useId()}`;
741
+ const routes = options.drag;
742
+ const latestRoutes = useRef2(routes);
743
+ useLayoutEffect2(() => {
744
+ latestRoutes.current = routes;
745
+ });
746
+ useLayoutEffect2(
747
+ () => session.registerControl(control, () => latestRoutes.current),
748
+ [session, control]
749
+ );
750
+ const canDrag = useGame(
751
+ (snapshot) => routes !== false && (dragOf(snapshot)?.getCanDrag(cardId, routes) ?? false)
752
+ );
753
+ const dragging = useGestureState(
754
+ session,
755
+ (state) => state.drag?.cardIds.includes(cardId) ?? false
756
+ );
757
+ const pending = useGestureState(
758
+ session,
759
+ (state) => !!state.drag?.settling && state.drag.cardIds.includes(cardId)
760
+ );
761
+ const inspecting = useGestureState(
762
+ session,
763
+ (state) => state.inspect?.cardId === cardId ? state.inspect.via : null
764
+ );
765
+ const isActive = useGestureState(
766
+ session,
767
+ (state) => state.activeTarget?.kind === "card" && state.activeTarget.value === cardId
768
+ );
769
+ return {
770
+ props: session.cardProps(cardId, control, routes, {
771
+ dragging,
772
+ inspecting
773
+ }),
774
+ canDrag,
775
+ isDragging: dragging,
776
+ isPending: pending,
777
+ isActive,
778
+ inspecting
779
+ };
780
+ }
781
+ function useDropArea(binding, options) {
782
+ const session = useGestureSession();
783
+ const id = `${session.id}${useId()}`;
784
+ const erased = binding;
785
+ const fixed = typeof erased === "function" ? null : erased;
786
+ const target = useGame(
787
+ (snapshot) => resolveDropArea(dragOf(snapshot), fixed),
788
+ { compare: sameDropTarget }
789
+ );
790
+ const isOver = useGame((snapshot) => {
791
+ const active = dragOf(snapshot)?.active?.target;
792
+ return !!target && sameDropTarget(active ?? null, target);
793
+ });
794
+ const following = useGestureState(
795
+ session,
796
+ (state) => typeof erased === "function" && state.drag?.area === id && state.drag.target !== null
797
+ );
798
+ const latest = useRef2(erased);
799
+ const latestPresentation = useRef2(options);
800
+ useLayoutEffect2(() => {
801
+ latest.current = erased;
802
+ latestPresentation.current = options;
803
+ });
804
+ useLayoutEffect2(
805
+ () => session.registerArea(
806
+ id,
807
+ (point) => {
808
+ const current = latest.current;
809
+ return typeof current === "function" ? current(point) : current;
810
+ },
811
+ () => latestPresentation.current
812
+ ),
813
+ [session, id]
814
+ );
815
+ return {
816
+ props: {
817
+ "data-drop-area": id,
818
+ "data-drop-target": target || following ? "true" : void 0,
819
+ "data-drop-over": isOver || following || void 0
820
+ },
821
+ isEligible: target !== null || following,
822
+ isOver: isOver || following
823
+ };
824
+ }
825
+ function useZonePresentation(zoneId, hostId, options) {
826
+ const game = useGame();
827
+ const session = useGestureSession();
828
+ const landing = useGestureState(
829
+ session,
830
+ (state) => state.drag?.settling ? state.drag.landing : null
831
+ );
832
+ const zone = game.zones.find(zoneId, hostId);
833
+ const actual = useMemo(
834
+ () => zone?.getCards() ?? [],
835
+ [zone]
836
+ );
837
+ const active = landing?.snapshot === game.snapshot ? landing : null;
838
+ const incoming = active?.zoneId === zoneId && active.hostId === hostId;
839
+ const outgoing = active?.cards.filter(
840
+ (card) => card.zone === zoneId && card.hostId === hostId
841
+ ) ?? [];
842
+ const cards = useMemo(() => {
843
+ const ordered = options ? options.order.flatMap((id) => {
844
+ const card = actual.find((card2) => card2.id === id);
845
+ return card ? [card] : [];
846
+ }) : actual;
847
+ if (!active) return ordered;
848
+ const moving = new Set(active.cards.map((card) => card.id));
849
+ const remaining = ordered.filter((card) => !moving.has(card.id));
850
+ if (incoming) {
851
+ const before = ordered.slice(0, active.index).filter((card) => moving.has(card.id)).length;
852
+ const index = Math.max(
853
+ 0,
854
+ Math.min(remaining.length, active.index - before)
855
+ );
856
+ remaining.splice(
857
+ index,
858
+ 0,
859
+ ...active.cards
860
+ );
861
+ }
862
+ return remaining;
863
+ }, [actual, active, incoming, options]);
864
+ const source = game.getOptions().source;
865
+ const me = game.me?.id;
866
+ const count = (zone?.count ?? 0) - outgoing.length + (incoming ? active.cards.length : 0);
867
+ const previous = useRef2({
868
+ source,
869
+ me,
870
+ ids: new Set(actual.map((card) => card.id)),
871
+ incoming: 0,
872
+ count: zone?.count ?? 0
873
+ });
874
+ const newlyArriving = previous.current.source === source && previous.current.me === me ? cards.filter(
875
+ (card) => !previous.current.ids.has(card.id) && !active?.cards.some((pending) => pending.id === card.id)
876
+ ).map((card) => card.id) : [];
877
+ const arrivingIds = previous.current.incoming > 0 && newlyArriving.length === previous.current.incoming && count === previous.current.count ? [] : newlyArriving;
878
+ useLayoutEffect2(() => {
879
+ previous.current = {
880
+ source,
881
+ me,
882
+ ids: new Set([...actual, ...cards].map((card) => card.id)),
883
+ incoming: incoming ? active.cards.length : 0,
884
+ count
885
+ };
886
+ });
887
+ return {
888
+ cards,
889
+ count,
890
+ arrivingIds
891
+ };
892
+ }
893
+ function useCardPresentation(cardId) {
894
+ const game = useGame();
895
+ const session = useGestureSession();
896
+ const landing = useGestureState(
897
+ session,
898
+ (state) => state.drag?.settling ? state.drag.landing : null
899
+ );
900
+ const active = landing?.snapshot === game.snapshot ? landing : null;
901
+ const captured = active?.cards.find((card2) => card2.id === cardId);
902
+ const card = game.cards.find(cardId) ?? captured;
903
+ return {
904
+ card,
905
+ isPending: !!captured,
906
+ hidden: card?.hidden === true || !!captured && active?.concealed === true
907
+ };
908
+ }
909
+ const shortcutsOf = (snapshot) => snapshot.shortcuts;
910
+ const noShortcutsConfiguration = createStore2(
911
+ null
912
+ );
913
+ const sameHints = (left, right) => left.length === right.length && left.every((hint, index) => {
914
+ const next = right[index];
915
+ return hint.label === next.label && hint.kind === next.kind && (hint.kind !== "interaction" || next.kind === "interaction" && hint.interaction === next.interaction) && hint.keys.length === next.keys.length && hint.keys.every((key, at) => key === next.keys[at]);
916
+ });
917
+ function useShortcutHints(target) {
918
+ const controller = shortcutsOf(useInstance());
919
+ const session = useGestureSession();
920
+ useSelector2(session.store, () => session.getShortcutActivity(), {
921
+ compare: (left, right) => left.focusedTarget === right.focusedTarget && left.pointerActive === right.pointerActive
922
+ });
923
+ useGame(
924
+ (snapshot) => shortcutsOf(snapshot)?.getHints(
925
+ target,
926
+ session.getShortcutActivity()
927
+ ) ?? [],
928
+ {
929
+ compare: sameHints
930
+ }
931
+ );
932
+ return useSelector2(
933
+ controller?.configuration ?? noShortcutsConfiguration,
934
+ () => controller?.getHints(
935
+ target,
936
+ session.getShortcutActivity()
937
+ ) ?? [],
938
+ { compare: sameHints }
939
+ );
940
+ }
941
+ function useGameShortcuts(options) {
942
+ const instance = useInstance();
943
+ const session = useGestureSession();
944
+ const controller = shortcutsOf(instance);
945
+ if (!controller)
946
+ throw new Error(
947
+ "useGameShortcuts requires shortcutsFeature in the game binding."
948
+ );
949
+ useShortcutsAdapter(instance, controller, session, options, (result) => {
950
+ if (result instanceof Error) instance.getOptions().onError?.(result);
951
+ else if (result.kind === "interaction" && !result.result.accepted)
952
+ instance.getOptions().onError?.(
953
+ new Error(result.result.message ?? result.result.errorCode, {
954
+ cause: result.result
955
+ })
956
+ );
957
+ });
958
+ const target = useGestureState(
959
+ session,
960
+ (state) => state.activeTarget
961
+ );
962
+ const hints = useShortcutHints(target);
963
+ return { target, hints };
964
+ }
965
+ function useShortcutTarget(target) {
966
+ const session = useGestureSession();
967
+ const id = `${session.id}${useId()}`;
968
+ const latest = useRef2(target);
969
+ const targetKey = inputValueKey(target);
970
+ useLayoutEffect2(() => {
971
+ latest.current = target;
972
+ });
973
+ useLayoutEffect2(
974
+ () => session.registerTarget(id, () => latest.current),
975
+ [session, id, targetKey]
976
+ );
977
+ return { props: session.targetProps(id) };
978
+ }
979
+ function useDragOverlay() {
980
+ const session = useGestureSession();
981
+ const drag = useGestureState(session, (state) => state.drag);
982
+ return drag ? {
983
+ cardId: drag.cardId,
984
+ cardIds: drag.cardIds,
985
+ settling: drag.settling,
986
+ // The drag feature resolved this target from this game's routes.
987
+ target: drag.target,
988
+ landing: drag.landing ? {
989
+ zoneId: drag.landing.zoneId,
990
+ hostId: drag.landing.hostId,
991
+ index: drag.landing.index
992
+ } : null,
993
+ ref: session.overlayRef
994
+ } : null;
995
+ }
996
+ function useCardRow(cardAt) {
997
+ const session = useGestureSession();
998
+ const id = `${session.id}${useId()}`;
999
+ const latest = useRef2(cardAt);
1000
+ useLayoutEffect2(() => {
1001
+ latest.current = cardAt;
1002
+ });
1003
+ useLayoutEffect2(
1004
+ () => session.registerRow(id, (point) => latest.current(point)),
1005
+ [session, id]
1006
+ );
1007
+ return { props: session.rowProps(id) };
1008
+ }
1009
+ function useActiveCard() {
1010
+ const session = useGestureSession();
1011
+ return useGestureState(
1012
+ session,
1013
+ (state) => state.activeTarget?.kind === "card" ? state.activeTarget.value : null
1014
+ );
1015
+ }
1016
+ return {
1017
+ GameProvider,
1018
+ useGame,
1019
+ Subscribe,
1020
+ useCardGesture,
1021
+ useCardRow,
1022
+ useActiveCard,
1023
+ useGameShortcuts,
1024
+ useShortcutTarget,
1025
+ useShortcutHints,
1026
+ useDropArea,
1027
+ useDragOverlay,
1028
+ useZonePresentation,
1029
+ useCardPresentation
1030
+ };
1031
+ };
1032
+ }
1033
+
1034
+ // src/react/marquee.ts
1035
+ import { useEffect, useRef as useRef3, useState as useState2 } from "react";
1036
+ function useMarqueeSelection(options) {
1037
+ const latest = useRef3(options);
1038
+ latest.current = options;
1039
+ const [bounds, setBounds] = useState2(null);
1040
+ const cleanup = useRef3(null);
1041
+ useEffect(() => () => cleanup.current?.(), []);
1042
+ useEffect(() => {
1043
+ if (!options.enabled) {
1044
+ cleanup.current?.();
1045
+ setBounds(null);
1046
+ }
1047
+ }, [options.enabled]);
1048
+ return {
1049
+ bounds,
1050
+ props: {
1051
+ onPointerDown(event) {
1052
+ if (!latest.current.enabled || event.button !== 0 || event.pointerType === "touch" || event.altKey || event.ctrlKey || event.metaKey || event.target.closest(
1053
+ "button, input, select, textarea, a, [contenteditable], [data-card-gesture]"
1054
+ ))
1055
+ return;
1056
+ event.preventDefault();
1057
+ cleanup.current?.();
1058
+ const x = event.clientX, y = event.clientY, pointerId = event.pointerId;
1059
+ const area = event.currentTarget;
1060
+ const additive = event.shiftKey;
1061
+ const doc = event.currentTarget.ownerDocument;
1062
+ let rectangle = null;
1063
+ function move(next) {
1064
+ if (next.pointerId !== pointerId) return;
1065
+ if (!rectangle && Math.hypot(next.clientX - x, next.clientY - y) < 8)
1066
+ return;
1067
+ const box = area.getBoundingClientRect();
1068
+ const right = Math.min(box.right, Math.max(x, next.clientX));
1069
+ const bottom = Math.min(box.bottom, Math.max(y, next.clientY));
1070
+ const left = Math.max(box.left, Math.min(x, next.clientX));
1071
+ const top = Math.max(box.top, Math.min(y, next.clientY));
1072
+ rectangle = { left, top, width: right - left, height: bottom - top };
1073
+ setBounds(rectangle);
1074
+ next.preventDefault();
1075
+ }
1076
+ function stop() {
1077
+ doc.removeEventListener("pointermove", move);
1078
+ doc.removeEventListener("pointerup", up);
1079
+ doc.removeEventListener("pointercancel", cancel);
1080
+ doc.removeEventListener("keydown", key);
1081
+ doc.defaultView?.removeEventListener("blur", cancel);
1082
+ cleanup.current = null;
1083
+ }
1084
+ function cancel() {
1085
+ stop();
1086
+ setBounds(null);
1087
+ }
1088
+ function key(next) {
1089
+ if (next.key === "Escape") {
1090
+ next.preventDefault();
1091
+ cancel();
1092
+ }
1093
+ }
1094
+ function up(next) {
1095
+ if (next.pointerId !== pointerId) return;
1096
+ const ids = rectangle ? latest.current.getCards().filter(({ element }) => {
1097
+ const box = element.getBoundingClientRect();
1098
+ const region = rectangle;
1099
+ return box.right > region.left && box.left < region.left + region.width && box.bottom > region.top && box.top < region.top + region.height;
1100
+ }).map((card) => card.id) : [];
1101
+ stop();
1102
+ setBounds(null);
1103
+ latest.current.onSelect(ids, additive);
1104
+ }
1105
+ cleanup.current = stop;
1106
+ doc.addEventListener("pointermove", move, { passive: false });
1107
+ doc.addEventListener("pointerup", up);
1108
+ doc.addEventListener("pointercancel", cancel);
1109
+ doc.addEventListener("keydown", key);
1110
+ doc.defaultView?.addEventListener("blur", cancel);
1111
+ }
1112
+ }
1113
+ };
1114
+ }
1115
+ export {
1116
+ createGameHook,
1117
+ useMarqueeSelection
1118
+ };
1119
+ //# sourceMappingURL=react.js.map