@dreamboard-games/sdk 0.2.0
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/LICENSE.md +96 -0
- package/README.md +12 -0
- package/dist/HandView-ncJIVLhN.d.ts +193 -0
- package/dist/ResourceCounter-CTREyF73.d.ts +102 -0
- package/dist/ThemeProvider-fy0_QzgO.d.ts +99 -0
- package/dist/bundle-TIZcw8LB.d.ts +281 -0
- package/dist/cards-Sl3b40Mv.d.ts +13 -0
- package/dist/chunk-7YAHLYBR.js +481 -0
- package/dist/chunk-7YAHLYBR.js.map +1 -0
- package/dist/chunk-FDNZTDD6.js +8085 -0
- package/dist/chunk-FDNZTDD6.js.map +1 -0
- package/dist/chunk-GKKBPPSW.js +598 -0
- package/dist/chunk-GKKBPPSW.js.map +1 -0
- package/dist/chunk-I46YJSOD.js +1 -0
- package/dist/chunk-I46YJSOD.js.map +1 -0
- package/dist/chunk-KAELH4KC.js +104 -0
- package/dist/chunk-KAELH4KC.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-T3ZKNUZ7.js +1 -0
- package/dist/chunk-T3ZKNUZ7.js.map +1 -0
- package/dist/chunk-T52J5RMF.js +1 -0
- package/dist/chunk-T52J5RMF.js.map +1 -0
- package/dist/chunk-TDSWKVZ4.js +5401 -0
- package/dist/chunk-TDSWKVZ4.js.map +1 -0
- package/dist/chunk-U5C6BONG.js +34 -0
- package/dist/chunk-U5C6BONG.js.map +1 -0
- package/dist/chunk-VDXOF4FW.js +69 -0
- package/dist/chunk-VDXOF4FW.js.map +1 -0
- package/dist/chunk-VFTAA4WO.js +115 -0
- package/dist/chunk-VFTAA4WO.js.map +1 -0
- package/dist/chunk-WN74KVNY.js +17 -0
- package/dist/chunk-WN74KVNY.js.map +1 -0
- package/dist/chunk-WYPQ3GG5.js +10990 -0
- package/dist/chunk-WYPQ3GG5.js.map +1 -0
- package/dist/components-D5ZRE2Hl.d.ts +1451 -0
- package/dist/generated/runtime/primitives.d.ts +12 -0
- package/dist/generated/runtime/primitives.js +180 -0
- package/dist/generated/runtime/primitives.js.map +1 -0
- package/dist/generated/runtime-api.d.ts +3 -0
- package/dist/generated/runtime-api.js +2 -0
- package/dist/generated/runtime-api.js.map +1 -0
- package/dist/generated/runtime.d.ts +14 -0
- package/dist/generated/runtime.js +18 -0
- package/dist/generated/runtime.js.map +1 -0
- package/dist/generated/workspace-contract.d.ts +14 -0
- package/dist/generated/workspace-contract.js +14 -0
- package/dist/generated/workspace-contract.js.map +1 -0
- package/dist/hex-board-view-D_07hO6O.d.ts +933 -0
- package/dist/hex-color-MhOyuY-o.d.ts +8 -0
- package/dist/index-BwqPQtBu.d.ts +1433 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/infrastructure/reducer-bundle-abi.d.ts +1083 -0
- package/dist/infrastructure/reducer-bundle-abi.js +14 -0
- package/dist/infrastructure/reducer-bundle-abi.js.map +1 -0
- package/dist/infrastructure/workspace-codegen.d.ts +53 -0
- package/dist/infrastructure/workspace-codegen.js +44 -0
- package/dist/infrastructure/workspace-codegen.js.map +1 -0
- package/dist/manifest-contract-BNHVGFtU.d.ts +9 -0
- package/dist/package-set.d.ts +13 -0
- package/dist/package-set.js +12 -0
- package/dist/package-set.js.map +1 -0
- package/dist/primitive-props-DpKs-GCr.d.ts +11 -0
- package/dist/reducer.d.ts +3786 -0
- package/dist/reducer.js +8131 -0
- package/dist/reducer.js.map +1 -0
- package/dist/runtime/primitives.d.ts +226 -0
- package/dist/runtime/primitives.js +180 -0
- package/dist/runtime/primitives.js.map +1 -0
- package/dist/runtime/types/runtime-api.d.ts +1 -0
- package/dist/runtime/types/runtime-api.js +2 -0
- package/dist/runtime/types/runtime-api.js.map +1 -0
- package/dist/runtime/workspace-contract.d.ts +172 -0
- package/dist/runtime/workspace-contract.js +14 -0
- package/dist/runtime/workspace-contract.js.map +1 -0
- package/dist/runtime-api-3dshj6kK.d.ts +101 -0
- package/dist/runtime-api-DWxvTr-O.d.ts +379 -0
- package/dist/runtime.d.ts +58 -0
- package/dist/runtime.js +13 -0
- package/dist/runtime.js.map +1 -0
- package/dist/slots-1GPGihk8.d.ts +8 -0
- package/dist/testing.d.ts +149 -0
- package/dist/testing.js +513 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +496 -0
- package/dist/types.js +28 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/components.d.ts +16 -0
- package/dist/ui/components.js +192 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/defaults.d.ts +19 -0
- package/dist/ui/defaults.js +104 -0
- package/dist/ui/defaults.js.map +1 -0
- package/dist/ui/plugin-styles.css +250 -0
- package/dist/ui/types/player-state.d.ts +365 -0
- package/dist/ui/types/player-state.js +1 -0
- package/dist/ui/types/player-state.js.map +1 -0
- package/dist/ui-contract-iQfTtUSL.d.ts +1161 -0
- package/dist/ui.d.ts +320 -0
- package/dist/ui.js +253 -0
- package/dist/ui.js.map +1 -0
- package/package.json +199 -0
- package/src/generated/reducer-contract/builders.ts +90 -0
- package/src/generated/reducer-contract/version.ts +9 -0
- package/src/generated/reducer-contract/wire.ts +100 -0
- package/src/generated/reducer-contract/zod.ts +101 -0
- package/src/generated/runtime/primitives.ts +2 -0
- package/src/generated/runtime-api.ts +5 -0
- package/src/generated/runtime.ts +35 -0
- package/src/generated/workspace-contract.ts +2 -0
- package/src/index.ts +7 -0
- package/src/infrastructure/reducer-bundle-abi.ts +8 -0
- package/src/infrastructure/reducer-contract/bundle.ts +37 -0
- package/src/infrastructure/workspace-codegen/hex-geometry.ts +69 -0
- package/src/infrastructure/workspace-codegen/index.ts +64 -0
- package/src/infrastructure/workspace-codegen/manifest-contract.ts +6632 -0
- package/src/infrastructure/workspace-codegen/manifest-validation.ts +795 -0
- package/src/infrastructure/workspace-codegen/ownership.ts +131 -0
- package/src/infrastructure/workspace-codegen/preset-card-sets.ts +169 -0
- package/src/infrastructure/workspace-codegen/seeds.ts +1705 -0
- package/src/infrastructure/workspace-codegen.ts +1 -0
- package/src/package-set.ts +19 -0
- package/src/reducer/authoring/contract.ts +157 -0
- package/src/reducer/authoring/effect.ts +224 -0
- package/src/reducer/authoring/game.ts +23 -0
- package/src/reducer/authoring/interaction.ts +98 -0
- package/src/reducer/authoring/phase.ts +300 -0
- package/src/reducer/authoring/types.ts +70 -0
- package/src/reducer/authoring/validation.ts +382 -0
- package/src/reducer/authoring/view-stage.ts +68 -0
- package/src/reducer/authoring.ts +29 -0
- package/src/reducer/bundle/ingress-bundle.ts +491 -0
- package/src/reducer/bundle/trusted/engine-instruction-resolver.ts +254 -0
- package/src/reducer/bundle/trusted/flow-instruction-resolver.ts +73 -0
- package/src/reducer/bundle/trusted/instruction-runner.ts +414 -0
- package/src/reducer/bundle/trusted/interaction-authorization.ts +137 -0
- package/src/reducer/bundle/trusted/interaction-collectors.ts +859 -0
- package/src/reducer/bundle/trusted/interaction-decision.ts +747 -0
- package/src/reducer/bundle/trusted/interaction-resolver.ts +95 -0
- package/src/reducer/bundle/trusted/interaction-types.ts +171 -0
- package/src/reducer/bundle/trusted/lifecycle-runner.ts +427 -0
- package/src/reducer/bundle/trusted/projection-builder.ts +356 -0
- package/src/reducer/bundle/trusted/projection-context.ts +39 -0
- package/src/reducer/bundle/trusted/rng-sampler.ts +150 -0
- package/src/reducer/bundle/trusted/runtime-registry.ts +120 -0
- package/src/reducer/bundle/trusted/runtime-scope.ts +336 -0
- package/src/reducer/bundle/trusted/simultaneous-player.ts +97 -0
- package/src/reducer/bundle/trusted/stage-resolver.ts +87 -0
- package/src/reducer/bundle/trusted/static-projection.ts +116 -0
- package/src/reducer/bundle/trusted/trusted-runtime-args.ts +97 -0
- package/src/reducer/bundle/trusted/trusted-runtime-result.ts +39 -0
- package/src/reducer/bundle/trusted/trusted-setup-profiles.ts +43 -0
- package/src/reducer/bundle/trusted/trusted-state-codec.ts +48 -0
- package/src/reducer/bundle/trusted-bundle.ts +97 -0
- package/src/reducer/bundle/types.ts +171 -0
- package/src/reducer/bundle.ts +2 -0
- package/src/reducer/client-param-schemas.ts +57 -0
- package/src/reducer/compose.ts +34 -0
- package/src/reducer/core/runtime-input.ts +30 -0
- package/src/reducer/core/runtime-instruction.ts +59 -0
- package/src/reducer/core/types.ts +62 -0
- package/src/reducer/definition-index.ts +277 -0
- package/src/reducer/derived.ts +106 -0
- package/src/reducer/effects.ts +92 -0
- package/src/reducer/engine/runtime-instruction-engine.ts +155 -0
- package/src/reducer/ingress/decode-runtime-input.ts +7 -0
- package/src/reducer/ingress/decode-session-state.ts +9 -0
- package/src/reducer/ingress/encode-session-state.ts +6 -0
- package/src/reducer/ingress/input-codec.ts +18 -0
- package/src/reducer/ingress/phase-schemas.ts +62 -0
- package/src/reducer/ingress/raw-types.ts +107 -0
- package/src/reducer/ingress/runtime-codec.ts +14 -0
- package/src/reducer/ingress/runtime-payload.ts +13 -0
- package/src/reducer/ingress/session-codec.ts +392 -0
- package/src/reducer/ingress/types.ts +6 -0
- package/src/reducer/inputs/boardInput.ts +217 -0
- package/src/reducer/inputs/boardTarget.ts +190 -0
- package/src/reducer/inputs/cardInput.ts +86 -0
- package/src/reducer/inputs/cardTarget.ts +101 -0
- package/src/reducer/inputs/choiceTarget.ts +104 -0
- package/src/reducer/inputs/defineInputs.ts +71 -0
- package/src/reducer/inputs/formInput.ts +809 -0
- package/src/reducer/inputs/many.ts +120 -0
- package/src/reducer/inputs/promptInput.ts +87 -0
- package/src/reducer/inputs/rngInput.ts +58 -0
- package/src/reducer/inputs/targetRule.ts +123 -0
- package/src/reducer/inputs.ts +41 -0
- package/src/reducer/model/definition.ts +1072 -0
- package/src/reducer/model/extract.ts +745 -0
- package/src/reducer/model/manifest.ts +570 -0
- package/src/reducer/model/queries.ts +641 -0
- package/src/reducer/model/runtime.ts +264 -0
- package/src/reducer/model/spec.ts +1386 -0
- package/src/reducer/model/table.ts +260 -0
- package/src/reducer/model.ts +7 -0
- package/src/reducer/ops.ts +1034 -0
- package/src/reducer/parse-utils.ts +28 -0
- package/src/reducer/per-player.ts +422 -0
- package/src/reducer/rng.ts +69 -0
- package/src/reducer/schema-helpers.ts +185 -0
- package/src/reducer/setup-bootstrap-helpers.ts +171 -0
- package/src/reducer/setup-bootstrap.ts +481 -0
- package/src/reducer/table-ops.ts +2671 -0
- package/src/reducer/table-queries.ts +372 -0
- package/src/reducer/transaction.ts +120 -0
- package/src/reducer.ts +314 -0
- package/src/runtime/primitives.ts +1 -0
- package/src/runtime/types/runtime-api.ts +1 -0
- package/src/runtime/workspace-contract.ts +32 -0
- package/src/runtime-internal/components/InteractionForm.tsx +1309 -0
- package/src/runtime-internal/components/PluginRuntime.tsx +103 -0
- package/src/runtime-internal/components/board/target-layer.ts +70 -0
- package/src/runtime-internal/context/ClientParamSchemaContext.tsx +44 -0
- package/src/runtime-internal/context/InteractionDraftContext.tsx +279 -0
- package/src/runtime-internal/context/PluginSessionContext.tsx +47 -0
- package/src/runtime-internal/context/PluginStateContext.tsx +262 -0
- package/src/runtime-internal/context/RuntimeContext.tsx +96 -0
- package/src/runtime-internal/defaults/components.tsx +409 -0
- package/src/runtime-internal/defaults/index.ts +11 -0
- package/src/runtime-internal/errors/ValidationError.ts +29 -0
- package/src/runtime-internal/hooks/useActivePlayers.ts +33 -0
- package/src/runtime-internal/hooks/useBoardInteractions.ts +665 -0
- package/src/runtime-internal/hooks/useGameSelector.ts +105 -0
- package/src/runtime-internal/hooks/useGameView.ts +9 -0
- package/src/runtime-internal/hooks/useInteractionByKey.ts +354 -0
- package/src/runtime-internal/hooks/useInteractionHandle.ts +438 -0
- package/src/runtime-internal/hooks/useIsMyTurn.ts +20 -0
- package/src/runtime-internal/hooks/useLobby.ts +76 -0
- package/src/runtime-internal/hooks/useMe.ts +48 -0
- package/src/runtime-internal/hooks/usePlayerInfo.ts +28 -0
- package/src/runtime-internal/hooks/usePlayerTurnOrder.ts +23 -0
- package/src/runtime-internal/hooks/usePluginRuntime.ts +147 -0
- package/src/runtime-internal/hooks/useSeatInbox.ts +61 -0
- package/src/runtime-internal/hooks/useSimultaneousPhase.ts +10 -0
- package/src/runtime-internal/index.ts +42 -0
- package/src/runtime-internal/internal.ts +43 -0
- package/src/runtime-internal/plugin-styles.css +250 -0
- package/src/runtime-internal/primitives/board.tsx +459 -0
- package/src/runtime-internal/primitives/dialog-lifecycle.ts +58 -0
- package/src/runtime-internal/primitives/dice.tsx +79 -0
- package/src/runtime-internal/primitives/game-ui-provider.tsx +35 -0
- package/src/runtime-internal/primitives/game.tsx +387 -0
- package/src/runtime-internal/primitives/hand-intent-adapter.ts +147 -0
- package/src/runtime-internal/primitives/hand-surface.tsx +594 -0
- package/src/runtime-internal/primitives/index.ts +196 -0
- package/src/runtime-internal/primitives/interaction-form-binding.tsx +56 -0
- package/src/runtime-internal/primitives/interaction-submit.ts +90 -0
- package/src/runtime-internal/primitives/interaction.tsx +987 -0
- package/src/runtime-internal/primitives/phase.tsx +43 -0
- package/src/runtime-internal/primitives/player-roster.tsx +302 -0
- package/src/runtime-internal/primitives/primitive-props.tsx +101 -0
- package/src/runtime-internal/primitives/prompt.tsx +255 -0
- package/src/runtime-internal/primitives/ui.tsx +60 -0
- package/src/runtime-internal/primitives/zone.tsx +791 -0
- package/src/runtime-internal/reducer.ts +30 -0
- package/src/runtime-internal/runtime/createPluginRuntimeAPI.ts +605 -0
- package/src/runtime-internal/types/plugin-state.ts +508 -0
- package/src/runtime-internal/types/reducer-state.ts +24 -0
- package/src/runtime-internal/types/runtime-api.ts +114 -0
- package/src/runtime-internal/ui-contract.ts +519 -0
- package/src/runtime-internal/utils/card-intent-adapter.ts +546 -0
- package/src/runtime-internal/utils/interaction-inputs.ts +492 -0
- package/src/runtime-internal/utils/interaction-labels.ts +23 -0
- package/src/runtime-internal/utils/interaction-router.ts +273 -0
- package/src/runtime-internal/utils/interaction-status.ts +74 -0
- package/src/runtime-internal/workspace-contract.ts +1170 -0
- package/src/runtime.ts +34 -0
- package/src/testing/create-expect-api.ts +352 -0
- package/src/testing/create-test-runtime.ts +381 -0
- package/src/testing/definitions.ts +127 -0
- package/src/testing/index.ts +3 -0
- package/src/testing.ts +1 -0
- package/src/type-stubs/manifest-contract.d.ts +42 -0
- package/src/type-stubs/manifest-contract.js +72 -0
- package/src/type-stubs/ui-contract.d.ts +5 -0
- package/src/type-stubs/ui-contract.js +1 -0
- package/src/types/authoring-card-properties.type-test.ts +266 -0
- package/src/types/authoring.ts +1282 -0
- package/src/types/cards.ts +19 -0
- package/src/types/contracts.ts +1550 -0
- package/src/types/generated-helpers.ts +35 -0
- package/src/types/index.ts +147 -0
- package/src/types/slots.ts +11 -0
- package/src/types.ts +1 -0
- package/src/ui/components/ActionButton.tsx +97 -0
- package/src/ui/components/ActionPanel.tsx +315 -0
- package/src/ui/components/Card.tsx +378 -0
- package/src/ui/components/CardDragSurface.tsx +1076 -0
- package/src/ui/components/ChromeSuppressionContext.tsx +70 -0
- package/src/ui/components/CostDisplay.tsx +145 -0
- package/src/ui/components/DiceRoller.tsx +581 -0
- package/src/ui/components/Drawer.tsx +180 -0
- package/src/ui/components/ErrorBoundary.tsx +275 -0
- package/src/ui/components/GameEndDisplay.tsx +398 -0
- package/src/ui/components/GameSkeleton.tsx +260 -0
- package/src/ui/components/Hand.tsx +468 -0
- package/src/ui/components/HandDock.tsx +299 -0
- package/src/ui/components/HandView.tsx +441 -0
- package/src/ui/components/MobileHandTray.tsx +381 -0
- package/src/ui/components/MoreActions.tsx +143 -0
- package/src/ui/components/PhaseIndicator.tsx +341 -0
- package/src/ui/components/PlayArea.tsx +146 -0
- package/src/ui/components/PrimaryActionButton.tsx +336 -0
- package/src/ui/components/PrimaryButton.tsx +45 -0
- package/src/ui/components/ResourceCounter.tsx +270 -0
- package/src/ui/components/StagingZone.tsx +134 -0
- package/src/ui/components/ThemedButton.tsx +113 -0
- package/src/ui/components/Toast.tsx +264 -0
- package/src/ui/components/board/HexGrid.tsx +1294 -0
- package/src/ui/components/board/NetworkGraph.tsx +476 -0
- package/src/ui/components/board/SlotSystem.tsx +388 -0
- package/src/ui/components/board/SquareGrid.tsx +1165 -0
- package/src/ui/components/board/TrackBoard.tsx +496 -0
- package/src/ui/components/board/ZoneMap.tsx +448 -0
- package/src/ui/components/board/hex-board-view.ts +123 -0
- package/src/ui/components/board/index.ts +142 -0
- package/src/ui/components/board/interaction-accessibility.ts +21 -0
- package/src/ui/components/board/target-layer.ts +66 -0
- package/src/ui/components/card-render-content.type-test.ts +27 -0
- package/src/ui/components/hand-layout-math.ts +163 -0
- package/src/ui/components/hand-pointer-engine.ts +413 -0
- package/src/ui/components/index.ts +245 -0
- package/src/ui/components.ts +1 -0
- package/src/ui/defaults/components.tsx +106 -0
- package/src/ui/defaults/index.ts +8 -0
- package/src/ui/defaults.ts +1 -0
- package/src/ui/errors/ValidationError.ts +29 -0
- package/src/ui/helpers/cards.ts +19 -0
- package/src/ui/helpers/track-board.ts +211 -0
- package/src/ui/hooks/useBoardTopology.ts +316 -0
- package/src/ui/hooks/useCards.ts +10 -0
- package/src/ui/hooks/useHandCardPointer.ts +381 -0
- package/src/ui/hooks/useHandLayout.ts +378 -0
- package/src/ui/hooks/useHandPresentation.ts +121 -0
- package/src/ui/hooks/useHexBoard.ts +74 -0
- package/src/ui/hooks/useHexGrid.ts +185 -0
- package/src/ui/hooks/useIsMobile.ts +35 -0
- package/src/ui/hooks/usePanZoom.ts +278 -0
- package/src/ui/hooks/useSquareBoard.ts +124 -0
- package/src/ui/hooks/useSquareGrid.ts +328 -0
- package/src/ui/index.ts +98 -0
- package/src/ui/internal/ui/alert.tsx +51 -0
- package/src/ui/internal/ui/button.tsx +58 -0
- package/src/ui/internal/ui/dialog.tsx +134 -0
- package/src/ui/internal/ui/input.tsx +21 -0
- package/src/ui/internal/ui/label.tsx +21 -0
- package/src/ui/internal/ui/select.tsx +129 -0
- package/src/ui/internal/ui/tooltip.tsx +54 -0
- package/src/ui/internal/ui/utils.ts +5 -0
- package/src/ui/plugin-styles.css +250 -0
- package/src/ui/primitives/dialog-lifecycle.ts +58 -0
- package/src/ui/primitives/dice.tsx +79 -0
- package/src/ui/primitives/primitive-props.tsx +101 -0
- package/src/ui/theme/ThemeProvider.tsx +252 -0
- package/src/ui/theme/board.ts +61 -0
- package/src/ui/theme/css-vars.ts +105 -0
- package/src/ui/theme/derive.ts +240 -0
- package/src/ui/theme/index.ts +61 -0
- package/src/ui/theme/presets/arcade.ts +261 -0
- package/src/ui/theme/presets/studio.ts +261 -0
- package/src/ui/theme/presets/tabletop.ts +266 -0
- package/src/ui/theme/tokens.ts +392 -0
- package/src/ui/types/hex-color.ts +20 -0
- package/src/ui/types/player-state.ts +463 -0
- package/src/ui/types/tiled-board.ts +785 -0
- package/src/ui/types/visual-state.ts +137 -0
- package/src/ui.ts +1 -0
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Fragment,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react";
|
|
9
|
+
import {
|
|
10
|
+
CardDragSurface,
|
|
11
|
+
CardDropTargetView,
|
|
12
|
+
HandView,
|
|
13
|
+
StagingZone,
|
|
14
|
+
type CardIntent,
|
|
15
|
+
type CardDropTargetVisualState,
|
|
16
|
+
type HandLayoutKind,
|
|
17
|
+
type HandLayoutPolicy,
|
|
18
|
+
type HandInteractionPolicy,
|
|
19
|
+
type InteractionVisualState,
|
|
20
|
+
type ViewCard,
|
|
21
|
+
} from "../../ui.js";
|
|
22
|
+
import { useStore } from "zustand";
|
|
23
|
+
import { useShallow } from "zustand/shallow";
|
|
24
|
+
import { usePluginState } from "../context/PluginStateContext.js";
|
|
25
|
+
import { useInteractionUiStore } from "../context/InteractionDraftContext.js";
|
|
26
|
+
import {
|
|
27
|
+
encodeRuntimeDropTargetKind,
|
|
28
|
+
projectDraftCardState,
|
|
29
|
+
selectedCardIdsForZone,
|
|
30
|
+
type RuntimeDropTargetKind,
|
|
31
|
+
} from "../utils/card-intent-adapter.js";
|
|
32
|
+
import {
|
|
33
|
+
isResolvedTargetDomain,
|
|
34
|
+
isTargetDomain,
|
|
35
|
+
validateInteractionInputDomains,
|
|
36
|
+
} from "../utils/interaction-inputs.js";
|
|
37
|
+
import {
|
|
38
|
+
useCardIntentAdapter,
|
|
39
|
+
type AuthoredCardIntent,
|
|
40
|
+
type RuntimeCardIntentResult,
|
|
41
|
+
} from "./hand-intent-adapter.js";
|
|
42
|
+
import type {
|
|
43
|
+
InteractionDescriptor,
|
|
44
|
+
PluginStateSnapshot,
|
|
45
|
+
} from "../types/plugin-state.js";
|
|
46
|
+
import { isInteractionAvailable } from "../utils/interaction-status.js";
|
|
47
|
+
import { createZoneCardRenderItem, type ZoneCardRenderItem } from "./zone.js";
|
|
48
|
+
|
|
49
|
+
export interface RuntimeDropTarget {
|
|
50
|
+
/** Stable opaque id; runtime adapter encodes the inputKey + value here. */
|
|
51
|
+
targetId: string;
|
|
52
|
+
/** Plain text label used by the SDK live-region announcements. */
|
|
53
|
+
label: string;
|
|
54
|
+
/** Optional `CardDropTargetVisualState` overrides. */
|
|
55
|
+
visualState?: CardDropTargetVisualState;
|
|
56
|
+
/** Visible content inside the drop target. */
|
|
57
|
+
render: (state: CardDropTargetVisualState) => ReactNode;
|
|
58
|
+
/** ARIA role override. Defaults to "button". */
|
|
59
|
+
role?: string;
|
|
60
|
+
/** Extra container className. */
|
|
61
|
+
className?: string;
|
|
62
|
+
/** Order hint for keyboard traversal. */
|
|
63
|
+
order?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Per-hand summary projected alongside the card list. Authors can render
|
|
68
|
+
* counts ("3 of 5 selected") without re-deriving selection state.
|
|
69
|
+
*/
|
|
70
|
+
export interface HandSelectionSummary {
|
|
71
|
+
/** How many cards in this hand are part of the active many-select draft. */
|
|
72
|
+
selectedCount: number;
|
|
73
|
+
/** Card ids currently in the many-select draft, in iteration order. */
|
|
74
|
+
selectedIds: readonly string[];
|
|
75
|
+
/**
|
|
76
|
+
* Whether at least one descriptor in this zone has form-side validation
|
|
77
|
+
* errors against the current draft. Authors can use this to render an
|
|
78
|
+
* inline hint without inspecting individual fields.
|
|
79
|
+
*/
|
|
80
|
+
hasInvalidSelection: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface HandSurfaceViewProps<Card extends ZoneCardRenderItem> {
|
|
84
|
+
zone: string;
|
|
85
|
+
cards: readonly Card[];
|
|
86
|
+
/** Visual surface for a single card. */
|
|
87
|
+
renderCard: (
|
|
88
|
+
card: Card,
|
|
89
|
+
state: InteractionVisualState,
|
|
90
|
+
index: number,
|
|
91
|
+
) => ReactNode;
|
|
92
|
+
/**
|
|
93
|
+
* Render-safe slot for selection summary content. Fires during render
|
|
94
|
+
* with the latest projected summary so authors can compose count chrome
|
|
95
|
+
* (e.g. "3 of 5 selected") inline. The returned node is hoisted above
|
|
96
|
+
* the hand region. Use this for visible UI; use `onSelectionSummary`
|
|
97
|
+
* only for analytics or external state mirrors that need an effect.
|
|
98
|
+
*/
|
|
99
|
+
renderSummary?: (summary: HandSelectionSummary) => ReactNode;
|
|
100
|
+
/**
|
|
101
|
+
* Optional selection summary observer. Invoked from a layout effect so
|
|
102
|
+
* consumers may safely call `setState` in response. Use `renderSummary`
|
|
103
|
+
* for inline rendering instead — that path avoids effect-timing
|
|
104
|
+
* surprises.
|
|
105
|
+
*/
|
|
106
|
+
onSelectionSummary?: (summary: HandSelectionSummary) => void;
|
|
107
|
+
/**
|
|
108
|
+
* Render-safe slot for the hand's commit/action chrome (e.g. a submit
|
|
109
|
+
* button for a many-select interaction). Receives the same live summary as
|
|
110
|
+
* `renderSummary`. Rendered below the hand inline on desktop and pinned as a
|
|
111
|
+
* sticky footer inside the mobile dock, so the action stays reachable while
|
|
112
|
+
* the hand is docked. Use this instead of anchoring the action elsewhere on
|
|
113
|
+
* the board, where the dock would otherwise hide it.
|
|
114
|
+
*/
|
|
115
|
+
renderActions?: (summary: HandSelectionSummary) => ReactNode;
|
|
116
|
+
/** Layout policy forwarded to the SDK HandView. */
|
|
117
|
+
layout?: HandLayoutKind | HandLayoutPolicy;
|
|
118
|
+
/** Mobile interaction policy. Defaults to `direct-activate`. */
|
|
119
|
+
mobileInteraction?: HandInteractionPolicy;
|
|
120
|
+
/**
|
|
121
|
+
* Drop targets to render around the hand. When provided, the hand renders
|
|
122
|
+
* inside a `CardDragSurface` so SDK `drop` intents arrive with an opaque
|
|
123
|
+
* target id that the runtime adapter can decode.
|
|
124
|
+
*/
|
|
125
|
+
dropTargets?: readonly RuntimeDropTarget[];
|
|
126
|
+
/**
|
|
127
|
+
* Optional outer chrome around the drop targets, when `dropTargets` is
|
|
128
|
+
* provided. Defaults to a flex row above the hand.
|
|
129
|
+
*/
|
|
130
|
+
renderDropTargets?: (children: ReactNode) => ReactNode;
|
|
131
|
+
/** Card width hint forwarded to the SDK HandView. */
|
|
132
|
+
cardSize?: "sm" | "md" | "lg";
|
|
133
|
+
/** Extra empty-state slot. */
|
|
134
|
+
renderEmpty?: () => ReactNode;
|
|
135
|
+
/** ARIA label for the hand region. */
|
|
136
|
+
ariaLabel?: string;
|
|
137
|
+
/**
|
|
138
|
+
* Optional analytics hook. Receives the decoded authored intent (so
|
|
139
|
+
* `targetId` is the manifest value, not the SDK opaque encoding) plus
|
|
140
|
+
* the runtime classification.
|
|
141
|
+
*/
|
|
142
|
+
onIntentRouted?: (
|
|
143
|
+
intent: AuthoredCardIntent,
|
|
144
|
+
result: RuntimeCardIntentResult,
|
|
145
|
+
) => void;
|
|
146
|
+
className?: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Generated hand presentation entry point.
|
|
151
|
+
*
|
|
152
|
+
* Lives in `ui-runtime` because it owns:
|
|
153
|
+
*
|
|
154
|
+
* - mapping descriptor/draft state into SDK `InteractionVisualState`
|
|
155
|
+
* - hiding selected cards from the visible hand layout while the runtime keeps
|
|
156
|
+
* the actual zone snapshot intact
|
|
157
|
+
* - forwarding SDK `CardIntent` through the canonical collector adapter
|
|
158
|
+
*
|
|
159
|
+
* The component is intentionally Dreamboard-aware. The presentational
|
|
160
|
+
* pieces (`HandView`, `CardDragSurface`, `CardDropTargetView`) come from
|
|
161
|
+
* `@dreamboard-games/sdk/ui` and stay descriptor/draft unaware.
|
|
162
|
+
*/
|
|
163
|
+
export function HandSurfaceView<Card extends ZoneCardRenderItem>({
|
|
164
|
+
zone,
|
|
165
|
+
cards,
|
|
166
|
+
renderCard,
|
|
167
|
+
layout,
|
|
168
|
+
mobileInteraction = "direct-activate",
|
|
169
|
+
dropTargets,
|
|
170
|
+
renderDropTargets,
|
|
171
|
+
cardSize = "sm",
|
|
172
|
+
renderEmpty,
|
|
173
|
+
ariaLabel,
|
|
174
|
+
onIntentRouted,
|
|
175
|
+
onSelectionSummary,
|
|
176
|
+
renderSummary,
|
|
177
|
+
renderActions,
|
|
178
|
+
className,
|
|
179
|
+
}: HandSurfaceViewProps<Card>) {
|
|
180
|
+
const route = useCardIntentAdapter({ zone, onResult: onIntentRouted });
|
|
181
|
+
const interactionStore = useInteractionUiStore();
|
|
182
|
+
// Subscribe to the entire drafts slice so selected/invalid state recomputes
|
|
183
|
+
// on every input mutation across all interactions for this zone.
|
|
184
|
+
const drafts = useStore(
|
|
185
|
+
interactionStore,
|
|
186
|
+
useShallow((state) => state.drafts),
|
|
187
|
+
);
|
|
188
|
+
const availableInteractions = usePluginState(
|
|
189
|
+
(state) => state.gameplay.availableInteractions,
|
|
190
|
+
);
|
|
191
|
+
const selectedIds = usePluginState(
|
|
192
|
+
useCallback(
|
|
193
|
+
(state: PluginStateSnapshot) =>
|
|
194
|
+
selectedCardIdsForZone(interactionStore, zone, state),
|
|
195
|
+
// drafts is part of the store snapshot — useStore handles re-render.
|
|
196
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
197
|
+
[interactionStore, zone, drafts],
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const visibleCards = useMemo(
|
|
202
|
+
() => cards.filter((card) => !selectedIds.includes(card.id as string)),
|
|
203
|
+
[cards, selectedIds],
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// SDK HandView renders ViewCard. Hidden zone items expose only an id;
|
|
207
|
+
// surface them as a face-down ViewCard so the lifted overlay/staging math
|
|
208
|
+
// still has stable card geometry.
|
|
209
|
+
const viewCards = useMemo<readonly ViewCard[]>(
|
|
210
|
+
() =>
|
|
211
|
+
visibleCards.map((card) =>
|
|
212
|
+
card.hidden
|
|
213
|
+
? {
|
|
214
|
+
id: card.id as string,
|
|
215
|
+
cardType: "hidden",
|
|
216
|
+
name: "Hidden card",
|
|
217
|
+
properties: {},
|
|
218
|
+
}
|
|
219
|
+
: (card as ViewCard),
|
|
220
|
+
),
|
|
221
|
+
[visibleCards],
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const cardByViewId = useMemo(() => {
|
|
225
|
+
const index = new Map<string, Card>();
|
|
226
|
+
for (const card of visibleCards) {
|
|
227
|
+
index.set(card.id as string, card);
|
|
228
|
+
}
|
|
229
|
+
return index;
|
|
230
|
+
}, [visibleCards]);
|
|
231
|
+
|
|
232
|
+
const cardDescriptorIndex = usePluginState((state) => {
|
|
233
|
+
const snapshot = state.gameplay.zones[zone];
|
|
234
|
+
return snapshot?.playableByCardId ?? {};
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Pre-compute, for each candidate descriptor in this zone, the set of
|
|
239
|
+
* card-target input keys plus their current draft values and validation
|
|
240
|
+
* field errors. This lets `stateForCard` answer selected/invalid in O(1)
|
|
241
|
+
* per card without re-running validation per render.
|
|
242
|
+
*/
|
|
243
|
+
const draftCardProjection = useMemo(
|
|
244
|
+
() =>
|
|
245
|
+
projectDraftCardState(
|
|
246
|
+
availableInteractions,
|
|
247
|
+
drafts,
|
|
248
|
+
validateInteractionInputDomains,
|
|
249
|
+
),
|
|
250
|
+
[availableInteractions, drafts],
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// Eligibility is descriptor-derived, so compute it once across the visible
|
|
254
|
+
// cards. `allVisibleEligible` lets us drop the per-card highlight when every
|
|
255
|
+
// card is a legal target (the highlight would carry no information then).
|
|
256
|
+
const eligibleCardIds = useMemo(() => {
|
|
257
|
+
const set = new Set<string>();
|
|
258
|
+
for (const card of visibleCards) {
|
|
259
|
+
const cardId = card.id as string;
|
|
260
|
+
const candidates = cardDescriptorIndex[cardId] ?? [];
|
|
261
|
+
const eligible = candidates.some(
|
|
262
|
+
(descriptor: InteractionDescriptor) =>
|
|
263
|
+
isInteractionAvailable(descriptor) &&
|
|
264
|
+
descriptor.inputs.some(
|
|
265
|
+
(input) =>
|
|
266
|
+
isResolvedTargetDomain(input.domain) &&
|
|
267
|
+
input.domain.eligibleTargets.includes(cardId),
|
|
268
|
+
),
|
|
269
|
+
);
|
|
270
|
+
if (eligible) set.add(cardId);
|
|
271
|
+
}
|
|
272
|
+
return set;
|
|
273
|
+
}, [visibleCards, cardDescriptorIndex]);
|
|
274
|
+
const allVisibleEligible =
|
|
275
|
+
visibleCards.length > 1 && eligibleCardIds.size === visibleCards.length;
|
|
276
|
+
|
|
277
|
+
const stateForCard = useCallback(
|
|
278
|
+
(viewCard: ViewCard): InteractionVisualState => {
|
|
279
|
+
const cardId = viewCard.id as string;
|
|
280
|
+
const sourceCard = cardByViewId.get(cardId);
|
|
281
|
+
const eligible = eligibleCardIds.has(cardId);
|
|
282
|
+
const selected = draftCardProjection.some((entry) =>
|
|
283
|
+
entry.draftCardIds.has(cardId),
|
|
284
|
+
);
|
|
285
|
+
const invalid = draftCardProjection.some((entry) =>
|
|
286
|
+
entry.invalidCardIds.has(cardId),
|
|
287
|
+
);
|
|
288
|
+
const disabled =
|
|
289
|
+
!eligible &&
|
|
290
|
+
!selected &&
|
|
291
|
+
sourceCard !== undefined &&
|
|
292
|
+
!sourceCard.hidden &&
|
|
293
|
+
!sourceCard.playable;
|
|
294
|
+
return {
|
|
295
|
+
eligible,
|
|
296
|
+
// Suppressed when the whole hand is eligible — see InteractionVisualState.
|
|
297
|
+
distinctlyEligible: eligible && !allVisibleEligible,
|
|
298
|
+
selected,
|
|
299
|
+
invalid,
|
|
300
|
+
disabled,
|
|
301
|
+
};
|
|
302
|
+
},
|
|
303
|
+
[allVisibleEligible, cardByViewId, draftCardProjection, eligibleCardIds],
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const selectionSummary = useMemo<HandSelectionSummary>(() => {
|
|
307
|
+
const ids = new Set<string>();
|
|
308
|
+
let hasInvalidSelection = false;
|
|
309
|
+
for (const entry of draftCardProjection) {
|
|
310
|
+
for (const cardId of entry.draftCardIds) ids.add(cardId);
|
|
311
|
+
if (entry.invalidCardIds.size > 0) hasInvalidSelection = true;
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
selectedCount: ids.size,
|
|
315
|
+
selectedIds: [...ids],
|
|
316
|
+
hasInvalidSelection,
|
|
317
|
+
};
|
|
318
|
+
}, [draftCardProjection]);
|
|
319
|
+
|
|
320
|
+
// Fire the summary observer from an effect so consumers can safely call
|
|
321
|
+
// setState in response. Rendering visible summary chrome should go
|
|
322
|
+
// through `renderSummary` instead.
|
|
323
|
+
const onSelectionSummaryRef = useRef(onSelectionSummary);
|
|
324
|
+
onSelectionSummaryRef.current = onSelectionSummary;
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
onSelectionSummaryRef.current?.(selectionSummary);
|
|
327
|
+
}, [selectionSummary]);
|
|
328
|
+
|
|
329
|
+
const summaryContent = renderSummary ? renderSummary(selectionSummary) : null;
|
|
330
|
+
const actionsNode = renderActions ? renderActions(selectionSummary) : null;
|
|
331
|
+
// Pinned as a sticky footer so the action stays reachable while the hand is
|
|
332
|
+
// docked on mobile (cards scroll behind it); inline on desktop `sticky` is
|
|
333
|
+
// inert and it simply sits below the hand. The opaque surface background
|
|
334
|
+
// occludes any cards scrolled underneath it in the dock.
|
|
335
|
+
const actionsContent =
|
|
336
|
+
actionsNode !== null && actionsNode !== undefined ? (
|
|
337
|
+
<div
|
|
338
|
+
data-dreamboard-hand-actions=""
|
|
339
|
+
data-zone={zone}
|
|
340
|
+
className="sticky bottom-0 z-10 flex w-full flex-col items-center gap-2 px-3 pb-1 pt-2"
|
|
341
|
+
style={{
|
|
342
|
+
background: "var(--background, #fdfbf7)",
|
|
343
|
+
borderTop: "1px solid var(--border, rgba(45,45,45,0.12))",
|
|
344
|
+
}}
|
|
345
|
+
>
|
|
346
|
+
{actionsNode}
|
|
347
|
+
</div>
|
|
348
|
+
) : null;
|
|
349
|
+
|
|
350
|
+
const handleIntent = useCallback(
|
|
351
|
+
(intent: CardIntent) => {
|
|
352
|
+
void route(intent);
|
|
353
|
+
},
|
|
354
|
+
[route],
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const handView = (
|
|
358
|
+
<HandView
|
|
359
|
+
cards={viewCards}
|
|
360
|
+
layout={layout}
|
|
361
|
+
mobileInteraction={mobileInteraction}
|
|
362
|
+
stateForCard={stateForCard}
|
|
363
|
+
renderCard={(viewCard, state, index) => {
|
|
364
|
+
const source = cardByViewId.get(viewCard.id as string);
|
|
365
|
+
if (!source) return null;
|
|
366
|
+
return renderCard(source, state, index);
|
|
367
|
+
}}
|
|
368
|
+
onCardIntent={handleIntent}
|
|
369
|
+
cardSize={cardSize}
|
|
370
|
+
renderEmpty={renderEmpty}
|
|
371
|
+
aria-label={ariaLabel}
|
|
372
|
+
className={className}
|
|
373
|
+
/>
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
if (!dropTargets || dropTargets.length === 0) {
|
|
377
|
+
if (summaryContent === null && actionsContent === null) {
|
|
378
|
+
return handView;
|
|
379
|
+
}
|
|
380
|
+
return (
|
|
381
|
+
<Fragment>
|
|
382
|
+
{summaryContent !== null ? (
|
|
383
|
+
<div
|
|
384
|
+
data-dreamboard-runtime-hand-summary=""
|
|
385
|
+
data-zone={zone}
|
|
386
|
+
data-selection-count={selectionSummary.selectedCount}
|
|
387
|
+
data-has-invalid-selection={
|
|
388
|
+
selectionSummary.hasInvalidSelection ? "true" : "false"
|
|
389
|
+
}
|
|
390
|
+
>
|
|
391
|
+
{summaryContent}
|
|
392
|
+
</div>
|
|
393
|
+
) : null}
|
|
394
|
+
{handView}
|
|
395
|
+
{actionsContent}
|
|
396
|
+
</Fragment>
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const targets = dropTargets.map((target) => (
|
|
401
|
+
<CardDropTargetView
|
|
402
|
+
key={target.targetId}
|
|
403
|
+
targetId={target.targetId}
|
|
404
|
+
state={target.visualState}
|
|
405
|
+
label={target.label}
|
|
406
|
+
role={target.role}
|
|
407
|
+
order={target.order}
|
|
408
|
+
className={target.className}
|
|
409
|
+
renderTarget={target.render}
|
|
410
|
+
/>
|
|
411
|
+
));
|
|
412
|
+
|
|
413
|
+
return (
|
|
414
|
+
<CardDragSurface onCardIntent={handleIntent}>
|
|
415
|
+
{summaryContent !== null ? (
|
|
416
|
+
<div
|
|
417
|
+
data-dreamboard-runtime-hand-summary=""
|
|
418
|
+
data-zone={zone}
|
|
419
|
+
data-selection-count={selectionSummary.selectedCount}
|
|
420
|
+
data-has-invalid-selection={
|
|
421
|
+
selectionSummary.hasInvalidSelection ? "true" : "false"
|
|
422
|
+
}
|
|
423
|
+
>
|
|
424
|
+
{summaryContent}
|
|
425
|
+
</div>
|
|
426
|
+
) : null}
|
|
427
|
+
{renderDropTargets ? (
|
|
428
|
+
renderDropTargets(<Fragment>{targets}</Fragment>)
|
|
429
|
+
) : (
|
|
430
|
+
<div data-dreamboard-runtime-drop-row="" className="flex gap-2 mb-2">
|
|
431
|
+
{targets}
|
|
432
|
+
</div>
|
|
433
|
+
)}
|
|
434
|
+
{handView}
|
|
435
|
+
{actionsContent}
|
|
436
|
+
</CardDragSurface>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export interface HandStagingViewProps {
|
|
441
|
+
/** Zone whose many-select collection this stages. */
|
|
442
|
+
zone: string;
|
|
443
|
+
/** Visual for a staged card. */
|
|
444
|
+
renderCard: (card: ZoneCardRenderItem) => ReactNode;
|
|
445
|
+
/** Custom empty-slot content. */
|
|
446
|
+
renderEmptySlot?: (index: number) => ReactNode;
|
|
447
|
+
/** Heading above the slots. */
|
|
448
|
+
label?: ReactNode;
|
|
449
|
+
cardSize?: "sm" | "md" | "lg";
|
|
450
|
+
ariaLabel?: string;
|
|
451
|
+
className?: string;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** First available many-select card-target selection scoped to `zone`. */
|
|
455
|
+
function manyCardSelectionForZone(
|
|
456
|
+
availableInteractions: readonly InteractionDescriptor[],
|
|
457
|
+
zone: string,
|
|
458
|
+
): { max?: number } | null {
|
|
459
|
+
for (const descriptor of availableInteractions) {
|
|
460
|
+
if (!isInteractionAvailable(descriptor)) continue;
|
|
461
|
+
for (const input of descriptor.inputs) {
|
|
462
|
+
if (!isTargetDomain(input.domain)) continue;
|
|
463
|
+
if (input.domain.type !== "cardTarget") continue;
|
|
464
|
+
const declaredZones =
|
|
465
|
+
input.domain.zoneId !== undefined
|
|
466
|
+
? [input.domain.zoneId]
|
|
467
|
+
: (input.domain.zoneIds ?? []);
|
|
468
|
+
if (declaredZones.length !== 0 && !declaredZones.includes(zone)) continue;
|
|
469
|
+
if (input.domain.selection?.mode === "many")
|
|
470
|
+
return input.domain.selection;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Dreamboard-aware staging surface for a many-select card collection (e.g. the
|
|
478
|
+
* cards you've chosen to pass). Projects the live draft into a fixed,
|
|
479
|
+
* always-visible row of slots (empty placeholders until something is staged);
|
|
480
|
+
* tapping a staged card routes an `activate` intent, which toggles it back out
|
|
481
|
+
* of the collection so it returns to the hand. Adds no draft logic — the
|
|
482
|
+
* presentational layout/theming lives in the SDK `StagingZone`.
|
|
483
|
+
*
|
|
484
|
+
* Renders nothing when the zone has no active many-select collection and
|
|
485
|
+
* nothing staged, so authors can mount it unconditionally.
|
|
486
|
+
*/
|
|
487
|
+
export function HandStagingView({
|
|
488
|
+
zone,
|
|
489
|
+
renderCard,
|
|
490
|
+
renderEmptySlot,
|
|
491
|
+
label,
|
|
492
|
+
cardSize = "sm",
|
|
493
|
+
ariaLabel,
|
|
494
|
+
className,
|
|
495
|
+
}: HandStagingViewProps) {
|
|
496
|
+
const interactionStore = useInteractionUiStore();
|
|
497
|
+
const drafts = useStore(
|
|
498
|
+
interactionStore,
|
|
499
|
+
useShallow((state) => state.drafts),
|
|
500
|
+
);
|
|
501
|
+
const stagedIds = usePluginState(
|
|
502
|
+
useCallback(
|
|
503
|
+
(state: PluginStateSnapshot) =>
|
|
504
|
+
selectedCardIdsForZone(interactionStore, zone, state),
|
|
505
|
+
// drafts is part of the store snapshot — useStore handles re-render.
|
|
506
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
507
|
+
[interactionStore, zone, drafts],
|
|
508
|
+
),
|
|
509
|
+
);
|
|
510
|
+
const zoneSnapshot = usePluginState(
|
|
511
|
+
(state) => state.gameplay.zones[zone] ?? null,
|
|
512
|
+
);
|
|
513
|
+
const availableInteractions = usePluginState(
|
|
514
|
+
(state) => state.gameplay.availableInteractions,
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
const cards = useMemo(
|
|
518
|
+
() =>
|
|
519
|
+
stagedIds
|
|
520
|
+
.map((cardId, index) =>
|
|
521
|
+
createZoneCardRenderItem(zone, zoneSnapshot, cardId, index),
|
|
522
|
+
)
|
|
523
|
+
.filter((card) => !card.hidden),
|
|
524
|
+
[stagedIds, zone, zoneSnapshot],
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
const selection = useMemo(
|
|
528
|
+
() => manyCardSelectionForZone(availableInteractions, zone),
|
|
529
|
+
[availableInteractions, zone],
|
|
530
|
+
);
|
|
531
|
+
const slotCount =
|
|
532
|
+
selection?.max ?? (selection ? Math.max(cards.length, 1) : cards.length);
|
|
533
|
+
|
|
534
|
+
const route = useCardIntentAdapter({ zone });
|
|
535
|
+
const onRemove = useCallback(
|
|
536
|
+
(cardId: string) => {
|
|
537
|
+
void route({ type: "activate", cardId, source: "tap" });
|
|
538
|
+
},
|
|
539
|
+
[route],
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
if (slotCount <= 0 && cards.length === 0) return null;
|
|
543
|
+
|
|
544
|
+
return (
|
|
545
|
+
<StagingZone
|
|
546
|
+
cards={cards as readonly ViewCard[]}
|
|
547
|
+
slotCount={slotCount}
|
|
548
|
+
size={cardSize}
|
|
549
|
+
renderCard={(card) => renderCard(card as ZoneCardRenderItem)}
|
|
550
|
+
onRemove={onRemove}
|
|
551
|
+
renderEmptySlot={renderEmptySlot}
|
|
552
|
+
label={label}
|
|
553
|
+
aria-label={ariaLabel}
|
|
554
|
+
className={className}
|
|
555
|
+
/>
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Convenience helper for builders of generated hand surfaces. Encodes a
|
|
561
|
+
* typed target kind plus value into the opaque `targetId` that the SDK
|
|
562
|
+
* presents to its drop pipeline. The runtime adapter resolves the kind to
|
|
563
|
+
* the matching descriptor input at drop time, so authors never need to
|
|
564
|
+
* know collector input names. Generated typed targets call this internally.
|
|
565
|
+
*/
|
|
566
|
+
export function dropTargetIdFor(
|
|
567
|
+
kind: RuntimeDropTargetKind,
|
|
568
|
+
value: string,
|
|
569
|
+
): string {
|
|
570
|
+
return encodeRuntimeDropTargetKind(kind, value);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Determine whether any descriptor in the available list expects a board or
|
|
575
|
+
* card destination input alongside a card target. Used by generated facades
|
|
576
|
+
* to decide whether to mount a drag surface even before the author wires
|
|
577
|
+
* dropTargets.
|
|
578
|
+
*/
|
|
579
|
+
export function descriptorsHaveDestinationInput(
|
|
580
|
+
state: PluginStateSnapshot,
|
|
581
|
+
): boolean {
|
|
582
|
+
for (const descriptor of state.gameplay.availableInteractions) {
|
|
583
|
+
const cardInputs = descriptor.inputs.filter(
|
|
584
|
+
(input) =>
|
|
585
|
+
isTargetDomain(input.domain) && input.domain.type === "cardTarget",
|
|
586
|
+
);
|
|
587
|
+
const destinationInputs = descriptor.inputs.filter(
|
|
588
|
+
(input) =>
|
|
589
|
+
isTargetDomain(input.domain) && input.domain.type !== "cardTarget",
|
|
590
|
+
);
|
|
591
|
+
if (cardInputs.length > 0 && destinationInputs.length > 0) return true;
|
|
592
|
+
}
|
|
593
|
+
return false;
|
|
594
|
+
}
|