@jgengine/react 0.8.0 → 0.10.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 (45) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/chat.d.ts +3 -3
  3. package/dist/chat.js +14 -5
  4. package/dist/chatBubbles.d.ts +15 -0
  5. package/dist/chatBubbles.js +46 -0
  6. package/dist/components.d.ts +1 -0
  7. package/dist/components.js +36 -23
  8. package/dist/display.d.ts +2 -1
  9. package/dist/display.js +3 -2
  10. package/dist/dragLayer.d.ts +6 -1
  11. package/dist/dragLayer.js +64 -31
  12. package/dist/engineStore.d.ts +1 -1
  13. package/dist/engineStore.js +10 -2
  14. package/dist/fogOverlay.d.ts +21 -0
  15. package/dist/fogOverlay.js +34 -0
  16. package/dist/gameViewport.d.ts +54 -0
  17. package/dist/gameViewport.js +340 -0
  18. package/dist/hooks.d.ts +11 -1
  19. package/dist/hooks.js +89 -21
  20. package/dist/hudLayout.d.ts +79 -0
  21. package/dist/hudLayout.js +518 -0
  22. package/dist/hudViewport.d.ts +21 -0
  23. package/dist/hudViewport.js +17 -0
  24. package/dist/index.d.ts +7 -0
  25. package/dist/index.js +7 -0
  26. package/dist/liveBind.d.ts +4 -10
  27. package/dist/liveBind.js +28 -16
  28. package/dist/map.d.ts +27 -7
  29. package/dist/map.js +152 -43
  30. package/dist/preview.d.ts +6 -0
  31. package/dist/preview.js +1 -0
  32. package/dist/provider.d.ts +2 -0
  33. package/dist/provider.js +4 -0
  34. package/dist/rotateDevice.d.ts +24 -0
  35. package/dist/rotateDevice.js +83 -0
  36. package/dist/selectSnapshot.d.ts +7 -0
  37. package/dist/selectSnapshot.js +16 -0
  38. package/dist/settings.d.ts +78 -0
  39. package/dist/settings.js +61 -0
  40. package/dist/skillCheckPaint.d.ts +4 -0
  41. package/dist/skillCheckPaint.js +28 -0
  42. package/dist/social.d.ts +3 -3
  43. package/dist/social.js +31 -10
  44. package/llms.txt +858 -860
  45. package/package.json +9 -5
@@ -0,0 +1,78 @@
1
+ import { type ReactNode } from "react";
2
+ import { type SettingCategory, type SettingKind, type SettingOption, type SettingsStore, type SettingsSurface, type SettingsVariant, type SettingValue } from "@jgengine/core/settings/settingsModel";
3
+ export declare function SettingsProvider({ store, children }: {
4
+ store: SettingsStore;
5
+ children: ReactNode;
6
+ }): import("react").JSX.Element;
7
+ /** The shared settings store, or a standalone one when no provider is mounted (game code read outside the shell). */
8
+ export declare function useSettingsStore(): SettingsStore;
9
+ /** Read + write one persisted setting; re-renders when the value changes anywhere. */
10
+ export declare function useSetting<T extends SettingValue>(id: string, fallback: T): readonly [T, (value: SettingValue) => void];
11
+ /** One editable setting rendered in a settings menu category. */
12
+ export interface SettingsRow {
13
+ id: string;
14
+ label: string;
15
+ kind: SettingKind;
16
+ value: SettingValue;
17
+ min?: number;
18
+ max?: number;
19
+ step?: number;
20
+ options?: readonly SettingOption[];
21
+ format?: (value: number) => string;
22
+ set: (value: SettingValue) => void;
23
+ }
24
+ /** One rebindable action row rendered in the controls settings category. */
25
+ export interface SettingsKeybindRow {
26
+ action: string;
27
+ label: string;
28
+ bindingLabel: string;
29
+ isDefault: boolean;
30
+ rebind: (code: string) => void;
31
+ reset: () => void;
32
+ }
33
+ /** A settings menu category with its rows and keybinds, ready to render. */
34
+ export interface SettingsCategoryView {
35
+ id: SettingCategory;
36
+ label: string;
37
+ rows: SettingsRow[];
38
+ keybinds: SettingsKeybindRow[];
39
+ }
40
+ /** A resolved game-state action — `run` is already bound to the game context and closes the menu. */
41
+ export interface SettingsActionView {
42
+ id: string;
43
+ label: string;
44
+ kind: "default" | "danger";
45
+ description?: string;
46
+ run: () => void;
47
+ }
48
+ /** The live settings controller — every category/row/keybind/action plus open-state. Render it any way you like or drive the engine menu. */
49
+ export interface SettingsController {
50
+ categories: SettingsCategoryView[];
51
+ /** Game-state actions (Restart, Quit…) — the menu shows them as its first "Game" tab. */
52
+ actions: SettingsActionView[];
53
+ variant: SettingsVariant;
54
+ surface: SettingsSurface | false;
55
+ isOpen: boolean;
56
+ open: () => void;
57
+ close: () => void;
58
+ setOpen: (open: boolean) => void;
59
+ }
60
+ export declare function SettingsControllerProvider({ controller, children, }: {
61
+ controller: SettingsController;
62
+ children: ReactNode;
63
+ }): import("react").JSX.Element;
64
+ /** The engine settings controller for the current game — render your own settings UI from `categories`, or open the built-in menu with `open()`. Null-safe stub when mounted outside the shell. */
65
+ export declare function useSettings(): SettingsController;
66
+ /** True when the game has any setting or game-action to show — gate your own settings entry on it. */
67
+ export declare function useHasSettings(): boolean;
68
+ /**
69
+ * Inline settings entry — drop it anywhere in your game's menu or HUD; it opens
70
+ * the themed settings menu. Headless: pass `className` for placement/skin and
71
+ * `children` to replace the default gear glyph. Renders nothing when the game
72
+ * has no settings to show, so it never leaves a dead button behind.
73
+ */
74
+ export declare function SettingsTrigger({ className, children, label, }: {
75
+ className?: string;
76
+ children?: ReactNode;
77
+ label?: string;
78
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createContext, useCallback, useContext, useSyncExternalStore } from "react";
3
+ import { createSettingsStore, } from "@jgengine/core/settings/settingsModel";
4
+ const SettingsStoreContext = createContext(null);
5
+ export function SettingsProvider({ store, children }) {
6
+ return _jsx(SettingsStoreContext.Provider, { value: store, children: children });
7
+ }
8
+ /** The shared settings store, or a standalone one when no provider is mounted (game code read outside the shell). */
9
+ export function useSettingsStore() {
10
+ const store = useContext(SettingsStoreContext);
11
+ return store ?? fallbackStore();
12
+ }
13
+ let standalone = null;
14
+ function fallbackStore() {
15
+ if (standalone === null)
16
+ standalone = createSettingsStore();
17
+ return standalone;
18
+ }
19
+ /** Read + write one persisted setting; re-renders when the value changes anywhere. */
20
+ export function useSetting(id, fallback) {
21
+ const store = useSettingsStore();
22
+ const value = useSyncExternalStore(store.subscribe, () => store.get(id, fallback), () => fallback);
23
+ const set = useCallback((next) => store.set(id, next), [store, id]);
24
+ return [value, set];
25
+ }
26
+ const SettingsControllerContext = createContext(null);
27
+ export function SettingsControllerProvider({ controller, children, }) {
28
+ return _jsx(SettingsControllerContext.Provider, { value: controller, children: children });
29
+ }
30
+ /** The engine settings controller for the current game — render your own settings UI from `categories`, or open the built-in menu with `open()`. Null-safe stub when mounted outside the shell. */
31
+ export function useSettings() {
32
+ return useContext(SettingsControllerContext) ?? EMPTY_CONTROLLER;
33
+ }
34
+ const noop = () => undefined;
35
+ const EMPTY_CONTROLLER = {
36
+ categories: [],
37
+ actions: [],
38
+ variant: "panel",
39
+ surface: false,
40
+ isOpen: false,
41
+ open: noop,
42
+ close: noop,
43
+ setOpen: noop,
44
+ };
45
+ /** True when the game has any setting or game-action to show — gate your own settings entry on it. */
46
+ export function useHasSettings() {
47
+ const s = useSettings();
48
+ return s.categories.length > 0 || s.actions.length > 0;
49
+ }
50
+ /**
51
+ * Inline settings entry — drop it anywhere in your game's menu or HUD; it opens
52
+ * the themed settings menu. Headless: pass `className` for placement/skin and
53
+ * `children` to replace the default gear glyph. Renders nothing when the game
54
+ * has no settings to show, so it never leaves a dead button behind.
55
+ */
56
+ export function SettingsTrigger({ className, children, label = "Settings", }) {
57
+ const settings = useSettings();
58
+ if (settings.categories.length === 0 && settings.actions.length === 0)
59
+ return null;
60
+ return (_jsx("button", { type: "button", "aria-label": label, onClick: settings.open, className: className, children: children ?? (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round", width: "1em", height: "1em", "aria-hidden": true, children: [_jsx("circle", { cx: "12", cy: "12", r: "3" }), _jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" })] })) }));
61
+ }
@@ -0,0 +1,4 @@
1
+ import type { SkillCheckConfig, SkillCheckResult } from "@jgengine/core/interaction/skillCheck";
2
+ import type { QteStep } from "@jgengine/core/interaction/qte";
3
+ export declare function paintSkillCheckDom(root: HTMLElement, zone: HTMLElement, marker: HTMLElement, config: SkillCheckConfig, result: SkillCheckResult): void;
4
+ export declare function paintQteStepDom(elements: ReadonlyMap<string, HTMLElement>, steps: readonly QteStep[], elapsed: number, activeId: string | null, stepClassName?: string, activeClassName?: string, doneClassName?: string): void;
@@ -0,0 +1,28 @@
1
+ export function paintSkillCheckDom(root, zone, marker, config, result) {
2
+ const zoneLeft = (result.zone.start / config.trackWidth) * 100;
3
+ const zoneWidth = ((result.zone.end - result.zone.start) / config.trackWidth) * 100;
4
+ const markerLeft = (result.markerPosition / config.trackWidth) * 100;
5
+ zone.style.left = `${zoneLeft}%`;
6
+ zone.style.width = `${zoneWidth}%`;
7
+ marker.style.left = `${markerLeft}%`;
8
+ root.dataset.inZone = result.success ? "true" : "false";
9
+ root.dataset.timedOut = result.timedOut ? "true" : "false";
10
+ }
11
+ export function paintQteStepDom(elements, steps, elapsed, activeId, stepClassName, activeClassName, doneClassName) {
12
+ for (const step of steps) {
13
+ const el = elements.get(step.id);
14
+ if (el === undefined)
15
+ continue;
16
+ const isActive = activeId === step.id;
17
+ const isDone = elapsed > step.windowEnd;
18
+ const classes = [stepClassName, isActive ? activeClassName : isDone ? doneClassName : undefined]
19
+ .filter(Boolean)
20
+ .join(" ");
21
+ if (classes.length > 0)
22
+ el.className = classes;
23
+ else
24
+ el.removeAttribute("class");
25
+ el.dataset.active = isActive ? "true" : "false";
26
+ el.dataset.done = isDone ? "true" : "false";
27
+ }
28
+ }
package/dist/social.d.ts CHANGED
@@ -24,7 +24,7 @@ export declare function AddFriendButton({ toUserId, className, children, onReque
24
24
  children?: ReactNode;
25
25
  onRequested?: (requestId: string) => void;
26
26
  onRejected?: (reason: string) => void;
27
- }): import("react").JSX.Element;
27
+ }): import("react").JSX.Element | null;
28
28
  export declare function FriendRequestsList({ className, rowClassName, acceptClassName, declineClassName, emptyState, renderRequest, }: {
29
29
  className?: string;
30
30
  rowClassName?: string;
@@ -32,7 +32,7 @@ export declare function FriendRequestsList({ className, rowClassName, acceptClas
32
32
  declineClassName?: string;
33
33
  emptyState?: ReactNode;
34
34
  renderRequest?: (request: FriendRequestEntry) => ReactNode;
35
- }): import("react").JSX.Element;
35
+ }): import("react").JSX.Element | null;
36
36
  export declare function PartyMemberRow({ member, className, dotClassName, children, }: {
37
37
  member: PartyMemberEntry;
38
38
  className?: string;
@@ -70,7 +70,7 @@ export declare function InviteToWorldButton({ toUserId, target, className, child
70
70
  children?: ReactNode;
71
71
  onInvited?: (inviteId: string) => void;
72
72
  onRejected?: (reason: string) => void;
73
- }): import("react").JSX.Element;
73
+ }): import("react").JSX.Element | null;
74
74
  export declare function WorldBrowser({ listings, onJoin, className, rowClassName, joinClassName, emptyState, renderListing, }: {
75
75
  listings: readonly SessionListing[];
76
76
  onJoin: (listing: SessionListing) => void;
package/dist/social.js CHANGED
@@ -18,9 +18,12 @@ export function FriendsList({ className, rowClassName, dotClassName, emptyState,
18
18
  }
19
19
  export function AddFriendButton({ toUserId, className, children, onRequested, onRejected, }) {
20
20
  const ctx = useGameContext();
21
- const denied = ctx.game.social.friends.canRequest(ctx.player.userId, toUserId);
21
+ const social = ctx.game.social;
22
+ if (social === undefined)
23
+ return null;
24
+ const denied = social.friends.canRequest(ctx.player.userId, toUserId);
22
25
  return (_jsx("button", { type: "button", className: className, "data-add-friend": toUserId, disabled: denied !== null, title: denied?.reason, onClick: () => {
23
- const result = ctx.game.social.friends.request(ctx.player.userId, toUserId);
26
+ const result = social.friends.request(ctx.player.userId, toUserId);
24
27
  if ("reason" in result)
25
28
  onRejected?.(result.reason);
26
29
  else
@@ -30,9 +33,12 @@ export function AddFriendButton({ toUserId, className, children, onRequested, on
30
33
  export function FriendRequestsList({ className, rowClassName, acceptClassName, declineClassName, emptyState, renderRequest, }) {
31
34
  const ctx = useGameContext();
32
35
  const requests = useFriendRequests();
36
+ const social = ctx.game.social;
37
+ if (social === undefined)
38
+ return null;
33
39
  return (_jsx("div", { className: className, "data-friend-requests": true, children: requests.length === 0
34
40
  ? emptyState ?? null
35
- : requests.map((request) => renderRequest !== undefined ? (renderRequest(request)) : (_jsxs("div", { className: rowClassName, "data-friend-request": request.requestId, children: [_jsx("span", { "data-request-from": true, children: request.fromUserId }), _jsx("button", { type: "button", className: acceptClassName, "data-accept": true, onClick: () => ctx.game.social.friends.accept(ctx.player.userId, request.requestId), children: "Accept" }), _jsx("button", { type: "button", className: declineClassName, "data-decline": true, onClick: () => ctx.game.social.friends.decline(ctx.player.userId, request.requestId), children: "Decline" })] }, request.requestId))) }));
41
+ : requests.map((request) => renderRequest !== undefined ? (renderRequest(request)) : (_jsxs("div", { className: rowClassName, "data-friend-request": request.requestId, children: [_jsx("span", { "data-request-from": true, children: request.fromUserId }), _jsx("button", { type: "button", className: acceptClassName, "data-accept": true, onClick: () => social.friends.accept(ctx.player.userId, request.requestId), children: "Accept" }), _jsx("button", { type: "button", className: declineClassName, "data-decline": true, onClick: () => social.friends.decline(ctx.player.userId, request.requestId), children: "Decline" })] }, request.requestId))) }));
36
42
  }
37
43
  export function PartyMemberRow({ member, className, dotClassName, children, }) {
38
44
  return (_jsxs("div", { className: className, "data-party-member": member.userId, "data-role": member.role, children: [_jsx(PresenceDot, { userId: member.userId, className: dotClassName }), _jsx("span", { "data-member-name": true, children: member.userId }), children] }));
@@ -46,33 +52,45 @@ export function PartyFrame({ className, rowClassName, dotClassName, emptyState,
46
52
  export function PartyInviteToast({ className, acceptClassName, declineClassName, renderInvite, }) {
47
53
  const ctx = useGameContext();
48
54
  const invites = usePartyInvites();
55
+ const social = ctx.game.social;
56
+ if (social === undefined)
57
+ return null;
49
58
  if (invites.length === 0)
50
59
  return null;
51
- return (_jsx("div", { className: className, "data-party-invites": true, children: invites.map((invite) => renderInvite !== undefined ? (renderInvite(invite)) : (_jsxs("div", { "data-party-invite": invite.inviteId, children: [_jsx("span", { "data-invite-from": true, children: invite.fromUserId }), _jsx("button", { type: "button", className: acceptClassName, "data-accept": true, onClick: () => ctx.game.social.party.accept(ctx.player.userId, invite.inviteId), children: "Join" }), _jsx("button", { type: "button", className: declineClassName, "data-decline": true, onClick: () => ctx.game.social.party.decline(ctx.player.userId, invite.inviteId), children: "Decline" })] }, invite.inviteId))) }));
60
+ return (_jsx("div", { className: className, "data-party-invites": true, children: invites.map((invite) => renderInvite !== undefined ? (renderInvite(invite)) : (_jsxs("div", { "data-party-invite": invite.inviteId, children: [_jsx("span", { "data-invite-from": true, children: invite.fromUserId }), _jsx("button", { type: "button", className: acceptClassName, "data-accept": true, onClick: () => social.party.accept(ctx.player.userId, invite.inviteId), children: "Join" }), _jsx("button", { type: "button", className: declineClassName, "data-decline": true, onClick: () => social.party.decline(ctx.player.userId, invite.inviteId), children: "Decline" })] }, invite.inviteId))) }));
52
61
  }
53
62
  export function LeavePartyButton({ className, children, }) {
54
63
  const ctx = useGameContext();
55
64
  const members = useParty();
65
+ const social = ctx.game.social;
66
+ if (social === undefined)
67
+ return null;
56
68
  if (members.length === 0)
57
69
  return null;
58
- return (_jsx("button", { type: "button", className: className, "data-leave-party": true, onClick: () => ctx.game.social.party.leave(ctx.player.userId), children: children ?? "Leave party" }));
70
+ return (_jsx("button", { type: "button", className: className, "data-leave-party": true, onClick: () => social.party.leave(ctx.player.userId), children: children ?? "Leave party" }));
59
71
  }
60
72
  export function WorldInviteToast({ className, acceptClassName, declineClassName, onAccepted, renderInvite, }) {
61
73
  const ctx = useGameContext();
62
74
  const invites = useWorldInvites();
75
+ const social = ctx.game.social;
76
+ if (social === undefined)
77
+ return null;
63
78
  if (invites.length === 0)
64
79
  return null;
65
80
  return (_jsx("div", { className: className, "data-world-invites": true, children: invites.map((invite) => renderInvite !== undefined ? (renderInvite(invite)) : (_jsxs("div", { "data-world-invite": invite.id, children: [_jsx("span", { "data-invite-from": true, children: invite.fromUserId }), _jsx("span", { "data-invite-server": true, children: invite.serverId }), _jsx("button", { type: "button", className: acceptClassName, "data-accept": true, onClick: () => {
66
- const result = ctx.game.social.worldInvites.accept(ctx.player.userId, invite.id);
81
+ const result = social.worldInvites.accept(ctx.player.userId, invite.id);
67
82
  if ("target" in result)
68
83
  onAccepted(result.target);
69
- }, children: "Join" }), _jsx("button", { type: "button", className: declineClassName, "data-decline": true, onClick: () => ctx.game.social.worldInvites.decline(ctx.player.userId, invite.id), children: "Decline" })] }, invite.id))) }));
84
+ }, children: "Join" }), _jsx("button", { type: "button", className: declineClassName, "data-decline": true, onClick: () => social.worldInvites.decline(ctx.player.userId, invite.id), children: "Decline" })] }, invite.id))) }));
70
85
  }
71
86
  export function InviteToWorldButton({ toUserId, target, className, children, onInvited, onRejected, }) {
72
87
  const ctx = useGameContext();
73
- const denied = ctx.game.social.worldInvites.canInvite(ctx.player.userId, toUserId);
88
+ const social = ctx.game.social;
89
+ if (social === undefined)
90
+ return null;
91
+ const denied = social.worldInvites.canInvite(ctx.player.userId, toUserId);
74
92
  return (_jsx("button", { type: "button", className: className, "data-invite-to-world": toUserId, disabled: denied !== null, title: denied?.reason, onClick: () => {
75
- const result = ctx.game.social.worldInvites.invite(ctx.player.userId, toUserId, target);
93
+ const result = social.worldInvites.invite(ctx.player.userId, toUserId, target);
76
94
  if ("reason" in result)
77
95
  onRejected?.(result.reason);
78
96
  else
@@ -107,10 +125,13 @@ export function QuickMatchButton({ listings, onJoin, onNoMatch, filter, classNam
107
125
  }
108
126
  export function EmoteWheel({ emotes, radius, open = true, className, emoteClassName, onPlayed, onRejected, renderEmote, }) {
109
127
  const ctx = useGameContext();
128
+ const social = ctx.game.social;
129
+ if (social === undefined)
130
+ return null;
110
131
  if (!open)
111
132
  return null;
112
133
  return (_jsx("div", { className: className, role: "menu", "data-emote-wheel": true, "data-emote-count": emotes.length, children: emotes.map((emoteId, index) => (_jsx("button", { type: "button", role: "menuitem", className: emoteClassName, "data-emote": emoteId, "data-emote-index": index, onClick: () => {
113
- const result = ctx.game.social.emotes.play(ctx.player.userId, emoteId, radius);
134
+ const result = social.emotes.play(ctx.player.userId, emoteId, radius);
114
135
  if ("reason" in result)
115
136
  onRejected?.(result.reason);
116
137
  else