react-rx 3.0.0-crx-749.1 → 3.0.1-canary.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/dist/index.cjs ADDED
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: !0 });
3
+ var react = require("react"), rxjs = require("rxjs"), operators = require("rxjs/operators"), observableCallback = require("observable-callback");
4
+ function getValue(value) {
5
+ return typeof value == "function" ? value() : value;
6
+ }
7
+ const cache = /* @__PURE__ */ new WeakMap();
8
+ function useObservable(observable, initialValue) {
9
+ const initialValueRef = react.useRef(getValue(initialValue));
10
+ react.useEffect(() => {
11
+ initialValueRef.current = getValue(initialValue);
12
+ }, [initialValue]);
13
+ const store = react.useMemo(() => {
14
+ if (!cache.has(observable)) {
15
+ const entry = {
16
+ snapshot: initialValueRef.current
17
+ };
18
+ entry.observable = observable.pipe(
19
+ operators.map((value) => ({ snapshot: value, error: void 0 })),
20
+ rxjs.catchError((error) => rxjs.of({ snapshot: void 0, error })),
21
+ operators.tap(({ snapshot, error }) => {
22
+ entry.snapshot = snapshot, entry.error = error;
23
+ }),
24
+ // Note: any value or error emitted by the provided observable will be mapped to the cache entry's mutable state
25
+ // and the observable is thereafter only used as a notifier to call `onStoreChange`, hence the `void` return type.
26
+ operators.map((value) => {
27
+ }),
28
+ // Ensure that the cache entry is deleted when the observable completes or errors.
29
+ rxjs.finalize(() => cache.delete(observable)),
30
+ rxjs.share()
31
+ ), entry.subscription = entry.observable.subscribe(), cache.set(observable, entry);
32
+ }
33
+ const instance = cache.get(observable);
34
+ return instance.subscription.closed && (instance.subscription = instance.observable.subscribe()), {
35
+ subscribe: (onStoreChange) => {
36
+ const subscription = instance.observable.subscribe(onStoreChange);
37
+ return instance.subscription.unsubscribe(), () => {
38
+ subscription.unsubscribe();
39
+ };
40
+ },
41
+ getSnapshot: () => {
42
+ if (instance.error)
43
+ throw instance.error;
44
+ return instance.snapshot;
45
+ }
46
+ };
47
+ }, [observable]);
48
+ return react.useSyncExternalStore(
49
+ store.subscribe,
50
+ store.getSnapshot,
51
+ typeof initialValueRef.current > "u" ? void 0 : () => initialValueRef.current
52
+ );
53
+ }
54
+ function useObservableEvent(fn) {
55
+ const [[calls$, call]] = react.useState(() => observableCallback.observableCallback()), callback = useEffectEvent(fn);
56
+ return react.useEffect(() => {
57
+ const subscription = calls$.pipe(callback).subscribe();
58
+ return () => subscription.unsubscribe();
59
+ }, [callback, calls$]), call;
60
+ }
61
+ function useEffectEvent(fn) {
62
+ const ref = react.useRef(null);
63
+ return react.useInsertionEffect(() => {
64
+ ref.current = fn;
65
+ }, [fn]), react.useCallback((...args) => {
66
+ const f = ref.current;
67
+ return f(...args);
68
+ }, []);
69
+ }
70
+ exports.useObservable = useObservable;
71
+ exports.useObservableEvent = useObservableEvent;
72
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/useObservable.ts","../src/useObservableEvent.ts"],"sourcesContent":["import {useEffect, useMemo, useRef, useSyncExternalStore} from 'react'\nimport {\n catchError,\n finalize,\n type Observable,\n type ObservedValueOf,\n of,\n share,\n type Subscription,\n} from 'rxjs'\nimport {map, tap} from 'rxjs/operators'\n\nfunction getValue<T>(value: T): T extends () => infer U ? U : T {\n return typeof value === 'function' ? value() : value\n}\n\ninterface CacheRecord<T> {\n subscription: Subscription\n observable: Observable<void>\n snapshot: T\n error?: unknown\n}\n\nconst cache = new WeakMap<Observable<any>, CacheRecord<any>>()\n\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>>(\n observable: ObservableType,\n initialValue: ObservedValueOf<ObservableType> | (() => ObservedValueOf<ObservableType>),\n): ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>>(\n observable: ObservableType,\n): undefined | ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>, InitialValue>(\n observable: ObservableType,\n initialValue: InitialValue,\n): InitialValue | ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>, InitialValue>(\n observable: ObservableType,\n initialValue?: InitialValue | (() => InitialValue),\n): InitialValue | ObservedValueOf<ObservableType> {\n /**\n * Store the initialValue in a ref, as we don't want a changed `initialValue` to trigger a re-subscription.\n * But we also don't want the initialValue to be stale if the observable changes.\n */\n const initialValueRef = useRef(getValue(initialValue) as ObservedValueOf<ObservableType>)\n\n /**\n * Ensures that the initialValue is always up-to-date in case the observable changes.\n */\n useEffect(() => {\n initialValueRef.current = getValue(initialValue) as ObservedValueOf<ObservableType>\n }, [initialValue])\n\n const store = useMemo(() => {\n if (!cache.has(observable)) {\n const entry: Partial<CacheRecord<ObservedValueOf<ObservableType>>> = {\n snapshot: initialValueRef.current,\n }\n entry.observable = observable.pipe(\n map((value) => ({snapshot: value, error: undefined})),\n catchError((error) => of({snapshot: undefined, error})),\n tap(({snapshot, error}) => {\n entry.snapshot = snapshot\n entry.error = error\n }),\n // Note: any value or error emitted by the provided observable will be mapped to the cache entry's mutable state\n // and the observable is thereafter only used as a notifier to call `onStoreChange`, hence the `void` return type.\n map((value) => void value),\n // Ensure that the cache entry is deleted when the observable completes or errors.\n finalize(() => cache.delete(observable)),\n share(),\n )\n\n // Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.\n entry.subscription = entry.observable.subscribe()\n\n cache.set(observable, entry as CacheRecord<ObservedValueOf<ObservableType>>)\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const instance = cache.get(observable)!\n if (instance.subscription.closed) {\n instance.subscription = instance.observable.subscribe()\n }\n\n return {\n subscribe: (onStoreChange: () => void) => {\n const subscription = instance.observable.subscribe(onStoreChange)\n instance.subscription.unsubscribe()\n return () => {\n subscription.unsubscribe()\n }\n },\n getSnapshot: () => {\n if (instance.error) {\n throw instance.error\n }\n return instance.snapshot\n },\n }\n }, [observable])\n\n return useSyncExternalStore<ObservedValueOf<ObservableType>>(\n store.subscribe,\n store.getSnapshot,\n typeof initialValueRef.current === 'undefined' ? undefined : () => initialValueRef.current,\n )\n}\n","import {observableCallback} from 'observable-callback'\nimport {useCallback, useEffect, useInsertionEffect, useRef, useState} from 'react'\nimport {type Observable} from 'rxjs'\n\n/** @public */\nexport function useObservableEvent<T, U>(\n fn: (arg: Observable<T>) => Observable<U>,\n): (arg: T) => void {\n const [[calls$, call]] = useState(() => observableCallback<T>())\n\n const callback = useEffectEvent(fn)\n\n useEffect(() => {\n const subscription = calls$.pipe(callback).subscribe()\n return () => subscription.unsubscribe()\n }, [callback, calls$])\n\n return call\n}\n\n/**\n * This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19.\n * https://19.react.dev/learn/separating-events-from-effects#declaring-an-effect-event\n * To learn more about the ponyfill itself, see: https://blog.bitsrc.io/a-look-inside-the-useevent-polyfill-from-the-new-react-docs-d1c4739e8072\n */\nfunction useEffectEvent<\n const T extends (\n ...args: // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any[]\n ) => void,\n>(fn: T): T {\n const ref = useRef<T | null>(null)\n useInsertionEffect(() => {\n ref.current = fn\n }, [fn])\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return useCallback((...args: any[]) => {\n const f = ref.current!\n return f(...args)\n }, []) as T\n}\n"],"names":["useRef","useEffect","useMemo","map","catchError","of","tap","finalize","share","useSyncExternalStore","useState","observableCallback","useInsertionEffect","useCallback"],"mappings":";;;AAYA,SAAS,SAAY,OAA2C;AAC9D,SAAO,OAAO,SAAU,aAAa,MAAA,IAAU;AACjD;AASA,MAAM,4BAAY;AAiBF,SAAA,cACd,YACA,cACgD;AAKhD,QAAM,kBAAkBA,MAAA,OAAO,SAAS,YAAY,CAAoC;AAKxFC,QAAAA,UAAU,MAAM;AACE,oBAAA,UAAU,SAAS,YAAY;AAAA,EAAA,GAC9C,CAAC,YAAY,CAAC;AAEX,QAAA,QAAQC,MAAAA,QAAQ,MAAM;AAC1B,QAAI,CAAC,MAAM,IAAI,UAAU,GAAG;AAC1B,YAAM,QAA+D;AAAA,QACnE,UAAU,gBAAgB;AAAA,MAAA;AAE5B,YAAM,aAAa,WAAW;AAAA,QAC5BC,cAAI,CAAC,WAAW,EAAC,UAAU,OAAO,OAAO,SAAW;AAAA,QACpDC,gBAAW,CAAC,UAAUC,QAAG,EAAC,UAAU,QAAW,MAAK,CAAC,CAAC;AAAA,QACtDC,UAAAA,IAAI,CAAC,EAAC,UAAU,YAAW;AACnB,gBAAA,WAAW,UACjB,MAAM,QAAQ;AAAA,QAAA,CACf;AAAA;AAAA;AAAA,QAGDH,UAAA,IAAI,CAAC,UAAO;AAAA,QAAA,CAAa;AAAA;AAAA,QAEzBI,KAAAA,SAAS,MAAM,MAAM,OAAO,UAAU,CAAC;AAAA,QACvCC,WAAM;AAAA,MACR,GAGA,MAAM,eAAe,MAAM,WAAW,aAEtC,MAAM,IAAI,YAAY,KAAqD;AAAA,IAC7E;AAEM,UAAA,WAAW,MAAM,IAAI,UAAU;AACjC,WAAA,SAAS,aAAa,WACxB,SAAS,eAAe,SAAS,WAAW,cAGvC;AAAA,MACL,WAAW,CAAC,kBAA8B;AACxC,cAAM,eAAe,SAAS,WAAW,UAAU,aAAa;AACvD,eAAA,SAAA,aAAa,YAAY,GAC3B,MAAM;AACX,uBAAa,YAAY;AAAA,QAAA;AAAA,MAE7B;AAAA,MACA,aAAa,MAAM;AACjB,YAAI,SAAS;AACX,gBAAM,SAAS;AAEjB,eAAO,SAAS;AAAA,MAClB;AAAA,IAAA;AAAA,EACF,GACC,CAAC,UAAU,CAAC;AAER,SAAAC,MAAA;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,gBAAgB,UAAY,MAAc,SAAY,MAAM,gBAAgB;AAAA,EAAA;AAEvF;ACzGO,SAAS,mBACd,IACkB;AAClB,QAAM,CAAC,CAAC,QAAQ,IAAI,CAAC,IAAIC,MAAS,SAAA,MAAMC,mBAAsB,mBAAA,CAAC,GAEzD,WAAW,eAAe,EAAE;AAElC,SAAAV,MAAA,UAAU,MAAM;AACd,UAAM,eAAe,OAAO,KAAK,QAAQ,EAAE,UAAU;AAC9C,WAAA,MAAM,aAAa;EACzB,GAAA,CAAC,UAAU,MAAM,CAAC,GAEd;AACT;AAOA,SAAS,eAKP,IAAU;AACJ,QAAA,MAAMD,aAAiB,IAAI;AACjC,SAAAY,MAAA,mBAAmB,MAAM;AACvB,QAAI,UAAU;AAAA,KACb,CAAC,EAAE,CAAC,GAEAC,MAAAA,YAAY,IAAI,SAAgB;AACrC,UAAM,IAAI,IAAI;AACP,WAAA,EAAE,GAAG,IAAI;AAAA,EAClB,GAAG,CAAE,CAAA;AACP;;;"}
@@ -0,0 +1,87 @@
1
+ import { c } from "react/compiler-runtime";
2
+ import { useRef, useEffect, useSyncExternalStore, useState, useInsertionEffect } from "react";
3
+ import { catchError, of, finalize, share } from "rxjs";
4
+ import { map, tap } from "rxjs/operators";
5
+ import { observableCallback } from "observable-callback";
6
+ function getValue(value) {
7
+ return typeof value == "function" ? value() : value;
8
+ }
9
+ const cache = /* @__PURE__ */ new WeakMap();
10
+ function useObservable(observable, initialValue) {
11
+ const $ = c(14), initialValueRef = useRef(getValue(initialValue));
12
+ let t0;
13
+ $[0] !== initialValue ? (t0 = [initialValue], $[0] = initialValue, $[1] = t0) : t0 = $[1], useEffect(() => {
14
+ initialValueRef.current = getValue(initialValue);
15
+ }, t0);
16
+ let t1;
17
+ if (!cache.has(observable)) {
18
+ const entry = {
19
+ snapshot: initialValueRef.current
20
+ };
21
+ entry.observable = observable.pipe(map((value) => ({
22
+ snapshot: value,
23
+ error: void 0
24
+ })), catchError((error) => of({
25
+ snapshot: void 0,
26
+ error
27
+ })), tap((t22) => {
28
+ const {
29
+ snapshot,
30
+ error: error_0
31
+ } = t22;
32
+ entry.snapshot = snapshot, entry.error = error_0;
33
+ }), map((value_0) => {
34
+ }), finalize(() => cache.delete(observable)), share()), entry.subscription = entry.observable.subscribe(), cache.set(observable, entry);
35
+ }
36
+ let instance;
37
+ $[2] !== observable ? (instance = cache.get(observable), instance.subscription.closed && (instance.subscription = instance.observable.subscribe()), $[2] = observable, $[3] = instance) : instance = $[3];
38
+ let t2;
39
+ $[4] !== instance.observable || $[5] !== instance.subscription ? (t2 = (onStoreChange) => {
40
+ const subscription = instance.observable.subscribe(onStoreChange);
41
+ return instance.subscription.unsubscribe(), () => {
42
+ subscription.unsubscribe();
43
+ };
44
+ }, $[4] = instance.observable, $[5] = instance.subscription, $[6] = t2) : t2 = $[6];
45
+ let t3;
46
+ $[7] !== instance.error || $[8] !== instance.snapshot ? (t3 = () => {
47
+ if (instance.error)
48
+ throw instance.error;
49
+ return instance.snapshot;
50
+ }, $[7] = instance.error, $[8] = instance.snapshot, $[9] = t3) : t3 = $[9];
51
+ let t4;
52
+ $[10] !== t2 || $[11] !== t3 ? (t4 = {
53
+ subscribe: t2,
54
+ getSnapshot: t3
55
+ }, $[10] = t2, $[11] = t3, $[12] = t4) : t4 = $[12], t1 = t4;
56
+ const store = t1;
57
+ let t5;
58
+ return $[13] === Symbol.for("react.memo_cache_sentinel") ? (t5 = typeof initialValueRef.current > "u" ? void 0 : () => initialValueRef.current, $[13] = t5) : t5 = $[13], useSyncExternalStore(store.subscribe, store.getSnapshot, t5);
59
+ }
60
+ function useObservableEvent(fn) {
61
+ const $ = c(5);
62
+ let t0;
63
+ $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = () => observableCallback(), $[0] = t0) : t0 = $[0];
64
+ const [t1] = useState(t0), [calls$, call] = t1, callback = useEffectEvent(fn);
65
+ let t2, t3;
66
+ return $[1] !== calls$ || $[2] !== callback ? (t2 = () => {
67
+ const subscription = calls$.pipe(callback).subscribe();
68
+ return () => subscription.unsubscribe();
69
+ }, t3 = [callback, calls$], $[1] = calls$, $[2] = callback, $[3] = t2, $[4] = t3) : (t2 = $[3], t3 = $[4]), useEffect(t2, t3), call;
70
+ }
71
+ function useEffectEvent(fn) {
72
+ const $ = c(4), ref = useRef(null);
73
+ let t0, t1;
74
+ $[0] !== fn ? (t0 = () => {
75
+ ref.current = fn;
76
+ }, t1 = [fn], $[0] = fn, $[1] = t0, $[2] = t1) : (t0 = $[1], t1 = $[2]), useInsertionEffect(t0, t1);
77
+ let t2;
78
+ return $[3] === Symbol.for("react.memo_cache_sentinel") ? (t2 = (...t3) => {
79
+ const args = t3, f = ref.current;
80
+ return f(...args);
81
+ }, $[3] = t2) : t2 = $[3], t2;
82
+ }
83
+ export {
84
+ useObservable,
85
+ useObservableEvent
86
+ };
87
+ //# sourceMappingURL=index.compiled.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.compiled.js","sources":["../src/useObservable.ts","../src/useObservableEvent.ts"],"sourcesContent":["import {useEffect, useMemo, useRef, useSyncExternalStore} from 'react'\nimport {\n catchError,\n finalize,\n type Observable,\n type ObservedValueOf,\n of,\n share,\n type Subscription,\n} from 'rxjs'\nimport {map, tap} from 'rxjs/operators'\n\nfunction getValue<T>(value: T): T extends () => infer U ? U : T {\n return typeof value === 'function' ? value() : value\n}\n\ninterface CacheRecord<T> {\n subscription: Subscription\n observable: Observable<void>\n snapshot: T\n error?: unknown\n}\n\nconst cache = new WeakMap<Observable<any>, CacheRecord<any>>()\n\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>>(\n observable: ObservableType,\n initialValue: ObservedValueOf<ObservableType> | (() => ObservedValueOf<ObservableType>),\n): ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>>(\n observable: ObservableType,\n): undefined | ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>, InitialValue>(\n observable: ObservableType,\n initialValue: InitialValue,\n): InitialValue | ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>, InitialValue>(\n observable: ObservableType,\n initialValue?: InitialValue | (() => InitialValue),\n): InitialValue | ObservedValueOf<ObservableType> {\n /**\n * Store the initialValue in a ref, as we don't want a changed `initialValue` to trigger a re-subscription.\n * But we also don't want the initialValue to be stale if the observable changes.\n */\n const initialValueRef = useRef(getValue(initialValue) as ObservedValueOf<ObservableType>)\n\n /**\n * Ensures that the initialValue is always up-to-date in case the observable changes.\n */\n useEffect(() => {\n initialValueRef.current = getValue(initialValue) as ObservedValueOf<ObservableType>\n }, [initialValue])\n\n const store = useMemo(() => {\n if (!cache.has(observable)) {\n const entry: Partial<CacheRecord<ObservedValueOf<ObservableType>>> = {\n snapshot: initialValueRef.current,\n }\n entry.observable = observable.pipe(\n map((value) => ({snapshot: value, error: undefined})),\n catchError((error) => of({snapshot: undefined, error})),\n tap(({snapshot, error}) => {\n entry.snapshot = snapshot\n entry.error = error\n }),\n // Note: any value or error emitted by the provided observable will be mapped to the cache entry's mutable state\n // and the observable is thereafter only used as a notifier to call `onStoreChange`, hence the `void` return type.\n map((value) => void value),\n // Ensure that the cache entry is deleted when the observable completes or errors.\n finalize(() => cache.delete(observable)),\n share(),\n )\n\n // Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.\n entry.subscription = entry.observable.subscribe()\n\n cache.set(observable, entry as CacheRecord<ObservedValueOf<ObservableType>>)\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const instance = cache.get(observable)!\n if (instance.subscription.closed) {\n instance.subscription = instance.observable.subscribe()\n }\n\n return {\n subscribe: (onStoreChange: () => void) => {\n const subscription = instance.observable.subscribe(onStoreChange)\n instance.subscription.unsubscribe()\n return () => {\n subscription.unsubscribe()\n }\n },\n getSnapshot: () => {\n if (instance.error) {\n throw instance.error\n }\n return instance.snapshot\n },\n }\n }, [observable])\n\n return useSyncExternalStore<ObservedValueOf<ObservableType>>(\n store.subscribe,\n store.getSnapshot,\n typeof initialValueRef.current === 'undefined' ? undefined : () => initialValueRef.current,\n )\n}\n","import {observableCallback} from 'observable-callback'\nimport {useCallback, useEffect, useInsertionEffect, useRef, useState} from 'react'\nimport {type Observable} from 'rxjs'\n\n/** @public */\nexport function useObservableEvent<T, U>(\n fn: (arg: Observable<T>) => Observable<U>,\n): (arg: T) => void {\n const [[calls$, call]] = useState(() => observableCallback<T>())\n\n const callback = useEffectEvent(fn)\n\n useEffect(() => {\n const subscription = calls$.pipe(callback).subscribe()\n return () => subscription.unsubscribe()\n }, [callback, calls$])\n\n return call\n}\n\n/**\n * This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19.\n * https://19.react.dev/learn/separating-events-from-effects#declaring-an-effect-event\n * To learn more about the ponyfill itself, see: https://blog.bitsrc.io/a-look-inside-the-useevent-polyfill-from-the-new-react-docs-d1c4739e8072\n */\nfunction useEffectEvent<\n const T extends (\n ...args: // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any[]\n ) => void,\n>(fn: T): T {\n const ref = useRef<T | null>(null)\n useInsertionEffect(() => {\n ref.current = fn\n }, [fn])\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return useCallback((...args: any[]) => {\n const f = ref.current!\n return f(...args)\n }, []) as T\n}\n"],"names":["getValue","value","cache","WeakMap","useObservable","observable","initialValue","$","_c","initialValueRef","useRef","t0","useEffect","current","t1","has","entry","snapshot","pipe","map","error","undefined","catchError","of","tap","t2","error_0","value_0","finalize","delete","share","subscription","subscribe","set","instance","get","closed","onStoreChange","unsubscribe","t3","t4","getSnapshot","store","t5","Symbol","for","useSyncExternalStore","useObservableEvent","fn","observableCallback","useState","calls$","call","callback","useEffectEvent","ref","useInsertionEffect","args","f"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import {Observable} from 'rxjs'
2
+ import {ObservedValueOf} from 'rxjs'
3
+
4
+ /** @public */
5
+ export declare function useObservable<ObservableType extends Observable<any>>(
6
+ observable: ObservableType,
7
+ initialValue: ObservedValueOf<ObservableType> | (() => ObservedValueOf<ObservableType>),
8
+ ): ObservedValueOf<ObservableType>
9
+
10
+ /** @public */
11
+ export declare function useObservable<ObservableType extends Observable<any>>(
12
+ observable: ObservableType,
13
+ ): undefined | ObservedValueOf<ObservableType>
14
+
15
+ /** @public */
16
+ export declare function useObservable<ObservableType extends Observable<any>, InitialValue>(
17
+ observable: ObservableType,
18
+ initialValue: InitialValue,
19
+ ): InitialValue | ObservedValueOf<ObservableType>
20
+
21
+ /** @public */
22
+ export declare function useObservableEvent<T, U>(
23
+ fn: (arg: Observable<T>) => Observable<U>,
24
+ ): (arg: T) => void
25
+
26
+ export {}
package/dist/index.d.ts CHANGED
@@ -1,13 +1,22 @@
1
1
  import {Observable} from 'rxjs'
2
+ import {ObservedValueOf} from 'rxjs'
2
3
 
3
- /** @internal */
4
- export declare type UnboxObservable<T> = T extends Observable<infer U> ? U : never
4
+ /** @public */
5
+ export declare function useObservable<ObservableType extends Observable<any>>(
6
+ observable: ObservableType,
7
+ initialValue: ObservedValueOf<ObservableType> | (() => ObservedValueOf<ObservableType>),
8
+ ): ObservedValueOf<ObservableType>
5
9
 
6
10
  /** @public */
7
11
  export declare function useObservable<ObservableType extends Observable<any>>(
8
12
  observable: ObservableType,
9
- initialValue?: UnboxObservable<ObservableType> | (() => UnboxObservable<ObservableType>),
10
- ): UnboxObservable<ObservableType>
13
+ ): undefined | ObservedValueOf<ObservableType>
14
+
15
+ /** @public */
16
+ export declare function useObservable<ObservableType extends Observable<any>, InitialValue>(
17
+ observable: ObservableType,
18
+ initialValue: InitialValue,
19
+ ): InitialValue | ObservedValueOf<ObservableType>
11
20
 
12
21
  /** @public */
13
22
  export declare function useObservableEvent<T, U>(
package/dist/index.js CHANGED
@@ -1,52 +1,75 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: !0 });
3
- var react = require("react"), operators = require("rxjs/operators"), observableCallback = require("observable-callback");
1
+ import { useRef, useEffect, useMemo, useSyncExternalStore, useState, useInsertionEffect, useCallback } from "react";
2
+ import { catchError, of, finalize, share } from "rxjs";
3
+ import { map, tap } from "rxjs/operators";
4
+ import { observableCallback } from "observable-callback";
4
5
  function getValue(value) {
5
6
  return typeof value == "function" ? value() : value;
6
7
  }
7
8
  const cache = /* @__PURE__ */ new WeakMap();
8
9
  function useObservable(observable, initialValue) {
9
- const initialValueRef = react.useRef(getValue(initialValue));
10
- react.useEffect(() => {
10
+ const initialValueRef = useRef(getValue(initialValue));
11
+ useEffect(() => {
11
12
  initialValueRef.current = getValue(initialValue);
12
13
  }, [initialValue]);
13
- const store = react.useMemo(() => {
14
+ const store = useMemo(() => {
14
15
  if (!cache.has(observable)) {
15
16
  const entry = {
16
17
  snapshot: initialValueRef.current
17
18
  };
18
19
  entry.observable = observable.pipe(
19
- operators.shareReplay({ refCount: !0, bufferSize: 1 }),
20
- operators.tap((value) => entry.snapshot = value)
20
+ map((value) => ({ snapshot: value, error: void 0 })),
21
+ catchError((error) => of({ snapshot: void 0, error })),
22
+ tap(({ snapshot, error }) => {
23
+ entry.snapshot = snapshot, entry.error = error;
24
+ }),
25
+ // Note: any value or error emitted by the provided observable will be mapped to the cache entry's mutable state
26
+ // and the observable is thereafter only used as a notifier to call `onStoreChange`, hence the `void` return type.
27
+ map((value) => {
28
+ }),
29
+ // Ensure that the cache entry is deleted when the observable completes or errors.
30
+ finalize(() => cache.delete(observable)),
31
+ share()
21
32
  ), entry.subscription = entry.observable.subscribe(), cache.set(observable, entry);
22
33
  }
23
34
  const instance = cache.get(observable);
24
35
  return instance.subscription.closed && (instance.subscription = instance.observable.subscribe()), {
25
36
  subscribe: (onStoreChange) => {
26
37
  const subscription = instance.observable.subscribe(onStoreChange);
27
- return instance.subscription.unsubscribe(), () => subscription.unsubscribe();
38
+ return instance.subscription.unsubscribe(), () => {
39
+ subscription.unsubscribe();
40
+ };
28
41
  },
29
- getSnapshot: () => instance.snapshot
42
+ getSnapshot: () => {
43
+ if (instance.error)
44
+ throw instance.error;
45
+ return instance.snapshot;
46
+ }
30
47
  };
31
48
  }, [observable]);
32
- return react.useSyncExternalStore(store.subscribe, store.getSnapshot);
49
+ return useSyncExternalStore(
50
+ store.subscribe,
51
+ store.getSnapshot,
52
+ typeof initialValueRef.current > "u" ? void 0 : () => initialValueRef.current
53
+ );
33
54
  }
34
55
  function useObservableEvent(fn) {
35
- const [[calls$, call]] = react.useState(() => observableCallback.observableCallback()), callback = useEffectEvent(fn);
36
- return react.useEffect(() => {
56
+ const [[calls$, call]] = useState(() => observableCallback()), callback = useEffectEvent(fn);
57
+ return useEffect(() => {
37
58
  const subscription = calls$.pipe(callback).subscribe();
38
59
  return () => subscription.unsubscribe();
39
60
  }, [callback, calls$]), call;
40
61
  }
41
62
  function useEffectEvent(fn) {
42
- const ref = react.useRef(null);
43
- return react.useInsertionEffect(() => {
63
+ const ref = useRef(null);
64
+ return useInsertionEffect(() => {
44
65
  ref.current = fn;
45
- }, [fn]), react.useCallback((...args) => {
66
+ }, [fn]), useCallback((...args) => {
46
67
  const f = ref.current;
47
68
  return f(...args);
48
69
  }, []);
49
70
  }
50
- exports.useObservable = useObservable;
51
- exports.useObservableEvent = useObservableEvent;
71
+ export {
72
+ useObservable,
73
+ useObservableEvent
74
+ };
52
75
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/useObservable.ts","../src/useObservableEvent.ts"],"sourcesContent":["import {useEffect, useMemo, useRef, useSyncExternalStore} from 'react'\nimport type {Observable, Subscription} from 'rxjs'\nimport {shareReplay, tap} from 'rxjs/operators'\n\nfunction getValue<T>(value: T): T extends () => infer U ? U : T {\n return typeof value === 'function' ? value() : value\n}\ninterface CacheRecord<T> {\n subscription: Subscription\n observable: Observable<T>\n snapshot: T\n}\n\nconst cache = new WeakMap<Observable<any>, CacheRecord<any>>()\n\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>>(\n observable: ObservableType,\n initialValue?: UnboxObservable<ObservableType> | (() => UnboxObservable<ObservableType>),\n): UnboxObservable<ObservableType> {\n /**\n * Store the initialValue in a ref, as we don't want a changed `initialValue` to trigger a re-subscription.\n * But we also don't want the initialValue to be stale if the observable changes.\n */\n const initialValueRef = useRef(getValue(initialValue) as UnboxObservable<ObservableType>)\n\n /**\n * Ensures that the initialValue is always up-to-date in case the observable changes.\n */\n useEffect(() => {\n initialValueRef.current = getValue(initialValue) as UnboxObservable<ObservableType>\n }, [initialValue])\n\n const store = useMemo(() => {\n if (!cache.has(observable)) {\n const entry: Partial<CacheRecord<UnboxObservable<ObservableType>>> = {\n snapshot: initialValueRef.current,\n }\n entry.observable = observable.pipe(\n shareReplay({refCount: true, bufferSize: 1}),\n tap((value) => (entry.snapshot = value)),\n )\n\n // Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.\n entry.subscription = entry.observable.subscribe()\n\n cache.set(observable, entry as CacheRecord<UnboxObservable<ObservableType>>)\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const instance = cache.get(observable)!\n if (instance.subscription.closed) {\n instance.subscription = instance.observable.subscribe()\n }\n\n return {\n subscribe: (onStoreChange: () => void) => {\n const subscription = instance.observable.subscribe(onStoreChange)\n instance.subscription.unsubscribe()\n return () => subscription.unsubscribe()\n },\n getSnapshot: () => instance.snapshot,\n }\n }, [observable])\n\n return useSyncExternalStore<UnboxObservable<ObservableType>>(store.subscribe, store.getSnapshot)\n}\n\n/** @internal */\nexport type UnboxObservable<T> = T extends Observable<infer U> ? U : never\n","import {observableCallback} from 'observable-callback'\nimport {useCallback, useEffect, useInsertionEffect, useRef, useState} from 'react'\nimport {type Observable} from 'rxjs'\n\n/** @public */\nexport function useObservableEvent<T, U>(\n fn: (arg: Observable<T>) => Observable<U>,\n): (arg: T) => void {\n const [[calls$, call]] = useState(() => observableCallback<T>())\n\n const callback = useEffectEvent(fn)\n\n useEffect(() => {\n const subscription = calls$.pipe(callback).subscribe()\n return () => subscription.unsubscribe()\n }, [callback, calls$])\n\n return call\n}\n\n/**\n * This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19\n */\nfunction useEffectEvent<\n const T extends (\n ...args: // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any[]\n ) => void,\n>(fn: T): T {\n const ref = useRef<T | null>(null)\n useInsertionEffect(() => {\n ref.current = fn\n }, [fn])\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return useCallback((...args: any[]) => {\n const f = ref.current!\n return f(...args)\n }, []) as T\n}\n"],"names":["useRef","useEffect","useMemo","shareReplay","tap","useSyncExternalStore","useState","observableCallback","useInsertionEffect","useCallback"],"mappings":";;;AAIA,SAAS,SAAY,OAA2C;AAC9D,SAAO,OAAO,SAAU,aAAa,MAAA,IAAU;AACjD;AAOA,MAAM,4BAAY;AAGF,SAAA,cACd,YACA,cACiC;AAKjC,QAAM,kBAAkBA,MAAA,OAAO,SAAS,YAAY,CAAoC;AAKxFC,QAAAA,UAAU,MAAM;AACE,oBAAA,UAAU,SAAS,YAAY;AAAA,EAAA,GAC9C,CAAC,YAAY,CAAC;AAEX,QAAA,QAAQC,MAAAA,QAAQ,MAAM;AAC1B,QAAI,CAAC,MAAM,IAAI,UAAU,GAAG;AAC1B,YAAM,QAA+D;AAAA,QACnE,UAAU,gBAAgB;AAAA,MAAA;AAE5B,YAAM,aAAa,WAAW;AAAA,QAC5BC,sBAAY,EAAC,UAAU,IAAM,YAAY,GAAE;AAAA,QAC3CC,UAAAA,IAAI,CAAC,UAAW,MAAM,WAAW,KAAM;AAAA,MACzC,GAGA,MAAM,eAAe,MAAM,WAAW,aAEtC,MAAM,IAAI,YAAY,KAAqD;AAAA,IAC7E;AAEM,UAAA,WAAW,MAAM,IAAI,UAAU;AACjC,WAAA,SAAS,aAAa,WACxB,SAAS,eAAe,SAAS,WAAW,cAGvC;AAAA,MACL,WAAW,CAAC,kBAA8B;AACxC,cAAM,eAAe,SAAS,WAAW,UAAU,aAAa;AAChE,eAAA,SAAS,aAAa,YACf,GAAA,MAAM,aAAa;MAC5B;AAAA,MACA,aAAa,MAAM,SAAS;AAAA,IAAA;AAAA,EAC9B,GACC,CAAC,UAAU,CAAC;AAEf,SAAOC,MAAsD,qBAAA,MAAM,WAAW,MAAM,WAAW;AACjG;AC5DO,SAAS,mBACd,IACkB;AAClB,QAAM,CAAC,CAAC,QAAQ,IAAI,CAAC,IAAIC,MAAS,SAAA,MAAMC,mBAAsB,mBAAA,CAAC,GAEzD,WAAW,eAAe,EAAE;AAElC,SAAAN,MAAA,UAAU,MAAM;AACd,UAAM,eAAe,OAAO,KAAK,QAAQ,EAAE,UAAU;AAC9C,WAAA,MAAM,aAAa;EACzB,GAAA,CAAC,UAAU,MAAM,CAAC,GAEd;AACT;AAKA,SAAS,eAKP,IAAU;AACJ,QAAA,MAAMD,aAAiB,IAAI;AACjC,SAAAQ,MAAA,mBAAmB,MAAM;AACvB,QAAI,UAAU;AAAA,KACb,CAAC,EAAE,CAAC,GAEAC,MAAAA,YAAY,IAAI,SAAgB;AACrC,UAAM,IAAI,IAAI;AACP,WAAA,EAAE,GAAG,IAAI;AAAA,EAClB,GAAG,CAAE,CAAA;AACP;;;"}
1
+ {"version":3,"file":"index.js","sources":["../src/useObservable.ts","../src/useObservableEvent.ts"],"sourcesContent":["import {useEffect, useMemo, useRef, useSyncExternalStore} from 'react'\nimport {\n catchError,\n finalize,\n type Observable,\n type ObservedValueOf,\n of,\n share,\n type Subscription,\n} from 'rxjs'\nimport {map, tap} from 'rxjs/operators'\n\nfunction getValue<T>(value: T): T extends () => infer U ? U : T {\n return typeof value === 'function' ? value() : value\n}\n\ninterface CacheRecord<T> {\n subscription: Subscription\n observable: Observable<void>\n snapshot: T\n error?: unknown\n}\n\nconst cache = new WeakMap<Observable<any>, CacheRecord<any>>()\n\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>>(\n observable: ObservableType,\n initialValue: ObservedValueOf<ObservableType> | (() => ObservedValueOf<ObservableType>),\n): ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>>(\n observable: ObservableType,\n): undefined | ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>, InitialValue>(\n observable: ObservableType,\n initialValue: InitialValue,\n): InitialValue | ObservedValueOf<ObservableType>\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>, InitialValue>(\n observable: ObservableType,\n initialValue?: InitialValue | (() => InitialValue),\n): InitialValue | ObservedValueOf<ObservableType> {\n /**\n * Store the initialValue in a ref, as we don't want a changed `initialValue` to trigger a re-subscription.\n * But we also don't want the initialValue to be stale if the observable changes.\n */\n const initialValueRef = useRef(getValue(initialValue) as ObservedValueOf<ObservableType>)\n\n /**\n * Ensures that the initialValue is always up-to-date in case the observable changes.\n */\n useEffect(() => {\n initialValueRef.current = getValue(initialValue) as ObservedValueOf<ObservableType>\n }, [initialValue])\n\n const store = useMemo(() => {\n if (!cache.has(observable)) {\n const entry: Partial<CacheRecord<ObservedValueOf<ObservableType>>> = {\n snapshot: initialValueRef.current,\n }\n entry.observable = observable.pipe(\n map((value) => ({snapshot: value, error: undefined})),\n catchError((error) => of({snapshot: undefined, error})),\n tap(({snapshot, error}) => {\n entry.snapshot = snapshot\n entry.error = error\n }),\n // Note: any value or error emitted by the provided observable will be mapped to the cache entry's mutable state\n // and the observable is thereafter only used as a notifier to call `onStoreChange`, hence the `void` return type.\n map((value) => void value),\n // Ensure that the cache entry is deleted when the observable completes or errors.\n finalize(() => cache.delete(observable)),\n share(),\n )\n\n // Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.\n entry.subscription = entry.observable.subscribe()\n\n cache.set(observable, entry as CacheRecord<ObservedValueOf<ObservableType>>)\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const instance = cache.get(observable)!\n if (instance.subscription.closed) {\n instance.subscription = instance.observable.subscribe()\n }\n\n return {\n subscribe: (onStoreChange: () => void) => {\n const subscription = instance.observable.subscribe(onStoreChange)\n instance.subscription.unsubscribe()\n return () => {\n subscription.unsubscribe()\n }\n },\n getSnapshot: () => {\n if (instance.error) {\n throw instance.error\n }\n return instance.snapshot\n },\n }\n }, [observable])\n\n return useSyncExternalStore<ObservedValueOf<ObservableType>>(\n store.subscribe,\n store.getSnapshot,\n typeof initialValueRef.current === 'undefined' ? undefined : () => initialValueRef.current,\n )\n}\n","import {observableCallback} from 'observable-callback'\nimport {useCallback, useEffect, useInsertionEffect, useRef, useState} from 'react'\nimport {type Observable} from 'rxjs'\n\n/** @public */\nexport function useObservableEvent<T, U>(\n fn: (arg: Observable<T>) => Observable<U>,\n): (arg: T) => void {\n const [[calls$, call]] = useState(() => observableCallback<T>())\n\n const callback = useEffectEvent(fn)\n\n useEffect(() => {\n const subscription = calls$.pipe(callback).subscribe()\n return () => subscription.unsubscribe()\n }, [callback, calls$])\n\n return call\n}\n\n/**\n * This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19.\n * https://19.react.dev/learn/separating-events-from-effects#declaring-an-effect-event\n * To learn more about the ponyfill itself, see: https://blog.bitsrc.io/a-look-inside-the-useevent-polyfill-from-the-new-react-docs-d1c4739e8072\n */\nfunction useEffectEvent<\n const T extends (\n ...args: // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any[]\n ) => void,\n>(fn: T): T {\n const ref = useRef<T | null>(null)\n useInsertionEffect(() => {\n ref.current = fn\n }, [fn])\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return useCallback((...args: any[]) => {\n const f = ref.current!\n return f(...args)\n }, []) as T\n}\n"],"names":[],"mappings":";;;;AAYA,SAAS,SAAY,OAA2C;AAC9D,SAAO,OAAO,SAAU,aAAa,MAAA,IAAU;AACjD;AASA,MAAM,4BAAY;AAiBF,SAAA,cACd,YACA,cACgD;AAKhD,QAAM,kBAAkB,OAAO,SAAS,YAAY,CAAoC;AAKxF,YAAU,MAAM;AACE,oBAAA,UAAU,SAAS,YAAY;AAAA,EAAA,GAC9C,CAAC,YAAY,CAAC;AAEX,QAAA,QAAQ,QAAQ,MAAM;AAC1B,QAAI,CAAC,MAAM,IAAI,UAAU,GAAG;AAC1B,YAAM,QAA+D;AAAA,QACnE,UAAU,gBAAgB;AAAA,MAAA;AAE5B,YAAM,aAAa,WAAW;AAAA,QAC5B,IAAI,CAAC,WAAW,EAAC,UAAU,OAAO,OAAO,SAAW;AAAA,QACpD,WAAW,CAAC,UAAU,GAAG,EAAC,UAAU,QAAW,MAAK,CAAC,CAAC;AAAA,QACtD,IAAI,CAAC,EAAC,UAAU,YAAW;AACnB,gBAAA,WAAW,UACjB,MAAM,QAAQ;AAAA,QAAA,CACf;AAAA;AAAA;AAAA,QAGD,IAAI,CAAC,UAAO;AAAA,QAAA,CAAa;AAAA;AAAA,QAEzB,SAAS,MAAM,MAAM,OAAO,UAAU,CAAC;AAAA,QACvC,MAAM;AAAA,MACR,GAGA,MAAM,eAAe,MAAM,WAAW,aAEtC,MAAM,IAAI,YAAY,KAAqD;AAAA,IAC7E;AAEM,UAAA,WAAW,MAAM,IAAI,UAAU;AACjC,WAAA,SAAS,aAAa,WACxB,SAAS,eAAe,SAAS,WAAW,cAGvC;AAAA,MACL,WAAW,CAAC,kBAA8B;AACxC,cAAM,eAAe,SAAS,WAAW,UAAU,aAAa;AACvD,eAAA,SAAA,aAAa,YAAY,GAC3B,MAAM;AACX,uBAAa,YAAY;AAAA,QAAA;AAAA,MAE7B;AAAA,MACA,aAAa,MAAM;AACjB,YAAI,SAAS;AACX,gBAAM,SAAS;AAEjB,eAAO,SAAS;AAAA,MAClB;AAAA,IAAA;AAAA,EACF,GACC,CAAC,UAAU,CAAC;AAER,SAAA;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,IACN,OAAO,gBAAgB,UAAY,MAAc,SAAY,MAAM,gBAAgB;AAAA,EAAA;AAEvF;ACzGO,SAAS,mBACd,IACkB;AAClB,QAAM,CAAC,CAAC,QAAQ,IAAI,CAAC,IAAI,SAAS,MAAM,mBAAsB,CAAC,GAEzD,WAAW,eAAe,EAAE;AAElC,SAAA,UAAU,MAAM;AACd,UAAM,eAAe,OAAO,KAAK,QAAQ,EAAE,UAAU;AAC9C,WAAA,MAAM,aAAa;EACzB,GAAA,CAAC,UAAU,MAAM,CAAC,GAEd;AACT;AAOA,SAAS,eAKP,IAAU;AACJ,QAAA,MAAM,OAAiB,IAAI;AACjC,SAAA,mBAAmB,MAAM;AACvB,QAAI,UAAU;AAAA,KACb,CAAC,EAAE,CAAC,GAEA,YAAY,IAAI,SAAgB;AACrC,UAAM,IAAI,IAAI;AACP,WAAA,EAAE,GAAG,IAAI;AAAA,EAClB,GAAG,CAAE,CAAA;AACP;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-rx",
3
- "version": "3.0.0-crx-749.1",
3
+ "version": "3.0.1-canary.0",
4
4
  "description": "React + RxJS = <3",
5
5
  "keywords": [
6
6
  "action",
@@ -15,6 +15,7 @@
15
15
  "pipe",
16
16
  "react",
17
17
  "react18",
18
+ "react19",
18
19
  "reactive",
19
20
  "realtime",
20
21
  "rx",
@@ -28,7 +29,6 @@
28
29
  "sync",
29
30
  "typesafe",
30
31
  "typescript",
31
- "use-sync-external-store",
32
32
  "use"
33
33
  ],
34
34
  "homepage": "https://react-rx.dev",
@@ -46,17 +46,21 @@
46
46
  "Cody Olsen <stipsan@gmail.com> (https://github.com/stipsan)"
47
47
  ],
48
48
  "sideEffects": false,
49
- "type": "commonjs",
49
+ "type": "module",
50
50
  "exports": {
51
51
  ".": {
52
52
  "source": "./src/index.ts",
53
- "import": "./dist/index.mjs",
53
+ "react-compiler": {
54
+ "source": "./src/index.ts",
55
+ "default": "./dist/index.compiled.js"
56
+ },
57
+ "require": "./dist/index.cjs",
54
58
  "default": "./dist/index.js"
55
59
  },
56
60
  "./package.json": "./package.json"
57
61
  },
58
- "main": "./dist/index.js",
59
- "module": "./dist/index.mjs",
62
+ "main": "./dist/index.cjs",
63
+ "module": "./dist/index.js",
60
64
  "types": "./dist/index.d.ts",
61
65
  "files": [
62
66
  "dist",
@@ -66,26 +70,17 @@
66
70
  "website",
67
71
  "."
68
72
  ],
69
- "scripts": {
70
- "build": "pkg build --strict --clean --check",
71
- "dev": "cd website && npm run dev",
72
- "prepublishOnly": "npm run build",
73
- "watch": "npm run build -- --watch",
74
- "test": "jest",
75
- "lint": "eslint --cache ."
76
- },
77
73
  "browserslist": "extends @sanity/browserslist-config",
78
74
  "prettier": "@sanity/prettier-config",
79
75
  "dependencies": {
80
76
  "observable-callback": "^1.0.3"
81
77
  },
82
78
  "devDependencies": {
83
- "@sanity/pkg-utils": "^6.9.2",
79
+ "@sanity/pkg-utils": "6.9.4-canary.0",
84
80
  "@sanity/prettier-config": "^1.0.2",
85
81
  "@sanity/semantic-release-preset": "^4.1.8",
86
82
  "@testing-library/dom": "^10.1.0",
87
83
  "@testing-library/react": "^16.0.0",
88
- "@types/jest": "^29.5.3",
89
84
  "@types/node": "^18.17.5",
90
85
  "@types/react": "^18.3.3",
91
86
  "@types/react-dom": "^18.3.0",
@@ -98,21 +93,26 @@
98
93
  "eslint-plugin-react-compiler": "0.0.0-experimental-51a85ea-20240601",
99
94
  "eslint-plugin-react-hooks": "^4.6.2",
100
95
  "eslint-plugin-simple-import-sort": "^12.1.0",
101
- "jest": "^29.6.2",
102
- "jest-environment-jsdom": "^29.6.2",
103
- "jsdom": "^20.0.3",
104
- "npm-run-all": "^4.1.5",
96
+ "jsdom": "^24.1.0",
105
97
  "prettier": "^3.3.1",
106
98
  "react": "^18.3.1",
107
99
  "react-dom": "^18.3.1",
108
100
  "react-test-renderer": "^18.3.1",
109
101
  "rxjs": "^7.8.1",
110
102
  "semantic-release": "^24.0.0",
111
- "ts-jest": "^29.1.1",
112
- "typescript": "5.4.5"
103
+ "typescript": "5.4.5",
104
+ "vitest": "^1.6.0"
113
105
  },
114
106
  "peerDependencies": {
115
107
  "react": "^18.3 || >=19.0.0-rc",
116
108
  "rxjs": "^7"
109
+ },
110
+ "scripts": {
111
+ "build": "pkg build --strict --clean --check",
112
+ "dev": "pnpm --filter 'react-rx-website' dev",
113
+ "format": "prettier --cache --write .",
114
+ "lint": "eslint --cache .",
115
+ "test": "vitest run --typecheck",
116
+ "watch": "pnpm build -- --watch"
117
117
  }
118
- }
118
+ }
@@ -0,0 +1,49 @@
1
+ import {render} from '@testing-library/react'
2
+ import React from 'react'
3
+ import {mergeMap, of, Subject, throwError} from 'rxjs'
4
+ import {expect, test, vitest} from 'vitest'
5
+
6
+ import {useObservable} from '../useObservable.ts'
7
+
8
+ test('errors emitted by the observable should be thrown during the react render phase', () => {
9
+ const subject = new Subject<{error: boolean; message: string}>()
10
+
11
+ const messages = subject
12
+ .asObservable()
13
+ .pipe(
14
+ mergeMap((value) =>
15
+ value.error ? throwError(() => new Error(value.message)) : of(value.message),
16
+ ),
17
+ )
18
+
19
+ function ObservableComponent() {
20
+ return useObservable(messages, '☺️')
21
+ }
22
+
23
+ const {container, rerender} = render(<ObservableComponent />)
24
+ // no error (yet)
25
+ expect(container).toMatchInlineSnapshot(`
26
+ <div>
27
+ ☺️
28
+ </div>
29
+ `)
30
+
31
+ // Note that the error is thrown later, during the render phase
32
+ subject.next({error: true, message: 'Boom'})
33
+
34
+ const consoleErrorSpy = vitest.spyOn(globalThis.console, 'error').mockImplementation(() => {
35
+ // silence console.error()'s
36
+ })
37
+ expect(() => rerender(<ObservableComponent />)).toThrowErrorMatchingInlineSnapshot(
38
+ `[Error: Boom]`,
39
+ )
40
+
41
+ // Assert that react warnings are captured and logged to the console
42
+ // note: this may change with React version
43
+ expect(consoleErrorSpy.mock.calls.flat().join('\n')).toMatchObject(
44
+ expect.stringContaining('Uncaught [Error: Boom]'),
45
+ )
46
+ expect(consoleErrorSpy.mock.calls.flat().join('\n')).toMatchObject(
47
+ expect.stringContaining('The above error occurred in the <ObservableComponent> component'),
48
+ )
49
+ })
@@ -1,6 +1,7 @@
1
1
  import {act, render} from '@testing-library/react'
2
2
  import {createElement, Fragment, StrictMode, useEffect} from 'react'
3
3
  import {BehaviorSubject, Observable} from 'rxjs'
4
+ import {expect, test} from 'vitest'
4
5
 
5
6
  import {useObservable} from '../useObservable'
6
7
 
@@ -0,0 +1,27 @@
1
+ import {of} from 'rxjs'
2
+ import {expectTypeOf, test} from 'vitest'
3
+
4
+ import {useObservable} from '../useObservable'
5
+
6
+ test('useObservable with no initial value can be undefined', () => {
7
+ const observable = of('foo')
8
+
9
+ expectTypeOf(useObservable(observable)).toEqualTypeOf<string | undefined>()
10
+
11
+ //@ts-expect-error - because initial value is not given, we can't guarantee the observable emits a sync value, so it could be undefined
12
+ expectTypeOf(useObservable(observable)).toEqualTypeOf<string>()
13
+ })
14
+
15
+ test('return type of useObservable with initial value is not undefined', () => {
16
+ const observable = of('foo')
17
+ //@ts-expect-error - because initial value is given, the return type can never be undefined
18
+ expectTypeOf(useObservable(observable, 'bar')).toEqualTypeOf<string | undefined>()
19
+ })
20
+
21
+ test('useObservable with initial value if a different type returns a union of the observed type and the initial value type', () => {
22
+ const observable = of('foo')
23
+
24
+ expectTypeOf(useObservable(observable, 1)).toEqualTypeOf<string | number>()
25
+
26
+ expectTypeOf(useObservable(observable, 'foo')).toEqualTypeOf<string>()
27
+ })
@@ -1,7 +1,8 @@
1
1
  import {act, render, renderHook} from '@testing-library/react'
2
2
  import {createElement, Fragment} from 'react'
3
- import {asyncScheduler, Observable, of, scheduled, Subject, timer} from 'rxjs'
4
- import {mapTo} from 'rxjs/operators'
3
+ import {asyncScheduler, Observable, of, ReplaySubject, scheduled, share, Subject, timer} from 'rxjs'
4
+ import {map} from 'rxjs/operators'
5
+ import {expect, test} from 'vitest'
5
6
 
6
7
  import {useObservable} from '../useObservable'
7
8
 
@@ -42,7 +43,7 @@ test('should only subscribe once when given same observable on re-renders', () =
42
43
  })
43
44
 
44
45
  test('should not return undefined during render if initial value is given', () => {
45
- const observable = timer(100).pipe(mapTo('emitted value'))
46
+ const observable = timer(100).pipe(map(() => 'emitted value'))
46
47
 
47
48
  const returnedValues: unknown[] = []
48
49
  function ObservableComponent() {
@@ -102,6 +103,90 @@ test('should have passed initialValue as initial value from delayed observables'
102
103
  unmount()
103
104
  })
104
105
 
106
+ test('should rerender with initial value if component unmounts and then remounts', () => {
107
+ const values$ = new Subject<string>()
108
+ const firstHook = renderHook(() => useObservable(values$, 'initial'))
109
+
110
+ expect(firstHook.result.current).toBe('initial')
111
+
112
+ act(() => values$.next('something'))
113
+ expect(firstHook.result.current).toBe('something')
114
+
115
+ firstHook.unmount()
116
+
117
+ const nextHook = renderHook(() => useObservable(values$, 'initial2'))
118
+
119
+ expect(nextHook.result.current).toBe('initial2')
120
+ })
121
+
122
+ test('should share the observable between each concurrent subscribing hook', () => {
123
+ let subscribeCount = 0
124
+ const observable = new Observable<number>((subscriber) => {
125
+ subscriber.next(subscribeCount++)
126
+ })
127
+ const firstHook = renderHook(() => useObservable(observable))
128
+ expect(firstHook.result.current).toBe(0)
129
+ const secondHook = renderHook(() => useObservable(observable))
130
+ expect(secondHook.result.current).toBe(0)
131
+ firstHook.unmount()
132
+ secondHook.unmount()
133
+
134
+ const thirdHook = renderHook(() => useObservable(observable))
135
+ expect(thirdHook.result.current).toBe(1)
136
+ thirdHook.unmount()
137
+ })
138
+
139
+ test('should restart any completed observable on mount', () => {
140
+ let subscribeCount = 0
141
+ let unsubscribeCount = 0
142
+
143
+ type Notification<T> =
144
+ | {kind: 'next'; value: T}
145
+ | {kind: 'error'; error: Error}
146
+ | {kind: 'complete'}
147
+
148
+ const notifications$ = new Subject<Notification<string>>()
149
+
150
+ const observable = new Observable<string>((subscriber) => {
151
+ subscribeCount++
152
+ const subscription = notifications$.subscribe((notification) => {
153
+ if (notification.kind === 'next') {
154
+ subscriber.next(notification.value)
155
+ } else if (notification.kind === 'error') {
156
+ subscriber.error(notification.error)
157
+ } else if (notification.kind === 'complete') {
158
+ subscriber.complete()
159
+ }
160
+ })
161
+ return () => {
162
+ unsubscribeCount++
163
+ subscription.unsubscribe()
164
+ }
165
+ }).pipe(share({connector: () => new ReplaySubject(1)}))
166
+
167
+ const firstHook = renderHook(() => useObservable(observable, 'initial'))
168
+ expect(firstHook.result.current).toBe('initial')
169
+
170
+ act(() => notifications$.next({kind: 'next', value: 'something'}))
171
+ expect(firstHook.result.current).toBe('something')
172
+ act(() => notifications$.next({kind: 'complete'}))
173
+ expect(firstHook.result.current).toBe('something')
174
+ act(() => notifications$.next({kind: 'next', value: 'after complete'}))
175
+ expect(firstHook.result.current).toBe('something')
176
+
177
+ expect(subscribeCount).toBe(1)
178
+ expect(unsubscribeCount).toBe(1)
179
+
180
+ firstHook.unmount()
181
+
182
+ const secondHook = renderHook(() => useObservable(observable))
183
+ expect(secondHook.result.current).toBe(undefined)
184
+ expect(subscribeCount).toBe(2)
185
+ expect(unsubscribeCount).toBe(1)
186
+ secondHook.unmount()
187
+ expect(unsubscribeCount).toBe(2)
188
+ })
189
+
105
190
  test('should update with values from observables', () => {
106
191
  const values$ = new Subject<string>()
107
192
  const {result, unmount} = renderHook(() => useObservable(values$))
@@ -1,14 +1,24 @@
1
1
  import {useEffect, useMemo, useRef, useSyncExternalStore} from 'react'
2
- import type {Observable, Subscription} from 'rxjs'
3
- import {shareReplay, tap} from 'rxjs/operators'
2
+ import {
3
+ catchError,
4
+ finalize,
5
+ type Observable,
6
+ type ObservedValueOf,
7
+ of,
8
+ share,
9
+ type Subscription,
10
+ } from 'rxjs'
11
+ import {map, tap} from 'rxjs/operators'
4
12
 
5
13
  function getValue<T>(value: T): T extends () => infer U ? U : T {
6
14
  return typeof value === 'function' ? value() : value
7
15
  }
16
+
8
17
  interface CacheRecord<T> {
9
18
  subscription: Subscription
10
- observable: Observable<T>
19
+ observable: Observable<void>
11
20
  snapshot: T
21
+ error?: unknown
12
22
  }
13
23
 
14
24
  const cache = new WeakMap<Observable<any>, CacheRecord<any>>()
@@ -16,35 +26,59 @@ const cache = new WeakMap<Observable<any>, CacheRecord<any>>()
16
26
  /** @public */
17
27
  export function useObservable<ObservableType extends Observable<any>>(
18
28
  observable: ObservableType,
19
- initialValue?: UnboxObservable<ObservableType> | (() => UnboxObservable<ObservableType>),
20
- ): UnboxObservable<ObservableType> {
29
+ initialValue: ObservedValueOf<ObservableType> | (() => ObservedValueOf<ObservableType>),
30
+ ): ObservedValueOf<ObservableType>
31
+ /** @public */
32
+ export function useObservable<ObservableType extends Observable<any>>(
33
+ observable: ObservableType,
34
+ ): undefined | ObservedValueOf<ObservableType>
35
+ /** @public */
36
+ export function useObservable<ObservableType extends Observable<any>, InitialValue>(
37
+ observable: ObservableType,
38
+ initialValue: InitialValue,
39
+ ): InitialValue | ObservedValueOf<ObservableType>
40
+ /** @public */
41
+ export function useObservable<ObservableType extends Observable<any>, InitialValue>(
42
+ observable: ObservableType,
43
+ initialValue?: InitialValue | (() => InitialValue),
44
+ ): InitialValue | ObservedValueOf<ObservableType> {
21
45
  /**
22
46
  * Store the initialValue in a ref, as we don't want a changed `initialValue` to trigger a re-subscription.
23
47
  * But we also don't want the initialValue to be stale if the observable changes.
24
48
  */
25
- const initialValueRef = useRef(getValue(initialValue) as UnboxObservable<ObservableType>)
49
+ const initialValueRef = useRef(getValue(initialValue) as ObservedValueOf<ObservableType>)
26
50
 
27
51
  /**
28
52
  * Ensures that the initialValue is always up-to-date in case the observable changes.
29
53
  */
30
54
  useEffect(() => {
31
- initialValueRef.current = getValue(initialValue) as UnboxObservable<ObservableType>
55
+ initialValueRef.current = getValue(initialValue) as ObservedValueOf<ObservableType>
32
56
  }, [initialValue])
33
57
 
34
58
  const store = useMemo(() => {
35
59
  if (!cache.has(observable)) {
36
- const entry: Partial<CacheRecord<UnboxObservable<ObservableType>>> = {
60
+ const entry: Partial<CacheRecord<ObservedValueOf<ObservableType>>> = {
37
61
  snapshot: initialValueRef.current,
38
62
  }
39
63
  entry.observable = observable.pipe(
40
- shareReplay({refCount: true, bufferSize: 1}),
41
- tap((value) => (entry.snapshot = value)),
64
+ map((value) => ({snapshot: value, error: undefined})),
65
+ catchError((error) => of({snapshot: undefined, error})),
66
+ tap(({snapshot, error}) => {
67
+ entry.snapshot = snapshot
68
+ entry.error = error
69
+ }),
70
+ // Note: any value or error emitted by the provided observable will be mapped to the cache entry's mutable state
71
+ // and the observable is thereafter only used as a notifier to call `onStoreChange`, hence the `void` return type.
72
+ map((value) => void value),
73
+ // Ensure that the cache entry is deleted when the observable completes or errors.
74
+ finalize(() => cache.delete(observable)),
75
+ share(),
42
76
  )
43
77
 
44
78
  // Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.
45
79
  entry.subscription = entry.observable.subscribe()
46
80
 
47
- cache.set(observable, entry as CacheRecord<UnboxObservable<ObservableType>>)
81
+ cache.set(observable, entry as CacheRecord<ObservedValueOf<ObservableType>>)
48
82
  }
49
83
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
50
84
  const instance = cache.get(observable)!
@@ -56,14 +90,22 @@ export function useObservable<ObservableType extends Observable<any>>(
56
90
  subscribe: (onStoreChange: () => void) => {
57
91
  const subscription = instance.observable.subscribe(onStoreChange)
58
92
  instance.subscription.unsubscribe()
59
- return () => subscription.unsubscribe()
93
+ return () => {
94
+ subscription.unsubscribe()
95
+ }
96
+ },
97
+ getSnapshot: () => {
98
+ if (instance.error) {
99
+ throw instance.error
100
+ }
101
+ return instance.snapshot
60
102
  },
61
- getSnapshot: () => instance.snapshot,
62
103
  }
63
104
  }, [observable])
64
105
 
65
- return useSyncExternalStore<UnboxObservable<ObservableType>>(store.subscribe, store.getSnapshot)
106
+ return useSyncExternalStore<ObservedValueOf<ObservableType>>(
107
+ store.subscribe,
108
+ store.getSnapshot,
109
+ typeof initialValueRef.current === 'undefined' ? undefined : () => initialValueRef.current,
110
+ )
66
111
  }
67
-
68
- /** @internal */
69
- export type UnboxObservable<T> = T extends Observable<infer U> ? U : never
@@ -19,7 +19,9 @@ export function useObservableEvent<T, U>(
19
19
  }
20
20
 
21
21
  /**
22
- * This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19
22
+ * This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19.
23
+ * https://19.react.dev/learn/separating-events-from-effects#declaring-an-effect-event
24
+ * To learn more about the ponyfill itself, see: https://blog.bitsrc.io/a-look-inside-the-useevent-polyfill-from-the-new-react-docs-d1c4739e8072
23
25
  */
24
26
  function useEffectEvent<
25
27
  const T extends (
package/dist/index.d.mts DELETED
@@ -1,17 +0,0 @@
1
- import {Observable} from 'rxjs'
2
-
3
- /** @internal */
4
- export declare type UnboxObservable<T> = T extends Observable<infer U> ? U : never
5
-
6
- /** @public */
7
- export declare function useObservable<ObservableType extends Observable<any>>(
8
- observable: ObservableType,
9
- initialValue?: UnboxObservable<ObservableType> | (() => UnboxObservable<ObservableType>),
10
- ): UnboxObservable<ObservableType>
11
-
12
- /** @public */
13
- export declare function useObservableEvent<T, U>(
14
- fn: (arg: Observable<T>) => Observable<U>,
15
- ): (arg: T) => void
16
-
17
- export {}
package/dist/index.mjs DELETED
@@ -1,54 +0,0 @@
1
- import { useRef, useEffect, useMemo, useSyncExternalStore, useState, useInsertionEffect, useCallback } from "react";
2
- import { shareReplay, tap } from "rxjs/operators";
3
- import { observableCallback } from "observable-callback";
4
- function getValue(value) {
5
- return typeof value == "function" ? value() : value;
6
- }
7
- const cache = /* @__PURE__ */ new WeakMap();
8
- function useObservable(observable, initialValue) {
9
- const initialValueRef = useRef(getValue(initialValue));
10
- useEffect(() => {
11
- initialValueRef.current = getValue(initialValue);
12
- }, [initialValue]);
13
- const store = useMemo(() => {
14
- if (!cache.has(observable)) {
15
- const entry = {
16
- snapshot: initialValueRef.current
17
- };
18
- entry.observable = observable.pipe(
19
- shareReplay({ refCount: !0, bufferSize: 1 }),
20
- tap((value) => entry.snapshot = value)
21
- ), entry.subscription = entry.observable.subscribe(), cache.set(observable, entry);
22
- }
23
- const instance = cache.get(observable);
24
- return instance.subscription.closed && (instance.subscription = instance.observable.subscribe()), {
25
- subscribe: (onStoreChange) => {
26
- const subscription = instance.observable.subscribe(onStoreChange);
27
- return instance.subscription.unsubscribe(), () => subscription.unsubscribe();
28
- },
29
- getSnapshot: () => instance.snapshot
30
- };
31
- }, [observable]);
32
- return useSyncExternalStore(store.subscribe, store.getSnapshot);
33
- }
34
- function useObservableEvent(fn) {
35
- const [[calls$, call]] = useState(() => observableCallback()), callback = useEffectEvent(fn);
36
- return useEffect(() => {
37
- const subscription = calls$.pipe(callback).subscribe();
38
- return () => subscription.unsubscribe();
39
- }, [callback, calls$]), call;
40
- }
41
- function useEffectEvent(fn) {
42
- const ref = useRef(null);
43
- return useInsertionEffect(() => {
44
- ref.current = fn;
45
- }, [fn]), useCallback((...args) => {
46
- const f = ref.current;
47
- return f(...args);
48
- }, []);
49
- }
50
- export {
51
- useObservable,
52
- useObservableEvent
53
- };
54
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","sources":["../src/useObservable.ts","../src/useObservableEvent.ts"],"sourcesContent":["import {useEffect, useMemo, useRef, useSyncExternalStore} from 'react'\nimport type {Observable, Subscription} from 'rxjs'\nimport {shareReplay, tap} from 'rxjs/operators'\n\nfunction getValue<T>(value: T): T extends () => infer U ? U : T {\n return typeof value === 'function' ? value() : value\n}\ninterface CacheRecord<T> {\n subscription: Subscription\n observable: Observable<T>\n snapshot: T\n}\n\nconst cache = new WeakMap<Observable<any>, CacheRecord<any>>()\n\n/** @public */\nexport function useObservable<ObservableType extends Observable<any>>(\n observable: ObservableType,\n initialValue?: UnboxObservable<ObservableType> | (() => UnboxObservable<ObservableType>),\n): UnboxObservable<ObservableType> {\n /**\n * Store the initialValue in a ref, as we don't want a changed `initialValue` to trigger a re-subscription.\n * But we also don't want the initialValue to be stale if the observable changes.\n */\n const initialValueRef = useRef(getValue(initialValue) as UnboxObservable<ObservableType>)\n\n /**\n * Ensures that the initialValue is always up-to-date in case the observable changes.\n */\n useEffect(() => {\n initialValueRef.current = getValue(initialValue) as UnboxObservable<ObservableType>\n }, [initialValue])\n\n const store = useMemo(() => {\n if (!cache.has(observable)) {\n const entry: Partial<CacheRecord<UnboxObservable<ObservableType>>> = {\n snapshot: initialValueRef.current,\n }\n entry.observable = observable.pipe(\n shareReplay({refCount: true, bufferSize: 1}),\n tap((value) => (entry.snapshot = value)),\n )\n\n // Eagerly subscribe to sync set `entry.currentValue` to what the observable returns, and keep the observable alive until the component unmounts.\n entry.subscription = entry.observable.subscribe()\n\n cache.set(observable, entry as CacheRecord<UnboxObservable<ObservableType>>)\n }\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const instance = cache.get(observable)!\n if (instance.subscription.closed) {\n instance.subscription = instance.observable.subscribe()\n }\n\n return {\n subscribe: (onStoreChange: () => void) => {\n const subscription = instance.observable.subscribe(onStoreChange)\n instance.subscription.unsubscribe()\n return () => subscription.unsubscribe()\n },\n getSnapshot: () => instance.snapshot,\n }\n }, [observable])\n\n return useSyncExternalStore<UnboxObservable<ObservableType>>(store.subscribe, store.getSnapshot)\n}\n\n/** @internal */\nexport type UnboxObservable<T> = T extends Observable<infer U> ? U : never\n","import {observableCallback} from 'observable-callback'\nimport {useCallback, useEffect, useInsertionEffect, useRef, useState} from 'react'\nimport {type Observable} from 'rxjs'\n\n/** @public */\nexport function useObservableEvent<T, U>(\n fn: (arg: Observable<T>) => Observable<U>,\n): (arg: T) => void {\n const [[calls$, call]] = useState(() => observableCallback<T>())\n\n const callback = useEffectEvent(fn)\n\n useEffect(() => {\n const subscription = calls$.pipe(callback).subscribe()\n return () => subscription.unsubscribe()\n }, [callback, calls$])\n\n return call\n}\n\n/**\n * This is a ponyfill of the upcoming `useEffectEvent` hook that'll arrive in React 19\n */\nfunction useEffectEvent<\n const T extends (\n ...args: // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any[]\n ) => void,\n>(fn: T): T {\n const ref = useRef<T | null>(null)\n useInsertionEffect(() => {\n ref.current = fn\n }, [fn])\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return useCallback((...args: any[]) => {\n const f = ref.current!\n return f(...args)\n }, []) as T\n}\n"],"names":[],"mappings":";;;AAIA,SAAS,SAAY,OAA2C;AAC9D,SAAO,OAAO,SAAU,aAAa,MAAA,IAAU;AACjD;AAOA,MAAM,4BAAY;AAGF,SAAA,cACd,YACA,cACiC;AAKjC,QAAM,kBAAkB,OAAO,SAAS,YAAY,CAAoC;AAKxF,YAAU,MAAM;AACE,oBAAA,UAAU,SAAS,YAAY;AAAA,EAAA,GAC9C,CAAC,YAAY,CAAC;AAEX,QAAA,QAAQ,QAAQ,MAAM;AAC1B,QAAI,CAAC,MAAM,IAAI,UAAU,GAAG;AAC1B,YAAM,QAA+D;AAAA,QACnE,UAAU,gBAAgB;AAAA,MAAA;AAE5B,YAAM,aAAa,WAAW;AAAA,QAC5B,YAAY,EAAC,UAAU,IAAM,YAAY,GAAE;AAAA,QAC3C,IAAI,CAAC,UAAW,MAAM,WAAW,KAAM;AAAA,MACzC,GAGA,MAAM,eAAe,MAAM,WAAW,aAEtC,MAAM,IAAI,YAAY,KAAqD;AAAA,IAC7E;AAEM,UAAA,WAAW,MAAM,IAAI,UAAU;AACjC,WAAA,SAAS,aAAa,WACxB,SAAS,eAAe,SAAS,WAAW,cAGvC;AAAA,MACL,WAAW,CAAC,kBAA8B;AACxC,cAAM,eAAe,SAAS,WAAW,UAAU,aAAa;AAChE,eAAA,SAAS,aAAa,YACf,GAAA,MAAM,aAAa;MAC5B;AAAA,MACA,aAAa,MAAM,SAAS;AAAA,IAAA;AAAA,EAC9B,GACC,CAAC,UAAU,CAAC;AAEf,SAAO,qBAAsD,MAAM,WAAW,MAAM,WAAW;AACjG;AC5DO,SAAS,mBACd,IACkB;AAClB,QAAM,CAAC,CAAC,QAAQ,IAAI,CAAC,IAAI,SAAS,MAAM,mBAAsB,CAAC,GAEzD,WAAW,eAAe,EAAE;AAElC,SAAA,UAAU,MAAM;AACd,UAAM,eAAe,OAAO,KAAK,QAAQ,EAAE,UAAU;AAC9C,WAAA,MAAM,aAAa;EACzB,GAAA,CAAC,UAAU,MAAM,CAAC,GAEd;AACT;AAKA,SAAS,eAKP,IAAU;AACJ,QAAA,MAAM,OAAiB,IAAI;AACjC,SAAA,mBAAmB,MAAM;AACvB,QAAI,UAAU;AAAA,KACb,CAAC,EAAE,CAAC,GAEA,YAAY,IAAI,SAAgB;AACrC,UAAM,IAAI,IAAI;AACP,WAAA,EAAE,GAAG,IAAI;AAAA,EAClB,GAAG,CAAE,CAAA;AACP;"}