@mentra/miniapp 3.2.1-beta.379 → 3.2.1-beta.392

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.
@@ -10,6 +10,7 @@ export type { MiniappHeaderProps } from "./MiniappHeader";
10
10
  export { MentraProvider } from "./MentraProvider";
11
11
  export type { MentraProviderProps } from "./MentraProvider";
12
12
  export { useRpc, type RpcCallable } from "./useRpc";
13
+ export { useHistoryState, type SetHistoryState } from "./useHistoryState";
13
14
  export { StreamPreview } from "./StreamPreview";
14
15
  export type { StreamPreviewProps, StreamPreviewStatus, PreviewFit, PreviewConnectionState } from "./StreamPreview";
15
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAA;AACzC,YAAY,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAA;AACpD,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,qBAAqB,EAAC,MAAM,yBAAyB,CAAA;AAC7D,YAAY,EAAC,4BAA4B,EAAC,MAAM,yBAAyB,CAAA;AACzE,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAC7C,YAAY,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAC/C,YAAY,EAAC,mBAAmB,EAAC,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAC,MAAM,EAAE,KAAK,WAAW,EAAC,MAAM,UAAU,CAAA;AACjD,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAC7C,YAAY,EAAC,kBAAkB,EAAE,mBAAmB,EAAE,UAAU,EAAE,sBAAsB,EAAC,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAA;AACzC,YAAY,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAA;AACpD,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,qBAAqB,EAAC,MAAM,yBAAyB,CAAA;AAC7D,YAAY,EAAC,4BAA4B,EAAC,MAAM,yBAAyB,CAAA;AACzE,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAC7C,YAAY,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAC/C,YAAY,EAAC,mBAAmB,EAAC,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAC,MAAM,EAAE,KAAK,WAAW,EAAC,MAAM,UAAU,CAAA;AACjD,OAAO,EAAC,eAAe,EAAE,KAAK,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACvE,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAC7C,YAAY,EAAC,kBAAkB,EAAE,mBAAmB,EAAE,UAAU,EAAE,sBAAsB,EAAC,MAAM,iBAAiB,CAAA"}
@@ -6,5 +6,6 @@ export { useCapsuleHeaderStyle } from "./useCapsuleHeaderStyle";
6
6
  export { MiniappHeader } from "./MiniappHeader";
7
7
  export { MentraProvider } from "./MentraProvider";
8
8
  export { useRpc } from "./useRpc";
9
+ export { useHistoryState } from "./useHistoryState";
9
10
  export { StreamPreview } from "./StreamPreview";
10
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAA;AAEzC,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,qBAAqB,EAAC,MAAM,yBAAyB,CAAA;AAE7D,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAE7C,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAE/C,OAAO,EAAC,MAAM,EAAmB,MAAM,UAAU,CAAA;AACjD,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAA;AAEzC,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAC,qBAAqB,EAAC,MAAM,yBAAyB,CAAA;AAE7D,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA;AAE7C,OAAO,EAAC,cAAc,EAAC,MAAM,kBAAkB,CAAA;AAE/C,OAAO,EAAC,MAAM,EAAmB,MAAM,UAAU,CAAA;AACjD,OAAO,EAAC,eAAe,EAAuB,MAAM,mBAAmB,CAAA;AACvE,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAA"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @fileoverview useHistoryState — `useState` whose changes are browser history
3
+ * entries, so the phone's back gesture steps back through them.
4
+ *
5
+ * The host sends the back gesture (iOS edge swipe, Android back) to the
6
+ * miniapp's WebView history first, and minimizes the miniapp only once that
7
+ * history is empty. Screens, tabs and dialogs kept in plain React state never
8
+ * create history, so back skips past them and minimizes the whole miniapp.
9
+ * This hook keeps its value in `history.state` instead:
10
+ *
11
+ * - Setting a new value pushes a history entry. Back pops it, and the hook
12
+ * re-renders with the previous value.
13
+ * - Setting the value back to what it was before its latest change(s) goes
14
+ * back instead of pushing. Closing a dialog with its own button therefore
15
+ * consumes the dialog's entry, and the next back gesture does not reopen it.
16
+ *
17
+ * Routers in hash or browser mode (react-router's `HashRouter`, wouter's
18
+ * `useHashLocation`) already create history for pages; use this hook for UI
19
+ * state that is not a route. Every hook on the page shares one history entry
20
+ * per step, and `key` names the value within it, so components passing the
21
+ * same key share the same value. Values are stored as structured clones.
22
+ */
23
+ export type SetHistoryState<T> = (value: T | ((previous: T) => T)) => void;
24
+ /**
25
+ * Like `useState`, but each change is a history entry that the phone's back
26
+ * gesture undoes. `key` names the value in history state and must be unique
27
+ * per piece of state on the page.
28
+ *
29
+ * ```tsx
30
+ * const [tab, setTab] = useHistoryState<"home" | "settings">("tab", "home")
31
+ * const [pickerOpen, setPickerOpen] = useHistoryState("languagePicker", false)
32
+ * ```
33
+ */
34
+ export declare function useHistoryState<T>(key: string, initialValue: T): [T, SetHistoryState<T>];
35
+ //# sourceMappingURL=useHistoryState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useHistoryState.d.ts","sourceRoot":"","sources":["../../src/react/useHistoryState.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAyFH,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAAA;AAE1E;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,CAYxF"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @fileoverview useHistoryState — `useState` whose changes are browser history
3
+ * entries, so the phone's back gesture steps back through them.
4
+ *
5
+ * The host sends the back gesture (iOS edge swipe, Android back) to the
6
+ * miniapp's WebView history first, and minimizes the miniapp only once that
7
+ * history is empty. Screens, tabs and dialogs kept in plain React state never
8
+ * create history, so back skips past them and minimizes the whole miniapp.
9
+ * This hook keeps its value in `history.state` instead:
10
+ *
11
+ * - Setting a new value pushes a history entry. Back pops it, and the hook
12
+ * re-renders with the previous value.
13
+ * - Setting the value back to what it was before its latest change(s) goes
14
+ * back instead of pushing. Closing a dialog with its own button therefore
15
+ * consumes the dialog's entry, and the next back gesture does not reopen it.
16
+ *
17
+ * Routers in hash or browser mode (react-router's `HashRouter`, wouter's
18
+ * `useHashLocation`) already create history for pages; use this hook for UI
19
+ * state that is not a route. Every hook on the page shares one history entry
20
+ * per step, and `key` names the value within it, so components passing the
21
+ * same key share the same value. Values are stored as structured clones.
22
+ */
23
+ import { useCallback, useRef, useSyncExternalStore } from "react";
24
+ const STATE_KEY = "__mentraHistoryState";
25
+ const EMPTY = { values: {}, trail: [] };
26
+ let cachedState = null;
27
+ let cachedEntry = EMPTY;
28
+ /** This hook's slice of the current entry, cached so snapshots stay referentially stable. */
29
+ function currentEntry() {
30
+ if (typeof window === "undefined")
31
+ return EMPTY;
32
+ const state = window.history.state;
33
+ if (state !== cachedState) {
34
+ cachedState = state;
35
+ cachedEntry = state?.[STATE_KEY] ?? EMPTY;
36
+ }
37
+ return cachedEntry;
38
+ }
39
+ const listeners = new Set();
40
+ let boundWindow = null;
41
+ // history.go() lands later, on popstate. A change applied before then would
42
+ // push on top of the entry being left, and the traversal would pop that push
43
+ // instead, so changes wait in order until the traversal lands.
44
+ let traversing = false;
45
+ const pending = [];
46
+ function notify() {
47
+ listeners.forEach((listener) => listener());
48
+ }
49
+ function flush() {
50
+ while (!traversing && pending.length > 0)
51
+ pending.shift()();
52
+ }
53
+ function onPopState() {
54
+ traversing = false;
55
+ notify();
56
+ // Let every popstate listener, including the host's history bridge, see the
57
+ // landed entry before a waiting change pushes the next one.
58
+ setTimeout(flush, 0);
59
+ }
60
+ function subscribe(listener) {
61
+ if (boundWindow !== window) {
62
+ boundWindow?.removeEventListener("popstate", onPopState);
63
+ window.addEventListener("popstate", onPopState);
64
+ boundWindow = window;
65
+ traversing = false;
66
+ pending.length = 0;
67
+ }
68
+ listeners.add(listener);
69
+ return () => listeners.delete(listener);
70
+ }
71
+ function whenSettled(change) {
72
+ if (traversing || pending.length > 0)
73
+ pending.push(change);
74
+ else
75
+ change();
76
+ }
77
+ function apply(key, initialValue, update) {
78
+ const { values, trail } = currentEntry();
79
+ const current = key in values ? values[key] : initialValue;
80
+ const next = typeof update === "function" ? update(current) : update;
81
+ if (Object.is(current, next))
82
+ return;
83
+ for (let index = trail.length - 1; index >= 0 && trail[index].key === key; index--) {
84
+ if (Object.is(trail[index].previous, next)) {
85
+ traversing = true;
86
+ window.history.go(index - trail.length);
87
+ return;
88
+ }
89
+ }
90
+ // Keep other fields (a router's index, the host's depth marker) on the new entry.
91
+ const state = window.history.state;
92
+ const base = state !== null && typeof state === "object" ? state : {};
93
+ const entry = { values: { ...values, [key]: next }, trail: [...trail, { key, previous: current }] };
94
+ window.history.pushState({ ...base, [STATE_KEY]: entry }, "");
95
+ notify();
96
+ }
97
+ /**
98
+ * Like `useState`, but each change is a history entry that the phone's back
99
+ * gesture undoes. `key` names the value in history state and must be unique
100
+ * per piece of state on the page.
101
+ *
102
+ * ```tsx
103
+ * const [tab, setTab] = useHistoryState<"home" | "settings">("tab", "home")
104
+ * const [pickerOpen, setPickerOpen] = useHistoryState("languagePicker", false)
105
+ * ```
106
+ */
107
+ export function useHistoryState(key, initialValue) {
108
+ const initialRef = useRef(initialValue);
109
+ const read = () => {
110
+ const { values } = currentEntry();
111
+ return (key in values ? values[key] : initialRef.current);
112
+ };
113
+ const value = useSyncExternalStore(subscribe, read, () => initialRef.current);
114
+ const setValue = useCallback((update) => whenSettled(() => apply(key, initialRef.current, update)), [key]);
115
+ return [value, setValue];
116
+ }
117
+ //# sourceMappingURL=useHistoryState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useHistoryState.js","sourceRoot":"","sources":["../../src/react/useHistoryState.ts"],"names":[],"mappings":"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"}
@@ -69,6 +69,7 @@ export { useConnected } from "../react/useConnected";
69
69
  export { useSafeArea } from "../react/useSafeArea";
70
70
  export { useCapsuleHeaderStyle } from "../react/useCapsuleHeaderStyle";
71
71
  export { useRpc, type RpcCallable } from "../react/useRpc";
72
+ export { useHistoryState, type SetHistoryState } from "../react/useHistoryState";
72
73
  export { StreamPreview } from "../react/StreamPreview";
73
74
  export type { StreamPreviewProps, StreamPreviewStatus, PreviewFit, PreviewConnectionState } from "../react/StreamPreview";
74
75
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAC,MAAM,eAAe,CAAA;AAC3E,YAAY,EAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,iBAAiB,EAAC,MAAM,eAAe,CAAA;AACnG,OAAO,EAAC,cAAc,EAAE,qBAAqB,EAAC,MAAM,eAAe,CAAA;AAEnE,MAAM,WAAW,cAAc,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAChF;;;;OAIG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,SAAS,GAAG,MAAM,EACrC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,KAAK,GAAG,CAAC,EACrD,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,GACpB,IAAI,CAAA;IAEP;;;OAGG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,SAAS,GAAG,MAAM,EACnC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,KAAK,GAAG,CAAC,EACrD,EAAE,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,GAClC,MAAM,IAAI,CAAA;IAEb;;;;;;;;OAQG;IACH,OAAO,CAAC,CAAC,SAAS,MAAM,SAAS,GAAG,MAAM,EACxC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,KAAK,EACrD,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAC7B,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAEhC;;;OAGG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAElC;;;;OAIG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAEnC;;;;;OAKG;IACH,KAAK,IAAI,IAAI,CAAA;CACd;AAKD,OAAO,EACL,cAAc,EACd,KAAK,mBAAmB,GACzB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAC,cAAc,EAAC,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAC,eAAe,EAAC,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAC,WAAW,EAAC,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAC,qBAAqB,EAAC,MAAM,gCAAgC,CAAA;AACpE,OAAO,EAAC,MAAM,EAAE,KAAK,WAAW,EAAC,MAAM,iBAAiB,CAAA;AACxD,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAA;AACpD,YAAY,EAAC,kBAAkB,EAAE,mBAAmB,EAAE,UAAU,EAAE,sBAAsB,EAAC,MAAM,wBAAwB,CAAA;AAEvH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,WAAW,CAAC,SAAS,SAAS,MAAM,IAAI,cAAc,CAAC,SAAS,CAAC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAC,MAAM,eAAe,CAAA;AAC3E,YAAY,EAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,iBAAiB,EAAC,MAAM,eAAe,CAAA;AACnG,OAAO,EAAC,cAAc,EAAE,qBAAqB,EAAC,MAAM,eAAe,CAAA;AAEnE,MAAM,WAAW,cAAc,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAChF;;;;OAIG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,SAAS,GAAG,MAAM,EACrC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,KAAK,GAAG,CAAC,EACrD,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,GACpB,IAAI,CAAA;IAEP;;;OAGG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,SAAS,GAAG,MAAM,EACnC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,KAAK,GAAG,CAAC,EACrD,EAAE,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,GAClC,MAAM,IAAI,CAAA;IAEb;;;;;;;;OAQG;IACH,OAAO,CAAC,CAAC,SAAS,MAAM,SAAS,GAAG,MAAM,EACxC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,KAAK,EACrD,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAC7B,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAEhC;;;OAGG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAElC;;;;OAIG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IAEnC;;;;;OAKG;IACH,KAAK,IAAI,IAAI,CAAA;CACd;AAKD,OAAO,EACL,cAAc,EACd,KAAK,mBAAmB,GACzB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAC,cAAc,EAAC,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAC,eAAe,EAAC,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAC,WAAW,EAAC,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAC,qBAAqB,EAAC,MAAM,gCAAgC,CAAA;AACpE,OAAO,EAAC,MAAM,EAAE,KAAK,WAAW,EAAC,MAAM,iBAAiB,CAAA;AACxD,OAAO,EAAC,eAAe,EAAE,KAAK,eAAe,EAAC,MAAM,0BAA0B,CAAA;AAC9E,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAA;AACpD,YAAY,EAAC,kBAAkB,EAAE,mBAAmB,EAAE,UAAU,EAAE,sBAAsB,EAAC,MAAM,wBAAwB,CAAA;AAEvH;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,WAAW,CAAC,SAAS,SAAS,MAAM,IAAI,cAAc,CAAC,SAAS,CAAC,CAAA"}
package/dist/ui/index.js CHANGED
@@ -29,5 +29,6 @@ export { useConnected } from "../react/useConnected";
29
29
  export { useSafeArea } from "../react/useSafeArea";
30
30
  export { useCapsuleHeaderStyle } from "../react/useCapsuleHeaderStyle";
31
31
  export { useRpc } from "../react/useRpc";
32
+ export { useHistoryState } from "../react/useHistoryState";
32
33
  export { StreamPreview } from "../react/StreamPreview";
33
34
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,EAAC,cAAc,EAAE,qBAAqB,EAAC,MAAM,eAAe,CAAA;AA2DnE,uEAAuE;AACvE,sEAAsE;AACtE,+DAA+D;AAC/D,OAAO,EACL,cAAc,GAEf,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAC,cAAc,EAAC,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAC,eAAe,EAAC,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAC,WAAW,EAAC,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAC,qBAAqB,EAAC,MAAM,gCAAgC,CAAA;AACpE,OAAO,EAAC,MAAM,EAAmB,MAAM,iBAAiB,CAAA;AACxD,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,EAAC,cAAc,EAAE,qBAAqB,EAAC,MAAM,eAAe,CAAA;AA2DnE,uEAAuE;AACvE,sEAAsE;AACtE,+DAA+D;AAC/D,OAAO,EACL,cAAc,GAEf,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAA;AACpD,OAAO,EAAC,cAAc,EAAC,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAC,eAAe,EAAC,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAC,YAAY,EAAC,MAAM,uBAAuB,CAAA;AAClD,OAAO,EAAC,WAAW,EAAC,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAC,qBAAqB,EAAC,MAAM,gCAAgC,CAAA;AACpE,OAAO,EAAC,MAAM,EAAmB,MAAM,iBAAiB,CAAA;AACxD,OAAO,EAAC,eAAe,EAAuB,MAAM,0BAA0B,CAAA;AAC9E,OAAO,EAAC,aAAa,EAAC,MAAM,wBAAwB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/miniapp",
3
- "version": "3.2.1-beta.379",
3
+ "version": "3.2.1-beta.392",
4
4
  "description": "SDK for building MentraOS local miniapps (browser/WebView)",
5
5
  "author": "Mentra Labs",
6
6
  "license": "Apache-2.0",
@@ -57,7 +57,7 @@
57
57
  }
58
58
  },
59
59
  "dependencies": {
60
- "@mentra/cloud-protocol": "3.2.1-beta.379",
60
+ "@mentra/cloud-protocol": "3.2.1-beta.392",
61
61
  "eventemitter3": "^5.0.1"
62
62
  },
63
63
  "devDependencies": {
@@ -10,5 +10,6 @@ export type {MiniappHeaderProps} from "./MiniappHeader"
10
10
  export {MentraProvider} from "./MentraProvider"
11
11
  export type {MentraProviderProps} from "./MentraProvider"
12
12
  export {useRpc, type RpcCallable} from "./useRpc"
13
+ export {useHistoryState, type SetHistoryState} from "./useHistoryState"
13
14
  export {StreamPreview} from "./StreamPreview"
14
15
  export type {StreamPreviewProps, StreamPreviewStatus, PreviewFit, PreviewConnectionState} from "./StreamPreview"
@@ -0,0 +1,135 @@
1
+ /**
2
+ * @fileoverview useHistoryState — `useState` whose changes are browser history
3
+ * entries, so the phone's back gesture steps back through them.
4
+ *
5
+ * The host sends the back gesture (iOS edge swipe, Android back) to the
6
+ * miniapp's WebView history first, and minimizes the miniapp only once that
7
+ * history is empty. Screens, tabs and dialogs kept in plain React state never
8
+ * create history, so back skips past them and minimizes the whole miniapp.
9
+ * This hook keeps its value in `history.state` instead:
10
+ *
11
+ * - Setting a new value pushes a history entry. Back pops it, and the hook
12
+ * re-renders with the previous value.
13
+ * - Setting the value back to what it was before its latest change(s) goes
14
+ * back instead of pushing. Closing a dialog with its own button therefore
15
+ * consumes the dialog's entry, and the next back gesture does not reopen it.
16
+ *
17
+ * Routers in hash or browser mode (react-router's `HashRouter`, wouter's
18
+ * `useHashLocation`) already create history for pages; use this hook for UI
19
+ * state that is not a route. Every hook on the page shares one history entry
20
+ * per step, and `key` names the value within it, so components passing the
21
+ * same key share the same value. Values are stored as structured clones.
22
+ */
23
+
24
+ import {useCallback, useRef, useSyncExternalStore} from "react"
25
+
26
+ const STATE_KEY = "__mentraHistoryState"
27
+
28
+ interface Entry {
29
+ values: Record<string, unknown>
30
+ /** One record per entry this hook pushed, oldest first, with the value it replaced. */
31
+ trail: Array<{key: string; previous: unknown}>
32
+ }
33
+
34
+ const EMPTY: Entry = {values: {}, trail: []}
35
+
36
+ let cachedState: unknown = null
37
+ let cachedEntry: Entry = EMPTY
38
+
39
+ /** This hook's slice of the current entry, cached so snapshots stay referentially stable. */
40
+ function currentEntry(): Entry {
41
+ if (typeof window === "undefined") return EMPTY
42
+ const state: unknown = window.history.state
43
+ if (state !== cachedState) {
44
+ cachedState = state
45
+ cachedEntry = (state as Record<string, Entry | undefined> | null)?.[STATE_KEY] ?? EMPTY
46
+ }
47
+ return cachedEntry
48
+ }
49
+
50
+ const listeners = new Set<() => void>()
51
+ let boundWindow: Window | null = null
52
+ // history.go() lands later, on popstate. A change applied before then would
53
+ // push on top of the entry being left, and the traversal would pop that push
54
+ // instead, so changes wait in order until the traversal lands.
55
+ let traversing = false
56
+ const pending: Array<() => void> = []
57
+
58
+ function notify() {
59
+ listeners.forEach((listener) => listener())
60
+ }
61
+
62
+ function flush() {
63
+ while (!traversing && pending.length > 0) pending.shift()!()
64
+ }
65
+
66
+ function onPopState() {
67
+ traversing = false
68
+ notify()
69
+ // Let every popstate listener, including the host's history bridge, see the
70
+ // landed entry before a waiting change pushes the next one.
71
+ setTimeout(flush, 0)
72
+ }
73
+
74
+ function subscribe(listener: () => void): () => void {
75
+ if (boundWindow !== window) {
76
+ boundWindow?.removeEventListener("popstate", onPopState)
77
+ window.addEventListener("popstate", onPopState)
78
+ boundWindow = window
79
+ traversing = false
80
+ pending.length = 0
81
+ }
82
+ listeners.add(listener)
83
+ return () => listeners.delete(listener)
84
+ }
85
+
86
+ function whenSettled(change: () => void) {
87
+ if (traversing || pending.length > 0) pending.push(change)
88
+ else change()
89
+ }
90
+
91
+ function apply(key: string, initialValue: unknown, update: unknown) {
92
+ const {values, trail} = currentEntry()
93
+ const current = key in values ? values[key] : initialValue
94
+ const next = typeof update === "function" ? (update as (previous: unknown) => unknown)(current) : update
95
+ if (Object.is(current, next)) return
96
+ for (let index = trail.length - 1; index >= 0 && trail[index].key === key; index--) {
97
+ if (Object.is(trail[index].previous, next)) {
98
+ traversing = true
99
+ window.history.go(index - trail.length)
100
+ return
101
+ }
102
+ }
103
+ // Keep other fields (a router's index, the host's depth marker) on the new entry.
104
+ const state: unknown = window.history.state
105
+ const base = state !== null && typeof state === "object" ? state : {}
106
+ const entry: Entry = {values: {...values, [key]: next}, trail: [...trail, {key, previous: current}]}
107
+ window.history.pushState({...base, [STATE_KEY]: entry}, "")
108
+ notify()
109
+ }
110
+
111
+ export type SetHistoryState<T> = (value: T | ((previous: T) => T)) => void
112
+
113
+ /**
114
+ * Like `useState`, but each change is a history entry that the phone's back
115
+ * gesture undoes. `key` names the value in history state and must be unique
116
+ * per piece of state on the page.
117
+ *
118
+ * ```tsx
119
+ * const [tab, setTab] = useHistoryState<"home" | "settings">("tab", "home")
120
+ * const [pickerOpen, setPickerOpen] = useHistoryState("languagePicker", false)
121
+ * ```
122
+ */
123
+ export function useHistoryState<T>(key: string, initialValue: T): [T, SetHistoryState<T>] {
124
+ const initialRef = useRef(initialValue)
125
+ const read = () => {
126
+ const {values} = currentEntry()
127
+ return (key in values ? values[key] : initialRef.current) as T
128
+ }
129
+ const value = useSyncExternalStore(subscribe, read, () => initialRef.current)
130
+ const setValue = useCallback<SetHistoryState<T>>(
131
+ (update) => whenSettled(() => apply(key, initialRef.current, update)),
132
+ [key],
133
+ )
134
+ return [value, setValue]
135
+ }
package/src/ui/index.ts CHANGED
@@ -93,6 +93,7 @@ export {useConnected} from "../react/useConnected"
93
93
  export {useSafeArea} from "../react/useSafeArea"
94
94
  export {useCapsuleHeaderStyle} from "../react/useCapsuleHeaderStyle"
95
95
  export {useRpc, type RpcCallable} from "../react/useRpc"
96
+ export {useHistoryState, type SetHistoryState} from "../react/useHistoryState"
96
97
  export {StreamPreview} from "../react/StreamPreview"
97
98
  export type {StreamPreviewProps, StreamPreviewStatus, PreviewFit, PreviewConnectionState} from "../react/StreamPreview"
98
99