@phreshos/react 0.1.17 → 0.1.19

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 CHANGED
@@ -20,47 +20,45 @@ domain state.
20
20
  ## Providers
21
21
 
22
22
  ```tsx
23
- import { ContextProvider, SystemProvider } from "@phreshos/react"
24
-
25
- <SystemProvider
26
- appearance={system.appearance}
27
- desktopSurface={system.desktop.surface}
28
- desktopPointer={system.desktop.pointer}
29
- desktopPreferences={system.desktop.preferences}
30
- >
31
- <ContextProvider
32
- program={() => context.program()}
33
- process={() => context.process()}
34
- parent={() => context.process().then(process => process.parent())}
35
- >
36
- {children}
37
- </ContextProvider>
23
+ import { ContextProvider, DesktopProvider, SystemProvider } from "@phreshos/react"
24
+
25
+ <SystemProvider system={system}>
26
+ <DesktopProvider desktop={desktop}>
27
+ <ContextProvider context={context}>
28
+ {children}
29
+ </ContextProvider>
30
+ </DesktopProvider>
38
31
  </SystemProvider>
39
32
  ```
40
33
 
41
- Each provider accepts explicitly selected sources and resolves only those
42
- values. A provider requires at least one source; mounting it does not fetch an
43
- entire runtime implicitly.
34
+ Each provider receives one complete Core contract. The three roots remain
35
+ separate: global System, current Client Context, and Client Desktop.
44
36
 
45
37
  ## Hooks
46
38
 
47
39
  Context hooks expose the supplied handles:
48
40
 
41
+ - `useContext()`
49
42
  - `useProgram()`
50
43
  - `useProcess()`
51
44
  - `useParent()`
52
45
 
53
- System hooks expose the supplied live snapshots:
46
+ System hooks expose the System and its live Appearance:
54
47
 
48
+ - `useSystem()`
55
49
  - `useSystemAppearance()`
50
+
51
+ Desktop hooks expose the Desktop and its live state:
52
+
53
+ - `useDesktop()`
56
54
  - `useDesktopSurface()`
57
- - `useDesktopPointer()`
58
55
  - `useDesktopPreferences()`
59
56
 
60
57
  State hooks compose one explicit read with future events:
61
58
 
62
59
  - `useProgramState()`
63
60
  - `useProcessState()`
61
+ - `useEndpointState()`
64
62
  - `useServiceState()`
65
63
  - `useWindowState()`
66
64
 
@@ -1,30 +1,23 @@
1
1
  import { type ReactNode } from "react";
2
- import type { Process, Program } from "@phreshos/core";
3
- /** Resolves only the runtime handles supplied by the surrounding runtime. */
4
- export default function ContextProvider({ children, fallback, parent, process, program }: ContextProviderProperties): string | number | bigint | boolean | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").FunctionComponentElement<import("react").ProviderProps<Partial<Readonly<{
5
- program: Program;
6
- process: Process;
7
- parent: Process | null;
8
- }>> | null>> | null;
9
- /** Returns the current Program supplied by the nearest provider. */
2
+ import type { ClientContext, Process, Program } from "@phreshos/core";
3
+ import LiveSnapshot from "./live-snapshot.js";
4
+ /** Provides one complete Client execution Context to a React tree. */
5
+ export default function ContextProvider({ children, context, fallback }: ContextProviderProperties): string | number | bigint | boolean | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").FunctionComponentElement<import("react").ProviderProps<Readonly<{
6
+ context: ClientContext;
7
+ process: LiveSnapshot<Process>;
8
+ program: LiveSnapshot<Program>;
9
+ parent: LiveSnapshot<Process | null>;
10
+ }> | null>> | null;
11
+ /** Returns the complete Client Context supplied by the nearest provider. */
12
+ export declare function useContext(): ClientContext;
13
+ /** Returns the current Program once it resolves. */
10
14
  export declare function useProgram<Handle extends Program = Program>(): Handle;
11
- /** Returns the current Process supplied by the nearest provider. */
15
+ /** Returns the current Process once it resolves. */
12
16
  export declare function useProcess<Handle extends Process = Process>(): Handle;
13
- /** Returns the current Process's supplied visible parent. */
17
+ /** Returns the current Process parent once it resolves. */
14
18
  export declare function useParent<Handle extends Process = Process>(): Handle | null;
15
- /** A stable handle or resolver supplied by one runtime integration. */
16
- export type ContextSource<Value> = Value | (() => Value | PromiseLike<Value>);
17
- /** Properties accepted by ContextProvider. */
18
19
  export type ContextProviderProperties = Readonly<{
19
- readonly children: ReactNode;
20
- readonly fallback?: ReactNode;
21
- }> & AtLeastOne<ContextSources>;
22
- type ContextSources = Readonly<{
23
- readonly parent?: ContextSource<Process | null>;
24
- readonly process?: ContextSource<Process>;
25
- readonly program?: ContextSource<Program>;
20
+ children: ReactNode;
21
+ context: ClientContext;
22
+ fallback?: ReactNode;
26
23
  }>;
27
- type AtLeastOne<Values> = {
28
- [Name in keyof Values]-?: Required<Pick<Values, Name>> & Partial<Omit<Values, Name>>;
29
- }[keyof Values];
30
- export {};
@@ -1,63 +1,44 @@
1
- import { createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
2
- const RuntimeContext = createContext(null);
3
- /** Resolves only the runtime handles supplied by the surrounding runtime. */
4
- export default function ContextProvider({ children, fallback = null, parent, process, program }) {
5
- const sources = useMemo(() => ({ parent, process, program }), [parent, process, program]);
6
- const [resolution, setResolution] = useState({ status: "pending", sources });
7
- if (parent === undefined && process === undefined && program === undefined) {
8
- throw new Error("ContextProvider requires at least one runtime handle");
9
- }
10
- useEffect(() => {
11
- let active = true;
12
- void resolveSources(sources).then(value => {
13
- if (active)
14
- setResolution({ status: "ready", sources, value });
15
- }, error => {
16
- if (active)
17
- setResolution({ status: "error", sources, error });
18
- });
19
- return () => { active = false; };
20
- }, [sources]);
21
- if (resolution.sources !== sources || resolution.status === "pending")
22
- return fallback;
23
- if (resolution.status === "error")
24
- throw resolution.error;
25
- return createElement(RuntimeContext.Provider, { value: resolution.value }, children);
26
- }
27
- async function resolveSources(sources) {
28
- const entries = await Promise.all(contextNames.flatMap(name => {
29
- const source = sources[name];
30
- return source === undefined ? [] : [resolveSource(name, source)];
31
- }));
32
- return Object.fromEntries(entries);
33
- }
34
- async function resolveSource(name, source) {
35
- const value = typeof source === "function" ? source() : source;
36
- return [name, await value];
37
- }
38
- /** Returns the current Program supplied by the nearest provider. */
1
+ import { createContext, createElement, useContext as useReactContext, useMemo, useSyncExternalStore } from "react";
2
+ import LiveSnapshot from "./live-snapshot.js";
3
+ import useProviderResolution from "./provider-resolution.js";
4
+ const CurrentContext = createContext(null);
5
+ /** Provides one complete Client execution Context to a React tree. */
6
+ export default function ContextProvider({ children, context, fallback = null }) {
7
+ const value = useMemo(() => ({
8
+ context,
9
+ process: fixed(() => context.process()),
10
+ program: fixed(() => context.program()),
11
+ parent: fixed(() => context.parent())
12
+ }), [context]);
13
+ const stores = useMemo(() => [value.process, value.program, value.parent], [value]);
14
+ const ready = useProviderResolution(stores);
15
+ return ready ? createElement(CurrentContext.Provider, { value }, children) : fallback;
16
+ }
17
+ /** Returns the complete Client Context supplied by the nearest provider. */
18
+ export function useContext() {
19
+ return useValue().context;
20
+ }
21
+ /** Returns the current Program once it resolves. */
39
22
  export function useProgram() {
40
- return useProvided("program");
23
+ return useResolved(useValue().program);
41
24
  }
42
- /** Returns the current Process supplied by the nearest provider. */
25
+ /** Returns the current Process once it resolves. */
43
26
  export function useProcess() {
44
- return useProvided("process");
27
+ return useResolved(useValue().process);
45
28
  }
46
- /** Returns the current Process's supplied visible parent. */
29
+ /** Returns the current Process parent once it resolves. */
47
30
  export function useParent() {
48
- return useProvided("parent");
49
- }
50
- function useProvided(name) {
51
- const context = useContext(RuntimeContext);
52
- if (!context)
53
- throw new Error(`${hookNames[name]} must be used inside ContextProvider`);
54
- if (!(name in context))
55
- throw new Error(`${hookNames[name]} requires ContextProvider's ${name} prop`);
56
- return context[name];
57
- }
58
- const contextNames = ["program", "process", "parent"];
59
- const hookNames = {
60
- parent: "useParent",
61
- process: "useProcess",
62
- program: "useProgram"
63
- };
31
+ return useResolved(useValue().parent);
32
+ }
33
+ function useResolved(store) {
34
+ return useSyncExternalStore(store.subscribe, store.snapshot, store.snapshot);
35
+ }
36
+ function useValue() {
37
+ const value = useReactContext(CurrentContext);
38
+ if (!value)
39
+ throw new Error("useContext must be used inside ContextProvider");
40
+ return value;
41
+ }
42
+ function fixed(read) {
43
+ return new LiveSnapshot(read, () => () => undefined);
44
+ }
@@ -0,0 +1,20 @@
1
+ import { type ReactNode } from "react";
2
+ import type { Desktop, DesktopPreferences, DesktopSurfaceSnapshot } from "@phreshos/core";
3
+ import LiveSnapshot from "./live-snapshot.js";
4
+ /** Provides one complete Client Desktop environment to a React tree. */
5
+ export default function DesktopProvider({ children, desktop, fallback }: DesktopProviderProperties): string | number | bigint | boolean | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").FunctionComponentElement<import("react").ProviderProps<Readonly<{
6
+ desktop: Desktop;
7
+ surface: LiveSnapshot<DesktopSurfaceSnapshot>;
8
+ preferences: LiveSnapshot<DesktopPreferences>;
9
+ }> | null>> | null;
10
+ /** Returns the complete Desktop supplied by the nearest provider. */
11
+ export declare function useDesktop(): Desktop;
12
+ /** Resolves and follows the current Desktop surface. */
13
+ export declare function useDesktopSurface(): DesktopSurfaceSnapshot;
14
+ /** Resolves and follows the effective Desktop preferences. */
15
+ export declare function useDesktopPreferences(): DesktopPreferences;
16
+ export type DesktopProviderProperties = Readonly<{
17
+ children: ReactNode;
18
+ desktop: Desktop;
19
+ fallback?: ReactNode;
20
+ }>;
@@ -0,0 +1,35 @@
1
+ import { createContext, createElement, useContext as useReactContext, useMemo, useSyncExternalStore } from "react";
2
+ import LiveSnapshot from "./live-snapshot.js";
3
+ import useProviderResolution from "./provider-resolution.js";
4
+ const DesktopContext = createContext(null);
5
+ /** Provides one complete Client Desktop environment to a React tree. */
6
+ export default function DesktopProvider({ children, desktop, fallback = null }) {
7
+ const value = useMemo(() => ({
8
+ desktop,
9
+ surface: new LiveSnapshot(() => desktop.surface.snapshot(), subscriber => desktop.surface.subscribe("resize", subscriber)),
10
+ preferences: new LiveSnapshot(() => desktop.preferences.snapshot(), subscriber => desktop.preferences.subscribe("change", subscriber))
11
+ }), [desktop]);
12
+ const stores = useMemo(() => [value.surface, value.preferences], [value]);
13
+ const ready = useProviderResolution(stores);
14
+ return ready ? createElement(DesktopContext.Provider, { value }, children) : fallback;
15
+ }
16
+ /** Returns the complete Desktop supplied by the nearest provider. */
17
+ export function useDesktop() {
18
+ return useValue().desktop;
19
+ }
20
+ /** Resolves and follows the current Desktop surface. */
21
+ export function useDesktopSurface() {
22
+ const store = useValue().surface;
23
+ return useSyncExternalStore(store.subscribe, store.snapshot, store.snapshot);
24
+ }
25
+ /** Resolves and follows the effective Desktop preferences. */
26
+ export function useDesktopPreferences() {
27
+ const store = useValue().preferences;
28
+ return useSyncExternalStore(store.subscribe, store.snapshot, store.snapshot);
29
+ }
30
+ function useValue() {
31
+ const value = useReactContext(DesktopContext);
32
+ if (!value)
33
+ throw new Error("useDesktop must be used inside DesktopProvider");
34
+ return value;
35
+ }
package/dist/main.d.ts CHANGED
@@ -1,8 +1,10 @@
1
- export { default as ContextProvider, useParent, useProcess, useProgram, type ContextSource, type ContextProviderProperties } from "./context-provider.js";
2
- export { default as SystemProvider, useDesktopPointer, useDesktopSurface, useDesktopPreferences, useSystemAppearance, type SystemProviderProperties } from "./system-provider.js";
1
+ export { default as ContextProvider, useContext, useParent, useProcess, useProgram, type ContextProviderProperties } from "./context-provider.js";
2
+ export { default as SystemProvider, useSystem, useSystemAppearance, type SystemProviderProperties } from "./system-provider.js";
3
+ export { default as DesktopProvider, useDesktop, useDesktopSurface, useDesktopPreferences, type DesktopProviderProperties } from "./desktop-provider.js";
3
4
  export { default as useSubscribe } from "./use-subscribe.js";
4
5
  export { default as useProgramState, type ProgramState } from "./use-program-state.js";
5
6
  export { default as useProcessState, type ProcessState } from "./use-process-state.js";
7
+ export { default as useEndpointState, type EndpointState } from "./use-endpoint-state.js";
6
8
  export { default as useServiceState, type ServiceState } from "./use-service-state.js";
7
9
  export { default as useWindowState } from "./use-window-state.js";
8
10
  export { default as useSubscribeAsks, type AskSubscribable } from "./use-subscribe-asks.js";
package/dist/main.js CHANGED
@@ -1,8 +1,10 @@
1
- export { default as ContextProvider, useParent, useProcess, useProgram } from "./context-provider.js";
2
- export { default as SystemProvider, useDesktopPointer, useDesktopSurface, useDesktopPreferences, useSystemAppearance } from "./system-provider.js";
1
+ export { default as ContextProvider, useContext, useParent, useProcess, useProgram } from "./context-provider.js";
2
+ export { default as SystemProvider, useSystem, useSystemAppearance } from "./system-provider.js";
3
+ export { default as DesktopProvider, useDesktop, useDesktopSurface, useDesktopPreferences } from "./desktop-provider.js";
3
4
  export { default as useSubscribe } from "./use-subscribe.js";
4
5
  export { default as useProgramState } from "./use-program-state.js";
5
6
  export { default as useProcessState } from "./use-process-state.js";
7
+ export { default as useEndpointState } from "./use-endpoint-state.js";
6
8
  export { default as useServiceState } from "./use-service-state.js";
7
9
  export { default as useWindowState } from "./use-window-state.js";
8
10
  export { default as useSubscribeAsks } from "./use-subscribe-asks.js";
@@ -0,0 +1,5 @@
1
+ import LiveSnapshot from "./live-snapshot.js";
2
+ type Stores = readonly LiveSnapshot<unknown>[];
3
+ /** Starts a provider's stores as one lifecycle and reports when all are ready. */
4
+ export default function useProviderResolution(stores: Stores): boolean;
5
+ export {};
@@ -0,0 +1,28 @@
1
+ import { useEffect, useState } from "react";
2
+ import LiveSnapshot from "./live-snapshot.js";
3
+ /** Starts a provider's stores as one lifecycle and reports when all are ready. */
4
+ export default function useProviderResolution(stores) {
5
+ const [resolution, setResolution] = useState({ stores, status: "pending" });
6
+ useEffect(() => {
7
+ let active = true;
8
+ void Promise.all(stores.map(store => store.start())).then(() => {
9
+ if (active)
10
+ setResolution({ stores, status: "ready" });
11
+ }, error => {
12
+ for (const store of stores)
13
+ store.stop();
14
+ if (active)
15
+ setResolution({ stores, status: "error", error });
16
+ });
17
+ return () => {
18
+ active = false;
19
+ for (const store of stores)
20
+ store.stop();
21
+ };
22
+ }, [stores]);
23
+ if (resolution.stores !== stores)
24
+ return false;
25
+ if (resolution.status === "error")
26
+ throw resolution.error;
27
+ return resolution.status === "ready";
28
+ }
@@ -1,38 +1,17 @@
1
1
  import { type ReactNode } from "react";
2
- import type { Appearance, AppearanceSource, DesktopPointerSnapshot, DesktopPointerSource, DesktopPreferences, DesktopPreferencesSource, DesktopSurfaceSnapshot, DesktopSurfaceSource } from "@phreshos/core";
2
+ import type { Appearance, System } from "@phreshos/core";
3
3
  import LiveSnapshot from "./live-snapshot.js";
4
- /** Resolves and follows only the System capabilities supplied by the runtime. */
5
- export default function SystemProvider({ appearance, children, desktopPointer, desktopPreferences, desktopSurface, fallback }: SystemProviderProperties): string | number | bigint | boolean | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").FunctionComponentElement<import("react").ProviderProps<SystemStores | null>> | null;
6
- /** Returns the complete unresolved Appearance and follows authoritative updates. */
4
+ /** Provides one complete global System contract to a React tree. */
5
+ export default function SystemProvider({ children, fallback, system }: SystemProviderProperties): string | number | bigint | boolean | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").FunctionComponentElement<import("react").ProviderProps<Readonly<{
6
+ system: System;
7
+ appearance: LiveSnapshot<Appearance>;
8
+ }> | null>> | null;
9
+ /** Returns the complete global System supplied by the nearest provider. */
10
+ export declare function useSystem(): System;
11
+ /** Resolves and follows the global System Appearance. */
7
12
  export declare function useSystemAppearance(): Appearance;
8
- /** Returns the Desktop surface snapshot and follows future resizes. */
9
- export declare function useDesktopSurface(): DesktopSurfaceSnapshot;
10
- /** Returns the Desktop pointer snapshot and follows future movement. */
11
- export declare function useDesktopPointer(): DesktopPointerSnapshot;
12
- /** Returns all effective Desktop preferences and follows authoritative updates. */
13
- export declare function useDesktopPreferences(): DesktopPreferences;
14
- /** Properties accepted by SystemProvider. */
15
13
  export type SystemProviderProperties = Readonly<{
16
- readonly children: ReactNode;
17
- readonly fallback?: ReactNode;
18
- }> & AtLeastOne<SystemSources>;
19
- type SystemSources = Readonly<{
20
- readonly appearance?: AppearanceSource;
21
- readonly desktopPointer?: DesktopPointerSource;
22
- readonly desktopPreferences?: DesktopPreferencesSource;
23
- readonly desktopSurface?: DesktopSurfaceSource;
14
+ children: ReactNode;
15
+ fallback?: ReactNode;
16
+ system: System;
24
17
  }>;
25
- type SystemValues = Readonly<{
26
- appearance: Appearance;
27
- desktopPointer: DesktopPointerSnapshot;
28
- desktopPreferences: DesktopPreferences;
29
- desktopSurface: DesktopSurfaceSnapshot;
30
- }>;
31
- type SystemName = keyof SystemValues;
32
- type SystemStores = {
33
- [Name in SystemName]?: LiveSnapshot<SystemValues[Name]>;
34
- };
35
- type AtLeastOne<Values> = {
36
- [Name in keyof Values]-?: Required<Pick<Values, Name>> & Partial<Omit<Values, Name>>;
37
- }[keyof Values];
38
- export {};
@@ -1,85 +1,29 @@
1
- import { createContext, createElement, useContext, useEffect, useMemo, useState, useSyncExternalStore } from "react";
1
+ import { createContext, createElement, useContext as useReactContext, useMemo, useSyncExternalStore } from "react";
2
2
  import LiveSnapshot from "./live-snapshot.js";
3
+ import useProviderResolution from "./provider-resolution.js";
3
4
  const SystemContext = createContext(null);
4
- /** Resolves and follows only the System capabilities supplied by the runtime. */
5
- export default function SystemProvider({ appearance, children, desktopPointer, desktopPreferences, desktopSurface, fallback = null }) {
6
- const stores = useMemo(() => createStores({
7
- appearance,
8
- desktopPointer,
9
- desktopPreferences,
10
- desktopSurface
11
- }), [appearance, desktopPointer, desktopPreferences, desktopSurface]);
12
- const [resolution, setResolution] = useState({ status: "pending", stores });
13
- if (Object.keys(stores).length === 0)
14
- throw new Error("SystemProvider requires at least one System capability");
15
- useEffect(() => {
16
- let active = true;
17
- const selected = Object.values(stores);
18
- void Promise.all(selected.map(store => store.start())).then(() => {
19
- if (active)
20
- setResolution({ status: "ready", stores });
21
- }, error => {
22
- for (const store of selected)
23
- store.stop();
24
- if (active)
25
- setResolution({ status: "error", stores, error });
26
- });
27
- return () => {
28
- active = false;
29
- for (const store of selected)
30
- store.stop();
31
- };
32
- }, [stores]);
33
- if (resolution.stores !== stores || resolution.status === "pending")
34
- return fallback;
35
- if (resolution.status === "error")
36
- throw resolution.error;
37
- return createElement(SystemContext.Provider, { value: stores }, children);
5
+ /** Provides one complete global System contract to a React tree. */
6
+ export default function SystemProvider({ children, fallback = null, system }) {
7
+ const value = useMemo(() => ({
8
+ system,
9
+ appearance: new LiveSnapshot(() => system.appearance.snapshot(), subscriber => system.appearance.subscribe("change", subscriber))
10
+ }), [system]);
11
+ const stores = useMemo(() => [value.appearance], [value]);
12
+ const ready = useProviderResolution(stores);
13
+ return ready ? createElement(SystemContext.Provider, { value }, children) : fallback;
38
14
  }
39
- /** Returns the complete unresolved Appearance and follows authoritative updates. */
40
- export function useSystemAppearance() {
41
- return useProvided("appearance");
42
- }
43
- /** Returns the Desktop surface snapshot and follows future resizes. */
44
- export function useDesktopSurface() {
45
- return useProvided("desktopSurface");
46
- }
47
- /** Returns the Desktop pointer snapshot and follows future movement. */
48
- export function useDesktopPointer() {
49
- return useProvided("desktopPointer");
15
+ /** Returns the complete global System supplied by the nearest provider. */
16
+ export function useSystem() {
17
+ return useValue().system;
50
18
  }
51
- /** Returns all effective Desktop preferences and follows authoritative updates. */
52
- export function useDesktopPreferences() {
53
- return useProvided("desktopPreferences");
54
- }
55
- function useProvided(name) {
56
- const context = useContext(SystemContext);
57
- if (!context)
58
- throw new Error(`${hookNames[name]} must be used inside SystemProvider`);
59
- const store = context[name];
60
- if (!store)
61
- throw new Error(`${hookNames[name]} requires SystemProvider's ${name} prop`);
19
+ /** Resolves and follows the global System Appearance. */
20
+ export function useSystemAppearance() {
21
+ const store = useValue().appearance;
62
22
  return useSyncExternalStore(store.subscribe, store.snapshot, store.snapshot);
63
23
  }
64
- function createStores(sources) {
65
- const stores = {};
66
- const appearance = sources.appearance;
67
- const surface = sources.desktopSurface;
68
- const pointer = sources.desktopPointer;
69
- const preferences = sources.desktopPreferences;
70
- if (appearance)
71
- stores.appearance = new LiveSnapshot(() => appearance.snapshot(), subscriber => appearance.subscribe("change", subscriber));
72
- if (surface)
73
- stores.desktopSurface = new LiveSnapshot(() => surface.snapshot(), subscriber => surface.subscribe("resize", subscriber));
74
- if (pointer)
75
- stores.desktopPointer = new LiveSnapshot(() => pointer.snapshot(), subscriber => pointer.subscribe("move", subscriber));
76
- if (preferences)
77
- stores.desktopPreferences = new LiveSnapshot(() => preferences.snapshot(), subscriber => preferences.subscribe("change", subscriber));
78
- return stores;
24
+ function useValue() {
25
+ const value = useReactContext(SystemContext);
26
+ if (!value)
27
+ throw new Error("useSystem must be used inside SystemProvider");
28
+ return value;
79
29
  }
80
- const hookNames = {
81
- appearance: "useSystemAppearance",
82
- desktopPointer: "useDesktopPointer",
83
- desktopPreferences: "useDesktopPreferences",
84
- desktopSurface: "useDesktopSurface"
85
- };
@@ -0,0 +1,6 @@
1
+ import type { Endpoint } from "@phreshos/core";
2
+ export type EndpointState = Readonly<{
3
+ exists: boolean;
4
+ }>;
5
+ /** Explicitly reads and follows one Endpoint address while mounted. */
6
+ export default function useEndpointState(endpoint: Endpoint): EndpointState | undefined;
@@ -0,0 +1,10 @@
1
+ import { useMemo, useSyncExternalStore } from "react";
2
+ import LiveState, { combineCleanups } from "./live-state.js";
3
+ /** Explicitly reads and follows one Endpoint address while mounted. */
4
+ export default function useEndpointState(endpoint) {
5
+ const state = useMemo(() => new LiveState(async () => ({ exists: await endpoint.exists() }), reduce => combineCleanups(endpoint.lifecycle.subscribe("start", () => reduce(current => setExists(current, true))), endpoint.lifecycle.subscribe("stop", () => reduce(current => setExists(current, false))))), [endpoint]);
6
+ return useSyncExternalStore(state.subscribe, state.snapshot, state.snapshot);
7
+ }
8
+ function setExists(state, exists) {
9
+ return state.exists === exists ? state : { exists };
10
+ }
@@ -1,4 +1,4 @@
1
- import type { Captures, Context, ContextMessage, Endpoint, EventMessage, EventName, Subscribable, SubscribableEvents, SubscribableFallback } from "@phreshos/core";
1
+ import type { Captures, EventMessage, EventName, Subscribable, SubscribableEvents, SubscribableFallback } from "@phreshos/core";
2
2
  type OpenEvent = string & {};
3
3
  type AvailableEvent<Events extends object, Fallback> = EventName<Events> | ([Fallback] extends [never] ? never : OpenEvent);
4
4
  type CompatibleEvent<Events extends object, Fallback, Narrowed> = {
@@ -14,12 +14,8 @@ type TargetMessage<Target, Event extends string> = EventMessage<SubscribableEven
14
14
  type TargetCapture<Target> = Captures<SubscribableEvents<Target>, SubscribableFallback<Target>>;
15
15
  /** Subscribes while mounted and retains the latest named message. */
16
16
  export default function useSubscribe<Target extends SubscribableTarget, Event extends TargetEvent<Target>>(target: Target, event: Event): TargetMessage<Target, Event> | undefined;
17
- export default function useSubscribe<Narrowed = unknown>(target: Endpoint, event: string): Narrowed | undefined;
18
- export default function useSubscribe<Narrowed extends ContextMessage<unknown> = ContextMessage<unknown>>(target: Context, event: string): Narrowed | undefined;
19
17
  /** Subscribes while mounted and retains the latest projected named message. */
20
18
  export default function useSubscribe<Target extends SubscribableTarget, Event extends TargetEvent<Target>, Result>(target: Target, event: Event, project: (message: TargetMessage<Target, Event>) => Result): Awaited<Result> | undefined;
21
- export default function useSubscribe<Narrowed, Result = unknown>(target: Endpoint, event: string, project: (message: Narrowed) => Result): Awaited<Result> | undefined;
22
- export default function useSubscribe<Narrowed extends ContextMessage<unknown>, Result = unknown>(target: Context, event: string, project: (message: Narrowed) => Result): Awaited<Result> | undefined;
23
19
  export default function useSubscribe<Narrowed, Events extends object, Fallback, Result>(target: Subscribable<Events, Fallback>, event: CompatibleEvent<Events, Fallback, Narrowed>, project: (message: Narrowed) => Result): Awaited<Result> | undefined;
24
20
  /** Subscribes across every event and retains the latest projected capture. */
25
21
  export default function useSubscribe<Target extends SubscribableTarget, Result>(target: Target, project: (capture: TargetCapture<Target>) => Result): Awaited<Result> | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/react",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Runtime-neutral React adapters for PhreshOS contracts.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -47,11 +47,11 @@
47
47
  "prepack": "node --run test && node --run build"
48
48
  },
49
49
  "peerDependencies": {
50
- "@phreshos/core": "^0.1.31",
50
+ "@phreshos/core": "^0.1.37",
51
51
  "react": "^19.2.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@phreshos/core": "^0.1.31",
54
+ "@phreshos/core": "^0.1.37",
55
55
  "@testing-library/react": "^16.3.0",
56
56
  "@types/react": "^19.2.18",
57
57
  "@types/react-dom": "^19.2.4",