@dreamboard-games/ui-sdk 0.0.43 → 0.0.45
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/components/ActionButton.d.ts.map +1 -1
- package/dist/components/ActionButton.js +2 -1
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.d.ts.map +1 -1
- package/dist/components/DiceRoller.d.ts +3 -2
- package/dist/components/DiceRoller.d.ts.map +1 -1
- package/dist/components/DiceRoller.js +4 -13
- package/dist/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/ErrorBoundary.js +94 -2
- package/dist/components/InteractionForm.d.ts +1 -1
- package/dist/components/InteractionForm.d.ts.map +1 -1
- package/dist/components/InteractionForm.js +29 -15
- package/dist/components/PrimaryActionButton.d.ts.map +1 -1
- package/dist/components/PrimaryActionButton.js +7 -6
- package/dist/components/ResourceCounter.d.ts +59 -25
- package/dist/components/ResourceCounter.d.ts.map +1 -1
- package/dist/components/ResourceCounter.js +106 -115
- package/dist/components/Toast.d.ts +13 -6
- package/dist/components/Toast.d.ts.map +1 -1
- package/dist/components/Toast.js +10 -5
- package/dist/components/board/HexGrid.js +6 -6
- package/dist/components/board/target-layer.d.ts +18 -2
- package/dist/components/board/target-layer.d.ts.map +1 -1
- package/dist/components/board/target-layer.js +20 -3
- package/dist/components/index.d.ts +3 -4
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +3 -4
- package/dist/components/surfaces/InboxSurface.d.ts.map +1 -1
- package/dist/components/surfaces/InboxSurface.js +2 -6
- package/dist/components/surfaces/PlayerCardsSurface.js +2 -2
- package/dist/components/surfaces/internal/CardZoneRoutedForm.d.ts +7 -0
- package/dist/components/surfaces/internal/CardZoneRoutedForm.d.ts.map +1 -0
- package/dist/components/surfaces/internal/CardZoneRoutedForm.js +9 -0
- package/dist/components/surfaces/internal/DefaultInteractionButton.d.ts.map +1 -1
- package/dist/components/surfaces/internal/DefaultInteractionButton.js +5 -8
- package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts +2 -2
- package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts.map +1 -1
- package/dist/components/surfaces/internal/useCardZoneInteractions.js +19 -43
- package/dist/context/InteractionDraftContext.d.ts +11 -2
- package/dist/context/InteractionDraftContext.d.ts.map +1 -1
- package/dist/context/InteractionDraftContext.js +41 -4
- package/dist/defaults/components.d.ts +0 -5
- package/dist/defaults/components.d.ts.map +1 -1
- package/dist/defaults/components.js +7 -11
- package/dist/hooks/useBoardInteractions.d.ts +35 -12
- package/dist/hooks/useBoardInteractions.d.ts.map +1 -1
- package/dist/hooks/useBoardInteractions.js +186 -82
- package/dist/hooks/useInteractionHandle.d.ts +1 -1
- package/dist/hooks/useInteractionHandle.d.ts.map +1 -1
- package/dist/hooks/useInteractionHandle.js +12 -27
- package/dist/index.d.ts +11 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -14
- package/dist/primitives/board.d.ts +53 -3
- package/dist/primitives/board.d.ts.map +1 -1
- package/dist/primitives/board.js +65 -41
- package/dist/primitives/dialog-lifecycle.d.ts +17 -0
- package/dist/primitives/dialog-lifecycle.d.ts.map +1 -0
- package/dist/primitives/dialog-lifecycle.js +24 -0
- package/dist/primitives/dice.d.ts +31 -0
- package/dist/primitives/dice.d.ts.map +1 -0
- package/dist/primitives/dice.js +33 -0
- package/dist/primitives/game.d.ts +55 -0
- package/dist/primitives/game.d.ts.map +1 -0
- package/dist/primitives/game.js +101 -0
- package/dist/primitives/index.d.ts +7 -4
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +7 -4
- package/dist/primitives/interaction-form-binding.d.ts +12 -0
- package/dist/primitives/interaction-form-binding.d.ts.map +1 -0
- package/dist/primitives/interaction-form-binding.js +14 -0
- package/dist/primitives/interaction-submit.d.ts +23 -0
- package/dist/primitives/interaction-submit.d.ts.map +1 -0
- package/dist/primitives/interaction-submit.js +41 -0
- package/dist/primitives/interaction.d.ts +76 -6
- package/dist/primitives/interaction.d.ts.map +1 -1
- package/dist/primitives/interaction.js +210 -26
- package/dist/primitives/player-roster.d.ts +2 -1
- package/dist/primitives/player-roster.d.ts.map +1 -1
- package/dist/primitives/prompt.d.ts +36 -11
- package/dist/primitives/prompt.d.ts.map +1 -1
- package/dist/primitives/prompt.js +29 -17
- package/dist/primitives/ui.d.ts +9 -0
- package/dist/primitives/ui.d.ts.map +1 -0
- package/dist/primitives/ui.js +7 -0
- package/dist/primitives/zone.d.ts +111 -5
- package/dist/primitives/zone.d.ts.map +1 -1
- package/dist/primitives/zone.js +349 -9
- package/dist/reducer.d.ts +2 -14
- package/dist/reducer.d.ts.map +1 -1
- package/dist/reducer.js +1 -14
- package/dist/runtime/createPluginRuntimeAPI.js +1 -1
- package/dist/types/hex-color.d.ts +7 -0
- package/dist/types/hex-color.d.ts.map +1 -0
- package/dist/types/hex-color.js +13 -0
- package/dist/types/player-state.d.ts +28 -14
- package/dist/types/player-state.d.ts.map +1 -1
- package/dist/types/plugin-state.d.ts +9 -3
- package/dist/types/plugin-state.d.ts.map +1 -1
- package/dist/ui-contract.d.ts +119 -14
- package/dist/ui-contract.d.ts.map +1 -1
- package/dist/ui-contract.js +4 -3
- package/dist/ui-sdk.d.ts +1637 -1245
- package/dist/utils/interaction-inputs.d.ts +8 -5
- package/dist/utils/interaction-inputs.d.ts.map +1 -1
- package/dist/utils/interaction-inputs.js +82 -14
- package/dist/utils/interaction-router.d.ts +31 -0
- package/dist/utils/interaction-router.d.ts.map +1 -0
- package/dist/utils/interaction-router.js +114 -0
- package/package.json +1 -1
- package/src/components/ActionButton.tsx +2 -1
- package/src/components/Card.tsx +1 -1
- package/src/components/DiceRoller.tsx +13 -22
- package/src/components/ErrorBoundary.test.tsx +19 -0
- package/src/components/ErrorBoundary.tsx +113 -24
- package/src/components/InteractionForm.test.tsx +24 -0
- package/src/components/InteractionForm.tsx +48 -23
- package/src/components/PrimaryActionButton.tsx +19 -5
- package/src/components/ResourceCounter.test.tsx +13 -13
- package/src/components/ResourceCounter.tsx +238 -244
- package/src/components/Toast.tsx +23 -10
- package/src/components/__fixtures__/ResourceCounter.fixture.tsx +70 -169
- package/src/components/board/HexGrid.tsx +6 -6
- package/src/components/board/target-layer.ts +44 -5
- package/src/components/index.ts +17 -10
- package/src/components/surfaces/InboxSurface.tsx +7 -5
- package/src/components/surfaces/PlayerCardsSurface.tsx +6 -6
- package/src/components/surfaces/internal/CardZoneRoutedForm.tsx +35 -0
- package/src/components/surfaces/internal/DefaultInteractionButton.tsx +17 -7
- package/src/components/surfaces/internal/useCardZoneInteractions.ts +25 -67
- package/src/context/InteractionDraftContext.tsx +51 -5
- package/src/defaults/components.tsx +12 -50
- package/src/defaults/defaults.test.tsx +1 -50
- package/src/hooks/useBoardInteractions.test.tsx +240 -17
- package/src/hooks/useBoardInteractions.ts +330 -105
- package/src/hooks/useInteractionHandle.ts +23 -28
- package/src/index.test.ts +60 -40
- package/src/index.ts +30 -36
- package/src/primitives/board.test.tsx +73 -0
- package/src/primitives/board.tsx +191 -40
- package/src/primitives/dialog-lifecycle.ts +58 -0
- package/src/primitives/dice.test.tsx +47 -0
- package/src/primitives/dice.tsx +79 -0
- package/src/primitives/game.test.tsx +98 -0
- package/src/primitives/game.tsx +213 -0
- package/src/primitives/index.ts +84 -0
- package/src/primitives/interaction-form-binding.tsx +56 -0
- package/src/primitives/interaction-submit.ts +90 -0
- package/src/primitives/interaction.test.tsx +396 -0
- package/src/primitives/interaction.tsx +451 -31
- package/src/primitives/player-roster.tsx +2 -1
- package/src/primitives/prompt.test.tsx +94 -3
- package/src/primitives/prompt.tsx +87 -48
- package/src/primitives/ui.test.tsx +131 -0
- package/src/primitives/ui.tsx +13 -0
- package/src/primitives/zone.test.tsx +305 -0
- package/src/primitives/zone.tsx +660 -12
- package/src/reducer.ts +7 -20
- package/src/runtime/createPluginRuntimeAPI.ts +1 -1
- package/src/types/hex-color.ts +20 -0
- package/src/types/player-state.ts +36 -18
- package/src/types/plugin-state.ts +10 -3
- package/src/ui-contract.ts +253 -21
- package/src/utils/interaction-inputs.test.ts +400 -0
- package/src/utils/interaction-inputs.ts +113 -11
- package/src/utils/interaction-router.ts +200 -0
|
@@ -9,14 +9,17 @@ import type {
|
|
|
9
9
|
ZoneHandlesSnapshot,
|
|
10
10
|
} from "../../../types/plugin-state.js";
|
|
11
11
|
import {
|
|
12
|
-
hasInteractionFieldErrors,
|
|
13
12
|
inputByTarget,
|
|
14
|
-
interactionArmScope,
|
|
15
13
|
interactionInputKeys,
|
|
16
|
-
isInputValueReady,
|
|
17
14
|
toggleManyValue,
|
|
18
|
-
validateInteractionInputDomains,
|
|
19
15
|
} from "../../../utils/interaction-inputs.js";
|
|
16
|
+
import {
|
|
17
|
+
applyInteractionDraftMutation,
|
|
18
|
+
clearInteractionRoute,
|
|
19
|
+
getInteractionDraftReadiness,
|
|
20
|
+
markInteractionPending,
|
|
21
|
+
shouldRouteInteractionPending,
|
|
22
|
+
} from "../../../utils/interaction-router.js";
|
|
20
23
|
import type {
|
|
21
24
|
InteractionActionState,
|
|
22
25
|
InteractionDisabledReason,
|
|
@@ -42,9 +45,8 @@ export function useCardZoneInteractions<I extends string = string>(
|
|
|
42
45
|
const draftStore = useInteractionUiStore();
|
|
43
46
|
const draftSnapshot = useStore(draftStore, (state) => state.drafts);
|
|
44
47
|
const submittingSnapshot = useStore(draftStore, (state) => state.submitting);
|
|
45
|
-
const [
|
|
46
|
-
null
|
|
47
|
-
);
|
|
48
|
+
const [routedInteraction, setRoutedInteraction] =
|
|
49
|
+
useState<InteractionDescriptor<I> | null>(null);
|
|
48
50
|
const zone = usePluginState((s) => s.gameplay.zones?.[zoneId]) as
|
|
49
51
|
| ZoneHandlesSnapshot<I>
|
|
50
52
|
| undefined;
|
|
@@ -79,11 +81,7 @@ export function useCardZoneInteractions<I extends string = string>(
|
|
|
79
81
|
descriptor.interactionId,
|
|
80
82
|
params,
|
|
81
83
|
);
|
|
82
|
-
draftStore
|
|
83
|
-
const armScope = interactionArmScope(descriptor);
|
|
84
|
-
if (draftStore.getArmed(armScope) === descriptor.interactionKey) {
|
|
85
|
-
draftStore.arm(armScope, null);
|
|
86
|
-
}
|
|
84
|
+
clearInteractionRoute(draftStore, descriptor);
|
|
87
85
|
} finally {
|
|
88
86
|
draftStore.setSubmitting(descriptor.interactionKey, false);
|
|
89
87
|
}
|
|
@@ -111,37 +109,20 @@ export function useCardZoneInteractions<I extends string = string>(
|
|
|
111
109
|
cardInput.domain.selection,
|
|
112
110
|
)
|
|
113
111
|
: card.id;
|
|
114
|
-
const nextDraft
|
|
115
|
-
...
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
for (const [key, value] of Object.entries(params ?? {})) {
|
|
120
|
-
draftStore.setInput(descriptor.interactionKey, key, value);
|
|
121
|
-
}
|
|
122
|
-
draftStore.setInput(
|
|
123
|
-
descriptor.interactionKey,
|
|
124
|
-
cardInputKey,
|
|
125
|
-
nextCardValue,
|
|
126
|
-
);
|
|
112
|
+
const nextDraft = applyInteractionDraftMutation(draftStore, descriptor, [
|
|
113
|
+
...Object.entries(params ?? {}).map(([key, value]) => ({ key, value })),
|
|
114
|
+
{ key: cardInputKey, value: nextCardValue },
|
|
115
|
+
]);
|
|
116
|
+
const readiness = getInteractionDraftReadiness(descriptor, nextDraft);
|
|
127
117
|
if (
|
|
128
118
|
cardInput.domain.type === "target" &&
|
|
129
119
|
cardInput.domain.selection?.mode === "many"
|
|
130
120
|
) {
|
|
131
|
-
if (
|
|
132
|
-
descriptor.commit.mode === "autoWhenReady" &&
|
|
133
|
-
isDraftReady(descriptor, nextDraft)
|
|
134
|
-
) {
|
|
121
|
+
if (descriptor.commit.mode === "autoWhenReady" && readiness.ready) {
|
|
135
122
|
await submitInteractionDraft(descriptor, nextDraft);
|
|
136
123
|
}
|
|
137
124
|
return;
|
|
138
125
|
}
|
|
139
|
-
const remaining = descriptor.inputs.filter((input) => {
|
|
140
|
-
const value =
|
|
141
|
-
nextDraft[input.key] ??
|
|
142
|
-
("defaultValue" in input ? input.defaultValue : undefined);
|
|
143
|
-
return input.key !== cardInputKey && value === undefined;
|
|
144
|
-
});
|
|
145
126
|
const needsBoardTarget = descriptor.inputs.some((input) => {
|
|
146
127
|
const value =
|
|
147
128
|
nextDraft[input.key] ??
|
|
@@ -153,26 +134,18 @@ export function useCardZoneInteractions<I extends string = string>(
|
|
|
153
134
|
);
|
|
154
135
|
});
|
|
155
136
|
if (needsBoardTarget) {
|
|
156
|
-
|
|
157
|
-
draftStore
|
|
158
|
-
|
|
159
|
-
descriptor.interactionKey,
|
|
160
|
-
);
|
|
137
|
+
setRoutedInteraction(null);
|
|
138
|
+
markInteractionPending(draftStore, descriptor);
|
|
139
|
+
draftStore.setPendingInteraction(null);
|
|
161
140
|
return;
|
|
162
141
|
}
|
|
163
|
-
if (
|
|
164
|
-
|
|
142
|
+
if (readiness.missingInputs.length > 0) {
|
|
143
|
+
setRoutedInteraction(descriptor);
|
|
165
144
|
return;
|
|
166
145
|
}
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
descriptor
|
|
170
|
-
) {
|
|
171
|
-
setFollowUp(null);
|
|
172
|
-
draftStore.arm(
|
|
173
|
-
interactionArmScope(descriptor),
|
|
174
|
-
descriptor.interactionKey,
|
|
175
|
-
);
|
|
146
|
+
if (shouldRouteInteractionPending(descriptor, readiness)) {
|
|
147
|
+
setRoutedInteraction(null);
|
|
148
|
+
markInteractionPending(draftStore, descriptor);
|
|
176
149
|
return;
|
|
177
150
|
}
|
|
178
151
|
await submitInteractionDraft(descriptor, nextDraft);
|
|
@@ -252,7 +225,7 @@ export function useCardZoneInteractions<I extends string = string>(
|
|
|
252
225
|
zone,
|
|
253
226
|
]);
|
|
254
227
|
|
|
255
|
-
return { cards, contexts,
|
|
228
|
+
return { cards, contexts, routedInteraction, setRoutedInteraction };
|
|
256
229
|
}
|
|
257
230
|
|
|
258
231
|
function isCardSelected(
|
|
@@ -283,21 +256,6 @@ function isCardInteractionSelectable(
|
|
|
283
256
|
return selection.max === undefined || current.length < selection.max;
|
|
284
257
|
}
|
|
285
258
|
|
|
286
|
-
function isDraftReady(
|
|
287
|
-
descriptor: InteractionDescriptor,
|
|
288
|
-
draft: Readonly<Record<string, unknown>>,
|
|
289
|
-
): boolean {
|
|
290
|
-
const fieldErrors = validateInteractionInputDomains(descriptor, draft);
|
|
291
|
-
if (hasInteractionFieldErrors(fieldErrors)) return false;
|
|
292
|
-
return interactionInputKeys(descriptor).every((key) => {
|
|
293
|
-
const input = descriptor.inputs.find((candidate) => candidate.key === key);
|
|
294
|
-
const value =
|
|
295
|
-
draft[key] ??
|
|
296
|
-
(input && "defaultValue" in input ? input.defaultValue : undefined);
|
|
297
|
-
return input ? isInputValueReady(input, value) : value !== undefined;
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
259
|
function resolveCardInput(
|
|
302
260
|
descriptor: InteractionDescriptor,
|
|
303
261
|
cardId: string,
|
|
@@ -11,10 +11,11 @@ interface DraftState {
|
|
|
11
11
|
drafts: Readonly<Record<string, Draft>>;
|
|
12
12
|
arms: Readonly<Record<string, string>>;
|
|
13
13
|
submitting: Readonly<Record<string, true>>;
|
|
14
|
+
pendingInteractionKey: string | null;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
|
-
* Imperative API exposed to
|
|
18
|
+
* Imperative API exposed to interaction primitives.
|
|
18
19
|
* Intentionally small; the vanilla zustand store underneath powers
|
|
19
20
|
* fine-grained subscriptions via {@link useDraft} and {@link useArmed}.
|
|
20
21
|
*/
|
|
@@ -31,8 +32,14 @@ export interface InteractionUiStore {
|
|
|
31
32
|
getArmed(surface: string): string | null;
|
|
32
33
|
/** Arm a specific interaction on a surface. Pass `null` to disarm. */
|
|
33
34
|
arm(surface: string, interactionId: string | null): void;
|
|
35
|
+
/** Which interaction draft currently needs route-owned remaining input UI. */
|
|
36
|
+
getPendingInteraction(): string | null;
|
|
37
|
+
/** Mark the interaction draft currently waiting for remaining input. */
|
|
38
|
+
setPendingInteraction(interactionId: string | null): void;
|
|
34
39
|
/** True while a local submission is in flight before the host echo arrives. */
|
|
35
40
|
isSubmitting(interactionId: string): boolean;
|
|
41
|
+
/** Atomically mark a submission in flight. Returns false if already busy. */
|
|
42
|
+
claimSubmitting(interactionId: string): boolean;
|
|
36
43
|
/** Mark or clear a local submission in flight. */
|
|
37
44
|
setSubmitting(interactionId: string, submitting: boolean): void;
|
|
38
45
|
}
|
|
@@ -45,6 +52,7 @@ export function createInteractionUiStore(): InteractionUiStoreApi {
|
|
|
45
52
|
drafts: {},
|
|
46
53
|
arms: {},
|
|
47
54
|
submitting: {},
|
|
55
|
+
pendingInteractionKey: null,
|
|
48
56
|
}));
|
|
49
57
|
|
|
50
58
|
const api: InteractionUiStore = {
|
|
@@ -89,11 +97,17 @@ export function createInteractionUiStore(): InteractionUiStoreApi {
|
|
|
89
97
|
if (
|
|
90
98
|
Object.keys(prev.drafts).length === 0 &&
|
|
91
99
|
Object.keys(prev.arms).length === 0 &&
|
|
92
|
-
Object.keys(prev.submitting).length === 0
|
|
100
|
+
Object.keys(prev.submitting).length === 0 &&
|
|
101
|
+
prev.pendingInteractionKey === null
|
|
93
102
|
) {
|
|
94
103
|
return prev;
|
|
95
104
|
}
|
|
96
|
-
return {
|
|
105
|
+
return {
|
|
106
|
+
drafts: {},
|
|
107
|
+
arms: {},
|
|
108
|
+
submitting: {},
|
|
109
|
+
pendingInteractionKey: null,
|
|
110
|
+
};
|
|
97
111
|
});
|
|
98
112
|
},
|
|
99
113
|
getArmed(surface) {
|
|
@@ -110,9 +124,30 @@ export function createInteractionUiStore(): InteractionUiStoreApi {
|
|
|
110
124
|
return { ...prev, arms: { ...prev.arms, [surface]: interactionId } };
|
|
111
125
|
});
|
|
112
126
|
},
|
|
127
|
+
getPendingInteraction() {
|
|
128
|
+
return store.getState().pendingInteractionKey;
|
|
129
|
+
},
|
|
130
|
+
setPendingInteraction(interactionId) {
|
|
131
|
+
store.setState((prev) => {
|
|
132
|
+
if (prev.pendingInteractionKey === interactionId) return prev;
|
|
133
|
+
return { ...prev, pendingInteractionKey: interactionId };
|
|
134
|
+
});
|
|
135
|
+
},
|
|
113
136
|
isSubmitting(interactionId) {
|
|
114
137
|
return store.getState().submitting[interactionId] === true;
|
|
115
138
|
},
|
|
139
|
+
claimSubmitting(interactionId) {
|
|
140
|
+
let claimed = false;
|
|
141
|
+
store.setState((prev) => {
|
|
142
|
+
if (prev.submitting[interactionId] === true) return prev;
|
|
143
|
+
claimed = true;
|
|
144
|
+
return {
|
|
145
|
+
...prev,
|
|
146
|
+
submitting: { ...prev.submitting, [interactionId]: true },
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
return claimed;
|
|
150
|
+
},
|
|
116
151
|
setSubmitting(interactionId, submitting) {
|
|
117
152
|
store.setState((prev) => {
|
|
118
153
|
const current = prev.submitting[interactionId] === true;
|
|
@@ -143,7 +178,7 @@ const InteractionUiCtx = createContext<InteractionUiStoreApi | null>(null);
|
|
|
143
178
|
* ```tsx
|
|
144
179
|
* <InteractionUiProvider>
|
|
145
180
|
* <PanelSurface />
|
|
146
|
-
* <
|
|
181
|
+
* <Board.Root>{...}</Board.Root>
|
|
147
182
|
* </InteractionUiProvider>
|
|
148
183
|
* ```
|
|
149
184
|
*/
|
|
@@ -180,12 +215,13 @@ export function useInteractionUiStore(): InteractionUiStoreApi {
|
|
|
180
215
|
*/
|
|
181
216
|
export function useInteractionDraft(interactionId: string): Draft {
|
|
182
217
|
const store = useInteractionUiStore();
|
|
183
|
-
|
|
218
|
+
useStore(
|
|
184
219
|
store,
|
|
185
220
|
useShallow(
|
|
186
221
|
(state: DraftState) => state.drafts[interactionId] ?? EMPTY_DRAFT,
|
|
187
222
|
),
|
|
188
223
|
);
|
|
224
|
+
return store.getDraft(interactionId);
|
|
189
225
|
}
|
|
190
226
|
|
|
191
227
|
/** Subscribe to the armed interaction id on a given surface. */
|
|
@@ -194,6 +230,16 @@ export function useArmedInteraction(surface: string): string | null {
|
|
|
194
230
|
return useStore(store, (state: DraftState) => state.arms[surface] ?? null);
|
|
195
231
|
}
|
|
196
232
|
|
|
233
|
+
/** Subscribe to the interaction draft currently waiting for pending input. */
|
|
234
|
+
export function usePendingInteractionKey(): string | null {
|
|
235
|
+
const store = useInteractionUiStore();
|
|
236
|
+
const subscribed = useStore(
|
|
237
|
+
store,
|
|
238
|
+
(state: DraftState) => state.pendingInteractionKey ?? null,
|
|
239
|
+
);
|
|
240
|
+
return store.getPendingInteraction() ?? subscribed;
|
|
241
|
+
}
|
|
242
|
+
|
|
197
243
|
/** Subscribe to local submitting status for a single interaction key. */
|
|
198
244
|
export function useInteractionSubmitting(interactionId: string): boolean {
|
|
199
245
|
const store = useInteractionUiStore();
|
|
@@ -6,8 +6,6 @@ import type {
|
|
|
6
6
|
} from "react";
|
|
7
7
|
import {
|
|
8
8
|
Interaction,
|
|
9
|
-
Prompt,
|
|
10
|
-
PromptInbox,
|
|
11
9
|
Zone,
|
|
12
10
|
renderPrimitive,
|
|
13
11
|
useInteractionPrimitiveContext,
|
|
@@ -127,49 +125,6 @@ export const GameLayout = {
|
|
|
127
125
|
Bottom: GameLayoutBottom,
|
|
128
126
|
};
|
|
129
127
|
|
|
130
|
-
export interface DefaultPromptInboxProps extends PrimitiveCommonProps {
|
|
131
|
-
empty?: ReactNode;
|
|
132
|
-
renderPrompt?: (prompt: InteractionDescriptor) => ReactNode;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function DefaultPromptInbox({
|
|
136
|
-
empty = "No available prompts.",
|
|
137
|
-
renderPrompt,
|
|
138
|
-
...props
|
|
139
|
-
}: DefaultPromptInboxProps) {
|
|
140
|
-
return (
|
|
141
|
-
<PromptInbox.Root>
|
|
142
|
-
{renderPrimitive("section", {
|
|
143
|
-
...props,
|
|
144
|
-
"data-dreamboard-default-prompt-inbox": "",
|
|
145
|
-
children: (
|
|
146
|
-
<>
|
|
147
|
-
<PromptInbox.Empty>{empty}</PromptInbox.Empty>
|
|
148
|
-
<PromptInbox.Items>
|
|
149
|
-
{(prompt) =>
|
|
150
|
-
renderPrompt ? (
|
|
151
|
-
renderPrompt(prompt)
|
|
152
|
-
) : (
|
|
153
|
-
<Prompt.Root
|
|
154
|
-
key={prompt.interactionKey}
|
|
155
|
-
interaction={prompt.interactionKey}
|
|
156
|
-
>
|
|
157
|
-
<Prompt.Title />
|
|
158
|
-
<Prompt.Message />
|
|
159
|
-
<div data-dreamboard-default-prompt-options="">
|
|
160
|
-
<Prompt.Options />
|
|
161
|
-
</div>
|
|
162
|
-
</Prompt.Root>
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
</PromptInbox.Items>
|
|
166
|
-
</>
|
|
167
|
-
),
|
|
168
|
-
})}
|
|
169
|
-
</PromptInbox.Root>
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
128
|
export interface DefaultInteractionListProps extends PrimitiveCommonProps {
|
|
174
129
|
empty?: ReactNode;
|
|
175
130
|
includeUnavailable?: boolean;
|
|
@@ -298,23 +253,30 @@ function DefaultInteractionField({
|
|
|
298
253
|
function DefaultChoiceField({ input }: { input: InteractionInputDescriptor }) {
|
|
299
254
|
const { handle } = useInteractionPrimitiveContext();
|
|
300
255
|
const value = handle?.draft[input.key] ?? handle?.values[input.key];
|
|
256
|
+
const encodeValue = (candidate: unknown) =>
|
|
257
|
+
candidate === null ? "__dreamboard_null_choice__" : String(candidate);
|
|
301
258
|
return (
|
|
302
259
|
<label data-dreamboard-default-interaction-field="">
|
|
303
260
|
{input.key}
|
|
304
261
|
<select
|
|
305
262
|
name={input.key}
|
|
306
|
-
value={value === undefined ? "" :
|
|
263
|
+
value={value === undefined ? "" : encodeValue(value)}
|
|
307
264
|
data-dreamboard-default-choice-input=""
|
|
308
265
|
disabled={!handle?.available}
|
|
309
266
|
onChange={(event) =>
|
|
310
|
-
handle?.setInput(
|
|
267
|
+
handle?.setInput(
|
|
268
|
+
input.key,
|
|
269
|
+
event.currentTarget.value === "__dreamboard_null_choice__"
|
|
270
|
+
? null
|
|
271
|
+
: event.currentTarget.value,
|
|
272
|
+
)
|
|
311
273
|
}
|
|
312
274
|
>
|
|
313
275
|
<option value="" />
|
|
314
276
|
{(input.domain.choices ?? []).map((choice) => (
|
|
315
277
|
<option
|
|
316
|
-
key={choice.value}
|
|
317
|
-
value={choice.value}
|
|
278
|
+
key={encodeValue(choice.value)}
|
|
279
|
+
value={encodeValue(choice.value)}
|
|
318
280
|
disabled={choice.disabled}
|
|
319
281
|
>
|
|
320
282
|
{choice.label}
|
|
@@ -399,7 +361,7 @@ function DefaultZoneList({
|
|
|
399
361
|
return (
|
|
400
362
|
<Zone.Item key={cardId} card={cardId}>
|
|
401
363
|
{input ? (
|
|
402
|
-
<Interaction.CardInput input={input}
|
|
364
|
+
<Interaction.CardInput input={input}>
|
|
403
365
|
{body}
|
|
404
366
|
</Interaction.CardInput>
|
|
405
367
|
) : (
|
|
@@ -7,12 +7,7 @@ import type {
|
|
|
7
7
|
} from "../types/plugin-state.js";
|
|
8
8
|
import type { PluginSessionState, RuntimeAPI } from "../types/runtime-api.js";
|
|
9
9
|
import { GameUIProvider } from "../primitives/game-ui-provider.js";
|
|
10
|
-
import {
|
|
11
|
-
DefaultInteractionList,
|
|
12
|
-
DefaultPromptInbox,
|
|
13
|
-
DefaultZone,
|
|
14
|
-
GameLayout,
|
|
15
|
-
} from "./index.js";
|
|
10
|
+
import { DefaultInteractionList, DefaultZone, GameLayout } from "./index.js";
|
|
16
11
|
|
|
17
12
|
function createSessionState(): PluginSessionState {
|
|
18
13
|
return {
|
|
@@ -66,50 +61,6 @@ function renderWithRuntime(
|
|
|
66
61
|
);
|
|
67
62
|
}
|
|
68
63
|
|
|
69
|
-
test("DefaultPromptInbox composes PromptInbox and Prompt primitives", () => {
|
|
70
|
-
const prompt: InteractionDescriptor = {
|
|
71
|
-
phaseName: "trading",
|
|
72
|
-
interactionKey: "trading.respond",
|
|
73
|
-
interactionId: "respond",
|
|
74
|
-
kind: "prompt",
|
|
75
|
-
inputs: [
|
|
76
|
-
{
|
|
77
|
-
key: "response",
|
|
78
|
-
kind: "choice",
|
|
79
|
-
domain: {
|
|
80
|
-
type: "choice",
|
|
81
|
-
choices: [
|
|
82
|
-
{ value: "accept", label: "Accept" },
|
|
83
|
-
{ value: "decline", label: "Decline" },
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
commit: { mode: "manual" },
|
|
89
|
-
available: true,
|
|
90
|
-
context: {
|
|
91
|
-
to: "player-1",
|
|
92
|
-
title: "Trade offer",
|
|
93
|
-
payload: { message: "Two wheat for one ore?" },
|
|
94
|
-
options: [
|
|
95
|
-
{ id: "accept", label: "Accept" },
|
|
96
|
-
{ id: "decline", label: "Decline" },
|
|
97
|
-
],
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const html = renderWithRuntime(
|
|
102
|
-
{ availableInteractions: [prompt] },
|
|
103
|
-
<DefaultPromptInbox />,
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
expect(html).toContain('data-dreamboard-default-prompt-inbox=""');
|
|
107
|
-
expect(html).toContain("Trade offer");
|
|
108
|
-
expect(html).toContain("Two wheat for one ore?");
|
|
109
|
-
expect(html).toContain('data-dreamboard-prompt-option=""');
|
|
110
|
-
expect(html).toContain('data-option-value="accept"');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
64
|
test("DefaultInteractionList renders action items without descriptor style metadata", () => {
|
|
114
65
|
const ready: InteractionDescriptor = {
|
|
115
66
|
phaseName: "setup",
|