@appstrata/react 0.1.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/README.md +863 -0
- package/dist/deep-equal.d.ts +10 -0
- package/dist/deep-equal.d.ts.map +1 -0
- package/dist/deep-equal.js +28 -0
- package/dist/hooks.d.ts +207 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +321 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/provider.d.ts +56 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +52 -0
- package/dist/store.d.ts +33 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +72 -0
- package/dist/use-interaction.d.ts +35 -0
- package/dist/use-interaction.d.ts.map +1 -0
- package/dist/use-interaction.js +46 -0
- package/dist/use-media-cache.d.ts +56 -0
- package/dist/use-media-cache.d.ts.map +1 -0
- package/dist/use-media-cache.js +100 -0
- package/dist/use-proxy.d.ts +68 -0
- package/dist/use-proxy.d.ts.map +1 -0
- package/dist/use-proxy.js +90 -0
- package/dist/use-static.d.ts +36 -0
- package/dist/use-static.d.ts.map +1 -0
- package/dist/use-static.js +45 -0
- package/dist/use-storage.d.ts +72 -0
- package/dist/use-storage.d.ts.map +1 -0
- package/dist/use-storage.js +161 -0
- package/package.json +58 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep equality comparison for plain objects, arrays, and primitives.
|
|
3
|
+
*
|
|
4
|
+
* Used by ContextStore to determine which parts of AppContext
|
|
5
|
+
* actually changed, enabling granular re-renders.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function deepEqual(a: unknown, b: unknown): boolean;
|
|
10
|
+
//# sourceMappingURL=deep-equal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deep-equal.d.ts","sourceRoot":"","sources":["../src/deep-equal.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAkBzD"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep equality comparison for plain objects, arrays, and primitives.
|
|
3
|
+
*
|
|
4
|
+
* Used by ContextStore to determine which parts of AppContext
|
|
5
|
+
* actually changed, enabling granular re-renders.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export function deepEqual(a, b) {
|
|
10
|
+
if (a === b)
|
|
11
|
+
return true;
|
|
12
|
+
if (typeof a !== typeof b)
|
|
13
|
+
return false;
|
|
14
|
+
if (typeof a !== "object" || a === null || b === null)
|
|
15
|
+
return false;
|
|
16
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
17
|
+
return false;
|
|
18
|
+
const keysA = Object.keys(a);
|
|
19
|
+
const keysB = Object.keys(b);
|
|
20
|
+
if (keysA.length !== keysB.length)
|
|
21
|
+
return false;
|
|
22
|
+
for (const key of keysA) {
|
|
23
|
+
if (!deepEqual(a[key], b[key])) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core React hooks for AppStrata.
|
|
3
|
+
*
|
|
4
|
+
* All hooks must be used within an AppStrataProvider.
|
|
5
|
+
*/
|
|
6
|
+
import { LifecyclePhase, type AppContext, type Capability, type SignagePlayer, type Resource, type DeviceInfo, type Source } from "@appstrata/core";
|
|
7
|
+
/**
|
|
8
|
+
* Access the complete AppContext.
|
|
9
|
+
* Re-renders on ANY context change.
|
|
10
|
+
*
|
|
11
|
+
* Returns null while waiting for the player READY message.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* const context = useAppContext();
|
|
16
|
+
* if (!context) return <div>Loading...</div>;
|
|
17
|
+
* return <h1>{context.config.title as string}</h1>;
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function useAppContext(): AppContext | null;
|
|
21
|
+
/**
|
|
22
|
+
* Access just the config portion of AppContext.
|
|
23
|
+
* Only re-renders when config actually changes (deep comparison).
|
|
24
|
+
*
|
|
25
|
+
* @typeParam T - The expected config shape
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* const config = useConfig<{ title: string; bgcolor: string }>();
|
|
30
|
+
* if (!config) return null;
|
|
31
|
+
* return <h1 style={{ background: config.bgcolor }}>{config.title}</h1>;
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function useConfig<T extends Record<string, unknown> = Record<string, unknown>>(): T | null;
|
|
35
|
+
/**
|
|
36
|
+
* Access just the resources from AppContext.
|
|
37
|
+
* Only re-renders when resources actually change (deep comparison).
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* const resources = useResources();
|
|
42
|
+
* const fonts = resources?.filter(r => r.category === "font");
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function useResources(): Resource[] | null;
|
|
46
|
+
/**
|
|
47
|
+
* Result of useResource hook.
|
|
48
|
+
*/
|
|
49
|
+
export interface UseResourceResult {
|
|
50
|
+
/** Resolved URI (including data URIs for inline sources). Null if not found or not loaded. */
|
|
51
|
+
uri: string | null;
|
|
52
|
+
/** Raw Source object. Null if not found or not loaded. */
|
|
53
|
+
source: Source | null;
|
|
54
|
+
/** Full Resource object for advanced access. Null if not found or not loaded. */
|
|
55
|
+
resource: Resource | null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Look up a single resource by category and name, returning a ready-to-use URI.
|
|
59
|
+
* Only re-renders when resources actually change (deep comparison).
|
|
60
|
+
*
|
|
61
|
+
* Optionally resolves a named variant instead of the main source.
|
|
62
|
+
*
|
|
63
|
+
* @param category - Resource category (e.g. "font", "image")
|
|
64
|
+
* @param name - Resource name (e.g. "heading", "logo")
|
|
65
|
+
* @param variant - Optional variant name (e.g. "thumbnail")
|
|
66
|
+
* @returns Object with `uri`, `source`, and `resource` fields
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* const { uri: fontUri } = useResource("font", "heading");
|
|
71
|
+
* const { uri: logoUri } = useResource("image", "logo");
|
|
72
|
+
*
|
|
73
|
+
* // With variant:
|
|
74
|
+
* const { uri: thumbUri } = useResource("image", "logo", "thumbnail");
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export declare function useResource(category: string, name: string, variant?: string): UseResourceResult;
|
|
78
|
+
/**
|
|
79
|
+
* Load all font resources from the player context.
|
|
80
|
+
*
|
|
81
|
+
* Injects `<link>` stylesheet elements for every resource with
|
|
82
|
+
* `category === "font"`. Re-loads when the resource list changes
|
|
83
|
+
* (e.g. during CMS live preview).
|
|
84
|
+
*
|
|
85
|
+
* @returns `true` once all font stylesheets have loaded, `false` while loading
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```tsx
|
|
89
|
+
* const fontsLoaded = useFonts();
|
|
90
|
+
* if (!fontsLoaded) return <div>Loading fonts...</div>;
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare function useFonts(): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Load all stylesheet resources from the player context.
|
|
96
|
+
*
|
|
97
|
+
* Injects `<link>` stylesheet elements for every resource with
|
|
98
|
+
* `category === "stylesheet"`. Re-loads when the resource list changes
|
|
99
|
+
* (e.g. during CMS live preview).
|
|
100
|
+
*
|
|
101
|
+
* @returns `true` once all stylesheets have loaded, `false` while loading
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```tsx
|
|
105
|
+
* const stylesLoaded = useStylesheets();
|
|
106
|
+
* if (!stylesLoaded) return <div>Loading styles...</div>;
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
export declare function useStylesheets(): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Access just the device information from AppContext.
|
|
112
|
+
* Only re-renders when device info actually changes (deep comparison).
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```tsx
|
|
116
|
+
* const device = useDevice();
|
|
117
|
+
* if (!device) return null;
|
|
118
|
+
* return <footer>Playing on: {device.name}</footer>;
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export declare function useDevice(): DeviceInfo | null;
|
|
122
|
+
/**
|
|
123
|
+
* Access the SignagePlayer instance directly.
|
|
124
|
+
* Returns a stable reference (singleton) -- never triggers re-renders.
|
|
125
|
+
*
|
|
126
|
+
* Use this for imperative operations or capabilities not covered by
|
|
127
|
+
* dedicated hooks.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```tsx
|
|
131
|
+
* const player = usePlayer();
|
|
132
|
+
* const handleComplete = () => player.notifyComplete();
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export declare function usePlayer(): SignagePlayer;
|
|
136
|
+
/**
|
|
137
|
+
* Check if a capability is available.
|
|
138
|
+
*
|
|
139
|
+
* Returns a tri-state value:
|
|
140
|
+
* - `null` while the player hasn't initialized yet (pre-READY)
|
|
141
|
+
* - `true` when the capability is supported
|
|
142
|
+
* - `false` when the capability is NOT supported
|
|
143
|
+
*
|
|
144
|
+
* Re-renders on any context change (delegates to useAppContext internally).
|
|
145
|
+
*
|
|
146
|
+
* @param capability - The capability to check (e.g., "storage", "proxy")
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```tsx
|
|
150
|
+
* const hasStorage = useCapability("storage");
|
|
151
|
+
* if (hasStorage === null) return <div>Initializing...</div>;
|
|
152
|
+
* if (hasStorage) { ... }
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export declare function useCapability(capability: Capability): boolean | null;
|
|
156
|
+
/**
|
|
157
|
+
* Lifecycle callbacks interface.
|
|
158
|
+
*/
|
|
159
|
+
export interface LifecycleCallbacks {
|
|
160
|
+
/** Called when app is about to become visible */
|
|
161
|
+
onShow?: () => void | (() => void);
|
|
162
|
+
/** Called when app is now visible and should start playback */
|
|
163
|
+
onStart?: () => void | (() => void);
|
|
164
|
+
/** Called when app is about to be hidden */
|
|
165
|
+
onHide?: () => void | (() => void);
|
|
166
|
+
/** Called when app is now hidden and should stop playback */
|
|
167
|
+
onStop?: () => void | (() => void);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Subscribe to lifecycle events.
|
|
171
|
+
* Callbacks can optionally return cleanup functions.
|
|
172
|
+
*
|
|
173
|
+
* Auto-subscribes on mount, auto-cleans up on unmount.
|
|
174
|
+
*
|
|
175
|
+
* **Note:** Because `useEffect` runs after paint, handlers for transient phases
|
|
176
|
+
* like `Show` and `Hide` may not fire if the phase has already passed by the
|
|
177
|
+
* time the effect registers them. Use `onStart`/`onStop` for work that must
|
|
178
|
+
* not be skipped, or read `useLifecyclePhase()` to derive state from the
|
|
179
|
+
* current phase.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```tsx
|
|
183
|
+
* useLifecycle({
|
|
184
|
+
* onStart: () => {
|
|
185
|
+
* const interval = setInterval(tick, 1000);
|
|
186
|
+
* return () => clearInterval(interval); // cleanup
|
|
187
|
+
* },
|
|
188
|
+
* onStop: () => console.log("Stopped"),
|
|
189
|
+
* });
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
export declare function useLifecycle(callbacks: LifecycleCallbacks): void;
|
|
193
|
+
/**
|
|
194
|
+
* Read the current lifecycle phase reactively.
|
|
195
|
+
*
|
|
196
|
+
* Re-renders whenever the phase transitions. Returns `LifecyclePhase.None`
|
|
197
|
+
* before the handshake completes.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```tsx
|
|
201
|
+
* const phase = useLifecyclePhase();
|
|
202
|
+
* const isPlaying = phase === LifecyclePhase.Start;
|
|
203
|
+
* const isVisible = phase === LifecyclePhase.Show || phase === LifecyclePhase.Start;
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
export declare function useLifecyclePhase(): LifecyclePhase;
|
|
207
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,KAAK,MAAM,EAAgB,MAAM,iBAAiB,CAAC;AA8BlK;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAOjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAOjG;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,IAAI,QAAQ,EAAE,GAAG,IAAI,CAOhD;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,8FAA8F;IAC9F,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,iFAAiF;IACjF,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAiB/F;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,IAAI,OAAO,CAkBlC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAkBxC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,IAAI,UAAU,GAAG,IAAI,CAO7C;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,IAAI,aAAa,CAGzC;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,GAAG,IAAI,CAIpE;AAMD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IACpC,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;IACnC,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAoChE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAgBlD"}
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core React hooks for AppStrata.
|
|
3
|
+
*
|
|
4
|
+
* All hooks must be used within an AppStrataProvider.
|
|
5
|
+
*/
|
|
6
|
+
import { useContext, useSyncExternalStore, useState, useEffect, useRef, useMemo } from "react";
|
|
7
|
+
import { AppStrataContext } from "./provider.js";
|
|
8
|
+
import { createLogger } from "@appstrata/core";
|
|
9
|
+
import { resolveSourceUri, loadFonts, loadStylesheets } from "@appstrata/utils";
|
|
10
|
+
const logger = createLogger("ReactHooks");
|
|
11
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
12
|
+
// INTERNAL HELPER
|
|
13
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
14
|
+
/**
|
|
15
|
+
* Internal hook to access the AppStrata React context.
|
|
16
|
+
* Throws if used outside of AppStrataProvider.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
function useAppStrataContext() {
|
|
21
|
+
const context = useContext(AppStrataContext);
|
|
22
|
+
if (!context) {
|
|
23
|
+
throw new Error("AppStrata hooks must be used within <AppStrataProvider>. " +
|
|
24
|
+
"Wrap your app with <AppStrataProvider> in your root component.");
|
|
25
|
+
}
|
|
26
|
+
return context;
|
|
27
|
+
}
|
|
28
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
29
|
+
// CONTEXT HOOKS (useSyncExternalStore-based)
|
|
30
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
31
|
+
/**
|
|
32
|
+
* Access the complete AppContext.
|
|
33
|
+
* Re-renders on ANY context change.
|
|
34
|
+
*
|
|
35
|
+
* Returns null while waiting for the player READY message.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* const context = useAppContext();
|
|
40
|
+
* if (!context) return <div>Loading...</div>;
|
|
41
|
+
* return <h1>{context.config.title as string}</h1>;
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function useAppContext() {
|
|
45
|
+
const { store } = useAppStrataContext();
|
|
46
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Access just the config portion of AppContext.
|
|
50
|
+
* Only re-renders when config actually changes (deep comparison).
|
|
51
|
+
*
|
|
52
|
+
* @typeParam T - The expected config shape
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* const config = useConfig<{ title: string; bgcolor: string }>();
|
|
57
|
+
* if (!config) return null;
|
|
58
|
+
* return <h1 style={{ background: config.bgcolor }}>{config.title}</h1>;
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function useConfig() {
|
|
62
|
+
const { store } = useAppStrataContext();
|
|
63
|
+
return useSyncExternalStore(store.subscribeConfig, store.getConfigSnapshot, store.getConfigSnapshot);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Access just the resources from AppContext.
|
|
67
|
+
* Only re-renders when resources actually change (deep comparison).
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```tsx
|
|
71
|
+
* const resources = useResources();
|
|
72
|
+
* const fonts = resources?.filter(r => r.category === "font");
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function useResources() {
|
|
76
|
+
const { store } = useAppStrataContext();
|
|
77
|
+
return useSyncExternalStore(store.subscribeResources, store.getResourcesSnapshot, store.getResourcesSnapshot);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Look up a single resource by category and name, returning a ready-to-use URI.
|
|
81
|
+
* Only re-renders when resources actually change (deep comparison).
|
|
82
|
+
*
|
|
83
|
+
* Optionally resolves a named variant instead of the main source.
|
|
84
|
+
*
|
|
85
|
+
* @param category - Resource category (e.g. "font", "image")
|
|
86
|
+
* @param name - Resource name (e.g. "heading", "logo")
|
|
87
|
+
* @param variant - Optional variant name (e.g. "thumbnail")
|
|
88
|
+
* @returns Object with `uri`, `source`, and `resource` fields
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```tsx
|
|
92
|
+
* const { uri: fontUri } = useResource("font", "heading");
|
|
93
|
+
* const { uri: logoUri } = useResource("image", "logo");
|
|
94
|
+
*
|
|
95
|
+
* // With variant:
|
|
96
|
+
* const { uri: thumbUri } = useResource("image", "logo", "thumbnail");
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function useResource(category, name, variant) {
|
|
100
|
+
const resources = useResources();
|
|
101
|
+
return useMemo(() => {
|
|
102
|
+
if (!resources)
|
|
103
|
+
return { uri: null, source: null, resource: null };
|
|
104
|
+
const resource = resources.find(r => r.category === category && r.name === name) ?? null;
|
|
105
|
+
if (!resource)
|
|
106
|
+
return { uri: null, source: null, resource: null };
|
|
107
|
+
const source = variant
|
|
108
|
+
? (resource.variants?.[variant]?.source ?? null)
|
|
109
|
+
: resource.source;
|
|
110
|
+
const uri = source ? resolveSourceUri(source) : null;
|
|
111
|
+
return { uri, source, resource };
|
|
112
|
+
}, [resources, category, name, variant]);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Load all font resources from the player context.
|
|
116
|
+
*
|
|
117
|
+
* Injects `<link>` stylesheet elements for every resource with
|
|
118
|
+
* `category === "font"`. Re-loads when the resource list changes
|
|
119
|
+
* (e.g. during CMS live preview).
|
|
120
|
+
*
|
|
121
|
+
* @returns `true` once all font stylesheets have loaded, `false` while loading
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```tsx
|
|
125
|
+
* const fontsLoaded = useFonts();
|
|
126
|
+
* if (!fontsLoaded) return <div>Loading fonts...</div>;
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export function useFonts() {
|
|
130
|
+
const resources = useResources();
|
|
131
|
+
const [loaded, setLoaded] = useState(false);
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (!resources)
|
|
134
|
+
return;
|
|
135
|
+
const fonts = resources.filter(r => r.category === "font");
|
|
136
|
+
if (fonts.length === 0) {
|
|
137
|
+
setLoaded(true);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
setLoaded(false);
|
|
141
|
+
loadFonts(resources).then(() => setLoaded(true));
|
|
142
|
+
}, [resources]);
|
|
143
|
+
return loaded;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Load all stylesheet resources from the player context.
|
|
147
|
+
*
|
|
148
|
+
* Injects `<link>` stylesheet elements for every resource with
|
|
149
|
+
* `category === "stylesheet"`. Re-loads when the resource list changes
|
|
150
|
+
* (e.g. during CMS live preview).
|
|
151
|
+
*
|
|
152
|
+
* @returns `true` once all stylesheets have loaded, `false` while loading
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```tsx
|
|
156
|
+
* const stylesLoaded = useStylesheets();
|
|
157
|
+
* if (!stylesLoaded) return <div>Loading styles...</div>;
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export function useStylesheets() {
|
|
161
|
+
const resources = useResources();
|
|
162
|
+
const [loaded, setLoaded] = useState(false);
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (!resources)
|
|
165
|
+
return;
|
|
166
|
+
const sheets = resources.filter(r => r.category === "stylesheet");
|
|
167
|
+
if (sheets.length === 0) {
|
|
168
|
+
setLoaded(true);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
setLoaded(false);
|
|
172
|
+
loadStylesheets(resources).then(() => setLoaded(true));
|
|
173
|
+
}, [resources]);
|
|
174
|
+
return loaded;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Access just the device information from AppContext.
|
|
178
|
+
* Only re-renders when device info actually changes (deep comparison).
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```tsx
|
|
182
|
+
* const device = useDevice();
|
|
183
|
+
* if (!device) return null;
|
|
184
|
+
* return <footer>Playing on: {device.name}</footer>;
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
export function useDevice() {
|
|
188
|
+
const { store } = useAppStrataContext();
|
|
189
|
+
return useSyncExternalStore(store.subscribeDevice, store.getDeviceSnapshot, store.getDeviceSnapshot);
|
|
190
|
+
}
|
|
191
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
192
|
+
// PLAYER ACCESS
|
|
193
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
194
|
+
/**
|
|
195
|
+
* Access the SignagePlayer instance directly.
|
|
196
|
+
* Returns a stable reference (singleton) -- never triggers re-renders.
|
|
197
|
+
*
|
|
198
|
+
* Use this for imperative operations or capabilities not covered by
|
|
199
|
+
* dedicated hooks.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```tsx
|
|
203
|
+
* const player = usePlayer();
|
|
204
|
+
* const handleComplete = () => player.notifyComplete();
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
export function usePlayer() {
|
|
208
|
+
const { player } = useAppStrataContext();
|
|
209
|
+
return player;
|
|
210
|
+
}
|
|
211
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
212
|
+
// CAPABILITY CHECK
|
|
213
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
214
|
+
/**
|
|
215
|
+
* Check if a capability is available.
|
|
216
|
+
*
|
|
217
|
+
* Returns a tri-state value:
|
|
218
|
+
* - `null` while the player hasn't initialized yet (pre-READY)
|
|
219
|
+
* - `true` when the capability is supported
|
|
220
|
+
* - `false` when the capability is NOT supported
|
|
221
|
+
*
|
|
222
|
+
* Re-renders on any context change (delegates to useAppContext internally).
|
|
223
|
+
*
|
|
224
|
+
* @param capability - The capability to check (e.g., "storage", "proxy")
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```tsx
|
|
228
|
+
* const hasStorage = useCapability("storage");
|
|
229
|
+
* if (hasStorage === null) return <div>Initializing...</div>;
|
|
230
|
+
* if (hasStorage) { ... }
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
export function useCapability(capability) {
|
|
234
|
+
const context = useAppContext();
|
|
235
|
+
if (context === null)
|
|
236
|
+
return null; // Not yet initialized
|
|
237
|
+
return context.hasCapability(capability);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Subscribe to lifecycle events.
|
|
241
|
+
* Callbacks can optionally return cleanup functions.
|
|
242
|
+
*
|
|
243
|
+
* Auto-subscribes on mount, auto-cleans up on unmount.
|
|
244
|
+
*
|
|
245
|
+
* **Note:** Because `useEffect` runs after paint, handlers for transient phases
|
|
246
|
+
* like `Show` and `Hide` may not fire if the phase has already passed by the
|
|
247
|
+
* time the effect registers them. Use `onStart`/`onStop` for work that must
|
|
248
|
+
* not be skipped, or read `useLifecyclePhase()` to derive state from the
|
|
249
|
+
* current phase.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```tsx
|
|
253
|
+
* useLifecycle({
|
|
254
|
+
* onStart: () => {
|
|
255
|
+
* const interval = setInterval(tick, 1000);
|
|
256
|
+
* return () => clearInterval(interval); // cleanup
|
|
257
|
+
* },
|
|
258
|
+
* onStop: () => console.log("Stopped"),
|
|
259
|
+
* });
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
export function useLifecycle(callbacks) {
|
|
263
|
+
const { player } = useAppStrataContext();
|
|
264
|
+
// Use refs to always call the latest callbacks without re-subscribing
|
|
265
|
+
const callbacksRef = useRef(callbacks);
|
|
266
|
+
callbacksRef.current = callbacks;
|
|
267
|
+
useEffect(() => {
|
|
268
|
+
const cleanups = [];
|
|
269
|
+
player.onShow(() => {
|
|
270
|
+
const cleanup = callbacksRef.current.onShow?.();
|
|
271
|
+
if (typeof cleanup === "function")
|
|
272
|
+
cleanups.push(cleanup);
|
|
273
|
+
});
|
|
274
|
+
player.onStart(() => {
|
|
275
|
+
const cleanup = callbacksRef.current.onStart?.();
|
|
276
|
+
if (typeof cleanup === "function")
|
|
277
|
+
cleanups.push(cleanup);
|
|
278
|
+
});
|
|
279
|
+
player.onHide(() => {
|
|
280
|
+
const cleanup = callbacksRef.current.onHide?.();
|
|
281
|
+
if (typeof cleanup === "function")
|
|
282
|
+
cleanups.push(cleanup);
|
|
283
|
+
});
|
|
284
|
+
player.onStop(() => {
|
|
285
|
+
const cleanup = callbacksRef.current.onStop?.();
|
|
286
|
+
if (typeof cleanup === "function")
|
|
287
|
+
cleanups.push(cleanup);
|
|
288
|
+
});
|
|
289
|
+
logger.info("Lifecycle hooks registered");
|
|
290
|
+
return () => {
|
|
291
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
292
|
+
};
|
|
293
|
+
}, [player]);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Read the current lifecycle phase reactively.
|
|
297
|
+
*
|
|
298
|
+
* Re-renders whenever the phase transitions. Returns `LifecyclePhase.None`
|
|
299
|
+
* before the handshake completes.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```tsx
|
|
303
|
+
* const phase = useLifecyclePhase();
|
|
304
|
+
* const isPlaying = phase === LifecyclePhase.Start;
|
|
305
|
+
* const isVisible = phase === LifecyclePhase.Show || phase === LifecyclePhase.Start;
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
export function useLifecyclePhase() {
|
|
309
|
+
const { player } = useAppStrataContext();
|
|
310
|
+
const [phase, setPhase] = useState(player.lifecyclePhase);
|
|
311
|
+
useEffect(() => {
|
|
312
|
+
// Sync in case the phase changed between render and effect
|
|
313
|
+
setPhase(player.lifecyclePhase);
|
|
314
|
+
player.onInit(() => setPhase(player.lifecyclePhase));
|
|
315
|
+
player.onShow(() => setPhase(player.lifecyclePhase));
|
|
316
|
+
player.onStart(() => setPhase(player.lifecyclePhase));
|
|
317
|
+
player.onHide(() => setPhase(player.lifecyclePhase));
|
|
318
|
+
player.onStop(() => setPhase(player.lifecyclePhase));
|
|
319
|
+
}, [player]);
|
|
320
|
+
return phase;
|
|
321
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @appstrata/react - React hooks and components for AppStrata Digital Signage SDK
|
|
3
|
+
*
|
|
4
|
+
* Provides React hooks for app developers to interact with the SignagePlayer runtime.
|
|
5
|
+
* All hooks must be used within an AppStrataProvider.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { AppStrataProvider, useAppContext, useLifecyclePhase, LifecyclePhase } from "@appstrata/react";
|
|
10
|
+
*
|
|
11
|
+
* function App() {
|
|
12
|
+
* const context = useAppContext();
|
|
13
|
+
* const phase = useLifecyclePhase();
|
|
14
|
+
*
|
|
15
|
+
* if (!context) return <div>Loading...</div>;
|
|
16
|
+
* return <>
|
|
17
|
+
* <h1>{context.config.title as string}</h1>
|
|
18
|
+
* <p>Phase: {phase}</p>
|
|
19
|
+
* </>;
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* root.render(
|
|
23
|
+
* <AppStrataProvider>
|
|
24
|
+
* <App />
|
|
25
|
+
* </AppStrataProvider>
|
|
26
|
+
* );
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @packageDocumentation
|
|
30
|
+
*/
|
|
31
|
+
export { LifecyclePhase } from "@appstrata/core";
|
|
32
|
+
export { AppStrataProvider } from "./provider.js";
|
|
33
|
+
export type { AppStrataProviderProps } from "./provider.js";
|
|
34
|
+
export { useAppContext, useConfig, useResources, useResource, useFonts, useStylesheets, useDevice, usePlayer, useCapability, useLifecycle, useLifecyclePhase, } from "./hooks.js";
|
|
35
|
+
export type { LifecycleCallbacks, UseResourceResult } from "./hooks.js";
|
|
36
|
+
export { useStorage } from "./use-storage.js";
|
|
37
|
+
export type { StorageFallbackAdapter, UseStorageOptions, UseStorageKeyResult, UseStorageFullResult, } from "./use-storage.js";
|
|
38
|
+
export { useProxy } from "./use-proxy.js";
|
|
39
|
+
export type { UseProxyOptions, UseProxyResult } from "./use-proxy.js";
|
|
40
|
+
export { useMediaCache } from "./use-media-cache.js";
|
|
41
|
+
export type { UseMediaCacheOptions, UseMediaCacheResult } from "./use-media-cache.js";
|
|
42
|
+
export { useStatic } from "./use-static.js";
|
|
43
|
+
export type { UseStaticResult } from "./use-static.js";
|
|
44
|
+
export { useInteraction } from "./use-interaction.js";
|
|
45
|
+
export type { UseInteractionResult } from "./use-interaction.js";
|
|
46
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,YAAY,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAG5D,OAAO,EACL,aAAa,EACb,SAAS,EACT,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,cAAc,EACd,SAAS,EACT,SAAS,EACT,aAAa,EACb,YAAY,EACZ,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGxE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EACV,sBAAsB,EACtB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGtE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGtF,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @appstrata/react - React hooks and components for AppStrata Digital Signage SDK
|
|
3
|
+
*
|
|
4
|
+
* Provides React hooks for app developers to interact with the SignagePlayer runtime.
|
|
5
|
+
* All hooks must be used within an AppStrataProvider.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { AppStrataProvider, useAppContext, useLifecyclePhase, LifecyclePhase } from "@appstrata/react";
|
|
10
|
+
*
|
|
11
|
+
* function App() {
|
|
12
|
+
* const context = useAppContext();
|
|
13
|
+
* const phase = useLifecyclePhase();
|
|
14
|
+
*
|
|
15
|
+
* if (!context) return <div>Loading...</div>;
|
|
16
|
+
* return <>
|
|
17
|
+
* <h1>{context.config.title as string}</h1>
|
|
18
|
+
* <p>Phase: {phase}</p>
|
|
19
|
+
* </>;
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* root.render(
|
|
23
|
+
* <AppStrataProvider>
|
|
24
|
+
* <App />
|
|
25
|
+
* </AppStrataProvider>
|
|
26
|
+
* );
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @packageDocumentation
|
|
30
|
+
*/
|
|
31
|
+
// Re-export LifecyclePhase for convenience (avoids needing @appstrata/core import)
|
|
32
|
+
export { LifecyclePhase } from "@appstrata/core";
|
|
33
|
+
// Provider
|
|
34
|
+
export { AppStrataProvider } from "./provider.js";
|
|
35
|
+
// Core hooks
|
|
36
|
+
export { useAppContext, useConfig, useResources, useResource, useFonts, useStylesheets, useDevice, usePlayer, useCapability, useLifecycle, useLifecyclePhase, } from "./hooks.js";
|
|
37
|
+
// Storage hook
|
|
38
|
+
export { useStorage } from "./use-storage.js";
|
|
39
|
+
// Proxy hook
|
|
40
|
+
export { useProxy } from "./use-proxy.js";
|
|
41
|
+
// Media Cache hook
|
|
42
|
+
export { useMediaCache } from "./use-media-cache.js";
|
|
43
|
+
// Static hook
|
|
44
|
+
export { useStatic } from "./use-static.js";
|
|
45
|
+
// Interaction hook
|
|
46
|
+
export { useInteraction } from "./use-interaction.js";
|