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

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/README.md CHANGED
@@ -352,7 +352,9 @@ the application bundler resolves its supported React subscription dependencies.
352
352
  `dragFeature`, export `useCardGesture`, `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, and
355
+ atomic domain routing. `fanLayout` computes arc positions for a hand,
356
+ `handFan` adds a focused, readable card with configurable presets and timing,
357
+ and
356
358
  `originsFeature` adds `card.getOrigin()` so a newly shown card can animate from
357
359
  the zone or player it came from.
358
360
 
@@ -7098,14 +7098,15 @@ export {
7098
7098
  isSparseMapSchema,
7099
7099
  getObjectShape,
7100
7100
  normalizeCommandParams,
7101
+ enumerateZoneHosts,
7102
+ resolveZoneAccess,
7103
+ assertContainmentAcyclic,
7104
+ assertZoneConsistency,
7101
7105
  REDUCER_CONTRACT_VERSION,
7102
7106
  assertReducerBundleContract,
7103
7107
  ZoneVisibilitySchema,
7104
7108
  TileTypeSpecSchema,
7105
7109
  GameTopologyManifestSchema,
7106
- enumerateZoneHosts,
7107
- assertContainmentAcyclic,
7108
- assertZoneConsistency,
7109
7110
  collectReducerDefinitionIndex,
7110
7111
  createIngressRuntimeCodec,
7111
7112
  noopDiagnosticsSink,
@@ -7122,4 +7123,4 @@ export {
7122
7123
  createProjectionBuilder,
7123
7124
  createReducerBundle
7124
7125
  };
7125
- //# sourceMappingURL=chunk-6ZNAIOYG.js.map
7126
+ //# sourceMappingURL=chunk-TJQTLYDI.js.map
package/dist/index.d.ts CHANGED
@@ -1500,6 +1500,122 @@ declare function fanLayout({ count, width, cardWidth, cardHeight, step, minStep,
1500
1500
  /** Moves a fanned card outward along its own tilt, as for a selected card. */
1501
1501
  declare function liftFanCard(card: FanCard, distance: number): FanCard;
1502
1502
 
1503
+ /**
1504
+ * How a hand rests and focuses one card. Lengths are shares of the resting
1505
+ * card, so one configuration fits every card size. Missing options come from
1506
+ * `handFanPresets.open`.
1507
+ */
1508
+ interface HandFanOptions extends Pick<FanOptions, "angle" | "maxSpread"> {
1509
+ /** Distance between neighbouring cards when there is room, in card widths. Default 0.6, as `fanLayout`. */
1510
+ readonly spacing?: number;
1511
+ /** Share of each resting card's height below the hand's bottom edge. 0 shows whole cards. */
1512
+ readonly tuck?: number;
1513
+ /** Focused face width as a multiple of the resting card. */
1514
+ readonly focusScale?: number;
1515
+ /** Tallest focused face, as a share of the window height. */
1516
+ readonly focusMaxHeight?: number;
1517
+ /**
1518
+ * Sideways shift of the first, second, third… neighbour, in card widths.
1519
+ * The focused face's extra width tapers over the same neighbours.
1520
+ */
1521
+ readonly push?: readonly number[];
1522
+ /** Narrowest strip of each neighbour kept visible, in card widths. */
1523
+ readonly exposed?: number;
1524
+ }
1525
+ /** Starting points for a hand; spread one and override what differs. */
1526
+ declare const handFanPresets: {
1527
+ /** Whole resting cards; focusing enlarges the card. */
1528
+ readonly open: {
1529
+ readonly tuck: 0;
1530
+ readonly focusScale: 2;
1531
+ readonly focusMaxHeight: 0.55;
1532
+ readonly push: readonly [0.29, 0.19, 0.1];
1533
+ readonly exposed: 0.35;
1534
+ };
1535
+ /**
1536
+ * Larger cards tucked below the bottom edge and spaced like a dealt hand;
1537
+ * focusing mostly reveals the card.
1538
+ */
1539
+ readonly tucked: {
1540
+ readonly spacing: 0.75;
1541
+ readonly tuck: 0.4;
1542
+ readonly focusScale: 1.4;
1543
+ readonly focusMaxHeight: 0.4;
1544
+ readonly push: readonly [0.29, 0.19, 0.1];
1545
+ readonly exposed: 0.35;
1546
+ };
1547
+ /** Coarse pointers: a shallower tuck and wider strips to tap. */
1548
+ readonly touch: {
1549
+ readonly spacing: 0.75;
1550
+ readonly tuck: 0.2;
1551
+ readonly focusScale: 1.6;
1552
+ readonly focusMaxHeight: 0.5;
1553
+ readonly push: readonly [0.35, 0.2, 0.1];
1554
+ readonly exposed: 0.45;
1555
+ };
1556
+ };
1557
+ /** A card's `fanLayout` pose with its scale and stacking order. */
1558
+ interface HandFanPose extends FanCard {
1559
+ readonly scale: number;
1560
+ /** Stacking order: fan order, with the focused card above the rest. */
1561
+ readonly layer: number;
1562
+ }
1563
+ interface HandFan {
1564
+ /** One pose per card, in the same coordinates as `fanLayout`. */
1565
+ readonly cards: readonly HandFanPose[];
1566
+ /** The fan's width, as `fanLayout` reports it. */
1567
+ readonly width: number;
1568
+ /** The visible band: the fan's height less the tucked part of its cards. */
1569
+ readonly height: number;
1570
+ /** Space above the band that a focused face can rise into. */
1571
+ readonly headroom: number;
1572
+ }
1573
+ interface HandFanInput extends Pick<FanOptions, "count" | "width" | "cardWidth" | "cardHeight"> {
1574
+ /** Index of the focused card, or -1 for none. */
1575
+ readonly focused?: number;
1576
+ /**
1577
+ * The part of the fan the viewer can see after scrolling, in fan
1578
+ * coordinates; a focused face stays inside it. Defaults to `width`,
1579
+ * centred on the fan.
1580
+ */
1581
+ readonly visible?: {
1582
+ readonly left: number;
1583
+ readonly width: number;
1584
+ };
1585
+ /** Window height in pixels; `focusMaxHeight` is a share of it. */
1586
+ readonly windowHeight?: number;
1587
+ /** Pixels above the band's bottom edge that a focused face may use. */
1588
+ readonly room?: number;
1589
+ readonly options?: HandFanOptions;
1590
+ }
1591
+ /**
1592
+ * Lays a hand on a `fanLayout` arc and focuses one card: upright on the
1593
+ * band's bottom edge at a readable size, with nearby cards pushed sideways.
1594
+ * Neighbours keep their order and at least their `exposed` strip.
1595
+ */
1596
+ declare function handFan({ count, width, cardWidth, cardHeight, focused, visible, windowHeight, room, options, }: HandFanInput): HandFan;
1597
+ /**
1598
+ * An exponential approach to the target. An animation restarted from the
1599
+ * current pose continues the same curve at full speed, so retargeting never
1600
+ * stalls a card or overshoots.
1601
+ */
1602
+ declare function exponentialOut(progress: number): number;
1603
+ /** Durations in seconds with an easing function, as Motion transitions take them. */
1604
+ declare const handFanTiming: {
1605
+ /** The focused face rises: 90% in about 45ms. */
1606
+ readonly focus: {
1607
+ readonly duration: 0.09;
1608
+ readonly ease: typeof exponentialOut;
1609
+ };
1610
+ /** Sideways travel, neighbours and returning cards: 90% in about 300ms. */
1611
+ readonly settle: {
1612
+ readonly duration: 0.6;
1613
+ readonly ease: typeof exponentialOut;
1614
+ };
1615
+ };
1616
+ /** An easing function as a CSS `linear()` easing, for CSS transitions and the Web Animations API. */
1617
+ declare function cssEasing(ease: (progress: number) => number, samples?: number): string;
1618
+
1503
1619
  declare function canonicalizePluginRuntimeJson(value: unknown): RuntimeJson;
1504
1620
  declare function encodeCanonicalPluginRuntimeJson(value: unknown): string;
1505
1621
  declare function digestPluginRuntimeJson(value: unknown): string;
@@ -4755,4 +4871,4 @@ declare function parsePluginGameplayFrame(value: unknown): PluginGameplayFrame;
4755
4871
  declare function parseHostToPluginEnvelope(value: unknown): PluginProtocolEnvelope<HostToPluginPayload>;
4756
4872
  declare function parsePluginToHostEnvelope(value: unknown): PluginProtocolEnvelope<PluginToHostPayload>;
4757
4873
 
4758
- export { ActionProps, type ActionSetVersionInput, AmbiguousTargetError, AuthAcceptedFrameSchema, AuthConnectFrameSchema, AuthRefreshFrameSchema, BoardBase, BoardCollection, BoardDataOf, BoardEdgeId, type BoardFeature, type BoardLayoutOptions, type BoardLayoutTile, BoardSpace, BoardSpaceCollection, BoardVertexId, type CancelInteractionCommand, CancelInteractionCommandSchema, Card, type CardOrigin, type ClientGameplayFrame, ClientGameplayFrameSchema, CommandSource, CoreInstance, DREAMBOARD_PLUGIN_PROTOCOL, DREAMBOARD_PLUGIN_PROTOCOL_VERSION, DREAMBOARD_SDK_PACKAGES, DREAMBOARD_SDK_PACKAGE_SET, DREAMBOARD_SDK_VERSION, type DragController, type DragState, type DreamboardSdkPackageName, type DreamboardSdkPackageSet, DropTarget, type FanCard, type FanLayout, type FanOptions, FeatureContext, Features, GESTURE_THRESHOLDS, GameEventDetailSchema, GameEventSchema, GameInstance, GameOutcomeSchema, GameSource, GameplayBackpressureFrameSchema, GameplayBackpressureOperationSchema, GameplayBackpressureReasonSchema, GameplayBasis, GameplayBasisSchema, GameplayCloseCode, type GameplayCredential, GameplayCredentialSchema, type GestureCallbacks, type GestureKind, type GestureOptions, type GesturePointer, type GestureRecognizer, type HandOptions, type HostToPluginEnvelope, HostToPluginEnvelopeSchema, type HostToPluginPayload, HostToPluginPayloadSchema, IdOf, InstanceOptions, InteractionDescriptor, type InteractionResult, InteractionResultSchema, type MaterializePluginGameplayFrameInput, type PanZoomOptions, PlayerId, PluginGameplayFrame, PluginGameplayFrameSchema, PluginPlayerSummarySchema, type PluginProtocolEnvelope, PluginSessionDescriptor, PluginSessionDescriptorSchema, type PluginToHostEnvelope, PluginToHostEnvelopeSchema, type PluginToHostPayload, PluginToHostPayloadSchema, ReducerSeatProjectionBundle, RuntimeJson, RuntimeTargetOptions, SeatBoardTopology, SeatCardId, SeatFrameSchema, SeatProjectionBundleSchema, SeatSpaceRef, SeatTileRef, type ServerGameplayFrame, ServerGameplayFrameSchema, SessionResumeFrameSchema, SessionSnapshotFrameSchema, SimultaneousPhaseSnapshotSchema, SourceSnapshot, type SubmitInteractionCommand, SubmitInteractionCommandSchema, SystemActionEventSchema, TargetOptions, type ViewportSurface, type ViewportTransform, type WheelInput, Zone, ZoneHandlesSnapshotSchema, ZoneHostId, boardFeature, canonicalizePluginRuntimeJson, computePluginActionSetVersion, createGameInstance, createGestureRecognizer, createPluginProtocolEnvelopeSchema, digestPluginCommandRequest, digestPluginGameplayFrame, digestPluginRuntimeJson, dragFeature, encodeCanonicalPluginRuntimeJson, fanLayout, handFeature, hostSource, iframeSource, liftFanCard, magneticDropPoint, materializePluginGameplayFrame, originsFeature, panZoomFeature, parseHostToPluginEnvelope, parsePluginGameplayFrame, parsePluginSessionDescriptor, parsePluginToHostEnvelope, staticSource };
4874
+ export { ActionProps, type ActionSetVersionInput, AmbiguousTargetError, AuthAcceptedFrameSchema, AuthConnectFrameSchema, AuthRefreshFrameSchema, BoardBase, BoardCollection, BoardDataOf, BoardEdgeId, type BoardFeature, type BoardLayoutOptions, type BoardLayoutTile, BoardSpace, BoardSpaceCollection, BoardVertexId, type CancelInteractionCommand, CancelInteractionCommandSchema, Card, type CardOrigin, type ClientGameplayFrame, ClientGameplayFrameSchema, CommandSource, CoreInstance, DREAMBOARD_PLUGIN_PROTOCOL, DREAMBOARD_PLUGIN_PROTOCOL_VERSION, DREAMBOARD_SDK_PACKAGES, DREAMBOARD_SDK_PACKAGE_SET, DREAMBOARD_SDK_VERSION, type DragController, type DragState, type DreamboardSdkPackageName, type DreamboardSdkPackageSet, DropTarget, type FanCard, type FanLayout, type FanOptions, FeatureContext, Features, GESTURE_THRESHOLDS, GameEventDetailSchema, GameEventSchema, GameInstance, GameOutcomeSchema, GameSource, GameplayBackpressureFrameSchema, GameplayBackpressureOperationSchema, GameplayBackpressureReasonSchema, GameplayBasis, GameplayBasisSchema, GameplayCloseCode, type GameplayCredential, GameplayCredentialSchema, type GestureCallbacks, type GestureKind, type GestureOptions, type GesturePointer, type GestureRecognizer, type HandFan, type HandFanInput, type HandFanOptions, type HandFanPose, type HandOptions, type HostToPluginEnvelope, HostToPluginEnvelopeSchema, type HostToPluginPayload, HostToPluginPayloadSchema, IdOf, InstanceOptions, InteractionDescriptor, type InteractionResult, InteractionResultSchema, type MaterializePluginGameplayFrameInput, type PanZoomOptions, PlayerId, PluginGameplayFrame, PluginGameplayFrameSchema, PluginPlayerSummarySchema, type PluginProtocolEnvelope, PluginSessionDescriptor, PluginSessionDescriptorSchema, type PluginToHostEnvelope, PluginToHostEnvelopeSchema, type PluginToHostPayload, PluginToHostPayloadSchema, ReducerSeatProjectionBundle, RuntimeJson, RuntimeTargetOptions, SeatBoardTopology, SeatCardId, SeatFrameSchema, SeatProjectionBundleSchema, SeatSpaceRef, SeatTileRef, type ServerGameplayFrame, ServerGameplayFrameSchema, SessionResumeFrameSchema, SessionSnapshotFrameSchema, SimultaneousPhaseSnapshotSchema, SourceSnapshot, type SubmitInteractionCommand, SubmitInteractionCommandSchema, SystemActionEventSchema, TargetOptions, type ViewportSurface, type ViewportTransform, type WheelInput, Zone, ZoneHandlesSnapshotSchema, ZoneHostId, boardFeature, canonicalizePluginRuntimeJson, computePluginActionSetVersion, createGameInstance, createGestureRecognizer, createPluginProtocolEnvelopeSchema, cssEasing, digestPluginCommandRequest, digestPluginGameplayFrame, digestPluginRuntimeJson, dragFeature, encodeCanonicalPluginRuntimeJson, exponentialOut, fanLayout, handFan, handFanPresets, handFanTiming, handFeature, hostSource, iframeSource, liftFanCard, magneticDropPoint, materializePluginGameplayFrame, originsFeature, panZoomFeature, parseHostToPluginEnvelope, parsePluginGameplayFrame, parsePluginSessionDescriptor, parsePluginToHostEnvelope, staticSource };
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ import {
71
71
  } from "./chunk-ES74C2IN.js";
72
72
 
73
73
  // src/package-set.ts
74
- var DREAMBOARD_SDK_VERSION = true ? "0.5.0-alpha.13" : "0.0.0-development";
74
+ var DREAMBOARD_SDK_VERSION = true ? "0.5.0-alpha.15" : "0.0.0-development";
75
75
  var DREAMBOARD_SDK_PACKAGES = {
76
76
  "@dreamboard-games/sdk": DREAMBOARD_SDK_VERSION
77
77
  };
@@ -1578,6 +1578,151 @@ function liftFanCard(card, distance) {
1578
1578
  rotate: card.rotate
1579
1579
  };
1580
1580
  }
1581
+
1582
+ // src/headless/hand-fan.ts
1583
+ var handFanPresets = {
1584
+ /** Whole resting cards; focusing enlarges the card. */
1585
+ open: {
1586
+ tuck: 0,
1587
+ focusScale: 2,
1588
+ focusMaxHeight: 0.55,
1589
+ push: [0.29, 0.19, 0.1],
1590
+ exposed: 0.35
1591
+ },
1592
+ /**
1593
+ * Larger cards tucked below the bottom edge and spaced like a dealt hand;
1594
+ * focusing mostly reveals the card.
1595
+ */
1596
+ tucked: {
1597
+ spacing: 0.75,
1598
+ tuck: 0.4,
1599
+ focusScale: 1.4,
1600
+ focusMaxHeight: 0.4,
1601
+ push: [0.29, 0.19, 0.1],
1602
+ exposed: 0.35
1603
+ },
1604
+ /** Coarse pointers: a shallower tuck and wider strips to tap. */
1605
+ touch: {
1606
+ spacing: 0.75,
1607
+ tuck: 0.2,
1608
+ focusScale: 1.6,
1609
+ focusMaxHeight: 0.5,
1610
+ push: [0.35, 0.2, 0.1],
1611
+ exposed: 0.45
1612
+ }
1613
+ };
1614
+ function handFan({
1615
+ count,
1616
+ width,
1617
+ cardWidth,
1618
+ cardHeight,
1619
+ focused = -1,
1620
+ visible,
1621
+ windowHeight = Infinity,
1622
+ room = Infinity,
1623
+ options
1624
+ }) {
1625
+ const o = { ...handFanPresets.open, ...options };
1626
+ const fan = fanLayout({
1627
+ count,
1628
+ width,
1629
+ cardWidth,
1630
+ cardHeight,
1631
+ angle: o.angle,
1632
+ maxSpread: o.maxSpread,
1633
+ step: o.spacing === void 0 ? void 0 : o.spacing * cardWidth
1634
+ });
1635
+ const band = Math.max(0, fan.height - o.tuck * cardHeight);
1636
+ const view = visible ?? {
1637
+ left: (fan.width - width) / 2,
1638
+ width
1639
+ };
1640
+ const exposed = cardWidth * o.exposed;
1641
+ const largest = Math.max(
1642
+ cardWidth,
1643
+ Math.min(
1644
+ cardWidth * o.focusScale,
1645
+ windowHeight * o.focusMaxHeight * cardWidth / cardHeight,
1646
+ room * cardWidth / cardHeight,
1647
+ view.width
1648
+ )
1649
+ );
1650
+ const headroom = Math.max(0, largest * cardHeight / cardWidth - band);
1651
+ const cards = fan.cards.map((card, index) => ({
1652
+ ...card,
1653
+ scale: 1,
1654
+ layer: index
1655
+ }));
1656
+ if (focused < 0 || focused >= count)
1657
+ return { cards, width: fan.width, height: band, headroom };
1658
+ const leftStrip = focused > 0 ? exposed : 0;
1659
+ const rightStrip = focused < count - 1 ? exposed : 0;
1660
+ const face = Math.max(
1661
+ cardWidth,
1662
+ Math.min(largest, view.width - leftStrip - rightStrip)
1663
+ );
1664
+ const scale = face / cardWidth;
1665
+ const half = face / 2;
1666
+ const centre = Math.max(
1667
+ view.left + leftStrip + half,
1668
+ Math.min(
1669
+ view.left + view.width - rightStrip - half,
1670
+ cards[focused].x + cardWidth / 2
1671
+ )
1672
+ );
1673
+ cards[focused] = {
1674
+ x: centre - cardWidth / 2,
1675
+ y: band - cardHeight * (scale + 1) / 2,
1676
+ rotate: 0,
1677
+ scale,
1678
+ layer: count
1679
+ };
1680
+ const extra = Math.max(0, half - cardWidth / 2);
1681
+ const reach = o.push.length;
1682
+ for (const direction of [-1, 1]) {
1683
+ let previous = centre;
1684
+ for (let distance = 1, index = focused + direction; index >= 0 && index < count; distance++, index += direction) {
1685
+ const card = cards[index];
1686
+ const shift = (o.push[distance - 1] ?? 0) * cardWidth + (reach ? extra * Math.max(0, 1 - (distance - 1) / reach) : 0);
1687
+ const desired = card.x + cardWidth / 2 + direction * shift;
1688
+ const angle = card.rotate * Math.PI / 180;
1689
+ const halfWidth = (cardWidth * Math.cos(angle) + cardHeight * Math.abs(Math.sin(angle))) / 2;
1690
+ const middleHalfWidth = cardWidth * Math.cos(angle) / 2;
1691
+ const clearance = distance === 1 ? Math.max(exposed, half - middleHalfWidth + exposed) : exposed;
1692
+ const boundary = direction === 1 && distance > 1 ? Math.max(previous + clearance, centre + half + exposed + halfWidth) : previous + direction * clearance;
1693
+ let next = direction === 1 ? Math.max(desired, boundary) : Math.min(desired, boundary);
1694
+ if (distance === 1)
1695
+ next = direction === 1 ? Math.min(
1696
+ next,
1697
+ view.left + view.width - middleHalfWidth,
1698
+ centre + half + middleHalfWidth
1699
+ ) : Math.max(
1700
+ next,
1701
+ view.left + middleHalfWidth,
1702
+ centre - half - middleHalfWidth
1703
+ );
1704
+ cards[index] = { ...card, x: next - cardWidth / 2 };
1705
+ previous = next;
1706
+ }
1707
+ }
1708
+ return { cards, width: fan.width, height: band, headroom };
1709
+ }
1710
+ function exponentialOut(progress) {
1711
+ return (1 - Math.exp(-4.5 * progress)) / (1 - Math.exp(-4.5));
1712
+ }
1713
+ var handFanTiming = {
1714
+ /** The focused face rises: 90% in about 45ms. */
1715
+ focus: { duration: 0.09, ease: exponentialOut },
1716
+ /** Sideways travel, neighbours and returning cards: 90% in about 300ms. */
1717
+ settle: { duration: 0.6, ease: exponentialOut }
1718
+ };
1719
+ function cssEasing(ease, samples = 24) {
1720
+ const points = Array.from(
1721
+ { length: samples + 1 },
1722
+ (_, index) => Number(ease(index / samples).toFixed(4))
1723
+ );
1724
+ return `linear(${points.join(", ")})`;
1725
+ }
1581
1726
  export {
1582
1727
  AmbiguousTargetError,
1583
1728
  AuthAcceptedFrameSchema,
@@ -1633,12 +1778,17 @@ export {
1633
1778
  createGameInstance,
1634
1779
  createGestureRecognizer,
1635
1780
  createPluginProtocolEnvelopeSchema,
1781
+ cssEasing,
1636
1782
  digestPluginCommandRequest,
1637
1783
  digestPluginGameplayFrame,
1638
1784
  digestPluginRuntimeJson,
1639
1785
  dragFeature,
1640
1786
  encodeCanonicalPluginRuntimeJson,
1787
+ exponentialOut,
1641
1788
  fanLayout,
1789
+ handFan,
1790
+ handFanPresets,
1791
+ handFanTiming,
1642
1792
  handFeature,
1643
1793
  hostSource,
1644
1794
  iframeSource,