{"version":3,"file":"option-list.cjs","names":[],"sources":["../../src/core/option-list.ts"],"sourcesContent":["import { restoreFocus } from '@vielzeug/focus';\nimport { computed, type Readable, signal } from '@vielzeug/ripple';\n\nimport { RefineConfigError } from '../errors';\nimport {\n  createListControl,\n  type ListNavigationAction,\n  type ListNavigationChange,\n  type ListNavigationTypeaheadOptions,\n} from './nav';\nimport { createOutsidePointerDismissal, type DropdownCloseReason, type OverlayOpenReason } from './overlay';\nimport { createDropdownPositioner, type DropdownPositionerOptions } from './positioner';\n\nexport type ListboxItem = object;\n\n/** Placement-only options for the listbox dropdown positioner. */\nexport type ListboxDropdownPlacementOptions = Omit<DropdownPositionerOptions, 'getFloating' | 'getReference'>;\n\nexport type ListboxDropdownOptions<T extends ListboxItem> = {\n  getBoundary: () => HTMLElement | null;\n  getFocusedOptionElement?: () => HTMLElement | null;\n  getItems: () => readonly T[];\n  getOptionId?: (index: number) => string;\n  getPanel: () => HTMLElement | null;\n  getReference: () => HTMLElement | null;\n  getTrigger?: () => HTMLElement | null;\n  isDisabled?: () => boolean;\n  isItemDisabled?: (item: T, index: number) => boolean;\n  keys?: Partial<Record<ListNavigationAction, readonly string[]>>;\n  loop?: boolean;\n  onClose?: (reason: DropdownCloseReason) => void;\n  onNavigate?: (change: ListNavigationChange<T>) => void;\n  onOpen?: (reason: OverlayOpenReason) => void;\n  orientation?: 'both' | 'horizontal' | 'vertical';\n  positioning?: ListboxDropdownPlacementOptions;\n  restoreFocus?: boolean | (() => boolean);\n  signal: AbortSignal;\n  typeahead?: ListNavigationTypeaheadOptions<T>;\n};\n\nexport type ListboxDropdown<T extends ListboxItem> = {\n  [Symbol.dispose](): void;\n  readonly ariaActiveDescendant: Readable<string | null>;\n  readonly ariaExpanded: Readable<string>;\n  close(reason?: DropdownCloseReason): void;\n  /** Closes the dropdown (without restoring focus) and disposes list navigation state. */\n  dispose(): void;\n  /** `true` after `dispose()` has been called. */\n  readonly disposed: boolean;\n  readonly focusedIndex: Readable<number>;\n  getActiveItem(): T | undefined;\n  handleKeydown(event: KeyboardEvent): boolean;\n  readonly isOpen: Readable<boolean>;\n  navigate(action: ListNavigationAction): number;\n  open(reason?: OverlayOpenReason): void;\n  scrollFocusedIntoView(): void;\n  set(index: number): number;\n  toggle(openReason?: OverlayOpenReason, closeReason?: DropdownCloseReason): void;\n  updatePosition(): void;\n};\n\n/**\n * Internal listbox dropdown behavior for select and combobox. It owns only\n * listbox interactions; menu and other overlay families keep their own logic.\n */\nexport const createListboxDropdown = <T extends ListboxItem>(\n  options: ListboxDropdownOptions<T>,\n): ListboxDropdown<T> => {\n  if (typeof options.getBoundary !== 'function') {\n    throw new RefineConfigError('createListboxDropdown: getBoundary is required');\n  }\n\n  if (typeof options.getPanel !== 'function') {\n    throw new RefineConfigError('createListboxDropdown: getPanel is required');\n  }\n\n  if (typeof options.getReference !== 'function') {\n    throw new RefineConfigError('createListboxDropdown: getReference is required');\n  }\n\n  const isOpen = signal(false);\n  const ariaExpanded = computed(() => String(isOpen.value));\n  let stopPositioning: (() => void) | null = null;\n\n  const positioner = createDropdownPositioner({\n    ...options.positioning,\n    getFloating: options.getPanel,\n    getReference: options.getReference,\n  });\n\n  const scrollFocusedIntoView = (): void => {\n    options.getFocusedOptionElement?.()?.scrollIntoView({ block: 'nearest' });\n  };\n\n  const list = createListControl<T>({\n    disabled: computed(() => !isOpen.value),\n    getItems: options.getItems,\n    isItemDisabled: options.isItemDisabled,\n    keys: options.keys,\n    loop: options.loop ?? true,\n    onNavigate: (change) => {\n      scrollFocusedIntoView();\n      options.onNavigate?.(change);\n    },\n    orientation: options.orientation,\n    signal: options.signal,\n    typeahead: options.typeahead,\n  });\n\n  const ariaActiveDescendant = computed<string | null>(() => {\n    const index = list.focusedIndex.value;\n\n    return options.getOptionId && isOpen.value && index >= 0 ? options.getOptionId(index) : null;\n  });\n\n  const shouldRestoreFocus = (): boolean => {\n    return typeof options.restoreFocus === 'function' ? options.restoreFocus() : (options.restoreFocus ?? true);\n  };\n\n  const close = (reason: DropdownCloseReason = 'programmatic', shouldRestore = true): void => {\n    if (!isOpen.value) return;\n\n    isOpen.value = false;\n    list.reset();\n    stopPositioning?.();\n    stopPositioning = null;\n\n    if (shouldRestore && shouldRestoreFocus() && options.getTrigger) restoreFocus(options.getTrigger);\n\n    options.onClose?.(reason);\n  };\n\n  const open = (reason: OverlayOpenReason = 'programmatic'): void => {\n    if (options.isDisabled?.() || isOpen.value) return;\n\n    isOpen.value = true;\n    positioner.update();\n    stopPositioning = positioner.startAutoUpdate?.() ?? null;\n    options.onOpen?.(reason);\n  };\n\n  const toggle = (openReason: OverlayOpenReason = 'click', closeReason: DropdownCloseReason = 'trigger'): void => {\n    if (isOpen.value) close(closeReason);\n    else open(openReason);\n  };\n\n  createOutsidePointerDismissal({\n    getTargets: () => [options.getBoundary(), options.getPanel()],\n    isActive: () => isOpen.value,\n    onDismiss: () => close('outsideClick'),\n    signal: options.signal,\n  });\n\n  let disposed = false;\n\n  const dispose = (): void => {\n    if (disposed) return;\n\n    disposed = true;\n\n    if (isOpen.value) {\n      isOpen.value = false;\n      list.reset();\n      stopPositioning?.();\n      stopPositioning = null;\n    }\n  };\n\n  options.signal.addEventListener('abort', dispose, { once: true });\n\n  const handleKeydown = (event: KeyboardEvent): boolean => {\n    if (!isOpen.value) return false;\n\n    if (event.key === 'Escape') {\n      event.preventDefault();\n      close('escape');\n\n      return true;\n    }\n\n    return list.handleKeydown(event);\n  };\n\n  return {\n    ariaActiveDescendant,\n    ariaExpanded,\n    close,\n    dispose,\n    get disposed() {\n      return disposed;\n    },\n    focusedIndex: list.focusedIndex,\n    getActiveItem: list.getActiveItem,\n    handleKeydown,\n    isOpen,\n    navigate: list.navigate,\n    open,\n    scrollFocusedIntoView,\n    set: list.set,\n    [Symbol.dispose]: dispose,\n    toggle,\n    updatePosition: positioner.update,\n  };\n};\n"],"mappings":"gLAiEA,IAAa,EACX,GACuB,CACvB,GAAI,OAAO,EAAQ,aAAgB,WACjC,MAAM,IAAI,EAAA,kBAAkB,gDAAgD,EAG9E,GAAI,OAAO,EAAQ,UAAa,WAC9B,MAAM,IAAI,EAAA,kBAAkB,6CAA6C,EAG3E,GAAI,OAAO,EAAQ,cAAiB,WAClC,MAAM,IAAI,EAAA,kBAAkB,iDAAiD,EAG/E,IAAM,GAAA,EAAS,EAAA,OAAA,CAAO,EAAK,EACrB,GAAA,EAAe,EAAA,SAAA,KAAe,OAAO,EAAO,KAAK,CAAC,EACpD,EAAuC,KAErC,EAAa,EAAA,yBAAyB,CAC1C,GAAG,EAAQ,YACX,YAAa,EAAQ,SACrB,aAAc,EAAQ,YACxB,CAAC,EAEK,MAAoC,CACxC,EAAQ,0BAA0B,CAAC,EAAE,eAAe,CAAE,MAAO,SAAU,CAAC,CAC1E,EAEM,EAAO,EAAA,kBAAqB,CAChC,UAAA,EAAU,EAAA,SAAA,KAAe,CAAC,EAAO,KAAK,EACtC,SAAU,EAAQ,SAClB,eAAgB,EAAQ,eACxB,KAAM,EAAQ,KACd,KAAM,EAAQ,MAAQ,GACtB,WAAa,GAAW,CACtB,EAAsB,EACtB,EAAQ,aAAa,CAAM,CAC7B,EACA,YAAa,EAAQ,YACrB,OAAQ,EAAQ,OAChB,UAAW,EAAQ,SACrB,CAAC,EAEK,GAAA,EAAuB,EAAA,SAAA,KAA8B,CACzD,IAAM,EAAQ,EAAK,aAAa,MAEhC,OAAO,EAAQ,aAAe,EAAO,OAAS,GAAS,EAAI,EAAQ,YAAY,CAAK,EAAI,IAC1F,CAAC,EAEK,MACG,OAAO,EAAQ,cAAiB,WAAa,EAAQ,aAAa,EAAK,EAAQ,cAAgB,GAGlG,GAAS,EAA8B,eAAgB,EAAgB,KAAe,CACrF,EAAO,QAEZ,EAAO,MAAQ,GACf,EAAK,MAAM,EACX,IAAkB,EAClB,EAAkB,KAEd,GAAiB,EAAmB,GAAK,EAAQ,aAAY,EAAA,EAAA,aAAA,CAAa,EAAQ,UAAU,EAEhG,EAAQ,UAAU,CAAM,EAC1B,EAEM,GAAQ,EAA4B,iBAAyB,CAC7D,EAAQ,aAAa,GAAK,EAAO,QAErC,EAAO,MAAQ,GACf,EAAW,OAAO,EAClB,EAAkB,EAAW,kBAAkB,GAAK,KACpD,EAAQ,SAAS,CAAM,EACzB,EAEM,GAAU,EAAgC,QAAS,EAAmC,YAAoB,CAC1G,EAAO,MAAO,EAAM,CAAW,EAC9B,EAAK,CAAU,CACtB,EAEA,EAAA,8BAA8B,CAC5B,eAAkB,CAAC,EAAQ,YAAY,EAAG,EAAQ,SAAS,CAAC,EAC5D,aAAgB,EAAO,MACvB,cAAiB,EAAM,cAAc,EACrC,OAAQ,EAAQ,MAClB,CAAC,EAED,IAAI,EAAW,GAET,MAAsB,CACtB,IAEJ,EAAW,GAEP,EAAO,QACT,EAAO,MAAQ,GACf,EAAK,MAAM,EACX,IAAkB,EAClB,EAAkB,MAEtB,EAiBA,OAfA,EAAQ,OAAO,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,EAezD,CACL,uBACA,eACA,QACA,UACA,IAAI,UAAW,CACb,OAAO,CACT,EACA,aAAc,EAAK,aACnB,cAAe,EAAK,cACpB,cAvBqB,GAChB,EAAO,MAER,EAAM,MAAQ,UAChB,EAAM,eAAe,EACrB,EAAM,QAAQ,EAEP,IAGF,EAAK,cAAc,CAAK,EATL,GAuB1B,SACA,SAAU,EAAK,SACf,OACA,wBACA,IAAK,EAAK,KACT,OAAO,SAAU,EAClB,SACA,eAAgB,EAAW,MAC7B,CACF"}