@bquery/bquery 1.11.0 → 1.12.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.
Files changed (110) hide show
  1. package/README.md +79 -59
  2. package/dist/{a11y-DgUQ8-fI.js → a11y-IV_bfLyn.js} +3 -3
  3. package/dist/{a11y-DgUQ8-fI.js.map → a11y-IV_bfLyn.js.map} +1 -1
  4. package/dist/a11y.es.mjs +1 -1
  5. package/dist/{component-D8ydhe58.js → component-BtsRbf6c.js} +7 -7
  6. package/dist/{component-D8ydhe58.js.map → component-BtsRbf6c.js.map} +1 -1
  7. package/dist/component.es.mjs +1 -1
  8. package/dist/{concurrency-BU1wPEsZ.js → concurrency-kycgAZvW.js} +3 -3
  9. package/dist/{concurrency-BU1wPEsZ.js.map → concurrency-kycgAZvW.js.map} +1 -1
  10. package/dist/concurrency.es.mjs +1 -1
  11. package/dist/{config-DhT9auRm.js → config-BP7KwiR5.js} +1 -1
  12. package/dist/{config-DhT9auRm.js.map → config-BP7KwiR5.js.map} +1 -1
  13. package/dist/constraints-Dlbx_m1b.js.map +1 -1
  14. package/dist/core-tOP6QOrY.js.map +1 -1
  15. package/dist/{core-CongXJuo.js → core-yg9rJXiR.js} +1 -1
  16. package/dist/{core-CongXJuo.js.map → core-yg9rJXiR.js.map} +1 -1
  17. package/dist/custom-directives-5DlKqvd2.js.map +1 -1
  18. package/dist/devtools-QosAqo0T.js.map +1 -1
  19. package/dist/dnd-d2OU4len.js.map +1 -1
  20. package/dist/{effect-Cc51IH91.js → effect-v8OIEmPs.js} +2 -2
  21. package/dist/{effect-Cc51IH91.js.map → effect-v8OIEmPs.js.map} +1 -1
  22. package/dist/env-PvwYHnJq.js.map +1 -1
  23. package/dist/{forms-BLx4ZzT7.js → forms-DYcdlk_h.js} +8 -8
  24. package/dist/{forms-BLx4ZzT7.js.map → forms-DYcdlk_h.js.map} +1 -1
  25. package/dist/forms.es.mjs +1 -1
  26. package/dist/full.d.ts +11 -11
  27. package/dist/full.d.ts.map +1 -1
  28. package/dist/full.es.mjs +324 -279
  29. package/dist/full.iife.js +26 -26
  30. package/dist/full.iife.js.map +1 -1
  31. package/dist/full.umd.js +26 -26
  32. package/dist/full.umd.js.map +1 -1
  33. package/dist/function-Cybd57JV.js.map +1 -1
  34. package/dist/{i18n--p7PM-9r.js → i18n-unHU1jMo.js} +3 -3
  35. package/dist/{i18n--p7PM-9r.js.map → i18n-unHU1jMo.js.map} +1 -1
  36. package/dist/i18n.es.mjs +1 -1
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.es.mjs +198 -196
  39. package/dist/match-CrZRVC4z.js.map +1 -1
  40. package/dist/{media-gjbWNq50.js → media-Chh6mA0v.js} +3 -3
  41. package/dist/{media-gjbWNq50.js.map → media-Chh6mA0v.js.map} +1 -1
  42. package/dist/media.es.mjs +1 -1
  43. package/dist/{motion-BBMso9Ir.js → motion-27Od9aFE.js} +2 -2
  44. package/dist/{motion-BBMso9Ir.js.map → motion-27Od9aFE.js.map} +1 -1
  45. package/dist/motion.es.mjs +1 -1
  46. package/dist/{mount-0A9qtcRJ.js → mount-DwUFujZ_.js} +4 -4
  47. package/dist/{mount-0A9qtcRJ.js.map → mount-DwUFujZ_.js.map} +1 -1
  48. package/dist/object-BCk-1c8T.js.map +1 -1
  49. package/dist/{platform-BPHIXbw8.js → platform-2YkFA11t.js} +4 -4
  50. package/dist/{platform-BPHIXbw8.js.map → platform-2YkFA11t.js.map} +1 -1
  51. package/dist/platform.es.mjs +2 -2
  52. package/dist/plugin-SZEirbwq.js.map +1 -1
  53. package/dist/reactive/index.d.ts +2 -2
  54. package/dist/reactive/index.d.ts.map +1 -1
  55. package/dist/reactive/signal.d.ts +7 -7
  56. package/dist/reactive/signal.d.ts.map +1 -1
  57. package/dist/reactive/websocket.d.ts +15 -3
  58. package/dist/reactive/websocket.d.ts.map +1 -1
  59. package/dist/{reactive-BAd2hfl8.js → reactive-BvPR_FYA.js} +10 -10
  60. package/dist/{reactive-BAd2hfl8.js.map → reactive-BvPR_FYA.js.map} +1 -1
  61. package/dist/reactive.es.mjs +40 -40
  62. package/dist/{readonly-C0ZwS1Tf.js → readonly-Br-6pAgj.js} +2 -2
  63. package/dist/{readonly-C0ZwS1Tf.js.map → readonly-Br-6pAgj.js.map} +1 -1
  64. package/dist/registry-jpUQHf4E.js.map +1 -1
  65. package/dist/{router-C4weu0QL.js → router-CbnWKprL.js} +7 -7
  66. package/dist/{router-C4weu0QL.js.map → router-CbnWKprL.js.map} +1 -1
  67. package/dist/router.es.mjs +1 -1
  68. package/dist/sanitize-DOMkRO9G.js.map +1 -1
  69. package/dist/server/create-server.d.ts.map +1 -1
  70. package/dist/server/types.d.ts.map +1 -1
  71. package/dist/{server-QdyKtCS1.js → server-Dwiq_F49.js} +2 -2
  72. package/dist/server-Dwiq_F49.js.map +1 -0
  73. package/dist/server.es.mjs +1 -1
  74. package/dist/ssr/context.d.ts.map +1 -1
  75. package/dist/ssr/renderer.d.ts.map +1 -1
  76. package/dist/ssr/suspense.d.ts.map +1 -1
  77. package/dist/{ssr-Bt6BQA3J.js → ssr-CqJU1Ogp.js} +8 -8
  78. package/dist/ssr-CqJU1Ogp.js.map +1 -0
  79. package/dist/ssr.es.mjs +1 -1
  80. package/dist/store/index.d.ts +1 -1
  81. package/dist/store/index.d.ts.map +1 -1
  82. package/dist/store/plugins.d.ts +38 -0
  83. package/dist/store/plugins.d.ts.map +1 -1
  84. package/dist/{store-DnXuu6Li.js → store-DzrhVQ29.js} +69 -62
  85. package/dist/store-DzrhVQ29.js.map +1 -0
  86. package/dist/store.es.mjs +13 -11
  87. package/dist/storybook.es.mjs.map +1 -1
  88. package/dist/{testing-CeMUwrRD.js → testing-ByjwS2_D.js} +3 -3
  89. package/dist/{testing-CeMUwrRD.js.map → testing-ByjwS2_D.js.map} +1 -1
  90. package/dist/testing.es.mjs +1 -1
  91. package/dist/type-guards-BMX2c0LP.js.map +1 -1
  92. package/dist/{untrack-bjWDNdyE.js → untrack-uzz3JDNK.js} +3 -3
  93. package/dist/{untrack-bjWDNdyE.js.map → untrack-uzz3JDNK.js.map} +1 -1
  94. package/dist/view.es.mjs +8 -8
  95. package/package.json +12 -11
  96. package/src/full.ts +75 -6
  97. package/src/index.ts +2 -2
  98. package/src/reactive/index.ts +5 -4
  99. package/src/reactive/signal.ts +7 -6
  100. package/src/reactive/websocket.ts +15 -2
  101. package/src/server/create-server.ts +10 -5
  102. package/src/server/types.ts +1 -5
  103. package/src/ssr/context.ts +6 -2
  104. package/src/ssr/renderer.ts +5 -2
  105. package/src/ssr/suspense.ts +17 -8
  106. package/src/store/index.ts +1 -1
  107. package/src/store/plugins.ts +48 -1
  108. package/dist/server-QdyKtCS1.js.map +0 -1
  109. package/dist/ssr-Bt6BQA3J.js.map +0 -1
  110. package/dist/store-DnXuu6Li.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"devtools-QosAqo0T.js","names":[],"sources":["../src/devtools/devtools.ts"],"sourcesContent":["/**\n * bQuery DevTools — runtime debugging utilities.\n *\n * Enable devtools to inspect signals, stores, components, and view a\n * timeline of reactive events.\n *\n * @module bquery/devtools\n */\n\nimport type {\n ComponentSnapshot,\n DevtoolsOptions,\n DevtoolsState,\n SignalSnapshot,\n StoreSnapshot,\n TimelineEntry,\n TimelineEventType,\n} from './types';\nimport { listStores as _listStores, getStore as _getStore } from '../store/registry';\n\n// ---------------------------------------------------------------------------\n// Internal state\n// ---------------------------------------------------------------------------\n\nlet _enabled = false;\nlet _options: DevtoolsOptions = {};\nconst _timeline: TimelineEntry[] = [];\n\n/** Registered signals keyed by label. */\nconst _trackedSignals = new Map<string, { peek: () => unknown; subscriberCount: () => number }>();\n\n/** Monotonic counter for auto-labelling anonymous signals. */\nlet _signalCounter = 0;\n\n// ---------------------------------------------------------------------------\n// Enable / disable\n// ---------------------------------------------------------------------------\n\n/**\n * Enable bQuery development mode.\n *\n * When enabled the devtools module records timeline events and\n * allows inspection of signals, stores, and components.\n *\n * @param enabled - `true` to enable, `false` to disable.\n * @param options - Optional configuration.\n *\n * @example\n * ```ts\n * import { enableDevtools } from '@bquery/bquery/devtools';\n * enableDevtools(true, { logToConsole: true });\n * ```\n */\nexport const enableDevtools = (enabled: boolean, options?: DevtoolsOptions): void => {\n _enabled = enabled;\n _options = options ?? {};\n\n if (!enabled) {\n _timeline.length = 0;\n _trackedSignals.clear();\n _signalCounter = 0;\n }\n};\n\n/**\n * Returns `true` when devtools are active.\n *\n * @example\n * ```ts\n * import { isDevtoolsEnabled } from '@bquery/bquery/devtools';\n * if (isDevtoolsEnabled()) { ... }\n * ```\n */\nexport const isDevtoolsEnabled = (): boolean => _enabled;\n\n// ---------------------------------------------------------------------------\n// Signal tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Register a signal for devtools inspection.\n *\n * @param label - Human-readable label (must be unique).\n * @param peek - Returns the signal's value without tracking.\n * @param subscriberCount - Returns the current subscriber count.\n * @throws If a signal with the same label is already tracked.\n *\n * @example\n * ```ts\n * import { trackSignal } from '@bquery/bquery/devtools';\n * import { signal } from '@bquery/bquery/reactive';\n *\n * const count = signal(0);\n * trackSignal('count', () => count.peek(), () => 0);\n * ```\n */\nexport const trackSignal = (\n label: string,\n peek: () => unknown,\n subscriberCount: () => number\n): void => {\n if (!_enabled) return;\n if (typeof label !== 'string' || label.length === 0) {\n throw new Error('bQuery devtools: trackSignal() requires a non-empty label');\n }\n _trackedSignals.set(label, { peek, subscriberCount });\n};\n\n/**\n * Remove a previously tracked signal.\n *\n * @param label - The label used during registration.\n *\n * @example\n * ```ts\n * import { untrackSignal } from '@bquery/bquery/devtools';\n * untrackSignal('count');\n * ```\n */\nexport const untrackSignal = (label: string): void => {\n _trackedSignals.delete(label);\n};\n\n/**\n * Generate a unique label for anonymous signals.\n *\n * @returns A label such as `signal_0`, `signal_1`, …\n *\n * @example\n * ```ts\n * import { generateSignalLabel } from '@bquery/bquery/devtools';\n * const label = generateSignalLabel(); // 'signal_0'\n * ```\n */\nexport const generateSignalLabel = (): string => `signal_${_signalCounter++}`;\n\n// ---------------------------------------------------------------------------\n// Signal inspector\n// ---------------------------------------------------------------------------\n\n/**\n * List all tracked signals with their current values.\n *\n * @returns An array of {@link SignalSnapshot} objects.\n *\n * @example\n * ```ts\n * import { inspectSignals } from '@bquery/bquery/devtools';\n * console.table(inspectSignals());\n * ```\n */\nexport const inspectSignals = (): SignalSnapshot[] => {\n const result: SignalSnapshot[] = [];\n for (const [label, entry] of _trackedSignals) {\n result.push({\n label,\n value: entry.peek(),\n subscriberCount: entry.subscriberCount(),\n });\n }\n return result;\n};\n\n// ---------------------------------------------------------------------------\n// Store inspector\n// ---------------------------------------------------------------------------\n\n/**\n * List all stores with their current state.\n *\n * Reads from the store registry (`listStores` / `getStore`) that is\n * already maintained by the store module.\n *\n * @returns An array of {@link StoreSnapshot} objects.\n *\n * @example\n * ```ts\n * import { inspectStores } from '@bquery/bquery/devtools';\n * console.table(inspectStores());\n * ```\n */\nexport const inspectStores = (): StoreSnapshot[] => {\n try {\n const ids: string[] = _listStores();\n return ids.map((id: string) => {\n const store = _getStore(id) as Record<string, unknown> | undefined;\n const state: Record<string, unknown> = {};\n if (store && typeof store === 'object' && '$state' in store) {\n const raw = (store as { $state: unknown }).$state;\n if (raw && typeof raw === 'object') {\n Object.assign(state, raw);\n }\n }\n return { id, state };\n });\n } catch {\n return [];\n }\n};\n\n// ---------------------------------------------------------------------------\n// Component inspector\n// ---------------------------------------------------------------------------\n\n/**\n * List all bQuery-registered custom elements visible in the document.\n *\n * @returns An array of {@link ComponentSnapshot} objects.\n *\n * @example\n * ```ts\n * import { inspectComponents } from '@bquery/bquery/devtools';\n * console.table(inspectComponents());\n * ```\n */\nexport const inspectComponents = (): ComponentSnapshot[] => {\n if (typeof document === 'undefined') return [];\n\n const result: ComponentSnapshot[] = [];\n const seen = new Set<string>();\n\n // Walk every custom element in the DOM (they must contain a hyphen)\n const all = document.querySelectorAll('*');\n for (let i = 0; i < all.length; i++) {\n const el = all[i];\n const tag = el.tagName.toLowerCase();\n if (!tag.includes('-')) continue;\n if (seen.has(tag)) continue;\n\n // Only include if registered via customElements\n if (typeof customElements !== 'undefined' && customElements.get(tag)) {\n seen.add(tag);\n const instances = document.querySelectorAll(tag);\n result.push({ tagName: tag, instanceCount: instances.length });\n }\n }\n return result;\n};\n\n// ---------------------------------------------------------------------------\n// Timeline\n// ---------------------------------------------------------------------------\n\n/**\n * Record a timeline event.\n *\n * This is called internally by bQuery modules (signals, store, router)\n * or can be called from user code / plugins to add custom entries.\n *\n * @param type - The event category.\n * @param detail - A human-readable description.\n *\n * @example\n * ```ts\n * import { recordEvent } from '@bquery/bquery/devtools';\n * recordEvent('signal:update', 'count changed to 5');\n * ```\n */\nexport const recordEvent = (type: TimelineEventType, detail: string): void => {\n if (!_enabled) return;\n\n const entry: TimelineEntry = {\n timestamp: Date.now(),\n type,\n detail,\n };\n _timeline.push(entry);\n\n if (_options.logToConsole) {\n console.log(`[bq:devtools] ${type} — ${detail}`);\n }\n};\n\n/**\n * Returns the full timeline log.\n *\n * @returns A read-only array of {@link TimelineEntry} objects.\n *\n * @example\n * ```ts\n * import { getTimeline } from '@bquery/bquery/devtools';\n * for (const e of getTimeline()) {\n * console.log(e.type, e.detail);\n * }\n * ```\n */\nexport const getTimeline = (): readonly TimelineEntry[] => _timeline;\n\n/**\n * Clear all timeline entries.\n *\n * @example\n * ```ts\n * import { clearTimeline } from '@bquery/bquery/devtools';\n * clearTimeline();\n * ```\n */\nexport const clearTimeline = (): void => {\n _timeline.length = 0;\n};\n\n// ---------------------------------------------------------------------------\n// Combined state\n// ---------------------------------------------------------------------------\n\n/**\n * Returns a snapshot of the full devtools state.\n *\n * @returns A {@link DevtoolsState} object.\n *\n * @example\n * ```ts\n * import { getDevtoolsState } from '@bquery/bquery/devtools';\n * const state = getDevtoolsState();\n * console.log(state.enabled, state.timeline.length);\n * ```\n */\nexport const getDevtoolsState = (): DevtoolsState => ({\n enabled: _enabled,\n options: { ..._options },\n timeline: [..._timeline],\n});\n\n// ---------------------------------------------------------------------------\n// Log helpers (convenience for interactive debugging)\n// ---------------------------------------------------------------------------\n\n/**\n * Pretty-print all tracked signals to the console.\n *\n * @example\n * ```ts\n * import { logSignals } from '@bquery/bquery/devtools';\n * logSignals(); // → table output\n * ```\n */\nexport const logSignals = (): void => {\n const signals = inspectSignals();\n if (signals.length === 0) {\n console.log('[bq:devtools] No tracked signals.');\n return;\n }\n\n console.table(signals);\n};\n\n/**\n * Pretty-print all stores to the console.\n *\n * @example\n * ```ts\n * import { logStores } from '@bquery/bquery/devtools';\n * logStores(); // → table output\n * ```\n */\nexport const logStores = (): void => {\n const stores = inspectStores();\n if (stores.length === 0) {\n console.log('[bq:devtools] No stores registered.');\n return;\n }\n\n console.table(stores.map((s) => ({ id: s.id, state: JSON.stringify(s.state) })));\n};\n\n/**\n * Pretty-print all registered components to the console.\n *\n * @example\n * ```ts\n * import { logComponents } from '@bquery/bquery/devtools';\n * logComponents(); // → table output\n * ```\n */\nexport const logComponents = (): void => {\n const components = inspectComponents();\n if (components.length === 0) {\n console.log('[bq:devtools] No custom elements found.');\n return;\n }\n\n console.table(components);\n};\n\n/**\n * Pretty-print the timeline to the console.\n *\n * @param last - Only show the last N entries (default: all).\n *\n * @example\n * ```ts\n * import { logTimeline } from '@bquery/bquery/devtools';\n * logTimeline(10);\n * ```\n */\nexport const logTimeline = (last?: number): void => {\n const entries = typeof last === 'number' && last > 0 ? _timeline.slice(-last) : _timeline;\n if (entries.length === 0) {\n console.log('[bq:devtools] Timeline is empty.');\n return;\n }\n\n console.table(\n entries.map((e) => ({\n time: new Date(e.timestamp).toISOString(),\n type: e.type,\n detail: e.detail,\n }))\n );\n};\n"],"mappings":";AAwBA,IAAI,IAAW,IACX,IAA4B,CAAA,GAC1B,IAA6B,CAAA,GAG7B,IAAkB,oBAAI,IAAA,GAGxB,IAAiB,GAqBR,IAAA,CAAkB,GAAkB,MAAoC;AACnF,EAAA,IAAW,GACX,IAAW,KAAW,CAAA,GAEjB,MACH,EAAU,SAAS,GACnB,EAAgB,MAAA,GAChB,IAAiB;GAaR,IAAA,MAAmC,GAuBnC,IAAA,CACX,GACA,GACA,MACS;AACT,MAAK,GACL;AAAA,QAAI,OAAO,KAAU,YAAY,EAAM,WAAW,EAChD,OAAM,IAAI,MAAM,2DAAA;AAElB,IAAA,EAAgB,IAAI,GAAO;AAAA,MAAE,MAAA;AAAA,MAAM,iBAAA;AAAA,KAAiB;AAAA;GAczC,IAAA,CAAiB,MAAwB;AACpD,EAAA,EAAgB,OAAO,CAAA;GAcZ,IAAA,MAAoC,UAAU,GAAA,IAiB9C,IAAA,MAAyC;AACpD,QAAM,IAA2B,CAAA;AACjC,aAAW,CAAC,GAAO,CAAA,KAAU,EAC3B,CAAA,EAAO,KAAK;AAAA,IACV,OAAA;AAAA,IACA,OAAO,EAAM,KAAA;AAAA,IACb,iBAAiB,EAAM,gBAAA;AAAA,GACxB;AAEH,SAAO;GAqBI,IAAA,MAAuC;AAClD,MAAI;AAEF,WADsB,EAAA,EACX,IAAA,CAAK,MAAe;AAC7B,YAAM,IAAQ,EAAU,CAAA,GAClB,IAAiC,CAAA;AACvC,UAAI,KAAS,OAAO,KAAU,YAAY,YAAY,GAAO;AAC3D,cAAM,IAAO,EAA8B;AAC3C,QAAI,KAAO,OAAO,KAAQ,YACxB,OAAO,OAAO,GAAO,CAAA;AAAA;AAGzB,aAAO;AAAA,QAAE,IAAA;AAAA,QAAI,OAAA;AAAA;;UAET;AACN,WAAO,CAAA;AAAA;GAmBE,IAAA,MAA+C;AAC1D,MAAI,OAAO,WAAa,IAAa,QAAO,CAAA;AAE5C,QAAM,IAA8B,CAAA,GAC9B,IAAO,oBAAI,IAAA,GAGX,IAAM,SAAS,iBAAiB,GAAA;AACtC,WAAS,IAAI,GAAG,IAAI,EAAI,QAAQ,KAAK;AAEnC,UAAM,IADK,EAAI,CAAA,EACA,QAAQ,YAAA;AACvB,QAAK,EAAI,SAAS,GAAA,KACd,CAAA,EAAK,IAAI,CAAA,KAGT,OAAO,iBAAmB,OAAe,eAAe,IAAI,CAAA,GAAM;AACpE,MAAA,EAAK,IAAI,CAAA;AACT,YAAM,IAAY,SAAS,iBAAiB,CAAA;AAC5C,MAAA,EAAO,KAAK;AAAA,QAAE,SAAS;AAAA,QAAK,eAAe,EAAU;AAAA,OAAQ;AAAA;;AAGjE,SAAO;GAsBI,IAAA,CAAe,GAAyB,MAAyB;AAC5E,MAAI,CAAC,EAAU;AAEf,QAAM,IAAuB;AAAA,IAC3B,WAAW,KAAK,IAAA;AAAA,IAChB,MAAA;AAAA,IACA,QAAA;AAAA;AAEF,EAAA,EAAU,KAAK,CAAA,GAEX,EAAS,gBACX,QAAQ,IAAI,iBAAiB,CAAA,MAAU,CAAA,EAAA;GAiB9B,IAAA,MAA8C,GAW9C,IAAA,MAA4B;AACvC,EAAA,EAAU,SAAS;GAmBR,IAAA,OAAyC;AAAA,EACpD,SAAS;AAAA,EACT,SAAS,EAAE,GAAG,EAAA;AAAA,EACd,UAAU,CAAC,GAAG,CAAA;IAgBH,IAAA,MAAyB;AACpC,QAAM,IAAU,EAAA;AAChB,MAAI,EAAQ,WAAW,GAAG;AACxB,YAAQ,IAAI,mCAAA;AACZ;AAAA;AAGF,UAAQ,MAAM,CAAA;GAYH,IAAA,MAAwB;AACnC,QAAM,IAAS,EAAA;AACf,MAAI,EAAO,WAAW,GAAG;AACvB,YAAQ,IAAI,qCAAA;AACZ;AAAA;AAGF,UAAQ,MAAM,EAAO,IAAA,CAAK,OAAO;AAAA,IAAE,IAAI,EAAE;AAAA,IAAI,OAAO,KAAK,UAAU,EAAE,KAAA;AAAA,IAAQ,CAAE;GAYpE,IAAA,MAA4B;AACvC,QAAM,IAAa,EAAA;AACnB,MAAI,EAAW,WAAW,GAAG;AAC3B,YAAQ,IAAI,yCAAA;AACZ;AAAA;AAGF,UAAQ,MAAM,CAAA;GAcH,IAAA,CAAe,MAAwB;AAClD,QAAM,IAAU,OAAO,KAAS,YAAY,IAAO,IAAI,EAAU,MAAM,CAAC,CAAA,IAAQ;AAChF,MAAI,EAAQ,WAAW,GAAG;AACxB,YAAQ,IAAI,kCAAA;AACZ;AAAA;AAGF,UAAQ,MACN,EAAQ,IAAA,CAAK,OAAO;AAAA,IAClB,MAAM,IAAI,KAAK,EAAE,SAAA,EAAW,YAAA;AAAA,IAC5B,MAAM,EAAE;AAAA,IACR,QAAQ,EAAE;AAAA,IACX,CAAE"}
1
+ {"version":3,"file":"devtools-QosAqo0T.js","names":[],"sources":["../src/devtools/devtools.ts"],"sourcesContent":["/**\n * bQuery DevTools — runtime debugging utilities.\n *\n * Enable devtools to inspect signals, stores, components, and view a\n * timeline of reactive events.\n *\n * @module bquery/devtools\n */\n\nimport type {\n ComponentSnapshot,\n DevtoolsOptions,\n DevtoolsState,\n SignalSnapshot,\n StoreSnapshot,\n TimelineEntry,\n TimelineEventType,\n} from './types';\nimport { listStores as _listStores, getStore as _getStore } from '../store/registry';\n\n// ---------------------------------------------------------------------------\n// Internal state\n// ---------------------------------------------------------------------------\n\nlet _enabled = false;\nlet _options: DevtoolsOptions = {};\nconst _timeline: TimelineEntry[] = [];\n\n/** Registered signals keyed by label. */\nconst _trackedSignals = new Map<string, { peek: () => unknown; subscriberCount: () => number }>();\n\n/** Monotonic counter for auto-labelling anonymous signals. */\nlet _signalCounter = 0;\n\n// ---------------------------------------------------------------------------\n// Enable / disable\n// ---------------------------------------------------------------------------\n\n/**\n * Enable bQuery development mode.\n *\n * When enabled the devtools module records timeline events and\n * allows inspection of signals, stores, and components.\n *\n * @param enabled - `true` to enable, `false` to disable.\n * @param options - Optional configuration.\n *\n * @example\n * ```ts\n * import { enableDevtools } from '@bquery/bquery/devtools';\n * enableDevtools(true, { logToConsole: true });\n * ```\n */\nexport const enableDevtools = (enabled: boolean, options?: DevtoolsOptions): void => {\n _enabled = enabled;\n _options = options ?? {};\n\n if (!enabled) {\n _timeline.length = 0;\n _trackedSignals.clear();\n _signalCounter = 0;\n }\n};\n\n/**\n * Returns `true` when devtools are active.\n *\n * @example\n * ```ts\n * import { isDevtoolsEnabled } from '@bquery/bquery/devtools';\n * if (isDevtoolsEnabled()) { ... }\n * ```\n */\nexport const isDevtoolsEnabled = (): boolean => _enabled;\n\n// ---------------------------------------------------------------------------\n// Signal tracking\n// ---------------------------------------------------------------------------\n\n/**\n * Register a signal for devtools inspection.\n *\n * @param label - Human-readable label (must be unique).\n * @param peek - Returns the signal's value without tracking.\n * @param subscriberCount - Returns the current subscriber count.\n * @throws If a signal with the same label is already tracked.\n *\n * @example\n * ```ts\n * import { trackSignal } from '@bquery/bquery/devtools';\n * import { signal } from '@bquery/bquery/reactive';\n *\n * const count = signal(0);\n * trackSignal('count', () => count.peek(), () => 0);\n * ```\n */\nexport const trackSignal = (\n label: string,\n peek: () => unknown,\n subscriberCount: () => number\n): void => {\n if (!_enabled) return;\n if (typeof label !== 'string' || label.length === 0) {\n throw new Error('bQuery devtools: trackSignal() requires a non-empty label');\n }\n _trackedSignals.set(label, { peek, subscriberCount });\n};\n\n/**\n * Remove a previously tracked signal.\n *\n * @param label - The label used during registration.\n *\n * @example\n * ```ts\n * import { untrackSignal } from '@bquery/bquery/devtools';\n * untrackSignal('count');\n * ```\n */\nexport const untrackSignal = (label: string): void => {\n _trackedSignals.delete(label);\n};\n\n/**\n * Generate a unique label for anonymous signals.\n *\n * @returns A label such as `signal_0`, `signal_1`, …\n *\n * @example\n * ```ts\n * import { generateSignalLabel } from '@bquery/bquery/devtools';\n * const label = generateSignalLabel(); // 'signal_0'\n * ```\n */\nexport const generateSignalLabel = (): string => `signal_${_signalCounter++}`;\n\n// ---------------------------------------------------------------------------\n// Signal inspector\n// ---------------------------------------------------------------------------\n\n/**\n * List all tracked signals with their current values.\n *\n * @returns An array of {@link SignalSnapshot} objects.\n *\n * @example\n * ```ts\n * import { inspectSignals } from '@bquery/bquery/devtools';\n * console.table(inspectSignals());\n * ```\n */\nexport const inspectSignals = (): SignalSnapshot[] => {\n const result: SignalSnapshot[] = [];\n for (const [label, entry] of _trackedSignals) {\n result.push({\n label,\n value: entry.peek(),\n subscriberCount: entry.subscriberCount(),\n });\n }\n return result;\n};\n\n// ---------------------------------------------------------------------------\n// Store inspector\n// ---------------------------------------------------------------------------\n\n/**\n * List all stores with their current state.\n *\n * Reads from the store registry (`listStores` / `getStore`) that is\n * already maintained by the store module.\n *\n * @returns An array of {@link StoreSnapshot} objects.\n *\n * @example\n * ```ts\n * import { inspectStores } from '@bquery/bquery/devtools';\n * console.table(inspectStores());\n * ```\n */\nexport const inspectStores = (): StoreSnapshot[] => {\n try {\n const ids: string[] = _listStores();\n return ids.map((id: string) => {\n const store = _getStore(id) as Record<string, unknown> | undefined;\n const state: Record<string, unknown> = {};\n if (store && typeof store === 'object' && '$state' in store) {\n const raw = (store as { $state: unknown }).$state;\n if (raw && typeof raw === 'object') {\n Object.assign(state, raw);\n }\n }\n return { id, state };\n });\n } catch {\n return [];\n }\n};\n\n// ---------------------------------------------------------------------------\n// Component inspector\n// ---------------------------------------------------------------------------\n\n/**\n * List all bQuery-registered custom elements visible in the document.\n *\n * @returns An array of {@link ComponentSnapshot} objects.\n *\n * @example\n * ```ts\n * import { inspectComponents } from '@bquery/bquery/devtools';\n * console.table(inspectComponents());\n * ```\n */\nexport const inspectComponents = (): ComponentSnapshot[] => {\n if (typeof document === 'undefined') return [];\n\n const result: ComponentSnapshot[] = [];\n const seen = new Set<string>();\n\n // Walk every custom element in the DOM (they must contain a hyphen)\n const all = document.querySelectorAll('*');\n for (let i = 0; i < all.length; i++) {\n const el = all[i];\n const tag = el.tagName.toLowerCase();\n if (!tag.includes('-')) continue;\n if (seen.has(tag)) continue;\n\n // Only include if registered via customElements\n if (typeof customElements !== 'undefined' && customElements.get(tag)) {\n seen.add(tag);\n const instances = document.querySelectorAll(tag);\n result.push({ tagName: tag, instanceCount: instances.length });\n }\n }\n return result;\n};\n\n// ---------------------------------------------------------------------------\n// Timeline\n// ---------------------------------------------------------------------------\n\n/**\n * Record a timeline event.\n *\n * This is called internally by bQuery modules (signals, store, router)\n * or can be called from user code / plugins to add custom entries.\n *\n * @param type - The event category.\n * @param detail - A human-readable description.\n *\n * @example\n * ```ts\n * import { recordEvent } from '@bquery/bquery/devtools';\n * recordEvent('signal:update', 'count changed to 5');\n * ```\n */\nexport const recordEvent = (type: TimelineEventType, detail: string): void => {\n if (!_enabled) return;\n\n const entry: TimelineEntry = {\n timestamp: Date.now(),\n type,\n detail,\n };\n _timeline.push(entry);\n\n if (_options.logToConsole) {\n console.log(`[bq:devtools] ${type} — ${detail}`);\n }\n};\n\n/**\n * Returns the full timeline log.\n *\n * @returns A read-only array of {@link TimelineEntry} objects.\n *\n * @example\n * ```ts\n * import { getTimeline } from '@bquery/bquery/devtools';\n * for (const e of getTimeline()) {\n * console.log(e.type, e.detail);\n * }\n * ```\n */\nexport const getTimeline = (): readonly TimelineEntry[] => _timeline;\n\n/**\n * Clear all timeline entries.\n *\n * @example\n * ```ts\n * import { clearTimeline } from '@bquery/bquery/devtools';\n * clearTimeline();\n * ```\n */\nexport const clearTimeline = (): void => {\n _timeline.length = 0;\n};\n\n// ---------------------------------------------------------------------------\n// Combined state\n// ---------------------------------------------------------------------------\n\n/**\n * Returns a snapshot of the full devtools state.\n *\n * @returns A {@link DevtoolsState} object.\n *\n * @example\n * ```ts\n * import { getDevtoolsState } from '@bquery/bquery/devtools';\n * const state = getDevtoolsState();\n * console.log(state.enabled, state.timeline.length);\n * ```\n */\nexport const getDevtoolsState = (): DevtoolsState => ({\n enabled: _enabled,\n options: { ..._options },\n timeline: [..._timeline],\n});\n\n// ---------------------------------------------------------------------------\n// Log helpers (convenience for interactive debugging)\n// ---------------------------------------------------------------------------\n\n/**\n * Pretty-print all tracked signals to the console.\n *\n * @example\n * ```ts\n * import { logSignals } from '@bquery/bquery/devtools';\n * logSignals(); // → table output\n * ```\n */\nexport const logSignals = (): void => {\n const signals = inspectSignals();\n if (signals.length === 0) {\n console.log('[bq:devtools] No tracked signals.');\n return;\n }\n\n console.table(signals);\n};\n\n/**\n * Pretty-print all stores to the console.\n *\n * @example\n * ```ts\n * import { logStores } from '@bquery/bquery/devtools';\n * logStores(); // → table output\n * ```\n */\nexport const logStores = (): void => {\n const stores = inspectStores();\n if (stores.length === 0) {\n console.log('[bq:devtools] No stores registered.');\n return;\n }\n\n console.table(stores.map((s) => ({ id: s.id, state: JSON.stringify(s.state) })));\n};\n\n/**\n * Pretty-print all registered components to the console.\n *\n * @example\n * ```ts\n * import { logComponents } from '@bquery/bquery/devtools';\n * logComponents(); // → table output\n * ```\n */\nexport const logComponents = (): void => {\n const components = inspectComponents();\n if (components.length === 0) {\n console.log('[bq:devtools] No custom elements found.');\n return;\n }\n\n console.table(components);\n};\n\n/**\n * Pretty-print the timeline to the console.\n *\n * @param last - Only show the last N entries (default: all).\n *\n * @example\n * ```ts\n * import { logTimeline } from '@bquery/bquery/devtools';\n * logTimeline(10);\n * ```\n */\nexport const logTimeline = (last?: number): void => {\n const entries = typeof last === 'number' && last > 0 ? _timeline.slice(-last) : _timeline;\n if (entries.length === 0) {\n console.log('[bq:devtools] Timeline is empty.');\n return;\n }\n\n console.table(\n entries.map((e) => ({\n time: new Date(e.timestamp).toISOString(),\n type: e.type,\n detail: e.detail,\n }))\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"dnd-d2OU4len.js","names":[],"sources":["../src/dnd/draggable.ts","../src/dnd/droppable.ts","../src/dnd/sortable.ts"],"sourcesContent":["/**\n * Make an element draggable using pointer events.\n *\n * Uses Pointer Events (not HTML5 Drag & Drop) for reliable\n * cross-platform behavior including touch support.\n *\n * @module bquery/dnd\n */\n\nimport type {\n BoundsRect,\n DragBounds,\n DragEventData,\n DragPosition,\n DraggableHandle,\n DraggableOptions,\n} from './types';\n\n/** Global registry of active draggable elements for drop zone detection. */\nconst activeDrags = new Map<HTMLElement, { element: HTMLElement; position: DragPosition }>();\n\n/**\n * Returns the currently active drag state, if any.\n * Used internally by `droppable()` to detect drag interactions.\n * @internal\n */\nexport const getActiveDrag = (): { element: HTMLElement; position: DragPosition } | undefined => {\n const entries = Array.from(activeDrags.values());\n return entries[entries.length - 1];\n};\n\n/**\n * Resolves a `DragBounds` value to an absolute `BoundsRect`.\n * @internal\n */\nconst resolveBounds = (el: HTMLElement, bounds: DragBounds): BoundsRect | null => {\n if (typeof bounds === 'object') {\n return bounds;\n }\n\n let target: HTMLElement | null = null;\n\n if (bounds === 'parent') {\n target = el.parentElement;\n } else {\n target = document.querySelector(bounds) as HTMLElement | null;\n }\n\n if (!target) return null;\n\n const rect = target.getBoundingClientRect();\n const elRect = el.getBoundingClientRect();\n const rawLeft = parseFloat(el.style.left || '0');\n const rawTop = parseFloat(el.style.top || '0');\n const leftOffset = Number.isNaN(rawLeft) ? 0 : rawLeft;\n const topOffset = Number.isNaN(rawTop) ? 0 : rawTop;\n\n return {\n left: rect.left - elRect.left + leftOffset,\n top: rect.top - elRect.top + topOffset,\n right: rect.right - elRect.right + leftOffset + (rect.width - elRect.width),\n bottom: rect.bottom - elRect.bottom + topOffset + (rect.height - elRect.height),\n };\n};\n\n/**\n * Clamp a position within bounds.\n * @internal\n */\nconst clampPosition = (pos: DragPosition, bounds: BoundsRect | null): DragPosition => {\n if (!bounds) return pos;\n return {\n x: Math.max(bounds.left, Math.min(bounds.right, pos.x)),\n y: Math.max(bounds.top, Math.min(bounds.bottom, pos.y)),\n };\n};\n\n/**\n * Makes an element draggable using pointer events.\n *\n * Features:\n * - Touch and mouse support via Pointer Events\n * - Axis locking (`x`, `y`, or `both`)\n * - Bounds constraint (parent, selector, or explicit rect)\n * - Optional drag handle\n * - Ghost/clone preview during drag\n * - Callbacks: `onDragStart`, `onDrag`, `onDragEnd`\n *\n * @param el - The element to make draggable\n * @param options - Configuration options\n * @returns A handle with `destroy()`, `disable()`, and `enable()` methods\n *\n * @example\n * ```ts\n * import { draggable } from '@bquery/bquery/dnd';\n *\n * const handle = draggable(document.querySelector('#box'), {\n * axis: 'both',\n * bounds: 'parent',\n * onDragEnd: ({ position }) => {\n * console.log('Dropped at', position.x, position.y);\n * },\n * });\n *\n * // Later:\n * handle.destroy();\n * ```\n */\nexport const draggable = (el: HTMLElement, options: DraggableOptions = {}): DraggableHandle => {\n const {\n axis = 'both',\n bounds,\n handle,\n ghost = false,\n ghostClass = 'bq-drag-ghost',\n draggingClass = 'bq-dragging',\n onDragStart,\n onDrag,\n onDragEnd,\n } = options;\n\n let enabled = !options.disabled;\n let isDragging = false;\n let startPointer: DragPosition = { x: 0, y: 0 };\n let currentPosition: DragPosition = { x: 0, y: 0 };\n let previousPosition: DragPosition = { x: 0, y: 0 };\n let ghostEl: HTMLElement | null = null;\n let ghostStartPosition: DragPosition | null = null;\n const previousTouchAction = el.style.touchAction;\n const previousUserSelect = el.style.userSelect;\n\n const createEventData = (event: PointerEvent): DragEventData => ({\n element: el,\n position: { ...currentPosition },\n delta: {\n x: currentPosition.x - previousPosition.x,\n y: currentPosition.y - previousPosition.y,\n },\n event,\n });\n\n const createGhost = (): HTMLElement => {\n const clone = el.cloneNode(true) as HTMLElement;\n const rect = el.getBoundingClientRect();\n clone.classList.add(ghostClass);\n clone.style.position = 'fixed';\n clone.style.left = `${rect.left}px`;\n clone.style.top = `${rect.top}px`;\n clone.style.width = `${rect.width}px`;\n clone.style.height = `${rect.height}px`;\n clone.style.pointerEvents = 'none';\n clone.style.zIndex = '999999';\n clone.style.opacity = '0.7';\n clone.style.margin = '0';\n document.body.appendChild(clone);\n return clone;\n };\n\n const removeGhost = (): void => {\n if (ghostEl) {\n ghostEl.remove();\n ghostEl = null;\n }\n ghostStartPosition = null;\n };\n\n const onPointerDown = (e: PointerEvent): void => {\n if (!enabled) return;\n\n // Check handle constraint\n if (handle) {\n const target = e.target as Element;\n if (!target.closest(handle)) return;\n }\n\n e.preventDefault();\n isDragging = true;\n startPointer = { x: e.clientX, y: e.clientY };\n previousPosition = { ...currentPosition };\n\n el.classList.add(draggingClass);\n el.setPointerCapture(e.pointerId);\n\n if (ghost) {\n const rect = el.getBoundingClientRect();\n ghostStartPosition = { x: rect.left, y: rect.top };\n ghostEl = createGhost();\n }\n\n // Register in global active drags\n activeDrags.set(el, { element: el, position: currentPosition });\n\n onDragStart?.(createEventData(e));\n };\n\n const onPointerMove = (e: PointerEvent): void => {\n if (!isDragging) return;\n\n e.preventDefault();\n previousPosition = { ...currentPosition };\n\n let newX = currentPosition.x + (e.clientX - startPointer.x);\n let newY = currentPosition.y + (e.clientY - startPointer.y);\n\n // Reset start pointer to current for delta calculation\n startPointer = { x: e.clientX, y: e.clientY };\n\n // Apply axis constraint\n if (axis === 'x') newY = currentPosition.y;\n if (axis === 'y') newX = currentPosition.x;\n\n let newPos: DragPosition = { x: newX, y: newY };\n\n // Apply bounds constraint\n if (bounds) {\n const resolvedBounds = resolveBounds(el, bounds);\n newPos = clampPosition(newPos, resolvedBounds);\n }\n\n currentPosition = newPos;\n\n // Update active drag position\n activeDrags.set(el, { element: el, position: currentPosition });\n\n // Apply the position\n if (ghost && ghostEl) {\n const start = ghostStartPosition ?? {\n x: el.getBoundingClientRect().left,\n y: el.getBoundingClientRect().top,\n };\n ghostEl.style.left = `${start.x + currentPosition.x}px`;\n ghostEl.style.top = `${start.y + currentPosition.y}px`;\n } else {\n el.style.transform = `translate(${currentPosition.x}px, ${currentPosition.y}px)`;\n }\n\n onDrag?.(createEventData(e));\n };\n\n const onPointerUp = (e: PointerEvent): void => {\n if (!isDragging) return;\n\n isDragging = false;\n el.classList.remove(draggingClass);\n try {\n if (\n typeof el.releasePointerCapture === 'function' &&\n (typeof el.hasPointerCapture !== 'function' || el.hasPointerCapture(e.pointerId))\n ) {\n el.releasePointerCapture(e.pointerId);\n }\n } catch {\n // Pointer capture may already be released in some interrupted drag flows.\n } finally {\n removeGhost();\n\n // Remove from active drags\n activeDrags.delete(el);\n\n onDragEnd?.(createEventData(e));\n }\n };\n\n // Attach listeners\n el.addEventListener('pointerdown', onPointerDown);\n el.addEventListener('pointermove', onPointerMove);\n el.addEventListener('pointerup', onPointerUp);\n el.addEventListener('pointercancel', onPointerUp);\n\n // Prevent default drag behavior\n el.style.touchAction = 'none';\n el.style.userSelect = 'none';\n\n return {\n destroy: () => {\n el.removeEventListener('pointerdown', onPointerDown);\n el.removeEventListener('pointermove', onPointerMove);\n el.removeEventListener('pointerup', onPointerUp);\n el.removeEventListener('pointercancel', onPointerUp);\n removeGhost();\n activeDrags.delete(el);\n el.style.touchAction = previousTouchAction;\n el.style.userSelect = previousUserSelect;\n el.classList.remove(draggingClass);\n },\n disable: () => {\n enabled = false;\n },\n enable: () => {\n enabled = true;\n },\n get enabled() {\n return enabled;\n },\n };\n};\n","/**\n * Define drop zones for draggable elements.\n *\n * Drop zones detect when draggable elements enter, move over,\n * leave, or are dropped onto them using pointer event hit-testing.\n *\n * @module bquery/dnd\n */\n\nimport { getActiveDrag } from './draggable';\nimport type { DropEventData, DroppableHandle, DroppableOptions } from './types';\n\ntype DroppableListener = {\n handlePointerMove: (event: PointerEvent) => void;\n handlePointerUp: (event: PointerEvent) => void;\n};\n\nconst passivePointerMoveListenerOptions = { passive: true } as const;\n\nconst droppableListeners = new Set<DroppableListener>();\nlet queuedPointerMove: PointerEvent | null = null;\nlet pointerMoveFrame: number | null = null;\n\nconst getDroppableListenersSnapshot = (): DroppableListener[] => Array.from(droppableListeners);\n\nconst hasDroppableEnvironment = (): boolean => {\n return (\n typeof document !== 'undefined' &&\n typeof document.addEventListener === 'function' &&\n typeof document.removeEventListener === 'function' &&\n typeof requestAnimationFrame === 'function' &&\n typeof cancelAnimationFrame === 'function'\n );\n};\n\nconst dispatchPointerMove = (event: PointerEvent): void => {\n for (const listener of getDroppableListenersSnapshot()) {\n listener.handlePointerMove(event);\n }\n};\n\nconst flushPointerMove = (): void => {\n pointerMoveFrame = null;\n const event = queuedPointerMove;\n queuedPointerMove = null;\n if (!event) return;\n dispatchPointerMove(event);\n};\n\nconst handleDocumentPointerMove = (event: PointerEvent): void => {\n queuedPointerMove = event;\n if (pointerMoveFrame === null) {\n pointerMoveFrame = requestAnimationFrame(flushPointerMove);\n }\n};\n\nconst handleDocumentPointerUp = (event: PointerEvent): void => {\n if (pointerMoveFrame !== null) {\n cancelAnimationFrame(pointerMoveFrame);\n pointerMoveFrame = null;\n queuedPointerMove = null;\n }\n\n for (const listener of getDroppableListenersSnapshot()) {\n listener.handlePointerUp(event);\n }\n};\n\nconst registerDroppableListener = (listener: DroppableListener): void => {\n if (droppableListeners.size === 0) {\n document.addEventListener(\n 'pointermove',\n handleDocumentPointerMove,\n passivePointerMoveListenerOptions\n );\n document.addEventListener('pointerup', handleDocumentPointerUp);\n }\n\n droppableListeners.add(listener);\n};\n\nconst unregisterDroppableListener = (listener: DroppableListener): void => {\n droppableListeners.delete(listener);\n\n if (droppableListeners.size !== 0) return;\n\n document.removeEventListener('pointermove', handleDocumentPointerMove);\n document.removeEventListener('pointerup', handleDocumentPointerUp);\n if (pointerMoveFrame !== null) {\n cancelAnimationFrame(pointerMoveFrame);\n pointerMoveFrame = null;\n }\n queuedPointerMove = null;\n};\n\n/**\n * Checks whether a dragged element is accepted by the drop zone.\n * @internal\n */\nconst isAccepted = (dragged: HTMLElement, accept: DroppableOptions['accept']): boolean => {\n if (!accept) return true;\n if (typeof accept === 'string') return dragged.matches(accept);\n return accept(dragged);\n};\n\n/**\n * Defines an element as a drop zone.\n *\n * Drop zones respond to draggable elements being moved over them\n * by firing callbacks and applying CSS classes. They work with\n * the `draggable()` function from this module.\n *\n * @param el - The drop zone element\n * @param options - Configuration options\n * @returns A handle with a `destroy()` method\n *\n * @example\n * ```ts\n * import { droppable } from '@bquery/bquery/dnd';\n *\n * const handle = droppable(document.querySelector('#dropzone'), {\n * accept: '.draggable-item',\n * overClass: 'drop-active',\n * onDrop: ({ dragged }) => {\n * console.log('Dropped:', dragged);\n * },\n * });\n *\n * // Later:\n * handle.destroy();\n * ```\n */\nexport const droppable = (el: HTMLElement, options: DroppableOptions = {}): DroppableHandle => {\n const {\n overClass = 'bq-drop-over',\n accept,\n onDragEnter,\n onDragOver,\n onDragLeave,\n onDrop,\n } = options;\n\n if (!hasDroppableEnvironment()) {\n return {\n destroy: () => {},\n };\n }\n\n let isOver = false;\n let currentDragged: HTMLElement | null = null;\n\n const createEventData = (dragged: HTMLElement, event: PointerEvent): DropEventData => ({\n zone: el,\n dragged,\n event,\n });\n\n const isPointerInside = (event: PointerEvent): boolean => {\n const rect = el.getBoundingClientRect();\n return (\n event.clientX >= rect.left &&\n event.clientX <= rect.right &&\n event.clientY >= rect.top &&\n event.clientY <= rect.bottom\n );\n };\n\n const resolveDraggedElement = (): HTMLElement | null => {\n return getActiveDrag()?.element ?? currentDragged;\n };\n\n const clearOverState = (event: PointerEvent, dragged = currentDragged): void => {\n if (!isOver) return;\n isOver = false;\n el.classList.remove(overClass);\n if (dragged) {\n onDragLeave?.(createEventData(dragged, event));\n }\n currentDragged = null;\n };\n\n const handlePointerMove = (e: PointerEvent): void => {\n const dragged = getActiveDrag()?.element ?? null;\n const isInside = isPointerInside(e);\n const acceptsDragged = dragged !== null && dragged !== el && isAccepted(dragged, accept);\n\n if (!acceptsDragged || !isInside) {\n clearOverState(e, dragged ?? currentDragged);\n return;\n }\n\n if (!isOver) {\n isOver = true;\n currentDragged = dragged;\n el.classList.add(overClass);\n onDragEnter?.(createEventData(dragged, e));\n } else {\n onDragOver?.(createEventData(dragged, e));\n }\n };\n\n const handlePointerUp = (e: PointerEvent): void => {\n const dragged = resolveDraggedElement();\n const isInside = isPointerInside(e);\n const acceptsDragged = dragged !== null && dragged !== el && isAccepted(dragged, accept);\n\n if (isInside && acceptsDragged && dragged) {\n onDrop?.(createEventData(dragged, e));\n }\n\n if (isOver) {\n isOver = false;\n el.classList.remove(overClass);\n }\n currentDragged = null;\n };\n\n const listener: DroppableListener = { handlePointerMove, handlePointerUp };\n registerDroppableListener(listener);\n\n return {\n destroy: () => {\n unregisterDroppableListener(listener);\n el.classList.remove(overClass);\n currentDragged = null;\n },\n };\n};\n","/**\n * Sortable list with animated reordering via pointer events.\n *\n * Makes children of a container sortable by dragging. Items are\n * rearranged in the DOM with optional CSS animation.\n *\n * @module bquery/dnd\n */\n\nimport type { SortEventData, SortableHandle, SortableOptions } from './types';\n\n/**\n * Gets the sortable items within a container.\n * @internal\n */\nconst getItems = (container: HTMLElement, selector: string): HTMLElement[] => {\n return Array.from(container.querySelectorAll(selector)) as HTMLElement[];\n};\n\n/**\n * Finds the closest sortable item to a given Y (or X) position.\n * @internal\n */\nconst getClosestItem = (\n items: HTMLElement[],\n clientPos: number,\n axis: 'x' | 'y',\n dragged: HTMLElement\n): { element: HTMLElement; index: number } | null => {\n let closest: { element: HTMLElement; index: number; distance: number } | null = null;\n\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n if (item === dragged) continue;\n\n const rect = item.getBoundingClientRect();\n const mid = axis === 'y' ? rect.top + rect.height / 2 : rect.left + rect.width / 2;\n const distance = clientPos - mid;\n\n if (\n closest === null ||\n (distance < 0 && distance > closest.distance) ||\n (closest.distance >= 0 && distance < 0 && Math.abs(distance) < Math.abs(closest.distance))\n ) {\n // Find the item we're just before\n if (distance < 0) {\n closest = { element: item, index: i, distance };\n }\n }\n }\n\n return closest ? { element: closest.element, index: closest.index } : null;\n};\n\n/**\n * Makes the children of a container sortable by dragging.\n *\n * Features:\n * - Pointer event based (touch + mouse)\n * - Animated reordering with configurable duration\n * - Axis constraint (vertical or horizontal)\n * - Optional drag handle\n * - Placeholder element during sort\n * - Callbacks: `onSortStart`, `onSortMove`, `onSortEnd`\n *\n * @param container - The container element whose children will be sortable\n * @param options - Configuration options\n * @returns A handle with `destroy()`, `disable()`, and `enable()` methods\n *\n * @example\n * ```ts\n * import { sortable } from '@bquery/bquery/dnd';\n *\n * const handle = sortable(document.querySelector('#list'), {\n * items: 'li',\n * axis: 'y',\n * animationDuration: 200,\n * onSortEnd: ({ oldIndex, newIndex }) => {\n * console.log(`Moved from ${oldIndex} to ${newIndex}`);\n * },\n * });\n *\n * // Later:\n * handle.destroy();\n * ```\n */\nexport const sortable = (container: HTMLElement, options: SortableOptions = {}): SortableHandle => {\n const {\n items: itemSelector = ':scope > *',\n axis = 'y',\n handle,\n placeholderClass = 'bq-sort-placeholder',\n sortingClass = 'bq-sorting',\n animationDuration = 200,\n onSortStart,\n onSortMove,\n onSortEnd,\n } = options;\n\n let enabled = !options.disabled;\n let isDragging = false;\n let dragItem: HTMLElement | null = null;\n let placeholder: HTMLElement | null = null;\n let startIndex = -1;\n let startPointerY = 0;\n let startPointerX = 0;\n let itemStartTop = 0;\n let itemStartLeft = 0;\n\n const createEventData = (item: HTMLElement, oldIdx: number, newIdx: number): SortEventData => ({\n container,\n item,\n oldIndex: oldIdx,\n newIndex: newIdx,\n });\n\n const onPointerDown = (e: PointerEvent): void => {\n if (!enabled) return;\n\n const target = e.target as HTMLElement;\n\n // Find the item being dragged\n const items = getItems(container, itemSelector);\n let item: HTMLElement | null = null;\n\n for (const it of items) {\n if (it.contains(target)) {\n item = it;\n break;\n }\n }\n\n if (!item) return;\n\n // Check handle constraint\n if (handle && !target.closest(handle)) return;\n\n e.preventDefault();\n\n isDragging = true;\n dragItem = item;\n startIndex = items.indexOf(item);\n startPointerY = e.clientY;\n startPointerX = e.clientX;\n\n const rect = item.getBoundingClientRect();\n itemStartTop = rect.top;\n itemStartLeft = rect.left;\n\n // Create placeholder\n placeholder = document.createElement('div');\n placeholder.classList.add(placeholderClass);\n placeholder.style.width = `${rect.width}px`;\n placeholder.style.height = `${rect.height}px`;\n placeholder.style.boxSizing = 'border-box';\n\n // Style the dragged item\n item.classList.add(sortingClass);\n item.style.position = 'fixed';\n item.style.width = `${rect.width}px`;\n item.style.height = `${rect.height}px`;\n item.style.left = `${rect.left}px`;\n item.style.top = `${rect.top}px`;\n item.style.zIndex = '999999';\n item.style.pointerEvents = 'none';\n item.style.margin = '0';\n\n // Insert placeholder where the item was\n item.parentNode?.insertBefore(placeholder, item);\n\n container.setPointerCapture(e.pointerId);\n\n onSortStart?.(createEventData(item, startIndex, startIndex));\n };\n\n const onPointerMove = (e: PointerEvent): void => {\n if (!isDragging || !dragItem || !placeholder) return;\n\n e.preventDefault();\n\n const deltaX = e.clientX - startPointerX;\n const deltaY = e.clientY - startPointerY;\n\n // Move the dragged item\n if (axis === 'y') {\n dragItem.style.top = `${itemStartTop + deltaY}px`;\n } else {\n dragItem.style.left = `${itemStartLeft + deltaX}px`;\n }\n\n // Find the closest item to determine insertion point\n const items = getItems(container, itemSelector);\n const clientPos = axis === 'y' ? e.clientY : e.clientX;\n const closest = getClosestItem(items, clientPos, axis, dragItem);\n\n if (closest) {\n // Move placeholder before the closest element\n container.insertBefore(placeholder, closest.element);\n } else {\n // Append to end\n container.appendChild(placeholder);\n }\n\n const currentIndex = Array.from(container.children).indexOf(placeholder);\n onSortMove?.(createEventData(dragItem, startIndex, currentIndex));\n };\n\n const onPointerUp = (e: PointerEvent): void => {\n if (!isDragging || !dragItem || !placeholder) return;\n\n isDragging = false;\n const draggedItem = dragItem;\n\n // Get final index\n const newIndex = Array.from(container.children).indexOf(placeholder);\n\n // Animate the item back to the placeholder position\n const placeholderRect = placeholder.getBoundingClientRect();\n const itemRect = draggedItem.getBoundingClientRect();\n\n if (animationDuration > 0) {\n const deltaX = placeholderRect.left - itemRect.left;\n const deltaY = placeholderRect.top - itemRect.top;\n\n draggedItem.style.transition = `transform ${animationDuration}ms ease`;\n draggedItem.style.transform = `translate(${deltaX}px, ${deltaY}px)`;\n\n let finalized = false;\n let timeoutId: number | null = null;\n const finalize = (): void => {\n if (finalized) return;\n finalized = true;\n if (timeoutId !== null) {\n window.clearTimeout(timeoutId);\n timeoutId = null;\n }\n resetDragItem();\n onSortEnd?.(createEventData(draggedItem, startIndex, newIndex));\n };\n timeoutId = window.setTimeout(() => {\n finalize();\n }, animationDuration + 50);\n\n draggedItem.addEventListener('transitionend', finalize, { once: true });\n } else {\n resetDragItem();\n onSortEnd?.(createEventData(draggedItem, startIndex, newIndex));\n }\n\n container.releasePointerCapture(e.pointerId);\n };\n\n const resetDragItem = (): void => {\n if (!dragItem || !placeholder) return;\n\n // Insert the real item where the placeholder is\n placeholder.parentNode?.insertBefore(dragItem, placeholder);\n placeholder.remove();\n placeholder = null;\n\n // Reset styles\n dragItem.classList.remove(sortingClass);\n dragItem.style.position = '';\n dragItem.style.width = '';\n dragItem.style.height = '';\n dragItem.style.left = '';\n dragItem.style.top = '';\n dragItem.style.zIndex = '';\n dragItem.style.pointerEvents = '';\n dragItem.style.margin = '';\n dragItem.style.transition = '';\n dragItem.style.transform = '';\n\n dragItem = null;\n };\n\n container.addEventListener('pointerdown', onPointerDown);\n container.addEventListener('pointermove', onPointerMove);\n container.addEventListener('pointerup', onPointerUp);\n container.addEventListener('pointercancel', onPointerUp);\n\n // Prevent default touch behavior on container\n container.style.touchAction = 'none';\n\n return {\n destroy: () => {\n container.removeEventListener('pointerdown', onPointerDown);\n container.removeEventListener('pointermove', onPointerMove);\n container.removeEventListener('pointerup', onPointerUp);\n container.removeEventListener('pointercancel', onPointerUp);\n container.style.touchAction = '';\n\n if (isDragging) {\n resetDragItem();\n }\n },\n disable: () => {\n enabled = false;\n },\n enable: () => {\n enabled = true;\n },\n get enabled() {\n return enabled;\n },\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"dnd-d2OU4len.js","names":[],"sources":["../src/dnd/draggable.ts","../src/dnd/droppable.ts","../src/dnd/sortable.ts"],"sourcesContent":["/**\n * Make an element draggable using pointer events.\n *\n * Uses Pointer Events (not HTML5 Drag & Drop) for reliable\n * cross-platform behavior including touch support.\n *\n * @module bquery/dnd\n */\n\nimport type {\n BoundsRect,\n DragBounds,\n DragEventData,\n DragPosition,\n DraggableHandle,\n DraggableOptions,\n} from './types';\n\n/** Global registry of active draggable elements for drop zone detection. */\nconst activeDrags = new Map<HTMLElement, { element: HTMLElement; position: DragPosition }>();\n\n/**\n * Returns the currently active drag state, if any.\n * Used internally by `droppable()` to detect drag interactions.\n * @internal\n */\nexport const getActiveDrag = (): { element: HTMLElement; position: DragPosition } | undefined => {\n const entries = Array.from(activeDrags.values());\n return entries[entries.length - 1];\n};\n\n/**\n * Resolves a `DragBounds` value to an absolute `BoundsRect`.\n * @internal\n */\nconst resolveBounds = (el: HTMLElement, bounds: DragBounds): BoundsRect | null => {\n if (typeof bounds === 'object') {\n return bounds;\n }\n\n let target: HTMLElement | null = null;\n\n if (bounds === 'parent') {\n target = el.parentElement;\n } else {\n target = document.querySelector(bounds) as HTMLElement | null;\n }\n\n if (!target) return null;\n\n const rect = target.getBoundingClientRect();\n const elRect = el.getBoundingClientRect();\n const rawLeft = parseFloat(el.style.left || '0');\n const rawTop = parseFloat(el.style.top || '0');\n const leftOffset = Number.isNaN(rawLeft) ? 0 : rawLeft;\n const topOffset = Number.isNaN(rawTop) ? 0 : rawTop;\n\n return {\n left: rect.left - elRect.left + leftOffset,\n top: rect.top - elRect.top + topOffset,\n right: rect.right - elRect.right + leftOffset + (rect.width - elRect.width),\n bottom: rect.bottom - elRect.bottom + topOffset + (rect.height - elRect.height),\n };\n};\n\n/**\n * Clamp a position within bounds.\n * @internal\n */\nconst clampPosition = (pos: DragPosition, bounds: BoundsRect | null): DragPosition => {\n if (!bounds) return pos;\n return {\n x: Math.max(bounds.left, Math.min(bounds.right, pos.x)),\n y: Math.max(bounds.top, Math.min(bounds.bottom, pos.y)),\n };\n};\n\n/**\n * Makes an element draggable using pointer events.\n *\n * Features:\n * - Touch and mouse support via Pointer Events\n * - Axis locking (`x`, `y`, or `both`)\n * - Bounds constraint (parent, selector, or explicit rect)\n * - Optional drag handle\n * - Ghost/clone preview during drag\n * - Callbacks: `onDragStart`, `onDrag`, `onDragEnd`\n *\n * @param el - The element to make draggable\n * @param options - Configuration options\n * @returns A handle with `destroy()`, `disable()`, and `enable()` methods\n *\n * @example\n * ```ts\n * import { draggable } from '@bquery/bquery/dnd';\n *\n * const handle = draggable(document.querySelector('#box'), {\n * axis: 'both',\n * bounds: 'parent',\n * onDragEnd: ({ position }) => {\n * console.log('Dropped at', position.x, position.y);\n * },\n * });\n *\n * // Later:\n * handle.destroy();\n * ```\n */\nexport const draggable = (el: HTMLElement, options: DraggableOptions = {}): DraggableHandle => {\n const {\n axis = 'both',\n bounds,\n handle,\n ghost = false,\n ghostClass = 'bq-drag-ghost',\n draggingClass = 'bq-dragging',\n onDragStart,\n onDrag,\n onDragEnd,\n } = options;\n\n let enabled = !options.disabled;\n let isDragging = false;\n let startPointer: DragPosition = { x: 0, y: 0 };\n let currentPosition: DragPosition = { x: 0, y: 0 };\n let previousPosition: DragPosition = { x: 0, y: 0 };\n let ghostEl: HTMLElement | null = null;\n let ghostStartPosition: DragPosition | null = null;\n const previousTouchAction = el.style.touchAction;\n const previousUserSelect = el.style.userSelect;\n\n const createEventData = (event: PointerEvent): DragEventData => ({\n element: el,\n position: { ...currentPosition },\n delta: {\n x: currentPosition.x - previousPosition.x,\n y: currentPosition.y - previousPosition.y,\n },\n event,\n });\n\n const createGhost = (): HTMLElement => {\n const clone = el.cloneNode(true) as HTMLElement;\n const rect = el.getBoundingClientRect();\n clone.classList.add(ghostClass);\n clone.style.position = 'fixed';\n clone.style.left = `${rect.left}px`;\n clone.style.top = `${rect.top}px`;\n clone.style.width = `${rect.width}px`;\n clone.style.height = `${rect.height}px`;\n clone.style.pointerEvents = 'none';\n clone.style.zIndex = '999999';\n clone.style.opacity = '0.7';\n clone.style.margin = '0';\n document.body.appendChild(clone);\n return clone;\n };\n\n const removeGhost = (): void => {\n if (ghostEl) {\n ghostEl.remove();\n ghostEl = null;\n }\n ghostStartPosition = null;\n };\n\n const onPointerDown = (e: PointerEvent): void => {\n if (!enabled) return;\n\n // Check handle constraint\n if (handle) {\n const target = e.target as Element;\n if (!target.closest(handle)) return;\n }\n\n e.preventDefault();\n isDragging = true;\n startPointer = { x: e.clientX, y: e.clientY };\n previousPosition = { ...currentPosition };\n\n el.classList.add(draggingClass);\n el.setPointerCapture(e.pointerId);\n\n if (ghost) {\n const rect = el.getBoundingClientRect();\n ghostStartPosition = { x: rect.left, y: rect.top };\n ghostEl = createGhost();\n }\n\n // Register in global active drags\n activeDrags.set(el, { element: el, position: currentPosition });\n\n onDragStart?.(createEventData(e));\n };\n\n const onPointerMove = (e: PointerEvent): void => {\n if (!isDragging) return;\n\n e.preventDefault();\n previousPosition = { ...currentPosition };\n\n let newX = currentPosition.x + (e.clientX - startPointer.x);\n let newY = currentPosition.y + (e.clientY - startPointer.y);\n\n // Reset start pointer to current for delta calculation\n startPointer = { x: e.clientX, y: e.clientY };\n\n // Apply axis constraint\n if (axis === 'x') newY = currentPosition.y;\n if (axis === 'y') newX = currentPosition.x;\n\n let newPos: DragPosition = { x: newX, y: newY };\n\n // Apply bounds constraint\n if (bounds) {\n const resolvedBounds = resolveBounds(el, bounds);\n newPos = clampPosition(newPos, resolvedBounds);\n }\n\n currentPosition = newPos;\n\n // Update active drag position\n activeDrags.set(el, { element: el, position: currentPosition });\n\n // Apply the position\n if (ghost && ghostEl) {\n const start = ghostStartPosition ?? {\n x: el.getBoundingClientRect().left,\n y: el.getBoundingClientRect().top,\n };\n ghostEl.style.left = `${start.x + currentPosition.x}px`;\n ghostEl.style.top = `${start.y + currentPosition.y}px`;\n } else {\n el.style.transform = `translate(${currentPosition.x}px, ${currentPosition.y}px)`;\n }\n\n onDrag?.(createEventData(e));\n };\n\n const onPointerUp = (e: PointerEvent): void => {\n if (!isDragging) return;\n\n isDragging = false;\n el.classList.remove(draggingClass);\n try {\n if (\n typeof el.releasePointerCapture === 'function' &&\n (typeof el.hasPointerCapture !== 'function' || el.hasPointerCapture(e.pointerId))\n ) {\n el.releasePointerCapture(e.pointerId);\n }\n } catch {\n // Pointer capture may already be released in some interrupted drag flows.\n } finally {\n removeGhost();\n\n // Remove from active drags\n activeDrags.delete(el);\n\n onDragEnd?.(createEventData(e));\n }\n };\n\n // Attach listeners\n el.addEventListener('pointerdown', onPointerDown);\n el.addEventListener('pointermove', onPointerMove);\n el.addEventListener('pointerup', onPointerUp);\n el.addEventListener('pointercancel', onPointerUp);\n\n // Prevent default drag behavior\n el.style.touchAction = 'none';\n el.style.userSelect = 'none';\n\n return {\n destroy: () => {\n el.removeEventListener('pointerdown', onPointerDown);\n el.removeEventListener('pointermove', onPointerMove);\n el.removeEventListener('pointerup', onPointerUp);\n el.removeEventListener('pointercancel', onPointerUp);\n removeGhost();\n activeDrags.delete(el);\n el.style.touchAction = previousTouchAction;\n el.style.userSelect = previousUserSelect;\n el.classList.remove(draggingClass);\n },\n disable: () => {\n enabled = false;\n },\n enable: () => {\n enabled = true;\n },\n get enabled() {\n return enabled;\n },\n };\n};\n","/**\n * Define drop zones for draggable elements.\n *\n * Drop zones detect when draggable elements enter, move over,\n * leave, or are dropped onto them using pointer event hit-testing.\n *\n * @module bquery/dnd\n */\n\nimport { getActiveDrag } from './draggable';\nimport type { DropEventData, DroppableHandle, DroppableOptions } from './types';\n\ntype DroppableListener = {\n handlePointerMove: (event: PointerEvent) => void;\n handlePointerUp: (event: PointerEvent) => void;\n};\n\nconst passivePointerMoveListenerOptions = { passive: true } as const;\n\nconst droppableListeners = new Set<DroppableListener>();\nlet queuedPointerMove: PointerEvent | null = null;\nlet pointerMoveFrame: number | null = null;\n\nconst getDroppableListenersSnapshot = (): DroppableListener[] => Array.from(droppableListeners);\n\nconst hasDroppableEnvironment = (): boolean => {\n return (\n typeof document !== 'undefined' &&\n typeof document.addEventListener === 'function' &&\n typeof document.removeEventListener === 'function' &&\n typeof requestAnimationFrame === 'function' &&\n typeof cancelAnimationFrame === 'function'\n );\n};\n\nconst dispatchPointerMove = (event: PointerEvent): void => {\n for (const listener of getDroppableListenersSnapshot()) {\n listener.handlePointerMove(event);\n }\n};\n\nconst flushPointerMove = (): void => {\n pointerMoveFrame = null;\n const event = queuedPointerMove;\n queuedPointerMove = null;\n if (!event) return;\n dispatchPointerMove(event);\n};\n\nconst handleDocumentPointerMove = (event: PointerEvent): void => {\n queuedPointerMove = event;\n if (pointerMoveFrame === null) {\n pointerMoveFrame = requestAnimationFrame(flushPointerMove);\n }\n};\n\nconst handleDocumentPointerUp = (event: PointerEvent): void => {\n if (pointerMoveFrame !== null) {\n cancelAnimationFrame(pointerMoveFrame);\n pointerMoveFrame = null;\n queuedPointerMove = null;\n }\n\n for (const listener of getDroppableListenersSnapshot()) {\n listener.handlePointerUp(event);\n }\n};\n\nconst registerDroppableListener = (listener: DroppableListener): void => {\n if (droppableListeners.size === 0) {\n document.addEventListener(\n 'pointermove',\n handleDocumentPointerMove,\n passivePointerMoveListenerOptions\n );\n document.addEventListener('pointerup', handleDocumentPointerUp);\n }\n\n droppableListeners.add(listener);\n};\n\nconst unregisterDroppableListener = (listener: DroppableListener): void => {\n droppableListeners.delete(listener);\n\n if (droppableListeners.size !== 0) return;\n\n document.removeEventListener('pointermove', handleDocumentPointerMove);\n document.removeEventListener('pointerup', handleDocumentPointerUp);\n if (pointerMoveFrame !== null) {\n cancelAnimationFrame(pointerMoveFrame);\n pointerMoveFrame = null;\n }\n queuedPointerMove = null;\n};\n\n/**\n * Checks whether a dragged element is accepted by the drop zone.\n * @internal\n */\nconst isAccepted = (dragged: HTMLElement, accept: DroppableOptions['accept']): boolean => {\n if (!accept) return true;\n if (typeof accept === 'string') return dragged.matches(accept);\n return accept(dragged);\n};\n\n/**\n * Defines an element as a drop zone.\n *\n * Drop zones respond to draggable elements being moved over them\n * by firing callbacks and applying CSS classes. They work with\n * the `draggable()` function from this module.\n *\n * @param el - The drop zone element\n * @param options - Configuration options\n * @returns A handle with a `destroy()` method\n *\n * @example\n * ```ts\n * import { droppable } from '@bquery/bquery/dnd';\n *\n * const handle = droppable(document.querySelector('#dropzone'), {\n * accept: '.draggable-item',\n * overClass: 'drop-active',\n * onDrop: ({ dragged }) => {\n * console.log('Dropped:', dragged);\n * },\n * });\n *\n * // Later:\n * handle.destroy();\n * ```\n */\nexport const droppable = (el: HTMLElement, options: DroppableOptions = {}): DroppableHandle => {\n const {\n overClass = 'bq-drop-over',\n accept,\n onDragEnter,\n onDragOver,\n onDragLeave,\n onDrop,\n } = options;\n\n if (!hasDroppableEnvironment()) {\n return {\n destroy: () => {},\n };\n }\n\n let isOver = false;\n let currentDragged: HTMLElement | null = null;\n\n const createEventData = (dragged: HTMLElement, event: PointerEvent): DropEventData => ({\n zone: el,\n dragged,\n event,\n });\n\n const isPointerInside = (event: PointerEvent): boolean => {\n const rect = el.getBoundingClientRect();\n return (\n event.clientX >= rect.left &&\n event.clientX <= rect.right &&\n event.clientY >= rect.top &&\n event.clientY <= rect.bottom\n );\n };\n\n const resolveDraggedElement = (): HTMLElement | null => {\n return getActiveDrag()?.element ?? currentDragged;\n };\n\n const clearOverState = (event: PointerEvent, dragged = currentDragged): void => {\n if (!isOver) return;\n isOver = false;\n el.classList.remove(overClass);\n if (dragged) {\n onDragLeave?.(createEventData(dragged, event));\n }\n currentDragged = null;\n };\n\n const handlePointerMove = (e: PointerEvent): void => {\n const dragged = getActiveDrag()?.element ?? null;\n const isInside = isPointerInside(e);\n const acceptsDragged = dragged !== null && dragged !== el && isAccepted(dragged, accept);\n\n if (!acceptsDragged || !isInside) {\n clearOverState(e, dragged ?? currentDragged);\n return;\n }\n\n if (!isOver) {\n isOver = true;\n currentDragged = dragged;\n el.classList.add(overClass);\n onDragEnter?.(createEventData(dragged, e));\n } else {\n onDragOver?.(createEventData(dragged, e));\n }\n };\n\n const handlePointerUp = (e: PointerEvent): void => {\n const dragged = resolveDraggedElement();\n const isInside = isPointerInside(e);\n const acceptsDragged = dragged !== null && dragged !== el && isAccepted(dragged, accept);\n\n if (isInside && acceptsDragged && dragged) {\n onDrop?.(createEventData(dragged, e));\n }\n\n if (isOver) {\n isOver = false;\n el.classList.remove(overClass);\n }\n currentDragged = null;\n };\n\n const listener: DroppableListener = { handlePointerMove, handlePointerUp };\n registerDroppableListener(listener);\n\n return {\n destroy: () => {\n unregisterDroppableListener(listener);\n el.classList.remove(overClass);\n currentDragged = null;\n },\n };\n};\n","/**\n * Sortable list with animated reordering via pointer events.\n *\n * Makes children of a container sortable by dragging. Items are\n * rearranged in the DOM with optional CSS animation.\n *\n * @module bquery/dnd\n */\n\nimport type { SortEventData, SortableHandle, SortableOptions } from './types';\n\n/**\n * Gets the sortable items within a container.\n * @internal\n */\nconst getItems = (container: HTMLElement, selector: string): HTMLElement[] => {\n return Array.from(container.querySelectorAll(selector)) as HTMLElement[];\n};\n\n/**\n * Finds the closest sortable item to a given Y (or X) position.\n * @internal\n */\nconst getClosestItem = (\n items: HTMLElement[],\n clientPos: number,\n axis: 'x' | 'y',\n dragged: HTMLElement\n): { element: HTMLElement; index: number } | null => {\n let closest: { element: HTMLElement; index: number; distance: number } | null = null;\n\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n if (item === dragged) continue;\n\n const rect = item.getBoundingClientRect();\n const mid = axis === 'y' ? rect.top + rect.height / 2 : rect.left + rect.width / 2;\n const distance = clientPos - mid;\n\n if (\n closest === null ||\n (distance < 0 && distance > closest.distance) ||\n (closest.distance >= 0 && distance < 0 && Math.abs(distance) < Math.abs(closest.distance))\n ) {\n // Find the item we're just before\n if (distance < 0) {\n closest = { element: item, index: i, distance };\n }\n }\n }\n\n return closest ? { element: closest.element, index: closest.index } : null;\n};\n\n/**\n * Makes the children of a container sortable by dragging.\n *\n * Features:\n * - Pointer event based (touch + mouse)\n * - Animated reordering with configurable duration\n * - Axis constraint (vertical or horizontal)\n * - Optional drag handle\n * - Placeholder element during sort\n * - Callbacks: `onSortStart`, `onSortMove`, `onSortEnd`\n *\n * @param container - The container element whose children will be sortable\n * @param options - Configuration options\n * @returns A handle with `destroy()`, `disable()`, and `enable()` methods\n *\n * @example\n * ```ts\n * import { sortable } from '@bquery/bquery/dnd';\n *\n * const handle = sortable(document.querySelector('#list'), {\n * items: 'li',\n * axis: 'y',\n * animationDuration: 200,\n * onSortEnd: ({ oldIndex, newIndex }) => {\n * console.log(`Moved from ${oldIndex} to ${newIndex}`);\n * },\n * });\n *\n * // Later:\n * handle.destroy();\n * ```\n */\nexport const sortable = (container: HTMLElement, options: SortableOptions = {}): SortableHandle => {\n const {\n items: itemSelector = ':scope > *',\n axis = 'y',\n handle,\n placeholderClass = 'bq-sort-placeholder',\n sortingClass = 'bq-sorting',\n animationDuration = 200,\n onSortStart,\n onSortMove,\n onSortEnd,\n } = options;\n\n let enabled = !options.disabled;\n let isDragging = false;\n let dragItem: HTMLElement | null = null;\n let placeholder: HTMLElement | null = null;\n let startIndex = -1;\n let startPointerY = 0;\n let startPointerX = 0;\n let itemStartTop = 0;\n let itemStartLeft = 0;\n\n const createEventData = (item: HTMLElement, oldIdx: number, newIdx: number): SortEventData => ({\n container,\n item,\n oldIndex: oldIdx,\n newIndex: newIdx,\n });\n\n const onPointerDown = (e: PointerEvent): void => {\n if (!enabled) return;\n\n const target = e.target as HTMLElement;\n\n // Find the item being dragged\n const items = getItems(container, itemSelector);\n let item: HTMLElement | null = null;\n\n for (const it of items) {\n if (it.contains(target)) {\n item = it;\n break;\n }\n }\n\n if (!item) return;\n\n // Check handle constraint\n if (handle && !target.closest(handle)) return;\n\n e.preventDefault();\n\n isDragging = true;\n dragItem = item;\n startIndex = items.indexOf(item);\n startPointerY = e.clientY;\n startPointerX = e.clientX;\n\n const rect = item.getBoundingClientRect();\n itemStartTop = rect.top;\n itemStartLeft = rect.left;\n\n // Create placeholder\n placeholder = document.createElement('div');\n placeholder.classList.add(placeholderClass);\n placeholder.style.width = `${rect.width}px`;\n placeholder.style.height = `${rect.height}px`;\n placeholder.style.boxSizing = 'border-box';\n\n // Style the dragged item\n item.classList.add(sortingClass);\n item.style.position = 'fixed';\n item.style.width = `${rect.width}px`;\n item.style.height = `${rect.height}px`;\n item.style.left = `${rect.left}px`;\n item.style.top = `${rect.top}px`;\n item.style.zIndex = '999999';\n item.style.pointerEvents = 'none';\n item.style.margin = '0';\n\n // Insert placeholder where the item was\n item.parentNode?.insertBefore(placeholder, item);\n\n container.setPointerCapture(e.pointerId);\n\n onSortStart?.(createEventData(item, startIndex, startIndex));\n };\n\n const onPointerMove = (e: PointerEvent): void => {\n if (!isDragging || !dragItem || !placeholder) return;\n\n e.preventDefault();\n\n const deltaX = e.clientX - startPointerX;\n const deltaY = e.clientY - startPointerY;\n\n // Move the dragged item\n if (axis === 'y') {\n dragItem.style.top = `${itemStartTop + deltaY}px`;\n } else {\n dragItem.style.left = `${itemStartLeft + deltaX}px`;\n }\n\n // Find the closest item to determine insertion point\n const items = getItems(container, itemSelector);\n const clientPos = axis === 'y' ? e.clientY : e.clientX;\n const closest = getClosestItem(items, clientPos, axis, dragItem);\n\n if (closest) {\n // Move placeholder before the closest element\n container.insertBefore(placeholder, closest.element);\n } else {\n // Append to end\n container.appendChild(placeholder);\n }\n\n const currentIndex = Array.from(container.children).indexOf(placeholder);\n onSortMove?.(createEventData(dragItem, startIndex, currentIndex));\n };\n\n const onPointerUp = (e: PointerEvent): void => {\n if (!isDragging || !dragItem || !placeholder) return;\n\n isDragging = false;\n const draggedItem = dragItem;\n\n // Get final index\n const newIndex = Array.from(container.children).indexOf(placeholder);\n\n // Animate the item back to the placeholder position\n const placeholderRect = placeholder.getBoundingClientRect();\n const itemRect = draggedItem.getBoundingClientRect();\n\n if (animationDuration > 0) {\n const deltaX = placeholderRect.left - itemRect.left;\n const deltaY = placeholderRect.top - itemRect.top;\n\n draggedItem.style.transition = `transform ${animationDuration}ms ease`;\n draggedItem.style.transform = `translate(${deltaX}px, ${deltaY}px)`;\n\n let finalized = false;\n let timeoutId: number | null = null;\n const finalize = (): void => {\n if (finalized) return;\n finalized = true;\n if (timeoutId !== null) {\n window.clearTimeout(timeoutId);\n timeoutId = null;\n }\n resetDragItem();\n onSortEnd?.(createEventData(draggedItem, startIndex, newIndex));\n };\n timeoutId = window.setTimeout(() => {\n finalize();\n }, animationDuration + 50);\n\n draggedItem.addEventListener('transitionend', finalize, { once: true });\n } else {\n resetDragItem();\n onSortEnd?.(createEventData(draggedItem, startIndex, newIndex));\n }\n\n container.releasePointerCapture(e.pointerId);\n };\n\n const resetDragItem = (): void => {\n if (!dragItem || !placeholder) return;\n\n // Insert the real item where the placeholder is\n placeholder.parentNode?.insertBefore(dragItem, placeholder);\n placeholder.remove();\n placeholder = null;\n\n // Reset styles\n dragItem.classList.remove(sortingClass);\n dragItem.style.position = '';\n dragItem.style.width = '';\n dragItem.style.height = '';\n dragItem.style.left = '';\n dragItem.style.top = '';\n dragItem.style.zIndex = '';\n dragItem.style.pointerEvents = '';\n dragItem.style.margin = '';\n dragItem.style.transition = '';\n dragItem.style.transform = '';\n\n dragItem = null;\n };\n\n container.addEventListener('pointerdown', onPointerDown);\n container.addEventListener('pointermove', onPointerMove);\n container.addEventListener('pointerup', onPointerUp);\n container.addEventListener('pointercancel', onPointerUp);\n\n // Prevent default touch behavior on container\n container.style.touchAction = 'none';\n\n return {\n destroy: () => {\n container.removeEventListener('pointerdown', onPointerDown);\n container.removeEventListener('pointermove', onPointerMove);\n container.removeEventListener('pointerup', onPointerUp);\n container.removeEventListener('pointercancel', onPointerUp);\n container.style.touchAction = '';\n\n if (isDragging) {\n resetDragItem();\n }\n },\n disable: () => {\n enabled = false;\n },\n enable: () => {\n enabled = true;\n },\n get enabled() {\n return enabled;\n },\n };\n};\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { i as l, l as h } from "./core-CongXJuo.js";
1
+ import { i as l, l as h } from "./core-yg9rJXiR.js";
2
2
  var t = [], n = (e) => typeof e == "object" && e !== null && "_addDisposer" in e, y = (e) => (typeof e == "object" || typeof e == "function") && e !== null && typeof e.then == "function", v = (e) => {
3
3
  const r = typeof e.constructor == "function" ? e.constructor.name : void 0;
4
4
  return typeof e == "function" && (Symbol.toStringTag in e && e[Symbol.toStringTag] === "AsyncFunction" || r === "AsyncFunction");
@@ -84,4 +84,4 @@ export {
84
84
  D as t
85
85
  };
86
86
 
87
- //# sourceMappingURL=effect-Cc51IH91.js.map
87
+ //# sourceMappingURL=effect-v8OIEmPs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"effect-Cc51IH91.js","names":[],"sources":["../src/reactive/scope.ts","../src/reactive/effect.ts"],"sourcesContent":["/**\n * Reactive effect scopes for grouped disposal.\n *\n * An `EffectScope` collects all effects, computed values, and watches created\n * inside its `run()` callback so they can be disposed together with a single\n * `stop()` call. Scopes nest — an inner scope is collected by its parent.\n *\n * @module bquery/reactive\n */\n\nimport type { CleanupFn } from './internals';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A scope that collects reactive resources for grouped disposal.\n *\n * @example\n * ```ts\n * import { effectScope, signal, effect, computed } from '@bquery/bquery/reactive';\n *\n * const scope = effectScope();\n *\n * scope.run(() => {\n * const count = signal(0);\n * effect(() => console.log(count.value));\n * const doubled = computed(() => count.value * 2);\n * });\n *\n * scope.stop(); // All effects and computed values disposed\n * ```\n */\nexport interface EffectScope {\n /** Whether the scope has not yet been stopped. */\n readonly active: boolean;\n\n /**\n * Executes `fn` inside this scope, collecting any reactive resources\n * (effects, computed values, watches, nested scopes) created during the call.\n *\n * `run()` is synchronous-only. Do not pass an async function or a function\n * that returns a Promise — resources created after an `await` cannot be\n * collected reliably.\n *\n * @template T - Return type of the provided function\n * @param fn - Function to run inside the scope\n * @returns The return value of `fn`\n * @throws {Error} If the scope has already been stopped\n */\n run<T>(fn: () => T): T;\n\n /**\n * Disposes all collected resources and marks the scope as inactive.\n * Calling `stop()` on an already-stopped scope is a safe no-op.\n */\n stop(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal scope stack\n// ---------------------------------------------------------------------------\n\n/** @internal */\ninterface ScopeInternal extends EffectScope {\n /** @internal – Register a cleanup callback to run when the scope stops. */\n _addDisposer(fn: CleanupFn): void;\n}\n\nconst scopeStack: ScopeInternal[] = [];\n\n/** @internal */\nexport const hasScopeDisposer = (\n scope: EffectScope | undefined\n): scope is EffectScope & { _addDisposer(fn: CleanupFn): void } =>\n typeof scope === 'object' && scope !== null && '_addDisposer' in scope;\n\nconst isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>\n (typeof value === 'object' || typeof value === 'function') &&\n value !== null &&\n typeof (value as { then?: unknown }).then === 'function';\n\n/**\n * Best-effort detection for native async functions so `run()` can reject them\n * before invocation. Transpiled async functions may not preserve this shape, so\n * promise-like return values are still checked after execution as a fallback.\n * @internal\n */\nconst isAsyncFunction = (value: unknown): value is (...args: never[]) => Promise<unknown> => {\n const constructorName =\n typeof (value as { constructor?: unknown }).constructor === 'function'\n ? (value as { constructor: { name?: unknown } }).constructor.name\n : undefined;\n\n return (\n typeof value === 'function' &&\n ((Symbol.toStringTag in value &&\n (value as { [Symbol.toStringTag]?: unknown })[Symbol.toStringTag] === 'AsyncFunction') ||\n constructorName === 'AsyncFunction')\n );\n};\n\n/**\n * Returns the currently active scope, or `undefined` if none.\n * @internal\n */\nexport const getActiveScope = (): EffectScope | undefined => {\n for (let i = scopeStack.length - 1; i >= 0; i--) {\n if (scopeStack[i].active) {\n return scopeStack[i];\n }\n }\n\n return undefined;\n};\n\n// ---------------------------------------------------------------------------\n// EffectScope implementation\n// ---------------------------------------------------------------------------\n\nclass EffectScopeImpl implements ScopeInternal {\n private disposers: CleanupFn[] = [];\n private _active = true;\n\n get active(): boolean {\n return this._active;\n }\n\n /** @internal */\n _addDisposer(fn: CleanupFn): void {\n if (this._active) {\n this.disposers.push(fn);\n }\n }\n\n run<T>(fn: () => T): T {\n if (!this._active) {\n throw new Error('bQuery reactive: Cannot run in a stopped effectScope');\n }\n if (isAsyncFunction(fn)) {\n throw new Error('bQuery reactive: effectScope.run() only supports synchronous callbacks');\n }\n\n scopeStack.push(this);\n try {\n const result = fn();\n if (isPromiseLike(result)) {\n this.stop();\n throw new Error('bQuery reactive: effectScope.run() only supports synchronous callbacks');\n }\n return result;\n } finally {\n scopeStack.pop();\n }\n }\n\n stop(): void {\n if (!this._active) return;\n this._active = false;\n\n // Dispose in reverse order (LIFO) to mirror creation order\n for (let i = this.disposers.length - 1; i >= 0; i--) {\n try {\n this.disposers[i]();\n } catch (error) {\n console.error('bQuery reactive: Error in scope cleanup', error);\n }\n }\n this.disposers.length = 0;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a new effect scope for grouped disposal of reactive resources.\n *\n * All `effect()`, `computed()`, `watch()`, and nested `effectScope()` calls\n * made inside `scope.run(fn)` are automatically collected. Calling\n * `scope.stop()` disposes them all at once.\n *\n * `run()` is synchronous-only. Create the scope outside async flows when\n * needed, but keep the callback itself synchronous so cleanup registration\n * stays deterministic.\n *\n * @returns A new {@link EffectScope}\n *\n * @example\n * ```ts\n * import { effectScope, signal, effect, onScopeDispose } from '@bquery/bquery/reactive';\n *\n * const scope = effectScope();\n *\n * scope.run(() => {\n * const count = signal(0);\n *\n * effect(() => console.log(count.value));\n *\n * onScopeDispose(() => {\n * console.log('Custom cleanup');\n * });\n * });\n *\n * scope.stop(); // logs \"Custom cleanup\", all effects stopped\n * ```\n */\nexport const effectScope = (): EffectScope => {\n const scope = new EffectScopeImpl();\n\n // If created inside another scope, auto-collect as a nested scope\n const parent = getActiveScope();\n if (hasScopeDisposer(parent)) {\n parent._addDisposer(() => scope.stop());\n }\n\n return scope;\n};\n\n/**\n * Returns the currently active {@link EffectScope}, or `undefined` if\n * code is not running inside any scope's `run()` callback.\n *\n * @returns The active scope, or `undefined`\n *\n * @example\n * ```ts\n * import { effectScope, getCurrentScope } from '@bquery/bquery/reactive';\n *\n * const scope = effectScope();\n * scope.run(() => {\n * console.log(getCurrentScope() !== undefined); // true\n * });\n *\n * console.log(getCurrentScope()); // undefined\n * ```\n */\nexport const getCurrentScope = (): EffectScope | undefined => getActiveScope();\n\n/**\n * Registers a cleanup callback on the currently active scope.\n *\n * The callback runs when the scope is stopped. This is useful for\n * registering arbitrary cleanup (e.g. event listeners, timers)\n * alongside effects and computed values.\n *\n * @param fn - Cleanup function to run when the scope stops\n * @throws {Error} If called outside an active scope\n *\n * @example\n * ```ts\n * import { effectScope, onScopeDispose } from '@bquery/bquery/reactive';\n *\n * const scope = effectScope();\n *\n * scope.run(() => {\n * const controller = new AbortController();\n * fetch('/api/data', { signal: controller.signal });\n *\n * onScopeDispose(() => controller.abort());\n * });\n *\n * scope.stop(); // abort() is called\n * ```\n */\nexport const onScopeDispose = (fn: CleanupFn): void => {\n const scope = getActiveScope();\n if (!scope || !scope.active || !hasScopeDisposer(scope)) {\n throw new Error(\n 'bQuery reactive: onScopeDispose() must be called inside an active effectScope'\n );\n }\n scope._addDisposer(fn);\n};\n","/**\n * Reactive effects.\n */\n\nimport { CleanupFn, Observer, track, clearDependencies } from './internals';\nimport { getActiveScope, hasScopeDisposer } from './scope';\n\n/**\n * Creates a side effect that automatically re-runs when dependencies change.\n *\n * The effect runs immediately upon creation and then re-runs whenever\n * any signal or computed value read inside it changes.\n *\n * If created inside an {@link effectScope}, the effect is automatically\n * collected and will be disposed when the scope stops.\n *\n * @param fn - The effect function to run\n * @returns A cleanup function to stop the effect\n */\nexport const effect = (fn: () => void | CleanupFn): CleanupFn => {\n let cleanupFn: CleanupFn | void;\n let isDisposed = false;\n const scope = getActiveScope();\n\n const runCleanup = (): void => {\n if (cleanupFn) {\n try {\n cleanupFn();\n } catch (error) {\n console.error('bQuery reactive: Error in effect cleanup', error);\n }\n cleanupFn = undefined;\n }\n };\n\n const clearEffectState = (): void => {\n runCleanup();\n // Clean up all dependencies when effect is disposed\n clearDependencies(observer);\n };\n\n const dispose: CleanupFn = () => {\n if (isDisposed) {\n return;\n }\n\n isDisposed = true;\n clearEffectState();\n };\n\n if (hasScopeDisposer(scope)) {\n scope._addDisposer(dispose);\n }\n\n const observer: Observer = () => {\n if (isDisposed) return;\n\n runCleanup();\n\n // Clear old dependencies before running to avoid stale subscriptions\n clearDependencies(observer);\n\n try {\n cleanupFn = track(observer, fn);\n } catch (error) {\n console.error('bQuery reactive: Error in effect', error);\n }\n\n if (isDisposed) {\n clearEffectState();\n }\n };\n\n observer();\n\n return dispose;\n};\n"],"mappings":";AAsEA,IAAM,IAA8B,CAAA,GAGvB,IAAA,CACX,MAEA,OAAO,KAAU,YAAY,MAAU,QAAQ,kBAAkB,GAE7D,IAAA,CAAiB,OACpB,OAAO,KAAU,YAAY,OAAO,KAAU,eAC/C,MAAU,QACV,OAAQ,EAA6B,QAAS,YAQ1C,IAAA,CAAmB,MAAoE;AAC3F,QAAM,IACJ,OAAQ,EAAoC,eAAgB,aACvD,EAA8C,YAAY,OAC3D;AAEN,SACE,OAAO,KAAU,eACf,OAAO,eAAe,KACrB,EAA6C,OAAO,WAAA,MAAiB,mBACtE,MAAoB;GAQb,IAAA,MAAgD;AAC3D,WAAS,IAAI,EAAW,SAAS,GAAG,KAAK,GAAG,IAC1C,KAAI,EAAW,CAAA,EAAG,OAChB,QAAO,EAAW,CAAA;GAWlB,IAAN,MAA+C;AAAA;qBACZ,CAAA,kBACf;AAAA;EAElB,IAAI,SAAkB;AACpB,WAAO,KAAK;AAAA;EAId,aAAa,GAAqB;AAChC,IAAI,KAAK,WACP,KAAK,UAAU,KAAK,CAAA;AAAA;EAIxB,IAAO,GAAgB;AACrB,QAAI,CAAC,KAAK,QACR,OAAM,IAAI,MAAM,sDAAA;AAElB,QAAI,EAAgB,CAAA,EAClB,OAAM,IAAI,MAAM,wEAAA;AAGlB,IAAA,EAAW,KAAK,IAAA;AAChB,QAAI;AACF,YAAM,IAAS,EAAA;AACf,UAAI,EAAc,CAAA;AAChB,mBAAK,KAAA,GACC,IAAI,MAAM,wEAAA;AAElB,aAAO;AAAA;AAEP,MAAA,EAAW,IAAA;AAAA;;EAIf,OAAa;AACX,QAAK,KAAK,SACV;AAAA,WAAK,UAAU;AAGf,eAAS,IAAI,KAAK,UAAU,SAAS,GAAG,KAAK,GAAG,IAC9C,KAAI;AACF,aAAK,UAAU,CAAA,EAAA;AAAA,eACR,GAAO;AACd,gBAAQ,MAAM,2CAA2C,CAAA;AAAA;AAG7D,WAAK,UAAU,SAAS;AAAA;AAAA;GAwCf,IAAA,MAAiC;AAC5C,QAAM,IAAQ,IAAI,EAAA,GAGZ,IAAS,EAAA;AACf,SAAI,EAAiB,CAAA,KACnB,EAAO,aAAA,MAAmB,EAAM,KAAA,CAAM,GAGjC;GAqBI,IAAA,MAAiD,EAAA,GA4BjD,IAAA,CAAkB,MAAwB;AACrD,QAAM,IAAQ,EAAA;AACd,MAAI,CAAC,KAAS,CAAC,EAAM,UAAU,CAAC,EAAiB,CAAA,EAC/C,OAAM,IAAI,MACR,+EAAA;AAGJ,EAAA,EAAM,aAAa,CAAA;GC/PR,IAAA,CAAU,MAA0C;AAC/D,MAAI,GACA,IAAa;AACjB,QAAM,IAAQ,EAAA,GAER,IAAA,MAAyB;AAC7B,QAAI,GAAW;AACb,UAAI;AACF,QAAA,EAAA;AAAA,eACO,GAAO;AACd,gBAAQ,MAAM,4CAA4C,CAAA;AAAA;AAE5D,MAAA,IAAY;AAAA;KAIV,IAAA,MAA+B;AACnC,IAAA,EAAA,GAEA,EAAkB,CAAA;AAAA,KAGd,IAAA,MAA2B;AAC/B,IAAI,MAIJ,IAAa,IACb,EAAA;AAAA;AAGF,EAAI,EAAiB,CAAA,KACnB,EAAM,aAAa,CAAA;AAGrB,QAAM,IAAA,MAA2B;AAC/B,QAAI,CAAA,GAEJ;AAAA,MAAA,EAAA,GAGA,EAAkB,CAAA;AAElB,UAAI;AACF,QAAA,IAAY,EAAM,GAAU,CAAA;AAAA,eACrB,GAAO;AACd,gBAAQ,MAAM,oCAAoC,CAAA;AAAA;AAGpD,MAAI,KACF,EAAA;AAAA;AAAA;AAIJ,SAAA,EAAA,GAEO"}
1
+ {"version":3,"file":"effect-v8OIEmPs.js","names":[],"sources":["../src/reactive/scope.ts","../src/reactive/effect.ts"],"sourcesContent":["/**\n * Reactive effect scopes for grouped disposal.\n *\n * An `EffectScope` collects all effects, computed values, and watches created\n * inside its `run()` callback so they can be disposed together with a single\n * `stop()` call. Scopes nest — an inner scope is collected by its parent.\n *\n * @module bquery/reactive\n */\n\nimport type { CleanupFn } from './internals';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A scope that collects reactive resources for grouped disposal.\n *\n * @example\n * ```ts\n * import { effectScope, signal, effect, computed } from '@bquery/bquery/reactive';\n *\n * const scope = effectScope();\n *\n * scope.run(() => {\n * const count = signal(0);\n * effect(() => console.log(count.value));\n * const doubled = computed(() => count.value * 2);\n * });\n *\n * scope.stop(); // All effects and computed values disposed\n * ```\n */\nexport interface EffectScope {\n /** Whether the scope has not yet been stopped. */\n readonly active: boolean;\n\n /**\n * Executes `fn` inside this scope, collecting any reactive resources\n * (effects, computed values, watches, nested scopes) created during the call.\n *\n * `run()` is synchronous-only. Do not pass an async function or a function\n * that returns a Promise — resources created after an `await` cannot be\n * collected reliably.\n *\n * @template T - Return type of the provided function\n * @param fn - Function to run inside the scope\n * @returns The return value of `fn`\n * @throws {Error} If the scope has already been stopped\n */\n run<T>(fn: () => T): T;\n\n /**\n * Disposes all collected resources and marks the scope as inactive.\n * Calling `stop()` on an already-stopped scope is a safe no-op.\n */\n stop(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal scope stack\n// ---------------------------------------------------------------------------\n\n/** @internal */\ninterface ScopeInternal extends EffectScope {\n /** @internal – Register a cleanup callback to run when the scope stops. */\n _addDisposer(fn: CleanupFn): void;\n}\n\nconst scopeStack: ScopeInternal[] = [];\n\n/** @internal */\nexport const hasScopeDisposer = (\n scope: EffectScope | undefined\n): scope is EffectScope & { _addDisposer(fn: CleanupFn): void } =>\n typeof scope === 'object' && scope !== null && '_addDisposer' in scope;\n\nconst isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>\n (typeof value === 'object' || typeof value === 'function') &&\n value !== null &&\n typeof (value as { then?: unknown }).then === 'function';\n\n/**\n * Best-effort detection for native async functions so `run()` can reject them\n * before invocation. Transpiled async functions may not preserve this shape, so\n * promise-like return values are still checked after execution as a fallback.\n * @internal\n */\nconst isAsyncFunction = (value: unknown): value is (...args: never[]) => Promise<unknown> => {\n const constructorName =\n typeof (value as { constructor?: unknown }).constructor === 'function'\n ? (value as { constructor: { name?: unknown } }).constructor.name\n : undefined;\n\n return (\n typeof value === 'function' &&\n ((Symbol.toStringTag in value &&\n (value as { [Symbol.toStringTag]?: unknown })[Symbol.toStringTag] === 'AsyncFunction') ||\n constructorName === 'AsyncFunction')\n );\n};\n\n/**\n * Returns the currently active scope, or `undefined` if none.\n * @internal\n */\nexport const getActiveScope = (): EffectScope | undefined => {\n for (let i = scopeStack.length - 1; i >= 0; i--) {\n if (scopeStack[i].active) {\n return scopeStack[i];\n }\n }\n\n return undefined;\n};\n\n// ---------------------------------------------------------------------------\n// EffectScope implementation\n// ---------------------------------------------------------------------------\n\nclass EffectScopeImpl implements ScopeInternal {\n private disposers: CleanupFn[] = [];\n private _active = true;\n\n get active(): boolean {\n return this._active;\n }\n\n /** @internal */\n _addDisposer(fn: CleanupFn): void {\n if (this._active) {\n this.disposers.push(fn);\n }\n }\n\n run<T>(fn: () => T): T {\n if (!this._active) {\n throw new Error('bQuery reactive: Cannot run in a stopped effectScope');\n }\n if (isAsyncFunction(fn)) {\n throw new Error('bQuery reactive: effectScope.run() only supports synchronous callbacks');\n }\n\n scopeStack.push(this);\n try {\n const result = fn();\n if (isPromiseLike(result)) {\n this.stop();\n throw new Error('bQuery reactive: effectScope.run() only supports synchronous callbacks');\n }\n return result;\n } finally {\n scopeStack.pop();\n }\n }\n\n stop(): void {\n if (!this._active) return;\n this._active = false;\n\n // Dispose in reverse order (LIFO) to mirror creation order\n for (let i = this.disposers.length - 1; i >= 0; i--) {\n try {\n this.disposers[i]();\n } catch (error) {\n console.error('bQuery reactive: Error in scope cleanup', error);\n }\n }\n this.disposers.length = 0;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a new effect scope for grouped disposal of reactive resources.\n *\n * All `effect()`, `computed()`, `watch()`, and nested `effectScope()` calls\n * made inside `scope.run(fn)` are automatically collected. Calling\n * `scope.stop()` disposes them all at once.\n *\n * `run()` is synchronous-only. Create the scope outside async flows when\n * needed, but keep the callback itself synchronous so cleanup registration\n * stays deterministic.\n *\n * @returns A new {@link EffectScope}\n *\n * @example\n * ```ts\n * import { effectScope, signal, effect, onScopeDispose } from '@bquery/bquery/reactive';\n *\n * const scope = effectScope();\n *\n * scope.run(() => {\n * const count = signal(0);\n *\n * effect(() => console.log(count.value));\n *\n * onScopeDispose(() => {\n * console.log('Custom cleanup');\n * });\n * });\n *\n * scope.stop(); // logs \"Custom cleanup\", all effects stopped\n * ```\n */\nexport const effectScope = (): EffectScope => {\n const scope = new EffectScopeImpl();\n\n // If created inside another scope, auto-collect as a nested scope\n const parent = getActiveScope();\n if (hasScopeDisposer(parent)) {\n parent._addDisposer(() => scope.stop());\n }\n\n return scope;\n};\n\n/**\n * Returns the currently active {@link EffectScope}, or `undefined` if\n * code is not running inside any scope's `run()` callback.\n *\n * @returns The active scope, or `undefined`\n *\n * @example\n * ```ts\n * import { effectScope, getCurrentScope } from '@bquery/bquery/reactive';\n *\n * const scope = effectScope();\n * scope.run(() => {\n * console.log(getCurrentScope() !== undefined); // true\n * });\n *\n * console.log(getCurrentScope()); // undefined\n * ```\n */\nexport const getCurrentScope = (): EffectScope | undefined => getActiveScope();\n\n/**\n * Registers a cleanup callback on the currently active scope.\n *\n * The callback runs when the scope is stopped. This is useful for\n * registering arbitrary cleanup (e.g. event listeners, timers)\n * alongside effects and computed values.\n *\n * @param fn - Cleanup function to run when the scope stops\n * @throws {Error} If called outside an active scope\n *\n * @example\n * ```ts\n * import { effectScope, onScopeDispose } from '@bquery/bquery/reactive';\n *\n * const scope = effectScope();\n *\n * scope.run(() => {\n * const controller = new AbortController();\n * fetch('/api/data', { signal: controller.signal });\n *\n * onScopeDispose(() => controller.abort());\n * });\n *\n * scope.stop(); // abort() is called\n * ```\n */\nexport const onScopeDispose = (fn: CleanupFn): void => {\n const scope = getActiveScope();\n if (!scope || !scope.active || !hasScopeDisposer(scope)) {\n throw new Error(\n 'bQuery reactive: onScopeDispose() must be called inside an active effectScope'\n );\n }\n scope._addDisposer(fn);\n};\n","/**\n * Reactive effects.\n */\n\nimport { CleanupFn, Observer, track, clearDependencies } from './internals';\nimport { getActiveScope, hasScopeDisposer } from './scope';\n\n/**\n * Creates a side effect that automatically re-runs when dependencies change.\n *\n * The effect runs immediately upon creation and then re-runs whenever\n * any signal or computed value read inside it changes.\n *\n * If created inside an {@link effectScope}, the effect is automatically\n * collected and will be disposed when the scope stops.\n *\n * @param fn - The effect function to run\n * @returns A cleanup function to stop the effect\n */\nexport const effect = (fn: () => void | CleanupFn): CleanupFn => {\n let cleanupFn: CleanupFn | void;\n let isDisposed = false;\n const scope = getActiveScope();\n\n const runCleanup = (): void => {\n if (cleanupFn) {\n try {\n cleanupFn();\n } catch (error) {\n console.error('bQuery reactive: Error in effect cleanup', error);\n }\n cleanupFn = undefined;\n }\n };\n\n const clearEffectState = (): void => {\n runCleanup();\n // Clean up all dependencies when effect is disposed\n clearDependencies(observer);\n };\n\n const dispose: CleanupFn = () => {\n if (isDisposed) {\n return;\n }\n\n isDisposed = true;\n clearEffectState();\n };\n\n if (hasScopeDisposer(scope)) {\n scope._addDisposer(dispose);\n }\n\n const observer: Observer = () => {\n if (isDisposed) return;\n\n runCleanup();\n\n // Clear old dependencies before running to avoid stale subscriptions\n clearDependencies(observer);\n\n try {\n cleanupFn = track(observer, fn);\n } catch (error) {\n console.error('bQuery reactive: Error in effect', error);\n }\n\n if (isDisposed) {\n clearEffectState();\n }\n };\n\n observer();\n\n return dispose;\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"env-PvwYHnJq.js","names":[],"sources":["../src/core/env.ts"],"sourcesContent":["/**\n * Shared environment detection helpers.\n *\n * @internal\n */\n\ntype BQueryEnvGlobal = typeof globalThis & {\n __BQUERY_DEV__?: boolean;\n process?: {\n env?: {\n NODE_ENV?: string;\n };\n release?: {\n name?: string;\n };\n versions?: {\n node?: string;\n };\n };\n};\n\n/**\n * Returns whether development-only diagnostics should be enabled.\n *\n * Priority:\n * 1. Explicit global override via `globalThis.__BQUERY_DEV__`\n * 2. `process.env.NODE_ENV`\n * 3. Actual Node-like runtimes without `NODE_ENV` default to development\n * 4. Production-safe fallback (`false`)\n *\n * @internal\n */\nexport const detectDevEnvironment = (): boolean => {\n try {\n const globalObject = globalThis as BQueryEnvGlobal;\n\n if (typeof globalObject.__BQUERY_DEV__ === 'boolean') {\n return globalObject.__BQUERY_DEV__;\n }\n\n const nodeEnv = globalObject.process?.env?.NODE_ENV;\n if (typeof nodeEnv === 'string') {\n return nodeEnv !== 'production';\n }\n\n const nodeVersion = globalObject.process?.versions?.node;\n if (typeof nodeVersion === 'string' && nodeVersion.length > 0) {\n return true;\n }\n\n const releaseName = globalObject.process?.release?.name;\n if (releaseName === 'node' || releaseName === 'io.js') {\n return true;\n }\n\n return false;\n } catch {\n return false;\n }\n};\n"],"mappings":"AAgCA,IAAa,IAAA,MAAsC;AACjD,MAAI;AACF,UAAM,IAAe;AAErB,QAAI,OAAO,EAAa,kBAAmB,UACzC,QAAO,EAAa;AAGtB,UAAM,IAAU,EAAa,SAAS,KAAK;AAC3C,QAAI,OAAO,KAAY,SACrB,QAAO,MAAY;AAGrB,UAAM,IAAc,EAAa,SAAS,UAAU;AACpD,QAAI,OAAO,KAAgB,YAAY,EAAY,SAAS,EAC1D,QAAO;AAGT,UAAM,IAAc,EAAa,SAAS,SAAS;AACnD,WAAI,MAAgB,UAAU,MAAgB;AAAA,UAKxC;AACN,WAAO;AAAA"}
1
+ {"version":3,"file":"env-PvwYHnJq.js","names":[],"sources":["../src/core/env.ts"],"sourcesContent":["/**\n * Shared environment detection helpers.\n *\n * @internal\n */\n\ntype BQueryEnvGlobal = typeof globalThis & {\n __BQUERY_DEV__?: boolean;\n process?: {\n env?: {\n NODE_ENV?: string;\n };\n release?: {\n name?: string;\n };\n versions?: {\n node?: string;\n };\n };\n};\n\n/**\n * Returns whether development-only diagnostics should be enabled.\n *\n * Priority:\n * 1. Explicit global override via `globalThis.__BQUERY_DEV__`\n * 2. `process.env.NODE_ENV`\n * 3. Actual Node-like runtimes without `NODE_ENV` default to development\n * 4. Production-safe fallback (`false`)\n *\n * @internal\n */\nexport const detectDevEnvironment = (): boolean => {\n try {\n const globalObject = globalThis as BQueryEnvGlobal;\n\n if (typeof globalObject.__BQUERY_DEV__ === 'boolean') {\n return globalObject.__BQUERY_DEV__;\n }\n\n const nodeEnv = globalObject.process?.env?.NODE_ENV;\n if (typeof nodeEnv === 'string') {\n return nodeEnv !== 'production';\n }\n\n const nodeVersion = globalObject.process?.versions?.node;\n if (typeof nodeVersion === 'string' && nodeVersion.length > 0) {\n return true;\n }\n\n const releaseName = globalObject.process?.release?.name;\n if (releaseName === 'node' || releaseName === 'io.js') {\n return true;\n }\n\n return false;\n } catch {\n return false;\n }\n};\n"],"mappings":"AAgCA,IAAa,IAAA,MAAsC;AACjD,MAAI;AACF,UAAM,IAAe;AAErB,QAAI,OAAO,EAAa,kBAAmB,UACzC,QAAO,EAAa;AAGtB,UAAM,IAAU,EAAa,SAAS,KAAK;AAC3C,QAAI,OAAO,KAAY,SACrB,QAAO,MAAY;AAGrB,UAAM,IAAc,EAAa,SAAS,UAAU;AACpD,QAAI,OAAO,KAAgB,YAAY,EAAY,SAAS,EAC1D,QAAO;AAGT,UAAM,IAAc,EAAa,SAAS,SAAS;AACnD,WAAI,MAAgB,UAAU,MAAgB;AAAA,EAKhD,QAAQ;AACN,WAAO;AAAA,EACT;AACF"}
@@ -1,10 +1,10 @@
1
1
  import { i as k } from "./object-BCk-1c8T.js";
2
2
  import { t as P } from "./function-Cybd57JV.js";
3
3
  import { l as M } from "./type-guards-BMX2c0LP.js";
4
- import { n as f, t as D } from "./core-CongXJuo.js";
5
- import { t as $ } from "./readonly-C0ZwS1Tf.js";
6
- import { t as x } from "./effect-Cc51IH91.js";
7
- import { n as R, r as m } from "./untrack-bjWDNdyE.js";
4
+ import { n as f, t as D } from "./core-yg9rJXiR.js";
5
+ import { t as $ } from "./effect-v8OIEmPs.js";
6
+ import { n as x, r as m } from "./untrack-uzz3JDNK.js";
7
+ import { t as R } from "./readonly-Br-6pAgj.js";
8
8
  var L = (r) => r === !0 || r === void 0, N = async (r, e) => {
9
9
  const t = r(e), s = M(t) ? await t : t;
10
10
  return L(s) ? void 0 : s;
@@ -114,7 +114,7 @@ var L = (r) => r === !0 || r === void 0, N = async (r, e) => {
114
114
  return T(s) ? void 0 : s;
115
115
  }, Z = (r, e = {}) => {
116
116
  let t;
117
- U(r) ? t = r : t = f($(r) || z(r) ? r.peek() : r);
117
+ U(r) ? t = r : t = f(R(r) || z(r) ? r.peek() : r);
118
118
  const s = t.peek(), o = f(e.initialError ?? ""), l = f(!1), c = f(!1), b = m(() => !Object.is(t.value, s)), h = m(() => !b.value), j = m(() => o.value === ""), y = e.validateOn ?? "manual", p = Math.max(0, e.debounceMs ?? 0);
119
119
  let d = 0, w = !1, n = !1, a = !1, i;
120
120
  const u = (O) => {
@@ -147,7 +147,7 @@ var L = (r) => r === !0 || r === void 0, N = async (r, e) => {
147
147
  V().catch(u);
148
148
  }
149
149
  };
150
- return (y === "change" || y === "both") && (i = x(() => {
150
+ return (y === "change" || y === "both") && (i = $(() => {
151
151
  if (t.value, !w) {
152
152
  w = !0;
153
153
  return;
@@ -176,7 +176,7 @@ var L = (r) => r === !0 || r === void 0, N = async (r, e) => {
176
176
  a || (a = !0, d += 1, v.cancel(), i?.(), i = void 0, b.dispose(), h.dispose(), j.dispose(), c.value = !1);
177
177
  }
178
178
  };
179
- }, U = (r) => r instanceof D, z = (r) => r instanceof R, _ = (r = "This field is required") => (e) => e == null || typeof e == "string" && e.trim() === "" || Array.isArray(e) && e.length === 0 ? r : !0, rr = (r, e) => {
179
+ }, U = (r) => r instanceof D, z = (r) => r instanceof x, _ = (r = "This field is required") => (e) => e == null || typeof e == "string" && e.trim() === "" || Array.isArray(e) && e.length === 0 ? r : !0, rr = (r, e) => {
180
180
  const t = e ?? `Must be at least ${r} characters`;
181
181
  return (s) => (typeof s == "string" ? s : String(s ?? "")).length >= r ? !0 : t;
182
182
  }, tr = (r, e) => {
@@ -225,4 +225,4 @@ export {
225
225
  _ as u
226
226
  };
227
227
 
228
- //# sourceMappingURL=forms-BLx4ZzT7.js.map
228
+ //# sourceMappingURL=forms-DYcdlk_h.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"forms-BLx4ZzT7.js","names":[],"sources":["../src/forms/create-form.ts","../src/forms/use-field.ts","../src/forms/validators.ts"],"sourcesContent":["/**\n * Reactive form creation and management.\n *\n * @module bquery/forms\n */\n\nimport { computed, signal } from '../reactive/index';\nimport { isPrototypePollutionKey } from '../core/utils/object';\nimport { isPromise } from '../core/utils/type-guards';\nimport type {\n CrossFieldValidator,\n FieldConfig,\n Form,\n FormConfig,\n FormErrors,\n FormField,\n FormFields,\n ValidationResult,\n Validator,\n} from './types';\n\n/**\n * Determines whether a validator returned a valid result.\n * @internal\n */\nconst isValid = (result: ValidationResult): boolean => result === true || result === undefined;\n\n/**\n * Runs a single validator, normalising sync and async results.\n * @internal\n */\nconst runValidator = async <T>(validator: Validator<T>, value: T): Promise<string | undefined> => {\n const result = validator(value);\n const resolved = isPromise(result) ? await result : result;\n return isValid(resolved) ? undefined : (resolved as string);\n};\n\n/**\n * Creates a reactive form field from its configuration.\n * @internal\n */\nconst createField = <T>(config: FieldConfig<T>): FormField<T> => {\n const initial = config.initialValue;\n const value = signal<T>(initial);\n const error = signal('');\n const isTouched = signal(false);\n\n const isDirty = computed(() => !Object.is(value.value, initial));\n const isPristine = computed(() => !isDirty.value);\n\n return {\n value,\n error,\n isDirty,\n isTouched,\n isPristine,\n touch: () => {\n isTouched.value = true;\n },\n reset: () => {\n value.value = initial;\n error.value = '';\n isTouched.value = false;\n },\n };\n};\n\n/**\n * Validates a single field against its validators.\n * Sets the field's error signal.\n *\n * @returns The first error message, or an empty string if valid.\n * @internal\n */\nconst validateSingleField = async <T>(\n field: FormField<T>,\n validators: Validator<T>[] | undefined\n): Promise<string> => {\n if (!validators || validators.length === 0) {\n field.error.value = '';\n return '';\n }\n\n for (const validator of validators) {\n const errorMsg = await runValidator(validator, field.value.value);\n if (errorMsg) {\n field.error.value = errorMsg;\n return errorMsg;\n }\n }\n\n field.error.value = '';\n return '';\n};\n\n/**\n * Creates a fully reactive form with field-level validation,\n * dirty/touched tracking, cross-field validation, and submission handling.\n *\n * Each field's `value`, `error`, `isDirty`, `isTouched`, and `isPristine`\n * are reactive signals/computed values that can be used in effects, computed\n * values, or directly read/written.\n *\n * @template T - Shape of the form values (e.g. `{ name: string; age: number }`)\n * @param config - Form configuration with field definitions, validators, and submit handler\n * @returns A reactive {@link Form} instance\n *\n * @example\n * ```ts\n * import { createForm, required, email, min } from '@bquery/bquery/forms';\n *\n * const form = createForm({\n * fields: {\n * name: { initialValue: '', validators: [required()] },\n * email: { initialValue: '', validators: [required(), email()] },\n * age: { initialValue: 0, validators: [min(18, 'Must be 18+')] },\n * },\n * onSubmit: async (values) => {\n * await fetch('/api/register', {\n * method: 'POST',\n * body: JSON.stringify(values),\n * });\n * },\n * });\n *\n * // Read reactive state\n * console.log(form.isValid.value); // true (initially, before validation runs)\n * console.log(form.fields.name.value.value); // ''\n *\n * // Update a field\n * form.fields.name.value.value = 'Ada';\n *\n * // Validate and submit\n * await form.handleSubmit();\n * ```\n */\nexport const createForm = <T extends Record<string, unknown>>(config: FormConfig<T>): Form<T> => {\n // Build reactive field objects\n const fieldEntries = Object.entries(config.fields) as [\n keyof T & string,\n FieldConfig<T[keyof T]>,\n ][];\n\n const fields = {} as FormFields<T>;\n const errors = {} as FormErrors<T>;\n\n for (const [name, fieldConfig] of fieldEntries) {\n const field = createField(fieldConfig as FieldConfig<T[typeof name]>);\n (fields as Record<string, FormField>)[name] = field;\n (errors as Record<string, typeof field.error>)[name] = field.error;\n }\n\n const isSubmitting = signal(false);\n\n // Computed: form is valid when all error signals are empty\n const isFormValid = computed(() => {\n for (const name of Object.keys(fields)) {\n if ((fields as Record<string, FormField>)[name].error.value !== '') {\n return false;\n }\n }\n return true;\n });\n\n // Computed: form is dirty when any field is dirty\n const isFormDirty = computed(() => {\n for (const name of Object.keys(fields)) {\n if ((fields as Record<string, FormField>)[name].isDirty.value) {\n return true;\n }\n }\n return false;\n });\n\n /**\n * Validate a single field by name.\n */\n const validateField = async (name: keyof T & string): Promise<void> => {\n const field = (fields as Record<string, FormField>)[name];\n const fieldConfig = (config.fields as Record<string, FieldConfig>)[name];\n if (!field || !fieldConfig) return;\n await validateSingleField(field, fieldConfig.validators);\n };\n\n /**\n * Validate all fields (per-field + cross-field).\n * Returns `true` if the entire form is valid.\n */\n const validate = async (): Promise<boolean> => {\n let hasError = false;\n\n // Per-field validation\n for (const [name, fieldConfig] of fieldEntries) {\n const field = (fields as Record<string, FormField>)[name];\n const error = await validateSingleField(field, (fieldConfig as FieldConfig).validators);\n if (error) hasError = true;\n }\n\n // Cross-field validation\n if (config.crossValidators && config.crossValidators.length > 0) {\n const values = getValues();\n for (const crossValidator of config.crossValidators as CrossFieldValidator<T>[]) {\n const crossErrors = await crossValidator(values);\n if (crossErrors) {\n for (const [fieldName, errorMsg] of Object.entries(crossErrors) as [\n string,\n string | undefined,\n ][]) {\n if (errorMsg) {\n const field = (fields as Record<string, FormField>)[fieldName];\n if (field) {\n // Only set cross-field error if no per-field error exists\n if (field.error.value === '') {\n field.error.value = errorMsg;\n }\n hasError = true;\n }\n }\n }\n }\n }\n }\n\n return !hasError;\n };\n\n /**\n * Validate all fields and, if valid, invoke the onSubmit handler.\n * Prevents concurrent submissions by setting isSubmitting before validation.\n */\n const handleSubmit = async (): Promise<void> => {\n if (isSubmitting.value) return;\n isSubmitting.value = true;\n\n try {\n const valid = await validate();\n if (!valid) return;\n\n if (config.onSubmit) {\n await config.onSubmit(getValues());\n }\n } finally {\n isSubmitting.value = false;\n }\n };\n\n /**\n * Reset every field to its initial value and clear all errors.\n */\n const reset = (): void => {\n for (const name of Object.keys(fields)) {\n (fields as Record<string, FormField>)[name].reset();\n }\n };\n\n /**\n * Return a plain object snapshot of all current field values.\n */\n const getValues = (): T => {\n const values = {} as Record<string, unknown>;\n for (const name of Object.keys(fields)) {\n values[name] = (fields as Record<string, FormField>)[name].value.value;\n }\n return values as T;\n };\n\n /**\n * Bulk-set field values from a partial object.\n * Only fields present in the object are updated; missing keys are left unchanged.\n */\n const setValues = (values: Partial<T>): void => {\n for (const [name, val] of Object.entries(values)) {\n // Ignore inherited keys and prototype-pollution vectors before mutating field state.\n if (isPrototypePollutionKey(name) || !Object.prototype.hasOwnProperty.call(fields, name)) {\n continue;\n }\n\n const field = (fields as Record<string, FormField>)[name];\n if (!field) {\n continue;\n }\n field.value.value = val;\n }\n };\n\n /**\n * Bulk-set field error messages from a partial object.\n * Useful for applying server-side validation errors.\n * Only fields present in the object are updated; missing keys are left unchanged.\n */\n const setErrors = (errorMap: Partial<Record<keyof T & string, string>>): void => {\n for (const [name, msg] of Object.entries(errorMap)) {\n // Ignore inherited keys and prototype-pollution vectors before mutating field state.\n if (isPrototypePollutionKey(name) || !Object.prototype.hasOwnProperty.call(fields, name)) {\n continue;\n }\n\n const field = (fields as Record<string, FormField>)[name];\n if (!field) {\n continue;\n }\n field.error.value = (msg as string) ?? '';\n }\n };\n\n return {\n fields,\n errors,\n isValid: isFormValid,\n isDirty: isFormDirty,\n isSubmitting,\n handleSubmit,\n validateField,\n validate,\n reset,\n getValues,\n setValues,\n setErrors,\n };\n};\n","/**\n * Standalone reactive field composable.\n *\n * @module bquery/forms\n */\n\nimport { debounce } from '../core/utils/function';\nimport { isPromise } from '../core/utils/type-guards';\nimport { Computed } from '../reactive/computed';\nimport { Signal } from '../reactive/core';\nimport { computed, effect, signal } from '../reactive/index';\nimport type { MaybeSignal } from '../reactive/index';\nimport { isReadonlySignal } from '../reactive/readonly';\nimport type { UseFormFieldOptions, UseFormFieldReturn, ValidationResult, Validator } from './types';\n\n/**\n * Determines whether a validator returned a valid result.\n * @internal\n */\nconst isValidationSuccess = (result: ValidationResult): boolean =>\n result === true || result === undefined;\n\n/**\n * Runs a single validator, normalising sync and async results.\n * @internal\n */\nconst runValidator = async <T>(validator: Validator<T>, value: T): Promise<string | undefined> => {\n const result = validator(value);\n const resolved = isPromise(result) ? await result : result;\n return isValidationSuccess(resolved) ? undefined : (resolved as string);\n};\n\n/**\n * Creates a standalone reactive form field with optional automatic validation.\n *\n * This helper is useful when you want field-level state without creating a full form,\n * or when you want to bind an existing signal to the forms validation model.\n *\n * @template T - The type of the field value\n * @param initialValue - Plain initial value, an existing writable signal to reuse, or a\n * computed / readonly reactive source to snapshot\n * @param options - Validation mode, validators, debounce, and initial error configuration\n * @returns A reactive field handle with validation helpers\n *\n * @example\n * ```ts\n * import { useFormField, required } from '@bquery/bquery/forms';\n *\n * const email = useFormField('', {\n * validators: [required()],\n * validateOn: 'blur',\n * });\n *\n * email.value.value = 'ada@example.com';\n * email.touch();\n * ```\n */\nexport const useFormField = <T>(\n initialValue: MaybeSignal<T>,\n options: UseFormFieldOptions<T> = {}\n): UseFormFieldReturn<T> => {\n let value: Signal<T>;\n\n if (isSignal(initialValue)) {\n value = initialValue as Signal<T>;\n } else {\n const startingValue: T =\n isReadonlySignal<T>(initialValue) || isComputedValue<T>(initialValue)\n ? initialValue.peek()\n : (initialValue as T);\n value = signal(startingValue);\n }\n\n const initial = value.peek();\n const error = signal(options.initialError ?? '');\n const isTouched = signal(false);\n const isValidating = signal(false);\n const isDirty = computed(() => !Object.is(value.value, initial));\n const isPristine = computed(() => !isDirty.value);\n const isValid = computed(() => error.value === '');\n const validateOn = options.validateOn ?? 'manual';\n const debounceMs = Math.max(0, options.debounceMs ?? 0);\n\n let validationId = 0;\n let changeInitialized = false;\n let suppressNextChangeValidation = false;\n let isDestroyed = false;\n let stopChangeValidationEffect: (() => void) | undefined;\n\n const logValidationError = (validationError: unknown): void => {\n console.error('bQuery forms: Error in scheduled field validation', validationError);\n };\n\n const runValidation = async (): Promise<boolean> => {\n const currentValidationId = ++validationId;\n const validators = options.validators;\n\n if (!validators || validators.length === 0) {\n error.value = '';\n isValidating.value = false;\n return true;\n }\n\n isValidating.value = true;\n\n try {\n const currentValue = value.peek();\n\n for (const validator of validators) {\n const nextError = await runValidator(validator, currentValue);\n\n if (currentValidationId !== validationId) {\n return error.peek() === '';\n }\n\n if (nextError) {\n error.value = nextError;\n return false;\n }\n }\n\n if (currentValidationId === validationId) {\n error.value = '';\n }\n return true;\n } finally {\n if (currentValidationId === validationId) {\n isValidating.value = false;\n }\n }\n };\n\n const debouncedValidate = debounce(() => {\n void runValidation().catch(logValidationError);\n }, debounceMs);\n\n const scheduleValidation = (): void => {\n if (isDestroyed) {\n return;\n }\n\n if (debounceMs > 0) {\n debouncedValidate();\n return;\n }\n\n void runValidation().catch(logValidationError);\n };\n\n if (validateOn === 'change' || validateOn === 'both') {\n stopChangeValidationEffect = effect(() => {\n void value.value;\n\n if (!changeInitialized) {\n changeInitialized = true;\n return;\n }\n\n if (suppressNextChangeValidation) {\n suppressNextChangeValidation = false;\n return;\n }\n\n scheduleValidation();\n });\n }\n\n const destroy = (): void => {\n if (isDestroyed) {\n return;\n }\n\n isDestroyed = true;\n validationId += 1;\n debouncedValidate.cancel();\n stopChangeValidationEffect?.();\n stopChangeValidationEffect = undefined;\n isDirty.dispose();\n isPristine.dispose();\n isValid.dispose();\n isValidating.value = false;\n };\n\n return {\n value,\n error,\n isDirty,\n isTouched,\n isPristine,\n isValid,\n isValidating,\n touch: () => {\n isTouched.value = true;\n if (validateOn === 'blur' || validateOn === 'both') {\n scheduleValidation();\n }\n },\n reset: () => {\n validationId += 1;\n debouncedValidate.cancel();\n if (!Object.is(value.peek(), initial)) {\n suppressNextChangeValidation = true;\n }\n value.value = initial;\n error.value = options.initialError ?? '';\n isTouched.value = false;\n isValidating.value = false;\n },\n validate: async () => {\n debouncedValidate.cancel();\n return runValidation();\n },\n destroy,\n };\n};\n\n/**\n * Determines whether a value looks like a writable signal.\n * @internal\n */\nconst isSignal = (value: unknown): value is Signal<unknown> => {\n return value instanceof Signal;\n};\n\n/**\n * Determines whether a value is a computed reactive source.\n * @internal\n */\nconst isComputedValue = <T>(value: unknown): value is Computed<T> => {\n return value instanceof Computed;\n};\n","/**\n * Built-in validation functions for form fields.\n *\n * Each factory returns a {@link SyncValidator} that can be passed\n * to a field's `validators` array in {@link FormConfig}.\n *\n * @module bquery/forms\n */\n\nimport type { AsyncValidator, SyncValidator } from './types';\n\n/**\n * Requires a non-empty value.\n *\n * Fails for `undefined`, `null`, empty strings (after trim), and empty arrays.\n *\n * @param message - Custom error message (default: `'This field is required'`)\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { required } from '@bquery/bquery/forms';\n * const validate = required('Name is required');\n * validate(''); // 'Name is required'\n * validate('Ada'); // true\n * ```\n */\nexport const required = (message = 'This field is required'): SyncValidator => {\n return (value: unknown) => {\n if (value == null) return message;\n if (typeof value === 'string' && value.trim() === '') return message;\n if (Array.isArray(value) && value.length === 0) return message;\n return true;\n };\n};\n\n/**\n * Requires a string to have at least `len` characters.\n *\n * Non-string values are coerced via `String()` before checking length.\n *\n * @param len - Minimum length\n * @param message - Custom error message\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { minLength } from '@bquery/bquery/forms';\n * const validate = minLength(3);\n * validate('ab'); // 'Must be at least 3 characters'\n * validate('abc'); // true\n * ```\n */\nexport const minLength = (len: number, message?: string): SyncValidator<unknown> => {\n const msg = message ?? `Must be at least ${len} characters`;\n return (value: unknown) => {\n const str = typeof value === 'string' ? value : String(value ?? '');\n return str.length >= len ? true : msg;\n };\n};\n\n/**\n * Requires a string to have at most `len` characters.\n *\n * Non-string values are coerced via `String()` before checking length.\n *\n * @param len - Maximum length\n * @param message - Custom error message\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { maxLength } from '@bquery/bquery/forms';\n * const validate = maxLength(10);\n * validate('hello world!!'); // 'Must be at most 10 characters'\n * validate('hello'); // true\n * ```\n */\nexport const maxLength = (len: number, message?: string): SyncValidator<unknown> => {\n const msg = message ?? `Must be at most ${len} characters`;\n return (value: unknown) => {\n const str = typeof value === 'string' ? value : String(value ?? '');\n return str.length <= len ? true : msg;\n };\n};\n\n/**\n * Requires a string to match a regular expression pattern.\n *\n * Non-string values are coerced via `String()` before testing.\n *\n * @param regex - Pattern to test against\n * @param message - Custom error message (default: `'Invalid format'`)\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { pattern } from '@bquery/bquery/forms';\n * const validate = pattern(/^\\d+$/, 'Numbers only');\n * validate('abc'); // 'Numbers only'\n * validate('123'); // true\n * ```\n */\nexport const pattern = (regex: RegExp, message = 'Invalid format'): SyncValidator<unknown> => {\n const safeRegex =\n regex.global || regex.sticky\n ? new RegExp(regex.source, regex.flags.replace(/[gy]/g, ''))\n : regex;\n\n return (value: unknown) => {\n const str = typeof value === 'string' ? value : String(value ?? '');\n safeRegex.lastIndex = 0;\n return safeRegex.test(str) ? true : message;\n };\n};\n\n/**\n * RFC 5322–simplified email validation.\n *\n * @param message - Custom error message (default: `'Invalid email address'`)\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { email } from '@bquery/bquery/forms';\n * const validate = email();\n * validate('nope'); // 'Invalid email address'\n * validate('ada@lovelace'); // 'Invalid email address'\n * validate('ada@love.co'); // true\n * ```\n */\nexport const email = (message = 'Invalid email address'): SyncValidator<unknown> => {\n // Intentionally simple — covers the vast majority of valid addresses\n // without re-implementing the full RFC 5322 grammar.\n const re = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n return (value: unknown) => {\n const str = typeof value === 'string' ? value : String(value ?? '');\n if (str === '') return true; // empty is handled by `required`\n return re.test(str) ? true : message;\n };\n};\n\n/**\n * Requires a string to be a valid URL.\n *\n * Uses the native `URL` constructor for validation.\n *\n * @param message - Custom error message (default: `'Invalid URL'`)\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { url } from '@bquery/bquery/forms';\n * const validate = url();\n * validate('not-a-url'); // 'Invalid URL'\n * validate('https://example.com'); // true\n * ```\n */\nexport const url = (message = 'Invalid URL'): SyncValidator<unknown> => {\n return (value: unknown) => {\n const str = typeof value === 'string' ? value : String(value ?? '');\n if (str === '') return true; // empty is handled by `required`\n try {\n new URL(str);\n return true;\n } catch {\n return message;\n }\n };\n};\n\n/**\n * Requires a numeric value to be at least `limit`.\n *\n * @param limit - Minimum allowed value (inclusive)\n * @param message - Custom error message\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { min } from '@bquery/bquery/forms';\n * const validate = min(1, 'Must be positive');\n * validate(0); // 'Must be positive'\n * validate(1); // true\n * ```\n */\nexport const min = (limit: number, message?: string): SyncValidator<unknown> => {\n const msg = message ?? `Must be at least ${limit}`;\n return (value: unknown) => {\n if (value == null) return true;\n if (typeof value === 'string' && value.trim() === '') return true;\n const num = typeof value === 'number' ? value : Number(value);\n return num >= limit ? true : msg;\n };\n};\n\n/**\n * Requires a numeric value to be at most `limit`.\n *\n * @param limit - Maximum allowed value (inclusive)\n * @param message - Custom error message\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { max } from '@bquery/bquery/forms';\n * const validate = max(100, 'Too high');\n * validate(101); // 'Too high'\n * validate(100); // true\n * ```\n */\nexport const max = (limit: number, message?: string): SyncValidator<unknown> => {\n const msg = message ?? `Must be at most ${limit}`;\n return (value: unknown) => {\n if (value == null) return true;\n if (typeof value === 'string' && value.trim() === '') return true;\n const num = typeof value === 'number' ? value : Number(value);\n return num <= limit ? true : msg;\n };\n};\n\n/**\n * Creates a custom synchronous validator from any predicate function.\n *\n * @param fn - Predicate that returns `true` when the value is valid\n * @param message - Error message when the predicate returns `false`\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { custom } from '@bquery/bquery/forms';\n * const isEven = custom((v: number) => v % 2 === 0, 'Must be even');\n * isEven(3); // 'Must be even'\n * isEven(4); // true\n * ```\n */\nexport const custom = <T = unknown>(\n fn: (value: T) => boolean,\n message: string\n): SyncValidator<T> => {\n return (value: T) => (fn(value) ? true : message);\n};\n\n/**\n * Creates a custom asynchronous validator.\n *\n * @param fn - Async predicate that resolves to `true` when valid\n * @param message - Error message when the predicate resolves to `false`\n * @returns An async validator function\n *\n * @example\n * ```ts\n * import { customAsync } from '@bquery/bquery/forms';\n * const isUnique = customAsync(\n * async (name: string) => !(await checkExists(name)),\n * 'Already taken',\n * );\n * ```\n */\nexport const customAsync = <T = unknown>(\n fn: (value: T) => Promise<boolean>,\n message: string\n): AsyncValidator<T> => {\n return async (value: T) => ((await fn(value)) ? true : message);\n};\n\n/**\n * Requires a field's value to match the current value of a reference signal.\n *\n * Typically used for \"confirm password\" or \"confirm email\" patterns where\n * one field must have the same value as another.\n *\n * @param ref - A reactive signal whose current value is the comparison target\n * @param message - Custom error message (default: `'Fields do not match'`)\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { signal } from '@bquery/bquery/reactive';\n * import { matchField } from '@bquery/bquery/forms';\n *\n * const password = signal('secret');\n * const confirmPassword = signal('');\n * const validateConfirmPassword = matchField(password, 'Passwords must match');\n *\n * validateConfirmPassword(confirmPassword.value);\n * ```\n */\nexport const matchField = <T>(\n ref: { readonly value: T },\n message = 'Fields do not match'\n): SyncValidator<T> => {\n return (value: T) => (Object.is(value, ref.value) ? true : message);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"forms-DYcdlk_h.js","names":[],"sources":["../src/forms/create-form.ts","../src/forms/use-field.ts","../src/forms/validators.ts"],"sourcesContent":["/**\n * Reactive form creation and management.\n *\n * @module bquery/forms\n */\n\nimport { computed, signal } from '../reactive/index';\nimport { isPrototypePollutionKey } from '../core/utils/object';\nimport { isPromise } from '../core/utils/type-guards';\nimport type {\n CrossFieldValidator,\n FieldConfig,\n Form,\n FormConfig,\n FormErrors,\n FormField,\n FormFields,\n ValidationResult,\n Validator,\n} from './types';\n\n/**\n * Determines whether a validator returned a valid result.\n * @internal\n */\nconst isValid = (result: ValidationResult): boolean => result === true || result === undefined;\n\n/**\n * Runs a single validator, normalising sync and async results.\n * @internal\n */\nconst runValidator = async <T>(validator: Validator<T>, value: T): Promise<string | undefined> => {\n const result = validator(value);\n const resolved = isPromise(result) ? await result : result;\n return isValid(resolved) ? undefined : (resolved as string);\n};\n\n/**\n * Creates a reactive form field from its configuration.\n * @internal\n */\nconst createField = <T>(config: FieldConfig<T>): FormField<T> => {\n const initial = config.initialValue;\n const value = signal<T>(initial);\n const error = signal('');\n const isTouched = signal(false);\n\n const isDirty = computed(() => !Object.is(value.value, initial));\n const isPristine = computed(() => !isDirty.value);\n\n return {\n value,\n error,\n isDirty,\n isTouched,\n isPristine,\n touch: () => {\n isTouched.value = true;\n },\n reset: () => {\n value.value = initial;\n error.value = '';\n isTouched.value = false;\n },\n };\n};\n\n/**\n * Validates a single field against its validators.\n * Sets the field's error signal.\n *\n * @returns The first error message, or an empty string if valid.\n * @internal\n */\nconst validateSingleField = async <T>(\n field: FormField<T>,\n validators: Validator<T>[] | undefined\n): Promise<string> => {\n if (!validators || validators.length === 0) {\n field.error.value = '';\n return '';\n }\n\n for (const validator of validators) {\n const errorMsg = await runValidator(validator, field.value.value);\n if (errorMsg) {\n field.error.value = errorMsg;\n return errorMsg;\n }\n }\n\n field.error.value = '';\n return '';\n};\n\n/**\n * Creates a fully reactive form with field-level validation,\n * dirty/touched tracking, cross-field validation, and submission handling.\n *\n * Each field's `value`, `error`, `isDirty`, `isTouched`, and `isPristine`\n * are reactive signals/computed values that can be used in effects, computed\n * values, or directly read/written.\n *\n * @template T - Shape of the form values (e.g. `{ name: string; age: number }`)\n * @param config - Form configuration with field definitions, validators, and submit handler\n * @returns A reactive {@link Form} instance\n *\n * @example\n * ```ts\n * import { createForm, required, email, min } from '@bquery/bquery/forms';\n *\n * const form = createForm({\n * fields: {\n * name: { initialValue: '', validators: [required()] },\n * email: { initialValue: '', validators: [required(), email()] },\n * age: { initialValue: 0, validators: [min(18, 'Must be 18+')] },\n * },\n * onSubmit: async (values) => {\n * await fetch('/api/register', {\n * method: 'POST',\n * body: JSON.stringify(values),\n * });\n * },\n * });\n *\n * // Read reactive state\n * console.log(form.isValid.value); // true (initially, before validation runs)\n * console.log(form.fields.name.value.value); // ''\n *\n * // Update a field\n * form.fields.name.value.value = 'Ada';\n *\n * // Validate and submit\n * await form.handleSubmit();\n * ```\n */\nexport const createForm = <T extends Record<string, unknown>>(config: FormConfig<T>): Form<T> => {\n // Build reactive field objects\n const fieldEntries = Object.entries(config.fields) as [\n keyof T & string,\n FieldConfig<T[keyof T]>,\n ][];\n\n const fields = {} as FormFields<T>;\n const errors = {} as FormErrors<T>;\n\n for (const [name, fieldConfig] of fieldEntries) {\n const field = createField(fieldConfig as FieldConfig<T[typeof name]>);\n (fields as Record<string, FormField>)[name] = field;\n (errors as Record<string, typeof field.error>)[name] = field.error;\n }\n\n const isSubmitting = signal(false);\n\n // Computed: form is valid when all error signals are empty\n const isFormValid = computed(() => {\n for (const name of Object.keys(fields)) {\n if ((fields as Record<string, FormField>)[name].error.value !== '') {\n return false;\n }\n }\n return true;\n });\n\n // Computed: form is dirty when any field is dirty\n const isFormDirty = computed(() => {\n for (const name of Object.keys(fields)) {\n if ((fields as Record<string, FormField>)[name].isDirty.value) {\n return true;\n }\n }\n return false;\n });\n\n /**\n * Validate a single field by name.\n */\n const validateField = async (name: keyof T & string): Promise<void> => {\n const field = (fields as Record<string, FormField>)[name];\n const fieldConfig = (config.fields as Record<string, FieldConfig>)[name];\n if (!field || !fieldConfig) return;\n await validateSingleField(field, fieldConfig.validators);\n };\n\n /**\n * Validate all fields (per-field + cross-field).\n * Returns `true` if the entire form is valid.\n */\n const validate = async (): Promise<boolean> => {\n let hasError = false;\n\n // Per-field validation\n for (const [name, fieldConfig] of fieldEntries) {\n const field = (fields as Record<string, FormField>)[name];\n const error = await validateSingleField(field, (fieldConfig as FieldConfig).validators);\n if (error) hasError = true;\n }\n\n // Cross-field validation\n if (config.crossValidators && config.crossValidators.length > 0) {\n const values = getValues();\n for (const crossValidator of config.crossValidators as CrossFieldValidator<T>[]) {\n const crossErrors = await crossValidator(values);\n if (crossErrors) {\n for (const [fieldName, errorMsg] of Object.entries(crossErrors) as [\n string,\n string | undefined,\n ][]) {\n if (errorMsg) {\n const field = (fields as Record<string, FormField>)[fieldName];\n if (field) {\n // Only set cross-field error if no per-field error exists\n if (field.error.value === '') {\n field.error.value = errorMsg;\n }\n hasError = true;\n }\n }\n }\n }\n }\n }\n\n return !hasError;\n };\n\n /**\n * Validate all fields and, if valid, invoke the onSubmit handler.\n * Prevents concurrent submissions by setting isSubmitting before validation.\n */\n const handleSubmit = async (): Promise<void> => {\n if (isSubmitting.value) return;\n isSubmitting.value = true;\n\n try {\n const valid = await validate();\n if (!valid) return;\n\n if (config.onSubmit) {\n await config.onSubmit(getValues());\n }\n } finally {\n isSubmitting.value = false;\n }\n };\n\n /**\n * Reset every field to its initial value and clear all errors.\n */\n const reset = (): void => {\n for (const name of Object.keys(fields)) {\n (fields as Record<string, FormField>)[name].reset();\n }\n };\n\n /**\n * Return a plain object snapshot of all current field values.\n */\n const getValues = (): T => {\n const values = {} as Record<string, unknown>;\n for (const name of Object.keys(fields)) {\n values[name] = (fields as Record<string, FormField>)[name].value.value;\n }\n return values as T;\n };\n\n /**\n * Bulk-set field values from a partial object.\n * Only fields present in the object are updated; missing keys are left unchanged.\n */\n const setValues = (values: Partial<T>): void => {\n for (const [name, val] of Object.entries(values)) {\n // Ignore inherited keys and prototype-pollution vectors before mutating field state.\n if (isPrototypePollutionKey(name) || !Object.prototype.hasOwnProperty.call(fields, name)) {\n continue;\n }\n\n const field = (fields as Record<string, FormField>)[name];\n if (!field) {\n continue;\n }\n field.value.value = val;\n }\n };\n\n /**\n * Bulk-set field error messages from a partial object.\n * Useful for applying server-side validation errors.\n * Only fields present in the object are updated; missing keys are left unchanged.\n */\n const setErrors = (errorMap: Partial<Record<keyof T & string, string>>): void => {\n for (const [name, msg] of Object.entries(errorMap)) {\n // Ignore inherited keys and prototype-pollution vectors before mutating field state.\n if (isPrototypePollutionKey(name) || !Object.prototype.hasOwnProperty.call(fields, name)) {\n continue;\n }\n\n const field = (fields as Record<string, FormField>)[name];\n if (!field) {\n continue;\n }\n field.error.value = (msg as string) ?? '';\n }\n };\n\n return {\n fields,\n errors,\n isValid: isFormValid,\n isDirty: isFormDirty,\n isSubmitting,\n handleSubmit,\n validateField,\n validate,\n reset,\n getValues,\n setValues,\n setErrors,\n };\n};\n","/**\n * Standalone reactive field composable.\n *\n * @module bquery/forms\n */\n\nimport { debounce } from '../core/utils/function';\nimport { isPromise } from '../core/utils/type-guards';\nimport { Computed } from '../reactive/computed';\nimport { Signal } from '../reactive/core';\nimport { computed, effect, signal } from '../reactive/index';\nimport type { MaybeSignal } from '../reactive/index';\nimport { isReadonlySignal } from '../reactive/readonly';\nimport type { UseFormFieldOptions, UseFormFieldReturn, ValidationResult, Validator } from './types';\n\n/**\n * Determines whether a validator returned a valid result.\n * @internal\n */\nconst isValidationSuccess = (result: ValidationResult): boolean =>\n result === true || result === undefined;\n\n/**\n * Runs a single validator, normalising sync and async results.\n * @internal\n */\nconst runValidator = async <T>(validator: Validator<T>, value: T): Promise<string | undefined> => {\n const result = validator(value);\n const resolved = isPromise(result) ? await result : result;\n return isValidationSuccess(resolved) ? undefined : (resolved as string);\n};\n\n/**\n * Creates a standalone reactive form field with optional automatic validation.\n *\n * This helper is useful when you want field-level state without creating a full form,\n * or when you want to bind an existing signal to the forms validation model.\n *\n * @template T - The type of the field value\n * @param initialValue - Plain initial value, an existing writable signal to reuse, or a\n * computed / readonly reactive source to snapshot\n * @param options - Validation mode, validators, debounce, and initial error configuration\n * @returns A reactive field handle with validation helpers\n *\n * @example\n * ```ts\n * import { useFormField, required } from '@bquery/bquery/forms';\n *\n * const email = useFormField('', {\n * validators: [required()],\n * validateOn: 'blur',\n * });\n *\n * email.value.value = 'ada@example.com';\n * email.touch();\n * ```\n */\nexport const useFormField = <T>(\n initialValue: MaybeSignal<T>,\n options: UseFormFieldOptions<T> = {}\n): UseFormFieldReturn<T> => {\n let value: Signal<T>;\n\n if (isSignal(initialValue)) {\n value = initialValue as Signal<T>;\n } else {\n const startingValue: T =\n isReadonlySignal<T>(initialValue) || isComputedValue<T>(initialValue)\n ? initialValue.peek()\n : (initialValue as T);\n value = signal(startingValue);\n }\n\n const initial = value.peek();\n const error = signal(options.initialError ?? '');\n const isTouched = signal(false);\n const isValidating = signal(false);\n const isDirty = computed(() => !Object.is(value.value, initial));\n const isPristine = computed(() => !isDirty.value);\n const isValid = computed(() => error.value === '');\n const validateOn = options.validateOn ?? 'manual';\n const debounceMs = Math.max(0, options.debounceMs ?? 0);\n\n let validationId = 0;\n let changeInitialized = false;\n let suppressNextChangeValidation = false;\n let isDestroyed = false;\n let stopChangeValidationEffect: (() => void) | undefined;\n\n const logValidationError = (validationError: unknown): void => {\n console.error('bQuery forms: Error in scheduled field validation', validationError);\n };\n\n const runValidation = async (): Promise<boolean> => {\n const currentValidationId = ++validationId;\n const validators = options.validators;\n\n if (!validators || validators.length === 0) {\n error.value = '';\n isValidating.value = false;\n return true;\n }\n\n isValidating.value = true;\n\n try {\n const currentValue = value.peek();\n\n for (const validator of validators) {\n const nextError = await runValidator(validator, currentValue);\n\n if (currentValidationId !== validationId) {\n return error.peek() === '';\n }\n\n if (nextError) {\n error.value = nextError;\n return false;\n }\n }\n\n if (currentValidationId === validationId) {\n error.value = '';\n }\n return true;\n } finally {\n if (currentValidationId === validationId) {\n isValidating.value = false;\n }\n }\n };\n\n const debouncedValidate = debounce(() => {\n void runValidation().catch(logValidationError);\n }, debounceMs);\n\n const scheduleValidation = (): void => {\n if (isDestroyed) {\n return;\n }\n\n if (debounceMs > 0) {\n debouncedValidate();\n return;\n }\n\n void runValidation().catch(logValidationError);\n };\n\n if (validateOn === 'change' || validateOn === 'both') {\n stopChangeValidationEffect = effect(() => {\n void value.value;\n\n if (!changeInitialized) {\n changeInitialized = true;\n return;\n }\n\n if (suppressNextChangeValidation) {\n suppressNextChangeValidation = false;\n return;\n }\n\n scheduleValidation();\n });\n }\n\n const destroy = (): void => {\n if (isDestroyed) {\n return;\n }\n\n isDestroyed = true;\n validationId += 1;\n debouncedValidate.cancel();\n stopChangeValidationEffect?.();\n stopChangeValidationEffect = undefined;\n isDirty.dispose();\n isPristine.dispose();\n isValid.dispose();\n isValidating.value = false;\n };\n\n return {\n value,\n error,\n isDirty,\n isTouched,\n isPristine,\n isValid,\n isValidating,\n touch: () => {\n isTouched.value = true;\n if (validateOn === 'blur' || validateOn === 'both') {\n scheduleValidation();\n }\n },\n reset: () => {\n validationId += 1;\n debouncedValidate.cancel();\n if (!Object.is(value.peek(), initial)) {\n suppressNextChangeValidation = true;\n }\n value.value = initial;\n error.value = options.initialError ?? '';\n isTouched.value = false;\n isValidating.value = false;\n },\n validate: async () => {\n debouncedValidate.cancel();\n return runValidation();\n },\n destroy,\n };\n};\n\n/**\n * Determines whether a value looks like a writable signal.\n * @internal\n */\nconst isSignal = (value: unknown): value is Signal<unknown> => {\n return value instanceof Signal;\n};\n\n/**\n * Determines whether a value is a computed reactive source.\n * @internal\n */\nconst isComputedValue = <T>(value: unknown): value is Computed<T> => {\n return value instanceof Computed;\n};\n","/**\n * Built-in validation functions for form fields.\n *\n * Each factory returns a {@link SyncValidator} that can be passed\n * to a field's `validators` array in {@link FormConfig}.\n *\n * @module bquery/forms\n */\n\nimport type { AsyncValidator, SyncValidator } from './types';\n\n/**\n * Requires a non-empty value.\n *\n * Fails for `undefined`, `null`, empty strings (after trim), and empty arrays.\n *\n * @param message - Custom error message (default: `'This field is required'`)\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { required } from '@bquery/bquery/forms';\n * const validate = required('Name is required');\n * validate(''); // 'Name is required'\n * validate('Ada'); // true\n * ```\n */\nexport const required = (message = 'This field is required'): SyncValidator => {\n return (value: unknown) => {\n if (value == null) return message;\n if (typeof value === 'string' && value.trim() === '') return message;\n if (Array.isArray(value) && value.length === 0) return message;\n return true;\n };\n};\n\n/**\n * Requires a string to have at least `len` characters.\n *\n * Non-string values are coerced via `String()` before checking length.\n *\n * @param len - Minimum length\n * @param message - Custom error message\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { minLength } from '@bquery/bquery/forms';\n * const validate = minLength(3);\n * validate('ab'); // 'Must be at least 3 characters'\n * validate('abc'); // true\n * ```\n */\nexport const minLength = (len: number, message?: string): SyncValidator<unknown> => {\n const msg = message ?? `Must be at least ${len} characters`;\n return (value: unknown) => {\n const str = typeof value === 'string' ? value : String(value ?? '');\n return str.length >= len ? true : msg;\n };\n};\n\n/**\n * Requires a string to have at most `len` characters.\n *\n * Non-string values are coerced via `String()` before checking length.\n *\n * @param len - Maximum length\n * @param message - Custom error message\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { maxLength } from '@bquery/bquery/forms';\n * const validate = maxLength(10);\n * validate('hello world!!'); // 'Must be at most 10 characters'\n * validate('hello'); // true\n * ```\n */\nexport const maxLength = (len: number, message?: string): SyncValidator<unknown> => {\n const msg = message ?? `Must be at most ${len} characters`;\n return (value: unknown) => {\n const str = typeof value === 'string' ? value : String(value ?? '');\n return str.length <= len ? true : msg;\n };\n};\n\n/**\n * Requires a string to match a regular expression pattern.\n *\n * Non-string values are coerced via `String()` before testing.\n *\n * @param regex - Pattern to test against\n * @param message - Custom error message (default: `'Invalid format'`)\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { pattern } from '@bquery/bquery/forms';\n * const validate = pattern(/^\\d+$/, 'Numbers only');\n * validate('abc'); // 'Numbers only'\n * validate('123'); // true\n * ```\n */\nexport const pattern = (regex: RegExp, message = 'Invalid format'): SyncValidator<unknown> => {\n const safeRegex =\n regex.global || regex.sticky\n ? new RegExp(regex.source, regex.flags.replace(/[gy]/g, ''))\n : regex;\n\n return (value: unknown) => {\n const str = typeof value === 'string' ? value : String(value ?? '');\n safeRegex.lastIndex = 0;\n return safeRegex.test(str) ? true : message;\n };\n};\n\n/**\n * RFC 5322–simplified email validation.\n *\n * @param message - Custom error message (default: `'Invalid email address'`)\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { email } from '@bquery/bquery/forms';\n * const validate = email();\n * validate('nope'); // 'Invalid email address'\n * validate('ada@lovelace'); // 'Invalid email address'\n * validate('ada@love.co'); // true\n * ```\n */\nexport const email = (message = 'Invalid email address'): SyncValidator<unknown> => {\n // Intentionally simple — covers the vast majority of valid addresses\n // without re-implementing the full RFC 5322 grammar.\n const re = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n return (value: unknown) => {\n const str = typeof value === 'string' ? value : String(value ?? '');\n if (str === '') return true; // empty is handled by `required`\n return re.test(str) ? true : message;\n };\n};\n\n/**\n * Requires a string to be a valid URL.\n *\n * Uses the native `URL` constructor for validation.\n *\n * @param message - Custom error message (default: `'Invalid URL'`)\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { url } from '@bquery/bquery/forms';\n * const validate = url();\n * validate('not-a-url'); // 'Invalid URL'\n * validate('https://example.com'); // true\n * ```\n */\nexport const url = (message = 'Invalid URL'): SyncValidator<unknown> => {\n return (value: unknown) => {\n const str = typeof value === 'string' ? value : String(value ?? '');\n if (str === '') return true; // empty is handled by `required`\n try {\n new URL(str);\n return true;\n } catch {\n return message;\n }\n };\n};\n\n/**\n * Requires a numeric value to be at least `limit`.\n *\n * @param limit - Minimum allowed value (inclusive)\n * @param message - Custom error message\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { min } from '@bquery/bquery/forms';\n * const validate = min(1, 'Must be positive');\n * validate(0); // 'Must be positive'\n * validate(1); // true\n * ```\n */\nexport const min = (limit: number, message?: string): SyncValidator<unknown> => {\n const msg = message ?? `Must be at least ${limit}`;\n return (value: unknown) => {\n if (value == null) return true;\n if (typeof value === 'string' && value.trim() === '') return true;\n const num = typeof value === 'number' ? value : Number(value);\n return num >= limit ? true : msg;\n };\n};\n\n/**\n * Requires a numeric value to be at most `limit`.\n *\n * @param limit - Maximum allowed value (inclusive)\n * @param message - Custom error message\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { max } from '@bquery/bquery/forms';\n * const validate = max(100, 'Too high');\n * validate(101); // 'Too high'\n * validate(100); // true\n * ```\n */\nexport const max = (limit: number, message?: string): SyncValidator<unknown> => {\n const msg = message ?? `Must be at most ${limit}`;\n return (value: unknown) => {\n if (value == null) return true;\n if (typeof value === 'string' && value.trim() === '') return true;\n const num = typeof value === 'number' ? value : Number(value);\n return num <= limit ? true : msg;\n };\n};\n\n/**\n * Creates a custom synchronous validator from any predicate function.\n *\n * @param fn - Predicate that returns `true` when the value is valid\n * @param message - Error message when the predicate returns `false`\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { custom } from '@bquery/bquery/forms';\n * const isEven = custom((v: number) => v % 2 === 0, 'Must be even');\n * isEven(3); // 'Must be even'\n * isEven(4); // true\n * ```\n */\nexport const custom = <T = unknown>(\n fn: (value: T) => boolean,\n message: string\n): SyncValidator<T> => {\n return (value: T) => (fn(value) ? true : message);\n};\n\n/**\n * Creates a custom asynchronous validator.\n *\n * @param fn - Async predicate that resolves to `true` when valid\n * @param message - Error message when the predicate resolves to `false`\n * @returns An async validator function\n *\n * @example\n * ```ts\n * import { customAsync } from '@bquery/bquery/forms';\n * const isUnique = customAsync(\n * async (name: string) => !(await checkExists(name)),\n * 'Already taken',\n * );\n * ```\n */\nexport const customAsync = <T = unknown>(\n fn: (value: T) => Promise<boolean>,\n message: string\n): AsyncValidator<T> => {\n return async (value: T) => ((await fn(value)) ? true : message);\n};\n\n/**\n * Requires a field's value to match the current value of a reference signal.\n *\n * Typically used for \"confirm password\" or \"confirm email\" patterns where\n * one field must have the same value as another.\n *\n * @param ref - A reactive signal whose current value is the comparison target\n * @param message - Custom error message (default: `'Fields do not match'`)\n * @returns A sync validator function\n *\n * @example\n * ```ts\n * import { signal } from '@bquery/bquery/reactive';\n * import { matchField } from '@bquery/bquery/forms';\n *\n * const password = signal('secret');\n * const confirmPassword = signal('');\n * const validateConfirmPassword = matchField(password, 'Passwords must match');\n *\n * validateConfirmPassword(confirmPassword.value);\n * ```\n */\nexport const matchField = <T>(\n ref: { readonly value: T },\n message = 'Fields do not match'\n): SyncValidator<T> => {\n return (value: T) => (Object.is(value, ref.value) ? true : message);\n};\n"],"mappings":"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"}
package/dist/forms.es.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as s, c as e, d as m, f as r, i as t, l as i, n as o, o as n, p as c, r as u, s as l, t as d, u as p } from "./forms-BLx4ZzT7.js";
1
+ import { a as s, c as e, d as m, f as r, i as t, l as i, n as o, o as n, p as c, r as u, s as l, t as d, u as p } from "./forms-DYcdlk_h.js";
2
2
  export {
3
3
  c as createForm,
4
4
  d as custom,