@ankhorage/zora-game 0.2.0 → 0.4.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.
Files changed (87) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +2 -1
  3. package/dist/ZORA_GAME_COMPONENT_META.d.ts +169 -1
  4. package/dist/ZORA_GAME_COMPONENT_META.d.ts.map +1 -1
  5. package/dist/ZORA_GAME_COMPONENT_META.js +2 -0
  6. package/dist/ZORA_GAME_COMPONENT_META.js.map +1 -1
  7. package/dist/ZORA_GAME_PLUGIN.d.ts +175 -2
  8. package/dist/ZORA_GAME_PLUGIN.d.ts.map +1 -1
  9. package/dist/ZORA_GAME_PLUGIN.js +2 -0
  10. package/dist/ZORA_GAME_PLUGIN.js.map +1 -1
  11. package/dist/ZORA_PLUGIN_METADATA.d.ts +173 -2
  12. package/dist/ZORA_PLUGIN_METADATA.d.ts.map +1 -1
  13. package/dist/ZORA_PLUGIN_METADATA.js +5 -1
  14. package/dist/ZORA_PLUGIN_METADATA.js.map +1 -1
  15. package/dist/features/game-presentation/adapters/inbound/Game.d.ts.map +1 -1
  16. package/dist/features/game-presentation/adapters/inbound/Game.js +8 -3
  17. package/dist/features/game-presentation/adapters/inbound/Game.js.map +1 -1
  18. package/dist/features/game-presentation/adapters/inbound/GameEntity.d.ts.map +1 -1
  19. package/dist/features/game-presentation/adapters/inbound/GameEntity.js +4 -11
  20. package/dist/features/game-presentation/adapters/inbound/GameEntity.js.map +1 -1
  21. package/dist/features/game-presentation/adapters/inbound/GameInputZone.d.ts +4 -0
  22. package/dist/features/game-presentation/adapters/inbound/GameInputZone.d.ts.map +1 -0
  23. package/dist/features/game-presentation/adapters/inbound/GameInputZone.js +35 -0
  24. package/dist/features/game-presentation/adapters/inbound/GameInputZone.js.map +1 -0
  25. package/dist/features/game-presentation/adapters/inbound/createGamePointerEvent.d.ts +17 -0
  26. package/dist/features/game-presentation/adapters/inbound/createGamePointerEvent.d.ts.map +1 -0
  27. package/dist/features/game-presentation/adapters/inbound/createGamePointerEvent.js +20 -0
  28. package/dist/features/game-presentation/adapters/inbound/createGamePointerEvent.js.map +1 -0
  29. package/dist/features/game-presentation/adapters/inbound/useGameInputZoneResponder.d.ts +9 -0
  30. package/dist/features/game-presentation/adapters/inbound/useGameInputZoneResponder.d.ts.map +1 -0
  31. package/dist/features/game-presentation/adapters/inbound/useGameInputZoneResponder.js +37 -0
  32. package/dist/features/game-presentation/adapters/inbound/useGameInputZoneResponder.js.map +1 -0
  33. package/dist/features/game-presentation/composition/createGameBindingContext.d.ts +4 -0
  34. package/dist/features/game-presentation/composition/createGameBindingContext.d.ts.map +1 -0
  35. package/dist/features/game-presentation/composition/createGameBindingContext.js +9 -0
  36. package/dist/features/game-presentation/composition/createGameBindingContext.js.map +1 -0
  37. package/dist/features/game-presentation/meta/gameEntityMeta.d.ts +58 -0
  38. package/dist/features/game-presentation/meta/gameEntityMeta.d.ts.map +1 -1
  39. package/dist/features/game-presentation/meta/gameEntityMeta.js +13 -0
  40. package/dist/features/game-presentation/meta/gameEntityMeta.js.map +1 -1
  41. package/dist/features/game-presentation/meta/gameInputZoneMeta.d.ts +112 -0
  42. package/dist/features/game-presentation/meta/gameInputZoneMeta.d.ts.map +1 -0
  43. package/dist/features/game-presentation/meta/gameInputZoneMeta.js +92 -0
  44. package/dist/features/game-presentation/meta/gameInputZoneMeta.js.map +1 -0
  45. package/dist/features/game-presentation/meta/gameMeta.d.ts +1 -1
  46. package/dist/features/game-presentation/meta/gameMeta.js +1 -1
  47. package/dist/features/game-presentation/meta/gameMeta.js.map +1 -1
  48. package/dist/features/game-presentation/public.d.ts +2 -1
  49. package/dist/features/game-presentation/public.d.ts.map +1 -1
  50. package/dist/features/game-presentation/public.js +1 -0
  51. package/dist/features/game-presentation/public.js.map +1 -1
  52. package/dist/features/game-presentation/utils/clampGamePercentage.d.ts +3 -0
  53. package/dist/features/game-presentation/utils/clampGamePercentage.d.ts.map +1 -0
  54. package/dist/features/game-presentation/utils/clampGamePercentage.js +5 -0
  55. package/dist/features/game-presentation/utils/clampGamePercentage.js.map +1 -0
  56. package/dist/features/game-presentation/utils/toGamePercentage.d.ts +3 -0
  57. package/dist/features/game-presentation/utils/toGamePercentage.d.ts.map +1 -0
  58. package/dist/features/game-presentation/utils/toGamePercentage.js +6 -0
  59. package/dist/features/game-presentation/utils/toGamePercentage.js.map +1 -0
  60. package/dist/index.d.ts +2 -2
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +1 -1
  63. package/dist/index.js.map +1 -1
  64. package/dist/types/gamePresentation.d.ts +13 -0
  65. package/dist/types/gamePresentation.d.ts.map +1 -1
  66. package/dist/types/gamePresentation.js.map +1 -1
  67. package/package.json +3 -2
  68. package/src/ZORA_GAME_COMPONENT_META.ts +2 -0
  69. package/src/ZORA_GAME_PLUGIN.ts +2 -0
  70. package/src/ZORA_PLUGIN_METADATA.test.ts +20 -0
  71. package/src/ZORA_PLUGIN_METADATA.ts +5 -1
  72. package/src/features/game-presentation/adapters/inbound/Game.tsx +19 -11
  73. package/src/features/game-presentation/adapters/inbound/GameEntity.tsx +4 -13
  74. package/src/features/game-presentation/adapters/inbound/GameInputZone.tsx +70 -0
  75. package/src/features/game-presentation/adapters/inbound/createGamePointerEvent.test.ts +56 -0
  76. package/src/features/game-presentation/adapters/inbound/createGamePointerEvent.ts +39 -0
  77. package/src/features/game-presentation/adapters/inbound/useGameInputZoneResponder.ts +49 -0
  78. package/src/features/game-presentation/composition/createGameBindingContext.test.ts +47 -0
  79. package/src/features/game-presentation/composition/createGameBindingContext.ts +10 -0
  80. package/src/features/game-presentation/meta/gameEntityMeta.ts +13 -0
  81. package/src/features/game-presentation/meta/gameInputZoneMeta.ts +94 -0
  82. package/src/features/game-presentation/meta/gameMeta.ts +1 -1
  83. package/src/features/game-presentation/public.ts +2 -0
  84. package/src/features/game-presentation/utils/clampGamePercentage.ts +4 -0
  85. package/src/features/game-presentation/utils/toGamePercentage.ts +6 -0
  86. package/src/index.ts +8 -1
  87. package/src/types/gamePresentation.ts +14 -0
@@ -2,6 +2,7 @@ import { View } from '@ankhorage/zora';
2
2
  import { StyleSheet, type ViewStyle } from 'react-native';
3
3
 
4
4
  import type { GameEntityProps } from '../../../../types/gamePresentation';
5
+ import { toGamePercentage } from '../../utils/toGamePercentage';
5
6
 
6
7
  /*** Render one generic positioned game entity without owning gameplay semantics. */
7
8
  export function GameEntity({
@@ -60,10 +61,10 @@ function createEntityStyle({
60
61
  }: GameEntityStyleInput): ViewStyle {
61
62
  return {
62
63
  display: hidden ? 'none' : 'flex',
63
- left: toPercent(x),
64
- opacity: clamp(opacity, 0, 1),
64
+ left: toGamePercentage(x),
65
+ opacity: Math.min(1, Math.max(0, opacity)),
65
66
  position: 'absolute',
66
- top: toPercent(y),
67
+ top: toGamePercentage(y),
67
68
  transform: [{ scale }, { rotate: `${rotation}deg` }],
68
69
  zIndex,
69
70
  ...(height === undefined ? {} : { height }),
@@ -71,16 +72,6 @@ function createEntityStyle({
71
72
  };
72
73
  }
73
74
 
74
- /*** Convert a bounded percentage number to React Native percentage syntax. */
75
- function toPercent(value: number): `${number}%` {
76
- return `${clamp(value, 0, 100)}%`;
77
- }
78
-
79
- /*** Clamp one numeric presentation value to an inclusive range. */
80
- function clamp(value: number, minimum: number, maximum: number): number {
81
- return Math.min(maximum, Math.max(minimum, value));
82
- }
83
-
84
75
  const styles = StyleSheet.create({
85
76
  root: {
86
77
  position: 'absolute',
@@ -0,0 +1,70 @@
1
+ import { StyleSheet, View, type ViewStyle } from 'react-native';
2
+
3
+ import type { GameInputZoneProps } from '../../../../types/gamePresentation';
4
+ import { toGamePercentage } from '../../utils/toGamePercentage';
5
+ import { useGameInputZoneResponder } from './useGameInputZoneResponder';
6
+
7
+ /*** Capture pointer/touch geometry and dispatch only normalized generic Game events. */
8
+ export function GameInputZone({
9
+ x = 0,
10
+ y = 0,
11
+ width = 100,
12
+ height = 100,
13
+ zIndex = 0,
14
+ enabled = true,
15
+ continuous = true,
16
+ accessibilityLabel,
17
+ testID,
18
+ ...inputProps
19
+ }: GameInputZoneProps) {
20
+ const { dispatchPointer, handleLayout, shouldSetResponder } = useGameInputZoneResponder({
21
+ ...inputProps,
22
+ x,
23
+ y,
24
+ width,
25
+ height,
26
+ enabled,
27
+ continuous,
28
+ });
29
+
30
+ return (
31
+ <View
32
+ {...(accessibilityLabel === undefined ? {} : { accessibilityLabel })}
33
+ {...(testID === undefined ? {} : { testID })}
34
+ {...(continuous
35
+ ? { onResponderGrant: dispatchPointer, onResponderMove: dispatchPointer }
36
+ : { onResponderRelease: dispatchPointer })}
37
+ collapsable={false}
38
+ onLayout={handleLayout}
39
+ onStartShouldSetResponder={shouldSetResponder}
40
+ pointerEvents={enabled ? 'auto' : 'none'}
41
+ style={createInputZoneStyle({ x, y, width, height, zIndex })}
42
+ />
43
+ );
44
+ }
45
+
46
+ interface InputZoneStyleInput {
47
+ readonly x: number;
48
+ readonly y: number;
49
+ readonly width: number;
50
+ readonly height: number;
51
+ readonly zIndex: number;
52
+ }
53
+
54
+ /*** Convert field-relative input-zone props into one absolute React Native style. */
55
+ function createInputZoneStyle({ x, y, width, height, zIndex }: InputZoneStyleInput): ViewStyle {
56
+ return {
57
+ left: toGamePercentage(x),
58
+ top: toGamePercentage(y),
59
+ width: toGamePercentage(width),
60
+ height: toGamePercentage(height),
61
+ zIndex,
62
+ ...styles.root,
63
+ };
64
+ }
65
+
66
+ const styles = StyleSheet.create({
67
+ root: {
68
+ position: 'absolute',
69
+ },
70
+ });
@@ -0,0 +1,56 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { createGamePointerEvent } from './createGamePointerEvent';
4
+
5
+ describe('createGamePointerEvent', () => {
6
+ test('maps zone-local pointer coordinates into field percentages', () => {
7
+ expect(
8
+ createGamePointerEvent({
9
+ eventType: 'player.move',
10
+ entityId: 'player',
11
+ localX: 100,
12
+ localY: 25,
13
+ measuredWidth: 200,
14
+ measuredHeight: 100,
15
+ zoneX: 10,
16
+ zoneY: 60,
17
+ zoneWidth: 80,
18
+ zoneHeight: 30,
19
+ }),
20
+ ).toEqual({
21
+ type: 'player.move',
22
+ entityId: 'player',
23
+ payload: { x: 50, y: 67.5 },
24
+ });
25
+ });
26
+
27
+ test('clamps out-of-bounds native coordinates and rejects unmeasured zones', () => {
28
+ expect(
29
+ createGamePointerEvent({
30
+ eventType: 'pointer.move',
31
+ localX: 500,
32
+ localY: -20,
33
+ measuredWidth: 100,
34
+ measuredHeight: 100,
35
+ zoneX: 20,
36
+ zoneY: 20,
37
+ zoneWidth: 50,
38
+ zoneHeight: 50,
39
+ }),
40
+ ).toEqual({ type: 'pointer.move', payload: { x: 70, y: 20 } });
41
+
42
+ expect(
43
+ createGamePointerEvent({
44
+ eventType: 'pointer.move',
45
+ localX: 0,
46
+ localY: 0,
47
+ measuredWidth: 0,
48
+ measuredHeight: 100,
49
+ zoneX: 0,
50
+ zoneY: 0,
51
+ zoneWidth: 100,
52
+ zoneHeight: 100,
53
+ }),
54
+ ).toBeUndefined();
55
+ });
56
+ });
@@ -0,0 +1,39 @@
1
+ import type { GameEvent } from '@ankhorage/game';
2
+
3
+ import { clampGamePercentage } from '../../utils/clampGamePercentage';
4
+
5
+ interface GamePointerEventInput {
6
+ readonly eventType: string;
7
+ readonly entityId?: string;
8
+ readonly localX: number;
9
+ readonly localY: number;
10
+ readonly measuredWidth: number;
11
+ readonly measuredHeight: number;
12
+ readonly zoneX: number;
13
+ readonly zoneY: number;
14
+ readonly zoneWidth: number;
15
+ readonly zoneHeight: number;
16
+ }
17
+
18
+ /*** Normalize one measured pointer/touch position into a field-relative generic Game event. */
19
+ export function createGamePointerEvent(input: GamePointerEventInput): GameEvent | undefined {
20
+ if (
21
+ !Number.isFinite(input.measuredWidth) ||
22
+ !Number.isFinite(input.measuredHeight) ||
23
+ input.measuredWidth <= 0 ||
24
+ input.measuredHeight <= 0
25
+ ) {
26
+ return undefined;
27
+ }
28
+
29
+ const localX = clampGamePercentage((input.localX / input.measuredWidth) * 100);
30
+ const localY = clampGamePercentage((input.localY / input.measuredHeight) * 100);
31
+ const x = clampGamePercentage(input.zoneX + (localX * input.zoneWidth) / 100);
32
+ const y = clampGamePercentage(input.zoneY + (localY * input.zoneHeight) / 100);
33
+
34
+ return {
35
+ type: input.eventType,
36
+ ...(input.entityId === undefined ? {} : { entityId: input.entityId }),
37
+ payload: { x, y },
38
+ };
39
+ }
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import type { GestureResponderEvent, LayoutChangeEvent } from 'react-native';
3
+
4
+ import type { GameInputZoneProps } from '../../../../types/gamePresentation';
5
+ import { GameRuntimeContext } from '../../composition/GameRuntimeContext';
6
+ import { createGamePointerEvent } from './createGamePointerEvent';
7
+
8
+ interface MeasuredSize {
9
+ readonly width: number;
10
+ readonly height: number;
11
+ }
12
+
13
+ /*** Own React Native responder measurement and event dispatch for one Game input zone. */
14
+ export function useGameInputZoneResponder(props: GameInputZoneProps) {
15
+ const runtime = React.useContext(GameRuntimeContext);
16
+ if (runtime === null) throw new Error('GameInputZone must be rendered inside Game.');
17
+
18
+ const measuredSizeRef = React.useRef<MeasuredSize>({ width: 0, height: 0 });
19
+ const handleLayout = React.useCallback((event: LayoutChangeEvent) => {
20
+ const { width, height } = event.nativeEvent.layout;
21
+ measuredSizeRef.current = { width, height };
22
+ }, []);
23
+
24
+ const dispatchPointer = React.useCallback(
25
+ (event: GestureResponderEvent) => {
26
+ const measured = measuredSizeRef.current;
27
+ const gameEvent = createGamePointerEvent({
28
+ eventType: props.eventType,
29
+ ...(props.entityId === undefined ? {} : { entityId: props.entityId }),
30
+ localX: event.nativeEvent.locationX,
31
+ localY: event.nativeEvent.locationY,
32
+ measuredWidth: measured.width,
33
+ measuredHeight: measured.height,
34
+ zoneX: props.x ?? 0,
35
+ zoneY: props.y ?? 0,
36
+ zoneWidth: props.width ?? 100,
37
+ zoneHeight: props.height ?? 100,
38
+ });
39
+ if (gameEvent !== undefined) runtime.dispatch(gameEvent);
40
+ },
41
+ [props.entityId, props.eventType, props.height, props.width, props.x, props.y, runtime],
42
+ );
43
+
44
+ return {
45
+ dispatchPointer,
46
+ handleLayout,
47
+ shouldSetResponder: () => props.enabled ?? true,
48
+ };
49
+ }
@@ -0,0 +1,47 @@
1
+ import type { GameSession } from '@ankhorage/game';
2
+ import { resolveRuntimeBindingValueSync } from '@ankhorage/runtime/bindings';
3
+ import { describe, expect, test } from 'bun:test';
4
+
5
+ import { createGameBindingContext } from './createGameBindingContext';
6
+
7
+ const session: GameSession = {
8
+ definitionId: 'binding-demo',
9
+ stageId: 'main',
10
+ phase: 'playing',
11
+ state: { health: 80, progress: 0.5 },
12
+ entities: {
13
+ player: {
14
+ id: 'player',
15
+ templateId: 'actor',
16
+ state: { x: 42, y: 73, label: 'Player' },
17
+ },
18
+ },
19
+ scheduled: [],
20
+ elapsedMs: 120,
21
+ sequence: 4,
22
+ };
23
+
24
+ describe('createGameBindingContext', () => {
25
+ test('resolves ordinary Runtime context bindings from the local Game session', () => {
26
+ const context = createGameBindingContext(session);
27
+
28
+ expect(
29
+ resolveRuntimeBindingValueSync(
30
+ { source: { kind: 'context', path: 'game.session.entities.player.state.x' } },
31
+ { context },
32
+ ),
33
+ ).toBe(42);
34
+ expect(
35
+ resolveRuntimeBindingValueSync(
36
+ { source: { kind: 'context', path: 'game.session.state.health' } },
37
+ { context },
38
+ ),
39
+ ).toBe(80);
40
+ });
41
+
42
+ test('owns only the local game namespace', () => {
43
+ expect(createGameBindingContext(session)).toEqual({
44
+ game: { session },
45
+ });
46
+ });
47
+ });
@@ -0,0 +1,10 @@
1
+ import type { GameSession } from '@ankhorage/game';
2
+
3
+ /*** Create the canonical Runtime context namespace for one local game session. */
4
+ export function createGameBindingContext(session: GameSession): Record<string, unknown> {
5
+ return {
6
+ game: {
7
+ session,
8
+ },
9
+ };
10
+ }
@@ -11,6 +11,19 @@ export const gameEntityMeta = {
11
11
  label: 'Game entity',
12
12
  defaultProps: { x: 0, y: 0, opacity: 1, scale: 1, rotation: 0, zIndex: 0 },
13
13
  },
14
+ bindings: {
15
+ props: {
16
+ x: { value: { type: 'number' }, acceptsFallback: true },
17
+ y: { value: { type: 'number' }, acceptsFallback: true },
18
+ width: { value: { type: 'number' }, acceptsFallback: true },
19
+ height: { value: { type: 'number' }, acceptsFallback: true },
20
+ opacity: { value: { type: 'number' }, acceptsFallback: true },
21
+ scale: { value: { type: 'number' }, acceptsFallback: true },
22
+ rotation: { value: { type: 'number' }, acceptsFallback: true },
23
+ zIndex: { value: { type: 'number' }, acceptsFallback: true },
24
+ hidden: { value: { type: 'boolean' }, acceptsFallback: true },
25
+ },
26
+ },
14
27
  props: {
15
28
  x: {
16
29
  type: 'number',
@@ -0,0 +1,94 @@
1
+ import type { ZoraComponentMeta } from '@ankhorage/zora/metadata';
2
+
3
+ /*** Describe a field-relative pointer/touch input adapter for one embeddable Game session. */
4
+ export const gameInputZoneMeta = {
5
+ name: 'GameInputZone',
6
+ category: 'component',
7
+ description:
8
+ 'Normalizes pointer and touch positions into field-relative percentages and dispatches a generic game event.',
9
+ directManifestNode: true,
10
+ allowedChildren: [],
11
+ blueprint: {
12
+ label: 'Game input zone',
13
+ defaultProps: {
14
+ eventType: 'game.pointer',
15
+ x: 0,
16
+ y: 0,
17
+ width: 100,
18
+ height: 100,
19
+ zIndex: 1,
20
+ enabled: true,
21
+ continuous: true,
22
+ },
23
+ },
24
+ props: {
25
+ eventType: {
26
+ type: 'string',
27
+ category: 'Input',
28
+ label: 'Game event type',
29
+ default: 'game.pointer',
30
+ authoring: { authority: 'instance' },
31
+ },
32
+ entityId: {
33
+ type: 'string',
34
+ category: 'Input',
35
+ label: 'Entity id',
36
+ authoring: { authority: 'instance' },
37
+ },
38
+ x: {
39
+ type: 'number',
40
+ category: 'Position',
41
+ label: 'X (%)',
42
+ default: 0,
43
+ authoring: { authority: 'instance' },
44
+ },
45
+ y: {
46
+ type: 'number',
47
+ category: 'Position',
48
+ label: 'Y (%)',
49
+ default: 0,
50
+ authoring: { authority: 'instance' },
51
+ },
52
+ width: {
53
+ type: 'number',
54
+ category: 'Size',
55
+ label: 'Width (%)',
56
+ default: 100,
57
+ authoring: { authority: 'instance' },
58
+ },
59
+ height: {
60
+ type: 'number',
61
+ category: 'Size',
62
+ label: 'Height (%)',
63
+ default: 100,
64
+ authoring: { authority: 'instance' },
65
+ },
66
+ zIndex: {
67
+ type: 'number',
68
+ category: 'Position',
69
+ label: 'Z index',
70
+ default: 1,
71
+ authoring: { authority: 'instance' },
72
+ },
73
+ enabled: {
74
+ type: 'boolean',
75
+ category: 'State',
76
+ label: 'Enabled',
77
+ default: true,
78
+ authoring: { authority: 'instance' },
79
+ },
80
+ continuous: {
81
+ type: 'boolean',
82
+ category: 'Input',
83
+ label: 'Dispatch while moving',
84
+ default: true,
85
+ authoring: { authority: 'instance' },
86
+ },
87
+ accessibilityLabel: {
88
+ type: 'string',
89
+ category: 'Accessibility',
90
+ label: 'Accessibility label',
91
+ authoring: { authority: 'instance' },
92
+ },
93
+ },
94
+ } as const satisfies ZoraComponentMeta;
@@ -7,7 +7,7 @@ export const gameMeta = {
7
7
  description:
8
8
  'Embeds one config-driven game session inside ordinary ZORA layout and emits domain-neutral game outputs.',
9
9
  directManifestNode: true,
10
- allowedChildren: ['GameEntity', 'GameOverlay', 'Gradient', 'Image', 'View'],
10
+ allowedChildren: ['GameEntity', 'GameInputZone', 'GameOverlay', 'Gradient', 'Image', 'View'],
11
11
  blueprint: {
12
12
  label: 'Game',
13
13
  defaultProps: {
@@ -1,6 +1,7 @@
1
1
  export type {
2
2
  GameEntityProps,
3
3
  GameFieldProps,
4
+ GameInputZoneProps,
4
5
  GameOverlayPlacement,
5
6
  GameOverlayProps,
6
7
  GamePointerEvents,
@@ -9,4 +10,5 @@ export type {
9
10
  export { Game } from './adapters/inbound/Game';
10
11
  export { GameEntity } from './adapters/inbound/GameEntity';
11
12
  export { GameField } from './adapters/inbound/GameField';
13
+ export { GameInputZone } from './adapters/inbound/GameInputZone';
12
14
  export { GameOverlay } from './adapters/inbound/GameOverlay';
@@ -0,0 +1,4 @@
1
+ /*** Clamp one game presentation coordinate to the inclusive field-percentage range. */
2
+ export function clampGamePercentage(value: number): number {
3
+ return Math.min(100, Math.max(0, value));
4
+ }
@@ -0,0 +1,6 @@
1
+ import { clampGamePercentage } from './clampGamePercentage';
2
+
3
+ /*** Convert one game percentage coordinate to React Native percentage style syntax. */
4
+ export function toGamePercentage(value: number): `${number}%` {
5
+ return `${clampGamePercentage(value)}%`;
6
+ }
package/src/index.ts CHANGED
@@ -1,10 +1,17 @@
1
1
  export type {
2
2
  GameEntityProps,
3
3
  GameFieldProps,
4
+ GameInputZoneProps,
4
5
  GameOverlayPlacement,
5
6
  GameOverlayProps,
6
7
  GamePointerEvents,
7
8
  GameProps,
8
9
  } from './features/game-presentation/public';
9
- export { Game, GameEntity, GameField, GameOverlay } from './features/game-presentation/public';
10
+ export {
11
+ Game,
12
+ GameEntity,
13
+ GameField,
14
+ GameInputZone,
15
+ GameOverlay,
16
+ } from './features/game-presentation/public';
10
17
  export { ZORA_GAME_PLUGIN } from './ZORA_GAME_PLUGIN';
@@ -32,6 +32,20 @@ export interface GameEntityProps {
32
32
  readonly testID?: string;
33
33
  }
34
34
 
35
+ export interface GameInputZoneProps {
36
+ readonly eventType: string;
37
+ readonly entityId?: string;
38
+ readonly x?: number;
39
+ readonly y?: number;
40
+ readonly width?: number;
41
+ readonly height?: number;
42
+ readonly zIndex?: number;
43
+ readonly enabled?: boolean;
44
+ readonly continuous?: boolean;
45
+ readonly accessibilityLabel?: string;
46
+ readonly testID?: string;
47
+ }
48
+
35
49
  export interface GameOverlayProps {
36
50
  readonly children?: React.ReactNode;
37
51
  readonly placement?: GameOverlayPlacement;