@dreamboard-games/sdk 0.5.0-alpha.3 → 0.5.0-alpha.30
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 +344 -21
- package/dist/board-element-DXLPBVk-.d.ts +13 -0
- package/dist/{chunk-LRILEVKC.js → chunk-63GWNBZX.js} +2763 -2327
- package/dist/chunk-63GWNBZX.js.map +1 -0
- package/dist/{chunk-SP234IY4.js → chunk-BVWJS3AB.js} +752 -343
- package/dist/chunk-BVWJS3AB.js.map +1 -0
- package/dist/{chunk-ZPRKWPEP.js → chunk-CWANHA4C.js} +284 -138
- package/dist/chunk-CWANHA4C.js.map +1 -0
- package/dist/chunk-CZRPUWCT.js +1602 -0
- package/dist/chunk-CZRPUWCT.js.map +1 -0
- package/dist/{chunk-FMM5LVJW.js → chunk-IEMZ26FI.js} +192 -383
- package/dist/chunk-IEMZ26FI.js.map +1 -0
- package/dist/{diagnostics-B0lgnuZ6.d.ts → diagnostics-D3mzqjxL.d.ts} +2 -3
- package/dist/extract-Bi6_tzUr.d.ts +6899 -0
- package/dist/index.d.ts +2058 -385
- package/dist/index.js +1260 -288
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +153 -8
- package/dist/react.js +1396 -30
- package/dist/react.js.map +1 -1
- package/dist/reducer.d.ts +592 -776
- package/dist/reducer.js +4617 -4055
- package/dist/reducer.js.map +1 -1
- package/dist/shortcuts-CuQXWAMv.d.ts +824 -0
- package/dist/testing.d.ts +59 -53
- package/dist/testing.js +335 -190
- package/dist/testing.js.map +1 -1
- package/dist/types-BlvVQ9WL.d.ts +125 -0
- package/package.json +17 -17
- package/dist/chunk-DMFVAZI2.js +0 -16
- package/dist/chunk-DMFVAZI2.js.map +0 -1
- 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/definition-BaHNPZol.d.ts +0 -4639
- package/dist/model-BHro6yXu.d.ts +0 -288
- package/dist/types-DimqnJBq.d.ts +0 -86
|
@@ -1,9 +1,73 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
BoardProjectionSchema,
|
|
3
|
+
immutableCopy,
|
|
4
|
+
inputDomainErrors,
|
|
5
|
+
inputTargetInDomain,
|
|
6
|
+
inputValueInDomain,
|
|
7
|
+
inputValueKey,
|
|
8
|
+
requireLookup,
|
|
9
|
+
zonePositions
|
|
10
|
+
} from "./chunk-CZRPUWCT.js";
|
|
11
|
+
|
|
12
|
+
// src/headless/runtime-features.ts
|
|
13
|
+
var runtimeFeatures = /* @__PURE__ */ Symbol("runtimeFeatures");
|
|
14
|
+
|
|
15
|
+
// src/headless/input-control.ts
|
|
16
|
+
function resourceValue(domain, value) {
|
|
17
|
+
const record = value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
18
|
+
return Object.fromEntries(
|
|
19
|
+
domain.resources.map((resource) => {
|
|
20
|
+
const amount = record[resource.resourceId];
|
|
21
|
+
return [resource.resourceId, typeof amount === "number" ? amount : 0];
|
|
22
|
+
})
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
function createInputControl(input, props) {
|
|
26
|
+
const domain = input.getDomain();
|
|
27
|
+
const current = input.getValue();
|
|
28
|
+
const common = { key: input.key, disabled: props.disabled, props };
|
|
29
|
+
if (domain.type === "boundedNumber") {
|
|
30
|
+
if (domain.selection?.mode === "many")
|
|
31
|
+
return {
|
|
32
|
+
...common,
|
|
33
|
+
type: "boundedNumber",
|
|
34
|
+
domain,
|
|
35
|
+
mode: "many",
|
|
36
|
+
value: Array.isArray(current) ? current.filter(
|
|
37
|
+
(value) => typeof value === "number"
|
|
38
|
+
) : [],
|
|
39
|
+
setValue: (value) => input.setValue(value)
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
...common,
|
|
43
|
+
type: "boundedNumber",
|
|
44
|
+
domain,
|
|
45
|
+
mode: "single",
|
|
46
|
+
value: typeof current === "number" ? current : void 0,
|
|
47
|
+
setValue: (value) => value === void 0 ? input.clear() : input.setValue(value)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (domain.type === "resourceMap") {
|
|
51
|
+
if (domain.selection?.mode === "many")
|
|
52
|
+
return {
|
|
53
|
+
...common,
|
|
54
|
+
type: "resourceMap",
|
|
55
|
+
domain,
|
|
56
|
+
mode: "many",
|
|
57
|
+
value: Array.isArray(current) ? current.map((value) => resourceValue(domain, value)) : [],
|
|
58
|
+
setValue: (value) => input.setValue(value)
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
...common,
|
|
62
|
+
type: "resourceMap",
|
|
63
|
+
domain,
|
|
64
|
+
mode: "single",
|
|
65
|
+
value: current === void 0 ? void 0 : resourceValue(domain, current),
|
|
66
|
+
setValue: (value) => value === void 0 ? input.clear() : input.setValue(value)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return { ...common, type: "targets", options: input.getTargetOptions() };
|
|
70
|
+
}
|
|
7
71
|
|
|
8
72
|
// src/headless/instance.ts
|
|
9
73
|
import { createStore } from "@tanstack/store";
|
|
@@ -30,68 +94,13 @@ function validateInteractionInputDomains(descriptor, params) {
|
|
|
30
94
|
for (const rawInput of descriptor.inputs) {
|
|
31
95
|
const input = rawInput;
|
|
32
96
|
const value = params[input.key];
|
|
33
|
-
if (value === void 0
|
|
34
|
-
const
|
|
35
|
-
|
|
97
|
+
if (value === void 0) continue;
|
|
98
|
+
for (const error of inputDomainErrors(
|
|
99
|
+
input.domain,
|
|
100
|
+
value,
|
|
101
|
+
input.domain.selection
|
|
102
|
+
))
|
|
36
103
|
pushFieldError(fieldErrors, input.key, error);
|
|
37
|
-
}
|
|
38
|
-
if (input.domain.type === "choiceList") {
|
|
39
|
-
if (!Array.isArray(value)) {
|
|
40
|
-
pushFieldError(fieldErrors, input.key, "Expected a list of choices.");
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
const min = input.domain.min ?? 0;
|
|
44
|
-
const max = input.domain.max ?? input.domain.choices?.length ?? Number.POSITIVE_INFINITY;
|
|
45
|
-
if (value.length < min) {
|
|
46
|
-
pushFieldError(
|
|
47
|
-
fieldErrors,
|
|
48
|
-
input.key,
|
|
49
|
-
`Choose at least ${min} ${pluralize("option", min)}.`
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
if (value.length > max) {
|
|
53
|
-
pushFieldError(
|
|
54
|
-
fieldErrors,
|
|
55
|
-
input.key,
|
|
56
|
-
`Choose at most ${max} ${pluralize("option", max)}.`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
const allowed = new Set(
|
|
60
|
-
input.domain.choices?.map((choice) => choice.value)
|
|
61
|
-
);
|
|
62
|
-
if (allowed.size > 0 && value.some((item) => !allowed.has(String(item)))) {
|
|
63
|
-
pushFieldError(
|
|
64
|
-
fieldErrors,
|
|
65
|
-
input.key,
|
|
66
|
-
"Selected choice is not eligible."
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (input.domain.type === "choice") {
|
|
71
|
-
const allowed = new Set(
|
|
72
|
-
input.domain.choices?.map((choice) => choice.value)
|
|
73
|
-
);
|
|
74
|
-
if (allowed.size > 0 && !allowed.has(value)) {
|
|
75
|
-
pushFieldError(
|
|
76
|
-
fieldErrors,
|
|
77
|
-
input.key,
|
|
78
|
-
"Selected choice is not eligible."
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (isResolvedTargetDomain(input.domain)) {
|
|
83
|
-
const values = valuesForSelection(input.domain.selection, value);
|
|
84
|
-
for (const item of values) {
|
|
85
|
-
if (!input.domain.eligibleTargets.includes(String(item))) {
|
|
86
|
-
pushFieldError(
|
|
87
|
-
fieldErrors,
|
|
88
|
-
input.key,
|
|
89
|
-
"Selected target is not eligible."
|
|
90
|
-
);
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
104
|
}
|
|
96
105
|
return fieldErrors;
|
|
97
106
|
}
|
|
@@ -110,10 +119,10 @@ function isManyInput(input) {
|
|
|
110
119
|
}
|
|
111
120
|
function toggleManyValue(current, value, selection) {
|
|
112
121
|
if (selection.mode !== "many") return [value];
|
|
113
|
-
const previous = Array.isArray(current) ? current
|
|
114
|
-
const existing = previous.
|
|
122
|
+
const previous = Array.isArray(current) ? current : [];
|
|
123
|
+
const existing = previous.findIndex((item) => sameValue(item, value));
|
|
115
124
|
if (existing >= 0) {
|
|
116
|
-
return previous.filter((item) => item
|
|
125
|
+
return previous.filter((item) => !sameValue(item, value));
|
|
117
126
|
}
|
|
118
127
|
if (selection.max !== void 0 && previous.length >= selection.max) {
|
|
119
128
|
return previous;
|
|
@@ -123,9 +132,8 @@ function toggleManyValue(current, value, selection) {
|
|
|
123
132
|
function isManyTargetSelectable(input, current, targetId) {
|
|
124
133
|
const selection = inputSelection(input);
|
|
125
134
|
if (selection?.mode !== "many") return true;
|
|
126
|
-
const currentValues = Array.isArray(current) ? current
|
|
127
|
-
if (currentValues.
|
|
128
|
-
if (selection.distinct && currentValues.includes(targetId)) return false;
|
|
135
|
+
const currentValues = Array.isArray(current) ? current : [];
|
|
136
|
+
if (currentValues.some((item) => sameValue(item, targetId))) return true;
|
|
129
137
|
return selection.max === void 0 || currentValues.length < selection.max;
|
|
130
138
|
}
|
|
131
139
|
function hasInteractionFieldErrors(fieldErrors) {
|
|
@@ -136,52 +144,21 @@ function hasInteractionFieldErrors(fieldErrors) {
|
|
|
136
144
|
function inputByKey(descriptor, key) {
|
|
137
145
|
return descriptor.inputs.find((input) => input.key === key);
|
|
138
146
|
}
|
|
139
|
-
function isTargetDomain(domain) {
|
|
140
|
-
if (!domain) return false;
|
|
141
|
-
return domain.type === "cardTarget" || domain.type === "boardTarget";
|
|
142
|
-
}
|
|
143
|
-
function isResolvedTargetDomain(domain) {
|
|
144
|
-
return (domain.type === "cardTarget" || domain.type === "boardTarget") && domain.projection === "resolved";
|
|
145
|
-
}
|
|
146
|
-
function validateInputSelection(input, value) {
|
|
147
|
-
const selection = inputSelection(input);
|
|
148
|
-
if (selection?.mode !== "many") return [];
|
|
149
|
-
if (!Array.isArray(value)) return ["Expected a list of values."];
|
|
150
|
-
const errors = [];
|
|
151
|
-
const min = selection.min;
|
|
152
|
-
if (value.length < min) {
|
|
153
|
-
errors.push(`Choose at least ${min} ${pluralize("value", min)}.`);
|
|
154
|
-
}
|
|
155
|
-
if (selection.max !== void 0 && value.length > selection.max) {
|
|
156
|
-
errors.push(
|
|
157
|
-
`Choose at most ${selection.max} ${pluralize("value", selection.max)}.`
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
if (selection.distinct) {
|
|
161
|
-
const seen = /* @__PURE__ */ new Set();
|
|
162
|
-
for (const item of value) {
|
|
163
|
-
const key = String(item);
|
|
164
|
-
if (seen.has(key)) {
|
|
165
|
-
errors.push("Choose each value only once.");
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
seen.add(key);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return errors;
|
|
172
|
-
}
|
|
173
|
-
function valuesForSelection(selection, value) {
|
|
174
|
-
if (selection?.mode === "many") return Array.isArray(value) ? value : [];
|
|
175
|
-
return [value];
|
|
176
|
-
}
|
|
177
147
|
function inputSelection(input) {
|
|
178
|
-
|
|
148
|
+
if (input.domain.selection) return input.domain.selection;
|
|
149
|
+
if (input.domain.type === "choiceList")
|
|
150
|
+
return {
|
|
151
|
+
mode: "many",
|
|
152
|
+
min: input.domain.min ?? 0,
|
|
153
|
+
max: input.domain.max ?? input.domain.choices.length
|
|
154
|
+
};
|
|
155
|
+
return void 0;
|
|
179
156
|
}
|
|
180
157
|
function pushFieldError(fieldErrors, key, message) {
|
|
181
158
|
fieldErrors[key] = [...fieldErrors[key] ?? [], message];
|
|
182
159
|
}
|
|
183
|
-
function
|
|
184
|
-
return
|
|
160
|
+
function sameValue(left, right) {
|
|
161
|
+
return inputValueKey(left) === inputValueKey(right);
|
|
185
162
|
}
|
|
186
163
|
|
|
187
164
|
// src/headless/interaction-router.ts
|
|
@@ -236,8 +213,12 @@ function getInteractionDraftReadiness(descriptor, draft) {
|
|
|
236
213
|
function routeInteractionTarget(store, descriptor, target) {
|
|
237
214
|
const input = inputByKey(descriptor, target.inputKey);
|
|
238
215
|
const currentDraft = store.getDraft(descriptor.interactionKey);
|
|
239
|
-
const selection =
|
|
240
|
-
const targetValue = selection?.mode === "many" ? toggleManyValue(
|
|
216
|
+
const selection = input ? inputSelection(input) : void 0;
|
|
217
|
+
const targetValue = selection?.mode === "many" ? toggleManyValue(
|
|
218
|
+
currentDraft[target.inputKey] ?? input?.defaultValue,
|
|
219
|
+
target.value,
|
|
220
|
+
selection
|
|
221
|
+
) : target.value;
|
|
241
222
|
const params = applyInteractionDraftMutation(store, descriptor, [
|
|
242
223
|
...Object.entries(target.extraInputs ?? {}).map(([key, value]) => ({
|
|
243
224
|
key,
|
|
@@ -258,18 +239,24 @@ function routeCardInputIntent(store, descriptor, intent) {
|
|
|
258
239
|
});
|
|
259
240
|
}
|
|
260
241
|
function shouldAutoSubmitInteraction(descriptor) {
|
|
261
|
-
return descriptor.commit.mode === "autoWhenReady" && !descriptor.inputs.some((input) =>
|
|
242
|
+
return descriptor.commit.mode === "autoWhenReady" && !descriptor.inputs.some((input) => input.domain.selection?.mode === "many");
|
|
262
243
|
}
|
|
263
244
|
|
|
264
245
|
// src/headless/instance.ts
|
|
265
246
|
var EMPTY = Object.freeze({});
|
|
266
247
|
var EMPTY_DRAFTS = Object.freeze({});
|
|
267
|
-
var equalValue = (a, b) => Object.is(a, b) || typeof a === "object" && typeof b === "object" &&
|
|
248
|
+
var equalValue = (a, b) => Object.is(a, b) || typeof a === "object" && typeof b === "object" && inputValueKey(a) === inputValueKey(b);
|
|
268
249
|
function inDomain(input, value, partial = false) {
|
|
269
250
|
return inputValueInDomain(input.domain, value, input.domain.selection, {
|
|
270
251
|
ignoreMinimum: partial
|
|
271
252
|
});
|
|
272
253
|
}
|
|
254
|
+
function matchesBoardTarget(input, target) {
|
|
255
|
+
const domain = input.domain;
|
|
256
|
+
return domain.type === "boardTarget" && domain.valueKind === target.valueKind && domain.targetKind === target.kind && (domain.valueKind === "board-id" && target.valueKind === "board-id" ? domain.boardId === target.boardId : domain.valueKind === "board-space" && target.valueKind === "board-space" && domain.eligibleTargets.some(
|
|
257
|
+
(candidate) => candidate.boardId === target.value.boardId
|
|
258
|
+
));
|
|
259
|
+
}
|
|
273
260
|
function immutableValues(value) {
|
|
274
261
|
return immutableCopy(value);
|
|
275
262
|
}
|
|
@@ -286,7 +273,7 @@ function data(action, interaction, input, value) {
|
|
|
286
273
|
var AmbiguousTargetError = class extends Error {
|
|
287
274
|
constructor(id) {
|
|
288
275
|
super(
|
|
289
|
-
`Target '${id}' belongs to more than one
|
|
276
|
+
`Target '${id}' belongs to more than one eligible input. Choose an interaction and input explicitly.`
|
|
290
277
|
);
|
|
291
278
|
this.name = "AmbiguousTargetError";
|
|
292
279
|
}
|
|
@@ -338,8 +325,9 @@ var InteractionObject = class {
|
|
|
338
325
|
getAvailability() {
|
|
339
326
|
return this.descriptor.availability;
|
|
340
327
|
}
|
|
341
|
-
getIsAvailable() {
|
|
342
|
-
|
|
328
|
+
getIsAvailable(params) {
|
|
329
|
+
const descriptor = params === void 0 ? this.descriptor : this.owner.descriptorFor(this.descriptor, params);
|
|
330
|
+
return descriptor.availability.status === "available";
|
|
343
331
|
}
|
|
344
332
|
getUnavailableReason() {
|
|
345
333
|
return this.descriptor.availability.status === "available" ? null : this.descriptor.availability.reason;
|
|
@@ -351,22 +339,26 @@ var InteractionObject = class {
|
|
|
351
339
|
return this.descriptor.step?.index ?? null;
|
|
352
340
|
}
|
|
353
341
|
getInput(key) {
|
|
342
|
+
return requireLookup(
|
|
343
|
+
this.findInput(key),
|
|
344
|
+
`Input in interaction ${this.key}`,
|
|
345
|
+
key
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
findInput(key) {
|
|
354
349
|
return this.inputObjects.find((input) => input.key === key);
|
|
355
350
|
}
|
|
356
351
|
getInputs() {
|
|
357
352
|
return this.inputObjects;
|
|
358
353
|
}
|
|
359
|
-
readiness() {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
(input) => inDomain(input, readiness.values[input.key])
|
|
365
|
-
)
|
|
366
|
-
};
|
|
354
|
+
readiness(params = this.draft) {
|
|
355
|
+
return getInteractionDraftReadiness(
|
|
356
|
+
this.owner.descriptorFor(this.descriptor, params),
|
|
357
|
+
params
|
|
358
|
+
);
|
|
367
359
|
}
|
|
368
|
-
getIsReady() {
|
|
369
|
-
return this.readiness().ready;
|
|
360
|
+
getIsReady(params) {
|
|
361
|
+
return this.readiness(params).ready;
|
|
370
362
|
}
|
|
371
363
|
getMissingInputs() {
|
|
372
364
|
return this.readiness().missingInputs;
|
|
@@ -377,8 +369,8 @@ var InteractionObject = class {
|
|
|
377
369
|
currentLifetime() {
|
|
378
370
|
return this.source === this.owner.options.source && this.epoch === this.owner.epoch && !this.owner.disposed;
|
|
379
371
|
}
|
|
380
|
-
submit() {
|
|
381
|
-
return this.currentLifetime() ? this.owner.submit(this.key, false) : Promise.resolve({ accepted: false, errorCode: "CLOSED" });
|
|
372
|
+
submit(params) {
|
|
373
|
+
return this.currentLifetime() ? this.owner.submit(this.key, false, params) : Promise.resolve({ accepted: false, errorCode: "CLOSED" });
|
|
382
374
|
}
|
|
383
375
|
cancel() {
|
|
384
376
|
return this.currentLifetime() ? this.owner.submit(this.key, true) : Promise.resolve({ accepted: false, errorCode: "CLOSED" });
|
|
@@ -389,39 +381,119 @@ var InteractionObject = class {
|
|
|
389
381
|
activate() {
|
|
390
382
|
if (this.currentLifetime()) this.owner.activate(this.key);
|
|
391
383
|
}
|
|
392
|
-
getSubmitHandler() {
|
|
384
|
+
getSubmitHandler(params) {
|
|
393
385
|
return (event) => {
|
|
394
386
|
event?.preventDefault();
|
|
395
|
-
this.owner.handle(this.submit());
|
|
387
|
+
this.owner.handle(() => this.submit(params));
|
|
396
388
|
};
|
|
397
389
|
}
|
|
398
|
-
getSubmitProps() {
|
|
399
|
-
const disabled = !this.getIsAvailable() || !this.getIsReady() || this.status !== "open" || !this.connected;
|
|
390
|
+
getSubmitProps(params) {
|
|
391
|
+
const disabled = !this.getIsAvailable(params) || !this.getIsReady(params) || this.status !== "open" || !this.connected;
|
|
400
392
|
return {
|
|
401
393
|
...data("submit", this.key),
|
|
402
394
|
"data-seat": this.seat,
|
|
403
395
|
type: "button",
|
|
404
396
|
disabled,
|
|
405
|
-
"data-ready": this.getIsReady(),
|
|
397
|
+
"data-ready": this.getIsReady(params),
|
|
406
398
|
"data-disabled": disabled,
|
|
407
|
-
onClick: this.getSubmitHandler()
|
|
399
|
+
onClick: this.getSubmitHandler(params)
|
|
408
400
|
};
|
|
409
401
|
}
|
|
410
402
|
};
|
|
403
|
+
function createInputTargetLabels(domain, snapshot, boards) {
|
|
404
|
+
const targetLabels = /* @__PURE__ */ new Map();
|
|
405
|
+
const hosts = Object.values(snapshot?.frame.zones ?? {}).flatMap(
|
|
406
|
+
(zones) => Object.values(zones)
|
|
407
|
+
);
|
|
408
|
+
if (domain.type === "cardTarget") {
|
|
409
|
+
domain.eligibleTargets.forEach((id, index) => {
|
|
410
|
+
const card = hosts.map((zone) => zone.cardViewsById[id]).find((card2) => card2 !== void 0);
|
|
411
|
+
targetLabels.set(
|
|
412
|
+
inputValueKey(id),
|
|
413
|
+
card ? card.name || `Card ${index + 1}` : `Card back ${index + 1}`
|
|
414
|
+
);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (domain.type === "zonePosition")
|
|
418
|
+
for (const value of zonePositions(domain.zones)) {
|
|
419
|
+
const zone = snapshot?.frame.zones[value.zoneId]?.[value.hostId];
|
|
420
|
+
const id = zone?.cardIds[value.index];
|
|
421
|
+
const name = id === void 0 ? void 0 : zone?.cardViewsById[id]?.name;
|
|
422
|
+
const place = id === void 0 ? "At the end" : `Before ${name || `card ${value.index + 1}`}`;
|
|
423
|
+
targetLabels.set(
|
|
424
|
+
inputValueKey(value),
|
|
425
|
+
domain.zones.length === 1 ? place : `${value.zoneId} (${value.hostId}): ${place}`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
if (domain.type === "tileTarget" || domain.type === "boardTarget") {
|
|
429
|
+
if (domain.type === "tileTarget") {
|
|
430
|
+
const presentations = [
|
|
431
|
+
...hosts.flatMap((zone) => zone.tiles),
|
|
432
|
+
...Object.values(boards).flatMap(
|
|
433
|
+
(board) => board.layout === "generic" ? [] : board.tiles
|
|
434
|
+
)
|
|
435
|
+
];
|
|
436
|
+
domain.eligibleTargets.forEach((ref, index) => {
|
|
437
|
+
const tile = presentations.find((tile2) => tile2.ref === ref);
|
|
438
|
+
targetLabels.set(
|
|
439
|
+
inputValueKey(ref),
|
|
440
|
+
tile?.disclosure === "visible" ? tile.name || `Tile ${index + 1}` : `Tile back ${index + 1}`
|
|
441
|
+
);
|
|
442
|
+
});
|
|
443
|
+
} else {
|
|
444
|
+
domain.eligibleTargets.forEach((value, index) => {
|
|
445
|
+
const boardId = typeof value === "string" ? domain.valueKind === "board-id" ? domain.boardId : void 0 : value.boardId;
|
|
446
|
+
const id = typeof value === "string" ? value : value.spaceId;
|
|
447
|
+
const board = boardId === void 0 ? void 0 : boards[boardId];
|
|
448
|
+
const name = domain.targetKind === "space" ? board?.spaces[id]?.name : board && board.layout !== "generic" ? (domain.targetKind === "edge" ? board.edges : board.vertices).find((target) => target.id === id)?.label : void 0;
|
|
449
|
+
const kind = domain.targetKind === "space" ? "Space" : domain.targetKind === "edge" ? "Edge" : "Vertex";
|
|
450
|
+
targetLabels.set(inputValueKey(value), name || `${kind} ${index + 1}`);
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return targetLabels;
|
|
455
|
+
}
|
|
411
456
|
var InputObject = class {
|
|
412
457
|
constructor(interaction, descriptor) {
|
|
413
458
|
this.interaction = interaction;
|
|
414
459
|
this.descriptor = descriptor;
|
|
415
460
|
this.key = descriptor.key;
|
|
416
461
|
this.kind = descriptor.kind;
|
|
462
|
+
const snapshot = interaction.owner.sourceState.snapshot;
|
|
463
|
+
this.snapshot = snapshot;
|
|
464
|
+
this.targetLabels = createInputTargetLabels(
|
|
465
|
+
descriptor.domain,
|
|
466
|
+
snapshot,
|
|
467
|
+
descriptor.domain.type === "tileTarget" || descriptor.domain.type === "boardTarget" ? interaction.owner.getBoardProjections(snapshot?.frame.view) : {}
|
|
468
|
+
);
|
|
417
469
|
}
|
|
418
470
|
interaction;
|
|
419
471
|
descriptor;
|
|
472
|
+
targetLabels;
|
|
473
|
+
snapshot;
|
|
420
474
|
key;
|
|
421
475
|
kind;
|
|
476
|
+
currentLifetime() {
|
|
477
|
+
return this.interaction.currentLifetime() && (this.descriptor.domain.type !== "tileTarget" || this.snapshot === this.interaction.owner.sourceState.snapshot);
|
|
478
|
+
}
|
|
422
479
|
get game() {
|
|
423
480
|
return this.interaction.game;
|
|
424
481
|
}
|
|
482
|
+
get domainType() {
|
|
483
|
+
return this.descriptor.domain.type;
|
|
484
|
+
}
|
|
485
|
+
get selectionMode() {
|
|
486
|
+
return isManyInput(this.descriptor) ? "many" : "single";
|
|
487
|
+
}
|
|
488
|
+
getTargetOptions() {
|
|
489
|
+
const domain = this.getDomain();
|
|
490
|
+
return this.getEligibleTargets().map((value) => ({
|
|
491
|
+
value,
|
|
492
|
+
label: this.targetLabels.get(inputValueKey(value)) ?? (domain.type === "choice" || domain.type === "choiceList" ? domain.choices.find((choice) => Object.is(choice.value, value))?.label : void 0) ?? (value === null ? "None" : typeof value === "object" ? JSON.stringify(value) : String(value)),
|
|
493
|
+
selected: this.getIsSelected(value),
|
|
494
|
+
props: this.getTargetProps(value)
|
|
495
|
+
}));
|
|
496
|
+
}
|
|
425
497
|
getDomain() {
|
|
426
498
|
return this.descriptor.domain;
|
|
427
499
|
}
|
|
@@ -429,11 +501,11 @@ var InputObject = class {
|
|
|
429
501
|
return this.interaction.draft[this.key] ?? (Object.hasOwn(this.interaction.draft, this.key) ? null : this.descriptor.defaultValue);
|
|
430
502
|
}
|
|
431
503
|
setValue(value) {
|
|
432
|
-
if (!this.
|
|
504
|
+
if (!this.currentLifetime()) return;
|
|
433
505
|
this.interaction.owner.setInput(this.interaction.key, this.key, value);
|
|
434
506
|
}
|
|
435
507
|
clear() {
|
|
436
|
-
if (!this.
|
|
508
|
+
if (!this.currentLifetime()) return;
|
|
437
509
|
this.interaction.owner.clearInput(this.interaction.key, this.key);
|
|
438
510
|
}
|
|
439
511
|
getIsReady() {
|
|
@@ -443,23 +515,20 @@ var InputObject = class {
|
|
|
443
515
|
};
|
|
444
516
|
return getInteractionDraftReadiness(descriptor, {
|
|
445
517
|
[this.key]: this.getValue()
|
|
446
|
-
}).ready
|
|
518
|
+
}).ready;
|
|
447
519
|
}
|
|
448
520
|
getEligibleTargets() {
|
|
449
521
|
const domain = this.descriptor.domain;
|
|
450
|
-
if (domain.type === "cardTarget" || domain.type === "boardTarget")
|
|
522
|
+
if (domain.type === "cardTarget" || domain.type === "boardTarget" || domain.type === "tileTarget")
|
|
451
523
|
return domain.eligibleTargets;
|
|
452
524
|
if (domain.type === "choice" || domain.type === "choiceList")
|
|
453
525
|
return domain.choices.filter((choice) => !choice.disabled).map((choice) => choice.value);
|
|
526
|
+
if (domain.type === "zonePosition") return zonePositions(domain.zones);
|
|
454
527
|
return [];
|
|
455
528
|
}
|
|
456
529
|
getIsEligible(value) {
|
|
457
530
|
const input = this.descriptor;
|
|
458
|
-
|
|
459
|
-
return inDomain(
|
|
460
|
-
{ ...input, domain: { ...input.domain, selection: void 0 } },
|
|
461
|
-
value
|
|
462
|
-
) && (!selection || selection.mode !== "many" || isManyTargetSelectable(input, this.getValue(), String(value)));
|
|
531
|
+
return inputTargetInDomain(input.domain, value) && isManyTargetSelectable(input, this.getValue(), value);
|
|
463
532
|
}
|
|
464
533
|
getIsSelected(value) {
|
|
465
534
|
const current = this.getValue();
|
|
@@ -467,7 +536,7 @@ var InputObject = class {
|
|
|
467
536
|
}
|
|
468
537
|
getSelectHandler(value) {
|
|
469
538
|
return () => {
|
|
470
|
-
if (this.
|
|
539
|
+
if (this.currentLifetime())
|
|
471
540
|
this.interaction.owner.select(this.interaction.key, this.key, value);
|
|
472
541
|
};
|
|
473
542
|
}
|
|
@@ -485,49 +554,43 @@ var InputObject = class {
|
|
|
485
554
|
onClick: () => this.getSelectHandler(value)()
|
|
486
555
|
};
|
|
487
556
|
}
|
|
488
|
-
|
|
557
|
+
getControl() {
|
|
489
558
|
const disabled = !this.interaction.getIsAvailable() || this.interaction.status !== "open" || !this.interaction.connected;
|
|
490
|
-
return {
|
|
559
|
+
return createInputControl(this, {
|
|
491
560
|
...data("option", this.interaction.key, this.key),
|
|
492
561
|
"data-seat": this.interaction.seat,
|
|
493
|
-
value: this.getValue() ?? "",
|
|
494
562
|
disabled,
|
|
495
|
-
"data-disabled": disabled
|
|
496
|
-
|
|
497
|
-
if (event.currentTarget.value === "") {
|
|
498
|
-
this.clear();
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
this.setValue(
|
|
502
|
-
this.descriptor.domain.type === "boundedNumber" ? Number(event.currentTarget.value) : event.currentTarget.value
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
};
|
|
563
|
+
"data-disabled": disabled
|
|
564
|
+
});
|
|
506
565
|
}
|
|
507
566
|
};
|
|
508
567
|
var CardObject = class {
|
|
509
|
-
constructor(owner, id, zone, index,
|
|
568
|
+
constructor(owner, id, zone, hostId, index, view, backImage, routes) {
|
|
510
569
|
this.owner = owner;
|
|
511
570
|
this.id = id;
|
|
512
571
|
this.zone = zone;
|
|
572
|
+
this.hostId = hostId;
|
|
513
573
|
this.index = index;
|
|
514
574
|
this.routes = routes;
|
|
515
575
|
this.epoch = owner.epoch;
|
|
516
576
|
this.seat = owner.sourceState.snapshot?.players.findIndex(
|
|
517
577
|
(player) => player.playerId === owner.sourceState.snapshot?.me
|
|
518
578
|
) ?? -1;
|
|
519
|
-
this.view =
|
|
579
|
+
this.view = view ?? null;
|
|
520
580
|
this.hidden = this.view === null;
|
|
581
|
+
this.backImage = backImage ?? null;
|
|
521
582
|
}
|
|
522
583
|
owner;
|
|
523
584
|
id;
|
|
524
585
|
zone;
|
|
586
|
+
hostId;
|
|
525
587
|
index;
|
|
526
588
|
routes;
|
|
527
589
|
epoch;
|
|
528
590
|
seat;
|
|
529
591
|
view;
|
|
530
592
|
hidden;
|
|
593
|
+
backImage;
|
|
531
594
|
get game() {
|
|
532
595
|
return this.owner.instance;
|
|
533
596
|
}
|
|
@@ -544,16 +607,16 @@ var CardObject = class {
|
|
|
544
607
|
)
|
|
545
608
|
);
|
|
546
609
|
}
|
|
547
|
-
getCanSelect() {
|
|
610
|
+
getCanSelect(options) {
|
|
548
611
|
return this.routes.some(
|
|
549
|
-
(route) => route.getIsAvailable() && route.status === "open" && route.connected && route.getInputs().some(
|
|
550
|
-
(input) => input.descriptor.domain.type === "cardTarget" && input.getIsEligible(this.id)
|
|
612
|
+
(route) => route.getIsAvailable() && (!options?.interaction || route.key === options.interaction) && route.status === "open" && route.connected && route.getInputs().some(
|
|
613
|
+
(input) => input.descriptor.domain.type === "cardTarget" && (!options?.input || input.key === options.input) && input.getIsEligible(this.id)
|
|
551
614
|
)
|
|
552
615
|
);
|
|
553
616
|
}
|
|
554
617
|
select(options) {
|
|
555
618
|
if (this.epoch !== this.owner.epoch || this.owner.disposed) return;
|
|
556
|
-
this.owner.selectCard(this.id, options
|
|
619
|
+
this.owner.selectCard(this.id, options);
|
|
557
620
|
}
|
|
558
621
|
getSelectHandler(options) {
|
|
559
622
|
return () => this.select(options);
|
|
@@ -561,38 +624,129 @@ var CardObject = class {
|
|
|
561
624
|
getProps(options) {
|
|
562
625
|
return {
|
|
563
626
|
type: "button",
|
|
564
|
-
disabled: !this.getCanSelect(),
|
|
627
|
+
disabled: !this.getCanSelect(options),
|
|
565
628
|
"data-action": "select",
|
|
566
629
|
"data-value": this.id,
|
|
567
630
|
"data-interaction": options?.interaction,
|
|
631
|
+
"data-input": options?.input,
|
|
568
632
|
"data-eligible": this.getIsEligible(),
|
|
569
633
|
"data-selected": this.getIsSelected(),
|
|
570
|
-
"data-disabled": !this.getCanSelect(),
|
|
634
|
+
"data-disabled": !this.getCanSelect(options),
|
|
571
635
|
"data-hidden": this.hidden,
|
|
572
636
|
"data-seat": this.seat,
|
|
573
637
|
onClick: this.getSelectHandler(options)
|
|
574
638
|
};
|
|
575
639
|
}
|
|
576
640
|
};
|
|
641
|
+
var TileObject = class {
|
|
642
|
+
constructor(owner, data2, zone, hostId, index, routes) {
|
|
643
|
+
this.owner = owner;
|
|
644
|
+
this.data = data2;
|
|
645
|
+
this.zone = zone;
|
|
646
|
+
this.hostId = hostId;
|
|
647
|
+
this.index = index;
|
|
648
|
+
this.routes = routes;
|
|
649
|
+
this.epoch = owner.epoch;
|
|
650
|
+
this.snapshot = owner.sourceState.snapshot;
|
|
651
|
+
}
|
|
652
|
+
owner;
|
|
653
|
+
data;
|
|
654
|
+
zone;
|
|
655
|
+
hostId;
|
|
656
|
+
index;
|
|
657
|
+
routes;
|
|
658
|
+
epoch;
|
|
659
|
+
snapshot;
|
|
660
|
+
get ref() {
|
|
661
|
+
return this.data.ref;
|
|
662
|
+
}
|
|
663
|
+
get game() {
|
|
664
|
+
return this.owner.instance;
|
|
665
|
+
}
|
|
666
|
+
getInteractions() {
|
|
667
|
+
return this.routes;
|
|
668
|
+
}
|
|
669
|
+
getIsEligible() {
|
|
670
|
+
return this.routes.some(
|
|
671
|
+
(route) => route.getInputs().some(
|
|
672
|
+
(input) => input.descriptor.domain.type === "tileTarget" && input.getIsEligible(this.ref)
|
|
673
|
+
)
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
getIsSelected() {
|
|
677
|
+
return this.routes.some(
|
|
678
|
+
(route) => route.getInputs().some(
|
|
679
|
+
(input) => input.descriptor.domain.type === "tileTarget" && input.getIsSelected(this.ref)
|
|
680
|
+
)
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
getCanSelect(options) {
|
|
684
|
+
return this.routes.some(
|
|
685
|
+
(route) => route.getIsAvailable() && (!options?.interaction || route.key === options.interaction) && route.getInputs().some(
|
|
686
|
+
(input) => input.descriptor.domain.type === "tileTarget" && (!options?.input || input.key === options.input) && !input.getTargetProps(this.ref).disabled
|
|
687
|
+
)
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
select(options) {
|
|
691
|
+
if (this.owner.disposed || this.epoch !== this.owner.epoch || this.snapshot !== this.owner.sourceState.snapshot)
|
|
692
|
+
return;
|
|
693
|
+
this.owner.routeTarget({ kind: "tile", value: this.ref }, options);
|
|
694
|
+
}
|
|
695
|
+
getSelectHandler(options) {
|
|
696
|
+
return () => this.select(options);
|
|
697
|
+
}
|
|
698
|
+
getTargetProps(options) {
|
|
699
|
+
const disabled = !this.getCanSelect(options);
|
|
700
|
+
return {
|
|
701
|
+
type: "button",
|
|
702
|
+
disabled,
|
|
703
|
+
"data-action": "select",
|
|
704
|
+
"data-value": this.ref,
|
|
705
|
+
"data-interaction": options?.interaction,
|
|
706
|
+
"data-input": options?.input,
|
|
707
|
+
"data-eligible": this.getIsEligible(),
|
|
708
|
+
"data-selected": this.getIsSelected(),
|
|
709
|
+
"data-disabled": disabled,
|
|
710
|
+
"data-hidden": this.data.disclosure === "concealed",
|
|
711
|
+
onClick: this.getSelectHandler(options)
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
};
|
|
577
715
|
var ZoneObject = class {
|
|
578
|
-
constructor(owner, id, cards) {
|
|
716
|
+
constructor(owner, id, hostId, cards, tiles) {
|
|
579
717
|
this.owner = owner;
|
|
580
718
|
this.id = id;
|
|
719
|
+
this.hostId = hostId;
|
|
581
720
|
this.cards = cards;
|
|
721
|
+
this.tiles = tiles;
|
|
582
722
|
}
|
|
583
723
|
owner;
|
|
584
724
|
id;
|
|
725
|
+
hostId;
|
|
585
726
|
cards;
|
|
727
|
+
tiles;
|
|
586
728
|
get game() {
|
|
587
729
|
return this.owner.instance;
|
|
588
730
|
}
|
|
589
731
|
get count() {
|
|
590
|
-
return this.cards.length;
|
|
732
|
+
return this.cards.length + this.tiles.length;
|
|
591
733
|
}
|
|
592
734
|
getIsEmpty() {
|
|
593
735
|
return this.count === 0;
|
|
594
736
|
}
|
|
737
|
+
getTiles() {
|
|
738
|
+
return this.tiles;
|
|
739
|
+
}
|
|
740
|
+
getTile(ref) {
|
|
741
|
+
return requireLookup(this.findTile(ref), `Tile in zone ${this.id}`, ref);
|
|
742
|
+
}
|
|
743
|
+
findTile(ref) {
|
|
744
|
+
return this.tiles.find((tile) => tile.ref === ref);
|
|
745
|
+
}
|
|
595
746
|
getCard(id) {
|
|
747
|
+
return requireLookup(this.findCard(id), `Card in zone ${this.id}`, id);
|
|
748
|
+
}
|
|
749
|
+
findCard(id) {
|
|
596
750
|
return this.cards.find((card) => card.id === id);
|
|
597
751
|
}
|
|
598
752
|
getCards(options) {
|
|
@@ -633,6 +787,17 @@ var PhaseObject = class {
|
|
|
633
787
|
}
|
|
634
788
|
};
|
|
635
789
|
var Controller = class {
|
|
790
|
+
boardProjections = /* @__PURE__ */ new WeakMap();
|
|
791
|
+
getBoardProjections(view) {
|
|
792
|
+
if (view === null || typeof view !== "object" || Array.isArray(view))
|
|
793
|
+
return {};
|
|
794
|
+
const cached = this.boardProjections.get(view);
|
|
795
|
+
if (cached) return cached;
|
|
796
|
+
const payload = Reflect.get(view, "boards");
|
|
797
|
+
const boards = payload === void 0 ? {} : immutableCopy(BoardProjectionSchema.parse(payload));
|
|
798
|
+
this.boardProjections.set(view, boards);
|
|
799
|
+
return boards;
|
|
800
|
+
}
|
|
636
801
|
epoch = 0;
|
|
637
802
|
options;
|
|
638
803
|
localDrafts;
|
|
@@ -649,6 +814,7 @@ var Controller = class {
|
|
|
649
814
|
};
|
|
650
815
|
subscription;
|
|
651
816
|
sourceState;
|
|
817
|
+
reportedFailures = /* @__PURE__ */ new WeakMap();
|
|
652
818
|
pending = null;
|
|
653
819
|
building;
|
|
654
820
|
rootProperties = /* @__PURE__ */ new Map();
|
|
@@ -675,7 +841,60 @@ var Controller = class {
|
|
|
675
841
|
this.localDrafts = options.initialState?.drafts ?? EMPTY_DRAFTS;
|
|
676
842
|
this.localActive = options.initialState?.activeInteraction ?? null;
|
|
677
843
|
this.sourceState = options.source.store.get();
|
|
844
|
+
const current = () => this.building ?? this.store.get();
|
|
678
845
|
const root = {
|
|
846
|
+
get snapshot() {
|
|
847
|
+
return current().snapshot;
|
|
848
|
+
},
|
|
849
|
+
get view() {
|
|
850
|
+
return current().view;
|
|
851
|
+
},
|
|
852
|
+
get version() {
|
|
853
|
+
return current().version;
|
|
854
|
+
},
|
|
855
|
+
get connection() {
|
|
856
|
+
return current().connection;
|
|
857
|
+
},
|
|
858
|
+
get failure() {
|
|
859
|
+
return current().failure;
|
|
860
|
+
},
|
|
861
|
+
get request() {
|
|
862
|
+
return current().request;
|
|
863
|
+
},
|
|
864
|
+
get state() {
|
|
865
|
+
return current().state;
|
|
866
|
+
},
|
|
867
|
+
get phase() {
|
|
868
|
+
return current().phase;
|
|
869
|
+
},
|
|
870
|
+
get turn() {
|
|
871
|
+
return current().turn;
|
|
872
|
+
},
|
|
873
|
+
get me() {
|
|
874
|
+
return current().me;
|
|
875
|
+
},
|
|
876
|
+
get players() {
|
|
877
|
+
return current().players;
|
|
878
|
+
},
|
|
879
|
+
get interactions() {
|
|
880
|
+
return current().interactions;
|
|
881
|
+
},
|
|
882
|
+
get inputs() {
|
|
883
|
+
return current().inputs;
|
|
884
|
+
},
|
|
885
|
+
get zones() {
|
|
886
|
+
return current().zones;
|
|
887
|
+
},
|
|
888
|
+
get cards() {
|
|
889
|
+
return current().cards;
|
|
890
|
+
},
|
|
891
|
+
get events() {
|
|
892
|
+
return current().events;
|
|
893
|
+
},
|
|
894
|
+
store: {
|
|
895
|
+
get: () => this.store.get(),
|
|
896
|
+
subscribe: (listener) => this.store.subscribe(listener)
|
|
897
|
+
},
|
|
679
898
|
getSnapshot: () => this.building ?? this.store.get(),
|
|
680
899
|
getOptions: () => this.options,
|
|
681
900
|
setOptions: (next) => this.setOptions(next),
|
|
@@ -684,37 +903,25 @@ var Controller = class {
|
|
|
684
903
|
return () => sub.unsubscribe();
|
|
685
904
|
},
|
|
686
905
|
dispose: () => this.dispose(),
|
|
687
|
-
assertCoverage: () => this.assertCoverage()
|
|
688
|
-
inspect: () => this.store.get()
|
|
906
|
+
assertCoverage: () => this.assertCoverage()
|
|
689
907
|
};
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
"phase",
|
|
699
|
-
"turn",
|
|
700
|
-
"me",
|
|
701
|
-
"players",
|
|
702
|
-
"interactions",
|
|
703
|
-
"inputs",
|
|
704
|
-
"zones",
|
|
705
|
-
"cards",
|
|
706
|
-
"events"
|
|
707
|
-
])
|
|
708
|
-
Object.defineProperty(root, key, {
|
|
709
|
-
get: () => (this.building ?? this.store.get())[key]
|
|
710
|
-
});
|
|
711
|
-
this.store = createStore(Object.freeze(this.build()));
|
|
712
|
-
Object.defineProperty(root, "store", {
|
|
713
|
-
value: {
|
|
714
|
-
get: () => this.store.get(),
|
|
715
|
-
subscribe: (listener) => this.store.subscribe(listener)
|
|
908
|
+
for (const [key, descriptor] of Object.entries(
|
|
909
|
+
Object.getOwnPropertyDescriptors(root)
|
|
910
|
+
)) {
|
|
911
|
+
if (descriptor.get) {
|
|
912
|
+
Object.defineProperty(root, key, {
|
|
913
|
+
enumerable: false,
|
|
914
|
+
configurable: false
|
|
915
|
+
});
|
|
716
916
|
}
|
|
917
|
+
}
|
|
918
|
+
Object.defineProperty(root, "store", {
|
|
919
|
+
enumerable: false,
|
|
920
|
+
configurable: false,
|
|
921
|
+
writable: false
|
|
717
922
|
});
|
|
923
|
+
this.instance = root;
|
|
924
|
+
this.store = createStore(Object.freeze(this.build()));
|
|
718
925
|
if ("apply" in options.source)
|
|
719
926
|
Object.defineProperty(root, "apply", {
|
|
720
927
|
value: (action) => this.options.source.apply(action)
|
|
@@ -723,18 +930,24 @@ var Controller = class {
|
|
|
723
930
|
Object.defineProperty(root, "explore", {
|
|
724
931
|
value: (...args) => this.options.source.explore(...args)
|
|
725
932
|
});
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
933
|
+
const runtime = {
|
|
934
|
+
game: this.instance,
|
|
935
|
+
getBoards: () => this.getBoardProjections(this.instance.view),
|
|
936
|
+
createBoard: (data2) => this.object("board", {
|
|
937
|
+
id: data2.id,
|
|
729
938
|
data: immutableCopy(data2),
|
|
730
939
|
game: this.instance
|
|
731
940
|
}),
|
|
732
|
-
routeTarget: (
|
|
941
|
+
routeTarget: (target, options2) => this.routeTarget(target, options2),
|
|
942
|
+
getCanDropCard: (cardId, target) => this.prepareCardDrop(cardId, target) !== void 0,
|
|
733
943
|
routeCardDrop: (cardId, target) => this.routeCardDrop(cardId, target),
|
|
944
|
+
getCanDropCardGroup: (cardIds, target) => !!this.prepareCardDrop(cardIds, target),
|
|
945
|
+
routeCardGroupDrop: (cardIds, target) => this.routeCardDrop(cardIds, target),
|
|
734
946
|
invalidate: () => {
|
|
735
947
|
if (!this.disposed) this.refresh();
|
|
736
948
|
}
|
|
737
949
|
};
|
|
950
|
+
const context = { ...runtime, [runtimeFeatures]: runtime };
|
|
738
951
|
for (const feature of Object.values(
|
|
739
952
|
features?.(this.instance, context) ?? {}
|
|
740
953
|
)) {
|
|
@@ -762,6 +975,7 @@ var Controller = class {
|
|
|
762
975
|
}
|
|
763
976
|
this.refresh();
|
|
764
977
|
this.subscribeSource();
|
|
978
|
+
this.reportSourceFailure();
|
|
765
979
|
}
|
|
766
980
|
install(target, extension) {
|
|
767
981
|
if (!extension) return;
|
|
@@ -796,7 +1010,8 @@ var Controller = class {
|
|
|
796
1010
|
);
|
|
797
1011
|
}
|
|
798
1012
|
object(hook, value) {
|
|
799
|
-
|
|
1013
|
+
Object.setPrototypeOf(value, this.prototypes[hook]);
|
|
1014
|
+
return Object.freeze(value);
|
|
800
1015
|
}
|
|
801
1016
|
drafts() {
|
|
802
1017
|
if (this.options.state && this.options.state === this.invalidControlledState)
|
|
@@ -833,7 +1048,9 @@ var Controller = class {
|
|
|
833
1048
|
const card = draft[input.key];
|
|
834
1049
|
if (input.domain.type !== "cardTarget" || typeof card !== "string")
|
|
835
1050
|
continue;
|
|
836
|
-
for (const zone of Object.values(zones)
|
|
1051
|
+
for (const zone of Object.values(zones).flatMap(
|
|
1052
|
+
(hosts) => Object.values(hosts)
|
|
1053
|
+
)) {
|
|
837
1054
|
const route = zone.playableByCardId[card]?.find(
|
|
838
1055
|
(value) => value.interactionKey === base.interactionKey
|
|
839
1056
|
);
|
|
@@ -843,7 +1060,7 @@ var Controller = class {
|
|
|
843
1060
|
return base;
|
|
844
1061
|
}
|
|
845
1062
|
current(key) {
|
|
846
|
-
return this.store.get().interactions.
|
|
1063
|
+
return this.store.get().interactions.find(key);
|
|
847
1064
|
}
|
|
848
1065
|
editable(key) {
|
|
849
1066
|
const interaction = this.current(key);
|
|
@@ -851,11 +1068,11 @@ var Controller = class {
|
|
|
851
1068
|
}
|
|
852
1069
|
setInput(key, inputKey, value) {
|
|
853
1070
|
const interaction = this.editable(key);
|
|
854
|
-
if (!interaction?.
|
|
1071
|
+
if (!interaction?.findInput(inputKey)) return;
|
|
855
1072
|
this.writeDraft(key, { ...this.drafts()[key], [inputKey]: value });
|
|
856
1073
|
}
|
|
857
1074
|
clearInput(key, inputKey) {
|
|
858
|
-
if (!this.editable(key)?.
|
|
1075
|
+
if (!this.editable(key)?.findInput(inputKey)) return;
|
|
859
1076
|
const value = { ...this.drafts()[key] };
|
|
860
1077
|
delete value[inputKey];
|
|
861
1078
|
this.writeDraft(key, value);
|
|
@@ -869,9 +1086,9 @@ var Controller = class {
|
|
|
869
1086
|
const interaction = route ?? this.editable(key);
|
|
870
1087
|
if (route && (!route.currentLifetime() || !route.getIsAvailable() || this.sourceState.connection !== "ready" || this.sourceState.request || this.pending))
|
|
871
1088
|
return;
|
|
872
|
-
const input = interaction?.
|
|
1089
|
+
const input = interaction?.findInput(inputKey);
|
|
873
1090
|
if (!interaction || !input?.getIsEligible(value)) return;
|
|
874
|
-
if (typeof value === "string" && (input.descriptor.domain.type === "cardTarget" || input.descriptor.domain.type === "boardTarget")) {
|
|
1091
|
+
if (isManyInput(input.descriptor) || typeof value === "string" && (input.descriptor.domain.type === "cardTarget" || input.descriptor.domain.type === "boardTarget" || input.descriptor.domain.type === "tileTarget")) {
|
|
875
1092
|
let next = { ...this.drafts()[key] };
|
|
876
1093
|
routeInteractionTarget(
|
|
877
1094
|
{
|
|
@@ -891,61 +1108,67 @@ var Controller = class {
|
|
|
891
1108
|
this.activate(key);
|
|
892
1109
|
const current = this.current(key);
|
|
893
1110
|
if (current && Object.hasOwn(this.drafts()[key] ?? EMPTY, inputKey) && shouldAutoSubmitInteraction(current.descriptor) && current.getIsReady())
|
|
894
|
-
this.handle(this.submit(key, false));
|
|
895
|
-
}
|
|
896
|
-
selectCard(id, explicit) {
|
|
897
|
-
const card = this.store.get().cards.get(id);
|
|
898
|
-
if (!card) return;
|
|
899
|
-
let routes = card.routes.filter(
|
|
900
|
-
(route2) => route2.getIsAvailable() && route2.currentLifetime() && !this.sourceState.request && this.sourceState.connection === "ready" && route2.getInputs().some(
|
|
901
|
-
(input2) => input2.descriptor.domain.type === "cardTarget" && input2.getIsEligible(id)
|
|
902
|
-
)
|
|
903
|
-
);
|
|
904
|
-
if (explicit) routes = routes.filter((route2) => route2.key === explicit);
|
|
905
|
-
if (routes.length > 1) throw new AmbiguousTargetError(id);
|
|
906
|
-
const route = routes[0];
|
|
907
|
-
const input = route?.getInputs().find(
|
|
908
|
-
(input2) => input2.descriptor.domain.type === "cardTarget" && input2.getIsEligible(id)
|
|
909
|
-
);
|
|
910
|
-
if (route && input) this.select(route.key, input.key, id, route);
|
|
1111
|
+
this.handle(() => this.submit(key, false));
|
|
911
1112
|
}
|
|
912
|
-
|
|
913
|
-
|
|
1113
|
+
selectCard(id, options) {
|
|
1114
|
+
const card = this.store.get().cards.find(id);
|
|
1115
|
+
if (!card || this.sourceState.request || this.sourceState.connection !== "ready")
|
|
1116
|
+
return;
|
|
1117
|
+
const candidates = card.routes.flatMap((interaction) => {
|
|
1118
|
+
if (!interaction.getIsAvailable() || !interaction.currentLifetime() || options?.interaction && interaction.key !== options.interaction)
|
|
1119
|
+
return [];
|
|
1120
|
+
return interaction.getInputs().filter(
|
|
1121
|
+
(input) => input.descriptor.domain.type === "cardTarget" && (!options?.input || input.key === options.input) && input.getIsEligible(id)
|
|
1122
|
+
).map((input) => ({ interaction, input }));
|
|
1123
|
+
});
|
|
1124
|
+
if (candidates.length > 1) throw new AmbiguousTargetError(id);
|
|
1125
|
+
const candidate = candidates[0];
|
|
1126
|
+
if (candidate)
|
|
1127
|
+
this.select(
|
|
1128
|
+
candidate.interaction.key,
|
|
1129
|
+
candidate.input.key,
|
|
1130
|
+
id,
|
|
1131
|
+
candidate.interaction
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
handle(submit) {
|
|
1135
|
+
const source = this.options.source;
|
|
1136
|
+
const active = () => !this.disposed && this.options.source === source;
|
|
1137
|
+
void submit().then(
|
|
914
1138
|
(result) => {
|
|
915
|
-
if (!result.accepted)
|
|
1139
|
+
if (active() && !result.accepted)
|
|
916
1140
|
this.options.onError?.(
|
|
917
1141
|
new Error(result.message ?? result.errorCode, { cause: result })
|
|
918
1142
|
);
|
|
919
1143
|
},
|
|
920
|
-
(error) =>
|
|
1144
|
+
(error) => {
|
|
1145
|
+
if (!active()) return;
|
|
1146
|
+
const state = source.store.get();
|
|
1147
|
+
if (state.failure === error || state.connection === "closed") return;
|
|
1148
|
+
this.options.onError?.(error);
|
|
1149
|
+
}
|
|
921
1150
|
);
|
|
922
1151
|
}
|
|
923
|
-
async submit(key, cancel) {
|
|
1152
|
+
async submit(key, cancel, params) {
|
|
924
1153
|
const source = this.options.source;
|
|
925
1154
|
const interaction = this.current(key);
|
|
926
1155
|
if (this.disposed || this.sourceState.connection !== "ready" || this.sourceState.request || this.pending)
|
|
927
1156
|
return { accepted: false, errorCode: "BUSY" };
|
|
928
1157
|
if (!interaction) return { accepted: false, errorCode: "UNAVAILABLE" };
|
|
929
|
-
if (cancel ? !interaction.getStep()?.canCancel : !interaction.getIsAvailable() || !interaction.getIsReady())
|
|
1158
|
+
if (cancel ? !interaction.getStep()?.canCancel : !interaction.getIsAvailable(params) || !interaction.getIsReady(params))
|
|
930
1159
|
return { accepted: false, errorCode: "UNAVAILABLE" };
|
|
931
1160
|
if (!("submit" in source) || !("cancel" in source))
|
|
932
1161
|
return { accepted: false, errorCode: "READ_ONLY" };
|
|
933
1162
|
const commandSource = source;
|
|
934
1163
|
const operation = {
|
|
935
|
-
source,
|
|
936
1164
|
key,
|
|
937
|
-
|
|
938
|
-
revision: this.revisions.get(key) ?? 0,
|
|
939
|
-
version: this.sourceState.snapshot.version,
|
|
940
|
-
epoch: this.epoch,
|
|
941
|
-
accepted: false,
|
|
942
|
-
cancel
|
|
1165
|
+
draftRevision: params === void 0 ? this.revisions.get(key) ?? 0 : void 0
|
|
943
1166
|
};
|
|
944
1167
|
this.pending = operation;
|
|
945
1168
|
try {
|
|
946
1169
|
const result = await (cancel ? commandSource.cancel(interaction.id) : commandSource.submit(
|
|
947
1170
|
interaction.id,
|
|
948
|
-
interaction.readiness().values
|
|
1171
|
+
interaction.readiness(params).values
|
|
949
1172
|
));
|
|
950
1173
|
if (this.pending !== operation) return result;
|
|
951
1174
|
if (!result.accepted) {
|
|
@@ -953,8 +1176,10 @@ var Controller = class {
|
|
|
953
1176
|
this.refresh();
|
|
954
1177
|
return result;
|
|
955
1178
|
}
|
|
956
|
-
|
|
957
|
-
this.
|
|
1179
|
+
this.pending = null;
|
|
1180
|
+
if (operation.draftRevision !== void 0 && (this.revisions.get(operation.key) ?? 0) === operation.draftRevision)
|
|
1181
|
+
this.reset(operation.key);
|
|
1182
|
+
else this.refresh();
|
|
958
1183
|
return result;
|
|
959
1184
|
} catch (error) {
|
|
960
1185
|
if (this.pending === operation) {
|
|
@@ -964,21 +1189,12 @@ var Controller = class {
|
|
|
964
1189
|
throw error;
|
|
965
1190
|
}
|
|
966
1191
|
}
|
|
967
|
-
|
|
968
|
-
const
|
|
969
|
-
if (!
|
|
970
|
-
return;
|
|
971
|
-
if (this.sourceState.connection === "closed") {
|
|
972
|
-
this.pending = null;
|
|
973
|
-
this.refresh();
|
|
974
|
-
return;
|
|
975
|
-
}
|
|
976
|
-
if (this.sourceState.connection !== "ready" || (this.sourceState.snapshot?.version ?? -1) <= operation.version)
|
|
1192
|
+
reportSourceFailure() {
|
|
1193
|
+
const failure = this.sourceState.failure;
|
|
1194
|
+
if (!failure || !this.options.onError || this.reportedFailures.get(this.options.source) === failure)
|
|
977
1195
|
return;
|
|
978
|
-
this.
|
|
979
|
-
|
|
980
|
-
this.reset(operation.key);
|
|
981
|
-
else this.refresh();
|
|
1196
|
+
this.reportedFailures.set(this.options.source, failure);
|
|
1197
|
+
this.options.onError?.(failure);
|
|
982
1198
|
}
|
|
983
1199
|
subscribeSource() {
|
|
984
1200
|
const source = this.options.source;
|
|
@@ -998,7 +1214,7 @@ var Controller = class {
|
|
|
998
1214
|
}
|
|
999
1215
|
this.reconcile();
|
|
1000
1216
|
this.refresh();
|
|
1001
|
-
this.
|
|
1217
|
+
this.reportSourceFailure();
|
|
1002
1218
|
});
|
|
1003
1219
|
}
|
|
1004
1220
|
setOptions(next) {
|
|
@@ -1022,6 +1238,7 @@ var Controller = class {
|
|
|
1022
1238
|
}
|
|
1023
1239
|
this.reconcile();
|
|
1024
1240
|
this.refresh();
|
|
1241
|
+
this.reportSourceFailure();
|
|
1025
1242
|
}
|
|
1026
1243
|
reconcile() {
|
|
1027
1244
|
const snapshot = this.sourceState.snapshot;
|
|
@@ -1047,11 +1264,12 @@ var Controller = class {
|
|
|
1047
1264
|
for (const [name, value] of Object.entries(values)) {
|
|
1048
1265
|
const input = descriptor.inputs.find((input2) => input2.key === name);
|
|
1049
1266
|
if (!input && this.pending?.key === key) continue;
|
|
1050
|
-
|
|
1267
|
+
const selection = input && inputSelection(input);
|
|
1268
|
+
if (input && selection?.mode === "many" && Array.isArray(value)) {
|
|
1051
1269
|
const eligible = value.filter(
|
|
1052
|
-
(item) =>
|
|
1270
|
+
(item) => inputTargetInDomain(input.domain, item)
|
|
1053
1271
|
);
|
|
1054
|
-
const max =
|
|
1272
|
+
const max = selection.max;
|
|
1055
1273
|
const retained = max === void 0 ? eligible : eligible.slice(0, max);
|
|
1056
1274
|
if (retained.length !== value.length) {
|
|
1057
1275
|
kept[name] = retained;
|
|
@@ -1068,7 +1286,13 @@ var Controller = class {
|
|
|
1068
1286
|
}
|
|
1069
1287
|
if (fieldChanged) next[key] = immutableValues(kept);
|
|
1070
1288
|
}
|
|
1071
|
-
if (changed)
|
|
1289
|
+
if (changed) {
|
|
1290
|
+
const operation = this.pending;
|
|
1291
|
+
const submitted = operation?.draftRevision !== void 0 && (this.revisions.get(operation.key) ?? 0) === operation.draftRevision;
|
|
1292
|
+
this.writeDrafts(Object.freeze(next));
|
|
1293
|
+
if (submitted)
|
|
1294
|
+
operation.draftRevision = this.revisions.get(operation.key);
|
|
1295
|
+
}
|
|
1072
1296
|
const active = this.active();
|
|
1073
1297
|
if (active && !snapshot.frame.availableInteractions.some(
|
|
1074
1298
|
(value) => value.interactionKey === active
|
|
@@ -1082,11 +1306,7 @@ var Controller = class {
|
|
|
1082
1306
|
this.subscription?.unsubscribe();
|
|
1083
1307
|
this.pending = null;
|
|
1084
1308
|
this.options.source.dispose();
|
|
1085
|
-
this.sourceState =
|
|
1086
|
-
...this.sourceState,
|
|
1087
|
-
connection: "closed",
|
|
1088
|
-
request: null
|
|
1089
|
-
};
|
|
1309
|
+
this.sourceState = this.options.source.store.get();
|
|
1090
1310
|
this.refresh();
|
|
1091
1311
|
}
|
|
1092
1312
|
assertCoverage() {
|
|
@@ -1096,63 +1316,98 @@ var Controller = class {
|
|
|
1096
1316
|
if (missing.length)
|
|
1097
1317
|
throw new Error(`Interactions not read: ${missing.join(", ")}`);
|
|
1098
1318
|
}
|
|
1099
|
-
routeTarget(
|
|
1100
|
-
if (kind === "card") {
|
|
1101
|
-
this.selectCard(
|
|
1319
|
+
routeTarget(target, options) {
|
|
1320
|
+
if (target.kind === "card") {
|
|
1321
|
+
this.selectCard(target.value, options);
|
|
1102
1322
|
return;
|
|
1103
1323
|
}
|
|
1104
1324
|
const candidates = this.lastInteractions.flatMap(
|
|
1105
1325
|
(interaction) => interaction.getInputs().filter(
|
|
1106
|
-
(input) =>
|
|
1326
|
+
(input) => (target.kind === "tile" ? input.descriptor.domain.type === "tileTarget" : matchesBoardTarget(input.descriptor, target)) && input.getIsEligible(target.value) && interaction.getIsAvailable() && (!options?.interaction || interaction.key === options.interaction) && (!options?.input || input.key === options.input)
|
|
1107
1327
|
).map((input) => ({ interaction, input }))
|
|
1108
1328
|
);
|
|
1109
|
-
if (candidates.length > 1)
|
|
1329
|
+
if (candidates.length > 1)
|
|
1330
|
+
throw new AmbiguousTargetError(JSON.stringify(target.value));
|
|
1110
1331
|
const match = candidates[0];
|
|
1111
|
-
if (match)
|
|
1332
|
+
if (match)
|
|
1333
|
+
this.select(match.interaction.key, match.input.key, target.value);
|
|
1112
1334
|
}
|
|
1113
|
-
|
|
1335
|
+
prepareCardDrop(cards, target) {
|
|
1114
1336
|
if (this.disposed || this.sourceState.request || this.pending || this.sourceState.connection !== "ready")
|
|
1115
1337
|
return;
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
const
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
return
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1338
|
+
const group = typeof cards !== "string";
|
|
1339
|
+
const cardIds = group ? cards : [cards];
|
|
1340
|
+
const cardId = cardIds[0];
|
|
1341
|
+
if (!cardId || new Set(cardIds).size !== cardIds.length) return;
|
|
1342
|
+
const card = this.store.get().cards.find(cardId);
|
|
1343
|
+
const interaction = card?.routes.find(
|
|
1344
|
+
(route) => route.key === target.interactionKey
|
|
1345
|
+
);
|
|
1346
|
+
if (!interaction?.currentLifetime() || !interaction.getIsAvailable())
|
|
1347
|
+
return;
|
|
1348
|
+
const cardInput = interaction.findInput(target.cardInputKey);
|
|
1349
|
+
if (!cardInput || cardInput.descriptor.domain.type !== "cardTarget" || (group ? !isManyInput(cardInput.descriptor) || !inputValueInDomain(
|
|
1350
|
+
cardInput.descriptor.domain,
|
|
1351
|
+
cardIds,
|
|
1352
|
+
cardInput.descriptor.domain.selection
|
|
1353
|
+
) : !cardInput.getIsEligible(cardId)))
|
|
1354
|
+
return;
|
|
1355
|
+
const input = target.kind === "interaction" ? void 0 : interaction.findInput(target.inputKey);
|
|
1356
|
+
if (target.kind !== "interaction" && (!input || !(target.kind === "position" ? input.descriptor.domain.type === "zonePosition" : matchesBoardTarget(input.descriptor, target)) || !input.getIsEligible(target.value)))
|
|
1357
|
+
return;
|
|
1358
|
+
const params = target.kind === "interaction" ? target.params : void 0;
|
|
1359
|
+
const submitBound = group || params !== void 0 || target.kind === "position";
|
|
1360
|
+
if (params && Object.keys(params).some(
|
|
1361
|
+
(key) => key === cardInput.key || !interaction.findInput(key)
|
|
1362
|
+
))
|
|
1363
|
+
return;
|
|
1364
|
+
let next = {
|
|
1365
|
+
...this.drafts()[interaction.key],
|
|
1366
|
+
...params
|
|
1367
|
+
};
|
|
1368
|
+
const chosen = next[cardInput.key];
|
|
1369
|
+
const alreadyChosen = !submitBound && !input && isManyInput(cardInput.descriptor) && Array.isArray(chosen) && chosen.includes(cardId);
|
|
1370
|
+
if (submitBound) {
|
|
1371
|
+
next[cardInput.key] = isManyInput(cardInput.descriptor) ? [...cardIds] : cardId;
|
|
1372
|
+
if (input && target.kind !== "interaction")
|
|
1373
|
+
next[input.key] = target.value;
|
|
1374
|
+
} else if (!alreadyChosen)
|
|
1375
|
+
routeCardInputIntent(
|
|
1376
|
+
{
|
|
1377
|
+
getDraft: () => next,
|
|
1378
|
+
setInput: (_key, key, value) => {
|
|
1379
|
+
next = { ...next, [key]: value };
|
|
1380
|
+
},
|
|
1381
|
+
clearInput: (_key, key) => {
|
|
1382
|
+
if (key) delete next[key];
|
|
1383
|
+
}
|
|
1139
1384
|
},
|
|
1140
|
-
|
|
1141
|
-
|
|
1385
|
+
interaction.descriptor,
|
|
1386
|
+
{
|
|
1387
|
+
cardId,
|
|
1388
|
+
cardInputKey: cardInput.key,
|
|
1389
|
+
dropTarget: input && target.kind !== "interaction" ? { inputKey: input.key, value: target.value } : void 0
|
|
1142
1390
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1391
|
+
);
|
|
1392
|
+
if (submitBound && (!interaction.getIsAvailable(next) || !interaction.getIsReady(next)))
|
|
1393
|
+
return;
|
|
1394
|
+
return {
|
|
1395
|
+
interaction,
|
|
1396
|
+
next,
|
|
1397
|
+
alreadyChosen,
|
|
1398
|
+
submitBound
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
routeCardDrop(cardId, target) {
|
|
1402
|
+
const prepared = this.prepareCardDrop(cardId, target);
|
|
1403
|
+
if (!prepared) return;
|
|
1404
|
+
const { interaction, next, alreadyChosen, submitBound } = prepared;
|
|
1405
|
+
if (!alreadyChosen || target.kind === "interaction" && target.params !== void 0)
|
|
1406
|
+
this.writeDraft(interaction.key, next);
|
|
1152
1407
|
this.activate(interaction.key);
|
|
1153
1408
|
const current = this.current(interaction.key);
|
|
1154
|
-
if (current && shouldAutoSubmitInteraction(current.descriptor) && current.getIsReady())
|
|
1155
|
-
this.handle(current.submit());
|
|
1409
|
+
if (!alreadyChosen && current && (submitBound || shouldAutoSubmitInteraction(current.descriptor)) && current.getIsReady())
|
|
1410
|
+
this.handle(() => current.submit());
|
|
1156
1411
|
}
|
|
1157
1412
|
warnUnread() {
|
|
1158
1413
|
const environment = import.meta.env?.DEV ?? globalThis.process?.env?.NODE_ENV === "development";
|
|
@@ -1170,7 +1425,7 @@ var Controller = class {
|
|
|
1170
1425
|
}
|
|
1171
1426
|
build(previous) {
|
|
1172
1427
|
this.trackDrafts();
|
|
1173
|
-
const { snapshot, connection, request } = this.sourceState;
|
|
1428
|
+
const { snapshot, connection, request, failure } = this.sourceState;
|
|
1174
1429
|
const old = previous?.snapshot;
|
|
1175
1430
|
const drafts = this.drafts();
|
|
1176
1431
|
const active = this.active();
|
|
@@ -1193,11 +1448,20 @@ var Controller = class {
|
|
|
1193
1448
|
);
|
|
1194
1449
|
const players = sameActors && previous ? previous.players : {
|
|
1195
1450
|
order: Object.freeze(playerObjects.map((p) => p.id)),
|
|
1196
|
-
get: (id) =>
|
|
1451
|
+
get: (id) => requireLookup(
|
|
1452
|
+
playerObjects.find((p) => p.id === id),
|
|
1453
|
+
"Player",
|
|
1454
|
+
id
|
|
1455
|
+
),
|
|
1456
|
+
find: (id) => playerObjects.find((p) => p.id === id),
|
|
1197
1457
|
getAll: () => playerObjects,
|
|
1198
1458
|
next: (id) => {
|
|
1199
1459
|
const index = playerObjects.findIndex((p) => p.id === id);
|
|
1200
|
-
return
|
|
1460
|
+
return requireLookup(
|
|
1461
|
+
index < 0 ? void 0 : playerObjects[(index + 1) % playerObjects.length],
|
|
1462
|
+
"Next player after",
|
|
1463
|
+
id
|
|
1464
|
+
);
|
|
1201
1465
|
}
|
|
1202
1466
|
};
|
|
1203
1467
|
const me = sameActors && previous ? previous.me : snapshot ? {
|
|
@@ -1212,7 +1476,7 @@ var Controller = class {
|
|
|
1212
1476
|
const key = base.interactionKey;
|
|
1213
1477
|
const draft = drafts[key] ?? EMPTY;
|
|
1214
1478
|
const descriptor = this.descriptorFor(base, draft);
|
|
1215
|
-
const status = request || this.pending ?
|
|
1479
|
+
const status = request || this.pending ? "submitting" : "open";
|
|
1216
1480
|
const oldObject = this.lastInteractions.find(
|
|
1217
1481
|
(value) => value.key === key
|
|
1218
1482
|
);
|
|
@@ -1233,6 +1497,14 @@ var Controller = class {
|
|
|
1233
1497
|
this.lastInteractions = Object.freeze(interactionObjects);
|
|
1234
1498
|
const interactions = interactionsSame ? previous.interactions : {
|
|
1235
1499
|
get: (key) => {
|
|
1500
|
+
this.observed.add(key);
|
|
1501
|
+
return requireLookup(
|
|
1502
|
+
interactionObjects.find((value) => value.key === key),
|
|
1503
|
+
"Interaction",
|
|
1504
|
+
key
|
|
1505
|
+
);
|
|
1506
|
+
},
|
|
1507
|
+
find: (key) => {
|
|
1236
1508
|
this.observed.add(key);
|
|
1237
1509
|
return interactionObjects.find((value) => value.key === key);
|
|
1238
1510
|
},
|
|
@@ -1249,37 +1521,65 @@ var Controller = class {
|
|
|
1249
1521
|
}
|
|
1250
1522
|
};
|
|
1251
1523
|
const inputs = interactionsSame && previous ? previous.inputs : {
|
|
1252
|
-
get: (key, name) =>
|
|
1524
|
+
get: (key, name) => requireLookup(
|
|
1525
|
+
interactions.find(key)?.findInput(name),
|
|
1526
|
+
`Input in interaction ${key}`,
|
|
1527
|
+
name
|
|
1528
|
+
),
|
|
1529
|
+
find: (key, name) => interactions.find(key)?.findInput(name)
|
|
1253
1530
|
};
|
|
1254
1531
|
const zonesSame = sameSnapshot && interactionsSame && previous;
|
|
1255
1532
|
const zones = zonesSame ? previous.zones : (() => {
|
|
1256
|
-
const objects = Object.entries(snapshot?.frame.zones ?? {}).
|
|
1257
|
-
([id,
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1533
|
+
const objects = Object.entries(snapshot?.frame.zones ?? {}).flatMap(
|
|
1534
|
+
([id, hosts]) => Object.entries(hosts).map(
|
|
1535
|
+
([hostId, zone]) => this.object(
|
|
1536
|
+
"zone",
|
|
1537
|
+
new ZoneObject(
|
|
1538
|
+
this,
|
|
1539
|
+
id,
|
|
1540
|
+
hostId,
|
|
1541
|
+
Object.freeze(
|
|
1542
|
+
zone.cardIds.map(
|
|
1543
|
+
(cardId, index) => this.object(
|
|
1544
|
+
"card",
|
|
1545
|
+
new CardObject(
|
|
1546
|
+
this,
|
|
1547
|
+
cardId,
|
|
1548
|
+
id,
|
|
1549
|
+
hostId,
|
|
1550
|
+
index,
|
|
1551
|
+
zone.cardViewsById[cardId],
|
|
1552
|
+
zone.cardBacksById[cardId],
|
|
1553
|
+
Object.freeze(
|
|
1554
|
+
(zone.playableByCardId[cardId] ?? []).map(
|
|
1555
|
+
(descriptor) => this.object(
|
|
1556
|
+
"interaction",
|
|
1557
|
+
new InteractionObject(
|
|
1558
|
+
this,
|
|
1559
|
+
descriptor,
|
|
1560
|
+
drafts[descriptor.interactionKey] ?? EMPTY,
|
|
1561
|
+
request || this.pending ? "submitting" : "open",
|
|
1562
|
+
connection === "ready"
|
|
1563
|
+
)
|
|
1564
|
+
)
|
|
1565
|
+
)
|
|
1566
|
+
)
|
|
1567
|
+
)
|
|
1568
|
+
)
|
|
1569
|
+
)
|
|
1570
|
+
),
|
|
1571
|
+
Object.freeze(
|
|
1572
|
+
zone.tiles.map(
|
|
1573
|
+
(tile, index) => new TileObject(
|
|
1267
1574
|
this,
|
|
1268
|
-
|
|
1575
|
+
tile,
|
|
1269
1576
|
id,
|
|
1577
|
+
hostId,
|
|
1270
1578
|
index,
|
|
1271
|
-
zone.cardViewsById[cardId],
|
|
1272
1579
|
Object.freeze(
|
|
1273
|
-
|
|
1274
|
-
(
|
|
1275
|
-
"
|
|
1276
|
-
new InteractionObject(
|
|
1277
|
-
this,
|
|
1278
|
-
descriptor,
|
|
1279
|
-
drafts[descriptor.interactionKey] ?? EMPTY,
|
|
1280
|
-
request || this.pending ? "submitting" : "open",
|
|
1281
|
-
connection === "ready"
|
|
1282
|
-
)
|
|
1580
|
+
interactionObjects.filter(
|
|
1581
|
+
(interaction) => interaction.getInputs().some(
|
|
1582
|
+
(input) => input.descriptor.domain.type === "tileTarget" && input.getIsEligible(tile.ref)
|
|
1283
1583
|
)
|
|
1284
1584
|
)
|
|
1285
1585
|
)
|
|
@@ -1290,13 +1590,20 @@ var Controller = class {
|
|
|
1290
1590
|
)
|
|
1291
1591
|
)
|
|
1292
1592
|
);
|
|
1593
|
+
const find = (id, hostId) => objects.find((zone) => zone.id === id && zone.hostId === hostId);
|
|
1293
1594
|
return {
|
|
1294
|
-
get: (id) =>
|
|
1595
|
+
get: (id, hostId) => requireLookup(find(id, hostId), "Zone", id),
|
|
1596
|
+
find,
|
|
1295
1597
|
getAll: () => Object.freeze(objects)
|
|
1296
1598
|
};
|
|
1297
1599
|
})();
|
|
1298
1600
|
const cards = zonesSame ? previous.cards : {
|
|
1299
|
-
get: (id) =>
|
|
1601
|
+
get: (id) => requireLookup(
|
|
1602
|
+
zones.getAll().map((zone) => zone.findCard(id)).find((card) => card !== void 0),
|
|
1603
|
+
"Card",
|
|
1604
|
+
id
|
|
1605
|
+
),
|
|
1606
|
+
find: (id) => zones.getAll().map((zone) => zone.findCard(id)).find((card) => card !== void 0)
|
|
1300
1607
|
};
|
|
1301
1608
|
const capturedState = this.lastSnapshotDrafts === drafts && previous?.state.activeInteraction === active ? previous.state : immutableCopy({ drafts, activeInteraction: active });
|
|
1302
1609
|
this.lastSnapshotDrafts = drafts;
|
|
@@ -1308,6 +1615,7 @@ var Controller = class {
|
|
|
1308
1615
|
version: snapshot?.version ?? null,
|
|
1309
1616
|
connection,
|
|
1310
1617
|
request,
|
|
1618
|
+
failure,
|
|
1311
1619
|
state: capturedState,
|
|
1312
1620
|
phase: sameSnapshot && previous ? previous.phase : Object.freeze(
|
|
1313
1621
|
new PhaseObject(snapshot?.frame.flow.currentPhase ?? null)
|
|
@@ -1356,8 +1664,109 @@ function createGameInstance() {
|
|
|
1356
1664
|
};
|
|
1357
1665
|
}
|
|
1358
1666
|
|
|
1667
|
+
// src/headless/gesture.ts
|
|
1668
|
+
var GESTURE_THRESHOLDS = Object.freeze({
|
|
1669
|
+
/** Movement, in CSS pixels, before a press becomes a drag or a browse. */
|
|
1670
|
+
slop: 8,
|
|
1671
|
+
/** A touch or pen held this long without moving inspects the card. */
|
|
1672
|
+
holdMs: 350,
|
|
1673
|
+
/** A mouse resting on a card this long inspects it. */
|
|
1674
|
+
hoverMs: 250,
|
|
1675
|
+
/** A finger drags when vertical travel exceeds this multiple of sideways travel. */
|
|
1676
|
+
axisBias: 1.25
|
|
1677
|
+
});
|
|
1678
|
+
function createGestureRecognizer(down, callbacks, options = {}) {
|
|
1679
|
+
const origin = { x: down.clientX, y: down.clientY };
|
|
1680
|
+
const mouse = down.pointerType === "mouse";
|
|
1681
|
+
let kind = "tap";
|
|
1682
|
+
let finished = false;
|
|
1683
|
+
let pull = origin;
|
|
1684
|
+
const holdTimer = mouse ? void 0 : setTimeout(() => {
|
|
1685
|
+
kind = "hold";
|
|
1686
|
+
callbacks.hold();
|
|
1687
|
+
}, GESTURE_THRESHOLDS.holdMs);
|
|
1688
|
+
const point = (event) => ({
|
|
1689
|
+
x: event.clientX,
|
|
1690
|
+
y: event.clientY
|
|
1691
|
+
});
|
|
1692
|
+
const ours = (event) => !finished && (!event || event.pointerId === down.pointerId);
|
|
1693
|
+
function finish() {
|
|
1694
|
+
finished = true;
|
|
1695
|
+
clearTimeout(holdTimer);
|
|
1696
|
+
}
|
|
1697
|
+
const steep = (from, to) => Math.abs(to.y - from.y) > GESTURE_THRESHOLDS.axisBias * Math.abs(to.x - from.x);
|
|
1698
|
+
function drag(from, to) {
|
|
1699
|
+
if (!callbacks.dragStart(from)) return false;
|
|
1700
|
+
kind = "drag";
|
|
1701
|
+
callbacks.dragMove(to);
|
|
1702
|
+
return true;
|
|
1703
|
+
}
|
|
1704
|
+
return {
|
|
1705
|
+
move(event) {
|
|
1706
|
+
if (!ours(event)) return;
|
|
1707
|
+
const at = point(event);
|
|
1708
|
+
if (kind === "drag") return callbacks.dragMove(at);
|
|
1709
|
+
if (kind === "browse") {
|
|
1710
|
+
if (!options.scrub) return;
|
|
1711
|
+
callbacks.browse?.(at);
|
|
1712
|
+
if (!steep(pull, at) || Math.abs(at.y - pull.y) >= GESTURE_THRESHOLDS.slop && !drag(pull, at))
|
|
1713
|
+
pull = at;
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
if (kind !== "tap" || Math.hypot(at.x - origin.x, at.y - origin.y) < GESTURE_THRESHOLDS.slop)
|
|
1717
|
+
return;
|
|
1718
|
+
clearTimeout(holdTimer);
|
|
1719
|
+
const lifts = mouse || options.dragDirection === "any" || steep(origin, at);
|
|
1720
|
+
if (lifts && drag(origin, at)) return;
|
|
1721
|
+
kind = "browse";
|
|
1722
|
+
pull = at;
|
|
1723
|
+
if (options.scrub) callbacks.browse?.(at);
|
|
1724
|
+
},
|
|
1725
|
+
up(event) {
|
|
1726
|
+
if (!ours(event)) return;
|
|
1727
|
+
finish();
|
|
1728
|
+
callbacks.end(kind, point(event));
|
|
1729
|
+
},
|
|
1730
|
+
cancel(event) {
|
|
1731
|
+
if (!ours(event)) return;
|
|
1732
|
+
finish();
|
|
1733
|
+
callbacks.cancel(kind);
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
function magneticDropPoint(point, box, retained, coarse) {
|
|
1738
|
+
const radius = coarse ? retained ? 52 : 28 : retained ? 36 : 18;
|
|
1739
|
+
if (box.width <= 0 || box.height <= 0) return null;
|
|
1740
|
+
const x = Math.max(box.x, Math.min(point.x, box.x + box.width));
|
|
1741
|
+
const y = Math.max(box.y, Math.min(point.y, box.y + box.height));
|
|
1742
|
+
return Math.hypot(point.x - x, point.y - y) <= radius ? { x, y } : null;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/headless/drop-targets.ts
|
|
1746
|
+
function isSameDropTarget(left, right) {
|
|
1747
|
+
if (left.interactionKey !== right.interactionKey || left.cardInputKey !== right.cardInputKey)
|
|
1748
|
+
return false;
|
|
1749
|
+
if (left.kind === "interaction" || right.kind === "interaction")
|
|
1750
|
+
return left.kind === "interaction" && right.kind === "interaction" && sameParams(left.params, right.params);
|
|
1751
|
+
if (left.kind === "position" || right.kind === "position")
|
|
1752
|
+
return left.kind === "position" && right.kind === "position" && left.inputKey === right.inputKey && inputValueKey(left.value) === inputValueKey(right.value);
|
|
1753
|
+
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.inputKey === right.inputKey;
|
|
1754
|
+
}
|
|
1755
|
+
function sameParams(left, right) {
|
|
1756
|
+
if (left === void 0 || right === void 0) return left === right;
|
|
1757
|
+
const keys = Object.keys(left);
|
|
1758
|
+
return keys.length === Object.keys(right).length && keys.every(
|
|
1759
|
+
(key) => key in right && inputValueKey(left[key]) === inputValueKey(right[key])
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1359
1763
|
export {
|
|
1764
|
+
runtimeFeatures,
|
|
1360
1765
|
AmbiguousTargetError,
|
|
1361
|
-
createGameInstance
|
|
1766
|
+
createGameInstance,
|
|
1767
|
+
isSameDropTarget,
|
|
1768
|
+
GESTURE_THRESHOLDS,
|
|
1769
|
+
createGestureRecognizer,
|
|
1770
|
+
magneticDropPoint
|
|
1362
1771
|
};
|
|
1363
|
-
//# sourceMappingURL=chunk-
|
|
1772
|
+
//# sourceMappingURL=chunk-BVWJS3AB.js.map
|