@dreamboard-games/sdk 0.5.0-alpha.15 → 0.5.0-alpha.17

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 (32) hide show
  1. package/README.md +74 -9
  2. package/dist/{board-element-CRN_hj5F.d.ts → board-element-Bi_CEmNa.d.ts} +1 -1
  3. package/dist/{chunk-TJQTLYDI.js → chunk-BNQF7QXR.js} +73 -11
  4. package/dist/chunk-BNQF7QXR.js.map +1 -0
  5. package/dist/{chunk-ES74C2IN.js → chunk-CZRPUWCT.js} +141 -115
  6. package/dist/chunk-CZRPUWCT.js.map +1 -0
  7. package/dist/{chunk-W5G6VMMQ.js → chunk-ECLANZFV.js} +108 -44
  8. package/dist/chunk-ECLANZFV.js.map +1 -0
  9. package/dist/{chunk-B4RMCLUN.js → chunk-U6R7FAH4.js} +15 -2
  10. package/dist/chunk-U6R7FAH4.js.map +1 -0
  11. package/dist/{chunk-YUEMDRIH.js → chunk-YT3RPO5J.js} +3 -3
  12. package/dist/{diagnostics-BokP7HN3.d.ts → diagnostics-C_07naxM.d.ts} +1 -1
  13. package/dist/{extract-DgzrJ5el.d.ts → extract-DcvrH9lh.d.ts} +103 -5
  14. package/dist/index.d.ts +319 -33
  15. package/dist/index.js +159 -39
  16. package/dist/index.js.map +1 -1
  17. package/dist/react.d.ts +23 -9
  18. package/dist/react.js +255 -87
  19. package/dist/react.js.map +1 -1
  20. package/dist/reducer.d.ts +18 -6
  21. package/dist/reducer.js +43 -4
  22. package/dist/reducer.js.map +1 -1
  23. package/dist/{shortcuts-Chlx8o7p.d.ts → shortcuts-BKKRbmvs.d.ts} +99 -12
  24. package/dist/testing.d.ts +3 -3
  25. package/dist/testing.js +4 -4
  26. package/dist/{types-Xg_RxV1n.d.ts → types-DU9GGPt6.d.ts} +1 -1
  27. package/package.json +1 -1
  28. package/dist/chunk-B4RMCLUN.js.map +0 -1
  29. package/dist/chunk-ES74C2IN.js.map +0 -1
  30. package/dist/chunk-TJQTLYDI.js.map +0 -1
  31. package/dist/chunk-W5G6VMMQ.js.map +0 -1
  32. /package/dist/{chunk-YUEMDRIH.js.map → chunk-YT3RPO5J.js.map} +0 -0
package/README.md CHANGED
@@ -349,15 +349,43 @@ install the React adapter. The `/react` entry delegates selectors to that packag
349
349
  the application bundler resolves its supported React subscription dependencies.
350
350
 
351
351
  `/react` classifies each card press as a tap, hold, drag or browse. Enable
352
- `dragFeature`, export `useCardGesture`, `useActiveCard`, `useDropArea` and `useDragOverlay` from the
352
+ `dragFeature`, export `useCardGesture`, `useCardRow`, `useActiveCard`, `useDropArea` and `useDragOverlay` from the
353
353
  binding, and use the copied Hand and BoardTargets components or your own renderer.
354
354
  The SDK prescribes no layout. Headless `game.drag` remains browser-free and handles
355
- atomic domain routing. `fanLayout` computes arc positions for a hand,
355
+ atomic domain routing. A drop area can bind typed inputs with
356
+ `{ interaction: "play.move", params: { destination: "discard" } }`; the SDK
357
+ adds the dragged card and admits the complete draft against current domains.
358
+ A ready bound single-card drop submits the action even when its commit mode is
359
+ manual; unbound drops and many-card drafts keep their normal commit behavior.
360
+ `game.drag.getIsDropTarget(target)` uses the same admission as dropping.
361
+ Interaction `getSubmitProps(params)` and `getSubmitHandler(params)` accept the
362
+ same explicit params as `submit(params)`, retaining pending and connection gates.
363
+ `fanLayout` computes arc positions that always fit a hand,
356
364
  `handFan` adds a focused, readable card with configurable presets and timing,
357
365
  and
358
366
  `originsFeature` adds `card.getOrigin()` so a newly shown card can animate from
359
367
  the zone or player it came from.
360
368
 
369
+ ## Local hand sorting
370
+
371
+ Install `handFeature(core, context, { zones })` with named sorts for each zone:
372
+ `{ hand: { defaultSort: "rank", sorts: { rank: { compare } } } }`.
373
+ Comparators receive the seat's projected `Card` objects; narrow `card.hidden`
374
+ before reading visible card data. Equal comparisons retain source order.
375
+
376
+ `game.hand.getSortModes(zone)` returns typed mode IDs in declaration order.
377
+ Read `getSortMode(zone)` and `getSortedCardIds(zone)`, then call
378
+ `setSortMode(zone, mode)` from your UI. The zone carries its canonical host;
379
+ each host's choice is independent. Labels, buttons and cycling are authored UI.
380
+ Without a selected or default mode, the order comes directly from the source.
381
+ The feature retains the zone's selected/selectable card ID helpers.
382
+
383
+ Sort changes notify subscribers and preserve drafts and card selection without
384
+ submitting. Choices survive frames and reset on source or seat changes. Captured
385
+ `game.hand` branches retain their modes, and sorted IDs are derived from the
386
+ projected zone passed to that branch. Old lifetime setters and disposed setters
387
+ do nothing. See [Optional features](../../docs/api/features.md).
388
+
361
389
  ## Opt-in game shortcuts
362
390
 
363
391
  Install `shortcutsFeature` in the same `createGameHook<Definition>()` binding as
@@ -370,6 +398,7 @@ function AppShortcuts() {
370
398
  useGameShortcuts({
371
399
  bindings: [
372
400
  {
401
+ kind: "interaction",
373
402
  keys: ["1", "2", "3", "4", "5", "6", "7", "8", "9"],
374
403
  label: "Draw cards",
375
404
  target: "zone",
@@ -378,6 +407,7 @@ function AppShortcuts() {
378
407
  inputs: ({ key }) => ({ count: Number(key) }),
379
408
  },
380
409
  {
410
+ kind: "interaction",
381
411
  keys: ["f"],
382
412
  label: "Flip card",
383
413
  target: "card",
@@ -385,18 +415,41 @@ function AppShortcuts() {
385
415
  interaction: "play.flip",
386
416
  inputs: ({ target }) => ({ card: target.value }),
387
417
  },
418
+ {
419
+ kind: "local",
420
+ keys: ["s"],
421
+ label: "Change hand order",
422
+ target: "zone",
423
+ zoneId: "hand",
424
+ getIsAvailable: ({ target }) => target.hostId === playerId,
425
+ run: ({ target }) => changeAuthoredOrder(target.hostId),
426
+ },
388
427
  ],
389
428
  });
390
429
  return null;
391
430
  }
392
431
  ```
393
432
 
394
- These interaction and input names are examples from an authored reducer. The
433
+ These interaction and input names are examples from an authored reducer;
434
+ `playerId` and `changeAuthoredOrder` are supplied by the game. The
395
435
  binding's discriminated types derive from that reducer: interaction names,
396
436
  input keys, target kinds, and every required payload field must match. A game
397
437
  that exposes a counted draw submits one `count` payload; shortcuts never repeat
398
438
  a draw-one action. No actions or keys are supplied by default.
399
439
 
440
+ Local bindings run game-authored presentation callbacks for a focused zone
441
+ control or a focused card in that zone. A focused card resolves its current
442
+ containing zone and actual host, including opaque hidden card references. Local
443
+ bindings always use real DOM focus, including focus acquired by clicking; hover
444
+ alone never activates them. Mouse browsing can change the interaction target
445
+ while local actions retain the focused control's zone. They are unavailable
446
+ during a card pointer gesture or its settling drag overlay. Games own any
447
+ further restrictions through the optional pure `getIsAvailable` predicate.
448
+ They do not submit source commands, alter interaction drafts, or require the
449
+ player's reducer turn; an interaction request does not block a local action.
450
+ `run` returns `void` or `Promise<void>` and is never evaluated for hints.
451
+ The SDK supplies no sorting modes or cycling policy.
452
+
400
453
  Card controls using `useCardGesture` share its canonical pointer and keyboard
401
454
  focus. Zone controls spread
402
455
  `useShortcutTarget({ kind: "zone", zoneId: "deck", hostId: "table" }).props`;
@@ -406,19 +459,29 @@ board and tile controls can use the same hook with their canonical
406
459
 
407
460
  Keys are exact browser `event.key` strings. The adapter ignores held repeats,
408
461
  composition, editing controls, modifier combinations, and open modal or menu
409
- surfaces. It prevents the browser default only when a single eligible binding
462
+ surfaces. Native radio inputs can carry a zone target for authored shortcuts;
463
+ their ordinary arrow and space keys remain native unless explicitly bound.
464
+ It prevents the browser default only when a single eligible binding
410
465
  handles the key. Source and seat changes clear the gesture target; unmounting
411
466
  removes the listener and binding hints.
412
467
 
413
468
  Input factories must be pure: eligibility and hints evaluate them against the
414
- current projected domains. `game.shortcuts.getHints(target)` returns only
415
- currently usable, unambiguous keys with their labels and typed interaction
416
- names, for menus or help. `useShortcutHints(target)` reads those hints reactively
469
+ current projected domains. Availability predicates for local bindings must
470
+ also be pure. `game.shortcuts.getHints(target, activity)` returns only
471
+ currently usable, unambiguous keys with their labels and `kind`. Interaction
472
+ hints also carry their typed interaction name; local hints do not. Both variants
473
+ share key collision handling. Headless callers supply local focus explicitly as
474
+ `activity: { focusedTarget, pointerActive }`; omitting activity admits only
475
+ interaction bindings. `useShortcutHints(target)` reads those hints reactively
417
476
  and returns no hints when the feature is absent. The mounted
418
477
  `useGameShortcuts` hook returns `{ target, hints }` for a reserved help/status
419
478
  area. Rejected actions and transport failures use the binding's existing
420
479
  `onError` handler; callbacks from an old source, seat, or unmounted child are
421
- ignored. `game.shortcuts.handle(key, target)` uses the same
480
+ ignored. `game.shortcuts.handle(key, target, activity)` returns `null` for an
481
+ unhandled
482
+ key, or a promise of `{ kind: "local" }` or
483
+ `{ kind: "interaction", result: SubmitResult }`. Callback failures reject that
484
+ promise and use the same `onError` path. Interaction bindings use the same
422
485
  atomic `interaction.submit(params)` path available to authored action buttons.
423
486
  Explicit params are validated against the current projected descriptor without
424
487
  mutating drafts. Existing `submit()` continues to submit the interaction draft
@@ -484,7 +547,9 @@ Standard playing cards are game-owned definitions with ordinary suit/rank
484
547
  properties; the SDK does not synthesize inventories or assign built-in rules.
485
548
 
486
549
  The exported `createGestureRecognizer(down, callbacks, options)` defaults to
487
- upward touch dragging so hands retain sideways scrolling. Pile controls can use
550
+ upward touch dragging so card containers retain sideways scrolling. A row that
551
+ fits uses `{ scrub: true }`: a sliding finger reports each move through
552
+ `browse`, and turning upward still drags. Pile controls can use
488
553
  `{ dragDirection: "any" }` with `touch-action: none` to drag toward a hand below
489
554
  or beside them. This classifies pointer intent only; submit an authored interaction
490
555
  through the bound game to enforce its rules, without selecting a hidden card ID.
@@ -1,4 +1,4 @@
1
- import { f as BoardEdgeId, n as BoardVertexId } from './extract-DgzrJ5el.js';
1
+ import { f as BoardEdgeId, n as BoardVertexId } from './extract-DcvrH9lh.js';
2
2
 
3
3
  declare function boardEdgeId<BoardId extends string>(layout: "hex" | "square", boardId: BoardId, latticeId: string): BoardEdgeId<BoardId>;
4
4
  declare function boardVertexId<BoardId extends string>(layout: "hex" | "square", boardId: BoardId, latticeId: string): BoardVertexId<BoardId>;
@@ -8,7 +8,7 @@ import {
8
8
  boardSpaceHostId,
9
9
  isCardReferenceNamespace,
10
10
  parseBoardSpaceHostId
11
- } from "./chunk-B4RMCLUN.js";
11
+ } from "./chunk-U6R7FAH4.js";
12
12
  import {
13
13
  AuthoredTileDisclosureSchema,
14
14
  BoardProjectionSchema,
@@ -30,11 +30,13 @@ import {
30
30
  inputValueInDomain,
31
31
  inputValueKey,
32
32
  isBoardSpaceTarget,
33
+ isPositionTarget,
33
34
  parsePerPlayerInstanceId,
34
35
  parseTileSpaceId,
35
36
  requireLookup,
36
- tileSpaceId
37
- } from "./chunk-ES74C2IN.js";
37
+ tileSpaceId,
38
+ zonePositions
39
+ } from "./chunk-CZRPUWCT.js";
38
40
 
39
41
  // src/reducer/model/error-codes.ts
40
42
  var FrameworkErrorCodes = {
@@ -1344,6 +1346,20 @@ function moveComponentToZoneInPlace(options) {
1344
1346
  destination.ids.splice(index, 0, componentId);
1345
1347
  place(table, definitions, componentId, destination.ref, playedBy);
1346
1348
  }
1349
+ function moveComponentToPositionInPlace(options) {
1350
+ const { table, definitions, componentId, at, playedBy } = options;
1351
+ const to = { zoneId: at.zoneId, hostId: at.hostId };
1352
+ const source = table.componentLocations[componentId];
1353
+ const from = source?.type === "InZone" && source.zoneId === at.zoneId && source.hostId === at.hostId ? resolveZone(table, definitions, to).ids.indexOf(componentId) : -1;
1354
+ moveComponentToZoneInPlace({
1355
+ table,
1356
+ definitions,
1357
+ componentId,
1358
+ to,
1359
+ position: from >= 0 && from < at.index ? at.index - 1 : at.index,
1360
+ playedBy
1361
+ });
1362
+ }
1347
1363
  function dealComponentsInPlace(options) {
1348
1364
  const { table, definitions } = options;
1349
1365
  assertNonNegativeSafeInteger(options.count, "Deal count");
@@ -2448,6 +2464,14 @@ var transactionMutations = {
2448
2464
  moveComponentToZoneInPlace({ table: state.table, definitions, ...args });
2449
2465
  return state;
2450
2466
  },
2467
+ moveComponentToPosition(state, args, definitions) {
2468
+ moveComponentToPositionInPlace({
2469
+ table: state.table,
2470
+ definitions,
2471
+ ...args
2472
+ });
2473
+ return state;
2474
+ },
2451
2475
  deal(state, args, definitions) {
2452
2476
  dealComponentsInPlace({ table: state.table, definitions, ...args });
2453
2477
  return state;
@@ -3806,6 +3830,14 @@ function baseValuesForDomain(domain, collector, context) {
3806
3830
  )
3807
3831
  );
3808
3832
  }
3833
+ case "zonePosition":
3834
+ return finiteValueSource(
3835
+ collector.eligibleTargets ? collector.eligibleTargets(
3836
+ context.domainState,
3837
+ context.playerId,
3838
+ context.queries()
3839
+ ) : zonePositions(domain.zones)
3840
+ );
3809
3841
  case "choiceList":
3810
3842
  return choiceListValueSource(domain);
3811
3843
  case "boundedNumber":
@@ -5772,6 +5804,22 @@ function boardReference(value, domain, disclosure, decode) {
5772
5804
  if (typeof value !== "string") return denied2;
5773
5805
  return map(domain.targetKind, domain.boardId, value) ?? denied2;
5774
5806
  }
5807
+ function zoneHostReference(zoneId, hostId, disclosure, decode) {
5808
+ const zone = disclosure.zones.find(
5809
+ (zone2) => zone2.zoneId === zoneId && (decode ? zone2.seatHostId : zone2.hostId) === hostId
5810
+ );
5811
+ return zone ? decode ? zone.hostId : zone.seatHostId : null;
5812
+ }
5813
+ function positionReference(value, disclosure, decode) {
5814
+ if (!isPositionTarget(value)) return denied2;
5815
+ const hostId = zoneHostReference(
5816
+ value.zoneId,
5817
+ value.hostId,
5818
+ disclosure,
5819
+ decode
5820
+ );
5821
+ return hostId === null ? denied2 : { ...value, hostId };
5822
+ }
5775
5823
  function domainMapper(domain, disclosure, cards, decode) {
5776
5824
  switch (domain.type) {
5777
5825
  case "tileTarget":
@@ -5780,12 +5828,16 @@ function domainMapper(domain, disclosure, cards, decode) {
5780
5828
  return (value) => cardReference(value, disclosure, cards, decode);
5781
5829
  case "boardTarget":
5782
5830
  return (value) => boardReference(value, domain, disclosure, decode);
5831
+ case "zonePosition":
5832
+ return (value) => positionReference(value, disclosure, decode);
5783
5833
  default:
5784
5834
  return void 0;
5785
5835
  }
5786
5836
  }
5787
5837
  function collectorMapper(collector, disclosure, cards, decode) {
5788
5838
  switch (collector.kind) {
5839
+ case "position":
5840
+ return (value) => positionReference(value, disclosure, decode);
5789
5841
  case "tile":
5790
5842
  return (value) => typeof value === "string" ? (decode ? disclosure.tileId(value) : disclosure.tileRef(value)) ?? denied2 : denied2;
5791
5843
  case "card":
@@ -5826,15 +5878,25 @@ function projectInput(input, disclosure, cards) {
5826
5878
  const map = domainMapper(input.domain, disclosure, cards, false);
5827
5879
  if (!map) return input;
5828
5880
  const domain = input.domain;
5829
- if (domain.type !== "tileTarget" && domain.type !== "cardTarget" && domain.type !== "boardTarget")
5881
+ if (domain.type !== "tileTarget" && domain.type !== "cardTarget" && domain.type !== "boardTarget" && domain.type !== "zonePosition")
5830
5882
  return input;
5831
- const eligibleTargets = domain.eligibleTargets.flatMap((value) => {
5832
- const mapped = map(value);
5833
- return mapped === denied2 ? [] : [mapped];
5834
- });
5835
- const projectedDomain = {
5883
+ const projectedDomain = domain.type === "zonePosition" ? {
5884
+ ...domain,
5885
+ zones: domain.zones.flatMap((zone) => {
5886
+ const hostId = zoneHostReference(
5887
+ zone.zoneId,
5888
+ zone.hostId,
5889
+ disclosure,
5890
+ false
5891
+ );
5892
+ return hostId === null ? [] : [{ ...zone, hostId }];
5893
+ })
5894
+ } : {
5836
5895
  ...domain,
5837
- eligibleTargets
5896
+ eligibleTargets: domain.eligibleTargets.flatMap((value) => {
5897
+ const mapped = map(value);
5898
+ return mapped === denied2 ? [] : [mapped];
5899
+ })
5838
5900
  };
5839
5901
  const { defaultValue, ...rest } = input;
5840
5902
  const mappedDefault = defaultValue === void 0 ? denied2 : mapSelection(
@@ -7123,4 +7185,4 @@ export {
7123
7185
  createProjectionBuilder,
7124
7186
  createReducerBundle
7125
7187
  };
7126
- //# sourceMappingURL=chunk-TJQTLYDI.js.map
7188
+ //# sourceMappingURL=chunk-BNQF7QXR.js.map