@jgengine/react 0.19.0 → 0.21.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.
@@ -0,0 +1,109 @@
1
+ import { useEffect, useRef } from "react";
2
+ const owners = new WeakMap();
3
+ function focusableWithin(root) {
4
+ return Array.from(root.querySelectorAll('a[href],button,textarea,input,select,[tabindex]')).filter((element) => {
5
+ if (element.tabIndex < 0 || element.matches(":disabled") || element.closest("[hidden],[inert]"))
6
+ return false;
7
+ const style = element.ownerDocument.defaultView?.getComputedStyle(element);
8
+ return style?.visibility !== "hidden" && style?.visibility !== "collapse" && element.getClientRects().length > 0;
9
+ });
10
+ }
11
+ function focusInitial(root, options) {
12
+ const controls = focusableWithin(root);
13
+ const preferred = options.initialFocus === "root" ? root : options.initialFocus?.();
14
+ (preferred !== null && preferred !== undefined && (preferred === root || controls.includes(preferred))
15
+ ? preferred : controls[0] ?? root).focus();
16
+ }
17
+ /**
18
+ * Focus entry, Tab trapping, Escape requests, and focus restoration for a caller-owned dialog.
19
+ * Attach the returned ref to the dialog root and supply `role="dialog"`, `aria-modal`, an accessible
20
+ * name, and `tabIndex={-1}`. The most recently opened dialog handles keys, including portal roots.
21
+ * Closing an inner dialog restores its opener; cleanup never steals focus from another active dialog.
22
+ * Replacing the root while open keeps its position and original return target.
23
+ *
24
+ * @capability dialog-behavior controlled dialog focus and keyboard behavior without owning markup or screen state
25
+ */
26
+ export function useDialogBehavior(options) {
27
+ const ref = useRef(null);
28
+ const activeOwner = useRef(null);
29
+ const latest = useRef(options);
30
+ latest.current = options;
31
+ useEffect(() => {
32
+ const root = ref.current;
33
+ if (!options.open || root === null)
34
+ return;
35
+ const document = root.ownerDocument;
36
+ const stack = owners.get(document) ?? [];
37
+ owners.set(document, stack);
38
+ const owner = { root, restore: document.activeElement };
39
+ activeOwner.current = owner;
40
+ const child = stack.findIndex((entry) => root.contains(entry.root));
41
+ if (child < 0)
42
+ stack.push(owner);
43
+ else
44
+ stack.splice(child, 0, owner);
45
+ const keydown = (event) => {
46
+ if (stack.at(-1) !== owner || event.defaultPrevented)
47
+ return;
48
+ if (event.key === "Escape" && latest.current.onClose !== undefined) {
49
+ event.preventDefault();
50
+ event.stopPropagation();
51
+ latest.current.onClose();
52
+ return;
53
+ }
54
+ if (event.key !== "Tab")
55
+ return;
56
+ const root = owner.root;
57
+ const controls = focusableWithin(root);
58
+ const first = controls[0], last = controls.at(-1);
59
+ const active = document.activeElement;
60
+ if (first === undefined || !root.contains(active) || active === root ||
61
+ (event.shiftKey ? active === first : active === last)) {
62
+ event.preventDefault();
63
+ event.stopPropagation();
64
+ (event.shiftKey ? last : first)?.focus();
65
+ if (first === undefined)
66
+ root.focus();
67
+ }
68
+ };
69
+ document.addEventListener("keydown", keydown, true);
70
+ return () => {
71
+ document.removeEventListener("keydown", keydown, true);
72
+ const wasTop = stack.at(-1) === owner;
73
+ const index = stack.indexOf(owner);
74
+ if (index >= 0)
75
+ stack.splice(index, 1);
76
+ for (const entry of stack) {
77
+ if (entry.restore !== null && owner.root.contains(entry.restore))
78
+ entry.restore = owner.restore;
79
+ }
80
+ if (wasTop && owner.restore?.isConnected && !owner.restore.closest("[hidden],[inert]")) {
81
+ owner.restore.focus();
82
+ }
83
+ if (stack.length === 0)
84
+ owners.delete(document);
85
+ activeOwner.current = null;
86
+ };
87
+ }, [options.open]);
88
+ useEffect(() => {
89
+ const root = ref.current;
90
+ const owner = activeOwner.current;
91
+ if (!options.open || root === null || owner === null || owner.root === root)
92
+ return;
93
+ const stack = owners.get(owner.root.ownerDocument);
94
+ for (const entry of stack ?? []) {
95
+ if (entry !== owner && entry.restore !== null && owner.root.contains(entry.restore))
96
+ entry.restore = root;
97
+ }
98
+ owner.root = root;
99
+ if (stack?.at(-1) === owner)
100
+ focusInitial(root, latest.current);
101
+ });
102
+ useEffect(() => {
103
+ const root = ref.current;
104
+ if (!options.open || root === null || owners.get(root.ownerDocument)?.at(-1)?.root !== root)
105
+ return;
106
+ focusInitial(root, latest.current);
107
+ }, [options.open, options.focusKey]);
108
+ return ref;
109
+ }
@@ -45,6 +45,13 @@ export interface DragLayer<T> {
45
45
  cancel(): void;
46
46
  attachGhost(element: HTMLElement | null): void;
47
47
  }
48
+ /**
49
+ * Pointer-owned drag state shared by cards and inventory grids. Registered DropZones are hit-tested
50
+ * through pointer capture; release finishes once, while cancellation, blur and unmount release ownership.
51
+ * Callers own drop policy, rotation and presentation; pointer movement paints the ghost without rerendering it.
52
+ *
53
+ * @capability drag-layer composable pointer-owned drag, drop targets and cancellation for custom cards and inventory UI
54
+ */
48
55
  export declare function useDragLayer<T>(options?: {
49
56
  onDrop?: (info: DropInfo<T>) => void;
50
57
  }): DragLayer<T>;
package/dist/dragLayer.js CHANGED
@@ -2,25 +2,41 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
3
3
  import { cellFromPoint, } from "@jgengine/core/inventory/shapedGrid";
4
4
  const EMPTY_POINT = { x: 0, y: 0 };
5
+ const targetRegistries = new WeakMap();
6
+ /**
7
+ * Pointer-owned drag state shared by cards and inventory grids. Registered DropZones are hit-tested
8
+ * through pointer capture; release finishes once, while cancellation, blur and unmount release ownership.
9
+ * Callers own drop policy, rotation and presentation; pointer movement paints the ghost without rerendering it.
10
+ *
11
+ * @capability drag-layer composable pointer-owned drag, drop targets and cancellation for custom cards and inventory UI
12
+ */
5
13
  export function useDragLayer(options) {
6
14
  const [state, setState] = useState({
7
- payload: null,
8
- point: EMPTY_POINT,
9
- origin: EMPTY_POINT,
10
- overTarget: null,
11
- overCell: null,
15
+ payload: null, point: EMPTY_POINT, origin: EMPTY_POINT, overTarget: null, overCell: null,
12
16
  });
13
17
  const pointRef = useRef(EMPTY_POINT);
14
18
  const ghostRef = useRef(null);
15
- const moveRef = useRef(null);
16
19
  const payloadRef = useRef(null);
17
20
  const overRef = useRef({ target: null, cell: null });
21
+ const overElement = useRef(null);
22
+ const targets = useRef(new Map());
23
+ const ownerRef = useRef(null);
24
+ const latest = useRef(options);
25
+ latest.current = options;
18
26
  const detach = useCallback(() => {
19
- if (moveRef.current !== null) {
20
- window.removeEventListener("pointermove", moveRef.current);
21
- moveRef.current = null;
22
- }
27
+ const owner = ownerRef.current;
28
+ ownerRef.current = null;
29
+ if (owner === null)
30
+ return;
31
+ owner.detach();
32
+ if (owner.source.hasPointerCapture(owner.pointerId))
33
+ owner.source.releasePointerCapture(owner.pointerId);
23
34
  }, []);
35
+ useEffect(() => () => {
36
+ detach();
37
+ payloadRef.current = null;
38
+ ghostRef.current = null;
39
+ }, [detach]);
24
40
  const writeGhost = useCallback((point, rotation) => {
25
41
  const el = ghostRef.current;
26
42
  if (el === null)
@@ -29,91 +45,139 @@ export function useDragLayer(options) {
29
45
  el.style.top = `${point.y}px`;
30
46
  el.style.transform = `translate(-50%, -50%) rotate(${rotation * 90}deg)`;
31
47
  }, []);
32
- const beginDrag = useCallback((payload, event) => {
33
- const point = { x: event.clientX, y: event.clientY };
34
- const nextPayload = { id: payload.id, value: payload.value, rotation: payload.rotation ?? 0 };
35
- payloadRef.current = nextPayload;
36
- overRef.current = { target: null, cell: null };
37
- pointRef.current = point;
38
- setState({
39
- payload: nextPayload,
40
- point,
41
- origin: point,
42
- overTarget: null,
43
- overCell: null,
44
- });
45
- writeGhost(point, nextPayload.rotation);
46
- const onMove = (moveEvent) => {
47
- if (payloadRef.current === null)
48
- return;
49
- const next = { x: moveEvent.clientX, y: moveEvent.clientY };
50
- pointRef.current = next;
51
- writeGhost(next, payloadRef.current.rotation);
52
- };
53
- detach();
54
- moveRef.current = onMove;
55
- window.addEventListener("pointermove", onMove);
56
- }, [detach, writeGhost]);
57
- const rotate = useCallback((quarterTurns = 1) => {
58
- setState((prev) => {
59
- if (prev.payload === null)
60
- return prev;
61
- const rotation = (((prev.payload.rotation + quarterTurns) % 4) + 4) % 4;
62
- const payload = { ...prev.payload, rotation };
63
- payloadRef.current = payload;
64
- writeGhost(pointRef.current, rotation);
65
- return { ...prev, payload, point: pointRef.current };
66
- });
67
- }, [writeGhost]);
68
48
  const setTarget = useCallback((target, cell = null) => {
49
+ const previous = overRef.current;
50
+ if (previous.target === target && previous.cell?.[0] === cell?.[0] && previous.cell?.[1] === cell?.[1])
51
+ return;
69
52
  overRef.current = { target, cell };
70
53
  setState((prev) => ({ ...prev, overTarget: target, overCell: cell, point: pointRef.current }));
71
54
  }, []);
55
+ const resolveTarget = useCallback((document, point) => {
56
+ if (targets.current.size === 0)
57
+ return;
58
+ let element = document.elementFromPoint(point.x, point.y);
59
+ while (element !== null) {
60
+ const target = targets.current.get(element);
61
+ if (target !== undefined) {
62
+ overElement.current = element;
63
+ const rect = element.getBoundingClientRect();
64
+ setTarget(target.id, target.cellSize === undefined ? null
65
+ : cellFromPoint({ x: point.x - rect.left, y: point.y - rect.top }, target.cellSize));
66
+ return;
67
+ }
68
+ element = element.parentElement;
69
+ }
70
+ overElement.current = null;
71
+ setTarget(null);
72
+ }, [setTarget]);
72
73
  const reset = useCallback(() => {
73
74
  detach();
74
75
  payloadRef.current = null;
75
76
  overRef.current = { target: null, cell: null };
77
+ overElement.current = null;
76
78
  pointRef.current = EMPTY_POINT;
77
- setState({
78
- payload: null,
79
- point: EMPTY_POINT,
80
- origin: EMPTY_POINT,
81
- overTarget: null,
82
- overCell: null,
83
- });
79
+ setState({ payload: null, point: EMPTY_POINT, origin: EMPTY_POINT, overTarget: null, overCell: null });
84
80
  }, [detach]);
85
81
  const endDrag = useCallback(() => {
86
82
  const payload = payloadRef.current;
87
- let info = null;
88
- if (payload !== null) {
89
- info = {
90
- payload,
91
- target: overRef.current.target,
92
- cell: overRef.current.cell,
93
- point: pointRef.current,
94
- };
95
- options?.onDrop?.(info);
96
- }
83
+ if (payload === null)
84
+ return null;
85
+ const info = {
86
+ payload, target: overRef.current.target, cell: overRef.current.cell, point: pointRef.current,
87
+ };
97
88
  reset();
89
+ latest.current?.onDrop?.(info);
98
90
  return info;
99
- }, [options, reset]);
91
+ }, [reset]);
92
+ const beginDrag = useCallback((payload, event) => {
93
+ if (event.button !== 0 || !event.isPrimary || payloadRef.current !== null)
94
+ return;
95
+ const source = event.currentTarget;
96
+ const view = source.ownerDocument.defaultView;
97
+ if (view === null)
98
+ return;
99
+ const point = { x: event.clientX, y: event.clientY };
100
+ const nextPayload = { id: payload.id, value: payload.value, rotation: payload.rotation ?? 0 };
101
+ payloadRef.current = nextPayload;
102
+ overRef.current = { target: null, cell: null };
103
+ pointRef.current = point;
104
+ setState({ payload: nextPayload, point, origin: point, overTarget: null, overCell: null });
105
+ writeGhost(point, nextPayload.rotation);
106
+ resolveTarget(source.ownerDocument, point);
107
+ const onMove = (move) => {
108
+ if (move.pointerId !== ownerRef.current?.pointerId || payloadRef.current === null)
109
+ return;
110
+ if (!source.isConnected) {
111
+ reset();
112
+ return;
113
+ }
114
+ const next = { x: move.clientX, y: move.clientY };
115
+ pointRef.current = next;
116
+ writeGhost(next, payloadRef.current.rotation);
117
+ resolveTarget(source.ownerDocument, next);
118
+ };
119
+ const onUp = (up) => {
120
+ if (up.pointerId !== ownerRef.current?.pointerId || up.button !== 0)
121
+ return;
122
+ if (!source.isConnected) {
123
+ reset();
124
+ return;
125
+ }
126
+ pointRef.current = { x: up.clientX, y: up.clientY };
127
+ resolveTarget(source.ownerDocument, pointRef.current);
128
+ endDrag();
129
+ };
130
+ const onCancel = (cancel) => { if (cancel.pointerId === ownerRef.current?.pointerId)
131
+ reset(); };
132
+ const onKey = (key) => {
133
+ if (key.key !== "Escape" || key.defaultPrevented)
134
+ return;
135
+ key.preventDefault();
136
+ key.stopPropagation();
137
+ reset();
138
+ };
139
+ ownerRef.current = { source, pointerId: event.pointerId, detach: () => {
140
+ view.removeEventListener("pointermove", onMove, true);
141
+ view.removeEventListener("pointerup", onUp, true);
142
+ view.removeEventListener("pointercancel", onCancel, true);
143
+ view.removeEventListener("lostpointercapture", onCancel, true);
144
+ view.removeEventListener("blur", reset);
145
+ view.removeEventListener("keydown", onKey, true);
146
+ } };
147
+ view.addEventListener("pointermove", onMove, true);
148
+ view.addEventListener("pointerup", onUp, true);
149
+ view.addEventListener("pointercancel", onCancel, true);
150
+ view.addEventListener("lostpointercapture", onCancel, true);
151
+ view.addEventListener("blur", reset);
152
+ view.addEventListener("keydown", onKey, true);
153
+ source.setPointerCapture(event.pointerId);
154
+ }, [endDrag, reset, resolveTarget, writeGhost]);
155
+ targetRegistries.set(beginDrag, (element, id, cellSize) => {
156
+ targets.current.set(element, { id, cellSize });
157
+ return () => {
158
+ targets.current.delete(element);
159
+ if (overElement.current === element) {
160
+ overElement.current = null;
161
+ setTarget(null);
162
+ }
163
+ };
164
+ });
165
+ const rotate = useCallback((quarterTurns = 1) => {
166
+ const payload = payloadRef.current;
167
+ if (payload === null)
168
+ return;
169
+ const rotation = (((payload.rotation + quarterTurns) % 4) + 4) % 4;
170
+ const next = { ...payload, rotation };
171
+ payloadRef.current = next;
172
+ writeGhost(pointRef.current, rotation);
173
+ setState((prev) => ({ ...prev, payload: next, point: pointRef.current }));
174
+ }, [writeGhost]);
100
175
  const attachGhost = useCallback((element) => {
101
176
  ghostRef.current = element;
102
- if (element !== null && payloadRef.current !== null) {
177
+ if (element !== null && payloadRef.current !== null)
103
178
  writeGhost(pointRef.current, payloadRef.current.rotation);
104
- }
105
179
  }, [writeGhost]);
106
- return {
107
- state,
108
- dragging: state.payload !== null,
109
- pointRef,
110
- beginDrag,
111
- rotate,
112
- setTarget,
113
- endDrag,
114
- cancel: reset,
115
- attachGhost,
116
- };
180
+ return { state, dragging: state.payload !== null, pointRef, beginDrag, rotate, setTarget, endDrag, cancel: reset, attachGhost };
117
181
  }
118
182
  export function DragGhost({ layer, className, style, children, }) {
119
183
  const ref = useRef(null);
@@ -136,12 +200,17 @@ export function DragGhost({ layer, className, style, children, }) {
136
200
  }
137
201
  export function DraggableCard({ id, value, layer, className, children, onRotate, }) {
138
202
  const isDragging = layer.state.payload?.id === id;
139
- return (_jsx("div", { className: className, "data-card": id, "data-dragging": isDragging ? "" : undefined, onPointerDown: (event) => {
203
+ const latest = useRef(layer);
204
+ latest.current = layer;
205
+ useEffect(() => () => {
206
+ if (latest.current.state.payload?.id === id)
207
+ latest.current.cancel();
208
+ }, [id, layer.cancel]);
209
+ return (_jsx("div", { className: className, "data-card": id, "data-dragging": isDragging ? "" : undefined, style: { touchAction: "none" }, onPointerDown: (event) => {
210
+ if (event.button !== 0 || !event.isPrimary)
211
+ return;
140
212
  event.preventDefault();
141
213
  layer.beginDrag({ id, value }, event);
142
- }, onPointerUp: () => {
143
- if (layer.dragging)
144
- layer.endDrag();
145
214
  }, onContextMenu: (event) => {
146
215
  if (onRotate === false)
147
216
  return;
@@ -153,24 +222,11 @@ export function DraggableCard({ id, value, layer, className, children, onRotate,
153
222
  export function DropZone({ id, layer, className, activeClassName, cellSize, children, }) {
154
223
  const ref = useRef(null);
155
224
  const active = layer.state.overTarget === id && layer.dragging;
156
- const resolveCell = useCallback((event) => {
157
- if (cellSize === undefined || ref.current === null)
158
- return null;
159
- const rect = ref.current.getBoundingClientRect();
160
- return cellFromPoint({ x: event.clientX - rect.left, y: event.clientY - rect.top }, cellSize);
161
- }, [cellSize]);
225
+ useEffect(() => {
226
+ const element = ref.current;
227
+ if (element !== null)
228
+ return targetRegistries.get(layer.beginDrag)?.(element, id, cellSize);
229
+ }, [layer.beginDrag, id, cellSize]);
162
230
  const composed = useMemo(() => [className, active ? activeClassName : undefined].filter(Boolean).join(" ") || undefined, [className, active, activeClassName]);
163
- return (_jsx("div", { ref: ref, className: composed, "data-dropzone": id, "data-active": active ? "" : undefined, onPointerEnter: (event) => {
164
- if (layer.dragging)
165
- layer.setTarget(id, resolveCell(event));
166
- }, onPointerMove: (event) => {
167
- if (layer.dragging && layer.state.overTarget === id)
168
- layer.setTarget(id, resolveCell(event));
169
- }, onPointerLeave: () => {
170
- if (layer.dragging && layer.state.overTarget === id)
171
- layer.setTarget(null, null);
172
- }, onPointerUp: () => {
173
- if (layer.dragging)
174
- layer.endDrag();
175
- }, children: children }));
231
+ return (_jsx("div", { ref: ref, className: composed, "data-dropzone": id, "data-active": active ? "" : undefined, children: children }));
176
232
  }
package/dist/hooks.d.ts CHANGED
@@ -2,6 +2,7 @@ import { type DependencyList } from "react";
2
2
  import { AxisChannel, type AxisChannelConfig } from "@jgengine/core/input/axisInput";
3
3
  import { type MarkerSet } from "@jgengine/core/world/markers";
4
4
  import type { GameContext } from "@jgengine/core/runtime/gameContext";
5
+ import { type DispatchedCommandResult } from "@jgengine/core/runtime/commandDispatch";
5
6
  import type { AbilityKit, AbilitySlotSnapshot } from "@jgengine/core/combat/abilityKit";
6
7
  import type { EventMeter } from "@jgengine/core/stats/eventMeter";
7
8
  import type { GameEvents } from "@jgengine/core/game/events";
@@ -23,8 +24,11 @@ import { type GamePhase } from "@jgengine/core/game/gamePhase";
23
24
  export declare function useGameStore<T>(selector: (ctx: GameContext) => T, isEqual?: (previous: T, next: T) => boolean): T;
24
25
  /** Subscribe to a single keyed slice of the game store, returning `fallback` until the key is set. */
25
26
  export declare function useGameStoreValue<T>(key: string, fallback: T): T;
27
+ /** UI command surface. Hosted commands return a promise; await it to display acknowledgement or rejection. Registration and runAs remain trusted local APIs. */
26
28
  export declare function useGame(): {
27
- commands: GameContext["game"]["commands"];
29
+ commands: Omit<GameContext["game"]["commands"], "run"> & {
30
+ run(name: string, input: unknown): DispatchedCommandResult;
31
+ };
28
32
  events: GameEvents;
29
33
  };
30
34
  export declare function usePlayer(): {
package/dist/hooks.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
2
2
  import { AxisChannel } from "@jgengine/core/input/axisInput";
3
3
  import { createMarkerSet } from "@jgengine/core/world/markers";
4
+ import { dispatchCommand } from "@jgengine/core/runtime/commandDispatch";
4
5
  import { browseSessions, } from "@jgengine/core/multiplayer/matchmaking";
5
6
  import { resolveActivePrompt, } from "@jgengine/core/interaction/proximityPrompt";
6
7
  import { gamePhase, setGamePhase } from "@jgengine/core/game/gamePhase";
@@ -21,9 +22,10 @@ export function useGameStore(selector, isEqual = Object.is) {
21
22
  export function useGameStoreValue(key, fallback) {
22
23
  return useGameStore((ctx) => ctx.game.store.get(key) ?? fallback);
23
24
  }
25
+ /** UI command surface. Hosted commands return a promise; await it to display acknowledgement or rejection. Registration and runAs remain trusted local APIs. */
24
26
  export function useGame() {
25
27
  const ctx = useGameContext();
26
- return useMemo(() => ({ commands: ctx.game.commands, events: ctx.game.events }), [ctx]);
28
+ return useMemo(() => ({ commands: { ...ctx.game.commands, run: (name, input) => dispatchCommand(ctx, name, input) }, events: ctx.game.events }), [ctx]);
27
29
  }
28
30
  export function usePlayer() {
29
31
  const ctx = useGameContext();
@@ -110,7 +110,7 @@ export interface HudTheme {
110
110
  ring: string;
111
111
  skin?: HudSkin;
112
112
  }
113
- /** Emits the CSS custom properties for a theme — spread onto any HUD ancestor to re-skin the subtree. */
113
+ /** Emits theme CSS properties, resetting inherited bar fills when absent. Spread onto a HUD ancestor. */
114
114
  export declare function hudThemeVars(theme: HudTheme): CSSProperties;
115
115
  /** Default theme tokens for previews/scaffold — not a finished game look; author a custom theme. */
116
116
  export declare const defaultHudTheme: HudTheme;
package/dist/hudTheme.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {} from "react";
2
2
  import { hudSkinVars } from "./hudSkin.js";
3
3
  export { hudSkinVars } from "./hudSkin.js";
4
- /** Emits the CSS custom properties for a theme — spread onto any HUD ancestor to re-skin the subtree. */
4
+ /** Emits theme CSS properties, resetting inherited bar fills when absent. Spread onto a HUD ancestor. */
5
5
  export function hudThemeVars(theme) {
6
6
  const { palette, surface, status, font, rarity, frame, bar, slot } = theme;
7
7
  return {
@@ -13,7 +13,8 @@ export function hudThemeVars(theme) {
13
13
  "--jg-slot-slice": "0",
14
14
  "--jg-slot-scale": "1",
15
15
  "--jg-bar-skin-track": "none",
16
- "--jg-bar-skin-fill": "none",
16
+ // `initial` clears inherited skins and lets var() fall back to the bar's palette color.
17
+ "--jg-bar-skin-fill": "initial",
17
18
  "--jg-skin-cursor": "auto",
18
19
  ...hudSkinVars(theme.skin),
19
20
  // atomic-bar tokens (same names the bars read — #1033)
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from "./chatBubbles.js";
5
5
  export * from "./voice.js";
6
6
  export * from "./social.js";
7
7
  export * from "./hooks.js";
8
+ export * from "./browserLifecycle.js";
8
9
  export * from "./useDebouncedCommit.js";
9
10
  export * from "./store.js";
10
11
  export * from "./settings.js";
@@ -72,3 +73,5 @@ export * from "./previewFixtures.js";
72
73
  export * from "./streetGeometryPreview.js";
73
74
  export * from "./saveSlots.js";
74
75
  export * from "./cameraShake.js";
76
+ export * from "./dialogBehavior.js";
77
+ export * from "./scrollRail.js";
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export * from "./chatBubbles.js";
5
5
  export * from "./voice.js";
6
6
  export * from "./social.js";
7
7
  export * from "./hooks.js";
8
+ export * from "./browserLifecycle.js";
8
9
  export * from "./useDebouncedCommit.js";
9
10
  export * from "./store.js";
10
11
  export * from "./settings.js";
@@ -72,3 +73,5 @@ export * from "./previewFixtures.js";
72
73
  export * from "./streetGeometryPreview.js";
73
74
  export * from "./saveSlots.js";
74
75
  export * from "./cameraShake.js";
76
+ export * from "./dialogBehavior.js";
77
+ export * from "./scrollRail.js";