@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.
Files changed (166) hide show
  1. package/dist/components/ActionButton.d.ts.map +1 -1
  2. package/dist/components/ActionButton.js +2 -1
  3. package/dist/components/Card.d.ts +1 -1
  4. package/dist/components/Card.d.ts.map +1 -1
  5. package/dist/components/DiceRoller.d.ts +3 -2
  6. package/dist/components/DiceRoller.d.ts.map +1 -1
  7. package/dist/components/DiceRoller.js +4 -13
  8. package/dist/components/ErrorBoundary.d.ts.map +1 -1
  9. package/dist/components/ErrorBoundary.js +94 -2
  10. package/dist/components/InteractionForm.d.ts +1 -1
  11. package/dist/components/InteractionForm.d.ts.map +1 -1
  12. package/dist/components/InteractionForm.js +29 -15
  13. package/dist/components/PrimaryActionButton.d.ts.map +1 -1
  14. package/dist/components/PrimaryActionButton.js +7 -6
  15. package/dist/components/ResourceCounter.d.ts +59 -25
  16. package/dist/components/ResourceCounter.d.ts.map +1 -1
  17. package/dist/components/ResourceCounter.js +106 -115
  18. package/dist/components/Toast.d.ts +13 -6
  19. package/dist/components/Toast.d.ts.map +1 -1
  20. package/dist/components/Toast.js +10 -5
  21. package/dist/components/board/HexGrid.js +6 -6
  22. package/dist/components/board/target-layer.d.ts +18 -2
  23. package/dist/components/board/target-layer.d.ts.map +1 -1
  24. package/dist/components/board/target-layer.js +20 -3
  25. package/dist/components/index.d.ts +3 -4
  26. package/dist/components/index.d.ts.map +1 -1
  27. package/dist/components/index.js +3 -4
  28. package/dist/components/surfaces/InboxSurface.d.ts.map +1 -1
  29. package/dist/components/surfaces/InboxSurface.js +2 -6
  30. package/dist/components/surfaces/PlayerCardsSurface.js +2 -2
  31. package/dist/components/surfaces/internal/CardZoneRoutedForm.d.ts +7 -0
  32. package/dist/components/surfaces/internal/CardZoneRoutedForm.d.ts.map +1 -0
  33. package/dist/components/surfaces/internal/CardZoneRoutedForm.js +9 -0
  34. package/dist/components/surfaces/internal/DefaultInteractionButton.d.ts.map +1 -1
  35. package/dist/components/surfaces/internal/DefaultInteractionButton.js +5 -8
  36. package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts +2 -2
  37. package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts.map +1 -1
  38. package/dist/components/surfaces/internal/useCardZoneInteractions.js +19 -43
  39. package/dist/context/InteractionDraftContext.d.ts +11 -2
  40. package/dist/context/InteractionDraftContext.d.ts.map +1 -1
  41. package/dist/context/InteractionDraftContext.js +41 -4
  42. package/dist/defaults/components.d.ts +0 -5
  43. package/dist/defaults/components.d.ts.map +1 -1
  44. package/dist/defaults/components.js +7 -11
  45. package/dist/hooks/useBoardInteractions.d.ts +35 -12
  46. package/dist/hooks/useBoardInteractions.d.ts.map +1 -1
  47. package/dist/hooks/useBoardInteractions.js +186 -82
  48. package/dist/hooks/useInteractionHandle.d.ts +1 -1
  49. package/dist/hooks/useInteractionHandle.d.ts.map +1 -1
  50. package/dist/hooks/useInteractionHandle.js +12 -27
  51. package/dist/index.d.ts +11 -17
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +5 -14
  54. package/dist/primitives/board.d.ts +53 -3
  55. package/dist/primitives/board.d.ts.map +1 -1
  56. package/dist/primitives/board.js +65 -41
  57. package/dist/primitives/dialog-lifecycle.d.ts +17 -0
  58. package/dist/primitives/dialog-lifecycle.d.ts.map +1 -0
  59. package/dist/primitives/dialog-lifecycle.js +24 -0
  60. package/dist/primitives/dice.d.ts +31 -0
  61. package/dist/primitives/dice.d.ts.map +1 -0
  62. package/dist/primitives/dice.js +33 -0
  63. package/dist/primitives/game.d.ts +55 -0
  64. package/dist/primitives/game.d.ts.map +1 -0
  65. package/dist/primitives/game.js +101 -0
  66. package/dist/primitives/index.d.ts +7 -4
  67. package/dist/primitives/index.d.ts.map +1 -1
  68. package/dist/primitives/index.js +7 -4
  69. package/dist/primitives/interaction-form-binding.d.ts +12 -0
  70. package/dist/primitives/interaction-form-binding.d.ts.map +1 -0
  71. package/dist/primitives/interaction-form-binding.js +14 -0
  72. package/dist/primitives/interaction-submit.d.ts +23 -0
  73. package/dist/primitives/interaction-submit.d.ts.map +1 -0
  74. package/dist/primitives/interaction-submit.js +41 -0
  75. package/dist/primitives/interaction.d.ts +76 -6
  76. package/dist/primitives/interaction.d.ts.map +1 -1
  77. package/dist/primitives/interaction.js +210 -26
  78. package/dist/primitives/player-roster.d.ts +2 -1
  79. package/dist/primitives/player-roster.d.ts.map +1 -1
  80. package/dist/primitives/prompt.d.ts +36 -11
  81. package/dist/primitives/prompt.d.ts.map +1 -1
  82. package/dist/primitives/prompt.js +29 -17
  83. package/dist/primitives/ui.d.ts +9 -0
  84. package/dist/primitives/ui.d.ts.map +1 -0
  85. package/dist/primitives/ui.js +7 -0
  86. package/dist/primitives/zone.d.ts +111 -5
  87. package/dist/primitives/zone.d.ts.map +1 -1
  88. package/dist/primitives/zone.js +349 -9
  89. package/dist/reducer.d.ts +2 -14
  90. package/dist/reducer.d.ts.map +1 -1
  91. package/dist/reducer.js +1 -14
  92. package/dist/runtime/createPluginRuntimeAPI.js +1 -1
  93. package/dist/types/hex-color.d.ts +7 -0
  94. package/dist/types/hex-color.d.ts.map +1 -0
  95. package/dist/types/hex-color.js +13 -0
  96. package/dist/types/player-state.d.ts +28 -14
  97. package/dist/types/player-state.d.ts.map +1 -1
  98. package/dist/types/plugin-state.d.ts +9 -3
  99. package/dist/types/plugin-state.d.ts.map +1 -1
  100. package/dist/ui-contract.d.ts +119 -14
  101. package/dist/ui-contract.d.ts.map +1 -1
  102. package/dist/ui-contract.js +4 -3
  103. package/dist/ui-sdk.d.ts +1637 -1245
  104. package/dist/utils/interaction-inputs.d.ts +8 -5
  105. package/dist/utils/interaction-inputs.d.ts.map +1 -1
  106. package/dist/utils/interaction-inputs.js +82 -14
  107. package/dist/utils/interaction-router.d.ts +31 -0
  108. package/dist/utils/interaction-router.d.ts.map +1 -0
  109. package/dist/utils/interaction-router.js +114 -0
  110. package/package.json +1 -1
  111. package/src/components/ActionButton.tsx +2 -1
  112. package/src/components/Card.tsx +1 -1
  113. package/src/components/DiceRoller.tsx +13 -22
  114. package/src/components/ErrorBoundary.test.tsx +19 -0
  115. package/src/components/ErrorBoundary.tsx +113 -24
  116. package/src/components/InteractionForm.test.tsx +24 -0
  117. package/src/components/InteractionForm.tsx +48 -23
  118. package/src/components/PrimaryActionButton.tsx +19 -5
  119. package/src/components/ResourceCounter.test.tsx +13 -13
  120. package/src/components/ResourceCounter.tsx +238 -244
  121. package/src/components/Toast.tsx +23 -10
  122. package/src/components/__fixtures__/ResourceCounter.fixture.tsx +70 -169
  123. package/src/components/board/HexGrid.tsx +6 -6
  124. package/src/components/board/target-layer.ts +44 -5
  125. package/src/components/index.ts +17 -10
  126. package/src/components/surfaces/InboxSurface.tsx +7 -5
  127. package/src/components/surfaces/PlayerCardsSurface.tsx +6 -6
  128. package/src/components/surfaces/internal/CardZoneRoutedForm.tsx +35 -0
  129. package/src/components/surfaces/internal/DefaultInteractionButton.tsx +17 -7
  130. package/src/components/surfaces/internal/useCardZoneInteractions.ts +25 -67
  131. package/src/context/InteractionDraftContext.tsx +51 -5
  132. package/src/defaults/components.tsx +12 -50
  133. package/src/defaults/defaults.test.tsx +1 -50
  134. package/src/hooks/useBoardInteractions.test.tsx +240 -17
  135. package/src/hooks/useBoardInteractions.ts +330 -105
  136. package/src/hooks/useInteractionHandle.ts +23 -28
  137. package/src/index.test.ts +60 -40
  138. package/src/index.ts +30 -36
  139. package/src/primitives/board.test.tsx +73 -0
  140. package/src/primitives/board.tsx +191 -40
  141. package/src/primitives/dialog-lifecycle.ts +58 -0
  142. package/src/primitives/dice.test.tsx +47 -0
  143. package/src/primitives/dice.tsx +79 -0
  144. package/src/primitives/game.test.tsx +98 -0
  145. package/src/primitives/game.tsx +213 -0
  146. package/src/primitives/index.ts +84 -0
  147. package/src/primitives/interaction-form-binding.tsx +56 -0
  148. package/src/primitives/interaction-submit.ts +90 -0
  149. package/src/primitives/interaction.test.tsx +396 -0
  150. package/src/primitives/interaction.tsx +451 -31
  151. package/src/primitives/player-roster.tsx +2 -1
  152. package/src/primitives/prompt.test.tsx +94 -3
  153. package/src/primitives/prompt.tsx +87 -48
  154. package/src/primitives/ui.test.tsx +131 -0
  155. package/src/primitives/ui.tsx +13 -0
  156. package/src/primitives/zone.test.tsx +305 -0
  157. package/src/primitives/zone.tsx +660 -12
  158. package/src/reducer.ts +7 -20
  159. package/src/runtime/createPluginRuntimeAPI.ts +1 -1
  160. package/src/types/hex-color.ts +20 -0
  161. package/src/types/player-state.ts +36 -18
  162. package/src/types/plugin-state.ts +10 -3
  163. package/src/ui-contract.ts +253 -21
  164. package/src/utils/interaction-inputs.test.ts +400 -0
  165. package/src/utils/interaction-inputs.ts +113 -11
  166. package/src/utils/interaction-router.ts +200 -0
@@ -2,6 +2,9 @@ import type { InteractionDescriptor, InteractionInputDescriptor, InputSelection
2
2
  export type BoardTargetKind = "edge" | "vertex" | "space" | "tile";
3
3
  export declare function interactionInputKeys(descriptor: Pick<InteractionDescriptor, "inputs">): string[];
4
4
  export declare function applyInteractionInputDefaults<Params extends Record<string, unknown>>(descriptor: Pick<InteractionDescriptor, "inputs">, params: Readonly<Partial<Params>>): Partial<Params>;
5
+ export declare function resolveInputDomain(input: InteractionInputDescriptor, params: Readonly<Record<string, unknown>>): InteractionInputDescriptor;
6
+ export declare function resolveInteractionInputs(descriptor: Pick<InteractionDescriptor, "inputs">, params: Readonly<Record<string, unknown>>): InteractionInputDescriptor[];
7
+ export declare function dependentInputKeys(descriptor: Pick<InteractionDescriptor, "inputs">, changedKey: string): string[];
5
8
  export declare function validateInteractionInputDomains(descriptor: Pick<InteractionDescriptor, "inputs">, params: Readonly<Record<string, unknown>>): Partial<Record<string, readonly string[]>>;
6
9
  export declare function isInputValueReady(input: InteractionInputDescriptor, value: unknown): boolean;
7
10
  export declare function isManyInput(input: InteractionInputDescriptor): boolean;
@@ -11,12 +14,12 @@ export declare function isManyTargetSelectable(input: InteractionInputDescriptor
11
14
  export declare function mergeInteractionFieldErrors(...sources: Array<Partial<Record<string, readonly string[]>>>): Partial<Record<string, readonly string[]>>;
12
15
  export declare function hasInteractionFieldErrors(fieldErrors: Partial<Record<string, readonly string[]>>): boolean;
13
16
  export declare function inputByKey(descriptor: Pick<InteractionDescriptor, "inputs">, key: string): InteractionInputDescriptor | undefined;
14
- export declare function inputByTarget(descriptor: Pick<InteractionDescriptor, "inputs">, targetKind: BoardTargetKind | "card", targetId: string): InteractionInputDescriptor | null;
15
- export declare function eligibleTargetsForInput(descriptor: Pick<InteractionDescriptor, "inputs">, key: string): readonly string[] | undefined;
16
- export declare function eligibleTargetsByInput(descriptor: Pick<InteractionDescriptor, "inputs">): Record<string, readonly string[]>;
17
- export declare function eligibleTargetsByBoardKind(descriptor: Pick<InteractionDescriptor, "inputs">): Partial<Record<BoardTargetKind, readonly string[]>>;
17
+ export declare function inputByTarget(descriptor: Pick<InteractionDescriptor, "inputs">, targetKind: BoardTargetKind | "card", targetId: string, params?: Readonly<Record<string, unknown>>): InteractionInputDescriptor | null;
18
+ export declare function eligibleTargetsForInput(descriptor: Pick<InteractionDescriptor, "inputs">, key: string, params?: Readonly<Record<string, unknown>>): readonly string[] | undefined;
19
+ export declare function eligibleTargetsByInput(descriptor: Pick<InteractionDescriptor, "inputs">, params?: Readonly<Record<string, unknown>>): Record<string, readonly string[]>;
20
+ export declare function eligibleTargetsByBoardKind(descriptor: Pick<InteractionDescriptor, "inputs">, params?: Readonly<Record<string, unknown>>): Partial<Record<BoardTargetKind, readonly string[]>>;
18
21
  export declare function hasBoardTargetInput(descriptor: Pick<InteractionDescriptor, "inputs">): boolean;
19
22
  export declare function hasCardTargetInput(descriptor: Pick<InteractionDescriptor, "inputs">): boolean;
20
23
  export declare function interactionArmScope(descriptor: Pick<InteractionDescriptor, "inputs" | "interactionKey" | "zoneId">): string;
21
- export declare function inputKeyForTarget(descriptor: Pick<InteractionDescriptor, "inputs">, targetKind: BoardTargetKind | "card", targetId: string): string | null;
24
+ export declare function inputKeyForTarget(descriptor: Pick<InteractionDescriptor, "inputs">, targetKind: BoardTargetKind | "card", targetId: string, params?: Readonly<Record<string, unknown>>): string | null;
22
25
  //# sourceMappingURL=interaction-inputs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"interaction-inputs.d.ts","sourceRoot":"","sources":["../../src/utils/interaction-inputs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"interaction-inputs.d.ts","sourceRoot":"","sources":["../../src/utils/interaction-inputs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,0BAA0B,EAC1B,cAAc,EACf,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAEnE,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,GAChD,MAAM,EAAE,CAEV;AAED,wBAAgB,6BAA6B,CAC3C,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAEtC,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,EACjD,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAChC,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,0BAA0B,EACjC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxC,0BAA0B,CAoB5B;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,EACjD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxC,0BAA0B,EAAE,CAE9B;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,EACjD,UAAU,EAAE,MAAM,GACjB,MAAM,EAAE,CAmBV;AAED,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,EACjD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAqF5C;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,0BAA0B,EACjC,KAAK,EAAE,OAAO,GACb,OAAO,CAKT;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAEtE;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,0BAA0B,GAChC,MAAM,GAAG,SAAS,CAGpB;AAED,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,cAAc,GACxB,MAAM,EAAE,CAYV;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,0BAA0B,EACjC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAST;AAED,wBAAgB,2BAA2B,CACzC,GAAG,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC,GAC5D,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAS5C;AAED,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,GACtD,OAAO,CAIT;AAED,wBAAgB,UAAU,CACxB,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,EACjD,GAAG,EAAE,MAAM,GACV,0BAA0B,GAAG,SAAS,CAExC;AAED,wBAAgB,aAAa,CAC3B,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,EACjD,UAAU,EAAE,eAAe,GAAG,MAAM,EACpC,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GAC7C,0BAA0B,GAAG,IAAI,CAQnC;AAED,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,EACjD,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GAC7C,SAAS,MAAM,EAAE,GAAG,SAAS,CAI/B;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,EACjD,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GAC7C,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAWnC;AAED,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,EACjD,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GAC7C,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAqBrD;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,GAChD,OAAO,CAET;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,GAChD,OAAO,CAKT;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,IAAI,CACd,qBAAqB,EACrB,QAAQ,GAAG,gBAAgB,GAAG,QAAQ,CACvC,GACA,MAAM,CAMR;AAED,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,QAAQ,CAAC,EACjD,UAAU,EAAE,eAAe,GAAG,MAAM,EACpC,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GAC7C,MAAM,GAAG,IAAI,CAEf"}
@@ -12,9 +12,52 @@ export function applyInteractionInputDefaults(descriptor, params) {
12
12
  }
13
13
  return next;
14
14
  }
15
+ export function resolveInputDomain(input, params) {
16
+ const dependencies = input.domain.dependsOn ?? [];
17
+ if (dependencies.length === 0)
18
+ return input;
19
+ const caseMatch = input.domain.dependentCases?.find((candidate) => dependencies.every((key) => params[key] !== undefined &&
20
+ params[key] !== null &&
21
+ String(params[key]) === candidate.when[key]));
22
+ if (!caseMatch)
23
+ return input;
24
+ return {
25
+ ...input,
26
+ domain: {
27
+ ...caseMatch.domain,
28
+ dependsOn: dependencies,
29
+ dependentCases: input.domain.dependentCases,
30
+ },
31
+ };
32
+ }
33
+ export function resolveInteractionInputs(descriptor, params) {
34
+ return descriptor.inputs.map((input) => resolveInputDomain(input, params));
35
+ }
36
+ export function dependentInputKeys(descriptor, changedKey) {
37
+ const dependentsByKey = new Map();
38
+ for (const input of descriptor.inputs) {
39
+ for (const dependency of input.domain.dependsOn ?? []) {
40
+ dependentsByKey.set(dependency, [
41
+ ...(dependentsByKey.get(dependency) ?? []),
42
+ input.key,
43
+ ]);
44
+ }
45
+ }
46
+ const result = [];
47
+ const queue = [...(dependentsByKey.get(changedKey) ?? [])];
48
+ while (queue.length > 0) {
49
+ const key = queue.shift();
50
+ if (!key || result.includes(key))
51
+ continue;
52
+ result.push(key);
53
+ queue.push(...(dependentsByKey.get(key) ?? []));
54
+ }
55
+ return result;
56
+ }
15
57
  export function validateInteractionInputDomains(descriptor, params) {
16
58
  const fieldErrors = {};
17
- for (const input of descriptor.inputs) {
59
+ for (const rawInput of descriptor.inputs) {
60
+ const input = resolveInputDomain(rawInput, params);
18
61
  const value = params[input.key];
19
62
  if (value === undefined || value === null)
20
63
  continue;
@@ -37,6 +80,17 @@ export function validateInteractionInputDomains(descriptor, params) {
37
80
  if (value.length > max) {
38
81
  pushFieldError(fieldErrors, input.key, `Choose at most ${max} ${pluralize("option", max)}.`);
39
82
  }
83
+ const allowed = new Set(input.domain.choices?.map((choice) => choice.value));
84
+ if (allowed.size > 0 &&
85
+ value.some((item) => !allowed.has(String(item)))) {
86
+ pushFieldError(fieldErrors, input.key, "Selected choice is not eligible.");
87
+ }
88
+ }
89
+ if (input.domain.type === "choice") {
90
+ const allowed = new Set(input.domain.choices?.map((choice) => choice.value));
91
+ if (allowed.size > 0 && !allowed.has(value)) {
92
+ pushFieldError(fieldErrors, input.key, "Selected choice is not eligible.");
93
+ }
40
94
  }
41
95
  if (input.domain.type === "target") {
42
96
  const values = valuesForSelection(input.domain.selection, value);
@@ -111,8 +165,9 @@ export function hasInteractionFieldErrors(fieldErrors) {
111
165
  export function inputByKey(descriptor, key) {
112
166
  return descriptor.inputs.find((input) => input.key === key);
113
167
  }
114
- export function inputByTarget(descriptor, targetKind, targetId) {
115
- for (const input of descriptor.inputs) {
168
+ export function inputByTarget(descriptor, targetKind, targetId, params = {}) {
169
+ for (const rawInput of descriptor.inputs) {
170
+ const input = resolveInputDomain(rawInput, params);
116
171
  if (input.domain.type !== "target")
117
172
  continue;
118
173
  if (input.domain.targetKind !== targetKind)
@@ -122,26 +177,29 @@ export function inputByTarget(descriptor, targetKind, targetId) {
122
177
  }
123
178
  return null;
124
179
  }
125
- export function eligibleTargetsForInput(descriptor, key) {
126
- const domain = inputByKey(descriptor, key)?.domain;
180
+ export function eligibleTargetsForInput(descriptor, key, params = {}) {
181
+ const rawInput = inputByKey(descriptor, key);
182
+ const domain = rawInput ? resolveInputDomain(rawInput, params).domain : null;
127
183
  return domain?.type === "target" ? domain.eligibleTargets : undefined;
128
184
  }
129
- export function eligibleTargetsByInput(descriptor) {
130
- return Object.fromEntries(descriptor.inputs.flatMap((input) => {
185
+ export function eligibleTargetsByInput(descriptor, params = {}) {
186
+ return Object.fromEntries(descriptor.inputs.flatMap((rawInput) => {
187
+ const input = resolveInputDomain(rawInput, params);
131
188
  const targets = input.domain.type === "target"
132
189
  ? input.domain.eligibleTargets
133
190
  : undefined;
134
191
  return targets ? [[input.key, targets]] : [];
135
192
  }));
136
193
  }
137
- export function eligibleTargetsByBoardKind(descriptor) {
194
+ export function eligibleTargetsByBoardKind(descriptor, params = {}) {
138
195
  const result = {
139
196
  edge: new Set(),
140
197
  vertex: new Set(),
141
198
  space: new Set(),
142
199
  tile: new Set(),
143
200
  };
144
- for (const input of descriptor.inputs) {
201
+ for (const rawInput of descriptor.inputs) {
202
+ const input = resolveInputDomain(rawInput, params);
145
203
  if (input.domain.type !== "target")
146
204
  continue;
147
205
  const targetKind = input.domain.targetKind;
@@ -154,14 +212,13 @@ export function eligibleTargetsByBoardKind(descriptor) {
154
212
  return Object.fromEntries(Object.entries(result).flatMap(([kind, ids]) => ids.size > 0 ? [[kind, [...ids]]] : []));
155
213
  }
156
214
  export function hasBoardTargetInput(descriptor) {
157
- return Object.keys(eligibleTargetsByBoardKind(descriptor)).length > 0;
215
+ return boardTargetKindsOf(descriptor).length > 0;
158
216
  }
159
217
  export function hasCardTargetInput(descriptor) {
160
218
  return descriptor.inputs.some((input) => input.domain.type === "target" && input.domain.targetKind === "card");
161
219
  }
162
220
  export function interactionArmScope(descriptor) {
163
- const byKind = eligibleTargetsByBoardKind(descriptor);
164
- const boardKinds = Object.keys(byKind).filter((kind) => byKind[kind] !== undefined);
221
+ const boardKinds = boardTargetKindsOf(descriptor);
165
222
  if (boardKinds.length === 1)
166
223
  return `board:${boardKinds[0]}`;
167
224
  if (boardKinds.length > 1)
@@ -170,8 +227,19 @@ export function interactionArmScope(descriptor) {
170
227
  return `zone:${descriptor.zoneId}`;
171
228
  return `interaction:${descriptor.interactionKey}`;
172
229
  }
173
- export function inputKeyForTarget(descriptor, targetKind, targetId) {
174
- return inputByTarget(descriptor, targetKind, targetId)?.key ?? null;
230
+ export function inputKeyForTarget(descriptor, targetKind, targetId, params = {}) {
231
+ return inputByTarget(descriptor, targetKind, targetId, params)?.key ?? null;
232
+ }
233
+ function boardTargetKindsOf(descriptor) {
234
+ const kinds = new Set();
235
+ for (const input of descriptor.inputs) {
236
+ if (input.domain.type !== "target")
237
+ continue;
238
+ if (isBoardTargetKind(input.domain.targetKind)) {
239
+ kinds.add(input.domain.targetKind);
240
+ }
241
+ }
242
+ return [...kinds];
175
243
  }
176
244
  function validateInputSelection(input, value) {
177
245
  const selection = input.domain.selection;
@@ -0,0 +1,31 @@
1
+ import type { InteractionUiStore } from "../context/InteractionDraftContext.js";
2
+ import type { InteractionDescriptor } from "../types/plugin-state.js";
3
+ export interface InteractionDraftMutation {
4
+ key: string;
5
+ value: unknown;
6
+ }
7
+ export interface InteractionDraftReadiness {
8
+ values: Record<string, unknown>;
9
+ missingInputs: readonly string[];
10
+ readyFrontier: readonly string[];
11
+ blockedInputs: readonly string[];
12
+ fieldErrors: Partial<Record<string, readonly string[]>>;
13
+ ready: boolean;
14
+ }
15
+ export interface RoutedInteractionTarget {
16
+ inputKey: string;
17
+ value: string;
18
+ extraInputs?: Record<string, unknown>;
19
+ }
20
+ export interface RoutedInteractionTargetResult {
21
+ params: Record<string, unknown>;
22
+ readiness: InteractionDraftReadiness;
23
+ }
24
+ export declare function applyInteractionDraftMutation(store: Pick<InteractionUiStore, "getDraft" | "setInput" | "clearInput">, descriptor: InteractionDescriptor, mutations: readonly InteractionDraftMutation[]): Record<string, unknown>;
25
+ export declare function getInteractionDraftReadiness(descriptor: InteractionDescriptor, draft: Readonly<Record<string, unknown>>): InteractionDraftReadiness;
26
+ export declare function routeInteractionTarget(store: Pick<InteractionUiStore, "getDraft" | "setInput" | "clearInput">, descriptor: InteractionDescriptor, target: RoutedInteractionTarget): RoutedInteractionTargetResult;
27
+ export declare function shouldRouteInteractionPending(descriptor: InteractionDescriptor, readiness: Pick<InteractionDraftReadiness, "ready">): boolean;
28
+ export declare function markInteractionPending(store: Pick<InteractionUiStore, "arm" | "setPendingInteraction">, descriptor: InteractionDescriptor): void;
29
+ export declare function clearInteractionRoute(store: Pick<InteractionUiStore, "clearInput" | "getArmed" | "arm" | "getPendingInteraction" | "setPendingInteraction">, descriptor: InteractionDescriptor): void;
30
+ export declare function claimInteractionSubmit(store: Pick<InteractionUiStore, "claimSubmitting">, descriptor: InteractionDescriptor): boolean;
31
+ //# sourceMappingURL=interaction-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interaction-router.d.ts","sourceRoot":"","sources":["../../src/utils/interaction-router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAChF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AActE,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IACxD,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,SAAS,EAAE,yBAAyB,CAAC;CACtC;AAED,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,GAAG,UAAU,GAAG,YAAY,CAAC,EACvE,UAAU,EAAE,qBAAqB,EACjC,SAAS,EAAE,SAAS,wBAAwB,EAAE,GAC7C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgCzB;AAeD,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,qBAAqB,EACjC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACvC,yBAAyB,CAoC3B;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,UAAU,GAAG,UAAU,GAAG,YAAY,CAAC,EACvE,UAAU,EAAE,qBAAqB,EACjC,MAAM,EAAE,uBAAuB,GAC9B,6BAA6B,CAoB/B;AAED,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,qBAAqB,EACjC,SAAS,EAAE,IAAI,CAAC,yBAAyB,EAAE,OAAO,CAAC,GAClD,OAAO,CAET;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,KAAK,GAAG,uBAAuB,CAAC,EAChE,UAAU,EAAE,qBAAqB,GAChC,IAAI,CAGN;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,IAAI,CACT,kBAAkB,EAChB,YAAY,GACZ,UAAU,GACV,KAAK,GACL,uBAAuB,GACvB,uBAAuB,CAC1B,EACD,UAAU,EAAE,qBAAqB,GAChC,IAAI,CASN;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,IAAI,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,EAClD,UAAU,EAAE,qBAAqB,GAChC,OAAO,CAET"}
@@ -0,0 +1,114 @@
1
+ import { applyInteractionInputDefaults, dependentInputKeys, hasInteractionFieldErrors, inputByKey, interactionArmScope, interactionInputKeys, isInputValueReady, resolveInputDomain, toggleManyValue, validateInteractionInputDomains, } from "./interaction-inputs.js";
2
+ export function applyInteractionDraftMutation(store, descriptor, mutations) {
3
+ const nextDraft = {
4
+ ...store.getDraft(descriptor.interactionKey),
5
+ };
6
+ const mutatedKeys = new Set(mutations.map((mutation) => mutation.key));
7
+ for (const { key, value } of mutations) {
8
+ nextDraft[key] = value;
9
+ }
10
+ for (const { key } of mutations) {
11
+ for (const dependentKey of dependentInputKeys(descriptor, key)) {
12
+ if (mutatedKeys.has(dependentKey))
13
+ continue;
14
+ if (shouldClearDependentInput(descriptor, nextDraft, dependentKey)) {
15
+ delete nextDraft[dependentKey];
16
+ }
17
+ }
18
+ }
19
+ for (const { key, value } of mutations) {
20
+ store.setInput(descriptor.interactionKey, key, value);
21
+ }
22
+ for (const { key } of mutations) {
23
+ for (const dependentKey of dependentInputKeys(descriptor, key)) {
24
+ if (mutatedKeys.has(dependentKey))
25
+ continue;
26
+ if (shouldClearDependentInput(descriptor, nextDraft, dependentKey)) {
27
+ store.clearInput(descriptor.interactionKey, dependentKey);
28
+ }
29
+ }
30
+ }
31
+ return nextDraft;
32
+ }
33
+ function shouldClearDependentInput(descriptor, draft, dependentKey) {
34
+ const input = inputByKey(descriptor, dependentKey);
35
+ if (!input)
36
+ return true;
37
+ const value = draft[dependentKey];
38
+ if (!isInputValueReady(resolveInputDomain(input, draft), value))
39
+ return true;
40
+ const errors = validateInteractionInputDomains(descriptor, draft);
41
+ return (errors[dependentKey]?.length ?? 0) > 0;
42
+ }
43
+ export function getInteractionDraftReadiness(descriptor, draft) {
44
+ const values = applyInteractionInputDefaults(descriptor, draft);
45
+ const missingInputs = interactionInputKeys(descriptor).filter((key) => {
46
+ const input = inputByKey(descriptor, key);
47
+ const value = values[key];
48
+ return input
49
+ ? !isInputValueReady(resolveInputDomain(input, values), value)
50
+ : value === null || value === undefined;
51
+ });
52
+ const missingInputSet = new Set(missingInputs);
53
+ const readyFrontier = missingInputs.filter((key) => {
54
+ const input = inputByKey(descriptor, key);
55
+ if (!input)
56
+ return true;
57
+ return (input.domain.dependsOn ?? []).every((dependencyKey) => {
58
+ if (missingInputSet.has(dependencyKey))
59
+ return false;
60
+ const dependency = inputByKey(descriptor, dependencyKey);
61
+ if (!dependency)
62
+ return values[dependencyKey] !== undefined;
63
+ return isInputValueReady(resolveInputDomain(dependency, values), values[dependencyKey]);
64
+ });
65
+ });
66
+ const fieldErrors = validateInteractionInputDomains(descriptor, values);
67
+ return {
68
+ values,
69
+ missingInputs,
70
+ readyFrontier,
71
+ blockedInputs: missingInputs.filter((key) => !readyFrontier.includes(key)),
72
+ fieldErrors,
73
+ ready: missingInputs.length === 0 && !hasInteractionFieldErrors(fieldErrors),
74
+ };
75
+ }
76
+ export function routeInteractionTarget(store, descriptor, target) {
77
+ const input = inputByKey(descriptor, target.inputKey);
78
+ const currentDraft = store.getDraft(descriptor.interactionKey);
79
+ const selection = input?.domain.type === "target" ? input.domain.selection : undefined;
80
+ const targetValue = selection?.mode === "many"
81
+ ? toggleManyValue(currentDraft[target.inputKey], target.value, selection)
82
+ : target.value;
83
+ const params = applyInteractionDraftMutation(store, descriptor, [
84
+ ...Object.entries(target.extraInputs ?? {}).map(([key, value]) => ({
85
+ key,
86
+ value,
87
+ })),
88
+ { key: target.inputKey, value: targetValue },
89
+ ]);
90
+ return {
91
+ params,
92
+ readiness: getInteractionDraftReadiness(descriptor, params),
93
+ };
94
+ }
95
+ export function shouldRouteInteractionPending(descriptor, readiness) {
96
+ return descriptor.commit.mode !== "autoWhenReady" || !readiness.ready;
97
+ }
98
+ export function markInteractionPending(store, descriptor) {
99
+ store.arm(interactionArmScope(descriptor), descriptor.interactionKey);
100
+ store.setPendingInteraction(descriptor.interactionKey);
101
+ }
102
+ export function clearInteractionRoute(store, descriptor) {
103
+ const armScope = interactionArmScope(descriptor);
104
+ store.clearInput(descriptor.interactionKey);
105
+ if (store.getArmed(armScope) === descriptor.interactionKey) {
106
+ store.arm(armScope, null);
107
+ }
108
+ if (store.getPendingInteraction() === descriptor.interactionKey) {
109
+ store.setPendingInteraction(null);
110
+ }
111
+ }
112
+ export function claimInteractionSubmit(store, descriptor) {
113
+ return store.claimSubmitting(descriptor.interactionKey);
114
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamboard-games/ui-sdk",
3
- "version": "0.0.43",
3
+ "version": "0.0.45",
4
4
  "description": "UI SDK for dreamboard plugin development",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -6,6 +6,7 @@
6
6
 
7
7
  import type { ButtonHTMLAttributes, ReactNode } from "react";
8
8
  import type { InteractionHandle } from "../hooks/useInteractionHandle.js";
9
+ import { submitInteraction } from "../primitives/interaction-submit.js";
9
10
  import { interactionLabel } from "../utils/interaction-labels.js";
10
11
  import { ThemedButton } from "./ThemedButton.js";
11
12
 
@@ -29,7 +30,7 @@ export function ActionButton({
29
30
  disabled={disabled || !handle.available}
30
31
  title={title ?? formatUnavailableReason(handle.unavailableReason)}
31
32
  onClick={() => {
32
- void handle.submit();
33
+ void submitInteraction(handle);
33
34
  }}
34
35
  {...rest}
35
36
  >
@@ -21,7 +21,7 @@ export interface CardProps<CardData extends ViewCard = ViewCard>
21
21
  size?: "sm" | "md" | "lg";
22
22
  faceDown?: boolean;
23
23
  renderContent?: (card: CardData) => React.ReactNode;
24
- onCardClick?: (cardId: string) => void;
24
+ onCardClick?: (cardId: CardData["id"]) => void;
25
25
  "aria-label"?: string;
26
26
  }
27
27
 
@@ -21,11 +21,12 @@ import type {
21
21
  InteractionHandle,
22
22
  InteractionParamsShape,
23
23
  } from "../hooks/useInteractionHandle.js";
24
+ import { normalizeDiceState, type DiceValue } from "../primitives/index.js";
24
25
  import { interactionLabel } from "../utils/interaction-labels.js";
25
26
  import { ThemedButton } from "./ThemedButton.js";
26
27
 
27
28
  export interface DiceRollerRenderProps {
28
- values: Array<number | undefined> | undefined;
29
+ values: ReadonlyArray<number | undefined> | undefined;
29
30
  /** Undefined if any die hasn't been rolled */
30
31
  sum: number | undefined;
31
32
  diceCount: number;
@@ -48,7 +49,7 @@ export interface DiceRollerRollAction<
48
49
  export interface DiceRollerProps<
49
50
  Params extends InteractionParamsShape = InteractionParamsShape,
50
51
  > {
51
- values?: Array<number | undefined>;
52
+ values?: readonly DiceValue[] | null;
52
53
  /** Used when values not provided */
53
54
  diceCount?: number;
54
55
  render?: (props: DiceRollerRenderProps) => ReactNode;
@@ -72,19 +73,7 @@ export function DiceRoller<
72
73
  rollAction,
73
74
  className,
74
75
  }: DiceRollerProps<Params>) {
75
- const allRolled =
76
- values?.every((v) => v !== null && v !== undefined) ?? false;
77
- const sum = allRolled
78
- ? values?.reduce((a, b) => (a ?? 0) + (b ?? 0), 0)
79
- : undefined;
80
- const displayCount = values?.length ?? diceCount;
81
-
82
- const renderProps: DiceRollerRenderProps = {
83
- values,
84
- sum,
85
- diceCount: displayCount,
86
- allRolled,
87
- };
76
+ const renderProps = normalizeDiceState({ values, count: diceCount });
88
77
 
89
78
  return (
90
79
  <div
@@ -97,15 +86,15 @@ export function DiceRoller<
97
86
  {rollAction ? (
98
87
  <DiceRollDialog
99
88
  action={rollAction}
100
- values={values}
101
- diceCount={displayCount}
89
+ values={renderProps.values}
90
+ diceCount={renderProps.diceCount}
102
91
  />
103
92
  ) : null}
104
93
 
105
94
  {/* Screen reader only: dice info */}
106
95
  <div className="sr-only" aria-live="polite">
107
- {allRolled && values
108
- ? `Rolled ${values.join(", ")}. Total: ${sum}`
96
+ {renderProps.allRolled && renderProps.values
97
+ ? `Rolled ${renderProps.values.join(", ")}. Total: ${renderProps.sum}`
109
98
  : "Dice not rolled yet"}
110
99
  </div>
111
100
  </div>
@@ -152,7 +141,7 @@ function DiceRollDialog<Params extends InteractionParamsShape>({
152
141
  diceCount,
153
142
  }: {
154
143
  action: DiceRollerRollAction<Params>;
155
- values: Array<number | undefined> | undefined;
144
+ values: ReadonlyArray<number | undefined> | undefined;
156
145
  diceCount: number;
157
146
  }) {
158
147
  const theme = useTheme();
@@ -409,7 +398,7 @@ function DiceRow({
409
398
  size,
410
399
  rolling,
411
400
  }: {
412
- values: Array<number | undefined>;
401
+ values: ReadonlyArray<number | undefined>;
413
402
  size: number;
414
403
  rolling: boolean;
415
404
  }) {
@@ -595,7 +584,9 @@ function nextAnimatedValues(count: number): number[] {
595
584
  });
596
585
  }
597
586
 
598
- function sumValues(values: Array<number | undefined>): number | undefined {
587
+ function sumValues(
588
+ values: ReadonlyArray<number | undefined>,
589
+ ): number | undefined {
599
590
  if (!values.every((value) => typeof value === "number")) return undefined;
600
591
  return values.reduce((total, value) => total + (value ?? 0), 0);
601
592
  }
@@ -0,0 +1,19 @@
1
+ import { expect, test } from "bun:test";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ test("ErrorBoundary fallback is styled without relying on consumer Tailwind output", () => {
6
+ const source = readFileSync(
7
+ join(import.meta.dir, "ErrorBoundary.tsx"),
8
+ "utf8",
9
+ );
10
+
11
+ expect(source).toContain("satisfies Record<string, CSSProperties>");
12
+ expect(source).toContain('background: "#fff"');
13
+ expect(source).toContain('border: "2px solid #0f172a"');
14
+ expect(source).toContain('boxShadow: "6px 6px 0 #111827"');
15
+ expect(source).toContain(
16
+ "<h1 style={styles.title}>Game failed to start</h1>",
17
+ );
18
+ expect(source).not.toContain("bg-gradient-to-br");
19
+ });