UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

260 lines (259 loc) 6.9 kB
"use client"; import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from 'react'; export function useWeakSharedState(id, initialData = undefined, onChange = null) { return useSharedState(id, initialData, onChange, { weak: true }); } export function useSharedState(id, initialData = undefined, onChange = null, { weak = false } = {}) { const instanceRef = useRef({}); const sharedState = useMemo(() => { if (id) { return createSharedState(id, initialData); } return undefined; }, [id, initialData]); const sharedAttachment = useMemo(() => { if (id) { return createSharedState(createReferenceKey(id, 'oc'), { onChange }); } return undefined; }, [id, onChange]); const syncAttachment = useCallback(newData => { if (id) { sharedAttachment.data?.onChange?.(newData); } }, [id, sharedAttachment]); const subscribe = useCallback(onStoreChange => { if (!id || !sharedState) { return () => {}; } const subscriber = () => onStoreChange(); subscriber['ref'] = instanceRef.current; sharedState.subscribe(subscriber); return () => { sharedState.unsubscribe(subscriber); if (weak && sharedState.subscribersRef.current.length === 0) { sharedState.update(undefined); } }; }, [id, sharedState, weak]); const getSnapshot = useCallback(() => { var _sharedState$get; return id ? (_sharedState$get = sharedState?.get?.()) !== null && _sharedState$get !== void 0 ? _sharedState$get : undefined : undefined; }, [id, sharedState]); const data = useSyncExternalStore(subscribe, getSnapshot, getSnapshot); const get = useCallback(() => { if (id) { return sharedState?.get?.(); } return undefined; }, [id, sharedState]); const update = useCallback((newData, opts) => { if (id) { sharedState.update(newData, { ...opts, callerRef: instanceRef.current }); } }, [id, sharedState]); const set = useCallback((newData, opts) => { if (!id) { return false; } const changed = sharedState.set(newData, { ...opts, callerRef: instanceRef.current }); if (changed) { syncAttachment(newData); } return changed; }, [id, sharedState, syncAttachment]); const extend = useCallback((newData, opts) => { if (!id) { return false; } const changed = sharedState.extend(newData, { ...opts, callerRef: instanceRef.current }); if (changed) { syncAttachment(newData); } return changed; }, [id, sharedState, syncAttachment]); useEffect(() => { if (id && onChange && !sharedAttachment.data?.onChange) { sharedAttachment.set({ onChange }); } }, [id, onChange, sharedAttachment]); return { get, data: data, hadInitialData: sharedState?.hadInitialData, update, set, extend }; } const sharedStates = new Map(); export function createSharedState(id, initialData) { if (!sharedStates.get(id)) { const subscribersRef = { current: [] }; const sync = (opts = {}) => { subscribersRef.current.forEach(subscriber => { if (opts.preventSyncOfSameInstance && opts.callerRef !== undefined && subscriber['ref'] === opts.callerRef) { return; } subscriber(); }); }; const get = () => sharedStates.get(id).data; const set = (newData, opts) => { const store = sharedStates.get(id); const current = store.data; if (newData === undefined) { if (current === undefined) { return false; } store.data = undefined; if (!opts?.silent) { sync(opts); } return true; } const next = cloneData(newData); if (!opts?.forceSync && shallowEqual(current, next)) { return false; } store.data = next; if (!opts?.silent) { sync(opts); } return true; }; const update = (newData, opts) => { set(newData, opts); }; const extend = (newData, opts) => { const store = sharedStates.get(id); const current = store.data; if (!isMergeableObject(newData)) { return set(newData, opts); } const base = isMergeableObject(current) ? current : {}; const next = { ...base, ...newData }; if (!opts?.forceSync && shallowEqual(base, next)) { return false; } store.data = next; if (!opts?.silent) { sync(opts); } return true; }; const subscribe = subscriber => { if (!subscribersRef.current.includes(subscriber)) { subscribersRef.current.push(subscriber); } }; const unsubscribe = subscriber => { subscribersRef.current = subscribersRef.current.filter(sub => sub !== subscriber); }; sharedStates.set(id, { data: undefined, get, set, extend, update, subscribe, unsubscribe, hadInitialData: Boolean(initialData), subscribersRef }); if (initialData) { extend(initialData); } } else if (sharedStates.get(id).data === undefined && initialData !== undefined) { sharedStates.get(id).data = cloneData(initialData); } return sharedStates.get(id); } export function preSeedSharedState(id, data) { createSharedState(id); const store = sharedStates.get(id); if (store && store.data === undefined && data !== undefined) { store.data = cloneData(data); } } export function createReferenceKey(ref1, ref2) { if (!cache.has(ref1)) { cache.set(ref1, new Map()); } const innerMap = cache.get(ref1); if (!innerMap.has(ref2)) { innerMap.set(ref2, {}); } return innerMap.get(ref2); } const cache = new Map(); function isObjectLike(value) { return typeof value === 'object' && value !== null; } function isMergeableObject(value) { return isObjectLike(value) && !Array.isArray(value); } function cloneData(value) { if (Array.isArray(value)) { return [...value]; } if (isMergeableObject(value)) { return { ...value }; } return value; } export function shallowEqual(a, b) { if (Object.is(a, b)) { return true; } if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) { return false; } for (let i = 0; i < a.length; i++) { if (!Object.is(a[i], b[i])) { return false; } } return true; } if (isMergeableObject(a) && isMergeableObject(b)) { const keysA = Object.keys(a); const keysB = Object.keys(b); if (keysA.length !== keysB.length) { return false; } for (const key of keysA) { if (!Object.hasOwn(b, key) || !Object.is(a[key], b[key])) { return false; } } return true; } return false; } //# sourceMappingURL=useSharedState.js.map