{"version":3,"file":"tabs.cjs","names":[],"sources":["../src/disclosure/tabs/tabs.ts"],"sourcesContent":["import {\n  bind,\n  createContext,\n  define,\n  getHost,\n  html,\n  onCleanup,\n  onMounted,\n  prop,\n  provide,\n  ref,\n  useEmit,\n} from '@vielzeug/ore';\nimport { computed, type Readable, signal, watch } from '@vielzeug/ripple';\nimport { createInteraction, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { sizableBundle, themableBundle } from '../../shared';\nimport { colorThemeMixin } from '../../styles';\nimport type { ComponentSize, SurfaceVariant, ThemeColor } from '../../types';\nimport styles from './tabs.css?inline';\n\n/** Context provided by ore-tabs to its ore-tab-item and ore-tab-panel children. */\nexport type TabsContext = {\n  color: Readable<ThemeColor | undefined>;\n  orientation: Readable<'horizontal' | 'vertical'>;\n  size: Readable<ComponentSize | undefined>;\n  value: Readable<string | undefined>;\n  variant: Readable<SurfaceVariant | undefined>;\n};\n/** Injection key for the tabs context. */\nexport const TABS_CTX = createContext<TabsContext>('TabsContext');\n\nexport type OreTabsEvents = {\n  change: { value: string };\n};\n\nexport type OreTabsProps = {\n  /**\n   * Keyboard activation mode.\n   * - `'auto'` (default): Selecting a tab on arrow-key focus immediately activates it (ARIA recommendation for most cases).\n   * - `'manual'`: Arrow keys only move focus; the user must press Enter or Space to activate the focused tab.\n   */\n  activation?: 'auto' | 'manual';\n  /** Theme color */\n  color?: ThemeColor;\n  /** Accessible label for the tablist (passed as aria-label). Use when there is no visible heading labelling the tabs. */\n  label?: string;\n  /** Tab list orientation */\n  orientation?: 'horizontal' | 'vertical';\n  /** Component size */\n  size?: ComponentSize;\n  /** Currently selected tab value */\n  value?: string;\n  /** Visual style variant */\n  variant?: SurfaceVariant;\n};\n\n/**\n * Tabs container. Manages tab selection and syncs state to child tab items and panels.\n *\n * @element ore-tabs\n * @element ore-tab-item - Child element for tab buttons (auto-discovered)\n * @element ore-tab-panel - Child element for tab content (auto-discovered)\n *\n * @attr {string} value - The value of the currently selected tab\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'ghost' | 'glass' | 'frost'\n * @attr {string} size - Size: 'sm' | 'md' | 'lg'\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n *\n * @fires change - Emitted when the active tab changes with detail: { value: string }\n *\n * @slot tabs - Place `ore-tab-item` elements here\n * @slot - Place `ore-tab-panel` elements here\n *\n * @cssprop --tabs-radius - Border radius of the tablist container and panels\n * @cssprop --tabs-transition - Transition duration/easing for the active indicator\n * @cssprop --tabs-indicator-color - Color of the sliding active indicator line\n * @cssprop --tabs-bg - Background of the host element (flat variant)\n * @cssprop --tabs-tablist-bg - Tablist container background (solid/glass/frost variants)\n * @cssprop --tabs-tablist-border-color - Tablist container border color\n * @part tablist - Container that holds the slotted tab items\n * @part indicator - Active tab indicator element\n * @part panels - Container that holds tab panel content\n * @example\n * ```html\n * <ore-tabs value=\"tab1\" variant=\"bordered\">\n *   <ore-tab-item slot=\"tabs\" value=\"tab1\">Overview</ore-tab-item>\n *   <ore-tab-item slot=\"tabs\" value=\"tab2\">Settings</ore-tab-item>\n *   <ore-tab-panel value=\"tab1\"><p>Overview content</p></ore-tab-panel>\n *   <ore-tab-panel value=\"tab2\"><p>Settings content</p></ore-tab-panel>\n * </ore-tabs>\n * ```\n */\nexport const TABS_TAG = 'ore-tabs' as const;\ndefine<OreTabsProps>(TABS_TAG, {\n  props: {\n    ...themableBundle,\n    ...sizableBundle,\n    activation: prop.oneOf(['auto', 'manual'] as const, 'auto'),\n    label: prop.string(),\n    orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'horizontal'),\n    value: prop.string(),\n    variant: prop.string<SurfaceVariant>(),\n  },\n  setup(props) {\n    const el = getHost();\n    const emit = useEmit<OreTabsEvents>();\n\n    const shadowRoot = el.shadowRoot;\n    const tablistRef = ref<HTMLElement>();\n    const indicatorRef = ref<HTMLElement>();\n    const selectedValue = signal<string | undefined>(props.value.value);\n    const isManualActivation = () => props.activation.value === 'manual';\n    const isVertical = () => props.orientation.value === 'vertical';\n\n    bind({\n      attr: {\n        value: () => selectedValue.value ?? null,\n      },\n    });\n\n    const getTabs = () => [...el.querySelectorAll<HTMLElement>(':scope > ore-tab-item[slot=\"tabs\"]')];\n    const getEnabledTabs = () => getTabs().filter((t) => !t.hasAttribute('disabled'));\n    const focusTab = (tab: HTMLElement | undefined) => {\n      if (!tab) return;\n\n      const focusable = tab.shadowRoot?.querySelector<HTMLElement>('[role=\"tab\"]') ?? tab;\n\n      focusable.focus();\n    };\n\n    // ────────────────────────────────────────────────────────────────\n    // Selection State Management\n    // ────────────────────────────────────────────────────────────────\n\n    const setSelection = (value: string | undefined, shouldEmit = false) => {\n      selectedValue.value = value;\n\n      if (shouldEmit && value) emit('change', { value });\n    };\n\n    const ensureSelection = () => {\n      const tabs = getTabs();\n\n      if (tabs.length === 0) return; // No tabs yet, keep current selection\n\n      const current = selectedValue.value;\n      const hasCurrent = current\n        ? tabs.some((tab) => tab.getAttribute('value') === current && !tab.hasAttribute('disabled'))\n        : false;\n\n      if (hasCurrent) return;\n\n      const firstEnabled = tabs.find((tab) => !tab.hasAttribute('disabled'))?.getAttribute('value') ?? undefined;\n\n      setSelection(firstEnabled, false);\n    };\n\n    watch(props.value, (value) => {\n      selectedValue.value = value;\n      ensureSelection();\n    });\n\n    // ────────────────────────────────────────────────────────────────\n    // List Control for Keyboard Navigation\n    // ────────────────────────────────────────────────────────────────\n\n    const listControl = createListControl({\n      direction: () => elementDirection(el),\n      getItems: () => getEnabledTabs(),\n      loop: true,\n      onNavigate: ({ item }) => {\n        focusTab(item);\n\n        if (!isManualActivation()) {\n          const value = item.getAttribute('value');\n\n          if (value) setSelection(value, true);\n        }\n      },\n      orientation: () => (isVertical() ? 'vertical' : 'both'),\n      signal: lifecycleSignal(onCleanup),\n    });\n\n    // ────────────────────────────────────────────────────────────────\n    // Context & Indicator Management\n    // ────────────────────────────────────────────────────────────────\n\n    provide(TABS_CTX, {\n      color: props.color,\n      orientation: computed(() => props.orientation.value ?? 'horizontal'),\n      size: props.size,\n      value: selectedValue,\n      variant: props.variant,\n    });\n\n    const moveIndicator = (activeTab: HTMLElement | undefined) => {\n      const indicator = indicatorRef.value;\n      const tablist = tablistRef.value;\n\n      if (!indicator || !tablist || !activeTab) return;\n\n      const tabRect = activeTab.getBoundingClientRect();\n      const listRect = tablist.getBoundingClientRect();\n\n      if (isVertical()) {\n        const y = tabRect.top - listRect.top + tablist.scrollTop;\n\n        indicator.style.transition = 'none';\n        indicator.style.height = `${tabRect.height}px`;\n        indicator.style.width = '';\n        indicator.style.left = '0';\n        indicator.style.top = '0';\n        void indicator.offsetWidth;\n        indicator.style.transition = '';\n        indicator.style.transform = `translateY(${y}px)`;\n      } else {\n        const x = tabRect.left - listRect.left + tablist.scrollLeft;\n\n        indicator.style.transition = 'none';\n        indicator.style.width = `${tabRect.width}px`;\n        indicator.style.height = '';\n        indicator.style.top = '';\n        indicator.style.left = '0';\n        void indicator.offsetWidth;\n        indicator.style.transition = '';\n        indicator.style.transform = `translateX(${x}px)`;\n      }\n    };\n\n    const updateIndicator = () => {\n      const value = selectedValue.value;\n\n      if (!value) return;\n\n      const activeTab = getTabs().find((t) => t.getAttribute('value') === value);\n\n      moveIndicator(activeTab);\n    };\n\n    watch(selectedValue, () => {\n      requestAnimationFrame(updateIndicator);\n    });\n\n    // ────────────────────────────────────────────────────────────────\n    // Event Handlers\n    // ────────────────────────────────────────────────────────────────\n\n    const handleTabClick = (e: Event) => {\n      const tab = e\n        .composedPath()\n        .find((node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-tab-item');\n\n      if (!tab || tab.hasAttribute('disabled')) return;\n\n      // Guard: only respond to tab-items that belong to THIS tabs instance\n      if (tab.closest('ore-tabs') !== el) return;\n\n      const value = tab.getAttribute('value');\n\n      if (!value || value === selectedValue.value) return;\n\n      setSelection(value, true);\n    };\n\n    const activateFocusedTab = (): void => {\n      const tabs = getEnabledTabs();\n      const focusedTab = tabs.find(\n        (tab) => tab === document.activeElement || tab.shadowRoot?.activeElement === document.activeElement,\n      );\n      const focusedValue = focusedTab?.getAttribute('value');\n\n      if (focusedValue && focusedValue !== selectedValue.value) setSelection(focusedValue, true);\n    };\n\n    const manualActivationPress = createInteraction({\n      disabled: computed(() => !isManualActivation()),\n      onPress: activateFocusedTab,\n    });\n\n    const handleKeydown = (e: KeyboardEvent) => {\n      const tabs = getEnabledTabs();\n\n      if (tabs.length === 0) return;\n\n      const path = e.composedPath();\n      const activeTabFromEvent = path.find(\n        (node): node is HTMLElement => node instanceof HTMLElement && node.localName === 'ore-tab-item',\n      );\n\n      const tabFromEvent = activeTabFromEvent ?? tabs.find((t) => t.getAttribute('value') === selectedValue.value);\n      const focused = tabFromEvent ? tabs.indexOf(tabFromEvent) : -1;\n\n      if (focused >= 0) listControl.set(focused);\n\n      if (listControl.handleKeydown(e)) return;\n\n      manualActivationPress.handleKeydown(e);\n    };\n\n    bind({\n      on: {\n        click: handleTabClick,\n        keydown: handleKeydown,\n      },\n    });\n\n    // ────────────────────────────────────────────────────────────────\n    // Lifecycle\n    // ────────────────────────────────────────────────────────────────\n\n    onMounted(() => {\n      const syncSelection = () => {\n        ensureSelection();\n        updateIndicator();\n      };\n\n      const tabsSlot = shadowRoot?.querySelector<HTMLSlotElement>('slot[name=\"tabs\"]');\n\n      if (tabsSlot) {\n        tabsSlot.addEventListener('slotchange', syncSelection);\n      }\n\n      syncSelection();\n      requestAnimationFrame(syncSelection);\n\n      return () => {\n        if (tabsSlot) {\n          tabsSlot.removeEventListener('slotchange', syncSelection);\n        }\n      };\n    });\n\n    return html`\n      <div class=\"tablist-wrapper\">\n        <div\n          role=\"tablist\"\n          ref=\"${tablistRef}\"\n          part=\"tablist\"\n          aria-orientation=\"${props.orientation}\"\n          aria-label=\"${props.label}\">\n          <slot name=\"tabs\"></slot>\n        </div>\n        <div class=\"indicator\" ref=\"${indicatorRef}\" part=\"indicator\"></div>\n      </div>\n      <div class=\"panels\" part=\"panels\">\n        <slot></slot>\n      </div>\n    `;\n  },\n  styles: [colorThemeMixin, styles],\n});\n"],"mappings":"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"}