applesauce-react 0.0.0-next-20250930093922 → 0.0.0-next-20251203172109
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/hooks/use-event-factory.d.ts +1 -1
- package/dist/hooks/use-observable-memo.js +2 -1
- package/dist/hooks/use-observable-state.d.ts +20 -0
- package/dist/hooks/use-observable-state.js +107 -0
- package/dist/hooks/use-observable.d.ts +2 -1
- package/dist/hooks/use-observable.js +4 -1
- package/dist/hooks/use-rendered-content.d.ts +2 -2
- package/dist/hooks/use-rendered-content.js +2 -2
- package/dist/providers/factory-provider.d.ts +1 -1
- package/package.json +6 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventFactory } from "applesauce-
|
|
1
|
+
import { EventFactory } from "applesauce-core";
|
|
2
2
|
export declare function useEventFactory(require: false): EventFactory | undefined;
|
|
3
3
|
export declare function useEventFactory(require: true): EventFactory;
|
|
4
4
|
export declare function useEventFactory(): EventFactory;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { useObservableEagerState
|
|
1
|
+
import { useObservableEagerState } from "observable-hooks";
|
|
2
2
|
import { useMemo } from "react";
|
|
3
3
|
import { of } from "rxjs";
|
|
4
|
+
import { useObservableState } from "./use-observable-state.js";
|
|
4
5
|
export function useObservableMemo(factory, deps) {
|
|
5
6
|
return useObservableState(useMemo(() => factory() || of(undefined), deps));
|
|
6
7
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { BehaviorSubject, Observable } from "rxjs";
|
|
2
|
+
/**
|
|
3
|
+
* A hook that subscribes to an Observable and returns its current value.
|
|
4
|
+
*
|
|
5
|
+
* Unlike the standard `useObservableState` from observable-hooks, this hook
|
|
6
|
+
* will synchronously get the initial value if the Observable emits synchronously.
|
|
7
|
+
* This prevents an extra render when the Observable has an immediate value.
|
|
8
|
+
*
|
|
9
|
+
* If the Observable does not emit synchronously, the hook returns `undefined`
|
|
10
|
+
* for the initial render (unlike `useObservableEagerState` which throws).
|
|
11
|
+
*
|
|
12
|
+
* The observable is only subscribed to once - the subscription created during
|
|
13
|
+
* the initial probe is kept alive and reused, avoiding issues with cold observables.
|
|
14
|
+
*
|
|
15
|
+
* @template TState State type.
|
|
16
|
+
* @param state$ An Observable of state values.
|
|
17
|
+
* @returns The current state value, or `undefined` if no value has been emitted yet.
|
|
18
|
+
*/
|
|
19
|
+
export declare function useObservableState<TState>(state$: BehaviorSubject<TState>): TState;
|
|
20
|
+
export declare function useObservableState<TState>(state$: Observable<TState>): TState | undefined;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useDebugValue, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
+
import { useForceUpdate } from "observable-hooks";
|
|
3
|
+
// Prevent React warning when using useLayoutEffect on server
|
|
4
|
+
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
5
|
+
// Symbol to indicate no value was emitted
|
|
6
|
+
const NO_VALUE = Symbol("NO_VALUE");
|
|
7
|
+
function createSubscription(observable) {
|
|
8
|
+
const subState = {
|
|
9
|
+
observable,
|
|
10
|
+
subscription: null,
|
|
11
|
+
latestValue: NO_VALUE,
|
|
12
|
+
latestError: null,
|
|
13
|
+
onValue: null,
|
|
14
|
+
onError: null,
|
|
15
|
+
};
|
|
16
|
+
subState.subscription = observable.subscribe({
|
|
17
|
+
next: (value) => {
|
|
18
|
+
subState.latestValue = value;
|
|
19
|
+
subState.onValue?.(value);
|
|
20
|
+
},
|
|
21
|
+
error: (error) => {
|
|
22
|
+
subState.latestError = error;
|
|
23
|
+
subState.onError?.();
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
return subState;
|
|
27
|
+
}
|
|
28
|
+
export function useObservableState(state$) {
|
|
29
|
+
const forceUpdate = useForceUpdate();
|
|
30
|
+
// Ref to hold the subscription state - persists across renders
|
|
31
|
+
const subStateRef = useRef(null);
|
|
32
|
+
// Initialize state - this only runs once per component instance
|
|
33
|
+
const [state, setState] = useState(() => {
|
|
34
|
+
// Clean up any existing subscription (shouldn't happen in useState init, but be safe)
|
|
35
|
+
if (subStateRef.current) {
|
|
36
|
+
subStateRef.current.subscription.unsubscribe();
|
|
37
|
+
}
|
|
38
|
+
// Create subscription and probe for sync value
|
|
39
|
+
const subState = createSubscription(state$);
|
|
40
|
+
subStateRef.current = subState;
|
|
41
|
+
// Return sync value if available
|
|
42
|
+
return subState.latestValue !== NO_VALUE ? subState.latestValue : undefined;
|
|
43
|
+
});
|
|
44
|
+
// Track current observable for staleness checks
|
|
45
|
+
const state$Ref = useRef(state$);
|
|
46
|
+
useIsomorphicLayoutEffect(() => {
|
|
47
|
+
state$Ref.current = state$;
|
|
48
|
+
});
|
|
49
|
+
// Handle observable changes and register callbacks
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
let subState = subStateRef.current;
|
|
52
|
+
// If observable changed, create new subscription
|
|
53
|
+
if (!subState || subState.observable !== state$) {
|
|
54
|
+
// Clean up old subscription
|
|
55
|
+
subState?.subscription.unsubscribe();
|
|
56
|
+
// Create new subscription
|
|
57
|
+
subState = createSubscription(state$);
|
|
58
|
+
subStateRef.current = subState;
|
|
59
|
+
// Update state if we got a sync value from new observable
|
|
60
|
+
if (subState.latestValue !== NO_VALUE) {
|
|
61
|
+
setState(subState.latestValue);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
setState(undefined);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Same observable - check if we missed any values between useState and useEffect
|
|
69
|
+
if (subState.latestValue !== NO_VALUE && subState.latestValue !== state) {
|
|
70
|
+
setState(subState.latestValue);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Check for errors that occurred before useEffect
|
|
74
|
+
if (subState.latestError !== null) {
|
|
75
|
+
forceUpdate();
|
|
76
|
+
}
|
|
77
|
+
// Register callbacks for future emissions
|
|
78
|
+
subState.onValue = (value) => {
|
|
79
|
+
if (state$Ref.current === state$) {
|
|
80
|
+
setState(value);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
subState.onError = () => {
|
|
84
|
+
if (state$Ref.current === state$) {
|
|
85
|
+
forceUpdate();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
return () => {
|
|
89
|
+
// Unregister callbacks
|
|
90
|
+
subState.onValue = null;
|
|
91
|
+
subState.onError = null;
|
|
92
|
+
// Unsubscribe
|
|
93
|
+
subState.subscription.unsubscribe();
|
|
94
|
+
// Clear the ref if this is still the current subscription
|
|
95
|
+
if (subStateRef.current === subState) {
|
|
96
|
+
subStateRef.current = null;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}, [state$]);
|
|
100
|
+
// Throw errors to be caught by error boundary
|
|
101
|
+
const subState = subStateRef.current;
|
|
102
|
+
if (subState?.latestError !== null && subState?.observable === state$) {
|
|
103
|
+
throw subState.latestError;
|
|
104
|
+
}
|
|
105
|
+
useDebugValue(state);
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { useObservableCallback, useSubscription, useObservableEagerState, useObservableGetState, useObservablePickState, useObservableSuspense, useForceUpdate, } from "observable-hooks";
|
|
2
|
+
export { useObservableState } from "./use-observable-state.js";
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
// NOTE: re-export hooks from observable-hooks to avoid confusion
|
|
2
|
-
|
|
2
|
+
// We override useObservableState with our own implementation that gets sync values
|
|
3
|
+
export { useObservableCallback, useSubscription, useObservableEagerState, useObservableGetState, useObservablePickState, useObservableSuspense, useForceUpdate, } from "observable-hooks";
|
|
4
|
+
// Export our custom useObservableState that gets sync values
|
|
5
|
+
export { useObservableState } from "./use-observable-state.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getParsedContent } from "applesauce-content/text";
|
|
2
|
-
import { EventTemplate, NostrEvent } from "
|
|
3
|
-
import { ComponentMap } from "../helpers/nast.js";
|
|
2
|
+
import { EventTemplate, NostrEvent } from "applesauce-core/helpers/event";
|
|
4
3
|
import { LinkRenderer } from "../helpers/build-link-renderer.js";
|
|
4
|
+
import { ComponentMap } from "../helpers/nast.js";
|
|
5
5
|
export { ComponentMap };
|
|
6
6
|
type Options = {
|
|
7
7
|
/** The key to cache the results under, passing null will disable */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
2
1
|
import { truncateContent } from "applesauce-content/nast";
|
|
3
2
|
import { getParsedContent } from "applesauce-content/text";
|
|
4
|
-
import {
|
|
3
|
+
import { useMemo } from "react";
|
|
5
4
|
import { buildLinkRenderer } from "../helpers/build-link-renderer.js";
|
|
5
|
+
import { useRenderNast } from "./use-render-nast.js";
|
|
6
6
|
/** Returns the parsed and render text content for an event */
|
|
7
7
|
export function useRenderedContent(event, components, opts) {
|
|
8
8
|
// if link renderers are set, override the link components
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventFactory } from "applesauce-
|
|
1
|
+
import { EventFactory } from "applesauce-core";
|
|
2
2
|
import { PropsWithChildren } from "react";
|
|
3
3
|
export declare const FactoryContext: import("react").Context<EventFactory | undefined>;
|
|
4
4
|
/** Provides an {@link EventFactory} to the component tree */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-react",
|
|
3
|
-
"version": "0.0.0-next-
|
|
3
|
+
"version": "0.0.0-next-20251203172109",
|
|
4
4
|
"description": "React hooks for applesauce",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -48,13 +48,11 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"applesauce-accounts": "0.0.0-next-
|
|
52
|
-
"applesauce-actions": "0.0.0-next-
|
|
53
|
-
"applesauce-content": "0.0.0-next-
|
|
54
|
-
"applesauce-core": "0.0.0-next-
|
|
55
|
-
"applesauce-factory": "0.0.0-next-20250930093922",
|
|
51
|
+
"applesauce-accounts": "0.0.0-next-20251203172109",
|
|
52
|
+
"applesauce-actions": "0.0.0-next-20251203172109",
|
|
53
|
+
"applesauce-content": "0.0.0-next-20251203172109",
|
|
54
|
+
"applesauce-core": "0.0.0-next-20251203172109",
|
|
56
55
|
"hash-sum": "^2.0.0",
|
|
57
|
-
"nostr-tools": "~2.17",
|
|
58
56
|
"observable-hooks": "^4.2.4",
|
|
59
57
|
"react": "^18.3.1",
|
|
60
58
|
"rxjs": "^7.8.1"
|
|
@@ -64,7 +62,7 @@
|
|
|
64
62
|
"@types/react": "^18.3.18",
|
|
65
63
|
"rimraf": "^6.0.1",
|
|
66
64
|
"typescript": "^5.8.3",
|
|
67
|
-
"vitest": "^
|
|
65
|
+
"vitest": "^4.0.15"
|
|
68
66
|
},
|
|
69
67
|
"funding": {
|
|
70
68
|
"type": "lightning",
|