@dreamboard-games/sdk 0.5.0-alpha.3 → 0.5.0-alpha.4
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/README.md +52 -2
- package/dist/board-target-B0QG6OYx.d.ts +8 -0
- package/dist/chunk-3JXUL4VN.js +139 -0
- package/dist/chunk-3JXUL4VN.js.map +1 -0
- package/dist/{chunk-LRILEVKC.js → chunk-C77SYIVT.js} +82 -109
- package/dist/chunk-C77SYIVT.js.map +1 -0
- package/dist/{chunk-SP234IY4.js → chunk-OOFMBCFF.js} +178 -203
- package/dist/chunk-OOFMBCFF.js.map +1 -0
- package/dist/{chunk-ZPRKWPEP.js → chunk-UG2Q4NLG.js} +108 -29
- package/dist/chunk-UG2Q4NLG.js.map +1 -0
- package/dist/{chunk-FMM5LVJW.js → chunk-XU6WJW6K.js} +26 -12
- package/dist/chunk-XU6WJW6K.js.map +1 -0
- package/dist/{diagnostics-B0lgnuZ6.d.ts → diagnostics-B8d5aLad.d.ts} +2 -2
- package/dist/{definition-BaHNPZol.d.ts → extract-gVKcB8Og.d.ts} +484 -387
- package/dist/index.d.ts +404 -175
- package/dist/index.js +266 -215
- package/dist/index.js.map +1 -1
- package/dist/model-vdpLHrxh.d.ts +432 -0
- package/dist/react.d.ts +35 -7
- package/dist/react.js +136 -8
- package/dist/react.js.map +1 -1
- package/dist/reducer.d.ts +169 -118
- package/dist/reducer.js +206 -328
- package/dist/reducer.js.map +1 -1
- package/dist/testing.d.ts +5 -3
- package/dist/testing.js +9 -6
- package/dist/testing.js.map +1 -1
- package/dist/{types-DimqnJBq.d.ts → types-Bj5ZLTUV.d.ts} +38 -10
- package/package.json +19 -16
- package/dist/chunk-FMM5LVJW.js.map +0 -1
- package/dist/chunk-LRILEVKC.js.map +0 -1
- package/dist/chunk-SP234IY4.js.map +0 -1
- package/dist/chunk-VUWNJXLJ.js +0 -52
- package/dist/chunk-VUWNJXLJ.js.map +0 -1
- package/dist/chunk-ZPRKWPEP.js.map +0 -1
- package/dist/model-BHro6yXu.d.ts +0 -288
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AmbiguousTargetError,
|
|
3
3
|
createGameInstance
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-OOFMBCFF.js";
|
|
5
5
|
import {
|
|
6
6
|
BoardStaticProjectionSchema,
|
|
7
7
|
CancelInteractionCommandSchema,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
parsePluginSessionDescriptor,
|
|
37
37
|
parsePluginToHostEnvelope,
|
|
38
38
|
staticSource
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-UG2Q4NLG.js";
|
|
40
40
|
import "./chunk-DMFVAZI2.js";
|
|
41
41
|
import {
|
|
42
42
|
InputDomainSchema,
|
|
@@ -49,11 +49,14 @@ import {
|
|
|
49
49
|
createHexBoardGeometry,
|
|
50
50
|
digestPluginRuntimeJson,
|
|
51
51
|
encodeCanonicalPluginRuntimeJson
|
|
52
|
-
} from "./chunk-
|
|
53
|
-
import
|
|
52
|
+
} from "./chunk-XU6WJW6K.js";
|
|
53
|
+
import {
|
|
54
|
+
inputValueKey,
|
|
55
|
+
requireLookup
|
|
56
|
+
} from "./chunk-3JXUL4VN.js";
|
|
54
57
|
|
|
55
58
|
// src/package-set.ts
|
|
56
|
-
var DREAMBOARD_SDK_VERSION = true ? "0.5.0-alpha.
|
|
59
|
+
var DREAMBOARD_SDK_VERSION = true ? "0.5.0-alpha.4" : "0.0.0-development";
|
|
57
60
|
var DREAMBOARD_SDK_PACKAGES = {
|
|
58
61
|
"@dreamboard-games/sdk": DREAMBOARD_SDK_VERSION
|
|
59
62
|
};
|
|
@@ -89,6 +92,10 @@ function iframeSource(options = {}) {
|
|
|
89
92
|
recover: () => send({ type: "runtime.resume" }),
|
|
90
93
|
close: () => target.removeEventListener("message", receive)
|
|
91
94
|
});
|
|
95
|
+
const isOpen = () => {
|
|
96
|
+
const { connection } = lifecycle.source.store.get();
|
|
97
|
+
return connection !== "closed" && connection !== "failed";
|
|
98
|
+
};
|
|
92
99
|
function receive(event) {
|
|
93
100
|
if (event.source !== parent) return;
|
|
94
101
|
const parsed = HostToPluginEnvelopeSchema.safeParse(event.data);
|
|
@@ -118,13 +125,11 @@ function iframeSource(options = {}) {
|
|
|
118
125
|
const payload = envelope.payload;
|
|
119
126
|
if (payload.type === "runtime.init") {
|
|
120
127
|
lifecycle.session(payload.session);
|
|
121
|
-
if (
|
|
122
|
-
send({ type: "runtime.ready" });
|
|
128
|
+
if (isOpen()) send({ type: "runtime.ready" });
|
|
123
129
|
} else if (payload.type === "gameplay.frame")
|
|
124
130
|
lifecycle.frame(payload.frame);
|
|
125
131
|
else lifecycle.result(payload);
|
|
126
|
-
if (
|
|
127
|
-
send({ type: "runtime.ack", sequence: envelope.sequence });
|
|
132
|
+
if (isOpen()) send({ type: "runtime.ack", sequence: envelope.sequence });
|
|
128
133
|
}
|
|
129
134
|
target.addEventListener("message", receive);
|
|
130
135
|
return lifecycle.source;
|
|
@@ -484,7 +489,10 @@ function boardFeature(game, context) {
|
|
|
484
489
|
return cached.collection;
|
|
485
490
|
const projected = model.view;
|
|
486
491
|
const boards = Object.values(projected?.boards?.byId ?? {}).map((data) => {
|
|
487
|
-
const board = context.createBoard(
|
|
492
|
+
const board = context.createBoard(
|
|
493
|
+
data.id,
|
|
494
|
+
data
|
|
495
|
+
);
|
|
488
496
|
let geometry = geometries.get(data);
|
|
489
497
|
if (!geometry && data.layout === "hex") {
|
|
490
498
|
geometry = createHexBoardGeometry({
|
|
@@ -500,6 +508,9 @@ function boardFeature(game, context) {
|
|
|
500
508
|
const byId = new Map(boards.map((board) => [board.id, board]));
|
|
501
509
|
const result = Object.freeze({
|
|
502
510
|
get(id) {
|
|
511
|
+
return requireLookup(byId.get(id), "Board", id);
|
|
512
|
+
},
|
|
513
|
+
find(id) {
|
|
503
514
|
return byId.get(id);
|
|
504
515
|
},
|
|
505
516
|
getAll: () => Object.freeze(boards)
|
|
@@ -512,6 +523,115 @@ function boardFeature(game, context) {
|
|
|
512
523
|
};
|
|
513
524
|
return result;
|
|
514
525
|
}
|
|
526
|
+
function targetsFor(owner) {
|
|
527
|
+
const board = owner.data;
|
|
528
|
+
const captured = captures.get(owner);
|
|
529
|
+
function matchingTargets(model, kind, id) {
|
|
530
|
+
const inputKinds = kind === "space" ? ["space", "tile"] : [kind];
|
|
531
|
+
return model.interactions.list().flatMap(
|
|
532
|
+
(interaction) => interaction.getInputs().filter((input) => {
|
|
533
|
+
const domain = input.getDomain();
|
|
534
|
+
return domain.type === "boardTarget" && inputKinds.includes(String(domain.targetKind)) && (domain.valueKind === "player-board-space" ? board.scope === "perPlayer" && domain.boardId === board.baseId : domain.boardId === board.id);
|
|
535
|
+
}).map((input) => {
|
|
536
|
+
const domain = input.getDomain();
|
|
537
|
+
return {
|
|
538
|
+
interaction,
|
|
539
|
+
input,
|
|
540
|
+
value: domain.type === "boardTarget" && domain.valueKind === "player-board-space" ? Object.freeze({
|
|
541
|
+
boardId: board.baseId,
|
|
542
|
+
playerId: board.playerId,
|
|
543
|
+
spaceId: id
|
|
544
|
+
}) : id
|
|
545
|
+
};
|
|
546
|
+
})
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
function target(kind, id) {
|
|
550
|
+
const matches = matchingTargets(captured.model, kind, id);
|
|
551
|
+
const eligible = matches.some(
|
|
552
|
+
({ input, value }) => input.getIsEligible(value)
|
|
553
|
+
);
|
|
554
|
+
const selected = matches.some(
|
|
555
|
+
({ input, value }) => input.getIsSelected(value)
|
|
556
|
+
);
|
|
557
|
+
const selectable = matches.some(
|
|
558
|
+
({ input, value }) => !input.getTargetProps(value).disabled
|
|
559
|
+
);
|
|
560
|
+
function select(options) {
|
|
561
|
+
if (game.getOptions().source !== captured.source || game.snapshot?.me !== captured.model.snapshot?.me)
|
|
562
|
+
return;
|
|
563
|
+
const matching = matchingTargets(game.getSnapshot(), kind, id).filter(
|
|
564
|
+
({ interaction, input, value: value2 }) => (!options?.interaction || options.interaction === interaction.key) && (!options?.input || options.input === input.key) && !input.getTargetProps(value2).disabled
|
|
565
|
+
);
|
|
566
|
+
if (!matching.length) return;
|
|
567
|
+
if (matching.length > 1) throw new AmbiguousTargetError(id);
|
|
568
|
+
const domain = matching[0].input.getDomain();
|
|
569
|
+
if (domain.type !== "boardTarget") return;
|
|
570
|
+
const value = matching[0].value;
|
|
571
|
+
const target2 = domain.valueKind === "player-board-space" && typeof value !== "string" ? { kind: "space", valueKind: domain.valueKind, value } : {
|
|
572
|
+
kind,
|
|
573
|
+
valueKind: "board-id",
|
|
574
|
+
value: id,
|
|
575
|
+
boardId: board.id
|
|
576
|
+
};
|
|
577
|
+
context.routeTarget(target2, {
|
|
578
|
+
interaction: matching[0].interaction.key,
|
|
579
|
+
input: matching[0].input.key
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
return {
|
|
583
|
+
id,
|
|
584
|
+
getIsEligible: () => eligible,
|
|
585
|
+
getIsSelectable: () => selectable,
|
|
586
|
+
getIsSelected: () => selected,
|
|
587
|
+
getSelectHandler: (options) => () => select(options),
|
|
588
|
+
getTargetProps(options) {
|
|
589
|
+
const candidates = matches.filter(
|
|
590
|
+
({ interaction, input, value }) => (!options?.interaction || interaction.key === options.interaction) && (!options?.input || options.input === input.key) && !input.getTargetProps(value).disabled
|
|
591
|
+
);
|
|
592
|
+
const only = candidates.length === 1 ? candidates[0] : void 0;
|
|
593
|
+
return {
|
|
594
|
+
type: "button",
|
|
595
|
+
disabled: candidates.length === 0,
|
|
596
|
+
"data-action": "select",
|
|
597
|
+
"data-value": only && typeof only.value !== "string" ? JSON.stringify(only.value) : id,
|
|
598
|
+
"data-board": board.id,
|
|
599
|
+
"data-interaction": options?.interaction ?? only?.interaction.key,
|
|
600
|
+
"data-input": only?.input.key,
|
|
601
|
+
"data-eligible": eligible,
|
|
602
|
+
"data-selected": selected,
|
|
603
|
+
"data-disabled": candidates.length === 0,
|
|
604
|
+
onClick: () => select(options)
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return target;
|
|
610
|
+
}
|
|
611
|
+
const spaceCollections = /* @__PURE__ */ new WeakMap();
|
|
612
|
+
function spacesFor(owner) {
|
|
613
|
+
const cached2 = spaceCollections.get(owner);
|
|
614
|
+
if (cached2) return cached2;
|
|
615
|
+
const target = targetsFor(owner);
|
|
616
|
+
const data = owner.data;
|
|
617
|
+
const values = Object.freeze(
|
|
618
|
+
Object.values(data.spaces).map(
|
|
619
|
+
(space) => Object.freeze({
|
|
620
|
+
...target("space", space.id),
|
|
621
|
+
data: space,
|
|
622
|
+
board: owner
|
|
623
|
+
})
|
|
624
|
+
)
|
|
625
|
+
);
|
|
626
|
+
const byId = new Map(values.map((space) => [space.id, space]));
|
|
627
|
+
const spaces = Object.freeze({
|
|
628
|
+
get: (id) => requireLookup(byId.get(id), `Space on board ${owner.id}`, id),
|
|
629
|
+
find: (id) => byId.get(id),
|
|
630
|
+
getAll: () => values
|
|
631
|
+
});
|
|
632
|
+
spaceCollections.set(owner, spaces);
|
|
633
|
+
return spaces;
|
|
634
|
+
}
|
|
515
635
|
return {
|
|
516
636
|
root: {
|
|
517
637
|
get boards() {
|
|
@@ -519,11 +639,14 @@ function boardFeature(game, context) {
|
|
|
519
639
|
}
|
|
520
640
|
},
|
|
521
641
|
board: {
|
|
642
|
+
get spaces() {
|
|
643
|
+
return spacesFor(this);
|
|
644
|
+
},
|
|
522
645
|
getLayout(options) {
|
|
523
646
|
const board = this.data;
|
|
524
647
|
if (board.layout === "generic")
|
|
525
648
|
throw new Error(
|
|
526
|
-
`Board '${board.id}' has no spatial geometry;
|
|
649
|
+
`Board '${board.id}' has no spatial geometry; use board.spaces for semantic selection.`
|
|
527
650
|
);
|
|
528
651
|
const {
|
|
529
652
|
hexSize,
|
|
@@ -546,70 +669,16 @@ function boardFeature(game, context) {
|
|
|
546
669
|
x: value.x * viewport.scale + viewport.x,
|
|
547
670
|
y: value.y * viewport.scale + viewport.y
|
|
548
671
|
});
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
return model.interactions.list().flatMap(
|
|
552
|
-
(interaction) => interaction.getInputs().filter((input) => {
|
|
553
|
-
const domain = input.getDomain();
|
|
554
|
-
return domain.type === "boardTarget" && inputKinds.includes(String(domain.targetKind)) && (domain.boardId === board.id || domain.boardId === board.baseId) && (board.scope !== "perPlayer" || board.playerId === model.me?.id);
|
|
555
|
-
}).map((input) => ({ interaction, input }))
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
function target(kind, id) {
|
|
559
|
-
const matches = matchingTargets(captured.model, kind);
|
|
560
|
-
const eligible = matches.some(({ input }) => input.getIsEligible(id));
|
|
561
|
-
const selected = matches.some(({ input }) => input.getIsSelected(id));
|
|
562
|
-
const selectable = matches.some(
|
|
563
|
-
({ input }) => !input.getTargetProps(id).disabled
|
|
564
|
-
);
|
|
565
|
-
function select(options2) {
|
|
566
|
-
if (game.getOptions().source !== captured.source || game.snapshot?.me !== captured.model.snapshot?.me)
|
|
567
|
-
return;
|
|
568
|
-
const matching = matchingTargets(game.getSnapshot(), kind).filter(
|
|
569
|
-
({ interaction, input }) => (!options2?.interaction || options2.interaction === interaction.key) && !input.getTargetProps(id).disabled
|
|
570
|
-
);
|
|
571
|
-
if (!matching.length) return;
|
|
572
|
-
const domain = matching[0].input.getDomain();
|
|
573
|
-
if (domain.type !== "boardTarget") return;
|
|
574
|
-
context.routeTarget(kind, id, {
|
|
575
|
-
...options2,
|
|
576
|
-
boardId: String(domain.boardId)
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
return {
|
|
580
|
-
id,
|
|
581
|
-
getIsEligible: () => eligible,
|
|
582
|
-
getIsSelectable: () => selectable,
|
|
583
|
-
getIsSelected: () => selected,
|
|
584
|
-
getSelectHandler: (options2) => () => select(options2),
|
|
585
|
-
getTargetProps(options2) {
|
|
586
|
-
const candidates = matches.filter(
|
|
587
|
-
({ interaction, input }) => (!options2?.interaction || interaction.key === options2.interaction) && !input.getTargetProps(id).disabled
|
|
588
|
-
);
|
|
589
|
-
const only = candidates.length === 1 ? candidates[0] : void 0;
|
|
590
|
-
return {
|
|
591
|
-
type: "button",
|
|
592
|
-
disabled: candidates.length === 0,
|
|
593
|
-
"data-action": "select",
|
|
594
|
-
"data-value": id,
|
|
595
|
-
"data-board": board.id,
|
|
596
|
-
"data-interaction": options2?.interaction ?? only?.interaction.key,
|
|
597
|
-
"data-input": only?.input.key,
|
|
598
|
-
"data-eligible": eligible,
|
|
599
|
-
"data-selected": selected,
|
|
600
|
-
"data-disabled": candidates.length === 0,
|
|
601
|
-
onClick: () => select(options2)
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
|
-
}
|
|
672
|
+
const target = targetsFor(this);
|
|
673
|
+
const semanticSpaces = spacesFor(this);
|
|
606
674
|
const spaces = Object.freeze(
|
|
607
675
|
geometry.spaces.map((space) => {
|
|
608
676
|
const corners = Object.freeze(space.corners.map(point));
|
|
609
677
|
const center = point(space.center);
|
|
610
678
|
return Object.freeze({
|
|
611
|
-
...
|
|
612
|
-
|
|
679
|
+
...semanticSpaces.get(
|
|
680
|
+
space.id
|
|
681
|
+
),
|
|
613
682
|
center,
|
|
614
683
|
points: () => corners,
|
|
615
684
|
transform: `translate(${center.x} ${center.y})`
|
|
@@ -655,6 +724,133 @@ function boardFeature(game, context) {
|
|
|
655
724
|
};
|
|
656
725
|
}
|
|
657
726
|
|
|
727
|
+
// src/headless/features/drag.ts
|
|
728
|
+
function dragFeature(game, context) {
|
|
729
|
+
let active = null;
|
|
730
|
+
let selection;
|
|
731
|
+
let source = game.getOptions().source;
|
|
732
|
+
let seat = game.snapshot?.me;
|
|
733
|
+
let disposed = false;
|
|
734
|
+
let version = game.snapshot?.version;
|
|
735
|
+
let lastInteractions = game.interactions;
|
|
736
|
+
let lastCards = game.cards;
|
|
737
|
+
let branch = snapshot();
|
|
738
|
+
function update(next) {
|
|
739
|
+
if (disposed) return;
|
|
740
|
+
active = next === null ? null : Object.freeze(next);
|
|
741
|
+
branch = snapshot();
|
|
742
|
+
context.invalidate();
|
|
743
|
+
}
|
|
744
|
+
function targets(cardId = active?.cardId, selected = selection) {
|
|
745
|
+
if (!cardId || game.connection !== "ready") return [];
|
|
746
|
+
const runtime = game;
|
|
747
|
+
const options = selected;
|
|
748
|
+
const resolved = (runtime.cards.find(cardId)?.getInteractions() ?? []).flatMap((candidate) => {
|
|
749
|
+
if (!candidate.getIsAvailable() || options?.interaction && candidate.key !== options.interaction)
|
|
750
|
+
return [];
|
|
751
|
+
const inputs = candidate.getInputs();
|
|
752
|
+
const cardInputs = inputs.filter(
|
|
753
|
+
(input) => input.getDomain().type === "cardTarget" && (!options?.input || input.key === options.input) && input.getIsEligible(cardId)
|
|
754
|
+
);
|
|
755
|
+
return cardInputs.flatMap(
|
|
756
|
+
(cardInput) => inputs.flatMap((input) => {
|
|
757
|
+
const domain = input.getDomain();
|
|
758
|
+
if (domain.type !== "boardTarget") return [];
|
|
759
|
+
const route = {
|
|
760
|
+
interactionKey: candidate.key,
|
|
761
|
+
cardInputKey: cardInput.key,
|
|
762
|
+
inputKey: input.key
|
|
763
|
+
};
|
|
764
|
+
if (domain.valueKind === "player-board-space")
|
|
765
|
+
return domain.eligibleTargets.filter((value) => !input.getTargetProps(value).disabled).map(
|
|
766
|
+
(value) => Object.freeze({
|
|
767
|
+
kind: "space",
|
|
768
|
+
valueKind: domain.valueKind,
|
|
769
|
+
value,
|
|
770
|
+
...route
|
|
771
|
+
})
|
|
772
|
+
);
|
|
773
|
+
return domain.eligibleTargets.filter((value) => !input.getTargetProps(value).disabled).map(
|
|
774
|
+
(value) => Object.freeze({
|
|
775
|
+
kind: domain.targetKind,
|
|
776
|
+
valueKind: domain.valueKind,
|
|
777
|
+
value,
|
|
778
|
+
boardId: domain.boardId,
|
|
779
|
+
...route
|
|
780
|
+
})
|
|
781
|
+
);
|
|
782
|
+
})
|
|
783
|
+
);
|
|
784
|
+
});
|
|
785
|
+
return resolved;
|
|
786
|
+
}
|
|
787
|
+
function sameTarget(left, right) {
|
|
788
|
+
return left.kind === right.kind && left.valueKind === right.valueKind && inputValueKey(left.value) === inputValueKey(right.value) && (left.valueKind !== "board-id" || right.valueKind === "board-id" && left.boardId === right.boardId) && left.interactionKey === right.interactionKey && left.cardInputKey === right.cardInputKey && left.inputKey === right.inputKey;
|
|
789
|
+
}
|
|
790
|
+
function cancel() {
|
|
791
|
+
if (active) update(null);
|
|
792
|
+
}
|
|
793
|
+
function snapshot() {
|
|
794
|
+
const captured = active;
|
|
795
|
+
const dropTargets = Object.freeze(targets());
|
|
796
|
+
return Object.freeze({
|
|
797
|
+
active: captured,
|
|
798
|
+
getCanDrag: (cardId, options) => !disposed && targets(cardId, options).length > 0,
|
|
799
|
+
begin(cardId, options) {
|
|
800
|
+
if (disposed || !targets(cardId, options).length) return false;
|
|
801
|
+
selection = options;
|
|
802
|
+
update({ cardId, target: null });
|
|
803
|
+
return true;
|
|
804
|
+
},
|
|
805
|
+
getDropTargets: () => dropTargets,
|
|
806
|
+
setDropTarget(target) {
|
|
807
|
+
if (!active || disposed) return;
|
|
808
|
+
if (target && !targets().some((candidate) => sameTarget(candidate, target))) {
|
|
809
|
+
update({ ...active, target: null });
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
update({ ...active, target });
|
|
813
|
+
},
|
|
814
|
+
drop() {
|
|
815
|
+
const finished = active;
|
|
816
|
+
const eligible = finished?.target && targets().some((target) => sameTarget(target, finished.target));
|
|
817
|
+
cancel();
|
|
818
|
+
if (!disposed && finished?.target && eligible)
|
|
819
|
+
context.routeCardDrop(finished.cardId, finished.target);
|
|
820
|
+
},
|
|
821
|
+
cancel
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
const unsubscribe = game.subscribe(() => {
|
|
825
|
+
const nextSource = game.getOptions().source;
|
|
826
|
+
const nextSeat = game.snapshot?.me;
|
|
827
|
+
const nextVersion = game.snapshot?.version;
|
|
828
|
+
if (nextSource !== source || nextSeat !== seat || nextVersion !== version || game.connection !== "ready") {
|
|
829
|
+
source = nextSource;
|
|
830
|
+
seat = nextSeat;
|
|
831
|
+
version = nextVersion;
|
|
832
|
+
cancel();
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
return {
|
|
836
|
+
root: {
|
|
837
|
+
get drag() {
|
|
838
|
+
if (lastInteractions !== game.interactions || lastCards !== game.cards) {
|
|
839
|
+
lastInteractions = game.interactions;
|
|
840
|
+
lastCards = game.cards;
|
|
841
|
+
branch = snapshot();
|
|
842
|
+
}
|
|
843
|
+
return branch;
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
dispose() {
|
|
847
|
+
unsubscribe();
|
|
848
|
+
cancel();
|
|
849
|
+
disposed = true;
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
|
|
658
854
|
// src/headless/features/pointer-session.ts
|
|
659
855
|
function createPointerSession(callbacks) {
|
|
660
856
|
let disposed = false;
|
|
@@ -717,151 +913,6 @@ function createPointerSession(callbacks) {
|
|
|
717
913
|
};
|
|
718
914
|
}
|
|
719
915
|
|
|
720
|
-
// src/headless/features/drag.ts
|
|
721
|
-
function dragFeature(game, context) {
|
|
722
|
-
let active = null;
|
|
723
|
-
let interaction;
|
|
724
|
-
let source = game.getOptions().source;
|
|
725
|
-
let seat = game.snapshot?.me;
|
|
726
|
-
let disposed = false;
|
|
727
|
-
let suppressPointerClick = false;
|
|
728
|
-
let dragged = false;
|
|
729
|
-
let lastInteractions = game.interactions;
|
|
730
|
-
let lastCards = game.cards;
|
|
731
|
-
let branch = snapshot();
|
|
732
|
-
function update(next) {
|
|
733
|
-
if (disposed) return;
|
|
734
|
-
active = next === null ? null : Object.freeze({ ...next, offset: Object.freeze(next.offset) });
|
|
735
|
-
branch = snapshot();
|
|
736
|
-
context.invalidate();
|
|
737
|
-
}
|
|
738
|
-
function targets() {
|
|
739
|
-
if (!active) return [];
|
|
740
|
-
const cardId = active.cardId;
|
|
741
|
-
return (game.cards.get(cardId)?.getInteractions() ?? []).filter(
|
|
742
|
-
(candidate) => candidate.getIsAvailable() && (!interaction || candidate.key === interaction) && candidate.getInputs().some(
|
|
743
|
-
(input) => input.getDomain().type === "cardTarget" && input.getIsEligible(cardId)
|
|
744
|
-
)
|
|
745
|
-
).flatMap(
|
|
746
|
-
(candidate) => candidate.getInputs().flatMap((input) => {
|
|
747
|
-
const domain = input.getDomain();
|
|
748
|
-
if (domain.type !== "boardTarget" || !["space", "edge", "vertex", "tile"].includes(
|
|
749
|
-
String(domain.targetKind)
|
|
750
|
-
))
|
|
751
|
-
return [];
|
|
752
|
-
return input.getEligibleTargets().filter(
|
|
753
|
-
(id) => typeof id === "string" && !input.getTargetProps(id).disabled
|
|
754
|
-
).map(
|
|
755
|
-
(id) => Object.freeze({
|
|
756
|
-
kind: domain.targetKind,
|
|
757
|
-
id,
|
|
758
|
-
boardId: String(domain.boardId),
|
|
759
|
-
interaction: candidate.key
|
|
760
|
-
})
|
|
761
|
-
);
|
|
762
|
-
})
|
|
763
|
-
);
|
|
764
|
-
}
|
|
765
|
-
function sameTarget(left, right) {
|
|
766
|
-
return left.kind === right.kind && left.id === right.id && left.boardId === right.boardId && left.interaction === right.interaction;
|
|
767
|
-
}
|
|
768
|
-
const pointer = createPointerSession({
|
|
769
|
-
move: ({ delta }) => {
|
|
770
|
-
if (Math.hypot(delta.x, delta.y) >= 5) dragged = true;
|
|
771
|
-
if (active) update({ ...active, offset: delta });
|
|
772
|
-
},
|
|
773
|
-
end({ delta }) {
|
|
774
|
-
if (Math.hypot(delta.x, delta.y) >= 5) dragged = true;
|
|
775
|
-
suppressPointerClick = true;
|
|
776
|
-
const finished = active;
|
|
777
|
-
update(null);
|
|
778
|
-
if (!finished) return;
|
|
779
|
-
if (dragged) {
|
|
780
|
-
if (finished.target)
|
|
781
|
-
context.routeCardDrop(finished.cardId, finished.target);
|
|
782
|
-
} else context.routeTarget("card", finished.cardId, { interaction });
|
|
783
|
-
},
|
|
784
|
-
cancel: () => {
|
|
785
|
-
suppressPointerClick = true;
|
|
786
|
-
update(null);
|
|
787
|
-
}
|
|
788
|
-
});
|
|
789
|
-
function snapshot() {
|
|
790
|
-
const captured = active;
|
|
791
|
-
const dropTargets = Object.freeze(targets());
|
|
792
|
-
return Object.freeze({
|
|
793
|
-
active: captured,
|
|
794
|
-
getDropTargets: () => dropTargets,
|
|
795
|
-
setDropTarget(target) {
|
|
796
|
-
if (!active || disposed) return;
|
|
797
|
-
if (target && !targets().some((candidate) => sameTarget(candidate, target)))
|
|
798
|
-
return;
|
|
799
|
-
update({ ...active, target });
|
|
800
|
-
},
|
|
801
|
-
cancel: () => pointer.cancel()
|
|
802
|
-
});
|
|
803
|
-
}
|
|
804
|
-
const unsubscribe = game.subscribe(() => {
|
|
805
|
-
const nextSource = game.getOptions().source;
|
|
806
|
-
const nextSeat = game.snapshot?.me;
|
|
807
|
-
if (nextSource !== source || nextSeat !== seat) {
|
|
808
|
-
source = nextSource;
|
|
809
|
-
seat = nextSeat;
|
|
810
|
-
pointer.cancel();
|
|
811
|
-
}
|
|
812
|
-
});
|
|
813
|
-
return {
|
|
814
|
-
root: {
|
|
815
|
-
get drag() {
|
|
816
|
-
if (lastInteractions !== game.interactions || lastCards !== game.cards) {
|
|
817
|
-
lastInteractions = game.interactions;
|
|
818
|
-
lastCards = game.cards;
|
|
819
|
-
branch = snapshot();
|
|
820
|
-
}
|
|
821
|
-
return branch;
|
|
822
|
-
}
|
|
823
|
-
},
|
|
824
|
-
card: {
|
|
825
|
-
getDragProps(options) {
|
|
826
|
-
return {
|
|
827
|
-
style: {
|
|
828
|
-
touchAction: "none",
|
|
829
|
-
transform: active?.cardId === this.id ? `translate(${active.offset.x}px, ${active.offset.y}px)` : void 0
|
|
830
|
-
},
|
|
831
|
-
"data-drag-card": this.id,
|
|
832
|
-
onPointerDown: (event) => {
|
|
833
|
-
if (disposed || game.cards.get(this.id) !== this || !this.getCanSelect())
|
|
834
|
-
return;
|
|
835
|
-
if (!pointer.start(event)) return;
|
|
836
|
-
suppressPointerClick = false;
|
|
837
|
-
dragged = false;
|
|
838
|
-
interaction = options?.interaction;
|
|
839
|
-
update({ cardId: this.id, offset: { x: 0, y: 0 }, target: null });
|
|
840
|
-
},
|
|
841
|
-
onClick: (event) => {
|
|
842
|
-
if (disposed) return;
|
|
843
|
-
if (event.detail !== 0 && suppressPointerClick) {
|
|
844
|
-
suppressPointerClick = false;
|
|
845
|
-
event.preventDefault();
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
this.select(options);
|
|
849
|
-
},
|
|
850
|
-
onPointerMove: pointer.move,
|
|
851
|
-
onPointerUp: pointer.end,
|
|
852
|
-
onPointerCancel: pointer.cancel,
|
|
853
|
-
onLostPointerCapture: pointer.cancel
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
},
|
|
857
|
-
dispose() {
|
|
858
|
-
unsubscribe();
|
|
859
|
-
pointer.dispose();
|
|
860
|
-
disposed = true;
|
|
861
|
-
}
|
|
862
|
-
};
|
|
863
|
-
}
|
|
864
|
-
|
|
865
916
|
// src/headless/features/pan-zoom.ts
|
|
866
917
|
function panZoomFeature(game, context, options = {}) {
|
|
867
918
|
const minScale = options.minScale ?? 0.25;
|