@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
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
* Catches React errors and displays a fallback UI
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
Component,
|
|
9
|
+
type CSSProperties,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
type ErrorInfo,
|
|
12
|
+
} from "react";
|
|
8
13
|
import { AlertTriangle, RefreshCw } from "lucide-react";
|
|
9
14
|
|
|
10
15
|
export interface ErrorBoundaryProps {
|
|
@@ -66,35 +71,123 @@ function DefaultErrorFallback({
|
|
|
66
71
|
error: Error;
|
|
67
72
|
onReset: () => void;
|
|
68
73
|
}) {
|
|
74
|
+
const styles = {
|
|
75
|
+
shell: {
|
|
76
|
+
alignItems: "center",
|
|
77
|
+
background: "#fdfbf7",
|
|
78
|
+
boxSizing: "border-box",
|
|
79
|
+
color: "#0f172a",
|
|
80
|
+
display: "flex",
|
|
81
|
+
justifyContent: "center",
|
|
82
|
+
minHeight: "100vh",
|
|
83
|
+
padding: "24px",
|
|
84
|
+
},
|
|
85
|
+
panel: {
|
|
86
|
+
background: "#fff",
|
|
87
|
+
border: "2px solid #0f172a",
|
|
88
|
+
borderRadius: "12px",
|
|
89
|
+
boxShadow: "6px 6px 0 #111827",
|
|
90
|
+
boxSizing: "border-box",
|
|
91
|
+
maxWidth: "520px",
|
|
92
|
+
padding: "28px",
|
|
93
|
+
width: "100%",
|
|
94
|
+
},
|
|
95
|
+
iconWrap: {
|
|
96
|
+
alignItems: "center",
|
|
97
|
+
background: "#fee2e2",
|
|
98
|
+
border: "2px solid #991b1b",
|
|
99
|
+
borderRadius: "999px",
|
|
100
|
+
display: "flex",
|
|
101
|
+
height: "56px",
|
|
102
|
+
justifyContent: "center",
|
|
103
|
+
marginBottom: "18px",
|
|
104
|
+
width: "56px",
|
|
105
|
+
},
|
|
106
|
+
eyebrow: {
|
|
107
|
+
color: "#991b1b",
|
|
108
|
+
fontSize: "12px",
|
|
109
|
+
fontWeight: 800,
|
|
110
|
+
letterSpacing: "0.08em",
|
|
111
|
+
margin: "0 0 8px",
|
|
112
|
+
textTransform: "uppercase",
|
|
113
|
+
},
|
|
114
|
+
title: {
|
|
115
|
+
fontSize: "28px",
|
|
116
|
+
fontWeight: 900,
|
|
117
|
+
lineHeight: 1.1,
|
|
118
|
+
margin: "0 0 12px",
|
|
119
|
+
},
|
|
120
|
+
body: {
|
|
121
|
+
color: "#475569",
|
|
122
|
+
fontSize: "15px",
|
|
123
|
+
lineHeight: 1.5,
|
|
124
|
+
margin: "0 0 20px",
|
|
125
|
+
},
|
|
126
|
+
details: {
|
|
127
|
+
marginBottom: "22px",
|
|
128
|
+
},
|
|
129
|
+
summary: {
|
|
130
|
+
color: "#334155",
|
|
131
|
+
cursor: "pointer",
|
|
132
|
+
fontSize: "14px",
|
|
133
|
+
fontWeight: 700,
|
|
134
|
+
marginBottom: "8px",
|
|
135
|
+
},
|
|
136
|
+
pre: {
|
|
137
|
+
background: "#f8fafc",
|
|
138
|
+
border: "1px solid #cbd5e1",
|
|
139
|
+
borderRadius: "8px",
|
|
140
|
+
color: "#334155",
|
|
141
|
+
fontSize: "12px",
|
|
142
|
+
lineHeight: 1.45,
|
|
143
|
+
margin: 0,
|
|
144
|
+
maxHeight: "180px",
|
|
145
|
+
overflow: "auto",
|
|
146
|
+
padding: "12px",
|
|
147
|
+
whiteSpace: "pre-wrap",
|
|
148
|
+
},
|
|
149
|
+
button: {
|
|
150
|
+
alignItems: "center",
|
|
151
|
+
background: "#0f172a",
|
|
152
|
+
border: "2px solid #0f172a",
|
|
153
|
+
borderRadius: "8px",
|
|
154
|
+
boxShadow: "3px 3px 0 #111827",
|
|
155
|
+
color: "#fff",
|
|
156
|
+
cursor: "pointer",
|
|
157
|
+
display: "inline-flex",
|
|
158
|
+
fontSize: "15px",
|
|
159
|
+
fontWeight: 800,
|
|
160
|
+
gap: "8px",
|
|
161
|
+
justifyContent: "center",
|
|
162
|
+
padding: "12px 16px",
|
|
163
|
+
width: "100%",
|
|
164
|
+
},
|
|
165
|
+
} satisfies Record<string, CSSProperties>;
|
|
166
|
+
|
|
69
167
|
return (
|
|
70
|
-
<div
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
aria-live="assertive"
|
|
74
|
-
>
|
|
75
|
-
<div className="max-w-md w-full bg-white rounded-2xl shadow-xl p-6 sm:p-8">
|
|
76
|
-
<div className="flex items-center justify-center w-16 h-16 mx-auto mb-4 bg-red-100 rounded-full">
|
|
168
|
+
<div style={styles.shell} role="alert" aria-live="assertive">
|
|
169
|
+
<div style={styles.panel}>
|
|
170
|
+
<div style={styles.iconWrap}>
|
|
77
171
|
<AlertTriangle
|
|
78
|
-
size={
|
|
79
|
-
|
|
172
|
+
size={28}
|
|
173
|
+
color="#991b1b"
|
|
174
|
+
strokeWidth={2.5}
|
|
80
175
|
aria-hidden="true"
|
|
81
176
|
/>
|
|
82
177
|
</div>
|
|
83
178
|
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
</h1>
|
|
179
|
+
<p style={styles.eyebrow}>Runtime error</p>
|
|
180
|
+
|
|
181
|
+
<h1 style={styles.title}>Game failed to start</h1>
|
|
87
182
|
|
|
88
|
-
<p
|
|
183
|
+
<p style={styles.body}>
|
|
89
184
|
The game encountered an error and couldn't continue. You can try
|
|
90
185
|
reloading to start fresh.
|
|
91
186
|
</p>
|
|
92
187
|
|
|
93
|
-
<details
|
|
94
|
-
<summary
|
|
95
|
-
|
|
96
|
-
</summary>
|
|
97
|
-
<pre className="text-xs bg-slate-50 text-slate-700 p-3 rounded-lg overflow-auto max-h-40 border border-slate-200">
|
|
188
|
+
<details style={styles.details}>
|
|
189
|
+
<summary style={styles.summary}>Technical details</summary>
|
|
190
|
+
<pre style={styles.pre}>
|
|
98
191
|
{error.message}
|
|
99
192
|
{error.stack && (
|
|
100
193
|
<>
|
|
@@ -105,11 +198,7 @@ function DefaultErrorFallback({
|
|
|
105
198
|
</pre>
|
|
106
199
|
</details>
|
|
107
200
|
|
|
108
|
-
<button
|
|
109
|
-
type="button"
|
|
110
|
-
onClick={onReset}
|
|
111
|
-
className="w-full flex items-center justify-center gap-2 px-4 py-3 bg-blue-600 hover:bg-blue-700 active:bg-blue-800 text-white font-medium rounded-lg transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
|
112
|
-
>
|
|
201
|
+
<button type="button" onClick={onReset} style={styles.button}>
|
|
113
202
|
<RefreshCw size={18} aria-hidden="true" />
|
|
114
203
|
Try again
|
|
115
204
|
</button>
|
|
@@ -66,6 +66,7 @@ function renderForm(
|
|
|
66
66
|
<InteractionForm
|
|
67
67
|
descriptor={descriptor}
|
|
68
68
|
handle={createHandle(descriptor, values)}
|
|
69
|
+
accordion={false}
|
|
69
70
|
/>
|
|
70
71
|
</ThemeProvider>,
|
|
71
72
|
);
|
|
@@ -117,6 +118,29 @@ test("InteractionForm renders choice select values with icons", () => {
|
|
|
117
118
|
expect(html).toContain("Wood");
|
|
118
119
|
});
|
|
119
120
|
|
|
121
|
+
test("InteractionForm renders explicit null choice defaults", () => {
|
|
122
|
+
const descriptor = createDescriptor([
|
|
123
|
+
{
|
|
124
|
+
key: "bonus",
|
|
125
|
+
kind: "form",
|
|
126
|
+
defaultValue: null,
|
|
127
|
+
domain: {
|
|
128
|
+
type: "choice",
|
|
129
|
+
choices: [
|
|
130
|
+
{ value: null, label: "No bonus" },
|
|
131
|
+
{ value: "coin", label: "Coin" },
|
|
132
|
+
{ value: "card", label: "Card" },
|
|
133
|
+
{ value: "point", label: "Point" },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
const html = renderForm(descriptor, { bonus: null });
|
|
140
|
+
|
|
141
|
+
expect(html).toContain("No bonus");
|
|
142
|
+
});
|
|
143
|
+
|
|
120
144
|
test("InteractionForm renders choice badges and selected descriptions", () => {
|
|
121
145
|
const descriptor = createDescriptor([
|
|
122
146
|
{
|
|
@@ -30,6 +30,10 @@ import type {
|
|
|
30
30
|
InputDomain,
|
|
31
31
|
} from "../types/plugin-state.js";
|
|
32
32
|
import { interactionLabel } from "../utils/interaction-labels.js";
|
|
33
|
+
import {
|
|
34
|
+
resolveInputDomain,
|
|
35
|
+
resolveInteractionInputs,
|
|
36
|
+
} from "../utils/interaction-inputs.js";
|
|
33
37
|
import { useChromeSuppression } from "./ChromeSuppressionContext.js";
|
|
34
38
|
import { ThemedButton } from "./ThemedButton.js";
|
|
35
39
|
|
|
@@ -125,12 +129,15 @@ export function InteractionForm<
|
|
|
125
129
|
const hidden = useMemo(() => new Set(hiddenFields ?? []), [hiddenFields]);
|
|
126
130
|
const visibleInputs = useMemo(() => {
|
|
127
131
|
const allowed = fields ? new Set(fields) : null;
|
|
128
|
-
return defaultFormInputs(
|
|
132
|
+
return defaultFormInputs(
|
|
133
|
+
descriptor,
|
|
134
|
+
handle.values as Readonly<Record<string, unknown>>,
|
|
135
|
+
).filter((input) => {
|
|
129
136
|
const key = input.key as keyof Params & string;
|
|
130
137
|
if (allowed && !allowed.has(key)) return false;
|
|
131
138
|
return !hidden.has(key);
|
|
132
139
|
});
|
|
133
|
-
}, [descriptor, fields, hidden]);
|
|
140
|
+
}, [descriptor, fields, hidden, handle.values]);
|
|
134
141
|
|
|
135
142
|
const currentValidation = validation;
|
|
136
143
|
const fieldErrors = (currentValidation?.fieldErrors ?? {}) as Partial<
|
|
@@ -236,17 +243,13 @@ export function InteractionForm<
|
|
|
236
243
|
);
|
|
237
244
|
})}
|
|
238
245
|
</div>
|
|
239
|
-
) :
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}}
|
|
245
|
-
>
|
|
246
|
-
No visible fields are required. Board or card selection may complete
|
|
247
|
-
this interaction.
|
|
248
|
-
</span>
|
|
246
|
+
) : null;
|
|
247
|
+
|
|
248
|
+
if (visibleInputs.length === 0 && !handle.isReady) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`InteractionForm '${descriptor.interactionKey}' has required inputs that cannot be rendered by the default form. Provide renderFields, select on a surface, or use a default-renderable input domain.`,
|
|
249
251
|
);
|
|
252
|
+
}
|
|
250
253
|
|
|
251
254
|
const formErrorContent =
|
|
252
255
|
formErrors.length > 0 ? (
|
|
@@ -396,7 +399,10 @@ export function InteractionField<
|
|
|
396
399
|
(candidate) => candidate.key === inputKey,
|
|
397
400
|
);
|
|
398
401
|
if (!input) return null;
|
|
399
|
-
const typedInput =
|
|
402
|
+
const typedInput = resolveInputDomain(
|
|
403
|
+
input,
|
|
404
|
+
handle.values as Readonly<Record<string, unknown>>,
|
|
405
|
+
) as InteractionInputDescriptor & { key: Key };
|
|
400
406
|
const value = handle.values[inputKey] as Params[Key] | undefined;
|
|
401
407
|
const props: InteractionFieldRenderProps<Params, Key> = {
|
|
402
408
|
descriptor,
|
|
@@ -421,8 +427,9 @@ export function hasDefaultInteractionFormFields(
|
|
|
421
427
|
|
|
422
428
|
export function defaultFormInputs(
|
|
423
429
|
descriptor: Pick<InteractionDescriptor, "inputs">,
|
|
430
|
+
values: Readonly<Record<string, unknown>> = {},
|
|
424
431
|
): InteractionInputDescriptor[] {
|
|
425
|
-
return descriptor.
|
|
432
|
+
return resolveInteractionInputs(descriptor, values).filter((input) => {
|
|
426
433
|
switch (input.domain.type) {
|
|
427
434
|
case "choice":
|
|
428
435
|
case "choiceList":
|
|
@@ -589,6 +596,21 @@ function ChoiceDescription({ choice }: { choice?: InteractionChoiceOption }) {
|
|
|
589
596
|
);
|
|
590
597
|
}
|
|
591
598
|
|
|
599
|
+
const NULL_CHOICE_SELECT_VALUE = "__dreamboard_null_choice__";
|
|
600
|
+
|
|
601
|
+
function choiceRenderKey(choice: InteractionChoiceOption): string {
|
|
602
|
+
return choice.value === null ? NULL_CHOICE_SELECT_VALUE : choice.value;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function encodeChoiceSelectValue(value: unknown): string | undefined {
|
|
606
|
+
if (value === null) return NULL_CHOICE_SELECT_VALUE;
|
|
607
|
+
return typeof value === "string" ? value : undefined;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function decodeChoiceSelectValue(value: string): string | null {
|
|
611
|
+
return value === NULL_CHOICE_SELECT_VALUE ? null : value;
|
|
612
|
+
}
|
|
613
|
+
|
|
592
614
|
function ChoiceField<
|
|
593
615
|
Params extends InteractionParamsShape,
|
|
594
616
|
Key extends keyof Params & string,
|
|
@@ -608,7 +630,7 @@ function ChoiceField<
|
|
|
608
630
|
const choices = domain.choices ?? [];
|
|
609
631
|
const controlId = useId();
|
|
610
632
|
const selectedChoice =
|
|
611
|
-
typeof value === "string"
|
|
633
|
+
typeof value === "string" || value === null
|
|
612
634
|
? choices.find((choice) => choice.value === value)
|
|
613
635
|
: undefined;
|
|
614
636
|
if (choices.length > 0 && choices.length <= 3) {
|
|
@@ -629,7 +651,7 @@ function ChoiceField<
|
|
|
629
651
|
const selected = value === choice.value;
|
|
630
652
|
return (
|
|
631
653
|
<ThemedButton
|
|
632
|
-
key={choice
|
|
654
|
+
key={choiceRenderKey(choice)}
|
|
633
655
|
type="button"
|
|
634
656
|
variant={selected ? "primary" : "secondary"}
|
|
635
657
|
size="sm"
|
|
@@ -656,8 +678,10 @@ function ChoiceField<
|
|
|
656
678
|
>
|
|
657
679
|
<Select
|
|
658
680
|
disabled={disabled}
|
|
659
|
-
value={
|
|
660
|
-
onValueChange={(next: string) =>
|
|
681
|
+
value={encodeChoiceSelectValue(value)}
|
|
682
|
+
onValueChange={(next: string) =>
|
|
683
|
+
setValue(decodeChoiceSelectValue(next) as Params[Key])
|
|
684
|
+
}
|
|
661
685
|
>
|
|
662
686
|
<SelectTrigger id={controlId} size="sm" className="w-full bg-white">
|
|
663
687
|
<span data-slot="select-value">
|
|
@@ -678,8 +702,8 @@ function ChoiceField<
|
|
|
678
702
|
>
|
|
679
703
|
{choices.map((choice) => (
|
|
680
704
|
<SelectItem
|
|
681
|
-
key={choice
|
|
682
|
-
value={choice
|
|
705
|
+
key={choiceRenderKey(choice)}
|
|
706
|
+
value={choiceRenderKey(choice)}
|
|
683
707
|
textValue={choice.label}
|
|
684
708
|
disabled={choice.disabled}
|
|
685
709
|
>
|
|
@@ -748,10 +772,11 @@ function ChoiceListField<
|
|
|
748
772
|
>
|
|
749
773
|
<span style={{ display: "flex", flexWrap: "wrap", gap: theme.space[1] }}>
|
|
750
774
|
{(domain.choices ?? []).map((choice) => {
|
|
751
|
-
const
|
|
775
|
+
const value = choice.value as string;
|
|
776
|
+
const checked = selected.has(value);
|
|
752
777
|
return (
|
|
753
778
|
<ThemedButton
|
|
754
|
-
key={
|
|
779
|
+
key={value}
|
|
755
780
|
type="button"
|
|
756
781
|
variant={checked ? "primary" : "secondary"}
|
|
757
782
|
size="sm"
|
|
@@ -762,7 +787,7 @@ function ChoiceListField<
|
|
|
762
787
|
}
|
|
763
788
|
aria-pressed={checked}
|
|
764
789
|
title={choice.disabledReason ?? choice.description}
|
|
765
|
-
onClick={() => toggle(
|
|
790
|
+
onClick={() => toggle(value)}
|
|
766
791
|
className="h-8 px-3 text-sm"
|
|
767
792
|
>
|
|
768
793
|
<ChoiceOptionLabel choice={choice} />
|
|
@@ -50,6 +50,10 @@ import type {
|
|
|
50
50
|
InteractionParamsShape,
|
|
51
51
|
} from "../hooks/useInteractionHandle.js";
|
|
52
52
|
import { interactionLabel } from "../utils/interaction-labels.js";
|
|
53
|
+
import {
|
|
54
|
+
submitInteractionDraft,
|
|
55
|
+
submitInteractionParams,
|
|
56
|
+
} from "../primitives/interaction-submit.js";
|
|
53
57
|
import { ThemedButton } from "./ThemedButton.js";
|
|
54
58
|
import type { SubmittedActionConfig } from "./surfaces/internal/DefaultInteractionButton.js";
|
|
55
59
|
|
|
@@ -289,13 +293,23 @@ export function PrimaryActionButton<
|
|
|
289
293
|
setPending(true);
|
|
290
294
|
try {
|
|
291
295
|
if (params !== undefined) {
|
|
292
|
-
await
|
|
296
|
+
await submitInteractionParams(
|
|
297
|
+
handle,
|
|
298
|
+
params as Params,
|
|
299
|
+
{},
|
|
300
|
+
{
|
|
301
|
+
unhandledError: "ignore",
|
|
302
|
+
},
|
|
303
|
+
);
|
|
293
304
|
} else {
|
|
294
|
-
await
|
|
305
|
+
await submitInteractionDraft(
|
|
306
|
+
handle,
|
|
307
|
+
{},
|
|
308
|
+
{
|
|
309
|
+
unhandledError: "ignore",
|
|
310
|
+
},
|
|
311
|
+
);
|
|
295
312
|
}
|
|
296
|
-
} catch {
|
|
297
|
-
// Descriptor availability is authoritative; submit errors
|
|
298
|
-
// surface via the runtime's error channel.
|
|
299
313
|
} finally {
|
|
300
314
|
setPending(false);
|
|
301
315
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "bun:test";
|
|
2
2
|
import { renderToString } from "react-dom/server";
|
|
3
|
-
import { ThemeProvider } from "../theme/ThemeProvider.js";
|
|
4
3
|
import {
|
|
5
4
|
ResourceCounter,
|
|
6
5
|
type ResourceDisplayConfig,
|
|
@@ -10,20 +9,21 @@ const resources: ResourceDisplayConfig[] = [
|
|
|
10
9
|
{ type: "gold", label: "Gold", icon: "🪙" },
|
|
11
10
|
];
|
|
12
11
|
|
|
13
|
-
test("ResourceCounter
|
|
12
|
+
test("ResourceCounter provides headless resource state to author markup", () => {
|
|
14
13
|
const html = renderToString(
|
|
15
|
-
<
|
|
16
|
-
<ResourceCounter
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
showZero
|
|
22
|
-
/>
|
|
23
|
-
</ThemeProvider>,
|
|
14
|
+
<ResourceCounter.Root resources={resources} counts={{ gold: 3 }}>
|
|
15
|
+
<ResourceCounter.Item className="author-chip">
|
|
16
|
+
<ResourceCounter.Icon className="author-icon" />
|
|
17
|
+
<ResourceCounter.Count />
|
|
18
|
+
</ResourceCounter.Item>
|
|
19
|
+
</ResourceCounter.Root>,
|
|
24
20
|
);
|
|
25
21
|
|
|
26
|
-
expect(html).toContain('data-
|
|
22
|
+
expect(html).toContain('data-dreamboard-resource-counter=""');
|
|
23
|
+
expect(html).toContain('class="author-chip"');
|
|
24
|
+
expect(html).toContain('class="author-icon"');
|
|
25
|
+
expect(html).toContain('data-resource-id="gold"');
|
|
26
|
+
expect(html).toContain('data-resource-count="3"');
|
|
27
27
|
expect(html).toContain('aria-label="Gold: 3"');
|
|
28
|
-
expect(html).not.toContain("
|
|
28
|
+
expect(html).not.toContain("tooltip");
|
|
29
29
|
});
|