@threenative/ui 0.3.6 → 0.3.8

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/dist/index.d.ts CHANGED
@@ -31,9 +31,25 @@ declare function UiLayer({ children }: {
31
31
  * The UI holds a mirror, never the game object: on every native target the game is in another
32
32
  * realm, and a HUD written against a live store would work on web and read nothing on a phone.
33
33
  * The mirror is fed at the store's published cadence, which is not the game loop.
34
+ *
35
+ * **Reads `undefined` while the loading cover is up**, so a HUD cannot draw over the loading bar
36
+ * and no game writes a `loaded` field to prevent it. A UI that draws its own loading screen reads
37
+ * through `useUiStateDuringStartup`.
34
38
  */
35
39
  declare function useUiState<TState extends object>(): TState | undefined;
36
40
  declare function useUiState<TState extends object, TSelected>(selector: (state: TState) => TSelected): TSelected | undefined;
41
+ /**
42
+ * The named override of `useUiState`'s loading-cover gate, for a UI that draws the loading screen
43
+ * itself (a staged overlay reading the startup progress the game publishes). It reads state while
44
+ * the cover is up. `reason` is required and reported once as `TN_UI_STARTUP_OVERRIDE <reason>`,
45
+ * so a HUD that merely skipped the gate shows up in the log.
46
+ */
47
+ declare function useUiStateDuringStartup<TState extends object>(options: {
48
+ readonly reason: string;
49
+ }): TState | undefined;
50
+ declare function useUiStateDuringStartup<TState extends object, TSelected>(options: {
51
+ readonly reason: string;
52
+ }, selector: (state: TState) => TSelected): TSelected | undefined;
37
53
  /**
38
54
  * Send an intent to the game — `restart`, `pause`, whatever the game named.
39
55
  *
@@ -43,4 +59,4 @@ declare function useUiState<TState extends object, TSelected>(selector: (state:
43
59
  */
44
60
  declare function useUiIntent(): (intent: string, payload?: unknown) => void;
45
61
 
46
- export { DebugOverlay, GameCanvas, UiLayer, useGameState, useUiIntent, useUiState };
62
+ export { DebugOverlay, GameCanvas, UiLayer, useGameState, useUiIntent, useUiState, useUiStateDuringStartup };
package/dist/index.js CHANGED
@@ -109,13 +109,18 @@ function DebugOverlayView() {
109
109
  const clearOutline = () => setSelected(void 0);
110
110
  hostWindow.addEventListener("keydown", toggle);
111
111
  hostWindow.addEventListener("resize", clearOutline);
112
- const timer = hostWindow.setInterval(() => setSnapshot(readSnapshot()), 100);
113
112
  return () => {
114
113
  hostWindow.removeEventListener("keydown", toggle);
115
114
  hostWindow.removeEventListener("resize", clearOutline);
116
- hostWindow.clearInterval(timer);
117
115
  };
118
116
  }, []);
117
+ useEffect(() => {
118
+ if (!open || typeof window === "undefined") return void 0;
119
+ const hostWindow = window;
120
+ setSnapshot(readSnapshot());
121
+ const timer = hostWindow.setInterval(() => setSnapshot(readSnapshot()), 100);
122
+ return () => hostWindow.clearInterval(timer);
123
+ }, [open]);
119
124
  const capture = useCallback(() => {
120
125
  const geometry = devTools()?.geometry;
121
126
  if (typeof geometry !== "function" || capturing) return;
@@ -520,12 +525,30 @@ function UiLayer({ children }) {
520
525
  ] });
521
526
  }
522
527
  function useUiState(selector) {
523
- const { mirror } = useUiLayer("useUiState");
528
+ return useMirrorState("useUiState", true, selector);
529
+ }
530
+ var reportedOverrides = /* @__PURE__ */ new Set();
531
+ function useUiStateDuringStartup(options, selector) {
532
+ const reason = options.reason;
533
+ if (typeof reason !== "string" || reason.trim().length === 0) {
534
+ throw new Error(
535
+ "TN_UI_STARTUP_OVERRIDE_REASON: useUiStateDuringStartup needs a non-empty reason."
536
+ );
537
+ }
538
+ if (!reportedOverrides.has(reason)) {
539
+ reportedOverrides.add(reason);
540
+ console.info(`TN_UI_STARTUP_OVERRIDE ${reason}`);
541
+ }
542
+ return useMirrorState("useUiStateDuringStartup", false, selector);
543
+ }
544
+ function useMirrorState(name, hideWhileCovered, selector) {
545
+ const { mirror } = useUiLayer(name);
524
546
  const selectorRef = useRef(selector);
525
547
  selectorRef.current = selector;
526
548
  return useSyncExternalStore(
527
549
  (onChange) => mirror.subscribe(onChange),
528
550
  () => {
551
+ if (hideWhileCovered && mirror.covered()) return void 0;
529
552
  const state = mirror.get();
530
553
  if (state === void 0) return void 0;
531
554
  return selectorRef.current === void 0 ? state : selectorRef.current(state);
@@ -541,4 +564,4 @@ function useUiIntent() {
541
564
  );
542
565
  }
543
566
 
544
- export { DebugOverlay, GameCanvas, UiLayer, useGameState, useUiIntent, useUiState };
567
+ export { DebugOverlay, GameCanvas, UiLayer, useGameState, useUiIntent, useUiState, useUiStateDuringStartup };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@threenative/ui",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "license": "MIT",
5
5
  "description": "React bindings for mounting a ThreeNative game and reading throttled state.",
6
6
  "keywords": [
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "sideEffects": false,
43
43
  "peerDependencies": {
44
- "@threenative/core": ">=0.3.6 <0.4.0",
44
+ "@threenative/core": ">=0.3.8 <0.4.0",
45
45
  "react": ">=19.2.0 <20",
46
46
  "react-dom": ">=19.2.0 <20"
47
47
  },
@@ -56,7 +56,7 @@
56
56
  "tsup": "8.5.1",
57
57
  "typescript": "npm:@typescript/typescript6@6.0.2",
58
58
  "@typescript/native": "npm:typescript@7.0.2",
59
- "@threenative/core": "0.3.6"
59
+ "@threenative/core": "0.3.8"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "tsup --config tsup.config.ts && publint",