@cdevhub/ngx-tw 0.6.2 → 0.8.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.
- package/fesm2022/cdevhub-ngx-tw-alert.mjs +8 -1
- package/fesm2022/cdevhub-ngx-tw-alert.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-badge.mjs +11 -1
- package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-button.mjs +7 -1
- package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +2 -2
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-popover-testing.mjs +213 -0
- package/fesm2022/cdevhub-ngx-tw-popover-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-popover.mjs +10 -4
- package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-radio.mjs +2 -2
- package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs +29 -9
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tooltip-testing.mjs +195 -0
- package/fesm2022/cdevhub-ngx-tw-tooltip-testing.mjs.map +1 -0
- package/index.json +1 -1
- package/package.json +9 -1
- package/types/cdevhub-ngx-tw-popover-testing.d.ts +138 -0
- package/types/cdevhub-ngx-tw-tabs.d.ts +23 -3
- package/types/cdevhub-ngx-tw-theme.d.ts +18 -1
- package/types/cdevhub-ngx-tw-tooltip-testing.d.ts +131 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdevhub-ngx-tw-tabs.mjs","sources":["../../../projects/ngx-tw/tabs/tabs.ts","../../../projects/ngx-tw/tabs/tabs.html","../../../projects/ngx-tw/tabs/cdevhub-ngx-tw-tabs.ts"],"sourcesContent":["import {\n afterNextRender,\n ChangeDetectionStrategy,\n Component,\n computed,\n contentChild,\n contentChildren,\n DestroyRef,\n Directive,\n effect,\n ElementRef,\n inject,\n input,\n linkedSignal,\n model,\n output,\n signal,\n TemplateRef,\n untracked,\n viewChild,\n viewChildren,\n type AfterViewInit,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { type FocusableOption, FocusKeyManager, LiveAnnouncer } from '@angular/cdk/a11y';\nimport { Directionality } from '@angular/cdk/bidi';\nimport { tv } from 'tailwind-variants';\nimport { twMerge } from 'tailwind-merge';\nimport {\n getActiveTriggerClasses,\n getInactiveTriggerClasses,\n tabTriggerVariants,\n type TabTriggerVariant,\n type TwColor,\n type TwSize,\n} from '@cdevhub/ngx-tw/core';\n\n/** Visual style of the tab strip. */\nexport type TabsVariant = TabTriggerVariant;\n\n// ── tv() config (component-local slots only — trigger config is shared) ──\n\nconst tabsLayoutVariants = tv(\n {\n slots: {\n root: 'flex',\n tablist: 'relative flex shrink-0',\n tablistInner: 'flex overflow-x-auto',\n panel: 'min-w-0 flex-1',\n scrollButton:\n 'inline-flex items-center justify-center shrink-0 cursor-pointer transition-colors duration-normal motion-reduce:transition-none disabled:opacity-30 disabled:cursor-default hover:bg-surface-muted',\n // No focus-visible ring: the close control is pointer-only by design\n // (see the ARIA note in tabs.html), so a focus style would be dead code.\n closeButton:\n 'inline-flex items-center justify-center rounded-md cursor-pointer transition-colors duration-normal motion-reduce:transition-none hover:bg-surface-muted',\n },\n variants: {\n variant: {\n underline: {\n tablist: 'border-b border-border',\n tablistInner: 'gap-0',\n panel: 'pt-4',\n },\n enclosed: {\n tablist: 'border-b border-border',\n tablistInner: 'gap-0',\n panel: 'pt-4',\n },\n pill: {\n tablist: 'bg-surface-muted rounded-xl p-1',\n tablistInner: 'gap-1',\n panel: 'pt-4',\n },\n },\n // Square interactive-target scale: see CLAUDE.md \"Icon Sizing\".\n size: {\n xs: { scrollButton: 'size-5', closeButton: 'size-6' },\n sm: { scrollButton: 'size-5', closeButton: 'size-7' },\n md: { scrollButton: 'size-6', closeButton: 'size-8' },\n lg: { scrollButton: 'size-7', closeButton: 'size-9' },\n xl: { scrollButton: 'size-8', closeButton: 'size-9' },\n },\n orientation: {\n horizontal: {\n root: 'flex-col',\n tablist: '',\n tablistInner: 'flex-row',\n },\n vertical: {\n root: 'flex-row',\n tablistInner: 'flex-col overflow-y-auto overflow-x-hidden',\n },\n },\n },\n compoundVariants: [\n {\n variant: 'underline',\n orientation: 'vertical',\n class: {\n tablist: 'border-b-0 border-r border-border',\n panel: 'pt-0 pl-4',\n },\n },\n {\n variant: 'enclosed',\n orientation: 'vertical',\n class: {\n tablist: 'border-b-0 border-r border-border',\n panel: 'pt-0 pl-4',\n },\n },\n {\n variant: 'pill',\n orientation: 'vertical',\n class: {\n panel: 'pt-0 pl-4',\n },\n },\n ],\n defaultVariants: {\n variant: 'underline',\n size: 'md',\n orientation: 'horizontal',\n },\n },\n { twMerge: true },\n);\n\n// ── Per-variant/orientation trigger additions (vertical underline/enclosed\n// re-route the active border axis, hence these compound overrides). ──\n\nfunction getTriggerOrientationExtras(\n variant: TabsVariant,\n orientation: 'horizontal' | 'vertical',\n): string {\n if (orientation !== 'vertical') return '';\n switch (variant) {\n case 'underline':\n return 'border-b-0 -mb-0 border-r-2 border-transparent -mr-px';\n case 'enclosed':\n return '-mb-0 -mr-px';\n case 'pill':\n return '';\n }\n}\n\n// ── Directives ──\n\n@Directive({ selector: 'ng-template[twTabTrigger]' })\nexport class TabTriggerDirective {\n readonly templateRef = inject(TemplateRef);\n}\n\n@Directive({ selector: 'ng-template[twTabContent]' })\nexport class TabContentDirective {\n readonly templateRef = inject(TemplateRef);\n}\n\n// ── TabComponent ──\n\n@Component({\n selector: 'tw-tab',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <ng-template #implicitContent>\n <ng-content />\n </ng-template>\n `,\n host: {\n 'style': 'display: none',\n },\n})\nexport class TabComponent {\n /** Unique identifier for this tab. Used to match the active tab value. */\n readonly value = input.required<string>();\n\n /** Plain text label shown in the trigger. Ignored when a custom trigger template is provided. Defaults to an empty string. */\n readonly label = input('');\n\n /** When true, the tab cannot be selected and is skipped by keyboard navigation. Defaults to false. */\n readonly disabled = input(false);\n\n /** When true, a dismiss control is rendered in the tab trigger. The control is pointer-only; the keyboard gesture is Delete on the focused tab. Defaults to false. */\n readonly closable = input(false);\n\n /** When true, the tab panel content is only instantiated when the tab becomes active for the first time. Defaults to false. */\n readonly lazy = input(false);\n\n /** @internal Custom trigger template, if provided. */\n readonly triggerTemplate = contentChild(TabTriggerDirective);\n\n /** @internal Custom content template, if provided via twTabContent. */\n readonly contentTemplate = contentChild(TabContentDirective);\n\n /** @internal Implicit content template wrapping ng-content. */\n readonly implicitContentTemplate = viewChild<TemplateRef<unknown>>('implicitContent');\n}\n\n// ── Tab trigger element wrapper ──\n\n@Directive({\n selector: '[twTabTriggerElement]',\n host: {\n '[attr.tabindex]': 'tabIndex()',\n },\n})\nexport class TabTriggerElementDirective implements FocusableOption {\n readonly elementRef = inject(ElementRef<HTMLElement>);\n\n /**\n * Whether this trigger owns the tablist's single tab stop. Drives\n * `tabindex`. Normally the active tab, but the parent falls back to the\n * first enabled tab when `value` matches nothing, so the tablist is never\n * left with zero tab stops. Defaults to `false`.\n */\n readonly isFocusStop = input(false);\n\n /** Whether this trigger is disabled. Read by `FocusKeyManager` so disabled triggers are skipped during arrow navigation. Defaults to `false`. */\n readonly isDisabled = input(false);\n\n readonly tabIndex = computed(() => this.isFocusStop() ? 0 : -1);\n\n /** Moves DOM focus to the trigger element. Called by `FocusKeyManager` during arrow-key navigation. */\n focus(): void {\n this.elementRef.nativeElement.focus();\n }\n\n /**\n * FocusableOption getter. NOTE: we cannot expose the `isDisabled` input\n * signal directly — the signal function itself is always truthy, which would\n * make FocusKeyManager treat every trigger as disabled. We resolve the\n * signal here so the manager skips the right items.\n */\n get disabled(): boolean {\n return this.isDisabled();\n }\n}\n\n// ── TabsComponent ──\n\nlet nextId = 0;\n\n@Component({\n selector: 'tw-tabs',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [NgTemplateOutlet, TabTriggerElementDirective],\n templateUrl: './tabs.html',\n host: {\n '[class]': 'rootClasses()',\n },\n})\nexport class TabsComponent implements AfterViewInit {\n /** Controls the visual style of the tab strip. Defaults to 'underline'. */\n readonly variant = input<TabsVariant>('underline');\n\n /** Sets the semantic color for active tab indicators and highlights. Defaults to 'primary'. */\n readonly color = input<TwColor>('primary');\n\n /** Controls padding, font size, and icon size of tab triggers. Defaults to 'md'. */\n readonly size = input<TwSize>('md');\n\n /** Layout direction of the tab strip. Defaults to 'horizontal'. */\n readonly orientation = input<'horizontal' | 'vertical'>('horizontal');\n\n /** When true, tab triggers stretch to fill the available width equally. Defaults to false. */\n readonly fitted = input(false);\n\n /** The value of the currently active tab. Two-way bound. Updates when the user selects a tab. Defaults to an empty string. */\n readonly value = model<string>('');\n\n /** Fires when a closable tab is dismissed, either by clicking its close control or by pressing Delete while the tab has focus. Payload is the tab's value. */\n readonly closed = output<string>();\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly liveAnnouncer = inject(LiveAnnouncer);\n private readonly directionality = inject(Directionality, { optional: true });\n\n /**\n * Live layout direction, read straight from CDK's `Directionality.valueSignal`\n * (CDK 22+) so the key-manager effect re-runs on a runtime `dir` flip with no\n * manual subscription mirror. Without a live direction the tablist hardcodes\n * 'ltr' and ArrowRight moves to the next tab in DOM order but the *previous*\n * tab visually in an RTL locale. CdkStepper and `tw-split` already honour\n * Directionality; `tw-paginator` reads `valueSignal` the same way.\n */\n private readonly layoutDirection = computed<'ltr' | 'rtl'>(\n () => this.directionality?.valueSignal() ?? 'ltr',\n );\n\n /** @internal */\n readonly tabs = contentChildren(TabComponent);\n\n /** @internal */\n readonly triggerElements = viewChildren(TabTriggerElementDirective);\n\n /** @internal */\n readonly tablistInnerRef = viewChild<ElementRef<HTMLElement>>('tablistInner');\n\n private readonly componentId = `tw-tabs-${nextId++}`;\n\n // ── Active tab tracking ──\n\n readonly activeValue = linkedSignal(() => this.value());\n\n // ── Lazy tab tracking — Set of tab values that have been activated at least once ──\n readonly activatedTabs = signal(new Set<string>());\n\n // ── Scroll overflow signals ──\n readonly canScrollStart = signal(false);\n readonly canScrollEnd = signal(false);\n\n private resizeObserver: ResizeObserver | null = null;\n private scrollCleanup: (() => void) | null = null;\n\n // ── Variant classes ──\n\n private readonly layoutResult = computed(() =>\n tabsLayoutVariants({\n variant: this.variant(),\n size: this.size(),\n orientation: this.orientation(),\n }),\n );\n\n private readonly triggerResult = computed(() =>\n tabTriggerVariants({\n variant: this.variant(),\n color: this.color(),\n size: this.size(),\n fitted: this.fitted(),\n }),\n );\n\n readonly rootClasses = computed(() => this.layoutResult().root());\n readonly tablistClasses = computed(() => this.layoutResult().tablist());\n readonly tablistInnerClasses = computed(() => this.layoutResult().tablistInner());\n readonly baseTriggerClasses = computed(() => this.triggerResult().trigger());\n readonly panelClasses = computed(() => this.layoutResult().panel());\n readonly scrollButtonClasses = computed(() => this.layoutResult().scrollButton());\n readonly closeButtonClasses = computed(() => this.layoutResult().closeButton());\n\n // ── Per-trigger active/inactive class computation ──\n\n /** @internal Get combined trigger classes for a given tab. */\n getTriggerClasses(_tabValue: string, isActive: boolean): string {\n const base = this.baseTriggerClasses();\n const orientationExtras = getTriggerOrientationExtras(this.variant(), this.orientation());\n const state = isActive\n ? getActiveTriggerClasses(this.variant(), this.color(), this.orientation())\n : getInactiveTriggerClasses(this.variant());\n return twMerge(base, orientationExtras, state);\n }\n\n // ── ID generation ──\n /** Builds the DOM id for a tab trigger, used by the panel's `aria-labelledby`. */\n getTabId(tabValue: string): string {\n return `${this.componentId}-tab-${tabValue}`;\n }\n\n /** Builds the DOM id for a tab panel, used by the trigger's `aria-controls`. */\n getPanelId(tabValue: string): string {\n return `${this.componentId}-panel-${tabValue}`;\n }\n\n // ── Tab visibility ──\n\n /** Whether the tab with the given value is the currently active one. */\n isTabActive(tabValue: string): boolean {\n return this.activeValue() === tabValue;\n }\n\n /**\n * @internal Value of the tab that owns the tablist's single tab stop.\n *\n * The active tab whenever it exists and is enabled. When `value` matches no\n * tab — a stale id, a value cleared after init, or an active tab that is\n * disabled — the first enabled tab takes the tab stop instead (standard\n * roving-tabindex recovery). Without the fallback every trigger renders\n * `tabindex=\"-1\"` and the whole tablist silently leaves the tab order\n * (WCAG SC 2.1.1).\n */\n readonly focusStopValue = computed<string | null>(() => {\n const tabs = this.tabs();\n const active = this.activeValue();\n const activeTab = tabs.find(t => t.value() === active && !t.disabled());\n if (activeTab) return activeTab.value();\n return tabs.find(t => !t.disabled())?.value() ?? null;\n });\n\n /** Whether the tab with the given value owns the tablist's single tab stop. */\n isTabFocusStop(tabValue: string): boolean {\n return this.focusStopValue() === tabValue;\n }\n\n /** Whether a tab's panel content should be in the DOM. Always true for eager tabs; for lazy tabs, true only once the tab has been activated at least once. */\n shouldRenderPanel(tab: TabComponent): boolean {\n const value = tab.value();\n if (!tab.lazy()) {\n return true;\n }\n // Lazy: render only if it has been activated at least once\n return this.activatedTabs().has(value);\n }\n\n // ── Selection ──\n\n /** Activates a tab, updating the two-way bound `value`, marking it rendered for lazy panels, and announcing the change via `LiveAnnouncer`. Ignored when the tab is disabled. */\n selectTab(tab: TabComponent): void {\n if (tab.disabled()) return;\n const val = tab.value();\n this.activeValue.set(val);\n this.value.set(val);\n\n // Mark as activated for lazy tabs\n this.activatedTabs.update(set => {\n const next = new Set(set);\n next.add(val);\n return next;\n });\n\n // Announce to screen readers\n const tabsArr = this.tabs();\n const idx = tabsArr.findIndex(t => t.value() === val);\n this.liveAnnouncer.announce(\n `${tab.label() || val} tab, ${idx + 1} of ${tabsArr.length}`,\n );\n }\n\n /** Closes a closable tab, emitting `closed` with its value and moving activation to the nearest enabled sibling when the closed tab was active. */\n closeTab(tab: TabComponent, event: Event): void {\n event.stopPropagation();\n if (event instanceof KeyboardEvent) {\n event.preventDefault();\n }\n const val = tab.value();\n this.closed.emit(val);\n\n // If closing the active tab, activate nearest enabled sibling\n if (this.isTabActive(val)) {\n const tabsArr = this.tabs();\n const idx = tabsArr.findIndex(t => t.value() === val);\n const next = this.findNearestEnabledTab(tabsArr, idx);\n if (next) {\n this.selectTab(next);\n }\n }\n }\n\n private findNearestEnabledTab(tabs: readonly TabComponent[], fromIndex: number): TabComponent | null {\n // Search forward first, then backward\n for (let i = fromIndex + 1; i < tabs.length; i++) {\n if (!tabs[i].disabled()) return tabs[i];\n }\n for (let i = fromIndex - 1; i >= 0; i--) {\n if (!tabs[i].disabled()) return tabs[i];\n }\n return null;\n }\n\n // ── Keyboard navigation (CDK FocusKeyManager) ──\n\n private keyManager: FocusKeyManager<TabTriggerElementDirective> | null = null;\n\n /**\n * @internal Handles keyboard navigation. The manager handles Arrow/Home/End;\n * we layer Enter/Space to select the focused tab (the wrapper is a `<div role=\"tab\">`\n * so we lose the native button activation) and Delete to close a closable tab.\n */\n onKeydown(event: KeyboardEvent): void {\n if (!this.keyManager) return;\n const triggers = this.triggerElements();\n if (triggers.length === 0) return;\n\n // Sync active item with the trigger DOM-focused by the user. We accept\n // either `event.target` (which works under shadow DOM) or the currently\n // active tab's index as a safety net (e.g. when the event was dispatched\n // on the tablist container itself, not a trigger).\n const focusedIdx = triggers.findIndex(\n t => t.elementRef.nativeElement === event.target,\n );\n const activeIdx = focusedIdx >= 0\n ? focusedIdx\n : this.tabs().findIndex(t => t.value() === this.activeValue());\n if (activeIdx >= 0 && activeIdx !== this.keyManager.activeItemIndex) {\n this.keyManager.setActiveItem(activeIdx);\n }\n\n // Enter / Space select the focused tab (we no longer get this for free\n // because the trigger is a div, not a button — see DOM restructure below).\n if (event.key === 'Enter' || event.key === ' ') {\n if (focusedIdx >= 0) {\n event.preventDefault();\n this.selectTab(this.tabs()[focusedIdx]);\n }\n return;\n }\n\n // Delete on a closable tab dismisses it (APG-recommended). This is the\n // ONLY keyboard path: the close control cannot be focusable without\n // failing axe's nested-interactive rule, so `aria-keyshortcuts=\"Delete\"`\n // on the trigger advertises this gesture to assistive tech.\n if (event.key === 'Delete' && focusedIdx >= 0) {\n const tab = this.tabs()[focusedIdx];\n if (tab.closable() && !tab.disabled()) {\n event.preventDefault();\n this.closeTab(tab, event);\n return;\n }\n }\n\n // Let the manager handle arrow/home/end navigation.\n const before = this.keyManager.activeItemIndex;\n this.keyManager.onKeydown(event);\n const after = this.keyManager.activeItemIndex;\n if (after !== null && after !== before && after >= 0) {\n // Automatic activation pattern (APG): selection follows focus.\n this.selectTab(this.tabs()[after]);\n }\n }\n\n // ── Scrolling ──\n\n /** Smoothly scrolls the tablist back toward its start by one step, along the current orientation's axis. */\n scrollStart(): void {\n const el = this.tablistInnerRef()?.nativeElement;\n if (!el) return;\n const isVertical = this.orientation() === 'vertical';\n if (isVertical) {\n el.scrollBy({ top: -120, behavior: 'smooth' });\n } else {\n el.scrollBy({ left: -120, behavior: 'smooth' });\n }\n }\n\n /** Smoothly scrolls the tablist forward toward its end by one step, along the current orientation's axis. */\n scrollEnd(): void {\n const el = this.tablistInnerRef()?.nativeElement;\n if (!el) return;\n const isVertical = this.orientation() === 'vertical';\n if (isVertical) {\n el.scrollBy({ top: 120, behavior: 'smooth' });\n } else {\n el.scrollBy({ left: 120, behavior: 'smooth' });\n }\n }\n\n private updateScrollState(): void {\n const el = this.tablistInnerRef()?.nativeElement;\n if (!el) return;\n\n const isVertical = this.orientation() === 'vertical';\n if (isVertical) {\n this.canScrollStart.set(el.scrollTop > 0);\n this.canScrollEnd.set(el.scrollTop + el.clientHeight < el.scrollHeight - 1);\n } else {\n this.canScrollStart.set(el.scrollLeft > 0);\n this.canScrollEnd.set(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);\n }\n }\n\n ngAfterViewInit(): void {\n this.initializeActiveTab();\n }\n\n constructor() {\n afterNextRender(() => {\n this.setupScrollDetection();\n });\n\n // Rebuild FocusKeyManager whenever triggers, orientation or direction\n // change. `layoutDirection()` reads `Directionality.valueSignal` directly,\n // so an app that flips `dir` at runtime rebuilds the manager with the new\n // horizontal orientation without a subscription mirror.\n effect((onCleanup) => {\n const triggers = this.triggerElements();\n const orientation = this.orientation();\n if (triggers.length === 0) {\n this.keyManager = null;\n return;\n }\n const direction = this.layoutDirection();\n const manager = new FocusKeyManager(triggers)\n .withWrap()\n .withHomeAndEnd();\n if (orientation === 'vertical') {\n manager.withVerticalOrientation();\n } else {\n manager.withHorizontalOrientation(direction);\n }\n this.keyManager = manager;\n\n // Initialise the manager's active index to the active tab so the first\n // arrow press moves from the active item, not from -1.\n //\n // `updateActiveItem`, NOT `setActiveItem`: on a FocusKeyManager,\n // `setActiveItem` also moves DOM focus to the item. Calling it here —\n // during construction — makes merely MOUNTING a `<tw-tabs>` steal focus\n // from wherever the user was and scroll the tablist into view. On a page\n // where the tabs sit below the fold that silently jumps the viewport.\n // `updateActiveItem` seeds the index without touching focus, which is\n // all this needs; the keydown handler still uses `setActiveItem`, where\n // moving focus is the point.\n untracked(() => {\n // Seeded from the DOM tab stop, not from `activeValue()`: when `value`\n // matches no tab the tab stop falls back to the first enabled tab, and\n // seeding the manager with -1 there would desynchronise the two.\n const stopValue = this.focusStopValue();\n const tabs = this.tabs();\n const idx = tabs.findIndex(t => t.value() === stopValue);\n if (idx >= 0) {\n manager.updateActiveItem(idx);\n }\n });\n\n onCleanup(() => {\n manager.destroy();\n });\n });\n\n this.destroyRef.onDestroy(() => {\n this.resizeObserver?.disconnect();\n this.scrollCleanup?.();\n });\n }\n\n private initializeActiveTab(): void {\n // If no value was provided, default to the first enabled tab\n if (!this.activeValue()) {\n const first = this.tabs().find(t => !t.disabled());\n if (first) {\n this.activeValue.set(first.value());\n this.value.set(first.value());\n }\n }\n\n // Mark the initial active tab as activated (for lazy tracking)\n const val = this.activeValue();\n if (val) {\n this.activatedTabs.update(set => {\n const next = new Set(set);\n next.add(val);\n return next;\n });\n }\n }\n\n private setupScrollDetection(): void {\n const el = this.tablistInnerRef()?.nativeElement;\n if (!el) return;\n\n // Initial check\n this.updateScrollState();\n\n // ResizeObserver for container size changes\n if (typeof ResizeObserver !== 'undefined') {\n this.resizeObserver = new ResizeObserver(() => {\n this.updateScrollState();\n });\n this.resizeObserver.observe(el);\n }\n\n // Scroll event for scroll position changes\n const onScroll = () => this.updateScrollState();\n el.addEventListener('scroll', onScroll, { passive: true });\n this.scrollCleanup = () => el.removeEventListener('scroll', onScroll);\n }\n}\n","<!-- Tablist container. WAI-ARIA tablist owns roving focus on the tab elements\n inside; the container itself relays keydown but isn't tabbable. -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n [class]=\"tablistClasses()\"\n role=\"tablist\"\n [attr.aria-orientation]=\"orientation()\"\n (keydown)=\"onKeydown($event)\"\n>\n <!-- Scroll start button (horizontal only) -->\n @if (orientation() === 'horizontal' && (canScrollStart() || canScrollEnd())) {\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"scrollButtonClasses()\"\n [disabled]=\"!canScrollStart()\"\n (click)=\"scrollStart()\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"size-4 shrink-0\">\n <path fill-rule=\"evenodd\" d=\"M9.78 4.22a.75.75 0 0 1 0 1.06L7.06 8l2.72 2.72a.75.75 0 1 1-1.06 1.06L5.47 8.53a.75.75 0 0 1 0-1.06l3.25-3.25a.75.75 0 0 1 1.06 0Z\" clip-rule=\"evenodd\" />\n </svg>\n </button>\n }\n\n <!-- Scrollable trigger strip -->\n <div #tablistInner [class]=\"tablistInnerClasses()\" style=\"scrollbar-width: none\">\n @for (tab of tabs(); track tab.value()) {\n <!-- Trigger is rendered as <div role=\"tab\">, not <button>: activation is\n wired explicitly via (click) and the parent tablist's keydown\n (Enter / Space), and the roving tab stop comes from\n twTabTriggerElement. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n role=\"tab\"\n twTabTriggerElement\n [isFocusStop]=\"isTabFocusStop(tab.value())\"\n [isDisabled]=\"tab.disabled()\"\n [id]=\"getTabId(tab.value())\"\n [attr.aria-selected]=\"isTabActive(tab.value())\"\n [attr.aria-controls]=\"getPanelId(tab.value())\"\n [attr.aria-disabled]=\"tab.disabled() || null\"\n [attr.aria-keyshortcuts]=\"tab.closable() && !tab.disabled() ? 'Delete' : null\"\n [class]=\"getTriggerClasses(tab.value(), isTabActive(tab.value()))\"\n [class.opacity-50]=\"tab.disabled()\"\n [class.pointer-events-none]=\"tab.disabled()\"\n (click)=\"selectTab(tab)\"\n >\n <!-- Custom trigger template or default label -->\n @if (tab.triggerTemplate(); as triggerDir) {\n <ng-container\n [ngTemplateOutlet]=\"triggerDir.templateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: { active: isTabActive(tab.value()), disabled: tab.disabled(), value: tab.value() } }\"\n />\n } @else {\n {{ tab.label() }}\n }\n\n <!-- Close affordance — a pointer-only span, deliberately NOT a button.\n Anything focusable inside an element carrying an interactive role\n fails axe's nested-interactive rule, and hoisting a real button\n out to a sibling only trades that for aria-required-children\n (a tablist may own nothing but tabs — both measured). The\n keyboard path is the APG-recommended Delete key on the tab\n itself, handled in onKeydown and advertised to assistive tech\n through aria-keyshortcuts on the trigger above. -->\n @if (tab.closable() && !tab.disabled()) {\n <span\n data-tw-tab-close\n aria-hidden=\"true\"\n [class]=\"closeButtonClasses()\"\n (click)=\"closeTab(tab, $event)\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"size-4 shrink-0\">\n <path d=\"M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z\"/>\n </svg>\n </span>\n }\n </div>\n }\n </div>\n\n <!-- Scroll end button (horizontal only) -->\n @if (orientation() === 'horizontal' && (canScrollStart() || canScrollEnd())) {\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"scrollButtonClasses()\"\n [disabled]=\"!canScrollEnd()\"\n (click)=\"scrollEnd()\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"size-4 shrink-0\">\n <path fill-rule=\"evenodd\" d=\"M6.22 4.22a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06L8.94 8 6.22 5.28a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\" />\n </svg>\n </button>\n }\n</div>\n\n<!-- Tab panels -->\n@for (tab of tabs(); track tab.value()) {\n @if (shouldRenderPanel(tab)) {\n <div\n role=\"tabpanel\"\n [id]=\"getPanelId(tab.value())\"\n [attr.aria-labelledby]=\"getTabId(tab.value())\"\n [class]=\"panelClasses()\"\n [hidden]=\"!isTabActive(tab.value())\"\n tabindex=\"0\"\n >\n @if (tab.contentTemplate(); as contentDir) {\n <ng-container [ngTemplateOutlet]=\"contentDir.templateRef\" />\n } @else if (tab.implicitContentTemplate(); as implicitTpl) {\n <ng-container [ngTemplateOutlet]=\"implicitTpl\" />\n }\n </div>\n }\n}\n\n<!-- Hidden projection slot for tw-tab children -->\n<div style=\"display: none\">\n <ng-content />\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cdevhub-ngx-tw-tabs.mjs","sources":["../../../projects/ngx-tw/tabs/tabs.ts","../../../projects/ngx-tw/tabs/tabs.html","../../../projects/ngx-tw/tabs/cdevhub-ngx-tw-tabs.ts"],"sourcesContent":["import {\n afterNextRender,\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n computed,\n contentChild,\n contentChildren,\n DestroyRef,\n Directive,\n effect,\n ElementRef,\n inject,\n input,\n linkedSignal,\n model,\n output,\n signal,\n TemplateRef,\n untracked,\n viewChild,\n viewChildren,\n type AfterViewInit,\n} from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { type FocusableOption, FocusKeyManager, LiveAnnouncer } from '@angular/cdk/a11y';\nimport { Directionality } from '@angular/cdk/bidi';\nimport { tv } from 'tailwind-variants';\nimport { twMerge } from 'tailwind-merge';\nimport {\n getActiveTriggerClasses,\n getInactiveTriggerClasses,\n tabTriggerVariants,\n type TabTriggerVariant,\n type TwColor,\n type TwSize,\n} from '@cdevhub/ngx-tw/core';\n\n/** Visual style of the tab strip. */\nexport type TabsVariant = TabTriggerVariant;\n\n// ── tv() config (component-local slots only — trigger config is shared) ──\n\nconst tabsLayoutVariants = tv(\n {\n slots: {\n root: 'flex',\n tablist: 'relative flex shrink-0',\n tablistInner: 'flex overflow-x-auto',\n panel: 'min-w-0 flex-1',\n scrollButton:\n 'inline-flex items-center justify-center shrink-0 cursor-pointer transition-colors duration-normal motion-reduce:transition-none disabled:opacity-30 disabled:cursor-default hover:bg-surface-muted',\n // No focus-visible ring: the close control is pointer-only by design\n // (see the ARIA note in tabs.html), so a focus style would be dead code.\n closeButton:\n 'inline-flex items-center justify-center rounded-md cursor-pointer transition-colors duration-normal motion-reduce:transition-none hover:bg-surface-muted',\n },\n variants: {\n variant: {\n underline: {\n tablist: 'border-b border-border',\n tablistInner: 'gap-0',\n panel: 'pt-4',\n },\n enclosed: {\n tablist: 'border-b border-border',\n tablistInner: 'gap-0',\n panel: 'pt-4',\n },\n pill: {\n tablist: 'bg-surface-muted rounded-xl p-1',\n tablistInner: 'gap-1',\n panel: 'pt-4',\n },\n },\n // Square interactive-target scale: see CLAUDE.md \"Icon Sizing\".\n size: {\n xs: { scrollButton: 'size-5', closeButton: 'size-6' },\n sm: { scrollButton: 'size-5', closeButton: 'size-7' },\n md: { scrollButton: 'size-6', closeButton: 'size-8' },\n lg: { scrollButton: 'size-7', closeButton: 'size-9' },\n xl: { scrollButton: 'size-8', closeButton: 'size-9' },\n },\n orientation: {\n horizontal: {\n root: 'flex-col',\n tablist: '',\n tablistInner: 'flex-row',\n },\n vertical: {\n root: 'flex-row',\n tablistInner: 'flex-col overflow-y-auto overflow-x-hidden',\n },\n },\n },\n compoundVariants: [\n {\n variant: 'underline',\n orientation: 'vertical',\n class: {\n tablist: 'border-b-0 border-r border-border',\n panel: 'pt-0 pl-4',\n },\n },\n {\n variant: 'enclosed',\n orientation: 'vertical',\n class: {\n tablist: 'border-b-0 border-r border-border',\n panel: 'pt-0 pl-4',\n },\n },\n {\n variant: 'pill',\n orientation: 'vertical',\n class: {\n panel: 'pt-0 pl-4',\n },\n },\n ],\n defaultVariants: {\n variant: 'underline',\n size: 'md',\n orientation: 'horizontal',\n },\n },\n { twMerge: true },\n);\n\n// ── Per-variant/orientation trigger additions (vertical underline/enclosed\n// re-route the active border axis, hence these compound overrides). ──\n\nfunction getTriggerOrientationExtras(\n variant: TabsVariant,\n orientation: 'horizontal' | 'vertical',\n): string {\n if (orientation !== 'vertical') return '';\n switch (variant) {\n case 'underline':\n return 'border-b-0 -mb-0 border-r-2 border-transparent -mr-px';\n case 'enclosed':\n return '-mb-0 -mr-px';\n case 'pill':\n return '';\n }\n}\n\n// ── Directives ──\n\n@Directive({ selector: 'ng-template[twTabTrigger]' })\nexport class TabTriggerDirective {\n readonly templateRef = inject(TemplateRef);\n}\n\n@Directive({ selector: 'ng-template[twTabContent]' })\nexport class TabContentDirective {\n readonly templateRef = inject(TemplateRef);\n}\n\n// ── TabComponent ──\n\n@Component({\n selector: 'tw-tab',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <ng-template #implicitContent>\n <ng-content />\n </ng-template>\n `,\n host: {\n 'style': 'display: none',\n },\n})\nexport class TabComponent {\n /** Unique identifier for this tab. Used to match the active tab value. */\n readonly value = input.required<string>();\n\n /** Plain text label shown in the trigger. Ignored when a custom trigger template is provided. Defaults to an empty string. */\n readonly label = input('');\n\n /** When true, the tab cannot be selected and is skipped by keyboard navigation. Defaults to false. */\n readonly disabled = input(false);\n\n /**\n * When true, a dismiss control is rendered in the tab trigger. The control is pointer-only; the keyboard gesture is Delete on the focused tab. Defaults to false.\n *\n * @deprecated Bind `dismissible` instead. `closable` was the library's only\n * spelling of this concept — `alert`, `badge` and `toast` all call it\n * `dismissible` — and one idea should not have two names. Both work; `closable`\n * is removed in the next major.\n */\n readonly closable = input(false, { transform: booleanAttribute });\n\n /**\n * When true, a dismiss control is rendered in the tab trigger. The control is pointer-only; the keyboard gesture is Delete on the focused tab. Defaults to false.\n *\n * Canonical spelling, matching `alert`, `badge` and `toast`. Supersedes the\n * deprecated `closable`; binding either one enables the control.\n */\n readonly dismissible = input(false, { transform: booleanAttribute });\n\n /**\n * @internal Whether a dismiss control should render, from either spelling.\n * Both inputs default to `false`, so `||` is the correct reconciliation: it is\n * true exactly when the consumer opted in through one name or the other.\n */\n readonly isDismissible = computed(() => this.dismissible() || this.closable());\n\n /** When true, the tab panel content is only instantiated when the tab becomes active for the first time. Defaults to false. */\n readonly lazy = input(false);\n\n /** @internal Custom trigger template, if provided. */\n readonly triggerTemplate = contentChild(TabTriggerDirective);\n\n /** @internal Custom content template, if provided via twTabContent. */\n readonly contentTemplate = contentChild(TabContentDirective);\n\n /** @internal Implicit content template wrapping ng-content. */\n readonly implicitContentTemplate = viewChild<TemplateRef<unknown>>('implicitContent');\n}\n\n// ── Tab trigger element wrapper ──\n\n@Directive({\n selector: '[twTabTriggerElement]',\n host: {\n '[attr.tabindex]': 'tabIndex()',\n },\n})\nexport class TabTriggerElementDirective implements FocusableOption {\n readonly elementRef = inject(ElementRef<HTMLElement>);\n\n /**\n * Whether this trigger owns the tablist's single tab stop. Drives\n * `tabindex`. Normally the active tab, but the parent falls back to the\n * first enabled tab when `value` matches nothing, so the tablist is never\n * left with zero tab stops. Defaults to `false`.\n */\n readonly isFocusStop = input(false);\n\n /** Whether this trigger is disabled. Read by `FocusKeyManager` so disabled triggers are skipped during arrow navigation. Defaults to `false`. */\n readonly isDisabled = input(false);\n\n readonly tabIndex = computed(() => this.isFocusStop() ? 0 : -1);\n\n /** Moves DOM focus to the trigger element. Called by `FocusKeyManager` during arrow-key navigation. */\n focus(): void {\n this.elementRef.nativeElement.focus();\n }\n\n /**\n * FocusableOption getter. NOTE: we cannot expose the `isDisabled` input\n * signal directly — the signal function itself is always truthy, which would\n * make FocusKeyManager treat every trigger as disabled. We resolve the\n * signal here so the manager skips the right items.\n */\n get disabled(): boolean {\n return this.isDisabled();\n }\n}\n\n// ── TabsComponent ──\n\nlet nextId = 0;\n\n@Component({\n selector: 'tw-tabs',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [NgTemplateOutlet, TabTriggerElementDirective],\n templateUrl: './tabs.html',\n host: {\n '[class]': 'rootClasses()',\n },\n})\nexport class TabsComponent implements AfterViewInit {\n /** Controls the visual style of the tab strip. Defaults to 'underline'. */\n readonly variant = input<TabsVariant>('underline');\n\n /** Sets the semantic color for active tab indicators and highlights. Defaults to 'primary'. */\n readonly color = input<TwColor>('primary');\n\n /** Controls padding, font size, and icon size of tab triggers. Defaults to 'md'. */\n readonly size = input<TwSize>('md');\n\n /** Layout direction of the tab strip. Defaults to 'horizontal'. */\n readonly orientation = input<'horizontal' | 'vertical'>('horizontal');\n\n /** When true, tab triggers stretch to fill the available width equally. Defaults to false. */\n readonly fitted = input(false);\n\n /** The value of the currently active tab. Two-way bound. Updates when the user selects a tab. Defaults to an empty string. */\n readonly value = model<string>('');\n\n /** Fires when a closable tab is dismissed, either by clicking its close control or by pressing Delete while the tab has focus. Payload is the tab's value. */\n readonly closed = output<string>();\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly liveAnnouncer = inject(LiveAnnouncer);\n private readonly directionality = inject(Directionality, { optional: true });\n\n /**\n * Live layout direction, read straight from CDK's `Directionality.valueSignal`\n * (CDK 22+) so the key-manager effect re-runs on a runtime `dir` flip with no\n * manual subscription mirror. Without a live direction the tablist hardcodes\n * 'ltr' and ArrowRight moves to the next tab in DOM order but the *previous*\n * tab visually in an RTL locale. CdkStepper and `tw-split` already honour\n * Directionality; `tw-paginator` reads `valueSignal` the same way.\n */\n private readonly layoutDirection = computed<'ltr' | 'rtl'>(\n () => this.directionality?.valueSignal() ?? 'ltr',\n );\n\n /** @internal */\n readonly tabs = contentChildren(TabComponent);\n\n /** @internal */\n readonly triggerElements = viewChildren(TabTriggerElementDirective);\n\n /** @internal */\n readonly tablistInnerRef = viewChild<ElementRef<HTMLElement>>('tablistInner');\n\n private readonly componentId = `tw-tabs-${nextId++}`;\n\n // ── Active tab tracking ──\n\n readonly activeValue = linkedSignal(() => this.value());\n\n // ── Lazy tab tracking — Set of tab values that have been activated at least once ──\n readonly activatedTabs = signal(new Set<string>());\n\n // ── Scroll overflow signals ──\n readonly canScrollStart = signal(false);\n readonly canScrollEnd = signal(false);\n\n private resizeObserver: ResizeObserver | null = null;\n private scrollCleanup: (() => void) | null = null;\n\n // ── Variant classes ──\n\n private readonly layoutResult = computed(() =>\n tabsLayoutVariants({\n variant: this.variant(),\n size: this.size(),\n orientation: this.orientation(),\n }),\n );\n\n private readonly triggerResult = computed(() =>\n tabTriggerVariants({\n variant: this.variant(),\n color: this.color(),\n size: this.size(),\n fitted: this.fitted(),\n }),\n );\n\n readonly rootClasses = computed(() => this.layoutResult().root());\n readonly tablistClasses = computed(() => this.layoutResult().tablist());\n readonly tablistInnerClasses = computed(() => this.layoutResult().tablistInner());\n readonly baseTriggerClasses = computed(() => this.triggerResult().trigger());\n readonly panelClasses = computed(() => this.layoutResult().panel());\n readonly scrollButtonClasses = computed(() => this.layoutResult().scrollButton());\n readonly closeButtonClasses = computed(() => this.layoutResult().closeButton());\n\n // ── Per-trigger active/inactive class computation ──\n\n /** @internal Get combined trigger classes for a given tab. */\n getTriggerClasses(_tabValue: string, isActive: boolean): string {\n const base = this.baseTriggerClasses();\n const orientationExtras = getTriggerOrientationExtras(this.variant(), this.orientation());\n const state = isActive\n ? getActiveTriggerClasses(this.variant(), this.color(), this.orientation())\n : getInactiveTriggerClasses(this.variant());\n return twMerge(base, orientationExtras, state);\n }\n\n // ── ID generation ──\n /** Builds the DOM id for a tab trigger, used by the panel's `aria-labelledby`. */\n getTabId(tabValue: string): string {\n return `${this.componentId}-tab-${tabValue}`;\n }\n\n /** Builds the DOM id for a tab panel, used by the trigger's `aria-controls`. */\n getPanelId(tabValue: string): string {\n return `${this.componentId}-panel-${tabValue}`;\n }\n\n // ── Tab visibility ──\n\n /** Whether the tab with the given value is the currently active one. */\n isTabActive(tabValue: string): boolean {\n return this.activeValue() === tabValue;\n }\n\n /**\n * @internal Value of the tab that owns the tablist's single tab stop.\n *\n * The active tab whenever it exists and is enabled. When `value` matches no\n * tab — a stale id, a value cleared after init, or an active tab that is\n * disabled — the first enabled tab takes the tab stop instead (standard\n * roving-tabindex recovery). Without the fallback every trigger renders\n * `tabindex=\"-1\"` and the whole tablist silently leaves the tab order\n * (WCAG SC 2.1.1).\n */\n readonly focusStopValue = computed<string | null>(() => {\n const tabs = this.tabs();\n const active = this.activeValue();\n const activeTab = tabs.find(t => t.value() === active && !t.disabled());\n if (activeTab) return activeTab.value();\n return tabs.find(t => !t.disabled())?.value() ?? null;\n });\n\n /** Whether the tab with the given value owns the tablist's single tab stop. */\n isTabFocusStop(tabValue: string): boolean {\n return this.focusStopValue() === tabValue;\n }\n\n /** Whether a tab's panel content should be in the DOM. Always true for eager tabs; for lazy tabs, true only once the tab has been activated at least once. */\n shouldRenderPanel(tab: TabComponent): boolean {\n const value = tab.value();\n if (!tab.lazy()) {\n return true;\n }\n // Lazy: render only if it has been activated at least once\n return this.activatedTabs().has(value);\n }\n\n // ── Selection ──\n\n /** Activates a tab, updating the two-way bound `value`, marking it rendered for lazy panels, and announcing the change via `LiveAnnouncer`. Ignored when the tab is disabled. */\n selectTab(tab: TabComponent): void {\n if (tab.disabled()) return;\n const val = tab.value();\n this.activeValue.set(val);\n this.value.set(val);\n\n // Mark as activated for lazy tabs\n this.activatedTabs.update(set => {\n const next = new Set(set);\n next.add(val);\n return next;\n });\n\n // Announce to screen readers\n const tabsArr = this.tabs();\n const idx = tabsArr.findIndex(t => t.value() === val);\n this.liveAnnouncer.announce(\n `${tab.label() || val} tab, ${idx + 1} of ${tabsArr.length}`,\n );\n }\n\n /** Closes a closable tab, emitting `closed` with its value and moving activation to the nearest enabled sibling when the closed tab was active. */\n closeTab(tab: TabComponent, event: Event): void {\n event.stopPropagation();\n if (event instanceof KeyboardEvent) {\n event.preventDefault();\n }\n const val = tab.value();\n this.closed.emit(val);\n\n // If closing the active tab, activate nearest enabled sibling\n if (this.isTabActive(val)) {\n const tabsArr = this.tabs();\n const idx = tabsArr.findIndex(t => t.value() === val);\n const next = this.findNearestEnabledTab(tabsArr, idx);\n if (next) {\n this.selectTab(next);\n }\n }\n }\n\n private findNearestEnabledTab(tabs: readonly TabComponent[], fromIndex: number): TabComponent | null {\n // Search forward first, then backward\n for (let i = fromIndex + 1; i < tabs.length; i++) {\n if (!tabs[i].disabled()) return tabs[i];\n }\n for (let i = fromIndex - 1; i >= 0; i--) {\n if (!tabs[i].disabled()) return tabs[i];\n }\n return null;\n }\n\n // ── Keyboard navigation (CDK FocusKeyManager) ──\n\n private keyManager: FocusKeyManager<TabTriggerElementDirective> | null = null;\n\n /**\n * @internal Handles keyboard navigation. The manager handles Arrow/Home/End;\n * we layer Enter/Space to select the focused tab (the wrapper is a `<div role=\"tab\">`\n * so we lose the native button activation) and Delete to close a closable tab.\n */\n onKeydown(event: KeyboardEvent): void {\n if (!this.keyManager) return;\n const triggers = this.triggerElements();\n if (triggers.length === 0) return;\n\n // Sync active item with the trigger DOM-focused by the user. We accept\n // either `event.target` (which works under shadow DOM) or the currently\n // active tab's index as a safety net (e.g. when the event was dispatched\n // on the tablist container itself, not a trigger).\n const focusedIdx = triggers.findIndex(\n t => t.elementRef.nativeElement === event.target,\n );\n const activeIdx = focusedIdx >= 0\n ? focusedIdx\n : this.tabs().findIndex(t => t.value() === this.activeValue());\n if (activeIdx >= 0 && activeIdx !== this.keyManager.activeItemIndex) {\n this.keyManager.setActiveItem(activeIdx);\n }\n\n // Enter / Space select the focused tab (we no longer get this for free\n // because the trigger is a div, not a button — see DOM restructure below).\n if (event.key === 'Enter' || event.key === ' ') {\n if (focusedIdx >= 0) {\n event.preventDefault();\n this.selectTab(this.tabs()[focusedIdx]);\n }\n return;\n }\n\n // Delete on a closable tab dismisses it (APG-recommended). This is the\n // ONLY keyboard path: the close control cannot be focusable without\n // failing axe's nested-interactive rule, so `aria-keyshortcuts=\"Delete\"`\n // on the trigger advertises this gesture to assistive tech.\n if (event.key === 'Delete' && focusedIdx >= 0) {\n const tab = this.tabs()[focusedIdx];\n if (tab.isDismissible() && !tab.disabled()) {\n event.preventDefault();\n this.closeTab(tab, event);\n return;\n }\n }\n\n // Let the manager handle arrow/home/end navigation.\n const before = this.keyManager.activeItemIndex;\n this.keyManager.onKeydown(event);\n const after = this.keyManager.activeItemIndex;\n if (after !== null && after !== before && after >= 0) {\n // Automatic activation pattern (APG): selection follows focus.\n this.selectTab(this.tabs()[after]);\n }\n }\n\n // ── Scrolling ──\n\n /** Smoothly scrolls the tablist back toward its start by one step, along the current orientation's axis. */\n scrollStart(): void {\n const el = this.tablistInnerRef()?.nativeElement;\n if (!el) return;\n const isVertical = this.orientation() === 'vertical';\n if (isVertical) {\n el.scrollBy({ top: -120, behavior: 'smooth' });\n } else {\n el.scrollBy({ left: -120, behavior: 'smooth' });\n }\n }\n\n /** Smoothly scrolls the tablist forward toward its end by one step, along the current orientation's axis. */\n scrollEnd(): void {\n const el = this.tablistInnerRef()?.nativeElement;\n if (!el) return;\n const isVertical = this.orientation() === 'vertical';\n if (isVertical) {\n el.scrollBy({ top: 120, behavior: 'smooth' });\n } else {\n el.scrollBy({ left: 120, behavior: 'smooth' });\n }\n }\n\n private updateScrollState(): void {\n const el = this.tablistInnerRef()?.nativeElement;\n if (!el) return;\n\n const isVertical = this.orientation() === 'vertical';\n if (isVertical) {\n this.canScrollStart.set(el.scrollTop > 0);\n this.canScrollEnd.set(el.scrollTop + el.clientHeight < el.scrollHeight - 1);\n } else {\n this.canScrollStart.set(el.scrollLeft > 0);\n this.canScrollEnd.set(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);\n }\n }\n\n ngAfterViewInit(): void {\n this.initializeActiveTab();\n }\n\n constructor() {\n afterNextRender(() => {\n this.setupScrollDetection();\n });\n\n // Rebuild FocusKeyManager whenever triggers, orientation or direction\n // change. `layoutDirection()` reads `Directionality.valueSignal` directly,\n // so an app that flips `dir` at runtime rebuilds the manager with the new\n // horizontal orientation without a subscription mirror.\n effect((onCleanup) => {\n const triggers = this.triggerElements();\n const orientation = this.orientation();\n if (triggers.length === 0) {\n this.keyManager = null;\n return;\n }\n const direction = this.layoutDirection();\n const manager = new FocusKeyManager(triggers)\n .withWrap()\n .withHomeAndEnd();\n if (orientation === 'vertical') {\n manager.withVerticalOrientation();\n } else {\n manager.withHorizontalOrientation(direction);\n }\n this.keyManager = manager;\n\n // Initialise the manager's active index to the active tab so the first\n // arrow press moves from the active item, not from -1.\n //\n // `updateActiveItem`, NOT `setActiveItem`: on a FocusKeyManager,\n // `setActiveItem` also moves DOM focus to the item. Calling it here —\n // during construction — makes merely MOUNTING a `<tw-tabs>` steal focus\n // from wherever the user was and scroll the tablist into view. On a page\n // where the tabs sit below the fold that silently jumps the viewport.\n // `updateActiveItem` seeds the index without touching focus, which is\n // all this needs; the keydown handler still uses `setActiveItem`, where\n // moving focus is the point.\n untracked(() => {\n // Seeded from the DOM tab stop, not from `activeValue()`: when `value`\n // matches no tab the tab stop falls back to the first enabled tab, and\n // seeding the manager with -1 there would desynchronise the two.\n const stopValue = this.focusStopValue();\n const tabs = this.tabs();\n const idx = tabs.findIndex(t => t.value() === stopValue);\n if (idx >= 0) {\n manager.updateActiveItem(idx);\n }\n });\n\n onCleanup(() => {\n manager.destroy();\n });\n });\n\n this.destroyRef.onDestroy(() => {\n this.resizeObserver?.disconnect();\n this.scrollCleanup?.();\n });\n }\n\n private initializeActiveTab(): void {\n // If no value was provided, default to the first enabled tab\n if (!this.activeValue()) {\n const first = this.tabs().find(t => !t.disabled());\n if (first) {\n this.activeValue.set(first.value());\n this.value.set(first.value());\n }\n }\n\n // Mark the initial active tab as activated (for lazy tracking)\n const val = this.activeValue();\n if (val) {\n this.activatedTabs.update(set => {\n const next = new Set(set);\n next.add(val);\n return next;\n });\n }\n }\n\n private setupScrollDetection(): void {\n const el = this.tablistInnerRef()?.nativeElement;\n if (!el) return;\n\n // Initial check\n this.updateScrollState();\n\n // ResizeObserver for container size changes\n if (typeof ResizeObserver !== 'undefined') {\n this.resizeObserver = new ResizeObserver(() => {\n this.updateScrollState();\n });\n this.resizeObserver.observe(el);\n }\n\n // Scroll event for scroll position changes\n const onScroll = () => this.updateScrollState();\n el.addEventListener('scroll', onScroll, { passive: true });\n this.scrollCleanup = () => el.removeEventListener('scroll', onScroll);\n }\n}\n","<!-- Tablist container. WAI-ARIA tablist owns roving focus on the tab elements\n inside; the container itself relays keydown but isn't tabbable. -->\n<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n [class]=\"tablistClasses()\"\n role=\"tablist\"\n [attr.aria-orientation]=\"orientation()\"\n (keydown)=\"onKeydown($event)\"\n>\n <!-- Scroll start button (horizontal only) -->\n @if (orientation() === 'horizontal' && (canScrollStart() || canScrollEnd())) {\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"scrollButtonClasses()\"\n [disabled]=\"!canScrollStart()\"\n (click)=\"scrollStart()\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"size-4 shrink-0\">\n <path fill-rule=\"evenodd\" d=\"M9.78 4.22a.75.75 0 0 1 0 1.06L7.06 8l2.72 2.72a.75.75 0 1 1-1.06 1.06L5.47 8.53a.75.75 0 0 1 0-1.06l3.25-3.25a.75.75 0 0 1 1.06 0Z\" clip-rule=\"evenodd\" />\n </svg>\n </button>\n }\n\n <!-- Scrollable trigger strip -->\n <div #tablistInner [class]=\"tablistInnerClasses()\" style=\"scrollbar-width: none\">\n @for (tab of tabs(); track tab.value()) {\n <!-- Trigger is rendered as <div role=\"tab\">, not <button>: activation is\n wired explicitly via (click) and the parent tablist's keydown\n (Enter / Space), and the roving tab stop comes from\n twTabTriggerElement. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n role=\"tab\"\n twTabTriggerElement\n [isFocusStop]=\"isTabFocusStop(tab.value())\"\n [isDisabled]=\"tab.disabled()\"\n [id]=\"getTabId(tab.value())\"\n [attr.aria-selected]=\"isTabActive(tab.value())\"\n [attr.aria-controls]=\"getPanelId(tab.value())\"\n [attr.aria-disabled]=\"tab.disabled() || null\"\n [attr.aria-keyshortcuts]=\"tab.isDismissible() && !tab.disabled() ? 'Delete' : null\"\n [class]=\"getTriggerClasses(tab.value(), isTabActive(tab.value()))\"\n [class.opacity-50]=\"tab.disabled()\"\n [class.pointer-events-none]=\"tab.disabled()\"\n (click)=\"selectTab(tab)\"\n >\n <!-- Custom trigger template or default label -->\n @if (tab.triggerTemplate(); as triggerDir) {\n <ng-container\n [ngTemplateOutlet]=\"triggerDir.templateRef\"\n [ngTemplateOutletContext]=\"{ $implicit: { active: isTabActive(tab.value()), disabled: tab.disabled(), value: tab.value() } }\"\n />\n } @else {\n {{ tab.label() }}\n }\n\n <!-- Close affordance — a pointer-only span, deliberately NOT a button.\n Anything focusable inside an element carrying an interactive role\n fails axe's nested-interactive rule, and hoisting a real button\n out to a sibling only trades that for aria-required-children\n (a tablist may own nothing but tabs — both measured). The\n keyboard path is the APG-recommended Delete key on the tab\n itself, handled in onKeydown and advertised to assistive tech\n through aria-keyshortcuts on the trigger above. -->\n @if (tab.isDismissible() && !tab.disabled()) {\n <span\n data-tw-tab-close\n aria-hidden=\"true\"\n [class]=\"closeButtonClasses()\"\n (click)=\"closeTab(tab, $event)\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"size-4 shrink-0\">\n <path d=\"M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z\"/>\n </svg>\n </span>\n }\n </div>\n }\n </div>\n\n <!-- Scroll end button (horizontal only) -->\n @if (orientation() === 'horizontal' && (canScrollStart() || canScrollEnd())) {\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [class]=\"scrollButtonClasses()\"\n [disabled]=\"!canScrollEnd()\"\n (click)=\"scrollEnd()\"\n >\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"size-4 shrink-0\">\n <path fill-rule=\"evenodd\" d=\"M6.22 4.22a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06l-3.25 3.25a.75.75 0 0 1-1.06-1.06L8.94 8 6.22 5.28a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\" />\n </svg>\n </button>\n }\n</div>\n\n<!-- Tab panels -->\n@for (tab of tabs(); track tab.value()) {\n @if (shouldRenderPanel(tab)) {\n <div\n role=\"tabpanel\"\n [id]=\"getPanelId(tab.value())\"\n [attr.aria-labelledby]=\"getTabId(tab.value())\"\n [class]=\"panelClasses()\"\n [hidden]=\"!isTabActive(tab.value())\"\n tabindex=\"0\"\n >\n @if (tab.contentTemplate(); as contentDir) {\n <ng-container [ngTemplateOutlet]=\"contentDir.templateRef\" />\n } @else if (tab.implicitContentTemplate(); as implicitTpl) {\n <ng-container [ngTemplateOutlet]=\"implicitTpl\" />\n }\n </div>\n }\n}\n\n<!-- Hidden projection slot for tw-tab children -->\n<div style=\"display: none\">\n <ng-content />\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;AAyCA;AAEA,MAAM,kBAAkB,GAAG,EAAE,CAC3B;AACE,IAAA,KAAK,EAAE;AACL,QAAA,IAAI,EAAE,MAAM;AACZ,QAAA,OAAO,EAAE,wBAAwB;AACjC,QAAA,YAAY,EAAE,sBAAsB;AACpC,QAAA,KAAK,EAAE,gBAAgB;AACvB,QAAA,YAAY,EACV,oMAAoM;;;AAGtM,QAAA,WAAW,EACT,0JAA0J;AAC7J,KAAA;AACD,IAAA,QAAQ,EAAE;AACR,QAAA,OAAO,EAAE;AACP,YAAA,SAAS,EAAE;AACT,gBAAA,OAAO,EAAE,wBAAwB;AACjC,gBAAA,YAAY,EAAE,OAAO;AACrB,gBAAA,KAAK,EAAE,MAAM;AACd,aAAA;AACD,YAAA,QAAQ,EAAE;AACR,gBAAA,OAAO,EAAE,wBAAwB;AACjC,gBAAA,YAAY,EAAE,OAAO;AACrB,gBAAA,KAAK,EAAE,MAAM;AACd,aAAA;AACD,YAAA,IAAI,EAAE;AACJ,gBAAA,OAAO,EAAE,iCAAiC;AAC1C,gBAAA,YAAY,EAAE,OAAO;AACrB,gBAAA,KAAK,EAAE,MAAM;AACd,aAAA;AACF,SAAA;;AAED,QAAA,IAAI,EAAE;YACJ,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE;YACrD,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE;YACrD,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE;YACrD,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE;YACrD,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE;AACtD,SAAA;AACD,QAAA,WAAW,EAAE;AACX,YAAA,UAAU,EAAE;AACV,gBAAA,IAAI,EAAE,UAAU;AAChB,gBAAA,OAAO,EAAE,EAAE;AACX,gBAAA,YAAY,EAAE,UAAU;AACzB,aAAA;AACD,YAAA,QAAQ,EAAE;AACR,gBAAA,IAAI,EAAE,UAAU;AAChB,gBAAA,YAAY,EAAE,4CAA4C;AAC3D,aAAA;AACF,SAAA;AACF,KAAA;AACD,IAAA,gBAAgB,EAAE;AAChB,QAAA;AACE,YAAA,OAAO,EAAE,WAAW;AACpB,YAAA,WAAW,EAAE,UAAU;AACvB,YAAA,KAAK,EAAE;AACL,gBAAA,OAAO,EAAE,mCAAmC;AAC5C,gBAAA,KAAK,EAAE,WAAW;AACnB,aAAA;AACF,SAAA;AACD,QAAA;AACE,YAAA,OAAO,EAAE,UAAU;AACnB,YAAA,WAAW,EAAE,UAAU;AACvB,YAAA,KAAK,EAAE;AACL,gBAAA,OAAO,EAAE,mCAAmC;AAC5C,gBAAA,KAAK,EAAE,WAAW;AACnB,aAAA;AACF,SAAA;AACD,QAAA;AACE,YAAA,OAAO,EAAE,MAAM;AACf,YAAA,WAAW,EAAE,UAAU;AACvB,YAAA,KAAK,EAAE;AACL,gBAAA,KAAK,EAAE,WAAW;AACnB,aAAA;AACF,SAAA;AACF,KAAA;AACD,IAAA,eAAe,EAAE;AACf,QAAA,OAAO,EAAE,WAAW;AACpB,QAAA,IAAI,EAAE,IAAI;AACV,QAAA,WAAW,EAAE,YAAY;AAC1B,KAAA;AACF,CAAA,EACD,EAAE,OAAO,EAAE,IAAI,EAAE,CAClB;AAED;AACA;AAEA,SAAS,2BAA2B,CAClC,OAAoB,EACpB,WAAsC,EAAA;IAEtC,IAAI,WAAW,KAAK,UAAU;AAAE,QAAA,OAAO,EAAE;IACzC,QAAQ,OAAO;AACb,QAAA,KAAK,WAAW;AACd,YAAA,OAAO,uDAAuD;AAChE,QAAA,KAAK,UAAU;AACb,YAAA,OAAO,cAAc;AACvB,QAAA,KAAK,MAAM;AACT,YAAA,OAAO,EAAE;;AAEf;AAEA;MAGa,mBAAmB,CAAA;AACrB,IAAA,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;uGAD/B,mBAAmB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAnB,mBAAmB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,2BAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAnB,mBAAmB,EAAA,UAAA,EAAA,CAAA;kBAD/B,SAAS;mBAAC,EAAE,QAAQ,EAAE,2BAA2B,EAAE;;MAMvC,mBAAmB,CAAA;AACrB,IAAA,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;uGAD/B,mBAAmB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAnB,mBAAmB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,2BAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAnB,mBAAmB,EAAA,UAAA,EAAA,CAAA;kBAD/B,SAAS;mBAAC,EAAE,QAAQ,EAAE,2BAA2B,EAAE;;AAKpD;MAca,YAAY,CAAA;;IAEd,KAAK,GAAG,KAAK,CAAC,QAAQ;8EAAU;;IAGhC,KAAK,GAAG,KAAK,CAAC,EAAE;8EAAC;;IAGjB,QAAQ,GAAG,KAAK,CAAC,KAAK;iFAAC;AAEhC;;;;;;;AAOG;IACM,QAAQ,GAAG,KAAK,CAAC,KAAK,gFAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;AAEjE;;;;;AAKG;IACM,WAAW,GAAG,KAAK,CAAC,KAAK,mFAAI,SAAS,EAAE,gBAAgB,EAAA,CAAG;AAEpE;;;;AAIG;AACM,IAAA,aAAa,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE;sFAAC;;IAGrE,IAAI,GAAG,KAAK,CAAC,KAAK;6EAAC;;IAGnB,eAAe,GAAG,YAAY,CAAC,mBAAmB;wFAAC;;IAGnD,eAAe,GAAG,YAAY,CAAC,mBAAmB;wFAAC;;IAGnD,uBAAuB,GAAG,SAAS,CAAuB,iBAAiB;gGAAC;uGA7C1E,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAY,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,QAAA,EAAA,MAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,WAAA,EAAA,EAAA,iBAAA,EAAA,aAAA,EAAA,UAAA,EAAA,aAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,cAAA,EAAA,eAAA,EAAA,EAAA,OAAA,EAAA,CAAA,EAAA,YAAA,EAAA,iBAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAuCiB,mBAAmB,EAAA,WAAA,EAAA,IAAA,EAAA,QAAA,EAAA,IAAA,EAAA,EAAA,EAAA,YAAA,EAAA,iBAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAGnB,mBAAmB,EAAA,WAAA,EAAA,IAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,yBAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAAA,CAAA,iBAAA,CAAA,EAAA,WAAA,EAAA,IAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAnDjD;;;;AAIT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FAKU,YAAY,EAAA,UAAA,EAAA,CAAA;kBAZxB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,QAAQ;oBAClB,eAAe,EAAE,uBAAuB,CAAC,MAAM;AAC/C,oBAAA,QAAQ,EAAE;;;;AAIT,EAAA,CAAA;AACD,oBAAA,IAAI,EAAE;AACJ,wBAAA,OAAO,EAAE,eAAe;AACzB,qBAAA;AACF,iBAAA;qpBAwCyC,mBAAmB,CAAA,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,YAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,UAAA,CAAA,MAGnB,mBAAmB,CAAA,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,uBAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,SAAA,EAAA,IAAA,EAAA,CAGQ,iBAAiB,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;AAGtF;MAQa,0BAA0B,CAAA;AAC5B,IAAA,UAAU,GAAG,MAAM,EAAC,UAAuB,EAAC;AAErD;;;;;AAKG;IACM,WAAW,GAAG,KAAK,CAAC,KAAK;oFAAC;;IAG1B,UAAU,GAAG,KAAK,CAAC,KAAK;mFAAC;AAEzB,IAAA,QAAQ,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;iFAAC;;IAG/D,KAAK,GAAA;AACH,QAAA,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,KAAK,EAAE;IACvC;AAEA;;;;;AAKG;AACH,IAAA,IAAI,QAAQ,GAAA;AACV,QAAA,OAAO,IAAI,CAAC,UAAU,EAAE;IAC1B;uGA7BW,0BAA0B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAA1B,0BAA0B,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,uBAAA,EAAA,MAAA,EAAA,EAAA,WAAA,EAAA,EAAA,iBAAA,EAAA,aAAA,EAAA,UAAA,EAAA,aAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,iBAAA,EAAA,YAAA,EAAA,UAAA,EAAA,YAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,eAAA,EAAA,YAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAA1B,0BAA0B,EAAA,UAAA,EAAA,CAAA;kBANtC,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,uBAAuB;AACjC,oBAAA,IAAI,EAAE;AACJ,wBAAA,iBAAiB,EAAE,YAAY;AAChC,qBAAA;AACF,iBAAA;;AAiCD;AAEA,IAAI,MAAM,GAAG,CAAC;MAWD,aAAa,CAAA;;IAEf,OAAO,GAAG,KAAK,CAAc,WAAW;gFAAC;;IAGzC,KAAK,GAAG,KAAK,CAAU,SAAS;8EAAC;;IAGjC,IAAI,GAAG,KAAK,CAAS,IAAI;6EAAC;;IAG1B,WAAW,GAAG,KAAK,CAA4B,YAAY;oFAAC;;IAG5D,MAAM,GAAG,KAAK,CAAC,KAAK;+EAAC;;IAGrB,KAAK,GAAG,KAAK,CAAS,EAAE;8EAAC;;IAGzB,MAAM,GAAG,MAAM,EAAU;AAEjB,IAAA,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AAC/B,IAAA,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;IACrC,cAAc,GAAG,MAAM,CAAC,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAE5E;;;;;;;AAOG;AACc,IAAA,eAAe,GAAG,QAAQ,CACzC,MAAM,IAAI,CAAC,cAAc,EAAE,WAAW,EAAE,IAAI,KAAK;wFAClD;;IAGQ,IAAI,GAAG,eAAe,CAAC,YAAY;6EAAC;;IAGpC,eAAe,GAAG,YAAY,CAAC,0BAA0B;wFAAC;;IAG1D,eAAe,GAAG,SAAS,CAA0B,cAAc;wFAAC;AAE5D,IAAA,WAAW,GAAG,CAAA,QAAA,EAAW,MAAM,EAAE,EAAE;;IAI3C,WAAW,GAAG,YAAY,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE;oFAAC;;AAG9C,IAAA,aAAa,GAAG,MAAM,CAAC,IAAI,GAAG,EAAU;sFAAC;;IAGzC,cAAc,GAAG,MAAM,CAAC,KAAK;uFAAC;IAC9B,YAAY,GAAG,MAAM,CAAC,KAAK;qFAAC;IAE7B,cAAc,GAA0B,IAAI;IAC5C,aAAa,GAAwB,IAAI;;AAIhC,IAAA,YAAY,GAAG,QAAQ,CAAC,MACvC,kBAAkB,CAAC;AACjB,QAAA,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;AACvB,QAAA,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;AACjB,QAAA,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE;KAChC,CAAC;qFACH;AAEgB,IAAA,aAAa,GAAG,QAAQ,CAAC,MACxC,kBAAkB,CAAC;AACjB,QAAA,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;AACvB,QAAA,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE;AACnB,QAAA,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;AACjB,QAAA,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;KACtB,CAAC;sFACH;AAEQ,IAAA,WAAW,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE;oFAAC;AACxD,IAAA,cAAc,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE;uFAAC;AAC9D,IAAA,mBAAmB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,YAAY,EAAE;4FAAC;AACxE,IAAA,kBAAkB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE;2FAAC;AACnE,IAAA,YAAY,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE;qFAAC;AAC1D,IAAA,mBAAmB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,YAAY,EAAE;4FAAC;AACxE,IAAA,kBAAkB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC,WAAW,EAAE;2FAAC;;;IAK/E,iBAAiB,CAAC,SAAiB,EAAE,QAAiB,EAAA;AACpD,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,EAAE;AACtC,QAAA,MAAM,iBAAiB,GAAG,2BAA2B,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC;QACzF,MAAM,KAAK,GAAG;AACZ,cAAE,uBAAuB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,WAAW,EAAE;cACxE,yBAAyB,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAC7C,OAAO,OAAO,CAAC,IAAI,EAAE,iBAAiB,EAAE,KAAK,CAAC;IAChD;;;AAIA,IAAA,QAAQ,CAAC,QAAgB,EAAA;AACvB,QAAA,OAAO,GAAG,IAAI,CAAC,WAAW,CAAA,KAAA,EAAQ,QAAQ,EAAE;IAC9C;;AAGA,IAAA,UAAU,CAAC,QAAgB,EAAA;AACzB,QAAA,OAAO,GAAG,IAAI,CAAC,WAAW,CAAA,OAAA,EAAU,QAAQ,EAAE;IAChD;;;AAKA,IAAA,WAAW,CAAC,QAAgB,EAAA;AAC1B,QAAA,OAAO,IAAI,CAAC,WAAW,EAAE,KAAK,QAAQ;IACxC;AAEA;;;;;;;;;AASG;AACM,IAAA,cAAc,GAAG,QAAQ,CAAgB,MAAK;AACrD,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE;AACxB,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE;QACjC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AACvE,QAAA,IAAI,SAAS;AAAE,YAAA,OAAO,SAAS,CAAC,KAAK,EAAE;AACvC,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,IAAI;IACvD,CAAC;uFAAC;;AAGF,IAAA,cAAc,CAAC,QAAgB,EAAA;AAC7B,QAAA,OAAO,IAAI,CAAC,cAAc,EAAE,KAAK,QAAQ;IAC3C;;AAGA,IAAA,iBAAiB,CAAC,GAAiB,EAAA;AACjC,QAAA,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,EAAE;AACzB,QAAA,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE;AACf,YAAA,OAAO,IAAI;QACb;;QAEA,OAAO,IAAI,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC;IACxC;;;AAKA,IAAA,SAAS,CAAC,GAAiB,EAAA;QACzB,IAAI,GAAG,CAAC,QAAQ,EAAE;YAAE;AACpB,QAAA,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,EAAE;AACvB,QAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC;AACzB,QAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;;AAGnB,QAAA,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,IAAG;AAC9B,YAAA,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC;AACzB,YAAA,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AACb,YAAA,OAAO,IAAI;AACb,QAAA,CAAC,CAAC;;AAGF,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE;AAC3B,QAAA,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC;QACrD,IAAI,CAAC,aAAa,CAAC,QAAQ,CACzB,CAAA,EAAG,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,CAAA,MAAA,EAAS,GAAG,GAAG,CAAC,CAAA,IAAA,EAAO,OAAO,CAAC,MAAM,CAAA,CAAE,CAC7D;IACH;;IAGA,QAAQ,CAAC,GAAiB,EAAE,KAAY,EAAA;QACtC,KAAK,CAAC,eAAe,EAAE;AACvB,QAAA,IAAI,KAAK,YAAY,aAAa,EAAE;YAClC,KAAK,CAAC,cAAc,EAAE;QACxB;AACA,QAAA,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,EAAE;AACvB,QAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;;AAGrB,QAAA,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE;AACzB,YAAA,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE;AAC3B,YAAA,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC;YACrD,MAAM,IAAI,GAAG,IAAI,CAAC,qBAAqB,CAAC,OAAO,EAAE,GAAG,CAAC;YACrD,IAAI,IAAI,EAAE;AACR,gBAAA,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YACtB;QACF;IACF;IAEQ,qBAAqB,CAAC,IAA6B,EAAE,SAAiB,EAAA;;AAE5E,QAAA,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AAChD,YAAA,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;AAAE,gBAAA,OAAO,IAAI,CAAC,CAAC,CAAC;QACzC;AACA,QAAA,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE;AACvC,YAAA,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;AAAE,gBAAA,OAAO,IAAI,CAAC,CAAC,CAAC;QACzC;AACA,QAAA,OAAO,IAAI;IACb;;IAIQ,UAAU,GAAuD,IAAI;AAE7E;;;;AAIG;AACH,IAAA,SAAS,CAAC,KAAoB,EAAA;QAC5B,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE;AACtB,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,EAAE;AACvC,QAAA,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE;;;;;QAM3B,MAAM,UAAU,GAAG,QAAQ,CAAC,SAAS,CACnC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,aAAa,KAAK,KAAK,CAAC,MAAM,CACjD;AACD,QAAA,MAAM,SAAS,GAAG,UAAU,IAAI;AAC9B,cAAE;cACA,IAAI,CAAC,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;AAChE,QAAA,IAAI,SAAS,IAAI,CAAC,IAAI,SAAS,KAAK,IAAI,CAAC,UAAU,CAAC,eAAe,EAAE;AACnE,YAAA,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC;QAC1C;;;AAIA,QAAA,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE;AAC9C,YAAA,IAAI,UAAU,IAAI,CAAC,EAAE;gBACnB,KAAK,CAAC,cAAc,EAAE;gBACtB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,CAAC;YACzC;YACA;QACF;;;;;QAMA,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,UAAU,IAAI,CAAC,EAAE;YAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC;YACnC,IAAI,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;gBAC1C,KAAK,CAAC,cAAc,EAAE;AACtB,gBAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC;gBACzB;YACF;QACF;;AAGA,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,eAAe;AAC9C,QAAA,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC;AAChC,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,eAAe;AAC7C,QAAA,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,IAAI,CAAC,EAAE;;YAEpD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;QACpC;IACF;;;IAKA,WAAW,GAAA;QACT,MAAM,EAAE,GAAG,IAAI,CAAC,eAAe,EAAE,EAAE,aAAa;AAChD,QAAA,IAAI,CAAC,EAAE;YAAE;QACT,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,UAAU;QACpD,IAAI,UAAU,EAAE;AACd,YAAA,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;QAChD;aAAO;AACL,YAAA,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;QACjD;IACF;;IAGA,SAAS,GAAA;QACP,MAAM,EAAE,GAAG,IAAI,CAAC,eAAe,EAAE,EAAE,aAAa;AAChD,QAAA,IAAI,CAAC,EAAE;YAAE;QACT,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,UAAU;QACpD,IAAI,UAAU,EAAE;AACd,YAAA,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;QAC/C;aAAO;AACL,YAAA,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;QAChD;IACF;IAEQ,iBAAiB,GAAA;QACvB,MAAM,EAAE,GAAG,IAAI,CAAC,eAAe,EAAE,EAAE,aAAa;AAChD,QAAA,IAAI,CAAC,EAAE;YAAE;QAET,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,UAAU;QACpD,IAAI,UAAU,EAAE;YACd,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;AACzC,YAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,SAAS,GAAG,EAAE,CAAC,YAAY,GAAG,EAAE,CAAC,YAAY,GAAG,CAAC,CAAC;QAC7E;aAAO;YACL,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,GAAG,CAAC,CAAC;AAC1C,YAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,WAAW,GAAG,EAAE,CAAC,WAAW,GAAG,CAAC,CAAC;QAC5E;IACF;IAEA,eAAe,GAAA;QACb,IAAI,CAAC,mBAAmB,EAAE;IAC5B;AAEA,IAAA,WAAA,GAAA;QACE,eAAe,CAAC,MAAK;YACnB,IAAI,CAAC,oBAAoB,EAAE;AAC7B,QAAA,CAAC,CAAC;;;;;AAMF,QAAA,MAAM,CAAC,CAAC,SAAS,KAAI;AACnB,YAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,EAAE;AACvC,YAAA,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,EAAE;AACtC,YAAA,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE;AACzB,gBAAA,IAAI,CAAC,UAAU,GAAG,IAAI;gBACtB;YACF;AACA,YAAA,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,EAAE;AACxC,YAAA,MAAM,OAAO,GAAG,IAAI,eAAe,CAAC,QAAQ;AACzC,iBAAA,QAAQ;AACR,iBAAA,cAAc,EAAE;AACnB,YAAA,IAAI,WAAW,KAAK,UAAU,EAAE;gBAC9B,OAAO,CAAC,uBAAuB,EAAE;YACnC;iBAAO;AACL,gBAAA,OAAO,CAAC,yBAAyB,CAAC,SAAS,CAAC;YAC9C;AACA,YAAA,IAAI,CAAC,UAAU,GAAG,OAAO;;;;;;;;;;;;YAazB,SAAS,CAAC,MAAK;;;;AAIb,gBAAA,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,EAAE;AACvC,gBAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE;AACxB,gBAAA,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,SAAS,CAAC;AACxD,gBAAA,IAAI,GAAG,IAAI,CAAC,EAAE;AACZ,oBAAA,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC;gBAC/B;AACF,YAAA,CAAC,CAAC;YAEF,SAAS,CAAC,MAAK;gBACb,OAAO,CAAC,OAAO,EAAE;AACnB,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;AAEF,QAAA,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAK;AAC7B,YAAA,IAAI,CAAC,cAAc,EAAE,UAAU,EAAE;AACjC,YAAA,IAAI,CAAC,aAAa,IAAI;AACxB,QAAA,CAAC,CAAC;IACJ;IAEQ,mBAAmB,GAAA;;AAEzB,QAAA,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE;AACvB,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;YAClD,IAAI,KAAK,EAAE;gBACT,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;gBACnC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YAC/B;QACF;;AAGA,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE;QAC9B,IAAI,GAAG,EAAE;AACP,YAAA,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,IAAG;AAC9B,gBAAA,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC;AACzB,gBAAA,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AACb,gBAAA,OAAO,IAAI;AACb,YAAA,CAAC,CAAC;QACJ;IACF;IAEQ,oBAAoB,GAAA;QAC1B,MAAM,EAAE,GAAG,IAAI,CAAC,eAAe,EAAE,EAAE,aAAa;AAChD,QAAA,IAAI,CAAC,EAAE;YAAE;;QAGT,IAAI,CAAC,iBAAiB,EAAE;;AAGxB,QAAA,IAAI,OAAO,cAAc,KAAK,WAAW,EAAE;AACzC,YAAA,IAAI,CAAC,cAAc,GAAG,IAAI,cAAc,CAAC,MAAK;gBAC5C,IAAI,CAAC,iBAAiB,EAAE;AAC1B,YAAA,CAAC,CAAC;AACF,YAAA,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QACjC;;QAGA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE;AAC/C,QAAA,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC1D,QAAA,IAAI,CAAC,aAAa,GAAG,MAAM,EAAE,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC;IACvE;uGA9ZW,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAb,aAAa,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,SAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,WAAA,EAAA,EAAA,iBAAA,EAAA,aAAA,EAAA,UAAA,EAAA,aAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,OAAA,EAAA,EAAA,KAAA,EAAA,aAAA,EAAA,MAAA,EAAA,QAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,OAAA,EAAA,eAAA,EAAA,EAAA,EAAA,OAAA,EAAA,CAAA,EAAA,YAAA,EAAA,MAAA,EAAA,SAAA,EAuCQ,YAAY,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,iBAAA,EAAA,SAAA,EAGJ,0BAA0B,kLC5TpE,u1KA2HA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EDiJY,gBAAgB,EAAA,QAAA,EAAA,oBAAA,EAAA,MAAA,EAAA,CAAA,yBAAA,EAAA,kBAAA,EAAA,0BAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAvCf,0BAA0B,EAAA,QAAA,EAAA,uBAAA,EAAA,MAAA,EAAA,CAAA,aAAA,EAAA,YAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA;;2FA6C1B,aAAa,EAAA,UAAA,EAAA,CAAA;kBATzB,SAAS;+BACE,SAAS,EAAA,eAAA,EACF,uBAAuB,CAAC,MAAM,EAAA,OAAA,EACtC,CAAC,gBAAgB,EAAE,0BAA0B,CAAC,EAAA,IAAA,EAEjD;AACJ,wBAAA,SAAS,EAAE,eAAe;AAC3B,qBAAA,EAAA,QAAA,EAAA,u1KAAA,EAAA;+vBAyC+B,YAAY,CAAA,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,YAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,UAAA,CAAA,MAGJ,0BAA0B,CAAA,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,CAAA,EAAA,IAAA,EAAA,EAAA,CAAA,SAAA,EAAA,IAAA,EAAA,CAGJ,cAAc,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;AE/T9E;;AAEG;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cdevhub-ngx-tw-theme.mjs","sources":["../../../projects/ngx-tw/theme/theme.types.ts","../../../projects/ngx-tw/theme/theme.config.ts","../../../projects/ngx-tw/theme/theme.service.ts","../../../projects/ngx-tw/theme/theme.directive.ts","../../../projects/ngx-tw/theme/theme.bootstrap.ts","../../../projects/ngx-tw/theme/cdevhub-ngx-tw-theme.ts"],"sourcesContent":["/**\n * The user-selectable theme. `'system'` defers to the OS, resolving\n * `prefers-color-scheme` and `prefers-contrast` as two independent axes: dark\n * plus increased contrast lands on `'high-contrast-dark'`, light plus increased\n * contrast on `'high-contrast'`.\n */\nexport type TwTheme = 'light' | 'dark' | 'high-contrast' | 'high-contrast-dark' | 'system';\n\n/**\n * The theme actually applied to the DOM after resolving `'system'` against the\n * OS preference.\n *\n * The two axes are appearance (light / dark) and contrast (normal / increased),\n * so the four values are their product: `'high-contrast'` is the light-based\n * increased-contrast scheme and `'high-contrast-dark'` the dark-based one.\n */\nexport type TwResolvedTheme = 'light' | 'dark' | 'high-contrast' | 'high-contrast-dark';\n\n/** Ordered list of every {@link TwTheme} value, used by `cycleTheme()` and for UI iteration. */\nexport const TW_THEMES = [\n 'light',\n 'dark',\n 'high-contrast',\n 'high-contrast-dark',\n 'system',\n] as const satisfies readonly TwTheme[];\n\n/** Ordered list of every {@link TwResolvedTheme} value (i.e. {@link TW_THEMES} minus `'system'`). */\nexport const TW_RESOLVED_THEMES = [\n 'light',\n 'dark',\n 'high-contrast',\n 'high-contrast-dark',\n] as const satisfies readonly TwResolvedTheme[];\n\n/**\n * Runtime configuration for {@link provideTheme}; controls storage, attribute\n * name, target element, and default.\n *\n * Every member is optional. This interface only ever reaches consumers through\n * `provideTheme(config?: Partial<TwThemeConfig>)`, which fills each unset key\n * from {@link DEFAULT_TW_THEME_CONFIG} — so a consumer holding a config object\n * typed as `TwThemeConfig` must not be forced to restate keys they do not\n * override, and adding a member in a future minor must not break them. The\n * resolved value handed to {@link TW_THEME_CONFIG} is `Required<TwThemeConfig>`.\n */\nexport interface TwThemeConfig {\n /** The default theme when no preference is stored. Defaults to `'system'`. */\n defaultTheme?: TwTheme;\n /**\n * localStorage key for persisting an explicit theme choice. Defaults to\n * `'ngx-tw-theme'`. Only `setTheme()` / `cycleTheme()` write it — providing\n * the service never does.\n */\n storageKey?: string;\n /** The HTML attribute written to the target element. Defaults to `'data-theme'`. */\n attribute?: string;\n /** Which element receives the theme attribute. Defaults to `'documentElement'`. */\n target?: 'documentElement' | 'body';\n}\n\n/**\n * Composite snapshot of `ThemeService` state — selected, resolved, system, and\n * boolean flags.\n *\n * The three flags are **not** mutually exclusive: `'high-contrast-dark'` sets\n * both {@link isDark} and {@link isHighContrast}, because it is dark *and*\n * high contrast. Branch on {@link resolvedTheme} when you need one case.\n */\nexport interface TwThemeState {\n /** The user-selected theme — may be `'system'`. */\n readonly theme: TwTheme;\n /** The theme actually applied to the DOM — never `'system'`. */\n readonly resolvedTheme: TwResolvedTheme;\n /** The OS appearance preference detected via `prefers-color-scheme` and `prefers-contrast`. */\n readonly systemTheme: TwResolvedTheme;\n /** True when {@link resolvedTheme} is a dark scheme — `'dark'` or `'high-contrast-dark'`. */\n readonly isDark: boolean;\n /** True when {@link resolvedTheme} is exactly `'light'`; the light-based `'high-contrast'` does not set it. */\n readonly isLight: boolean;\n /** True when {@link resolvedTheme} is an increased-contrast scheme — `'high-contrast'` or `'high-contrast-dark'`. */\n readonly isHighContrast: boolean;\n}\n\n/**\n * Built-in defaults merged under any user-provided {@link TwThemeConfig} by\n * `provideTheme()`. Typed `Required<TwThemeConfig>` so readers keep a\n * non-optional `string` / `TwTheme` for every field even though the interface\n * itself is all-optional.\n */\nexport const DEFAULT_TW_THEME_CONFIG: Required<TwThemeConfig> = {\n defaultTheme: 'system',\n storageKey: 'ngx-tw-theme',\n attribute: 'data-theme',\n target: 'documentElement',\n};\n","import {\n InjectionToken,\n inject,\n makeEnvironmentProviders,\n provideEnvironmentInitializer,\n type EnvironmentProviders,\n} from '@angular/core';\nimport { type TwThemeConfig, DEFAULT_TW_THEME_CONFIG } from './theme.types';\nimport { ThemeService } from './theme.service';\n\n/**\n * Injection token carrying the resolved {@link TwThemeConfig} (storage key,\n * attribute, target element, default theme).\n *\n * The value is `Required<TwThemeConfig>`: `TwThemeConfig`'s members are\n * optional so consumers can pass a partial config, but `provideTheme` fills\n * every key from {@link DEFAULT_TW_THEME_CONFIG} before providing it. Injectors\n * therefore still read a non-optional field for each setting, exactly as\n * before this token's type was tightened.\n */\nexport const TW_THEME_CONFIG = new InjectionToken<Required<TwThemeConfig>>('TW_THEME_CONFIG');\n\n/**\n * @deprecated Renamed to {@link TW_THEME_CONFIG} for consistency with every\n * other ngx-tw injection token. This is the *same token instance*, not a copy —\n * providing under either name and injecting under the other resolves — so the\n * rename is safe to adopt incrementally. Removed in the next major.\n */\nexport const THEME_CONFIG = TW_THEME_CONFIG;\n\n/**\n * Registers {@link ThemeService}, a {@link TW_THEME_CONFIG} value built by merging\n * `config` over {@link DEFAULT_TW_THEME_CONFIG}, and an environment initializer\n * that constructs the service at bootstrap. Call once in the app's environment\n * providers.\n *\n * The initializer is what makes the call self-sufficient. Everything that\n * applies a theme lives in `ThemeService`'s field initialisers and constructor\n * effect, so without it the stored preference was read — and `data-theme`\n * written — only once something happened to `inject(ThemeService)`. An app\n * whose theme toggle sits in a lazily-loaded route therefore rendered every\n * other route with no `data-theme` at all, silently falling back to the\n * `prefers-color-scheme` CSS branch; the failure looked intermittent because\n * it disappeared whenever the stored choice agreed with the OS. Injecting the\n * service yourself is still supported and is idempotent.\n *\n * Keys explicitly set to `undefined` are dropped before merging. Root\n * `tsconfig.json` does not set `exactOptionalPropertyTypes`, so\n * `provideTheme({ storageKey: env.themeKey })` type-checks even when\n * `env.themeKey` is `string | undefined` — and a plain spread would then write\n * `undefined` into a field `TwThemeConfig` types as `string`, making\n * `ThemeService` persist under the literal `localStorage` key `\"undefined\"`\n * (and, for `attribute`, call `setAttribute(\"undefined\", …)`).\n *\n * Providing the service does **not** write to `localStorage`; only an explicit\n * `setTheme()` / `cycleTheme()` does. For a flash-free first paint when the\n * stored choice disagrees with the OS, pair this with\n * {@link TW_THEME_BOOTSTRAP_SCRIPT} in `index.html`.\n */\nexport function provideTheme(config?: Partial<TwThemeConfig>): EnvironmentProviders {\n const overrides = Object.fromEntries(\n Object.entries(config ?? {}).filter(([, value]) => value !== undefined),\n );\n return makeEnvironmentProviders([\n {\n provide: TW_THEME_CONFIG,\n useValue: { ...DEFAULT_TW_THEME_CONFIG, ...overrides } as Required<TwThemeConfig>,\n },\n ThemeService,\n provideEnvironmentInitializer(() => {\n inject(ThemeService);\n }),\n ]);\n}\n","import {\n Injectable,\n NgZone,\n computed,\n effect,\n inject,\n signal,\n PLATFORM_ID,\n type OnDestroy,\n} from '@angular/core';\nimport { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { TW_THEME_CONFIG } from './theme.config';\nimport { TW_THEMES, type TwTheme, type TwResolvedTheme, type TwThemeState } from './theme.types';\n\n/** OS colour-scheme query backing `'system'` resolution. */\nconst DARK_QUERY = '(prefers-color-scheme: dark)';\n\n/** OS contrast query backing `'system'` → `'high-contrast'` / `'high-contrast-dark'` resolution. */\nconst CONTRAST_QUERY = '(prefers-contrast: more)';\n\n/**\n * Stateful runtime service that owns the active theme, reacts to OS\n * `prefers-color-scheme` / `prefers-contrast` changes, persists an explicit\n * user selection to `localStorage`, and writes the resolved theme onto the\n * configured DOM target as a `data-theme` attribute.\n *\n * The selected {@link theme} may be `'system'` (defer to the OS); the\n * {@link resolvedTheme} computed from it is always one of `'light'`,\n * `'dark'`, `'high-contrast'`, or `'high-contrast-dark'` — never `'system'`.\n * Register via\n * {@link provideTheme} in the app's environment providers; `provideTheme`\n * also constructs the service at bootstrap, so injecting it is only needed\n * to read or change the theme.\n *\n * Storage is written **only** by {@link setTheme} / {@link cycleTheme}.\n * Merely providing the service never touches `localStorage`, so the\n * configured `defaultTheme` keeps applying until the user actually picks\n * something, and apps under a storage-consent flow can provide the service\n * before consent is granted.\n */\n@Injectable()\nexport class ThemeService implements OnDestroy {\n private readonly config = inject(TW_THEME_CONFIG);\n private readonly document = inject(DOCUMENT);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly ngZone = inject(NgZone);\n\n private darkQuery: MediaQueryList | null = null;\n private contrastQuery: MediaQueryList | null = null;\n\n // One listener for both queries: the resolution rule reads them together, so\n // a handler that only knew its own query would clobber the other's decision\n // on the next OS tick. The event payload is deliberately ignored — a\n // `MediaQueryList`'s `matches` is already updated when `change` fires, so\n // re-reading both is the single source of truth.\n private readonly mediaListener = () => {\n this.systemTheme.set(this.detectSystemTheme());\n };\n\n /** The user-selected theme (may be `'system'`). */\n readonly theme = signal<TwTheme>(this.loadInitialTheme());\n\n /** The OS appearance preference — `prefers-color-scheme` plus `prefers-contrast`. */\n readonly systemTheme = signal<TwResolvedTheme>(this.detectSystemTheme());\n\n /** The resolved theme actually applied to the DOM (never `'system'`). */\n readonly resolvedTheme = computed<TwResolvedTheme>(() => {\n const t = this.theme();\n return t === 'system' ? this.systemTheme() : t;\n });\n\n /**\n * True when the resolved theme is a **dark** scheme — `'dark'` or\n * `'high-contrast-dark'`.\n *\n * It answers the appearance question, not \"which scheme\": the canonical use\n * is picking a colour that has to sit on the page background (a chart grid,\n * a canvas fill), and dark high contrast needs the dark answer there just as\n * much as plain dark does. It is therefore **not** mutually exclusive with\n * {@link isHighContrast}; branch on {@link resolvedTheme} for one case.\n */\n readonly isDark = computed(() => {\n const t = this.resolvedTheme();\n return t === 'dark' || t === 'high-contrast-dark';\n });\n /**\n * True when the resolved theme is exactly `'light'`.\n *\n * Deliberately narrower than {@link isDark}'s mirror image: the light-based\n * `'high-contrast'` does **not** set it, because widening it would change\n * what a shipped scheme reports to consumers already reading this flag.\n * Use `!isDark()` for the appearance question.\n */\n readonly isLight = computed(() => this.resolvedTheme() === 'light');\n /**\n * True when the resolved theme is an **increased-contrast** scheme —\n * `'high-contrast'` or `'high-contrast-dark'`.\n *\n * It answers the contrast question, so it can be true at the same time as\n * {@link isDark}.\n */\n readonly isHighContrast = computed(() => {\n const t = this.resolvedTheme();\n return t === 'high-contrast' || t === 'high-contrast-dark';\n });\n\n /** Snapshot of the full theme state — selected, resolved, system, and boolean flags. */\n readonly state = computed<TwThemeState>(() => ({\n theme: this.theme(),\n resolvedTheme: this.resolvedTheme(),\n systemTheme: this.systemTheme(),\n isDark: this.isDark(),\n isLight: this.isLight(),\n isHighContrast: this.isHighContrast(),\n }));\n\n constructor() {\n if (this.isBrowser) {\n // Register the media-query listeners outside the Angular zone — they fire\n // on OS preference changes, not user interaction, and we don't want\n // zone.js bookkeeping for every system tick. The listener calls\n // `signal.set()`, which schedules its own change detection.\n // Both `MediaQueryList`s already exist: the `systemTheme` field\n // initialiser above ran `detectSystemTheme()`, which mints them. The\n // `??=` keeps this correct if that ever stops being true.\n this.ngZone.runOutsideAngular(() => {\n this.darkQuery ??= window.matchMedia(DARK_QUERY);\n this.contrastQuery ??= window.matchMedia(CONTRAST_QUERY);\n this.darkQuery.addEventListener('change', this.mediaListener);\n this.contrastQuery.addEventListener('change', this.mediaListener);\n });\n }\n\n effect(() => {\n this.applyToDocument(this.resolvedTheme());\n });\n }\n\n /**\n * Sets the selected theme and persists it. Pass `'system'` to follow the OS\n * preference.\n *\n * This is the only entry point that writes to `localStorage` — see the note\n * on the class.\n */\n setTheme(theme: TwTheme): void {\n this.theme.set(theme);\n this.persistTheme(theme);\n }\n\n /** Advances the selected theme to the next entry in {@link TW_THEMES}, wrapping around, and persists it. */\n cycleTheme(): void {\n const idx = TW_THEMES.indexOf(this.theme());\n this.setTheme(TW_THEMES[(idx + 1) % TW_THEMES.length]);\n }\n\n /**\n * Writes the configured theme attribute onto an arbitrary element, scoping\n * that subtree to the given theme.\n *\n * Each of the four schemes ships an element-agnostic `[data-theme=…]` block\n * (`_light.css`, `_dark.css`, `_high-contrast.css`,\n * `_high-contrast-dark.css`), so the tokens really do re-resolve on the\n * element and cascade into its descendants — including back to `'light'`\n * from inside a dark page.\n *\n * Caveat: those CSS blocks key off the literal `data-theme` attribute. If\n * `provideTheme({ attribute })` renamed it, this method writes the renamed\n * attribute and the shipped stylesheet will not react — a custom attribute\n * only works with matching custom CSS.\n */\n applyToElement(element: HTMLElement, theme: TwResolvedTheme): void {\n element.setAttribute(this.config.attribute, theme);\n }\n\n ngOnDestroy(): void {\n this.darkQuery?.removeEventListener('change', this.mediaListener);\n this.contrastQuery?.removeEventListener('change', this.mediaListener);\n }\n\n private loadInitialTheme(): TwTheme {\n if (!this.isBrowser) return this.config.defaultTheme;\n try {\n const stored = localStorage.getItem(this.config.storageKey);\n if (stored && (TW_THEMES as readonly string[]).includes(stored)) {\n return stored as TwTheme;\n }\n } catch {\n /* localStorage unavailable */\n }\n return this.config.defaultTheme;\n }\n\n /**\n * Resolves `'system'` against the OS.\n *\n * The two preferences are treated as independent axes and composed, not\n * ranked: `prefers-color-scheme` picks the appearance and\n * `prefers-contrast: more` picks the contrast, giving the full 2×2 —\n * `light` / `dark` / `high-contrast` / `high-contrast-dark`.\n *\n * This is only correct because the library ships **both** high-contrast\n * ramps. While `_high-contrast.css` was the only one, contrast had to be\n * suppressed whenever the OS also asked for dark, or a user running dark +\n * increased contrast — a common pairing — would have been moved from a dark\n * surface onto a white one. `_high-contrast-dark.css` removed that\n * constraint, so the ranking is gone; if a future change ever drops one of\n * the two ramps, this method has to go back to ranking them.\n *\n * Both queries are minted here (not in the constructor) because the\n * `systemTheme` field initialiser calls this before the constructor body\n * runs; the constructor then attaches `change` listeners to the same two\n * objects, so the resolution stays live rather than read-once.\n */\n private detectSystemTheme(): TwResolvedTheme {\n if (!this.isBrowser) return 'light';\n const prefersDark = (this.darkQuery ??= window.matchMedia(DARK_QUERY)).matches;\n const prefersMoreContrast = (this.contrastQuery ??= window.matchMedia(CONTRAST_QUERY)).matches;\n if (prefersMoreContrast) return prefersDark ? 'high-contrast-dark' : 'high-contrast';\n return prefersDark ? 'dark' : 'light';\n }\n\n private applyToDocument(theme: TwResolvedTheme): void {\n if (!this.isBrowser) return;\n const target =\n this.config.target === 'documentElement'\n ? this.document.documentElement\n : this.document.body;\n target.setAttribute(this.config.attribute, theme);\n }\n\n private persistTheme(theme: TwTheme): void {\n if (!this.isBrowser) return;\n try {\n localStorage.setItem(this.config.storageKey, theme);\n } catch {\n /* localStorage unavailable */\n }\n }\n}\n","import { Directive, input } from '@angular/core';\nimport type { TwResolvedTheme } from './theme.types';\n\n@Directive({\n selector: '[twTheme]',\n host: {\n '[attr.data-theme]': 'twTheme()',\n },\n})\nexport class ThemeDirective {\n /**\n * Scopes a subtree to a specific resolved theme by writing `data-theme` on\n * the host. Required.\n *\n * Works in every direction: each of the four schemes ships an\n * element-agnostic `[data-theme=\"…\"]` block (`_light.css`, `_dark.css`,\n * `_high-contrast.css`, `_high-contrast-dark.css`), so a `'light'` pane\n * inside a dark page re-resolves the tokens rather than inheriting the\n * ancestor's.\n *\n * The attribute name is the literal `data-theme` that the shipped CSS keys\n * off — it deliberately ignores `provideTheme({ attribute })`, since a\n * renamed attribute matches none of those blocks.\n */\n readonly twTheme = input.required<TwResolvedTheme>();\n}\n","import { DEFAULT_TW_THEME_CONFIG } from './theme.types';\n\n/**\n * Body of an inline `<head>` script that applies a previously persisted theme\n * before the app bundle runs, eliminating the flash of the wrong theme on\n * reload.\n *\n * `ThemeService` cannot cover this case: it is JavaScript in the application\n * bundle, so the browser has already painted by the time it writes\n * `data-theme` — and `provideTheme`'s environment initializer does not change\n * that. The CSS `@media (prefers-color-scheme: dark)` fallback covers users\n * who never chose a theme; the flash is what an *explicit* choice that\n * disagrees with the OS looks like — light chosen on a dark machine, or\n * either high-contrast scheme chosen on a machine not already asking for\n * increased contrast (the CSS has a `prefers-color-scheme` branch but\n * deliberately no `prefers-contrast` one, so neither contrast scheme is\n * reachable without an explicit `data-theme`).\n *\n * The string is built from {@link DEFAULT_TW_THEME_CONFIG}, so the storage key\n * and attribute cannot drift from what `ThemeService` actually uses — that\n * drift is the reason this ships as code rather than as a README snippet\n * alone. Copy the literal script into `index.html`, or interpolate this\n * constant during an SSR / index transform:\n *\n * ```html\n * <head>\n * <script>try{var t=localStorage.getItem('ngx-tw-theme');if(t&&t!=='system')document.documentElement.setAttribute('data-theme',t)}catch(e){}</script>\n * </head>\n * ```\n *\n * Notes on the behaviour it deliberately does *not* have:\n * - A stored `'system'` writes nothing, so the CSS `prefers-color-scheme`\n * fallback keeps deciding — which is exactly what `'system'` means.\n * - It assumes the default `target: 'documentElement'`. A `<head>` script runs\n * before `<body>` exists, so `target: 'body'` cannot be bootstrapped this\n * way.\n * - If you overrode `storageKey` or `attribute` via `provideTheme`, adapt the\n * literal snippet to match; this constant only encodes the defaults.\n * - It is dependency-free and wrapped in `try`/`catch`, so a browser with\n * storage blocked falls through to the CSS behaviour instead of throwing\n * before the app loads.\n */\nexport const TW_THEME_BOOTSTRAP_SCRIPT =\n `try{var t=localStorage.getItem(${JSON.stringify(DEFAULT_TW_THEME_CONFIG.storageKey)});` +\n `if(t&&t!=='system')document.documentElement.setAttribute(` +\n `${JSON.stringify(DEFAULT_TW_THEME_CONFIG.attribute)},t)}catch(e){}`;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cdevhub-ngx-tw-theme.mjs","sources":["../../../projects/ngx-tw/theme/theme.types.ts","../../../projects/ngx-tw/theme/theme.config.ts","../../../projects/ngx-tw/theme/theme.service.ts","../../../projects/ngx-tw/theme/theme.directive.ts","../../../projects/ngx-tw/theme/theme.bootstrap.ts","../../../projects/ngx-tw/theme/cdevhub-ngx-tw-theme.ts"],"sourcesContent":["/**\n * The user-selectable theme. `'system'` defers to the OS, resolving\n * `prefers-color-scheme` and `prefers-contrast` as two independent axes: dark\n * plus increased contrast lands on `'high-contrast-dark'`, light plus increased\n * contrast on `'high-contrast'`.\n */\nexport type TwTheme = 'light' | 'dark' | 'high-contrast' | 'high-contrast-dark' | 'system';\n\n/**\n * The theme actually applied to the DOM after resolving `'system'` against the\n * OS preference.\n *\n * The two axes are appearance (light / dark) and contrast (normal / increased),\n * so the four values are their product: `'high-contrast'` is the light-based\n * increased-contrast scheme and `'high-contrast-dark'` the dark-based one.\n */\nexport type TwResolvedTheme = 'light' | 'dark' | 'high-contrast' | 'high-contrast-dark';\n\n/** Ordered list of every {@link TwTheme} value, used by `cycleTheme()` and for UI iteration. */\nexport const TW_THEMES = [\n 'light',\n 'dark',\n 'high-contrast',\n 'high-contrast-dark',\n 'system',\n] as const satisfies readonly TwTheme[];\n\n/** Ordered list of every {@link TwResolvedTheme} value (i.e. {@link TW_THEMES} minus `'system'`). */\nexport const TW_RESOLVED_THEMES = [\n 'light',\n 'dark',\n 'high-contrast',\n 'high-contrast-dark',\n] as const satisfies readonly TwResolvedTheme[];\n\n/**\n * Runtime configuration for {@link provideTheme}; controls storage, attribute\n * name, target element, and default.\n *\n * Every member is optional. This interface only ever reaches consumers through\n * `provideTheme(config?: Partial<TwThemeConfig>)`, which fills each unset key\n * from {@link DEFAULT_TW_THEME_CONFIG} — so a consumer holding a config object\n * typed as `TwThemeConfig` must not be forced to restate keys they do not\n * override, and adding a member in a future minor must not break them. The\n * resolved value handed to {@link TW_THEME_CONFIG} is `Required<TwThemeConfig>`.\n */\nexport interface TwThemeConfig {\n /** The default theme when no preference is stored. Defaults to `'system'`. */\n defaultTheme?: TwTheme;\n /**\n * localStorage key for persisting an explicit theme choice. Defaults to\n * `'ngx-tw-theme'`. Only `setTheme()` / `cycleTheme()` write it — providing\n * the service never does.\n */\n storageKey?: string;\n /**\n * The HTML attribute written to the target element. Defaults to `'data-theme'`.\n *\n * @deprecated Changing this breaks theming, and cannot be made to work. The\n * shipped stylesheets key off the **literal** `data-theme` — `_light.css`,\n * `_dark.css` and both high-contrast files each declare an element-agnostic\n * `[data-theme=\"…\"]` block, and CSS cannot parameterise an attribute name. So\n * a renamed attribute matches none of them: `ThemeService.applyToElement()`\n * writes the new name and nothing reacts, while `ThemeDirective` ignores this\n * option entirely and keeps writing `data-theme` (which is the only reason\n * `[twTheme]` still works when it is set). Two of the three consumers\n * therefore disagree with it by design.\n *\n * It is deprecated rather than removed because removing it is a breaking\n * change; it will go in the next major. To scope a theme to a subtree use the\n * `[twTheme]` directive, and to retheme, override the semantic tokens in your\n * own `@theme` block rather than renaming the attribute they hang off.\n */\n attribute?: string;\n /** Which element receives the theme attribute. Defaults to `'documentElement'`. */\n target?: 'documentElement' | 'body';\n}\n\n/**\n * Composite snapshot of `ThemeService` state — selected, resolved, system, and\n * boolean flags.\n *\n * The three flags are **not** mutually exclusive: `'high-contrast-dark'` sets\n * both {@link isDark} and {@link isHighContrast}, because it is dark *and*\n * high contrast. Branch on {@link resolvedTheme} when you need one case.\n */\nexport interface TwThemeState {\n /** The user-selected theme — may be `'system'`. */\n readonly theme: TwTheme;\n /** The theme actually applied to the DOM — never `'system'`. */\n readonly resolvedTheme: TwResolvedTheme;\n /** The OS appearance preference detected via `prefers-color-scheme` and `prefers-contrast`. */\n readonly systemTheme: TwResolvedTheme;\n /** True when {@link resolvedTheme} is a dark scheme — `'dark'` or `'high-contrast-dark'`. */\n readonly isDark: boolean;\n /** True when {@link resolvedTheme} is exactly `'light'`; the light-based `'high-contrast'` does not set it. */\n readonly isLight: boolean;\n /** True when {@link resolvedTheme} is an increased-contrast scheme — `'high-contrast'` or `'high-contrast-dark'`. */\n readonly isHighContrast: boolean;\n}\n\n/**\n * Built-in defaults merged under any user-provided {@link TwThemeConfig} by\n * `provideTheme()`. Typed `Required<TwThemeConfig>` so readers keep a\n * non-optional `string` / `TwTheme` for every field even though the interface\n * itself is all-optional.\n */\nexport const DEFAULT_TW_THEME_CONFIG: Required<TwThemeConfig> = {\n defaultTheme: 'system',\n storageKey: 'ngx-tw-theme',\n attribute: 'data-theme',\n target: 'documentElement',\n};\n","import {\n InjectionToken,\n inject,\n makeEnvironmentProviders,\n provideEnvironmentInitializer,\n type EnvironmentProviders,\n} from '@angular/core';\nimport { type TwThemeConfig, DEFAULT_TW_THEME_CONFIG } from './theme.types';\nimport { ThemeService } from './theme.service';\n\n/**\n * Injection token carrying the resolved {@link TwThemeConfig} (storage key,\n * attribute, target element, default theme).\n *\n * The value is `Required<TwThemeConfig>`: `TwThemeConfig`'s members are\n * optional so consumers can pass a partial config, but `provideTheme` fills\n * every key from {@link DEFAULT_TW_THEME_CONFIG} before providing it. Injectors\n * therefore still read a non-optional field for each setting, exactly as\n * before this token's type was tightened.\n */\nexport const TW_THEME_CONFIG = new InjectionToken<Required<TwThemeConfig>>('TW_THEME_CONFIG');\n\n/**\n * @deprecated Renamed to {@link TW_THEME_CONFIG} for consistency with every\n * other ngx-tw injection token. This is the *same token instance*, not a copy —\n * providing under either name and injecting under the other resolves — so the\n * rename is safe to adopt incrementally. Removed in the next major.\n */\nexport const THEME_CONFIG = TW_THEME_CONFIG;\n\n/**\n * Registers {@link ThemeService}, a {@link TW_THEME_CONFIG} value built by merging\n * `config` over {@link DEFAULT_TW_THEME_CONFIG}, and an environment initializer\n * that constructs the service at bootstrap. Call once in the app's environment\n * providers.\n *\n * The initializer is what makes the call self-sufficient. Everything that\n * applies a theme lives in `ThemeService`'s field initialisers and constructor\n * effect, so without it the stored preference was read — and `data-theme`\n * written — only once something happened to `inject(ThemeService)`. An app\n * whose theme toggle sits in a lazily-loaded route therefore rendered every\n * other route with no `data-theme` at all, silently falling back to the\n * `prefers-color-scheme` CSS branch; the failure looked intermittent because\n * it disappeared whenever the stored choice agreed with the OS. Injecting the\n * service yourself is still supported and is idempotent.\n *\n * Keys explicitly set to `undefined` are dropped before merging. Root\n * `tsconfig.json` does not set `exactOptionalPropertyTypes`, so\n * `provideTheme({ storageKey: env.themeKey })` type-checks even when\n * `env.themeKey` is `string | undefined` — and a plain spread would then write\n * `undefined` into a field `TwThemeConfig` types as `string`, making\n * `ThemeService` persist under the literal `localStorage` key `\"undefined\"`\n * (and, for `attribute`, call `setAttribute(\"undefined\", …)`).\n *\n * Providing the service does **not** write to `localStorage`; only an explicit\n * `setTheme()` / `cycleTheme()` does. For a flash-free first paint when the\n * stored choice disagrees with the OS, pair this with\n * {@link TW_THEME_BOOTSTRAP_SCRIPT} in `index.html`.\n */\nexport function provideTheme(config?: Partial<TwThemeConfig>): EnvironmentProviders {\n const overrides = Object.fromEntries(\n Object.entries(config ?? {}).filter(([, value]) => value !== undefined),\n );\n return makeEnvironmentProviders([\n {\n provide: TW_THEME_CONFIG,\n useValue: { ...DEFAULT_TW_THEME_CONFIG, ...overrides } as Required<TwThemeConfig>,\n },\n ThemeService,\n provideEnvironmentInitializer(() => {\n inject(ThemeService);\n }),\n ]);\n}\n","import {\n Injectable,\n NgZone,\n computed,\n effect,\n inject,\n signal,\n PLATFORM_ID,\n type OnDestroy,\n} from '@angular/core';\nimport { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { TW_THEME_CONFIG } from './theme.config';\nimport { TW_THEMES, type TwTheme, type TwResolvedTheme, type TwThemeState } from './theme.types';\n\n/** OS colour-scheme query backing `'system'` resolution. */\nconst DARK_QUERY = '(prefers-color-scheme: dark)';\n\n/** OS contrast query backing `'system'` → `'high-contrast'` / `'high-contrast-dark'` resolution. */\nconst CONTRAST_QUERY = '(prefers-contrast: more)';\n\n/**\n * Stateful runtime service that owns the active theme, reacts to OS\n * `prefers-color-scheme` / `prefers-contrast` changes, persists an explicit\n * user selection to `localStorage`, and writes the resolved theme onto the\n * configured DOM target as a `data-theme` attribute.\n *\n * The selected {@link theme} may be `'system'` (defer to the OS); the\n * {@link resolvedTheme} computed from it is always one of `'light'`,\n * `'dark'`, `'high-contrast'`, or `'high-contrast-dark'` — never `'system'`.\n * Register via\n * {@link provideTheme} in the app's environment providers; `provideTheme`\n * also constructs the service at bootstrap, so injecting it is only needed\n * to read or change the theme.\n *\n * Storage is written **only** by {@link setTheme} / {@link cycleTheme}.\n * Merely providing the service never touches `localStorage`, so the\n * configured `defaultTheme` keeps applying until the user actually picks\n * something, and apps under a storage-consent flow can provide the service\n * before consent is granted.\n */\n@Injectable()\nexport class ThemeService implements OnDestroy {\n private readonly config = inject(TW_THEME_CONFIG);\n private readonly document = inject(DOCUMENT);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly ngZone = inject(NgZone);\n\n private darkQuery: MediaQueryList | null = null;\n private contrastQuery: MediaQueryList | null = null;\n\n // One listener for both queries: the resolution rule reads them together, so\n // a handler that only knew its own query would clobber the other's decision\n // on the next OS tick. The event payload is deliberately ignored — a\n // `MediaQueryList`'s `matches` is already updated when `change` fires, so\n // re-reading both is the single source of truth.\n private readonly mediaListener = () => {\n this.systemTheme.set(this.detectSystemTheme());\n };\n\n /** The user-selected theme (may be `'system'`). */\n readonly theme = signal<TwTheme>(this.loadInitialTheme());\n\n /** The OS appearance preference — `prefers-color-scheme` plus `prefers-contrast`. */\n readonly systemTheme = signal<TwResolvedTheme>(this.detectSystemTheme());\n\n /** The resolved theme actually applied to the DOM (never `'system'`). */\n readonly resolvedTheme = computed<TwResolvedTheme>(() => {\n const t = this.theme();\n return t === 'system' ? this.systemTheme() : t;\n });\n\n /**\n * True when the resolved theme is a **dark** scheme — `'dark'` or\n * `'high-contrast-dark'`.\n *\n * It answers the appearance question, not \"which scheme\": the canonical use\n * is picking a colour that has to sit on the page background (a chart grid,\n * a canvas fill), and dark high contrast needs the dark answer there just as\n * much as plain dark does. It is therefore **not** mutually exclusive with\n * {@link isHighContrast}; branch on {@link resolvedTheme} for one case.\n */\n readonly isDark = computed(() => {\n const t = this.resolvedTheme();\n return t === 'dark' || t === 'high-contrast-dark';\n });\n /**\n * True when the resolved theme is exactly `'light'`.\n *\n * Deliberately narrower than {@link isDark}'s mirror image: the light-based\n * `'high-contrast'` does **not** set it, because widening it would change\n * what a shipped scheme reports to consumers already reading this flag.\n * Use `!isDark()` for the appearance question.\n */\n readonly isLight = computed(() => this.resolvedTheme() === 'light');\n /**\n * True when the resolved theme is an **increased-contrast** scheme —\n * `'high-contrast'` or `'high-contrast-dark'`.\n *\n * It answers the contrast question, so it can be true at the same time as\n * {@link isDark}.\n */\n readonly isHighContrast = computed(() => {\n const t = this.resolvedTheme();\n return t === 'high-contrast' || t === 'high-contrast-dark';\n });\n\n /** Snapshot of the full theme state — selected, resolved, system, and boolean flags. */\n readonly state = computed<TwThemeState>(() => ({\n theme: this.theme(),\n resolvedTheme: this.resolvedTheme(),\n systemTheme: this.systemTheme(),\n isDark: this.isDark(),\n isLight: this.isLight(),\n isHighContrast: this.isHighContrast(),\n }));\n\n constructor() {\n if (this.isBrowser) {\n // Register the media-query listeners outside the Angular zone — they fire\n // on OS preference changes, not user interaction, and we don't want\n // zone.js bookkeeping for every system tick. The listener calls\n // `signal.set()`, which schedules its own change detection.\n // Both `MediaQueryList`s already exist: the `systemTheme` field\n // initialiser above ran `detectSystemTheme()`, which mints them. The\n // `??=` keeps this correct if that ever stops being true.\n this.ngZone.runOutsideAngular(() => {\n this.darkQuery ??= window.matchMedia(DARK_QUERY);\n this.contrastQuery ??= window.matchMedia(CONTRAST_QUERY);\n this.darkQuery.addEventListener('change', this.mediaListener);\n this.contrastQuery.addEventListener('change', this.mediaListener);\n });\n }\n\n effect(() => {\n this.applyToDocument(this.resolvedTheme());\n });\n }\n\n /**\n * Sets the selected theme and persists it. Pass `'system'` to follow the OS\n * preference.\n *\n * This is the only entry point that writes to `localStorage` — see the note\n * on the class.\n */\n setTheme(theme: TwTheme): void {\n this.theme.set(theme);\n this.persistTheme(theme);\n }\n\n /** Advances the selected theme to the next entry in {@link TW_THEMES}, wrapping around, and persists it. */\n cycleTheme(): void {\n const idx = TW_THEMES.indexOf(this.theme());\n this.setTheme(TW_THEMES[(idx + 1) % TW_THEMES.length]);\n }\n\n /**\n * Writes the configured theme attribute onto an arbitrary element, scoping\n * that subtree to the given theme.\n *\n * Each of the four schemes ships an element-agnostic `[data-theme=…]` block\n * (`_light.css`, `_dark.css`, `_high-contrast.css`,\n * `_high-contrast-dark.css`), so the tokens really do re-resolve on the\n * element and cascade into its descendants — including back to `'light'`\n * from inside a dark page.\n *\n * Caveat: those CSS blocks key off the literal `data-theme` attribute. If\n * `provideTheme({ attribute })` renamed it, this method writes the renamed\n * attribute and the shipped stylesheet will not react — a custom attribute\n * only works with matching custom CSS.\n */\n applyToElement(element: HTMLElement, theme: TwResolvedTheme): void {\n element.setAttribute(this.config.attribute, theme);\n }\n\n ngOnDestroy(): void {\n this.darkQuery?.removeEventListener('change', this.mediaListener);\n this.contrastQuery?.removeEventListener('change', this.mediaListener);\n }\n\n private loadInitialTheme(): TwTheme {\n if (!this.isBrowser) return this.config.defaultTheme;\n try {\n const stored = localStorage.getItem(this.config.storageKey);\n if (stored && (TW_THEMES as readonly string[]).includes(stored)) {\n return stored as TwTheme;\n }\n } catch {\n /* localStorage unavailable */\n }\n return this.config.defaultTheme;\n }\n\n /**\n * Resolves `'system'` against the OS.\n *\n * The two preferences are treated as independent axes and composed, not\n * ranked: `prefers-color-scheme` picks the appearance and\n * `prefers-contrast: more` picks the contrast, giving the full 2×2 —\n * `light` / `dark` / `high-contrast` / `high-contrast-dark`.\n *\n * This is only correct because the library ships **both** high-contrast\n * ramps. While `_high-contrast.css` was the only one, contrast had to be\n * suppressed whenever the OS also asked for dark, or a user running dark +\n * increased contrast — a common pairing — would have been moved from a dark\n * surface onto a white one. `_high-contrast-dark.css` removed that\n * constraint, so the ranking is gone; if a future change ever drops one of\n * the two ramps, this method has to go back to ranking them.\n *\n * Both queries are minted here (not in the constructor) because the\n * `systemTheme` field initialiser calls this before the constructor body\n * runs; the constructor then attaches `change` listeners to the same two\n * objects, so the resolution stays live rather than read-once.\n */\n private detectSystemTheme(): TwResolvedTheme {\n if (!this.isBrowser) return 'light';\n const prefersDark = (this.darkQuery ??= window.matchMedia(DARK_QUERY)).matches;\n const prefersMoreContrast = (this.contrastQuery ??= window.matchMedia(CONTRAST_QUERY)).matches;\n if (prefersMoreContrast) return prefersDark ? 'high-contrast-dark' : 'high-contrast';\n return prefersDark ? 'dark' : 'light';\n }\n\n private applyToDocument(theme: TwResolvedTheme): void {\n if (!this.isBrowser) return;\n const target =\n this.config.target === 'documentElement'\n ? this.document.documentElement\n : this.document.body;\n target.setAttribute(this.config.attribute, theme);\n }\n\n private persistTheme(theme: TwTheme): void {\n if (!this.isBrowser) return;\n try {\n localStorage.setItem(this.config.storageKey, theme);\n } catch {\n /* localStorage unavailable */\n }\n }\n}\n","import { Directive, input } from '@angular/core';\nimport type { TwResolvedTheme } from './theme.types';\n\n@Directive({\n selector: '[twTheme]',\n host: {\n '[attr.data-theme]': 'twTheme()',\n },\n})\nexport class ThemeDirective {\n /**\n * Scopes a subtree to a specific resolved theme by writing `data-theme` on\n * the host. Required.\n *\n * Works in every direction: each of the four schemes ships an\n * element-agnostic `[data-theme=\"…\"]` block (`_light.css`, `_dark.css`,\n * `_high-contrast.css`, `_high-contrast-dark.css`), so a `'light'` pane\n * inside a dark page re-resolves the tokens rather than inheriting the\n * ancestor's.\n *\n * The attribute name is the literal `data-theme` that the shipped CSS keys\n * off — it deliberately ignores `provideTheme({ attribute })`, since a\n * renamed attribute matches none of those blocks.\n */\n readonly twTheme = input.required<TwResolvedTheme>();\n}\n","import { DEFAULT_TW_THEME_CONFIG } from './theme.types';\n\n/**\n * Body of an inline `<head>` script that applies a previously persisted theme\n * before the app bundle runs, eliminating the flash of the wrong theme on\n * reload.\n *\n * `ThemeService` cannot cover this case: it is JavaScript in the application\n * bundle, so the browser has already painted by the time it writes\n * `data-theme` — and `provideTheme`'s environment initializer does not change\n * that. The CSS `@media (prefers-color-scheme: dark)` fallback covers users\n * who never chose a theme; the flash is what an *explicit* choice that\n * disagrees with the OS looks like — light chosen on a dark machine, or\n * either high-contrast scheme chosen on a machine not already asking for\n * increased contrast (the CSS has a `prefers-color-scheme` branch but\n * deliberately no `prefers-contrast` one, so neither contrast scheme is\n * reachable without an explicit `data-theme`).\n *\n * The string is built from {@link DEFAULT_TW_THEME_CONFIG}, so the storage key\n * and attribute cannot drift from what `ThemeService` actually uses — that\n * drift is the reason this ships as code rather than as a README snippet\n * alone. Copy the literal script into `index.html`, or interpolate this\n * constant during an SSR / index transform:\n *\n * ```html\n * <head>\n * <script>try{var t=localStorage.getItem('ngx-tw-theme');if(t&&t!=='system')document.documentElement.setAttribute('data-theme',t)}catch(e){}</script>\n * </head>\n * ```\n *\n * Notes on the behaviour it deliberately does *not* have:\n * - A stored `'system'` writes nothing, so the CSS `prefers-color-scheme`\n * fallback keeps deciding — which is exactly what `'system'` means.\n * - It assumes the default `target: 'documentElement'`. A `<head>` script runs\n * before `<body>` exists, so `target: 'body'` cannot be bootstrapped this\n * way.\n * - If you overrode `storageKey` or `attribute` via `provideTheme`, adapt the\n * literal snippet to match; this constant only encodes the defaults.\n * - It is dependency-free and wrapped in `try`/`catch`, so a browser with\n * storage blocked falls through to the CSS behaviour instead of throwing\n * before the app loads.\n */\nexport const TW_THEME_BOOTSTRAP_SCRIPT =\n `try{var t=localStorage.getItem(${JSON.stringify(DEFAULT_TW_THEME_CONFIG.storageKey)});` +\n `if(t&&t!=='system')document.documentElement.setAttribute(` +\n `${JSON.stringify(DEFAULT_TW_THEME_CONFIG.attribute)},t)}catch(e){}`;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { ComponentHarness, manualChangeDetection, HarnessPredicate } from '@angular/cdk/testing';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lets the zoneless change-detection scheduler run its pending tick.
|
|
5
|
+
*
|
|
6
|
+
* `ApplicationRef` schedules a tick with `setTimeout(cb)` raced against
|
|
7
|
+
* `requestAnimationFrame`. A timer registered *after* the notify that dirtied a
|
|
8
|
+
* signal therefore fires *after* that tick, so one macrotask is enough to
|
|
9
|
+
* observe everything already scheduled. It is a fixed, bounded yield, not a
|
|
10
|
+
* stabilization await, so it cannot hang.
|
|
11
|
+
*
|
|
12
|
+
* Every method spends one: an action yields after dispatching, so the effects
|
|
13
|
+
* the directive applied synchronously are rendered; a read yields before
|
|
14
|
+
* looking, so it sees any tick that was already pending. The composition is what
|
|
15
|
+
* matters — `show()` returns while the panel is still behind
|
|
16
|
+
* `twTooltipShowDelay`, and the following read's own yield is what picks the
|
|
17
|
+
* rendered content up.
|
|
18
|
+
*/
|
|
19
|
+
function afterSchedulerTick() {
|
|
20
|
+
return new Promise((resolve) => setTimeout(resolve));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Harness for a `[twTooltip]` trigger and the panel it shows.
|
|
24
|
+
*
|
|
25
|
+
* Deliberately narrow: a tooltip's whole observable surface is *whether it is
|
|
26
|
+
* showing and what it says*. Position, delays, color, size and arrow are
|
|
27
|
+
* configuration, not state, and a harness method for any of them would freeze an
|
|
28
|
+
* API that may still move — so none is offered.
|
|
29
|
+
*
|
|
30
|
+
* ## Nothing here awaits application stabilization, and that is load-bearing
|
|
31
|
+
*
|
|
32
|
+
* `TestbedHarnessEnvironment` routes every `TestElement` operation through
|
|
33
|
+
* `forceStabilize()` — `fixture.detectChanges()` then
|
|
34
|
+
* `await fixture.whenStable()` — and that await resolves only when Angular's
|
|
35
|
+
* `PendingTasks` set is empty. Under full-suite contention it was observed
|
|
36
|
+
* **not to resolve at all**, and everything built on it hung for the whole test
|
|
37
|
+
* budget instead of failing. This harness was withdrawn once for that, on five
|
|
38
|
+
* green local runs followed by one red CI run.
|
|
39
|
+
*
|
|
40
|
+
* Every method body therefore runs inside CDK's `manualChangeDetection()`,
|
|
41
|
+
* which sets the flag `forceStabilize()` early-returns on, and so does
|
|
42
|
+
* acquisition, via {@link load} / {@link loadAll}. The spec beside this file
|
|
43
|
+
* adds the third piece: it never awaits `fixture.whenStable()` either, not even
|
|
44
|
+
* in `beforeEach`. All three were needed — each of the two CI failures during
|
|
45
|
+
* this restoration was traced to one of them, and the second landed on
|
|
46
|
+
* `popover` rather than here, which is how it became clear the fault belongs to
|
|
47
|
+
* whichever harness spec lands in the unlucky worker slot rather than to any
|
|
48
|
+
* one component. `grep -c whenStable` over this file and its spec returns zero,
|
|
49
|
+
* which is the whole claim and is checkable in one command rather than by
|
|
50
|
+
* counting green runs. The spec pins the rest with tests that hold a real
|
|
51
|
+
* `PendingTasks` entry open across acquisition and every method.
|
|
52
|
+
*
|
|
53
|
+
* Why the application stops stabilizing is **not** known; this removes the
|
|
54
|
+
* dependency rather than curing it.
|
|
55
|
+
*
|
|
56
|
+
* The cost is that change detection is not forced on your behalf. Instead every
|
|
57
|
+
* method spends one macrotask on the scheduler (see {@link afterSchedulerTick}),
|
|
58
|
+
* which covers everything already scheduled — including the panel's first
|
|
59
|
+
* render, which is why {@link getTooltipText} does not come back empty on a
|
|
60
|
+
* tooltip that has only just attached. What it does not cover is state behind
|
|
61
|
+
* the component's own timers: {@link show} and {@link hide} dispatch the
|
|
62
|
+
* interaction and return, and the panel appears or detaches only once
|
|
63
|
+
* `twTooltipShowDelay` (200 ms by default) or `twTooltipHideDelay` (150 ms)
|
|
64
|
+
* elapses. Set both to `0` in a fixture, poll the DOM for the panel —
|
|
65
|
+
* `document.querySelector` needs no stabilization and so can neither hang nor
|
|
66
|
+
* burn a fixed interval — and only then read through the harness.
|
|
67
|
+
*
|
|
68
|
+
* ## Loading it
|
|
69
|
+
*
|
|
70
|
+
* The host is the trigger, which lives in the fixture, so the ordinary
|
|
71
|
+
* `TestbedHarnessEnvironment.loader(fixture)` is correct. The panel renders into
|
|
72
|
+
* the CDK overlay container outside the fixture, and this harness resolves it
|
|
73
|
+
* internally via `documentRootLocatorFactory()` — a consumer never needs
|
|
74
|
+
* `documentRootLoader`.
|
|
75
|
+
*
|
|
76
|
+
* The host selector is the directive's static `data-tw-tooltip-trigger` marker,
|
|
77
|
+
* which matches both spellings of the input: `twTooltip="literal"` and the bound
|
|
78
|
+
* `[twTooltip]="expr()"`, for which Angular renders no attribute at all. A
|
|
79
|
+
* harness matching the directive's own selector would silently miss every bound
|
|
80
|
+
* trigger.
|
|
81
|
+
*
|
|
82
|
+
* Unlike `MenuHarness` and `PopoverHarness`, a tooltip trigger carries no
|
|
83
|
+
* `aria-controls` linking it to its panel (`aria-describedby` points at CDK
|
|
84
|
+
* `AriaDescriber`'s shared hidden message element for string content), so the
|
|
85
|
+
* panel is resolved as "the tooltip showing in the document". That is exact for
|
|
86
|
+
* the hover/focus model, where only one tooltip is visible at a time, but a test
|
|
87
|
+
* that forces two open at once cannot tell them apart.
|
|
88
|
+
*/
|
|
89
|
+
class TooltipHarness extends ComponentHarness {
|
|
90
|
+
static hostSelector = '[data-tw-tooltip-trigger]';
|
|
91
|
+
/** Resolves the tooltip panel, which lives outside this harness's host. */
|
|
92
|
+
panel = this.documentRootLocatorFactory().locatorForOptional('tw-tooltip-overlay');
|
|
93
|
+
/**
|
|
94
|
+
* Acquires one harness without waiting for the application to stabilize —
|
|
95
|
+
* the counterpart to the guarantee the methods below make.
|
|
96
|
+
*
|
|
97
|
+
* `loader.getHarness(...)` is CDK's own acquisition path and it stabilizes:
|
|
98
|
+
* `getAllRawElements` calls `forceStabilize()`, and `HarnessPredicate`
|
|
99
|
+
* filtering routes through `parallel()`, which asks *every* active fixture in
|
|
100
|
+
* the worker to settle. Both await `fixture.whenStable()`, which is the one
|
|
101
|
+
* thing this harness exists to avoid — and the failure that withdrew it was
|
|
102
|
+
* observed there, at acquisition, before any method had run.
|
|
103
|
+
*
|
|
104
|
+
* So acquisition is wrapped too, and `manualChangeDetection()` nests: the
|
|
105
|
+
* inner `parallel()` sees the flag already set and skips the stabilization
|
|
106
|
+
* entirely. **Render the fixture first** (`fixture.detectChanges()`), because
|
|
107
|
+
* nothing here will do it for you; an unrendered fixture fails loudly with
|
|
108
|
+
* CDK's "failed to find element" rather than returning something wrong.
|
|
109
|
+
*
|
|
110
|
+
* Plain `loader.getHarness(TooltipHarness)` still works and is still supported.
|
|
111
|
+
* This is the path to use when a suite must not be able to hang.
|
|
112
|
+
*/
|
|
113
|
+
static load(loader, options = {}) {
|
|
114
|
+
return manualChangeDetection(() => loader.getHarness(TooltipHarness.with(options)));
|
|
115
|
+
}
|
|
116
|
+
/** {@link load} for every matching trigger rather than the first. */
|
|
117
|
+
static loadAll(loader, options = {}) {
|
|
118
|
+
return manualChangeDetection(() => loader.getAllHarnesses(TooltipHarness.with(options)));
|
|
119
|
+
}
|
|
120
|
+
/** Predicate for `locatorFor` / `locatorForAll`. */
|
|
121
|
+
static with(options = {}) {
|
|
122
|
+
return new HarnessPredicate(TooltipHarness, options).addOption('triggerText', options.triggerText, async (h, text) => HarnessPredicate.stringMatches(await h.getTriggerText(), text));
|
|
123
|
+
}
|
|
124
|
+
/** The text currently rendered in the trigger, trimmed. */
|
|
125
|
+
async getTriggerText() {
|
|
126
|
+
return manualChangeDetection(async () => {
|
|
127
|
+
await afterSchedulerTick();
|
|
128
|
+
return (await (await this.host()).text()).trim();
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/** Whether a tooltip panel is currently showing. */
|
|
132
|
+
async isOpen() {
|
|
133
|
+
return manualChangeDetection(async () => {
|
|
134
|
+
await afterSchedulerTick();
|
|
135
|
+
return (await this.panel()) !== null;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The tooltip's message, trimmed, or `null` when nothing is showing. Works
|
|
140
|
+
* for string and `TemplateRef` content alike.
|
|
141
|
+
*/
|
|
142
|
+
async getTooltipText() {
|
|
143
|
+
return manualChangeDetection(async () => {
|
|
144
|
+
await afterSchedulerTick();
|
|
145
|
+
const panel = await this.panel();
|
|
146
|
+
return panel ? (await panel.text()).trim() : null;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/** Hovers the trigger. The panel appears once `twTooltipShowDelay` elapses. */
|
|
150
|
+
async show() {
|
|
151
|
+
await manualChangeDetection(async () => {
|
|
152
|
+
await (await this.host()).hover();
|
|
153
|
+
await afterSchedulerTick();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/** Moves the pointer off the trigger. The panel detaches once `twTooltipHideDelay` elapses. */
|
|
157
|
+
async hide() {
|
|
158
|
+
await manualChangeDetection(async () => {
|
|
159
|
+
await (await this.host()).mouseAway();
|
|
160
|
+
await afterSchedulerTick();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Focuses the trigger — the keyboard equivalent of {@link show}, and the path
|
|
165
|
+
* WCAG 2.1 SC 1.4.13 requires to work.
|
|
166
|
+
*
|
|
167
|
+
* Moves real DOM focus *and* dispatches `focusin`, because a programmatic
|
|
168
|
+
* `focus()` does not reliably raise `focusin` in every test DOM. The directive
|
|
169
|
+
* treats a repeated show as a no-op, so the belt-and-braces pair is safe.
|
|
170
|
+
*/
|
|
171
|
+
async focusTrigger() {
|
|
172
|
+
await manualChangeDetection(async () => {
|
|
173
|
+
const host = await this.host();
|
|
174
|
+
await host.focus();
|
|
175
|
+
await host.dispatchEvent('focusin');
|
|
176
|
+
await afterSchedulerTick();
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/** Blurs the trigger — the keyboard equivalent of {@link hide}. */
|
|
180
|
+
async blurTrigger() {
|
|
181
|
+
await manualChangeDetection(async () => {
|
|
182
|
+
const host = await this.host();
|
|
183
|
+
await host.blur();
|
|
184
|
+
await host.dispatchEvent('focusout');
|
|
185
|
+
await afterSchedulerTick();
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Generated bundle index. Do not edit.
|
|
192
|
+
*/
|
|
193
|
+
|
|
194
|
+
export { TooltipHarness };
|
|
195
|
+
//# sourceMappingURL=cdevhub-ngx-tw-tooltip-testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdevhub-ngx-tw-tooltip-testing.mjs","sources":["../../../projects/ngx-tw/tooltip/testing/tooltip-harness.ts","../../../projects/ngx-tw/tooltip/testing/cdevhub-ngx-tw-tooltip-testing.ts"],"sourcesContent":["import { ComponentHarness, HarnessPredicate, manualChangeDetection } from '@angular/cdk/testing';\nimport type { BaseHarnessFilters, HarnessLoader, TestElement } from '@angular/cdk/testing';\n\n/** Filters accepted by `TooltipHarness.with`. */\nexport interface TooltipHarnessFilters extends BaseHarnessFilters {\n /** Match by the text rendered in the trigger. */\n triggerText?: string | RegExp;\n}\n\n/**\n * Lets the zoneless change-detection scheduler run its pending tick.\n *\n * `ApplicationRef` schedules a tick with `setTimeout(cb)` raced against\n * `requestAnimationFrame`. A timer registered *after* the notify that dirtied a\n * signal therefore fires *after* that tick, so one macrotask is enough to\n * observe everything already scheduled. It is a fixed, bounded yield, not a\n * stabilization await, so it cannot hang.\n *\n * Every method spends one: an action yields after dispatching, so the effects\n * the directive applied synchronously are rendered; a read yields before\n * looking, so it sees any tick that was already pending. The composition is what\n * matters — `show()` returns while the panel is still behind\n * `twTooltipShowDelay`, and the following read's own yield is what picks the\n * rendered content up.\n */\nfunction afterSchedulerTick(): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve));\n}\n\n/**\n * Harness for a `[twTooltip]` trigger and the panel it shows.\n *\n * Deliberately narrow: a tooltip's whole observable surface is *whether it is\n * showing and what it says*. Position, delays, color, size and arrow are\n * configuration, not state, and a harness method for any of them would freeze an\n * API that may still move — so none is offered.\n *\n * ## Nothing here awaits application stabilization, and that is load-bearing\n *\n * `TestbedHarnessEnvironment` routes every `TestElement` operation through\n * `forceStabilize()` — `fixture.detectChanges()` then\n * `await fixture.whenStable()` — and that await resolves only when Angular's\n * `PendingTasks` set is empty. Under full-suite contention it was observed\n * **not to resolve at all**, and everything built on it hung for the whole test\n * budget instead of failing. This harness was withdrawn once for that, on five\n * green local runs followed by one red CI run.\n *\n * Every method body therefore runs inside CDK's `manualChangeDetection()`,\n * which sets the flag `forceStabilize()` early-returns on, and so does\n * acquisition, via {@link load} / {@link loadAll}. The spec beside this file\n * adds the third piece: it never awaits `fixture.whenStable()` either, not even\n * in `beforeEach`. All three were needed — each of the two CI failures during\n * this restoration was traced to one of them, and the second landed on\n * `popover` rather than here, which is how it became clear the fault belongs to\n * whichever harness spec lands in the unlucky worker slot rather than to any\n * one component. `grep -c whenStable` over this file and its spec returns zero,\n * which is the whole claim and is checkable in one command rather than by\n * counting green runs. The spec pins the rest with tests that hold a real\n * `PendingTasks` entry open across acquisition and every method.\n *\n * Why the application stops stabilizing is **not** known; this removes the\n * dependency rather than curing it.\n *\n * The cost is that change detection is not forced on your behalf. Instead every\n * method spends one macrotask on the scheduler (see {@link afterSchedulerTick}),\n * which covers everything already scheduled — including the panel's first\n * render, which is why {@link getTooltipText} does not come back empty on a\n * tooltip that has only just attached. What it does not cover is state behind\n * the component's own timers: {@link show} and {@link hide} dispatch the\n * interaction and return, and the panel appears or detaches only once\n * `twTooltipShowDelay` (200 ms by default) or `twTooltipHideDelay` (150 ms)\n * elapses. Set both to `0` in a fixture, poll the DOM for the panel —\n * `document.querySelector` needs no stabilization and so can neither hang nor\n * burn a fixed interval — and only then read through the harness.\n *\n * ## Loading it\n *\n * The host is the trigger, which lives in the fixture, so the ordinary\n * `TestbedHarnessEnvironment.loader(fixture)` is correct. The panel renders into\n * the CDK overlay container outside the fixture, and this harness resolves it\n * internally via `documentRootLocatorFactory()` — a consumer never needs\n * `documentRootLoader`.\n *\n * The host selector is the directive's static `data-tw-tooltip-trigger` marker,\n * which matches both spellings of the input: `twTooltip=\"literal\"` and the bound\n * `[twTooltip]=\"expr()\"`, for which Angular renders no attribute at all. A\n * harness matching the directive's own selector would silently miss every bound\n * trigger.\n *\n * Unlike `MenuHarness` and `PopoverHarness`, a tooltip trigger carries no\n * `aria-controls` linking it to its panel (`aria-describedby` points at CDK\n * `AriaDescriber`'s shared hidden message element for string content), so the\n * panel is resolved as \"the tooltip showing in the document\". That is exact for\n * the hover/focus model, where only one tooltip is visible at a time, but a test\n * that forces two open at once cannot tell them apart.\n */\nexport class TooltipHarness extends ComponentHarness {\n static hostSelector = '[data-tw-tooltip-trigger]';\n\n /** Resolves the tooltip panel, which lives outside this harness's host. */\n private readonly panel =\n this.documentRootLocatorFactory().locatorForOptional('tw-tooltip-overlay');\n\n /**\n * Acquires one harness without waiting for the application to stabilize —\n * the counterpart to the guarantee the methods below make.\n *\n * `loader.getHarness(...)` is CDK's own acquisition path and it stabilizes:\n * `getAllRawElements` calls `forceStabilize()`, and `HarnessPredicate`\n * filtering routes through `parallel()`, which asks *every* active fixture in\n * the worker to settle. Both await `fixture.whenStable()`, which is the one\n * thing this harness exists to avoid — and the failure that withdrew it was\n * observed there, at acquisition, before any method had run.\n *\n * So acquisition is wrapped too, and `manualChangeDetection()` nests: the\n * inner `parallel()` sees the flag already set and skips the stabilization\n * entirely. **Render the fixture first** (`fixture.detectChanges()`), because\n * nothing here will do it for you; an unrendered fixture fails loudly with\n * CDK's \"failed to find element\" rather than returning something wrong.\n *\n * Plain `loader.getHarness(TooltipHarness)` still works and is still supported.\n * This is the path to use when a suite must not be able to hang.\n */\n static load(loader: HarnessLoader, options: TooltipHarnessFilters = {}): Promise<TooltipHarness> {\n return manualChangeDetection(() => loader.getHarness(TooltipHarness.with(options)));\n }\n\n /** {@link load} for every matching trigger rather than the first. */\n static loadAll(\n loader: HarnessLoader,\n options: TooltipHarnessFilters = {},\n ): Promise<TooltipHarness[]> {\n return manualChangeDetection(() => loader.getAllHarnesses(TooltipHarness.with(options)));\n }\n\n /** Predicate for `locatorFor` / `locatorForAll`. */\n static with(options: TooltipHarnessFilters = {}): HarnessPredicate<TooltipHarness> {\n return new HarnessPredicate(TooltipHarness, options).addOption(\n 'triggerText',\n options.triggerText,\n async (h, text) => HarnessPredicate.stringMatches(await h.getTriggerText(), text),\n );\n }\n\n /** The text currently rendered in the trigger, trimmed. */\n async getTriggerText(): Promise<string> {\n return manualChangeDetection(async () => {\n await afterSchedulerTick();\n return (await (await this.host()).text()).trim();\n });\n }\n\n /** Whether a tooltip panel is currently showing. */\n async isOpen(): Promise<boolean> {\n return manualChangeDetection(async () => {\n await afterSchedulerTick();\n return (await this.panel()) !== null;\n });\n }\n\n /**\n * The tooltip's message, trimmed, or `null` when nothing is showing. Works\n * for string and `TemplateRef` content alike.\n */\n async getTooltipText(): Promise<string | null> {\n return manualChangeDetection(async () => {\n await afterSchedulerTick();\n const panel: TestElement | null = await this.panel();\n return panel ? (await panel.text()).trim() : null;\n });\n }\n\n /** Hovers the trigger. The panel appears once `twTooltipShowDelay` elapses. */\n async show(): Promise<void> {\n await manualChangeDetection(async () => {\n await (await this.host()).hover();\n await afterSchedulerTick();\n });\n }\n\n /** Moves the pointer off the trigger. The panel detaches once `twTooltipHideDelay` elapses. */\n async hide(): Promise<void> {\n await manualChangeDetection(async () => {\n await (await this.host()).mouseAway();\n await afterSchedulerTick();\n });\n }\n\n /**\n * Focuses the trigger — the keyboard equivalent of {@link show}, and the path\n * WCAG 2.1 SC 1.4.13 requires to work.\n *\n * Moves real DOM focus *and* dispatches `focusin`, because a programmatic\n * `focus()` does not reliably raise `focusin` in every test DOM. The directive\n * treats a repeated show as a no-op, so the belt-and-braces pair is safe.\n */\n async focusTrigger(): Promise<void> {\n await manualChangeDetection(async () => {\n const host = await this.host();\n await host.focus();\n await host.dispatchEvent('focusin');\n await afterSchedulerTick();\n });\n }\n\n /** Blurs the trigger — the keyboard equivalent of {@link hide}. */\n async blurTrigger(): Promise<void> {\n await manualChangeDetection(async () => {\n const host = await this.host();\n await host.blur();\n await host.dispatchEvent('focusout');\n await afterSchedulerTick();\n });\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|