react-sync-ui 1.0.2 → 2.0.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/src/syncUI.tsx CHANGED
@@ -1,163 +1,433 @@
1
- import React, { useCallback, useEffect, useState } from "react";
1
+ import {
2
+ Component,
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useReducer,
7
+ useRef,
8
+ useState,
9
+ useSyncExternalStore
10
+ } from "react";
11
+ import type { ComponentType, ErrorInfo, ReactElement, ReactNode } from "react";
2
12
 
3
- const useComponentDidMount = (fn: Parameters<typeof useEffect>[0]) => {
4
- useEffect(fn, []);
13
+ // The library build leaves `process.env.NODE_ENV` untouched on purpose so the
14
+ // consumer's bundler decides, exactly like React itself. It has to stay a
15
+ // bare read: a `typeof process` guard is NOT replaced by bundlers, and a
16
+ // browser has no `process`, so the guard would silently disable every dev
17
+ // warning under Vite or webpack dev. Wrapping it in an IIFE or try/catch
18
+ // stops the minifier from folding it, and the warning strings would ship to
19
+ // production. The trade-off is the same one React makes: importing the
20
+ // package with no bundler and no `process` global throws at module scope.
21
+ declare const process: { env: { NODE_ENV?: string } };
22
+ const isDev = process.env.NODE_ENV !== "production";
23
+
24
+ // ------------------------------------------------------------------------------------
25
+ // public types
26
+
27
+ export type SyncUIProps<InputData, ResolveValue = void> = {
28
+ data: InputData;
29
+ resolve: (value: ResolveValue) => void;
30
+ reject: (reason?: unknown) => void;
5
31
  };
6
32
 
7
- // this function broke the working of the hot reloading
8
- /*
9
- const getSingletonComponentCheck = (errorMsg: string) => {
10
- let globalMountCounter = 0;
33
+ // ComponentType, not a bare function type: React 19's FC returns
34
+ // `ReactNode | Promise<ReactNode>`, so `React.FC<SyncUIProps<...>>`, memo(),
35
+ // forwardRef() and class components all have to be accepted here.
36
+ export type SyncUIComponent<InputData, ResolveValue = void> = ComponentType<
37
+ SyncUIProps<InputData, ResolveValue>
38
+ >;
11
39
 
12
- return () => {
13
- useComponentDidMount(() => {
14
- if (globalMountCounter > 0) throw new Error(errorMsg);
15
- globalMountCounter++;
16
- });
17
- return <React.Fragment />;
18
- };
40
+ // The awaitable function `makeSyncUI` returns. Named, so a wrapper, a context
41
+ // value or a props type can refer to it instead of re-spelling the signature.
42
+ export type SyncUIFunction<InputData, ResolveValue = void> = (
43
+ input: InputData
44
+ ) => Promise<ResolveValue>;
45
+
46
+ // `head` is exactly what a sync component receives, so it reuses SyncUIProps.
47
+ export type PromiseQueueAPI<InputData, ResolveValue = void> = {
48
+ head?: SyncUIProps<InputData, ResolveValue>;
49
+ push: (data: InputData) => Promise<ResolveValue>;
50
+ };
51
+
52
+ export type SyncUIFactory = {
53
+ makeSyncUI: <InputData, ResolveValue = void>(
54
+ Component: SyncUIComponent<InputData, ResolveValue>
55
+ ) => SyncUIFunction<InputData, ResolveValue>;
56
+ SyncUI: () => ReactElement | null;
19
57
  };
20
- */
21
58
 
22
59
  // ------------------------------------------------------------------------------------
23
- // TODO: there is tsdx old typescript parser and new ts fancy syntax is not working...
24
- // https://github.com/jaredpalmer/tsdx/issues/200
25
- // type PromiseQueueAPI<Data, ResolveValue> = ReturnType<typeof usePromiseQueue<any, any>>
26
- type PromiseQueueAPI<Data, ResolveValue> = {
27
- head?: {
28
- data: Data;
29
- resolve: (value: ResolveValue) => void;
30
- reject: (reason?: any) => void;
31
- };
32
- push: (data: Data) => Promise<ResolveValue>;
60
+ // queue store
61
+
62
+ type Listener = () => void;
63
+
64
+ type Entry<Data, ResolveValue> = {
65
+ readonly id: number;
66
+ readonly type: symbol;
67
+ readonly data: Data;
68
+ readonly resolve: (value: ResolveValue) => void;
69
+ readonly reject: (reason?: unknown) => void;
33
70
  };
34
71
 
35
- export const usePromiseQueue = <Data, ResolveValue = void>(): PromiseQueueAPI<
36
- Data,
37
- ResolveValue
38
- > => {
39
- const [asyncQueue, setAsyncQueue] = useState(
40
- [] as {
41
- data: Data;
42
- resolve: (arg: ResolveValue) => void;
43
- reject: (arg: any) => void;
44
- }[]
45
- );
72
+ // Monotonic, so React keys are unique per queued item (no Math.random()).
73
+ let entrySeq = 0;
46
74
 
47
- const push = useCallback((data: Data) => {
48
- return new Promise<ResolveValue>((resolve, reject) =>
49
- setAsyncQueue(p => [...p, { data, resolve, reject }])
50
- );
51
- }, []);
75
+ const defaultRejectReason = () =>
76
+ new Error("react-sync-ui: rejected without a reason");
52
77
 
53
- const resolveHeadItem = useCallback((value: ResolveValue) => {
54
- setAsyncQueue(queue => {
55
- const [first, ...rest] = queue;
56
- first?.resolve(value);
57
- return rest;
58
- });
59
- }, []);
78
+ /**
79
+ * The queue lives OUTSIDE React. Every mutation happens in an event handler, an
80
+ * async continuation or a commit-phase lifecycle (the error boundary's
81
+ * `componentDidCatch`), never during render and never inside a setState
82
+ * updater, so StrictMode's double render / double updater passes and HMR
83
+ * remounts can neither duplicate nor lose a promise settlement. The
84
+ * commit-phase case is safe because a mutation only schedules a rerender
85
+ * through the subscription; it never runs while React is rendering.
86
+ */
87
+ const createQueueStore = <Data, ResolveValue>() => {
88
+ let queue: readonly Entry<Data, ResolveValue>[] = [];
89
+ const listeners = new Set<Listener>();
90
+
91
+ const emit = () => {
92
+ listeners.forEach(listener => listener());
93
+ };
94
+
95
+ // Stable identity, so React never re-subscribes.
96
+ const subscribe = (listener: Listener) => {
97
+ listeners.add(listener);
98
+ return () => {
99
+ listeners.delete(listener);
100
+ };
101
+ };
102
+
103
+ // useSyncExternalStore needs a cached snapshot: `queue` is only ever
104
+ // replaced, never mutated, so the head entry keeps its identity until it
105
+ // actually leaves the queue.
106
+ const getHead = (): Entry<Data, ResolveValue> | null => queue[0] ?? null;
60
107
 
61
- const rejectHeadItem = useCallback((reason?: any) => {
62
- setAsyncQueue(queue => {
63
- const [first, ...rest] = queue;
64
- first?.reject(reason);
65
- return rest;
108
+ const push = (type: symbol, data: Data) =>
109
+ new Promise<ResolveValue>((resolve, reject) => {
110
+ // The Promise executor runs synchronously, so the entry is queued
111
+ // before push() returns.
112
+ queue = [...queue, { id: ++entrySeq, type, data, resolve, reject }];
113
+ emit();
66
114
  });
67
- }, []);
115
+
116
+ // Settles exactly once. Membership in the queue is the "not yet settled"
117
+ // flag, so a second call, or a call from a stale closure, is a no-op.
118
+ const settle = (
119
+ entry: Entry<Data, ResolveValue>,
120
+ run: (entry: Entry<Data, ResolveValue>) => void
121
+ ) => {
122
+ const next = queue.filter(item => item !== entry);
123
+ if (next.length === queue.length) return;
124
+ queue = next;
125
+ emit();
126
+ run(entry);
127
+ };
128
+
129
+ // Rejects everything still queued at once (used when the owner of the queue
130
+ // goes away), so no caller is left awaiting a promise nobody can settle.
131
+ const drain = (reason: unknown) => {
132
+ if (queue.length === 0) return;
133
+ const abandoned = queue;
134
+ queue = [];
135
+ emit();
136
+ abandoned.forEach(item => item.reject(reason));
137
+ };
68
138
 
69
139
  return {
70
- head: asyncQueue[0]
71
- ? {
72
- data: asyncQueue[0]?.data,
73
- resolve: resolveHeadItem,
74
- reject: rejectHeadItem
75
- }
76
- : undefined,
77
- push
140
+ subscribe,
141
+ emit,
142
+ getHead,
143
+ push,
144
+ drain,
145
+ size: () => queue.length,
146
+ resolveEntry: (entry: Entry<Data, ResolveValue>, value: ResolveValue) =>
147
+ settle(entry, item => item.resolve(value)),
148
+ // `reject()` with no reason would reject with `undefined`, so the
149
+ // idiomatic `catch (error) { toast(error.message) }` would throw on top of
150
+ // the cancellation. One default, applied for every caller.
151
+ rejectEntry: (entry: Entry<Data, ResolveValue>, reason?: unknown) =>
152
+ settle(entry, item => item.reject(reason ?? defaultRejectReason()))
78
153
  };
79
154
  };
80
155
 
156
+ const getNullSnapshot = () => null;
157
+
81
158
  // ------------------------------------------------------------------------------------
159
+ // usePromiseQueue
82
160
 
83
- export const syncUIFactory = () => {
84
- const mutSyncUIComponentsRenderQueue = [] as React.FC<
85
- PromiseQueueAPI<any, any>
86
- >[];
161
+ const defaultQueueType = Symbol("usePromiseQueue");
87
162
 
88
- /*
89
- // this check broke the working of the hot reloading
90
- const ThrowIfMoreInstances = getSingletonComponentCheck(
91
- "<SyncUI /> has to be initialized only once"
163
+ export const usePromiseQueue = <
164
+ InputData,
165
+ ResolveValue = void
166
+ >(): PromiseQueueAPI<InputData, ResolveValue> => {
167
+ // Lazy initializer: StrictMode may run it twice, but it only allocates.
168
+ const [store] = useState(() => createQueueStore<InputData, ResolveValue>());
169
+ const head = useSyncExternalStore(
170
+ store.subscribe,
171
+ store.getHead,
172
+ getNullSnapshot
173
+ );
174
+ const push = useCallback(
175
+ (data: InputData) => store.push(defaultQueueType, data),
176
+ [store]
92
177
  );
93
- */
94
-
95
- return {
96
- makeSyncUI: <InputData, ResolveValue = void>(
97
- SyncUIUserComp: React.FC<{
98
- data: InputData;
99
- resolve: (value: ResolveValue) => void;
100
- reject: (reason?: any) => void;
101
- }>
102
- ) => {
103
- type QItem = PromiseQueueAPI<
104
- { type: Symbol; inputData: InputData },
105
- ResolveValue
106
- >;
107
-
108
- const _debugName =
109
- SyncUIUserComp.displayName ??
110
- SyncUIUserComp.name ??
111
- "uniqSymbolMessageType";
112
-
113
- const syncUIComponentType = Symbol(_debugName);
114
-
115
- // object pointer reference with the push key has to be there to change returned mutable reference object while the function is already called
116
- const singletonSyncUIRef = {
117
- push: undefined as undefined | QItem["push"]
118
- };
119
178
 
120
- const SyncUISingletonComponent = (props: QItem) => {
121
- useComponentDidMount(() => {
122
- singletonSyncUIRef.push = props.push;
123
- return () => (singletonSyncUIRef.push = undefined);
124
- });
125
-
126
- if (!props.head) return null;
127
- if (props.head.data.type !== syncUIComponentType) return null;
128
-
129
- return (
130
- <SyncUIUserComp
131
- data={props.head.data.inputData}
132
- resolve={props.head.resolve}
133
- reject={props.head.reject}
134
- />
179
+ // Unlike the factory queue (which outlives every host), this store is owned
180
+ // by the component, so anything still queued when it unmounts could never be
181
+ // settled by anyone: every `await push(...)` would stay suspended forever.
182
+ // The drain is deferred to a microtask and cancelled if the effect runs
183
+ // again, because StrictMode (and Fast Refresh) replay mount/unmount/mount
184
+ // synchronously: draining on that simulated unmount would reject items a
185
+ // sibling had just pushed from its own mount effect.
186
+ const drainPending = useRef(false);
187
+ useEffect(() => {
188
+ drainPending.current = false;
189
+ return () => {
190
+ drainPending.current = true;
191
+ queueMicrotask(() => {
192
+ if (!drainPending.current) return;
193
+ drainPending.current = false;
194
+ store.drain(
195
+ new Error(
196
+ "react-sync-ui: usePromiseQueue unmounted with pending items"
197
+ )
135
198
  );
136
- };
199
+ });
200
+ };
201
+ }, [store]);
202
+
203
+ return useMemo(
204
+ () => ({
205
+ head: head
206
+ ? {
207
+ data: head.data,
208
+ // Bound to THIS entry: stale or repeated calls are no-ops.
209
+ resolve: (value: ResolveValue) => store.resolveEntry(head, value),
210
+ reject: (reason?: unknown) => store.rejectEntry(head, reason)
211
+ }
212
+ : undefined,
213
+ push
214
+ }),
215
+ [head, push, store]
216
+ );
217
+ };
218
+
219
+ // ------------------------------------------------------------------------------------
220
+ // syncUIFactory
137
221
 
138
- mutSyncUIComponentsRenderQueue.push(SyncUISingletonComponent);
222
+ // The registry has to accept components of every Data/Result shape.
223
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
224
+ type AnySyncUIComponent = SyncUIComponent<any, any>;
139
225
 
140
- return (input: InputData) => {
141
- if (!singletonSyncUIRef.push)
142
- throw new Error(`You have to initialize <SyncUI />`);
143
- return singletonSyncUIRef.push({
144
- type: syncUIComponentType,
145
- inputData: input
146
- });
226
+ // A dialog that throws during render used to be a poison pill: the app's own
227
+ // error boundary caught it, <SyncUI /> unmounted, and the entry stayed at the
228
+ // head of the queue forever, hanging its own promise and every queued one.
229
+ // This boundary keeps the failure local: the entry is rejected (the caller's
230
+ // `await` throws, which is the error channel) and the queue moves on. It is
231
+ // remounted per entry via `key`, so the next dialog renders fresh.
232
+ type SyncUIBoundaryProps<Data, ResolveValue> = {
233
+ entry: Entry<Data, ResolveValue>;
234
+ onError: (entry: Entry<Data, ResolveValue>, error: unknown) => void;
235
+ children: ReactNode;
236
+ };
237
+
238
+ type SyncUIBoundaryState = { failed: boolean };
239
+
240
+ class SyncUIErrorBoundary extends Component<
241
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
242
+ SyncUIBoundaryProps<any, any>,
243
+ SyncUIBoundaryState
244
+ > {
245
+ state: SyncUIBoundaryState = { failed: false };
246
+
247
+ static getDerivedStateFromError(): SyncUIBoundaryState {
248
+ return { failed: true };
249
+ }
250
+
251
+ componentDidCatch(error: unknown, _info: ErrorInfo) {
252
+ // Not re-thrown on purpose: the promise rejection is the error channel,
253
+ // and React already logs the caught error itself in development.
254
+ this.props.onError(this.props.entry, error);
255
+ }
256
+
257
+ render() {
258
+ return this.state.failed ? null : this.props.children;
259
+ }
260
+ }
261
+
262
+ export const syncUIFactory = (): SyncUIFactory => {
263
+ const store = createQueueStore<unknown, unknown>();
264
+
265
+ // Live registry, resolved at render time. A module that calls makeSyncUI
266
+ // after <SyncUI /> has mounted (lazy chunk, Vite HMR) simply lands here.
267
+ const components = new Map<symbol, AnySyncUIComponent>();
268
+
269
+ // Every mounted <SyncUI /> instance, in mount order. A Set instead of a
270
+ // boolean or a single ref: a replacement instance can mount before the old
271
+ // one's cleanup runs (Fast Refresh remounts, a second root, route layouts),
272
+ // and that overlap must not break anything.
273
+ const hosts = new Set<object>();
274
+ const primaryHost = () => hosts.values().next().value;
275
+
276
+ let hostEverMounted = false;
277
+ let warnedNoHost = false;
278
+ let warnedMultipleHosts = false;
279
+ let noHostTimer: ReturnType<typeof setTimeout> | undefined;
280
+
281
+ // Pushing before <SyncUI /> is mounted is a race, not a configuration error
282
+ // (child effects run before parent effects), so items just wait in the
283
+ // queue. A silent hang would hide a forgotten <SyncUI />, hence the dev
284
+ // warning if nothing has mounted after a while.
285
+ const scheduleNoHostWarning = () => {
286
+ if (!isDev || hostEverMounted || warnedNoHost || noHostTimer) return;
287
+ // On the server <SyncUI /> never mounts, so the warning would be noise
288
+ // and the timer would keep the process alive.
289
+ if (typeof window === "undefined") return;
290
+ noHostTimer = setTimeout(() => {
291
+ noHostTimer = undefined;
292
+ if (hostEverMounted || store.size() === 0) return;
293
+ warnedNoHost = true;
294
+ console.error(
295
+ "[react-sync-ui] a sync UI has been pending for 3s and no <SyncUI /> " +
296
+ "is mounted. Render <SyncUI /> once, near the root of your app."
297
+ );
298
+ }, 3000);
299
+ };
300
+
301
+ const makeSyncUI = <InputData, ResolveValue = void>(
302
+ Component: SyncUIComponent<InputData, ResolveValue>
303
+ ): SyncUIFunction<InputData, ResolveValue> => {
304
+ const type = Symbol(
305
+ (Component as { displayName?: string }).displayName ||
306
+ Component.name ||
307
+ "SyncUI"
308
+ );
309
+ components.set(type, Component as AnySyncUIComponent);
310
+
311
+ return (input: InputData): Promise<ResolveValue> => {
312
+ scheduleNoHostWarning();
313
+ return store.push(type, input) as Promise<ResolveValue>;
314
+ };
315
+ };
316
+
317
+ const SyncUI = (): ReactElement | null => {
318
+ // Stable per-instance identity.
319
+ const [token] = useState(() => ({}));
320
+ const [, rerender] = useReducer((n: number) => n + 1, 0);
321
+
322
+ // Only the first mounted host renders; the others stay empty so a dialog
323
+ // is never shown twice. Declared BEFORE the registration effect so React
324
+ // is subscribed by the time that effect emits.
325
+ const getSnapshot = useCallback(
326
+ () => (primaryHost() === token ? store.getHead() : null),
327
+ [token]
328
+ );
329
+ const head = useSyncExternalStore(
330
+ store.subscribe,
331
+ getSnapshot,
332
+ getNullSnapshot
333
+ );
334
+
335
+ useEffect(() => {
336
+ hosts.add(token);
337
+ hostEverMounted = true;
338
+ // A host is here: the "nothing ever mounted" warning can no longer fire,
339
+ // so the handle should not linger (it would hold a Node test run open).
340
+ if (noHostTimer !== undefined) {
341
+ clearTimeout(noHostTimer);
342
+ noHostTimer = undefined;
343
+ }
344
+ store.emit();
345
+ // React 19 <Activity mode="hidden"> disconnects the store subscription
346
+ // and re-shows with a stale cached snapshot, so emit() alone compares
347
+ // equal and skips the render. A local state bump cannot be skipped.
348
+ // Only worth it when there is something to re-read: mounting with an
349
+ // empty queue is the common case and should not cost an extra render.
350
+ if (store.getHead()) rerender();
351
+
352
+ // Deferred one tick and cleared on cleanup, so the HMR overlap (new
353
+ // instance mounted, old one not yet unmounted) never false-warns.
354
+ let warnTimer: ReturnType<typeof setTimeout> | undefined;
355
+ if (isDev) {
356
+ warnTimer = setTimeout(() => {
357
+ if (hosts.size > 1 && !warnedMultipleHosts) {
358
+ warnedMultipleHosts = true;
359
+ console.warn(
360
+ "[react-sync-ui] more than one <SyncUI /> of the same factory " +
361
+ "is mounted; only the first mounted one renders."
362
+ );
363
+ }
364
+ }, 0);
365
+ }
366
+
367
+ return () => {
368
+ if (warnTimer !== undefined) clearTimeout(warnTimer);
369
+ hosts.delete(token);
370
+ // The queue stays intact; hand over to the next host, if any.
371
+ store.emit();
147
372
  };
148
- },
149
- SyncUI: () => {
150
- const queue = usePromiseQueue();
151
- return (
152
- <>
153
- {/* <ThrowIfMoreInstances /> */}
154
- {mutSyncUIComponentsRenderQueue.map((SyncComp, key) => (
155
- <React.Fragment key={key}>
156
- <SyncComp {...queue} />
157
- </React.Fragment>
158
- ))}
159
- </>
373
+ }, [token]);
374
+
375
+ const Dialog = head ? components.get(head.type) : undefined;
376
+
377
+ // An entry whose component is missing can never be rendered, so "log and
378
+ // stall" would block it AND everything queued behind it forever; reject it
379
+ // instead and let the queue move on. Kept out of render so StrictMode
380
+ // cannot run it twice. Not reachable through the public API, because
381
+ // makeSyncUI registers the component in the same statement that mints its
382
+ // symbol; this is the recovery path for a registry that lost the entry (a
383
+ // module graph reset under a live queue).
384
+ useEffect(() => {
385
+ if (!head || components.get(head.type)) return;
386
+ if (isDev) {
387
+ console.error(
388
+ "[react-sync-ui] no component registered for the queued item",
389
+ head.type
390
+ );
391
+ }
392
+ store.rejectEntry(
393
+ head,
394
+ new Error("react-sync-ui: no component registered for this sync UI")
160
395
  );
161
- }
396
+ }, [head]);
397
+
398
+ const handlers = useMemo(
399
+ () =>
400
+ head
401
+ ? {
402
+ resolve: (value: unknown) => store.resolveEntry(head, value),
403
+ reject: (reason?: unknown) => store.rejectEntry(head, reason)
404
+ }
405
+ : null,
406
+ [head]
407
+ );
408
+
409
+ if (!head || !handlers || !Dialog) return null;
410
+
411
+ // The key changes per queued item, so two consecutive items of the same
412
+ // component get a fresh instance (no leaked local state) and a boundary
413
+ // that caught an error is reset for the next one.
414
+ // `Dialog` is a registry lookup, not a component created during render;
415
+ // the lint rule cannot tell the difference.
416
+ return (
417
+ <SyncUIErrorBoundary
418
+ key={head.id}
419
+ entry={head}
420
+ onError={(entry, error) => store.rejectEntry(entry, error)}
421
+ >
422
+ {/* eslint-disable-next-line react-hooks/static-components */}
423
+ <Dialog
424
+ data={head.data}
425
+ resolve={handlers.resolve}
426
+ reject={handlers.reject}
427
+ />
428
+ </SyncUIErrorBoundary>
429
+ );
162
430
  };
431
+
432
+ return { makeSyncUI, SyncUI };
163
433
  };
@@ -1,115 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
6
-
7
- var React = require('react');
8
- var React__default = _interopDefault(React);
9
-
10
- var useComponentDidMount = function useComponentDidMount(fn) {
11
- React.useEffect(fn, []);
12
- };
13
- var usePromiseQueue = function usePromiseQueue() {
14
- var _asyncQueue$;
15
- var _useState = React.useState([]),
16
- asyncQueue = _useState[0],
17
- setAsyncQueue = _useState[1];
18
- var push = React.useCallback(function (data) {
19
- return new Promise(function (resolve, reject) {
20
- return setAsyncQueue(function (p) {
21
- return [].concat(p, [{
22
- data: data,
23
- resolve: resolve,
24
- reject: reject
25
- }]);
26
- });
27
- });
28
- }, []);
29
- var resolveHeadItem = React.useCallback(function (value) {
30
- setAsyncQueue(function (queue) {
31
- var first = queue[0],
32
- rest = queue.slice(1);
33
- first == null ? void 0 : first.resolve(value);
34
- return rest;
35
- });
36
- }, []);
37
- var rejectHeadItem = React.useCallback(function (reason) {
38
- setAsyncQueue(function (queue) {
39
- var first = queue[0],
40
- rest = queue.slice(1);
41
- first == null ? void 0 : first.reject(reason);
42
- return rest;
43
- });
44
- }, []);
45
- return {
46
- head: asyncQueue[0] ? {
47
- data: (_asyncQueue$ = asyncQueue[0]) == null ? void 0 : _asyncQueue$.data,
48
- resolve: resolveHeadItem,
49
- reject: rejectHeadItem
50
- } : undefined,
51
- push: push
52
- };
53
- };
54
- // ------------------------------------------------------------------------------------
55
- var syncUIFactory = function syncUIFactory() {
56
- var mutSyncUIComponentsRenderQueue = [];
57
- /*
58
- // this check broke the working of the hot reloading
59
- const ThrowIfMoreInstances = getSingletonComponentCheck(
60
- "<SyncUI /> has to be initialized only once"
61
- );
62
- */
63
- return {
64
- makeSyncUI: function makeSyncUI(SyncUIUserComp) {
65
- var _ref, _SyncUIUserComp$displ;
66
- var _debugName = (_ref = (_SyncUIUserComp$displ = SyncUIUserComp.displayName) != null ? _SyncUIUserComp$displ : SyncUIUserComp.name) != null ? _ref : "uniqSymbolMessageType";
67
- var syncUIComponentType = Symbol(_debugName);
68
- // object pointer reference with the push key has to be there to change returned mutable reference object while the function is already called
69
- var singletonSyncUIRef = {
70
- push: undefined
71
- };
72
- var SyncUISingletonComponent = function SyncUISingletonComponent(props) {
73
- useComponentDidMount(function () {
74
- singletonSyncUIRef.push = props.push;
75
- return function () {
76
- return singletonSyncUIRef.push = undefined;
77
- };
78
- });
79
- if (!props.head) return null;
80
- if (props.head.data.type !== syncUIComponentType) return null;
81
- return React__default.createElement(SyncUIUserComp, {
82
- data: props.head.data.inputData,
83
- resolve: props.head.resolve,
84
- reject: props.head.reject
85
- });
86
- };
87
- mutSyncUIComponentsRenderQueue.push(SyncUISingletonComponent);
88
- return function (input) {
89
- if (!singletonSyncUIRef.push) throw new Error("You have to initialize <SyncUI />");
90
- return singletonSyncUIRef.push({
91
- type: syncUIComponentType,
92
- inputData: input
93
- });
94
- };
95
- },
96
- SyncUI: function SyncUI() {
97
- var queue = usePromiseQueue();
98
- return React__default.createElement(React__default.Fragment, null, mutSyncUIComponentsRenderQueue.map(function (SyncComp, key) {
99
- return React__default.createElement(React__default.Fragment, {
100
- key: key
101
- }, React__default.createElement(SyncComp, Object.assign({}, queue)));
102
- }));
103
- }
104
- };
105
- };
106
-
107
- var syncUIFactory$1 = syncUIFactory;
108
- var _syncUIFactory2 = /*#__PURE__*/syncUIFactory$1(),
109
- makeSyncUI = _syncUIFactory2.makeSyncUI,
110
- SyncUI = _syncUIFactory2.SyncUI;
111
-
112
- exports.SyncUI = SyncUI;
113
- exports.makeSyncUI = makeSyncUI;
114
- exports.syncUIFactory = syncUIFactory$1;
115
- //# sourceMappingURL=react-sync-ui.cjs.development.js.map