@foresightjs/react 0.1.0 → 0.2.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/README.md +1 -0
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @foresightjs/react
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@foresightjs/react)
|
|
4
|
+
[](https://www.npmjs.com/package/@foresightjs/react)
|
|
4
5
|
[](https://opensource.org/licenses/MIT)
|
|
5
6
|
[](http://www.typescriptlang.org/)
|
|
6
7
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ForesightManager as e,ForesightManager as t,createUnregisteredSnapshot as n}from"js.foresight";import{useCallback as r,useEffect as i,useRef as a,useState as o,useSyncExternalStore as s}from"react";const c=e=>{if(e==null)return``;try{return JSON.stringify(e)}catch{return String(e)}},l=()=>()=>{},u=n(!1),d=()=>u,f=e=>{let n=a(e);n.current=e;let[
|
|
1
|
+
import{ForesightManager as e,ForesightManager as t,createUnregisteredSnapshot as n}from"js.foresight";import{useCallback as r,useEffect as i,useRef as a,useState as o,useSyncExternalStore as s}from"react";const c=e=>{if(e==null)return``;try{return JSON.stringify(e)}catch{return String(e)}},l=()=>()=>{},u=n(!1),d=()=>u,f=e=>{let n=a(e);n.current=e;let[f,p]=o(null),[m,h]=o(null),g=r(e=>{p(e)},[]);i(()=>{if(!f)return;let e=t.instance.register({...n.current,element:f,callback:e=>n.current.callback(e)});return h(e),()=>{e.unregister(),h(null)}},[f]);let _=c(e.meta),v=c(e.hitSlop);return i(()=>{!m||!f||t.instance.updateElementOptions(f,{...n.current,callback:e=>n.current.callback(e)})},[e.reactivateAfter,e.name,_,e.enabled,v,f,m]),{elementRef:g,...s(m?.subscribe??l,()=>m?.getSnapshot()??u,d)}},p=n(!1),m=()=>()=>{},h=[],g=e=>{let n=a(e);n.current=e;let l=a(new Map),u=a(new Map),d=a(h),f=a(new Map),[g,_]=o(new Map),[v,y]=o(new Map),b=r(e=>{let t=u.current.get(e);return t||(t=t=>{(l.current.get(e)??null)!==t&&(t?l.current.set(e,t):l.current.delete(e),_(new Map(l.current)))},u.current.set(e,t)),t},[]);i(()=>{let r=new Map(f.current),i=new Map;for(let a=0;a<e.length;a++){let e=g.get(a);if(!e)continue;let o=r.get(a);if(o&&o.element===e){i.set(a,o),r.delete(a);continue}let s=t.instance.register({...n.current[a],element:e,callback:e=>n.current[a]?.callback(e)});i.set(a,{element:e,result:s})}for(let[,e]of r)e.result.unregister();return f.current=i,y(new Map(Array.from(i.entries()).map(([e,t])=>[e,t.result]))),()=>{for(let[,e]of f.current)e.result.unregister();f.current=new Map}},[e.length,g]);let x=e.map(e=>`${e.reactivateAfter??``},${e.name??``},${c(e.meta)},${e.enabled??``},${c(e.hitSlop)}`).join(`|`);i(()=>{for(let r=0;r<e.length;r++){let e=f.current.get(r);e&&t.instance.updateElementOptions(e.element,{...n.current[r],callback:e=>n.current[r]?.callback(e)})}},[e.length,x]);let S=s(r(e=>{if(v.size===0)return m();let t=!1,n=!1,r=()=>{t||(t=!0,queueMicrotask(()=>{t=!1,n||e()}))},i=[];for(let[,e]of v)i.push(e.subscribe(r));return()=>{n=!0,i.forEach(e=>e())}},[v]),r(()=>{let e=n.current.length,t=d.current,r=t.length!==e;if(!r)for(let n=0;n<e;n++){let e=v.get(n)?.getSnapshot()??p;if(t[n]!==e){r=!0;break}}if(!r)return t;let i=[];for(let t=0;t<e;t++){let e=v.get(t);i.push(e?.getSnapshot()??p)}return d.current=i,i},[v]),r(()=>n.current.map(()=>p),[]));return e.map((e,t)=>({elementRef:b(t),...S[t]??p}))},_=(e,n)=>{let r=a(n);r.current=n,i(()=>{let n=new AbortController;return t.instance.addEventListener(e,e=>r.current(e),{signal:n.signal}),()=>n.abort()},[e])};export{e as ForesightManager,f as useForesight,_ as useForesightEvent,g as useForesights};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["NOOP_SUBSCRIBE","INITIAL_SNAPSHOT","ForesightManager","ForesightManager"],"sources":["../src/hooks/serializeOption.ts","../src/hooks/useForesight.ts","../src/hooks/useForesights.ts","../src/hooks/useForesightEvent.ts"],"sourcesContent":["/**\n * Serialize an object option (`meta`, `hitSlop`) for use as an effect\n * dependency / patch key so a change to its contents re-triggers the patch\n * effect. Depending on the object itself would compare by identity, re-firing\n * the effect for every inline `meta={{...}}` even when its contents are\n * unchanged. Falls back to String() when the object can't be serialized\n * (e.g. circular references).\n */\nexport const serializeOption = (value: unknown): string => {\n if (value === undefined || value === null) {\n return \"\"\n }\n\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from \"react\"\nimport {\n ForesightManager,\n createUnregisteredSnapshot,\n type ForesightElementState,\n type ForesightRegisterResult,\n} from \"js.foresight\"\nimport type { UseForesightOptions, UseForesightResult } from \"../types\"\nimport { serializeOption } from \"./serializeOption\"\n\nconst NOOP_SUBSCRIBE = () => () => {}\nconst INITIAL_SNAPSHOT = createUnregisteredSnapshot(false)\nconst GET_INITIAL_SNAPSHOT = () => INITIAL_SNAPSHOT\n\nexport const useForesight = <T extends HTMLElement = HTMLElement>(\n options: UseForesightOptions\n): UseForesightResult<T> => {\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const [element, setElement] = useState<T | null>(null)\n const [registerResults, setRegisterResults] = useState<ForesightRegisterResult | null>(null)\n\n const elementRef = useCallback((node: T | null) => {\n setElement(node)\n }, [])\n\n // Register/unregister when the DOM node attaches or swaps.\n useEffect(() => {\n if (!element) {\n return\n }\n\n const result = ForesightManager.instance.register({\n ...optionsRef.current,\n element,\n callback: (state: ForesightElementState) => optionsRef.current.callback(state),\n })\n setRegisterResults(result)\n\n return () => {\n result.unregister()\n setRegisterResults(null)\n }\n }, [element])\n\n // Patch options on the existing registration without tearing it down.\n // meta and hitSlop are compared by content, not identity - an inline\n // `meta={{...}}` object would otherwise re-fire this effect every render and\n // loop with the useSyncExternalStore re-render it triggers.\n const metaKey = serializeOption(options.meta)\n const hitSlopKey = serializeOption(options.hitSlop)\n useEffect(() => {\n if (!registerResults || !element) {\n return\n }\n\n ForesightManager.instance.updateElementOptions(element, {\n ...optionsRef.current,\n callback: (state: ForesightElementState) => optionsRef.current.callback(state),\n })\n }, [\n options.reactivateAfter,\n options.name,\n metaKey,\n options.enabled,\n hitSlopKey,\n element,\n registerResults,\n ])\n\n const state = useSyncExternalStore<ForesightElementState>(\n registerResults?.subscribe ?? NOOP_SUBSCRIBE,\n registerResults?.getSnapshot ?? GET_INITIAL_SNAPSHOT,\n GET_INITIAL_SNAPSHOT\n )\n\n return { elementRef, ...state }\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from \"react\"\nimport {\n ForesightManager,\n createUnregisteredSnapshot,\n type ForesightElementState,\n type ForesightRegisterResult,\n} from \"js.foresight\"\nimport type { UseForesightOptions, UseForesightResult } from \"../types\"\nimport { serializeOption } from \"./serializeOption\"\n\nconst INITIAL_SNAPSHOT = createUnregisteredSnapshot(false)\nconst NOOP_SUBSCRIBE = () => () => {}\nconst EMPTY_SNAPSHOTS: ForesightElementState[] = []\n\ntype SlotEntry = {\n element: Element\n result: ForesightRegisterResult\n}\n\nexport const useForesights = <T extends HTMLElement = HTMLElement>(\n optionsArray: UseForesightOptions[]\n): UseForesightResult<T>[] => {\n const optionsRef = useRef(optionsArray)\n optionsRef.current = optionsArray\n\n const elementsRef = useRef(new Map<number, T>())\n const elementRefCallbacksRef = useRef(new Map<number, (node: T | null) => void>())\n const cachedSnapshotsRef = useRef<ForesightElementState[]>(EMPTY_SNAPSHOTS)\n const slotsRef = useRef(new Map<number, SlotEntry>())\n\n const [elements, setElements] = useState<ReadonlyMap<number, T>>(new Map())\n const [resultsList, setResultsList] = useState<ReadonlyMap<number, ForesightRegisterResult>>(\n new Map()\n )\n\n // Stable elementRef factory - one callback per index, reused across renders\n const getElementRef = useCallback((index: number): ((node: T | null) => void) => {\n let existing = elementRefCallbacksRef.current.get(index)\n if (!existing) {\n existing = (node: T | null) => {\n const prev = elementsRef.current.get(index) ?? null\n if (prev === node) {\n return\n }\n\n if (node) {\n elementsRef.current.set(index, node)\n } else {\n elementsRef.current.delete(index)\n }\n\n setElements(new Map(elementsRef.current))\n }\n elementRefCallbacksRef.current.set(index, existing)\n }\n\n return existing\n }, [])\n\n // Register/unregister when elements change or the array length changes\n useEffect(() => {\n const prevResults = new Map(slotsRef.current)\n const nextSlots = new Map<number, SlotEntry>()\n\n // Register each slot that has an element\n for (let i = 0; i < optionsArray.length; i++) {\n const el = elements.get(i)\n if (!el) {\n continue\n }\n\n const prev = prevResults.get(i)\n\n // If same element is already registered, keep the existing result\n if (prev && prev.element === el) {\n nextSlots.set(i, prev)\n prevResults.delete(i)\n continue\n }\n\n // New or swapped element - register it\n const result = ForesightManager.instance.register({\n ...optionsRef.current[i],\n element: el,\n callback: (state: ForesightElementState) => optionsRef.current[i]?.callback(state),\n })\n nextSlots.set(i, { element: el, result })\n }\n\n // Unregister everything that's no longer needed\n for (const [, slot] of prevResults) {\n slot.result.unregister()\n }\n\n slotsRef.current = nextSlots\n setResultsList(new Map(Array.from(nextSlots.entries()).map(([k, v]) => [k, v.result])))\n\n return () => {\n for (const [, slot] of slotsRef.current) {\n slot.result.unregister()\n }\n slotsRef.current = new Map()\n }\n }, [optionsArray.length, elements])\n\n // Patch options on existing registrations without tearing them down\n const patchKey = optionsArray\n .map(\n o =>\n `${o.reactivateAfter ?? \"\"},${o.name ?? \"\"},${serializeOption(o.meta)},${o.enabled ?? \"\"},${serializeOption(o.hitSlop)}`\n )\n .join(\"|\")\n\n useEffect(() => {\n for (let i = 0; i < optionsArray.length; i++) {\n const slot = slotsRef.current.get(i)\n if (!slot) {\n continue\n }\n\n ForesightManager.instance.updateElementOptions(slot.element, {\n ...optionsRef.current[i],\n callback: (state: ForesightElementState) => optionsRef.current[i]?.callback(state),\n })\n }\n }, [optionsArray.length, patchKey])\n\n // Subscribe to all active registrations. Re-subscribes when the set of results changes.\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n if (resultsList.size === 0) {\n return NOOP_SUBSCRIBE()\n }\n\n const unsubs: (() => void)[] = []\n for (const [, result] of resultsList) {\n unsubs.push(result.subscribe(onStoreChange))\n }\n\n return () => unsubs.forEach(u => u())\n },\n [resultsList]\n )\n\n // getSnapshot must return a referentially stable value when nothing changed.\n const getSnapshot = useCallback((): ForesightElementState[] => {\n const length = optionsRef.current.length\n const cached = cachedSnapshotsRef.current\n\n let changed = cached.length !== length\n if (!changed) {\n for (let i = 0; i < length; i++) {\n const result = resultsList.get(i)\n const current = result?.getSnapshot() ?? INITIAL_SNAPSHOT\n if (current !== cached[i]) {\n changed = true\n break\n }\n }\n }\n\n if (!changed) {\n return cached\n }\n\n const next: ForesightElementState[] = []\n for (let i = 0; i < length; i++) {\n const result = resultsList.get(i)\n next.push(result?.getSnapshot() ?? INITIAL_SNAPSHOT)\n }\n cachedSnapshotsRef.current = next\n\n return next\n }, [resultsList])\n\n const getServerSnapshot = useCallback(\n (): ForesightElementState[] => optionsRef.current.map(() => INITIAL_SNAPSHOT),\n []\n )\n\n const states = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n\n return optionsArray.map((_, i) => ({\n elementRef: getElementRef(i),\n ...(states[i] ?? INITIAL_SNAPSHOT),\n }))\n}\n","import { useEffect, useRef } from \"react\"\nimport { ForesightManager, type ForesightEvent, type ForesightEventMap } from \"js.foresight\"\n\nexport const useForesightEvent = <K extends ForesightEvent>(\n eventType: K,\n listener: (event: ForesightEventMap[K]) => void\n): void => {\n const listenerRef = useRef(listener)\n listenerRef.current = listener\n\n useEffect(() => {\n const controller = new AbortController()\n const stableListener = (event: ForesightEventMap[K]) => listenerRef.current(event)\n\n ForesightManager.instance.addEventListener(eventType, stableListener, {\n signal: controller.signal,\n })\n\n return () => controller.abort()\n }, [eventType])\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["NOOP_SUBSCRIBE","INITIAL_SNAPSHOT","ForesightManager","ForesightManager"],"sources":["../src/hooks/serializeOption.ts","../src/hooks/useForesight.ts","../src/hooks/useForesights.ts","../src/hooks/useForesightEvent.ts"],"sourcesContent":["/**\n * Serialize an object option (`meta`, `hitSlop`) for use as an effect\n * dependency / patch key so a change to its contents re-triggers the patch\n * effect. Depending on the object itself would compare by identity, re-firing\n * the effect for every inline `meta={{...}}` even when its contents are\n * unchanged. Falls back to String() when the object can't be serialized\n * (e.g. circular references).\n */\nexport const serializeOption = (value: unknown): string => {\n if (value === undefined || value === null) {\n return \"\"\n }\n\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from \"react\"\nimport {\n ForesightManager,\n createUnregisteredSnapshot,\n type ForesightElementState,\n type ForesightRegisterResult,\n} from \"js.foresight\"\nimport type { UseForesightOptions, UseForesightResult } from \"../types\"\nimport { serializeOption } from \"./serializeOption\"\n\nconst NOOP_SUBSCRIBE = () => () => {}\nconst INITIAL_SNAPSHOT = createUnregisteredSnapshot(false)\nconst GET_INITIAL_SNAPSHOT = () => INITIAL_SNAPSHOT\n\nexport const useForesight = <T extends HTMLElement = HTMLElement>(\n options: UseForesightOptions\n): UseForesightResult<T> => {\n const optionsRef = useRef(options)\n optionsRef.current = options\n\n const [element, setElement] = useState<T | null>(null)\n const [registerResults, setRegisterResults] = useState<ForesightRegisterResult | null>(null)\n\n const elementRef = useCallback((node: T | null) => {\n setElement(node)\n }, [])\n\n // Register/unregister when the DOM node attaches or swaps.\n useEffect(() => {\n if (!element) {\n return\n }\n\n const result = ForesightManager.instance.register({\n ...optionsRef.current,\n element,\n callback: (state: ForesightElementState) => optionsRef.current.callback(state),\n })\n setRegisterResults(result)\n\n return () => {\n result.unregister()\n setRegisterResults(null)\n }\n }, [element])\n\n // Patch options on the existing registration without tearing it down.\n // meta and hitSlop are compared by content, not identity - an inline\n // `meta={{...}}` object would otherwise re-fire this effect every render and\n // loop with the useSyncExternalStore re-render it triggers.\n const metaKey = serializeOption(options.meta)\n const hitSlopKey = serializeOption(options.hitSlop)\n useEffect(() => {\n if (!registerResults || !element) {\n return\n }\n\n ForesightManager.instance.updateElementOptions(element, {\n ...optionsRef.current,\n callback: (state: ForesightElementState) => optionsRef.current.callback(state),\n })\n }, [\n options.reactivateAfter,\n options.name,\n metaKey,\n options.enabled,\n hitSlopKey,\n element,\n registerResults,\n ])\n\n const state = useSyncExternalStore<ForesightElementState>(\n registerResults?.subscribe ?? NOOP_SUBSCRIBE,\n () => registerResults?.getSnapshot() ?? INITIAL_SNAPSHOT,\n GET_INITIAL_SNAPSHOT\n )\n\n return { elementRef, ...state }\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from \"react\"\nimport {\n ForesightManager,\n createUnregisteredSnapshot,\n type ForesightElementState,\n type ForesightRegisterResult,\n} from \"js.foresight\"\nimport type { UseForesightOptions, UseForesightResult } from \"../types\"\nimport { serializeOption } from \"./serializeOption\"\n\nconst INITIAL_SNAPSHOT = createUnregisteredSnapshot(false)\nconst NOOP_SUBSCRIBE = () => () => {}\nconst EMPTY_SNAPSHOTS: ForesightElementState[] = []\n\ntype SlotEntry = {\n element: Element\n result: ForesightRegisterResult\n}\n\nexport const useForesights = <T extends HTMLElement = HTMLElement>(\n optionsArray: UseForesightOptions[]\n): UseForesightResult<T>[] => {\n const optionsRef = useRef(optionsArray)\n optionsRef.current = optionsArray\n\n const elementsRef = useRef(new Map<number, T>())\n const elementRefCallbacksRef = useRef(new Map<number, (node: T | null) => void>())\n const cachedSnapshotsRef = useRef<ForesightElementState[]>(EMPTY_SNAPSHOTS)\n const slotsRef = useRef(new Map<number, SlotEntry>())\n\n const [elements, setElements] = useState<ReadonlyMap<number, T>>(new Map())\n const [resultsList, setResultsList] = useState<ReadonlyMap<number, ForesightRegisterResult>>(\n new Map()\n )\n\n // Stable elementRef factory - one callback per index, reused across renders\n const getElementRef = useCallback((index: number): ((node: T | null) => void) => {\n let existing = elementRefCallbacksRef.current.get(index)\n if (!existing) {\n existing = (node: T | null) => {\n const prev = elementsRef.current.get(index) ?? null\n if (prev === node) {\n return\n }\n\n if (node) {\n elementsRef.current.set(index, node)\n } else {\n elementsRef.current.delete(index)\n }\n\n setElements(new Map(elementsRef.current))\n }\n elementRefCallbacksRef.current.set(index, existing)\n }\n\n return existing\n }, [])\n\n // Register/unregister when elements change or the array length changes\n useEffect(() => {\n const prevResults = new Map(slotsRef.current)\n const nextSlots = new Map<number, SlotEntry>()\n\n // Register each slot that has an element\n for (let i = 0; i < optionsArray.length; i++) {\n const el = elements.get(i)\n if (!el) {\n continue\n }\n\n const prev = prevResults.get(i)\n\n // If same element is already registered, keep the existing result\n if (prev && prev.element === el) {\n nextSlots.set(i, prev)\n prevResults.delete(i)\n continue\n }\n\n // New or swapped element - register it\n const result = ForesightManager.instance.register({\n ...optionsRef.current[i],\n element: el,\n callback: (state: ForesightElementState) => optionsRef.current[i]?.callback(state),\n })\n nextSlots.set(i, { element: el, result })\n }\n\n // Unregister everything that's no longer needed\n for (const [, slot] of prevResults) {\n slot.result.unregister()\n }\n\n slotsRef.current = nextSlots\n setResultsList(new Map(Array.from(nextSlots.entries()).map(([k, v]) => [k, v.result])))\n\n return () => {\n for (const [, slot] of slotsRef.current) {\n slot.result.unregister()\n }\n slotsRef.current = new Map()\n }\n }, [optionsArray.length, elements])\n\n // Patch options on existing registrations without tearing them down\n const patchKey = optionsArray\n .map(\n o =>\n `${o.reactivateAfter ?? \"\"},${o.name ?? \"\"},${serializeOption(o.meta)},${o.enabled ?? \"\"},${serializeOption(o.hitSlop)}`\n )\n .join(\"|\")\n\n useEffect(() => {\n for (let i = 0; i < optionsArray.length; i++) {\n const slot = slotsRef.current.get(i)\n if (!slot) {\n continue\n }\n\n ForesightManager.instance.updateElementOptions(slot.element, {\n ...optionsRef.current[i],\n callback: (state: ForesightElementState) => optionsRef.current[i]?.callback(state),\n })\n }\n }, [optionsArray.length, patchKey])\n\n // Subscribe to all active registrations. Re-subscribes when the set of results changes.\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n if (resultsList.size === 0) {\n return NOOP_SUBSCRIBE()\n }\n\n // A single scroll tick notifies every registered element; coalesce them\n // into one snapshot check per microtask instead of one per element.\n let scheduled = false\n let disposed = false\n const coalescedStoreChange = () => {\n if (scheduled) {\n return\n }\n\n scheduled = true\n queueMicrotask(() => {\n scheduled = false\n if (!disposed) {\n onStoreChange()\n }\n })\n }\n\n const unsubs: (() => void)[] = []\n for (const [, result] of resultsList) {\n unsubs.push(result.subscribe(coalescedStoreChange))\n }\n\n return () => {\n disposed = true\n unsubs.forEach(u => u())\n }\n },\n [resultsList]\n )\n\n // getSnapshot must return a referentially stable array when nothing changed.\n // Manager snapshot identities only change on logical state changes (geometry\n // lives on a separate bounds channel), so a plain reference compare suffices.\n const getSnapshot = useCallback((): ForesightElementState[] => {\n const length = optionsRef.current.length\n const cached = cachedSnapshotsRef.current\n\n let changed = cached.length !== length\n if (!changed) {\n for (let i = 0; i < length; i++) {\n const result = resultsList.get(i)\n const current = result?.getSnapshot() ?? INITIAL_SNAPSHOT\n if (cached[i] !== current) {\n changed = true\n break\n }\n }\n }\n\n if (!changed) {\n return cached\n }\n\n const next: ForesightElementState[] = []\n for (let i = 0; i < length; i++) {\n const result = resultsList.get(i)\n next.push(result?.getSnapshot() ?? INITIAL_SNAPSHOT)\n }\n cachedSnapshotsRef.current = next\n\n return next\n }, [resultsList])\n\n const getServerSnapshot = useCallback(\n (): ForesightElementState[] => optionsRef.current.map(() => INITIAL_SNAPSHOT),\n []\n )\n\n const states = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n\n return optionsArray.map((_, i) => ({\n elementRef: getElementRef(i),\n ...(states[i] ?? INITIAL_SNAPSHOT),\n }))\n}\n","import { useEffect, useRef } from \"react\"\nimport { ForesightManager, type ForesightEvent, type ForesightEventMap } from \"js.foresight\"\n\nexport const useForesightEvent = <K extends ForesightEvent>(\n eventType: K,\n listener: (event: ForesightEventMap[K]) => void\n): void => {\n const listenerRef = useRef(listener)\n listenerRef.current = listener\n\n useEffect(() => {\n const controller = new AbortController()\n const stableListener = (event: ForesightEventMap[K]) => listenerRef.current(event)\n\n ForesightManager.instance.addEventListener(eventType, stableListener, {\n signal: controller.signal,\n })\n\n return () => controller.abort()\n }, [eventType])\n}\n"],"mappings":"6MAQA,MAAa,EAAmB,GAA2B,CACzD,GAAI,GAAiC,KACnC,MAAO,GAGT,GAAI,CACF,OAAO,KAAK,UAAU,EAAM,MACtB,CACN,OAAO,OAAO,EAAM,GCNlBA,UAA6B,GAC7BC,EAAmB,EAA2B,GAAM,CACpD,MAA6BA,EAEtB,EACX,GAC0B,CAC1B,IAAM,EAAa,EAAO,EAAQ,CAClC,EAAW,QAAU,EAErB,GAAM,CAAC,EAAS,GAAc,EAAmB,KAAK,CAChD,CAAC,EAAiB,GAAsB,EAAyC,KAAK,CAEtF,EAAa,EAAa,GAAmB,CACjD,EAAW,EAAK,EACf,EAAE,CAAC,CAGN,MAAgB,CACd,GAAI,CAAC,EACH,OAGF,IAAM,EAASC,EAAiB,SAAS,SAAS,CAChD,GAAG,EAAW,QACd,UACA,SAAW,GAAiC,EAAW,QAAQ,SAAS,EAAM,CAC/E,CAAC,CAGF,OAFA,EAAmB,EAAO,KAEb,CACX,EAAO,YAAY,CACnB,EAAmB,KAAK,GAEzB,CAAC,EAAQ,CAAC,CAMb,IAAM,EAAU,EAAgB,EAAQ,KAAK,CACvC,EAAa,EAAgB,EAAQ,QAAQ,CA0BnD,OAzBA,MAAgB,CACV,CAAC,GAAmB,CAAC,GAIzB,EAAiB,SAAS,qBAAqB,EAAS,CACtD,GAAG,EAAW,QACd,SAAW,GAAiC,EAAW,QAAQ,SAAS,EAAM,CAC/E,CAAC,EACD,CACD,EAAQ,gBACR,EAAQ,KACR,EACA,EAAQ,QACR,EACA,EACA,EACD,CAAC,CAQK,CAAE,aAAY,GANP,EACZ,GAAiB,WAAaF,MACxB,GAAiB,aAAa,EAAIC,EACxC,EACD,CAE8B,ECnE3B,EAAmB,EAA2B,GAAM,CACpD,UAA6B,GAC7B,EAA2C,EAAE,CAOtC,EACX,GAC4B,CAC5B,IAAM,EAAa,EAAO,EAAa,CACvC,EAAW,QAAU,EAErB,IAAM,EAAc,EAAO,IAAI,IAAiB,CAC1C,EAAyB,EAAO,IAAI,IAAwC,CAC5E,EAAqB,EAAgC,EAAgB,CACrE,EAAW,EAAO,IAAI,IAAyB,CAE/C,CAAC,EAAU,GAAe,EAAiC,IAAI,IAAM,CACrE,CAAC,EAAa,GAAkB,EACpC,IAAI,IACL,CAGK,EAAgB,EAAa,GAA8C,CAC/E,IAAI,EAAW,EAAuB,QAAQ,IAAI,EAAM,CAmBxD,OAlBK,IACH,EAAY,GAAmB,EAChB,EAAY,QAAQ,IAAI,EAAM,EAAI,QAClC,IAIT,EACF,EAAY,QAAQ,IAAI,EAAO,EAAK,CAEpC,EAAY,QAAQ,OAAO,EAAM,CAGnC,EAAY,IAAI,IAAI,EAAY,QAAQ,CAAC,GAE3C,EAAuB,QAAQ,IAAI,EAAO,EAAS,EAG9C,GACN,EAAE,CAAC,CAGN,MAAgB,CACd,IAAM,EAAc,IAAI,IAAI,EAAS,QAAQ,CACvC,EAAY,IAAI,IAGtB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAa,OAAQ,IAAK,CAC5C,IAAM,EAAK,EAAS,IAAI,EAAE,CAC1B,GAAI,CAAC,EACH,SAGF,IAAM,EAAO,EAAY,IAAI,EAAE,CAG/B,GAAI,GAAQ,EAAK,UAAY,EAAI,CAC/B,EAAU,IAAI,EAAG,EAAK,CACtB,EAAY,OAAO,EAAE,CACrB,SAIF,IAAM,EAASE,EAAiB,SAAS,SAAS,CAChD,GAAG,EAAW,QAAQ,GACtB,QAAS,EACT,SAAW,GAAiC,EAAW,QAAQ,IAAI,SAAS,EAAM,CACnF,CAAC,CACF,EAAU,IAAI,EAAG,CAAE,QAAS,EAAI,SAAQ,CAAC,CAI3C,IAAK,GAAM,EAAG,KAAS,EACrB,EAAK,OAAO,YAAY,CAM1B,MAHA,GAAS,QAAU,EACnB,EAAe,IAAI,IAAI,MAAM,KAAK,EAAU,SAAS,CAAC,CAAC,KAAK,CAAC,EAAG,KAAO,CAAC,EAAG,EAAE,OAAO,CAAC,CAAC,CAAC,KAE1E,CACX,IAAK,GAAM,EAAG,KAAS,EAAS,QAC9B,EAAK,OAAO,YAAY,CAE1B,EAAS,QAAU,IAAI,MAExB,CAAC,EAAa,OAAQ,EAAS,CAAC,CAGnC,IAAM,EAAW,EACd,IACC,GACE,GAAG,EAAE,iBAAmB,GAAG,GAAG,EAAE,MAAQ,GAAG,GAAG,EAAgB,EAAE,KAAK,CAAC,GAAG,EAAE,SAAW,GAAG,GAAG,EAAgB,EAAE,QAAQ,GACzH,CACA,KAAK,IAAI,CAEZ,MAAgB,CACd,IAAK,IAAI,EAAI,EAAG,EAAI,EAAa,OAAQ,IAAK,CAC5C,IAAM,EAAO,EAAS,QAAQ,IAAI,EAAE,CAC/B,GAIL,EAAiB,SAAS,qBAAqB,EAAK,QAAS,CAC3D,GAAG,EAAW,QAAQ,GACtB,SAAW,GAAiC,EAAW,QAAQ,IAAI,SAAS,EAAM,CACnF,CAAC,GAEH,CAAC,EAAa,OAAQ,EAAS,CAAC,CA8EnC,IAAM,EAAS,EA3EG,EACf,GAA8B,CAC7B,GAAI,EAAY,OAAS,EACvB,OAAO,GAAgB,CAKzB,IAAI,EAAY,GACZ,EAAW,GACT,MAA6B,CAC7B,IAIJ,EAAY,GACZ,mBAAqB,CACnB,EAAY,GACP,GACH,GAAe,EAEjB,GAGE,EAAyB,EAAE,CACjC,IAAK,GAAM,EAAG,KAAW,EACvB,EAAO,KAAK,EAAO,UAAU,EAAqB,CAAC,CAGrD,UAAa,CACX,EAAW,GACX,EAAO,QAAQ,GAAK,GAAG,CAAC,GAG5B,CAAC,EAAY,CACd,CAKmB,MAA2C,CAC7D,IAAM,EAAS,EAAW,QAAQ,OAC5B,EAAS,EAAmB,QAE9B,EAAU,EAAO,SAAW,EAChC,GAAI,CAAC,EACH,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,IAAK,CAE/B,IAAM,EADS,EAAY,IAAI,EAAE,EACT,aAAa,EAAI,EACzC,GAAI,EAAO,KAAO,EAAS,CACzB,EAAU,GACV,OAKN,GAAI,CAAC,EACH,OAAO,EAGT,IAAM,EAAgC,EAAE,CACxC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,IAAK,CAC/B,IAAM,EAAS,EAAY,IAAI,EAAE,CACjC,EAAK,KAAK,GAAQ,aAAa,EAAI,EAAiB,CAItD,MAFA,GAAmB,QAAU,EAEtB,GACN,CAAC,EAAY,CAAC,CAES,MACO,EAAW,QAAQ,QAAU,EAAiB,CAC7E,EAAE,CACH,CAE6E,CAE9E,OAAO,EAAa,KAAK,EAAG,KAAO,CACjC,WAAY,EAAc,EAAE,CAC5B,GAAI,EAAO,IAAM,EAClB,EAAE,EC7MQ,GACX,EACA,IACS,CACT,IAAM,EAAc,EAAO,EAAS,CACpC,EAAY,QAAU,EAEtB,MAAgB,CACd,IAAM,EAAa,IAAI,gBAOvB,OAJA,EAAiB,SAAS,iBAAiB,EAFnB,GAAgC,EAAY,QAAQ,EAAM,CAEZ,CACpE,OAAQ,EAAW,OACpB,CAAC,KAEW,EAAW,OAAO,EAC9B,CAAC,EAAU,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foresightjs/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "React bindings for ForesightJS - hooks and helpers to register elements with the ForesightManager from React components.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"llms": "https://foresightjs.com/llms.txt",
|
|
44
44
|
"llmsFull": "https://foresightjs.com/llms-full.txt",
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"js.foresight": "^4.
|
|
46
|
+
"js.foresight": "^4.1.0",
|
|
47
47
|
"react": "^18.0.0 || ^19.0.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"tsdown": "^0.21.7",
|
|
57
57
|
"typescript": "^6.0.2",
|
|
58
58
|
"vitest": "^4.1.2",
|
|
59
|
-
"js.foresight": "4.
|
|
59
|
+
"js.foresight": "4.1.0"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "tsdown --sourcemap",
|