@dreamboard-games/sdk 0.5.0-alpha.21 → 0.5.0-alpha.23
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/dist/{chunk-DMSXVMYY.js → chunk-UUJSLFIJ.js} +6 -2
- package/dist/chunk-UUJSLFIJ.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/react.d.ts +20 -1
- package/dist/react.js +357 -60
- package/dist/react.js.map +1 -1
- package/dist/reducer.js +1 -1
- package/dist/testing.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-DMSXVMYY.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -73,7 +73,7 @@ import {
|
|
|
73
73
|
} from "./chunk-CZRPUWCT.js";
|
|
74
74
|
|
|
75
75
|
// src/package-set.ts
|
|
76
|
-
var DREAMBOARD_SDK_VERSION = true ? "0.5.0-alpha.
|
|
76
|
+
var DREAMBOARD_SDK_VERSION = true ? "0.5.0-alpha.23" : "0.0.0-development";
|
|
77
77
|
var DREAMBOARD_SDK_PACKAGES = {
|
|
78
78
|
"@dreamboard-games/sdk": DREAMBOARD_SDK_VERSION
|
|
79
79
|
};
|
package/dist/react.d.ts
CHANGED
|
@@ -7,6 +7,19 @@ import 'zod';
|
|
|
7
7
|
import 'honeycomb-grid';
|
|
8
8
|
import '@tanstack/store';
|
|
9
9
|
|
|
10
|
+
/** How the dragged card would show where it lands if dropped now. */
|
|
11
|
+
interface DragFit {
|
|
12
|
+
/**
|
|
13
|
+
* The width of the card-sized box marked `data-drop-landing` there,
|
|
14
|
+
* relative to the picked-up card's layout width. Null until the card has
|
|
15
|
+
* been over such a box; between areas it keeps the last one's.
|
|
16
|
+
*/
|
|
17
|
+
readonly scale: number | null;
|
|
18
|
+
/** Drawn in a pile's slot. */
|
|
19
|
+
readonly snapped: boolean;
|
|
20
|
+
/** The area's zone would conceal the card. */
|
|
21
|
+
readonly concealed: boolean;
|
|
22
|
+
}
|
|
10
23
|
interface CardGestureProps {
|
|
11
24
|
onPointerDown(event: PointerEvent<Element>): void;
|
|
12
25
|
onFocus(event: FocusEvent<Element>): void;
|
|
@@ -83,6 +96,12 @@ interface DragOverlay<G> {
|
|
|
83
96
|
readonly target: DropTarget<G> | null;
|
|
84
97
|
/** A registered zone landing; absent for interactions with no zone destination. */
|
|
85
98
|
readonly landing: PositionTarget<IdOf<G, "zoneId">, ZoneHostId<G>> | null;
|
|
99
|
+
/**
|
|
100
|
+
* Draw the copy at `fit.scale` once it is known, in place of its lifted
|
|
101
|
+
* size, and as its back while `fit.concealed`. The ref already moves it
|
|
102
|
+
* into a pile's slot while `fit.snapped`.
|
|
103
|
+
*/
|
|
104
|
+
readonly fit: DragFit;
|
|
86
105
|
/** Attach to a fixed-position copy of the card; it follows the pointer. */
|
|
87
106
|
readonly ref: (element: HTMLElement | null) => (() => void) | undefined;
|
|
88
107
|
}
|
|
@@ -155,4 +174,4 @@ declare function useMarqueeSelection<Id extends string>(options: {
|
|
|
155
174
|
};
|
|
156
175
|
};
|
|
157
176
|
|
|
158
|
-
export { type CardGesture, type CardGestureOptions, type CardGestureProps, type CardPresentation, type CardRow, type DragOverlay, type DropArea, DropAreaBinding, type DropAreaOptions, type MarqueeBounds, type SelectionOptions, type ZonePresentation, createGameHook, useMarqueeSelection };
|
|
177
|
+
export { type CardGesture, type CardGestureOptions, type CardGestureProps, type CardPresentation, type CardRow, type DragFit, type DragOverlay, type DropArea, DropAreaBinding, type DropAreaOptions, type MarqueeBounds, type SelectionOptions, type ZonePresentation, createGameHook, useMarqueeSelection };
|
package/dist/react.js
CHANGED
|
@@ -93,6 +93,165 @@ import {
|
|
|
93
93
|
createContext,
|
|
94
94
|
useContext
|
|
95
95
|
} from "react";
|
|
96
|
+
|
|
97
|
+
// src/headless/drop-snap.ts
|
|
98
|
+
var SNAP = Object.freeze({
|
|
99
|
+
/** A pile catches a card whose centre comes this close to its own. */
|
|
100
|
+
slotCatch: 0.75,
|
|
101
|
+
/** A caught card leaves the pile beyond this. */
|
|
102
|
+
slotRelease: 1.15,
|
|
103
|
+
/** A finger reaches this much further. */
|
|
104
|
+
coarseReach: 0.2,
|
|
105
|
+
/** Another pile takes a caught card only when this much closer. */
|
|
106
|
+
slotSwitch: 0.1,
|
|
107
|
+
/** A card enters a zone once this share of it covers the zone... */
|
|
108
|
+
zoneEnter: 0.35,
|
|
109
|
+
/** ...and leaves it once less than this share does. */
|
|
110
|
+
zoneLeave: 0.1,
|
|
111
|
+
/** How much a card in a pile still follows the pointer. */
|
|
112
|
+
slotGive: 0.15,
|
|
113
|
+
/** How much a card held inside a zone's edge follows the pointer beyond it. */
|
|
114
|
+
edgeGive: 0.25
|
|
115
|
+
});
|
|
116
|
+
var centerOf = (box) => ({
|
|
117
|
+
x: box.x + box.width / 2,
|
|
118
|
+
y: box.y + box.height / 2
|
|
119
|
+
});
|
|
120
|
+
var toward = (from, to, share) => ({
|
|
121
|
+
x: from.x + (to.x - from.x) * share,
|
|
122
|
+
y: from.y + (to.y - from.y) * share
|
|
123
|
+
});
|
|
124
|
+
var contains = (box, point) => point.x >= box.x && point.x <= box.x + box.width && point.y >= box.y && point.y <= box.y + box.height;
|
|
125
|
+
function cover(card, box) {
|
|
126
|
+
const width = Math.min(card.x + card.width, box.x + box.width) - Math.max(card.x, box.x);
|
|
127
|
+
const height = Math.min(card.y + card.height, box.y + box.height) - Math.max(card.y, box.y);
|
|
128
|
+
return width > 0 && height > 0 ? width * height / (card.width * card.height) : 0;
|
|
129
|
+
}
|
|
130
|
+
function snapDrop({
|
|
131
|
+
pointer,
|
|
132
|
+
card,
|
|
133
|
+
areas,
|
|
134
|
+
hit,
|
|
135
|
+
retained,
|
|
136
|
+
coarse
|
|
137
|
+
}) {
|
|
138
|
+
const free = centerOf(card);
|
|
139
|
+
const inSlot = (index, box) => ({
|
|
140
|
+
index,
|
|
141
|
+
center: toward(centerOf(box), free, SNAP.slotGive),
|
|
142
|
+
held: true
|
|
143
|
+
});
|
|
144
|
+
let best = -1;
|
|
145
|
+
let score = Infinity;
|
|
146
|
+
areas.forEach((area, index) => {
|
|
147
|
+
const box = area?.slot ? area.box : null;
|
|
148
|
+
if (!box?.width || !box.height) return;
|
|
149
|
+
const middle = centerOf(box);
|
|
150
|
+
const distance = Math.hypot(
|
|
151
|
+
(free.x - middle.x) / box.width,
|
|
152
|
+
(free.y - middle.y) / box.height
|
|
153
|
+
);
|
|
154
|
+
const kept2 = index === retained;
|
|
155
|
+
const reach = (kept2 ? SNAP.slotRelease : SNAP.slotCatch) + (coarse ? SNAP.coarseReach : 0);
|
|
156
|
+
const value = kept2 ? distance - SNAP.slotSwitch : distance;
|
|
157
|
+
if (distance <= reach && value < score) {
|
|
158
|
+
best = index;
|
|
159
|
+
score = value;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
if (best >= 0) return inSlot(best, areas[best].box);
|
|
163
|
+
const under = hit >= 0 ? areas[hit] : null;
|
|
164
|
+
if (under)
|
|
165
|
+
return under.slot && under.box?.width ? inSlot(hit, under.box) : { index: hit, center: free, held: false };
|
|
166
|
+
const hold = (index, box) => {
|
|
167
|
+
if (contains(box, pointer)) return { index, center: free, held: false };
|
|
168
|
+
const inside = {
|
|
169
|
+
x: Math.max(box.x, Math.min(free.x, box.x + box.width)),
|
|
170
|
+
y: Math.max(box.y, Math.min(free.y, box.y + box.height))
|
|
171
|
+
};
|
|
172
|
+
return { index, center: toward(inside, free, SNAP.edgeGive), held: true };
|
|
173
|
+
};
|
|
174
|
+
const zone = (index) => {
|
|
175
|
+
const area = areas[index];
|
|
176
|
+
return area && !area.slot && area.box?.width ? area.box : null;
|
|
177
|
+
};
|
|
178
|
+
const kept = retained >= 0 ? zone(retained) : null;
|
|
179
|
+
if (kept && (cover(card, kept) >= SNAP.zoneLeave || magneticDropPoint(pointer, kept, true, coarse)))
|
|
180
|
+
return hold(retained, kept);
|
|
181
|
+
let chosen = -1;
|
|
182
|
+
let share = 0;
|
|
183
|
+
areas.forEach((_, index) => {
|
|
184
|
+
const box = zone(index);
|
|
185
|
+
if (!box) return;
|
|
186
|
+
const value = cover(card, box);
|
|
187
|
+
if (value < SNAP.zoneEnter && !magneticDropPoint(pointer, box, false, coarse))
|
|
188
|
+
return;
|
|
189
|
+
const deeper = chosen < 0 || areas[index].depth > areas[chosen].depth;
|
|
190
|
+
if (deeper || areas[index].depth === areas[chosen].depth && value > share) {
|
|
191
|
+
chosen = index;
|
|
192
|
+
share = value;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
return chosen >= 0 ? hold(chosen, zone(chosen)) : { index: -1, center: free, held: false };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/react/gesture.tsx
|
|
199
|
+
var UNFIT = Object.freeze({
|
|
200
|
+
scale: null,
|
|
201
|
+
snapped: false,
|
|
202
|
+
concealed: false
|
|
203
|
+
});
|
|
204
|
+
var CATCH_UP_MS = 50;
|
|
205
|
+
var SCROLL_EDGE = { fine: 48, coarse: 64 };
|
|
206
|
+
var SCROLL_SPEED = 16;
|
|
207
|
+
var VISUALLY_HIDDEN = {
|
|
208
|
+
position: "fixed",
|
|
209
|
+
width: "1px",
|
|
210
|
+
height: "1px",
|
|
211
|
+
overflow: "hidden",
|
|
212
|
+
clipPath: "inset(50%)",
|
|
213
|
+
whiteSpace: "nowrap"
|
|
214
|
+
};
|
|
215
|
+
function landingIn(area) {
|
|
216
|
+
for (const element of area?.querySelectorAll("[data-drop-landing]") ?? [])
|
|
217
|
+
if (element instanceof HTMLElement && element.closest("[data-drop-area]") === area)
|
|
218
|
+
return element;
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
function visibleBox(element, clips) {
|
|
222
|
+
let { left, top, right, bottom } = element.getBoundingClientRect();
|
|
223
|
+
for (const clip of clips) {
|
|
224
|
+
const box = clip.getBoundingClientRect();
|
|
225
|
+
left = Math.max(left, box.left);
|
|
226
|
+
top = Math.max(top, box.top);
|
|
227
|
+
right = Math.min(right, box.right);
|
|
228
|
+
bottom = Math.min(bottom, box.bottom);
|
|
229
|
+
}
|
|
230
|
+
return right > left && bottom > top ? { x: left, y: top, width: right - left, height: bottom - top } : null;
|
|
231
|
+
}
|
|
232
|
+
function scrollStep(node, at, edge, frames) {
|
|
233
|
+
const root = node === document.scrollingElement;
|
|
234
|
+
const { overflowX, overflowY } = getComputedStyle(node);
|
|
235
|
+
const box = root ? { left: 0, top: 0, right: innerWidth, bottom: innerHeight } : node.getBoundingClientRect();
|
|
236
|
+
const speed = (gap) => gap < edge ? SCROLL_SPEED * frames * (1 - Math.max(0, gap) / edge) ** 2 : 0;
|
|
237
|
+
const axis = (overflow, position, room, before, after) => (root || overflow === "auto" || overflow === "scroll") && room > 0 ? (position < room ? speed(after) : 0) - (position > 0 ? speed(before) : 0) : 0;
|
|
238
|
+
return {
|
|
239
|
+
x: axis(
|
|
240
|
+
overflowX,
|
|
241
|
+
node.scrollLeft,
|
|
242
|
+
node.scrollWidth - node.clientWidth,
|
|
243
|
+
at.x - box.left,
|
|
244
|
+
box.right - at.x
|
|
245
|
+
),
|
|
246
|
+
y: axis(
|
|
247
|
+
overflowY,
|
|
248
|
+
node.scrollTop,
|
|
249
|
+
node.scrollHeight - node.clientHeight,
|
|
250
|
+
at.y - box.top,
|
|
251
|
+
box.bottom - at.y
|
|
252
|
+
)
|
|
253
|
+
};
|
|
254
|
+
}
|
|
96
255
|
var CARD_STYLE = Object.freeze({
|
|
97
256
|
// Sideways movement stays native scrolling; everything else reaches the recognizer.
|
|
98
257
|
touchAction: "pan-x",
|
|
@@ -152,6 +311,18 @@ function createGestureSession(game) {
|
|
|
152
311
|
let domFocused = null;
|
|
153
312
|
let alt = false;
|
|
154
313
|
let pointer = { x: 0, y: 0 };
|
|
314
|
+
let goal = { x: 0, y: 0 };
|
|
315
|
+
let offset = { x: 0, y: 0 };
|
|
316
|
+
let lag = { x: 0, y: 0 };
|
|
317
|
+
let shown = { x: 0, y: 0 };
|
|
318
|
+
let held = false;
|
|
319
|
+
let reduced = false;
|
|
320
|
+
let frame = 0;
|
|
321
|
+
let lastFrame = 0;
|
|
322
|
+
let remeasure = false;
|
|
323
|
+
const located = /* @__PURE__ */ new Map();
|
|
324
|
+
const engaged = /* @__PURE__ */ new Set();
|
|
325
|
+
let announcer = null;
|
|
155
326
|
let swallowing = false;
|
|
156
327
|
let guardingClicks = false;
|
|
157
328
|
let disposed = false;
|
|
@@ -209,57 +380,174 @@ function createGestureSession(game) {
|
|
|
209
380
|
if (drag?.settling && (game.request === null || changedSnapshot || changedLifetime))
|
|
210
381
|
set({ drag: null });
|
|
211
382
|
});
|
|
212
|
-
function
|
|
383
|
+
function hitAreas(at) {
|
|
213
384
|
for (const element of document.elementsFromPoint(at.x, at.y)) {
|
|
214
385
|
if (element.closest("[data-drag-overlay]")) continue;
|
|
215
|
-
const
|
|
216
|
-
|
|
386
|
+
const ids = [];
|
|
387
|
+
for (let area = element.closest("[data-drop-area]"); area; area = area.parentElement?.closest("[data-drop-area]") ?? null)
|
|
388
|
+
ids.push(area.getAttribute("data-drop-area"));
|
|
389
|
+
if (ids.length) return ids;
|
|
217
390
|
}
|
|
218
|
-
return
|
|
391
|
+
return [];
|
|
219
392
|
}
|
|
220
|
-
function
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
393
|
+
function locate(id2) {
|
|
394
|
+
const known = located.get(id2);
|
|
395
|
+
const element = known?.element?.isConnected ? known.element : document.querySelector(`[data-drop-area="${CSS.escape(id2)}"]`);
|
|
396
|
+
const landing = landingIn(element);
|
|
397
|
+
const slot = landing?.dataset.dropLanding === "slot" ? landing : null;
|
|
398
|
+
if (known && known.element === element && known.slot === slot) return known;
|
|
399
|
+
const clips = [];
|
|
400
|
+
for (let node = (slot ?? element)?.parentElement; node; node = node.parentElement) {
|
|
401
|
+
const { overflowX, overflowY } = getComputedStyle(node);
|
|
402
|
+
if (/auto|scroll|hidden|clip/.test(overflowX + overflowY))
|
|
403
|
+
clips.push(node);
|
|
404
|
+
}
|
|
405
|
+
let depth = 0;
|
|
406
|
+
for (let node = element?.parentElement; node; node = node.parentElement) {
|
|
407
|
+
if (node.hasAttribute("data-drop-area")) depth++;
|
|
408
|
+
}
|
|
409
|
+
const next = {
|
|
410
|
+
element,
|
|
411
|
+
clips,
|
|
412
|
+
slot,
|
|
413
|
+
depth
|
|
414
|
+
};
|
|
415
|
+
located.set(id2, next);
|
|
416
|
+
return next;
|
|
417
|
+
}
|
|
418
|
+
function destinationOf(area, target) {
|
|
419
|
+
const areaPresentation = area ? presentations.get(area)?.() : void 0;
|
|
420
|
+
const position = target?.kind === "position" ? target.value : null;
|
|
421
|
+
const zone = position ?? areaPresentation?.zone;
|
|
422
|
+
const presentation = zone ? [
|
|
423
|
+
areaPresentation,
|
|
424
|
+
...Array.from(presentations.values(), (read) => read())
|
|
425
|
+
].find(
|
|
426
|
+
(candidate) => candidate?.zone.zoneId === zone.zoneId && candidate.zone.hostId === zone.hostId
|
|
427
|
+
) : void 0;
|
|
428
|
+
return { zone, position, presentation };
|
|
429
|
+
}
|
|
430
|
+
const conceals = (presentation, hostId) => presentation.visibility === "hidden" || presentation.visibility === "ownerOnly" && hostId !== game.me?.id;
|
|
431
|
+
function announce(area) {
|
|
432
|
+
const element = area === null ? null : locate(area).element;
|
|
433
|
+
const label = element?.getAttribute("aria-label") ?? element?.getAttribute("aria-labelledby")?.split(/\s+/).map((labelId) => document.getElementById(labelId)?.textContent).join(" ") ?? "";
|
|
434
|
+
if (!announcer?.isConnected) {
|
|
435
|
+
announcer = document.createElement("div");
|
|
436
|
+
announcer.setAttribute("role", "status");
|
|
437
|
+
Object.assign(announcer.style, VISUALLY_HIDDEN);
|
|
438
|
+
document.body.append(announcer);
|
|
254
439
|
}
|
|
255
|
-
|
|
440
|
+
announcer.textContent = label;
|
|
441
|
+
}
|
|
442
|
+
function retarget(at) {
|
|
443
|
+
const drag = store.get().drag;
|
|
444
|
+
if (!press?.dragging || !drag) return;
|
|
445
|
+
pointer = at;
|
|
446
|
+
const ids = [...areas.keys()];
|
|
447
|
+
const found = ids.map((id2) => {
|
|
448
|
+
const target2 = resolveDropArea(game.drag, areas.get(id2)(at));
|
|
449
|
+
if (!target2) return null;
|
|
450
|
+
const { element, clips, slot, depth } = locate(id2);
|
|
451
|
+
const box = element instanceof HTMLElement ? visibleBox(slot ?? element, clips) : null;
|
|
452
|
+
return { target: target2, box, slot: slot !== null, depth };
|
|
453
|
+
});
|
|
454
|
+
const pickup = drag.width > 0 && drag.size.width > 0 ? drag.size.width / drag.width : 1;
|
|
455
|
+
const ratio = drag.fit.scale === null ? 1 : drag.fit.scale / pickup;
|
|
456
|
+
const width = drag.size.width * ratio;
|
|
457
|
+
const height = drag.size.height * ratio;
|
|
458
|
+
const free = {
|
|
459
|
+
x: at.x - (drag.grab.x - drag.size.width / 2) * ratio,
|
|
460
|
+
y: at.y - (drag.grab.y - drag.size.height / 2) * ratio
|
|
461
|
+
};
|
|
462
|
+
const snap = snapDrop({
|
|
463
|
+
pointer: at,
|
|
464
|
+
card: { x: free.x - width / 2, y: free.y - height / 2, width, height },
|
|
465
|
+
areas: found,
|
|
466
|
+
hit: ids.indexOf(hitAreas(at).find((id2) => found[ids.indexOf(id2)]) ?? ""),
|
|
467
|
+
retained: press.area === null ? -1 : ids.indexOf(press.area),
|
|
468
|
+
coarse: press.pointerType === "touch"
|
|
469
|
+
});
|
|
470
|
+
const area = snap.index < 0 ? null : ids[snap.index];
|
|
471
|
+
const target = snap.index < 0 ? null : found[snap.index].target;
|
|
472
|
+
const changed = area !== press.area || !sameDropTarget(target, press.target);
|
|
473
|
+
if (changed) {
|
|
256
474
|
press.area = area;
|
|
257
475
|
press.target = target;
|
|
258
476
|
game.drag?.setDropTarget(target);
|
|
259
|
-
|
|
260
|
-
|
|
477
|
+
announce(area);
|
|
478
|
+
remeasure = true;
|
|
261
479
|
}
|
|
262
|
-
|
|
480
|
+
const { zone, presentation } = destinationOf(area, target);
|
|
481
|
+
const place = area === null ? null : locate(area);
|
|
482
|
+
const landing = place && (place.slot ?? landingIn(place.element));
|
|
483
|
+
const fit = {
|
|
484
|
+
scale: landing && drag.width > 0 ? landing.offsetWidth / drag.width : drag.fit.scale,
|
|
485
|
+
snapped: snap.held && !!found[snap.index]?.slot,
|
|
486
|
+
concealed: !!presentation && !!zone && conceals(presentation, zone.hostId)
|
|
487
|
+
};
|
|
488
|
+
const refit = Math.abs((fit.scale ?? 0) - (drag.fit.scale ?? 0)) > 0.01 || fit.snapped !== drag.fit.snapped || fit.concealed !== drag.fit.concealed;
|
|
489
|
+
if (changed || refit)
|
|
490
|
+
set({
|
|
491
|
+
drag: {
|
|
492
|
+
...drag,
|
|
493
|
+
area,
|
|
494
|
+
target,
|
|
495
|
+
fit: refit ? fit : drag.fit
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
draw(snap.center, changed || refit || snap.held !== held);
|
|
499
|
+
held = snap.held;
|
|
500
|
+
schedule();
|
|
501
|
+
}
|
|
502
|
+
function draw(next, jump) {
|
|
503
|
+
const nextOffset = { x: next.x - pointer.x, y: next.y - pointer.y };
|
|
504
|
+
if (jump && !reduced)
|
|
505
|
+
lag = {
|
|
506
|
+
x: lag.x + offset.x - nextOffset.x,
|
|
507
|
+
y: lag.y + offset.y - nextOffset.y
|
|
508
|
+
};
|
|
509
|
+
offset = nextOffset;
|
|
510
|
+
goal = next;
|
|
511
|
+
show();
|
|
512
|
+
if (lag.x || lag.y) schedule();
|
|
513
|
+
}
|
|
514
|
+
function show() {
|
|
515
|
+
shown = { x: goal.x + lag.x, y: goal.y + lag.y };
|
|
516
|
+
for (const follow of followers) follow(shown);
|
|
517
|
+
}
|
|
518
|
+
function schedule() {
|
|
519
|
+
if (frame || disposed) return;
|
|
520
|
+
lastFrame = performance.now();
|
|
521
|
+
frame = requestAnimationFrame(tick);
|
|
522
|
+
}
|
|
523
|
+
function tick(now) {
|
|
524
|
+
frame = 0;
|
|
525
|
+
if (!store.get().drag) return;
|
|
526
|
+
const elapsed = Math.min(64, Math.max(0, now - lastFrame));
|
|
527
|
+
const decay = Math.exp(-elapsed / CATCH_UP_MS);
|
|
528
|
+
lag = Math.hypot(lag.x, lag.y) * decay < 0.5 ? { x: 0, y: 0 } : { x: lag.x * decay, y: lag.y * decay };
|
|
529
|
+
const scrolling = press?.dragging ? autoScroll(elapsed / 16.7) : false;
|
|
530
|
+
if (scrolling || remeasure) {
|
|
531
|
+
remeasure = false;
|
|
532
|
+
retarget(pointer);
|
|
533
|
+
} else show();
|
|
534
|
+
if (lag.x || lag.y || scrolling) schedule();
|
|
535
|
+
}
|
|
536
|
+
function autoScroll(frames) {
|
|
537
|
+
const edge = SCROLL_EDGE[press?.pointerType === "touch" ? "coarse" : "fine"];
|
|
538
|
+
const top = document.elementsFromPoint(pointer.x, pointer.y).find((element) => !element.closest("[data-drag-overlay]"));
|
|
539
|
+
for (let node = top ?? null; node; node = node.parentElement) {
|
|
540
|
+
const step = scrollStep(node, pointer, edge, frames);
|
|
541
|
+
if (!step.x && !step.y) {
|
|
542
|
+
if (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth)
|
|
543
|
+
engaged.add(node);
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (!engaged.has(node)) return false;
|
|
547
|
+
node.scrollBy(step.x, step.y);
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
return false;
|
|
263
551
|
}
|
|
264
552
|
function swallow(event) {
|
|
265
553
|
if (!swallowing || event.detail === 0) return;
|
|
@@ -386,12 +674,26 @@ function createGestureSession(game) {
|
|
|
386
674
|
clearHover();
|
|
387
675
|
focused = null;
|
|
388
676
|
press.dragging = true;
|
|
677
|
+
located.clear();
|
|
678
|
+
engaged.clear();
|
|
679
|
+
if (announcer) announcer.textContent = "";
|
|
680
|
+
reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
681
|
+
held = false;
|
|
682
|
+
remeasure = false;
|
|
683
|
+
lag = { x: 0, y: 0 };
|
|
684
|
+
goal = shown = {
|
|
685
|
+
x: box.left + box.width / 2,
|
|
686
|
+
y: box.top + box.height / 2
|
|
687
|
+
};
|
|
688
|
+
offset = { x: goal.x - origin.x, y: goal.y - origin.y };
|
|
389
689
|
set({
|
|
390
690
|
drag: {
|
|
391
691
|
cardIds: game.drag?.active?.cardIds ?? [cardId2],
|
|
392
692
|
cardId: cardId2,
|
|
393
693
|
grab: { x: origin.x - box.left, y: origin.y - box.top },
|
|
394
694
|
size: { width: box.width, height: box.height },
|
|
695
|
+
width: element2 instanceof HTMLElement ? element2.offsetWidth : 0,
|
|
696
|
+
fit: UNFIT,
|
|
395
697
|
settling: false,
|
|
396
698
|
area: null,
|
|
397
699
|
target: null,
|
|
@@ -402,10 +704,7 @@ function createGestureSession(game) {
|
|
|
402
704
|
});
|
|
403
705
|
return true;
|
|
404
706
|
},
|
|
405
|
-
dragMove
|
|
406
|
-
pointer = retarget(at);
|
|
407
|
-
for (const follow of followers) follow(pointer);
|
|
408
|
-
},
|
|
707
|
+
dragMove: retarget,
|
|
409
708
|
browse: scrubTo,
|
|
410
709
|
end(kind, at) {
|
|
411
710
|
const dragging = press.dragging;
|
|
@@ -423,15 +722,10 @@ function createGestureSession(game) {
|
|
|
423
722
|
}
|
|
424
723
|
const before = game.request;
|
|
425
724
|
const drag = store.get().drag;
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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;
|
|
725
|
+
const { zone, position, presentation } = destinationOf(
|
|
726
|
+
drag?.area ?? null,
|
|
727
|
+
drag?.target ?? null
|
|
728
|
+
);
|
|
435
729
|
const destination = zone && game.zones.find(zone.zoneId, zone.hostId);
|
|
436
730
|
const cards = drag?.cardIds.flatMap((id2) => {
|
|
437
731
|
const card2 = game.cards.find(id2);
|
|
@@ -441,7 +735,7 @@ function createGestureSession(game) {
|
|
|
441
735
|
zoneId: zone.zoneId,
|
|
442
736
|
hostId: zone.hostId,
|
|
443
737
|
index: position?.index ?? presentation?.index ?? destination.getCards().length,
|
|
444
|
-
concealed: presentation ? presentation
|
|
738
|
+
concealed: presentation ? conceals(presentation, zone.hostId) : cards.some(
|
|
445
739
|
(card2) => card2.zone !== zone.zoneId || card2.hostId !== zone.hostId || card2.hidden
|
|
446
740
|
),
|
|
447
741
|
cards: Object.freeze(cards),
|
|
@@ -542,11 +836,11 @@ function createGestureSession(game) {
|
|
|
542
836
|
width: px(drag.size.width),
|
|
543
837
|
height: px(drag.size.height)
|
|
544
838
|
});
|
|
545
|
-
const follow = (
|
|
546
|
-
element.style.left = px(
|
|
547
|
-
element.style.top = px(
|
|
839
|
+
const follow = (center) => {
|
|
840
|
+
element.style.left = px(center.x - drag.size.width / 2);
|
|
841
|
+
element.style.top = px(center.y - drag.size.height / 2);
|
|
548
842
|
};
|
|
549
|
-
follow(
|
|
843
|
+
follow(shown);
|
|
550
844
|
followers.add(follow);
|
|
551
845
|
return () => {
|
|
552
846
|
followers.delete(follow);
|
|
@@ -630,6 +924,8 @@ function createGestureSession(game) {
|
|
|
630
924
|
dispose() {
|
|
631
925
|
disposed = true;
|
|
632
926
|
press?.recognizer.cancel();
|
|
927
|
+
cancelAnimationFrame(frame);
|
|
928
|
+
announcer?.remove();
|
|
633
929
|
clearHover();
|
|
634
930
|
removeEventListener("keydown", key);
|
|
635
931
|
removeEventListener("keyup", key);
|
|
@@ -990,6 +1286,7 @@ function createGameHook() {
|
|
|
990
1286
|
hostId: drag.landing.hostId,
|
|
991
1287
|
index: drag.landing.index
|
|
992
1288
|
} : null,
|
|
1289
|
+
fit: drag.fit,
|
|
993
1290
|
ref: session.overlayRef
|
|
994
1291
|
} : null;
|
|
995
1292
|
}
|