{"version":3,"file":"index.cjs","sources":["../src/batch.ts","../src/signal.ts","../src/computed.ts"],"sourcesContent":["import { Signal } from './signal.js';\n\nlet callbacks: Map<Signal<any>, () => void> | undefined;\n\nexport function runInBatchMode(signal: Signal<any>, fn: () => void): void {\n  callbacks && callbacks.set(signal, fn) || fn();\n}\n\n/**\n * Runs the specified function in the batch mode.\n *\n * It makes all signals' subscribers to be called only after signals changes inside the specified\n * function were applied.\n * @param fn - function to run in the batch mode.\n * @example\n * const a = signal(1);\n * const b = signal(1);\n * const c = computed(() => a() + b());\n *\n * function subscriber(value) {\n *   console.log(value);\n * }\n *\n * c.sub(subscriber);\n *\n * batch(() => {\n *   a.set(2);\n *   a.set(3);\n *   b.set(2);\n *   b.set(3);\n * });\n *\n * // Despite the fact that we called signals setters four times, the `subscriber` function will\n * // be called only once. Removing the `batch` function usage will lead to calling the `subscriber`\n * // function four times.\n */\nexport function batch(fn: () => void): void {\n  // There could be a case when a batch is called inside other batches.\n  // In this case, we should just ignore the current call.\n  if (callbacks) {\n    return fn();\n  }\n  callbacks = new Map();\n\n  try {\n    fn();\n  } finally {\n    callbacks.forEach(cb => cb());\n    callbacks = undefined;\n    // TODO: What if something went wrong in fn or its callbacks?\n  }\n}\n","import { collectSignal } from './computed.js';\nimport { runInBatchMode } from './batch.js';\n\nexport type SubscribeListenerFn<T> = (current: T, previous: T) => void;\nexport type RemoveListenerFn = () => void;\n\nexport interface SignalOptions<T> {\n  /**\n   * Previous and next values comparator.\n   *\n   * This function is used during the actual and incoming values comparison in the `set` method.\n   * If values are considered the same, no subscribers will be called.\n   *\n   * @default Object.is\n   * @param current - the actual value.\n   * @param next - an incoming value.\n   * @returns True if values are considered the same.\n   */\n  equals?: (current: T, next: T) => boolean;\n}\n\nexport interface SubOptions {\n  /**\n   * Should the listener be called only once.\n   */\n  once?: boolean;\n  /**\n   * True if the subscriber was added by another signal.\n   */\n  signal?: boolean;\n}\n\nexport interface Signal<T> {\n  /**\n   * @returns An underlying signal value.\n   */\n  (): T;\n  /**\n   * Destroys the signal removing all bound listeners.\n   *\n   * We usually use this method when the signal is not needed anymore.\n   *\n   * Take note that as long as call of this method removes all bound listeners, computed signals\n   * based on the current one will stop listening to its changes, possibly making it work\n   * improperly.\n   */\n  destroy: () => void;\n  /**\n   * Resets the signal to its initial value.\n   */\n  reset: () => void;\n  /**\n   * Updates the signal notifying all subscribers about changes.\n   * @param value - value to set.\n   */\n  set: (value: T) => void;\n  /**\n   * Adds a new listener, tracking the signal changes.\n   * @param fn - event listener.\n   * @param onceOrOptions - was this listener added for a single call, or additional\n   * options.\n   * @returns A function to remove the bound listener.\n   */\n  sub: (fn: SubscribeListenerFn<T>, onceOrOptions?: boolean | SubOptions) => RemoveListenerFn;\n  /**\n   * Removes a listener, tracking the signal changes.\n   * @param fn - event listener.\n   * @param onceOrOptions - was this listener added for a single call, or additional\n   * options. Default: false\n   */\n  unsub: (fn: SubscribeListenerFn<T>, onceOrOptions?: boolean | SubOptions) => void;\n  /**\n   * Remove all non-signal listeners.\n   */\n  unsubAll: () => void;\n}\n\n/**\n * Creates a new signal with initial value.\n * @param initialValue - initial value.\n * @param options - additional options.\n */\nexport function signal<T>(\n  initialValue: T,\n  options?: SignalOptions<T>,\n): Signal<T>;\n\n/**\n * Creates a new signal without initial value.\n * @param initialValue\n * @param options - additional options.\n */\nexport function signal<T>(\n  initialValue?: T,\n  options?: SignalOptions<T | undefined>,\n): Signal<T | undefined>;\n\n// #__NO_SIDE_EFFECTS__\nexport function signal<T>(\n  initialValue?: T,\n  options?: SignalOptions<T | undefined>,\n): Signal<T | undefined> {\n  type CurrentSignal = Signal<T | undefined>;\n\n  options ||= {};\n  const equals = options.equals || Object.is;\n\n  let listeners: [\n    listener: SubscribeListenerFn<T | undefined>,\n    options: Required<SubOptions>\n  ][] = [];\n  let value: ReturnType<CurrentSignal> = initialValue;\n\n  const set: CurrentSignal['set'] = v => {\n    if (!equals(value, v)) {\n      const prev = value;\n      value = v;\n\n      // We are making a copy of listeners as long as they may mutate the listeners' array,\n      // leading to an unexpected behavior.\n      //\n      // We want the setter to make sure that all listeners will be called in predefined\n      // order within a single update frame.\n      runInBatchMode(s, () => {\n        [...listeners].forEach(([fn, once]) => {\n          fn(v, prev);\n\n          // Remove \"once\" listeners.\n          if (once) {\n            unsub(fn, true);\n          }\n        });\n      });\n    }\n  };\n\n  function formatSubOptions(onceOrOptions: boolean | SubOptions | undefined): Required<SubOptions> {\n    const options = typeof onceOrOptions !== 'object'\n      ? { once: onceOrOptions }\n      : onceOrOptions;\n    return {\n      once: options.once || false,\n      signal: options.signal || false,\n    };\n  }\n\n  const unsub: CurrentSignal['unsub'] = (fn, onceOrOptions) => {\n    const options = formatSubOptions(onceOrOptions);\n    const idx = listeners.findIndex(([listener, lOptions]) => {\n      return listener === fn\n        && lOptions.once === options.once\n        && lOptions.signal === options.signal;\n    });\n    if (idx >= 0) {\n      listeners.splice(idx, 1);\n    }\n  };\n\n  const s = Object.assign(\n    function get() {\n      collectSignal(s);\n      return value;\n    },\n    {\n      destroy() {\n        listeners = [];\n      },\n      set,\n      reset() {\n        set(initialValue);\n      },\n      sub(fn, onceOrOptions) {\n        listeners.push([fn, formatSubOptions(onceOrOptions)]);\n        return () => unsub(fn, onceOrOptions);\n      },\n      unsub,\n      unsubAll() {\n        listeners = listeners.filter(l => l[1].signal);\n      },\n    } satisfies Pick<CurrentSignal, 'destroy' | 'set' | 'reset' | 'sub' | 'unsub' | 'unsubAll'>,\n  );\n\n  return s;\n}\n","import { signal, type Signal, type SignalOptions } from './signal.js';\n\nexport interface Computed<T> extends Omit<Signal<T>, 'set' | 'reset'> {\n  /**\n   * @returns An underlying signal value.\n   */\n  (): T;\n}\n\nconst collectContexts: Set<Signal<unknown>>[] = [];\n\nexport function collectSignal(signal: Signal<any>): void {\n  collectContexts.length && collectContexts[collectContexts.length - 1].add(signal);\n}\n\n/**\n * Creates a signal, which wil be automatically updated if some of its dependant signals were\n * modified.\n * @param fn - computation function.\n * @param options - additional functions.\n */\n// #__NO_SIDE_EFFECTS__\nexport function computed<T>(\n  fn: (prev?: T) => T,\n  options?: SignalOptions<T>,\n): Computed<T> {\n  let deps = new Set<Signal<unknown>>();\n\n  // An underlying signal.\n  let $signal: Signal<T> | undefined;\n\n  function s(): Signal<T> {\n    return $signal || ($signal = signal<T>(compute(), options));\n  }\n\n  function update() {\n    s().set(compute());\n  }\n\n  function compute(): T {\n    // As long as in this iteration, we may receive new signals as dependencies, we stop\n    // listening to the previous signals.\n    deps.forEach(s => {\n      s.unsub(update, { signal: true });\n    });\n\n    // Signals we collected during current computation.\n    const collectedSignals = new Set<Signal<unknown>>();\n    let result: T;\n\n    // Add this set to the global variable, so dependant signals will be catched.\n    collectContexts.push(collectedSignals);\n\n    try {\n      // Run the function and collect all called signals.\n      result = fn();\n    } finally {\n      // Remember to untrack the reactive context.\n      collectContexts.pop();\n    }\n\n    // Start tracking for all dependencies' changes and re-compute the computed value.\n    collectedSignals.forEach(s => {\n      s.sub(update, { signal: true });\n    });\n    deps = collectedSignals;\n\n    return result;\n  }\n\n  return Object.assign(function computed(): T {\n    return s()();\n  }, {\n    destroy() {\n      s().destroy();\n    },\n    sub(...args) {\n      return s().sub(...args);\n    },\n    unsub(...args) {\n      s().unsub(...args);\n    },\n    unsubAll(...args) {\n      s().unsubAll(...args);\n    },\n  } satisfies Pick<Computed<T>, 'destroy' | 'sub' | 'unsub' | 'unsubAll'>);\n}\n"],"names":["callbacks","runInBatchMode","signal","fn","batch","cb","initialValue","options","equals","listeners","value","set","v","prev","s","once","unsub","formatSubOptions","onceOrOptions","idx","listener","lOptions","collectSignal","l","collectContexts","computed","deps","$signal","compute","update","collectedSignals","result","args"],"mappings":"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"}