@dreamboard-games/sdk 0.2.0 → 0.2.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ThemeProvider-fy0_QzgO.d.ts → ThemeProvider-BBMVT3KG.d.ts} +1 -1
- package/dist/attributes-BeRyboMS.d.ts +279 -0
- package/dist/browser-interaction.d.ts +708 -0
- package/dist/browser-interaction.js +106 -0
- package/dist/browser-interaction.js.map +1 -0
- package/dist/{bundle-TIZcw8LB.d.ts → bundle-CDd5FKeD.d.ts} +3 -1
- package/dist/{chunk-U5C6BONG.js → chunk-326PGVAA.js} +2 -2
- package/dist/{chunk-VFTAA4WO.js → chunk-MKXPVOUT.js} +4 -2
- package/dist/chunk-MKXPVOUT.js.map +1 -0
- package/dist/{chunk-GKKBPPSW.js → chunk-MZNVHMJ5.js} +4 -4
- package/dist/{chunk-KAELH4KC.js → chunk-NKCRKGR2.js} +2 -2
- package/dist/{chunk-WN74KVNY.js → chunk-PEI3FIL2.js} +2 -2
- package/dist/chunk-PEI3FIL2.js.map +1 -0
- package/dist/chunk-QLG6VEMW.js +1691 -0
- package/dist/chunk-QLG6VEMW.js.map +1 -0
- package/dist/{chunk-WYPQ3GG5.js → chunk-WG4JQL3S.js} +4 -1
- package/dist/{chunk-WYPQ3GG5.js.map → chunk-WG4JQL3S.js.map} +1 -1
- package/dist/{chunk-7YAHLYBR.js → chunk-XV6D3ET4.js} +8 -4
- package/dist/{chunk-7YAHLYBR.js.map → chunk-XV6D3ET4.js.map} +1 -1
- package/dist/{chunk-TDSWKVZ4.js → chunk-ZABVH7AO.js} +1236 -17
- package/dist/chunk-ZABVH7AO.js.map +1 -0
- package/dist/{components-D5ZRE2Hl.d.ts → components-BoiVSYqx.d.ts} +1 -1
- package/dist/generated/runtime/primitives.d.ts +5 -4
- package/dist/generated/runtime/primitives.js +4 -3
- package/dist/generated/runtime-api.d.ts +1 -1
- package/dist/generated/runtime.d.ts +5 -4
- package/dist/generated/runtime.js +7 -6
- package/dist/generated/workspace-contract.d.ts +5 -4
- package/dist/generated/workspace-contract.js +6 -5
- package/dist/{hex-board-view-D_07hO6O.d.ts → hex-board-view-1iAyJRFn.d.ts} +1 -0
- package/dist/index.js +1 -1
- package/dist/infrastructure/reducer-bundle-abi.d.ts +113 -113
- package/dist/infrastructure/reducer-bundle-abi.js +1 -1
- package/dist/package-set.d.ts +2 -2
- package/dist/package-set.js +1 -1
- package/dist/reducer.d.ts +1 -1
- package/dist/reducer.js +305 -12
- package/dist/reducer.js.map +1 -1
- package/dist/runtime/primitives.d.ts +6 -5
- package/dist/runtime/primitives.js +4 -3
- package/dist/runtime/workspace-contract.d.ts +6 -5
- package/dist/runtime/workspace-contract.js +6 -5
- package/dist/{runtime-api-DWxvTr-O.d.ts → runtime-api-CPLm_XDG.d.ts} +6 -0
- package/dist/runtime.d.ts +5 -4
- package/dist/runtime.js +6 -5
- package/dist/testing.d.ts +2 -2
- package/dist/ui/components.d.ts +2 -2
- package/dist/ui/components.js +1 -1
- package/dist/{ui-contract-iQfTtUSL.d.ts → ui-contract-rzKBwOLC.d.ts} +5 -3
- package/dist/ui.d.ts +5 -5
- package/dist/ui.js +2 -2
- package/package.json +15 -9
- package/src/browser-interaction/attributes.ts +211 -0
- package/src/browser-interaction/canonical.ts +77 -0
- package/src/browser-interaction/constants.ts +77 -0
- package/src/browser-interaction/effects.ts +176 -0
- package/src/browser-interaction/index.ts +111 -0
- package/src/browser-interaction/normalize.ts +997 -0
- package/src/browser-interaction/registry.ts +70 -0
- package/src/browser-interaction/resolve.ts +596 -0
- package/src/browser-interaction/schemas.ts +152 -0
- package/src/browser-interaction/types.ts +304 -0
- package/src/browser-interaction.ts +1 -0
- package/src/generated/reducer-contract/wire.ts +1 -1
- package/src/generated/reducer-contract/zod.ts +3 -1
- package/src/package-set.ts +1 -1
- package/src/reducer/bundle/ingress-bundle.ts +1 -1
- package/src/reducer/bundle/trusted/interaction-types.ts +3 -0
- package/src/reducer/bundle/trusted/projection-builder.ts +337 -13
- package/src/reducer/ingress/input-codec.ts +1 -1
- package/src/reducer/ingress/session-codec.ts +1 -1
- package/src/runtime-internal/components/InteractionForm.tsx +345 -7
- package/src/runtime-internal/components/PluginRuntime.tsx +2 -0
- package/src/runtime-internal/components/board/target-layer.ts +2 -0
- package/src/runtime-internal/context/PluginStateContext.tsx +41 -0
- package/src/runtime-internal/hooks/useBoardInteractions.ts +73 -11
- package/src/runtime-internal/primitives/board.tsx +71 -0
- package/src/runtime-internal/primitives/interaction.tsx +160 -1
- package/src/runtime-internal/types/plugin-state.ts +6 -0
- package/src/runtime-internal/utils/browser-interaction-effects.ts +240 -0
- package/src/runtime-internal/utils/interaction-draft-digest.ts +252 -0
- package/src/runtime-internal/utils/semantic-projection-digest.ts +407 -0
- package/src/ui/components/board/HexGrid.tsx +3 -0
- package/src/ui/components/board/target-layer.ts +1 -0
- package/dist/chunk-TDSWKVZ4.js.map +0 -1
- package/dist/chunk-VFTAA4WO.js.map +0 -1
- package/dist/chunk-WN74KVNY.js.map +0 -1
- /package/dist/{chunk-U5C6BONG.js.map → chunk-326PGVAA.js.map} +0 -0
- /package/dist/{chunk-GKKBPPSW.js.map → chunk-MZNVHMJ5.js.map} +0 -0
- /package/dist/{chunk-KAELH4KC.js.map → chunk-NKCRKGR2.js.map} +0 -0
|
@@ -20,6 +20,14 @@ import {
|
|
|
20
20
|
useTheme,
|
|
21
21
|
useThemeCssVars,
|
|
22
22
|
} from "../../ui.js";
|
|
23
|
+
import {
|
|
24
|
+
createGameplayActuatorAttributes,
|
|
25
|
+
createGameplayInteractionRootAttributes,
|
|
26
|
+
type GameplayActuatorAttributesInput,
|
|
27
|
+
type BrowserInteractionAttributeMap,
|
|
28
|
+
type BrowserInteractionCandidateState,
|
|
29
|
+
type GameplayBrowserInteractionIntent,
|
|
30
|
+
} from "../../browser-interaction/index.js";
|
|
23
31
|
import type {
|
|
24
32
|
DraftValidation,
|
|
25
33
|
InteractionHandle,
|
|
@@ -40,7 +48,16 @@ import {
|
|
|
40
48
|
resolveInteractionInputs,
|
|
41
49
|
toggleManyValue,
|
|
42
50
|
} from "../utils/interaction-inputs.js";
|
|
51
|
+
import { interactionDraftDigestForValues } from "../utils/interaction-draft-digest.js";
|
|
43
52
|
import { isInteractionAvailable } from "../utils/interaction-status.js";
|
|
53
|
+
import {
|
|
54
|
+
gameplayCandidateMetadata,
|
|
55
|
+
gameplayPreparationPatternsForDescriptor,
|
|
56
|
+
gameplayResourceMetadata,
|
|
57
|
+
gameplayScalarFillMetadata,
|
|
58
|
+
gameplayScalarStepMetadata,
|
|
59
|
+
gameplaySubmitMetadata,
|
|
60
|
+
} from "../utils/browser-interaction-effects.js";
|
|
44
61
|
import { useChromeSuppression, ThemedButton } from "../../ui.js";
|
|
45
62
|
|
|
46
63
|
export interface InteractionFieldRenderProps<
|
|
@@ -163,6 +180,92 @@ export interface InteractionFormProps<
|
|
|
163
180
|
}
|
|
164
181
|
|
|
165
182
|
const EMPTY_FIELD_ERRORS: readonly string[] = [];
|
|
183
|
+
const GAMEPLAY_BROWSER_SCOPE_ID = "runtime";
|
|
184
|
+
|
|
185
|
+
function gameplayInteractionRootAttributes({
|
|
186
|
+
descriptor,
|
|
187
|
+
draftValues,
|
|
188
|
+
ready,
|
|
189
|
+
available,
|
|
190
|
+
}: {
|
|
191
|
+
descriptor: InteractionDescriptor;
|
|
192
|
+
draftValues?: Readonly<Record<string, unknown>>;
|
|
193
|
+
ready: boolean;
|
|
194
|
+
available: boolean;
|
|
195
|
+
}): BrowserInteractionAttributeMap {
|
|
196
|
+
return createGameplayInteractionRootAttributes({
|
|
197
|
+
scopeId: GAMEPLAY_BROWSER_SCOPE_ID,
|
|
198
|
+
interactionKey: descriptor.interactionKey,
|
|
199
|
+
interactionId: descriptor.interactionId,
|
|
200
|
+
...(descriptor.descriptorDigest !== undefined
|
|
201
|
+
? { descriptorDigest: descriptor.descriptorDigest }
|
|
202
|
+
: {}),
|
|
203
|
+
...(descriptor.draftDigest !== undefined
|
|
204
|
+
? {
|
|
205
|
+
draftDigest: interactionDraftDigestForValues(
|
|
206
|
+
descriptor,
|
|
207
|
+
draftValues ?? {},
|
|
208
|
+
),
|
|
209
|
+
}
|
|
210
|
+
: {}),
|
|
211
|
+
readiness: available ? (ready ? "ready" : "blocked") : "unavailable",
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function gameplayActuatorAttributes({
|
|
216
|
+
descriptor,
|
|
217
|
+
inputKey,
|
|
218
|
+
intent,
|
|
219
|
+
candidateValue,
|
|
220
|
+
candidateState,
|
|
221
|
+
semanticEffects,
|
|
222
|
+
acceptedEffectPatterns,
|
|
223
|
+
preparationPatterns,
|
|
224
|
+
draftValues,
|
|
225
|
+
enabled,
|
|
226
|
+
actuatorKind,
|
|
227
|
+
actuatorId,
|
|
228
|
+
}: {
|
|
229
|
+
descriptor: InteractionDescriptor;
|
|
230
|
+
inputKey?: string;
|
|
231
|
+
intent: GameplayBrowserInteractionIntent;
|
|
232
|
+
candidateValue?: unknown;
|
|
233
|
+
candidateState?: BrowserInteractionCandidateState;
|
|
234
|
+
semanticEffects?: GameplayActuatorAttributesInput["semanticEffects"];
|
|
235
|
+
acceptedEffectPatterns?: GameplayActuatorAttributesInput["acceptedEffectPatterns"];
|
|
236
|
+
preparationPatterns?: GameplayActuatorAttributesInput["preparationPatterns"];
|
|
237
|
+
draftValues?: Readonly<Record<string, unknown>>;
|
|
238
|
+
enabled: boolean;
|
|
239
|
+
actuatorKind: GameplayActuatorAttributesInput["actuatorKind"];
|
|
240
|
+
actuatorId: string;
|
|
241
|
+
}): BrowserInteractionAttributeMap {
|
|
242
|
+
return createGameplayActuatorAttributes({
|
|
243
|
+
scopeId: GAMEPLAY_BROWSER_SCOPE_ID,
|
|
244
|
+
interactionKey: descriptor.interactionKey,
|
|
245
|
+
interactionId: descriptor.interactionId,
|
|
246
|
+
intent,
|
|
247
|
+
enabled,
|
|
248
|
+
actuatorKind,
|
|
249
|
+
actuatorId,
|
|
250
|
+
...(descriptor.descriptorDigest !== undefined
|
|
251
|
+
? { descriptorDigest: descriptor.descriptorDigest }
|
|
252
|
+
: {}),
|
|
253
|
+
...(descriptor.draftDigest !== undefined
|
|
254
|
+
? {
|
|
255
|
+
draftDigest: interactionDraftDigestForValues(
|
|
256
|
+
descriptor,
|
|
257
|
+
draftValues ?? {},
|
|
258
|
+
),
|
|
259
|
+
}
|
|
260
|
+
: {}),
|
|
261
|
+
...(inputKey !== undefined ? { inputKey } : {}),
|
|
262
|
+
...(candidateValue !== undefined ? { candidateValue } : {}),
|
|
263
|
+
...(candidateState !== undefined ? { candidateState } : {}),
|
|
264
|
+
...(semanticEffects !== undefined ? { semanticEffects } : {}),
|
|
265
|
+
...(acceptedEffectPatterns !== undefined ? { acceptedEffectPatterns } : {}),
|
|
266
|
+
...(preparationPatterns !== undefined ? { preparationPatterns } : {}),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
166
269
|
|
|
167
270
|
export function InteractionForm<
|
|
168
271
|
Params extends InteractionParamsShape = InteractionParamsShape,
|
|
@@ -227,6 +330,34 @@ export function InteractionForm<
|
|
|
227
330
|
];
|
|
228
331
|
const isDisabled = disabled || pending || !isInteractionAvailable(descriptor);
|
|
229
332
|
const useAccordion = accordion && visibleInputs.length > 0;
|
|
333
|
+
const rootBrowserAttributes = gameplayInteractionRootAttributes({
|
|
334
|
+
descriptor,
|
|
335
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
336
|
+
ready: handle.isReady,
|
|
337
|
+
available: !isDisabled,
|
|
338
|
+
});
|
|
339
|
+
const armBrowserAttributes = gameplayActuatorAttributes({
|
|
340
|
+
descriptor,
|
|
341
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
342
|
+
intent: "arm",
|
|
343
|
+
enabled: !isDisabled,
|
|
344
|
+
actuatorKind: "click",
|
|
345
|
+
actuatorId: "arm",
|
|
346
|
+
preparationPatterns: gameplayPreparationPatternsForDescriptor(
|
|
347
|
+
descriptor,
|
|
348
|
+
handle.values as Readonly<Record<string, unknown>>,
|
|
349
|
+
),
|
|
350
|
+
});
|
|
351
|
+
const submitMetadata = gameplaySubmitMetadata({ descriptor });
|
|
352
|
+
const submitBrowserAttributes = gameplayActuatorAttributes({
|
|
353
|
+
descriptor,
|
|
354
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
355
|
+
intent: submitMetadata.intent,
|
|
356
|
+
enabled: !isDisabled && handle.isReady,
|
|
357
|
+
actuatorKind: "click",
|
|
358
|
+
actuatorId: "submit",
|
|
359
|
+
semanticEffects: submitMetadata.semanticEffects,
|
|
360
|
+
});
|
|
230
361
|
|
|
231
362
|
useEffect(() => {
|
|
232
363
|
setAccordionOpen(defaultOpen);
|
|
@@ -391,6 +522,7 @@ export function InteractionForm<
|
|
|
391
522
|
size="sm"
|
|
392
523
|
disabled={isDisabled}
|
|
393
524
|
className="h-9 px-3 text-sm"
|
|
525
|
+
{...submitBrowserAttributes}
|
|
394
526
|
{...buttonProps}
|
|
395
527
|
>
|
|
396
528
|
{pending
|
|
@@ -408,6 +540,7 @@ export function InteractionForm<
|
|
|
408
540
|
size="sm"
|
|
409
541
|
disabled={isDisabled}
|
|
410
542
|
className="h-9 px-3 text-sm"
|
|
543
|
+
{...submitBrowserAttributes}
|
|
411
544
|
>
|
|
412
545
|
{pending ? "Submitting..." : (submitLabel ?? fallbackLabel)}
|
|
413
546
|
</ThemedButton>
|
|
@@ -429,6 +562,7 @@ export function InteractionForm<
|
|
|
429
562
|
data-interaction-id={descriptor.interactionId}
|
|
430
563
|
onSubmit={(event) => void submit(event)}
|
|
431
564
|
style={containerStyle}
|
|
565
|
+
{...rootBrowserAttributes}
|
|
432
566
|
>
|
|
433
567
|
{useAccordion ? (
|
|
434
568
|
<AccordionPrimitive.Root
|
|
@@ -442,6 +576,7 @@ export function InteractionForm<
|
|
|
442
576
|
<AccordionPrimitive.Item value="fields">
|
|
443
577
|
<AccordionPrimitive.Header style={{ margin: 0 }}>
|
|
444
578
|
<AccordionPrimitive.Trigger
|
|
579
|
+
{...armBrowserAttributes}
|
|
445
580
|
style={{
|
|
446
581
|
alignItems: "center",
|
|
447
582
|
appearance: "none",
|
|
@@ -543,6 +678,24 @@ function createInteractionInputSlot<
|
|
|
543
678
|
kind === "card"
|
|
544
679
|
? { "data-dreamboard-interaction-card-slot": "" }
|
|
545
680
|
: { "data-dreamboard-interaction-target-slot": "" };
|
|
681
|
+
const browserAttributes = gameplayActuatorAttributes({
|
|
682
|
+
descriptor,
|
|
683
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
684
|
+
inputKey: input.key,
|
|
685
|
+
intent: selection?.mode === "many" ? "toggle" : "select",
|
|
686
|
+
candidateValue: targetValue,
|
|
687
|
+
candidateState: selected ? "selected" : "unselected",
|
|
688
|
+
enabled: !isDisabled,
|
|
689
|
+
actuatorKind: "click",
|
|
690
|
+
actuatorId: `${kind}:${input.key}:${targetValue}`,
|
|
691
|
+
semanticEffects: gameplayCandidateMetadata({
|
|
692
|
+
descriptor,
|
|
693
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
694
|
+
inputKey: input.key,
|
|
695
|
+
candidateValue: targetValue,
|
|
696
|
+
intent: selection?.mode === "many" ? "toggle" : "select",
|
|
697
|
+
}).semanticEffects,
|
|
698
|
+
});
|
|
546
699
|
return (
|
|
547
700
|
<button
|
|
548
701
|
type="button"
|
|
@@ -556,6 +709,7 @@ function createInteractionInputSlot<
|
|
|
556
709
|
data-selected={selected || undefined}
|
|
557
710
|
data-disabled={isDisabled || undefined}
|
|
558
711
|
{...dataAttribute}
|
|
712
|
+
{...browserAttributes}
|
|
559
713
|
{...buttonProps}
|
|
560
714
|
onClick={() => {
|
|
561
715
|
if (isDisabled) return;
|
|
@@ -588,6 +742,26 @@ function createInteractionInputSlot<
|
|
|
588
742
|
Default: ({ children }) => {
|
|
589
743
|
const hasDefault = "defaultValue" in input;
|
|
590
744
|
const isDisabled = disabled || !hasDefault;
|
|
745
|
+
const browserAttributes = gameplayActuatorAttributes({
|
|
746
|
+
descriptor,
|
|
747
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
748
|
+
inputKey: input.key,
|
|
749
|
+
intent: "select",
|
|
750
|
+
candidateValue: input.defaultValue,
|
|
751
|
+
candidateState: "unselected",
|
|
752
|
+
enabled: !isDisabled,
|
|
753
|
+
actuatorKind: "click",
|
|
754
|
+
actuatorId: `default:${input.key}`,
|
|
755
|
+
semanticEffects: hasDefault
|
|
756
|
+
? gameplayCandidateMetadata({
|
|
757
|
+
descriptor,
|
|
758
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
759
|
+
inputKey: input.key,
|
|
760
|
+
candidateValue: input.defaultValue,
|
|
761
|
+
intent: "select",
|
|
762
|
+
}).semanticEffects
|
|
763
|
+
: undefined,
|
|
764
|
+
});
|
|
591
765
|
return (
|
|
592
766
|
<button
|
|
593
767
|
type="button"
|
|
@@ -596,6 +770,7 @@ function createInteractionInputSlot<
|
|
|
596
770
|
data-dreamboard-interaction-default-slot=""
|
|
597
771
|
data-input-name={input.key}
|
|
598
772
|
data-disabled={isDisabled || undefined}
|
|
773
|
+
{...browserAttributes}
|
|
599
774
|
onClick={() => {
|
|
600
775
|
if (isDisabled) return;
|
|
601
776
|
handle.setInput(input.key, input.defaultValue as Params[Key]);
|
|
@@ -869,6 +1044,8 @@ function ChoiceField<
|
|
|
869
1044
|
Params extends InteractionParamsShape,
|
|
870
1045
|
Key extends keyof Params & string,
|
|
871
1046
|
>({
|
|
1047
|
+
descriptor,
|
|
1048
|
+
handle,
|
|
872
1049
|
input,
|
|
873
1050
|
value,
|
|
874
1051
|
setValue,
|
|
@@ -903,15 +1080,38 @@ function ChoiceField<
|
|
|
903
1080
|
>
|
|
904
1081
|
{choices.map((choice) => {
|
|
905
1082
|
const selected = value === choice.value;
|
|
1083
|
+
const isDisabled = disabled || choice.disabled;
|
|
906
1084
|
return (
|
|
907
1085
|
<ThemedButton
|
|
908
1086
|
key={choiceRenderKey(choice)}
|
|
909
1087
|
type="button"
|
|
910
1088
|
variant={selected ? "primary" : "secondary"}
|
|
911
1089
|
size="sm"
|
|
912
|
-
disabled={
|
|
1090
|
+
disabled={isDisabled}
|
|
913
1091
|
aria-pressed={selected}
|
|
914
1092
|
title={choice.disabledReason ?? choice.description}
|
|
1093
|
+
{...gameplayActuatorAttributes({
|
|
1094
|
+
descriptor,
|
|
1095
|
+
draftValues: handle.values as Readonly<
|
|
1096
|
+
Record<string, unknown>
|
|
1097
|
+
>,
|
|
1098
|
+
inputKey: input.key,
|
|
1099
|
+
intent: "select",
|
|
1100
|
+
candidateValue: choice.value,
|
|
1101
|
+
candidateState: selected ? "selected" : "unselected",
|
|
1102
|
+
enabled: !isDisabled,
|
|
1103
|
+
actuatorKind: "click",
|
|
1104
|
+
actuatorId: `choice:${input.key}:${choiceRenderKey(choice)}`,
|
|
1105
|
+
semanticEffects: gameplayCandidateMetadata({
|
|
1106
|
+
descriptor,
|
|
1107
|
+
draftValues: handle.values as Readonly<
|
|
1108
|
+
Record<string, unknown>
|
|
1109
|
+
>,
|
|
1110
|
+
inputKey: input.key,
|
|
1111
|
+
candidateValue: choice.value,
|
|
1112
|
+
intent: "select",
|
|
1113
|
+
}).semanticEffects,
|
|
1114
|
+
})}
|
|
915
1115
|
onClick={() => setValue(choice.value as Params[Key])}
|
|
916
1116
|
className="h-8 px-3 text-sm"
|
|
917
1117
|
>
|
|
@@ -937,7 +1137,24 @@ function ChoiceField<
|
|
|
937
1137
|
setValue(decodeChoiceSelectValue(next) as Params[Key])
|
|
938
1138
|
}
|
|
939
1139
|
>
|
|
940
|
-
<SelectTrigger
|
|
1140
|
+
<SelectTrigger
|
|
1141
|
+
id={controlId}
|
|
1142
|
+
size="sm"
|
|
1143
|
+
className="w-full bg-white"
|
|
1144
|
+
{...gameplayActuatorAttributes({
|
|
1145
|
+
descriptor,
|
|
1146
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
1147
|
+
inputKey: input.key,
|
|
1148
|
+
intent: "reveal",
|
|
1149
|
+
enabled: !disabled,
|
|
1150
|
+
actuatorKind: "click",
|
|
1151
|
+
actuatorId: `choice-reveal:${input.key}`,
|
|
1152
|
+
preparationPatterns: gameplayPreparationPatternsForDescriptor(
|
|
1153
|
+
{ inputs: [input] },
|
|
1154
|
+
handle.values as Readonly<Record<string, unknown>>,
|
|
1155
|
+
),
|
|
1156
|
+
})}
|
|
1157
|
+
>
|
|
941
1158
|
<span data-slot="select-value">
|
|
942
1159
|
{selectedChoice ? (
|
|
943
1160
|
<ChoiceOptionLabel choice={selectedChoice} />
|
|
@@ -960,6 +1177,27 @@ function ChoiceField<
|
|
|
960
1177
|
value={choiceRenderKey(choice)}
|
|
961
1178
|
textValue={choice.label}
|
|
962
1179
|
disabled={choice.disabled}
|
|
1180
|
+
{...gameplayActuatorAttributes({
|
|
1181
|
+
descriptor,
|
|
1182
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
1183
|
+
inputKey: input.key,
|
|
1184
|
+
intent: "select",
|
|
1185
|
+
candidateValue: choice.value,
|
|
1186
|
+
candidateState:
|
|
1187
|
+
value === choice.value ? "selected" : "unselected",
|
|
1188
|
+
enabled: !disabled && !choice.disabled,
|
|
1189
|
+
actuatorKind: "click",
|
|
1190
|
+
actuatorId: `choice:${input.key}:${choiceRenderKey(choice)}`,
|
|
1191
|
+
semanticEffects: gameplayCandidateMetadata({
|
|
1192
|
+
descriptor,
|
|
1193
|
+
draftValues: handle.values as Readonly<
|
|
1194
|
+
Record<string, unknown>
|
|
1195
|
+
>,
|
|
1196
|
+
inputKey: input.key,
|
|
1197
|
+
candidateValue: choice.value,
|
|
1198
|
+
intent: "select",
|
|
1199
|
+
}).semanticEffects,
|
|
1200
|
+
})}
|
|
963
1201
|
>
|
|
964
1202
|
<ChoiceOptionLabel choice={choice} />
|
|
965
1203
|
</SelectItem>
|
|
@@ -975,6 +1213,8 @@ function ChoiceListField<
|
|
|
975
1213
|
Params extends InteractionParamsShape,
|
|
976
1214
|
Key extends keyof Params & string,
|
|
977
1215
|
>({
|
|
1216
|
+
descriptor,
|
|
1217
|
+
handle,
|
|
978
1218
|
input,
|
|
979
1219
|
value,
|
|
980
1220
|
setValue,
|
|
@@ -1028,19 +1268,37 @@ function ChoiceListField<
|
|
|
1028
1268
|
{(domain.choices ?? []).map((choice) => {
|
|
1029
1269
|
const value = choice.value as string;
|
|
1030
1270
|
const checked = selected.has(value);
|
|
1271
|
+
const isDisabled =
|
|
1272
|
+
disabled || choice.disabled || (!checked && selected.size >= max);
|
|
1031
1273
|
return (
|
|
1032
1274
|
<ThemedButton
|
|
1033
1275
|
key={value}
|
|
1034
1276
|
type="button"
|
|
1035
1277
|
variant={checked ? "primary" : "secondary"}
|
|
1036
1278
|
size="sm"
|
|
1037
|
-
disabled={
|
|
1038
|
-
disabled ||
|
|
1039
|
-
choice.disabled ||
|
|
1040
|
-
(!checked && selected.size >= max)
|
|
1041
|
-
}
|
|
1279
|
+
disabled={isDisabled}
|
|
1042
1280
|
aria-pressed={checked}
|
|
1043
1281
|
title={choice.disabledReason ?? choice.description}
|
|
1282
|
+
{...gameplayActuatorAttributes({
|
|
1283
|
+
descriptor,
|
|
1284
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
1285
|
+
inputKey: input.key,
|
|
1286
|
+
intent: "toggle",
|
|
1287
|
+
candidateValue: value,
|
|
1288
|
+
candidateState: checked ? "selected" : "unselected",
|
|
1289
|
+
enabled: !isDisabled,
|
|
1290
|
+
actuatorKind: "click",
|
|
1291
|
+
actuatorId: `choice-list:${input.key}:${value}`,
|
|
1292
|
+
semanticEffects: gameplayCandidateMetadata({
|
|
1293
|
+
descriptor,
|
|
1294
|
+
draftValues: handle.values as Readonly<
|
|
1295
|
+
Record<string, unknown>
|
|
1296
|
+
>,
|
|
1297
|
+
inputKey: input.key,
|
|
1298
|
+
candidateValue: value,
|
|
1299
|
+
intent: "toggle",
|
|
1300
|
+
}).semanticEffects,
|
|
1301
|
+
})}
|
|
1044
1302
|
onClick={() => toggle(value)}
|
|
1045
1303
|
className="h-8 px-3 text-sm"
|
|
1046
1304
|
>
|
|
@@ -1057,6 +1315,8 @@ function ResourceMapField<
|
|
|
1057
1315
|
Params extends InteractionParamsShape,
|
|
1058
1316
|
Key extends keyof Params & string,
|
|
1059
1317
|
>({
|
|
1318
|
+
descriptor,
|
|
1319
|
+
handle,
|
|
1060
1320
|
input,
|
|
1061
1321
|
value,
|
|
1062
1322
|
setValue,
|
|
@@ -1123,6 +1383,23 @@ function ResourceMapField<
|
|
|
1123
1383
|
<StepperButton
|
|
1124
1384
|
label={`Decrease ${resource.label ?? resource.resourceId}`}
|
|
1125
1385
|
disabled={disabled || amount <= min}
|
|
1386
|
+
browserAttributes={gameplayActuatorAttributes({
|
|
1387
|
+
descriptor,
|
|
1388
|
+
draftValues: handle.values as Readonly<
|
|
1389
|
+
Record<string, unknown>
|
|
1390
|
+
>,
|
|
1391
|
+
inputKey: input.key,
|
|
1392
|
+
intent: "decrement",
|
|
1393
|
+
candidateValue: resource.resourceId,
|
|
1394
|
+
enabled: !(disabled || amount <= min),
|
|
1395
|
+
actuatorKind: "click",
|
|
1396
|
+
actuatorId: `resource-decrement:${input.key}:${resource.resourceId}`,
|
|
1397
|
+
semanticEffects: gameplayResourceMetadata({
|
|
1398
|
+
inputKey: input.key,
|
|
1399
|
+
resourceKey: resource.resourceId,
|
|
1400
|
+
delta: -1,
|
|
1401
|
+
}).semanticEffects,
|
|
1402
|
+
})}
|
|
1126
1403
|
onClick={() => update(resource.resourceId, -1, min, max)}
|
|
1127
1404
|
>
|
|
1128
1405
|
-
|
|
@@ -1139,6 +1416,23 @@ function ResourceMapField<
|
|
|
1139
1416
|
<StepperButton
|
|
1140
1417
|
label={`Increase ${resource.label ?? resource.resourceId}`}
|
|
1141
1418
|
disabled={disabled || amount >= max}
|
|
1419
|
+
browserAttributes={gameplayActuatorAttributes({
|
|
1420
|
+
descriptor,
|
|
1421
|
+
draftValues: handle.values as Readonly<
|
|
1422
|
+
Record<string, unknown>
|
|
1423
|
+
>,
|
|
1424
|
+
inputKey: input.key,
|
|
1425
|
+
intent: "increment",
|
|
1426
|
+
candidateValue: resource.resourceId,
|
|
1427
|
+
enabled: !(disabled || amount >= max),
|
|
1428
|
+
actuatorKind: "click",
|
|
1429
|
+
actuatorId: `resource-increment:${input.key}:${resource.resourceId}`,
|
|
1430
|
+
semanticEffects: gameplayResourceMetadata({
|
|
1431
|
+
inputKey: input.key,
|
|
1432
|
+
resourceKey: resource.resourceId,
|
|
1433
|
+
delta: 1,
|
|
1434
|
+
}).semanticEffects,
|
|
1435
|
+
})}
|
|
1142
1436
|
onClick={() => update(resource.resourceId, 1, min, max)}
|
|
1143
1437
|
>
|
|
1144
1438
|
+
|
|
@@ -1155,6 +1449,8 @@ function BoundedNumberField<
|
|
|
1155
1449
|
Params extends InteractionParamsShape,
|
|
1156
1450
|
Key extends keyof Params & string,
|
|
1157
1451
|
>({
|
|
1452
|
+
descriptor,
|
|
1453
|
+
handle,
|
|
1158
1454
|
input,
|
|
1159
1455
|
value,
|
|
1160
1456
|
setValue,
|
|
@@ -1190,6 +1486,19 @@ function BoundedNumberField<
|
|
|
1190
1486
|
<StepperButton
|
|
1191
1487
|
label={`Decrease ${input.key}`}
|
|
1192
1488
|
disabled={disabled || current <= min}
|
|
1489
|
+
browserAttributes={gameplayActuatorAttributes({
|
|
1490
|
+
descriptor,
|
|
1491
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
1492
|
+
inputKey: input.key,
|
|
1493
|
+
intent: "decrement",
|
|
1494
|
+
enabled: !(disabled || current <= min),
|
|
1495
|
+
actuatorKind: "click",
|
|
1496
|
+
actuatorId: `bounded-decrement:${input.key}`,
|
|
1497
|
+
semanticEffects: gameplayScalarStepMetadata({
|
|
1498
|
+
inputKey: input.key,
|
|
1499
|
+
value: Math.max(min, Math.min(max, current - step)),
|
|
1500
|
+
}).semanticEffects,
|
|
1501
|
+
})}
|
|
1193
1502
|
onClick={() => update(current - step)}
|
|
1194
1503
|
>
|
|
1195
1504
|
-
|
|
@@ -1202,12 +1511,38 @@ function BoundedNumberField<
|
|
|
1202
1511
|
step={step}
|
|
1203
1512
|
value={current}
|
|
1204
1513
|
disabled={disabled}
|
|
1514
|
+
{...gameplayActuatorAttributes({
|
|
1515
|
+
descriptor,
|
|
1516
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
1517
|
+
inputKey: input.key,
|
|
1518
|
+
intent: "fill",
|
|
1519
|
+
enabled: !disabled,
|
|
1520
|
+
actuatorKind: "fill",
|
|
1521
|
+
actuatorId: `bounded-fill:${input.key}`,
|
|
1522
|
+
acceptedEffectPatterns: gameplayScalarFillMetadata({
|
|
1523
|
+
inputKey: input.key,
|
|
1524
|
+
domain,
|
|
1525
|
+
}).acceptedEffectPatterns,
|
|
1526
|
+
})}
|
|
1205
1527
|
onChange={(event) => update(Number(event.target.value))}
|
|
1206
1528
|
className="h-9 w-[8ch] px-2 text-center text-sm md:text-sm"
|
|
1207
1529
|
/>
|
|
1208
1530
|
<StepperButton
|
|
1209
1531
|
label={`Increase ${input.key}`}
|
|
1210
1532
|
disabled={disabled || current >= max}
|
|
1533
|
+
browserAttributes={gameplayActuatorAttributes({
|
|
1534
|
+
descriptor,
|
|
1535
|
+
draftValues: handle.values as Readonly<Record<string, unknown>>,
|
|
1536
|
+
inputKey: input.key,
|
|
1537
|
+
intent: "increment",
|
|
1538
|
+
enabled: !(disabled || current >= max),
|
|
1539
|
+
actuatorKind: "click",
|
|
1540
|
+
actuatorId: `bounded-increment:${input.key}`,
|
|
1541
|
+
semanticEffects: gameplayScalarStepMetadata({
|
|
1542
|
+
inputKey: input.key,
|
|
1543
|
+
value: Math.max(min, Math.min(max, current + step)),
|
|
1544
|
+
}).semanticEffects,
|
|
1545
|
+
})}
|
|
1211
1546
|
onClick={() => update(current + step)}
|
|
1212
1547
|
>
|
|
1213
1548
|
+
|
|
@@ -1259,11 +1594,13 @@ function targetSelectionLabel(domain: InputDomain): string {
|
|
|
1259
1594
|
function StepperButton({
|
|
1260
1595
|
label,
|
|
1261
1596
|
disabled,
|
|
1597
|
+
browserAttributes,
|
|
1262
1598
|
onClick,
|
|
1263
1599
|
children,
|
|
1264
1600
|
}: {
|
|
1265
1601
|
label: string;
|
|
1266
1602
|
disabled: boolean;
|
|
1603
|
+
browserAttributes?: BrowserInteractionAttributeMap;
|
|
1267
1604
|
onClick: () => void;
|
|
1268
1605
|
children: ReactNode;
|
|
1269
1606
|
}) {
|
|
@@ -1274,6 +1611,7 @@ function StepperButton({
|
|
|
1274
1611
|
size="sm"
|
|
1275
1612
|
aria-label={label}
|
|
1276
1613
|
disabled={disabled}
|
|
1614
|
+
{...browserAttributes}
|
|
1277
1615
|
onClick={onClick}
|
|
1278
1616
|
className="h-8 w-8 text-sm"
|
|
1279
1617
|
>
|
|
@@ -2,6 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { InteractionUiProvider } from "../context/InteractionDraftContext.js";
|
|
3
3
|
import { RuntimeProvider } from "../context/RuntimeContext.js";
|
|
4
4
|
import { usePluginSession } from "../context/PluginSessionContext.js";
|
|
5
|
+
import { RuntimeSemanticProjectionMarker } from "../context/PluginStateContext.js";
|
|
5
6
|
import { usePluginRuntime } from "../hooks/usePluginRuntime.js";
|
|
6
7
|
import { GameSkeleton } from "../../ui.js";
|
|
7
8
|
|
|
@@ -97,6 +98,7 @@ function SessionScopedInteractionUiProvider({
|
|
|
97
98
|
const { controllingPlayerId } = usePluginSession();
|
|
98
99
|
return (
|
|
99
100
|
<InteractionUiProvider key={controllingPlayerId ?? "__no_player__"}>
|
|
101
|
+
<RuntimeSemanticProjectionMarker />
|
|
100
102
|
{children}
|
|
101
103
|
</InteractionUiProvider>
|
|
102
104
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BoardTargetKind } from "../../utils/interaction-inputs.js";
|
|
2
|
+
import type { BrowserInteractionAttributeMap } from "../../../browser-interaction/index.js";
|
|
2
3
|
|
|
3
4
|
export interface InteractiveTargetState {
|
|
4
5
|
kind?: BoardTargetKind;
|
|
@@ -13,6 +14,7 @@ export interface InteractiveTargetState {
|
|
|
13
14
|
conflict: boolean;
|
|
14
15
|
conflictInteractionKeys?: readonly string[];
|
|
15
16
|
unavailableReason?: string;
|
|
17
|
+
browserAttributes?: BrowserInteractionAttributeMap;
|
|
16
18
|
select?: () => unknown | Promise<unknown>;
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -10,6 +10,12 @@ import {
|
|
|
10
10
|
import type { PluginStateSnapshot } from "../types/plugin-state.js";
|
|
11
11
|
import { useRuntimeContext } from "./RuntimeContext.js";
|
|
12
12
|
import type { PluginRuntimeAPI } from "../runtime/createPluginRuntimeAPI.js";
|
|
13
|
+
import {
|
|
14
|
+
BROWSER_INTERACTION_ATTRIBUTES,
|
|
15
|
+
DREAMBOARD_BROWSER_INTERACTION_PROTOCOL_VERSION,
|
|
16
|
+
GAMEPLAY_BROWSER_INTERACTION_SURFACE,
|
|
17
|
+
} from "../../browser-interaction/index.js";
|
|
18
|
+
import { semanticProjectionDigestForState } from "../utils/semantic-projection-digest.js";
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
21
|
* React Context for providing plugin state from state-sync messages.
|
|
@@ -121,11 +127,46 @@ export function PluginStateProvider({
|
|
|
121
127
|
|
|
122
128
|
return (
|
|
123
129
|
<PluginStateContext.Provider value={state}>
|
|
130
|
+
<SemanticProjectionMarker state={state} />
|
|
124
131
|
{children}
|
|
125
132
|
</PluginStateContext.Provider>
|
|
126
133
|
);
|
|
127
134
|
}
|
|
128
135
|
|
|
136
|
+
const GAMEPLAY_BROWSER_SCOPE_ID = "runtime";
|
|
137
|
+
const BROWSER_PROJECTION_DIGEST_ATTRIBUTE = "data-dreamboard-projection-digest";
|
|
138
|
+
|
|
139
|
+
export function RuntimeSemanticProjectionMarker() {
|
|
140
|
+
const state = usePluginState((snapshot) => snapshot);
|
|
141
|
+
return <SemanticProjectionMarker state={state} />;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function SemanticProjectionMarker({
|
|
145
|
+
state,
|
|
146
|
+
}: {
|
|
147
|
+
state: PluginStateSnapshot;
|
|
148
|
+
}) {
|
|
149
|
+
const digest = semanticProjectionDigestForState(state);
|
|
150
|
+
if (!digest) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return (
|
|
154
|
+
<span
|
|
155
|
+
aria-hidden="true"
|
|
156
|
+
style={{ display: "none" }}
|
|
157
|
+
{...{
|
|
158
|
+
[BROWSER_INTERACTION_ATTRIBUTES.protocol]:
|
|
159
|
+
DREAMBOARD_BROWSER_INTERACTION_PROTOCOL_VERSION,
|
|
160
|
+
[BROWSER_INTERACTION_ATTRIBUTES.surface]:
|
|
161
|
+
GAMEPLAY_BROWSER_INTERACTION_SURFACE,
|
|
162
|
+
[BROWSER_INTERACTION_ATTRIBUTES.scope]: GAMEPLAY_BROWSER_SCOPE_ID,
|
|
163
|
+
[BROWSER_INTERACTION_ATTRIBUTES.role]: "projection",
|
|
164
|
+
[BROWSER_PROJECTION_DIGEST_ATTRIBUTE]: digest,
|
|
165
|
+
}}
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
129
170
|
/**
|
|
130
171
|
* Hook to access the full plugin state snapshot.
|
|
131
172
|
*
|