@jgengine/react 0.9.0 → 0.11.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/CHANGELOG.md +34 -3
- package/LICENSE +203 -661
- package/README.md +1 -1
- package/dist/actionHud.d.ts +174 -0
- package/dist/actionHud.js +292 -0
- package/dist/cards.d.ts +59 -0
- package/dist/cards.js +116 -0
- package/dist/chat.d.ts +6 -4
- package/dist/chat.js +16 -7
- package/dist/chatBubbles.js +2 -1
- package/dist/components.d.ts +20 -0
- package/dist/components.js +29 -1
- package/dist/display.d.ts +1 -0
- package/dist/display.js +1 -0
- package/dist/engineStore.d.ts +3 -0
- package/dist/engineStore.js +3 -0
- package/dist/fogOverlay.d.ts +3 -0
- package/dist/fogOverlay.js +3 -0
- package/dist/gameViewport.d.ts +54 -0
- package/dist/gameViewport.js +356 -0
- package/dist/hooks.d.ts +22 -0
- package/dist/hooks.js +58 -12
- package/dist/hud.d.ts +87 -0
- package/dist/hud.js +144 -0
- package/dist/hudLayout.d.ts +39 -4
- package/dist/hudLayout.js +152 -11
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/keyHint.d.ts +30 -0
- package/dist/keyHint.js +31 -0
- package/dist/map.d.ts +69 -1
- package/dist/map.js +54 -1
- package/dist/provider.d.ts +2 -0
- package/dist/provider.js +4 -0
- package/dist/rotateDevice.d.ts +24 -0
- package/dist/rotateDevice.js +83 -0
- package/dist/save.d.ts +25 -0
- package/dist/save.js +37 -0
- package/dist/selectSnapshot.d.ts +2 -0
- package/dist/selectSnapshot.js +2 -0
- package/dist/selectionHud.d.ts +133 -0
- package/dist/selectionHud.js +182 -0
- package/dist/settings.d.ts +3 -0
- package/dist/skillCheckPaint.d.ts +2 -0
- package/dist/skillCheckPaint.js +2 -0
- package/dist/social.d.ts +3 -3
- package/dist/social.js +31 -10
- package/dist/startScreen.d.ts +62 -0
- package/dist/startScreen.js +62 -0
- package/dist/store.d.ts +24 -0
- package/dist/store.js +9 -0
- package/package.json +4 -4
- package/skills/jgengine/SKILL.md +51 -0
- package/skills/jgengine/api.md +434 -0
- package/skills/jgengine/capabilities.md +29 -0
- package/skills/jgengine/reference-cartridge.md +72 -0
- package/skills/jgengine-ui/SKILL.md +36 -0
- package/skills/jgengine-ui/api.md +1852 -0
- package/skills/jgengine-ui/capabilities.md +142 -0
- package/skills/jgengine-ui/reference.md +500 -0
- package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
- package/llms.txt +0 -1633
package/dist/chat.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare function ChatInput({ channelId, className, inputClassName, button
|
|
|
23
23
|
sendLabel?: ReactNode;
|
|
24
24
|
onSent?: (message: ChatMessage) => void;
|
|
25
25
|
onRejected?: (reason: string) => void;
|
|
26
|
-
}): import("react").JSX.Element;
|
|
26
|
+
}): import("react").JSX.Element | null;
|
|
27
27
|
export declare function ChannelTabs({ channels, active, onSelect, className, tabClassName, activeTabClassName, renderTab, }: {
|
|
28
28
|
channels?: readonly string[];
|
|
29
29
|
active: string;
|
|
@@ -32,8 +32,8 @@ export declare function ChannelTabs({ channels, active, onSelect, className, tab
|
|
|
32
32
|
tabClassName?: string;
|
|
33
33
|
activeTabClassName?: string;
|
|
34
34
|
renderTab?: (channelId: string, isActive: boolean) => ReactNode;
|
|
35
|
-
}): import("react").JSX.Element;
|
|
36
|
-
export declare function ChatPanel({ channels, initialChannel, limit, className, tabsClassName, tabClassName, activeTabClassName, logClassName, messageClassName, inputClassName, inputFieldClassName, sendButtonClassName, placeholder, renderMessage, onRejected, }: {
|
|
35
|
+
}): import("react").JSX.Element | null;
|
|
36
|
+
export declare function ChatPanel({ channels, initialChannel, limit, className, tabsClassName, tabClassName, activeTabClassName, logClassName, messageClassName, inputClassName, inputFieldClassName, sendButtonClassName, placeholder, renderMessage, renderTab, onRejected, }: {
|
|
37
37
|
channels?: readonly string[];
|
|
38
38
|
initialChannel?: string;
|
|
39
39
|
limit?: number;
|
|
@@ -48,5 +48,7 @@ export declare function ChatPanel({ channels, initialChannel, limit, className,
|
|
|
48
48
|
sendButtonClassName?: string;
|
|
49
49
|
placeholder?: string;
|
|
50
50
|
renderMessage?: (message: ChatMessage) => ReactNode;
|
|
51
|
+
/** Overrides a tab's label — e.g. render channel id `"proximity"` as "Nearby". */
|
|
52
|
+
renderTab?: (channelId: string, isActive: boolean) => ReactNode;
|
|
51
53
|
onRejected?: (reason: string) => void;
|
|
52
|
-
}): import("react").JSX.Element;
|
|
54
|
+
}): import("react").JSX.Element | null;
|
package/dist/chat.js
CHANGED
|
@@ -37,21 +37,27 @@ export function ChatLog({ channelId, limit, className, messageClassName, renderM
|
|
|
37
37
|
export function ChatInput({ channelId, className, inputClassName, buttonClassName, placeholder, sendLabel, onSent, onRejected, }) {
|
|
38
38
|
const ctx = useGameContext();
|
|
39
39
|
const [value, setValue] = useState("");
|
|
40
|
-
|
|
40
|
+
const chat = ctx.game.chat;
|
|
41
|
+
if (chat === undefined)
|
|
42
|
+
return null;
|
|
43
|
+
const submit = (event) => {
|
|
41
44
|
event.preventDefault();
|
|
42
|
-
const result =
|
|
45
|
+
const result = chat.send(ctx.player.userId, channelId, value);
|
|
43
46
|
if ("reason" in result) {
|
|
44
47
|
onRejected?.(result.reason);
|
|
45
48
|
return;
|
|
46
49
|
}
|
|
47
50
|
setValue("");
|
|
48
51
|
onSent?.(result.message);
|
|
49
|
-
}
|
|
52
|
+
};
|
|
50
53
|
return (_jsxs("form", { className: className, "data-chat-input": channelId, onSubmit: submit, children: [_jsx("input", { className: inputClassName, type: "text", value: value, placeholder: placeholder, onChange: (event) => setValue(event.target.value) }), _jsx("button", { type: "submit", className: buttonClassName, "data-chat-send": true, children: sendLabel ?? "Send" })] }));
|
|
51
54
|
}
|
|
52
55
|
export function ChannelTabs({ channels, active, onSelect, className, tabClassName, activeTabClassName, renderTab, }) {
|
|
53
56
|
const ctx = useGameContext();
|
|
54
|
-
const
|
|
57
|
+
const chat = ctx.game.chat;
|
|
58
|
+
if (chat === undefined)
|
|
59
|
+
return null;
|
|
60
|
+
const ids = channels ?? chat.channels().map((def) => def.id);
|
|
55
61
|
return (_jsx("div", { className: className, role: "tablist", "data-chat-tabs": true, children: ids.map((channelId) => {
|
|
56
62
|
const isActive = channelId === active;
|
|
57
63
|
const classes = [tabClassName, isActive ? activeTabClassName : undefined]
|
|
@@ -60,9 +66,12 @@ export function ChannelTabs({ channels, active, onSelect, className, tabClassNam
|
|
|
60
66
|
return (_jsx("button", { type: "button", role: "tab", "aria-selected": isActive, className: classes.length > 0 ? classes : undefined, "data-channel": channelId, onClick: () => onSelect(channelId), children: renderTab !== undefined ? renderTab(channelId, isActive) : channelId }, channelId));
|
|
61
67
|
}) }));
|
|
62
68
|
}
|
|
63
|
-
export function ChatPanel({ channels, initialChannel, limit, className, tabsClassName, tabClassName, activeTabClassName, logClassName, messageClassName, inputClassName, inputFieldClassName, sendButtonClassName, placeholder, renderMessage, onRejected, }) {
|
|
69
|
+
export function ChatPanel({ channels, initialChannel, limit, className, tabsClassName, tabClassName, activeTabClassName, logClassName, messageClassName, inputClassName, inputFieldClassName, sendButtonClassName, placeholder, renderMessage, renderTab, onRejected, }) {
|
|
64
70
|
const ctx = useGameContext();
|
|
65
|
-
const
|
|
71
|
+
const chat = ctx.game.chat;
|
|
72
|
+
const ids = channels ?? chat?.channels().map((def) => def.id) ?? [];
|
|
66
73
|
const [active, setActive] = useState(initialChannel ?? ids[0] ?? "global");
|
|
67
|
-
|
|
74
|
+
if (chat === undefined)
|
|
75
|
+
return null;
|
|
76
|
+
return (_jsxs("section", { className: className, "data-chat-panel": true, children: [_jsx(ChannelTabs, { channels: ids, active: active, onSelect: setActive, className: tabsClassName, tabClassName: tabClassName, activeTabClassName: activeTabClassName, renderTab: renderTab }), _jsx(ChatLog, { channelId: active, limit: limit, className: logClassName, messageClassName: messageClassName, renderMessage: renderMessage }), _jsx(ChatInput, { channelId: active, className: inputClassName, inputClassName: inputFieldClassName, buttonClassName: sendButtonClassName, placeholder: placeholder, onRejected: onRejected })] }));
|
|
68
77
|
}
|
package/dist/chatBubbles.js
CHANGED
|
@@ -4,6 +4,7 @@ const DEFAULT_CHAT_BUBBLE_CHANNEL = "proximity";
|
|
|
4
4
|
const DEFAULT_CHAT_BUBBLE_TTL_MS = 4000;
|
|
5
5
|
const DEFAULT_CHAT_BUBBLE_LIMIT = 8;
|
|
6
6
|
const CHAT_BUBBLE_TICK_MS = 500;
|
|
7
|
+
const EMPTY_CHAT_MESSAGES = [];
|
|
7
8
|
export function latestChatBubbles(messages, nowMs, ttlMs) {
|
|
8
9
|
const cutoff = nowMs - ttlMs;
|
|
9
10
|
const latestByUser = new Map();
|
|
@@ -28,7 +29,7 @@ export function useChatBubbles(options) {
|
|
|
28
29
|
const channelId = options?.channelId ?? DEFAULT_CHAT_BUBBLE_CHANNEL;
|
|
29
30
|
const ttlMs = options?.ttlMs ?? DEFAULT_CHAT_BUBBLE_TTL_MS;
|
|
30
31
|
const limit = options?.limit ?? DEFAULT_CHAT_BUBBLE_LIMIT;
|
|
31
|
-
const messages = useGameStore((ctx) => ctx.game.chat
|
|
32
|
+
const messages = useGameStore((ctx) => ctx.game.chat?.history(channelId, { limit }) ?? EMPTY_CHAT_MESSAGES);
|
|
32
33
|
const [now, setNow] = useState(() => Date.now());
|
|
33
34
|
const hasLiveMessage = messages.some((message) => message.at > now - ttlMs);
|
|
34
35
|
useEffect(() => {
|
package/dist/components.d.ts
CHANGED
|
@@ -84,6 +84,20 @@ export declare function DialogueBox({ dialogue, onChoice, rng, className, lineCl
|
|
|
84
84
|
choiceClassName?: string;
|
|
85
85
|
checkClassName?: string;
|
|
86
86
|
}): import("react").JSX.Element;
|
|
87
|
+
/**
|
|
88
|
+
* The id of the dialogue `ctx.game.dialogue` (or a `talkable(id)` prompt) currently has open, or `null`.
|
|
89
|
+
* The read side of the `features.dialogue` bridge — a panel looks the id up in its own dialogue catalog and
|
|
90
|
+
* renders {@link DialogueBox}, with no per-game open/close store.
|
|
91
|
+
*/
|
|
92
|
+
export declare function useOpenDialogueId(): string | null;
|
|
93
|
+
/**
|
|
94
|
+
* Route a {@link DialogueBox} choice through the `features.dialogue` bridge: resolve the choice's invoke
|
|
95
|
+
* (honoring a skill-check `result`), run that command, and otherwise close the dialogue — the write side that
|
|
96
|
+
* replaces a per-game `onChoice` that hand-rolls `resolveDialogueInvoke` + `dialogue.close`.
|
|
97
|
+
*/
|
|
98
|
+
export declare function runDialogueChoice(commands: {
|
|
99
|
+
run(name: string, input?: unknown): unknown;
|
|
100
|
+
}, choice: DialogueChoice, result: CheckResult | null): void;
|
|
87
101
|
export declare function SkillCheckBar({ config, startedAt, className, trackClassName, zoneClassName, markerClassName, renderStatus, }: {
|
|
88
102
|
config: SkillCheckConfig;
|
|
89
103
|
startedAt: number;
|
|
@@ -106,6 +120,12 @@ export declare function CaptureOdds({ chance, className, fillClassName, }: {
|
|
|
106
120
|
className?: string;
|
|
107
121
|
fillClassName?: string;
|
|
108
122
|
}): import("react").JSX.Element;
|
|
123
|
+
/**
|
|
124
|
+
* Render `ctx.game.feed`'s entries for `action` as a newest-first toast stack — the feed-backed sibling of
|
|
125
|
+
* `@jgengine/core/game/toasts`' `createToastQueue`. Reach for this when the message source is already an engine
|
|
126
|
+
* event bound onto `ctx.game.feed` (kill feed, quest updates, loot log); reach for a `createToastQueue` when the
|
|
127
|
+
* game raises ad-hoc messages that need their own TTL independent of the feed's ring buffer.
|
|
128
|
+
*/
|
|
109
129
|
export declare function ToastStack({ action, limit, className, renderToast, }: {
|
|
110
130
|
action: string;
|
|
111
131
|
limit?: number;
|
package/dist/components.js
CHANGED
|
@@ -3,8 +3,10 @@ import { useEffect, useRef, useState } from "react";
|
|
|
3
3
|
import { evaluateSkillCheck, } from "@jgengine/core/interaction/skillCheck";
|
|
4
4
|
import { pendingQteStep } from "@jgengine/core/interaction/qte";
|
|
5
5
|
import { rollCheck } from "@jgengine/core/stats/rollCheck";
|
|
6
|
+
import { dialogueSlot } from "@jgengine/core/game/dialogue";
|
|
6
7
|
import { useGameContext } from "./provider.js";
|
|
7
8
|
import { useCurrency, useEntityStat, useFeed, useInventory, useLocalPlayerDead } from "./hooks.js";
|
|
9
|
+
import { useStore } from "./store.js";
|
|
8
10
|
import { paintQteStepDom, paintSkillCheckDom } from "./skillCheckPaint.js";
|
|
9
11
|
export { paintQteStepDom, paintSkillCheckDom } from "./skillCheckPaint.js";
|
|
10
12
|
export function SlotGrid({ inventoryId, className, renderSlot, }) {
|
|
@@ -30,7 +32,7 @@ export function CurrencyPill({ currencyId, className }) {
|
|
|
30
32
|
export function ProximityPrompt({ prompt, className, }) {
|
|
31
33
|
const display = prompt.display;
|
|
32
34
|
if (display.kind === "keybind") {
|
|
33
|
-
return (
|
|
35
|
+
return (_jsxs("span", { className: className, "data-prompt": "keybind", "data-action": display.actionId, children: [_jsx("kbd", { children: display.actionId }), display.label !== undefined ? _jsx("span", { "data-prompt-label": true, children: display.label }) : null] }));
|
|
34
36
|
}
|
|
35
37
|
if (display.kind === "gauge") {
|
|
36
38
|
return (_jsx("span", { className: className, "data-prompt": "gauge", "data-gauge": display.gaugeId, children: display.gaugeId }));
|
|
@@ -55,6 +57,26 @@ export function DialogueBox({ dialogue, onChoice, rng, className, lineClassName,
|
|
|
55
57
|
? ` (${choice.check.advantage})`
|
|
56
58
|
: ""] }))] }, choice.label))) }, index)) : (_jsxs("p", { className: lineClassName, children: [_jsx("span", { className: speakerClassName, "data-speaker": true, children: line.speaker }), _jsx("span", { children: line.text })] }, index))) }));
|
|
57
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* The id of the dialogue `ctx.game.dialogue` (or a `talkable(id)` prompt) currently has open, or `null`.
|
|
62
|
+
* The read side of the `features.dialogue` bridge — a panel looks the id up in its own dialogue catalog and
|
|
63
|
+
* renders {@link DialogueBox}, with no per-game open/close store.
|
|
64
|
+
*/
|
|
65
|
+
export function useOpenDialogueId() {
|
|
66
|
+
return useStore(dialogueSlot, (value) => value ?? null);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Route a {@link DialogueBox} choice through the `features.dialogue` bridge: resolve the choice's invoke
|
|
70
|
+
* (honoring a skill-check `result`), run that command, and otherwise close the dialogue — the write side that
|
|
71
|
+
* replaces a per-game `onChoice` that hand-rolls `resolveDialogueInvoke` + `dialogue.close`.
|
|
72
|
+
*/
|
|
73
|
+
export function runDialogueChoice(commands, choice, result) {
|
|
74
|
+
const invoke = resolveDialogueInvoke(choice, result);
|
|
75
|
+
if (invoke !== null && invoke !== undefined)
|
|
76
|
+
commands.run(invoke.command, invoke.args ?? {});
|
|
77
|
+
else
|
|
78
|
+
commands.run("dialogue.close", {});
|
|
79
|
+
}
|
|
58
80
|
export function SkillCheckBar({ config, startedAt, className, trackClassName, zoneClassName, markerClassName, renderStatus, }) {
|
|
59
81
|
const ctx = useGameContext();
|
|
60
82
|
const rootRef = useRef(null);
|
|
@@ -119,6 +141,12 @@ function defaultToast(entry) {
|
|
|
119
141
|
}
|
|
120
142
|
return typeof entry.data === "string" ? entry.data : JSON.stringify(entry.data);
|
|
121
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Render `ctx.game.feed`'s entries for `action` as a newest-first toast stack — the feed-backed sibling of
|
|
146
|
+
* `@jgengine/core/game/toasts`' `createToastQueue`. Reach for this when the message source is already an engine
|
|
147
|
+
* event bound onto `ctx.game.feed` (kill feed, quest updates, loot log); reach for a `createToastQueue` when the
|
|
148
|
+
* game raises ad-hoc messages that need their own TTL independent of the feed's ring buffer.
|
|
149
|
+
*/
|
|
122
150
|
export function ToastStack({ action, limit = 4, className, renderToast, }) {
|
|
123
151
|
const entries = useFeed({ action, limit });
|
|
124
152
|
if (entries.length === 0)
|
package/dist/display.d.ts
CHANGED
package/dist/display.js
CHANGED
package/dist/engineStore.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ export interface ReadableEngineStore<TState> {
|
|
|
5
5
|
export interface EventfulEngineStore<TEventMap extends object> {
|
|
6
6
|
on<K extends keyof TEventMap>(eventName: K, listener: (payload: TEventMap[K]) => void): () => void;
|
|
7
7
|
}
|
|
8
|
+
/** @internal */
|
|
8
9
|
export declare function useEngineState<TState>(store: ReadableEngineStore<TState>): TState;
|
|
10
|
+
/** @internal */
|
|
9
11
|
export declare function useEngineStore<TState, TSelected>(store: ReadableEngineStore<TState>, selector: (state: TState) => TSelected, isEqual?: (previous: TSelected, next: TSelected) => boolean): TSelected;
|
|
12
|
+
/** @internal */
|
|
10
13
|
export declare function useEngineEvent<TEventMap extends object, K extends keyof TEventMap>(store: EventfulEngineStore<TEventMap>, eventName: K, handler: (payload: TEventMap[K]) => void): void;
|
package/dist/engineStore.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
|
|
2
2
|
import { createSelectCache, readSelectSnapshot } from "./selectSnapshot.js";
|
|
3
|
+
/** @internal */
|
|
3
4
|
export function useEngineState(store) {
|
|
4
5
|
const subscribe = useCallback((onChange) => store.subscribe(() => onChange()), [store]);
|
|
5
6
|
const getSnapshot = useCallback(() => store.getState(), [store]);
|
|
6
7
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
7
8
|
}
|
|
9
|
+
/** @internal */
|
|
8
10
|
export function useEngineStore(store, selector, isEqual = Object.is) {
|
|
9
11
|
const selectorRef = useRef(selector);
|
|
10
12
|
selectorRef.current = selector;
|
|
@@ -15,6 +17,7 @@ export function useEngineStore(store, selector, isEqual = Object.is) {
|
|
|
15
17
|
const getSnapshot = useCallback(() => readSelectSnapshot(cacheRef.current, store.getState(), (state) => selectorRef.current(state), (previous, next) => isEqualRef.current(previous, next)), [store]);
|
|
16
18
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
17
19
|
}
|
|
20
|
+
/** @internal */
|
|
18
21
|
export function useEngineEvent(store, eventName, handler) {
|
|
19
22
|
const handlerRef = useRef(handler);
|
|
20
23
|
handlerRef.current = handler;
|
package/dist/fogOverlay.d.ts
CHANGED
|
@@ -10,11 +10,14 @@ export interface FogPaintSurface {
|
|
|
10
10
|
fillRect(x: number, y: number, width: number, height: number): void;
|
|
11
11
|
clearRect(x: number, y: number, width: number, height: number): void;
|
|
12
12
|
}
|
|
13
|
+
/** @internal */
|
|
13
14
|
export declare function forEachUnrevealedFogCell(fog: FogCells, visit: (col: number, row: number) => void): number;
|
|
15
|
+
/** @internal */
|
|
14
16
|
export declare function paintFogOverlay(surface: FogPaintSurface, canvasSize: {
|
|
15
17
|
width: number;
|
|
16
18
|
height: number;
|
|
17
19
|
}, fog: FogCells, cellRect: (col: number, row: number) => FogCellRect | null, fill?: string): number;
|
|
20
|
+
/** @internal */
|
|
18
21
|
export declare function createFogDataUrl(fog: FogCells, canvasSize: {
|
|
19
22
|
width: number;
|
|
20
23
|
height: number;
|
package/dist/fogOverlay.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @internal */
|
|
1
2
|
export function forEachUnrevealedFogCell(fog, visit) {
|
|
2
3
|
let count = 0;
|
|
3
4
|
for (let row = 0; row < fog.rows; row += 1) {
|
|
@@ -10,6 +11,7 @@ export function forEachUnrevealedFogCell(fog, visit) {
|
|
|
10
11
|
}
|
|
11
12
|
return count;
|
|
12
13
|
}
|
|
14
|
+
/** @internal */
|
|
13
15
|
export function paintFogOverlay(surface, canvasSize, fog, cellRect, fill = "rgba(11, 15, 20, 0.82)") {
|
|
14
16
|
surface.clearRect(0, 0, canvasSize.width, canvasSize.height);
|
|
15
17
|
surface.fillStyle = fill;
|
|
@@ -20,6 +22,7 @@ export function paintFogOverlay(surface, canvasSize, fog, cellRect, fill = "rgba
|
|
|
20
22
|
surface.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
21
23
|
});
|
|
22
24
|
}
|
|
25
|
+
/** @internal */
|
|
23
26
|
export function createFogDataUrl(fog, canvasSize, cellRect, fill = "rgba(11, 15, 20, 0.82)") {
|
|
24
27
|
if (typeof document === "undefined")
|
|
25
28
|
return null;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared mobile-composition runtime. `GameViewportProvider` allocates the live
|
|
3
|
+
* game viewport once — tracking `visualViewport`, safe-area insets, orientation
|
|
4
|
+
* and layout mode — and hosts a registry every UI subsystem publishes its
|
|
5
|
+
* occupied rectangle to. HUD panels, touch-control zones, and system chrome all
|
|
6
|
+
* register here so the engine can detect forbidden overlaps instead of each
|
|
7
|
+
* subsystem independently claiming an edge.
|
|
8
|
+
*/
|
|
9
|
+
import { type CSSProperties, type ReactNode, type RefObject } from "react";
|
|
10
|
+
import { type GameLayoutMode, type GameViewportLayout, type LayoutCollision, type LayoutRect, type LayoutRegion } from "@jgengine/core/ui/gameLayout";
|
|
11
|
+
import type { LayoutOrientation } from "@jgengine/core/ui/orientation";
|
|
12
|
+
import type { HudPlatform } from "@jgengine/core/ui/hudScale";
|
|
13
|
+
/** A region registration: the full `LayoutRegion` plus the live element (dev outlining), rect measured by the shell/react side. */
|
|
14
|
+
export interface RegionRecord extends LayoutRegion {
|
|
15
|
+
element?: HTMLElement | null;
|
|
16
|
+
}
|
|
17
|
+
/** A region descriptor without its measured rectangle — the caller supplies geometry through `useRegisterLayoutRegion`. */
|
|
18
|
+
export type LayoutRegionSpec = Omit<LayoutRegion, "rect">;
|
|
19
|
+
/** Live viewport rectangles: the layout viewport and the visible `visualViewport`. */
|
|
20
|
+
export interface ViewportMetrics {
|
|
21
|
+
layout: LayoutRect;
|
|
22
|
+
visual: LayoutRect;
|
|
23
|
+
}
|
|
24
|
+
/** Live visible viewport, tracking `window.visualViewport` (mobile browser chrome, pinch-zoom) with a layout-viewport fallback. */
|
|
25
|
+
export declare function useViewportMetrics(): ViewportMetrics;
|
|
26
|
+
/**
|
|
27
|
+
* Provides the shared game viewport layout to everything it wraps. Mount it
|
|
28
|
+
* once around the whole game presentation (world, HUD, controls, system UI) so
|
|
29
|
+
* every subsystem reads one coordinated geometry and registers its rect for
|
|
30
|
+
* collision detection. Publishes live `--jg-viewport-*` / `--jg-visual-viewport-*`
|
|
31
|
+
* / `--jg-safe-*` CSS variables on its root.
|
|
32
|
+
*/
|
|
33
|
+
export declare function GameViewportProvider({ platforms, className, style, children, }: {
|
|
34
|
+
platforms?: readonly HudPlatform[];
|
|
35
|
+
className?: string;
|
|
36
|
+
style?: CSSProperties;
|
|
37
|
+
children?: ReactNode;
|
|
38
|
+
}): import("react").JSX.Element;
|
|
39
|
+
/** The live shared viewport layout. Returns a neutral default outside a `GameViewportProvider` so it never throws in previews. */
|
|
40
|
+
export declare function useGameViewportLayout(): GameViewportLayout;
|
|
41
|
+
/** The resolved explicit composition mode (`desktop-wide` … `mobile-portrait`). */
|
|
42
|
+
export declare function useGameLayoutMode(): GameLayoutMode;
|
|
43
|
+
/** The live device orientation. */
|
|
44
|
+
export declare function useGameOrientation(): LayoutOrientation;
|
|
45
|
+
/** Rectangles reserved by touch controls and system UI — HUD placement should avoid these. */
|
|
46
|
+
export declare function useReservedControlZones(): readonly LayoutRect[];
|
|
47
|
+
/** Live forbidden/warned region collisions (empty outside a provider). */
|
|
48
|
+
export declare function useLayoutCollisions(): readonly LayoutCollision[];
|
|
49
|
+
/**
|
|
50
|
+
* Register the element behind `ref` as a layout region and keep its measured
|
|
51
|
+
* rectangle live (ResizeObserver + viewport changes). No-op outside a
|
|
52
|
+
* `GameViewportProvider`, so a component using it still works in isolation.
|
|
53
|
+
*/
|
|
54
|
+
export declare function useRegisterLayoutRegion(spec: LayoutRegionSpec, ref: RefObject<HTMLElement | null>, enabled?: boolean): void;
|