{"version":3,"file":"react.mjs","names":[],"sources":["../../src/react/miniStore.ts","../../src/react/index.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport {\n  type ArrayVal,\n  isArray,\n  isPlainObject,\n  objectHasProp,\n  type TweenProps,\n} from \"@thednp/tween\";\n\n/** The marker property name used to flag already-proxied values */\nconst STATE_PROXY = \"_proxy\";\n/** The descriptor used for the non-enumerable `_proxy` marker property */\nconst proxyProps = {\n  value: 1,\n  enumerable: false,\n  configurable: false,\n  writable: false,\n};\n\n/**\n * A listener function notified whenever the reactive store state changes.\n *\n * @template T - The type of the state object\n * @param state - The current state of the store\n */\ntype Listener<T> = (state: T) => void;\n\n/**\n * Defines a reactive proxy for a single array index, with a nested\n * proxy for sub-arrays. Listeners are only notified when the last\n * element changes, to batch updates.\n *\n * @template T - The type of the array value\n * @param index - The array index to proxy\n * @param value - The initial value at the given index\n * @param target - The array being built\n * @param sourceLen - The length of the source array\n * @param notifyListeners - The function called when the last element changes\n */\nfunction defineArrayProxy<T extends ArrayVal>(\n  index: number,\n  value: T[number] | ArrayVal,\n  target: T | ArrayVal | ArrayVal[],\n  sourceLen: number,\n  notifyListeners: () => void,\n) {\n  const itemIsLast = index === sourceLen - 1;\n\n  if (isArray(value)) {\n    const subArray: typeof value = [];\n    const valueLen = value.length;\n\n    value.forEach((itm, idx) => {\n      const subItemIsLast = itemIsLast && idx === valueLen - 1;\n\n      let currentItem = itm;\n      Object.defineProperty(subArray, idx, {\n        get: () => currentItem,\n        set: (newValue: typeof itm) => {\n          currentItem = newValue;\n\n          // Only notify on last element to batch updates\n          if (subItemIsLast) {\n            notifyListeners();\n          }\n        },\n        enumerable: true,\n      });\n    });\n    target[index] = subArray;\n  } else {\n    let currentValue = value;\n    const getter = () => currentValue;\n    const setter = (newVal: typeof value) => {\n      currentValue = newVal;\n      if (itemIsLast) {\n        notifyListeners();\n      }\n    };\n    Object.defineProperties(target, {\n      [index]: {\n        get: getter,\n        set: setter,\n        enumerable: true,\n      },\n    });\n  }\n}\n\n/**\n * Defines a reactive getter/setter pair for a state property, wrapping\n * array values with the version-toggle trick so mutations stay reactive\n * without replacing the array reference.\n *\n * @template T - The type of the state object\n * @param key - The property key to proxy\n * @param value - The initial property value\n * @param target - The object being built\n * @param notifyListeners - The function called when the value changes\n */\nfunction defineStateProxy<T extends Omit<TweenProps, \"_proxy\">>(\n  key: number | keyof T,\n  value: T[keyof T],\n  target: T | ArrayVal,\n  notifyListeners: () => void,\n) {\n  const valueIsArray = isArray(value);\n  let currentValue = value as ArrayVal | ArrayVal[];\n\n  const getter = () => currentValue;\n  let setter;\n\n  if (valueIsArray) {\n    // Build array proxy structure\n    const arrayProxy: ArrayVal | ArrayVal[] = [];\n    const valLength = value.length;\n\n    for (let i = 0; i < valLength; i++) {\n      defineArrayProxy(\n        i,\n        (value as ArrayVal)[i],\n        arrayProxy as ArrayVal,\n        valLength,\n        notifyListeners,\n      );\n    }\n    currentValue = arrayProxy;\n  } else {\n    setter = (newValue: typeof currentValue) => {\n      if (currentValue !== newValue) {\n        currentValue = newValue;\n        notifyListeners();\n      }\n    };\n  }\n\n  Object.defineProperties(target, {\n    [STATE_PROXY]: proxyProps,\n    [key]: {\n      get: getter,\n      set: setter,\n      enumerable: true,\n    },\n  });\n}\n\n/**\n * Recursively builds the reactive state object for a plain object,\n * proxying primitive and array values and delegating plain objects.\n *\n * @template T - The type of the state object\n * @param obj - The source object\n * @param parentReceiver - The object being built\n * @param notifyListeners - The function called when a value changes\n * @returns The built reactive state object\n */\nfunction createMiniState<T extends TweenProps>(\n  obj: T,\n  parentReceiver: TweenProps,\n  notifyListeners: () => void,\n) {\n  if (objectHasProp(obj, STATE_PROXY)) return obj;\n\n  for (const [key, value] of Object.entries(obj)) {\n    if (isPlainObject(value)) {\n      parentReceiver[key] = createMiniState(value, {}, notifyListeners);\n    } else {\n      defineStateProxy(key, value, parentReceiver, notifyListeners);\n    }\n  }\n\n  return parentReceiver as T;\n}\n\n/**\n * Creates a reactive store for the given initial values, where every\n * property is backed by a getter/setter pair that notifies subscribed\n * listeners on change. Use it with `useMiniStore` inside a component.\n *\n * @template T - The type of the state object\n * @param init - The initial values object\n * @returns An object with the reactive `state` and a `subscribe` method\n * that registers listeners and returns an unsubscribe function\n */\nexport function miniStore<\n  T extends TweenProps,\n>(\n  init: T,\n): { readonly state: T; subscribe(listener: Listener<T>): () => void } {\n  const listeners = new Set<Listener<T>>();\n  const notifyListeners = () => {\n    listeners.forEach((listener) => listener(store));\n  };\n\n  const store = createMiniState(init, {}, notifyListeners) as T;\n\n  return {\n    get state() {\n      return store;\n    },\n    subscribe: (listener: Listener<T>) => {\n      listeners.add(listener);\n      return () => {\n        listeners.delete(listener);\n      };\n    },\n  };\n}\n\n/**\n * A hook that creates (once per component) a reactive store for the\n * given initial values and subscribes the component to its changes,\n * forcing a re-render via a version-toggle signal.\n *\n * @template T - The type of the state object\n * @param initialValue - The initial values object\n * @returns The reactive state object\n */\nexport function useMiniStore<\n  T extends TweenProps,\n>(initialValue: T): T {\n  const storeRef = useRef<ReturnType<typeof miniStore<T>>>(null);\n  const [, setVersion] = useState(0);\n\n  // istanbul ignore else @preserve\n  if (!storeRef.current) {\n    storeRef.current = miniStore(initialValue);\n  }\n\n  useEffect(\n    () => storeRef.current!.subscribe(() => setVersion((v) => (v + 1) % 3)),\n    [],\n  );\n\n  return storeRef.current!.state;\n}\n","/**\n * React integration for {@link @thednp/tween}.\n *\n * Provides `useTween` and `useTimeline` hooks backed by a reactive\n * miniStore, plus re-exports of the core {@link Tween} / {@link Timeline}\n * classes and the {@link useMiniStore} helper.\n *\n * @module\n */\nimport { useEffect, useRef } from \"react\";\nimport {\n  dummyInstance,\n  isServer,\n  Timeline,\n  Tween,\n  type TweenProps,\n} from \"@thednp/tween\";\nimport { useMiniStore } from \"./miniStore.ts\";\n\n/**\n * Re-exports the core classes, the miniStore helper and the reactive store hook.\n */\nexport { Timeline, Tween, useMiniStore };\n\n/**\n * Hook for updating values with Tween.\n *\n * **NOTE**: - configuration must be wrapped in `useEffect` or `eventListener`.\n * This has two important aspects: never configure or start update loop in SSR\n * and only configure or start the loop when component is mounted in the client.\n *\n * @param initialValues - Initial tween values\n * @returns [store, tween] Tuple of reactive store and Tween instance\n * @example\n * const App = () => {\n *    const [state, tween] = useTween({ x: 0, y: 0 })\n *\n *    useEffect(() => {\n *      tween.to({ x: 100, y: 100 }).start()\n *    }, [])\n *\n *    return (\n *      <div style={{ translate: `${state.x}px ${state.y}px` }} />\n *    );\n * }\n */\nexport const useTween = <T extends TweenProps>(\n  initialValues: T,\n): readonly [T, Tween<T>] => {\n  if (isServer) {\n    return [initialValues, dummyInstance as unknown as Tween<T>] as const;\n  }\n  const tweenRef = useRef<Tween<T> | null>(null);\n  const state = useMiniStore(initialValues);\n\n  // istanbul ignore else @preserve\n  if (!tweenRef.current) {\n    tweenRef.current = new Tween(state);\n  }\n\n  useEffect(() => {\n    return () => {\n      tweenRef.current?.stop();\n      tweenRef.current?.clear();\n    };\n  }, []);\n\n  return [state, tweenRef.current] as [T, Tween<T>];\n};\n\n/**\n * Hook for sequencing values update with Timeline.\n *\n * **NOTE**: - configuration must be wrapped in `useEffect` or `eventListener`.\n * This has two important aspects: never configure or start update loop in SSR\n * and only configure or start the loop when component is mounted in the client.\n *\n * @param initialValues - Initial tween values\n * @returns [store, timeline] Tuple of reactive store and Timeline instance\n * @example\n * const App = () => {\n *    const [state, timeline] = useTimeline({ x: 0, y: 0 })\n *\n *    useEffect(() => {\n *      timeline.to({ x: 100, y: 100 }).play()\n *    }, [])\n *\n *    return (\n *      <div style={{ translate: `${state.x}px ${state.y}px` }} />\n *    );\n * }\n */\nexport function useTimeline<T extends TweenProps>(\n  initialValues: T,\n): readonly [T, Timeline<T>] {\n  if (isServer) {\n    return [initialValues, dummyInstance as unknown as Timeline<T>] as const;\n  }\n  const timelineRef = useRef<Timeline<T> | null>(null);\n  const state = useMiniStore(initialValues);\n\n  // istanbul ignore else @preserve\n  if (!timelineRef.current) {\n    timelineRef.current = new Timeline(state);\n  }\n\n  useEffect(() => {\n    return () => {\n      timelineRef.current?.clear();\n      timelineRef.current?.stop();\n    };\n  }, []);\n\n  return [state, timelineRef.current] as [T, Timeline<T>];\n}\n"],"mappings":"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"}