{"version":3,"file":"dom-57kEp7KB.cjs","names":[],"sources":["../src/utils/dom.ts"],"sourcesContent":["/**\r\n * DOM utility helpers for @bquery/ui components.\r\n */\r\n\r\n/** Dispatch a custom event from a host element with bubbles + composed. */\r\nexport function dispatch<T = unknown>(\r\n  element: HTMLElement,\r\n  name: string,\r\n  detail?: T,\r\n  options?: Partial<CustomEventInit<T>>\r\n): boolean {\r\n  const init: CustomEventInit<T> = {\r\n    bubbles: true,\r\n    composed: true,\r\n    cancelable: true,\r\n    ...options,\r\n  };\r\n  // Only set detail when provided to respect exactOptionalPropertyTypes\r\n  if (detail !== undefined) {\r\n    init.detail = detail;\r\n  }\r\n  return element.dispatchEvent(new CustomEvent<T>(name, init));\r\n}\r\n\r\n/** Query a slot's assigned elements (flattened). */\r\nexport function getSlotted(shadow: ShadowRoot, slotName?: string): Element[] {\r\n  const selector = slotName ? `slot[name=\"${slotName}\"]` : 'slot:not([name])';\r\n  const slot = shadow.querySelector<HTMLSlotElement>(selector);\r\n  return slot ? slot.assignedElements({ flatten: true }) : [];\r\n}\r\n\r\n/** Returns true if element is focusable. */\r\nexport function isFocusable(el: Element): el is HTMLElement {\r\n  if (!(el instanceof HTMLElement)) return false;\r\n  if ((el as HTMLElement & { disabled?: boolean }).disabled) return false;\r\n  if (el.getAttribute('tabindex') === '-1') return false;\r\n  const style = window.getComputedStyle(el);\r\n  if (style.display === 'none' || style.visibility === 'hidden') return false;\r\n  return true;\r\n}\r\n\r\n/** Get all focusable descendants within a container. */\r\nexport function getFocusableElements(container: Element): HTMLElement[] {\r\n  const candidates = Array.from(\r\n    container.querySelectorAll<HTMLElement>(\r\n      'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"]), details > summary'\r\n    )\r\n  );\r\n  return candidates.filter(isFocusable);\r\n}\r\n\r\n/** Trap focus within a container element. Returns a cleanup function. */\r\nexport function trapFocus(container: HTMLElement): () => void {\r\n  const handleKeyDown = (e: KeyboardEvent) => {\r\n    if (e.key !== 'Tab') return;\r\n    const focusable = getFocusableElements(container);\r\n    if (focusable.length === 0) {\r\n      e.preventDefault();\r\n      return;\r\n    }\r\n    const first = focusable[0];\r\n    const last = focusable[focusable.length - 1];\r\n    if (!first || !last) return;\r\n\r\n    if (e.shiftKey) {\r\n      if (document.activeElement === first) {\r\n        e.preventDefault();\r\n        last.focus();\r\n      }\r\n    } else {\r\n      if (document.activeElement === last) {\r\n        e.preventDefault();\r\n        first.focus();\r\n      }\r\n    }\r\n  };\r\n  container.addEventListener('keydown', handleKeyDown);\r\n  return () => container.removeEventListener('keydown', handleKeyDown);\r\n}\r\n\r\n/** Generate a unique element ID. */\r\nlet _counter = 0;\r\nexport function uniqueId(prefix = 'bq'): string {\r\n  return `${prefix}-${++_counter}`;\r\n}\r\n\r\nfunction parseTimeValueToMs(value: string): number {\r\n  const trimmed = value.trim();\r\n  if (!trimmed) return 0;\r\n  if (trimmed.endsWith('ms')) return Number.parseFloat(trimmed);\r\n  if (trimmed.endsWith('s')) return Number.parseFloat(trimmed) * 1000;\r\n  return Number.parseFloat(trimmed) || 0;\r\n}\r\n\r\nexport function getAnimationTimeoutMs(\r\n  el: Element,\r\n  fallbackDuration = '0ms'\r\n): number {\r\n  const fallbackMs = parseTimeValueToMs(fallbackDuration);\r\n  const view = el.ownerDocument.defaultView;\r\n  if (!view?.getComputedStyle) return fallbackMs;\r\n\r\n  const styles = view.getComputedStyle(el);\r\n  const animationDuration = styles.animationDuration || '';\r\n  const animationDelay = styles.animationDelay || '';\r\n  const animationName = styles.animationName || '';\r\n  const durationValues = animationDuration.split(',');\r\n  const delayValues = animationDelay.split(',');\r\n  const hasTimingValues =\r\n    durationValues.some((value) => value.trim() !== '') ||\r\n    delayValues.some((value) => value.trim() !== '');\r\n  const hasRealAnimationName = animationName.split(',').some((name) => {\r\n    const trimmed = name.trim();\r\n    return trimmed !== '' && trimmed !== 'none';\r\n  });\r\n\r\n  if (!hasTimingValues) return fallbackMs;\r\n\r\n  const durations = durationValues.map((value) => parseTimeValueToMs(value));\r\n  const delays = delayValues.map((value) => parseTimeValueToMs(value));\r\n  const count = Math.max(durations.length, delays.length);\r\n\r\n  let longest = 0;\r\n  for (let i = 0; i < count; i += 1) {\r\n    const duration = durations[i] ?? durations[durations.length - 1] ?? 0;\r\n    const delay = delays[i] ?? delays[delays.length - 1] ?? 0;\r\n    longest = Math.max(longest, duration + delay);\r\n  }\r\n\r\n  if (!hasRealAnimationName) return fallbackMs;\r\n  return longest;\r\n}\r\n\r\n/**\r\n * Manages focus trapping, initial focus, and focus restoration for overlay\r\n * components (Dialog, Drawer). Call from `updated()`.\r\n */\r\nexport interface OverlayFocusState {\r\n  _wasOpen?: boolean;\r\n  _previousFocus?: HTMLElement | null;\r\n  _focusRaf?: number;\r\n  _releaseFocus?: () => void;\r\n}\r\n\r\nexport function updateOverlayFocus(\r\n  host: HTMLElement,\r\n  state: OverlayFocusState,\r\n  panelSelector: string\r\n): void {\r\n  const wasOpen = state._wasOpen === true;\r\n  const isOpen = host.hasAttribute('open');\r\n\r\n  if (isOpen && !wasOpen) {\r\n    state._wasOpen = true;\r\n    if (!state._previousFocus) {\r\n      state._previousFocus = document.activeElement as HTMLElement | null;\r\n    }\r\n    const panel = host.shadowRoot?.querySelector(\r\n      panelSelector\r\n    ) as HTMLElement | null;\r\n    if (panel) {\r\n      state._releaseFocus?.();\r\n      state._releaseFocus = trapFocus(panel);\r\n      if (state._focusRaf !== undefined) cancelAnimationFrame(state._focusRaf);\r\n      state._focusRaf = requestAnimationFrame(() => {\r\n        delete state._focusRaf;\r\n        if (!host.hasAttribute('open') || !host.isConnected) return;\r\n        const focusable = panel.querySelector<HTMLElement>(\r\n          'button, a[href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\r\n        );\r\n        (focusable ?? panel).focus();\r\n      });\r\n    }\r\n  } else if (!isOpen && wasOpen) {\r\n    state._wasOpen = false;\r\n    if (state._focusRaf !== undefined) cancelAnimationFrame(state._focusRaf);\r\n    delete state._focusRaf;\r\n    state._releaseFocus?.();\r\n    delete state._releaseFocus;\r\n    const prev = state._previousFocus;\r\n    if (prev && typeof prev.focus === 'function') {\r\n      prev.focus();\r\n    }\r\n    delete state._previousFocus;\r\n  } else if (!isOpen) {\r\n    state._wasOpen = false;\r\n  }\r\n}\r\n\r\nexport function cleanupOverlayFocus(state: OverlayFocusState): void {\r\n  if (state._focusRaf !== undefined) cancelAnimationFrame(state._focusRaf);\r\n  state._releaseFocus?.();\r\n  const prev = state._previousFocus;\r\n  if (prev && typeof prev.focus === 'function') prev.focus();\r\n  delete state._previousFocus;\r\n  delete state._wasOpen;\r\n  delete state._focusRaf;\r\n  delete state._releaseFocus;\r\n}\r\n"],"mappings":";;;;;AAKA,SAAgB,SACd,SACA,MACA,QACA,SACS;CACT,MAAM,OAA2B;EAC/B,SAAS;EACT,UAAU;EACV,YAAY;EACZ,GAAG;EACJ;AAED,KAAI,WAAW,KAAA,EACb,MAAK,SAAS;AAEhB,QAAO,QAAQ,cAAc,IAAI,YAAe,MAAM,KAAK,CAAC;;;AAI9D,SAAgB,WAAW,QAAoB,UAA8B;CAC3E,MAAM,WAAW,WAAW,cAAc,SAAS,MAAM;CACzD,MAAM,OAAO,OAAO,cAA+B,SAAS;AAC5D,QAAO,OAAO,KAAK,iBAAiB,EAAE,SAAS,MAAM,CAAC,GAAG,EAAE;;;AAI7D,SAAgB,YAAY,IAAgC;AAC1D,KAAI,EAAE,cAAc,aAAc,QAAO;AACzC,KAAK,GAA4C,SAAU,QAAO;AAClE,KAAI,GAAG,aAAa,WAAW,KAAK,KAAM,QAAO;CACjD,MAAM,QAAQ,OAAO,iBAAiB,GAAG;AACzC,KAAI,MAAM,YAAY,UAAU,MAAM,eAAe,SAAU,QAAO;AACtE,QAAO;;;AAIT,SAAgB,qBAAqB,WAAmC;AAMtE,QALmB,MAAM,KACvB,UAAU,iBACR,iKACD,CAEI,CAAW,OAAO,YAAY;;;AAIvC,SAAgB,UAAU,WAAoC;CAC5D,MAAM,iBAAiB,MAAqB;AAC1C,MAAI,EAAE,QAAQ,MAAO;EACrB,MAAM,YAAY,qBAAqB,UAAU;AACjD,MAAI,UAAU,WAAW,GAAG;AAC1B,KAAE,gBAAgB;AAClB;;EAEF,MAAM,QAAQ,UAAU;EACxB,MAAM,OAAO,UAAU,UAAU,SAAS;AAC1C,MAAI,CAAC,SAAS,CAAC,KAAM;AAErB,MAAI,EAAE;OACA,SAAS,kBAAkB,OAAO;AACpC,MAAE,gBAAgB;AAClB,SAAK,OAAO;;aAGV,SAAS,kBAAkB,MAAM;AACnC,KAAE,gBAAgB;AAClB,SAAM,OAAO;;;AAInB,WAAU,iBAAiB,WAAW,cAAc;AACpD,cAAa,UAAU,oBAAoB,WAAW,cAAc;;;AAItE,IAAI,WAAW;AACf,SAAgB,SAAS,SAAS,MAAc;AAC9C,QAAO,GAAG,OAAO,GAAG,EAAE;;AAGxB,SAAS,mBAAmB,OAAuB;CACjD,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,CAAC,QAAS,QAAO;AACrB,KAAI,QAAQ,SAAS,KAAK,CAAE,QAAO,OAAO,WAAW,QAAQ;AAC7D,KAAI,QAAQ,SAAS,IAAI,CAAE,QAAO,OAAO,WAAW,QAAQ,GAAG;AAC/D,QAAO,OAAO,WAAW,QAAQ,IAAI;;AAGvC,SAAgB,sBACd,IACA,mBAAmB,OACX;CACR,MAAM,aAAa,mBAAmB,iBAAiB;CACvD,MAAM,OAAO,GAAG,cAAc;AAC9B,KAAI,CAAC,MAAM,iBAAkB,QAAO;CAEpC,MAAM,SAAS,KAAK,iBAAiB,GAAG;CACxC,MAAM,oBAAoB,OAAO,qBAAqB;CACtD,MAAM,iBAAiB,OAAO,kBAAkB;CAChD,MAAM,gBAAgB,OAAO,iBAAiB;CAC9C,MAAM,iBAAiB,kBAAkB,MAAM,IAAI;CACnD,MAAM,cAAc,eAAe,MAAM,IAAI;CAC7C,MAAM,kBACJ,eAAe,MAAM,UAAU,MAAM,MAAM,KAAK,GAAG,IACnD,YAAY,MAAM,UAAU,MAAM,MAAM,KAAK,GAAG;CAClD,MAAM,uBAAuB,cAAc,MAAM,IAAI,CAAC,MAAM,SAAS;EACnE,MAAM,UAAU,KAAK,MAAM;AAC3B,SAAO,YAAY,MAAM,YAAY;GACrC;AAEF,KAAI,CAAC,gBAAiB,QAAO;CAE7B,MAAM,YAAY,eAAe,KAAK,UAAU,mBAAmB,MAAM,CAAC;CAC1E,MAAM,SAAS,YAAY,KAAK,UAAU,mBAAmB,MAAM,CAAC;CACpE,MAAM,QAAQ,KAAK,IAAI,UAAU,QAAQ,OAAO,OAAO;CAEvD,IAAI,UAAU;AACd,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK,GAAG;EACjC,MAAM,WAAW,UAAU,MAAM,UAAU,UAAU,SAAS,MAAM;EACpE,MAAM,QAAQ,OAAO,MAAM,OAAO,OAAO,SAAS,MAAM;AACxD,YAAU,KAAK,IAAI,SAAS,WAAW,MAAM;;AAG/C,KAAI,CAAC,qBAAsB,QAAO;AAClC,QAAO;;AAcT,SAAgB,mBACd,MACA,OACA,eACM;CACN,MAAM,UAAU,MAAM,aAAa;CACnC,MAAM,SAAS,KAAK,aAAa,OAAO;AAExC,KAAI,UAAU,CAAC,SAAS;AACtB,QAAM,WAAW;AACjB,MAAI,CAAC,MAAM,eACT,OAAM,iBAAiB,SAAS;EAElC,MAAM,QAAQ,KAAK,YAAY,cAC7B,cACD;AACD,MAAI,OAAO;AACT,SAAM,iBAAiB;AACvB,SAAM,gBAAgB,UAAU,MAAM;AACtC,OAAI,MAAM,cAAc,KAAA,EAAW,sBAAqB,MAAM,UAAU;AACxE,SAAM,YAAY,4BAA4B;AAC5C,WAAO,MAAM;AACb,QAAI,CAAC,KAAK,aAAa,OAAO,IAAI,CAAC,KAAK,YAAa;AAIrD,KAHkB,MAAM,cACtB,8EAED,IAAa,OAAO,OAAO;KAC5B;;YAEK,CAAC,UAAU,SAAS;AAC7B,QAAM,WAAW;AACjB,MAAI,MAAM,cAAc,KAAA,EAAW,sBAAqB,MAAM,UAAU;AACxE,SAAO,MAAM;AACb,QAAM,iBAAiB;AACvB,SAAO,MAAM;EACb,MAAM,OAAO,MAAM;AACnB,MAAI,QAAQ,OAAO,KAAK,UAAU,WAChC,MAAK,OAAO;AAEd,SAAO,MAAM;YACJ,CAAC,OACV,OAAM,WAAW;;AAIrB,SAAgB,oBAAoB,OAAgC;AAClE,KAAI,MAAM,cAAc,KAAA,EAAW,sBAAqB,MAAM,UAAU;AACxE,OAAM,iBAAiB;CACvB,MAAM,OAAO,MAAM;AACnB,KAAI,QAAQ,OAAO,KAAK,UAAU,WAAY,MAAK,OAAO;AAC1D,QAAO,MAAM;AACb,QAAO,MAAM;AACb,QAAO,MAAM;AACb,QAAO,MAAM"}