{"version":3,"file":"vanjs.mjs","names":[],"sources":["../../src/vanjs/lifecycle.ts","../../src/vanjs/miniStore.ts","../../src/vanjs/index.ts"],"sourcesContent":["import type { TweenProps } from \"../types.ts\";\nimport van from \"vanjs-core\";\nimport type { State } from \"vanjs-core\";\n\n/**\n * A VanJS state extended with the internal `_bindings` tracking\n * information used by the auto-cleanup observer.\n *\n * @template T - The type of the state value\n */\ninterface VanState<T> extends State<T> {\n  /** The internal DOM bindings registered by VanJS for this state */\n  _bindings?: { _dom?: Node }[];\n}\n\n/**\n * An internal tracked animation instance, holding the state refs that\n * may be bound to DOM nodes and a way to stop the instance.\n */\ninterface TweenInstance {\n  /** The VanJS states created for the instance's store */\n  states: VanState<unknown>[];\n  /** Stops the animation instance */\n  stop: () => void;\n  /** Whether the instance is currently playing */\n  isPlaying: boolean;\n}\n\n/** The set of tracked animation instances */\nconst instances = new Set<TweenInstance>();\n/** The global MutationObserver watching for removed DOM nodes */\nlet observer: MutationObserver | null = null;\n\n/** The current session id, incremented on every {@link nextId} call */\nlet sessionId = 0;\n/** The VanJS states collected per session id */\nconst sessionStates = new Map<number, VanState<unknown>[]>();\n\n/**\n * Creates a VanJS state and collects it in the current session,\n * so it can later be associated with an animation instance.\n *\n * @template T - The type of the state value\n * @param initial - The initial state value\n * @returns The created VanJS state\n */\nexport function vanState<T>(initial: T): State<T> {\n  const stateObj = van.state(initial);\n  sessionStates.get(sessionId)?.push(stateObj);\n  return stateObj;\n}\n\n/**\n * Iterates all tracked instances and stops the ones whose DOM bindings\n * are all disconnected from the document.\n */\nfunction checkRemovedBindings() {\n  for (const instance of instances) {\n    if (!instance.isPlaying) {\n      instances.delete(instance);\n      continue;\n    }\n\n    let hasActiveBinding = false;\n    for (const state of instance.states) {\n      const bindings = state._bindings;\n      // istanbul ignore else\n      if (bindings?.length) {\n        for (const b of bindings) {\n          if (b._dom?.isConnected) {\n            hasActiveBinding = true;\n            break;\n          }\n        }\n      }\n      if (hasActiveBinding) break;\n    }\n\n    if (!hasActiveBinding) {\n      instance.stop();\n      instances.delete(instance);\n    }\n  }\n}\n\n/**\n * Creates (once) the global MutationObserver on `document.body` that\n * triggers {@link checkRemovedBindings} whenever nodes are removed.\n */\nfunction initObserver() {\n  if (observer) return;\n  observer = new MutationObserver((mutations) => {\n    for (const m of mutations) {\n      if (m.removedNodes.length) {\n        checkRemovedBindings();\n      }\n    }\n  });\n  observer.observe(document.body, { childList: true, subtree: true });\n}\n\n/**\n * The minimal shape of an animation instance that can be tracked\n * for auto-cleanup.\n */\nexport interface TweenLike {\n  /** The animated state object */\n  state: TweenProps;\n  /** Stops the animation instance */\n  stop(): TweenLike;\n  /** Whether the instance is currently playing */\n  readonly isPlaying: boolean;\n}\n\n/**\n * Starts a new tracking session and returns its id. The VanJS states\n * created via {@link vanState} until {@link mount} is called with the\n * returned id are associated with the mounted instance.\n *\n * @returns The new session id\n */\nexport function nextId(): number {\n  sessionId++;\n  sessionStates.set(sessionId, []);\n  return sessionId;\n}\n\n/**\n * Associates the states collected in the session `id` with the given\n * animation instance, registers it for auto-cleanup and starts the\n * global MutationObserver.\n *\n * @param twObject - The Tween / Timeline instance to track\n * @param id - The session id returned by {@link nextId}\n */\nexport function mount(twObject: TweenLike, id: number) {\n  const states = sessionStates.get(id)!;\n  sessionStates.delete(id);\n\n  const instance: TweenInstance = {\n    states,\n    stop: () => {\n      twObject.stop();\n    },\n    get isPlaying() {\n      return twObject.isPlaying;\n    },\n  };\n\n  const origStop = twObject.stop.bind(twObject);\n  twObject.stop = function () {\n    unmount(instance);\n    return origStop();\n  };\n  instances.add(instance);\n  initObserver();\n}\n\n/**\n * Removes an instance from the auto-cleanup tracking set.\n *\n * @param instance - The tracked instance to remove\n */\nexport function unmount(instance: TweenInstance) {\n  instances.delete(instance);\n}\n\n/**\n * Returns the set of currently tracked instances, mainly used by tests.\n *\n * @returns The set of tracked {@link TweenInstance} objects\n */\nexport function getInstances() {\n  return instances;\n}\n","import {\n  type ArrayVal,\n  isArray,\n  isPlainObject,\n  objectHasProp,\n  type TweenProps,\n} from \"@thednp/tween\";\n\nimport { vanState } from \"./lifecycle.ts\";\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/** The property name holding the state refs collected for a store */\nconst STATES_KEY = \"_states\";\n\n/**\n * Defines a reactive proxy for a single array index, with a nested\n * proxy for sub-arrays. Array mutations are tracked with a\n * version-toggle VanJS state so the owning getter stays reactive.\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          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, backed by\n * a VanJS state created via {@link vanState}. Array values are wrapped\n * with a version-toggle state so element mutations stay reactive without\n * replacing the array.\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 * @returns The created VanJS state\n */\nfunction defineStateProxy<T extends Omit<TweenProps, \"_proxy\">>(\n  key: number | keyof T,\n  value: T[keyof T] | ArrayVal,\n  target: T | ArrayVal,\n) {\n  const stateObj = vanState(value);\n  let getter: () => typeof value;\n  let setter;\n\n  if (isArray(value)) {\n    const arrayProxy: typeof value = [];\n    const valLength = value.length;\n    const version = vanState(0);\n    for (let i = 0; i < valLength; i++) {\n      defineArrayProxy(i, (value as ArrayVal)[i], arrayProxy, valLength, () => {\n        version.val = 1 - version.val;\n      });\n    }\n    getter = () => {\n      version.val;\n      return stateObj.val;\n    };\n    stateObj.val = arrayProxy;\n  } else {\n    getter = () => stateObj.val;\n    setter = (newVal: typeof value) => {\n      stateObj.val = newVal;\n    };\n    stateObj.val = value as never;\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  return stateObj;\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 * All created VanJS states are collected on the non-enumerable\n * `_states` property of the store.\n *\n * @template T - The type of the state object\n * @param obj - The source object\n * @param parentReceiver - The object being built\n * @returns The built reactive state object\n */\nfunction createMiniState<T extends TweenProps>(\n  obj: T,\n  parentReceiver: TweenProps | number[] | [string, ...number[]][],\n) {\n  if (objectHasProp(obj, STATE_PROXY)) return obj;\n\n  const states: unknown[] = [];\n\n  for (const [key, value] of Object.entries(obj)) {\n    if (isPlainObject(value)) {\n      (parentReceiver as TweenProps)[key] = createMiniState(value, {});\n    } else {\n      const stateObj = defineStateProxy(key, value, parentReceiver);\n      states.push(stateObj);\n    }\n  }\n\n  Object.defineProperty(parentReceiver, STATES_KEY, {\n    value: states,\n    enumerable: false,\n    configurable: false,\n    writable: false,\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 VanJS state. Reading a property inside a\n * VanJS binding tracks the state; writing it (e.g. by the tween update\n * loop) triggers reactivity. The state refs are stored on the\n * non-enumerable `_states` property for auto-cleanup tracking.\n *\n * @template T - The type of the state object\n * @param init - The initial values object\n * @returns The reactive state object\n */\nexport function miniStore<\n  T extends TweenProps,\n>(init: T): T {\n  return createMiniState(init, {}) as T;\n}\n","/**\n * VanJS integration for {@link @thednp/tween}.\n *\n * Provides `createTween` and `createTimeline` primitives backed by a\n * reactive miniStore with automatic DOM cleanup, plus re-exports of the\n * core {@link Tween} / {@link Timeline} classes and the {@link miniStore}\n * helper.\n *\n * @module\n */\nimport {\n  dummyInstance,\n  isServer,\n  Timeline,\n  Tween,\n  type TweenProps,\n} from \"@thednp/tween\";\nimport { miniStore } from \"./miniStore.ts\";\nimport { mount, nextId } from \"./lifecycle.ts\";\n\n/**\n * Re-exports the core classes and the miniStore helper.\n */\nexport { miniStore, Timeline, Tween };\n\n/**\n * VanJS primitive for updating values with Tween.\n *\n * Automatically stops the tween when all bound DOM nodes are removed\n * (leveraging a global MutationObserver that monitors state bindings).\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] = createTween({ x: 0 })\n *    tween.to({ x: 100 }).duration(1)\n *\n *    return div(\n *      { style: () => `translate: ${state.x}px` },\n *      \"Animated\"\n *    )\n * }\n */\nexport function createTween<\n  T extends TweenProps,\n>(initialValues: T): readonly [T, Tween<T>] {\n  if (isServer) {\n    return [initialValues, dummyInstance as unknown as Tween<T>] as const;\n  }\n  const id = nextId();\n  const store = miniStore(initialValues);\n  const tween = new Tween(store);\n\n  mount(tween, id);\n\n  return [store, tween] as [T, Tween<T>];\n}\n\n/**\n * VanJS primitive for sequencing values update with Timeline.\n *\n * Automatically stops the timeline when all bound DOM nodes are removed\n * (leveraging a global MutationObserver that monitors state bindings).\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] = createTimeline({ x: 0, y: 0 })\n *    timeline.to({ x: 100, y: 100 }).duration(2)\n *\n *    return div(\n *      { style: () => `translate: ${state.x}px ${state.y}px` },\n *      \"Animated\"\n *    )\n * }\n */\nexport function createTimeline<\n  T extends TweenProps,\n>(initialValues: T): readonly [T, Timeline<T>] {\n  if (isServer) {\n    return [initialValues, dummyInstance as unknown as Timeline<T>] as const;\n  }\n  const id = nextId();\n  const store = miniStore(initialValues);\n  const timeline = new Timeline(store);\n\n  mount(timeline, id);\n\n  return [store, timeline] as [T, Timeline<T>];\n}\n"],"mappings":"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"}