{"version":3,"sources":["../../../../src/sensors/useTextSelection/core.ts"],"sourcesContent":["import { batch, ObservableHint, observable, type OpaqueObject } from \"@legendapp/state\";\nimport {\n  type DeepMaybeObservable,\n  type MaybeObservable,\n  type ReadonlyObservable,\n  createDebounceFn,\n  createThrottleFn,\n} from \"@usels/core\";\nimport {\n  type ConfigurableWindow,\n  defaultDocument,\n  resolveWindowSource,\n} from \"@shared/configurable\";\nimport { createEventListener } from \"../../browser/useEventListener/core\";\n\nexport interface UseTextSelectionOptions extends ConfigurableWindow {\n  /** Throttle selectionchange handler in ms. Mutually exclusive with debounce. */\n  throttle?: MaybeObservable<number>;\n  /** Debounce selectionchange handler in ms. Mutually exclusive with throttle. */\n  debounce?: MaybeObservable<number>;\n}\n\nexport interface UseTextSelectionReturn {\n  /** Selected text content */\n  text$: ReadonlyObservable<string>;\n  /** Bounding rectangles of selected ranges */\n  rects$: ReadonlyObservable<DOMRect[]>;\n  /** Selection ranges */\n  ranges$: ReadonlyObservable<Range[]>;\n  /** Current Selection object */\n  selection$: ReadonlyObservable<OpaqueObject<Selection> | null>;\n}\n\nfunction getRangesFromSelection(selection: Selection): Range[] {\n  const rangeCount = selection.rangeCount ?? 0;\n  return Array.from({ length: rangeCount }, (_, i) => selection.getRangeAt(i));\n}\n\nfunction opaqueArray<T>(arr: T[]): OpaqueObject<{ items: T[] }> {\n  return ObservableHint.opaque({ items: arr }) as OpaqueObject<{ items: T[] }>;\n}\n\n/**\n * Framework-agnostic reactive text-selection tracker. Listens to\n * `selectionchange` on the document and exposes the current selection's text,\n * ranges, rects (lazily computed), and raw `Selection` object.\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function createTextSelection(\n  options?: DeepMaybeObservable<UseTextSelectionOptions>\n): UseTextSelectionReturn {\n  const opts$ = observable(options);\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- window field type varies by hint map\n  const window$ = resolveWindowSource(opts$.window as any);\n\n  // Derive document from resolved window; fall back to defaultDocument.\n  const doc$ = observable<OpaqueObject<Document> | null>(() => {\n    const doc = window$.get()?.document ?? defaultDocument;\n    return doc ? (ObservableHint.opaque(doc) as OpaqueObject<Document>) : null;\n  });\n\n  const text$ = observable(\"\");\n  const rangesBox$ = observable<OpaqueObject<{ items: Range[] }>>(\n    ObservableHint.opaque({ items: [] }) as OpaqueObject<{ items: Range[] }>\n  );\n  const selection$ = observable<OpaqueObject<Selection> | null>(null);\n\n  const invoke = () => {\n    const win = window$.peek();\n    if (!win) return;\n    const sel = win.getSelection();\n    if (sel) {\n      const r = getRangesFromSelection(sel);\n      batch(() => {\n        selection$.set(ObservableHint.opaque(sel));\n        text$.set(sel.toString());\n        rangesBox$.set(opaqueArray(r));\n      });\n    } else {\n      batch(() => {\n        selection$.set(null);\n        text$.set(\"\");\n        rangesBox$.set(opaqueArray([]));\n      });\n    }\n  };\n\n  // Mount-time selection: throttle/debounce/plain decided once at creation\n  // time (the wrapper function itself is swapped at mount and kept stable).\n  const raw = opts$.peek();\n  const throttleMs = raw?.throttle;\n  const debounceMs = raw?.debounce;\n\n  let handler: (...args: unknown[]) => unknown = invoke;\n  if (throttleMs !== undefined) {\n    handler = createThrottleFn(invoke, throttleMs).throttledFn as typeof handler;\n  } else if (debounceMs !== undefined) {\n    handler = createDebounceFn(invoke, debounceMs).debouncedFn as typeof handler;\n  }\n\n  createEventListener(doc$, \"selectionchange\", handler as () => void, { passive: true });\n\n  // Lazy computed — getBoundingClientRect runs only when rects$ is accessed.\n  const rects$ = observable<DOMRect[]>(() =>\n    (rangesBox$.get()?.items ?? []).map((range) => range.getBoundingClientRect())\n  );\n  const ranges$ = observable<Range[]>(() => rangesBox$.get()?.items ?? []);\n\n  return {\n    text$: text$ as ReadonlyObservable<string>,\n    rects$: rects$ as ReadonlyObservable<DOMRect[]>,\n    ranges$: ranges$ as ReadonlyObservable<Range[]>,\n    selection$: selection$ as ReadonlyObservable<OpaqueObject<Selection> | null>,\n  };\n}\n"],"mappings":"AAAA,SAAS,OAAO,gBAAgB,kBAAqC;AACrE;AAAA,EAIE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EAEE;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AAoBpC,SAAS,uBAAuB,WAA+B;AAC7D,QAAM,aAAa,UAAU,cAAc;AAC3C,SAAO,MAAM,KAAK,EAAE,QAAQ,WAAW,GAAG,CAAC,GAAG,MAAM,UAAU,WAAW,CAAC,CAAC;AAC7E;AAEA,SAAS,YAAe,KAAwC;AAC9D,SAAO,eAAe,OAAO,EAAE,OAAO,IAAI,CAAC;AAC7C;AAAA;AAQO,SAAS,oBACd,SACwB;AACxB,QAAM,QAAQ,WAAW,OAAO;AAEhC,QAAM,UAAU,oBAAoB,MAAM,MAAa;AAGvD,QAAM,OAAO,WAA0C,MAAM;AAC3D,UAAM,MAAM,QAAQ,IAAI,GAAG,YAAY;AACvC,WAAO,MAAO,eAAe,OAAO,GAAG,IAA+B;AAAA,EACxE,CAAC;AAED,QAAM,QAAQ,WAAW,EAAE;AAC3B,QAAM,aAAa;AAAA,IACjB,eAAe,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;AAAA,EACrC;AACA,QAAM,aAAa,WAA2C,IAAI;AAElE,QAAM,SAAS,MAAM;AACnB,UAAM,MAAM,QAAQ,KAAK;AACzB,QAAI,CAAC,IAAK;AACV,UAAM,MAAM,IAAI,aAAa;AAC7B,QAAI,KAAK;AACP,YAAM,IAAI,uBAAuB,GAAG;AACpC,YAAM,MAAM;AACV,mBAAW,IAAI,eAAe,OAAO,GAAG,CAAC;AACzC,cAAM,IAAI,IAAI,SAAS,CAAC;AACxB,mBAAW,IAAI,YAAY,CAAC,CAAC;AAAA,MAC/B,CAAC;AAAA,IACH,OAAO;AACL,YAAM,MAAM;AACV,mBAAW,IAAI,IAAI;AACnB,cAAM,IAAI,EAAE;AACZ,mBAAW,IAAI,YAAY,CAAC,CAAC,CAAC;AAAA,MAChC,CAAC;AAAA,IACH;AAAA,EACF;AAIA,QAAM,MAAM,MAAM,KAAK;AACvB,QAAM,aAAa,KAAK;AACxB,QAAM,aAAa,KAAK;AAExB,MAAI,UAA2C;AAC/C,MAAI,eAAe,QAAW;AAC5B,cAAU,iBAAiB,QAAQ,UAAU,EAAE;AAAA,EACjD,WAAW,eAAe,QAAW;AACnC,cAAU,iBAAiB,QAAQ,UAAU,EAAE;AAAA,EACjD;AAEA,sBAAoB,MAAM,mBAAmB,SAAuB,EAAE,SAAS,KAAK,CAAC;AAGrF,QAAM,SAAS;AAAA,IAAsB,OAClC,WAAW,IAAI,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,UAAU,MAAM,sBAAsB,CAAC;AAAA,EAC9E;AACA,QAAM,UAAU,WAAoB,MAAM,WAAW,IAAI,GAAG,SAAS,CAAC,CAAC;AAEvE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}