@juxtui/ui 0.2.0 → 0.3.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/README.md +1 -1
- package/dist/components/aspect-ratio/JAspectRatio.vue.d.ts +18 -0
- package/dist/components/avatar/JAvatar.vue.d.ts +1 -1
- package/dist/components/avatar-group/JAvatarGroup.vue.d.ts +19 -0
- package/dist/components/banner/JBanner.vue.d.ts +30 -0
- package/dist/components/breadcrumb/JBreadcrumb.vue.d.ts +29 -0
- package/dist/components/card/JCard.vue.d.ts +1 -1
- package/dist/components/collapsible/JCollapsible.vue.d.ts +28 -0
- package/dist/components/command-palette/JCommandPalette.vue.d.ts +1 -1
- package/dist/components/dropdown-menu/JDropdownMenu.vue.d.ts +1 -1
- package/dist/components/empty-state/JEmptyState.vue.d.ts +23 -0
- package/dist/components/field/JFieldShell.vue.d.ts +1 -1
- package/dist/components/pagination/JPagination.vue.d.ts +25 -0
- package/dist/components/primitives/JSlot.d.ts +1 -1
- package/dist/components/rating/JRating.vue.d.ts +27 -0
- package/dist/components/slider/JSlider.vue.d.ts +31 -0
- package/dist/components/stat/JStat.vue.d.ts +22 -0
- package/dist/components/tag/JTag.vue.d.ts +28 -0
- package/dist/components/timeline/JTimeline.vue.d.ts +12 -0
- package/dist/components/timeline/JTimelineItem.vue.d.ts +24 -0
- package/dist/components/toast/JToaster.vue.d.ts +1 -1
- package/dist/components/toggle-group/JToggleGroup.vue.d.ts +33 -0
- package/dist/components/toggle-group/JToggleGroupItem.vue.d.ts +19 -0
- package/dist/components/toggle-group/context.d.ts +9 -0
- package/dist/components/visually-hidden/JVisuallyHidden.vue.d.ts +19 -0
- package/dist/components.d.ts +16 -0
- package/dist/composables/useModal.d.ts +1 -1
- package/dist/composables/useToast.d.ts +1 -1
- package/dist/icons.d.ts +4 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +1161 -196
- package/dist/index.js.map +1 -1
- package/dist/nuxt.js +17 -1
- package/dist/nuxt.js.map +1 -1
- package/dist/style.css +1526 -548
- package/dist/tokens.css +5 -5
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["_useModel","_openBlock","_createElementBlock","_renderSlot","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_openBlock","_createElementBlock","_normalizeClass","_createBlock","_resolveDynamicComponent","_withCtx","_createElementVNode","_unref","_renderSlot","_createTextVNode","_toDisplayString","_createVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_openBlock","_createElementBlock","_normalizeClass","_renderSlot","_createBlock","_resolveDynamicComponent","_createCommentVNode","_createElementVNode","_toDisplayString","_createTextVNode","_createVNode","_unref","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_openBlock","_createElementBlock","_normalizeClass","_createElementVNode","_createCommentVNode","_renderSlot","_Fragment","_createTextVNode","_toDisplayString","_sfc_main","$slots","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_normalizeClass","_renderSlot","_createCommentVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_openBlock","_createBlock","_resolveDynamicComponent","_mergeProps","_withCtx","_createElementVNode","_createElementBlock","_renderSlot","_createCommentVNode","_createVNode","_Transition","_unref","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_openBlock","_createBlock","_resolveDynamicComponent","_normalizeClass","_withCtx","_createElementBlock","_renderSlot","_createElementVNode","_toDisplayString","_createCommentVNode","_sfc_main","$attrs","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_useModel","_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createElementVNode","_mergeProps","_createStaticVNode","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_sfc_main","listening","onKeydown","listen","unlisten","_openBlock","_createBlock","_Teleport","_renderSlot","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_useModel","_openBlock","_createBlock","JPortal","_withCtx","_createVNode","_Transition","_createElementBlock","_createCommentVNode","_createElementVNode","_unref","_withDirectives","_vModelText","_withModifiers","_Fragment","_renderList","_toDisplayString","_normalizeClass","_renderSlot","_resolveDynamicComponent","_createTextVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_useModel","_openBlock","_createBlock","JPortal","_withCtx","_createVNode","_Transition","_createElementBlock","_createCommentVNode","_unref","_createElementVNode","_normalizeClass","_renderSlot","_toDisplayString","_sfc_main","_hoisted_1","_openBlock","_createBlock","JPortal","_withCtx","_createVNode","_Transition","_createElementBlock","_normalizeClass","_normalizeStyle","_unref","_renderSlot","_createCommentVNode","_sfc_main","_useModel","_openBlock","_createElementBlock","_Fragment","_createVNode","_unref","_mergeProps","_withCtx","_renderSlot","JMenuContent","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createBlock","_resolveDynamicComponent","_normalizeClass","_withCtx","_createElementBlock","_renderSlot","_createCommentVNode","_createElementVNode","_toDisplayString","_unref","_sfc_main","_hoisted_1","_openBlock","_createElementBlock","_renderSlot","_sfc_main","_hoisted_1","_openBlock","_createElementBlock","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createElementBlock","_Fragment","_createElementVNode","_renderSlot","_createCommentVNode","_toDisplayString","_createVNode","_unref","JMenuContent","_withCtx","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_openBlock","_createElementBlock","_normalizeClass","_createBlock","_resolveDynamicComponent","_withCtx","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_createElementVNode","_createVNode","_Transition","_sfc_main","$attrs","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_useModel","_openBlock","_createBlock","JFieldShell","_unref","_normalizeClass","_normalizeStyle","_createSlots","_withCtx","_createElementVNode","_createElementBlock","_renderSlot","_createCommentVNode","_withDirectives","_mergeProps","_vModelDynamic","_sfc_main","_openBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","_toDisplayString","_renderSlot","_sfc_main","_hoisted_1","_useModel","_openBlock","_createElementBlock","_Fragment","_createVNode","_unref","_mergeProps","_withCtx","_renderSlot","JPortal","_Transition","_normalizeStyle","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_openBlock","_createElementBlock","_normalizeClass","_toDisplayString","_createCommentVNode","_createElementVNode","_normalizeStyle","_sfc_main","$attrs","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createElementVNode","_mergeProps","_unref","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_useModel","_openBlock","_createElementBlock","_normalizeClass","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_createElementVNode","_sfc_main","$attrs","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_useModel","_openBlock","_createBlock","JFieldShell","_unref","_normalizeClass","_normalizeStyle","_createSlots","_withCtx","_createElementVNode","_mergeProps","_renderSlot","_createTextVNode","_toDisplayString","_createElementBlock","_Fragment","_createVNode","_createCommentVNode","JPortal","_Transition","_withModifiers","_renderList","_sfc_main","$slots","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_normalizeClass","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_useModel","_openBlock","_createBlock","JPortal","_withCtx","_createVNode","_Transition","_createElementBlock","_createCommentVNode","_normalizeClass","_unref","_createElementVNode","_renderSlot","_toDisplayString","_sfc_main","_openBlock","_createElementBlock","_normalizeStyle","_Fragment","_renderList","_normalizeClass","_sfc_main","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_createVNode","_unref","_toDisplayString","_createCommentVNode","_sfc_main","$attrs","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_useModel","_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createElementVNode","_mergeProps","_createCommentVNode","_renderSlot","_createTextVNode","_toDisplayString","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_useModel","_openBlock","_createElementBlock","_normalizeClass","_createElementVNode","_normalizeStyle","_Fragment","_renderList","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_sfc_main","$attrs","$slots","_hoisted_1","_useModel","_openBlock","_createBlock","JFieldShell","_unref","_normalizeClass","_normalizeStyle","_createSlots","_withCtx","_createElementVNode","_withDirectives","_mergeProps","_vModelText","_renderSlot","_sfc_main","_hoisted_1","_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createVNode","_Transition","_withCtx","_createBlock","_unref","_resolveDynamicComponent","_createCommentVNode","_createElementVNode","_toDisplayString","_sfc_main","_openBlock","_createBlock","JPortal","_withCtx","_createElementVNode","_normalizeClass","_createVNode","_TransitionGroup","_createElementBlock","_Fragment","_renderList","JToastItem","_sfc_main","_openBlock","_createElementBlock","_Fragment","_createVNode","_unref","_mergeProps","_withCtx","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","JPortal","_Transition","_normalizeStyle","_sfc_main","components"],"sources":["../src/utils/dom.ts","../src/components/accordion/context.ts","../src/components/accordion/JAccordion.vue","../src/components/accordion/JAccordion.vue","../src/icons.ts","../src/components/accordion/JAccordionItem.vue","../src/components/accordion/JAccordionItem.vue","../src/components/alert/JAlert.vue","../src/components/alert/JAlert.vue","../src/components/avatar/JAvatar.vue","../src/components/avatar/JAvatar.vue","../src/components/badge/JBadge.vue","../src/components/badge/JBadge.vue","../src/components/button/JButton.vue","../src/components/button/JButton.vue","../src/components/card/JCard.vue","../src/components/card/JCard.vue","../src/components/checkbox/JCheckbox.vue","../src/components/checkbox/JCheckbox.vue","../src/composables/useFocusTrap.ts","../src/composables/useHotkey.ts","../src/composables/useLayer.ts","../src/composables/useScrollLock.ts","../src/components/command-palette/search.ts","../src/components/primitives/JPortal.vue","../src/components/primitives/JPortal.vue","../src/components/command-palette/JCommandPalette.vue","../src/components/command-palette/JCommandPalette.vue","../src/composables/useModal.ts","../src/components/dialog/JDialog.vue","../src/components/dialog/JDialog.vue","../src/components/primitives/JSlot.ts","../src/components/dropdown-menu/context.ts","../src/composables/useFloating.ts","../src/composables/useTypeahead.ts","../src/components/dropdown-menu/JMenuContent.vue","../src/components/dropdown-menu/JMenuContent.vue","../src/components/dropdown-menu/JDropdownMenu.vue","../src/components/dropdown-menu/JDropdownMenu.vue","../src/components/dropdown-menu/JDropdownMenuItem.vue","../src/components/dropdown-menu/JDropdownMenuItem.vue","../src/components/dropdown-menu/JDropdownMenuLabel.vue","../src/components/dropdown-menu/JDropdownMenuLabel.vue","../src/components/dropdown-menu/JDropdownMenuSeparator.vue","../src/components/dropdown-menu/JDropdownMenuSub.vue","../src/components/dropdown-menu/JDropdownMenuSub.vue","../src/components/field/JFieldShell.vue","../src/components/field/JFieldShell.vue","../src/components/field/useField.ts","../src/components/input/JInput.vue","../src/components/input/JInput.vue","../src/components/kbd/JKbd.vue","../src/components/kbd/JKbd.vue","../src/components/popover/JPopover.vue","../src/components/popover/JPopover.vue","../src/components/progress/JProgress.vue","../src/components/progress/JProgress.vue","../src/components/radio/context.ts","../src/components/radio/JRadio.vue","../src/components/radio/JRadio.vue","../src/components/radio/JRadioGroup.vue","../src/components/radio/JRadioGroup.vue","../src/components/select/JSelect.vue","../src/components/select/JSelect.vue","../src/components/separator/JSeparator.vue","../src/components/separator/JSeparator.vue","../src/components/sheet/JSheet.vue","../src/components/sheet/JSheet.vue","../src/components/skeleton/JSkeleton.vue","../src/components/skeleton/JSkeleton.vue","../src/components/spinner/JSpinner.vue","../src/components/spinner/JSpinner.vue","../src/components/switch/JSwitch.vue","../src/components/switch/JSwitch.vue","../src/components/tabs/JTabs.vue","../src/components/tabs/JTabs.vue","../src/components/textarea/JTextarea.vue","../src/components/textarea/JTextarea.vue","../src/composables/useToast.ts","../src/components/toast/JToastItem.vue","../src/components/toast/JToastItem.vue","../src/components/toast/JToaster.vue","../src/components/toast/JToaster.vue","../src/components/tooltip/JTooltip.vue","../src/components/tooltip/JTooltip.vue","../src/components.ts","../src/composables/useRecentCommands.ts","../src/plugin.ts"],"sourcesContent":["import type { ComponentPublicInstance, MaybeRef } from 'vue'\r\nimport { unref } from 'vue'\r\n\r\nexport const isClient = typeof window !== 'undefined' && typeof document !== 'undefined'\r\n\r\nexport type MaybeElement = HTMLElement | SVGElement | ComponentPublicInstance | null | undefined\r\n\r\n/** Resolve a template ref that may point at an element or a component instance. */\r\nexport function unrefElement(target: MaybeRef<MaybeElement>): HTMLElement | null {\r\n const value = unref(target)\r\n if (!value) return null\r\n if (value instanceof Element) return value as HTMLElement\r\n const el = (value as ComponentPublicInstance).$el as Node | null\r\n // A component whose root is a fragment exposes a text node as $el.\r\n if (el && el.nodeType !== Node.ELEMENT_NODE) {\r\n const next = el.nextSibling\r\n return next instanceof HTMLElement ? next : null\r\n }\r\n return (el as HTMLElement | null) ?? null\r\n}\r\n\r\nconst FOCUSABLE = [\r\n 'a[href]',\r\n 'area[href]',\r\n 'button:not([disabled])',\r\n 'input:not([disabled]):not([type=\"hidden\"])',\r\n 'select:not([disabled])',\r\n 'textarea:not([disabled])',\r\n 'iframe',\r\n 'audio[controls]',\r\n 'video[controls]',\r\n '[contenteditable]:not([contenteditable=\"false\"])',\r\n '[tabindex]',\r\n].join(',')\r\n\r\nfunction isVisible(el: HTMLElement): boolean {\r\n if (el.hidden) return false\r\n // `checkVisibility` is precise where supported; the rect check covers the rest.\r\n if (typeof el.checkVisibility === 'function') return el.checkVisibility({ visibilityProperty: true })\r\n for (let node: HTMLElement | null = el; node; node = node.parentElement) {\r\n const style = getComputedStyle(node)\r\n if (style.display === 'none' || style.visibility === 'hidden') return false\r\n }\r\n return true\r\n}\r\n\r\n/** Elements that can receive keyboard focus, in DOM order. */\r\nexport function getFocusableElements(container: HTMLElement): HTMLElement[] {\r\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\r\n el => el.tabIndex >= 0 && !el.closest('[inert]') && isVisible(el),\r\n )\r\n}\r\n\r\nexport function focusElement(el: HTMLElement | null | undefined, options?: FocusOptions) {\r\n if (!el) return\r\n el.focus({ preventScroll: true, ...options })\r\n}\r\n\r\nexport const isMac = () => isClient && /mac|iphone|ipad|ipod/i.test(navigator.platform || navigator.userAgent)\r\n\r\nexport function prefersReducedMotion() {\r\n return isClient && window.matchMedia('(prefers-reduced-motion: reduce)').matches\r\n}\r\n","import type { InjectionKey } from 'vue'\n\nexport interface AccordionContext {\n isOpen: (value: string) => boolean\n toggle: (value: string) => void\n headingLevel: () => number\n}\n\nexport const ACCORDION: InjectionKey<AccordionContext> = Symbol('JAccordion')\n","<script setup lang=\"ts\">\nimport { provide } from 'vue'\nimport { focusElement } from '../../utils/dom'\nimport { ACCORDION } from './context'\n\nexport interface AccordionProps {\n /** `single` keeps at most one item open; `multiple` lets any number stay open. */\n type?: 'single' | 'multiple'\n /** In `single` mode, allow closing the open item so none are open. */\n collapsible?: boolean\n /** Heading level wrapping each trigger, for a correct document outline. */\n headingLevel?: 2 | 3 | 4 | 5 | 6\n}\n\nconst props = withDefaults(defineProps<AccordionProps>(), {\n type: 'single',\n collapsible: true,\n headingLevel: 3,\n})\n\n/** Open item(s): a value in `single` mode, an array in `multiple` mode. */\nconst model = defineModel<string | string[] | null>({ default: null })\n\ndefineSlots<{ default?: () => unknown }>()\n\nfunction openValues(): string[] {\n const value = model.value\n if (Array.isArray(value)) return value\n return value ? [value] : []\n}\n\nprovide(ACCORDION, {\n isOpen: value => openValues().includes(value),\n toggle: (value) => {\n const current = openValues()\n const isOpen = current.includes(value)\n if (props.type === 'multiple') {\n model.value = isOpen ? current.filter(v => v !== value) : [...current, value]\n return\n }\n if (isOpen) {\n if (props.collapsible) model.value = null\n return\n }\n model.value = value\n },\n headingLevel: () => props.headingLevel,\n})\n\n// Arrow keys move between headers, per the WAI-ARIA accordion pattern.\nfunction onKeydown(event: KeyboardEvent) {\n const target = event.target as HTMLElement\n if (!target.classList.contains('j-accordion__trigger')) return\n const root = event.currentTarget as HTMLElement\n const triggers = Array.from(root.querySelectorAll<HTMLElement>('.j-accordion__trigger:not(:disabled)'))\n .filter(el => el.closest('.j-accordion') === root)\n const index = triggers.indexOf(target)\n let next: HTMLElement | undefined\n if (event.key === 'ArrowDown') next = triggers[(index + 1) % triggers.length]\n else if (event.key === 'ArrowUp') next = triggers[(index - 1 + triggers.length) % triggers.length]\n else if (event.key === 'Home') next = triggers[0]\n else if (event.key === 'End') next = triggers[triggers.length - 1]\n if (!next) return\n event.preventDefault()\n focusElement(next)\n}\n</script>\n\n<template>\n <div class=\"j-accordion\" @keydown=\"onKeydown\">\n <slot />\n </div>\n</template>\n\n<style>\n@layer juxt {\n.j-accordion {\n display: flex;\n flex-direction: column;\n font-family: var(--juxt-font-sans);\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { provide } from 'vue'\nimport { focusElement } from '../../utils/dom'\nimport { ACCORDION } from './context'\n\nexport interface AccordionProps {\n /** `single` keeps at most one item open; `multiple` lets any number stay open. */\n type?: 'single' | 'multiple'\n /** In `single` mode, allow closing the open item so none are open. */\n collapsible?: boolean\n /** Heading level wrapping each trigger, for a correct document outline. */\n headingLevel?: 2 | 3 | 4 | 5 | 6\n}\n\nconst props = withDefaults(defineProps<AccordionProps>(), {\n type: 'single',\n collapsible: true,\n headingLevel: 3,\n})\n\n/** Open item(s): a value in `single` mode, an array in `multiple` mode. */\nconst model = defineModel<string | string[] | null>({ default: null })\n\ndefineSlots<{ default?: () => unknown }>()\n\nfunction openValues(): string[] {\n const value = model.value\n if (Array.isArray(value)) return value\n return value ? [value] : []\n}\n\nprovide(ACCORDION, {\n isOpen: value => openValues().includes(value),\n toggle: (value) => {\n const current = openValues()\n const isOpen = current.includes(value)\n if (props.type === 'multiple') {\n model.value = isOpen ? current.filter(v => v !== value) : [...current, value]\n return\n }\n if (isOpen) {\n if (props.collapsible) model.value = null\n return\n }\n model.value = value\n },\n headingLevel: () => props.headingLevel,\n})\n\n// Arrow keys move between headers, per the WAI-ARIA accordion pattern.\nfunction onKeydown(event: KeyboardEvent) {\n const target = event.target as HTMLElement\n if (!target.classList.contains('j-accordion__trigger')) return\n const root = event.currentTarget as HTMLElement\n const triggers = Array.from(root.querySelectorAll<HTMLElement>('.j-accordion__trigger:not(:disabled)'))\n .filter(el => el.closest('.j-accordion') === root)\n const index = triggers.indexOf(target)\n let next: HTMLElement | undefined\n if (event.key === 'ArrowDown') next = triggers[(index + 1) % triggers.length]\n else if (event.key === 'ArrowUp') next = triggers[(index - 1 + triggers.length) % triggers.length]\n else if (event.key === 'Home') next = triggers[0]\n else if (event.key === 'End') next = triggers[triggers.length - 1]\n if (!next) return\n event.preventDefault()\n focusElement(next)\n}\n</script>\n\n<template>\n <div class=\"j-accordion\" @keydown=\"onKeydown\">\n <slot />\n </div>\n</template>\n\n<style>\n@layer juxt {\n.j-accordion {\n display: flex;\n flex-direction: column;\n font-family: var(--juxt-font-sans);\n}\n}\n</style>\n","import type { FunctionalComponent, SVGAttributes } from 'vue'\r\nimport { h } from 'vue'\r\n\r\n/*\r\n * The handful of glyphs Juxt needs internally. Drawn on a 16px grid with a\r\n * 1.5px stroke so they sit at the same optical weight as 13–14px text.\r\n */\r\n\r\ntype Icon = FunctionalComponent<SVGAttributes>\r\n\r\nfunction icon(name: string, children: () => ReturnType<typeof h>[], viewBox = '0 0 16 16'): Icon {\r\n const component: Icon = (props) =>\r\n h(\r\n 'svg',\r\n {\r\n 'xmlns': 'http://www.w3.org/2000/svg',\r\n viewBox,\r\n 'width': 16,\r\n 'height': 16,\r\n 'fill': 'none',\r\n 'stroke': 'currentColor',\r\n 'stroke-width': 1.5,\r\n 'stroke-linecap': 'round',\r\n 'stroke-linejoin': 'round',\r\n 'aria-hidden': 'true',\r\n 'focusable': 'false',\r\n ...props,\r\n },\r\n children(),\r\n )\r\n component.displayName = name\r\n return component\r\n}\r\n\r\nexport const IconCheck = icon('IconCheck', () => [h('path', { d: 'M3.5 8.5l3 3 6-7' })])\r\nexport const IconChevronDown = icon('IconChevronDown', () => [h('path', { d: 'M4.5 6.25L8 9.75l3.5-3.5' })])\r\nexport const IconChevronUpDown = icon('IconChevronUpDown', () => [h('path', { d: 'M5.25 6L8 3.25 10.75 6M5.25 10L8 12.75 10.75 10' })])\r\nexport const IconChevronRight = icon('IconChevronRight', () => [h('path', { d: 'M6.25 4.5L9.75 8l-3.5 3.5' })])\r\nexport const IconX = icon('IconX', () => [h('path', { d: 'M4.5 4.5l7 7M11.5 4.5l-7 7' })])\r\nexport const IconSearch = icon('IconSearch', () => [h('circle', { cx: 7, cy: 7, r: 4.25 }), h('path', { d: 'M10.25 10.25L13 13' })])\r\nexport const IconCircleCheck = icon('IconCircleCheck', () => [h('circle', { cx: 8, cy: 8, r: 6.25 }), h('path', { d: 'M5.5 8.25l1.75 1.75L10.5 6.5' })])\r\nexport const IconCircleX = icon('IconCircleX', () => [h('circle', { cx: 8, cy: 8, r: 6.25 }), h('path', { d: 'M6.25 6.25l3.5 3.5M9.75 6.25l-3.5 3.5' })])\r\nexport const IconAlert = icon('IconAlert', () => [\r\n h('path', { d: 'M7.13 2.75a1 1 0 0 1 1.74 0l5.15 9a1 1 0 0 1-.87 1.5H2.85a1 1 0 0 1-.87-1.5z' }),\r\n h('path', { d: 'M8 6.5v2.5' }),\r\n h('path', { 'd': 'M8 11.1v.01', 'stroke-width': 1.75 }),\r\n])\r\nexport const IconInfo = icon('IconInfo', () => [\r\n h('circle', { cx: 8, cy: 8, r: 6.25 }),\r\n h('path', { d: 'M8 7.5v3.25' }),\r\n h('path', { 'd': 'M8 5.25v.01', 'stroke-width': 1.75 }),\r\n])\r\nexport const IconCornerDownLeft = icon('IconCornerDownLeft', () => [h('path', { d: 'M12.5 3.5v4.25a2 2 0 0 1-2 2h-7' }), h('path', { d: 'M6 7l-2.5 2.75L6 12.5' })])\r\n\r\n/** The loading spinner used across Juxt. A quarter arc over a faint track. */\r\nexport const Spinner: FunctionalComponent<{ size?: number }> = (props) => {\r\n const size = props.size ?? 16\r\n return h(\r\n 'svg',\r\n { 'class': 'j-spinner', 'viewBox': '0 0 16 16', 'width': size, 'height': size, 'aria-hidden': 'true', 'focusable': 'false' },\r\n [\r\n h('circle', { 'class': 'j-spinner__track', 'cx': 8, 'cy': 8, 'r': 6, 'stroke-width': 1.75 }),\r\n h('circle', { 'cx': 8, 'cy': 8, 'r': 6, 'stroke-width': 1.75, 'stroke-dasharray': '9.5 28', 'stroke-dashoffset': 0 }),\r\n ],\r\n )\r\n}\r\nSpinner.displayName = 'JSpinner'\r\n","<script setup lang=\"ts\">\nimport { computed, inject, useId } from 'vue'\nimport { IconChevronDown } from '../../icons'\nimport { ACCORDION } from './context'\n\nexport interface AccordionItemProps {\n /** Identifies the item in the accordion's `v-model`. */\n value: string\n title?: string\n disabled?: boolean\n}\n\nconst props = defineProps<AccordionItemProps>()\n\ndefineSlots<{ default?: () => unknown, title?: () => unknown }>()\n\nconst accordion = inject(ACCORDION)!\nconst id = `j-${useId()}`\nconst triggerId = `${id}-trigger`\nconst panelId = `${id}-panel`\nconst open = computed(() => accordion.isOpen(props.value))\nconst heading = computed(() => `h${accordion.headingLevel()}`)\n</script>\n\n<template>\n <div class=\"j-accordion__item\" :data-state=\"open ? 'open' : 'closed'\" :class=\"{ 'is-disabled': disabled }\">\n <component :is=\"heading\" class=\"j-accordion__heading\">\n <button\n :id=\"triggerId\"\n type=\"button\"\n class=\"j-accordion__trigger\"\n :aria-expanded=\"open\"\n :aria-controls=\"panelId\"\n :disabled=\"disabled\"\n @click=\"accordion.toggle(value)\"\n >\n <span class=\"j-accordion__title\"><slot name=\"title\">{{ title }}</slot></span>\n <IconChevronDown class=\"j-accordion__chevron\" />\n </button>\n </component>\n <div\n :id=\"panelId\"\n role=\"region\"\n class=\"j-accordion__panel\"\n :aria-labelledby=\"triggerId\"\n :inert=\"!open\"\n >\n <div class=\"j-accordion__panel-inner\">\n <div class=\"j-accordion__content\">\n <slot />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@layer juxt {\n.j-accordion__item {\n border-bottom: 1px solid var(--juxt-border-subtle);\n}\n\n.dark .j-accordion__item,\n[data-theme='dark'] .j-accordion__item {\n border-bottom-color: var(--juxt-border);\n}\n\n.j-accordion__heading {\n margin: 0;\n font: inherit;\n}\n\n.j-accordion__trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--juxt-space-4);\n width: 100%;\n min-height: 3rem;\n padding: var(--juxt-space-3) 0;\n border: 0;\n border-radius: var(--juxt-radius-xs);\n background: transparent;\n color: var(--juxt-fg);\n font: inherit;\n font-size: var(--juxt-text-md);\n font-weight: var(--juxt-weight-medium);\n letter-spacing: var(--juxt-tracking-tight);\n text-align: left;\n cursor: pointer;\n outline: 2px solid transparent;\n outline-offset: 0;\n -webkit-tap-highlight-color: transparent;\n transition:\n color var(--juxt-duration-fast) var(--juxt-ease-standard),\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\n}\n\n.j-accordion__trigger:focus-visible {\n outline-color: var(--juxt-ring);\n outline-offset: 2px;\n}\n\n.j-accordion__trigger:disabled {\n color: var(--juxt-fg-disabled);\n cursor: not-allowed;\n}\n\n.j-accordion__chevron {\n flex: none;\n width: 1rem;\n height: 1rem;\n color: var(--juxt-fg-muted);\n transition:\n transform var(--juxt-duration-normal) var(--juxt-ease-out),\n color var(--juxt-duration-fast) var(--juxt-ease-standard);\n}\n\n.j-accordion__trigger:hover:not(:disabled) .j-accordion__chevron {\n color: var(--juxt-fg);\n}\n\n.j-accordion__item[data-state='open'] .j-accordion__chevron {\n transform: rotate(180deg);\n}\n\n/* Height follows content via grid rows — no measuring, no magic numbers. */\n.j-accordion__panel {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows var(--juxt-duration-slow) var(--juxt-ease-out);\n}\n\n.j-accordion__item[data-state='open'] .j-accordion__panel {\n grid-template-rows: 1fr;\n}\n\n.j-accordion__panel-inner {\n min-height: 0;\n overflow: hidden;\n}\n\n.j-accordion__content {\n padding-bottom: var(--juxt-space-4);\n color: var(--juxt-fg-secondary);\n font-size: var(--juxt-text-md);\n line-height: var(--juxt-leading-normal);\n opacity: 0;\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-standard);\n}\n\n.j-accordion__item[data-state='open'] .j-accordion__content {\n opacity: 1;\n transition-delay: 60ms;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .j-accordion__panel,\n .j-accordion__chevron {\n transition: none;\n }\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, inject, useId } from 'vue'\nimport { IconChevronDown } from '../../icons'\nimport { ACCORDION } from './context'\n\nexport interface AccordionItemProps {\n /** Identifies the item in the accordion's `v-model`. */\n value: string\n title?: string\n disabled?: boolean\n}\n\nconst props = defineProps<AccordionItemProps>()\n\ndefineSlots<{ default?: () => unknown, title?: () => unknown }>()\n\nconst accordion = inject(ACCORDION)!\nconst id = `j-${useId()}`\nconst triggerId = `${id}-trigger`\nconst panelId = `${id}-panel`\nconst open = computed(() => accordion.isOpen(props.value))\nconst heading = computed(() => `h${accordion.headingLevel()}`)\n</script>\n\n<template>\n <div class=\"j-accordion__item\" :data-state=\"open ? 'open' : 'closed'\" :class=\"{ 'is-disabled': disabled }\">\n <component :is=\"heading\" class=\"j-accordion__heading\">\n <button\n :id=\"triggerId\"\n type=\"button\"\n class=\"j-accordion__trigger\"\n :aria-expanded=\"open\"\n :aria-controls=\"panelId\"\n :disabled=\"disabled\"\n @click=\"accordion.toggle(value)\"\n >\n <span class=\"j-accordion__title\"><slot name=\"title\">{{ title }}</slot></span>\n <IconChevronDown class=\"j-accordion__chevron\" />\n </button>\n </component>\n <div\n :id=\"panelId\"\n role=\"region\"\n class=\"j-accordion__panel\"\n :aria-labelledby=\"triggerId\"\n :inert=\"!open\"\n >\n <div class=\"j-accordion__panel-inner\">\n <div class=\"j-accordion__content\">\n <slot />\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@layer juxt {\n.j-accordion__item {\n border-bottom: 1px solid var(--juxt-border-subtle);\n}\n\n.dark .j-accordion__item,\n[data-theme='dark'] .j-accordion__item {\n border-bottom-color: var(--juxt-border);\n}\n\n.j-accordion__heading {\n margin: 0;\n font: inherit;\n}\n\n.j-accordion__trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--juxt-space-4);\n width: 100%;\n min-height: 3rem;\n padding: var(--juxt-space-3) 0;\n border: 0;\n border-radius: var(--juxt-radius-xs);\n background: transparent;\n color: var(--juxt-fg);\n font: inherit;\n font-size: var(--juxt-text-md);\n font-weight: var(--juxt-weight-medium);\n letter-spacing: var(--juxt-tracking-tight);\n text-align: left;\n cursor: pointer;\n outline: 2px solid transparent;\n outline-offset: 0;\n -webkit-tap-highlight-color: transparent;\n transition:\n color var(--juxt-duration-fast) var(--juxt-ease-standard),\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\n}\n\n.j-accordion__trigger:focus-visible {\n outline-color: var(--juxt-ring);\n outline-offset: 2px;\n}\n\n.j-accordion__trigger:disabled {\n color: var(--juxt-fg-disabled);\n cursor: not-allowed;\n}\n\n.j-accordion__chevron {\n flex: none;\n width: 1rem;\n height: 1rem;\n color: var(--juxt-fg-muted);\n transition:\n transform var(--juxt-duration-normal) var(--juxt-ease-out),\n color var(--juxt-duration-fast) var(--juxt-ease-standard);\n}\n\n.j-accordion__trigger:hover:not(:disabled) .j-accordion__chevron {\n color: var(--juxt-fg);\n}\n\n.j-accordion__item[data-state='open'] .j-accordion__chevron {\n transform: rotate(180deg);\n}\n\n/* Height follows content via grid rows — no measuring, no magic numbers. */\n.j-accordion__panel {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows var(--juxt-duration-slow) var(--juxt-ease-out);\n}\n\n.j-accordion__item[data-state='open'] .j-accordion__panel {\n grid-template-rows: 1fr;\n}\n\n.j-accordion__panel-inner {\n min-height: 0;\n overflow: hidden;\n}\n\n.j-accordion__content {\n padding-bottom: var(--juxt-space-4);\n color: var(--juxt-fg-secondary);\n font-size: var(--juxt-text-md);\n line-height: var(--juxt-leading-normal);\n opacity: 0;\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-standard);\n}\n\n.j-accordion__item[data-state='open'] .j-accordion__content {\n opacity: 1;\n transition-delay: 60ms;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .j-accordion__panel,\n .j-accordion__chevron {\n transition: none;\n }\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { IconAlert, IconCircleCheck, IconCircleX, IconInfo, IconX } from '../../icons'\n\nexport type AlertVariant = 'neutral' | 'info' | 'success' | 'warning' | 'destructive'\n\nexport interface AlertProps {\n variant?: AlertVariant\n title?: string\n description?: string\n /** Show a close button; emits `dismiss`. */\n dismissible?: boolean\n /** Hide the leading icon. */\n hideIcon?: boolean\n}\n\nwithDefaults(defineProps<AlertProps>(), { variant: 'neutral' })\n\nconst emit = defineEmits<{ dismiss: [] }>()\n\ndefineSlots<{\n default?: () => unknown\n icon?: () => unknown\n actions?: () => unknown\n}>()\n\nconst icons = {\n neutral: IconInfo,\n info: IconInfo,\n success: IconCircleCheck,\n warning: IconAlert,\n destructive: IconCircleX,\n}\n</script>\n\n<template>\n <div class=\"j-alert\" :class=\"`j-alert--${variant}`\">\n <span v-if=\"!hideIcon\" class=\"j-alert__icon\" aria-hidden=\"true\">\n <slot name=\"icon\"><component :is=\"icons[variant]\" /></slot>\n </span>\n <div class=\"j-alert__body\">\n <p v-if=\"title\" class=\"j-alert__title\">\n {{ title }}\n </p>\n <div v-if=\"description || $slots.default\" class=\"j-alert__description\">\n <slot>{{ description }}</slot>\n </div>\n <div v-if=\"$slots.actions\" class=\"j-alert__actions\">\n <slot name=\"actions\" />\n </div>\n </div>\n <button v-if=\"dismissible\" type=\"button\" class=\"j-alert__close j-focusable\" aria-label=\"Dismiss\" @click=\"emit('dismiss')\">\n <IconX />\n </button>\n </div>\n</template>\n\n<style>\n@layer juxt {\n/*\n * Alerts stay on the page's surface. Colour lives in the icon and a hairline\n * of the border — enough to be noticed, never enough to shout.\n */\n.j-alert {\n --j-alert-accent: var(--juxt-fg-muted);\n --j-alert-border: var(--juxt-border);\n\n position: relative;\n display: flex;\n align-items: flex-start;\n gap: var(--juxt-space-3);\n padding: var(--juxt-space-3) var(--juxt-space-4);\n border: 1px solid var(--j-alert-border);\n border-radius: var(--juxt-radius-md);\n background: var(--juxt-surface);\n color: var(--juxt-fg);\n font-family: var(--juxt-font-sans);\n}\n\n.j-alert--info {\n --j-alert-accent: var(--juxt-info-text);\n}\n\n.j-alert--success {\n --j-alert-accent: var(--juxt-success-text);\n}\n\n.j-alert--warning {\n --j-alert-accent: var(--juxt-warning-text);\n --j-alert-border: var(--juxt-warning-border);\n}\n\n.j-alert--destructive {\n --j-alert-accent: var(--juxt-danger-text);\n --j-alert-border: var(--juxt-danger-border);\n}\n\n.j-alert__icon {\n display: grid;\n flex: none;\n place-items: center;\n height: 1.25rem;\n color: var(--j-alert-accent);\n}\n\n.j-alert__icon > svg {\n width: 1rem;\n height: 1rem;\n}\n\n.j-alert__body {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: var(--juxt-space-0-5);\n min-width: 0;\n}\n\n.j-alert__title {\n margin: 0;\n font-size: var(--juxt-text-md);\n font-weight: var(--juxt-weight-medium);\n line-height: 1.25rem;\n letter-spacing: var(--juxt-tracking-tight);\n}\n\n.j-alert__description {\n color: var(--juxt-fg-secondary);\n font-size: var(--juxt-text-sm);\n line-height: var(--juxt-leading-normal);\n}\n\n.j-alert__description > p {\n margin: 0;\n}\n\n.j-alert__actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--juxt-space-2);\n margin-top: var(--juxt-space-2);\n}\n\n.j-alert:has(.j-alert__close) {\n padding-right: var(--juxt-space-10);\n}\n\n.j-alert__close {\n position: absolute;\n top: var(--juxt-space-2-5);\n right: var(--juxt-space-2-5);\n display: grid;\n place-items: center;\n width: 1.5rem;\n height: 1.5rem;\n padding: 0;\n border: 0;\n border-radius: var(--juxt-radius-xs);\n background: transparent;\n color: var(--juxt-fg-muted);\n cursor: pointer;\n transition-property: background-color, color, outline-color, outline-offset;\n}\n\n.j-alert__close:hover {\n background: var(--juxt-surface-hover);\n color: var(--juxt-fg);\n}\n\n.j-alert__close > svg {\n width: 0.875rem;\n height: 0.875rem;\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { IconAlert, IconCircleCheck, IconCircleX, IconInfo, IconX } from '../../icons'\n\nexport type AlertVariant = 'neutral' | 'info' | 'success' | 'warning' | 'destructive'\n\nexport interface AlertProps {\n variant?: AlertVariant\n title?: string\n description?: string\n /** Show a close button; emits `dismiss`. */\n dismissible?: boolean\n /** Hide the leading icon. */\n hideIcon?: boolean\n}\n\nwithDefaults(defineProps<AlertProps>(), { variant: 'neutral' })\n\nconst emit = defineEmits<{ dismiss: [] }>()\n\ndefineSlots<{\n default?: () => unknown\n icon?: () => unknown\n actions?: () => unknown\n}>()\n\nconst icons = {\n neutral: IconInfo,\n info: IconInfo,\n success: IconCircleCheck,\n warning: IconAlert,\n destructive: IconCircleX,\n}\n</script>\n\n<template>\n <div class=\"j-alert\" :class=\"`j-alert--${variant}`\">\n <span v-if=\"!hideIcon\" class=\"j-alert__icon\" aria-hidden=\"true\">\n <slot name=\"icon\"><component :is=\"icons[variant]\" /></slot>\n </span>\n <div class=\"j-alert__body\">\n <p v-if=\"title\" class=\"j-alert__title\">\n {{ title }}\n </p>\n <div v-if=\"description || $slots.default\" class=\"j-alert__description\">\n <slot>{{ description }}</slot>\n </div>\n <div v-if=\"$slots.actions\" class=\"j-alert__actions\">\n <slot name=\"actions\" />\n </div>\n </div>\n <button v-if=\"dismissible\" type=\"button\" class=\"j-alert__close j-focusable\" aria-label=\"Dismiss\" @click=\"emit('dismiss')\">\n <IconX />\n </button>\n </div>\n</template>\n\n<style>\n@layer juxt {\n/*\n * Alerts stay on the page's surface. Colour lives in the icon and a hairline\n * of the border — enough to be noticed, never enough to shout.\n */\n.j-alert {\n --j-alert-accent: var(--juxt-fg-muted);\n --j-alert-border: var(--juxt-border);\n\n position: relative;\n display: flex;\n align-items: flex-start;\n gap: var(--juxt-space-3);\n padding: var(--juxt-space-3) var(--juxt-space-4);\n border: 1px solid var(--j-alert-border);\n border-radius: var(--juxt-radius-md);\n background: var(--juxt-surface);\n color: var(--juxt-fg);\n font-family: var(--juxt-font-sans);\n}\n\n.j-alert--info {\n --j-alert-accent: var(--juxt-info-text);\n}\n\n.j-alert--success {\n --j-alert-accent: var(--juxt-success-text);\n}\n\n.j-alert--warning {\n --j-alert-accent: var(--juxt-warning-text);\n --j-alert-border: var(--juxt-warning-border);\n}\n\n.j-alert--destructive {\n --j-alert-accent: var(--juxt-danger-text);\n --j-alert-border: var(--juxt-danger-border);\n}\n\n.j-alert__icon {\n display: grid;\n flex: none;\n place-items: center;\n height: 1.25rem;\n color: var(--j-alert-accent);\n}\n\n.j-alert__icon > svg {\n width: 1rem;\n height: 1rem;\n}\n\n.j-alert__body {\n display: flex;\n flex: 1;\n flex-direction: column;\n gap: var(--juxt-space-0-5);\n min-width: 0;\n}\n\n.j-alert__title {\n margin: 0;\n font-size: var(--juxt-text-md);\n font-weight: var(--juxt-weight-medium);\n line-height: 1.25rem;\n letter-spacing: var(--juxt-tracking-tight);\n}\n\n.j-alert__description {\n color: var(--juxt-fg-secondary);\n font-size: var(--juxt-text-sm);\n line-height: var(--juxt-leading-normal);\n}\n\n.j-alert__description > p {\n margin: 0;\n}\n\n.j-alert__actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--juxt-space-2);\n margin-top: var(--juxt-space-2);\n}\n\n.j-alert:has(.j-alert__close) {\n padding-right: var(--juxt-space-10);\n}\n\n.j-alert__close {\n position: absolute;\n top: var(--juxt-space-2-5);\n right: var(--juxt-space-2-5);\n display: grid;\n place-items: center;\n width: 1.5rem;\n height: 1.5rem;\n padding: 0;\n border: 0;\n border-radius: var(--juxt-radius-xs);\n background: transparent;\n color: var(--juxt-fg-muted);\n cursor: pointer;\n transition-property: background-color, color, outline-color, outline-offset;\n}\n\n.j-alert__close:hover {\n background: var(--juxt-surface-hover);\n color: var(--juxt-fg);\n}\n\n.j-alert__close > svg {\n width: 0.875rem;\n height: 0.875rem;\n}\n}\n</style>\n","<script setup lang=\"ts\">\r\nimport { computed, onMounted, ref, watch } from 'vue'\r\n\r\nexport type AvatarStatus = 'online' | 'away' | 'busy' | 'offline'\r\n\r\nexport interface AvatarProps {\r\n src?: string\r\n /** Image alt text. Defaults to `name`. */\r\n alt?: string\r\n /** Full name — used for initials and as the accessible name. */\r\n name?: string\r\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'\r\n shape?: 'circle' | 'square'\r\n status?: AvatarStatus\r\n}\r\n\r\nconst props = withDefaults(defineProps<AvatarProps>(), { size: 'md', shape: 'circle' })\r\n\r\ndefineSlots<{ fallback?: () => unknown }>()\r\n\r\nconst state = ref<'loading' | 'loaded' | 'error'>(props.src ? 'loading' : 'error')\r\nconst img = ref<HTMLImageElement | null>(null)\r\n\r\nwatch(() => props.src, (src) => {\r\n state.value = src ? 'loading' : 'error'\r\n})\r\n\r\n// An image cached before hydration may have finished loading already.\r\nonMounted(() => {\r\n if (img.value?.complete && img.value.naturalWidth > 0) state.value = 'loaded'\r\n})\r\n\r\nconst initials = computed(() => {\r\n const parts = (props.name ?? '').trim().split(/\\s+/).filter(Boolean)\r\n if (parts.length === 0) return ''\r\n const first = parts[0]!\r\n const last = parts.length > 1 ? parts[parts.length - 1]! : ''\r\n return (first[0]! + (last[0] ?? '')).toUpperCase()\r\n})\r\n\r\nconst statusLabel: Record<AvatarStatus, string> = {\r\n online: 'Online',\r\n away: 'Away',\r\n busy: 'Busy',\r\n offline: 'Offline',\r\n}\r\n\r\nconst label = computed(() => {\r\n const who = props.alt ?? props.name\r\n if (!who) return undefined\r\n return props.status ? `${who} (${statusLabel[props.status]})` : who\r\n})\r\n</script>\r\n\r\n<template>\r\n <span\r\n class=\"j-avatar\"\r\n :class=\"[`j-avatar--${size}`, `j-avatar--${shape}`]\"\r\n :role=\"label ? 'img' : undefined\"\r\n :aria-label=\"label\"\r\n :data-state=\"state\"\r\n >\r\n <span class=\"j-avatar__frame\">\r\n <img\r\n v-if=\"src && state !== 'error'\"\r\n ref=\"img\"\r\n class=\"j-avatar__image\"\r\n :src=\"src\"\r\n alt=\"\"\r\n @load=\"state = 'loaded'\"\r\n @error=\"state = 'error'\"\r\n />\r\n <span v-if=\"state !== 'loaded'\" class=\"j-avatar__fallback\" aria-hidden=\"true\">\r\n <slot name=\"fallback\">\r\n <template v-if=\"initials\">{{ initials }}</template>\r\n <svg v-else viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"j-avatar__glyph\">\r\n <circle cx=\"8\" cy=\"6\" r=\"2.75\" />\r\n <path d=\"M2.75 14c.6-2.6 2.7-4.25 5.25-4.25S12.65 11.4 13.25 14z\" />\r\n </svg>\r\n </slot>\r\n </span>\r\n </span>\r\n <span v-if=\"status\" class=\"j-avatar__status\" :class=\"`j-avatar__status--${status}`\" aria-hidden=\"true\" />\r\n </span>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-avatar {\r\n --j-avatar-size: 2rem;\r\n --j-avatar-font: var(--juxt-text-xs);\r\n --j-avatar-status: 0.5rem;\r\n --j-avatar-radius: var(--juxt-radius-full);\r\n\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n width: var(--j-avatar-size);\r\n height: var(--j-avatar-size);\r\n font-family: var(--juxt-font-sans);\r\n vertical-align: middle;\r\n}\r\n\r\n.j-avatar--xs {\r\n --j-avatar-size: 1.25rem;\r\n --j-avatar-font: 0.5625rem;\r\n --j-avatar-status: 0.375rem;\r\n}\r\n\r\n.j-avatar--sm {\r\n --j-avatar-size: 1.5rem;\r\n --j-avatar-font: 0.625rem;\r\n --j-avatar-status: 0.4375rem;\r\n}\r\n\r\n.j-avatar--lg {\r\n --j-avatar-size: 2.5rem;\r\n --j-avatar-font: var(--juxt-text-sm);\r\n --j-avatar-status: 0.625rem;\r\n}\r\n\r\n.j-avatar--xl {\r\n --j-avatar-size: 3.5rem;\r\n --j-avatar-font: var(--juxt-text-lg);\r\n --j-avatar-status: 0.75rem;\r\n}\r\n\r\n.j-avatar--square {\r\n --j-avatar-radius: calc(var(--j-avatar-size) * 0.22);\r\n}\r\n\r\n.j-avatar__frame {\r\n position: relative;\r\n display: grid;\r\n width: 100%;\r\n height: 100%;\r\n overflow: hidden;\r\n border-radius: var(--j-avatar-radius);\r\n background: var(--juxt-surface-active);\r\n /* A hairline keeps light photos from bleeding into light backgrounds. */\r\n box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.06);\r\n}\r\n\r\n.dark .j-avatar__frame,\r\n[data-theme='dark'] .j-avatar__frame {\r\n box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.06);\r\n}\r\n\r\n.j-avatar__image,\r\n.j-avatar__fallback {\r\n grid-area: 1 / 1;\r\n width: 100%;\r\n height: 100%;\r\n}\r\n\r\n.j-avatar__image {\r\n object-fit: cover;\r\n opacity: 0;\r\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-avatar[data-state='loaded'] .j-avatar__image {\r\n opacity: 1;\r\n}\r\n\r\n.j-avatar__fallback {\r\n display: grid;\r\n place-items: center;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--j-avatar-font);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: 0.02em;\r\n line-height: 1;\r\n user-select: none;\r\n}\r\n\r\n.j-avatar__glyph {\r\n width: 62%;\r\n height: 62%;\r\n margin-top: 18%;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-avatar__status {\r\n position: absolute;\r\n right: 0;\r\n bottom: 0;\r\n width: var(--j-avatar-status);\r\n height: var(--j-avatar-status);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-fg-muted);\r\n /* The ring is the page background, so the dot cuts cleanly out of the photo. */\r\n box-shadow: 0 0 0 2px var(--j-avatar-ring, var(--juxt-bg));\r\n}\r\n\r\n.j-avatar--square .j-avatar__status {\r\n right: -2px;\r\n bottom: -2px;\r\n}\r\n\r\n.j-avatar__status--online {\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-avatar__status--away {\r\n background: var(--juxt-warning);\r\n}\r\n\r\n.j-avatar__status--busy {\r\n background: var(--juxt-danger);\r\n}\r\n\r\n.j-avatar__status--offline {\r\n background: var(--juxt-fg-disabled);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, onMounted, ref, watch } from 'vue'\r\n\r\nexport type AvatarStatus = 'online' | 'away' | 'busy' | 'offline'\r\n\r\nexport interface AvatarProps {\r\n src?: string\r\n /** Image alt text. Defaults to `name`. */\r\n alt?: string\r\n /** Full name — used for initials and as the accessible name. */\r\n name?: string\r\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'\r\n shape?: 'circle' | 'square'\r\n status?: AvatarStatus\r\n}\r\n\r\nconst props = withDefaults(defineProps<AvatarProps>(), { size: 'md', shape: 'circle' })\r\n\r\ndefineSlots<{ fallback?: () => unknown }>()\r\n\r\nconst state = ref<'loading' | 'loaded' | 'error'>(props.src ? 'loading' : 'error')\r\nconst img = ref<HTMLImageElement | null>(null)\r\n\r\nwatch(() => props.src, (src) => {\r\n state.value = src ? 'loading' : 'error'\r\n})\r\n\r\n// An image cached before hydration may have finished loading already.\r\nonMounted(() => {\r\n if (img.value?.complete && img.value.naturalWidth > 0) state.value = 'loaded'\r\n})\r\n\r\nconst initials = computed(() => {\r\n const parts = (props.name ?? '').trim().split(/\\s+/).filter(Boolean)\r\n if (parts.length === 0) return ''\r\n const first = parts[0]!\r\n const last = parts.length > 1 ? parts[parts.length - 1]! : ''\r\n return (first[0]! + (last[0] ?? '')).toUpperCase()\r\n})\r\n\r\nconst statusLabel: Record<AvatarStatus, string> = {\r\n online: 'Online',\r\n away: 'Away',\r\n busy: 'Busy',\r\n offline: 'Offline',\r\n}\r\n\r\nconst label = computed(() => {\r\n const who = props.alt ?? props.name\r\n if (!who) return undefined\r\n return props.status ? `${who} (${statusLabel[props.status]})` : who\r\n})\r\n</script>\r\n\r\n<template>\r\n <span\r\n class=\"j-avatar\"\r\n :class=\"[`j-avatar--${size}`, `j-avatar--${shape}`]\"\r\n :role=\"label ? 'img' : undefined\"\r\n :aria-label=\"label\"\r\n :data-state=\"state\"\r\n >\r\n <span class=\"j-avatar__frame\">\r\n <img\r\n v-if=\"src && state !== 'error'\"\r\n ref=\"img\"\r\n class=\"j-avatar__image\"\r\n :src=\"src\"\r\n alt=\"\"\r\n @load=\"state = 'loaded'\"\r\n @error=\"state = 'error'\"\r\n />\r\n <span v-if=\"state !== 'loaded'\" class=\"j-avatar__fallback\" aria-hidden=\"true\">\r\n <slot name=\"fallback\">\r\n <template v-if=\"initials\">{{ initials }}</template>\r\n <svg v-else viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"j-avatar__glyph\">\r\n <circle cx=\"8\" cy=\"6\" r=\"2.75\" />\r\n <path d=\"M2.75 14c.6-2.6 2.7-4.25 5.25-4.25S12.65 11.4 13.25 14z\" />\r\n </svg>\r\n </slot>\r\n </span>\r\n </span>\r\n <span v-if=\"status\" class=\"j-avatar__status\" :class=\"`j-avatar__status--${status}`\" aria-hidden=\"true\" />\r\n </span>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-avatar {\r\n --j-avatar-size: 2rem;\r\n --j-avatar-font: var(--juxt-text-xs);\r\n --j-avatar-status: 0.5rem;\r\n --j-avatar-radius: var(--juxt-radius-full);\r\n\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n width: var(--j-avatar-size);\r\n height: var(--j-avatar-size);\r\n font-family: var(--juxt-font-sans);\r\n vertical-align: middle;\r\n}\r\n\r\n.j-avatar--xs {\r\n --j-avatar-size: 1.25rem;\r\n --j-avatar-font: 0.5625rem;\r\n --j-avatar-status: 0.375rem;\r\n}\r\n\r\n.j-avatar--sm {\r\n --j-avatar-size: 1.5rem;\r\n --j-avatar-font: 0.625rem;\r\n --j-avatar-status: 0.4375rem;\r\n}\r\n\r\n.j-avatar--lg {\r\n --j-avatar-size: 2.5rem;\r\n --j-avatar-font: var(--juxt-text-sm);\r\n --j-avatar-status: 0.625rem;\r\n}\r\n\r\n.j-avatar--xl {\r\n --j-avatar-size: 3.5rem;\r\n --j-avatar-font: var(--juxt-text-lg);\r\n --j-avatar-status: 0.75rem;\r\n}\r\n\r\n.j-avatar--square {\r\n --j-avatar-radius: calc(var(--j-avatar-size) * 0.22);\r\n}\r\n\r\n.j-avatar__frame {\r\n position: relative;\r\n display: grid;\r\n width: 100%;\r\n height: 100%;\r\n overflow: hidden;\r\n border-radius: var(--j-avatar-radius);\r\n background: var(--juxt-surface-active);\r\n /* A hairline keeps light photos from bleeding into light backgrounds. */\r\n box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.06);\r\n}\r\n\r\n.dark .j-avatar__frame,\r\n[data-theme='dark'] .j-avatar__frame {\r\n box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.06);\r\n}\r\n\r\n.j-avatar__image,\r\n.j-avatar__fallback {\r\n grid-area: 1 / 1;\r\n width: 100%;\r\n height: 100%;\r\n}\r\n\r\n.j-avatar__image {\r\n object-fit: cover;\r\n opacity: 0;\r\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-avatar[data-state='loaded'] .j-avatar__image {\r\n opacity: 1;\r\n}\r\n\r\n.j-avatar__fallback {\r\n display: grid;\r\n place-items: center;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--j-avatar-font);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: 0.02em;\r\n line-height: 1;\r\n user-select: none;\r\n}\r\n\r\n.j-avatar__glyph {\r\n width: 62%;\r\n height: 62%;\r\n margin-top: 18%;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-avatar__status {\r\n position: absolute;\r\n right: 0;\r\n bottom: 0;\r\n width: var(--j-avatar-status);\r\n height: var(--j-avatar-status);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-fg-muted);\r\n /* The ring is the page background, so the dot cuts cleanly out of the photo. */\r\n box-shadow: 0 0 0 2px var(--j-avatar-ring, var(--juxt-bg));\r\n}\r\n\r\n.j-avatar--square .j-avatar__status {\r\n right: -2px;\r\n bottom: -2px;\r\n}\r\n\r\n.j-avatar__status--online {\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-avatar__status--away {\r\n background: var(--juxt-warning);\r\n}\r\n\r\n.j-avatar__status--busy {\r\n background: var(--juxt-danger);\r\n}\r\n\r\n.j-avatar__status--offline {\r\n background: var(--juxt-fg-disabled);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nexport type BadgeVariant = 'neutral' | 'success' | 'warning' | 'destructive' | 'accent'\r\n\r\nexport interface BadgeProps {\r\n variant?: BadgeVariant\r\n size?: 'sm' | 'md'\r\n /**\r\n * Status style: plain text led by a small square marker, no fill.\r\n * Reads as a state rather than a label.\r\n */\r\n dot?: boolean\r\n}\r\n\r\nwithDefaults(defineProps<BadgeProps>(), { variant: 'neutral', size: 'md' })\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <span class=\"j-badge\" :class=\"[`j-badge--${variant}`, `j-badge--${size}`, { 'j-badge--status': dot }]\">\r\n <span v-if=\"dot\" class=\"j-badge__marker\" aria-hidden=\"true\" />\r\n <span v-else-if=\"$slots.leading\" class=\"j-badge__icon\"><slot name=\"leading\" /></span>\r\n <slot />\r\n </span>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n/*\r\n * Badges are small, square-shouldered labels — closer to a stamp than a pill.\r\n * No borders: a quiet tint and the text colour carry the meaning.\r\n */\r\n.j-badge {\r\n --j-badge-fg: var(--juxt-fg-secondary);\r\n --j-badge-bg: var(--juxt-surface-active);\r\n --j-badge-marker: var(--juxt-fg-muted);\r\n\r\n display: inline-flex;\r\n flex: none;\r\n align-items: center;\r\n gap: 0.375rem;\r\n height: 1.25rem;\r\n padding: 0 0.375rem;\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--j-badge-bg);\r\n color: var(--j-badge-fg);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1;\r\n letter-spacing: 0;\r\n white-space: nowrap;\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-badge--sm {\r\n gap: 0.3125rem;\r\n height: 1.0625rem;\r\n padding: 0 0.3125rem;\r\n font-size: var(--juxt-text-2xs);\r\n}\r\n\r\n.j-badge--success {\r\n --j-badge-fg: var(--juxt-success-text);\r\n --j-badge-bg: var(--juxt-success-soft);\r\n --j-badge-marker: var(--juxt-success);\r\n}\r\n\r\n.j-badge--warning {\r\n --j-badge-fg: var(--juxt-warning-text);\r\n --j-badge-bg: var(--juxt-warning-soft);\r\n --j-badge-marker: var(--juxt-warning);\r\n}\r\n\r\n.j-badge--destructive {\r\n --j-badge-fg: var(--juxt-danger-text);\r\n --j-badge-bg: var(--juxt-danger-soft);\r\n --j-badge-marker: var(--juxt-danger);\r\n}\r\n\r\n/* Accent is the only solid badge — reserve it for the thing that matters. */\r\n.j-badge--accent {\r\n --j-badge-fg: var(--juxt-accent-fg);\r\n --j-badge-bg: var(--juxt-accent-strong);\r\n --j-badge-marker: var(--juxt-accent);\r\n}\r\n\r\n/* Status: text first, colour only in the marker. */\r\n.j-badge--status {\r\n padding: 0;\r\n background: none;\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-badge__marker {\r\n flex: none;\r\n width: 0.4375rem;\r\n height: 0.4375rem;\r\n border-radius: 1.5px;\r\n background: var(--j-badge-marker);\r\n}\r\n\r\n.j-badge--sm .j-badge__marker {\r\n width: 0.375rem;\r\n height: 0.375rem;\r\n}\r\n\r\n.j-badge__icon {\r\n display: inline-flex;\r\n margin-left: -0.0625rem;\r\n}\r\n\r\n.j-badge__icon > svg {\r\n width: 0.75rem;\r\n height: 0.75rem;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nexport type BadgeVariant = 'neutral' | 'success' | 'warning' | 'destructive' | 'accent'\r\n\r\nexport interface BadgeProps {\r\n variant?: BadgeVariant\r\n size?: 'sm' | 'md'\r\n /**\r\n * Status style: plain text led by a small square marker, no fill.\r\n * Reads as a state rather than a label.\r\n */\r\n dot?: boolean\r\n}\r\n\r\nwithDefaults(defineProps<BadgeProps>(), { variant: 'neutral', size: 'md' })\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <span class=\"j-badge\" :class=\"[`j-badge--${variant}`, `j-badge--${size}`, { 'j-badge--status': dot }]\">\r\n <span v-if=\"dot\" class=\"j-badge__marker\" aria-hidden=\"true\" />\r\n <span v-else-if=\"$slots.leading\" class=\"j-badge__icon\"><slot name=\"leading\" /></span>\r\n <slot />\r\n </span>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n/*\r\n * Badges are small, square-shouldered labels — closer to a stamp than a pill.\r\n * No borders: a quiet tint and the text colour carry the meaning.\r\n */\r\n.j-badge {\r\n --j-badge-fg: var(--juxt-fg-secondary);\r\n --j-badge-bg: var(--juxt-surface-active);\r\n --j-badge-marker: var(--juxt-fg-muted);\r\n\r\n display: inline-flex;\r\n flex: none;\r\n align-items: center;\r\n gap: 0.375rem;\r\n height: 1.25rem;\r\n padding: 0 0.375rem;\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--j-badge-bg);\r\n color: var(--j-badge-fg);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1;\r\n letter-spacing: 0;\r\n white-space: nowrap;\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-badge--sm {\r\n gap: 0.3125rem;\r\n height: 1.0625rem;\r\n padding: 0 0.3125rem;\r\n font-size: var(--juxt-text-2xs);\r\n}\r\n\r\n.j-badge--success {\r\n --j-badge-fg: var(--juxt-success-text);\r\n --j-badge-bg: var(--juxt-success-soft);\r\n --j-badge-marker: var(--juxt-success);\r\n}\r\n\r\n.j-badge--warning {\r\n --j-badge-fg: var(--juxt-warning-text);\r\n --j-badge-bg: var(--juxt-warning-soft);\r\n --j-badge-marker: var(--juxt-warning);\r\n}\r\n\r\n.j-badge--destructive {\r\n --j-badge-fg: var(--juxt-danger-text);\r\n --j-badge-bg: var(--juxt-danger-soft);\r\n --j-badge-marker: var(--juxt-danger);\r\n}\r\n\r\n/* Accent is the only solid badge — reserve it for the thing that matters. */\r\n.j-badge--accent {\r\n --j-badge-fg: var(--juxt-accent-fg);\r\n --j-badge-bg: var(--juxt-accent-strong);\r\n --j-badge-marker: var(--juxt-accent);\r\n}\r\n\r\n/* Status: text first, colour only in the marker. */\r\n.j-badge--status {\r\n padding: 0;\r\n background: none;\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-badge__marker {\r\n flex: none;\r\n width: 0.4375rem;\r\n height: 0.4375rem;\r\n border-radius: 1.5px;\r\n background: var(--j-badge-marker);\r\n}\r\n\r\n.j-badge--sm .j-badge__marker {\r\n width: 0.375rem;\r\n height: 0.375rem;\r\n}\r\n\r\n.j-badge__icon {\r\n display: inline-flex;\r\n margin-left: -0.0625rem;\r\n}\r\n\r\n.j-badge__icon > svg {\r\n width: 0.75rem;\r\n height: 0.75rem;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\nimport { computed } from 'vue'\r\nimport { Spinner } from '../../icons'\r\n\r\nexport type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive'\r\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg'\r\n\r\nexport interface ButtonProps {\r\n /** Visual weight. `primary` is for the single most important action in a view. */\r\n variant?: ButtonVariant\r\n size?: ButtonSize\r\n /** Shows a spinner, keeps the width, and blocks activation while staying focusable. */\r\n loading?: boolean\r\n disabled?: boolean\r\n /** Square button for a single icon. Provide an `aria-label`. */\r\n icon?: boolean\r\n /** Stretch to the full width of the container. */\r\n block?: boolean\r\n /** Render as another element or component, e.g. `a` or `NuxtLink`. */\r\n as?: string | Component\r\n type?: 'button' | 'submit' | 'reset'\r\n}\r\n\r\nconst props = withDefaults(defineProps<ButtonProps>(), {\r\n variant: 'primary',\r\n size: 'md',\r\n as: 'button',\r\n type: 'button',\r\n})\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n leading?: () => unknown\r\n trailing?: () => unknown\r\n}>()\r\n\r\nconst isNativeButton = computed(() => props.as === 'button')\r\nconst inert = computed(() => props.disabled || props.loading)\r\n\r\nconst attrs = computed(() => {\r\n if (isNativeButton.value) {\r\n return {\r\n type: props.type,\r\n // A loading button stays focusable so keyboard users don't lose their place.\r\n 'disabled': props.disabled || undefined,\r\n 'aria-disabled': props.loading || undefined,\r\n }\r\n }\r\n return {\r\n 'aria-disabled': inert.value || undefined,\r\n 'tabindex': props.disabled ? -1 : undefined,\r\n }\r\n})\r\n\r\nfunction onClick(event: MouseEvent) {\r\n if (inert.value) {\r\n event.preventDefault()\r\n event.stopImmediatePropagation()\r\n }\r\n}\r\n</script>\r\n\r\n<template>\r\n <component\r\n :is=\"as\"\r\n v-bind=\"attrs\"\r\n class=\"j-button j-focusable\"\r\n :class=\"[\r\n `j-button--${variant}`,\r\n `j-button--${size}`,\r\n { 'j-button--icon': icon, 'j-button--block': block, 'is-loading': loading },\r\n ]\"\r\n :aria-busy=\"loading || undefined\"\r\n :data-disabled=\"disabled || undefined\"\r\n @click.capture=\"onClick\"\r\n >\r\n <span class=\"j-button__content\">\r\n <span v-if=\"$slots.leading\" class=\"j-button__icon\"><slot name=\"leading\" /></span>\r\n <slot />\r\n <span v-if=\"$slots.trailing\" class=\"j-button__icon\"><slot name=\"trailing\" /></span>\r\n </span>\r\n <Transition name=\"j-button-spinner\">\r\n <span v-if=\"loading\" class=\"j-button__spinner\">\r\n <Spinner :size=\"size === 'xs' ? 12 : size === 'lg' ? 16 : 14\" />\r\n </span>\r\n </Transition>\r\n </component>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-button {\r\n --j-button-height: var(--juxt-control-md);\r\n --j-button-px: var(--juxt-space-3);\r\n --j-button-gap: var(--juxt-space-1-5);\r\n --j-button-font: var(--juxt-text-md);\r\n --j-button-radius: var(--juxt-radius-sm);\r\n\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n height: var(--j-button-height);\r\n padding: 0 var(--j-button-px);\r\n border: 1px solid transparent;\r\n border-radius: var(--j-button-radius);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--j-button-font);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n white-space: nowrap;\r\n text-decoration: none;\r\n cursor: pointer;\r\n user-select: none;\r\n -webkit-tap-highlight-color: transparent;\r\n touch-action: manipulation;\r\n transition-property: background-color, border-color, color, box-shadow, transform, outline-color, outline-offset;\r\n transition-duration: var(--juxt-duration-fast);\r\n transition-timing-function: var(--juxt-ease-standard);\r\n}\r\n\r\n/* Pressed: a short, physical dip. Released slower than pressed. */\r\n.j-button:active:not([aria-disabled='true'], :disabled) {\r\n transform: scale(0.97);\r\n transition-duration: var(--juxt-duration-instant);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-button:active:not([aria-disabled='true'], :disabled) {\r\n transform: none;\r\n }\r\n}\r\n\r\n.j-button__content {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: var(--j-button-gap);\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-button__icon {\r\n display: inline-flex;\r\n margin-inline: -0.125rem;\r\n opacity: 0.85;\r\n}\r\n\r\n.j-button__icon > svg {\r\n width: 1em;\r\n height: 1em;\r\n}\r\n\r\n.j-button.is-loading {\r\n cursor: progress;\r\n}\r\n\r\n.j-button.is-loading .j-button__content {\r\n opacity: 0;\r\n transform: scale(0.96);\r\n}\r\n\r\n.j-button__spinner {\r\n position: absolute;\r\n inset: 0;\r\n display: grid;\r\n place-items: center;\r\n}\r\n\r\n.j-button-spinner-enter-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out) 40ms,\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out) 40ms;\r\n}\r\n\r\n.j-button-spinner-leave-active {\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-in),\r\n transform var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-button-spinner-enter-from,\r\n.j-button-spinner-leave-to {\r\n opacity: 0;\r\n transform: scale(0.8);\r\n}\r\n\r\n/* Sizes */\r\n.j-button--xs {\r\n --j-button-height: var(--juxt-control-xs);\r\n --j-button-px: var(--juxt-space-2);\r\n --j-button-gap: var(--juxt-space-1);\r\n --j-button-font: var(--juxt-text-xs);\r\n --j-button-radius: var(--juxt-radius-xs);\r\n}\r\n\r\n.j-button--sm {\r\n --j-button-height: var(--juxt-control-sm);\r\n --j-button-px: var(--juxt-space-2-5);\r\n --j-button-gap: var(--juxt-space-1-5);\r\n --j-button-font: var(--juxt-text-sm);\r\n}\r\n\r\n.j-button--lg {\r\n --j-button-height: var(--juxt-control-lg);\r\n --j-button-px: var(--juxt-space-4);\r\n --j-button-gap: var(--juxt-space-2);\r\n --j-button-font: var(--juxt-text-md);\r\n --j-button-radius: var(--juxt-radius-md);\r\n}\r\n\r\n.j-button--icon {\r\n width: var(--j-button-height);\r\n padding: 0;\r\n}\r\n\r\n.j-button--icon .j-button__content > svg,\r\n.j-button--icon .j-button__content > :where(span, i) > svg {\r\n width: calc(var(--j-button-height) * 0.46);\r\n height: calc(var(--j-button-height) * 0.46);\r\n}\r\n\r\n.j-button--block {\r\n display: flex;\r\n width: 100%;\r\n}\r\n\r\n/* Variants */\r\n.j-button--primary {\r\n background: var(--juxt-ink);\r\n color: var(--juxt-ink-fg);\r\n box-shadow: var(--juxt-shadow-xs), var(--juxt-control-highlight);\r\n}\r\n\r\n.j-button--primary:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-ink-hover);\r\n}\r\n\r\n.j-button--secondary {\r\n background: var(--juxt-surface-sunken);\r\n border-color: var(--juxt-border-subtle);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.dark .j-button--secondary,\r\n[data-theme='dark'] .j-button--secondary {\r\n background: var(--juxt-surface-raised);\r\n border-color: var(--juxt-border-subtle);\r\n}\r\n\r\n.j-button--secondary:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n.j-button--outline {\r\n background: var(--juxt-surface);\r\n border-color: var(--juxt-border);\r\n color: var(--juxt-fg);\r\n box-shadow: var(--juxt-shadow-xs);\r\n}\r\n\r\n.j-button--outline:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-hover);\r\n border-color: var(--juxt-border-strong);\r\n}\r\n\r\n.j-button--ghost {\r\n background: transparent;\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-button--ghost:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-button--ghost:active:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n.j-button--destructive {\r\n background: var(--juxt-danger);\r\n color: var(--juxt-danger-fg);\r\n box-shadow: var(--juxt-shadow-xs), var(--juxt-control-highlight);\r\n}\r\n\r\n.j-button--destructive:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-danger-hover);\r\n}\r\n\r\n.j-button--destructive:focus-visible {\r\n outline-color: var(--juxt-danger);\r\n}\r\n\r\n/* Disabled: quiet, not ghostly. */\r\n.j-button:disabled,\r\n.j-button[data-disabled] {\r\n cursor: not-allowed;\r\n opacity: 0.45;\r\n box-shadow: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\nimport { computed } from 'vue'\r\nimport { Spinner } from '../../icons'\r\n\r\nexport type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive'\r\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg'\r\n\r\nexport interface ButtonProps {\r\n /** Visual weight. `primary` is for the single most important action in a view. */\r\n variant?: ButtonVariant\r\n size?: ButtonSize\r\n /** Shows a spinner, keeps the width, and blocks activation while staying focusable. */\r\n loading?: boolean\r\n disabled?: boolean\r\n /** Square button for a single icon. Provide an `aria-label`. */\r\n icon?: boolean\r\n /** Stretch to the full width of the container. */\r\n block?: boolean\r\n /** Render as another element or component, e.g. `a` or `NuxtLink`. */\r\n as?: string | Component\r\n type?: 'button' | 'submit' | 'reset'\r\n}\r\n\r\nconst props = withDefaults(defineProps<ButtonProps>(), {\r\n variant: 'primary',\r\n size: 'md',\r\n as: 'button',\r\n type: 'button',\r\n})\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n leading?: () => unknown\r\n trailing?: () => unknown\r\n}>()\r\n\r\nconst isNativeButton = computed(() => props.as === 'button')\r\nconst inert = computed(() => props.disabled || props.loading)\r\n\r\nconst attrs = computed(() => {\r\n if (isNativeButton.value) {\r\n return {\r\n type: props.type,\r\n // A loading button stays focusable so keyboard users don't lose their place.\r\n 'disabled': props.disabled || undefined,\r\n 'aria-disabled': props.loading || undefined,\r\n }\r\n }\r\n return {\r\n 'aria-disabled': inert.value || undefined,\r\n 'tabindex': props.disabled ? -1 : undefined,\r\n }\r\n})\r\n\r\nfunction onClick(event: MouseEvent) {\r\n if (inert.value) {\r\n event.preventDefault()\r\n event.stopImmediatePropagation()\r\n }\r\n}\r\n</script>\r\n\r\n<template>\r\n <component\r\n :is=\"as\"\r\n v-bind=\"attrs\"\r\n class=\"j-button j-focusable\"\r\n :class=\"[\r\n `j-button--${variant}`,\r\n `j-button--${size}`,\r\n { 'j-button--icon': icon, 'j-button--block': block, 'is-loading': loading },\r\n ]\"\r\n :aria-busy=\"loading || undefined\"\r\n :data-disabled=\"disabled || undefined\"\r\n @click.capture=\"onClick\"\r\n >\r\n <span class=\"j-button__content\">\r\n <span v-if=\"$slots.leading\" class=\"j-button__icon\"><slot name=\"leading\" /></span>\r\n <slot />\r\n <span v-if=\"$slots.trailing\" class=\"j-button__icon\"><slot name=\"trailing\" /></span>\r\n </span>\r\n <Transition name=\"j-button-spinner\">\r\n <span v-if=\"loading\" class=\"j-button__spinner\">\r\n <Spinner :size=\"size === 'xs' ? 12 : size === 'lg' ? 16 : 14\" />\r\n </span>\r\n </Transition>\r\n </component>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-button {\r\n --j-button-height: var(--juxt-control-md);\r\n --j-button-px: var(--juxt-space-3);\r\n --j-button-gap: var(--juxt-space-1-5);\r\n --j-button-font: var(--juxt-text-md);\r\n --j-button-radius: var(--juxt-radius-sm);\r\n\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n height: var(--j-button-height);\r\n padding: 0 var(--j-button-px);\r\n border: 1px solid transparent;\r\n border-radius: var(--j-button-radius);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--j-button-font);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n white-space: nowrap;\r\n text-decoration: none;\r\n cursor: pointer;\r\n user-select: none;\r\n -webkit-tap-highlight-color: transparent;\r\n touch-action: manipulation;\r\n transition-property: background-color, border-color, color, box-shadow, transform, outline-color, outline-offset;\r\n transition-duration: var(--juxt-duration-fast);\r\n transition-timing-function: var(--juxt-ease-standard);\r\n}\r\n\r\n/* Pressed: a short, physical dip. Released slower than pressed. */\r\n.j-button:active:not([aria-disabled='true'], :disabled) {\r\n transform: scale(0.97);\r\n transition-duration: var(--juxt-duration-instant);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-button:active:not([aria-disabled='true'], :disabled) {\r\n transform: none;\r\n }\r\n}\r\n\r\n.j-button__content {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: var(--j-button-gap);\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-button__icon {\r\n display: inline-flex;\r\n margin-inline: -0.125rem;\r\n opacity: 0.85;\r\n}\r\n\r\n.j-button__icon > svg {\r\n width: 1em;\r\n height: 1em;\r\n}\r\n\r\n.j-button.is-loading {\r\n cursor: progress;\r\n}\r\n\r\n.j-button.is-loading .j-button__content {\r\n opacity: 0;\r\n transform: scale(0.96);\r\n}\r\n\r\n.j-button__spinner {\r\n position: absolute;\r\n inset: 0;\r\n display: grid;\r\n place-items: center;\r\n}\r\n\r\n.j-button-spinner-enter-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out) 40ms,\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out) 40ms;\r\n}\r\n\r\n.j-button-spinner-leave-active {\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-in),\r\n transform var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-button-spinner-enter-from,\r\n.j-button-spinner-leave-to {\r\n opacity: 0;\r\n transform: scale(0.8);\r\n}\r\n\r\n/* Sizes */\r\n.j-button--xs {\r\n --j-button-height: var(--juxt-control-xs);\r\n --j-button-px: var(--juxt-space-2);\r\n --j-button-gap: var(--juxt-space-1);\r\n --j-button-font: var(--juxt-text-xs);\r\n --j-button-radius: var(--juxt-radius-xs);\r\n}\r\n\r\n.j-button--sm {\r\n --j-button-height: var(--juxt-control-sm);\r\n --j-button-px: var(--juxt-space-2-5);\r\n --j-button-gap: var(--juxt-space-1-5);\r\n --j-button-font: var(--juxt-text-sm);\r\n}\r\n\r\n.j-button--lg {\r\n --j-button-height: var(--juxt-control-lg);\r\n --j-button-px: var(--juxt-space-4);\r\n --j-button-gap: var(--juxt-space-2);\r\n --j-button-font: var(--juxt-text-md);\r\n --j-button-radius: var(--juxt-radius-md);\r\n}\r\n\r\n.j-button--icon {\r\n width: var(--j-button-height);\r\n padding: 0;\r\n}\r\n\r\n.j-button--icon .j-button__content > svg,\r\n.j-button--icon .j-button__content > :where(span, i) > svg {\r\n width: calc(var(--j-button-height) * 0.46);\r\n height: calc(var(--j-button-height) * 0.46);\r\n}\r\n\r\n.j-button--block {\r\n display: flex;\r\n width: 100%;\r\n}\r\n\r\n/* Variants */\r\n.j-button--primary {\r\n background: var(--juxt-ink);\r\n color: var(--juxt-ink-fg);\r\n box-shadow: var(--juxt-shadow-xs), var(--juxt-control-highlight);\r\n}\r\n\r\n.j-button--primary:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-ink-hover);\r\n}\r\n\r\n.j-button--secondary {\r\n background: var(--juxt-surface-sunken);\r\n border-color: var(--juxt-border-subtle);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.dark .j-button--secondary,\r\n[data-theme='dark'] .j-button--secondary {\r\n background: var(--juxt-surface-raised);\r\n border-color: var(--juxt-border-subtle);\r\n}\r\n\r\n.j-button--secondary:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n.j-button--outline {\r\n background: var(--juxt-surface);\r\n border-color: var(--juxt-border);\r\n color: var(--juxt-fg);\r\n box-shadow: var(--juxt-shadow-xs);\r\n}\r\n\r\n.j-button--outline:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-hover);\r\n border-color: var(--juxt-border-strong);\r\n}\r\n\r\n.j-button--ghost {\r\n background: transparent;\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-button--ghost:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-button--ghost:active:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n.j-button--destructive {\r\n background: var(--juxt-danger);\r\n color: var(--juxt-danger-fg);\r\n box-shadow: var(--juxt-shadow-xs), var(--juxt-control-highlight);\r\n}\r\n\r\n.j-button--destructive:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-danger-hover);\r\n}\r\n\r\n.j-button--destructive:focus-visible {\r\n outline-color: var(--juxt-danger);\r\n}\r\n\r\n/* Disabled: quiet, not ghostly. */\r\n.j-button:disabled,\r\n.j-button[data-disabled] {\r\n cursor: not-allowed;\r\n opacity: 0.45;\r\n box-shadow: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\n\r\nexport interface CardProps {\r\n title?: string\r\n description?: string\r\n /** `outline` sits on the page; `muted` recedes into it. */\r\n variant?: 'outline' | 'muted'\r\n padding?: 'none' | 'sm' | 'md' | 'lg'\r\n as?: string | Component\r\n}\r\n\r\nwithDefaults(defineProps<CardProps>(), { variant: 'outline', padding: 'md', as: 'div' })\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n header?: () => unknown\r\n /** Sits to the right of the title — a button, badge or menu. */\r\n actions?: () => unknown\r\n footer?: () => unknown\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <component :is=\"as\" class=\"j-card\" :class=\"[`j-card--${variant}`, `j-card--p-${padding}`]\">\r\n <header v-if=\"title || description || $slots.header || $slots.actions\" class=\"j-card__header\">\r\n <slot name=\"header\">\r\n <div class=\"j-card__heading\">\r\n <h3 v-if=\"title\" class=\"j-card__title\">\r\n {{ title }}\r\n </h3>\r\n <p v-if=\"description\" class=\"j-card__description\">\r\n {{ description }}\r\n </p>\r\n </div>\r\n </slot>\r\n <div v-if=\"$slots.actions\" class=\"j-card__actions\">\r\n <slot name=\"actions\" />\r\n </div>\r\n </header>\r\n <div v-if=\"$slots.default\" class=\"j-card__body\">\r\n <slot />\r\n </div>\r\n <footer v-if=\"$slots.footer\" class=\"j-card__footer\">\r\n <slot name=\"footer\" />\r\n </footer>\r\n </component>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-card {\r\n --j-card-p: var(--juxt-space-5);\r\n\r\n display: flex;\r\n flex-direction: column;\r\n min-width: 0;\r\n border-radius: var(--juxt-radius-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-card--outline {\r\n --j-avatar-ring: var(--juxt-surface);\r\n\r\n border: 1px solid var(--juxt-border);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-xs);\r\n}\r\n\r\n.j-card--muted {\r\n --j-avatar-ring: var(--juxt-surface-sunken);\r\n\r\n border: 1px solid transparent;\r\n background: var(--juxt-surface-sunken);\r\n}\r\n\r\n.j-card--p-none {\r\n --j-card-p: 0;\r\n}\r\n\r\n.j-card--p-sm {\r\n --j-card-p: var(--juxt-space-3);\r\n}\r\n\r\n.j-card--p-lg {\r\n --j-card-p: var(--juxt-space-6);\r\n}\r\n\r\n.j-card__header {\r\n display: flex;\r\n align-items: flex-start;\r\n justify-content: space-between;\r\n gap: var(--juxt-space-4);\r\n padding: var(--j-card-p) var(--j-card-p) 0;\r\n}\r\n\r\n.j-card__heading {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1);\r\n min-width: 0;\r\n}\r\n\r\n.j-card__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-card__description {\r\n margin: 0;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-card__actions {\r\n display: flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-2);\r\n margin: calc(var(--juxt-space-1) * -1) 0;\r\n}\r\n\r\n.j-card__body {\r\n flex: 1;\r\n padding: var(--j-card-p);\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-card__header + .j-card__body {\r\n padding-top: var(--juxt-space-4);\r\n}\r\n\r\n.j-card__footer {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-end;\r\n gap: var(--juxt-space-2);\r\n padding: var(--juxt-space-3) var(--j-card-p);\r\n border-top: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.j-card__header:last-child {\r\n padding-bottom: var(--j-card-p);\r\n}\r\n\r\n.j-card--muted .j-card__footer {\r\n border-top-color: var(--juxt-border);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\n\r\nexport interface CardProps {\r\n title?: string\r\n description?: string\r\n /** `outline` sits on the page; `muted` recedes into it. */\r\n variant?: 'outline' | 'muted'\r\n padding?: 'none' | 'sm' | 'md' | 'lg'\r\n as?: string | Component\r\n}\r\n\r\nwithDefaults(defineProps<CardProps>(), { variant: 'outline', padding: 'md', as: 'div' })\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n header?: () => unknown\r\n /** Sits to the right of the title — a button, badge or menu. */\r\n actions?: () => unknown\r\n footer?: () => unknown\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <component :is=\"as\" class=\"j-card\" :class=\"[`j-card--${variant}`, `j-card--p-${padding}`]\">\r\n <header v-if=\"title || description || $slots.header || $slots.actions\" class=\"j-card__header\">\r\n <slot name=\"header\">\r\n <div class=\"j-card__heading\">\r\n <h3 v-if=\"title\" class=\"j-card__title\">\r\n {{ title }}\r\n </h3>\r\n <p v-if=\"description\" class=\"j-card__description\">\r\n {{ description }}\r\n </p>\r\n </div>\r\n </slot>\r\n <div v-if=\"$slots.actions\" class=\"j-card__actions\">\r\n <slot name=\"actions\" />\r\n </div>\r\n </header>\r\n <div v-if=\"$slots.default\" class=\"j-card__body\">\r\n <slot />\r\n </div>\r\n <footer v-if=\"$slots.footer\" class=\"j-card__footer\">\r\n <slot name=\"footer\" />\r\n </footer>\r\n </component>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-card {\r\n --j-card-p: var(--juxt-space-5);\r\n\r\n display: flex;\r\n flex-direction: column;\r\n min-width: 0;\r\n border-radius: var(--juxt-radius-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-card--outline {\r\n --j-avatar-ring: var(--juxt-surface);\r\n\r\n border: 1px solid var(--juxt-border);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-xs);\r\n}\r\n\r\n.j-card--muted {\r\n --j-avatar-ring: var(--juxt-surface-sunken);\r\n\r\n border: 1px solid transparent;\r\n background: var(--juxt-surface-sunken);\r\n}\r\n\r\n.j-card--p-none {\r\n --j-card-p: 0;\r\n}\r\n\r\n.j-card--p-sm {\r\n --j-card-p: var(--juxt-space-3);\r\n}\r\n\r\n.j-card--p-lg {\r\n --j-card-p: var(--juxt-space-6);\r\n}\r\n\r\n.j-card__header {\r\n display: flex;\r\n align-items: flex-start;\r\n justify-content: space-between;\r\n gap: var(--juxt-space-4);\r\n padding: var(--j-card-p) var(--j-card-p) 0;\r\n}\r\n\r\n.j-card__heading {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1);\r\n min-width: 0;\r\n}\r\n\r\n.j-card__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-card__description {\r\n margin: 0;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-card__actions {\r\n display: flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-2);\r\n margin: calc(var(--juxt-space-1) * -1) 0;\r\n}\r\n\r\n.j-card__body {\r\n flex: 1;\r\n padding: var(--j-card-p);\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-card__header + .j-card__body {\r\n padding-top: var(--juxt-space-4);\r\n}\r\n\r\n.j-card__footer {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-end;\r\n gap: var(--juxt-space-2);\r\n padding: var(--juxt-space-3) var(--j-card-p);\r\n border-top: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.j-card__header:last-child {\r\n padding-bottom: var(--j-card-p);\r\n}\r\n\r\n.j-card--muted .j-card__footer {\r\n border-top-color: var(--juxt-border);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, ref, useId, watchEffect } from 'vue'\r\n\r\nexport interface CheckboxProps {\r\n label?: string\r\n description?: string\r\n disabled?: boolean\r\n required?: boolean\r\n /** Shows a dash. Typically used for \"select all\" when only some items are selected. */\r\n indeterminate?: boolean\r\n name?: string\r\n value?: string\r\n id?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = defineProps<CheckboxProps>()\r\nconst model = defineModel<boolean>({ default: false })\r\n\r\ndefineSlots<{ default?: () => unknown, description?: () => unknown }>()\r\n\r\nconst generated = useId()\r\nconst id = computed(() => props.id ?? `j-${generated}`)\r\nconst descriptionId = computed(() => `${id.value}-description`)\r\nconst input = ref<HTMLInputElement | null>(null)\r\n\r\n// `indeterminate` is a DOM property, not an attribute.\r\nwatchEffect(() => {\r\n if (input.value) input.value.indeterminate = !!props.indeterminate\r\n})\r\n\r\nconst state = computed(() => (props.indeterminate ? 'indeterminate' : model.value ? 'checked' : 'unchecked'))\r\n\r\nfunction onChange(event: Event) {\r\n model.value = (event.target as HTMLInputElement).checked\r\n}\r\n\r\ndefineExpose({ input, focus: () => input.value?.focus() })\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-checkbox\"\r\n :class=\"[$attrs.class, { 'is-disabled': disabled }]\"\r\n :style=\"$attrs.style as any\"\r\n :data-state=\"state\"\r\n >\r\n <span class=\"j-checkbox__control\">\r\n <input\r\n :id=\"id\"\r\n ref=\"input\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"checkbox\"\r\n class=\"j-checkbox__input\"\r\n :checked=\"model\"\r\n :disabled=\"disabled\"\r\n :required=\"required\"\r\n :name=\"name\"\r\n :value=\"value\"\r\n :aria-describedby=\"description || $slots.description ? descriptionId : undefined\"\r\n @change=\"onChange\"\r\n />\r\n <span class=\"j-checkbox__box\" aria-hidden=\"true\">\r\n <svg class=\"j-checkbox__check\" viewBox=\"0 0 16 16\" fill=\"none\">\r\n <path d=\"M4 8.25l2.75 2.75L12 5.5\" pathLength=\"1\" />\r\n </svg>\r\n <svg class=\"j-checkbox__dash\" viewBox=\"0 0 16 16\" fill=\"none\">\r\n <path d=\"M4.5 8h7\" />\r\n </svg>\r\n </span>\r\n </span>\r\n <span v-if=\"label || $slots.default || description || $slots.description\" class=\"j-checkbox__text\">\r\n <label v-if=\"label || $slots.default\" :for=\"id\" class=\"j-checkbox__label\">\r\n <slot>{{ label }}</slot>\r\n </label>\r\n <span v-if=\"description || $slots.description\" :id=\"descriptionId\" class=\"j-checkbox__description\">\r\n <slot name=\"description\">{{ description }}</slot>\r\n </span>\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-checkbox {\r\n --j-checkbox-size: 1rem;\r\n\r\n display: inline-flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-checkbox__control {\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n /* Align the box with the first line of the label. */\r\n margin-top: calc((var(--juxt-text-md) * var(--juxt-leading-snug) - var(--j-checkbox-size)) / 2);\r\n}\r\n\r\n.j-checkbox__input {\r\n position: absolute;\r\n inset: -4px;\r\n z-index: 1;\r\n width: calc(100% + 8px);\r\n height: calc(100% + 8px);\r\n margin: 0;\r\n opacity: 0;\r\n cursor: pointer;\r\n}\r\n\r\n.j-checkbox__input:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-checkbox__box {\r\n display: grid;\r\n place-items: center;\r\n width: var(--j-checkbox-size);\r\n height: var(--j-checkbox-size);\r\n border: 1px solid var(--juxt-border-strong);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-xs);\r\n color: var(--juxt-accent-fg);\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-checkbox__box > svg {\r\n grid-area: 1 / 1;\r\n width: 100%;\r\n height: 100%;\r\n stroke: currentColor;\r\n stroke-width: 2;\r\n stroke-linecap: round;\r\n stroke-linejoin: round;\r\n}\r\n\r\n.j-checkbox__input:hover:not(:disabled) + .j-checkbox__box {\r\n border-color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-checkbox__input:active:not(:disabled) + .j-checkbox__box {\r\n transform: scale(0.9);\r\n}\r\n\r\n.j-checkbox__input:focus-visible + .j-checkbox__box {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-checkbox[data-state='checked'] .j-checkbox__box,\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__box {\r\n border-color: var(--juxt-accent);\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-checkbox[data-state='checked'] .j-checkbox__input:hover:not(:disabled) + .j-checkbox__box,\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__input:hover:not(:disabled) + .j-checkbox__box {\r\n border-color: var(--juxt-accent-hover);\r\n background: var(--juxt-accent-hover);\r\n}\r\n\r\n/* The check draws itself in, just after the fill lands. */\r\n.j-checkbox__check path {\r\n stroke-dasharray: 1;\r\n stroke-dashoffset: 1;\r\n transition: stroke-dashoffset var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-checkbox[data-state='checked'] .j-checkbox__check path {\r\n stroke-dashoffset: 0;\r\n transition: stroke-dashoffset var(--juxt-duration-normal) var(--juxt-ease-out) 40ms;\r\n}\r\n\r\n.j-checkbox__dash {\r\n opacity: 0;\r\n transform: scaleX(0.4);\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__dash {\r\n opacity: 1;\r\n transform: none;\r\n}\r\n\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__check {\r\n opacity: 0;\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-checkbox__check path,\r\n .j-checkbox[data-state='checked'] .j-checkbox__check path {\r\n transition-duration: 0ms;\r\n }\r\n\r\n .j-checkbox__input:active:not(:disabled) + .j-checkbox__box {\r\n transform: none;\r\n }\r\n}\r\n\r\n.j-checkbox__text {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-checkbox__label {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-checkbox__description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-checkbox.is-disabled .j-checkbox__box {\r\n opacity: 0.5;\r\n box-shadow: none;\r\n}\r\n\r\n.j-checkbox.is-disabled .j-checkbox__label {\r\n color: var(--juxt-fg-muted);\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, ref, useId, watchEffect } from 'vue'\r\n\r\nexport interface CheckboxProps {\r\n label?: string\r\n description?: string\r\n disabled?: boolean\r\n required?: boolean\r\n /** Shows a dash. Typically used for \"select all\" when only some items are selected. */\r\n indeterminate?: boolean\r\n name?: string\r\n value?: string\r\n id?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = defineProps<CheckboxProps>()\r\nconst model = defineModel<boolean>({ default: false })\r\n\r\ndefineSlots<{ default?: () => unknown, description?: () => unknown }>()\r\n\r\nconst generated = useId()\r\nconst id = computed(() => props.id ?? `j-${generated}`)\r\nconst descriptionId = computed(() => `${id.value}-description`)\r\nconst input = ref<HTMLInputElement | null>(null)\r\n\r\n// `indeterminate` is a DOM property, not an attribute.\r\nwatchEffect(() => {\r\n if (input.value) input.value.indeterminate = !!props.indeterminate\r\n})\r\n\r\nconst state = computed(() => (props.indeterminate ? 'indeterminate' : model.value ? 'checked' : 'unchecked'))\r\n\r\nfunction onChange(event: Event) {\r\n model.value = (event.target as HTMLInputElement).checked\r\n}\r\n\r\ndefineExpose({ input, focus: () => input.value?.focus() })\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-checkbox\"\r\n :class=\"[$attrs.class, { 'is-disabled': disabled }]\"\r\n :style=\"$attrs.style as any\"\r\n :data-state=\"state\"\r\n >\r\n <span class=\"j-checkbox__control\">\r\n <input\r\n :id=\"id\"\r\n ref=\"input\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"checkbox\"\r\n class=\"j-checkbox__input\"\r\n :checked=\"model\"\r\n :disabled=\"disabled\"\r\n :required=\"required\"\r\n :name=\"name\"\r\n :value=\"value\"\r\n :aria-describedby=\"description || $slots.description ? descriptionId : undefined\"\r\n @change=\"onChange\"\r\n />\r\n <span class=\"j-checkbox__box\" aria-hidden=\"true\">\r\n <svg class=\"j-checkbox__check\" viewBox=\"0 0 16 16\" fill=\"none\">\r\n <path d=\"M4 8.25l2.75 2.75L12 5.5\" pathLength=\"1\" />\r\n </svg>\r\n <svg class=\"j-checkbox__dash\" viewBox=\"0 0 16 16\" fill=\"none\">\r\n <path d=\"M4.5 8h7\" />\r\n </svg>\r\n </span>\r\n </span>\r\n <span v-if=\"label || $slots.default || description || $slots.description\" class=\"j-checkbox__text\">\r\n <label v-if=\"label || $slots.default\" :for=\"id\" class=\"j-checkbox__label\">\r\n <slot>{{ label }}</slot>\r\n </label>\r\n <span v-if=\"description || $slots.description\" :id=\"descriptionId\" class=\"j-checkbox__description\">\r\n <slot name=\"description\">{{ description }}</slot>\r\n </span>\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-checkbox {\r\n --j-checkbox-size: 1rem;\r\n\r\n display: inline-flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-checkbox__control {\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n /* Align the box with the first line of the label. */\r\n margin-top: calc((var(--juxt-text-md) * var(--juxt-leading-snug) - var(--j-checkbox-size)) / 2);\r\n}\r\n\r\n.j-checkbox__input {\r\n position: absolute;\r\n inset: -4px;\r\n z-index: 1;\r\n width: calc(100% + 8px);\r\n height: calc(100% + 8px);\r\n margin: 0;\r\n opacity: 0;\r\n cursor: pointer;\r\n}\r\n\r\n.j-checkbox__input:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-checkbox__box {\r\n display: grid;\r\n place-items: center;\r\n width: var(--j-checkbox-size);\r\n height: var(--j-checkbox-size);\r\n border: 1px solid var(--juxt-border-strong);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-xs);\r\n color: var(--juxt-accent-fg);\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-checkbox__box > svg {\r\n grid-area: 1 / 1;\r\n width: 100%;\r\n height: 100%;\r\n stroke: currentColor;\r\n stroke-width: 2;\r\n stroke-linecap: round;\r\n stroke-linejoin: round;\r\n}\r\n\r\n.j-checkbox__input:hover:not(:disabled) + .j-checkbox__box {\r\n border-color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-checkbox__input:active:not(:disabled) + .j-checkbox__box {\r\n transform: scale(0.9);\r\n}\r\n\r\n.j-checkbox__input:focus-visible + .j-checkbox__box {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-checkbox[data-state='checked'] .j-checkbox__box,\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__box {\r\n border-color: var(--juxt-accent);\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-checkbox[data-state='checked'] .j-checkbox__input:hover:not(:disabled) + .j-checkbox__box,\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__input:hover:not(:disabled) + .j-checkbox__box {\r\n border-color: var(--juxt-accent-hover);\r\n background: var(--juxt-accent-hover);\r\n}\r\n\r\n/* The check draws itself in, just after the fill lands. */\r\n.j-checkbox__check path {\r\n stroke-dasharray: 1;\r\n stroke-dashoffset: 1;\r\n transition: stroke-dashoffset var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-checkbox[data-state='checked'] .j-checkbox__check path {\r\n stroke-dashoffset: 0;\r\n transition: stroke-dashoffset var(--juxt-duration-normal) var(--juxt-ease-out) 40ms;\r\n}\r\n\r\n.j-checkbox__dash {\r\n opacity: 0;\r\n transform: scaleX(0.4);\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__dash {\r\n opacity: 1;\r\n transform: none;\r\n}\r\n\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__check {\r\n opacity: 0;\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-checkbox__check path,\r\n .j-checkbox[data-state='checked'] .j-checkbox__check path {\r\n transition-duration: 0ms;\r\n }\r\n\r\n .j-checkbox__input:active:not(:disabled) + .j-checkbox__box {\r\n transform: none;\r\n }\r\n}\r\n\r\n.j-checkbox__text {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-checkbox__label {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-checkbox__description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-checkbox.is-disabled .j-checkbox__box {\r\n opacity: 0.5;\r\n box-shadow: none;\r\n}\r\n\r\n.j-checkbox.is-disabled .j-checkbox__label {\r\n color: var(--juxt-fg-muted);\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","import type { Ref } from 'vue'\r\nimport { nextTick, onBeforeUnmount, watch } from 'vue'\r\nimport { focusElement, getFocusableElements, isClient } from '../utils/dom'\r\n\r\nexport interface FocusTrapOptions {\r\n /** Element to focus when the trap activates. Falls back to the first focusable element, then the container. */\r\n initialFocus?: () => HTMLElement | null | undefined\r\n /** Return focus to whatever was focused before activation. Defaults to true. */\r\n restoreFocus?: boolean\r\n}\r\n\r\ninterface Trap {\r\n container: Ref<HTMLElement | null>\r\n}\r\n\r\n// Traps nest: only the most recently activated one is in charge.\r\nconst stack: Trap[] = []\r\n\r\nfunction isTop(trap: Trap) {\r\n return stack[stack.length - 1] === trap\r\n}\r\n\r\nlet listening = false\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key !== 'Tab' || event.defaultPrevented) return\r\n const trap = stack[stack.length - 1]\r\n const container = trap?.container.value\r\n if (!container) return\r\n\r\n const focusable = getFocusableElements(container)\r\n if (focusable.length === 0) {\r\n event.preventDefault()\r\n focusElement(container)\r\n return\r\n }\r\n\r\n const first = focusable[0]!\r\n const last = focusable[focusable.length - 1]!\r\n const active = document.activeElement as HTMLElement | null\r\n const inside = active ? container.contains(active) : false\r\n\r\n if (event.shiftKey && (active === first || active === container || !inside)) {\r\n event.preventDefault()\r\n focusElement(last)\r\n } else if (!event.shiftKey && (active === last || !inside)) {\r\n event.preventDefault()\r\n focusElement(first)\r\n }\r\n}\r\n\r\nfunction onFocusIn(event: FocusEvent) {\r\n const trap = stack[stack.length - 1]\r\n const container = trap?.container.value\r\n const target = event.target as Node | null\r\n if (!container || !target || container.contains(target)) return\r\n // Focus escaped (e.g. a programmatic focus elsewhere) — bring it back.\r\n if ((target as HTMLElement).closest?.('[data-juxt-layer-ignore]')) return\r\n const [first] = getFocusableElements(container)\r\n focusElement(first ?? container)\r\n}\r\n\r\nfunction listen() {\r\n if (listening || !isClient) return\r\n document.addEventListener('keydown', onKeydown)\r\n document.addEventListener('focusin', onFocusIn)\r\n listening = true\r\n}\r\n\r\nfunction unlisten() {\r\n if (!listening || stack.length > 0) return\r\n document.removeEventListener('keydown', onKeydown)\r\n document.removeEventListener('focusin', onFocusIn)\r\n listening = false\r\n}\r\n\r\n/**\r\n * Keep keyboard focus inside `container` while `active` is true, and put it\r\n * back where it came from afterwards.\r\n */\r\nexport function useFocusTrap(container: Ref<HTMLElement | null>, active: Ref<boolean>, options: FocusTrapOptions = {}) {\r\n const trap: Trap = { container }\r\n let returnTo: HTMLElement | null = null\r\n // The container as it was while active. By the time we deactivate, Vue may\r\n // already have cleared the template ref while the element animates out.\r\n let activeContainer: HTMLElement | null = null\r\n\r\n async function activate() {\r\n returnTo = document.activeElement as HTMLElement | null\r\n stack.push(trap)\r\n listen()\r\n await nextTick()\r\n const el = container.value\r\n activeContainer = el\r\n if (!el || !isTop(trap)) return\r\n if (el.contains(document.activeElement)) return\r\n const target = options.initialFocus?.() ?? el.querySelector<HTMLElement>('[autofocus], [data-autofocus]') ?? getFocusableElements(el)[0] ?? el\r\n focusElement(target)\r\n }\r\n\r\n function deactivate() {\r\n const index = stack.indexOf(trap)\r\n if (index === -1) return\r\n stack.splice(index, 1)\r\n unlisten()\r\n\r\n if (options.restoreFocus === false) return\r\n const target = returnTo\r\n returnTo = null\r\n if (!target || !target.isConnected) return\r\n const current = document.activeElement as HTMLElement | null\r\n // Only restore when focus would otherwise be lost. If the user deliberately\r\n // moved focus somewhere meaningful, respect that.\r\n const lost = !current || current === document.body || !current.isConnected\r\n || !!activeContainer?.contains(current) || !!container.value?.contains(current)\r\n activeContainer = null\r\n if (lost) focusElement(target)\r\n }\r\n\r\n if (isClient) {\r\n watch(\r\n active,\r\n (value) => {\r\n if (value) activate()\r\n else deactivate()\r\n },\r\n { immediate: true, flush: 'post' },\r\n )\r\n onBeforeUnmount(deactivate)\r\n }\r\n}\r\n","import type { MaybeRefOrGetter } from 'vue'\r\nimport { onBeforeUnmount, onMounted, toValue } from 'vue'\r\nimport { isMac } from '../utils/dom'\r\n\r\n/**\r\n * Parse a shortcut such as `mod+k`, `shift+/` or `alt+t`.\r\n * `mod` is ⌘ on Apple platforms and Ctrl elsewhere.\r\n */\r\nexport function matchesHotkey(event: KeyboardEvent, hotkey: string): boolean {\r\n const parts = hotkey.toLowerCase().split('+').map(part => part.trim())\r\n const key = parts.pop()\r\n if (!key) return false\r\n const mac = isMac()\r\n const want = {\r\n meta: parts.includes('meta') || (mac && parts.includes('mod')),\r\n ctrl: parts.includes('ctrl') || (!mac && parts.includes('mod')),\r\n alt: parts.includes('alt'),\r\n shift: parts.includes('shift'),\r\n }\r\n if (event.metaKey !== want.meta || event.ctrlKey !== want.ctrl || event.altKey !== want.alt || event.shiftKey !== want.shift)\r\n return false\r\n // `code` survives keyboard layouts and Alt/Option remapping.\r\n const code = event.code.toLowerCase()\r\n return event.key.toLowerCase() === key || code === `key${key}` || code === `digit${key}`\r\n}\r\n\r\n/** Human-readable parts of a shortcut, e.g. `mod+k` → `['⌘', 'K']`. */\r\nexport function formatHotkey(hotkey: string): string[] {\r\n const mac = isMac()\r\n const labels: Record<string, string> = mac\r\n ? { mod: '⌘', meta: '⌘', ctrl: '⌃', alt: '⌥', shift: '⇧', enter: '↵', escape: 'Esc' }\r\n : { mod: 'Ctrl', meta: 'Win', ctrl: 'Ctrl', alt: 'Alt', shift: 'Shift', enter: 'Enter', escape: 'Esc' }\r\n return hotkey.split('+').map((part) => {\r\n const lower = part.trim().toLowerCase()\r\n return labels[lower] ?? (lower.length === 1 ? lower.toUpperCase() : part.trim())\r\n })\r\n}\r\n\r\n/** Run `handler` whenever the shortcut is pressed anywhere on the page. */\r\nexport function useHotkey(hotkey: MaybeRefOrGetter<string | false | undefined>, handler: (event: KeyboardEvent) => void) {\r\n function onKeydown(event: KeyboardEvent) {\r\n const value = toValue(hotkey)\r\n if (!value || event.repeat || event.isComposing) return\r\n if (!matchesHotkey(event, value)) return\r\n event.preventDefault()\r\n handler(event)\r\n }\r\n onMounted(() => document.addEventListener('keydown', onKeydown))\r\n onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))\r\n}\r\n","import type { Ref } from 'vue'\r\nimport { onBeforeUnmount, watch } from 'vue'\r\nimport { isClient } from '../utils/dom'\r\n\r\nexport interface LayerOptions {\r\n /** The layer is registered only while this is true. */\r\n active: Ref<boolean>\r\n /** Elements that count as \"inside\" the layer. */\r\n elements: () => Array<HTMLElement | null | undefined>\r\n onEscape?: (event: KeyboardEvent) => void\r\n /** Called when a pointer goes down outside every element of the top-most layer. */\r\n onPointerDownOutside?: (event: PointerEvent) => void\r\n}\r\n\r\ninterface Layer {\r\n options: LayerOptions\r\n}\r\n\r\n/*\r\n * Overlays stack. A select inside a dialog, a submenu inside a menu, a tooltip\r\n * inside anything: Escape and outside clicks belong to whichever layer is on\r\n * top, and only to that one.\r\n */\r\nconst layers: Layer[] = []\r\nlet listening = false\r\n\r\nfunction top() {\r\n return layers[layers.length - 1]\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key !== 'Escape' || event.defaultPrevented || event.isComposing) return\r\n const layer = top()\r\n if (!layer?.options.onEscape) return\r\n event.preventDefault()\r\n event.stopPropagation()\r\n layer.options.onEscape(event)\r\n}\r\n\r\nfunction onPointerDown(event: PointerEvent) {\r\n const layer = top()\r\n if (!layer?.options.onPointerDownOutside) return\r\n const target = event.target as Node | null\r\n if (!target) return\r\n if ((target as HTMLElement).closest?.('[data-juxt-layer-ignore]')) return\r\n const inside = layer.options.elements().some(el => el?.contains(target))\r\n if (!inside) layer.options.onPointerDownOutside(event)\r\n}\r\n\r\nfunction listen() {\r\n if (listening || !isClient) return\r\n document.addEventListener('keydown', onKeydown)\r\n document.addEventListener('pointerdown', onPointerDown, true)\r\n listening = true\r\n}\r\n\r\nfunction unlisten() {\r\n if (!listening || layers.length > 0) return\r\n document.removeEventListener('keydown', onKeydown)\r\n document.removeEventListener('pointerdown', onPointerDown, true)\r\n listening = false\r\n}\r\n\r\nexport function useLayer(options: LayerOptions) {\r\n const layer: Layer = { options }\r\n\r\n const add = () => {\r\n if (layers.includes(layer)) return\r\n layers.push(layer)\r\n listen()\r\n }\r\n\r\n const remove = () => {\r\n const index = layers.indexOf(layer)\r\n if (index === -1) return\r\n layers.splice(index, 1)\r\n unlisten()\r\n }\r\n\r\n if (isClient) {\r\n watch(options.active, value => (value ? add() : remove()), { immediate: true })\r\n onBeforeUnmount(remove)\r\n }\r\n\r\n return {\r\n isTopLayer: () => top() === layer,\r\n }\r\n}\r\n","import type { Ref } from 'vue'\r\nimport { onBeforeUnmount, watch } from 'vue'\r\nimport { isClient } from '../utils/dom'\r\n\r\nlet locks = 0\r\nlet saved: { overflow: string, paddingRight: string } | null = null\r\n\r\nfunction lock() {\r\n locks++\r\n if (locks > 1) return\r\n const body = document.body\r\n const scrollbar = window.innerWidth - document.documentElement.clientWidth\r\n saved = { overflow: body.style.overflow, paddingRight: body.style.paddingRight }\r\n body.style.overflow = 'hidden'\r\n if (scrollbar > 0) {\r\n // Compensate for the vanished scrollbar so the page doesn't jump sideways.\r\n const current = Number.parseFloat(getComputedStyle(body).paddingRight) || 0\r\n body.style.paddingRight = `${current + scrollbar}px`\r\n }\r\n document.documentElement.style.setProperty('--juxt-scrollbar-width', `${scrollbar}px`)\r\n document.documentElement.dataset.juxtScrollLocked = ''\r\n}\r\n\r\nfunction unlock() {\r\n locks = Math.max(0, locks - 1)\r\n if (locks > 0 || !saved) return\r\n document.body.style.overflow = saved.overflow\r\n document.body.style.paddingRight = saved.paddingRight\r\n document.documentElement.style.removeProperty('--juxt-scrollbar-width')\r\n delete document.documentElement.dataset.juxtScrollLocked\r\n saved = null\r\n}\r\n\r\n/** Prevent the page behind an overlay from scrolling while `active` is true. */\r\nexport function useScrollLock(active: Ref<boolean>) {\r\n if (!isClient) return\r\n let held = false\r\n\r\n const set = (value: boolean) => {\r\n if (value === held) return\r\n held = value\r\n if (value) lock()\r\n else unlock()\r\n }\r\n\r\n watch(active, set, { immediate: true })\r\n onBeforeUnmount(() => set(false))\r\n}\r\n","/**\r\n * Ranking for the command palette. Cheap enough to run on every keystroke\r\n * over a few thousand items, and ordered the way people expect:\r\n * exact › prefix › word start › substring › in-order letters.\r\n */\r\n\r\nexport type MatchRange = [start: number, end: number]\r\n\r\nexport interface MatchResult {\r\n score: number\r\n ranges: MatchRange[]\r\n}\r\n\r\nconst NO_MATCH: MatchResult = { score: 0, ranges: [] }\r\n\r\nexport function matchText(query: string, text: string): MatchResult {\r\n const q = query.trim().toLowerCase()\r\n if (!q) return { score: 1, ranges: [] }\r\n const t = text.toLowerCase()\r\n if (!t) return NO_MATCH\r\n\r\n if (t === q) return { score: 1000, ranges: [[0, t.length]] }\r\n if (t.startsWith(q)) return { score: 900 - Math.min(t.length - q.length, 100), ranges: [[0, q.length]] }\r\n\r\n const wordStart = findWordStart(t, q)\r\n if (wordStart > 0) return { score: 700 - Math.min(wordStart, 100), ranges: [[wordStart, wordStart + q.length]] }\r\n\r\n const index = t.indexOf(q)\r\n if (index > 0) return { score: 500 - Math.min(index, 100), ranges: [[index, index + q.length]] }\r\n\r\n return subsequence(q, t)\r\n}\r\n\r\nfunction findWordStart(t: string, q: string): number {\r\n let from = 0\r\n while (from < t.length) {\r\n const index = t.indexOf(q, from)\r\n if (index === -1) return -1\r\n if (index > 0 && /[\\s\\-_/.:]/.test(t[index - 1]!)) return index\r\n from = index + 1\r\n }\r\n return -1\r\n}\r\n\r\n/** Letters in order, not necessarily adjacent — \"gtst\" finds \"Go to settings\". */\r\nfunction subsequence(q: string, t: string): MatchResult {\r\n const ranges: MatchRange[] = []\r\n let ti = 0\r\n let score = 100\r\n let previous = -2\r\n for (const char of q) {\r\n if (char === ' ') continue\r\n const found = t.indexOf(char, ti)\r\n if (found === -1) return NO_MATCH\r\n if (found === previous + 1 && ranges.length) {\r\n ranges[ranges.length - 1]![1] = found + 1\r\n score += 12\r\n } else {\r\n ranges.push([found, found + 1])\r\n if (found === 0 || /[\\s\\-_/.:]/.test(t[found - 1]!)) score += 8\r\n }\r\n // Spread-out matches rank lower.\r\n score -= Math.min(found - ti, 10)\r\n previous = found\r\n ti = found + 1\r\n }\r\n return { score: Math.max(score, 1), ranges }\r\n}\r\n\r\n/** Split text into highlighted and plain runs. */\r\nexport function splitByRanges(text: string, ranges: MatchRange[]): Array<{ text: string, match: boolean }> {\r\n if (!ranges.length) return [{ text, match: false }]\r\n const parts: Array<{ text: string, match: boolean }> = []\r\n let cursor = 0\r\n for (const [start, end] of ranges) {\r\n if (start > cursor) parts.push({ text: text.slice(cursor, start), match: false })\r\n parts.push({ text: text.slice(start, end), match: true })\r\n cursor = end\r\n }\r\n if (cursor < text.length) parts.push({ text: text.slice(cursor), match: false })\r\n return parts\r\n}\r\n","<script setup lang=\"ts\">\r\n/**\r\n * Internal: teleports its content to `<body>`, but only once mounted.\r\n *\r\n * Server-rendered teleports to `body` never make it into the HTML, so a plain\r\n * `<Teleport>` hydrates against nothing and warns. Rendering nothing until\r\n * mount keeps server and client output identical — and overlays are closed on\r\n * first paint anyway.\r\n */\r\nimport { onMounted, ref } from 'vue'\r\n\r\nwithDefaults(defineProps<{ to?: string }>(), { to: 'body' })\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\nconst mounted = ref(false)\r\nonMounted(() => (mounted.value = true))\r\n</script>\r\n\r\n<template>\r\n <Teleport v-if=\"mounted\" :to=\"to\">\r\n <slot />\r\n </Teleport>\r\n</template>\r\n","<script setup lang=\"ts\">\r\n/**\r\n * Internal: teleports its content to `<body>`, but only once mounted.\r\n *\r\n * Server-rendered teleports to `body` never make it into the HTML, so a plain\r\n * `<Teleport>` hydrates against nothing and warns. Rendering nothing until\r\n * mount keeps server and client output identical — and overlays are closed on\r\n * first paint anyway.\r\n */\r\nimport { onMounted, ref } from 'vue'\r\n\r\nwithDefaults(defineProps<{ to?: string }>(), { to: 'body' })\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\nconst mounted = ref(false)\r\nonMounted(() => (mounted.value = true))\r\n</script>\r\n\r\n<template>\r\n <Teleport v-if=\"mounted\" :to=\"to\">\r\n <slot />\r\n </Teleport>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\nimport type { MatchRange } from './search'\r\nimport { computed, nextTick, onMounted, ref, useId, watch } from 'vue'\r\nimport { useFocusTrap } from '../../composables/useFocusTrap'\r\nimport { formatHotkey, useHotkey } from '../../composables/useHotkey'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { useScrollLock } from '../../composables/useScrollLock'\r\nimport { IconSearch, Spinner } from '../../icons'\r\nimport { matchText, splitByRanges } from './search'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface CommandItem {\r\n id: string\r\n label: string\r\n description?: string\r\n /** An icon component, rendered at 16px. */\r\n icon?: Component\r\n /** Shortcut to display, in hotkey syntax: `mod+shift+p`. Display only. */\r\n shortcut?: string\r\n /** Extra terms that should find this item. */\r\n keywords?: string[]\r\n disabled?: boolean\r\n /** Called when the item is chosen, before `select` is emitted. */\r\n onSelect?: (item: CommandItem) => void\r\n}\r\n\r\nexport interface CommandGroup {\r\n id: string\r\n label?: string\r\n items: CommandItem[]\r\n}\r\n\r\nexport interface CommandPaletteProps {\r\n groups: CommandGroup[]\r\n /** Shown as a \"Recent\" group while the search is empty. */\r\n recent?: CommandItem[]\r\n placeholder?: string\r\n /** Show a loading indicator — for async results. */\r\n loading?: boolean\r\n /**\r\n * Built-in ranking and filtering. Set to `false` when you filter yourself\r\n * (e.g. server-side search driven by `v-model:search`).\r\n */\r\n filter?: boolean\r\n /** Global shortcut that toggles the palette. `false` to disable. */\r\n hotkey?: string | false\r\n /** Accessible name of the dialog. */\r\n label?: string\r\n emptyText?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<CommandPaletteProps>(), {\r\n placeholder: 'Type a command or search…',\r\n filter: true,\r\n hotkey: 'mod+k',\r\n label: 'Command palette',\r\n emptyText: 'No results',\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\nconst search = defineModel<string>('search', { default: '' })\r\n\r\nconst emit = defineEmits<{\r\n select: [item: CommandItem]\r\n}>()\r\n\r\ndefineSlots<{\r\n item?: (props: { item: CommandItem, active: boolean }) => unknown\r\n empty?: (props: { search: string }) => unknown\r\n footer?: () => unknown\r\n}>()\r\n\r\nconst id = `j-${useId()}`\r\nconst listboxId = `${id}-listbox`\r\n// Keys contain group and item ids; keep the DOM id selector-safe.\r\nconst optionId = (key: string) => `${id}-option-${key.replace(/[^\\w-]/g, '_')}`\r\n\r\n// --- Results ---------------------------------------------------------------\r\ninterface Row {\r\n key: string\r\n item: CommandItem\r\n ranges: MatchRange[]\r\n score: number\r\n}\r\n\r\ninterface Section {\r\n id: string\r\n label?: string\r\n rows: Row[]\r\n}\r\n\r\nconst sections = computed<Section[]>(() => {\r\n const query = search.value.trim()\r\n const toKey = (groupId: string, item: CommandItem) => `${groupId}:${item.id}`\r\n\r\n if (!query || !props.filter) {\r\n const base: Section[] = []\r\n if (!query && props.recent?.length)\r\n base.push({ id: 'recent', label: 'Recent', rows: props.recent.map(item => ({ key: toKey('recent', item), item, ranges: [], score: 1 })) })\r\n for (const group of props.groups) {\r\n if (group.items.length)\r\n base.push({ id: group.id, label: group.label, rows: group.items.map(item => ({ key: toKey(group.id, item), item, ranges: [], score: 1 })) })\r\n }\r\n return base\r\n }\r\n\r\n const ranked: Section[] = []\r\n for (const group of props.groups) {\r\n const rows: Row[] = []\r\n for (const item of group.items) {\r\n const label = matchText(query, item.label)\r\n let score = label.score\r\n if (item.keywords) {\r\n for (const keyword of item.keywords) score = Math.max(score, matchText(query, keyword).score * 0.9)\r\n }\r\n if (item.description) score = Math.max(score, matchText(query, item.description).score * 0.5)\r\n if (score > 0) rows.push({ key: toKey(group.id, item), item, ranges: label.ranges, score })\r\n }\r\n if (rows.length) {\r\n rows.sort((a, b) => b.score - a.score)\r\n ranked.push({ id: group.id, label: group.label, rows })\r\n }\r\n }\r\n // The group holding the best match comes first.\r\n return ranked.sort((a, b) => b.rows[0]!.score - a.rows[0]!.score)\r\n})\r\n\r\nconst rows = computed(() => sections.value.flatMap(section => section.rows))\r\nconst rowIndex = computed(() => new Map(rows.value.map((row, index) => [row.key, index])))\r\n\r\nconst activeIndex = ref(0)\r\nconst activeRow = computed(() => rows.value[activeIndex.value])\r\n\r\nfunction firstEnabled(from = 0, delta: 1 | -1 = 1): number {\r\n const count = rows.value.length\r\n for (let i = 0; i < count; i++) {\r\n const index = (from + i * delta + count * 2) % count\r\n if (!rows.value[index]!.item.disabled) return index\r\n }\r\n return -1\r\n}\r\n\r\n// Every keystroke puts the best match under the cursor.\r\nwatch(rows, () => (activeIndex.value = Math.max(0, firstEnabled())))\r\n\r\n// --- Opening & closing -----------------------------------------------------\r\nconst panel = ref<HTMLElement | null>(null)\r\nconst input = ref<HTMLInputElement | null>(null)\r\nconst list = ref<HTMLElement | null>(null)\r\n\r\nuseHotkey(() => props.hotkey, () => (open.value = !open.value))\r\nuseLayer({ active: open, elements: () => [panel.value], onEscape: () => close() })\r\nuseFocusTrap(panel, open, { initialFocus: () => input.value })\r\nuseScrollLock(open)\r\n\r\nwatch(open, (value) => {\r\n if (!value) return\r\n search.value = ''\r\n activeIndex.value = Math.max(0, firstEnabled())\r\n nextTick(() => list.value?.scrollTo({ top: 0 }))\r\n})\r\n\r\nfunction close() {\r\n open.value = false\r\n}\r\n\r\nlet pressedOutside = false\r\nfunction onPointerDown(event: PointerEvent) {\r\n pressedOutside = event.target === event.currentTarget\r\n}\r\nfunction onClick(event: MouseEvent) {\r\n if (pressedOutside && event.target === event.currentTarget) close()\r\n pressedOutside = false\r\n}\r\n\r\n// --- Selection & navigation ------------------------------------------------\r\nfunction choose(row: Row | undefined) {\r\n if (!row || row.item.disabled) return\r\n row.item.onSelect?.(row.item)\r\n emit('select', row.item)\r\n close()\r\n}\r\n\r\nfunction setActive(index: number) {\r\n if (index < 0) return\r\n activeIndex.value = index\r\n const row = rows.value[index]\r\n if (!row) return\r\n nextTick(() => {\r\n const el = document.getElementById(optionId(row.key))\r\n // Keep a little context around the active row at the list edges.\r\n if (index === 0) list.value?.scrollTo({ top: 0 })\r\n else el?.scrollIntoView({ block: 'nearest' })\r\n })\r\n}\r\n\r\nfunction move(delta: 1 | -1, distance = 1) {\r\n const count = rows.value.length\r\n if (!count) return\r\n let index = activeIndex.value\r\n for (let step = 0; step < distance; step++) {\r\n const next = firstEnabled((index + delta + count) % count, delta)\r\n if (next === -1) return\r\n // Page jumps stop at the ends instead of wrapping.\r\n if (distance > 1 && ((delta === 1 && next < index) || (delta === -1 && next > index))) break\r\n index = next\r\n }\r\n setActive(index)\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.isComposing) return\r\n // Emacs-style Ctrl+N / Ctrl+P for people who never leave the home row.\r\n const ctrl = event.ctrlKey && !event.metaKey && !event.altKey\r\n const key = event.key\r\n if (key === 'ArrowDown' || (ctrl && key === 'n')) move(1)\r\n else if (key === 'ArrowUp' || (ctrl && key === 'p')) move(-1)\r\n else if (key === 'PageDown') move(1, 5)\r\n else if (key === 'PageUp') move(-1, 5)\r\n else if (key === 'Enter') choose(activeRow.value)\r\n else return\r\n event.preventDefault()\r\n}\r\n\r\n// Hover follows the pointer only when it moves — scrolling with the keyboard\r\n// under a resting cursor must not steal the selection.\r\nfunction onRowPointerMove(index: number) {\r\n if (activeIndex.value !== index && !rows.value[index]?.item.disabled) activeIndex.value = index\r\n}\r\n\r\n// --- Announcements ---------------------------------------------------------\r\nconst announcement = ref('')\r\nlet announceTimer: ReturnType<typeof setTimeout> | undefined\r\nwatch([rows, () => props.loading], () => {\r\n clearTimeout(announceTimer)\r\n announceTimer = setTimeout(() => {\r\n if (!open.value) return\r\n if (props.loading) announcement.value = 'Loading results'\r\n else announcement.value = rows.value.length === 1 ? '1 result' : `${rows.value.length} results`\r\n }, 350)\r\n})\r\n\r\n// Shortcut glyphs depend on the platform — resolve them on the client only.\r\nconst mounted = ref(false)\r\nonMounted(() => (mounted.value = true))\r\nconst keys = (hotkey: string) => (mounted.value ? formatHotkey(hotkey) : [])\r\n\r\ndefineExpose({ close, focus: () => input.value?.focus() })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-command-overlay\">\r\n <div v-if=\"open\" class=\"j-command__overlay\" aria-hidden=\"true\" />\r\n </Transition>\r\n <Transition name=\"j-command\" :duration=\"{ enter: 180, leave: 110 }\">\r\n <div v-if=\"open\" class=\"j-command__positioner\" @pointerdown=\"onPointerDown\" @click=\"onClick\">\r\n <div\r\n ref=\"panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n :aria-label=\"label\"\r\n class=\"j-command\"\r\n >\r\n <div class=\"j-command__search\">\r\n <IconSearch class=\"j-command__search-icon\" />\r\n <input\r\n ref=\"input\"\r\n v-model=\"search\"\r\n class=\"j-command__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n aria-expanded=\"true\"\r\n aria-autocomplete=\"list\"\r\n :aria-controls=\"listboxId\"\r\n :aria-activedescendant=\"activeRow ? optionId(activeRow.key) : undefined\"\r\n :aria-label=\"label\"\r\n :placeholder=\"placeholder\"\r\n autocomplete=\"off\"\r\n autocorrect=\"off\"\r\n autocapitalize=\"off\"\r\n spellcheck=\"false\"\r\n enterkeyhint=\"go\"\r\n @keydown=\"onKeydown\"\r\n />\r\n <button type=\"button\" class=\"j-command__esc j-kbd\" tabindex=\"-1\" aria-label=\"Close\" @click=\"close\">\r\n Esc\r\n </button>\r\n <span v-if=\"loading\" class=\"j-command__progress\" aria-hidden=\"true\" />\r\n </div>\r\n\r\n <div\r\n :id=\"listboxId\"\r\n ref=\"list\"\r\n role=\"listbox\"\r\n :aria-label=\"label\"\r\n class=\"j-command__list\"\r\n @pointerdown.prevent\r\n >\r\n <div\r\n v-for=\"section in sections\"\r\n :key=\"section.id\"\r\n role=\"group\"\r\n class=\"j-command__group\"\r\n :aria-labelledby=\"section.label ? `${id}-group-${section.id}` : undefined\"\r\n >\r\n <div v-if=\"section.label\" :id=\"`${id}-group-${section.id}`\" class=\"j-command__group-label\" role=\"presentation\">\r\n {{ section.label }}\r\n </div>\r\n <div\r\n v-for=\"row in section.rows\"\r\n :id=\"optionId(row.key)\"\r\n :key=\"row.key\"\r\n role=\"option\"\r\n class=\"j-command__item\"\r\n :class=\"{ 'is-active': row.key === activeRow?.key }\"\r\n :aria-selected=\"row.key === activeRow?.key\"\r\n :aria-disabled=\"row.item.disabled || undefined\"\r\n @pointermove=\"onRowPointerMove(rowIndex.get(row.key)!)\"\r\n @click=\"choose(row)\"\r\n >\r\n <slot name=\"item\" :item=\"row.item\" :active=\"row.key === activeRow?.key\">\r\n <span v-if=\"row.item.icon\" class=\"j-command__item-icon\">\r\n <component :is=\"row.item.icon\" />\r\n </span>\r\n <span class=\"j-command__item-text\">\r\n <span class=\"j-command__item-label\" :class=\"{ 'is-searching': row.ranges.length }\">\r\n <template v-for=\"(part, i) in splitByRanges(row.item.label, row.ranges)\" :key=\"i\">\r\n <mark v-if=\"part.match\">{{ part.text }}</mark>\r\n <template v-else>{{ part.text }}</template>\r\n </template>\r\n </span>\r\n <span v-if=\"row.item.description\" class=\"j-command__item-description\">{{ row.item.description }}</span>\r\n </span>\r\n <span v-if=\"row.item.shortcut\" class=\"j-command__item-shortcut\" aria-hidden=\"true\">\r\n <kbd v-for=\"key in keys(row.item.shortcut)\" :key=\"key\" class=\"j-kbd\">{{ key }}</kbd>\r\n </span>\r\n </slot>\r\n </div>\r\n </div>\r\n\r\n <div v-if=\"!rows.length\" class=\"j-command__empty\" role=\"presentation\">\r\n <template v-if=\"loading\">\r\n <Spinner :size=\"16\" />\r\n <span>Searching…</span>\r\n </template>\r\n <slot v-else name=\"empty\" :search=\"search\">\r\n <span class=\"j-command__empty-title\">{{ emptyText }}</span>\r\n <span v-if=\"search\" class=\"j-command__empty-hint\">Nothing matches “{{ search }}”. Try fewer words.</span>\r\n </slot>\r\n </div>\r\n </div>\r\n\r\n <div class=\"j-command__footer\">\r\n <slot name=\"footer\">\r\n <span class=\"j-command__hint\"><kbd class=\"j-kbd\">↑</kbd><kbd class=\"j-kbd\">↓</kbd> Navigate</span>\r\n <span class=\"j-command__hint\"><kbd class=\"j-kbd\">↵</kbd> Select</span>\r\n <span class=\"j-command__hint\"><kbd class=\"j-kbd\">Esc</kbd> Close</span>\r\n </slot>\r\n </div>\r\n\r\n <span class=\"j-sr-only\" aria-live=\"polite\" aria-atomic=\"true\">{{ announcement }}</span>\r\n </div>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-command__overlay {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-overlay);\r\n background: var(--juxt-overlay);\r\n}\r\n\r\n.j-command__positioner {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-modal);\r\n display: flex;\r\n align-items: flex-start;\r\n justify-content: center;\r\n padding: max(12vh, var(--juxt-space-4)) var(--juxt-space-4) var(--juxt-space-4);\r\n}\r\n\r\n.j-command {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n max-width: 40rem;\r\n max-height: min(34rem, calc(100dvh - 12vh - var(--juxt-space-8)));\r\n overflow: hidden;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-lg);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n/* Search */\r\n.j-command__search {\r\n position: relative;\r\n display: flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-3);\r\n height: 3.25rem;\r\n padding: 0 var(--juxt-space-3) 0 var(--juxt-space-4);\r\n border-bottom: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-command__search,\r\n[data-theme='dark'] .j-command__search {\r\n border-bottom-color: var(--juxt-border);\r\n}\r\n\r\n.j-command__search-icon {\r\n flex: none;\r\n width: 1.125rem;\r\n height: 1.125rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-command__input {\r\n flex: 1;\r\n min-width: 0;\r\n height: 100%;\r\n padding: 0;\r\n border: 0;\r\n outline: none;\r\n background: transparent;\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-lg);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n caret-color: var(--juxt-accent);\r\n}\r\n\r\n.j-command__input::placeholder {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-command__esc {\r\n height: 1.375rem;\r\n padding: 0 var(--juxt-space-1-5);\r\n cursor: pointer;\r\n}\r\n\r\n.j-command__esc:hover {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n/* An indeterminate hairline: present, never loud. */\r\n.j-command__progress {\r\n position: absolute;\r\n right: 0;\r\n bottom: -1px;\r\n left: 0;\r\n height: 1px;\r\n overflow: hidden;\r\n}\r\n\r\n.j-command__progress::after {\r\n content: '';\r\n position: absolute;\r\n inset: 0;\r\n width: 40%;\r\n background: linear-gradient(90deg, transparent, var(--juxt-accent), transparent);\r\n animation: j-command-progress 1.1s var(--juxt-ease-standard) infinite;\r\n}\r\n\r\n@keyframes j-command-progress {\r\n from {\r\n transform: translateX(-100%);\r\n }\r\n\r\n to {\r\n transform: translateX(250%);\r\n }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-command__progress::after {\r\n width: 100%;\r\n animation: j-command-pulse 1.4s ease-in-out infinite;\r\n }\r\n\r\n @keyframes j-command-pulse {\r\n 50% {\r\n opacity: 0.3;\r\n }\r\n }\r\n}\r\n\r\n/* List */\r\n.j-command__list {\r\n flex: 1;\r\n min-height: 0;\r\n padding: var(--juxt-space-1-5);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n scroll-padding-block: var(--juxt-space-8) var(--juxt-space-1-5);\r\n}\r\n\r\n.j-command__group + .j-command__group {\r\n margin-top: var(--juxt-space-1);\r\n}\r\n\r\n.j-command__group-label {\r\n padding: var(--juxt-space-2) var(--juxt-space-2-5) var(--juxt-space-1-5);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n user-select: none;\r\n}\r\n\r\n.j-command__item {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2-5);\r\n height: 2.5rem;\r\n padding: 0 var(--juxt-space-2-5);\r\n border-radius: var(--juxt-radius-sm);\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n/* The one place the palette uses green: a hairline marking where you are. */\r\n.j-command__item::before {\r\n content: '';\r\n position: absolute;\r\n top: 0.625rem;\r\n bottom: 0.625rem;\r\n left: 0;\r\n width: 2px;\r\n border-radius: 0 2px 2px 0;\r\n background: var(--juxt-accent);\r\n opacity: 0;\r\n transform: scaleY(0.4);\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-command__item.is-active {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-command__item.is-active::before {\r\n opacity: 1;\r\n transform: none;\r\n}\r\n\r\n.j-command__item[aria-disabled='true'] {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-command__item-icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 1.25rem;\r\n height: 1.25rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-command__item-icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-command__item.is-active .j-command__item-icon {\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-command__item-text {\r\n display: flex;\r\n flex: 1;\r\n align-items: baseline;\r\n gap: var(--juxt-space-2);\r\n min-width: 0;\r\n overflow: hidden;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-command__item-label {\r\n flex: none;\r\n max-width: 100%;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.j-command__item-label.is-searching {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-command__item-label mark {\r\n background: none;\r\n color: var(--juxt-fg);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n\r\n.j-command__item-description {\r\n overflow: hidden;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.j-command__item-shortcut {\r\n display: inline-flex;\r\n flex: none;\r\n gap: 3px;\r\n}\r\n\r\n.j-command__empty {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: var(--juxt-space-1-5);\r\n min-height: 8rem;\r\n padding: var(--juxt-space-6) var(--juxt-space-4);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n text-align: center;\r\n}\r\n\r\n.j-command__empty-title {\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n\r\n/* Footer */\r\n.j-command__footer {\r\n display: flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-4);\r\n height: 2.25rem;\r\n padding: 0 var(--juxt-space-4);\r\n border-top: 1px solid var(--juxt-border-subtle);\r\n background: var(--juxt-surface-sunken);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n}\r\n\r\n.dark .j-command__footer,\r\n[data-theme='dark'] .j-command__footer {\r\n border-top-color: var(--juxt-border);\r\n background: var(--juxt-surface);\r\n}\r\n\r\n.j-command__hint {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1);\r\n}\r\n\r\n.j-command__hint .j-kbd + .j-kbd {\r\n margin-left: -1px;\r\n}\r\n\r\n/* Motion: quick in, quicker out. */\r\n.j-command-overlay-enter-active {\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-command-overlay-leave-active {\r\n transition: opacity var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-command-overlay-enter-from,\r\n.j-command-overlay-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-command-enter-active .j-command {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-command-leave-active .j-command {\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-in),\r\n transform var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-command-enter-from .j-command,\r\n.j-command-leave-to .j-command {\r\n opacity: 0;\r\n transform: translateY(calc(var(--juxt-motion-shift) * -1)) scale(0.985);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-command-enter-from .j-command,\r\n .j-command-leave-to .j-command {\r\n transform: none;\r\n }\r\n}\r\n\r\n@media (max-width: 639px) {\r\n .j-command__positioner {\r\n padding: var(--juxt-space-2);\r\n }\r\n\r\n .j-command {\r\n max-height: calc(100dvh - var(--juxt-space-4));\r\n }\r\n\r\n .j-command__footer {\r\n display: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\nimport type { MatchRange } from './search'\r\nimport { computed, nextTick, onMounted, ref, useId, watch } from 'vue'\r\nimport { useFocusTrap } from '../../composables/useFocusTrap'\r\nimport { formatHotkey, useHotkey } from '../../composables/useHotkey'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { useScrollLock } from '../../composables/useScrollLock'\r\nimport { IconSearch, Spinner } from '../../icons'\r\nimport { matchText, splitByRanges } from './search'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface CommandItem {\r\n id: string\r\n label: string\r\n description?: string\r\n /** An icon component, rendered at 16px. */\r\n icon?: Component\r\n /** Shortcut to display, in hotkey syntax: `mod+shift+p`. Display only. */\r\n shortcut?: string\r\n /** Extra terms that should find this item. */\r\n keywords?: string[]\r\n disabled?: boolean\r\n /** Called when the item is chosen, before `select` is emitted. */\r\n onSelect?: (item: CommandItem) => void\r\n}\r\n\r\nexport interface CommandGroup {\r\n id: string\r\n label?: string\r\n items: CommandItem[]\r\n}\r\n\r\nexport interface CommandPaletteProps {\r\n groups: CommandGroup[]\r\n /** Shown as a \"Recent\" group while the search is empty. */\r\n recent?: CommandItem[]\r\n placeholder?: string\r\n /** Show a loading indicator — for async results. */\r\n loading?: boolean\r\n /**\r\n * Built-in ranking and filtering. Set to `false` when you filter yourself\r\n * (e.g. server-side search driven by `v-model:search`).\r\n */\r\n filter?: boolean\r\n /** Global shortcut that toggles the palette. `false` to disable. */\r\n hotkey?: string | false\r\n /** Accessible name of the dialog. */\r\n label?: string\r\n emptyText?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<CommandPaletteProps>(), {\r\n placeholder: 'Type a command or search…',\r\n filter: true,\r\n hotkey: 'mod+k',\r\n label: 'Command palette',\r\n emptyText: 'No results',\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\nconst search = defineModel<string>('search', { default: '' })\r\n\r\nconst emit = defineEmits<{\r\n select: [item: CommandItem]\r\n}>()\r\n\r\ndefineSlots<{\r\n item?: (props: { item: CommandItem, active: boolean }) => unknown\r\n empty?: (props: { search: string }) => unknown\r\n footer?: () => unknown\r\n}>()\r\n\r\nconst id = `j-${useId()}`\r\nconst listboxId = `${id}-listbox`\r\n// Keys contain group and item ids; keep the DOM id selector-safe.\r\nconst optionId = (key: string) => `${id}-option-${key.replace(/[^\\w-]/g, '_')}`\r\n\r\n// --- Results ---------------------------------------------------------------\r\ninterface Row {\r\n key: string\r\n item: CommandItem\r\n ranges: MatchRange[]\r\n score: number\r\n}\r\n\r\ninterface Section {\r\n id: string\r\n label?: string\r\n rows: Row[]\r\n}\r\n\r\nconst sections = computed<Section[]>(() => {\r\n const query = search.value.trim()\r\n const toKey = (groupId: string, item: CommandItem) => `${groupId}:${item.id}`\r\n\r\n if (!query || !props.filter) {\r\n const base: Section[] = []\r\n if (!query && props.recent?.length)\r\n base.push({ id: 'recent', label: 'Recent', rows: props.recent.map(item => ({ key: toKey('recent', item), item, ranges: [], score: 1 })) })\r\n for (const group of props.groups) {\r\n if (group.items.length)\r\n base.push({ id: group.id, label: group.label, rows: group.items.map(item => ({ key: toKey(group.id, item), item, ranges: [], score: 1 })) })\r\n }\r\n return base\r\n }\r\n\r\n const ranked: Section[] = []\r\n for (const group of props.groups) {\r\n const rows: Row[] = []\r\n for (const item of group.items) {\r\n const label = matchText(query, item.label)\r\n let score = label.score\r\n if (item.keywords) {\r\n for (const keyword of item.keywords) score = Math.max(score, matchText(query, keyword).score * 0.9)\r\n }\r\n if (item.description) score = Math.max(score, matchText(query, item.description).score * 0.5)\r\n if (score > 0) rows.push({ key: toKey(group.id, item), item, ranges: label.ranges, score })\r\n }\r\n if (rows.length) {\r\n rows.sort((a, b) => b.score - a.score)\r\n ranked.push({ id: group.id, label: group.label, rows })\r\n }\r\n }\r\n // The group holding the best match comes first.\r\n return ranked.sort((a, b) => b.rows[0]!.score - a.rows[0]!.score)\r\n})\r\n\r\nconst rows = computed(() => sections.value.flatMap(section => section.rows))\r\nconst rowIndex = computed(() => new Map(rows.value.map((row, index) => [row.key, index])))\r\n\r\nconst activeIndex = ref(0)\r\nconst activeRow = computed(() => rows.value[activeIndex.value])\r\n\r\nfunction firstEnabled(from = 0, delta: 1 | -1 = 1): number {\r\n const count = rows.value.length\r\n for (let i = 0; i < count; i++) {\r\n const index = (from + i * delta + count * 2) % count\r\n if (!rows.value[index]!.item.disabled) return index\r\n }\r\n return -1\r\n}\r\n\r\n// Every keystroke puts the best match under the cursor.\r\nwatch(rows, () => (activeIndex.value = Math.max(0, firstEnabled())))\r\n\r\n// --- Opening & closing -----------------------------------------------------\r\nconst panel = ref<HTMLElement | null>(null)\r\nconst input = ref<HTMLInputElement | null>(null)\r\nconst list = ref<HTMLElement | null>(null)\r\n\r\nuseHotkey(() => props.hotkey, () => (open.value = !open.value))\r\nuseLayer({ active: open, elements: () => [panel.value], onEscape: () => close() })\r\nuseFocusTrap(panel, open, { initialFocus: () => input.value })\r\nuseScrollLock(open)\r\n\r\nwatch(open, (value) => {\r\n if (!value) return\r\n search.value = ''\r\n activeIndex.value = Math.max(0, firstEnabled())\r\n nextTick(() => list.value?.scrollTo({ top: 0 }))\r\n})\r\n\r\nfunction close() {\r\n open.value = false\r\n}\r\n\r\nlet pressedOutside = false\r\nfunction onPointerDown(event: PointerEvent) {\r\n pressedOutside = event.target === event.currentTarget\r\n}\r\nfunction onClick(event: MouseEvent) {\r\n if (pressedOutside && event.target === event.currentTarget) close()\r\n pressedOutside = false\r\n}\r\n\r\n// --- Selection & navigation ------------------------------------------------\r\nfunction choose(row: Row | undefined) {\r\n if (!row || row.item.disabled) return\r\n row.item.onSelect?.(row.item)\r\n emit('select', row.item)\r\n close()\r\n}\r\n\r\nfunction setActive(index: number) {\r\n if (index < 0) return\r\n activeIndex.value = index\r\n const row = rows.value[index]\r\n if (!row) return\r\n nextTick(() => {\r\n const el = document.getElementById(optionId(row.key))\r\n // Keep a little context around the active row at the list edges.\r\n if (index === 0) list.value?.scrollTo({ top: 0 })\r\n else el?.scrollIntoView({ block: 'nearest' })\r\n })\r\n}\r\n\r\nfunction move(delta: 1 | -1, distance = 1) {\r\n const count = rows.value.length\r\n if (!count) return\r\n let index = activeIndex.value\r\n for (let step = 0; step < distance; step++) {\r\n const next = firstEnabled((index + delta + count) % count, delta)\r\n if (next === -1) return\r\n // Page jumps stop at the ends instead of wrapping.\r\n if (distance > 1 && ((delta === 1 && next < index) || (delta === -1 && next > index))) break\r\n index = next\r\n }\r\n setActive(index)\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.isComposing) return\r\n // Emacs-style Ctrl+N / Ctrl+P for people who never leave the home row.\r\n const ctrl = event.ctrlKey && !event.metaKey && !event.altKey\r\n const key = event.key\r\n if (key === 'ArrowDown' || (ctrl && key === 'n')) move(1)\r\n else if (key === 'ArrowUp' || (ctrl && key === 'p')) move(-1)\r\n else if (key === 'PageDown') move(1, 5)\r\n else if (key === 'PageUp') move(-1, 5)\r\n else if (key === 'Enter') choose(activeRow.value)\r\n else return\r\n event.preventDefault()\r\n}\r\n\r\n// Hover follows the pointer only when it moves — scrolling with the keyboard\r\n// under a resting cursor must not steal the selection.\r\nfunction onRowPointerMove(index: number) {\r\n if (activeIndex.value !== index && !rows.value[index]?.item.disabled) activeIndex.value = index\r\n}\r\n\r\n// --- Announcements ---------------------------------------------------------\r\nconst announcement = ref('')\r\nlet announceTimer: ReturnType<typeof setTimeout> | undefined\r\nwatch([rows, () => props.loading], () => {\r\n clearTimeout(announceTimer)\r\n announceTimer = setTimeout(() => {\r\n if (!open.value) return\r\n if (props.loading) announcement.value = 'Loading results'\r\n else announcement.value = rows.value.length === 1 ? '1 result' : `${rows.value.length} results`\r\n }, 350)\r\n})\r\n\r\n// Shortcut glyphs depend on the platform — resolve them on the client only.\r\nconst mounted = ref(false)\r\nonMounted(() => (mounted.value = true))\r\nconst keys = (hotkey: string) => (mounted.value ? formatHotkey(hotkey) : [])\r\n\r\ndefineExpose({ close, focus: () => input.value?.focus() })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-command-overlay\">\r\n <div v-if=\"open\" class=\"j-command__overlay\" aria-hidden=\"true\" />\r\n </Transition>\r\n <Transition name=\"j-command\" :duration=\"{ enter: 180, leave: 110 }\">\r\n <div v-if=\"open\" class=\"j-command__positioner\" @pointerdown=\"onPointerDown\" @click=\"onClick\">\r\n <div\r\n ref=\"panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n :aria-label=\"label\"\r\n class=\"j-command\"\r\n >\r\n <div class=\"j-command__search\">\r\n <IconSearch class=\"j-command__search-icon\" />\r\n <input\r\n ref=\"input\"\r\n v-model=\"search\"\r\n class=\"j-command__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n aria-expanded=\"true\"\r\n aria-autocomplete=\"list\"\r\n :aria-controls=\"listboxId\"\r\n :aria-activedescendant=\"activeRow ? optionId(activeRow.key) : undefined\"\r\n :aria-label=\"label\"\r\n :placeholder=\"placeholder\"\r\n autocomplete=\"off\"\r\n autocorrect=\"off\"\r\n autocapitalize=\"off\"\r\n spellcheck=\"false\"\r\n enterkeyhint=\"go\"\r\n @keydown=\"onKeydown\"\r\n />\r\n <button type=\"button\" class=\"j-command__esc j-kbd\" tabindex=\"-1\" aria-label=\"Close\" @click=\"close\">\r\n Esc\r\n </button>\r\n <span v-if=\"loading\" class=\"j-command__progress\" aria-hidden=\"true\" />\r\n </div>\r\n\r\n <div\r\n :id=\"listboxId\"\r\n ref=\"list\"\r\n role=\"listbox\"\r\n :aria-label=\"label\"\r\n class=\"j-command__list\"\r\n @pointerdown.prevent\r\n >\r\n <div\r\n v-for=\"section in sections\"\r\n :key=\"section.id\"\r\n role=\"group\"\r\n class=\"j-command__group\"\r\n :aria-labelledby=\"section.label ? `${id}-group-${section.id}` : undefined\"\r\n >\r\n <div v-if=\"section.label\" :id=\"`${id}-group-${section.id}`\" class=\"j-command__group-label\" role=\"presentation\">\r\n {{ section.label }}\r\n </div>\r\n <div\r\n v-for=\"row in section.rows\"\r\n :id=\"optionId(row.key)\"\r\n :key=\"row.key\"\r\n role=\"option\"\r\n class=\"j-command__item\"\r\n :class=\"{ 'is-active': row.key === activeRow?.key }\"\r\n :aria-selected=\"row.key === activeRow?.key\"\r\n :aria-disabled=\"row.item.disabled || undefined\"\r\n @pointermove=\"onRowPointerMove(rowIndex.get(row.key)!)\"\r\n @click=\"choose(row)\"\r\n >\r\n <slot name=\"item\" :item=\"row.item\" :active=\"row.key === activeRow?.key\">\r\n <span v-if=\"row.item.icon\" class=\"j-command__item-icon\">\r\n <component :is=\"row.item.icon\" />\r\n </span>\r\n <span class=\"j-command__item-text\">\r\n <span class=\"j-command__item-label\" :class=\"{ 'is-searching': row.ranges.length }\">\r\n <template v-for=\"(part, i) in splitByRanges(row.item.label, row.ranges)\" :key=\"i\">\r\n <mark v-if=\"part.match\">{{ part.text }}</mark>\r\n <template v-else>{{ part.text }}</template>\r\n </template>\r\n </span>\r\n <span v-if=\"row.item.description\" class=\"j-command__item-description\">{{ row.item.description }}</span>\r\n </span>\r\n <span v-if=\"row.item.shortcut\" class=\"j-command__item-shortcut\" aria-hidden=\"true\">\r\n <kbd v-for=\"key in keys(row.item.shortcut)\" :key=\"key\" class=\"j-kbd\">{{ key }}</kbd>\r\n </span>\r\n </slot>\r\n </div>\r\n </div>\r\n\r\n <div v-if=\"!rows.length\" class=\"j-command__empty\" role=\"presentation\">\r\n <template v-if=\"loading\">\r\n <Spinner :size=\"16\" />\r\n <span>Searching…</span>\r\n </template>\r\n <slot v-else name=\"empty\" :search=\"search\">\r\n <span class=\"j-command__empty-title\">{{ emptyText }}</span>\r\n <span v-if=\"search\" class=\"j-command__empty-hint\">Nothing matches “{{ search }}”. Try fewer words.</span>\r\n </slot>\r\n </div>\r\n </div>\r\n\r\n <div class=\"j-command__footer\">\r\n <slot name=\"footer\">\r\n <span class=\"j-command__hint\"><kbd class=\"j-kbd\">↑</kbd><kbd class=\"j-kbd\">↓</kbd> Navigate</span>\r\n <span class=\"j-command__hint\"><kbd class=\"j-kbd\">↵</kbd> Select</span>\r\n <span class=\"j-command__hint\"><kbd class=\"j-kbd\">Esc</kbd> Close</span>\r\n </slot>\r\n </div>\r\n\r\n <span class=\"j-sr-only\" aria-live=\"polite\" aria-atomic=\"true\">{{ announcement }}</span>\r\n </div>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-command__overlay {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-overlay);\r\n background: var(--juxt-overlay);\r\n}\r\n\r\n.j-command__positioner {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-modal);\r\n display: flex;\r\n align-items: flex-start;\r\n justify-content: center;\r\n padding: max(12vh, var(--juxt-space-4)) var(--juxt-space-4) var(--juxt-space-4);\r\n}\r\n\r\n.j-command {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n max-width: 40rem;\r\n max-height: min(34rem, calc(100dvh - 12vh - var(--juxt-space-8)));\r\n overflow: hidden;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-lg);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n/* Search */\r\n.j-command__search {\r\n position: relative;\r\n display: flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-3);\r\n height: 3.25rem;\r\n padding: 0 var(--juxt-space-3) 0 var(--juxt-space-4);\r\n border-bottom: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-command__search,\r\n[data-theme='dark'] .j-command__search {\r\n border-bottom-color: var(--juxt-border);\r\n}\r\n\r\n.j-command__search-icon {\r\n flex: none;\r\n width: 1.125rem;\r\n height: 1.125rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-command__input {\r\n flex: 1;\r\n min-width: 0;\r\n height: 100%;\r\n padding: 0;\r\n border: 0;\r\n outline: none;\r\n background: transparent;\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-lg);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n caret-color: var(--juxt-accent);\r\n}\r\n\r\n.j-command__input::placeholder {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-command__esc {\r\n height: 1.375rem;\r\n padding: 0 var(--juxt-space-1-5);\r\n cursor: pointer;\r\n}\r\n\r\n.j-command__esc:hover {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n/* An indeterminate hairline: present, never loud. */\r\n.j-command__progress {\r\n position: absolute;\r\n right: 0;\r\n bottom: -1px;\r\n left: 0;\r\n height: 1px;\r\n overflow: hidden;\r\n}\r\n\r\n.j-command__progress::after {\r\n content: '';\r\n position: absolute;\r\n inset: 0;\r\n width: 40%;\r\n background: linear-gradient(90deg, transparent, var(--juxt-accent), transparent);\r\n animation: j-command-progress 1.1s var(--juxt-ease-standard) infinite;\r\n}\r\n\r\n@keyframes j-command-progress {\r\n from {\r\n transform: translateX(-100%);\r\n }\r\n\r\n to {\r\n transform: translateX(250%);\r\n }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-command__progress::after {\r\n width: 100%;\r\n animation: j-command-pulse 1.4s ease-in-out infinite;\r\n }\r\n\r\n @keyframes j-command-pulse {\r\n 50% {\r\n opacity: 0.3;\r\n }\r\n }\r\n}\r\n\r\n/* List */\r\n.j-command__list {\r\n flex: 1;\r\n min-height: 0;\r\n padding: var(--juxt-space-1-5);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n scroll-padding-block: var(--juxt-space-8) var(--juxt-space-1-5);\r\n}\r\n\r\n.j-command__group + .j-command__group {\r\n margin-top: var(--juxt-space-1);\r\n}\r\n\r\n.j-command__group-label {\r\n padding: var(--juxt-space-2) var(--juxt-space-2-5) var(--juxt-space-1-5);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n user-select: none;\r\n}\r\n\r\n.j-command__item {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2-5);\r\n height: 2.5rem;\r\n padding: 0 var(--juxt-space-2-5);\r\n border-radius: var(--juxt-radius-sm);\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n/* The one place the palette uses green: a hairline marking where you are. */\r\n.j-command__item::before {\r\n content: '';\r\n position: absolute;\r\n top: 0.625rem;\r\n bottom: 0.625rem;\r\n left: 0;\r\n width: 2px;\r\n border-radius: 0 2px 2px 0;\r\n background: var(--juxt-accent);\r\n opacity: 0;\r\n transform: scaleY(0.4);\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-command__item.is-active {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-command__item.is-active::before {\r\n opacity: 1;\r\n transform: none;\r\n}\r\n\r\n.j-command__item[aria-disabled='true'] {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-command__item-icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 1.25rem;\r\n height: 1.25rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-command__item-icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-command__item.is-active .j-command__item-icon {\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-command__item-text {\r\n display: flex;\r\n flex: 1;\r\n align-items: baseline;\r\n gap: var(--juxt-space-2);\r\n min-width: 0;\r\n overflow: hidden;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-command__item-label {\r\n flex: none;\r\n max-width: 100%;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.j-command__item-label.is-searching {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-command__item-label mark {\r\n background: none;\r\n color: var(--juxt-fg);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n\r\n.j-command__item-description {\r\n overflow: hidden;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.j-command__item-shortcut {\r\n display: inline-flex;\r\n flex: none;\r\n gap: 3px;\r\n}\r\n\r\n.j-command__empty {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: var(--juxt-space-1-5);\r\n min-height: 8rem;\r\n padding: var(--juxt-space-6) var(--juxt-space-4);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n text-align: center;\r\n}\r\n\r\n.j-command__empty-title {\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n\r\n/* Footer */\r\n.j-command__footer {\r\n display: flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-4);\r\n height: 2.25rem;\r\n padding: 0 var(--juxt-space-4);\r\n border-top: 1px solid var(--juxt-border-subtle);\r\n background: var(--juxt-surface-sunken);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n}\r\n\r\n.dark .j-command__footer,\r\n[data-theme='dark'] .j-command__footer {\r\n border-top-color: var(--juxt-border);\r\n background: var(--juxt-surface);\r\n}\r\n\r\n.j-command__hint {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1);\r\n}\r\n\r\n.j-command__hint .j-kbd + .j-kbd {\r\n margin-left: -1px;\r\n}\r\n\r\n/* Motion: quick in, quicker out. */\r\n.j-command-overlay-enter-active {\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-command-overlay-leave-active {\r\n transition: opacity var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-command-overlay-enter-from,\r\n.j-command-overlay-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-command-enter-active .j-command {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-command-leave-active .j-command {\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-in),\r\n transform var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-command-enter-from .j-command,\r\n.j-command-leave-to .j-command {\r\n opacity: 0;\r\n transform: translateY(calc(var(--juxt-motion-shift) * -1)) scale(0.985);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-command-enter-from .j-command,\r\n .j-command-leave-to .j-command {\r\n transform: none;\r\n }\r\n}\r\n\r\n@media (max-width: 639px) {\r\n .j-command__positioner {\r\n padding: var(--juxt-space-2);\r\n }\r\n\r\n .j-command {\r\n max-height: calc(100dvh - var(--juxt-space-4));\r\n }\r\n\r\n .j-command__footer {\r\n display: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","import type { Ref } from 'vue'\nimport { ref } from 'vue'\nimport { prefersReducedMotion } from '../utils/dom'\nimport { useFocusTrap } from './useFocusTrap'\nimport { useLayer } from './useLayer'\nimport { useScrollLock } from './useScrollLock'\n\nexport interface ModalOptions {\n closeOnEscape: () => boolean\n closeOnOverlay: () => boolean\n close: () => void\n /** Selector for the element to focus first, inside the panel. */\n focusSelector?: string\n}\n\n/**\n * Internal: everything a modal surface (Dialog, Sheet) shares — layering,\n * focus trap and restoration, scroll lock, backdrop clicks and the \"nudge\"\n * when dismissal is refused.\n */\nexport function useModal(panel: Ref<HTMLElement | null>, open: Ref<boolean>, options: ModalOptions) {\n const nudging = ref(false)\n\n // A modal that refuses to close gives a small, polite nudge instead.\n function nudge() {\n if (prefersReducedMotion()) return\n nudging.value = false\n requestAnimationFrame(() => (nudging.value = true))\n }\n\n const { isTopLayer } = useLayer({\n active: open,\n elements: () => [panel.value],\n onEscape: () => (options.closeOnEscape() ? options.close() : nudge()),\n })\n\n useFocusTrap(panel, open, {\n // Prefer an explicit autofocus, then the first form field. Otherwise focus the\n // panel itself so screen readers announce it without jumping to a button.\n initialFocus: () =>\n panel.value?.querySelector<HTMLElement>('[autofocus], [data-autofocus]')\n ?? (options.focusSelector ? panel.value?.querySelector<HTMLElement>(options.focusSelector) : null)\n ?? panel.value,\n })\n\n useScrollLock(open)\n\n // Close on a click that both starts and ends on the backdrop, so dragging a\n // text selection out of the panel never dismisses it.\n let pressedOutside = false\n function onBackdropPointerDown(event: PointerEvent) {\n pressedOutside = event.target === event.currentTarget && isTopLayer()\n }\n function onBackdropClick(event: MouseEvent) {\n const outside = pressedOutside && event.target === event.currentTarget\n pressedOutside = false\n if (!outside) return\n if (options.closeOnOverlay()) options.close()\n else nudge()\n }\n\n return { nudging, onBackdropPointerDown, onBackdropClick }\n}\n\n/** Form fields worth focusing first in a modal body. */\nexport const FIELD_SELECTOR = ':is(input:not([type=hidden]), textarea, select, [role=combobox]):not(:disabled)'\n","<script setup lang=\"ts\">\r\nimport { ref, useId } from 'vue'\r\nimport { FIELD_SELECTOR, useModal } from '../../composables/useModal'\r\nimport { IconX } from '../../icons'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface DialogProps {\r\n title?: string\r\n description?: string\r\n size?: 'sm' | 'md' | 'lg'\r\n /** Close when the backdrop is clicked. Defaults to true. */\r\n closeOnOverlay?: boolean\r\n /** Close when Escape is pressed. Defaults to true. */\r\n closeOnEscape?: boolean\r\n /** Hide the close button in the corner. */\r\n hideClose?: boolean\r\n /** Accessible label when there is no visible title. */\r\n ariaLabel?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<DialogProps>(), {\r\n size: 'md',\r\n closeOnOverlay: true,\r\n closeOnEscape: true,\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\n\r\nconst emit = defineEmits<{\r\n /** Fired after the leave transition finishes — a good moment to reset form state. */\r\n afterClose: []\r\n}>()\r\n\r\ndefineSlots<{\r\n default?: (props: { close: () => void }) => unknown\r\n /** Custom header. Put `titleId` on your heading so the dialog stays labelled. */\r\n header?: (props: { titleId: string, descriptionId: string }) => unknown\r\n footer?: (props: { close: () => void }) => unknown\r\n}>()\r\n\r\nconst id = useId()\r\nconst titleId = `j-${id}-title`\r\nconst descriptionId = `j-${id}-description`\r\n\r\nconst panel = ref<HTMLElement | null>(null)\r\n\r\nfunction close() {\r\n open.value = false\r\n}\r\n\r\nconst { nudging, onBackdropPointerDown, onBackdropClick } = useModal(panel, open, {\r\n closeOnEscape: () => props.closeOnEscape,\r\n closeOnOverlay: () => props.closeOnOverlay,\r\n close,\r\n focusSelector: `.j-dialog__body ${FIELD_SELECTOR}`,\r\n})\r\n\r\ndefineExpose({ close })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-dialog-overlay\">\r\n <div v-if=\"open\" class=\"j-dialog__overlay\" aria-hidden=\"true\" />\r\n </Transition>\r\n <Transition name=\"j-dialog\" :duration=\"{ enter: 240, leave: 150 }\" @after-leave=\"emit('afterClose')\">\r\n <div v-if=\"open\" class=\"j-dialog__positioner\" @pointerdown=\"onBackdropPointerDown\" @click=\"onBackdropClick\">\r\n <div\r\n ref=\"panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n class=\"j-dialog__panel\"\r\n :class=\"[`j-dialog__panel--${size}`, { 'is-nudging': nudging }]\"\r\n :aria-labelledby=\"title || $slots.header ? titleId : undefined\"\r\n :aria-label=\"ariaLabel\"\r\n :aria-describedby=\"description ? descriptionId : undefined\"\r\n @animationend=\"nudging = false\"\r\n >\r\n <header v-if=\"title || description || $slots.header\" class=\"j-dialog__header\">\r\n <slot name=\"header\" :title-id=\"titleId\" :description-id=\"descriptionId\">\r\n <h2 :id=\"titleId\" class=\"j-dialog__title\">\r\n {{ title }}\r\n </h2>\r\n <p v-if=\"description\" :id=\"descriptionId\" class=\"j-dialog__description\">\r\n {{ description }}\r\n </p>\r\n </slot>\r\n </header>\r\n <div v-if=\"$slots.default\" class=\"j-dialog__body\">\r\n <slot :close=\"close\" />\r\n </div>\r\n <footer v-if=\"$slots.footer\" class=\"j-dialog__footer\">\r\n <slot name=\"footer\" :close=\"close\" />\r\n </footer>\r\n <button\r\n v-if=\"!hideClose\"\r\n type=\"button\"\r\n class=\"j-dialog__close j-focusable\"\r\n aria-label=\"Close\"\r\n @click=\"close\"\r\n >\r\n <IconX />\r\n </button>\r\n </div>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-dialog__overlay {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-overlay);\r\n background: var(--juxt-overlay);\r\n}\r\n\r\n.j-dialog__positioner {\r\n --j-dialog-shift: calc(var(--juxt-motion-shift) * 2);\r\n\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-modal);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: var(--juxt-space-4);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n}\r\n\r\n.j-dialog__panel {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n max-height: calc(100dvh - var(--juxt-space-8));\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-lg);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n outline: none;\r\n}\r\n\r\n.j-dialog__panel--sm {\r\n max-width: 25rem;\r\n}\r\n\r\n.j-dialog__panel--md {\r\n max-width: 30rem;\r\n}\r\n\r\n.j-dialog__panel--lg {\r\n max-width: 40rem;\r\n}\r\n\r\n.j-dialog__header {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n padding: var(--juxt-space-5) var(--juxt-space-12) 0 var(--juxt-space-5);\r\n}\r\n\r\n.j-dialog__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-lg);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-dialog__description {\r\n margin: 0;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-dialog__body {\r\n flex: 1;\r\n min-height: 0;\r\n padding: var(--juxt-space-4) var(--juxt-space-5) 0;\r\n overflow-y: auto;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-dialog__header + .j-dialog__footer {\r\n margin-top: var(--juxt-space-1);\r\n}\r\n\r\n.j-dialog__footer {\r\n display: flex;\r\n justify-content: flex-end;\r\n gap: var(--juxt-space-2);\r\n padding: var(--juxt-space-5);\r\n}\r\n\r\n.j-dialog__body:last-child {\r\n padding-bottom: var(--juxt-space-5);\r\n}\r\n\r\n.j-dialog__close {\r\n position: absolute;\r\n top: var(--juxt-space-3);\r\n right: var(--juxt-space-3);\r\n display: grid;\r\n place-items: center;\r\n width: var(--juxt-control-sm);\r\n height: var(--juxt-control-sm);\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-dialog__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-dialog__close > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n/* Motion: the backdrop fades; the panel fades, lifts and settles. */\r\n.j-dialog-overlay-enter-active {\r\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-dialog-overlay-leave-active {\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-dialog-overlay-enter-from,\r\n.j-dialog-overlay-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-dialog-enter-active .j-dialog__panel {\r\n transition:\r\n opacity var(--juxt-duration-normal) var(--juxt-ease-out),\r\n transform var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-dialog-leave-active .j-dialog__panel {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-in),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-dialog-enter-from .j-dialog__panel,\r\n.j-dialog-leave-to .j-dialog__panel {\r\n opacity: 0;\r\n transform: translateY(var(--j-dialog-shift)) scale(var(--juxt-motion-scale));\r\n}\r\n\r\n.j-dialog__panel.is-nudging {\r\n animation: j-dialog-nudge 220ms var(--juxt-ease-out);\r\n}\r\n\r\n@keyframes j-dialog-nudge {\r\n 40% {\r\n transform: scale(1.012);\r\n }\r\n}\r\n\r\n/* Small screens: a sheet anchored to the bottom, within thumb reach. */\r\n@media (max-width: 639px) {\r\n .j-dialog__positioner {\r\n --j-dialog-shift: calc(var(--juxt-motion-shift) * 6);\r\n\r\n align-items: flex-end;\r\n padding: var(--juxt-space-2);\r\n padding-bottom: max(var(--juxt-space-2), env(safe-area-inset-bottom));\r\n }\r\n\r\n .j-dialog__panel {\r\n max-width: none;\r\n max-height: calc(100dvh - var(--juxt-space-12));\r\n }\r\n\r\n .j-dialog-enter-from .j-dialog__panel,\r\n .j-dialog-leave-to .j-dialog__panel {\r\n transform: translateY(var(--j-dialog-shift));\r\n }\r\n\r\n .j-dialog__footer {\r\n flex-direction: column-reverse;\r\n }\r\n\r\n .j-dialog__footer > * {\r\n width: 100%;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { ref, useId } from 'vue'\r\nimport { FIELD_SELECTOR, useModal } from '../../composables/useModal'\r\nimport { IconX } from '../../icons'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface DialogProps {\r\n title?: string\r\n description?: string\r\n size?: 'sm' | 'md' | 'lg'\r\n /** Close when the backdrop is clicked. Defaults to true. */\r\n closeOnOverlay?: boolean\r\n /** Close when Escape is pressed. Defaults to true. */\r\n closeOnEscape?: boolean\r\n /** Hide the close button in the corner. */\r\n hideClose?: boolean\r\n /** Accessible label when there is no visible title. */\r\n ariaLabel?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<DialogProps>(), {\r\n size: 'md',\r\n closeOnOverlay: true,\r\n closeOnEscape: true,\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\n\r\nconst emit = defineEmits<{\r\n /** Fired after the leave transition finishes — a good moment to reset form state. */\r\n afterClose: []\r\n}>()\r\n\r\ndefineSlots<{\r\n default?: (props: { close: () => void }) => unknown\r\n /** Custom header. Put `titleId` on your heading so the dialog stays labelled. */\r\n header?: (props: { titleId: string, descriptionId: string }) => unknown\r\n footer?: (props: { close: () => void }) => unknown\r\n}>()\r\n\r\nconst id = useId()\r\nconst titleId = `j-${id}-title`\r\nconst descriptionId = `j-${id}-description`\r\n\r\nconst panel = ref<HTMLElement | null>(null)\r\n\r\nfunction close() {\r\n open.value = false\r\n}\r\n\r\nconst { nudging, onBackdropPointerDown, onBackdropClick } = useModal(panel, open, {\r\n closeOnEscape: () => props.closeOnEscape,\r\n closeOnOverlay: () => props.closeOnOverlay,\r\n close,\r\n focusSelector: `.j-dialog__body ${FIELD_SELECTOR}`,\r\n})\r\n\r\ndefineExpose({ close })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-dialog-overlay\">\r\n <div v-if=\"open\" class=\"j-dialog__overlay\" aria-hidden=\"true\" />\r\n </Transition>\r\n <Transition name=\"j-dialog\" :duration=\"{ enter: 240, leave: 150 }\" @after-leave=\"emit('afterClose')\">\r\n <div v-if=\"open\" class=\"j-dialog__positioner\" @pointerdown=\"onBackdropPointerDown\" @click=\"onBackdropClick\">\r\n <div\r\n ref=\"panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n class=\"j-dialog__panel\"\r\n :class=\"[`j-dialog__panel--${size}`, { 'is-nudging': nudging }]\"\r\n :aria-labelledby=\"title || $slots.header ? titleId : undefined\"\r\n :aria-label=\"ariaLabel\"\r\n :aria-describedby=\"description ? descriptionId : undefined\"\r\n @animationend=\"nudging = false\"\r\n >\r\n <header v-if=\"title || description || $slots.header\" class=\"j-dialog__header\">\r\n <slot name=\"header\" :title-id=\"titleId\" :description-id=\"descriptionId\">\r\n <h2 :id=\"titleId\" class=\"j-dialog__title\">\r\n {{ title }}\r\n </h2>\r\n <p v-if=\"description\" :id=\"descriptionId\" class=\"j-dialog__description\">\r\n {{ description }}\r\n </p>\r\n </slot>\r\n </header>\r\n <div v-if=\"$slots.default\" class=\"j-dialog__body\">\r\n <slot :close=\"close\" />\r\n </div>\r\n <footer v-if=\"$slots.footer\" class=\"j-dialog__footer\">\r\n <slot name=\"footer\" :close=\"close\" />\r\n </footer>\r\n <button\r\n v-if=\"!hideClose\"\r\n type=\"button\"\r\n class=\"j-dialog__close j-focusable\"\r\n aria-label=\"Close\"\r\n @click=\"close\"\r\n >\r\n <IconX />\r\n </button>\r\n </div>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-dialog__overlay {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-overlay);\r\n background: var(--juxt-overlay);\r\n}\r\n\r\n.j-dialog__positioner {\r\n --j-dialog-shift: calc(var(--juxt-motion-shift) * 2);\r\n\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-modal);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: var(--juxt-space-4);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n}\r\n\r\n.j-dialog__panel {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n max-height: calc(100dvh - var(--juxt-space-8));\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-lg);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n outline: none;\r\n}\r\n\r\n.j-dialog__panel--sm {\r\n max-width: 25rem;\r\n}\r\n\r\n.j-dialog__panel--md {\r\n max-width: 30rem;\r\n}\r\n\r\n.j-dialog__panel--lg {\r\n max-width: 40rem;\r\n}\r\n\r\n.j-dialog__header {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n padding: var(--juxt-space-5) var(--juxt-space-12) 0 var(--juxt-space-5);\r\n}\r\n\r\n.j-dialog__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-lg);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-dialog__description {\r\n margin: 0;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-dialog__body {\r\n flex: 1;\r\n min-height: 0;\r\n padding: var(--juxt-space-4) var(--juxt-space-5) 0;\r\n overflow-y: auto;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-dialog__header + .j-dialog__footer {\r\n margin-top: var(--juxt-space-1);\r\n}\r\n\r\n.j-dialog__footer {\r\n display: flex;\r\n justify-content: flex-end;\r\n gap: var(--juxt-space-2);\r\n padding: var(--juxt-space-5);\r\n}\r\n\r\n.j-dialog__body:last-child {\r\n padding-bottom: var(--juxt-space-5);\r\n}\r\n\r\n.j-dialog__close {\r\n position: absolute;\r\n top: var(--juxt-space-3);\r\n right: var(--juxt-space-3);\r\n display: grid;\r\n place-items: center;\r\n width: var(--juxt-control-sm);\r\n height: var(--juxt-control-sm);\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-dialog__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-dialog__close > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n/* Motion: the backdrop fades; the panel fades, lifts and settles. */\r\n.j-dialog-overlay-enter-active {\r\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-dialog-overlay-leave-active {\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-dialog-overlay-enter-from,\r\n.j-dialog-overlay-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-dialog-enter-active .j-dialog__panel {\r\n transition:\r\n opacity var(--juxt-duration-normal) var(--juxt-ease-out),\r\n transform var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-dialog-leave-active .j-dialog__panel {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-in),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-dialog-enter-from .j-dialog__panel,\r\n.j-dialog-leave-to .j-dialog__panel {\r\n opacity: 0;\r\n transform: translateY(var(--j-dialog-shift)) scale(var(--juxt-motion-scale));\r\n}\r\n\r\n.j-dialog__panel.is-nudging {\r\n animation: j-dialog-nudge 220ms var(--juxt-ease-out);\r\n}\r\n\r\n@keyframes j-dialog-nudge {\r\n 40% {\r\n transform: scale(1.012);\r\n }\r\n}\r\n\r\n/* Small screens: a sheet anchored to the bottom, within thumb reach. */\r\n@media (max-width: 639px) {\r\n .j-dialog__positioner {\r\n --j-dialog-shift: calc(var(--juxt-motion-shift) * 6);\r\n\r\n align-items: flex-end;\r\n padding: var(--juxt-space-2);\r\n padding-bottom: max(var(--juxt-space-2), env(safe-area-inset-bottom));\r\n }\r\n\r\n .j-dialog__panel {\r\n max-width: none;\r\n max-height: calc(100dvh - var(--juxt-space-12));\r\n }\r\n\r\n .j-dialog-enter-from .j-dialog__panel,\r\n .j-dialog-leave-to .j-dialog__panel {\r\n transform: translateY(var(--j-dialog-shift));\r\n }\r\n\r\n .j-dialog__footer {\r\n flex-direction: column-reverse;\r\n }\r\n\r\n .j-dialog__footer > * {\r\n width: 100%;\r\n }\r\n}\r\n}\r\n</style>\r\n","import type { VNode } from 'vue'\r\nimport { cloneVNode, Comment, defineComponent, Fragment, h, Text } from 'vue'\r\n\r\nfunction firstElement(nodes: VNode[] | undefined): VNode | undefined {\r\n if (!nodes) return undefined\r\n for (const node of nodes) {\r\n if (node.type === Comment) continue\r\n if (node.type === Fragment && Array.isArray(node.children)) {\r\n const found = firstElement(node.children as VNode[])\r\n if (found) return found\r\n continue\r\n }\r\n if (node.type === Text) {\r\n if (typeof node.children === 'string' && node.children.trim() === '') continue\r\n // Bare text can't hold attributes; give it an element.\r\n return h('span', null, node.children as string)\r\n }\r\n return node\r\n }\r\n return undefined\r\n}\r\n\r\n/**\r\n * Internal: renders its first child element and merges its own attributes\r\n * (ARIA, listeners, ids) onto it — no wrapper element.\r\n */\r\nexport const JSlot = defineComponent({\r\n name: 'JSlot',\r\n inheritAttrs: false,\r\n setup(_, { slots, attrs }) {\r\n return () => {\r\n const child = firstElement(slots.default?.())\r\n if (!child) return null\r\n // The consumer's own id wins; everything else is merged.\r\n const { id, ...rest } = attrs\r\n return cloneVNode(child, child.props?.id ? rest : { id, ...rest }, true)\r\n }\r\n },\r\n})\r\n","import type { InjectionKey, Ref } from 'vue'\r\n\r\nexport interface MenuRootContext {\r\n /** Close the whole menu tree. */\r\n close: (restoreFocus?: boolean) => void\r\n}\r\n\r\nexport interface MenuLevelContext {\r\n content: Ref<HTMLElement | null>\r\n /** Id of the submenu currently open at this level. */\r\n openSub: Ref<string | null>\r\n setOpenSub: (id: string | null) => void\r\n /** Close the open submenu shortly, unless the pointer is on its way into it. */\r\n scheduleSubClose: () => void\r\n cancelSubClose: () => void\r\n}\r\n\r\nexport const MENU_ROOT: InjectionKey<MenuRootContext> = Symbol('JDropdownMenu')\r\nexport const MENU_LEVEL: InjectionKey<MenuLevelContext> = Symbol('JDropdownMenuLevel')\r\n\r\nexport function getMenuItems(content: HTMLElement | null): HTMLElement[] {\r\n if (!content) return []\r\n return Array.from(content.querySelectorAll<HTMLElement>('[role^=\"menuitem\"]:not([aria-disabled=\"true\"])'))\r\n}\r\n","import type { Placement } from '@floating-ui/dom'\r\nimport type { CSSProperties, Ref } from 'vue'\r\nimport { autoUpdate, computePosition, flip, offset, shift, size } from '@floating-ui/dom'\r\nimport { computed, onBeforeUnmount, ref, shallowRef, watch } from 'vue'\r\n\r\nexport type { Placement }\r\n\r\nexport interface FloatingOptions {\r\n placement?: Placement\r\n /** Gap between reference and floating element, in px. */\r\n offset?: number | { mainAxis?: number, crossAxis?: number }\r\n /** Make the floating element at least as wide as the reference. */\r\n matchWidth?: boolean\r\n /** Padding kept from the viewport edge, in px. */\r\n padding?: number\r\n}\r\n\r\nconst ORIGIN: Record<string, string> = {\r\n top: 'bottom',\r\n bottom: 'top',\r\n left: 'right',\r\n right: 'left',\r\n}\r\n\r\n/**\r\n * Position a floating element next to a reference element and keep it there\r\n * through scroll and resize. Coordinates are written to `left`/`top` so\r\n * `transform` stays free for enter/leave motion.\r\n */\r\nexport function useFloating(\r\n reference: Ref<HTMLElement | null>,\r\n floating: Ref<HTMLElement | null>,\r\n options: FloatingOptions | (() => FloatingOptions) = {},\r\n) {\r\n const read = () => (typeof options === 'function' ? options() : options)\r\n const x = ref(0)\r\n const y = ref(0)\r\n const placement = ref<Placement>(read().placement ?? 'bottom-start')\r\n const availableHeight = ref<number>()\r\n const referenceWidth = ref<number>()\r\n const stop = shallowRef<() => void>()\r\n\r\n async function update() {\r\n const referenceEl = reference.value\r\n const floatingEl = floating.value\r\n if (!referenceEl || !floatingEl) return\r\n const opts = read()\r\n const padding = opts.padding ?? 8\r\n const result = await computePosition(referenceEl, floatingEl, {\r\n strategy: 'fixed',\r\n placement: opts.placement ?? 'bottom-start',\r\n middleware: [\r\n offset(opts.offset ?? 6),\r\n flip({ padding }),\r\n shift({ padding }),\r\n size({\r\n padding,\r\n apply({ availableHeight: height, rects }) {\r\n availableHeight.value = Math.max(120, Math.floor(height))\r\n referenceWidth.value = rects.reference.width\r\n },\r\n }),\r\n ],\r\n })\r\n x.value = Math.round(result.x)\r\n y.value = Math.round(result.y)\r\n placement.value = result.placement\r\n }\r\n\r\n watch(\r\n [reference, floating],\r\n ([referenceEl, floatingEl]) => {\r\n stop.value?.()\r\n stop.value = undefined\r\n if (referenceEl && floatingEl) stop.value = autoUpdate(referenceEl, floatingEl, update)\r\n },\r\n { flush: 'post' },\r\n )\r\n\r\n onBeforeUnmount(() => stop.value?.())\r\n\r\n const side = computed(() => placement.value.split('-')[0] as 'top' | 'bottom' | 'left' | 'right')\r\n\r\n const styles = computed<CSSProperties>(() => {\r\n const align = placement.value.split('-')[1]\r\n const cross = side.value === 'top' || side.value === 'bottom'\r\n ? (align === 'start' ? 'left' : align === 'end' ? 'right' : 'center')\r\n : (align === 'start' ? 'top' : align === 'end' ? 'bottom' : 'center')\r\n const origin = side.value === 'top' || side.value === 'bottom'\r\n ? `${cross} ${ORIGIN[side.value]}`\r\n : `${ORIGIN[side.value]} ${cross}`\r\n const dir = side.value === 'top' ? 1 : side.value === 'bottom' ? -1 : 0\r\n return {\r\n 'position': 'fixed',\r\n 'left': `${x.value}px`,\r\n 'top': `${y.value}px`,\r\n 'transformOrigin': origin,\r\n 'minWidth': read().matchWidth && referenceWidth.value ? `${referenceWidth.value}px` : undefined,\r\n '--j-available-height': availableHeight.value ? `${availableHeight.value}px` : undefined,\r\n '--j-pop-dir': String(dir),\r\n '--j-pop-x': String(side.value === 'left' ? 1 : side.value === 'right' ? -1 : 0),\r\n } as CSSProperties\r\n })\r\n\r\n return { x, y, placement, side, styles, update }\r\n}\r\n","/**\r\n * Type-to-jump for lists. Characters typed in quick succession form a query;\r\n * repeating the same letter cycles through matches.\r\n */\r\nexport function useTypeahead(timeout = 500) {\r\n let query = ''\r\n let timer: ReturnType<typeof setTimeout> | undefined\r\n\r\n function search<T>(key: string, items: T[], getLabel: (item: T) => string, currentIndex: number): number {\r\n if (key.length !== 1 || (key === ' ' && query === '')) return -1\r\n clearTimeout(timer)\r\n timer = setTimeout(() => (query = ''), timeout)\r\n query += key.toLowerCase()\r\n\r\n const repeated = query.length > 1 && [...query].every(char => char === query[0])\r\n const needle = repeated ? query[0]! : query\r\n // A fresh search starts after the current item so repeated keys cycle.\r\n const start = repeated || query.length === 1 ? currentIndex + 1 : currentIndex\r\n for (let i = 0; i < items.length; i++) {\r\n const index = (start + i + items.length) % items.length\r\n if (getLabel(items[index]!).trim().toLowerCase().startsWith(needle)) return index\r\n }\r\n return -1\r\n }\r\n\r\n const isSearching = () => query.length > 0\r\n\r\n return { search, isSearching }\r\n}\r\n","<script setup lang=\"ts\">\r\n/**\r\n * Internal: the floating list shared by the root menu and submenus.\r\n * Owns positioning, keyboard navigation, typeahead and the layer.\r\n */\r\nimport type { Placement } from '../../composables/useFloating'\r\nimport type { MenuLevelContext } from './context'\r\nimport { inject, nextTick, provide, ref, toRef, watch } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { useTypeahead } from '../../composables/useTypeahead'\r\nimport { focusElement } from '../../utils/dom'\r\nimport JPortal from '../primitives/JPortal.vue'\r\nimport { getMenuItems, MENU_LEVEL, MENU_ROOT } from './context'\r\n\r\nconst props = defineProps<{\r\n open: boolean\r\n reference: HTMLElement | null\r\n id: string\r\n labelledby?: string\r\n placement: Placement\r\n offset: number | { mainAxis?: number, crossAxis?: number }\r\n /** What to focus once open. */\r\n initialFocus: 'first' | 'last' | 'content' | 'none'\r\n isSub?: boolean\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n escape: []\r\n outside: [event: PointerEvent]\r\n /** Submenus only: ArrowLeft asks to go back to the parent. */\r\n back: []\r\n pointerenter: []\r\n}>()\r\n\r\nconst root = inject(MENU_ROOT)!\r\nconst content = ref<HTMLElement | null>(null)\r\nconst reference = toRef(props, 'reference')\r\n\r\nconst { styles } = useFloating(reference, content, () => ({ placement: props.placement, offset: props.offset }))\r\nconst typeahead = useTypeahead()\r\n\r\nuseLayer({\r\n active: toRef(props, 'open'),\r\n elements: () => [content.value, props.reference],\r\n onEscape: () => emit('escape'),\r\n onPointerDownOutside: event => emit('outside', event),\r\n})\r\n\r\n// Level context for the items inside this list.\r\nconst openSub = ref<string | null>(null)\r\nlet subTimer: ReturnType<typeof setTimeout> | undefined\r\nconst level: MenuLevelContext = {\r\n content,\r\n openSub,\r\n setOpenSub: (id) => {\r\n clearTimeout(subTimer)\r\n openSub.value = id\r\n },\r\n scheduleSubClose: () => {\r\n if (!openSub.value) return\r\n clearTimeout(subTimer)\r\n subTimer = setTimeout(() => (openSub.value = null), 180)\r\n },\r\n cancelSubClose: () => clearTimeout(subTimer),\r\n}\r\nprovide(MENU_LEVEL, level)\r\n\r\nwatch(\r\n () => props.open,\r\n async (value) => {\r\n if (!value) {\r\n openSub.value = null\r\n return\r\n }\r\n if (props.initialFocus === 'none') return\r\n await nextTick()\r\n const items = getMenuItems(content.value)\r\n if (props.initialFocus === 'first') focusElement(items[0] ?? content.value)\r\n else if (props.initialFocus === 'last') focusElement(items[items.length - 1] ?? content.value)\r\n else focusElement(content.value)\r\n },\r\n { flush: 'post' },\r\n)\r\n\r\nfunction move(delta: 1 | -1 | 'first' | 'last') {\r\n const items = getMenuItems(content.value)\r\n if (items.length === 0) return\r\n const current = items.indexOf(document.activeElement as HTMLElement)\r\n let next: number\r\n if (delta === 'first') next = 0\r\n else if (delta === 'last') next = items.length - 1\r\n else if (current === -1) next = delta === 1 ? 0 : items.length - 1\r\n else next = (current + delta + items.length) % items.length\r\n focusElement(items[next])\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const { key } = event\r\n switch (key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n move(1)\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n move(-1)\r\n break\r\n case 'Home':\r\n case 'PageUp':\r\n event.preventDefault()\r\n move('first')\r\n break\r\n case 'End':\r\n case 'PageDown':\r\n event.preventDefault()\r\n move('last')\r\n break\r\n case 'ArrowLeft':\r\n if (props.isSub) {\r\n event.preventDefault()\r\n emit('back')\r\n }\r\n break\r\n case 'Tab':\r\n event.preventDefault()\r\n root.close(true)\r\n break\r\n case 'Enter':\r\n case ' ': {\r\n const active = document.activeElement as HTMLElement | null\r\n if (active && active !== content.value && content.value?.contains(active)) {\r\n event.preventDefault()\r\n active.click()\r\n }\r\n break\r\n }\r\n default:\r\n if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\r\n const items = getMenuItems(content.value)\r\n const index = typeahead.search(key, items, item => item.textContent ?? '', items.indexOf(document.activeElement as HTMLElement))\r\n if (index >= 0) focusElement(items[index])\r\n }\r\n }\r\n}\r\n\r\ndefineExpose({ content })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-pop\">\r\n <div\r\n v-if=\"open\"\r\n :id=\"id\"\r\n ref=\"content\"\r\n role=\"menu\"\r\n tabindex=\"-1\"\r\n class=\"j-menu j-popover-surface\"\r\n :class=\"{ 'j-menu--sub': isSub }\"\r\n :style=\"styles\"\r\n :aria-labelledby=\"labelledby\"\r\n aria-orientation=\"vertical\"\r\n @keydown=\"onKeydown\"\r\n @pointerenter=\"emit('pointerenter')\"\r\n >\r\n <slot />\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-menu {\r\n z-index: var(--juxt-z-popover);\r\n box-sizing: border-box;\r\n min-width: 11rem;\r\n max-width: calc(100vw - 16px);\r\n max-height: var(--j-available-height, 24rem);\r\n padding: var(--juxt-space-1);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n outline: none;\r\n}\r\n\r\n.j-menu__item {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2);\r\n min-height: var(--juxt-control-sm);\r\n padding: 0 var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-xs);\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n text-decoration: none;\r\n cursor: pointer;\r\n outline: none;\r\n user-select: none;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.j-menu__item:focus,\r\n.j-menu__item[aria-expanded='true'] {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-menu__item[aria-disabled='true'] {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-menu__item--destructive {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-menu__item--destructive:focus {\r\n background: var(--juxt-danger-soft);\r\n}\r\n\r\n.j-menu__icon {\r\n display: inline-flex;\r\n flex: none;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-menu__icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-menu__item:focus .j-menu__icon {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-menu__item--destructive .j-menu__icon {\r\n color: inherit;\r\n}\r\n\r\n.j-menu__text {\r\n flex: 1;\r\n min-width: 0;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-menu__shortcut {\r\n margin-left: var(--juxt-space-4);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n letter-spacing: 0.04em;\r\n}\r\n\r\n.j-menu__check {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n margin-left: var(--juxt-space-4);\r\n color: var(--juxt-accent-text);\r\n opacity: 0;\r\n}\r\n\r\n.j-menu__check.is-checked {\r\n opacity: 1;\r\n}\r\n\r\n.j-menu__chevron {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n margin-right: -0.125rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-menu__separator {\r\n height: 1px;\r\n margin: var(--juxt-space-1) calc(var(--juxt-space-1) * -1);\r\n background: var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-menu__separator,\r\n[data-theme='dark'] .j-menu__separator {\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-menu__label {\r\n padding: var(--juxt-space-1-5) var(--juxt-space-2) var(--juxt-space-1);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\n/**\r\n * Internal: the floating list shared by the root menu and submenus.\r\n * Owns positioning, keyboard navigation, typeahead and the layer.\r\n */\r\nimport type { Placement } from '../../composables/useFloating'\r\nimport type { MenuLevelContext } from './context'\r\nimport { inject, nextTick, provide, ref, toRef, watch } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { useTypeahead } from '../../composables/useTypeahead'\r\nimport { focusElement } from '../../utils/dom'\r\nimport JPortal from '../primitives/JPortal.vue'\r\nimport { getMenuItems, MENU_LEVEL, MENU_ROOT } from './context'\r\n\r\nconst props = defineProps<{\r\n open: boolean\r\n reference: HTMLElement | null\r\n id: string\r\n labelledby?: string\r\n placement: Placement\r\n offset: number | { mainAxis?: number, crossAxis?: number }\r\n /** What to focus once open. */\r\n initialFocus: 'first' | 'last' | 'content' | 'none'\r\n isSub?: boolean\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n escape: []\r\n outside: [event: PointerEvent]\r\n /** Submenus only: ArrowLeft asks to go back to the parent. */\r\n back: []\r\n pointerenter: []\r\n}>()\r\n\r\nconst root = inject(MENU_ROOT)!\r\nconst content = ref<HTMLElement | null>(null)\r\nconst reference = toRef(props, 'reference')\r\n\r\nconst { styles } = useFloating(reference, content, () => ({ placement: props.placement, offset: props.offset }))\r\nconst typeahead = useTypeahead()\r\n\r\nuseLayer({\r\n active: toRef(props, 'open'),\r\n elements: () => [content.value, props.reference],\r\n onEscape: () => emit('escape'),\r\n onPointerDownOutside: event => emit('outside', event),\r\n})\r\n\r\n// Level context for the items inside this list.\r\nconst openSub = ref<string | null>(null)\r\nlet subTimer: ReturnType<typeof setTimeout> | undefined\r\nconst level: MenuLevelContext = {\r\n content,\r\n openSub,\r\n setOpenSub: (id) => {\r\n clearTimeout(subTimer)\r\n openSub.value = id\r\n },\r\n scheduleSubClose: () => {\r\n if (!openSub.value) return\r\n clearTimeout(subTimer)\r\n subTimer = setTimeout(() => (openSub.value = null), 180)\r\n },\r\n cancelSubClose: () => clearTimeout(subTimer),\r\n}\r\nprovide(MENU_LEVEL, level)\r\n\r\nwatch(\r\n () => props.open,\r\n async (value) => {\r\n if (!value) {\r\n openSub.value = null\r\n return\r\n }\r\n if (props.initialFocus === 'none') return\r\n await nextTick()\r\n const items = getMenuItems(content.value)\r\n if (props.initialFocus === 'first') focusElement(items[0] ?? content.value)\r\n else if (props.initialFocus === 'last') focusElement(items[items.length - 1] ?? content.value)\r\n else focusElement(content.value)\r\n },\r\n { flush: 'post' },\r\n)\r\n\r\nfunction move(delta: 1 | -1 | 'first' | 'last') {\r\n const items = getMenuItems(content.value)\r\n if (items.length === 0) return\r\n const current = items.indexOf(document.activeElement as HTMLElement)\r\n let next: number\r\n if (delta === 'first') next = 0\r\n else if (delta === 'last') next = items.length - 1\r\n else if (current === -1) next = delta === 1 ? 0 : items.length - 1\r\n else next = (current + delta + items.length) % items.length\r\n focusElement(items[next])\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const { key } = event\r\n switch (key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n move(1)\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n move(-1)\r\n break\r\n case 'Home':\r\n case 'PageUp':\r\n event.preventDefault()\r\n move('first')\r\n break\r\n case 'End':\r\n case 'PageDown':\r\n event.preventDefault()\r\n move('last')\r\n break\r\n case 'ArrowLeft':\r\n if (props.isSub) {\r\n event.preventDefault()\r\n emit('back')\r\n }\r\n break\r\n case 'Tab':\r\n event.preventDefault()\r\n root.close(true)\r\n break\r\n case 'Enter':\r\n case ' ': {\r\n const active = document.activeElement as HTMLElement | null\r\n if (active && active !== content.value && content.value?.contains(active)) {\r\n event.preventDefault()\r\n active.click()\r\n }\r\n break\r\n }\r\n default:\r\n if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\r\n const items = getMenuItems(content.value)\r\n const index = typeahead.search(key, items, item => item.textContent ?? '', items.indexOf(document.activeElement as HTMLElement))\r\n if (index >= 0) focusElement(items[index])\r\n }\r\n }\r\n}\r\n\r\ndefineExpose({ content })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-pop\">\r\n <div\r\n v-if=\"open\"\r\n :id=\"id\"\r\n ref=\"content\"\r\n role=\"menu\"\r\n tabindex=\"-1\"\r\n class=\"j-menu j-popover-surface\"\r\n :class=\"{ 'j-menu--sub': isSub }\"\r\n :style=\"styles\"\r\n :aria-labelledby=\"labelledby\"\r\n aria-orientation=\"vertical\"\r\n @keydown=\"onKeydown\"\r\n @pointerenter=\"emit('pointerenter')\"\r\n >\r\n <slot />\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-menu {\r\n z-index: var(--juxt-z-popover);\r\n box-sizing: border-box;\r\n min-width: 11rem;\r\n max-width: calc(100vw - 16px);\r\n max-height: var(--j-available-height, 24rem);\r\n padding: var(--juxt-space-1);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n outline: none;\r\n}\r\n\r\n.j-menu__item {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2);\r\n min-height: var(--juxt-control-sm);\r\n padding: 0 var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-xs);\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n text-decoration: none;\r\n cursor: pointer;\r\n outline: none;\r\n user-select: none;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.j-menu__item:focus,\r\n.j-menu__item[aria-expanded='true'] {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-menu__item[aria-disabled='true'] {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-menu__item--destructive {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-menu__item--destructive:focus {\r\n background: var(--juxt-danger-soft);\r\n}\r\n\r\n.j-menu__icon {\r\n display: inline-flex;\r\n flex: none;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-menu__icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-menu__item:focus .j-menu__icon {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-menu__item--destructive .j-menu__icon {\r\n color: inherit;\r\n}\r\n\r\n.j-menu__text {\r\n flex: 1;\r\n min-width: 0;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-menu__shortcut {\r\n margin-left: var(--juxt-space-4);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n letter-spacing: 0.04em;\r\n}\r\n\r\n.j-menu__check {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n margin-left: var(--juxt-space-4);\r\n color: var(--juxt-accent-text);\r\n opacity: 0;\r\n}\r\n\r\n.j-menu__check.is-checked {\r\n opacity: 1;\r\n}\r\n\r\n.j-menu__chevron {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n margin-right: -0.125rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-menu__separator {\r\n height: 1px;\r\n margin: var(--juxt-space-1) calc(var(--juxt-space-1) * -1);\r\n background: var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-menu__separator,\r\n[data-theme='dark'] .j-menu__separator {\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-menu__label {\r\n padding: var(--juxt-space-1-5) var(--juxt-space-2) var(--juxt-space-1);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Placement } from '../../composables/useFloating'\r\nimport { computed, nextTick, provide, ref, useId } from 'vue'\r\nimport { focusElement, unrefElement } from '../../utils/dom'\r\nimport { JSlot } from '../primitives/JSlot'\r\nimport { MENU_ROOT } from './context'\r\nimport JMenuContent from './JMenuContent.vue'\r\n\r\nexport interface DropdownMenuProps {\r\n placement?: Placement\r\n /** Distance from the trigger, in px. */\r\n offset?: number\r\n disabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<DropdownMenuProps>(), {\r\n placement: 'bottom-start',\r\n offset: 6,\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\n\r\ndefineSlots<{\r\n /** The element that opens the menu — usually a `JButton`. */\r\n trigger?: (props: { open: boolean }) => unknown\r\n default?: () => unknown\r\n}>()\r\n\r\nconst id = useId()\r\nconst triggerId = `j-${id}-trigger`\r\nconst menuId = `j-${id}-menu`\r\n\r\nconst triggerRef = ref()\r\nconst triggerEl = computed(() => unrefElement(triggerRef.value))\r\nconst initialFocus = ref<'first' | 'last' | 'content'>('content')\r\n\r\nfunction show(focus: 'first' | 'last' | 'content') {\r\n if (props.disabled) return\r\n initialFocus.value = focus\r\n open.value = true\r\n}\r\n\r\nfunction close(restoreFocus = true) {\r\n if (!open.value) return\r\n open.value = false\r\n if (restoreFocus) nextTick(() => focusElement(triggerEl.value))\r\n}\r\n\r\nprovide(MENU_ROOT, { close })\r\n\r\nfunction onClick(event: MouseEvent) {\r\n if (open.value) return close(false)\r\n // `detail` is 0 when a click came from the keyboard (Enter / Space).\r\n show(event.detail === 0 ? 'first' : 'content')\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\r\n event.preventDefault()\r\n show(event.key === 'ArrowDown' ? 'first' : 'last')\r\n }\r\n}\r\n\r\nconst triggerProps = computed(() => ({\r\n 'id': triggerId,\r\n 'aria-haspopup': 'menu',\r\n 'aria-expanded': open.value,\r\n 'aria-controls': open.value ? menuId : undefined,\r\n 'data-state': open.value ? 'open' : 'closed',\r\n 'onClick': onClick,\r\n 'onKeydown': onKeydown,\r\n}))\r\n\r\ndefineExpose({ open: () => show('content'), close })\r\n</script>\r\n\r\n<template>\r\n <JSlot ref=\"triggerRef\" v-bind=\"triggerProps\">\r\n <slot name=\"trigger\" :open=\"open\" />\r\n </JSlot>\r\n <JMenuContent\r\n :id=\"menuId\"\r\n :open=\"open\"\r\n :reference=\"triggerEl\"\r\n :labelledby=\"triggerEl?.id || triggerId\"\r\n :placement=\"placement\"\r\n :offset=\"offset\"\r\n :initial-focus=\"initialFocus\"\r\n @escape=\"close(true)\"\r\n @outside=\"close(false)\"\r\n >\r\n <slot />\r\n </JMenuContent>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport type { Placement } from '../../composables/useFloating'\r\nimport { computed, nextTick, provide, ref, useId } from 'vue'\r\nimport { focusElement, unrefElement } from '../../utils/dom'\r\nimport { JSlot } from '../primitives/JSlot'\r\nimport { MENU_ROOT } from './context'\r\nimport JMenuContent from './JMenuContent.vue'\r\n\r\nexport interface DropdownMenuProps {\r\n placement?: Placement\r\n /** Distance from the trigger, in px. */\r\n offset?: number\r\n disabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<DropdownMenuProps>(), {\r\n placement: 'bottom-start',\r\n offset: 6,\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\n\r\ndefineSlots<{\r\n /** The element that opens the menu — usually a `JButton`. */\r\n trigger?: (props: { open: boolean }) => unknown\r\n default?: () => unknown\r\n}>()\r\n\r\nconst id = useId()\r\nconst triggerId = `j-${id}-trigger`\r\nconst menuId = `j-${id}-menu`\r\n\r\nconst triggerRef = ref()\r\nconst triggerEl = computed(() => unrefElement(triggerRef.value))\r\nconst initialFocus = ref<'first' | 'last' | 'content'>('content')\r\n\r\nfunction show(focus: 'first' | 'last' | 'content') {\r\n if (props.disabled) return\r\n initialFocus.value = focus\r\n open.value = true\r\n}\r\n\r\nfunction close(restoreFocus = true) {\r\n if (!open.value) return\r\n open.value = false\r\n if (restoreFocus) nextTick(() => focusElement(triggerEl.value))\r\n}\r\n\r\nprovide(MENU_ROOT, { close })\r\n\r\nfunction onClick(event: MouseEvent) {\r\n if (open.value) return close(false)\r\n // `detail` is 0 when a click came from the keyboard (Enter / Space).\r\n show(event.detail === 0 ? 'first' : 'content')\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\r\n event.preventDefault()\r\n show(event.key === 'ArrowDown' ? 'first' : 'last')\r\n }\r\n}\r\n\r\nconst triggerProps = computed(() => ({\r\n 'id': triggerId,\r\n 'aria-haspopup': 'menu',\r\n 'aria-expanded': open.value,\r\n 'aria-controls': open.value ? menuId : undefined,\r\n 'data-state': open.value ? 'open' : 'closed',\r\n 'onClick': onClick,\r\n 'onKeydown': onKeydown,\r\n}))\r\n\r\ndefineExpose({ open: () => show('content'), close })\r\n</script>\r\n\r\n<template>\r\n <JSlot ref=\"triggerRef\" v-bind=\"triggerProps\">\r\n <slot name=\"trigger\" :open=\"open\" />\r\n </JSlot>\r\n <JMenuContent\r\n :id=\"menuId\"\r\n :open=\"open\"\r\n :reference=\"triggerEl\"\r\n :labelledby=\"triggerEl?.id || triggerId\"\r\n :placement=\"placement\"\r\n :offset=\"offset\"\r\n :initial-focus=\"initialFocus\"\r\n @escape=\"close(true)\"\r\n @outside=\"close(false)\"\r\n >\r\n <slot />\r\n </JMenuContent>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\nimport { inject, ref } from 'vue'\r\nimport { IconCheck } from '../../icons'\r\nimport { focusElement } from '../../utils/dom'\r\nimport { MENU_LEVEL, MENU_ROOT } from './context'\r\n\r\nexport interface DropdownMenuItemProps {\r\n disabled?: boolean\r\n /** Styles the item as a destructive action. */\r\n destructive?: boolean\r\n /** A keyboard shortcut to display, e.g. `⌘D`. Display only. */\r\n shortcut?: string\r\n /** Render as another element, e.g. `a` or `NuxtLink` for navigation items. */\r\n as?: string | Component\r\n /**\r\n * Makes the item one choice in a set (e.g. a theme picker). `true` shows a\r\n * check at the end of the row. Leave undefined for a plain action.\r\n */\r\n checked?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<DropdownMenuItemProps>(), { as: 'div', checked: undefined })\r\n\r\nconst emit = defineEmits<{\r\n /** Fired when the item is chosen. Call `event.preventDefault()` to keep the menu open. */\r\n select: [event: Event]\r\n}>()\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n\r\nconst root = inject(MENU_ROOT)!\r\nconst level = inject(MENU_LEVEL)!\r\nconst el = ref<HTMLElement | null>(null)\r\n\r\nfunction onClick(event: MouseEvent) {\r\n if (props.disabled) {\r\n event.preventDefault()\r\n return\r\n }\r\n const selectEvent = new Event('select', { cancelable: true })\r\n emit('select', selectEvent)\r\n if (!selectEvent.defaultPrevented) root.close(true)\r\n}\r\n\r\nfunction resolve(target: unknown): HTMLElement | null {\r\n const value = target as { $el?: HTMLElement } | HTMLElement | null\r\n return value instanceof HTMLElement ? value : (value?.$el ?? null)\r\n}\r\n\r\n// Hover moves real focus, so pointer and keyboard always agree on one item.\r\nfunction onPointerMove() {\r\n level.scheduleSubClose()\r\n const node = resolve(el.value)\r\n if (props.disabled || !node || document.activeElement === node) return\r\n focusElement(node)\r\n}\r\n\r\nfunction onPointerLeave() {\r\n const node = resolve(el.value)\r\n if (node && document.activeElement === node) focusElement(level.content.value)\r\n}\r\n</script>\r\n\r\n<template>\r\n <component\r\n :is=\"as\"\r\n ref=\"el\"\r\n :role=\"checked === undefined ? 'menuitem' : 'menuitemradio'\"\r\n :aria-checked=\"checked === undefined ? undefined : checked\"\r\n tabindex=\"-1\"\r\n class=\"j-menu__item\"\r\n :class=\"{ 'j-menu__item--destructive': destructive }\"\r\n :aria-disabled=\"disabled || undefined\"\r\n @click=\"onClick\"\r\n @pointermove=\"onPointerMove\"\r\n @pointerleave=\"onPointerLeave\"\r\n >\r\n <span v-if=\"$slots.leading\" class=\"j-menu__icon\"><slot name=\"leading\" /></span>\r\n <span class=\"j-menu__text\"><slot /></span>\r\n <span v-if=\"shortcut\" class=\"j-menu__shortcut\">{{ shortcut }}</span>\r\n <IconCheck v-if=\"checked !== undefined\" class=\"j-menu__check\" :class=\"{ 'is-checked': checked }\" />\r\n </component>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\nimport { inject, ref } from 'vue'\r\nimport { IconCheck } from '../../icons'\r\nimport { focusElement } from '../../utils/dom'\r\nimport { MENU_LEVEL, MENU_ROOT } from './context'\r\n\r\nexport interface DropdownMenuItemProps {\r\n disabled?: boolean\r\n /** Styles the item as a destructive action. */\r\n destructive?: boolean\r\n /** A keyboard shortcut to display, e.g. `⌘D`. Display only. */\r\n shortcut?: string\r\n /** Render as another element, e.g. `a` or `NuxtLink` for navigation items. */\r\n as?: string | Component\r\n /**\r\n * Makes the item one choice in a set (e.g. a theme picker). `true` shows a\r\n * check at the end of the row. Leave undefined for a plain action.\r\n */\r\n checked?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<DropdownMenuItemProps>(), { as: 'div', checked: undefined })\r\n\r\nconst emit = defineEmits<{\r\n /** Fired when the item is chosen. Call `event.preventDefault()` to keep the menu open. */\r\n select: [event: Event]\r\n}>()\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n\r\nconst root = inject(MENU_ROOT)!\r\nconst level = inject(MENU_LEVEL)!\r\nconst el = ref<HTMLElement | null>(null)\r\n\r\nfunction onClick(event: MouseEvent) {\r\n if (props.disabled) {\r\n event.preventDefault()\r\n return\r\n }\r\n const selectEvent = new Event('select', { cancelable: true })\r\n emit('select', selectEvent)\r\n if (!selectEvent.defaultPrevented) root.close(true)\r\n}\r\n\r\nfunction resolve(target: unknown): HTMLElement | null {\r\n const value = target as { $el?: HTMLElement } | HTMLElement | null\r\n return value instanceof HTMLElement ? value : (value?.$el ?? null)\r\n}\r\n\r\n// Hover moves real focus, so pointer and keyboard always agree on one item.\r\nfunction onPointerMove() {\r\n level.scheduleSubClose()\r\n const node = resolve(el.value)\r\n if (props.disabled || !node || document.activeElement === node) return\r\n focusElement(node)\r\n}\r\n\r\nfunction onPointerLeave() {\r\n const node = resolve(el.value)\r\n if (node && document.activeElement === node) focusElement(level.content.value)\r\n}\r\n</script>\r\n\r\n<template>\r\n <component\r\n :is=\"as\"\r\n ref=\"el\"\r\n :role=\"checked === undefined ? 'menuitem' : 'menuitemradio'\"\r\n :aria-checked=\"checked === undefined ? undefined : checked\"\r\n tabindex=\"-1\"\r\n class=\"j-menu__item\"\r\n :class=\"{ 'j-menu__item--destructive': destructive }\"\r\n :aria-disabled=\"disabled || undefined\"\r\n @click=\"onClick\"\r\n @pointermove=\"onPointerMove\"\r\n @pointerleave=\"onPointerLeave\"\r\n >\r\n <span v-if=\"$slots.leading\" class=\"j-menu__icon\"><slot name=\"leading\" /></span>\r\n <span class=\"j-menu__text\"><slot /></span>\r\n <span v-if=\"shortcut\" class=\"j-menu__shortcut\">{{ shortcut }}</span>\r\n <IconCheck v-if=\"checked !== undefined\" class=\"j-menu__check\" :class=\"{ 'is-checked': checked }\" />\r\n </component>\r\n</template>\r\n","<script setup lang=\"ts\">\r\ndefineSlots<{ default?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-menu__label\" role=\"presentation\">\r\n <slot />\r\n </div>\r\n</template>\r\n","<script setup lang=\"ts\">\r\ndefineSlots<{ default?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-menu__label\" role=\"presentation\">\r\n <slot />\r\n </div>\r\n</template>\r\n","<template>\r\n <div role=\"separator\" aria-orientation=\"horizontal\" class=\"j-menu__separator\" />\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport { computed, inject, nextTick, ref, useId } from 'vue'\r\nimport { IconChevronRight } from '../../icons'\r\nimport { focusElement } from '../../utils/dom'\r\nimport { getMenuItems, MENU_LEVEL, MENU_ROOT } from './context'\r\nimport JMenuContent from './JMenuContent.vue'\r\n\r\nexport interface DropdownMenuSubProps {\r\n label: string\r\n disabled?: boolean\r\n}\r\n\r\nconst props = defineProps<DropdownMenuSubProps>()\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n\r\nconst root = inject(MENU_ROOT)!\r\nconst level = inject(MENU_LEVEL)!\r\n\r\nconst id = useId()\r\nconst itemId = `j-${id}-subtrigger`\r\nconst menuId = `j-${id}-submenu`\r\nconst item = ref<HTMLElement | null>(null)\r\nconst sub = ref<InstanceType<typeof JMenuContent> | null>(null)\r\nconst initialFocus = ref<'first' | 'none'>('none')\r\nconst isOpen = computed(() => level.openSub.value === menuId)\r\n\r\nlet openTimer: ReturnType<typeof setTimeout> | undefined\r\n\r\nfunction openSub(focus: 'first' | 'none') {\r\n if (props.disabled) return\r\n initialFocus.value = focus\r\n level.setOpenSub(menuId)\r\n}\r\n\r\nfunction closeSub(returnFocus: boolean) {\r\n if (level.openSub.value === menuId) level.setOpenSub(null)\r\n if (returnFocus) nextTick(() => focusElement(item.value))\r\n}\r\n\r\nfunction onPointerMove() {\r\n level.cancelSubClose()\r\n if (props.disabled) return\r\n if (document.activeElement !== item.value) focusElement(item.value)\r\n if (isOpen.value) return\r\n clearTimeout(openTimer)\r\n openTimer = setTimeout(() => openSub('none'), 90)\r\n}\r\n\r\nfunction onPointerLeave() {\r\n clearTimeout(openTimer)\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key === 'ArrowRight' || event.key === 'Enter' || event.key === ' ') {\r\n event.preventDefault()\r\n event.stopPropagation()\r\n if (isOpen.value) focusElement(getMenuItems(sub.value?.content ?? null)[0])\r\n else openSub('first')\r\n }\r\n}\r\n\r\nfunction onClick() {\r\n openSub('first')\r\n}\r\n\r\n// A press outside the submenu either lands back in the parent (close just the\r\n// submenu) or somewhere else entirely (close the whole menu).\r\nfunction onOutside(event: PointerEvent) {\r\n const target = event.target as Node\r\n if (level.content.value?.contains(target)) closeSub(false)\r\n else root.close(false)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div\r\n :id=\"itemId\"\r\n ref=\"item\"\r\n role=\"menuitem\"\r\n tabindex=\"-1\"\r\n class=\"j-menu__item\"\r\n aria-haspopup=\"menu\"\r\n :aria-expanded=\"isOpen\"\r\n :aria-controls=\"isOpen ? menuId : undefined\"\r\n :aria-disabled=\"disabled || undefined\"\r\n @pointermove=\"onPointerMove\"\r\n @pointerleave=\"onPointerLeave\"\r\n @keydown=\"onKeydown\"\r\n @click=\"onClick\"\r\n >\r\n <span v-if=\"$slots.leading\" class=\"j-menu__icon\"><slot name=\"leading\" /></span>\r\n <span class=\"j-menu__text\">{{ label }}</span>\r\n <IconChevronRight class=\"j-menu__chevron\" />\r\n </div>\r\n <JMenuContent\r\n :id=\"menuId\"\r\n ref=\"sub\"\r\n :open=\"isOpen\"\r\n :reference=\"item\"\r\n :labelledby=\"itemId\"\r\n placement=\"right-start\"\r\n :offset=\"{ mainAxis: 2, crossAxis: -5 }\"\r\n :initial-focus=\"initialFocus\"\r\n is-sub\r\n @escape=\"closeSub(true)\"\r\n @back=\"closeSub(true)\"\r\n @outside=\"onOutside\"\r\n @pointerenter=\"level.cancelSubClose()\"\r\n >\r\n <slot />\r\n </JMenuContent>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport { computed, inject, nextTick, ref, useId } from 'vue'\r\nimport { IconChevronRight } from '../../icons'\r\nimport { focusElement } from '../../utils/dom'\r\nimport { getMenuItems, MENU_LEVEL, MENU_ROOT } from './context'\r\nimport JMenuContent from './JMenuContent.vue'\r\n\r\nexport interface DropdownMenuSubProps {\r\n label: string\r\n disabled?: boolean\r\n}\r\n\r\nconst props = defineProps<DropdownMenuSubProps>()\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n\r\nconst root = inject(MENU_ROOT)!\r\nconst level = inject(MENU_LEVEL)!\r\n\r\nconst id = useId()\r\nconst itemId = `j-${id}-subtrigger`\r\nconst menuId = `j-${id}-submenu`\r\nconst item = ref<HTMLElement | null>(null)\r\nconst sub = ref<InstanceType<typeof JMenuContent> | null>(null)\r\nconst initialFocus = ref<'first' | 'none'>('none')\r\nconst isOpen = computed(() => level.openSub.value === menuId)\r\n\r\nlet openTimer: ReturnType<typeof setTimeout> | undefined\r\n\r\nfunction openSub(focus: 'first' | 'none') {\r\n if (props.disabled) return\r\n initialFocus.value = focus\r\n level.setOpenSub(menuId)\r\n}\r\n\r\nfunction closeSub(returnFocus: boolean) {\r\n if (level.openSub.value === menuId) level.setOpenSub(null)\r\n if (returnFocus) nextTick(() => focusElement(item.value))\r\n}\r\n\r\nfunction onPointerMove() {\r\n level.cancelSubClose()\r\n if (props.disabled) return\r\n if (document.activeElement !== item.value) focusElement(item.value)\r\n if (isOpen.value) return\r\n clearTimeout(openTimer)\r\n openTimer = setTimeout(() => openSub('none'), 90)\r\n}\r\n\r\nfunction onPointerLeave() {\r\n clearTimeout(openTimer)\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key === 'ArrowRight' || event.key === 'Enter' || event.key === ' ') {\r\n event.preventDefault()\r\n event.stopPropagation()\r\n if (isOpen.value) focusElement(getMenuItems(sub.value?.content ?? null)[0])\r\n else openSub('first')\r\n }\r\n}\r\n\r\nfunction onClick() {\r\n openSub('first')\r\n}\r\n\r\n// A press outside the submenu either lands back in the parent (close just the\r\n// submenu) or somewhere else entirely (close the whole menu).\r\nfunction onOutside(event: PointerEvent) {\r\n const target = event.target as Node\r\n if (level.content.value?.contains(target)) closeSub(false)\r\n else root.close(false)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div\r\n :id=\"itemId\"\r\n ref=\"item\"\r\n role=\"menuitem\"\r\n tabindex=\"-1\"\r\n class=\"j-menu__item\"\r\n aria-haspopup=\"menu\"\r\n :aria-expanded=\"isOpen\"\r\n :aria-controls=\"isOpen ? menuId : undefined\"\r\n :aria-disabled=\"disabled || undefined\"\r\n @pointermove=\"onPointerMove\"\r\n @pointerleave=\"onPointerLeave\"\r\n @keydown=\"onKeydown\"\r\n @click=\"onClick\"\r\n >\r\n <span v-if=\"$slots.leading\" class=\"j-menu__icon\"><slot name=\"leading\" /></span>\r\n <span class=\"j-menu__text\">{{ label }}</span>\r\n <IconChevronRight class=\"j-menu__chevron\" />\r\n </div>\r\n <JMenuContent\r\n :id=\"menuId\"\r\n ref=\"sub\"\r\n :open=\"isOpen\"\r\n :reference=\"item\"\r\n :labelledby=\"itemId\"\r\n placement=\"right-start\"\r\n :offset=\"{ mainAxis: 2, crossAxis: -5 }\"\r\n :initial-focus=\"initialFocus\"\r\n is-sub\r\n @escape=\"closeSub(true)\"\r\n @back=\"closeSub(true)\"\r\n @outside=\"onOutside\"\r\n @pointerenter=\"level.cancelSubClose()\"\r\n >\r\n <slot />\r\n </JMenuContent>\r\n</template>\r\n","<script setup lang=\"ts\">\r\n/**\r\n * Internal: lays out label, control, description and error for form fields.\r\n * Not exported — every field component composes it.\r\n */\r\ndefineProps<{\r\n id: string\r\n labelId: string\r\n descriptionId: string\r\n errorId: string\r\n label?: string\r\n description?: string\r\n errorMessage?: string\r\n showDescription?: boolean\r\n required?: boolean\r\n disabled?: boolean\r\n /** Render the label as a plain element when the control isn't labelable (e.g. a button). */\r\n labelFor?: string | null\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-field\" :class=\"{ 'is-disabled': disabled }\">\r\n <component\r\n :is=\"labelFor === null ? 'span' : 'label'\"\r\n v-if=\"label || $slots.label\"\r\n :id=\"labelId\"\r\n class=\"j-field__label\"\r\n :for=\"labelFor === null ? undefined : labelFor ?? id\"\r\n >\r\n <slot name=\"label\">{{ label }}</slot>\r\n <span v-if=\"required\" class=\"j-field__required\" aria-hidden=\"true\">*</span>\r\n </component>\r\n <slot />\r\n <div class=\"j-field__messages\">\r\n <Transition name=\"j-field-message\" mode=\"out-in\">\r\n <p v-if=\"errorMessage\" :id=\"errorId\" :key=\"errorMessage\" class=\"j-field__error\">\r\n {{ errorMessage }}\r\n </p>\r\n <p v-else-if=\"showDescription\" :id=\"descriptionId\" class=\"j-field__description\">\r\n {{ description }}\r\n </p>\r\n </Transition>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-field {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n min-width: 0;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-field__label {\r\n display: inline-flex;\r\n align-items: baseline;\r\n gap: var(--juxt-space-0-5);\r\n width: fit-content;\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-field.is-disabled .j-field__label {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-field__required {\r\n color: var(--juxt-fg-muted);\r\n font-weight: var(--juxt-weight-regular);\r\n}\r\n\r\n.j-field__messages:empty {\r\n display: none;\r\n}\r\n\r\n.j-field__description,\r\n.j-field__error {\r\n margin: 0;\r\n font-size: var(--juxt-text-xs);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-field__description {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-field__error {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-field-message-enter-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-field-message-leave-active {\r\n transition: opacity var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-field-message-enter-from {\r\n opacity: 0;\r\n transform: translateY(calc(var(--juxt-motion-shift) * -0.5));\r\n}\r\n\r\n.j-field-message-leave-to {\r\n opacity: 0;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\n/**\r\n * Internal: lays out label, control, description and error for form fields.\r\n * Not exported — every field component composes it.\r\n */\r\ndefineProps<{\r\n id: string\r\n labelId: string\r\n descriptionId: string\r\n errorId: string\r\n label?: string\r\n description?: string\r\n errorMessage?: string\r\n showDescription?: boolean\r\n required?: boolean\r\n disabled?: boolean\r\n /** Render the label as a plain element when the control isn't labelable (e.g. a button). */\r\n labelFor?: string | null\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-field\" :class=\"{ 'is-disabled': disabled }\">\r\n <component\r\n :is=\"labelFor === null ? 'span' : 'label'\"\r\n v-if=\"label || $slots.label\"\r\n :id=\"labelId\"\r\n class=\"j-field__label\"\r\n :for=\"labelFor === null ? undefined : labelFor ?? id\"\r\n >\r\n <slot name=\"label\">{{ label }}</slot>\r\n <span v-if=\"required\" class=\"j-field__required\" aria-hidden=\"true\">*</span>\r\n </component>\r\n <slot />\r\n <div class=\"j-field__messages\">\r\n <Transition name=\"j-field-message\" mode=\"out-in\">\r\n <p v-if=\"errorMessage\" :id=\"errorId\" :key=\"errorMessage\" class=\"j-field__error\">\r\n {{ errorMessage }}\r\n </p>\r\n <p v-else-if=\"showDescription\" :id=\"descriptionId\" class=\"j-field__description\">\r\n {{ description }}\r\n </p>\r\n </Transition>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-field {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n min-width: 0;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-field__label {\r\n display: inline-flex;\r\n align-items: baseline;\r\n gap: var(--juxt-space-0-5);\r\n width: fit-content;\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-field.is-disabled .j-field__label {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-field__required {\r\n color: var(--juxt-fg-muted);\r\n font-weight: var(--juxt-weight-regular);\r\n}\r\n\r\n.j-field__messages:empty {\r\n display: none;\r\n}\r\n\r\n.j-field__description,\r\n.j-field__error {\r\n margin: 0;\r\n font-size: var(--juxt-text-xs);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-field__description {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-field__error {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-field-message-enter-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-field-message-leave-active {\r\n transition: opacity var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-field-message-enter-from {\r\n opacity: 0;\r\n transform: translateY(calc(var(--juxt-motion-shift) * -0.5));\r\n}\r\n\r\n.j-field-message-leave-to {\r\n opacity: 0;\r\n}\r\n}\r\n</style>\r\n","import type { MaybeRefOrGetter } from 'vue'\r\nimport { computed, toValue, useId } from 'vue'\r\n\r\nexport interface FieldProps {\r\n /** Visible label. Also used as the accessible name. */\r\n label?: string\r\n /** Supporting text under the control. Hidden while an error message is shown. */\r\n description?: string\r\n /** An error message, or `true` to mark the field invalid without a message. */\r\n error?: string | boolean\r\n required?: boolean\r\n disabled?: boolean\r\n /** Id for the control. Generated when omitted. */\r\n id?: string\r\n}\r\n\r\n/** Ids and ARIA wiring shared by every form control. */\r\nexport function useField(props: MaybeRefOrGetter<FieldProps>) {\r\n const generated = useId()\r\n const p = () => toValue(props)\r\n\r\n const id = computed(() => p().id ?? `j-${generated}`)\r\n const labelId = computed(() => `${id.value}-label`)\r\n const descriptionId = computed(() => `${id.value}-description`)\r\n const errorId = computed(() => `${id.value}-error`)\r\n\r\n const invalid = computed(() => !!p().error)\r\n const errorMessage = computed(() => (typeof p().error === 'string' ? (p().error as string) : ''))\r\n const showDescription = computed(() => !!p().description && !errorMessage.value)\r\n\r\n const describedBy = computed(() => {\r\n const ids: string[] = []\r\n if (errorMessage.value) ids.push(errorId.value)\r\n else if (p().description) ids.push(descriptionId.value)\r\n return ids.length ? ids.join(' ') : undefined\r\n })\r\n\r\n return { id, labelId, descriptionId, errorId, invalid, errorMessage, showDescription, describedBy }\r\n}\r\n","<script setup lang=\"ts\">\r\nimport type { FieldProps } from '../field/useField'\r\nimport { ref } from 'vue'\r\nimport JFieldShell from '../field/JFieldShell.vue'\r\nimport { useField } from '../field/useField'\r\n\r\nexport type InputSize = 'sm' | 'md' | 'lg'\r\n\r\nexport interface InputProps extends FieldProps {\r\n type?: string\r\n placeholder?: string\r\n size?: InputSize\r\n readonly?: boolean\r\n name?: string\r\n autocomplete?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<InputProps>(), {\r\n type: 'text',\r\n size: 'md',\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<string | number | null>({ default: '' })\r\n\r\ndefineSlots<{\r\n leading?: () => unknown\r\n trailing?: () => unknown\r\n label?: () => unknown\r\n}>()\r\n\r\nconst field = useField(props)\r\nconst input = ref<HTMLInputElement | null>(null)\r\n\r\n// Clicking the frame (padding, icons) focuses the input without a blur flicker.\r\nfunction onFramePointerDown(event: PointerEvent) {\r\n if (event.target === input.value || props.disabled) return\r\n if ((event.target as HTMLElement).closest('button, a, input, select, textarea, [tabindex]')) return\r\n event.preventDefault()\r\n input.value?.focus()\r\n}\r\n\r\ndefineExpose({\r\n /** The underlying `<input>` element. */\r\n input,\r\n focus: () => input.value?.focus(),\r\n select: () => input.value?.select(),\r\n})\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n <div\r\n class=\"j-control j-input\"\r\n :class=\"[`j-control--${size}`, { 'is-invalid': field.invalid.value, 'is-disabled': disabled, 'is-readonly': readonly }]\"\r\n @pointerdown=\"onFramePointerDown\"\r\n >\r\n <span v-if=\"$slots.leading\" class=\"j-control__adornment j-control__adornment--leading\">\r\n <slot name=\"leading\" />\r\n </span>\r\n <input\r\n :id=\"field.id.value\"\r\n ref=\"input\"\r\n v-model=\"model\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n class=\"j-input__native\"\r\n :type=\"type\"\r\n :name=\"name\"\r\n :placeholder=\"placeholder\"\r\n :disabled=\"disabled\"\r\n :readonly=\"readonly\"\r\n :required=\"required\"\r\n :autocomplete=\"autocomplete\"\r\n :aria-invalid=\"field.invalid.value || undefined\"\r\n :aria-describedby=\"field.describedBy.value\"\r\n />\r\n <span v-if=\"$slots.trailing\" class=\"j-control__adornment j-control__adornment--trailing\">\r\n <slot name=\"trailing\" />\r\n </span>\r\n </div>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-input__native {\r\n flex: 1;\r\n width: 100%;\r\n min-width: 0;\r\n height: calc(var(--j-control-height) - 2px);\r\n padding: 0;\r\n border: 0;\r\n outline: 0;\r\n background: transparent;\r\n color: inherit;\r\n font: inherit;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-input__native::placeholder {\r\n color: var(--juxt-fg-muted);\r\n opacity: 1;\r\n}\r\n\r\n.j-input__native:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-input__native::-webkit-search-cancel-button {\r\n display: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { FieldProps } from '../field/useField'\r\nimport { ref } from 'vue'\r\nimport JFieldShell from '../field/JFieldShell.vue'\r\nimport { useField } from '../field/useField'\r\n\r\nexport type InputSize = 'sm' | 'md' | 'lg'\r\n\r\nexport interface InputProps extends FieldProps {\r\n type?: string\r\n placeholder?: string\r\n size?: InputSize\r\n readonly?: boolean\r\n name?: string\r\n autocomplete?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<InputProps>(), {\r\n type: 'text',\r\n size: 'md',\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<string | number | null>({ default: '' })\r\n\r\ndefineSlots<{\r\n leading?: () => unknown\r\n trailing?: () => unknown\r\n label?: () => unknown\r\n}>()\r\n\r\nconst field = useField(props)\r\nconst input = ref<HTMLInputElement | null>(null)\r\n\r\n// Clicking the frame (padding, icons) focuses the input without a blur flicker.\r\nfunction onFramePointerDown(event: PointerEvent) {\r\n if (event.target === input.value || props.disabled) return\r\n if ((event.target as HTMLElement).closest('button, a, input, select, textarea, [tabindex]')) return\r\n event.preventDefault()\r\n input.value?.focus()\r\n}\r\n\r\ndefineExpose({\r\n /** The underlying `<input>` element. */\r\n input,\r\n focus: () => input.value?.focus(),\r\n select: () => input.value?.select(),\r\n})\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n <div\r\n class=\"j-control j-input\"\r\n :class=\"[`j-control--${size}`, { 'is-invalid': field.invalid.value, 'is-disabled': disabled, 'is-readonly': readonly }]\"\r\n @pointerdown=\"onFramePointerDown\"\r\n >\r\n <span v-if=\"$slots.leading\" class=\"j-control__adornment j-control__adornment--leading\">\r\n <slot name=\"leading\" />\r\n </span>\r\n <input\r\n :id=\"field.id.value\"\r\n ref=\"input\"\r\n v-model=\"model\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n class=\"j-input__native\"\r\n :type=\"type\"\r\n :name=\"name\"\r\n :placeholder=\"placeholder\"\r\n :disabled=\"disabled\"\r\n :readonly=\"readonly\"\r\n :required=\"required\"\r\n :autocomplete=\"autocomplete\"\r\n :aria-invalid=\"field.invalid.value || undefined\"\r\n :aria-describedby=\"field.describedBy.value\"\r\n />\r\n <span v-if=\"$slots.trailing\" class=\"j-control__adornment j-control__adornment--trailing\">\r\n <slot name=\"trailing\" />\r\n </span>\r\n </div>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-input__native {\r\n flex: 1;\r\n width: 100%;\r\n min-width: 0;\r\n height: calc(var(--j-control-height) - 2px);\r\n padding: 0;\r\n border: 0;\r\n outline: 0;\r\n background: transparent;\r\n color: inherit;\r\n font: inherit;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-input__native::placeholder {\r\n color: var(--juxt-fg-muted);\r\n opacity: 1;\r\n}\r\n\r\n.j-input__native:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-input__native::-webkit-search-cancel-button {\r\n display: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\nimport { formatHotkey } from '../../composables/useHotkey'\n\nexport interface KbdProps {\n /**\n * A shortcut in hotkey syntax, e.g. `mod+k` or `shift+enter`. `mod` renders\n * as ⌘ on Apple platforms and Ctrl elsewhere. Use the slot for literal keys.\n */\n keys?: string\n size?: 'sm' | 'md'\n}\n\nconst props = withDefaults(defineProps<KbdProps>(), { size: 'md' })\n\ndefineSlots<{ default?: () => unknown }>()\n\n// Platform glyphs are only known in the browser; resolve after mount so\n// server and client render the same markup.\nconst mounted = ref(false)\nonMounted(() => (mounted.value = true))\n\nconst parts = computed(() => {\n if (!props.keys) return []\n if (!mounted.value) return props.keys.split('+').map(part => (part === 'mod' ? 'Ctrl' : part.length === 1 ? part.toUpperCase() : part))\n return formatHotkey(props.keys)\n})\n</script>\n\n<template>\n <span v-if=\"keys\" class=\"j-kbd-group\" :class=\"`j-kbd-group--${size}`\">\n <kbd v-for=\"(part, i) in parts\" :key=\"i\" class=\"j-kbd\">{{ part }}</kbd>\n </span>\n <kbd v-else class=\"j-kbd\" :class=\"{ 'j-kbd--sm': size === 'sm' }\"><slot /></kbd>\n</template>\n\n<style>\n@layer juxt {\n.j-kbd-group {\n display: inline-flex;\n gap: 3px;\n vertical-align: middle;\n}\n\n.j-kbd-group--sm .j-kbd,\n.j-kbd--sm {\n min-width: 1.0625rem;\n height: 1.0625rem;\n font-size: 0.625rem;\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\nimport { formatHotkey } from '../../composables/useHotkey'\n\nexport interface KbdProps {\n /**\n * A shortcut in hotkey syntax, e.g. `mod+k` or `shift+enter`. `mod` renders\n * as ⌘ on Apple platforms and Ctrl elsewhere. Use the slot for literal keys.\n */\n keys?: string\n size?: 'sm' | 'md'\n}\n\nconst props = withDefaults(defineProps<KbdProps>(), { size: 'md' })\n\ndefineSlots<{ default?: () => unknown }>()\n\n// Platform glyphs are only known in the browser; resolve after mount so\n// server and client render the same markup.\nconst mounted = ref(false)\nonMounted(() => (mounted.value = true))\n\nconst parts = computed(() => {\n if (!props.keys) return []\n if (!mounted.value) return props.keys.split('+').map(part => (part === 'mod' ? 'Ctrl' : part.length === 1 ? part.toUpperCase() : part))\n return formatHotkey(props.keys)\n})\n</script>\n\n<template>\n <span v-if=\"keys\" class=\"j-kbd-group\" :class=\"`j-kbd-group--${size}`\">\n <kbd v-for=\"(part, i) in parts\" :key=\"i\" class=\"j-kbd\">{{ part }}</kbd>\n </span>\n <kbd v-else class=\"j-kbd\" :class=\"{ 'j-kbd--sm': size === 'sm' }\"><slot /></kbd>\n</template>\n\n<style>\n@layer juxt {\n.j-kbd-group {\n display: inline-flex;\n gap: 3px;\n vertical-align: middle;\n}\n\n.j-kbd-group--sm .j-kbd,\n.j-kbd--sm {\n min-width: 1.0625rem;\n height: 1.0625rem;\n font-size: 0.625rem;\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport type { Placement } from '../../composables/useFloating'\nimport { computed, nextTick, ref, useId, watch } from 'vue'\nimport { useFloating } from '../../composables/useFloating'\nimport { useLayer } from '../../composables/useLayer'\nimport { focusElement, getFocusableElements, unrefElement } from '../../utils/dom'\nimport JPortal from '../primitives/JPortal.vue'\nimport { JSlot } from '../primitives/JSlot'\n\nexport interface PopoverProps {\n placement?: Placement\n /** Distance from the trigger in px. */\n offset?: number\n /** Accessible name of the popover content. */\n ariaLabel?: string\n /** Close when focus or a click leaves the popover. Defaults to true. */\n closeOnOutside?: boolean\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<PopoverProps>(), {\n placement: 'bottom',\n offset: 8,\n closeOnOutside: true,\n})\n\nconst open = defineModel<boolean>('open', { default: false })\n\ndefineSlots<{\n trigger?: (props: { open: boolean }) => unknown\n default?: (props: { close: () => void }) => unknown\n}>()\n\nconst id = `j-${useId()}-popover`\nconst triggerRef = ref()\nconst triggerEl = computed(() => unrefElement(triggerRef.value))\nconst content = ref<HTMLElement | null>(null)\n\nconst { styles } = useFloating(triggerEl, content, () => ({ placement: props.placement, offset: props.offset }))\n\nfunction close(restoreFocus = false) {\n if (!open.value) return\n open.value = false\n if (restoreFocus) nextTick(() => focusElement(triggerEl.value))\n}\n\nuseLayer({\n active: open,\n elements: () => [triggerEl.value, content.value],\n onEscape: () => close(true),\n onPointerDownOutside: () => props.closeOnOutside && close(false),\n})\n\n// Moving focus in makes the content reachable without a trap: this is a\n// non-modal surface, so Tab is allowed to leave (and that closes it).\nwatch(open, async (value) => {\n if (!value) return\n await nextTick()\n const el = content.value\n if (!el) return\n focusElement(el.querySelector<HTMLElement>('[autofocus], [data-autofocus]') ?? getFocusableElements(el)[0] ?? el)\n}, { flush: 'post' })\n\nfunction onFocusOut(event: FocusEvent) {\n if (!props.closeOnOutside) return\n const next = event.relatedTarget as Node | null\n if (!next) return\n if (content.value?.contains(next) || triggerEl.value?.contains(next)) return\n close(false)\n}\n\nfunction toggle() {\n if (props.disabled) return\n open.value = !open.value\n}\n\nconst triggerProps = computed(() => ({\n 'aria-haspopup': 'dialog',\n 'aria-expanded': open.value,\n 'aria-controls': open.value ? id : undefined,\n 'data-state': open.value ? 'open' : 'closed',\n 'onClick': toggle,\n}))\n\ndefineExpose({ close: () => close(true) })\n</script>\n\n<template>\n <JSlot ref=\"triggerRef\" v-bind=\"triggerProps\">\n <slot name=\"trigger\" :open=\"open\" />\n </JSlot>\n <JPortal>\n <Transition name=\"j-pop\">\n <div\n v-if=\"open\"\n :id=\"id\"\n ref=\"content\"\n role=\"dialog\"\n tabindex=\"-1\"\n class=\"j-popover j-popover-surface\"\n :style=\"styles\"\n :aria-label=\"ariaLabel\"\n @focusout=\"onFocusOut\"\n >\n <slot :close=\"() => close(true)\" />\n </div>\n </Transition>\n </JPortal>\n</template>\n\n<style>\n@layer juxt {\n.j-popover {\n z-index: var(--juxt-z-popover);\n box-sizing: border-box;\n max-width: calc(100vw - 16px);\n max-height: var(--j-available-height, 32rem);\n padding: var(--juxt-space-4);\n overflow: auto;\n outline: none;\n font-size: var(--juxt-text-md);\n line-height: var(--juxt-leading-normal);\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport type { Placement } from '../../composables/useFloating'\nimport { computed, nextTick, ref, useId, watch } from 'vue'\nimport { useFloating } from '../../composables/useFloating'\nimport { useLayer } from '../../composables/useLayer'\nimport { focusElement, getFocusableElements, unrefElement } from '../../utils/dom'\nimport JPortal from '../primitives/JPortal.vue'\nimport { JSlot } from '../primitives/JSlot'\n\nexport interface PopoverProps {\n placement?: Placement\n /** Distance from the trigger in px. */\n offset?: number\n /** Accessible name of the popover content. */\n ariaLabel?: string\n /** Close when focus or a click leaves the popover. Defaults to true. */\n closeOnOutside?: boolean\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<PopoverProps>(), {\n placement: 'bottom',\n offset: 8,\n closeOnOutside: true,\n})\n\nconst open = defineModel<boolean>('open', { default: false })\n\ndefineSlots<{\n trigger?: (props: { open: boolean }) => unknown\n default?: (props: { close: () => void }) => unknown\n}>()\n\nconst id = `j-${useId()}-popover`\nconst triggerRef = ref()\nconst triggerEl = computed(() => unrefElement(triggerRef.value))\nconst content = ref<HTMLElement | null>(null)\n\nconst { styles } = useFloating(triggerEl, content, () => ({ placement: props.placement, offset: props.offset }))\n\nfunction close(restoreFocus = false) {\n if (!open.value) return\n open.value = false\n if (restoreFocus) nextTick(() => focusElement(triggerEl.value))\n}\n\nuseLayer({\n active: open,\n elements: () => [triggerEl.value, content.value],\n onEscape: () => close(true),\n onPointerDownOutside: () => props.closeOnOutside && close(false),\n})\n\n// Moving focus in makes the content reachable without a trap: this is a\n// non-modal surface, so Tab is allowed to leave (and that closes it).\nwatch(open, async (value) => {\n if (!value) return\n await nextTick()\n const el = content.value\n if (!el) return\n focusElement(el.querySelector<HTMLElement>('[autofocus], [data-autofocus]') ?? getFocusableElements(el)[0] ?? el)\n}, { flush: 'post' })\n\nfunction onFocusOut(event: FocusEvent) {\n if (!props.closeOnOutside) return\n const next = event.relatedTarget as Node | null\n if (!next) return\n if (content.value?.contains(next) || triggerEl.value?.contains(next)) return\n close(false)\n}\n\nfunction toggle() {\n if (props.disabled) return\n open.value = !open.value\n}\n\nconst triggerProps = computed(() => ({\n 'aria-haspopup': 'dialog',\n 'aria-expanded': open.value,\n 'aria-controls': open.value ? id : undefined,\n 'data-state': open.value ? 'open' : 'closed',\n 'onClick': toggle,\n}))\n\ndefineExpose({ close: () => close(true) })\n</script>\n\n<template>\n <JSlot ref=\"triggerRef\" v-bind=\"triggerProps\">\n <slot name=\"trigger\" :open=\"open\" />\n </JSlot>\n <JPortal>\n <Transition name=\"j-pop\">\n <div\n v-if=\"open\"\n :id=\"id\"\n ref=\"content\"\n role=\"dialog\"\n tabindex=\"-1\"\n class=\"j-popover j-popover-surface\"\n :style=\"styles\"\n :aria-label=\"ariaLabel\"\n @focusout=\"onFocusOut\"\n >\n <slot :close=\"() => close(true)\" />\n </div>\n </Transition>\n </JPortal>\n</template>\n\n<style>\n@layer juxt {\n.j-popover {\n z-index: var(--juxt-z-popover);\n box-sizing: border-box;\n max-width: calc(100vw - 16px);\n max-height: var(--j-available-height, 32rem);\n padding: var(--juxt-space-4);\n overflow: auto;\n outline: none;\n font-size: var(--juxt-text-md);\n line-height: var(--juxt-leading-normal);\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\nexport interface ProgressProps {\n /** Current value. `null` shows an indeterminate bar. */\n value?: number | null\n max?: number\n size?: 'sm' | 'md'\n /** Visible label above the bar; also its accessible name. */\n label?: string\n /** Show the percentage next to the label. */\n showValue?: boolean\n /** `accent` for progress toward a goal, `neutral` for plain measurement. */\n variant?: 'accent' | 'neutral'\n}\n\nconst props = withDefaults(defineProps<ProgressProps>(), {\n value: null,\n max: 100,\n size: 'md',\n variant: 'accent',\n})\n\nconst id = `j-${useId()}`\nconst indeterminate = computed(() => props.value === null || props.value === undefined)\nconst ratio = computed(() => (indeterminate.value ? 0 : Math.min(1, Math.max(0, Number(props.value) / props.max))))\nconst percent = computed(() => Math.round(ratio.value * 100))\n</script>\n\n<template>\n <div class=\"j-progress\" :class=\"[`j-progress--${size}`, `j-progress--${variant}`, { 'is-indeterminate': indeterminate }]\">\n <div v-if=\"label || showValue\" class=\"j-progress__header\">\n <span v-if=\"label\" :id=\"`${id}-label`\" class=\"j-progress__label\">{{ label }}</span>\n <span v-if=\"showValue && !indeterminate\" class=\"j-progress__value\">{{ percent }}%</span>\n </div>\n <div\n class=\"j-progress__track\"\n role=\"progressbar\"\n :aria-labelledby=\"label ? `${id}-label` : undefined\"\n aria-valuemin=\"0\"\n :aria-valuemax=\"max\"\n :aria-valuenow=\"indeterminate ? undefined : value!\"\n :aria-valuetext=\"indeterminate ? 'Loading' : `${percent}%`\"\n >\n <span class=\"j-progress__fill\" :style=\"indeterminate ? undefined : { transform: `scaleX(${ratio})` }\" />\n </div>\n </div>\n</template>\n\n<style>\n@layer juxt {\n.j-progress {\n --j-progress-height: 0.375rem;\n --j-progress-fill: var(--juxt-accent);\n\n display: flex;\n flex-direction: column;\n gap: var(--juxt-space-2);\n width: 100%;\n font-family: var(--juxt-font-sans);\n}\n\n.j-progress--sm {\n --j-progress-height: 0.25rem;\n}\n\n.j-progress--neutral {\n --j-progress-fill: var(--juxt-fg);\n}\n\n.j-progress__header {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--juxt-space-3);\n font-size: var(--juxt-text-sm);\n}\n\n.j-progress__label {\n color: var(--juxt-fg);\n font-weight: var(--juxt-weight-medium);\n letter-spacing: var(--juxt-tracking-tight);\n}\n\n.j-progress__value {\n color: var(--juxt-fg-muted);\n font-variant-numeric: tabular-nums;\n}\n\n.j-progress__track {\n position: relative;\n height: var(--j-progress-height);\n overflow: hidden;\n border-radius: var(--juxt-radius-full);\n background: var(--juxt-surface-active);\n}\n\n/* Width changes are transforms, so updates glide without reflow. */\n.j-progress__fill {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background: var(--j-progress-fill);\n transform: scaleX(0);\n transform-origin: left;\n transition: transform var(--juxt-duration-slow) var(--juxt-ease-out);\n}\n\n.j-progress.is-indeterminate .j-progress__fill {\n width: 40%;\n transform: none;\n animation: j-progress-indeterminate 1.4s var(--juxt-ease-standard) infinite;\n}\n\n@keyframes j-progress-indeterminate {\n from {\n transform: translateX(-100%);\n }\n\n to {\n transform: translateX(250%);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .j-progress.is-indeterminate .j-progress__fill {\n width: 100%;\n animation: j-progress-pulse 1.6s ease-in-out infinite;\n }\n\n @keyframes j-progress-pulse {\n 50% {\n opacity: 0.35;\n }\n }\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\nexport interface ProgressProps {\n /** Current value. `null` shows an indeterminate bar. */\n value?: number | null\n max?: number\n size?: 'sm' | 'md'\n /** Visible label above the bar; also its accessible name. */\n label?: string\n /** Show the percentage next to the label. */\n showValue?: boolean\n /** `accent` for progress toward a goal, `neutral` for plain measurement. */\n variant?: 'accent' | 'neutral'\n}\n\nconst props = withDefaults(defineProps<ProgressProps>(), {\n value: null,\n max: 100,\n size: 'md',\n variant: 'accent',\n})\n\nconst id = `j-${useId()}`\nconst indeterminate = computed(() => props.value === null || props.value === undefined)\nconst ratio = computed(() => (indeterminate.value ? 0 : Math.min(1, Math.max(0, Number(props.value) / props.max))))\nconst percent = computed(() => Math.round(ratio.value * 100))\n</script>\n\n<template>\n <div class=\"j-progress\" :class=\"[`j-progress--${size}`, `j-progress--${variant}`, { 'is-indeterminate': indeterminate }]\">\n <div v-if=\"label || showValue\" class=\"j-progress__header\">\n <span v-if=\"label\" :id=\"`${id}-label`\" class=\"j-progress__label\">{{ label }}</span>\n <span v-if=\"showValue && !indeterminate\" class=\"j-progress__value\">{{ percent }}%</span>\n </div>\n <div\n class=\"j-progress__track\"\n role=\"progressbar\"\n :aria-labelledby=\"label ? `${id}-label` : undefined\"\n aria-valuemin=\"0\"\n :aria-valuemax=\"max\"\n :aria-valuenow=\"indeterminate ? undefined : value!\"\n :aria-valuetext=\"indeterminate ? 'Loading' : `${percent}%`\"\n >\n <span class=\"j-progress__fill\" :style=\"indeterminate ? undefined : { transform: `scaleX(${ratio})` }\" />\n </div>\n </div>\n</template>\n\n<style>\n@layer juxt {\n.j-progress {\n --j-progress-height: 0.375rem;\n --j-progress-fill: var(--juxt-accent);\n\n display: flex;\n flex-direction: column;\n gap: var(--juxt-space-2);\n width: 100%;\n font-family: var(--juxt-font-sans);\n}\n\n.j-progress--sm {\n --j-progress-height: 0.25rem;\n}\n\n.j-progress--neutral {\n --j-progress-fill: var(--juxt-fg);\n}\n\n.j-progress__header {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--juxt-space-3);\n font-size: var(--juxt-text-sm);\n}\n\n.j-progress__label {\n color: var(--juxt-fg);\n font-weight: var(--juxt-weight-medium);\n letter-spacing: var(--juxt-tracking-tight);\n}\n\n.j-progress__value {\n color: var(--juxt-fg-muted);\n font-variant-numeric: tabular-nums;\n}\n\n.j-progress__track {\n position: relative;\n height: var(--j-progress-height);\n overflow: hidden;\n border-radius: var(--juxt-radius-full);\n background: var(--juxt-surface-active);\n}\n\n/* Width changes are transforms, so updates glide without reflow. */\n.j-progress__fill {\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background: var(--j-progress-fill);\n transform: scaleX(0);\n transform-origin: left;\n transition: transform var(--juxt-duration-slow) var(--juxt-ease-out);\n}\n\n.j-progress.is-indeterminate .j-progress__fill {\n width: 40%;\n transform: none;\n animation: j-progress-indeterminate 1.4s var(--juxt-ease-standard) infinite;\n}\n\n@keyframes j-progress-indeterminate {\n from {\n transform: translateX(-100%);\n }\n\n to {\n transform: translateX(250%);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .j-progress.is-indeterminate .j-progress__fill {\n width: 100%;\n animation: j-progress-pulse 1.6s ease-in-out infinite;\n }\n\n @keyframes j-progress-pulse {\n 50% {\n opacity: 0.35;\n }\n }\n}\n}\n</style>\n","import type { ComputedRef, InjectionKey } from 'vue'\n\nexport type RadioValue = string | number\n\nexport interface RadioGroupContext {\n name: ComputedRef<string>\n value: ComputedRef<RadioValue | null | undefined>\n disabled: ComputedRef<boolean>\n required: ComputedRef<boolean>\n invalid: ComputedRef<boolean>\n select: (value: RadioValue) => void\n}\n\nexport const RADIO_GROUP: InjectionKey<RadioGroupContext> = Symbol('JRadioGroup')\n","<script setup lang=\"ts\">\nimport type { RadioValue } from './context'\nimport { computed, inject, useId } from 'vue'\nimport { RADIO_GROUP } from './context'\n\nexport interface RadioProps {\n value: RadioValue\n label?: string\n description?: string\n disabled?: boolean\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = defineProps<RadioProps>()\n\ndefineSlots<{ default?: () => unknown, description?: () => unknown }>()\n\nconst group = inject(RADIO_GROUP, null)\n\nconst id = `j-${useId()}`\nconst descriptionId = `${id}-description`\nconst checked = computed(() => group?.value.value === props.value)\nconst isDisabled = computed(() => props.disabled || !!group?.disabled.value)\n</script>\n\n<template>\n <div\n class=\"j-radio\"\n :class=\"[$attrs.class, { 'is-disabled': isDisabled }]\"\n :style=\"$attrs.style as any\"\n :data-state=\"checked ? 'checked' : 'unchecked'\"\n >\n <span class=\"j-radio__control\">\n <input\n :id=\"id\"\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\n type=\"radio\"\n class=\"j-radio__input\"\n :name=\"group?.name.value\"\n :value=\"value\"\n :checked=\"checked\"\n :disabled=\"isDisabled\"\n :required=\"group?.required.value\"\n :aria-describedby=\"description || $slots.description ? descriptionId : undefined\"\n @change=\"group?.select(value)\"\n />\n <span class=\"j-radio__circle\" aria-hidden=\"true\" />\n </span>\n <span v-if=\"label || $slots.default || description || $slots.description\" class=\"j-radio__text\">\n <label v-if=\"label || $slots.default\" :for=\"id\" class=\"j-radio__label\">\n <slot>{{ label }}</slot>\n </label>\n <span v-if=\"description || $slots.description\" :id=\"descriptionId\" class=\"j-radio__description\">\n <slot name=\"description\">{{ description }}</slot>\n </span>\n </span>\n </div>\n</template>\n\n<style>\n@layer juxt {\n.j-radio {\n --j-radio-size: 1rem;\n\n display: inline-flex;\n align-items: flex-start;\n gap: var(--juxt-space-2-5);\n font-family: var(--juxt-font-sans);\n}\n\n.j-radio__control {\n position: relative;\n display: inline-flex;\n flex: none;\n margin-top: calc((var(--juxt-text-md) * var(--juxt-leading-snug) - var(--j-radio-size)) / 2);\n}\n\n.j-radio__input {\n position: absolute;\n inset: -4px;\n z-index: 1;\n width: calc(100% + 8px);\n height: calc(100% + 8px);\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n\n.j-radio__input:disabled {\n cursor: not-allowed;\n}\n\n.j-radio__circle {\n position: relative;\n display: block;\n width: var(--j-radio-size);\n height: var(--j-radio-size);\n border: 1px solid var(--juxt-border-strong);\n border-radius: var(--juxt-radius-full);\n background: var(--juxt-surface);\n box-shadow: var(--juxt-shadow-xs);\n outline: 2px solid transparent;\n outline-offset: 0;\n transition:\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\n transform var(--juxt-duration-fast) var(--juxt-ease-out),\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\n}\n\n/* The inner dot grows out of the centre once the fill lands. */\n.j-radio__circle::after {\n content: '';\n position: absolute;\n inset: 0;\n margin: auto;\n width: 0.375rem;\n height: 0.375rem;\n border-radius: var(--juxt-radius-full);\n background: var(--juxt-accent-fg);\n opacity: 0;\n transform: scale(0.3);\n transition:\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\n}\n\n.j-radio__input:hover:not(:disabled) + .j-radio__circle {\n border-color: var(--juxt-fg-muted);\n}\n\n.j-radio__input:active:not(:disabled) + .j-radio__circle {\n transform: scale(0.9);\n}\n\n.j-radio__input:focus-visible + .j-radio__circle {\n outline-color: var(--juxt-ring);\n outline-offset: 2px;\n}\n\n.j-radio[data-state='checked'] .j-radio__circle {\n border-color: var(--juxt-accent);\n background: var(--juxt-accent);\n}\n\n.j-radio[data-state='checked'] .j-radio__input:hover:not(:disabled) + .j-radio__circle {\n border-color: var(--juxt-accent-hover);\n background: var(--juxt-accent-hover);\n}\n\n.j-radio[data-state='checked'] .j-radio__circle::after {\n opacity: 1;\n transform: none;\n transition-delay: 30ms;\n}\n\n.j-radio-group.is-invalid .j-radio:not([data-state='checked']) .j-radio__circle {\n border-color: var(--juxt-danger-border);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .j-radio__input:active:not(:disabled) + .j-radio__circle {\n transform: none;\n }\n}\n\n.j-radio__text {\n display: flex;\n flex-direction: column;\n gap: var(--juxt-space-0-5);\n min-width: 0;\n}\n\n.j-radio__label {\n color: var(--juxt-fg);\n font-size: var(--juxt-text-md);\n line-height: var(--juxt-leading-snug);\n letter-spacing: var(--juxt-tracking-tight);\n cursor: pointer;\n user-select: none;\n}\n\n.j-radio__description {\n color: var(--juxt-fg-muted);\n font-size: var(--juxt-text-sm);\n line-height: var(--juxt-leading-normal);\n}\n\n.j-radio.is-disabled .j-radio__circle {\n opacity: 0.5;\n box-shadow: none;\n}\n\n.j-radio.is-disabled .j-radio__label {\n color: var(--juxt-fg-muted);\n cursor: not-allowed;\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport type { RadioValue } from './context'\nimport { computed, inject, useId } from 'vue'\nimport { RADIO_GROUP } from './context'\n\nexport interface RadioProps {\n value: RadioValue\n label?: string\n description?: string\n disabled?: boolean\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = defineProps<RadioProps>()\n\ndefineSlots<{ default?: () => unknown, description?: () => unknown }>()\n\nconst group = inject(RADIO_GROUP, null)\n\nconst id = `j-${useId()}`\nconst descriptionId = `${id}-description`\nconst checked = computed(() => group?.value.value === props.value)\nconst isDisabled = computed(() => props.disabled || !!group?.disabled.value)\n</script>\n\n<template>\n <div\n class=\"j-radio\"\n :class=\"[$attrs.class, { 'is-disabled': isDisabled }]\"\n :style=\"$attrs.style as any\"\n :data-state=\"checked ? 'checked' : 'unchecked'\"\n >\n <span class=\"j-radio__control\">\n <input\n :id=\"id\"\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\n type=\"radio\"\n class=\"j-radio__input\"\n :name=\"group?.name.value\"\n :value=\"value\"\n :checked=\"checked\"\n :disabled=\"isDisabled\"\n :required=\"group?.required.value\"\n :aria-describedby=\"description || $slots.description ? descriptionId : undefined\"\n @change=\"group?.select(value)\"\n />\n <span class=\"j-radio__circle\" aria-hidden=\"true\" />\n </span>\n <span v-if=\"label || $slots.default || description || $slots.description\" class=\"j-radio__text\">\n <label v-if=\"label || $slots.default\" :for=\"id\" class=\"j-radio__label\">\n <slot>{{ label }}</slot>\n </label>\n <span v-if=\"description || $slots.description\" :id=\"descriptionId\" class=\"j-radio__description\">\n <slot name=\"description\">{{ description }}</slot>\n </span>\n </span>\n </div>\n</template>\n\n<style>\n@layer juxt {\n.j-radio {\n --j-radio-size: 1rem;\n\n display: inline-flex;\n align-items: flex-start;\n gap: var(--juxt-space-2-5);\n font-family: var(--juxt-font-sans);\n}\n\n.j-radio__control {\n position: relative;\n display: inline-flex;\n flex: none;\n margin-top: calc((var(--juxt-text-md) * var(--juxt-leading-snug) - var(--j-radio-size)) / 2);\n}\n\n.j-radio__input {\n position: absolute;\n inset: -4px;\n z-index: 1;\n width: calc(100% + 8px);\n height: calc(100% + 8px);\n margin: 0;\n opacity: 0;\n cursor: pointer;\n}\n\n.j-radio__input:disabled {\n cursor: not-allowed;\n}\n\n.j-radio__circle {\n position: relative;\n display: block;\n width: var(--j-radio-size);\n height: var(--j-radio-size);\n border: 1px solid var(--juxt-border-strong);\n border-radius: var(--juxt-radius-full);\n background: var(--juxt-surface);\n box-shadow: var(--juxt-shadow-xs);\n outline: 2px solid transparent;\n outline-offset: 0;\n transition:\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\n transform var(--juxt-duration-fast) var(--juxt-ease-out),\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\n}\n\n/* The inner dot grows out of the centre once the fill lands. */\n.j-radio__circle::after {\n content: '';\n position: absolute;\n inset: 0;\n margin: auto;\n width: 0.375rem;\n height: 0.375rem;\n border-radius: var(--juxt-radius-full);\n background: var(--juxt-accent-fg);\n opacity: 0;\n transform: scale(0.3);\n transition:\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\n}\n\n.j-radio__input:hover:not(:disabled) + .j-radio__circle {\n border-color: var(--juxt-fg-muted);\n}\n\n.j-radio__input:active:not(:disabled) + .j-radio__circle {\n transform: scale(0.9);\n}\n\n.j-radio__input:focus-visible + .j-radio__circle {\n outline-color: var(--juxt-ring);\n outline-offset: 2px;\n}\n\n.j-radio[data-state='checked'] .j-radio__circle {\n border-color: var(--juxt-accent);\n background: var(--juxt-accent);\n}\n\n.j-radio[data-state='checked'] .j-radio__input:hover:not(:disabled) + .j-radio__circle {\n border-color: var(--juxt-accent-hover);\n background: var(--juxt-accent-hover);\n}\n\n.j-radio[data-state='checked'] .j-radio__circle::after {\n opacity: 1;\n transform: none;\n transition-delay: 30ms;\n}\n\n.j-radio-group.is-invalid .j-radio:not([data-state='checked']) .j-radio__circle {\n border-color: var(--juxt-danger-border);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .j-radio__input:active:not(:disabled) + .j-radio__circle {\n transform: none;\n }\n}\n\n.j-radio__text {\n display: flex;\n flex-direction: column;\n gap: var(--juxt-space-0-5);\n min-width: 0;\n}\n\n.j-radio__label {\n color: var(--juxt-fg);\n font-size: var(--juxt-text-md);\n line-height: var(--juxt-leading-snug);\n letter-spacing: var(--juxt-tracking-tight);\n cursor: pointer;\n user-select: none;\n}\n\n.j-radio__description {\n color: var(--juxt-fg-muted);\n font-size: var(--juxt-text-sm);\n line-height: var(--juxt-leading-normal);\n}\n\n.j-radio.is-disabled .j-radio__circle {\n opacity: 0.5;\n box-shadow: none;\n}\n\n.j-radio.is-disabled .j-radio__label {\n color: var(--juxt-fg-muted);\n cursor: not-allowed;\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport type { RadioValue } from './context'\nimport { computed, provide, useId } from 'vue'\nimport { RADIO_GROUP } from './context'\n\nexport interface RadioGroupProps {\n /** Group label, rendered as the fieldset legend. */\n label?: string\n description?: string\n /** An error message, or `true` to mark the group invalid without one. */\n error?: string | boolean\n orientation?: 'vertical' | 'horizontal'\n disabled?: boolean\n required?: boolean\n /** Form field name. Generated when omitted. */\n name?: string\n}\n\nconst props = withDefaults(defineProps<RadioGroupProps>(), { orientation: 'vertical', error: undefined })\n\nconst model = defineModel<RadioValue | null>({ default: null })\n\ndefineSlots<{ default?: () => unknown, label?: () => unknown }>()\n\nconst id = `j-${useId()}`\nconst descriptionId = `${id}-description`\nconst errorId = `${id}-error`\nconst errorMessage = computed(() => (typeof props.error === 'string' ? props.error : ''))\n\nprovide(RADIO_GROUP, {\n name: computed(() => props.name ?? id),\n value: computed(() => model.value),\n disabled: computed(() => props.disabled),\n required: computed(() => props.required),\n invalid: computed(() => !!props.error),\n select: (value) => {\n if (!props.disabled) model.value = value\n },\n})\n\nconst describedBy = computed(() => (errorMessage.value ? errorId : props.description ? descriptionId : undefined))\n</script>\n\n<template>\n <fieldset\n class=\"j-radio-group\"\n :class=\"{ 'is-disabled': disabled, 'is-invalid': !!error }\"\n :disabled=\"disabled\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"!!error || undefined\"\n :aria-required=\"required || undefined\"\n >\n <legend v-if=\"label || $slots.label\" class=\"j-radio-group__legend\">\n <slot name=\"label\">{{ label }}</slot>\n <span v-if=\"required\" class=\"j-field__required\" aria-hidden=\"true\">*</span>\n </legend>\n <p v-if=\"description && !errorMessage\" :id=\"descriptionId\" class=\"j-radio-group__description\">\n {{ description }}\n </p>\n <div class=\"j-radio-group__items\" :class=\"`j-radio-group__items--${orientation}`\">\n <slot />\n </div>\n <p v-if=\"errorMessage\" :id=\"errorId\" class=\"j-radio-group__error\">\n {{ errorMessage }}\n </p>\n </fieldset>\n</template>\n\n<style>\n@layer juxt {\n.j-radio-group {\n display: flex;\n flex-direction: column;\n gap: var(--juxt-space-1-5);\n min-width: 0;\n margin: 0;\n padding: 0;\n border: 0;\n font-family: var(--juxt-font-sans);\n}\n\n.j-radio-group__legend {\n display: inline-flex;\n gap: var(--juxt-space-0-5);\n margin-bottom: var(--juxt-space-1-5);\n padding: 0;\n color: var(--juxt-fg);\n font-size: var(--juxt-text-sm);\n font-weight: var(--juxt-weight-medium);\n line-height: var(--juxt-leading-snug);\n letter-spacing: var(--juxt-tracking-tight);\n}\n\n.j-radio-group__description,\n.j-radio-group__error {\n margin: calc(var(--juxt-space-1) * -1) 0 var(--juxt-space-1-5);\n color: var(--juxt-fg-muted);\n font-size: var(--juxt-text-xs);\n line-height: var(--juxt-leading-normal);\n}\n\n.j-radio-group__error {\n margin: var(--juxt-space-1) 0 0;\n color: var(--juxt-danger-text);\n}\n\n.j-radio-group__items {\n display: flex;\n}\n\n.j-radio-group__items--vertical {\n flex-direction: column;\n gap: var(--juxt-space-3);\n}\n\n.j-radio-group__items--horizontal {\n flex-wrap: wrap;\n gap: var(--juxt-space-3) var(--juxt-space-6);\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport type { RadioValue } from './context'\nimport { computed, provide, useId } from 'vue'\nimport { RADIO_GROUP } from './context'\n\nexport interface RadioGroupProps {\n /** Group label, rendered as the fieldset legend. */\n label?: string\n description?: string\n /** An error message, or `true` to mark the group invalid without one. */\n error?: string | boolean\n orientation?: 'vertical' | 'horizontal'\n disabled?: boolean\n required?: boolean\n /** Form field name. Generated when omitted. */\n name?: string\n}\n\nconst props = withDefaults(defineProps<RadioGroupProps>(), { orientation: 'vertical', error: undefined })\n\nconst model = defineModel<RadioValue | null>({ default: null })\n\ndefineSlots<{ default?: () => unknown, label?: () => unknown }>()\n\nconst id = `j-${useId()}`\nconst descriptionId = `${id}-description`\nconst errorId = `${id}-error`\nconst errorMessage = computed(() => (typeof props.error === 'string' ? props.error : ''))\n\nprovide(RADIO_GROUP, {\n name: computed(() => props.name ?? id),\n value: computed(() => model.value),\n disabled: computed(() => props.disabled),\n required: computed(() => props.required),\n invalid: computed(() => !!props.error),\n select: (value) => {\n if (!props.disabled) model.value = value\n },\n})\n\nconst describedBy = computed(() => (errorMessage.value ? errorId : props.description ? descriptionId : undefined))\n</script>\n\n<template>\n <fieldset\n class=\"j-radio-group\"\n :class=\"{ 'is-disabled': disabled, 'is-invalid': !!error }\"\n :disabled=\"disabled\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"!!error || undefined\"\n :aria-required=\"required || undefined\"\n >\n <legend v-if=\"label || $slots.label\" class=\"j-radio-group__legend\">\n <slot name=\"label\">{{ label }}</slot>\n <span v-if=\"required\" class=\"j-field__required\" aria-hidden=\"true\">*</span>\n </legend>\n <p v-if=\"description && !errorMessage\" :id=\"descriptionId\" class=\"j-radio-group__description\">\n {{ description }}\n </p>\n <div class=\"j-radio-group__items\" :class=\"`j-radio-group__items--${orientation}`\">\n <slot />\n </div>\n <p v-if=\"errorMessage\" :id=\"errorId\" class=\"j-radio-group__error\">\n {{ errorMessage }}\n </p>\n </fieldset>\n</template>\n\n<style>\n@layer juxt {\n.j-radio-group {\n display: flex;\n flex-direction: column;\n gap: var(--juxt-space-1-5);\n min-width: 0;\n margin: 0;\n padding: 0;\n border: 0;\n font-family: var(--juxt-font-sans);\n}\n\n.j-radio-group__legend {\n display: inline-flex;\n gap: var(--juxt-space-0-5);\n margin-bottom: var(--juxt-space-1-5);\n padding: 0;\n color: var(--juxt-fg);\n font-size: var(--juxt-text-sm);\n font-weight: var(--juxt-weight-medium);\n line-height: var(--juxt-leading-snug);\n letter-spacing: var(--juxt-tracking-tight);\n}\n\n.j-radio-group__description,\n.j-radio-group__error {\n margin: calc(var(--juxt-space-1) * -1) 0 var(--juxt-space-1-5);\n color: var(--juxt-fg-muted);\n font-size: var(--juxt-text-xs);\n line-height: var(--juxt-leading-normal);\n}\n\n.j-radio-group__error {\n margin: var(--juxt-space-1) 0 0;\n color: var(--juxt-danger-text);\n}\n\n.j-radio-group__items {\n display: flex;\n}\n\n.j-radio-group__items--vertical {\n flex-direction: column;\n gap: var(--juxt-space-3);\n}\n\n.j-radio-group__items--horizontal {\n flex-wrap: wrap;\n gap: var(--juxt-space-3) var(--juxt-space-6);\n}\n}\n</style>\n","<script setup lang=\"ts\" generic=\"T extends string | number = string\">\r\nimport type { FieldProps } from '../field/useField'\r\nimport { computed, nextTick, ref, watch } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { useTypeahead } from '../../composables/useTypeahead'\r\nimport { IconCheck, IconChevronUpDown } from '../../icons'\r\nimport JFieldShell from '../field/JFieldShell.vue'\r\nimport { useField } from '../field/useField'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface SelectOption<V = string | number> {\r\n value: V\r\n label: string\r\n description?: string\r\n disabled?: boolean\r\n}\r\n\r\nexport interface SelectProps<V> extends FieldProps {\r\n /** Options as objects, or plain strings when value and label are the same. */\r\n options: Array<SelectOption<V> | V>\r\n placeholder?: string\r\n size?: 'sm' | 'md' | 'lg'\r\n /** Submits the value under this name in a form. */\r\n name?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<SelectProps<T>>(), {\r\n placeholder: 'Select…',\r\n size: 'md',\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<T | null>({ default: null })\r\n\r\nconst emit = defineEmits<{\r\n open: []\r\n close: []\r\n}>()\r\n\r\ndefineSlots<{\r\n option?: (props: { option: SelectOption<T>, selected: boolean, active: boolean }) => unknown\r\n value?: (props: { option: SelectOption<T> }) => unknown\r\n label?: () => unknown\r\n}>()\r\n\r\nconst field = useField(props)\r\nconst listboxId = computed(() => `${field.id.value}-listbox`)\r\nconst valueId = computed(() => `${field.id.value}-value`)\r\nconst optionId = (index: number) => `${field.id.value}-option-${index}`\r\n\r\nconst normalized = computed<SelectOption<T>[]>(() =>\r\n props.options.map(option =>\r\n typeof option === 'object' && option !== null ? option : { value: option as T, label: String(option) },\r\n ),\r\n)\r\n\r\n/*\r\n * The list the user sees and navigates. Today it's every option; a search\r\n * input only needs to set `query` for this to become a filtered view.\r\n */\r\nconst query = ref('')\r\nconst visibleOptions = computed(() => {\r\n const q = query.value.trim().toLowerCase()\r\n if (!q) return normalized.value\r\n return normalized.value.filter(option => option.label.toLowerCase().includes(q))\r\n})\r\n\r\nconst selected = computed(() => normalized.value.find(option => option.value === model.value))\r\n\r\nconst open = ref(false)\r\nconst activeIndex = ref(-1)\r\nconst trigger = ref<HTMLButtonElement | null>(null)\r\nconst listbox = ref<HTMLElement | null>(null)\r\nconst pointerActive = ref(false)\r\n\r\nconst { styles } = useFloating(trigger, listbox, { placement: 'bottom-start', offset: 4, matchWidth: true })\r\nconst typeahead = useTypeahead()\r\n\r\nuseLayer({\r\n active: open,\r\n elements: () => [trigger.value, listbox.value],\r\n onEscape: () => close(),\r\n onPointerDownOutside: () => close(),\r\n})\r\n\r\nconst isEnabled = (index: number) => {\r\n const option = visibleOptions.value[index]\r\n return !!option && !option.disabled\r\n}\r\n\r\nfunction step(from: number, delta: 1 | -1): number {\r\n const count = visibleOptions.value.length\r\n for (let i = from + delta; i >= 0 && i < count; i += delta) {\r\n if (isEnabled(i)) return i\r\n }\r\n return from\r\n}\r\n\r\nconst first = () => step(-1, 1)\r\nconst last = () => step(visibleOptions.value.length, -1)\r\n\r\nfunction setActive(index: number, scroll = true) {\r\n activeIndex.value = index\r\n if (!scroll) return\r\n nextTick(() => {\r\n document.getElementById(optionId(index))?.scrollIntoView({ block: 'nearest' })\r\n })\r\n}\r\n\r\nfunction openList(focus: 'selected' | 'first' | 'last' = 'selected') {\r\n if (props.disabled || open.value) return\r\n open.value = true\r\n emit('open')\r\n const selectedIndex = visibleOptions.value.findIndex(option => option.value === model.value)\r\n const target = focus === 'last' ? last() : focus === 'first' ? first() : selectedIndex >= 0 ? selectedIndex : first()\r\n setActive(target)\r\n}\r\n\r\nfunction close() {\r\n if (!open.value) return\r\n open.value = false\r\n pointerActive.value = false\r\n emit('close')\r\n}\r\n\r\nfunction commit(index: number) {\r\n const option = visibleOptions.value[index]\r\n if (!option || option.disabled) return\r\n model.value = option.value\r\n close()\r\n}\r\n\r\nfunction onTriggerClick() {\r\n if (open.value) close()\r\n else openList()\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (props.disabled) return\r\n const { key } = event\r\n\r\n if (!open.value) {\r\n if (key === 'ArrowDown' || key === 'Enter' || key === ' ') {\r\n event.preventDefault()\r\n openList()\r\n } else if (key === 'ArrowUp') {\r\n event.preventDefault()\r\n openList()\r\n } else if (key === 'Home') {\r\n event.preventDefault()\r\n openList('first')\r\n } else if (key === 'End') {\r\n event.preventDefault()\r\n openList('last')\r\n } else if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\r\n const index = typeahead.search(key, visibleOptions.value, option => option.label, activeIndex.value)\r\n if (index >= 0) {\r\n openList()\r\n setActive(index)\r\n }\r\n }\r\n return\r\n }\r\n\r\n switch (key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n if (event.altKey) return\r\n setActive(step(activeIndex.value, 1))\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n if (event.altKey) return commit(activeIndex.value)\r\n setActive(step(activeIndex.value, -1))\r\n break\r\n case 'Home':\r\n event.preventDefault()\r\n setActive(first())\r\n break\r\n case 'End':\r\n event.preventDefault()\r\n setActive(last())\r\n break\r\n case 'PageDown':\r\n event.preventDefault()\r\n setActive(Math.min(last(), step(Math.min(activeIndex.value + 9, visibleOptions.value.length - 1), 1)))\r\n break\r\n case 'PageUp':\r\n event.preventDefault()\r\n setActive(Math.max(first(), step(Math.max(activeIndex.value - 9, 0), -1)))\r\n break\r\n case 'Enter':\r\n event.preventDefault()\r\n commit(activeIndex.value)\r\n break\r\n case 'Tab':\r\n commit(activeIndex.value)\r\n close()\r\n break\r\n case ' ':\r\n event.preventDefault()\r\n if (typeahead.isSearching()) typeahead.search(key, visibleOptions.value, option => option.label, activeIndex.value)\r\n else commit(activeIndex.value)\r\n break\r\n default:\r\n if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\r\n const index = typeahead.search(key, visibleOptions.value, option => option.label, activeIndex.value)\r\n if (index >= 0) setActive(index)\r\n }\r\n }\r\n}\r\n\r\n// Only let the pointer steer the active option once it actually moves, so a\r\n// list opening under a resting cursor doesn't hijack the keyboard position.\r\nfunction onOptionPointerMove(index: number) {\r\n pointerActive.value = true\r\n if (activeIndex.value !== index && isEnabled(index)) setActive(index, false)\r\n}\r\n\r\nwatch(() => props.disabled, value => value && close())\r\n\r\ndefineExpose({ focus: () => trigger.value?.focus(), open: () => openList(), close })\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n <button\r\n :id=\"field.id.value\"\r\n ref=\"trigger\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"button\"\r\n role=\"combobox\"\r\n class=\"j-control j-select__trigger\"\r\n :class=\"[`j-control--${size}`, { 'is-open': open, 'is-invalid': field.invalid.value, 'is-disabled': disabled }]\"\r\n aria-haspopup=\"listbox\"\r\n :aria-expanded=\"open\"\r\n :aria-controls=\"open ? listboxId : undefined\"\r\n :aria-activedescendant=\"open && activeIndex >= 0 ? optionId(activeIndex) : undefined\"\r\n :aria-labelledby=\"label || $slots.label ? `${field.labelId.value} ${valueId}` : undefined\"\r\n :aria-describedby=\"field.describedBy.value\"\r\n :aria-invalid=\"field.invalid.value || undefined\"\r\n :aria-required=\"required || undefined\"\r\n :disabled=\"disabled\"\r\n @click=\"onTriggerClick\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <span :id=\"valueId\" class=\"j-select__value\" :class=\"{ 'is-placeholder': !selected }\">\r\n <slot v-if=\"selected\" name=\"value\" :option=\"selected\">{{ selected.label }}</slot>\r\n <template v-else>{{ placeholder }}</template>\r\n </span>\r\n <IconChevronUpDown class=\"j-select__chevron\" />\r\n </button>\r\n <input v-if=\"name\" type=\"hidden\" :name=\"name\" :value=\"model ?? ''\" />\r\n\r\n <JPortal>\r\n <Transition name=\"j-pop\">\r\n <ul\r\n v-if=\"open\"\r\n :id=\"listboxId\"\r\n ref=\"listbox\"\r\n role=\"listbox\"\r\n class=\"j-select__listbox j-popover-surface\"\r\n :class=\"{ 'is-pointer': pointerActive }\"\r\n :style=\"styles\"\r\n :aria-labelledby=\"label ? field.labelId.value : undefined\"\r\n tabindex=\"-1\"\r\n @pointerdown.prevent\r\n >\r\n <li\r\n v-for=\"(option, index) in visibleOptions\"\r\n :id=\"optionId(index)\"\r\n :key=\"String(option.value)\"\r\n role=\"option\"\r\n class=\"j-select__option\"\r\n :class=\"{ 'is-active': index === activeIndex, 'is-selected': option.value === model }\"\r\n :aria-selected=\"option.value === model\"\r\n :aria-disabled=\"option.disabled || undefined\"\r\n @pointermove=\"onOptionPointerMove(index)\"\r\n @click=\"commit(index)\"\r\n >\r\n <span class=\"j-select__option-text\">\r\n <slot name=\"option\" :option=\"option\" :selected=\"option.value === model\" :active=\"index === activeIndex\">\r\n <span class=\"j-select__option-label\">{{ option.label }}</span>\r\n <span v-if=\"option.description\" class=\"j-select__option-description\">{{ option.description }}</span>\r\n </slot>\r\n </span>\r\n <IconCheck class=\"j-select__check\" />\r\n </li>\r\n <li v-if=\"visibleOptions.length === 0\" class=\"j-select__empty\" role=\"presentation\">\r\n No options\r\n </li>\r\n </ul>\r\n </Transition>\r\n </JPortal>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-select__trigger {\r\n width: 100%;\r\n justify-content: space-between;\r\n text-align: left;\r\n cursor: pointer;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.j-select__trigger:focus-visible {\r\n outline: none;\r\n}\r\n\r\n.j-select__trigger:focus-visible:not(.is-invalid) {\r\n border-color: var(--juxt-accent);\r\n box-shadow: 0 0 0 3px var(--juxt-ring-soft);\r\n}\r\n\r\n.j-select__trigger:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-select__value {\r\n overflow: hidden;\r\n min-width: 0;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-select__value.is-placeholder {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-select__trigger.is-disabled .j-select__value {\r\n color: var(--juxt-fg-disabled);\r\n}\r\n\r\n.j-select__chevron {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n margin-right: -0.125rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-select__listbox {\r\n z-index: var(--juxt-z-popover);\r\n box-sizing: border-box;\r\n max-width: calc(100vw - 16px);\r\n max-height: min(18rem, var(--j-available-height, 18rem));\r\n margin: 0;\r\n padding: var(--juxt-space-1);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n list-style: none;\r\n outline: none;\r\n scroll-padding-block: var(--juxt-space-1);\r\n}\r\n\r\n.j-select__option {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2);\r\n min-height: var(--juxt-control-sm);\r\n padding: var(--juxt-space-1-5) var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-xs);\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-select__option.is-active {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-select__option[aria-disabled='true'] {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-select__option-text {\r\n display: flex;\r\n flex: 1;\r\n flex-direction: column;\r\n gap: 1px;\r\n min-width: 0;\r\n}\r\n\r\n.j-select__option-label {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-select__option-description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n}\r\n\r\n.j-select__check {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n color: var(--juxt-accent-text);\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-select__option.is-selected .j-select__check {\r\n opacity: 1;\r\n transform: none;\r\n}\r\n\r\n.j-select__empty {\r\n padding: var(--juxt-space-3) var(--juxt-space-2);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n text-align: center;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\" generic=\"T extends string | number = string\">\r\nimport type { FieldProps } from '../field/useField'\r\nimport { computed, nextTick, ref, watch } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { useTypeahead } from '../../composables/useTypeahead'\r\nimport { IconCheck, IconChevronUpDown } from '../../icons'\r\nimport JFieldShell from '../field/JFieldShell.vue'\r\nimport { useField } from '../field/useField'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface SelectOption<V = string | number> {\r\n value: V\r\n label: string\r\n description?: string\r\n disabled?: boolean\r\n}\r\n\r\nexport interface SelectProps<V> extends FieldProps {\r\n /** Options as objects, or plain strings when value and label are the same. */\r\n options: Array<SelectOption<V> | V>\r\n placeholder?: string\r\n size?: 'sm' | 'md' | 'lg'\r\n /** Submits the value under this name in a form. */\r\n name?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<SelectProps<T>>(), {\r\n placeholder: 'Select…',\r\n size: 'md',\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<T | null>({ default: null })\r\n\r\nconst emit = defineEmits<{\r\n open: []\r\n close: []\r\n}>()\r\n\r\ndefineSlots<{\r\n option?: (props: { option: SelectOption<T>, selected: boolean, active: boolean }) => unknown\r\n value?: (props: { option: SelectOption<T> }) => unknown\r\n label?: () => unknown\r\n}>()\r\n\r\nconst field = useField(props)\r\nconst listboxId = computed(() => `${field.id.value}-listbox`)\r\nconst valueId = computed(() => `${field.id.value}-value`)\r\nconst optionId = (index: number) => `${field.id.value}-option-${index}`\r\n\r\nconst normalized = computed<SelectOption<T>[]>(() =>\r\n props.options.map(option =>\r\n typeof option === 'object' && option !== null ? option : { value: option as T, label: String(option) },\r\n ),\r\n)\r\n\r\n/*\r\n * The list the user sees and navigates. Today it's every option; a search\r\n * input only needs to set `query` for this to become a filtered view.\r\n */\r\nconst query = ref('')\r\nconst visibleOptions = computed(() => {\r\n const q = query.value.trim().toLowerCase()\r\n if (!q) return normalized.value\r\n return normalized.value.filter(option => option.label.toLowerCase().includes(q))\r\n})\r\n\r\nconst selected = computed(() => normalized.value.find(option => option.value === model.value))\r\n\r\nconst open = ref(false)\r\nconst activeIndex = ref(-1)\r\nconst trigger = ref<HTMLButtonElement | null>(null)\r\nconst listbox = ref<HTMLElement | null>(null)\r\nconst pointerActive = ref(false)\r\n\r\nconst { styles } = useFloating(trigger, listbox, { placement: 'bottom-start', offset: 4, matchWidth: true })\r\nconst typeahead = useTypeahead()\r\n\r\nuseLayer({\r\n active: open,\r\n elements: () => [trigger.value, listbox.value],\r\n onEscape: () => close(),\r\n onPointerDownOutside: () => close(),\r\n})\r\n\r\nconst isEnabled = (index: number) => {\r\n const option = visibleOptions.value[index]\r\n return !!option && !option.disabled\r\n}\r\n\r\nfunction step(from: number, delta: 1 | -1): number {\r\n const count = visibleOptions.value.length\r\n for (let i = from + delta; i >= 0 && i < count; i += delta) {\r\n if (isEnabled(i)) return i\r\n }\r\n return from\r\n}\r\n\r\nconst first = () => step(-1, 1)\r\nconst last = () => step(visibleOptions.value.length, -1)\r\n\r\nfunction setActive(index: number, scroll = true) {\r\n activeIndex.value = index\r\n if (!scroll) return\r\n nextTick(() => {\r\n document.getElementById(optionId(index))?.scrollIntoView({ block: 'nearest' })\r\n })\r\n}\r\n\r\nfunction openList(focus: 'selected' | 'first' | 'last' = 'selected') {\r\n if (props.disabled || open.value) return\r\n open.value = true\r\n emit('open')\r\n const selectedIndex = visibleOptions.value.findIndex(option => option.value === model.value)\r\n const target = focus === 'last' ? last() : focus === 'first' ? first() : selectedIndex >= 0 ? selectedIndex : first()\r\n setActive(target)\r\n}\r\n\r\nfunction close() {\r\n if (!open.value) return\r\n open.value = false\r\n pointerActive.value = false\r\n emit('close')\r\n}\r\n\r\nfunction commit(index: number) {\r\n const option = visibleOptions.value[index]\r\n if (!option || option.disabled) return\r\n model.value = option.value\r\n close()\r\n}\r\n\r\nfunction onTriggerClick() {\r\n if (open.value) close()\r\n else openList()\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (props.disabled) return\r\n const { key } = event\r\n\r\n if (!open.value) {\r\n if (key === 'ArrowDown' || key === 'Enter' || key === ' ') {\r\n event.preventDefault()\r\n openList()\r\n } else if (key === 'ArrowUp') {\r\n event.preventDefault()\r\n openList()\r\n } else if (key === 'Home') {\r\n event.preventDefault()\r\n openList('first')\r\n } else if (key === 'End') {\r\n event.preventDefault()\r\n openList('last')\r\n } else if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\r\n const index = typeahead.search(key, visibleOptions.value, option => option.label, activeIndex.value)\r\n if (index >= 0) {\r\n openList()\r\n setActive(index)\r\n }\r\n }\r\n return\r\n }\r\n\r\n switch (key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n if (event.altKey) return\r\n setActive(step(activeIndex.value, 1))\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n if (event.altKey) return commit(activeIndex.value)\r\n setActive(step(activeIndex.value, -1))\r\n break\r\n case 'Home':\r\n event.preventDefault()\r\n setActive(first())\r\n break\r\n case 'End':\r\n event.preventDefault()\r\n setActive(last())\r\n break\r\n case 'PageDown':\r\n event.preventDefault()\r\n setActive(Math.min(last(), step(Math.min(activeIndex.value + 9, visibleOptions.value.length - 1), 1)))\r\n break\r\n case 'PageUp':\r\n event.preventDefault()\r\n setActive(Math.max(first(), step(Math.max(activeIndex.value - 9, 0), -1)))\r\n break\r\n case 'Enter':\r\n event.preventDefault()\r\n commit(activeIndex.value)\r\n break\r\n case 'Tab':\r\n commit(activeIndex.value)\r\n close()\r\n break\r\n case ' ':\r\n event.preventDefault()\r\n if (typeahead.isSearching()) typeahead.search(key, visibleOptions.value, option => option.label, activeIndex.value)\r\n else commit(activeIndex.value)\r\n break\r\n default:\r\n if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\r\n const index = typeahead.search(key, visibleOptions.value, option => option.label, activeIndex.value)\r\n if (index >= 0) setActive(index)\r\n }\r\n }\r\n}\r\n\r\n// Only let the pointer steer the active option once it actually moves, so a\r\n// list opening under a resting cursor doesn't hijack the keyboard position.\r\nfunction onOptionPointerMove(index: number) {\r\n pointerActive.value = true\r\n if (activeIndex.value !== index && isEnabled(index)) setActive(index, false)\r\n}\r\n\r\nwatch(() => props.disabled, value => value && close())\r\n\r\ndefineExpose({ focus: () => trigger.value?.focus(), open: () => openList(), close })\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n <button\r\n :id=\"field.id.value\"\r\n ref=\"trigger\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"button\"\r\n role=\"combobox\"\r\n class=\"j-control j-select__trigger\"\r\n :class=\"[`j-control--${size}`, { 'is-open': open, 'is-invalid': field.invalid.value, 'is-disabled': disabled }]\"\r\n aria-haspopup=\"listbox\"\r\n :aria-expanded=\"open\"\r\n :aria-controls=\"open ? listboxId : undefined\"\r\n :aria-activedescendant=\"open && activeIndex >= 0 ? optionId(activeIndex) : undefined\"\r\n :aria-labelledby=\"label || $slots.label ? `${field.labelId.value} ${valueId}` : undefined\"\r\n :aria-describedby=\"field.describedBy.value\"\r\n :aria-invalid=\"field.invalid.value || undefined\"\r\n :aria-required=\"required || undefined\"\r\n :disabled=\"disabled\"\r\n @click=\"onTriggerClick\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <span :id=\"valueId\" class=\"j-select__value\" :class=\"{ 'is-placeholder': !selected }\">\r\n <slot v-if=\"selected\" name=\"value\" :option=\"selected\">{{ selected.label }}</slot>\r\n <template v-else>{{ placeholder }}</template>\r\n </span>\r\n <IconChevronUpDown class=\"j-select__chevron\" />\r\n </button>\r\n <input v-if=\"name\" type=\"hidden\" :name=\"name\" :value=\"model ?? ''\" />\r\n\r\n <JPortal>\r\n <Transition name=\"j-pop\">\r\n <ul\r\n v-if=\"open\"\r\n :id=\"listboxId\"\r\n ref=\"listbox\"\r\n role=\"listbox\"\r\n class=\"j-select__listbox j-popover-surface\"\r\n :class=\"{ 'is-pointer': pointerActive }\"\r\n :style=\"styles\"\r\n :aria-labelledby=\"label ? field.labelId.value : undefined\"\r\n tabindex=\"-1\"\r\n @pointerdown.prevent\r\n >\r\n <li\r\n v-for=\"(option, index) in visibleOptions\"\r\n :id=\"optionId(index)\"\r\n :key=\"String(option.value)\"\r\n role=\"option\"\r\n class=\"j-select__option\"\r\n :class=\"{ 'is-active': index === activeIndex, 'is-selected': option.value === model }\"\r\n :aria-selected=\"option.value === model\"\r\n :aria-disabled=\"option.disabled || undefined\"\r\n @pointermove=\"onOptionPointerMove(index)\"\r\n @click=\"commit(index)\"\r\n >\r\n <span class=\"j-select__option-text\">\r\n <slot name=\"option\" :option=\"option\" :selected=\"option.value === model\" :active=\"index === activeIndex\">\r\n <span class=\"j-select__option-label\">{{ option.label }}</span>\r\n <span v-if=\"option.description\" class=\"j-select__option-description\">{{ option.description }}</span>\r\n </slot>\r\n </span>\r\n <IconCheck class=\"j-select__check\" />\r\n </li>\r\n <li v-if=\"visibleOptions.length === 0\" class=\"j-select__empty\" role=\"presentation\">\r\n No options\r\n </li>\r\n </ul>\r\n </Transition>\r\n </JPortal>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-select__trigger {\r\n width: 100%;\r\n justify-content: space-between;\r\n text-align: left;\r\n cursor: pointer;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.j-select__trigger:focus-visible {\r\n outline: none;\r\n}\r\n\r\n.j-select__trigger:focus-visible:not(.is-invalid) {\r\n border-color: var(--juxt-accent);\r\n box-shadow: 0 0 0 3px var(--juxt-ring-soft);\r\n}\r\n\r\n.j-select__trigger:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-select__value {\r\n overflow: hidden;\r\n min-width: 0;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-select__value.is-placeholder {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-select__trigger.is-disabled .j-select__value {\r\n color: var(--juxt-fg-disabled);\r\n}\r\n\r\n.j-select__chevron {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n margin-right: -0.125rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-select__listbox {\r\n z-index: var(--juxt-z-popover);\r\n box-sizing: border-box;\r\n max-width: calc(100vw - 16px);\r\n max-height: min(18rem, var(--j-available-height, 18rem));\r\n margin: 0;\r\n padding: var(--juxt-space-1);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n list-style: none;\r\n outline: none;\r\n scroll-padding-block: var(--juxt-space-1);\r\n}\r\n\r\n.j-select__option {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2);\r\n min-height: var(--juxt-control-sm);\r\n padding: var(--juxt-space-1-5) var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-xs);\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-select__option.is-active {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-select__option[aria-disabled='true'] {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-select__option-text {\r\n display: flex;\r\n flex: 1;\r\n flex-direction: column;\r\n gap: 1px;\r\n min-width: 0;\r\n}\r\n\r\n.j-select__option-label {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-select__option-description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n}\r\n\r\n.j-select__check {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n color: var(--juxt-accent-text);\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-select__option.is-selected .j-select__check {\r\n opacity: 1;\r\n transform: none;\r\n}\r\n\r\n.j-select__empty {\r\n padding: var(--juxt-space-3) var(--juxt-space-2);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n text-align: center;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\nexport interface SeparatorProps {\n orientation?: 'horizontal' | 'vertical'\n /** Text in the middle of a horizontal separator, e.g. \"or\". */\n label?: string\n /**\n * Purely visual (the default). Set to false when the separator divides\n * meaningful groups and should be announced.\n */\n decorative?: boolean\n}\n\nwithDefaults(defineProps<SeparatorProps>(), { orientation: 'horizontal', decorative: true })\n\ndefineSlots<{ default?: () => unknown }>()\n</script>\n\n<template>\n <div\n class=\"j-separator\"\n :class=\"[`j-separator--${orientation}`, { 'j-separator--labelled': label || $slots.default }]\"\n :role=\"decorative ? 'none' : 'separator'\"\n :aria-orientation=\"decorative ? undefined : orientation\"\n >\n <span v-if=\"label || $slots.default\" class=\"j-separator__label\"><slot>{{ label }}</slot></span>\n </div>\n</template>\n\n<style>\n@layer juxt {\n.j-separator {\n flex: none;\n background: var(--juxt-border-subtle);\n}\n\n.dark .j-separator,\n[data-theme='dark'] .j-separator {\n background: var(--juxt-border);\n}\n\n.j-separator--horizontal {\n width: 100%;\n height: 1px;\n}\n\n.j-separator--vertical {\n align-self: stretch;\n width: 1px;\n min-height: 1rem;\n}\n\n.j-separator--labelled {\n display: flex;\n align-items: center;\n gap: var(--juxt-space-3);\n height: auto;\n background: none !important;\n color: var(--juxt-fg-muted);\n font-family: var(--juxt-font-sans);\n font-size: var(--juxt-text-xs);\n}\n\n.j-separator--labelled::before,\n.j-separator--labelled::after {\n content: '';\n flex: 1;\n height: 1px;\n background: var(--juxt-border-subtle);\n}\n\n.dark .j-separator--labelled::before,\n.dark .j-separator--labelled::after,\n[data-theme='dark'] .j-separator--labelled::before,\n[data-theme='dark'] .j-separator--labelled::after {\n background: var(--juxt-border);\n}\n\n.j-separator__label {\n flex: none;\n}\n}\n</style>\n","<script setup lang=\"ts\">\nexport interface SeparatorProps {\n orientation?: 'horizontal' | 'vertical'\n /** Text in the middle of a horizontal separator, e.g. \"or\". */\n label?: string\n /**\n * Purely visual (the default). Set to false when the separator divides\n * meaningful groups and should be announced.\n */\n decorative?: boolean\n}\n\nwithDefaults(defineProps<SeparatorProps>(), { orientation: 'horizontal', decorative: true })\n\ndefineSlots<{ default?: () => unknown }>()\n</script>\n\n<template>\n <div\n class=\"j-separator\"\n :class=\"[`j-separator--${orientation}`, { 'j-separator--labelled': label || $slots.default }]\"\n :role=\"decorative ? 'none' : 'separator'\"\n :aria-orientation=\"decorative ? undefined : orientation\"\n >\n <span v-if=\"label || $slots.default\" class=\"j-separator__label\"><slot>{{ label }}</slot></span>\n </div>\n</template>\n\n<style>\n@layer juxt {\n.j-separator {\n flex: none;\n background: var(--juxt-border-subtle);\n}\n\n.dark .j-separator,\n[data-theme='dark'] .j-separator {\n background: var(--juxt-border);\n}\n\n.j-separator--horizontal {\n width: 100%;\n height: 1px;\n}\n\n.j-separator--vertical {\n align-self: stretch;\n width: 1px;\n min-height: 1rem;\n}\n\n.j-separator--labelled {\n display: flex;\n align-items: center;\n gap: var(--juxt-space-3);\n height: auto;\n background: none !important;\n color: var(--juxt-fg-muted);\n font-family: var(--juxt-font-sans);\n font-size: var(--juxt-text-xs);\n}\n\n.j-separator--labelled::before,\n.j-separator--labelled::after {\n content: '';\n flex: 1;\n height: 1px;\n background: var(--juxt-border-subtle);\n}\n\n.dark .j-separator--labelled::before,\n.dark .j-separator--labelled::after,\n[data-theme='dark'] .j-separator--labelled::before,\n[data-theme='dark'] .j-separator--labelled::after {\n background: var(--juxt-border);\n}\n\n.j-separator__label {\n flex: none;\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, useId } from 'vue'\nimport { FIELD_SELECTOR, useModal } from '../../composables/useModal'\nimport { IconX } from '../../icons'\nimport JPortal from '../primitives/JPortal.vue'\n\nexport interface SheetProps {\n title?: string\n description?: string\n /** Edge the sheet slides in from. */\n side?: 'right' | 'left' | 'bottom'\n /** Width (or height for `bottom`): 320, 400 or 560px. */\n size?: 'sm' | 'md' | 'lg'\n closeOnOverlay?: boolean\n closeOnEscape?: boolean\n hideClose?: boolean\n /** Accessible label when there is no visible title. */\n ariaLabel?: string\n}\n\nconst props = withDefaults(defineProps<SheetProps>(), {\n side: 'right',\n size: 'md',\n closeOnOverlay: true,\n closeOnEscape: true,\n})\n\nconst open = defineModel<boolean>('open', { default: false })\n\nconst emit = defineEmits<{\n /** After the leave transition finishes. */\n afterClose: []\n}>()\n\ndefineSlots<{\n default?: (props: { close: () => void }) => unknown\n header?: (props: { titleId: string, descriptionId: string }) => unknown\n footer?: (props: { close: () => void }) => unknown\n}>()\n\nconst id = useId()\nconst titleId = `j-${id}-title`\nconst descriptionId = `j-${id}-description`\nconst panel = ref<HTMLElement | null>(null)\n\nfunction close() {\n open.value = false\n}\n\nconst { nudging, onBackdropPointerDown, onBackdropClick } = useModal(panel, open, {\n closeOnEscape: () => props.closeOnEscape,\n closeOnOverlay: () => props.closeOnOverlay,\n close,\n focusSelector: `.j-sheet__body ${FIELD_SELECTOR}`,\n})\n\ndefineExpose({ close })\n</script>\n\n<template>\n <JPortal>\n <Transition name=\"j-sheet-overlay\">\n <div v-if=\"open\" class=\"j-sheet__overlay\" aria-hidden=\"true\" />\n </Transition>\n <Transition name=\"j-sheet\" :duration=\"{ enter: 280, leave: 180 }\" @after-leave=\"emit('afterClose')\">\n <div\n v-if=\"open\"\n class=\"j-sheet__positioner\"\n :class=\"`j-sheet__positioner--${side}`\"\n @pointerdown=\"onBackdropPointerDown\"\n @click=\"onBackdropClick\"\n >\n <div\n ref=\"panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n tabindex=\"-1\"\n class=\"j-sheet\"\n :class=\"[`j-sheet--${side}`, `j-sheet--${size}`, { 'is-nudging': nudging }]\"\n :aria-labelledby=\"title || $slots.header ? titleId : undefined\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"description ? descriptionId : undefined\"\n @animationend=\"nudging = false\"\n >\n <header v-if=\"title || description || $slots.header\" class=\"j-sheet__header\">\n <slot name=\"header\" :title-id=\"titleId\" :description-id=\"descriptionId\">\n <h2 :id=\"titleId\" class=\"j-sheet__title\">\n {{ title }}\n </h2>\n <p v-if=\"description\" :id=\"descriptionId\" class=\"j-sheet__description\">\n {{ description }}\n </p>\n </slot>\n </header>\n <div class=\"j-sheet__body\">\n <slot :close=\"close\" />\n </div>\n <footer v-if=\"$slots.footer\" class=\"j-sheet__footer\">\n <slot name=\"footer\" :close=\"close\" />\n </footer>\n <button v-if=\"!hideClose\" type=\"button\" class=\"j-sheet__close j-focusable\" aria-label=\"Close\" @click=\"close\">\n <IconX />\n </button>\n </div>\n </div>\n </Transition>\n </JPortal>\n</template>\n\n<style>\n@layer juxt {\n.j-sheet__overlay {\n position: fixed;\n inset: 0;\n z-index: var(--juxt-z-overlay);\n background: var(--juxt-overlay);\n}\n\n.j-sheet__positioner {\n position: fixed;\n inset: 0;\n z-index: var(--juxt-z-modal);\n display: flex;\n padding: var(--juxt-space-2);\n}\n\n.j-sheet__positioner--right {\n justify-content: flex-end;\n}\n\n.j-sheet__positioner--left {\n justify-content: flex-start;\n}\n\n.j-sheet__positioner--bottom {\n align-items: flex-end;\n padding-bottom: max(var(--juxt-space-2), env(safe-area-inset-bottom));\n}\n\n/* A floating panel with a small inset from the edges — calmer than edge-to-edge. */\n.j-sheet {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n border: 1px solid var(--juxt-border);\n border-radius: var(--juxt-radius-lg);\n background: var(--juxt-surface-raised);\n box-shadow: var(--juxt-shadow-lg);\n color: var(--juxt-fg);\n font-family: var(--juxt-font-sans);\n outline: none;\n}\n\n.j-sheet--right,\n.j-sheet--left {\n height: 100%;\n}\n\n.j-sheet--right.j-sheet--sm,\n.j-sheet--left.j-sheet--sm {\n max-width: 20rem;\n}\n\n.j-sheet--right.j-sheet--md,\n.j-sheet--left.j-sheet--md {\n max-width: 25rem;\n}\n\n.j-sheet--right.j-sheet--lg,\n.j-sheet--left.j-sheet--lg {\n max-width: 35rem;\n}\n\n.j-sheet--bottom.j-sheet--sm {\n max-height: 20rem;\n}\n\n.j-sheet--bottom.j-sheet--md {\n max-height: 25rem;\n}\n\n.j-sheet--bottom.j-sheet--lg {\n max-height: min(35rem, calc(100dvh - var(--juxt-space-12)));\n}\n\n.j-sheet__header {\n display: flex;\n flex-direction: column;\n gap: var(--juxt-space-1-5);\n padding: var(--juxt-space-5) var(--juxt-space-12) var(--juxt-space-4) var(--juxt-space-5);\n border-bottom: 1px solid var(--juxt-border-subtle);\n}\n\n.dark .j-sheet__header,\n[data-theme='dark'] .j-sheet__header {\n border-bottom-color: var(--juxt-border);\n}\n\n.j-sheet__title {\n margin: 0;\n font-size: var(--juxt-text-lg);\n font-weight: var(--juxt-weight-semibold);\n line-height: var(--juxt-leading-snug);\n letter-spacing: var(--juxt-tracking-tight);\n}\n\n.j-sheet__description {\n margin: 0;\n color: var(--juxt-fg-secondary);\n font-size: var(--juxt-text-md);\n line-height: var(--juxt-leading-normal);\n}\n\n.j-sheet__body {\n flex: 1;\n min-height: 0;\n padding: var(--juxt-space-5);\n overflow-y: auto;\n overscroll-behavior: contain;\n color: var(--juxt-fg-secondary);\n font-size: var(--juxt-text-md);\n line-height: var(--juxt-leading-normal);\n}\n\n.j-sheet__footer {\n display: flex;\n justify-content: flex-end;\n gap: var(--juxt-space-2);\n padding: var(--juxt-space-4) var(--juxt-space-5);\n border-top: 1px solid var(--juxt-border-subtle);\n}\n\n.dark .j-sheet__footer,\n[data-theme='dark'] .j-sheet__footer {\n border-top-color: var(--juxt-border);\n}\n\n.j-sheet__close {\n position: absolute;\n top: var(--juxt-space-3);\n right: var(--juxt-space-3);\n display: grid;\n place-items: center;\n width: var(--juxt-control-sm);\n height: var(--juxt-control-sm);\n padding: 0;\n border: 0;\n border-radius: var(--juxt-radius-sm);\n background: transparent;\n color: var(--juxt-fg-muted);\n cursor: pointer;\n transition-property: background-color, color, outline-color, outline-offset;\n}\n\n.j-sheet__close:hover {\n background: var(--juxt-surface-hover);\n color: var(--juxt-fg);\n}\n\n.j-sheet__close > svg {\n width: 1rem;\n height: 1rem;\n}\n\n.j-sheet.is-nudging {\n animation: j-sheet-nudge 220ms var(--juxt-ease-out);\n}\n\n@keyframes j-sheet-nudge {\n 40% {\n transform: scale(1.006);\n }\n}\n\n/* Motion: the backdrop fades; the sheet slides in from its edge. */\n.j-sheet-overlay-enter-active {\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-out);\n}\n\n.j-sheet-overlay-leave-active {\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-in);\n}\n\n.j-sheet-overlay-enter-from,\n.j-sheet-overlay-leave-to {\n opacity: 0;\n}\n\n.j-sheet-enter-active .j-sheet {\n transition: transform 280ms var(--juxt-ease-out);\n}\n\n.j-sheet-leave-active .j-sheet {\n transition: transform var(--juxt-duration-normal) var(--juxt-ease-in);\n}\n\n.j-sheet-enter-from .j-sheet--right,\n.j-sheet-leave-to .j-sheet--right {\n transform: translateX(calc(100% + var(--juxt-space-4)));\n}\n\n.j-sheet-enter-from .j-sheet--left,\n.j-sheet-leave-to .j-sheet--left {\n transform: translateX(calc(-100% - var(--juxt-space-4)));\n}\n\n.j-sheet-enter-from .j-sheet--bottom,\n.j-sheet-leave-to .j-sheet--bottom {\n transform: translateY(calc(100% + var(--juxt-space-4)));\n}\n\n/* Reduced motion: the sheet fades in place instead of travelling. */\n@media (prefers-reduced-motion: reduce) {\n .j-sheet-enter-active .j-sheet,\n .j-sheet-leave-active .j-sheet {\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-standard);\n }\n\n .j-sheet-enter-from .j-sheet,\n .j-sheet-leave-to .j-sheet {\n opacity: 0;\n transform: none !important;\n }\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { ref, useId } from 'vue'\nimport { FIELD_SELECTOR, useModal } from '../../composables/useModal'\nimport { IconX } from '../../icons'\nimport JPortal from '../primitives/JPortal.vue'\n\nexport interface SheetProps {\n title?: string\n description?: string\n /** Edge the sheet slides in from. */\n side?: 'right' | 'left' | 'bottom'\n /** Width (or height for `bottom`): 320, 400 or 560px. */\n size?: 'sm' | 'md' | 'lg'\n closeOnOverlay?: boolean\n closeOnEscape?: boolean\n hideClose?: boolean\n /** Accessible label when there is no visible title. */\n ariaLabel?: string\n}\n\nconst props = withDefaults(defineProps<SheetProps>(), {\n side: 'right',\n size: 'md',\n closeOnOverlay: true,\n closeOnEscape: true,\n})\n\nconst open = defineModel<boolean>('open', { default: false })\n\nconst emit = defineEmits<{\n /** After the leave transition finishes. */\n afterClose: []\n}>()\n\ndefineSlots<{\n default?: (props: { close: () => void }) => unknown\n header?: (props: { titleId: string, descriptionId: string }) => unknown\n footer?: (props: { close: () => void }) => unknown\n}>()\n\nconst id = useId()\nconst titleId = `j-${id}-title`\nconst descriptionId = `j-${id}-description`\nconst panel = ref<HTMLElement | null>(null)\n\nfunction close() {\n open.value = false\n}\n\nconst { nudging, onBackdropPointerDown, onBackdropClick } = useModal(panel, open, {\n closeOnEscape: () => props.closeOnEscape,\n closeOnOverlay: () => props.closeOnOverlay,\n close,\n focusSelector: `.j-sheet__body ${FIELD_SELECTOR}`,\n})\n\ndefineExpose({ close })\n</script>\n\n<template>\n <JPortal>\n <Transition name=\"j-sheet-overlay\">\n <div v-if=\"open\" class=\"j-sheet__overlay\" aria-hidden=\"true\" />\n </Transition>\n <Transition name=\"j-sheet\" :duration=\"{ enter: 280, leave: 180 }\" @after-leave=\"emit('afterClose')\">\n <div\n v-if=\"open\"\n class=\"j-sheet__positioner\"\n :class=\"`j-sheet__positioner--${side}`\"\n @pointerdown=\"onBackdropPointerDown\"\n @click=\"onBackdropClick\"\n >\n <div\n ref=\"panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n tabindex=\"-1\"\n class=\"j-sheet\"\n :class=\"[`j-sheet--${side}`, `j-sheet--${size}`, { 'is-nudging': nudging }]\"\n :aria-labelledby=\"title || $slots.header ? titleId : undefined\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"description ? descriptionId : undefined\"\n @animationend=\"nudging = false\"\n >\n <header v-if=\"title || description || $slots.header\" class=\"j-sheet__header\">\n <slot name=\"header\" :title-id=\"titleId\" :description-id=\"descriptionId\">\n <h2 :id=\"titleId\" class=\"j-sheet__title\">\n {{ title }}\n </h2>\n <p v-if=\"description\" :id=\"descriptionId\" class=\"j-sheet__description\">\n {{ description }}\n </p>\n </slot>\n </header>\n <div class=\"j-sheet__body\">\n <slot :close=\"close\" />\n </div>\n <footer v-if=\"$slots.footer\" class=\"j-sheet__footer\">\n <slot name=\"footer\" :close=\"close\" />\n </footer>\n <button v-if=\"!hideClose\" type=\"button\" class=\"j-sheet__close j-focusable\" aria-label=\"Close\" @click=\"close\">\n <IconX />\n </button>\n </div>\n </div>\n </Transition>\n </JPortal>\n</template>\n\n<style>\n@layer juxt {\n.j-sheet__overlay {\n position: fixed;\n inset: 0;\n z-index: var(--juxt-z-overlay);\n background: var(--juxt-overlay);\n}\n\n.j-sheet__positioner {\n position: fixed;\n inset: 0;\n z-index: var(--juxt-z-modal);\n display: flex;\n padding: var(--juxt-space-2);\n}\n\n.j-sheet__positioner--right {\n justify-content: flex-end;\n}\n\n.j-sheet__positioner--left {\n justify-content: flex-start;\n}\n\n.j-sheet__positioner--bottom {\n align-items: flex-end;\n padding-bottom: max(var(--juxt-space-2), env(safe-area-inset-bottom));\n}\n\n/* A floating panel with a small inset from the edges — calmer than edge-to-edge. */\n.j-sheet {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n border: 1px solid var(--juxt-border);\n border-radius: var(--juxt-radius-lg);\n background: var(--juxt-surface-raised);\n box-shadow: var(--juxt-shadow-lg);\n color: var(--juxt-fg);\n font-family: var(--juxt-font-sans);\n outline: none;\n}\n\n.j-sheet--right,\n.j-sheet--left {\n height: 100%;\n}\n\n.j-sheet--right.j-sheet--sm,\n.j-sheet--left.j-sheet--sm {\n max-width: 20rem;\n}\n\n.j-sheet--right.j-sheet--md,\n.j-sheet--left.j-sheet--md {\n max-width: 25rem;\n}\n\n.j-sheet--right.j-sheet--lg,\n.j-sheet--left.j-sheet--lg {\n max-width: 35rem;\n}\n\n.j-sheet--bottom.j-sheet--sm {\n max-height: 20rem;\n}\n\n.j-sheet--bottom.j-sheet--md {\n max-height: 25rem;\n}\n\n.j-sheet--bottom.j-sheet--lg {\n max-height: min(35rem, calc(100dvh - var(--juxt-space-12)));\n}\n\n.j-sheet__header {\n display: flex;\n flex-direction: column;\n gap: var(--juxt-space-1-5);\n padding: var(--juxt-space-5) var(--juxt-space-12) var(--juxt-space-4) var(--juxt-space-5);\n border-bottom: 1px solid var(--juxt-border-subtle);\n}\n\n.dark .j-sheet__header,\n[data-theme='dark'] .j-sheet__header {\n border-bottom-color: var(--juxt-border);\n}\n\n.j-sheet__title {\n margin: 0;\n font-size: var(--juxt-text-lg);\n font-weight: var(--juxt-weight-semibold);\n line-height: var(--juxt-leading-snug);\n letter-spacing: var(--juxt-tracking-tight);\n}\n\n.j-sheet__description {\n margin: 0;\n color: var(--juxt-fg-secondary);\n font-size: var(--juxt-text-md);\n line-height: var(--juxt-leading-normal);\n}\n\n.j-sheet__body {\n flex: 1;\n min-height: 0;\n padding: var(--juxt-space-5);\n overflow-y: auto;\n overscroll-behavior: contain;\n color: var(--juxt-fg-secondary);\n font-size: var(--juxt-text-md);\n line-height: var(--juxt-leading-normal);\n}\n\n.j-sheet__footer {\n display: flex;\n justify-content: flex-end;\n gap: var(--juxt-space-2);\n padding: var(--juxt-space-4) var(--juxt-space-5);\n border-top: 1px solid var(--juxt-border-subtle);\n}\n\n.dark .j-sheet__footer,\n[data-theme='dark'] .j-sheet__footer {\n border-top-color: var(--juxt-border);\n}\n\n.j-sheet__close {\n position: absolute;\n top: var(--juxt-space-3);\n right: var(--juxt-space-3);\n display: grid;\n place-items: center;\n width: var(--juxt-control-sm);\n height: var(--juxt-control-sm);\n padding: 0;\n border: 0;\n border-radius: var(--juxt-radius-sm);\n background: transparent;\n color: var(--juxt-fg-muted);\n cursor: pointer;\n transition-property: background-color, color, outline-color, outline-offset;\n}\n\n.j-sheet__close:hover {\n background: var(--juxt-surface-hover);\n color: var(--juxt-fg);\n}\n\n.j-sheet__close > svg {\n width: 1rem;\n height: 1rem;\n}\n\n.j-sheet.is-nudging {\n animation: j-sheet-nudge 220ms var(--juxt-ease-out);\n}\n\n@keyframes j-sheet-nudge {\n 40% {\n transform: scale(1.006);\n }\n}\n\n/* Motion: the backdrop fades; the sheet slides in from its edge. */\n.j-sheet-overlay-enter-active {\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-out);\n}\n\n.j-sheet-overlay-leave-active {\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-in);\n}\n\n.j-sheet-overlay-enter-from,\n.j-sheet-overlay-leave-to {\n opacity: 0;\n}\n\n.j-sheet-enter-active .j-sheet {\n transition: transform 280ms var(--juxt-ease-out);\n}\n\n.j-sheet-leave-active .j-sheet {\n transition: transform var(--juxt-duration-normal) var(--juxt-ease-in);\n}\n\n.j-sheet-enter-from .j-sheet--right,\n.j-sheet-leave-to .j-sheet--right {\n transform: translateX(calc(100% + var(--juxt-space-4)));\n}\n\n.j-sheet-enter-from .j-sheet--left,\n.j-sheet-leave-to .j-sheet--left {\n transform: translateX(calc(-100% - var(--juxt-space-4)));\n}\n\n.j-sheet-enter-from .j-sheet--bottom,\n.j-sheet-leave-to .j-sheet--bottom {\n transform: translateY(calc(100% + var(--juxt-space-4)));\n}\n\n/* Reduced motion: the sheet fades in place instead of travelling. */\n@media (prefers-reduced-motion: reduce) {\n .j-sheet-enter-active .j-sheet,\n .j-sheet-leave-active .j-sheet {\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-standard);\n }\n\n .j-sheet-enter-from .j-sheet,\n .j-sheet-leave-to .j-sheet {\n opacity: 0;\n transform: none !important;\n }\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nexport interface SkeletonProps {\n /** `text` renders one or more lines; `circle` suits avatars. */\n shape?: 'rect' | 'text' | 'circle'\n /** Number of lines for `text`. The last one is shorter, like a real paragraph. */\n lines?: number\n /** Any CSS length, e.g. `12rem` or `100%`. */\n width?: string\n height?: string\n}\n\nconst props = withDefaults(defineProps<SkeletonProps>(), { shape: 'rect', lines: 1 })\n\nconst style = computed(() => ({ width: props.width, height: props.height }))\n</script>\n\n<template>\n <span v-if=\"shape === 'text'\" class=\"j-skeleton-lines\" :style=\"{ width }\" aria-hidden=\"true\">\n <span\n v-for=\"i in lines\"\n :key=\"i\"\n class=\"j-skeleton j-skeleton--text\"\n :style=\"{ width: i === lines && lines > 1 ? '62%' : undefined, height }\"\n />\n </span>\n <span v-else class=\"j-skeleton\" :class=\"`j-skeleton--${shape}`\" :style=\"style\" aria-hidden=\"true\" />\n</template>\n\n<style>\n@layer juxt {\n.j-skeleton {\n display: block;\n width: 100%;\n height: 1rem;\n border-radius: var(--juxt-radius-sm);\n background: var(--juxt-surface-active);\n animation: j-skeleton-pulse 1.8s var(--juxt-ease-standard) infinite;\n}\n\n.j-skeleton--circle {\n width: 2rem;\n height: 2rem;\n border-radius: var(--juxt-radius-full);\n}\n\n.j-skeleton--text {\n height: 0.75rem;\n border-radius: var(--juxt-radius-xs);\n}\n\n.j-skeleton-lines {\n display: flex;\n flex-direction: column;\n gap: var(--juxt-space-2);\n width: 100%;\n}\n\n/* A slow breath, not a shimmer — loading shouldn't compete for attention. */\n@keyframes j-skeleton-pulse {\n 50% {\n opacity: 0.5;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .j-skeleton {\n animation: none;\n }\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nexport interface SkeletonProps {\n /** `text` renders one or more lines; `circle` suits avatars. */\n shape?: 'rect' | 'text' | 'circle'\n /** Number of lines for `text`. The last one is shorter, like a real paragraph. */\n lines?: number\n /** Any CSS length, e.g. `12rem` or `100%`. */\n width?: string\n height?: string\n}\n\nconst props = withDefaults(defineProps<SkeletonProps>(), { shape: 'rect', lines: 1 })\n\nconst style = computed(() => ({ width: props.width, height: props.height }))\n</script>\n\n<template>\n <span v-if=\"shape === 'text'\" class=\"j-skeleton-lines\" :style=\"{ width }\" aria-hidden=\"true\">\n <span\n v-for=\"i in lines\"\n :key=\"i\"\n class=\"j-skeleton j-skeleton--text\"\n :style=\"{ width: i === lines && lines > 1 ? '62%' : undefined, height }\"\n />\n </span>\n <span v-else class=\"j-skeleton\" :class=\"`j-skeleton--${shape}`\" :style=\"style\" aria-hidden=\"true\" />\n</template>\n\n<style>\n@layer juxt {\n.j-skeleton {\n display: block;\n width: 100%;\n height: 1rem;\n border-radius: var(--juxt-radius-sm);\n background: var(--juxt-surface-active);\n animation: j-skeleton-pulse 1.8s var(--juxt-ease-standard) infinite;\n}\n\n.j-skeleton--circle {\n width: 2rem;\n height: 2rem;\n border-radius: var(--juxt-radius-full);\n}\n\n.j-skeleton--text {\n height: 0.75rem;\n border-radius: var(--juxt-radius-xs);\n}\n\n.j-skeleton-lines {\n display: flex;\n flex-direction: column;\n gap: var(--juxt-space-2);\n width: 100%;\n}\n\n/* A slow breath, not a shimmer — loading shouldn't compete for attention. */\n@keyframes j-skeleton-pulse {\n 50% {\n opacity: 0.5;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .j-skeleton {\n animation: none;\n }\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { Spinner } from '../../icons'\n\nexport interface SpinnerProps {\n size?: 'sm' | 'md' | 'lg'\n /** Announced to screen readers. Set to an empty string when the context already says it. */\n label?: string\n}\n\nconst props = withDefaults(defineProps<SpinnerProps>(), { size: 'md', label: 'Loading' })\n\nconst pixels = computed(() => ({ sm: 14, md: 16, lg: 20 })[props.size])\n</script>\n\n<template>\n <span class=\"j-spinner-wrap\" :role=\"label ? 'status' : undefined\">\n <Spinner :size=\"pixels\" />\n <span v-if=\"label\" class=\"j-sr-only\">{{ label }}</span>\n </span>\n</template>\n\n<style>\n@layer juxt {\n.j-spinner-wrap {\n display: inline-flex;\n color: var(--juxt-fg-muted);\n vertical-align: middle;\n}\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { Spinner } from '../../icons'\n\nexport interface SpinnerProps {\n size?: 'sm' | 'md' | 'lg'\n /** Announced to screen readers. Set to an empty string when the context already says it. */\n label?: string\n}\n\nconst props = withDefaults(defineProps<SpinnerProps>(), { size: 'md', label: 'Loading' })\n\nconst pixels = computed(() => ({ sm: 14, md: 16, lg: 20 })[props.size])\n</script>\n\n<template>\n <span class=\"j-spinner-wrap\" :role=\"label ? 'status' : undefined\">\n <Spinner :size=\"pixels\" />\n <span v-if=\"label\" class=\"j-sr-only\">{{ label }}</span>\n </span>\n</template>\n\n<style>\n@layer juxt {\n.j-spinner-wrap {\n display: inline-flex;\n color: var(--juxt-fg-muted);\n vertical-align: middle;\n}\n}\n</style>\n","<script setup lang=\"ts\">\r\nimport { computed, ref, useId } from 'vue'\r\n\r\nexport interface SwitchProps {\r\n label?: string\r\n description?: string\r\n disabled?: boolean\r\n size?: 'sm' | 'md'\r\n /** Submits `value` under `name` in a form when on. */\r\n name?: string\r\n value?: string\r\n id?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<SwitchProps>(), { size: 'md', value: 'on' })\r\nconst model = defineModel<boolean>({ default: false })\r\n\r\ndefineSlots<{ default?: () => unknown, description?: () => unknown }>()\r\n\r\nconst generated = useId()\r\nconst id = computed(() => props.id ?? `j-${generated}`)\r\nconst labelId = computed(() => `${id.value}-label`)\r\nconst descriptionId = computed(() => `${id.value}-description`)\r\nconst button = ref<HTMLButtonElement | null>(null)\r\n\r\nfunction toggle() {\r\n if (props.disabled) return\r\n model.value = !model.value\r\n}\r\n\r\ndefineExpose({ focus: () => button.value?.focus() })\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-switch\"\r\n :class=\"[$attrs.class, `j-switch--${size}`, { 'is-disabled': disabled }]\"\r\n :style=\"$attrs.style as any\"\r\n :data-state=\"model ? 'on' : 'off'\"\r\n >\r\n <button\r\n :id=\"id\"\r\n ref=\"button\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"button\"\r\n role=\"switch\"\r\n class=\"j-switch__track\"\r\n :aria-checked=\"model\"\r\n :aria-labelledby=\"label || $slots.default ? labelId : undefined\"\r\n :aria-describedby=\"description || $slots.description ? descriptionId : undefined\"\r\n :disabled=\"disabled\"\r\n @click=\"toggle\"\r\n >\r\n <span class=\"j-switch__thumb\" />\r\n </button>\r\n <input v-if=\"name && model\" type=\"hidden\" :name=\"name\" :value=\"value\" />\r\n <span v-if=\"label || $slots.default || description || $slots.description\" class=\"j-switch__text\">\r\n <label v-if=\"label || $slots.default\" :id=\"labelId\" :for=\"id\" class=\"j-switch__label\">\r\n <slot>{{ label }}</slot>\r\n </label>\r\n <span v-if=\"description || $slots.description\" :id=\"descriptionId\" class=\"j-switch__description\">\r\n <slot name=\"description\">{{ description }}</slot>\r\n </span>\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-switch {\r\n --j-switch-w: 2rem;\r\n --j-switch-h: 1.125rem;\r\n --j-switch-pad: 2px;\r\n --j-switch-thumb: calc(var(--j-switch-h) - var(--j-switch-pad) * 2);\r\n\r\n display: inline-flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-switch--sm {\r\n --j-switch-w: 1.625rem;\r\n --j-switch-h: 0.9375rem;\r\n}\r\n\r\n.j-switch__track {\r\n position: relative;\r\n flex: none;\r\n width: var(--j-switch-w);\r\n height: var(--j-switch-h);\r\n margin: calc((var(--juxt-text-md) * var(--juxt-leading-snug) - var(--j-switch-h)) / 2) 0 0;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-border-strong);\r\n box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.08);\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n background-color var(--juxt-duration-normal) var(--juxt-ease-standard),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-switch__track:hover:not(:disabled) {\r\n background: var(--juxt-fg-disabled);\r\n}\r\n\r\n.j-switch__track:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__track {\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__track:hover:not(:disabled) {\r\n background: var(--juxt-accent-hover);\r\n}\r\n\r\n.j-switch__thumb {\r\n position: absolute;\r\n top: var(--j-switch-pad);\r\n left: var(--j-switch-pad);\r\n width: var(--j-switch-thumb);\r\n height: var(--j-switch-thumb);\r\n border-radius: var(--juxt-radius-full);\r\n background: #fff;\r\n box-shadow: 0 1px 2px rgb(0 0 0 / 0.2), 0 0 0 0.5px rgb(0 0 0 / 0.04);\r\n transition:\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out),\r\n width var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__thumb {\r\n transform: translateX(calc(var(--j-switch-w) - var(--j-switch-thumb) - var(--j-switch-pad) * 2));\r\n}\r\n\r\n/* A whisper of stretch while pressed, so the thumb feels held. */\r\n.j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n width: calc(var(--j-switch-thumb) + 3px);\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n transform: translateX(calc(var(--j-switch-w) - var(--j-switch-thumb) - var(--j-switch-pad) * 2 - 3px));\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n width: var(--j-switch-thumb);\r\n }\r\n\r\n .j-switch[data-state='on'] .j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n transform: translateX(calc(var(--j-switch-w) - var(--j-switch-thumb) - var(--j-switch-pad) * 2));\r\n }\r\n}\r\n\r\n.j-switch__text {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-switch__label {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-switch__description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-switch.is-disabled .j-switch__track {\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n}\r\n\r\n.j-switch.is-disabled .j-switch__label {\r\n color: var(--juxt-fg-muted);\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, ref, useId } from 'vue'\r\n\r\nexport interface SwitchProps {\r\n label?: string\r\n description?: string\r\n disabled?: boolean\r\n size?: 'sm' | 'md'\r\n /** Submits `value` under `name` in a form when on. */\r\n name?: string\r\n value?: string\r\n id?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<SwitchProps>(), { size: 'md', value: 'on' })\r\nconst model = defineModel<boolean>({ default: false })\r\n\r\ndefineSlots<{ default?: () => unknown, description?: () => unknown }>()\r\n\r\nconst generated = useId()\r\nconst id = computed(() => props.id ?? `j-${generated}`)\r\nconst labelId = computed(() => `${id.value}-label`)\r\nconst descriptionId = computed(() => `${id.value}-description`)\r\nconst button = ref<HTMLButtonElement | null>(null)\r\n\r\nfunction toggle() {\r\n if (props.disabled) return\r\n model.value = !model.value\r\n}\r\n\r\ndefineExpose({ focus: () => button.value?.focus() })\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-switch\"\r\n :class=\"[$attrs.class, `j-switch--${size}`, { 'is-disabled': disabled }]\"\r\n :style=\"$attrs.style as any\"\r\n :data-state=\"model ? 'on' : 'off'\"\r\n >\r\n <button\r\n :id=\"id\"\r\n ref=\"button\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"button\"\r\n role=\"switch\"\r\n class=\"j-switch__track\"\r\n :aria-checked=\"model\"\r\n :aria-labelledby=\"label || $slots.default ? labelId : undefined\"\r\n :aria-describedby=\"description || $slots.description ? descriptionId : undefined\"\r\n :disabled=\"disabled\"\r\n @click=\"toggle\"\r\n >\r\n <span class=\"j-switch__thumb\" />\r\n </button>\r\n <input v-if=\"name && model\" type=\"hidden\" :name=\"name\" :value=\"value\" />\r\n <span v-if=\"label || $slots.default || description || $slots.description\" class=\"j-switch__text\">\r\n <label v-if=\"label || $slots.default\" :id=\"labelId\" :for=\"id\" class=\"j-switch__label\">\r\n <slot>{{ label }}</slot>\r\n </label>\r\n <span v-if=\"description || $slots.description\" :id=\"descriptionId\" class=\"j-switch__description\">\r\n <slot name=\"description\">{{ description }}</slot>\r\n </span>\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-switch {\r\n --j-switch-w: 2rem;\r\n --j-switch-h: 1.125rem;\r\n --j-switch-pad: 2px;\r\n --j-switch-thumb: calc(var(--j-switch-h) - var(--j-switch-pad) * 2);\r\n\r\n display: inline-flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-switch--sm {\r\n --j-switch-w: 1.625rem;\r\n --j-switch-h: 0.9375rem;\r\n}\r\n\r\n.j-switch__track {\r\n position: relative;\r\n flex: none;\r\n width: var(--j-switch-w);\r\n height: var(--j-switch-h);\r\n margin: calc((var(--juxt-text-md) * var(--juxt-leading-snug) - var(--j-switch-h)) / 2) 0 0;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-border-strong);\r\n box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.08);\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n background-color var(--juxt-duration-normal) var(--juxt-ease-standard),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-switch__track:hover:not(:disabled) {\r\n background: var(--juxt-fg-disabled);\r\n}\r\n\r\n.j-switch__track:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__track {\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__track:hover:not(:disabled) {\r\n background: var(--juxt-accent-hover);\r\n}\r\n\r\n.j-switch__thumb {\r\n position: absolute;\r\n top: var(--j-switch-pad);\r\n left: var(--j-switch-pad);\r\n width: var(--j-switch-thumb);\r\n height: var(--j-switch-thumb);\r\n border-radius: var(--juxt-radius-full);\r\n background: #fff;\r\n box-shadow: 0 1px 2px rgb(0 0 0 / 0.2), 0 0 0 0.5px rgb(0 0 0 / 0.04);\r\n transition:\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out),\r\n width var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__thumb {\r\n transform: translateX(calc(var(--j-switch-w) - var(--j-switch-thumb) - var(--j-switch-pad) * 2));\r\n}\r\n\r\n/* A whisper of stretch while pressed, so the thumb feels held. */\r\n.j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n width: calc(var(--j-switch-thumb) + 3px);\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n transform: translateX(calc(var(--j-switch-w) - var(--j-switch-thumb) - var(--j-switch-pad) * 2 - 3px));\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n width: var(--j-switch-thumb);\r\n }\r\n\r\n .j-switch[data-state='on'] .j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n transform: translateX(calc(var(--j-switch-w) - var(--j-switch-thumb) - var(--j-switch-pad) * 2));\r\n }\r\n}\r\n\r\n.j-switch__text {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-switch__label {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-switch__description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-switch.is-disabled .j-switch__track {\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n}\r\n\r\n.j-switch.is-disabled .j-switch__label {\r\n color: var(--juxt-fg-muted);\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch } from 'vue'\r\nimport { focusElement } from '../../utils/dom'\r\n\r\nexport interface TabItem {\r\n value: string\r\n label: string\r\n disabled?: boolean\r\n}\r\n\r\nexport interface TabsProps {\r\n items: TabItem[]\r\n /** `line` for page sections, `pill` for compact view switches. */\r\n variant?: 'line' | 'pill'\r\n size?: 'sm' | 'md'\r\n /** Accessible name for the tab list. */\r\n label?: string\r\n /** Stretch tabs to fill the width. */\r\n fill?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<TabsProps>(), { variant: 'line', size: 'md' })\r\n\r\nconst model = defineModel<string>()\r\n\r\ndefineSlots<{\r\n /** Custom tab label. */\r\n tab?: (props: { item: TabItem, selected: boolean }) => unknown\r\n /** Panels are named after each item's `value`. */\r\n [value: string]: ((props: { item: TabItem, selected: boolean }) => unknown) | undefined\r\n}>()\r\n\r\nconst id = `j-${useId()}`\r\nconst tabId = (value: string) => `${id}-tab-${value}`\r\nconst panelId = (value: string) => `${id}-panel-${value}`\r\n\r\nconst selected = computed(() => model.value ?? props.items.find(item => !item.disabled)?.value)\r\n\r\nconst list = ref<HTMLElement | null>(null)\r\nconst indicator = ref({ x: 0, width: 0, ready: false })\r\n// The indicator glides only for deliberate changes, never for layout shifts.\r\nconst animated = ref(false)\r\n\r\nfunction measure() {\r\n const el = list.value?.querySelector<HTMLElement>('[role=\"tab\"][aria-selected=\"true\"]')\r\n indicator.value = el ? { x: el.offsetLeft, width: el.offsetWidth, ready: true } : { ...indicator.value, ready: false }\r\n}\r\n\r\nfunction measureWithoutMotion() {\r\n animated.value = false\r\n measure()\r\n requestAnimationFrame(() => requestAnimationFrame(() => (animated.value = true)))\r\n}\r\n\r\nlet observer: ResizeObserver | undefined\r\nonMounted(() => {\r\n measureWithoutMotion()\r\n observer = new ResizeObserver(measureWithoutMotion)\r\n if (list.value) observer.observe(list.value)\r\n // Web fonts change tab widths once they arrive.\r\n document.fonts?.ready.then(measureWithoutMotion)\r\n})\r\nonBeforeUnmount(() => observer?.disconnect())\r\n\r\nwatch(selected, () => nextTick(measure), { flush: 'post' })\r\nwatch(() => props.items, () => nextTick(measure), { deep: true, flush: 'post' })\r\n\r\nfunction select(item: TabItem) {\r\n if (item.disabled) return\r\n model.value = item.value\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const enabled = props.items.filter(item => !item.disabled)\r\n if (!enabled.length) return\r\n const current = enabled.findIndex(item => item.value === selected.value)\r\n let next: number | undefined\r\n if (event.key === 'ArrowRight') next = (current + 1) % enabled.length\r\n else if (event.key === 'ArrowLeft') next = (current - 1 + enabled.length) % enabled.length\r\n else if (event.key === 'Home') next = 0\r\n else if (event.key === 'End') next = enabled.length - 1\r\n if (next === undefined) return\r\n event.preventDefault()\r\n const item = enabled[next]!\r\n select(item)\r\n nextTick(() => focusElement(document.getElementById(tabId(item.value))))\r\n}\r\n\r\nconst indicatorStyle = computed(() => ({\r\n transform: `translateX(${indicator.value.x}px)`,\r\n width: `${indicator.value.width}px`,\r\n}))\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-tabs\" :class=\"[`j-tabs--${variant}`, `j-tabs--${size}`, { 'j-tabs--fill': fill }]\">\r\n <div\r\n ref=\"list\"\r\n role=\"tablist\"\r\n aria-orientation=\"horizontal\"\r\n :aria-label=\"label\"\r\n class=\"j-tabs__list\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <span\r\n class=\"j-tabs__indicator\"\r\n :class=\"{ 'is-ready': indicator.ready, 'is-animated': animated }\"\r\n :style=\"indicatorStyle\"\r\n aria-hidden=\"true\"\r\n />\r\n <button\r\n v-for=\"item in items\"\r\n :id=\"tabId(item.value)\"\r\n :key=\"item.value\"\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"j-tabs__tab\"\r\n :aria-selected=\"item.value === selected\"\r\n :aria-controls=\"panelId(item.value)\"\r\n :tabindex=\"item.value === selected ? 0 : -1\"\r\n :disabled=\"item.disabled\"\r\n @click=\"select(item)\"\r\n >\r\n <slot name=\"tab\" :item=\"item\" :selected=\"item.value === selected\">\r\n {{ item.label }}\r\n </slot>\r\n </button>\r\n </div>\r\n <template v-for=\"item in items\" :key=\"item.value\">\r\n <div\r\n v-if=\"$slots[item.value]\"\r\n :id=\"panelId(item.value)\"\r\n role=\"tabpanel\"\r\n class=\"j-tabs__panel\"\r\n :aria-labelledby=\"tabId(item.value)\"\r\n :hidden=\"item.value !== selected\"\r\n tabindex=\"0\"\r\n >\r\n <slot :name=\"item.value\" :item=\"item\" :selected=\"item.value === selected\" />\r\n </div>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-tabs {\r\n --j-tab-height: var(--juxt-control-md);\r\n --j-tab-font: var(--juxt-text-md);\r\n\r\n display: flex;\r\n flex-direction: column;\r\n min-width: 0;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-tabs--sm {\r\n --j-tab-height: var(--juxt-control-sm);\r\n --j-tab-font: var(--juxt-text-sm);\r\n}\r\n\r\n.j-tabs__list {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n max-width: 100%;\r\n overflow-x: auto;\r\n scrollbar-width: none;\r\n}\r\n\r\n.j-tabs__list::-webkit-scrollbar {\r\n display: none;\r\n}\r\n\r\n.j-tabs__tab {\r\n position: relative;\r\n z-index: 1;\r\n display: inline-flex;\r\n flex: none;\r\n align-items: center;\r\n justify-content: center;\r\n gap: var(--juxt-space-1-5);\r\n height: var(--j-tab-height);\r\n padding: 0 var(--juxt-space-3);\r\n border: 0;\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n font: inherit;\r\n font-size: var(--j-tab-font);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n white-space: nowrap;\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: -2px;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-tabs__tab:hover:not(:disabled) {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-tabs__tab[aria-selected='true'] {\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-tabs__tab:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n}\r\n\r\n.j-tabs__tab:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-tabs--fill .j-tabs__tab {\r\n flex: 1;\r\n}\r\n\r\n.j-tabs__indicator {\r\n position: absolute;\r\n left: 0;\r\n opacity: 0;\r\n pointer-events: none;\r\n}\r\n\r\n.j-tabs__indicator.is-ready {\r\n opacity: 1;\r\n}\r\n\r\n.j-tabs__indicator.is-animated {\r\n transition:\r\n transform var(--juxt-duration-slow) var(--juxt-ease-out),\r\n width var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-tabs__indicator.is-animated {\r\n transition: none;\r\n }\r\n}\r\n\r\n/* Line */\r\n.j-tabs--line .j-tabs__list {\r\n gap: var(--juxt-space-1);\r\n box-shadow: inset 0 -1px 0 var(--juxt-border);\r\n}\r\n\r\n.j-tabs--line .j-tabs__tab {\r\n padding: 0 var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-sm) var(--juxt-radius-sm) 0 0;\r\n}\r\n\r\n.j-tabs--line .j-tabs__indicator {\r\n bottom: 0;\r\n height: 2px;\r\n border-radius: 2px 2px 0 0;\r\n background: var(--juxt-fg);\r\n}\r\n\r\n/* Pill */\r\n.j-tabs--pill .j-tabs__list {\r\n align-self: flex-start;\r\n padding: 3px;\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-sunken);\r\n box-shadow: inset 0 0 0 1px var(--juxt-border-subtle);\r\n}\r\n\r\n.j-tabs--pill.j-tabs--fill .j-tabs__list {\r\n align-self: stretch;\r\n}\r\n\r\n.j-tabs--pill .j-tabs__tab {\r\n --j-tab-height: calc(var(--juxt-control-md) - 6px);\r\n\r\n border-radius: var(--juxt-radius-sm);\r\n}\r\n\r\n.j-tabs--pill.j-tabs--sm .j-tabs__tab {\r\n --j-tab-height: calc(var(--juxt-control-sm) - 6px);\r\n\r\n padding: 0 var(--juxt-space-2-5);\r\n}\r\n\r\n.j-tabs--pill .j-tabs__indicator {\r\n top: 3px;\r\n bottom: 3px;\r\n border-radius: var(--juxt-radius-sm);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-sm);\r\n}\r\n\r\n.dark .j-tabs--pill .j-tabs__indicator,\r\n[data-theme='dark'] .j-tabs--pill .j-tabs__indicator {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n/* Panels */\r\n.j-tabs__panel {\r\n padding-top: var(--juxt-space-4);\r\n border-radius: var(--juxt-radius-sm);\r\n outline: 2px solid transparent;\r\n outline-offset: 2px;\r\n animation: j-tabs-panel-in var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-tabs__panel:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n}\r\n\r\n@keyframes j-tabs-panel-in {\r\n from {\r\n opacity: 0;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch } from 'vue'\r\nimport { focusElement } from '../../utils/dom'\r\n\r\nexport interface TabItem {\r\n value: string\r\n label: string\r\n disabled?: boolean\r\n}\r\n\r\nexport interface TabsProps {\r\n items: TabItem[]\r\n /** `line` for page sections, `pill` for compact view switches. */\r\n variant?: 'line' | 'pill'\r\n size?: 'sm' | 'md'\r\n /** Accessible name for the tab list. */\r\n label?: string\r\n /** Stretch tabs to fill the width. */\r\n fill?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<TabsProps>(), { variant: 'line', size: 'md' })\r\n\r\nconst model = defineModel<string>()\r\n\r\ndefineSlots<{\r\n /** Custom tab label. */\r\n tab?: (props: { item: TabItem, selected: boolean }) => unknown\r\n /** Panels are named after each item's `value`. */\r\n [value: string]: ((props: { item: TabItem, selected: boolean }) => unknown) | undefined\r\n}>()\r\n\r\nconst id = `j-${useId()}`\r\nconst tabId = (value: string) => `${id}-tab-${value}`\r\nconst panelId = (value: string) => `${id}-panel-${value}`\r\n\r\nconst selected = computed(() => model.value ?? props.items.find(item => !item.disabled)?.value)\r\n\r\nconst list = ref<HTMLElement | null>(null)\r\nconst indicator = ref({ x: 0, width: 0, ready: false })\r\n// The indicator glides only for deliberate changes, never for layout shifts.\r\nconst animated = ref(false)\r\n\r\nfunction measure() {\r\n const el = list.value?.querySelector<HTMLElement>('[role=\"tab\"][aria-selected=\"true\"]')\r\n indicator.value = el ? { x: el.offsetLeft, width: el.offsetWidth, ready: true } : { ...indicator.value, ready: false }\r\n}\r\n\r\nfunction measureWithoutMotion() {\r\n animated.value = false\r\n measure()\r\n requestAnimationFrame(() => requestAnimationFrame(() => (animated.value = true)))\r\n}\r\n\r\nlet observer: ResizeObserver | undefined\r\nonMounted(() => {\r\n measureWithoutMotion()\r\n observer = new ResizeObserver(measureWithoutMotion)\r\n if (list.value) observer.observe(list.value)\r\n // Web fonts change tab widths once they arrive.\r\n document.fonts?.ready.then(measureWithoutMotion)\r\n})\r\nonBeforeUnmount(() => observer?.disconnect())\r\n\r\nwatch(selected, () => nextTick(measure), { flush: 'post' })\r\nwatch(() => props.items, () => nextTick(measure), { deep: true, flush: 'post' })\r\n\r\nfunction select(item: TabItem) {\r\n if (item.disabled) return\r\n model.value = item.value\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const enabled = props.items.filter(item => !item.disabled)\r\n if (!enabled.length) return\r\n const current = enabled.findIndex(item => item.value === selected.value)\r\n let next: number | undefined\r\n if (event.key === 'ArrowRight') next = (current + 1) % enabled.length\r\n else if (event.key === 'ArrowLeft') next = (current - 1 + enabled.length) % enabled.length\r\n else if (event.key === 'Home') next = 0\r\n else if (event.key === 'End') next = enabled.length - 1\r\n if (next === undefined) return\r\n event.preventDefault()\r\n const item = enabled[next]!\r\n select(item)\r\n nextTick(() => focusElement(document.getElementById(tabId(item.value))))\r\n}\r\n\r\nconst indicatorStyle = computed(() => ({\r\n transform: `translateX(${indicator.value.x}px)`,\r\n width: `${indicator.value.width}px`,\r\n}))\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-tabs\" :class=\"[`j-tabs--${variant}`, `j-tabs--${size}`, { 'j-tabs--fill': fill }]\">\r\n <div\r\n ref=\"list\"\r\n role=\"tablist\"\r\n aria-orientation=\"horizontal\"\r\n :aria-label=\"label\"\r\n class=\"j-tabs__list\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <span\r\n class=\"j-tabs__indicator\"\r\n :class=\"{ 'is-ready': indicator.ready, 'is-animated': animated }\"\r\n :style=\"indicatorStyle\"\r\n aria-hidden=\"true\"\r\n />\r\n <button\r\n v-for=\"item in items\"\r\n :id=\"tabId(item.value)\"\r\n :key=\"item.value\"\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"j-tabs__tab\"\r\n :aria-selected=\"item.value === selected\"\r\n :aria-controls=\"panelId(item.value)\"\r\n :tabindex=\"item.value === selected ? 0 : -1\"\r\n :disabled=\"item.disabled\"\r\n @click=\"select(item)\"\r\n >\r\n <slot name=\"tab\" :item=\"item\" :selected=\"item.value === selected\">\r\n {{ item.label }}\r\n </slot>\r\n </button>\r\n </div>\r\n <template v-for=\"item in items\" :key=\"item.value\">\r\n <div\r\n v-if=\"$slots[item.value]\"\r\n :id=\"panelId(item.value)\"\r\n role=\"tabpanel\"\r\n class=\"j-tabs__panel\"\r\n :aria-labelledby=\"tabId(item.value)\"\r\n :hidden=\"item.value !== selected\"\r\n tabindex=\"0\"\r\n >\r\n <slot :name=\"item.value\" :item=\"item\" :selected=\"item.value === selected\" />\r\n </div>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-tabs {\r\n --j-tab-height: var(--juxt-control-md);\r\n --j-tab-font: var(--juxt-text-md);\r\n\r\n display: flex;\r\n flex-direction: column;\r\n min-width: 0;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-tabs--sm {\r\n --j-tab-height: var(--juxt-control-sm);\r\n --j-tab-font: var(--juxt-text-sm);\r\n}\r\n\r\n.j-tabs__list {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n max-width: 100%;\r\n overflow-x: auto;\r\n scrollbar-width: none;\r\n}\r\n\r\n.j-tabs__list::-webkit-scrollbar {\r\n display: none;\r\n}\r\n\r\n.j-tabs__tab {\r\n position: relative;\r\n z-index: 1;\r\n display: inline-flex;\r\n flex: none;\r\n align-items: center;\r\n justify-content: center;\r\n gap: var(--juxt-space-1-5);\r\n height: var(--j-tab-height);\r\n padding: 0 var(--juxt-space-3);\r\n border: 0;\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n font: inherit;\r\n font-size: var(--j-tab-font);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n white-space: nowrap;\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: -2px;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-tabs__tab:hover:not(:disabled) {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-tabs__tab[aria-selected='true'] {\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-tabs__tab:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n}\r\n\r\n.j-tabs__tab:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-tabs--fill .j-tabs__tab {\r\n flex: 1;\r\n}\r\n\r\n.j-tabs__indicator {\r\n position: absolute;\r\n left: 0;\r\n opacity: 0;\r\n pointer-events: none;\r\n}\r\n\r\n.j-tabs__indicator.is-ready {\r\n opacity: 1;\r\n}\r\n\r\n.j-tabs__indicator.is-animated {\r\n transition:\r\n transform var(--juxt-duration-slow) var(--juxt-ease-out),\r\n width var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-tabs__indicator.is-animated {\r\n transition: none;\r\n }\r\n}\r\n\r\n/* Line */\r\n.j-tabs--line .j-tabs__list {\r\n gap: var(--juxt-space-1);\r\n box-shadow: inset 0 -1px 0 var(--juxt-border);\r\n}\r\n\r\n.j-tabs--line .j-tabs__tab {\r\n padding: 0 var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-sm) var(--juxt-radius-sm) 0 0;\r\n}\r\n\r\n.j-tabs--line .j-tabs__indicator {\r\n bottom: 0;\r\n height: 2px;\r\n border-radius: 2px 2px 0 0;\r\n background: var(--juxt-fg);\r\n}\r\n\r\n/* Pill */\r\n.j-tabs--pill .j-tabs__list {\r\n align-self: flex-start;\r\n padding: 3px;\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-sunken);\r\n box-shadow: inset 0 0 0 1px var(--juxt-border-subtle);\r\n}\r\n\r\n.j-tabs--pill.j-tabs--fill .j-tabs__list {\r\n align-self: stretch;\r\n}\r\n\r\n.j-tabs--pill .j-tabs__tab {\r\n --j-tab-height: calc(var(--juxt-control-md) - 6px);\r\n\r\n border-radius: var(--juxt-radius-sm);\r\n}\r\n\r\n.j-tabs--pill.j-tabs--sm .j-tabs__tab {\r\n --j-tab-height: calc(var(--juxt-control-sm) - 6px);\r\n\r\n padding: 0 var(--juxt-space-2-5);\r\n}\r\n\r\n.j-tabs--pill .j-tabs__indicator {\r\n top: 3px;\r\n bottom: 3px;\r\n border-radius: var(--juxt-radius-sm);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-sm);\r\n}\r\n\r\n.dark .j-tabs--pill .j-tabs__indicator,\r\n[data-theme='dark'] .j-tabs--pill .j-tabs__indicator {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n/* Panels */\r\n.j-tabs__panel {\r\n padding-top: var(--juxt-space-4);\r\n border-radius: var(--juxt-radius-sm);\r\n outline: 2px solid transparent;\r\n outline-offset: 2px;\r\n animation: j-tabs-panel-in var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-tabs__panel:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n}\r\n\r\n@keyframes j-tabs-panel-in {\r\n from {\r\n opacity: 0;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { FieldProps } from '../field/useField'\r\nimport { nextTick, onMounted, ref, watch } from 'vue'\r\nimport JFieldShell from '../field/JFieldShell.vue'\r\nimport { useField } from '../field/useField'\r\n\r\nexport interface TextareaProps extends FieldProps {\r\n placeholder?: string\r\n rows?: number\r\n /** Grow with the content, up to `maxRows`. */\r\n autoresize?: boolean\r\n maxRows?: number\r\n readonly?: boolean\r\n name?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<TextareaProps>(), {\r\n rows: 3,\r\n maxRows: 12,\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<string | null>({ default: '' })\r\n\r\ndefineSlots<{ label?: () => unknown }>()\r\n\r\nconst field = useField(props)\r\nconst textarea = ref<HTMLTextAreaElement | null>(null)\r\n\r\nfunction resize() {\r\n const el = textarea.value\r\n if (!props.autoresize || !el) return\r\n const style = getComputedStyle(el)\r\n const line = Number.parseFloat(style.lineHeight) || 20\r\n const chrome = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom)\r\n el.style.height = 'auto'\r\n const max = line * props.maxRows + chrome\r\n const next = Math.min(el.scrollHeight, max)\r\n el.style.height = `${next}px`\r\n el.style.overflowY = el.scrollHeight > max ? 'auto' : 'hidden'\r\n}\r\n\r\nonMounted(resize)\r\nwatch(model, () => nextTick(resize))\r\nwatch(() => props.autoresize, (value) => {\r\n if (value) return nextTick(resize)\r\n if (textarea.value) textarea.value.style.height = ''\r\n})\r\n\r\ndefineExpose({\r\n textarea,\r\n focus: () => textarea.value?.focus(),\r\n})\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n <div\r\n class=\"j-control j-textarea\"\r\n :class=\"{ 'is-invalid': field.invalid.value, 'is-disabled': disabled, 'is-readonly': readonly }\"\r\n >\r\n <textarea\r\n :id=\"field.id.value\"\r\n ref=\"textarea\"\r\n v-model=\"model\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n class=\"j-textarea__native\"\r\n :class=\"{ 'is-autoresize': autoresize }\"\r\n :rows=\"rows\"\r\n :name=\"name\"\r\n :placeholder=\"placeholder\"\r\n :disabled=\"disabled\"\r\n :readonly=\"readonly\"\r\n :required=\"required\"\r\n :aria-invalid=\"field.invalid.value || undefined\"\r\n :aria-describedby=\"field.describedBy.value\"\r\n />\r\n </div>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-textarea {\r\n align-items: stretch;\r\n padding: 0;\r\n cursor: auto;\r\n}\r\n\r\n.j-textarea__native {\r\n display: block;\r\n width: 100%;\r\n min-height: calc(var(--juxt-control-md) * 2);\r\n padding: var(--juxt-space-2) var(--juxt-space-2-5);\r\n border: 0;\r\n border-radius: inherit;\r\n outline: 0;\r\n background: transparent;\r\n color: inherit;\r\n font: inherit;\r\n line-height: var(--juxt-leading-normal);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n resize: vertical;\r\n}\r\n\r\n.j-textarea__native.is-autoresize {\r\n resize: none;\r\n overflow-y: hidden;\r\n}\r\n\r\n.j-textarea__native::placeholder {\r\n color: var(--juxt-fg-muted);\r\n opacity: 1;\r\n}\r\n\r\n.j-textarea__native:disabled {\r\n cursor: not-allowed;\r\n resize: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { FieldProps } from '../field/useField'\r\nimport { nextTick, onMounted, ref, watch } from 'vue'\r\nimport JFieldShell from '../field/JFieldShell.vue'\r\nimport { useField } from '../field/useField'\r\n\r\nexport interface TextareaProps extends FieldProps {\r\n placeholder?: string\r\n rows?: number\r\n /** Grow with the content, up to `maxRows`. */\r\n autoresize?: boolean\r\n maxRows?: number\r\n readonly?: boolean\r\n name?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<TextareaProps>(), {\r\n rows: 3,\r\n maxRows: 12,\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<string | null>({ default: '' })\r\n\r\ndefineSlots<{ label?: () => unknown }>()\r\n\r\nconst field = useField(props)\r\nconst textarea = ref<HTMLTextAreaElement | null>(null)\r\n\r\nfunction resize() {\r\n const el = textarea.value\r\n if (!props.autoresize || !el) return\r\n const style = getComputedStyle(el)\r\n const line = Number.parseFloat(style.lineHeight) || 20\r\n const chrome = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom)\r\n el.style.height = 'auto'\r\n const max = line * props.maxRows + chrome\r\n const next = Math.min(el.scrollHeight, max)\r\n el.style.height = `${next}px`\r\n el.style.overflowY = el.scrollHeight > max ? 'auto' : 'hidden'\r\n}\r\n\r\nonMounted(resize)\r\nwatch(model, () => nextTick(resize))\r\nwatch(() => props.autoresize, (value) => {\r\n if (value) return nextTick(resize)\r\n if (textarea.value) textarea.value.style.height = ''\r\n})\r\n\r\ndefineExpose({\r\n textarea,\r\n focus: () => textarea.value?.focus(),\r\n})\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n <div\r\n class=\"j-control j-textarea\"\r\n :class=\"{ 'is-invalid': field.invalid.value, 'is-disabled': disabled, 'is-readonly': readonly }\"\r\n >\r\n <textarea\r\n :id=\"field.id.value\"\r\n ref=\"textarea\"\r\n v-model=\"model\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n class=\"j-textarea__native\"\r\n :class=\"{ 'is-autoresize': autoresize }\"\r\n :rows=\"rows\"\r\n :name=\"name\"\r\n :placeholder=\"placeholder\"\r\n :disabled=\"disabled\"\r\n :readonly=\"readonly\"\r\n :required=\"required\"\r\n :aria-invalid=\"field.invalid.value || undefined\"\r\n :aria-describedby=\"field.describedBy.value\"\r\n />\r\n </div>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-textarea {\r\n align-items: stretch;\r\n padding: 0;\r\n cursor: auto;\r\n}\r\n\r\n.j-textarea__native {\r\n display: block;\r\n width: 100%;\r\n min-height: calc(var(--juxt-control-md) * 2);\r\n padding: var(--juxt-space-2) var(--juxt-space-2-5);\r\n border: 0;\r\n border-radius: inherit;\r\n outline: 0;\r\n background: transparent;\r\n color: inherit;\r\n font: inherit;\r\n line-height: var(--juxt-leading-normal);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n resize: vertical;\r\n}\r\n\r\n.j-textarea__native.is-autoresize {\r\n resize: none;\r\n overflow-y: hidden;\r\n}\r\n\r\n.j-textarea__native::placeholder {\r\n color: var(--juxt-fg-muted);\r\n opacity: 1;\r\n}\r\n\r\n.j-textarea__native:disabled {\r\n cursor: not-allowed;\r\n resize: none;\r\n}\r\n}\r\n</style>\r\n","import { reactive, readonly } from 'vue'\r\n\r\nexport type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info' | 'loading'\r\n\r\nexport interface ToastAction {\r\n label: string\r\n onClick: () => void\r\n}\r\n\r\nexport interface ToastOptions {\r\n /** Reuse an id to replace an existing toast instead of stacking a new one. */\r\n id?: string\r\n description?: string\r\n type?: ToastType\r\n /** Time on screen in ms. `Infinity` keeps it until dismissed. Loading toasts never expire. */\r\n duration?: number\r\n action?: ToastAction\r\n /** Show the close button. Defaults to true. */\r\n dismissible?: boolean\r\n onDismiss?: () => void\r\n}\r\n\r\nexport interface Toast {\r\n id: string\r\n title: string\r\n description?: string\r\n type: ToastType\r\n duration: number\r\n action?: ToastAction\r\n dismissible: boolean\r\n onDismiss?: () => void\r\n /** Bumps whenever the toast changes, restarting its timer. */\r\n version: number\r\n}\r\n\r\nexport const DEFAULT_TOAST_DURATION = 5000\r\n\r\nconst state = reactive<{ toasts: Toast[] }>({ toasts: [] })\r\nlet counter = 0\r\n\r\nfunction resolveDuration(type: ToastType, duration?: number) {\r\n if (duration !== undefined) return duration\r\n if (type === 'loading') return Number.POSITIVE_INFINITY\r\n // Errors deserve a little longer to be read.\r\n if (type === 'error') return DEFAULT_TOAST_DURATION + 2000\r\n return DEFAULT_TOAST_DURATION\r\n}\r\n\r\nfunction create(title: string, options: ToastOptions = {}): string {\r\n const type = options.type ?? 'default'\r\n const existing = options.id ? state.toasts.find(t => t.id === options.id) : undefined\r\n if (existing) {\r\n update(existing.id, { title, ...options })\r\n return existing.id\r\n }\r\n const id = options.id ?? `toast-${++counter}`\r\n state.toasts.push({\r\n id,\r\n title,\r\n description: options.description,\r\n type,\r\n duration: resolveDuration(type, options.duration),\r\n action: options.action,\r\n dismissible: options.dismissible ?? true,\r\n onDismiss: options.onDismiss,\r\n version: 0,\r\n })\r\n return id\r\n}\r\n\r\nfunction update(id: string, patch: Partial<ToastOptions> & { title?: string }) {\r\n const item = state.toasts.find(t => t.id === id)\r\n if (!item) return\r\n const type = patch.type ?? item.type\r\n Object.assign(item, {\r\n ...patch,\r\n type,\r\n duration: patch.duration !== undefined || patch.type ? resolveDuration(type, patch.duration) : item.duration,\r\n version: item.version + 1,\r\n })\r\n}\r\n\r\nfunction dismiss(id?: string) {\r\n if (id === undefined) {\r\n const all = state.toasts.splice(0)\r\n all.forEach(t => t.onDismiss?.())\r\n return\r\n }\r\n const index = state.toasts.findIndex(t => t.id === id)\r\n if (index === -1) return\r\n const [removed] = state.toasts.splice(index, 1)\r\n removed?.onDismiss?.()\r\n}\r\n\r\ntype Message<T> = string | ((value: T) => string)\r\n\r\ninterface PromiseMessages<T> {\r\n loading: string\r\n success: Message<T>\r\n error: Message<unknown>\r\n description?: string\r\n}\r\n\r\nfunction promise<T>(input: Promise<T> | (() => Promise<T>), messages: PromiseMessages<T>): Promise<T> {\r\n const id = create(messages.loading, { type: 'loading', description: messages.description })\r\n const run = typeof input === 'function' ? input() : input\r\n run.then(\r\n (value) => {\r\n const title = typeof messages.success === 'function' ? messages.success(value) : messages.success\r\n update(id, { title, type: 'success', description: undefined })\r\n },\r\n (error: unknown) => {\r\n const title = typeof messages.error === 'function' ? messages.error(error) : messages.error\r\n update(id, { title, type: 'error', description: undefined })\r\n },\r\n )\r\n return run\r\n}\r\n\r\ntype Shortcut = (title: string, options?: Omit<ToastOptions, 'type'>) => string\r\nconst typed = (type: ToastType): Shortcut => (title, options) => create(title, { ...options, type })\r\n\r\nexport interface ToastFn {\r\n (title: string, options?: ToastOptions): string\r\n success: Shortcut\r\n error: Shortcut\r\n warning: Shortcut\r\n info: Shortcut\r\n loading: Shortcut\r\n promise: typeof promise\r\n update: typeof update\r\n dismiss: typeof dismiss\r\n}\r\n\r\n/** Show a toast. Works anywhere — inside or outside components. */\r\nexport const toast: ToastFn = Object.assign(create, {\r\n success: typed('success'),\r\n error: typed('error'),\r\n warning: typed('warning'),\r\n info: typed('info'),\r\n loading: typed('loading'),\r\n promise,\r\n update,\r\n dismiss,\r\n})\r\n\r\nexport function useToast() {\r\n return {\r\n toast,\r\n dismiss,\r\n update,\r\n toasts: readonly(state).toasts,\r\n }\r\n}\r\n\r\n/** @internal Mutable access for JToaster and tests. */\r\nexport const toastState = state\r\n","<script setup lang=\"ts\">\r\n/** Internal: one toast. Owns its timer, swipe gesture and keyboard dismissal. */\r\nimport type { Toast } from '../../composables/useToast'\r\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport { IconAlert, IconCircleCheck, IconCircleX, IconInfo, IconX, Spinner } from '../../icons'\r\n\r\nconst props = defineProps<{\r\n toast: Toast\r\n paused: boolean\r\n}>()\r\n\r\nconst emit = defineEmits<{ dismiss: [id: string] }>()\r\n\r\nconst el = ref<HTMLElement | null>(null)\r\nconst timed = computed(() => Number.isFinite(props.toast.duration) && props.toast.duration > 0)\r\n\r\n// --- Timer -----------------------------------------------------------------\r\nlet timer: ReturnType<typeof setTimeout> | undefined\r\nlet remaining = 0\r\nlet startedAt = 0\r\n\r\nfunction start() {\r\n clearTimeout(timer)\r\n if (!timed.value || props.paused) return\r\n startedAt = Date.now()\r\n timer = setTimeout(() => emit('dismiss', props.toast.id), remaining)\r\n}\r\n\r\nfunction pause() {\r\n if (!timer) return\r\n clearTimeout(timer)\r\n timer = undefined\r\n remaining -= Date.now() - startedAt\r\n}\r\n\r\nfunction reset() {\r\n remaining = props.toast.duration\r\n start()\r\n}\r\n\r\nonMounted(reset)\r\nonBeforeUnmount(() => clearTimeout(timer))\r\nwatch(() => props.toast.version, reset)\r\nwatch(() => props.paused, value => (value ? pause() : start()))\r\n\r\n// --- Swipe to dismiss ------------------------------------------------------\r\nconst offset = ref(0)\r\nconst swiping = ref(false)\r\nconst leaving = ref<0 | 1 | -1>(0)\r\nlet origin: { x: number, y: number, id: number } | null = null\r\n\r\nfunction onPointerDown(event: PointerEvent) {\r\n if (event.button !== 0 || (event.target as HTMLElement).closest('button, a')) return\r\n origin = { x: event.clientX, y: event.clientY, id: event.pointerId }\r\n}\r\n\r\nfunction onPointerMove(event: PointerEvent) {\r\n if (!origin || event.pointerId !== origin.id) return\r\n const dx = event.clientX - origin.x\r\n if (!swiping.value) {\r\n // Commit to a swipe only once the gesture is clearly horizontal.\r\n if (Math.abs(dx) < 6 || Math.abs(dx) < Math.abs(event.clientY - origin.y)) return\r\n swiping.value = true\r\n el.value?.setPointerCapture(event.pointerId)\r\n }\r\n offset.value = dx\r\n}\r\n\r\nfunction onPointerUp() {\r\n if (!origin) return\r\n origin = null\r\n if (!swiping.value) return\r\n swiping.value = false\r\n if (Math.abs(offset.value) > 64 && props.toast.dismissible) {\r\n leaving.value = offset.value > 0 ? 1 : -1\r\n emit('dismiss', props.toast.id)\r\n } else {\r\n offset.value = 0\r\n }\r\n}\r\n\r\nconst style = computed(() => {\r\n if (!offset.value) return undefined\r\n return {\r\n '--j-toast-swipe': `${offset.value}px`,\r\n '--j-toast-swipe-opacity': String(Math.max(0, 1 - Math.abs(offset.value) / 200)),\r\n }\r\n})\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key === 'Escape' && props.toast.dismissible) {\r\n event.preventDefault()\r\n event.stopPropagation()\r\n const next = (el.value?.nextElementSibling ?? el.value?.previousElementSibling) as HTMLElement | null\r\n emit('dismiss', props.toast.id)\r\n next?.focus()\r\n }\r\n}\r\n\r\nfunction runAction() {\r\n props.toast.action?.onClick()\r\n emit('dismiss', props.toast.id)\r\n}\r\n\r\nconst icons = {\r\n success: IconCircleCheck,\r\n error: IconCircleX,\r\n warning: IconAlert,\r\n info: IconInfo,\r\n}\r\n</script>\r\n\r\n<template>\r\n <li\r\n ref=\"el\"\r\n class=\"j-toast\"\r\n :class=\"[`j-toast--${toast.type}`, { 'is-swiping': swiping, 'is-swiped': leaving !== 0 }]\"\r\n :style=\"style\"\r\n :data-swipe-direction=\"leaving\"\r\n :role=\"toast.type === 'error' ? 'alert' : undefined\"\r\n tabindex=\"0\"\r\n @pointerdown=\"onPointerDown\"\r\n @pointermove=\"onPointerMove\"\r\n @pointerup=\"onPointerUp\"\r\n @pointercancel=\"onPointerUp\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <span v-if=\"toast.type !== 'default'\" class=\"j-toast__icon\" aria-hidden=\"true\">\r\n <Transition name=\"j-toast-icon\" mode=\"out-in\">\r\n <Spinner v-if=\"toast.type === 'loading'\" :size=\"16\" />\r\n <component :is=\"icons[toast.type]\" v-else :key=\"toast.type\" />\r\n </Transition>\r\n </span>\r\n <div class=\"j-toast__body\">\r\n <p class=\"j-toast__title\">\r\n {{ toast.title }}\r\n </p>\r\n <p v-if=\"toast.description\" class=\"j-toast__description\">\r\n {{ toast.description }}\r\n </p>\r\n <div v-if=\"toast.action\" class=\"j-toast__actions\">\r\n <button type=\"button\" class=\"j-toast__action j-focusable\" @click=\"runAction\">\r\n {{ toast.action.label }}\r\n </button>\r\n </div>\r\n </div>\r\n <button\r\n v-if=\"toast.dismissible\"\r\n type=\"button\"\r\n class=\"j-toast__close j-focusable\"\r\n aria-label=\"Dismiss notification\"\r\n @click=\"emit('dismiss', toast.id)\"\r\n >\r\n <IconX />\r\n </button>\r\n <span\r\n v-if=\"timed\"\r\n :key=\"toast.version\"\r\n class=\"j-toast__progress\"\r\n :class=\"{ 'is-paused': paused }\"\r\n :style=\"{ animationDuration: `${toast.duration}ms` }\"\r\n aria-hidden=\"true\"\r\n />\r\n </li>\r\n</template>\r\n","<script setup lang=\"ts\">\r\n/** Internal: one toast. Owns its timer, swipe gesture and keyboard dismissal. */\r\nimport type { Toast } from '../../composables/useToast'\r\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport { IconAlert, IconCircleCheck, IconCircleX, IconInfo, IconX, Spinner } from '../../icons'\r\n\r\nconst props = defineProps<{\r\n toast: Toast\r\n paused: boolean\r\n}>()\r\n\r\nconst emit = defineEmits<{ dismiss: [id: string] }>()\r\n\r\nconst el = ref<HTMLElement | null>(null)\r\nconst timed = computed(() => Number.isFinite(props.toast.duration) && props.toast.duration > 0)\r\n\r\n// --- Timer -----------------------------------------------------------------\r\nlet timer: ReturnType<typeof setTimeout> | undefined\r\nlet remaining = 0\r\nlet startedAt = 0\r\n\r\nfunction start() {\r\n clearTimeout(timer)\r\n if (!timed.value || props.paused) return\r\n startedAt = Date.now()\r\n timer = setTimeout(() => emit('dismiss', props.toast.id), remaining)\r\n}\r\n\r\nfunction pause() {\r\n if (!timer) return\r\n clearTimeout(timer)\r\n timer = undefined\r\n remaining -= Date.now() - startedAt\r\n}\r\n\r\nfunction reset() {\r\n remaining = props.toast.duration\r\n start()\r\n}\r\n\r\nonMounted(reset)\r\nonBeforeUnmount(() => clearTimeout(timer))\r\nwatch(() => props.toast.version, reset)\r\nwatch(() => props.paused, value => (value ? pause() : start()))\r\n\r\n// --- Swipe to dismiss ------------------------------------------------------\r\nconst offset = ref(0)\r\nconst swiping = ref(false)\r\nconst leaving = ref<0 | 1 | -1>(0)\r\nlet origin: { x: number, y: number, id: number } | null = null\r\n\r\nfunction onPointerDown(event: PointerEvent) {\r\n if (event.button !== 0 || (event.target as HTMLElement).closest('button, a')) return\r\n origin = { x: event.clientX, y: event.clientY, id: event.pointerId }\r\n}\r\n\r\nfunction onPointerMove(event: PointerEvent) {\r\n if (!origin || event.pointerId !== origin.id) return\r\n const dx = event.clientX - origin.x\r\n if (!swiping.value) {\r\n // Commit to a swipe only once the gesture is clearly horizontal.\r\n if (Math.abs(dx) < 6 || Math.abs(dx) < Math.abs(event.clientY - origin.y)) return\r\n swiping.value = true\r\n el.value?.setPointerCapture(event.pointerId)\r\n }\r\n offset.value = dx\r\n}\r\n\r\nfunction onPointerUp() {\r\n if (!origin) return\r\n origin = null\r\n if (!swiping.value) return\r\n swiping.value = false\r\n if (Math.abs(offset.value) > 64 && props.toast.dismissible) {\r\n leaving.value = offset.value > 0 ? 1 : -1\r\n emit('dismiss', props.toast.id)\r\n } else {\r\n offset.value = 0\r\n }\r\n}\r\n\r\nconst style = computed(() => {\r\n if (!offset.value) return undefined\r\n return {\r\n '--j-toast-swipe': `${offset.value}px`,\r\n '--j-toast-swipe-opacity': String(Math.max(0, 1 - Math.abs(offset.value) / 200)),\r\n }\r\n})\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key === 'Escape' && props.toast.dismissible) {\r\n event.preventDefault()\r\n event.stopPropagation()\r\n const next = (el.value?.nextElementSibling ?? el.value?.previousElementSibling) as HTMLElement | null\r\n emit('dismiss', props.toast.id)\r\n next?.focus()\r\n }\r\n}\r\n\r\nfunction runAction() {\r\n props.toast.action?.onClick()\r\n emit('dismiss', props.toast.id)\r\n}\r\n\r\nconst icons = {\r\n success: IconCircleCheck,\r\n error: IconCircleX,\r\n warning: IconAlert,\r\n info: IconInfo,\r\n}\r\n</script>\r\n\r\n<template>\r\n <li\r\n ref=\"el\"\r\n class=\"j-toast\"\r\n :class=\"[`j-toast--${toast.type}`, { 'is-swiping': swiping, 'is-swiped': leaving !== 0 }]\"\r\n :style=\"style\"\r\n :data-swipe-direction=\"leaving\"\r\n :role=\"toast.type === 'error' ? 'alert' : undefined\"\r\n tabindex=\"0\"\r\n @pointerdown=\"onPointerDown\"\r\n @pointermove=\"onPointerMove\"\r\n @pointerup=\"onPointerUp\"\r\n @pointercancel=\"onPointerUp\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <span v-if=\"toast.type !== 'default'\" class=\"j-toast__icon\" aria-hidden=\"true\">\r\n <Transition name=\"j-toast-icon\" mode=\"out-in\">\r\n <Spinner v-if=\"toast.type === 'loading'\" :size=\"16\" />\r\n <component :is=\"icons[toast.type]\" v-else :key=\"toast.type\" />\r\n </Transition>\r\n </span>\r\n <div class=\"j-toast__body\">\r\n <p class=\"j-toast__title\">\r\n {{ toast.title }}\r\n </p>\r\n <p v-if=\"toast.description\" class=\"j-toast__description\">\r\n {{ toast.description }}\r\n </p>\r\n <div v-if=\"toast.action\" class=\"j-toast__actions\">\r\n <button type=\"button\" class=\"j-toast__action j-focusable\" @click=\"runAction\">\r\n {{ toast.action.label }}\r\n </button>\r\n </div>\r\n </div>\r\n <button\r\n v-if=\"toast.dismissible\"\r\n type=\"button\"\r\n class=\"j-toast__close j-focusable\"\r\n aria-label=\"Dismiss notification\"\r\n @click=\"emit('dismiss', toast.id)\"\r\n >\r\n <IconX />\r\n </button>\r\n <span\r\n v-if=\"timed\"\r\n :key=\"toast.version\"\r\n class=\"j-toast__progress\"\r\n :class=\"{ 'is-paused': paused }\"\r\n :style=\"{ animationDuration: `${toast.duration}ms` }\"\r\n aria-hidden=\"true\"\r\n />\r\n </li>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\r\nimport { formatHotkey, useHotkey } from '../../composables/useHotkey'\r\nimport { toastState } from '../../composables/useToast'\r\nimport { focusElement, unrefElement } from '../../utils/dom'\r\nimport JToastItem from './JToastItem.vue'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport type ToasterPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'\r\n\r\nexport interface ToasterProps {\r\n position?: ToasterPosition\r\n /** How many toasts are visible at once. Extra toasts wait their turn. */\r\n max?: number\r\n /** Shortcut that moves focus to the newest toast. */\r\n hotkey?: string\r\n /** Accessible name for the notification region. */\r\n label?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<ToasterProps>(), {\r\n position: 'bottom-right',\r\n max: 4,\r\n hotkey: 'alt+t',\r\n label: 'Notifications',\r\n})\r\n\r\nconst list = ref()\r\nconst hovered = ref(false)\r\nconst focused = ref(false)\r\nconst hidden = ref(false)\r\nconst paused = computed(() => hovered.value || focused.value || hidden.value)\r\n\r\nconst isTop = computed(() => props.position.startsWith('top'))\r\n\r\n// Oldest first in the store; show the newest `max`, nearest the screen edge.\r\nconst visible = computed(() => {\r\n const items = toastState.toasts.slice(-props.max)\r\n return isTop.value ? items.reverse() : items\r\n})\r\n\r\nfunction dismiss(id: string) {\r\n const index = toastState.toasts.findIndex(t => t.id === id)\r\n if (index === -1) return\r\n const [removed] = toastState.toasts.splice(index, 1)\r\n removed?.onDismiss?.()\r\n}\r\n\r\nconst hotkeyLabel = ref('')\r\nonMounted(() => (hotkeyLabel.value = formatHotkey(props.hotkey).join('+')))\r\n\r\nuseHotkey(() => props.hotkey, () => {\r\n const items = unrefElement(list.value)?.querySelectorAll<HTMLElement>('.j-toast')\r\n if (!items?.length) return\r\n focusElement(isTop.value ? items[0] : items[items.length - 1])\r\n})\r\n\r\nfunction onVisibility() {\r\n hidden.value = document.visibilityState === 'hidden'\r\n}\r\nonMounted(() => document.addEventListener('visibilitychange', onVisibility))\r\nonBeforeUnmount(() => document.removeEventListener('visibilitychange', onVisibility))\r\n\r\nfunction onFocusOut(event: FocusEvent) {\r\n const current = event.currentTarget as HTMLElement\r\n if (!current.contains(event.relatedTarget as Node | null)) focused.value = false\r\n}\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <section\r\n class=\"j-toaster\"\r\n :class=\"`j-toaster--${position}`\"\r\n :aria-label=\"hotkeyLabel ? `${label} (${hotkeyLabel})` : label\"\r\n data-juxt-layer-ignore\r\n >\r\n <TransitionGroup\r\n ref=\"list\"\r\n tag=\"ol\"\r\n name=\"j-toast\"\r\n class=\"j-toaster__list\"\r\n aria-live=\"polite\"\r\n aria-relevant=\"additions text\"\r\n @pointerenter=\"hovered = true\"\r\n @pointerleave=\"hovered = false\"\r\n @focusin=\"focused = true\"\r\n @focusout=\"onFocusOut\"\r\n >\r\n <JToastItem\r\n v-for=\"item in visible\"\r\n :key=\"item.id\"\r\n :toast=\"item\"\r\n :paused=\"paused\"\r\n @dismiss=\"dismiss\"\r\n />\r\n </TransitionGroup>\r\n </section>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-toaster {\r\n --j-toast-width: 22.5rem;\r\n --j-toast-edge: var(--juxt-space-4);\r\n\r\n position: fixed;\r\n z-index: var(--juxt-z-toast);\r\n width: var(--j-toast-width);\r\n max-width: calc(100vw - var(--j-toast-edge) * 2);\r\n pointer-events: none;\r\n}\r\n\r\n.j-toaster--top-left,\r\n.j-toaster--top-center,\r\n.j-toaster--top-right {\r\n top: max(var(--j-toast-edge), env(safe-area-inset-top));\r\n}\r\n\r\n.j-toaster--bottom-left,\r\n.j-toaster--bottom-center,\r\n.j-toaster--bottom-right {\r\n bottom: max(var(--j-toast-edge), env(safe-area-inset-bottom));\r\n}\r\n\r\n.j-toaster--top-left,\r\n.j-toaster--bottom-left {\r\n left: var(--j-toast-edge);\r\n}\r\n\r\n.j-toaster--top-right,\r\n.j-toaster--bottom-right {\r\n right: calc(var(--j-toast-edge) + var(--juxt-scrollbar-width, 0px));\r\n}\r\n\r\n.j-toaster--top-center,\r\n.j-toaster--bottom-center {\r\n left: 50%;\r\n transform: translateX(-50%);\r\n}\r\n\r\n.j-toaster__list {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-2);\r\n margin: 0;\r\n padding: 0;\r\n list-style: none;\r\n}\r\n\r\n.j-toast {\r\n --j-toast-enter: calc(var(--juxt-motion-shift) * 3);\r\n\r\n position: relative;\r\n display: flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n box-sizing: border-box;\r\n width: 100%;\r\n padding: var(--juxt-space-3) var(--juxt-space-10) var(--juxt-space-3) var(--juxt-space-3);\r\n overflow: hidden;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-md);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n pointer-events: auto;\r\n opacity: var(--j-toast-swipe-opacity, 1);\r\n transform: translateX(var(--j-toast-swipe, 0));\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n touch-action: pan-y;\r\n transition:\r\n transform var(--juxt-duration-slow) var(--juxt-ease-out),\r\n opacity var(--juxt-duration-normal) var(--juxt-ease-out),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-toaster--top-left .j-toast,\r\n.j-toaster--top-center .j-toast,\r\n.j-toaster--top-right .j-toast {\r\n --j-toast-enter: calc(var(--juxt-motion-shift) * -3);\r\n}\r\n\r\n.j-toast.is-swiping {\r\n transition: none;\r\n user-select: none;\r\n}\r\n\r\n.j-toast:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-toast__icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 1rem;\r\n height: 1.25rem;\r\n}\r\n\r\n.j-toast__icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-toast--success .j-toast__icon {\r\n color: var(--juxt-success-text);\r\n}\r\n\r\n.j-toast--error .j-toast__icon {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-toast--warning .j-toast__icon {\r\n color: var(--juxt-warning-text);\r\n}\r\n\r\n.j-toast--info .j-toast__icon {\r\n color: var(--juxt-info-text);\r\n}\r\n\r\n.j-toast--loading .j-toast__icon {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-toast__body {\r\n display: flex;\r\n flex: 1;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-toast__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1.25rem;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-toast__description {\r\n margin: 0;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-toast__actions {\r\n display: flex;\r\n gap: var(--juxt-space-2);\r\n margin-top: var(--juxt-space-2);\r\n}\r\n\r\n.j-toast__action {\r\n height: var(--juxt-control-xs);\r\n padding: 0 var(--juxt-space-2);\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-surface);\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n cursor: pointer;\r\n transition-property: background-color, border-color, outline-color, outline-offset;\r\n}\r\n\r\n.j-toast__action:hover {\r\n border-color: var(--juxt-border-strong);\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-toast__close {\r\n position: absolute;\r\n top: var(--juxt-space-2-5);\r\n right: var(--juxt-space-2-5);\r\n display: grid;\r\n place-items: center;\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-xs);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-toast__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-toast__close > svg {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n\r\n/* Remaining time, drawn as a hairline that drains toward the start. */\r\n.j-toast__progress {\r\n position: absolute;\r\n right: 0;\r\n bottom: 0;\r\n left: 0;\r\n height: 2px;\r\n background: var(--juxt-border-strong);\r\n opacity: 0.6;\r\n transform-origin: left;\r\n animation: j-toast-progress linear forwards;\r\n}\r\n\r\n.j-toast--success .j-toast__progress {\r\n background: var(--juxt-accent-border);\r\n}\r\n\r\n.j-toast__progress.is-paused {\r\n animation-play-state: paused;\r\n}\r\n\r\n@keyframes j-toast-progress {\r\n from {\r\n transform: scaleX(1);\r\n }\r\n\r\n to {\r\n transform: scaleX(0);\r\n }\r\n}\r\n\r\n/* List motion */\r\n.j-toast-enter-from {\r\n opacity: 0;\r\n transform: translateY(var(--j-toast-enter)) scale(0.98);\r\n}\r\n\r\n.j-toast-leave-active {\r\n position: absolute;\r\n width: 100%;\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-in),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-in);\r\n}\r\n\r\n.j-toast-leave-to {\r\n opacity: 0;\r\n transform: scale(0.96);\r\n}\r\n\r\n.j-toast-leave-to.is-swiped {\r\n transform: translateX(calc(var(--j-toast-width) * var(--j-swipe-sign, 1)));\r\n}\r\n\r\n.j-toast-leave-to.is-swiped[data-swipe-direction='-1'] {\r\n --j-swipe-sign: -1;\r\n}\r\n\r\n.j-toast-move {\r\n transition: transform var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-toast-icon-enter-active,\r\n.j-toast-icon-leave-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-toast-icon-enter-from,\r\n.j-toast-icon-leave-to {\r\n opacity: 0;\r\n transform: scale(0.6);\r\n}\r\n\r\n@media (max-width: 639px) {\r\n .j-toaster {\r\n --j-toast-edge: var(--juxt-space-2);\r\n\r\n right: var(--j-toast-edge);\r\n left: var(--j-toast-edge);\r\n width: auto;\r\n max-width: none;\r\n transform: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\r\nimport { formatHotkey, useHotkey } from '../../composables/useHotkey'\r\nimport { toastState } from '../../composables/useToast'\r\nimport { focusElement, unrefElement } from '../../utils/dom'\r\nimport JToastItem from './JToastItem.vue'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport type ToasterPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'\r\n\r\nexport interface ToasterProps {\r\n position?: ToasterPosition\r\n /** How many toasts are visible at once. Extra toasts wait their turn. */\r\n max?: number\r\n /** Shortcut that moves focus to the newest toast. */\r\n hotkey?: string\r\n /** Accessible name for the notification region. */\r\n label?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<ToasterProps>(), {\r\n position: 'bottom-right',\r\n max: 4,\r\n hotkey: 'alt+t',\r\n label: 'Notifications',\r\n})\r\n\r\nconst list = ref()\r\nconst hovered = ref(false)\r\nconst focused = ref(false)\r\nconst hidden = ref(false)\r\nconst paused = computed(() => hovered.value || focused.value || hidden.value)\r\n\r\nconst isTop = computed(() => props.position.startsWith('top'))\r\n\r\n// Oldest first in the store; show the newest `max`, nearest the screen edge.\r\nconst visible = computed(() => {\r\n const items = toastState.toasts.slice(-props.max)\r\n return isTop.value ? items.reverse() : items\r\n})\r\n\r\nfunction dismiss(id: string) {\r\n const index = toastState.toasts.findIndex(t => t.id === id)\r\n if (index === -1) return\r\n const [removed] = toastState.toasts.splice(index, 1)\r\n removed?.onDismiss?.()\r\n}\r\n\r\nconst hotkeyLabel = ref('')\r\nonMounted(() => (hotkeyLabel.value = formatHotkey(props.hotkey).join('+')))\r\n\r\nuseHotkey(() => props.hotkey, () => {\r\n const items = unrefElement(list.value)?.querySelectorAll<HTMLElement>('.j-toast')\r\n if (!items?.length) return\r\n focusElement(isTop.value ? items[0] : items[items.length - 1])\r\n})\r\n\r\nfunction onVisibility() {\r\n hidden.value = document.visibilityState === 'hidden'\r\n}\r\nonMounted(() => document.addEventListener('visibilitychange', onVisibility))\r\nonBeforeUnmount(() => document.removeEventListener('visibilitychange', onVisibility))\r\n\r\nfunction onFocusOut(event: FocusEvent) {\r\n const current = event.currentTarget as HTMLElement\r\n if (!current.contains(event.relatedTarget as Node | null)) focused.value = false\r\n}\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <section\r\n class=\"j-toaster\"\r\n :class=\"`j-toaster--${position}`\"\r\n :aria-label=\"hotkeyLabel ? `${label} (${hotkeyLabel})` : label\"\r\n data-juxt-layer-ignore\r\n >\r\n <TransitionGroup\r\n ref=\"list\"\r\n tag=\"ol\"\r\n name=\"j-toast\"\r\n class=\"j-toaster__list\"\r\n aria-live=\"polite\"\r\n aria-relevant=\"additions text\"\r\n @pointerenter=\"hovered = true\"\r\n @pointerleave=\"hovered = false\"\r\n @focusin=\"focused = true\"\r\n @focusout=\"onFocusOut\"\r\n >\r\n <JToastItem\r\n v-for=\"item in visible\"\r\n :key=\"item.id\"\r\n :toast=\"item\"\r\n :paused=\"paused\"\r\n @dismiss=\"dismiss\"\r\n />\r\n </TransitionGroup>\r\n </section>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-toaster {\r\n --j-toast-width: 22.5rem;\r\n --j-toast-edge: var(--juxt-space-4);\r\n\r\n position: fixed;\r\n z-index: var(--juxt-z-toast);\r\n width: var(--j-toast-width);\r\n max-width: calc(100vw - var(--j-toast-edge) * 2);\r\n pointer-events: none;\r\n}\r\n\r\n.j-toaster--top-left,\r\n.j-toaster--top-center,\r\n.j-toaster--top-right {\r\n top: max(var(--j-toast-edge), env(safe-area-inset-top));\r\n}\r\n\r\n.j-toaster--bottom-left,\r\n.j-toaster--bottom-center,\r\n.j-toaster--bottom-right {\r\n bottom: max(var(--j-toast-edge), env(safe-area-inset-bottom));\r\n}\r\n\r\n.j-toaster--top-left,\r\n.j-toaster--bottom-left {\r\n left: var(--j-toast-edge);\r\n}\r\n\r\n.j-toaster--top-right,\r\n.j-toaster--bottom-right {\r\n right: calc(var(--j-toast-edge) + var(--juxt-scrollbar-width, 0px));\r\n}\r\n\r\n.j-toaster--top-center,\r\n.j-toaster--bottom-center {\r\n left: 50%;\r\n transform: translateX(-50%);\r\n}\r\n\r\n.j-toaster__list {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-2);\r\n margin: 0;\r\n padding: 0;\r\n list-style: none;\r\n}\r\n\r\n.j-toast {\r\n --j-toast-enter: calc(var(--juxt-motion-shift) * 3);\r\n\r\n position: relative;\r\n display: flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n box-sizing: border-box;\r\n width: 100%;\r\n padding: var(--juxt-space-3) var(--juxt-space-10) var(--juxt-space-3) var(--juxt-space-3);\r\n overflow: hidden;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-md);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n pointer-events: auto;\r\n opacity: var(--j-toast-swipe-opacity, 1);\r\n transform: translateX(var(--j-toast-swipe, 0));\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n touch-action: pan-y;\r\n transition:\r\n transform var(--juxt-duration-slow) var(--juxt-ease-out),\r\n opacity var(--juxt-duration-normal) var(--juxt-ease-out),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-toaster--top-left .j-toast,\r\n.j-toaster--top-center .j-toast,\r\n.j-toaster--top-right .j-toast {\r\n --j-toast-enter: calc(var(--juxt-motion-shift) * -3);\r\n}\r\n\r\n.j-toast.is-swiping {\r\n transition: none;\r\n user-select: none;\r\n}\r\n\r\n.j-toast:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-toast__icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 1rem;\r\n height: 1.25rem;\r\n}\r\n\r\n.j-toast__icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-toast--success .j-toast__icon {\r\n color: var(--juxt-success-text);\r\n}\r\n\r\n.j-toast--error .j-toast__icon {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-toast--warning .j-toast__icon {\r\n color: var(--juxt-warning-text);\r\n}\r\n\r\n.j-toast--info .j-toast__icon {\r\n color: var(--juxt-info-text);\r\n}\r\n\r\n.j-toast--loading .j-toast__icon {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-toast__body {\r\n display: flex;\r\n flex: 1;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-toast__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1.25rem;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-toast__description {\r\n margin: 0;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-toast__actions {\r\n display: flex;\r\n gap: var(--juxt-space-2);\r\n margin-top: var(--juxt-space-2);\r\n}\r\n\r\n.j-toast__action {\r\n height: var(--juxt-control-xs);\r\n padding: 0 var(--juxt-space-2);\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-surface);\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n cursor: pointer;\r\n transition-property: background-color, border-color, outline-color, outline-offset;\r\n}\r\n\r\n.j-toast__action:hover {\r\n border-color: var(--juxt-border-strong);\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-toast__close {\r\n position: absolute;\r\n top: var(--juxt-space-2-5);\r\n right: var(--juxt-space-2-5);\r\n display: grid;\r\n place-items: center;\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-xs);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-toast__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-toast__close > svg {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n\r\n/* Remaining time, drawn as a hairline that drains toward the start. */\r\n.j-toast__progress {\r\n position: absolute;\r\n right: 0;\r\n bottom: 0;\r\n left: 0;\r\n height: 2px;\r\n background: var(--juxt-border-strong);\r\n opacity: 0.6;\r\n transform-origin: left;\r\n animation: j-toast-progress linear forwards;\r\n}\r\n\r\n.j-toast--success .j-toast__progress {\r\n background: var(--juxt-accent-border);\r\n}\r\n\r\n.j-toast__progress.is-paused {\r\n animation-play-state: paused;\r\n}\r\n\r\n@keyframes j-toast-progress {\r\n from {\r\n transform: scaleX(1);\r\n }\r\n\r\n to {\r\n transform: scaleX(0);\r\n }\r\n}\r\n\r\n/* List motion */\r\n.j-toast-enter-from {\r\n opacity: 0;\r\n transform: translateY(var(--j-toast-enter)) scale(0.98);\r\n}\r\n\r\n.j-toast-leave-active {\r\n position: absolute;\r\n width: 100%;\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-in),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-in);\r\n}\r\n\r\n.j-toast-leave-to {\r\n opacity: 0;\r\n transform: scale(0.96);\r\n}\r\n\r\n.j-toast-leave-to.is-swiped {\r\n transform: translateX(calc(var(--j-toast-width) * var(--j-swipe-sign, 1)));\r\n}\r\n\r\n.j-toast-leave-to.is-swiped[data-swipe-direction='-1'] {\r\n --j-swipe-sign: -1;\r\n}\r\n\r\n.j-toast-move {\r\n transition: transform var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-toast-icon-enter-active,\r\n.j-toast-icon-leave-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-toast-icon-enter-from,\r\n.j-toast-icon-leave-to {\r\n opacity: 0;\r\n transform: scale(0.6);\r\n}\r\n\r\n@media (max-width: 639px) {\r\n .j-toaster {\r\n --j-toast-edge: var(--juxt-space-2);\r\n\r\n right: var(--j-toast-edge);\r\n left: var(--j-toast-edge);\r\n width: auto;\r\n max-width: none;\r\n transform: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script lang=\"ts\">\r\n// Shared across every tooltip: once one has been shown, neighbours open\r\n// instantly for a moment. Scanning a toolbar shouldn't mean waiting at each icon.\r\nlet warmUntil = 0\r\nconst WARM_WINDOW = 400\r\n</script>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, onBeforeUnmount, ref, useId } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { unrefElement } from '../../utils/dom'\r\nimport { JSlot } from '../primitives/JSlot'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface TooltipProps {\r\n /** Tooltip text. Use the `content` slot for richer content. */\r\n content?: string\r\n side?: 'top' | 'right' | 'bottom' | 'left'\r\n align?: 'start' | 'center' | 'end'\r\n /** Hover delay before opening, in ms. Keyboard focus opens immediately. */\r\n delay?: number\r\n disabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<TooltipProps>(), {\r\n side: 'top',\r\n align: 'center',\r\n delay: 450,\r\n})\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n content?: () => unknown\r\n}>()\r\n\r\nconst id = `j-${useId()}-tooltip`\r\nconst open = ref(false)\r\nconst triggerRef = ref()\r\nconst triggerEl = computed(() => unrefElement(triggerRef.value))\r\nconst floating = ref<HTMLElement | null>(null)\r\n\r\nconst placement = computed(() => (props.align === 'center' ? props.side : `${props.side}-${props.align}` as const))\r\nconst { styles } = useFloating(triggerEl, floating, () => ({ placement: placement.value, offset: 6 }))\r\n\r\nuseLayer({\r\n active: open,\r\n elements: () => [triggerEl.value, floating.value],\r\n onEscape: () => hide(true),\r\n})\r\n\r\nlet openTimer: ReturnType<typeof setTimeout> | undefined\r\nlet closeTimer: ReturnType<typeof setTimeout> | undefined\r\n// After a click, stay quiet until the pointer leaves — the user is acting, not reading.\r\nlet suppressed = false\r\n\r\nfunction show(immediate = false) {\r\n if (props.disabled || suppressed) return\r\n clearTimeout(closeTimer)\r\n if (open.value) return\r\n clearTimeout(openTimer)\r\n if (immediate || Date.now() < warmUntil) open.value = true\r\n else openTimer = setTimeout(() => (open.value = true), props.delay)\r\n}\r\n\r\nfunction hide(immediate = false) {\r\n clearTimeout(openTimer)\r\n clearTimeout(closeTimer)\r\n const close = () => {\r\n if (open.value) warmUntil = Date.now() + WARM_WINDOW\r\n open.value = false\r\n }\r\n if (immediate) close()\r\n // A short grace period lets the pointer travel onto the tooltip itself.\r\n else closeTimer = setTimeout(close, 80)\r\n}\r\n\r\nonBeforeUnmount(() => {\r\n clearTimeout(openTimer)\r\n clearTimeout(closeTimer)\r\n})\r\n\r\nconst triggerProps = computed(() => ({\r\n 'aria-describedby': props.disabled ? undefined : id,\r\n 'onPointerenter': (event: PointerEvent) => event.pointerType === 'mouse' && show(),\r\n 'onPointerleave': (event: PointerEvent) => {\r\n suppressed = false\r\n if (event.pointerType === 'mouse') hide()\r\n },\r\n 'onPointerdown': () => {\r\n suppressed = true\r\n hide(true)\r\n },\r\n 'onFocus': (event: FocusEvent) => {\r\n const target = event.target as HTMLElement\r\n if (target.matches(':focus-visible')) show(true)\r\n },\r\n 'onBlur': () => hide(true),\r\n}))\r\n</script>\r\n\r\n<template>\r\n <JSlot ref=\"triggerRef\" v-bind=\"triggerProps\">\r\n <slot />\r\n </JSlot>\r\n <span v-if=\"!disabled\" :id=\"id\" class=\"j-sr-only\">\r\n <slot name=\"content\">{{ content }}</slot>\r\n </span>\r\n <JPortal>\r\n <Transition name=\"j-tooltip\">\r\n <div\r\n v-if=\"open\"\r\n ref=\"floating\"\r\n class=\"j-tooltip\"\r\n :style=\"styles\"\r\n aria-hidden=\"true\"\r\n data-juxt-layer-ignore\r\n @pointerenter=\"show(true)\"\r\n @pointerleave=\"hide()\"\r\n >\r\n <slot name=\"content\">\r\n {{ content }}\r\n </slot>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-tooltip {\r\n z-index: var(--juxt-z-tooltip);\r\n box-sizing: border-box;\r\n max-width: min(17.5rem, calc(100vw - 16px));\r\n padding: var(--juxt-space-1) var(--juxt-space-2);\r\n border: 1px solid var(--juxt-tooltip-border);\r\n border-radius: var(--juxt-radius-sm);\r\n background: var(--juxt-tooltip-bg);\r\n box-shadow: var(--juxt-shadow-sm);\r\n color: var(--juxt-tooltip-fg);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: 0;\r\n overflow-wrap: break-word;\r\n pointer-events: auto;\r\n}\r\n\r\n.j-tooltip .j-kbd {\r\n height: 1rem;\r\n min-width: 1rem;\r\n margin-left: var(--juxt-space-1);\r\n border-color: rgb(255 255 255 / 0.14);\r\n background: rgb(255 255 255 / 0.08);\r\n color: rgb(255 255 255 / 0.72);\r\n}\r\n\r\n.j-tooltip-enter-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-tooltip-leave-active {\r\n transition: opacity var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-tooltip-enter-from,\r\n.j-tooltip-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-tooltip-enter-from {\r\n transform: translate(\r\n calc(var(--juxt-motion-shift) * 0.5 * var(--j-pop-x, 0)),\r\n calc(var(--juxt-motion-shift) * 0.5 * var(--j-pop-dir, 0))\r\n );\r\n}\r\n}\r\n</style>\r\n","<script lang=\"ts\">\r\n// Shared across every tooltip: once one has been shown, neighbours open\r\n// instantly for a moment. Scanning a toolbar shouldn't mean waiting at each icon.\r\nlet warmUntil = 0\r\nconst WARM_WINDOW = 400\r\n</script>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, onBeforeUnmount, ref, useId } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { unrefElement } from '../../utils/dom'\r\nimport { JSlot } from '../primitives/JSlot'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface TooltipProps {\r\n /** Tooltip text. Use the `content` slot for richer content. */\r\n content?: string\r\n side?: 'top' | 'right' | 'bottom' | 'left'\r\n align?: 'start' | 'center' | 'end'\r\n /** Hover delay before opening, in ms. Keyboard focus opens immediately. */\r\n delay?: number\r\n disabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<TooltipProps>(), {\r\n side: 'top',\r\n align: 'center',\r\n delay: 450,\r\n})\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n content?: () => unknown\r\n}>()\r\n\r\nconst id = `j-${useId()}-tooltip`\r\nconst open = ref(false)\r\nconst triggerRef = ref()\r\nconst triggerEl = computed(() => unrefElement(triggerRef.value))\r\nconst floating = ref<HTMLElement | null>(null)\r\n\r\nconst placement = computed(() => (props.align === 'center' ? props.side : `${props.side}-${props.align}` as const))\r\nconst { styles } = useFloating(triggerEl, floating, () => ({ placement: placement.value, offset: 6 }))\r\n\r\nuseLayer({\r\n active: open,\r\n elements: () => [triggerEl.value, floating.value],\r\n onEscape: () => hide(true),\r\n})\r\n\r\nlet openTimer: ReturnType<typeof setTimeout> | undefined\r\nlet closeTimer: ReturnType<typeof setTimeout> | undefined\r\n// After a click, stay quiet until the pointer leaves — the user is acting, not reading.\r\nlet suppressed = false\r\n\r\nfunction show(immediate = false) {\r\n if (props.disabled || suppressed) return\r\n clearTimeout(closeTimer)\r\n if (open.value) return\r\n clearTimeout(openTimer)\r\n if (immediate || Date.now() < warmUntil) open.value = true\r\n else openTimer = setTimeout(() => (open.value = true), props.delay)\r\n}\r\n\r\nfunction hide(immediate = false) {\r\n clearTimeout(openTimer)\r\n clearTimeout(closeTimer)\r\n const close = () => {\r\n if (open.value) warmUntil = Date.now() + WARM_WINDOW\r\n open.value = false\r\n }\r\n if (immediate) close()\r\n // A short grace period lets the pointer travel onto the tooltip itself.\r\n else closeTimer = setTimeout(close, 80)\r\n}\r\n\r\nonBeforeUnmount(() => {\r\n clearTimeout(openTimer)\r\n clearTimeout(closeTimer)\r\n})\r\n\r\nconst triggerProps = computed(() => ({\r\n 'aria-describedby': props.disabled ? undefined : id,\r\n 'onPointerenter': (event: PointerEvent) => event.pointerType === 'mouse' && show(),\r\n 'onPointerleave': (event: PointerEvent) => {\r\n suppressed = false\r\n if (event.pointerType === 'mouse') hide()\r\n },\r\n 'onPointerdown': () => {\r\n suppressed = true\r\n hide(true)\r\n },\r\n 'onFocus': (event: FocusEvent) => {\r\n const target = event.target as HTMLElement\r\n if (target.matches(':focus-visible')) show(true)\r\n },\r\n 'onBlur': () => hide(true),\r\n}))\r\n</script>\r\n\r\n<template>\r\n <JSlot ref=\"triggerRef\" v-bind=\"triggerProps\">\r\n <slot />\r\n </JSlot>\r\n <span v-if=\"!disabled\" :id=\"id\" class=\"j-sr-only\">\r\n <slot name=\"content\">{{ content }}</slot>\r\n </span>\r\n <JPortal>\r\n <Transition name=\"j-tooltip\">\r\n <div\r\n v-if=\"open\"\r\n ref=\"floating\"\r\n class=\"j-tooltip\"\r\n :style=\"styles\"\r\n aria-hidden=\"true\"\r\n data-juxt-layer-ignore\r\n @pointerenter=\"show(true)\"\r\n @pointerleave=\"hide()\"\r\n >\r\n <slot name=\"content\">\r\n {{ content }}\r\n </slot>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer juxt {\r\n.j-tooltip {\r\n z-index: var(--juxt-z-tooltip);\r\n box-sizing: border-box;\r\n max-width: min(17.5rem, calc(100vw - 16px));\r\n padding: var(--juxt-space-1) var(--juxt-space-2);\r\n border: 1px solid var(--juxt-tooltip-border);\r\n border-radius: var(--juxt-radius-sm);\r\n background: var(--juxt-tooltip-bg);\r\n box-shadow: var(--juxt-shadow-sm);\r\n color: var(--juxt-tooltip-fg);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: 0;\r\n overflow-wrap: break-word;\r\n pointer-events: auto;\r\n}\r\n\r\n.j-tooltip .j-kbd {\r\n height: 1rem;\r\n min-width: 1rem;\r\n margin-left: var(--juxt-space-1);\r\n border-color: rgb(255 255 255 / 0.14);\r\n background: rgb(255 255 255 / 0.08);\r\n color: rgb(255 255 255 / 0.72);\r\n}\r\n\r\n.j-tooltip-enter-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-tooltip-leave-active {\r\n transition: opacity var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-tooltip-enter-from,\r\n.j-tooltip-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-tooltip-enter-from {\r\n transform: translate(\r\n calc(var(--juxt-motion-shift) * 0.5 * var(--j-pop-x, 0)),\r\n calc(var(--juxt-motion-shift) * 0.5 * var(--j-pop-dir, 0))\r\n );\r\n}\r\n}\r\n</style>\r\n","export { default as JAccordion } from './components/accordion/JAccordion.vue'\nexport { default as JAccordionItem } from './components/accordion/JAccordionItem.vue'\nexport { default as JAlert } from './components/alert/JAlert.vue'\nexport { default as JAvatar } from './components/avatar/JAvatar.vue'\nexport { default as JBadge } from './components/badge/JBadge.vue'\nexport { default as JButton } from './components/button/JButton.vue'\nexport { default as JCard } from './components/card/JCard.vue'\nexport { default as JCheckbox } from './components/checkbox/JCheckbox.vue'\nexport { default as JCommandPalette } from './components/command-palette/JCommandPalette.vue'\nexport { default as JDialog } from './components/dialog/JDialog.vue'\nexport { default as JDropdownMenu } from './components/dropdown-menu/JDropdownMenu.vue'\nexport { default as JDropdownMenuItem } from './components/dropdown-menu/JDropdownMenuItem.vue'\nexport { default as JDropdownMenuLabel } from './components/dropdown-menu/JDropdownMenuLabel.vue'\nexport { default as JDropdownMenuSeparator } from './components/dropdown-menu/JDropdownMenuSeparator.vue'\nexport { default as JDropdownMenuSub } from './components/dropdown-menu/JDropdownMenuSub.vue'\nexport { default as JInput } from './components/input/JInput.vue'\nexport { default as JKbd } from './components/kbd/JKbd.vue'\nexport { default as JPopover } from './components/popover/JPopover.vue'\nexport { default as JProgress } from './components/progress/JProgress.vue'\nexport { default as JRadio } from './components/radio/JRadio.vue'\nexport { default as JRadioGroup } from './components/radio/JRadioGroup.vue'\nexport { default as JSelect } from './components/select/JSelect.vue'\nexport { default as JSeparator } from './components/separator/JSeparator.vue'\nexport { default as JSheet } from './components/sheet/JSheet.vue'\nexport { default as JSkeleton } from './components/skeleton/JSkeleton.vue'\nexport { default as JSpinner } from './components/spinner/JSpinner.vue'\nexport { default as JSwitch } from './components/switch/JSwitch.vue'\nexport { default as JTabs } from './components/tabs/JTabs.vue'\nexport { default as JTextarea } from './components/textarea/JTextarea.vue'\nexport { default as JToaster } from './components/toast/JToaster.vue'\nexport { default as JTooltip } from './components/tooltip/JTooltip.vue'\n","import { onMounted, ref } from 'vue'\r\n\r\nexport interface RecentCommandsOptions {\r\n /** localStorage key. */\r\n key?: string\r\n /** How many ids to remember. */\r\n limit?: number\r\n}\r\n\r\n/**\r\n * Remembers which commands were used most recently, per browser.\r\n * Pair `record(item.id)` in your select handler with `resolve(allItems)`\r\n * to feed the palette's `recent` prop.\r\n */\r\nexport function useRecentCommands(options: RecentCommandsOptions = {}) {\r\n const key = options.key ?? 'juxt:recent-commands'\r\n const limit = options.limit ?? 5\r\n const ids = ref<string[]>([])\r\n\r\n function read(): string[] {\r\n try {\r\n const value = JSON.parse(localStorage.getItem(key) ?? '[]')\r\n return Array.isArray(value) ? value.filter((id): id is string => typeof id === 'string') : []\r\n } catch {\r\n return []\r\n }\r\n }\r\n\r\n function write() {\r\n try {\r\n localStorage.setItem(key, JSON.stringify(ids.value))\r\n } catch {\r\n // Private mode or storage disabled: recents simply won't persist.\r\n }\r\n }\r\n\r\n onMounted(() => (ids.value = read()))\r\n\r\n function record(id: string) {\r\n ids.value = [id, ...ids.value.filter(existing => existing !== id)].slice(0, limit)\r\n write()\r\n }\r\n\r\n function clear() {\r\n ids.value = []\r\n write()\r\n }\r\n\r\n /** Map remembered ids back to items, dropping any that no longer exist. */\r\n function resolve<T extends { id: string }>(items: T[]): T[] {\r\n const byId = new Map(items.map(item => [item.id, item]))\r\n return ids.value.map(id => byId.get(id)).filter((item): item is T => !!item)\r\n }\r\n\r\n return { ids, record, clear, resolve }\r\n}\r\n","import type { Component, Plugin } from 'vue'\r\nimport * as components from './components'\r\n\r\n/**\r\n * Registers every Juxt component globally.\r\n *\r\n * app.use(JuxtUI)\r\n *\r\n * Prefer direct imports (or the Nuxt module) if you want tree-shaking.\r\n */\r\nexport const JuxtUI: Plugin = {\r\n install(app) {\r\n for (const [name, component] of Object.entries(components)) app.component(name, component as Component)\r\n },\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["_useModel","_openBlock","_createElementBlock","_renderSlot","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_openBlock","_createElementBlock","_normalizeClass","_createBlock","_resolveDynamicComponent","_withCtx","_createElementVNode","_unref","_renderSlot","_createTextVNode","_toDisplayString","_createVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_openBlock","_createElementBlock","_normalizeClass","_renderSlot","_createBlock","_resolveDynamicComponent","_createCommentVNode","_createElementVNode","_toDisplayString","_createTextVNode","_createVNode","_unref","_sfc_main","_hoisted_1","_openBlock","_createElementBlock","_normalizeStyle","_createElementVNode","_renderSlot","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_openBlock","_createElementBlock","_normalizeClass","_createElementVNode","_createCommentVNode","_renderSlot","_Fragment","_createTextVNode","_toDisplayString","_sfc_main","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","_createVNode","JAvatar","_toDisplayString","_createCommentVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_normalizeClass","_renderSlot","_createCommentVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_openBlock","_createElementBlock","_normalizeClass","_renderSlot","_createBlock","_resolveDynamicComponent","_createCommentVNode","_createElementVNode","_toDisplayString","_createTextVNode","_createVNode","_unref","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_openBlock","_createElementBlock","_createElementVNode","_Fragment","_renderList","_toDisplayString","_renderSlot","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_openBlock","_createBlock","_resolveDynamicComponent","_mergeProps","_withCtx","_createElementVNode","_createElementBlock","_renderSlot","_createCommentVNode","_createVNode","_Transition","_unref","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_openBlock","_createBlock","_resolveDynamicComponent","_normalizeClass","_withCtx","_createElementBlock","_renderSlot","_createElementVNode","_toDisplayString","_createCommentVNode","_sfc_main","$attrs","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_useModel","_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createElementVNode","_mergeProps","_createStaticVNode","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_useModel","_openBlock","_createElementBlock","_createElementVNode","_createVNode","_unref","_renderSlot","_createTextVNode","_toDisplayString","_sfc_main","listening","onKeydown","listen","unlisten","_openBlock","_createBlock","_Teleport","_renderSlot","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_hoisted_8","_hoisted_9","_useModel","_openBlock","_createBlock","JPortal","_withCtx","_createVNode","_Transition","_createElementBlock","_createCommentVNode","_createElementVNode","_unref","_withDirectives","_vModelText","_withModifiers","_Fragment","_renderList","_toDisplayString","_normalizeClass","_renderSlot","_resolveDynamicComponent","_createTextVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_useModel","_openBlock","_createBlock","JPortal","_withCtx","_createVNode","_Transition","_createElementBlock","_createCommentVNode","_unref","_createElementVNode","_normalizeClass","_renderSlot","_toDisplayString","_sfc_main","_hoisted_1","_openBlock","_createBlock","JPortal","_withCtx","_createVNode","_Transition","_createElementBlock","_normalizeClass","_normalizeStyle","_unref","_renderSlot","_createCommentVNode","_sfc_main","_useModel","_openBlock","_createElementBlock","_Fragment","_createVNode","_unref","_mergeProps","_withCtx","_renderSlot","JMenuContent","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createBlock","_resolveDynamicComponent","_normalizeClass","_withCtx","_createElementBlock","_renderSlot","_createCommentVNode","_createElementVNode","_toDisplayString","_unref","_sfc_main","_hoisted_1","_openBlock","_createElementBlock","_renderSlot","_sfc_main","_hoisted_1","_openBlock","_createElementBlock","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createElementBlock","_Fragment","_createElementVNode","_renderSlot","_createCommentVNode","_toDisplayString","_createVNode","_unref","JMenuContent","_withCtx","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_openBlock","_createElementBlock","_normalizeClass","_renderSlot","_createCommentVNode","_toDisplayString","_createTextVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_openBlock","_createElementBlock","_normalizeClass","_createBlock","_resolveDynamicComponent","_withCtx","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_createElementVNode","_createVNode","_Transition","_sfc_main","$attrs","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_useModel","_openBlock","_createBlock","JFieldShell","_unref","_normalizeClass","_normalizeStyle","_createSlots","_withCtx","_createElementVNode","_createElementBlock","_renderSlot","_createCommentVNode","_withDirectives","_mergeProps","_vModelDynamic","_sfc_main","_openBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","_toDisplayString","_renderSlot","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_useModel","_openBlock","_createElementBlock","_normalizeClass","_createElementVNode","_createVNode","_unref","_Fragment","_renderList","_toDisplayString","_sfc_main","_hoisted_1","_useModel","_openBlock","_createElementBlock","_Fragment","_createVNode","_unref","_mergeProps","_withCtx","_renderSlot","JPortal","_Transition","_normalizeStyle","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_openBlock","_createElementBlock","_normalizeClass","_toDisplayString","_createCommentVNode","_createElementVNode","_normalizeStyle","_sfc_main","$attrs","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createElementVNode","_mergeProps","_unref","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_useModel","_openBlock","_createElementBlock","_normalizeClass","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_createElementVNode","_sfc_main","_hoisted_1","_hoisted_2","_useModel","_openBlock","_createElementBlock","_normalizeClass","_Fragment","_renderList","_createElementVNode","_normalizeStyle","_sfc_main","$attrs","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_useModel","_openBlock","_createBlock","JFieldShell","_unref","_normalizeClass","_normalizeStyle","_createSlots","_withCtx","_createElementVNode","_mergeProps","_renderSlot","_createTextVNode","_toDisplayString","_createElementBlock","_Fragment","_createVNode","_createCommentVNode","JPortal","_Transition","_withModifiers","_renderList","_sfc_main","$slots","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_normalizeClass","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_useModel","_openBlock","_createBlock","JPortal","_withCtx","_createVNode","_Transition","_createElementBlock","_createCommentVNode","_normalizeClass","_unref","_createElementVNode","_renderSlot","_toDisplayString","_sfc_main","_openBlock","_createElementBlock","_normalizeStyle","_Fragment","_renderList","_normalizeClass","_sfc_main","_hoisted_1","_hoisted_2","_useModel","_openBlock","_createElementBlock","_normalizeClass","_createElementVNode","_normalizeStyle","_Fragment","_renderList","_toDisplayString","_createCommentVNode","_sfc_main","_hoisted_1","_hoisted_2","_openBlock","_createElementBlock","_createVNode","_unref","_toDisplayString","_createCommentVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_hoisted_7","_openBlock","_createElementBlock","_createElementVNode","_renderSlot","_createCommentVNode","_toDisplayString","_normalizeClass","_createBlock","_resolveDynamicComponent","_createTextVNode","_sfc_main","$attrs","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_useModel","_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createElementVNode","_mergeProps","_createCommentVNode","_renderSlot","_createTextVNode","_toDisplayString","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_useModel","_openBlock","_createElementBlock","_normalizeClass","_createElementVNode","_normalizeStyle","_Fragment","_renderList","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_openBlock","_createElementBlock","_normalizeClass","_renderSlot","_createCommentVNode","_createElementVNode","_createVNode","_unref","_sfc_main","$attrs","$slots","_hoisted_1","_useModel","_openBlock","_createBlock","JFieldShell","_unref","_normalizeClass","_normalizeStyle","_createSlots","_withCtx","_createElementVNode","_withDirectives","_mergeProps","_vModelText","_renderSlot","_sfc_main","_hoisted_1","_openBlock","_createElementBlock","_renderSlot","_sfc_main","$slots","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","_openBlock","_createElementBlock","_normalizeClass","_createElementVNode","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","_sfc_main","_hoisted_1","_openBlock","_createElementBlock","_normalizeClass","_normalizeStyle","_createVNode","_Transition","_withCtx","_createBlock","_unref","_resolveDynamicComponent","_createCommentVNode","_createElementVNode","_toDisplayString","_sfc_main","_hoisted_1","_openBlock","_createBlock","JPortal","_withCtx","_createElementVNode","_normalizeClass","_createVNode","_TransitionGroup","_createElementBlock","_Fragment","_renderList","JToastItem","_sfc_main","_hoisted_1","_useModel","_openBlock","_createElementBlock","_normalizeClass","_renderSlot","_sfc_main","_openBlock","_createElementBlock","_normalizeClass","_unref","_renderSlot","_sfc_main","_openBlock","_createElementBlock","_Fragment","_createVNode","_unref","_mergeProps","_withCtx","_renderSlot","_createTextVNode","_toDisplayString","_createCommentVNode","JPortal","_Transition","_normalizeStyle","_sfc_main","_openBlock","_createBlock","_resolveDynamicComponent","_withCtx","_renderSlot","_sfc_main","components"],"sources":["../src/utils/dom.ts","../src/components/accordion/context.ts","../src/components/accordion/JAccordion.vue","../src/components/accordion/JAccordion.vue","../src/icons.ts","../src/components/accordion/JAccordionItem.vue","../src/components/accordion/JAccordionItem.vue","../src/components/alert/JAlert.vue","../src/components/alert/JAlert.vue","../src/components/aspect-ratio/JAspectRatio.vue","../src/components/aspect-ratio/JAspectRatio.vue","../src/components/avatar/JAvatar.vue","../src/components/avatar/JAvatar.vue","../src/components/avatar-group/JAvatarGroup.vue","../src/components/avatar-group/JAvatarGroup.vue","../src/components/badge/JBadge.vue","../src/components/badge/JBadge.vue","../src/components/banner/JBanner.vue","../src/components/banner/JBanner.vue","../src/components/breadcrumb/JBreadcrumb.vue","../src/components/breadcrumb/JBreadcrumb.vue","../src/components/button/JButton.vue","../src/components/button/JButton.vue","../src/components/card/JCard.vue","../src/components/card/JCard.vue","../src/components/checkbox/JCheckbox.vue","../src/components/checkbox/JCheckbox.vue","../src/components/collapsible/JCollapsible.vue","../src/components/collapsible/JCollapsible.vue","../src/composables/useFocusTrap.ts","../src/composables/useHotkey.ts","../src/composables/useLayer.ts","../src/composables/useScrollLock.ts","../src/components/command-palette/search.ts","../src/components/primitives/JPortal.vue","../src/components/primitives/JPortal.vue","../src/components/command-palette/JCommandPalette.vue","../src/components/command-palette/JCommandPalette.vue","../src/composables/useModal.ts","../src/components/dialog/JDialog.vue","../src/components/dialog/JDialog.vue","../src/components/primitives/JSlot.ts","../src/components/dropdown-menu/context.ts","../src/composables/useFloating.ts","../src/composables/useTypeahead.ts","../src/components/dropdown-menu/JMenuContent.vue","../src/components/dropdown-menu/JMenuContent.vue","../src/components/dropdown-menu/JDropdownMenu.vue","../src/components/dropdown-menu/JDropdownMenu.vue","../src/components/dropdown-menu/JDropdownMenuItem.vue","../src/components/dropdown-menu/JDropdownMenuItem.vue","../src/components/dropdown-menu/JDropdownMenuLabel.vue","../src/components/dropdown-menu/JDropdownMenuLabel.vue","../src/components/dropdown-menu/JDropdownMenuSeparator.vue","../src/components/dropdown-menu/JDropdownMenuSub.vue","../src/components/dropdown-menu/JDropdownMenuSub.vue","../src/components/empty-state/JEmptyState.vue","../src/components/empty-state/JEmptyState.vue","../src/components/field/JFieldShell.vue","../src/components/field/JFieldShell.vue","../src/components/field/useField.ts","../src/components/input/JInput.vue","../src/components/input/JInput.vue","../src/components/kbd/JKbd.vue","../src/components/kbd/JKbd.vue","../src/components/pagination/JPagination.vue","../src/components/pagination/JPagination.vue","../src/components/popover/JPopover.vue","../src/components/popover/JPopover.vue","../src/components/progress/JProgress.vue","../src/components/progress/JProgress.vue","../src/components/radio/context.ts","../src/components/radio/JRadio.vue","../src/components/radio/JRadio.vue","../src/components/radio/JRadioGroup.vue","../src/components/radio/JRadioGroup.vue","../src/components/rating/JRating.vue","../src/components/rating/JRating.vue","../src/components/select/JSelect.vue","../src/components/select/JSelect.vue","../src/components/separator/JSeparator.vue","../src/components/separator/JSeparator.vue","../src/components/sheet/JSheet.vue","../src/components/sheet/JSheet.vue","../src/components/skeleton/JSkeleton.vue","../src/components/skeleton/JSkeleton.vue","../src/components/slider/JSlider.vue","../src/components/slider/JSlider.vue","../src/components/spinner/JSpinner.vue","../src/components/spinner/JSpinner.vue","../src/components/stat/JStat.vue","../src/components/stat/JStat.vue","../src/components/switch/JSwitch.vue","../src/components/switch/JSwitch.vue","../src/components/tabs/JTabs.vue","../src/components/tabs/JTabs.vue","../src/components/tag/JTag.vue","../src/components/tag/JTag.vue","../src/components/textarea/JTextarea.vue","../src/components/textarea/JTextarea.vue","../src/components/timeline/JTimeline.vue","../src/components/timeline/JTimeline.vue","../src/components/timeline/JTimelineItem.vue","../src/components/timeline/JTimelineItem.vue","../src/composables/useToast.ts","../src/components/toast/JToastItem.vue","../src/components/toast/JToastItem.vue","../src/components/toast/JToaster.vue","../src/components/toast/JToaster.vue","../src/components/toggle-group/context.ts","../src/components/toggle-group/JToggleGroup.vue","../src/components/toggle-group/JToggleGroup.vue","../src/components/toggle-group/JToggleGroupItem.vue","../src/components/toggle-group/JToggleGroupItem.vue","../src/components/tooltip/JTooltip.vue","../src/components/tooltip/JTooltip.vue","../src/components/visually-hidden/JVisuallyHidden.vue","../src/components/visually-hidden/JVisuallyHidden.vue","../src/components.ts","../src/composables/useRecentCommands.ts","../src/plugin.ts"],"sourcesContent":["import type { ComponentPublicInstance, MaybeRef } from 'vue'\r\nimport { unref } from 'vue'\r\n\r\nexport const isClient = typeof window !== 'undefined' && typeof document !== 'undefined'\r\n\r\nexport type MaybeElement = HTMLElement | SVGElement | ComponentPublicInstance | null | undefined\r\n\r\n/** Resolve a template ref that may point at an element or a component instance. */\r\nexport function unrefElement(target: MaybeRef<MaybeElement>): HTMLElement | null {\r\n const value = unref(target)\r\n if (!value) return null\r\n if (value instanceof Element) return value as HTMLElement\r\n const el = (value as ComponentPublicInstance).$el as Node | null\r\n // A component whose root is a fragment exposes a text node as $el.\r\n if (el && el.nodeType !== Node.ELEMENT_NODE) {\r\n const next = el.nextSibling\r\n return next instanceof HTMLElement ? next : null\r\n }\r\n return (el as HTMLElement | null) ?? null\r\n}\r\n\r\nconst FOCUSABLE = [\r\n 'a[href]',\r\n 'area[href]',\r\n 'button:not([disabled])',\r\n 'input:not([disabled]):not([type=\"hidden\"])',\r\n 'select:not([disabled])',\r\n 'textarea:not([disabled])',\r\n 'iframe',\r\n 'audio[controls]',\r\n 'video[controls]',\r\n '[contenteditable]:not([contenteditable=\"false\"])',\r\n '[tabindex]',\r\n].join(',')\r\n\r\nfunction isVisible(el: HTMLElement): boolean {\r\n if (el.hidden) return false\r\n // `checkVisibility` is precise where supported; the rect check covers the rest.\r\n if (typeof el.checkVisibility === 'function') return el.checkVisibility({ visibilityProperty: true })\r\n for (let node: HTMLElement | null = el; node; node = node.parentElement) {\r\n const style = getComputedStyle(node)\r\n if (style.display === 'none' || style.visibility === 'hidden') return false\r\n }\r\n return true\r\n}\r\n\r\n/** Elements that can receive keyboard focus, in DOM order. */\r\nexport function getFocusableElements(container: HTMLElement): HTMLElement[] {\r\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\r\n el => el.tabIndex >= 0 && !el.closest('[inert]') && isVisible(el),\r\n )\r\n}\r\n\r\nexport function focusElement(el: HTMLElement | null | undefined, options?: FocusOptions) {\r\n if (!el) return\r\n el.focus({ preventScroll: true, ...options })\r\n}\r\n\r\nexport const isMac = () => isClient && /mac|iphone|ipad|ipod/i.test(navigator.platform || navigator.userAgent)\r\n\r\nexport function prefersReducedMotion() {\r\n return isClient && window.matchMedia('(prefers-reduced-motion: reduce)').matches\r\n}\r\n","import type { InjectionKey } from 'vue'\n\nexport interface AccordionContext {\n isOpen: (value: string) => boolean\n toggle: (value: string) => void\n headingLevel: () => number\n}\n\nexport const ACCORDION: InjectionKey<AccordionContext> = Symbol('JAccordion')\n","<script setup lang=\"ts\">\r\nimport { provide } from 'vue'\r\nimport { focusElement } from '../../utils/dom'\r\nimport { ACCORDION } from './context'\r\n\r\nexport interface AccordionProps {\r\n /** `single` keeps at most one item open; `multiple` lets any number stay open. */\r\n type?: 'single' | 'multiple'\r\n /** In `single` mode, allow closing the open item so none are open. */\r\n collapsible?: boolean\r\n /** Heading level wrapping each trigger, for a correct document outline. */\r\n headingLevel?: 2 | 3 | 4 | 5 | 6\r\n}\r\n\r\nconst props = withDefaults(defineProps<AccordionProps>(), {\r\n type: 'single',\r\n collapsible: true,\r\n headingLevel: 3,\r\n})\r\n\r\n/** Open item(s): a value in `single` mode, an array in `multiple` mode. */\r\nconst model = defineModel<string | string[] | null>({ default: null })\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\nfunction openValues(): string[] {\r\n const value = model.value\r\n if (Array.isArray(value)) return value\r\n return value ? [value] : []\r\n}\r\n\r\nprovide(ACCORDION, {\r\n isOpen: value => openValues().includes(value),\r\n toggle: (value) => {\r\n const current = openValues()\r\n const isOpen = current.includes(value)\r\n if (props.type === 'multiple') {\r\n model.value = isOpen ? current.filter(v => v !== value) : [...current, value]\r\n return\r\n }\r\n if (isOpen) {\r\n if (props.collapsible) model.value = null\r\n return\r\n }\r\n model.value = value\r\n },\r\n headingLevel: () => props.headingLevel,\r\n})\r\n\r\n// Arrow keys move between headers, per the WAI-ARIA accordion pattern.\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const target = event.target as HTMLElement\r\n if (!target.classList.contains('j-accordion__trigger')) return\r\n const root = event.currentTarget as HTMLElement\r\n const triggers = Array.from(root.querySelectorAll<HTMLElement>('.j-accordion__trigger:not(:disabled)'))\r\n .filter(el => el.closest('.j-accordion') === root)\r\n const index = triggers.indexOf(target)\r\n let next: HTMLElement | undefined\r\n if (event.key === 'ArrowDown') next = triggers[(index + 1) % triggers.length]\r\n else if (event.key === 'ArrowUp') next = triggers[(index - 1 + triggers.length) % triggers.length]\r\n else if (event.key === 'Home') next = triggers[0]\r\n else if (event.key === 'End') next = triggers[triggers.length - 1]\r\n if (!next) return\r\n event.preventDefault()\r\n focusElement(next)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-accordion\" @keydown=\"onKeydown\">\r\n <slot />\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-accordion {\r\n display: flex;\r\n flex-direction: column;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { provide } from 'vue'\r\nimport { focusElement } from '../../utils/dom'\r\nimport { ACCORDION } from './context'\r\n\r\nexport interface AccordionProps {\r\n /** `single` keeps at most one item open; `multiple` lets any number stay open. */\r\n type?: 'single' | 'multiple'\r\n /** In `single` mode, allow closing the open item so none are open. */\r\n collapsible?: boolean\r\n /** Heading level wrapping each trigger, for a correct document outline. */\r\n headingLevel?: 2 | 3 | 4 | 5 | 6\r\n}\r\n\r\nconst props = withDefaults(defineProps<AccordionProps>(), {\r\n type: 'single',\r\n collapsible: true,\r\n headingLevel: 3,\r\n})\r\n\r\n/** Open item(s): a value in `single` mode, an array in `multiple` mode. */\r\nconst model = defineModel<string | string[] | null>({ default: null })\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\nfunction openValues(): string[] {\r\n const value = model.value\r\n if (Array.isArray(value)) return value\r\n return value ? [value] : []\r\n}\r\n\r\nprovide(ACCORDION, {\r\n isOpen: value => openValues().includes(value),\r\n toggle: (value) => {\r\n const current = openValues()\r\n const isOpen = current.includes(value)\r\n if (props.type === 'multiple') {\r\n model.value = isOpen ? current.filter(v => v !== value) : [...current, value]\r\n return\r\n }\r\n if (isOpen) {\r\n if (props.collapsible) model.value = null\r\n return\r\n }\r\n model.value = value\r\n },\r\n headingLevel: () => props.headingLevel,\r\n})\r\n\r\n// Arrow keys move between headers, per the WAI-ARIA accordion pattern.\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const target = event.target as HTMLElement\r\n if (!target.classList.contains('j-accordion__trigger')) return\r\n const root = event.currentTarget as HTMLElement\r\n const triggers = Array.from(root.querySelectorAll<HTMLElement>('.j-accordion__trigger:not(:disabled)'))\r\n .filter(el => el.closest('.j-accordion') === root)\r\n const index = triggers.indexOf(target)\r\n let next: HTMLElement | undefined\r\n if (event.key === 'ArrowDown') next = triggers[(index + 1) % triggers.length]\r\n else if (event.key === 'ArrowUp') next = triggers[(index - 1 + triggers.length) % triggers.length]\r\n else if (event.key === 'Home') next = triggers[0]\r\n else if (event.key === 'End') next = triggers[triggers.length - 1]\r\n if (!next) return\r\n event.preventDefault()\r\n focusElement(next)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-accordion\" @keydown=\"onKeydown\">\r\n <slot />\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-accordion {\r\n display: flex;\r\n flex-direction: column;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n}\r\n</style>\r\n","import type { FunctionalComponent, SVGAttributes } from 'vue'\r\nimport { h } from 'vue'\r\n\r\n/*\r\n * The handful of glyphs Juxt needs internally. Drawn on a 16px grid with a\r\n * 1.5px stroke so they sit at the same optical weight as 13–14px text.\r\n */\r\n\r\ntype Icon = FunctionalComponent<SVGAttributes>\r\n\r\nfunction icon(name: string, children: () => ReturnType<typeof h>[], viewBox = '0 0 16 16'): Icon {\r\n const component: Icon = (props) =>\r\n h(\r\n 'svg',\r\n {\r\n 'xmlns': 'http://www.w3.org/2000/svg',\r\n viewBox,\r\n 'width': 16,\r\n 'height': 16,\r\n 'fill': 'none',\r\n 'stroke': 'currentColor',\r\n 'stroke-width': 1.5,\r\n 'stroke-linecap': 'round',\r\n 'stroke-linejoin': 'round',\r\n 'aria-hidden': 'true',\r\n 'focusable': 'false',\r\n ...props,\r\n },\r\n children(),\r\n )\r\n component.displayName = name\r\n return component\r\n}\r\n\r\nexport const IconCheck = icon('IconCheck', () => [h('path', { d: 'M3.5 8.5l3 3 6-7' })])\r\nexport const IconChevronDown = icon('IconChevronDown', () => [h('path', { d: 'M4.5 6.25L8 9.75l3.5-3.5' })])\r\nexport const IconChevronUpDown = icon('IconChevronUpDown', () => [h('path', { d: 'M5.25 6L8 3.25 10.75 6M5.25 10L8 12.75 10.75 10' })])\r\nexport const IconChevronRight = icon('IconChevronRight', () => [h('path', { d: 'M6.25 4.5L9.75 8l-3.5 3.5' })])\r\nexport const IconX = icon('IconX', () => [h('path', { d: 'M4.5 4.5l7 7M11.5 4.5l-7 7' })])\r\nexport const IconSearch = icon('IconSearch', () => [h('circle', { cx: 7, cy: 7, r: 4.25 }), h('path', { d: 'M10.25 10.25L13 13' })])\r\nexport const IconCircleCheck = icon('IconCircleCheck', () => [h('circle', { cx: 8, cy: 8, r: 6.25 }), h('path', { d: 'M5.5 8.25l1.75 1.75L10.5 6.5' })])\r\nexport const IconCircleX = icon('IconCircleX', () => [h('circle', { cx: 8, cy: 8, r: 6.25 }), h('path', { d: 'M6.25 6.25l3.5 3.5M9.75 6.25l-3.5 3.5' })])\r\nexport const IconAlert = icon('IconAlert', () => [\r\n h('path', { d: 'M7.13 2.75a1 1 0 0 1 1.74 0l5.15 9a1 1 0 0 1-.87 1.5H2.85a1 1 0 0 1-.87-1.5z' }),\r\n h('path', { d: 'M8 6.5v2.5' }),\r\n h('path', { 'd': 'M8 11.1v.01', 'stroke-width': 1.75 }),\r\n])\r\nexport const IconInfo = icon('IconInfo', () => [\r\n h('circle', { cx: 8, cy: 8, r: 6.25 }),\r\n h('path', { d: 'M8 7.5v3.25' }),\r\n h('path', { 'd': 'M8 5.25v.01', 'stroke-width': 1.75 }),\r\n])\r\nexport const IconCornerDownLeft = icon('IconCornerDownLeft', () => [h('path', { d: 'M12.5 3.5v4.25a2 2 0 0 1-2 2h-7' }), h('path', { d: 'M6 7l-2.5 2.75L6 12.5' })])\r\nexport const IconChevronLeft = icon('IconChevronLeft', () => [h('path', { d: 'M9.75 4.5L6.25 8l3.5 3.5' })])\r\nexport const IconMoreHorizontal = icon('IconMoreHorizontal', () => [\r\n h('circle', { cx: 3.5, cy: 8, r: 1, 'fill': 'currentColor', 'stroke': 'none' }),\r\n h('circle', { cx: 8, cy: 8, r: 1, 'fill': 'currentColor', 'stroke': 'none' }),\r\n h('circle', { cx: 12.5, cy: 8, r: 1, 'fill': 'currentColor', 'stroke': 'none' }),\r\n])\r\nexport const IconArrowUpRight = icon('IconArrowUpRight', () => [h('path', { d: 'M5 11L11 5M5.5 5h5.5v5.5' })])\r\nexport const IconArrowDownRight = icon('IconArrowDownRight', () => [h('path', { d: 'M5 5l6 6M10.5 11H5V5.5' })])\r\n\r\n/** The loading spinner used across Juxt. A quarter arc over a faint track. */\r\nexport const Spinner: FunctionalComponent<{ size?: number }> = (props) => {\r\n const size = props.size ?? 16\r\n return h(\r\n 'svg',\r\n { 'class': 'j-spinner', 'viewBox': '0 0 16 16', 'width': size, 'height': size, 'aria-hidden': 'true', 'focusable': 'false' },\r\n [\r\n h('circle', { 'class': 'j-spinner__track', 'cx': 8, 'cy': 8, 'r': 6, 'stroke-width': 1.75 }),\r\n h('circle', { 'cx': 8, 'cy': 8, 'r': 6, 'stroke-width': 1.75, 'stroke-dasharray': '9.5 28', 'stroke-dashoffset': 0 }),\r\n ],\r\n )\r\n}\r\nSpinner.displayName = 'JSpinner'\r\n","<script setup lang=\"ts\">\r\nimport { computed, inject, useId } from 'vue'\r\nimport { IconChevronDown } from '../../icons'\r\nimport { ACCORDION } from './context'\r\n\r\nexport interface AccordionItemProps {\r\n /** Identifies the item in the accordion's `v-model`. */\r\n value: string\r\n title?: string\r\n disabled?: boolean\r\n}\r\n\r\nconst props = defineProps<AccordionItemProps>()\r\n\r\ndefineSlots<{ default?: () => unknown, title?: () => unknown }>()\r\n\r\nconst accordion = inject(ACCORDION)!\r\nconst id = `j-${useId()}`\r\nconst triggerId = `${id}-trigger`\r\nconst panelId = `${id}-panel`\r\nconst open = computed(() => accordion.isOpen(props.value))\r\nconst heading = computed(() => `h${accordion.headingLevel()}`)\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-accordion__item\" :data-state=\"open ? 'open' : 'closed'\" :class=\"{ 'is-disabled': disabled }\">\r\n <component :is=\"heading\" class=\"j-accordion__heading\">\r\n <button\r\n :id=\"triggerId\"\r\n type=\"button\"\r\n class=\"j-accordion__trigger\"\r\n :aria-expanded=\"open\"\r\n :aria-controls=\"panelId\"\r\n :disabled=\"disabled\"\r\n @click=\"accordion.toggle(value)\"\r\n >\r\n <span class=\"j-accordion__title\"><slot name=\"title\">{{ title }}</slot></span>\r\n <IconChevronDown class=\"j-accordion__chevron\" />\r\n </button>\r\n </component>\r\n <div\r\n :id=\"panelId\"\r\n role=\"region\"\r\n class=\"j-accordion__panel\"\r\n :aria-labelledby=\"triggerId\"\r\n :inert=\"!open\"\r\n >\r\n <div class=\"j-accordion__panel-inner\">\r\n <div class=\"j-accordion__content\">\r\n <slot />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-accordion__item {\r\n border-bottom: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-accordion__item,\r\n[data-theme='dark'] .j-accordion__item {\r\n border-bottom-color: var(--juxt-border);\r\n}\r\n\r\n.j-accordion__heading {\r\n margin: 0;\r\n font: inherit;\r\n}\r\n\r\n.j-accordion__trigger {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: var(--juxt-space-4);\r\n width: 100%;\r\n min-height: 3rem;\r\n padding: var(--juxt-space-3) 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-xs);\r\n background: transparent;\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n text-align: left;\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-accordion__trigger:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-accordion__trigger:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-accordion__chevron {\r\n flex: none;\r\n width: 1rem;\r\n height: 1rem;\r\n color: var(--juxt-fg-muted);\r\n transition:\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out),\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-accordion__trigger:hover:not(:disabled) .j-accordion__chevron {\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-accordion__item[data-state='open'] .j-accordion__chevron {\r\n transform: rotate(180deg);\r\n}\r\n\r\n/* Height follows content via grid rows | no measuring, no magic numbers. */\r\n.j-accordion__panel {\r\n display: grid;\r\n grid-template-rows: 0fr;\r\n transition: grid-template-rows var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-accordion__item[data-state='open'] .j-accordion__panel {\r\n grid-template-rows: 1fr;\r\n}\r\n\r\n.j-accordion__panel-inner {\r\n min-height: 0;\r\n overflow: hidden;\r\n}\r\n\r\n.j-accordion__content {\r\n padding-bottom: var(--juxt-space-4);\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n opacity: 0;\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-accordion__item[data-state='open'] .j-accordion__content {\r\n opacity: 1;\r\n transition-delay: 60ms;\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-accordion__panel,\r\n .j-accordion__chevron {\r\n transition: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, inject, useId } from 'vue'\r\nimport { IconChevronDown } from '../../icons'\r\nimport { ACCORDION } from './context'\r\n\r\nexport interface AccordionItemProps {\r\n /** Identifies the item in the accordion's `v-model`. */\r\n value: string\r\n title?: string\r\n disabled?: boolean\r\n}\r\n\r\nconst props = defineProps<AccordionItemProps>()\r\n\r\ndefineSlots<{ default?: () => unknown, title?: () => unknown }>()\r\n\r\nconst accordion = inject(ACCORDION)!\r\nconst id = `j-${useId()}`\r\nconst triggerId = `${id}-trigger`\r\nconst panelId = `${id}-panel`\r\nconst open = computed(() => accordion.isOpen(props.value))\r\nconst heading = computed(() => `h${accordion.headingLevel()}`)\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-accordion__item\" :data-state=\"open ? 'open' : 'closed'\" :class=\"{ 'is-disabled': disabled }\">\r\n <component :is=\"heading\" class=\"j-accordion__heading\">\r\n <button\r\n :id=\"triggerId\"\r\n type=\"button\"\r\n class=\"j-accordion__trigger\"\r\n :aria-expanded=\"open\"\r\n :aria-controls=\"panelId\"\r\n :disabled=\"disabled\"\r\n @click=\"accordion.toggle(value)\"\r\n >\r\n <span class=\"j-accordion__title\"><slot name=\"title\">{{ title }}</slot></span>\r\n <IconChevronDown class=\"j-accordion__chevron\" />\r\n </button>\r\n </component>\r\n <div\r\n :id=\"panelId\"\r\n role=\"region\"\r\n class=\"j-accordion__panel\"\r\n :aria-labelledby=\"triggerId\"\r\n :inert=\"!open\"\r\n >\r\n <div class=\"j-accordion__panel-inner\">\r\n <div class=\"j-accordion__content\">\r\n <slot />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-accordion__item {\r\n border-bottom: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-accordion__item,\r\n[data-theme='dark'] .j-accordion__item {\r\n border-bottom-color: var(--juxt-border);\r\n}\r\n\r\n.j-accordion__heading {\r\n margin: 0;\r\n font: inherit;\r\n}\r\n\r\n.j-accordion__trigger {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: var(--juxt-space-4);\r\n width: 100%;\r\n min-height: 3rem;\r\n padding: var(--juxt-space-3) 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-xs);\r\n background: transparent;\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n text-align: left;\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-accordion__trigger:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-accordion__trigger:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-accordion__chevron {\r\n flex: none;\r\n width: 1rem;\r\n height: 1rem;\r\n color: var(--juxt-fg-muted);\r\n transition:\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out),\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-accordion__trigger:hover:not(:disabled) .j-accordion__chevron {\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-accordion__item[data-state='open'] .j-accordion__chevron {\r\n transform: rotate(180deg);\r\n}\r\n\r\n/* Height follows content via grid rows | no measuring, no magic numbers. */\r\n.j-accordion__panel {\r\n display: grid;\r\n grid-template-rows: 0fr;\r\n transition: grid-template-rows var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-accordion__item[data-state='open'] .j-accordion__panel {\r\n grid-template-rows: 1fr;\r\n}\r\n\r\n.j-accordion__panel-inner {\r\n min-height: 0;\r\n overflow: hidden;\r\n}\r\n\r\n.j-accordion__content {\r\n padding-bottom: var(--juxt-space-4);\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n opacity: 0;\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-accordion__item[data-state='open'] .j-accordion__content {\r\n opacity: 1;\r\n transition-delay: 60ms;\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-accordion__panel,\r\n .j-accordion__chevron {\r\n transition: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { IconAlert, IconCircleCheck, IconCircleX, IconInfo, IconX } from '../../icons'\r\n\r\nexport type AlertVariant = 'neutral' | 'info' | 'success' | 'warning' | 'destructive'\r\n\r\nexport interface AlertProps {\r\n variant?: AlertVariant\r\n title?: string\r\n description?: string\r\n /** Show a close button; emits `dismiss`. */\r\n dismissible?: boolean\r\n /** Hide the leading icon. */\r\n hideIcon?: boolean\r\n}\r\n\r\nwithDefaults(defineProps<AlertProps>(), { variant: 'neutral' })\r\n\r\nconst emit = defineEmits<{ dismiss: [] }>()\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n icon?: () => unknown\r\n actions?: () => unknown\r\n}>()\r\n\r\nconst icons = {\r\n neutral: IconInfo,\r\n info: IconInfo,\r\n success: IconCircleCheck,\r\n warning: IconAlert,\r\n destructive: IconCircleX,\r\n}\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-alert\" :class=\"`j-alert--${variant}`\">\r\n <span v-if=\"!hideIcon\" class=\"j-alert__icon\" aria-hidden=\"true\">\r\n <slot name=\"icon\"><component :is=\"icons[variant]\" /></slot>\r\n </span>\r\n <div class=\"j-alert__body\">\r\n <p v-if=\"title\" class=\"j-alert__title\">\r\n {{ title }}\r\n </p>\r\n <div v-if=\"description || $slots.default\" class=\"j-alert__description\">\r\n <slot>{{ description }}</slot>\r\n </div>\r\n <div v-if=\"$slots.actions\" class=\"j-alert__actions\">\r\n <slot name=\"actions\" />\r\n </div>\r\n </div>\r\n <button v-if=\"dismissible\" type=\"button\" class=\"j-alert__close j-focusable\" aria-label=\"Dismiss\" @click=\"emit('dismiss')\">\r\n <IconX />\r\n </button>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n/*\r\n * Alerts stay on the page's surface. Colour lives in the icon and a hairline\r\n * of the border | enough to be noticed, never enough to shout.\r\n */\r\n.j-alert {\r\n --j-alert-accent: var(--juxt-fg-muted);\r\n --j-alert-border: var(--juxt-border);\r\n\r\n position: relative;\r\n display: flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-3);\r\n padding: var(--juxt-space-3) var(--juxt-space-4);\r\n border: 1px solid var(--j-alert-border);\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-alert--info {\r\n --j-alert-accent: var(--juxt-info-text);\r\n}\r\n\r\n.j-alert--success {\r\n --j-alert-accent: var(--juxt-success-text);\r\n}\r\n\r\n.j-alert--warning {\r\n --j-alert-accent: var(--juxt-warning-text);\r\n --j-alert-border: var(--juxt-warning-border);\r\n}\r\n\r\n.j-alert--destructive {\r\n --j-alert-accent: var(--juxt-danger-text);\r\n --j-alert-border: var(--juxt-danger-border);\r\n}\r\n\r\n.j-alert__icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n height: 1.25rem;\r\n color: var(--j-alert-accent);\r\n}\r\n\r\n.j-alert__icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-alert__body {\r\n display: flex;\r\n flex: 1;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-alert__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1.25rem;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-alert__description {\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-alert__description > p {\r\n margin: 0;\r\n}\r\n\r\n.j-alert__actions {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: var(--juxt-space-2);\r\n margin-top: var(--juxt-space-2);\r\n}\r\n\r\n.j-alert:has(.j-alert__close) {\r\n padding-right: var(--juxt-space-10);\r\n}\r\n\r\n.j-alert__close {\r\n position: absolute;\r\n top: var(--juxt-space-2-5);\r\n right: var(--juxt-space-2-5);\r\n display: grid;\r\n place-items: center;\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-xs);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-alert__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-alert__close > svg {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { IconAlert, IconCircleCheck, IconCircleX, IconInfo, IconX } from '../../icons'\r\n\r\nexport type AlertVariant = 'neutral' | 'info' | 'success' | 'warning' | 'destructive'\r\n\r\nexport interface AlertProps {\r\n variant?: AlertVariant\r\n title?: string\r\n description?: string\r\n /** Show a close button; emits `dismiss`. */\r\n dismissible?: boolean\r\n /** Hide the leading icon. */\r\n hideIcon?: boolean\r\n}\r\n\r\nwithDefaults(defineProps<AlertProps>(), { variant: 'neutral' })\r\n\r\nconst emit = defineEmits<{ dismiss: [] }>()\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n icon?: () => unknown\r\n actions?: () => unknown\r\n}>()\r\n\r\nconst icons = {\r\n neutral: IconInfo,\r\n info: IconInfo,\r\n success: IconCircleCheck,\r\n warning: IconAlert,\r\n destructive: IconCircleX,\r\n}\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-alert\" :class=\"`j-alert--${variant}`\">\r\n <span v-if=\"!hideIcon\" class=\"j-alert__icon\" aria-hidden=\"true\">\r\n <slot name=\"icon\"><component :is=\"icons[variant]\" /></slot>\r\n </span>\r\n <div class=\"j-alert__body\">\r\n <p v-if=\"title\" class=\"j-alert__title\">\r\n {{ title }}\r\n </p>\r\n <div v-if=\"description || $slots.default\" class=\"j-alert__description\">\r\n <slot>{{ description }}</slot>\r\n </div>\r\n <div v-if=\"$slots.actions\" class=\"j-alert__actions\">\r\n <slot name=\"actions\" />\r\n </div>\r\n </div>\r\n <button v-if=\"dismissible\" type=\"button\" class=\"j-alert__close j-focusable\" aria-label=\"Dismiss\" @click=\"emit('dismiss')\">\r\n <IconX />\r\n </button>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n/*\r\n * Alerts stay on the page's surface. Colour lives in the icon and a hairline\r\n * of the border | enough to be noticed, never enough to shout.\r\n */\r\n.j-alert {\r\n --j-alert-accent: var(--juxt-fg-muted);\r\n --j-alert-border: var(--juxt-border);\r\n\r\n position: relative;\r\n display: flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-3);\r\n padding: var(--juxt-space-3) var(--juxt-space-4);\r\n border: 1px solid var(--j-alert-border);\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-alert--info {\r\n --j-alert-accent: var(--juxt-info-text);\r\n}\r\n\r\n.j-alert--success {\r\n --j-alert-accent: var(--juxt-success-text);\r\n}\r\n\r\n.j-alert--warning {\r\n --j-alert-accent: var(--juxt-warning-text);\r\n --j-alert-border: var(--juxt-warning-border);\r\n}\r\n\r\n.j-alert--destructive {\r\n --j-alert-accent: var(--juxt-danger-text);\r\n --j-alert-border: var(--juxt-danger-border);\r\n}\r\n\r\n.j-alert__icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n height: 1.25rem;\r\n color: var(--j-alert-accent);\r\n}\r\n\r\n.j-alert__icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-alert__body {\r\n display: flex;\r\n flex: 1;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-alert__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1.25rem;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-alert__description {\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-alert__description > p {\r\n margin: 0;\r\n}\r\n\r\n.j-alert__actions {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: var(--juxt-space-2);\r\n margin-top: var(--juxt-space-2);\r\n}\r\n\r\n.j-alert:has(.j-alert__close) {\r\n padding-right: var(--juxt-space-10);\r\n}\r\n\r\n.j-alert__close {\r\n position: absolute;\r\n top: var(--juxt-space-2-5);\r\n right: var(--juxt-space-2-5);\r\n display: grid;\r\n place-items: center;\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-xs);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-alert__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-alert__close > svg {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\n\r\nexport interface AspectRatioProps {\r\n /** Width/height ratio, e.g. `16 / 9` or `1`. */\r\n ratio?: number\r\n}\r\n\r\nconst props = withDefaults(defineProps<AspectRatioProps>(), { ratio: 16 / 9 })\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\nconst style = computed(() => ({ '--j-aspect-ratio': String(props.ratio) }))\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-aspect-ratio\" :style=\"style\">\r\n <div class=\"j-aspect-ratio__content\">\r\n <slot />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-aspect-ratio {\r\n position: relative;\r\n width: 100%;\r\n aspect-ratio: var(--j-aspect-ratio, 16 / 9);\r\n}\r\n\r\n.j-aspect-ratio__content {\r\n position: absolute;\r\n inset: 0;\r\n overflow: hidden;\r\n}\r\n\r\n.j-aspect-ratio__content > :where(img, video, iframe, canvas) {\r\n display: block;\r\n width: 100%;\r\n height: 100%;\r\n object-fit: cover;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\n\r\nexport interface AspectRatioProps {\r\n /** Width/height ratio, e.g. `16 / 9` or `1`. */\r\n ratio?: number\r\n}\r\n\r\nconst props = withDefaults(defineProps<AspectRatioProps>(), { ratio: 16 / 9 })\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\nconst style = computed(() => ({ '--j-aspect-ratio': String(props.ratio) }))\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-aspect-ratio\" :style=\"style\">\r\n <div class=\"j-aspect-ratio__content\">\r\n <slot />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-aspect-ratio {\r\n position: relative;\r\n width: 100%;\r\n aspect-ratio: var(--j-aspect-ratio, 16 / 9);\r\n}\r\n\r\n.j-aspect-ratio__content {\r\n position: absolute;\r\n inset: 0;\r\n overflow: hidden;\r\n}\r\n\r\n.j-aspect-ratio__content > :where(img, video, iframe, canvas) {\r\n display: block;\r\n width: 100%;\r\n height: 100%;\r\n object-fit: cover;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, onMounted, ref, watch } from 'vue'\r\n\r\nexport type AvatarStatus = 'online' | 'away' | 'busy' | 'offline'\r\n\r\nexport interface AvatarProps {\r\n src?: string\r\n /** Image alt text. Defaults to `name`. */\r\n alt?: string\r\n /** Full name | used for initials and as the accessible name. */\r\n name?: string\r\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'\r\n shape?: 'circle' | 'square'\r\n status?: AvatarStatus\r\n}\r\n\r\nconst props = withDefaults(defineProps<AvatarProps>(), { size: 'md', shape: 'circle' })\r\n\r\ndefineSlots<{ fallback?: () => unknown }>()\r\n\r\nconst state = ref<'loading' | 'loaded' | 'error'>(props.src ? 'loading' : 'error')\r\nconst img = ref<HTMLImageElement | null>(null)\r\n\r\nwatch(() => props.src, (src) => {\r\n state.value = src ? 'loading' : 'error'\r\n})\r\n\r\n// An image cached before hydration may have finished loading already.\r\nonMounted(() => {\r\n if (img.value?.complete && img.value.naturalWidth > 0) state.value = 'loaded'\r\n})\r\n\r\nconst initials = computed(() => {\r\n const parts = (props.name ?? '').trim().split(/\\s+/).filter(Boolean)\r\n if (parts.length === 0) return ''\r\n const first = parts[0]!\r\n const last = parts.length > 1 ? parts[parts.length - 1]! : ''\r\n return (first[0]! + (last[0] ?? '')).toUpperCase()\r\n})\r\n\r\nconst statusLabel: Record<AvatarStatus, string> = {\r\n online: 'Online',\r\n away: 'Away',\r\n busy: 'Busy',\r\n offline: 'Offline',\r\n}\r\n\r\nconst label = computed(() => {\r\n const who = props.alt ?? props.name\r\n if (!who) return undefined\r\n return props.status ? `${who} (${statusLabel[props.status]})` : who\r\n})\r\n</script>\r\n\r\n<template>\r\n <span\r\n class=\"j-avatar\"\r\n :class=\"[`j-avatar--${size}`, `j-avatar--${shape}`]\"\r\n :role=\"label ? 'img' : undefined\"\r\n :aria-label=\"label\"\r\n :data-state=\"state\"\r\n >\r\n <span class=\"j-avatar__frame\">\r\n <img\r\n v-if=\"src && state !== 'error'\"\r\n ref=\"img\"\r\n class=\"j-avatar__image\"\r\n :src=\"src\"\r\n alt=\"\"\r\n @load=\"state = 'loaded'\"\r\n @error=\"state = 'error'\"\r\n />\r\n <span v-if=\"state !== 'loaded'\" class=\"j-avatar__fallback\" aria-hidden=\"true\">\r\n <slot name=\"fallback\">\r\n <template v-if=\"initials\">{{ initials }}</template>\r\n <svg v-else viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"j-avatar__glyph\">\r\n <circle cx=\"8\" cy=\"6\" r=\"2.75\" />\r\n <path d=\"M2.75 14c.6-2.6 2.7-4.25 5.25-4.25S12.65 11.4 13.25 14z\" />\r\n </svg>\r\n </slot>\r\n </span>\r\n </span>\r\n <span v-if=\"status\" class=\"j-avatar__status\" :class=\"`j-avatar__status--${status}`\" aria-hidden=\"true\" />\r\n </span>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-avatar {\r\n --j-avatar-size: 2rem;\r\n --j-avatar-font: var(--juxt-text-xs);\r\n --j-avatar-status: 0.5rem;\r\n --j-avatar-radius: var(--juxt-radius-full);\r\n\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n width: var(--j-avatar-size);\r\n height: var(--j-avatar-size);\r\n font-family: var(--juxt-font-sans);\r\n vertical-align: middle;\r\n}\r\n\r\n.j-avatar--xs {\r\n --j-avatar-size: 1.25rem;\r\n --j-avatar-font: 0.5625rem;\r\n --j-avatar-status: 0.375rem;\r\n}\r\n\r\n.j-avatar--sm {\r\n --j-avatar-size: 1.5rem;\r\n --j-avatar-font: 0.625rem;\r\n --j-avatar-status: 0.4375rem;\r\n}\r\n\r\n.j-avatar--lg {\r\n --j-avatar-size: 2.5rem;\r\n --j-avatar-font: var(--juxt-text-sm);\r\n --j-avatar-status: 0.625rem;\r\n}\r\n\r\n.j-avatar--xl {\r\n --j-avatar-size: 3.5rem;\r\n --j-avatar-font: var(--juxt-text-lg);\r\n --j-avatar-status: 0.75rem;\r\n}\r\n\r\n.j-avatar--square {\r\n --j-avatar-radius: calc(var(--j-avatar-size) * 0.22);\r\n}\r\n\r\n.j-avatar__frame {\r\n position: relative;\r\n display: grid;\r\n width: 100%;\r\n height: 100%;\r\n overflow: hidden;\r\n border-radius: var(--j-avatar-radius);\r\n background: var(--juxt-surface-active);\r\n /* A hairline keeps light photos from bleeding into light backgrounds. */\r\n box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.06);\r\n}\r\n\r\n.dark .j-avatar__frame,\r\n[data-theme='dark'] .j-avatar__frame {\r\n box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.06);\r\n}\r\n\r\n.j-avatar__image,\r\n.j-avatar__fallback {\r\n grid-area: 1 / 1;\r\n width: 100%;\r\n height: 100%;\r\n}\r\n\r\n.j-avatar__image {\r\n object-fit: cover;\r\n opacity: 0;\r\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-avatar[data-state='loaded'] .j-avatar__image {\r\n opacity: 1;\r\n}\r\n\r\n.j-avatar__fallback {\r\n display: grid;\r\n place-items: center;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--j-avatar-font);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: 0.02em;\r\n line-height: 1;\r\n user-select: none;\r\n}\r\n\r\n.j-avatar__glyph {\r\n width: 62%;\r\n height: 62%;\r\n margin-top: 18%;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-avatar__status {\r\n position: absolute;\r\n right: 0;\r\n bottom: 0;\r\n width: var(--j-avatar-status);\r\n height: var(--j-avatar-status);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-fg-muted);\r\n /* The ring is the page background, so the dot cuts cleanly out of the photo. */\r\n box-shadow: 0 0 0 2px var(--j-avatar-ring, var(--juxt-bg));\r\n}\r\n\r\n.j-avatar--square .j-avatar__status {\r\n right: -2px;\r\n bottom: -2px;\r\n}\r\n\r\n.j-avatar__status--online {\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-avatar__status--away {\r\n background: var(--juxt-warning);\r\n}\r\n\r\n.j-avatar__status--busy {\r\n background: var(--juxt-danger);\r\n}\r\n\r\n.j-avatar__status--offline {\r\n background: var(--juxt-fg-disabled);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, onMounted, ref, watch } from 'vue'\r\n\r\nexport type AvatarStatus = 'online' | 'away' | 'busy' | 'offline'\r\n\r\nexport interface AvatarProps {\r\n src?: string\r\n /** Image alt text. Defaults to `name`. */\r\n alt?: string\r\n /** Full name | used for initials and as the accessible name. */\r\n name?: string\r\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'\r\n shape?: 'circle' | 'square'\r\n status?: AvatarStatus\r\n}\r\n\r\nconst props = withDefaults(defineProps<AvatarProps>(), { size: 'md', shape: 'circle' })\r\n\r\ndefineSlots<{ fallback?: () => unknown }>()\r\n\r\nconst state = ref<'loading' | 'loaded' | 'error'>(props.src ? 'loading' : 'error')\r\nconst img = ref<HTMLImageElement | null>(null)\r\n\r\nwatch(() => props.src, (src) => {\r\n state.value = src ? 'loading' : 'error'\r\n})\r\n\r\n// An image cached before hydration may have finished loading already.\r\nonMounted(() => {\r\n if (img.value?.complete && img.value.naturalWidth > 0) state.value = 'loaded'\r\n})\r\n\r\nconst initials = computed(() => {\r\n const parts = (props.name ?? '').trim().split(/\\s+/).filter(Boolean)\r\n if (parts.length === 0) return ''\r\n const first = parts[0]!\r\n const last = parts.length > 1 ? parts[parts.length - 1]! : ''\r\n return (first[0]! + (last[0] ?? '')).toUpperCase()\r\n})\r\n\r\nconst statusLabel: Record<AvatarStatus, string> = {\r\n online: 'Online',\r\n away: 'Away',\r\n busy: 'Busy',\r\n offline: 'Offline',\r\n}\r\n\r\nconst label = computed(() => {\r\n const who = props.alt ?? props.name\r\n if (!who) return undefined\r\n return props.status ? `${who} (${statusLabel[props.status]})` : who\r\n})\r\n</script>\r\n\r\n<template>\r\n <span\r\n class=\"j-avatar\"\r\n :class=\"[`j-avatar--${size}`, `j-avatar--${shape}`]\"\r\n :role=\"label ? 'img' : undefined\"\r\n :aria-label=\"label\"\r\n :data-state=\"state\"\r\n >\r\n <span class=\"j-avatar__frame\">\r\n <img\r\n v-if=\"src && state !== 'error'\"\r\n ref=\"img\"\r\n class=\"j-avatar__image\"\r\n :src=\"src\"\r\n alt=\"\"\r\n @load=\"state = 'loaded'\"\r\n @error=\"state = 'error'\"\r\n />\r\n <span v-if=\"state !== 'loaded'\" class=\"j-avatar__fallback\" aria-hidden=\"true\">\r\n <slot name=\"fallback\">\r\n <template v-if=\"initials\">{{ initials }}</template>\r\n <svg v-else viewBox=\"0 0 16 16\" fill=\"currentColor\" class=\"j-avatar__glyph\">\r\n <circle cx=\"8\" cy=\"6\" r=\"2.75\" />\r\n <path d=\"M2.75 14c.6-2.6 2.7-4.25 5.25-4.25S12.65 11.4 13.25 14z\" />\r\n </svg>\r\n </slot>\r\n </span>\r\n </span>\r\n <span v-if=\"status\" class=\"j-avatar__status\" :class=\"`j-avatar__status--${status}`\" aria-hidden=\"true\" />\r\n </span>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-avatar {\r\n --j-avatar-size: 2rem;\r\n --j-avatar-font: var(--juxt-text-xs);\r\n --j-avatar-status: 0.5rem;\r\n --j-avatar-radius: var(--juxt-radius-full);\r\n\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n width: var(--j-avatar-size);\r\n height: var(--j-avatar-size);\r\n font-family: var(--juxt-font-sans);\r\n vertical-align: middle;\r\n}\r\n\r\n.j-avatar--xs {\r\n --j-avatar-size: 1.25rem;\r\n --j-avatar-font: 0.5625rem;\r\n --j-avatar-status: 0.375rem;\r\n}\r\n\r\n.j-avatar--sm {\r\n --j-avatar-size: 1.5rem;\r\n --j-avatar-font: 0.625rem;\r\n --j-avatar-status: 0.4375rem;\r\n}\r\n\r\n.j-avatar--lg {\r\n --j-avatar-size: 2.5rem;\r\n --j-avatar-font: var(--juxt-text-sm);\r\n --j-avatar-status: 0.625rem;\r\n}\r\n\r\n.j-avatar--xl {\r\n --j-avatar-size: 3.5rem;\r\n --j-avatar-font: var(--juxt-text-lg);\r\n --j-avatar-status: 0.75rem;\r\n}\r\n\r\n.j-avatar--square {\r\n --j-avatar-radius: calc(var(--j-avatar-size) * 0.22);\r\n}\r\n\r\n.j-avatar__frame {\r\n position: relative;\r\n display: grid;\r\n width: 100%;\r\n height: 100%;\r\n overflow: hidden;\r\n border-radius: var(--j-avatar-radius);\r\n background: var(--juxt-surface-active);\r\n /* A hairline keeps light photos from bleeding into light backgrounds. */\r\n box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.06);\r\n}\r\n\r\n.dark .j-avatar__frame,\r\n[data-theme='dark'] .j-avatar__frame {\r\n box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.06);\r\n}\r\n\r\n.j-avatar__image,\r\n.j-avatar__fallback {\r\n grid-area: 1 / 1;\r\n width: 100%;\r\n height: 100%;\r\n}\r\n\r\n.j-avatar__image {\r\n object-fit: cover;\r\n opacity: 0;\r\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-avatar[data-state='loaded'] .j-avatar__image {\r\n opacity: 1;\r\n}\r\n\r\n.j-avatar__fallback {\r\n display: grid;\r\n place-items: center;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--j-avatar-font);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: 0.02em;\r\n line-height: 1;\r\n user-select: none;\r\n}\r\n\r\n.j-avatar__glyph {\r\n width: 62%;\r\n height: 62%;\r\n margin-top: 18%;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-avatar__status {\r\n position: absolute;\r\n right: 0;\r\n bottom: 0;\r\n width: var(--j-avatar-status);\r\n height: var(--j-avatar-status);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-fg-muted);\r\n /* The ring is the page background, so the dot cuts cleanly out of the photo. */\r\n box-shadow: 0 0 0 2px var(--j-avatar-ring, var(--juxt-bg));\r\n}\r\n\r\n.j-avatar--square .j-avatar__status {\r\n right: -2px;\r\n bottom: -2px;\r\n}\r\n\r\n.j-avatar__status--online {\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-avatar__status--away {\r\n background: var(--juxt-warning);\r\n}\r\n\r\n.j-avatar__status--busy {\r\n background: var(--juxt-danger);\r\n}\r\n\r\n.j-avatar__status--offline {\r\n background: var(--juxt-fg-disabled);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport JAvatar from '../avatar/JAvatar.vue'\r\n\r\nexport interface AvatarGroupItem {\r\n src?: string\r\n name?: string\r\n}\r\n\r\nexport interface AvatarGroupProps {\r\n items: AvatarGroupItem[]\r\n /** Avatars shown before the rest collapse into a \"+N\" indicator. */\r\n max?: number\r\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'\r\n shape?: 'circle' | 'square'\r\n /** Accessible name for the group. Defaults to a plain count, not an assumption about who's in it. */\r\n label?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<AvatarGroupProps>(), { size: 'md', shape: 'circle' })\r\n\r\nconst visible = computed(() => (props.max ? props.items.slice(0, props.max) : props.items))\r\nconst overflow = computed(() => Math.max(0, props.items.length - visible.value.length))\r\nconst groupLabel = computed(() => props.label ?? `${props.items.length} ${props.items.length === 1 ? 'avatar' : 'avatars'}`)\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-avatar-group\" :class=\"`j-avatar-group--${size}`\" role=\"group\" :aria-label=\"groupLabel\">\r\n <span v-for=\"(item, index) in visible\" :key=\"index\" class=\"j-avatar-group__item\">\r\n <JAvatar :src=\"item.src\" :name=\"item.name\" :size=\"size\" :shape=\"shape\" />\r\n </span>\r\n <span\r\n v-if=\"overflow > 0\"\r\n class=\"j-avatar-group__item j-avatar-group__overflow j-focusable\"\r\n :class=\"{ 'j-avatar-group__overflow--square': shape === 'square' }\"\r\n tabindex=\"0\"\r\n :aria-label=\"`${overflow} more`\"\r\n >\r\n +{{ overflow }}\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-avatar-group {\r\n --j-avatar-group-overlap: 0.5em;\r\n\r\n display: inline-flex;\r\n align-items: center;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-avatar-group__item {\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n border-radius: var(--juxt-radius-full);\r\n box-shadow: 0 0 0 2px var(--juxt-bg);\r\n}\r\n\r\n.j-avatar-group__item:not(:first-child) {\r\n margin-left: calc(var(--j-avatar-group-overlap) * -1);\r\n}\r\n\r\n.j-avatar-group__overflow {\r\n display: grid;\r\n place-items: center;\r\n width: 2rem;\r\n height: 2rem;\r\n background: var(--juxt-surface-active);\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-avatar-group--xs .j-avatar-group__overflow {\r\n width: 1.25rem;\r\n height: 1.25rem;\r\n font-size: var(--juxt-text-2xs);\r\n}\r\n\r\n.j-avatar-group--sm .j-avatar-group__overflow {\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n font-size: 0.625rem;\r\n}\r\n\r\n.j-avatar-group--lg .j-avatar-group__overflow {\r\n width: 2.5rem;\r\n height: 2.5rem;\r\n font-size: var(--juxt-text-sm);\r\n}\r\n\r\n.j-avatar-group--xl .j-avatar-group__overflow {\r\n width: 3.5rem;\r\n height: 3.5rem;\r\n font-size: var(--juxt-text-lg);\r\n}\r\n\r\n.j-avatar-group__overflow--square {\r\n border-radius: 22%;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport JAvatar from '../avatar/JAvatar.vue'\r\n\r\nexport interface AvatarGroupItem {\r\n src?: string\r\n name?: string\r\n}\r\n\r\nexport interface AvatarGroupProps {\r\n items: AvatarGroupItem[]\r\n /** Avatars shown before the rest collapse into a \"+N\" indicator. */\r\n max?: number\r\n size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'\r\n shape?: 'circle' | 'square'\r\n /** Accessible name for the group. Defaults to a plain count, not an assumption about who's in it. */\r\n label?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<AvatarGroupProps>(), { size: 'md', shape: 'circle' })\r\n\r\nconst visible = computed(() => (props.max ? props.items.slice(0, props.max) : props.items))\r\nconst overflow = computed(() => Math.max(0, props.items.length - visible.value.length))\r\nconst groupLabel = computed(() => props.label ?? `${props.items.length} ${props.items.length === 1 ? 'avatar' : 'avatars'}`)\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-avatar-group\" :class=\"`j-avatar-group--${size}`\" role=\"group\" :aria-label=\"groupLabel\">\r\n <span v-for=\"(item, index) in visible\" :key=\"index\" class=\"j-avatar-group__item\">\r\n <JAvatar :src=\"item.src\" :name=\"item.name\" :size=\"size\" :shape=\"shape\" />\r\n </span>\r\n <span\r\n v-if=\"overflow > 0\"\r\n class=\"j-avatar-group__item j-avatar-group__overflow j-focusable\"\r\n :class=\"{ 'j-avatar-group__overflow--square': shape === 'square' }\"\r\n tabindex=\"0\"\r\n :aria-label=\"`${overflow} more`\"\r\n >\r\n +{{ overflow }}\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-avatar-group {\r\n --j-avatar-group-overlap: 0.5em;\r\n\r\n display: inline-flex;\r\n align-items: center;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-avatar-group__item {\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n border-radius: var(--juxt-radius-full);\r\n box-shadow: 0 0 0 2px var(--juxt-bg);\r\n}\r\n\r\n.j-avatar-group__item:not(:first-child) {\r\n margin-left: calc(var(--j-avatar-group-overlap) * -1);\r\n}\r\n\r\n.j-avatar-group__overflow {\r\n display: grid;\r\n place-items: center;\r\n width: 2rem;\r\n height: 2rem;\r\n background: var(--juxt-surface-active);\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-avatar-group--xs .j-avatar-group__overflow {\r\n width: 1.25rem;\r\n height: 1.25rem;\r\n font-size: var(--juxt-text-2xs);\r\n}\r\n\r\n.j-avatar-group--sm .j-avatar-group__overflow {\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n font-size: 0.625rem;\r\n}\r\n\r\n.j-avatar-group--lg .j-avatar-group__overflow {\r\n width: 2.5rem;\r\n height: 2.5rem;\r\n font-size: var(--juxt-text-sm);\r\n}\r\n\r\n.j-avatar-group--xl .j-avatar-group__overflow {\r\n width: 3.5rem;\r\n height: 3.5rem;\r\n font-size: var(--juxt-text-lg);\r\n}\r\n\r\n.j-avatar-group__overflow--square {\r\n border-radius: 22%;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nexport type BadgeVariant = 'neutral' | 'success' | 'warning' | 'destructive' | 'accent'\r\n\r\nexport interface BadgeProps {\r\n variant?: BadgeVariant\r\n size?: 'sm' | 'md'\r\n /**\r\n * Status style: plain text led by a small square marker, no fill.\r\n * Reads as a state rather than a label.\r\n */\r\n dot?: boolean\r\n}\r\n\r\nwithDefaults(defineProps<BadgeProps>(), { variant: 'neutral', size: 'md' })\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <span class=\"j-badge\" :class=\"[`j-badge--${variant}`, `j-badge--${size}`, { 'j-badge--status': dot }]\">\r\n <span v-if=\"dot\" class=\"j-badge__marker\" aria-hidden=\"true\" />\r\n <span v-else-if=\"$slots.leading\" class=\"j-badge__icon\"><slot name=\"leading\" /></span>\r\n <slot />\r\n </span>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n/*\r\n * Badges are small, square-shouldered labels | closer to a stamp than a pill.\r\n * No borders: a quiet tint and the text colour carry the meaning.\r\n */\r\n.j-badge {\r\n --j-badge-fg: var(--juxt-fg-secondary);\r\n --j-badge-bg: var(--juxt-surface-active);\r\n --j-badge-marker: var(--juxt-fg-muted);\r\n\r\n display: inline-flex;\r\n flex: none;\r\n align-items: center;\r\n gap: 0.375rem;\r\n height: 1.25rem;\r\n padding: 0 0.375rem;\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--j-badge-bg);\r\n color: var(--j-badge-fg);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1;\r\n letter-spacing: 0;\r\n white-space: nowrap;\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-badge--sm {\r\n gap: 0.3125rem;\r\n height: 1.0625rem;\r\n padding: 0 0.3125rem;\r\n font-size: var(--juxt-text-2xs);\r\n}\r\n\r\n.j-badge--success {\r\n --j-badge-fg: var(--juxt-success-text);\r\n --j-badge-bg: var(--juxt-success-soft);\r\n --j-badge-marker: var(--juxt-success);\r\n}\r\n\r\n.j-badge--warning {\r\n --j-badge-fg: var(--juxt-warning-text);\r\n --j-badge-bg: var(--juxt-warning-soft);\r\n --j-badge-marker: var(--juxt-warning);\r\n}\r\n\r\n.j-badge--destructive {\r\n --j-badge-fg: var(--juxt-danger-text);\r\n --j-badge-bg: var(--juxt-danger-soft);\r\n --j-badge-marker: var(--juxt-danger);\r\n}\r\n\r\n/* Accent is the only solid badge | reserve it for the thing that matters. */\r\n.j-badge--accent {\r\n --j-badge-fg: var(--juxt-accent-fg);\r\n --j-badge-bg: var(--juxt-accent-strong);\r\n --j-badge-marker: var(--juxt-accent);\r\n}\r\n\r\n/* Status: text first, colour only in the marker. */\r\n.j-badge--status {\r\n padding: 0;\r\n background: none;\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-badge__marker {\r\n flex: none;\r\n width: 0.4375rem;\r\n height: 0.4375rem;\r\n border-radius: 1.5px;\r\n background: var(--j-badge-marker);\r\n}\r\n\r\n.j-badge--sm .j-badge__marker {\r\n width: 0.375rem;\r\n height: 0.375rem;\r\n}\r\n\r\n.j-badge__icon {\r\n display: inline-flex;\r\n margin-left: -0.0625rem;\r\n}\r\n\r\n.j-badge__icon > svg {\r\n width: 0.75rem;\r\n height: 0.75rem;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nexport type BadgeVariant = 'neutral' | 'success' | 'warning' | 'destructive' | 'accent'\r\n\r\nexport interface BadgeProps {\r\n variant?: BadgeVariant\r\n size?: 'sm' | 'md'\r\n /**\r\n * Status style: plain text led by a small square marker, no fill.\r\n * Reads as a state rather than a label.\r\n */\r\n dot?: boolean\r\n}\r\n\r\nwithDefaults(defineProps<BadgeProps>(), { variant: 'neutral', size: 'md' })\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <span class=\"j-badge\" :class=\"[`j-badge--${variant}`, `j-badge--${size}`, { 'j-badge--status': dot }]\">\r\n <span v-if=\"dot\" class=\"j-badge__marker\" aria-hidden=\"true\" />\r\n <span v-else-if=\"$slots.leading\" class=\"j-badge__icon\"><slot name=\"leading\" /></span>\r\n <slot />\r\n </span>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n/*\r\n * Badges are small, square-shouldered labels | closer to a stamp than a pill.\r\n * No borders: a quiet tint and the text colour carry the meaning.\r\n */\r\n.j-badge {\r\n --j-badge-fg: var(--juxt-fg-secondary);\r\n --j-badge-bg: var(--juxt-surface-active);\r\n --j-badge-marker: var(--juxt-fg-muted);\r\n\r\n display: inline-flex;\r\n flex: none;\r\n align-items: center;\r\n gap: 0.375rem;\r\n height: 1.25rem;\r\n padding: 0 0.375rem;\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--j-badge-bg);\r\n color: var(--j-badge-fg);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1;\r\n letter-spacing: 0;\r\n white-space: nowrap;\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-badge--sm {\r\n gap: 0.3125rem;\r\n height: 1.0625rem;\r\n padding: 0 0.3125rem;\r\n font-size: var(--juxt-text-2xs);\r\n}\r\n\r\n.j-badge--success {\r\n --j-badge-fg: var(--juxt-success-text);\r\n --j-badge-bg: var(--juxt-success-soft);\r\n --j-badge-marker: var(--juxt-success);\r\n}\r\n\r\n.j-badge--warning {\r\n --j-badge-fg: var(--juxt-warning-text);\r\n --j-badge-bg: var(--juxt-warning-soft);\r\n --j-badge-marker: var(--juxt-warning);\r\n}\r\n\r\n.j-badge--destructive {\r\n --j-badge-fg: var(--juxt-danger-text);\r\n --j-badge-bg: var(--juxt-danger-soft);\r\n --j-badge-marker: var(--juxt-danger);\r\n}\r\n\r\n/* Accent is the only solid badge | reserve it for the thing that matters. */\r\n.j-badge--accent {\r\n --j-badge-fg: var(--juxt-accent-fg);\r\n --j-badge-bg: var(--juxt-accent-strong);\r\n --j-badge-marker: var(--juxt-accent);\r\n}\r\n\r\n/* Status: text first, colour only in the marker. */\r\n.j-badge--status {\r\n padding: 0;\r\n background: none;\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-badge__marker {\r\n flex: none;\r\n width: 0.4375rem;\r\n height: 0.4375rem;\r\n border-radius: 1.5px;\r\n background: var(--j-badge-marker);\r\n}\r\n\r\n.j-badge--sm .j-badge__marker {\r\n width: 0.375rem;\r\n height: 0.375rem;\r\n}\r\n\r\n.j-badge__icon {\r\n display: inline-flex;\r\n margin-left: -0.0625rem;\r\n}\r\n\r\n.j-badge__icon > svg {\r\n width: 0.75rem;\r\n height: 0.75rem;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { IconAlert, IconCircleCheck, IconCircleX, IconInfo, IconX } from '../../icons'\r\n\r\nexport type BannerVariant = 'neutral' | 'info' | 'success' | 'warning' | 'destructive'\r\n\r\nexport interface BannerProps {\r\n variant?: BannerVariant\r\n title?: string\r\n description?: string\r\n /** Show a close button; emits `dismiss`. */\r\n dismissible?: boolean\r\n /** Hide the leading icon. */\r\n hideIcon?: boolean\r\n}\r\n\r\nwithDefaults(defineProps<BannerProps>(), { variant: 'neutral' })\r\n\r\nconst emit = defineEmits<{ dismiss: [] }>()\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n icon?: () => unknown\r\n actions?: () => unknown\r\n}>()\r\n\r\nconst icons = {\r\n neutral: IconInfo,\r\n info: IconInfo,\r\n success: IconCircleCheck,\r\n warning: IconAlert,\r\n destructive: IconCircleX,\r\n}\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-banner\" :class=\"`j-banner--${variant}`\" role=\"region\" :aria-label=\"title\">\r\n <span v-if=\"!hideIcon\" class=\"j-banner__icon\" aria-hidden=\"true\">\r\n <slot name=\"icon\"><component :is=\"icons[variant]\" /></slot>\r\n </span>\r\n <div class=\"j-banner__body\">\r\n <p v-if=\"title\" class=\"j-banner__title\">\r\n {{ title }}\r\n </p>\r\n <div v-if=\"description || $slots.default\" class=\"j-banner__description\">\r\n <slot>{{ description }}</slot>\r\n </div>\r\n </div>\r\n <div v-if=\"$slots.actions\" class=\"j-banner__actions\">\r\n <slot name=\"actions\" />\r\n </div>\r\n <button v-if=\"dismissible\" type=\"button\" class=\"j-banner__close j-focusable\" aria-label=\"Dismiss\" @click=\"emit('dismiss')\">\r\n <IconX />\r\n </button>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n/*\r\n * Banners run edge-to-edge across a page or section, unlike JAlert which sits\r\n * inline in a card or form. A flat tinted surface and a left accent bar\r\n * distinguish them from the bordered, neutral surface of an alert.\r\n */\r\n.j-banner {\r\n --j-banner-accent: var(--juxt-fg-muted);\r\n --j-banner-bg: var(--juxt-surface-sunken);\r\n\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-3);\r\n padding: var(--juxt-space-3) var(--juxt-space-5);\r\n border-left: 3px solid var(--j-banner-accent);\r\n background: var(--j-banner-bg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-banner--info {\r\n --j-banner-accent: var(--juxt-info);\r\n --j-banner-bg: var(--juxt-info-soft);\r\n}\r\n\r\n.j-banner--success {\r\n --j-banner-accent: var(--juxt-success);\r\n --j-banner-bg: var(--juxt-success-soft);\r\n}\r\n\r\n.j-banner--warning {\r\n --j-banner-accent: var(--juxt-warning);\r\n --j-banner-bg: var(--juxt-warning-soft);\r\n}\r\n\r\n.j-banner--destructive {\r\n --j-banner-accent: var(--juxt-danger);\r\n --j-banner-bg: var(--juxt-danger-soft);\r\n}\r\n\r\n.j-banner__icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n height: 1.25rem;\r\n color: var(--j-banner-accent);\r\n}\r\n\r\n.j-banner__icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-banner__body {\r\n display: flex;\r\n flex: 1;\r\n flex-wrap: wrap;\r\n align-items: baseline;\r\n gap: var(--juxt-space-1) var(--juxt-space-2);\r\n min-width: 0;\r\n}\r\n\r\n.j-banner__title {\r\n margin: 0;\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-banner__description {\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-banner__description > p {\r\n margin: 0;\r\n}\r\n\r\n.j-banner__actions {\r\n display: flex;\r\n flex: none;\r\n flex-wrap: wrap;\r\n gap: var(--juxt-space-2);\r\n}\r\n\r\n.j-banner__close {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-xs);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-banner__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-banner__close > svg {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { IconAlert, IconCircleCheck, IconCircleX, IconInfo, IconX } from '../../icons'\r\n\r\nexport type BannerVariant = 'neutral' | 'info' | 'success' | 'warning' | 'destructive'\r\n\r\nexport interface BannerProps {\r\n variant?: BannerVariant\r\n title?: string\r\n description?: string\r\n /** Show a close button; emits `dismiss`. */\r\n dismissible?: boolean\r\n /** Hide the leading icon. */\r\n hideIcon?: boolean\r\n}\r\n\r\nwithDefaults(defineProps<BannerProps>(), { variant: 'neutral' })\r\n\r\nconst emit = defineEmits<{ dismiss: [] }>()\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n icon?: () => unknown\r\n actions?: () => unknown\r\n}>()\r\n\r\nconst icons = {\r\n neutral: IconInfo,\r\n info: IconInfo,\r\n success: IconCircleCheck,\r\n warning: IconAlert,\r\n destructive: IconCircleX,\r\n}\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-banner\" :class=\"`j-banner--${variant}`\" role=\"region\" :aria-label=\"title\">\r\n <span v-if=\"!hideIcon\" class=\"j-banner__icon\" aria-hidden=\"true\">\r\n <slot name=\"icon\"><component :is=\"icons[variant]\" /></slot>\r\n </span>\r\n <div class=\"j-banner__body\">\r\n <p v-if=\"title\" class=\"j-banner__title\">\r\n {{ title }}\r\n </p>\r\n <div v-if=\"description || $slots.default\" class=\"j-banner__description\">\r\n <slot>{{ description }}</slot>\r\n </div>\r\n </div>\r\n <div v-if=\"$slots.actions\" class=\"j-banner__actions\">\r\n <slot name=\"actions\" />\r\n </div>\r\n <button v-if=\"dismissible\" type=\"button\" class=\"j-banner__close j-focusable\" aria-label=\"Dismiss\" @click=\"emit('dismiss')\">\r\n <IconX />\r\n </button>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n/*\r\n * Banners run edge-to-edge across a page or section, unlike JAlert which sits\r\n * inline in a card or form. A flat tinted surface and a left accent bar\r\n * distinguish them from the bordered, neutral surface of an alert.\r\n */\r\n.j-banner {\r\n --j-banner-accent: var(--juxt-fg-muted);\r\n --j-banner-bg: var(--juxt-surface-sunken);\r\n\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-3);\r\n padding: var(--juxt-space-3) var(--juxt-space-5);\r\n border-left: 3px solid var(--j-banner-accent);\r\n background: var(--j-banner-bg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-banner--info {\r\n --j-banner-accent: var(--juxt-info);\r\n --j-banner-bg: var(--juxt-info-soft);\r\n}\r\n\r\n.j-banner--success {\r\n --j-banner-accent: var(--juxt-success);\r\n --j-banner-bg: var(--juxt-success-soft);\r\n}\r\n\r\n.j-banner--warning {\r\n --j-banner-accent: var(--juxt-warning);\r\n --j-banner-bg: var(--juxt-warning-soft);\r\n}\r\n\r\n.j-banner--destructive {\r\n --j-banner-accent: var(--juxt-danger);\r\n --j-banner-bg: var(--juxt-danger-soft);\r\n}\r\n\r\n.j-banner__icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n height: 1.25rem;\r\n color: var(--j-banner-accent);\r\n}\r\n\r\n.j-banner__icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-banner__body {\r\n display: flex;\r\n flex: 1;\r\n flex-wrap: wrap;\r\n align-items: baseline;\r\n gap: var(--juxt-space-1) var(--juxt-space-2);\r\n min-width: 0;\r\n}\r\n\r\n.j-banner__title {\r\n margin: 0;\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-banner__description {\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-banner__description > p {\r\n margin: 0;\r\n}\r\n\r\n.j-banner__actions {\r\n display: flex;\r\n flex: none;\r\n flex-wrap: wrap;\r\n gap: var(--juxt-space-2);\r\n}\r\n\r\n.j-banner__close {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-xs);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-banner__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-banner__close > svg {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\n\r\nexport interface BreadcrumbItem {\r\n label: string\r\n href?: string\r\n}\r\n\r\nexport interface BreadcrumbProps {\r\n /** Trail of items, first to last. The last item renders as the current page. */\r\n items?: BreadcrumbItem[]\r\n /** Accessible name for the nav landmark. Defaults to \"Breadcrumb\". */\r\n label?: string\r\n /** Collapse middle items behind an ellipsis once the trail is longer than this. */\r\n maxItems?: number\r\n}\r\n\r\nconst props = defineProps<BreadcrumbProps>()\r\n\r\n/**\r\n * Provide `items` for the common case, or omit it and pass your own `<li>`\r\n * elements (with class `j-breadcrumb__item`) into the default slot for full\r\n * control over each crumb.\r\n */\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\ninterface ResolvedItem extends BreadcrumbItem {\r\n ellipsis?: boolean\r\n}\r\n\r\nconst visibleItems = computed<ResolvedItem[]>(() => {\r\n const list = props.items ?? []\r\n if (!props.maxItems || list.length <= props.maxItems) return list\r\n const tailCount = Math.max(1, props.maxItems - 2)\r\n return [list[0]!, { label: '…', ellipsis: true }, ...list.slice(-tailCount)]\r\n})\r\n</script>\r\n\r\n<template>\r\n <nav class=\"j-breadcrumb\" :aria-label=\"label || 'Breadcrumb'\">\r\n <ol class=\"j-breadcrumb__list\">\r\n <template v-if=\"items && items.length\">\r\n <li v-for=\"(item, index) in visibleItems\" :key=\"`${item.label}-${index}`\" class=\"j-breadcrumb__item\">\r\n <span v-if=\"item.ellipsis\" class=\"j-breadcrumb__ellipsis\" aria-hidden=\"true\">{{ item.label }}</span>\r\n <a v-else-if=\"item.href && index !== visibleItems.length - 1\" class=\"j-breadcrumb__link j-focusable\" :href=\"item.href\">{{ item.label }}</a>\r\n <span v-else class=\"j-breadcrumb__current\" :aria-current=\"index === visibleItems.length - 1 ? 'page' : undefined\">{{ item.label }}</span>\r\n </li>\r\n </template>\r\n <slot v-else />\r\n </ol>\r\n </nav>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-breadcrumb {\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-breadcrumb__list {\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-items: center;\r\n gap: var(--juxt-space-1-5);\r\n margin: 0;\r\n padding: 0;\r\n list-style: none;\r\n}\r\n\r\n.j-breadcrumb__item {\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1-5);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-snug);\r\n}\r\n\r\n.j-breadcrumb__item:not(:first-child)::before {\r\n content: '';\r\n display: inline-block;\r\n width: 0.3125rem;\r\n height: 0.3125rem;\r\n margin-right: var(--juxt-space-1-5);\r\n border-top: 1.5px solid var(--juxt-fg-disabled);\r\n border-right: 1.5px solid var(--juxt-fg-disabled);\r\n transform: rotate(45deg);\r\n}\r\n\r\n.j-breadcrumb__link {\r\n border-radius: var(--juxt-radius-xs);\r\n color: var(--juxt-fg-muted);\r\n text-decoration: none;\r\n transition: color var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-breadcrumb__link:hover {\r\n color: var(--juxt-fg);\r\n text-decoration: underline;\r\n text-underline-offset: 2px;\r\n}\r\n\r\n.j-breadcrumb__current {\r\n color: var(--juxt-fg);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-breadcrumb__ellipsis {\r\n color: var(--juxt-fg-disabled);\r\n user-select: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\n\r\nexport interface BreadcrumbItem {\r\n label: string\r\n href?: string\r\n}\r\n\r\nexport interface BreadcrumbProps {\r\n /** Trail of items, first to last. The last item renders as the current page. */\r\n items?: BreadcrumbItem[]\r\n /** Accessible name for the nav landmark. Defaults to \"Breadcrumb\". */\r\n label?: string\r\n /** Collapse middle items behind an ellipsis once the trail is longer than this. */\r\n maxItems?: number\r\n}\r\n\r\nconst props = defineProps<BreadcrumbProps>()\r\n\r\n/**\r\n * Provide `items` for the common case, or omit it and pass your own `<li>`\r\n * elements (with class `j-breadcrumb__item`) into the default slot for full\r\n * control over each crumb.\r\n */\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\ninterface ResolvedItem extends BreadcrumbItem {\r\n ellipsis?: boolean\r\n}\r\n\r\nconst visibleItems = computed<ResolvedItem[]>(() => {\r\n const list = props.items ?? []\r\n if (!props.maxItems || list.length <= props.maxItems) return list\r\n const tailCount = Math.max(1, props.maxItems - 2)\r\n return [list[0]!, { label: '…', ellipsis: true }, ...list.slice(-tailCount)]\r\n})\r\n</script>\r\n\r\n<template>\r\n <nav class=\"j-breadcrumb\" :aria-label=\"label || 'Breadcrumb'\">\r\n <ol class=\"j-breadcrumb__list\">\r\n <template v-if=\"items && items.length\">\r\n <li v-for=\"(item, index) in visibleItems\" :key=\"`${item.label}-${index}`\" class=\"j-breadcrumb__item\">\r\n <span v-if=\"item.ellipsis\" class=\"j-breadcrumb__ellipsis\" aria-hidden=\"true\">{{ item.label }}</span>\r\n <a v-else-if=\"item.href && index !== visibleItems.length - 1\" class=\"j-breadcrumb__link j-focusable\" :href=\"item.href\">{{ item.label }}</a>\r\n <span v-else class=\"j-breadcrumb__current\" :aria-current=\"index === visibleItems.length - 1 ? 'page' : undefined\">{{ item.label }}</span>\r\n </li>\r\n </template>\r\n <slot v-else />\r\n </ol>\r\n </nav>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-breadcrumb {\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-breadcrumb__list {\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-items: center;\r\n gap: var(--juxt-space-1-5);\r\n margin: 0;\r\n padding: 0;\r\n list-style: none;\r\n}\r\n\r\n.j-breadcrumb__item {\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1-5);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-snug);\r\n}\r\n\r\n.j-breadcrumb__item:not(:first-child)::before {\r\n content: '';\r\n display: inline-block;\r\n width: 0.3125rem;\r\n height: 0.3125rem;\r\n margin-right: var(--juxt-space-1-5);\r\n border-top: 1.5px solid var(--juxt-fg-disabled);\r\n border-right: 1.5px solid var(--juxt-fg-disabled);\r\n transform: rotate(45deg);\r\n}\r\n\r\n.j-breadcrumb__link {\r\n border-radius: var(--juxt-radius-xs);\r\n color: var(--juxt-fg-muted);\r\n text-decoration: none;\r\n transition: color var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-breadcrumb__link:hover {\r\n color: var(--juxt-fg);\r\n text-decoration: underline;\r\n text-underline-offset: 2px;\r\n}\r\n\r\n.j-breadcrumb__current {\r\n color: var(--juxt-fg);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-breadcrumb__ellipsis {\r\n color: var(--juxt-fg-disabled);\r\n user-select: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\nimport { computed } from 'vue'\r\nimport { Spinner } from '../../icons'\r\n\r\nexport type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive'\r\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg'\r\n\r\nexport interface ButtonProps {\r\n /** Visual weight. `primary` is for the single most important action in a view. */\r\n variant?: ButtonVariant\r\n size?: ButtonSize\r\n /** Shows a spinner, keeps the width, and blocks activation while staying focusable. */\r\n loading?: boolean\r\n disabled?: boolean\r\n /** Square button for a single icon. Provide an `aria-label`. */\r\n icon?: boolean\r\n /** Stretch to the full width of the container. */\r\n block?: boolean\r\n /** Render as another element or component, e.g. `a` or `NuxtLink`. */\r\n as?: string | Component\r\n type?: 'button' | 'submit' | 'reset'\r\n}\r\n\r\nconst props = withDefaults(defineProps<ButtonProps>(), {\r\n variant: 'primary',\r\n size: 'md',\r\n as: 'button',\r\n type: 'button',\r\n})\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n leading?: () => unknown\r\n trailing?: () => unknown\r\n}>()\r\n\r\nconst isNativeButton = computed(() => props.as === 'button')\r\nconst inert = computed(() => props.disabled || props.loading)\r\n\r\nconst attrs = computed(() => {\r\n if (isNativeButton.value) {\r\n return {\r\n type: props.type,\r\n // A loading button stays focusable so keyboard users don't lose their place.\r\n 'disabled': props.disabled || undefined,\r\n 'aria-disabled': props.loading || undefined,\r\n }\r\n }\r\n return {\r\n 'aria-disabled': inert.value || undefined,\r\n 'tabindex': props.disabled ? -1 : undefined,\r\n }\r\n})\r\n\r\nfunction onClick(event: MouseEvent) {\r\n if (inert.value) {\r\n event.preventDefault()\r\n event.stopImmediatePropagation()\r\n }\r\n}\r\n</script>\r\n\r\n<template>\r\n <component\r\n :is=\"as\"\r\n v-bind=\"attrs\"\r\n class=\"j-button j-focusable\"\r\n :class=\"[\r\n `j-button--${variant}`,\r\n `j-button--${size}`,\r\n { 'j-button--icon': icon, 'j-button--block': block, 'is-loading': loading },\r\n ]\"\r\n :aria-busy=\"loading || undefined\"\r\n :data-disabled=\"disabled || undefined\"\r\n @click.capture=\"onClick\"\r\n >\r\n <span class=\"j-button__content\">\r\n <span v-if=\"$slots.leading\" class=\"j-button__icon\"><slot name=\"leading\" /></span>\r\n <slot />\r\n <span v-if=\"$slots.trailing\" class=\"j-button__icon\"><slot name=\"trailing\" /></span>\r\n </span>\r\n <Transition name=\"j-button-spinner\">\r\n <span v-if=\"loading\" class=\"j-button__spinner\">\r\n <Spinner :size=\"size === 'xs' ? 12 : size === 'lg' ? 16 : 14\" />\r\n </span>\r\n </Transition>\r\n </component>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-button {\r\n --j-button-height: var(--juxt-control-md);\r\n --j-button-px: var(--juxt-space-3);\r\n --j-button-gap: var(--juxt-space-1-5);\r\n --j-button-font: var(--juxt-text-md);\r\n --j-button-radius: var(--juxt-radius-sm);\r\n\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n height: var(--j-button-height);\r\n padding: 0 var(--j-button-px);\r\n border: 1px solid transparent;\r\n border-radius: var(--j-button-radius);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--j-button-font);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n white-space: nowrap;\r\n text-decoration: none;\r\n cursor: pointer;\r\n user-select: none;\r\n -webkit-tap-highlight-color: transparent;\r\n touch-action: manipulation;\r\n transition-property: background-color, border-color, color, box-shadow, transform, outline-color, outline-offset;\r\n transition-duration: var(--juxt-duration-fast);\r\n transition-timing-function: var(--juxt-ease-standard);\r\n}\r\n\r\n/* Pressed: a short, physical dip. Released slower than pressed. */\r\n.j-button:active:not([aria-disabled='true'], :disabled) {\r\n transform: scale(0.97);\r\n transition-duration: var(--juxt-duration-instant);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-button:active:not([aria-disabled='true'], :disabled) {\r\n transform: none;\r\n }\r\n}\r\n\r\n.j-button__content {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: var(--j-button-gap);\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-button__icon {\r\n display: inline-flex;\r\n margin-inline: -0.125rem;\r\n opacity: 0.85;\r\n}\r\n\r\n.j-button__icon > svg {\r\n width: 1em;\r\n height: 1em;\r\n}\r\n\r\n.j-button.is-loading {\r\n cursor: progress;\r\n}\r\n\r\n.j-button.is-loading .j-button__content {\r\n opacity: 0;\r\n transform: scale(0.96);\r\n}\r\n\r\n.j-button__spinner {\r\n position: absolute;\r\n inset: 0;\r\n display: grid;\r\n place-items: center;\r\n}\r\n\r\n.j-button-spinner-enter-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out) 40ms,\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out) 40ms;\r\n}\r\n\r\n.j-button-spinner-leave-active {\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-in),\r\n transform var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-button-spinner-enter-from,\r\n.j-button-spinner-leave-to {\r\n opacity: 0;\r\n transform: scale(0.8);\r\n}\r\n\r\n/* Sizes */\r\n.j-button--xs {\r\n --j-button-height: var(--juxt-control-xs);\r\n --j-button-px: var(--juxt-space-2);\r\n --j-button-gap: var(--juxt-space-1);\r\n --j-button-font: var(--juxt-text-xs);\r\n --j-button-radius: var(--juxt-radius-xs);\r\n}\r\n\r\n.j-button--sm {\r\n --j-button-height: var(--juxt-control-sm);\r\n --j-button-px: var(--juxt-space-2-5);\r\n --j-button-gap: var(--juxt-space-1-5);\r\n --j-button-font: var(--juxt-text-sm);\r\n}\r\n\r\n.j-button--lg {\r\n --j-button-height: var(--juxt-control-lg);\r\n --j-button-px: var(--juxt-space-4);\r\n --j-button-gap: var(--juxt-space-2);\r\n --j-button-font: var(--juxt-text-md);\r\n --j-button-radius: var(--juxt-radius-md);\r\n}\r\n\r\n.j-button--icon {\r\n width: var(--j-button-height);\r\n padding: 0;\r\n}\r\n\r\n.j-button--icon .j-button__content > svg,\r\n.j-button--icon .j-button__content > :where(span, i) > svg {\r\n width: calc(var(--j-button-height) * 0.46);\r\n height: calc(var(--j-button-height) * 0.46);\r\n}\r\n\r\n.j-button--block {\r\n display: flex;\r\n width: 100%;\r\n}\r\n\r\n/* Variants */\r\n.j-button--primary {\r\n background: var(--juxt-ink);\r\n color: var(--juxt-ink-fg);\r\n box-shadow: var(--juxt-shadow-xs), var(--juxt-control-highlight);\r\n}\r\n\r\n.j-button--primary:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-ink-hover);\r\n}\r\n\r\n.j-button--secondary {\r\n background: var(--juxt-surface-sunken);\r\n border-color: var(--juxt-border-subtle);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.dark .j-button--secondary,\r\n[data-theme='dark'] .j-button--secondary {\r\n background: var(--juxt-surface-raised);\r\n border-color: var(--juxt-border-subtle);\r\n}\r\n\r\n.j-button--secondary:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n.j-button--outline {\r\n background: var(--juxt-surface);\r\n border-color: var(--juxt-border);\r\n color: var(--juxt-fg);\r\n box-shadow: var(--juxt-shadow-xs);\r\n}\r\n\r\n.j-button--outline:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-hover);\r\n border-color: var(--juxt-border-strong);\r\n}\r\n\r\n.j-button--ghost {\r\n background: transparent;\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-button--ghost:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-button--ghost:active:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n.j-button--destructive {\r\n background: var(--juxt-danger);\r\n color: var(--juxt-danger-fg);\r\n box-shadow: var(--juxt-shadow-xs), var(--juxt-control-highlight);\r\n}\r\n\r\n.j-button--destructive:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-danger-hover);\r\n}\r\n\r\n.j-button--destructive:focus-visible {\r\n outline-color: var(--juxt-danger);\r\n}\r\n\r\n/* Disabled: quiet, not ghostly. */\r\n.j-button:disabled,\r\n.j-button[data-disabled] {\r\n cursor: not-allowed;\r\n opacity: 0.45;\r\n box-shadow: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\nimport { computed } from 'vue'\r\nimport { Spinner } from '../../icons'\r\n\r\nexport type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive'\r\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg'\r\n\r\nexport interface ButtonProps {\r\n /** Visual weight. `primary` is for the single most important action in a view. */\r\n variant?: ButtonVariant\r\n size?: ButtonSize\r\n /** Shows a spinner, keeps the width, and blocks activation while staying focusable. */\r\n loading?: boolean\r\n disabled?: boolean\r\n /** Square button for a single icon. Provide an `aria-label`. */\r\n icon?: boolean\r\n /** Stretch to the full width of the container. */\r\n block?: boolean\r\n /** Render as another element or component, e.g. `a` or `NuxtLink`. */\r\n as?: string | Component\r\n type?: 'button' | 'submit' | 'reset'\r\n}\r\n\r\nconst props = withDefaults(defineProps<ButtonProps>(), {\r\n variant: 'primary',\r\n size: 'md',\r\n as: 'button',\r\n type: 'button',\r\n})\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n leading?: () => unknown\r\n trailing?: () => unknown\r\n}>()\r\n\r\nconst isNativeButton = computed(() => props.as === 'button')\r\nconst inert = computed(() => props.disabled || props.loading)\r\n\r\nconst attrs = computed(() => {\r\n if (isNativeButton.value) {\r\n return {\r\n type: props.type,\r\n // A loading button stays focusable so keyboard users don't lose their place.\r\n 'disabled': props.disabled || undefined,\r\n 'aria-disabled': props.loading || undefined,\r\n }\r\n }\r\n return {\r\n 'aria-disabled': inert.value || undefined,\r\n 'tabindex': props.disabled ? -1 : undefined,\r\n }\r\n})\r\n\r\nfunction onClick(event: MouseEvent) {\r\n if (inert.value) {\r\n event.preventDefault()\r\n event.stopImmediatePropagation()\r\n }\r\n}\r\n</script>\r\n\r\n<template>\r\n <component\r\n :is=\"as\"\r\n v-bind=\"attrs\"\r\n class=\"j-button j-focusable\"\r\n :class=\"[\r\n `j-button--${variant}`,\r\n `j-button--${size}`,\r\n { 'j-button--icon': icon, 'j-button--block': block, 'is-loading': loading },\r\n ]\"\r\n :aria-busy=\"loading || undefined\"\r\n :data-disabled=\"disabled || undefined\"\r\n @click.capture=\"onClick\"\r\n >\r\n <span class=\"j-button__content\">\r\n <span v-if=\"$slots.leading\" class=\"j-button__icon\"><slot name=\"leading\" /></span>\r\n <slot />\r\n <span v-if=\"$slots.trailing\" class=\"j-button__icon\"><slot name=\"trailing\" /></span>\r\n </span>\r\n <Transition name=\"j-button-spinner\">\r\n <span v-if=\"loading\" class=\"j-button__spinner\">\r\n <Spinner :size=\"size === 'xs' ? 12 : size === 'lg' ? 16 : 14\" />\r\n </span>\r\n </Transition>\r\n </component>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-button {\r\n --j-button-height: var(--juxt-control-md);\r\n --j-button-px: var(--juxt-space-3);\r\n --j-button-gap: var(--juxt-space-1-5);\r\n --j-button-font: var(--juxt-text-md);\r\n --j-button-radius: var(--juxt-radius-sm);\r\n\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n height: var(--j-button-height);\r\n padding: 0 var(--j-button-px);\r\n border: 1px solid transparent;\r\n border-radius: var(--j-button-radius);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--j-button-font);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n white-space: nowrap;\r\n text-decoration: none;\r\n cursor: pointer;\r\n user-select: none;\r\n -webkit-tap-highlight-color: transparent;\r\n touch-action: manipulation;\r\n transition-property: background-color, border-color, color, box-shadow, transform, outline-color, outline-offset;\r\n transition-duration: var(--juxt-duration-fast);\r\n transition-timing-function: var(--juxt-ease-standard);\r\n}\r\n\r\n/* Pressed: a short, physical dip. Released slower than pressed. */\r\n.j-button:active:not([aria-disabled='true'], :disabled) {\r\n transform: scale(0.97);\r\n transition-duration: var(--juxt-duration-instant);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-button:active:not([aria-disabled='true'], :disabled) {\r\n transform: none;\r\n }\r\n}\r\n\r\n.j-button__content {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: var(--j-button-gap);\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-button__icon {\r\n display: inline-flex;\r\n margin-inline: -0.125rem;\r\n opacity: 0.85;\r\n}\r\n\r\n.j-button__icon > svg {\r\n width: 1em;\r\n height: 1em;\r\n}\r\n\r\n.j-button.is-loading {\r\n cursor: progress;\r\n}\r\n\r\n.j-button.is-loading .j-button__content {\r\n opacity: 0;\r\n transform: scale(0.96);\r\n}\r\n\r\n.j-button__spinner {\r\n position: absolute;\r\n inset: 0;\r\n display: grid;\r\n place-items: center;\r\n}\r\n\r\n.j-button-spinner-enter-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out) 40ms,\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out) 40ms;\r\n}\r\n\r\n.j-button-spinner-leave-active {\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-in),\r\n transform var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-button-spinner-enter-from,\r\n.j-button-spinner-leave-to {\r\n opacity: 0;\r\n transform: scale(0.8);\r\n}\r\n\r\n/* Sizes */\r\n.j-button--xs {\r\n --j-button-height: var(--juxt-control-xs);\r\n --j-button-px: var(--juxt-space-2);\r\n --j-button-gap: var(--juxt-space-1);\r\n --j-button-font: var(--juxt-text-xs);\r\n --j-button-radius: var(--juxt-radius-xs);\r\n}\r\n\r\n.j-button--sm {\r\n --j-button-height: var(--juxt-control-sm);\r\n --j-button-px: var(--juxt-space-2-5);\r\n --j-button-gap: var(--juxt-space-1-5);\r\n --j-button-font: var(--juxt-text-sm);\r\n}\r\n\r\n.j-button--lg {\r\n --j-button-height: var(--juxt-control-lg);\r\n --j-button-px: var(--juxt-space-4);\r\n --j-button-gap: var(--juxt-space-2);\r\n --j-button-font: var(--juxt-text-md);\r\n --j-button-radius: var(--juxt-radius-md);\r\n}\r\n\r\n.j-button--icon {\r\n width: var(--j-button-height);\r\n padding: 0;\r\n}\r\n\r\n.j-button--icon .j-button__content > svg,\r\n.j-button--icon .j-button__content > :where(span, i) > svg {\r\n width: calc(var(--j-button-height) * 0.46);\r\n height: calc(var(--j-button-height) * 0.46);\r\n}\r\n\r\n.j-button--block {\r\n display: flex;\r\n width: 100%;\r\n}\r\n\r\n/* Variants */\r\n.j-button--primary {\r\n background: var(--juxt-ink);\r\n color: var(--juxt-ink-fg);\r\n box-shadow: var(--juxt-shadow-xs), var(--juxt-control-highlight);\r\n}\r\n\r\n.j-button--primary:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-ink-hover);\r\n}\r\n\r\n.j-button--secondary {\r\n background: var(--juxt-surface-sunken);\r\n border-color: var(--juxt-border-subtle);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.dark .j-button--secondary,\r\n[data-theme='dark'] .j-button--secondary {\r\n background: var(--juxt-surface-raised);\r\n border-color: var(--juxt-border-subtle);\r\n}\r\n\r\n.j-button--secondary:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n.j-button--outline {\r\n background: var(--juxt-surface);\r\n border-color: var(--juxt-border);\r\n color: var(--juxt-fg);\r\n box-shadow: var(--juxt-shadow-xs);\r\n}\r\n\r\n.j-button--outline:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-hover);\r\n border-color: var(--juxt-border-strong);\r\n}\r\n\r\n.j-button--ghost {\r\n background: transparent;\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-button--ghost:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-button--ghost:active:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n.j-button--destructive {\r\n background: var(--juxt-danger);\r\n color: var(--juxt-danger-fg);\r\n box-shadow: var(--juxt-shadow-xs), var(--juxt-control-highlight);\r\n}\r\n\r\n.j-button--destructive:hover:not([aria-disabled='true'], :disabled) {\r\n background: var(--juxt-danger-hover);\r\n}\r\n\r\n.j-button--destructive:focus-visible {\r\n outline-color: var(--juxt-danger);\r\n}\r\n\r\n/* Disabled: quiet, not ghostly. */\r\n.j-button:disabled,\r\n.j-button[data-disabled] {\r\n cursor: not-allowed;\r\n opacity: 0.45;\r\n box-shadow: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\n\r\nexport interface CardProps {\r\n title?: string\r\n description?: string\r\n /** `outline` sits on the page; `muted` recedes into it. */\r\n variant?: 'outline' | 'muted'\r\n padding?: 'none' | 'sm' | 'md' | 'lg'\r\n as?: string | Component\r\n}\r\n\r\nwithDefaults(defineProps<CardProps>(), { variant: 'outline', padding: 'md', as: 'div' })\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n header?: () => unknown\r\n /** Sits to the right of the title | a button, badge or menu. */\r\n actions?: () => unknown\r\n footer?: () => unknown\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <component :is=\"as\" class=\"j-card\" :class=\"[`j-card--${variant}`, `j-card--p-${padding}`]\">\r\n <header v-if=\"title || description || $slots.header || $slots.actions\" class=\"j-card__header\">\r\n <slot name=\"header\">\r\n <div class=\"j-card__heading\">\r\n <h3 v-if=\"title\" class=\"j-card__title\">\r\n {{ title }}\r\n </h3>\r\n <p v-if=\"description\" class=\"j-card__description\">\r\n {{ description }}\r\n </p>\r\n </div>\r\n </slot>\r\n <div v-if=\"$slots.actions\" class=\"j-card__actions\">\r\n <slot name=\"actions\" />\r\n </div>\r\n </header>\r\n <div v-if=\"$slots.default\" class=\"j-card__body\">\r\n <slot />\r\n </div>\r\n <footer v-if=\"$slots.footer\" class=\"j-card__footer\">\r\n <slot name=\"footer\" />\r\n </footer>\r\n </component>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-card {\r\n --j-card-p: var(--juxt-space-5);\r\n\r\n display: flex;\r\n flex-direction: column;\r\n min-width: 0;\r\n border-radius: var(--juxt-radius-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-card--outline {\r\n --j-avatar-ring: var(--juxt-surface);\r\n\r\n border: 1px solid var(--juxt-border);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-xs);\r\n}\r\n\r\n.j-card--muted {\r\n --j-avatar-ring: var(--juxt-surface-sunken);\r\n\r\n border: 1px solid transparent;\r\n background: var(--juxt-surface-sunken);\r\n}\r\n\r\n.j-card--p-none {\r\n --j-card-p: 0;\r\n}\r\n\r\n.j-card--p-sm {\r\n --j-card-p: var(--juxt-space-3);\r\n}\r\n\r\n.j-card--p-lg {\r\n --j-card-p: var(--juxt-space-6);\r\n}\r\n\r\n.j-card__header {\r\n display: flex;\r\n align-items: flex-start;\r\n justify-content: space-between;\r\n gap: var(--juxt-space-4);\r\n padding: var(--j-card-p) var(--j-card-p) 0;\r\n}\r\n\r\n.j-card__heading {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1);\r\n min-width: 0;\r\n}\r\n\r\n.j-card__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-card__description {\r\n margin: 0;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-card__actions {\r\n display: flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-2);\r\n margin: calc(var(--juxt-space-1) * -1) 0;\r\n}\r\n\r\n.j-card__body {\r\n flex: 1;\r\n padding: var(--j-card-p);\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-card__header + .j-card__body {\r\n padding-top: var(--juxt-space-4);\r\n}\r\n\r\n.j-card__footer {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-end;\r\n gap: var(--juxt-space-2);\r\n padding: var(--juxt-space-3) var(--j-card-p);\r\n border-top: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.j-card__header:last-child {\r\n padding-bottom: var(--j-card-p);\r\n}\r\n\r\n.j-card--muted .j-card__footer {\r\n border-top-color: var(--juxt-border);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\n\r\nexport interface CardProps {\r\n title?: string\r\n description?: string\r\n /** `outline` sits on the page; `muted` recedes into it. */\r\n variant?: 'outline' | 'muted'\r\n padding?: 'none' | 'sm' | 'md' | 'lg'\r\n as?: string | Component\r\n}\r\n\r\nwithDefaults(defineProps<CardProps>(), { variant: 'outline', padding: 'md', as: 'div' })\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n header?: () => unknown\r\n /** Sits to the right of the title | a button, badge or menu. */\r\n actions?: () => unknown\r\n footer?: () => unknown\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <component :is=\"as\" class=\"j-card\" :class=\"[`j-card--${variant}`, `j-card--p-${padding}`]\">\r\n <header v-if=\"title || description || $slots.header || $slots.actions\" class=\"j-card__header\">\r\n <slot name=\"header\">\r\n <div class=\"j-card__heading\">\r\n <h3 v-if=\"title\" class=\"j-card__title\">\r\n {{ title }}\r\n </h3>\r\n <p v-if=\"description\" class=\"j-card__description\">\r\n {{ description }}\r\n </p>\r\n </div>\r\n </slot>\r\n <div v-if=\"$slots.actions\" class=\"j-card__actions\">\r\n <slot name=\"actions\" />\r\n </div>\r\n </header>\r\n <div v-if=\"$slots.default\" class=\"j-card__body\">\r\n <slot />\r\n </div>\r\n <footer v-if=\"$slots.footer\" class=\"j-card__footer\">\r\n <slot name=\"footer\" />\r\n </footer>\r\n </component>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-card {\r\n --j-card-p: var(--juxt-space-5);\r\n\r\n display: flex;\r\n flex-direction: column;\r\n min-width: 0;\r\n border-radius: var(--juxt-radius-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-card--outline {\r\n --j-avatar-ring: var(--juxt-surface);\r\n\r\n border: 1px solid var(--juxt-border);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-xs);\r\n}\r\n\r\n.j-card--muted {\r\n --j-avatar-ring: var(--juxt-surface-sunken);\r\n\r\n border: 1px solid transparent;\r\n background: var(--juxt-surface-sunken);\r\n}\r\n\r\n.j-card--p-none {\r\n --j-card-p: 0;\r\n}\r\n\r\n.j-card--p-sm {\r\n --j-card-p: var(--juxt-space-3);\r\n}\r\n\r\n.j-card--p-lg {\r\n --j-card-p: var(--juxt-space-6);\r\n}\r\n\r\n.j-card__header {\r\n display: flex;\r\n align-items: flex-start;\r\n justify-content: space-between;\r\n gap: var(--juxt-space-4);\r\n padding: var(--j-card-p) var(--j-card-p) 0;\r\n}\r\n\r\n.j-card__heading {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1);\r\n min-width: 0;\r\n}\r\n\r\n.j-card__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-card__description {\r\n margin: 0;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-card__actions {\r\n display: flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-2);\r\n margin: calc(var(--juxt-space-1) * -1) 0;\r\n}\r\n\r\n.j-card__body {\r\n flex: 1;\r\n padding: var(--j-card-p);\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-card__header + .j-card__body {\r\n padding-top: var(--juxt-space-4);\r\n}\r\n\r\n.j-card__footer {\r\n display: flex;\r\n align-items: center;\r\n justify-content: flex-end;\r\n gap: var(--juxt-space-2);\r\n padding: var(--juxt-space-3) var(--j-card-p);\r\n border-top: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.j-card__header:last-child {\r\n padding-bottom: var(--j-card-p);\r\n}\r\n\r\n.j-card--muted .j-card__footer {\r\n border-top-color: var(--juxt-border);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, ref, useId, watchEffect } from 'vue'\r\n\r\nexport interface CheckboxProps {\r\n label?: string\r\n description?: string\r\n disabled?: boolean\r\n required?: boolean\r\n /** Shows a dash. Typically used for \"select all\" when only some items are selected. */\r\n indeterminate?: boolean\r\n name?: string\r\n value?: string\r\n id?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = defineProps<CheckboxProps>()\r\nconst model = defineModel<boolean>({ default: false })\r\n\r\ndefineSlots<{ default?: () => unknown, description?: () => unknown }>()\r\n\r\nconst generated = useId()\r\nconst id = computed(() => props.id ?? `j-${generated}`)\r\nconst descriptionId = computed(() => `${id.value}-description`)\r\nconst input = ref<HTMLInputElement | null>(null)\r\n\r\n// `indeterminate` is a DOM property, not an attribute.\r\nwatchEffect(() => {\r\n if (input.value) input.value.indeterminate = !!props.indeterminate\r\n})\r\n\r\nconst state = computed(() => (props.indeterminate ? 'indeterminate' : model.value ? 'checked' : 'unchecked'))\r\n\r\nfunction onChange(event: Event) {\r\n model.value = (event.target as HTMLInputElement).checked\r\n}\r\n\r\ndefineExpose({ input, focus: () => input.value?.focus() })\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-checkbox\"\r\n :class=\"[$attrs.class, { 'is-disabled': disabled }]\"\r\n :style=\"$attrs.style as any\"\r\n :data-state=\"state\"\r\n >\r\n <span class=\"j-checkbox__control\">\r\n <input\r\n :id=\"id\"\r\n ref=\"input\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"checkbox\"\r\n class=\"j-checkbox__input\"\r\n :checked=\"model\"\r\n :disabled=\"disabled\"\r\n :required=\"required\"\r\n :name=\"name\"\r\n :value=\"value\"\r\n :aria-describedby=\"description || $slots.description ? descriptionId : undefined\"\r\n @change=\"onChange\"\r\n />\r\n <span class=\"j-checkbox__box\" aria-hidden=\"true\">\r\n <svg class=\"j-checkbox__check\" viewBox=\"0 0 16 16\" fill=\"none\">\r\n <path d=\"M4 8.25l2.75 2.75L12 5.5\" pathLength=\"1\" />\r\n </svg>\r\n <svg class=\"j-checkbox__dash\" viewBox=\"0 0 16 16\" fill=\"none\">\r\n <path d=\"M4.5 8h7\" />\r\n </svg>\r\n </span>\r\n </span>\r\n <span v-if=\"label || $slots.default || description || $slots.description\" class=\"j-checkbox__text\">\r\n <label v-if=\"label || $slots.default\" :for=\"id\" class=\"j-checkbox__label\">\r\n <slot>{{ label }}</slot>\r\n </label>\r\n <span v-if=\"description || $slots.description\" :id=\"descriptionId\" class=\"j-checkbox__description\">\r\n <slot name=\"description\">{{ description }}</slot>\r\n </span>\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-checkbox {\r\n --j-checkbox-size: 1rem;\r\n\r\n display: inline-flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-checkbox__control {\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n /* Align the box with the first line of the label. */\r\n margin-top: calc((var(--juxt-text-md) * var(--juxt-leading-snug) - var(--j-checkbox-size)) / 2);\r\n}\r\n\r\n.j-checkbox__input {\r\n position: absolute;\r\n inset: -4px;\r\n z-index: 1;\r\n width: calc(100% + 8px);\r\n height: calc(100% + 8px);\r\n margin: 0;\r\n opacity: 0;\r\n cursor: pointer;\r\n}\r\n\r\n.j-checkbox__input:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-checkbox__box {\r\n display: grid;\r\n place-items: center;\r\n width: var(--j-checkbox-size);\r\n height: var(--j-checkbox-size);\r\n border: 1px solid var(--juxt-border-strong);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-xs);\r\n color: var(--juxt-accent-fg);\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-checkbox__box > svg {\r\n grid-area: 1 / 1;\r\n width: 100%;\r\n height: 100%;\r\n stroke: currentColor;\r\n stroke-width: 2;\r\n stroke-linecap: round;\r\n stroke-linejoin: round;\r\n}\r\n\r\n.j-checkbox__input:hover:not(:disabled) + .j-checkbox__box {\r\n border-color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-checkbox__input:active:not(:disabled) + .j-checkbox__box {\r\n transform: scale(0.9);\r\n}\r\n\r\n.j-checkbox__input:focus-visible + .j-checkbox__box {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-checkbox[data-state='checked'] .j-checkbox__box,\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__box {\r\n border-color: var(--juxt-accent);\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-checkbox[data-state='checked'] .j-checkbox__input:hover:not(:disabled) + .j-checkbox__box,\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__input:hover:not(:disabled) + .j-checkbox__box {\r\n border-color: var(--juxt-accent-hover);\r\n background: var(--juxt-accent-hover);\r\n}\r\n\r\n/* The check draws itself in, just after the fill lands. */\r\n.j-checkbox__check path {\r\n stroke-dasharray: 1;\r\n stroke-dashoffset: 1;\r\n transition: stroke-dashoffset var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-checkbox[data-state='checked'] .j-checkbox__check path {\r\n stroke-dashoffset: 0;\r\n transition: stroke-dashoffset var(--juxt-duration-normal) var(--juxt-ease-out) 40ms;\r\n}\r\n\r\n.j-checkbox__dash {\r\n opacity: 0;\r\n transform: scaleX(0.4);\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__dash {\r\n opacity: 1;\r\n transform: none;\r\n}\r\n\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__check {\r\n opacity: 0;\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-checkbox__check path,\r\n .j-checkbox[data-state='checked'] .j-checkbox__check path {\r\n transition-duration: 0ms;\r\n }\r\n\r\n .j-checkbox__input:active:not(:disabled) + .j-checkbox__box {\r\n transform: none;\r\n }\r\n}\r\n\r\n.j-checkbox__text {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-checkbox__label {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-checkbox__description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-checkbox.is-disabled .j-checkbox__box {\r\n opacity: 0.5;\r\n box-shadow: none;\r\n}\r\n\r\n.j-checkbox.is-disabled .j-checkbox__label {\r\n color: var(--juxt-fg-muted);\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, ref, useId, watchEffect } from 'vue'\r\n\r\nexport interface CheckboxProps {\r\n label?: string\r\n description?: string\r\n disabled?: boolean\r\n required?: boolean\r\n /** Shows a dash. Typically used for \"select all\" when only some items are selected. */\r\n indeterminate?: boolean\r\n name?: string\r\n value?: string\r\n id?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = defineProps<CheckboxProps>()\r\nconst model = defineModel<boolean>({ default: false })\r\n\r\ndefineSlots<{ default?: () => unknown, description?: () => unknown }>()\r\n\r\nconst generated = useId()\r\nconst id = computed(() => props.id ?? `j-${generated}`)\r\nconst descriptionId = computed(() => `${id.value}-description`)\r\nconst input = ref<HTMLInputElement | null>(null)\r\n\r\n// `indeterminate` is a DOM property, not an attribute.\r\nwatchEffect(() => {\r\n if (input.value) input.value.indeterminate = !!props.indeterminate\r\n})\r\n\r\nconst state = computed(() => (props.indeterminate ? 'indeterminate' : model.value ? 'checked' : 'unchecked'))\r\n\r\nfunction onChange(event: Event) {\r\n model.value = (event.target as HTMLInputElement).checked\r\n}\r\n\r\ndefineExpose({ input, focus: () => input.value?.focus() })\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-checkbox\"\r\n :class=\"[$attrs.class, { 'is-disabled': disabled }]\"\r\n :style=\"$attrs.style as any\"\r\n :data-state=\"state\"\r\n >\r\n <span class=\"j-checkbox__control\">\r\n <input\r\n :id=\"id\"\r\n ref=\"input\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"checkbox\"\r\n class=\"j-checkbox__input\"\r\n :checked=\"model\"\r\n :disabled=\"disabled\"\r\n :required=\"required\"\r\n :name=\"name\"\r\n :value=\"value\"\r\n :aria-describedby=\"description || $slots.description ? descriptionId : undefined\"\r\n @change=\"onChange\"\r\n />\r\n <span class=\"j-checkbox__box\" aria-hidden=\"true\">\r\n <svg class=\"j-checkbox__check\" viewBox=\"0 0 16 16\" fill=\"none\">\r\n <path d=\"M4 8.25l2.75 2.75L12 5.5\" pathLength=\"1\" />\r\n </svg>\r\n <svg class=\"j-checkbox__dash\" viewBox=\"0 0 16 16\" fill=\"none\">\r\n <path d=\"M4.5 8h7\" />\r\n </svg>\r\n </span>\r\n </span>\r\n <span v-if=\"label || $slots.default || description || $slots.description\" class=\"j-checkbox__text\">\r\n <label v-if=\"label || $slots.default\" :for=\"id\" class=\"j-checkbox__label\">\r\n <slot>{{ label }}</slot>\r\n </label>\r\n <span v-if=\"description || $slots.description\" :id=\"descriptionId\" class=\"j-checkbox__description\">\r\n <slot name=\"description\">{{ description }}</slot>\r\n </span>\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-checkbox {\r\n --j-checkbox-size: 1rem;\r\n\r\n display: inline-flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-checkbox__control {\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n /* Align the box with the first line of the label. */\r\n margin-top: calc((var(--juxt-text-md) * var(--juxt-leading-snug) - var(--j-checkbox-size)) / 2);\r\n}\r\n\r\n.j-checkbox__input {\r\n position: absolute;\r\n inset: -4px;\r\n z-index: 1;\r\n width: calc(100% + 8px);\r\n height: calc(100% + 8px);\r\n margin: 0;\r\n opacity: 0;\r\n cursor: pointer;\r\n}\r\n\r\n.j-checkbox__input:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-checkbox__box {\r\n display: grid;\r\n place-items: center;\r\n width: var(--j-checkbox-size);\r\n height: var(--j-checkbox-size);\r\n border: 1px solid var(--juxt-border-strong);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-xs);\r\n color: var(--juxt-accent-fg);\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-checkbox__box > svg {\r\n grid-area: 1 / 1;\r\n width: 100%;\r\n height: 100%;\r\n stroke: currentColor;\r\n stroke-width: 2;\r\n stroke-linecap: round;\r\n stroke-linejoin: round;\r\n}\r\n\r\n.j-checkbox__input:hover:not(:disabled) + .j-checkbox__box {\r\n border-color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-checkbox__input:active:not(:disabled) + .j-checkbox__box {\r\n transform: scale(0.9);\r\n}\r\n\r\n.j-checkbox__input:focus-visible + .j-checkbox__box {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-checkbox[data-state='checked'] .j-checkbox__box,\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__box {\r\n border-color: var(--juxt-accent);\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-checkbox[data-state='checked'] .j-checkbox__input:hover:not(:disabled) + .j-checkbox__box,\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__input:hover:not(:disabled) + .j-checkbox__box {\r\n border-color: var(--juxt-accent-hover);\r\n background: var(--juxt-accent-hover);\r\n}\r\n\r\n/* The check draws itself in, just after the fill lands. */\r\n.j-checkbox__check path {\r\n stroke-dasharray: 1;\r\n stroke-dashoffset: 1;\r\n transition: stroke-dashoffset var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-checkbox[data-state='checked'] .j-checkbox__check path {\r\n stroke-dashoffset: 0;\r\n transition: stroke-dashoffset var(--juxt-duration-normal) var(--juxt-ease-out) 40ms;\r\n}\r\n\r\n.j-checkbox__dash {\r\n opacity: 0;\r\n transform: scaleX(0.4);\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__dash {\r\n opacity: 1;\r\n transform: none;\r\n}\r\n\r\n.j-checkbox[data-state='indeterminate'] .j-checkbox__check {\r\n opacity: 0;\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-checkbox__check path,\r\n .j-checkbox[data-state='checked'] .j-checkbox__check path {\r\n transition-duration: 0ms;\r\n }\r\n\r\n .j-checkbox__input:active:not(:disabled) + .j-checkbox__box {\r\n transform: none;\r\n }\r\n}\r\n\r\n.j-checkbox__text {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-checkbox__label {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-checkbox__description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-checkbox.is-disabled .j-checkbox__box {\r\n opacity: 0.5;\r\n box-shadow: none;\r\n}\r\n\r\n.j-checkbox.is-disabled .j-checkbox__label {\r\n color: var(--juxt-fg-muted);\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { useId } from 'vue'\r\nimport { IconChevronDown } from '../../icons'\r\n\r\nexport interface CollapsibleProps {\r\n /** Visible summary text; use the `trigger` slot for custom content instead. */\r\n label?: string\r\n disabled?: boolean\r\n}\r\n\r\ndefineProps<CollapsibleProps>()\r\n\r\n/** A single disclosure primitive | for a group of them, use `JAccordion`. */\r\nconst model = defineModel<boolean>({ default: false })\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n trigger?: () => unknown\r\n}>()\r\n\r\nconst id = `j-${useId()}`\r\nconst triggerId = `${id}-trigger`\r\nconst panelId = `${id}-panel`\r\n\r\nfunction toggle() {\r\n model.value = !model.value\r\n}\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-collapsible\" :data-state=\"model ? 'open' : 'closed'\">\r\n <button\r\n :id=\"triggerId\"\r\n type=\"button\"\r\n class=\"j-collapsible__trigger j-focusable\"\r\n :aria-expanded=\"model\"\r\n :aria-controls=\"panelId\"\r\n :disabled=\"disabled\"\r\n @click=\"toggle\"\r\n >\r\n <IconChevronDown class=\"j-collapsible__chevron\" />\r\n <span class=\"j-collapsible__label\"><slot name=\"trigger\">{{ label }}</slot></span>\r\n </button>\r\n <div\r\n :id=\"panelId\"\r\n role=\"region\"\r\n class=\"j-collapsible__panel\"\r\n :aria-labelledby=\"triggerId\"\r\n :inert=\"!model\"\r\n >\r\n <div class=\"j-collapsible__panel-inner\">\r\n <div class=\"j-collapsible__content\">\r\n <slot />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-collapsible {\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-collapsible__trigger {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1-5);\r\n padding: var(--juxt-space-1) 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-xs);\r\n background: transparent;\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: 2px;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-collapsible__trigger:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-collapsible__chevron {\r\n flex: none;\r\n width: 1rem;\r\n height: 1rem;\r\n color: var(--juxt-fg-muted);\r\n transition: transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-collapsible[data-state='open'] .j-collapsible__chevron {\r\n transform: rotate(180deg);\r\n}\r\n\r\n.j-collapsible__panel {\r\n display: grid;\r\n grid-template-rows: 0fr;\r\n transition: grid-template-rows var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-collapsible[data-state='open'] .j-collapsible__panel {\r\n grid-template-rows: 1fr;\r\n}\r\n\r\n.j-collapsible__panel-inner {\r\n min-height: 0;\r\n overflow: hidden;\r\n}\r\n\r\n.j-collapsible__content {\r\n padding-top: var(--juxt-space-2);\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n opacity: 0;\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-collapsible[data-state='open'] .j-collapsible__content {\r\n opacity: 1;\r\n transition-delay: 60ms;\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-collapsible__panel,\r\n .j-collapsible__chevron {\r\n transition: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { useId } from 'vue'\r\nimport { IconChevronDown } from '../../icons'\r\n\r\nexport interface CollapsibleProps {\r\n /** Visible summary text; use the `trigger` slot for custom content instead. */\r\n label?: string\r\n disabled?: boolean\r\n}\r\n\r\ndefineProps<CollapsibleProps>()\r\n\r\n/** A single disclosure primitive | for a group of them, use `JAccordion`. */\r\nconst model = defineModel<boolean>({ default: false })\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n trigger?: () => unknown\r\n}>()\r\n\r\nconst id = `j-${useId()}`\r\nconst triggerId = `${id}-trigger`\r\nconst panelId = `${id}-panel`\r\n\r\nfunction toggle() {\r\n model.value = !model.value\r\n}\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-collapsible\" :data-state=\"model ? 'open' : 'closed'\">\r\n <button\r\n :id=\"triggerId\"\r\n type=\"button\"\r\n class=\"j-collapsible__trigger j-focusable\"\r\n :aria-expanded=\"model\"\r\n :aria-controls=\"panelId\"\r\n :disabled=\"disabled\"\r\n @click=\"toggle\"\r\n >\r\n <IconChevronDown class=\"j-collapsible__chevron\" />\r\n <span class=\"j-collapsible__label\"><slot name=\"trigger\">{{ label }}</slot></span>\r\n </button>\r\n <div\r\n :id=\"panelId\"\r\n role=\"region\"\r\n class=\"j-collapsible__panel\"\r\n :aria-labelledby=\"triggerId\"\r\n :inert=\"!model\"\r\n >\r\n <div class=\"j-collapsible__panel-inner\">\r\n <div class=\"j-collapsible__content\">\r\n <slot />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-collapsible {\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-collapsible__trigger {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1-5);\r\n padding: var(--juxt-space-1) 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-xs);\r\n background: transparent;\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: 2px;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-collapsible__trigger:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-collapsible__chevron {\r\n flex: none;\r\n width: 1rem;\r\n height: 1rem;\r\n color: var(--juxt-fg-muted);\r\n transition: transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-collapsible[data-state='open'] .j-collapsible__chevron {\r\n transform: rotate(180deg);\r\n}\r\n\r\n.j-collapsible__panel {\r\n display: grid;\r\n grid-template-rows: 0fr;\r\n transition: grid-template-rows var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-collapsible[data-state='open'] .j-collapsible__panel {\r\n grid-template-rows: 1fr;\r\n}\r\n\r\n.j-collapsible__panel-inner {\r\n min-height: 0;\r\n overflow: hidden;\r\n}\r\n\r\n.j-collapsible__content {\r\n padding-top: var(--juxt-space-2);\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n opacity: 0;\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-collapsible[data-state='open'] .j-collapsible__content {\r\n opacity: 1;\r\n transition-delay: 60ms;\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-collapsible__panel,\r\n .j-collapsible__chevron {\r\n transition: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","import type { Ref } from 'vue'\r\nimport { nextTick, onBeforeUnmount, watch } from 'vue'\r\nimport { focusElement, getFocusableElements, isClient } from '../utils/dom'\r\n\r\nexport interface FocusTrapOptions {\r\n /** Element to focus when the trap activates. Falls back to the first focusable element, then the container. */\r\n initialFocus?: () => HTMLElement | null | undefined\r\n /** Return focus to whatever was focused before activation. Defaults to true. */\r\n restoreFocus?: boolean\r\n}\r\n\r\ninterface Trap {\r\n container: Ref<HTMLElement | null>\r\n}\r\n\r\n// Traps nest: only the most recently activated one is in charge.\r\nconst stack: Trap[] = []\r\n\r\nfunction isTop(trap: Trap) {\r\n return stack[stack.length - 1] === trap\r\n}\r\n\r\nlet listening = false\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key !== 'Tab' || event.defaultPrevented) return\r\n const trap = stack[stack.length - 1]\r\n const container = trap?.container.value\r\n if (!container) return\r\n\r\n const focusable = getFocusableElements(container)\r\n if (focusable.length === 0) {\r\n event.preventDefault()\r\n focusElement(container)\r\n return\r\n }\r\n\r\n const first = focusable[0]!\r\n const last = focusable[focusable.length - 1]!\r\n const active = document.activeElement as HTMLElement | null\r\n const inside = active ? container.contains(active) : false\r\n\r\n if (event.shiftKey && (active === first || active === container || !inside)) {\r\n event.preventDefault()\r\n focusElement(last)\r\n } else if (!event.shiftKey && (active === last || !inside)) {\r\n event.preventDefault()\r\n focusElement(first)\r\n }\r\n}\r\n\r\nfunction onFocusIn(event: FocusEvent) {\r\n const trap = stack[stack.length - 1]\r\n const container = trap?.container.value\r\n const target = event.target as Node | null\r\n if (!container || !target || container.contains(target)) return\r\n // Focus escaped (e.g. a programmatic focus elsewhere) | bring it back.\r\n if ((target as HTMLElement).closest?.('[data-juxt-layer-ignore]')) return\r\n const [first] = getFocusableElements(container)\r\n focusElement(first ?? container)\r\n}\r\n\r\nfunction listen() {\r\n if (listening || !isClient) return\r\n document.addEventListener('keydown', onKeydown)\r\n document.addEventListener('focusin', onFocusIn)\r\n listening = true\r\n}\r\n\r\nfunction unlisten() {\r\n if (!listening || stack.length > 0) return\r\n document.removeEventListener('keydown', onKeydown)\r\n document.removeEventListener('focusin', onFocusIn)\r\n listening = false\r\n}\r\n\r\n/**\r\n * Keep keyboard focus inside `container` while `active` is true, and put it\r\n * back where it came from afterwards.\r\n */\r\nexport function useFocusTrap(container: Ref<HTMLElement | null>, active: Ref<boolean>, options: FocusTrapOptions = {}) {\r\n const trap: Trap = { container }\r\n let returnTo: HTMLElement | null = null\r\n // The container as it was while active. By the time we deactivate, Vue may\r\n // already have cleared the template ref while the element animates out.\r\n let activeContainer: HTMLElement | null = null\r\n\r\n async function activate() {\r\n returnTo = document.activeElement as HTMLElement | null\r\n stack.push(trap)\r\n listen()\r\n await nextTick()\r\n const el = container.value\r\n activeContainer = el\r\n if (!el || !isTop(trap)) return\r\n if (el.contains(document.activeElement)) return\r\n const target = options.initialFocus?.() ?? el.querySelector<HTMLElement>('[autofocus], [data-autofocus]') ?? getFocusableElements(el)[0] ?? el\r\n focusElement(target)\r\n }\r\n\r\n function deactivate() {\r\n const index = stack.indexOf(trap)\r\n if (index === -1) return\r\n stack.splice(index, 1)\r\n unlisten()\r\n\r\n if (options.restoreFocus === false) return\r\n const target = returnTo\r\n returnTo = null\r\n if (!target || !target.isConnected) return\r\n const current = document.activeElement as HTMLElement | null\r\n // Only restore when focus would otherwise be lost. If the user deliberately\r\n // moved focus somewhere meaningful, respect that.\r\n const lost = !current || current === document.body || !current.isConnected\r\n || !!activeContainer?.contains(current) || !!container.value?.contains(current)\r\n activeContainer = null\r\n if (lost) focusElement(target)\r\n }\r\n\r\n if (isClient) {\r\n watch(\r\n active,\r\n (value) => {\r\n if (value) activate()\r\n else deactivate()\r\n },\r\n { immediate: true, flush: 'post' },\r\n )\r\n onBeforeUnmount(deactivate)\r\n }\r\n}\r\n","import type { MaybeRefOrGetter } from 'vue'\r\nimport { onBeforeUnmount, onMounted, toValue } from 'vue'\r\nimport { isMac } from '../utils/dom'\r\n\r\n/**\r\n * Parse a shortcut such as `mod+k`, `shift+/` or `alt+t`.\r\n * `mod` is ⌘ on Apple platforms and Ctrl elsewhere.\r\n */\r\nexport function matchesHotkey(event: KeyboardEvent, hotkey: string): boolean {\r\n const parts = hotkey.toLowerCase().split('+').map(part => part.trim())\r\n const key = parts.pop()\r\n if (!key) return false\r\n const mac = isMac()\r\n const want = {\r\n meta: parts.includes('meta') || (mac && parts.includes('mod')),\r\n ctrl: parts.includes('ctrl') || (!mac && parts.includes('mod')),\r\n alt: parts.includes('alt'),\r\n shift: parts.includes('shift'),\r\n }\r\n if (event.metaKey !== want.meta || event.ctrlKey !== want.ctrl || event.altKey !== want.alt || event.shiftKey !== want.shift)\r\n return false\r\n // `code` survives keyboard layouts and Alt/Option remapping.\r\n const code = event.code.toLowerCase()\r\n return event.key.toLowerCase() === key || code === `key${key}` || code === `digit${key}`\r\n}\r\n\r\n/** Human-readable parts of a shortcut, e.g. `mod+k` → `['⌘', 'K']`. */\r\nexport function formatHotkey(hotkey: string): string[] {\r\n const mac = isMac()\r\n const labels: Record<string, string> = mac\r\n ? { mod: '⌘', meta: '⌘', ctrl: '⌃', alt: '⌥', shift: '⇧', enter: '↵', escape: 'Esc' }\r\n : { mod: 'Ctrl', meta: 'Win', ctrl: 'Ctrl', alt: 'Alt', shift: 'Shift', enter: 'Enter', escape: 'Esc' }\r\n return hotkey.split('+').map((part) => {\r\n const lower = part.trim().toLowerCase()\r\n return labels[lower] ?? (lower.length === 1 ? lower.toUpperCase() : part.trim())\r\n })\r\n}\r\n\r\n/** Run `handler` whenever the shortcut is pressed anywhere on the page. */\r\nexport function useHotkey(hotkey: MaybeRefOrGetter<string | false | undefined>, handler: (event: KeyboardEvent) => void) {\r\n function onKeydown(event: KeyboardEvent) {\r\n const value = toValue(hotkey)\r\n if (!value || event.repeat || event.isComposing) return\r\n if (!matchesHotkey(event, value)) return\r\n event.preventDefault()\r\n handler(event)\r\n }\r\n onMounted(() => document.addEventListener('keydown', onKeydown))\r\n onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))\r\n}\r\n","import type { Ref } from 'vue'\r\nimport { onBeforeUnmount, watch } from 'vue'\r\nimport { isClient } from '../utils/dom'\r\n\r\nexport interface LayerOptions {\r\n /** The layer is registered only while this is true. */\r\n active: Ref<boolean>\r\n /** Elements that count as \"inside\" the layer. */\r\n elements: () => Array<HTMLElement | null | undefined>\r\n onEscape?: (event: KeyboardEvent) => void\r\n /** Called when a pointer goes down outside every element of the top-most layer. */\r\n onPointerDownOutside?: (event: PointerEvent) => void\r\n}\r\n\r\ninterface Layer {\r\n options: LayerOptions\r\n}\r\n\r\n/*\r\n * Overlays stack. A select inside a dialog, a submenu inside a menu, a tooltip\r\n * inside anything: Escape and outside clicks belong to whichever layer is on\r\n * top, and only to that one.\r\n */\r\nconst layers: Layer[] = []\r\nlet listening = false\r\n\r\nfunction top() {\r\n return layers[layers.length - 1]\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key !== 'Escape' || event.defaultPrevented || event.isComposing) return\r\n const layer = top()\r\n if (!layer?.options.onEscape) return\r\n event.preventDefault()\r\n event.stopPropagation()\r\n layer.options.onEscape(event)\r\n}\r\n\r\nfunction onPointerDown(event: PointerEvent) {\r\n const layer = top()\r\n if (!layer?.options.onPointerDownOutside) return\r\n const target = event.target as Node | null\r\n if (!target) return\r\n if ((target as HTMLElement).closest?.('[data-juxt-layer-ignore]')) return\r\n const inside = layer.options.elements().some(el => el?.contains(target))\r\n if (!inside) layer.options.onPointerDownOutside(event)\r\n}\r\n\r\nfunction listen() {\r\n if (listening || !isClient) return\r\n document.addEventListener('keydown', onKeydown)\r\n document.addEventListener('pointerdown', onPointerDown, true)\r\n listening = true\r\n}\r\n\r\nfunction unlisten() {\r\n if (!listening || layers.length > 0) return\r\n document.removeEventListener('keydown', onKeydown)\r\n document.removeEventListener('pointerdown', onPointerDown, true)\r\n listening = false\r\n}\r\n\r\nexport function useLayer(options: LayerOptions) {\r\n const layer: Layer = { options }\r\n\r\n const add = () => {\r\n if (layers.includes(layer)) return\r\n layers.push(layer)\r\n listen()\r\n }\r\n\r\n const remove = () => {\r\n const index = layers.indexOf(layer)\r\n if (index === -1) return\r\n layers.splice(index, 1)\r\n unlisten()\r\n }\r\n\r\n if (isClient) {\r\n watch(options.active, value => (value ? add() : remove()), { immediate: true })\r\n onBeforeUnmount(remove)\r\n }\r\n\r\n return {\r\n isTopLayer: () => top() === layer,\r\n }\r\n}\r\n","import type { Ref } from 'vue'\r\nimport { onBeforeUnmount, watch } from 'vue'\r\nimport { isClient } from '../utils/dom'\r\n\r\nlet locks = 0\r\nlet saved: { overflow: string, paddingRight: string } | null = null\r\n\r\nfunction lock() {\r\n locks++\r\n if (locks > 1) return\r\n const body = document.body\r\n const scrollbar = window.innerWidth - document.documentElement.clientWidth\r\n saved = { overflow: body.style.overflow, paddingRight: body.style.paddingRight }\r\n body.style.overflow = 'hidden'\r\n if (scrollbar > 0) {\r\n // Compensate for the vanished scrollbar so the page doesn't jump sideways.\r\n const current = Number.parseFloat(getComputedStyle(body).paddingRight) || 0\r\n body.style.paddingRight = `${current + scrollbar}px`\r\n }\r\n document.documentElement.style.setProperty('--juxt-scrollbar-width', `${scrollbar}px`)\r\n document.documentElement.dataset.juxtScrollLocked = ''\r\n}\r\n\r\nfunction unlock() {\r\n locks = Math.max(0, locks - 1)\r\n if (locks > 0 || !saved) return\r\n document.body.style.overflow = saved.overflow\r\n document.body.style.paddingRight = saved.paddingRight\r\n document.documentElement.style.removeProperty('--juxt-scrollbar-width')\r\n delete document.documentElement.dataset.juxtScrollLocked\r\n saved = null\r\n}\r\n\r\n/** Prevent the page behind an overlay from scrolling while `active` is true. */\r\nexport function useScrollLock(active: Ref<boolean>) {\r\n if (!isClient) return\r\n let held = false\r\n\r\n const set = (value: boolean) => {\r\n if (value === held) return\r\n held = value\r\n if (value) lock()\r\n else unlock()\r\n }\r\n\r\n watch(active, set, { immediate: true })\r\n onBeforeUnmount(() => set(false))\r\n}\r\n","/**\r\n * Ranking for the command palette. Cheap enough to run on every keystroke\r\n * over a few thousand items, and ordered the way people expect:\r\n * exact › prefix › word start › substring › in-order letters.\r\n */\r\n\r\nexport type MatchRange = [start: number, end: number]\r\n\r\nexport interface MatchResult {\r\n score: number\r\n ranges: MatchRange[]\r\n}\r\n\r\nconst NO_MATCH: MatchResult = { score: 0, ranges: [] }\r\n\r\nexport function matchText(query: string, text: string): MatchResult {\r\n const q = query.trim().toLowerCase()\r\n if (!q) return { score: 1, ranges: [] }\r\n const t = text.toLowerCase()\r\n if (!t) return NO_MATCH\r\n\r\n if (t === q) return { score: 1000, ranges: [[0, t.length]] }\r\n if (t.startsWith(q)) return { score: 900 - Math.min(t.length - q.length, 100), ranges: [[0, q.length]] }\r\n\r\n const wordStart = findWordStart(t, q)\r\n if (wordStart > 0) return { score: 700 - Math.min(wordStart, 100), ranges: [[wordStart, wordStart + q.length]] }\r\n\r\n const index = t.indexOf(q)\r\n if (index > 0) return { score: 500 - Math.min(index, 100), ranges: [[index, index + q.length]] }\r\n\r\n return subsequence(q, t)\r\n}\r\n\r\nfunction findWordStart(t: string, q: string): number {\r\n let from = 0\r\n while (from < t.length) {\r\n const index = t.indexOf(q, from)\r\n if (index === -1) return -1\r\n if (index > 0 && /[\\s\\-_/.:]/.test(t[index - 1]!)) return index\r\n from = index + 1\r\n }\r\n return -1\r\n}\r\n\r\n/** Letters in order, not necessarily adjacent | \"gtst\" finds \"Go to settings\". */\r\nfunction subsequence(q: string, t: string): MatchResult {\r\n const ranges: MatchRange[] = []\r\n let ti = 0\r\n let score = 100\r\n let previous = -2\r\n for (const char of q) {\r\n if (char === ' ') continue\r\n const found = t.indexOf(char, ti)\r\n if (found === -1) return NO_MATCH\r\n if (found === previous + 1 && ranges.length) {\r\n ranges[ranges.length - 1]![1] = found + 1\r\n score += 12\r\n } else {\r\n ranges.push([found, found + 1])\r\n if (found === 0 || /[\\s\\-_/.:]/.test(t[found - 1]!)) score += 8\r\n }\r\n // Spread-out matches rank lower.\r\n score -= Math.min(found - ti, 10)\r\n previous = found\r\n ti = found + 1\r\n }\r\n return { score: Math.max(score, 1), ranges }\r\n}\r\n\r\n/** Split text into highlighted and plain runs. */\r\nexport function splitByRanges(text: string, ranges: MatchRange[]): Array<{ text: string, match: boolean }> {\r\n if (!ranges.length) return [{ text, match: false }]\r\n const parts: Array<{ text: string, match: boolean }> = []\r\n let cursor = 0\r\n for (const [start, end] of ranges) {\r\n if (start > cursor) parts.push({ text: text.slice(cursor, start), match: false })\r\n parts.push({ text: text.slice(start, end), match: true })\r\n cursor = end\r\n }\r\n if (cursor < text.length) parts.push({ text: text.slice(cursor), match: false })\r\n return parts\r\n}\r\n","<script setup lang=\"ts\">\r\n/**\r\n * Internal: teleports its content to `<body>`, but only once mounted.\r\n *\r\n * Server-rendered teleports to `body` never make it into the HTML, so a plain\r\n * `<Teleport>` hydrates against nothing and warns. Rendering nothing until\r\n * mount keeps server and client output identical | and overlays are closed on\r\n * first paint anyway.\r\n */\r\nimport { onMounted, ref } from 'vue'\r\n\r\nwithDefaults(defineProps<{ to?: string }>(), { to: 'body' })\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\nconst mounted = ref(false)\r\nonMounted(() => (mounted.value = true))\r\n</script>\r\n\r\n<template>\r\n <Teleport v-if=\"mounted\" :to=\"to\">\r\n <slot />\r\n </Teleport>\r\n</template>\r\n","<script setup lang=\"ts\">\r\n/**\r\n * Internal: teleports its content to `<body>`, but only once mounted.\r\n *\r\n * Server-rendered teleports to `body` never make it into the HTML, so a plain\r\n * `<Teleport>` hydrates against nothing and warns. Rendering nothing until\r\n * mount keeps server and client output identical | and overlays are closed on\r\n * first paint anyway.\r\n */\r\nimport { onMounted, ref } from 'vue'\r\n\r\nwithDefaults(defineProps<{ to?: string }>(), { to: 'body' })\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\nconst mounted = ref(false)\r\nonMounted(() => (mounted.value = true))\r\n</script>\r\n\r\n<template>\r\n <Teleport v-if=\"mounted\" :to=\"to\">\r\n <slot />\r\n </Teleport>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\nimport type { MatchRange } from './search'\r\nimport { computed, nextTick, onMounted, ref, useId, watch } from 'vue'\r\nimport { useFocusTrap } from '../../composables/useFocusTrap'\r\nimport { formatHotkey, useHotkey } from '../../composables/useHotkey'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { useScrollLock } from '../../composables/useScrollLock'\r\nimport { IconSearch, Spinner } from '../../icons'\r\nimport { matchText, splitByRanges } from './search'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface CommandItem {\r\n id: string\r\n label: string\r\n description?: string\r\n /** An icon component, rendered at 16px. */\r\n icon?: Component\r\n /** Shortcut to display, in hotkey syntax: `mod+shift+p`. Display only. */\r\n shortcut?: string\r\n /** Extra terms that should find this item. */\r\n keywords?: string[]\r\n disabled?: boolean\r\n /** Called when the item is chosen, before `select` is emitted. */\r\n onSelect?: (item: CommandItem) => void\r\n}\r\n\r\nexport interface CommandGroup {\r\n id: string\r\n label?: string\r\n items: CommandItem[]\r\n}\r\n\r\nexport interface CommandPaletteProps {\r\n groups: CommandGroup[]\r\n /** Shown as a \"Recent\" group while the search is empty. */\r\n recent?: CommandItem[]\r\n placeholder?: string\r\n /** Show a loading indicator | for async results. */\r\n loading?: boolean\r\n /**\r\n * Built-in ranking and filtering. Set to `false` when you filter yourself\r\n * (e.g. server-side search driven by `v-model:search`).\r\n */\r\n filter?: boolean\r\n /** Global shortcut that toggles the palette. `false` to disable. */\r\n hotkey?: string | false\r\n /** Accessible name of the dialog. */\r\n label?: string\r\n emptyText?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<CommandPaletteProps>(), {\r\n placeholder: 'Type a command or search…',\r\n filter: true,\r\n hotkey: 'mod+k',\r\n label: 'Command palette',\r\n emptyText: 'No results',\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\nconst search = defineModel<string>('search', { default: '' })\r\n\r\nconst emit = defineEmits<{\r\n select: [item: CommandItem]\r\n}>()\r\n\r\ndefineSlots<{\r\n item?: (props: { item: CommandItem, active: boolean }) => unknown\r\n empty?: (props: { search: string }) => unknown\r\n footer?: () => unknown\r\n}>()\r\n\r\nconst id = `j-${useId()}`\r\nconst listboxId = `${id}-listbox`\r\n// Keys contain group and item ids; keep the DOM id selector-safe.\r\nconst optionId = (key: string) => `${id}-option-${key.replace(/[^\\w-]/g, '_')}`\r\n\r\n// --- Results ---------------------------------------------------------------\r\ninterface Row {\r\n key: string\r\n item: CommandItem\r\n ranges: MatchRange[]\r\n score: number\r\n}\r\n\r\ninterface Section {\r\n id: string\r\n label?: string\r\n rows: Row[]\r\n}\r\n\r\nconst sections = computed<Section[]>(() => {\r\n const query = search.value.trim()\r\n const toKey = (groupId: string, item: CommandItem) => `${groupId}:${item.id}`\r\n\r\n if (!query || !props.filter) {\r\n const base: Section[] = []\r\n if (!query && props.recent?.length)\r\n base.push({ id: 'recent', label: 'Recent', rows: props.recent.map(item => ({ key: toKey('recent', item), item, ranges: [], score: 1 })) })\r\n for (const group of props.groups) {\r\n if (group.items.length)\r\n base.push({ id: group.id, label: group.label, rows: group.items.map(item => ({ key: toKey(group.id, item), item, ranges: [], score: 1 })) })\r\n }\r\n return base\r\n }\r\n\r\n const ranked: Section[] = []\r\n for (const group of props.groups) {\r\n const rows: Row[] = []\r\n for (const item of group.items) {\r\n const label = matchText(query, item.label)\r\n let score = label.score\r\n if (item.keywords) {\r\n for (const keyword of item.keywords) score = Math.max(score, matchText(query, keyword).score * 0.9)\r\n }\r\n if (item.description) score = Math.max(score, matchText(query, item.description).score * 0.5)\r\n if (score > 0) rows.push({ key: toKey(group.id, item), item, ranges: label.ranges, score })\r\n }\r\n if (rows.length) {\r\n rows.sort((a, b) => b.score - a.score)\r\n ranked.push({ id: group.id, label: group.label, rows })\r\n }\r\n }\r\n // The group holding the best match comes first.\r\n return ranked.sort((a, b) => b.rows[0]!.score - a.rows[0]!.score)\r\n})\r\n\r\nconst rows = computed(() => sections.value.flatMap(section => section.rows))\r\nconst rowIndex = computed(() => new Map(rows.value.map((row, index) => [row.key, index])))\r\n\r\nconst activeIndex = ref(0)\r\nconst activeRow = computed(() => rows.value[activeIndex.value])\r\n\r\nfunction firstEnabled(from = 0, delta: 1 | -1 = 1): number {\r\n const count = rows.value.length\r\n for (let i = 0; i < count; i++) {\r\n const index = (from + i * delta + count * 2) % count\r\n if (!rows.value[index]!.item.disabled) return index\r\n }\r\n return -1\r\n}\r\n\r\n// Every keystroke puts the best match under the cursor.\r\nwatch(rows, () => (activeIndex.value = Math.max(0, firstEnabled())))\r\n\r\n// --- Opening & closing -----------------------------------------------------\r\nconst panel = ref<HTMLElement | null>(null)\r\nconst input = ref<HTMLInputElement | null>(null)\r\nconst list = ref<HTMLElement | null>(null)\r\n\r\nuseHotkey(() => props.hotkey, () => (open.value = !open.value))\r\nuseLayer({ active: open, elements: () => [panel.value], onEscape: () => close() })\r\nuseFocusTrap(panel, open, { initialFocus: () => input.value })\r\nuseScrollLock(open)\r\n\r\nwatch(open, (value) => {\r\n if (!value) return\r\n search.value = ''\r\n activeIndex.value = Math.max(0, firstEnabled())\r\n nextTick(() => list.value?.scrollTo({ top: 0 }))\r\n})\r\n\r\nfunction close() {\r\n open.value = false\r\n}\r\n\r\nlet pressedOutside = false\r\nfunction onPointerDown(event: PointerEvent) {\r\n pressedOutside = event.target === event.currentTarget\r\n}\r\nfunction onClick(event: MouseEvent) {\r\n if (pressedOutside && event.target === event.currentTarget) close()\r\n pressedOutside = false\r\n}\r\n\r\n// --- Selection & navigation ------------------------------------------------\r\nfunction choose(row: Row | undefined) {\r\n if (!row || row.item.disabled) return\r\n row.item.onSelect?.(row.item)\r\n emit('select', row.item)\r\n close()\r\n}\r\n\r\nfunction setActive(index: number) {\r\n if (index < 0) return\r\n activeIndex.value = index\r\n const row = rows.value[index]\r\n if (!row) return\r\n nextTick(() => {\r\n const el = document.getElementById(optionId(row.key))\r\n // Keep a little context around the active row at the list edges.\r\n if (index === 0) list.value?.scrollTo({ top: 0 })\r\n else el?.scrollIntoView({ block: 'nearest' })\r\n })\r\n}\r\n\r\nfunction move(delta: 1 | -1, distance = 1) {\r\n const count = rows.value.length\r\n if (!count) return\r\n let index = activeIndex.value\r\n for (let step = 0; step < distance; step++) {\r\n const next = firstEnabled((index + delta + count) % count, delta)\r\n if (next === -1) return\r\n // Page jumps stop at the ends instead of wrapping.\r\n if (distance > 1 && ((delta === 1 && next < index) || (delta === -1 && next > index))) break\r\n index = next\r\n }\r\n setActive(index)\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.isComposing) return\r\n // Emacs-style Ctrl+N / Ctrl+P for people who never leave the home row.\r\n const ctrl = event.ctrlKey && !event.metaKey && !event.altKey\r\n const key = event.key\r\n if (key === 'ArrowDown' || (ctrl && key === 'n')) move(1)\r\n else if (key === 'ArrowUp' || (ctrl && key === 'p')) move(-1)\r\n else if (key === 'PageDown') move(1, 5)\r\n else if (key === 'PageUp') move(-1, 5)\r\n else if (key === 'Enter') choose(activeRow.value)\r\n else return\r\n event.preventDefault()\r\n}\r\n\r\n// Hover follows the pointer only when it moves | scrolling with the keyboard\r\n// under a resting cursor must not steal the selection.\r\nfunction onRowPointerMove(index: number) {\r\n if (activeIndex.value !== index && !rows.value[index]?.item.disabled) activeIndex.value = index\r\n}\r\n\r\n// --- Announcements ---------------------------------------------------------\r\nconst announcement = ref('')\r\nlet announceTimer: ReturnType<typeof setTimeout> | undefined\r\nwatch([rows, () => props.loading], () => {\r\n clearTimeout(announceTimer)\r\n announceTimer = setTimeout(() => {\r\n if (!open.value) return\r\n if (props.loading) announcement.value = 'Loading results'\r\n else announcement.value = rows.value.length === 1 ? '1 result' : `${rows.value.length} results`\r\n }, 350)\r\n})\r\n\r\n// Shortcut glyphs depend on the platform | resolve them on the client only.\r\nconst mounted = ref(false)\r\nonMounted(() => (mounted.value = true))\r\nconst keys = (hotkey: string) => (mounted.value ? formatHotkey(hotkey) : [])\r\n\r\ndefineExpose({ close, focus: () => input.value?.focus() })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-command-overlay\">\r\n <div v-if=\"open\" class=\"j-command__overlay\" aria-hidden=\"true\" />\r\n </Transition>\r\n <Transition name=\"j-command\" :duration=\"{ enter: 180, leave: 110 }\">\r\n <div v-if=\"open\" class=\"j-command__positioner\" @pointerdown=\"onPointerDown\" @click=\"onClick\">\r\n <div\r\n ref=\"panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n :aria-label=\"label\"\r\n class=\"j-command\"\r\n >\r\n <div class=\"j-command__search\">\r\n <IconSearch class=\"j-command__search-icon\" />\r\n <input\r\n ref=\"input\"\r\n v-model=\"search\"\r\n class=\"j-command__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n aria-expanded=\"true\"\r\n aria-autocomplete=\"list\"\r\n :aria-controls=\"listboxId\"\r\n :aria-activedescendant=\"activeRow ? optionId(activeRow.key) : undefined\"\r\n :aria-label=\"label\"\r\n :placeholder=\"placeholder\"\r\n autocomplete=\"off\"\r\n autocorrect=\"off\"\r\n autocapitalize=\"off\"\r\n spellcheck=\"false\"\r\n enterkeyhint=\"go\"\r\n @keydown=\"onKeydown\"\r\n />\r\n <button type=\"button\" class=\"j-command__esc j-kbd\" tabindex=\"-1\" aria-label=\"Close\" @click=\"close\">\r\n Esc\r\n </button>\r\n <span v-if=\"loading\" class=\"j-command__progress\" aria-hidden=\"true\" />\r\n </div>\r\n\r\n <div\r\n :id=\"listboxId\"\r\n ref=\"list\"\r\n role=\"listbox\"\r\n :aria-label=\"label\"\r\n class=\"j-command__list\"\r\n @pointerdown.prevent\r\n >\r\n <div\r\n v-for=\"section in sections\"\r\n :key=\"section.id\"\r\n role=\"group\"\r\n class=\"j-command__group\"\r\n :aria-labelledby=\"section.label ? `${id}-group-${section.id}` : undefined\"\r\n >\r\n <div v-if=\"section.label\" :id=\"`${id}-group-${section.id}`\" class=\"j-command__group-label\" role=\"presentation\">\r\n {{ section.label }}\r\n </div>\r\n <div\r\n v-for=\"row in section.rows\"\r\n :id=\"optionId(row.key)\"\r\n :key=\"row.key\"\r\n role=\"option\"\r\n class=\"j-command__item\"\r\n :class=\"{ 'is-active': row.key === activeRow?.key }\"\r\n :aria-selected=\"row.key === activeRow?.key\"\r\n :aria-disabled=\"row.item.disabled || undefined\"\r\n @pointermove=\"onRowPointerMove(rowIndex.get(row.key)!)\"\r\n @click=\"choose(row)\"\r\n >\r\n <slot name=\"item\" :item=\"row.item\" :active=\"row.key === activeRow?.key\">\r\n <span v-if=\"row.item.icon\" class=\"j-command__item-icon\">\r\n <component :is=\"row.item.icon\" />\r\n </span>\r\n <span class=\"j-command__item-text\">\r\n <span class=\"j-command__item-label\" :class=\"{ 'is-searching': row.ranges.length }\">\r\n <template v-for=\"(part, i) in splitByRanges(row.item.label, row.ranges)\" :key=\"i\">\r\n <mark v-if=\"part.match\">{{ part.text }}</mark>\r\n <template v-else>{{ part.text }}</template>\r\n </template>\r\n </span>\r\n <span v-if=\"row.item.description\" class=\"j-command__item-description\">{{ row.item.description }}</span>\r\n </span>\r\n <span v-if=\"row.item.shortcut\" class=\"j-command__item-shortcut\" aria-hidden=\"true\">\r\n <kbd v-for=\"key in keys(row.item.shortcut)\" :key=\"key\" class=\"j-kbd\">{{ key }}</kbd>\r\n </span>\r\n </slot>\r\n </div>\r\n </div>\r\n\r\n <div v-if=\"!rows.length\" class=\"j-command__empty\" role=\"presentation\">\r\n <template v-if=\"loading\">\r\n <Spinner :size=\"16\" />\r\n <span>Searching…</span>\r\n </template>\r\n <slot v-else name=\"empty\" :search=\"search\">\r\n <span class=\"j-command__empty-title\">{{ emptyText }}</span>\r\n <span v-if=\"search\" class=\"j-command__empty-hint\">Nothing matches “{{ search }}”. Try fewer words.</span>\r\n </slot>\r\n </div>\r\n </div>\r\n\r\n <div class=\"j-command__footer\">\r\n <slot name=\"footer\">\r\n <span class=\"j-command__hint\"><kbd class=\"j-kbd\">↑</kbd><kbd class=\"j-kbd\">↓</kbd> Navigate</span>\r\n <span class=\"j-command__hint\"><kbd class=\"j-kbd\">↵</kbd> Select</span>\r\n <span class=\"j-command__hint\"><kbd class=\"j-kbd\">Esc</kbd> Close</span>\r\n </slot>\r\n </div>\r\n\r\n <span class=\"j-sr-only\" aria-live=\"polite\" aria-atomic=\"true\">{{ announcement }}</span>\r\n </div>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-command__overlay {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-overlay);\r\n background: var(--juxt-overlay);\r\n}\r\n\r\n.j-command__positioner {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-modal);\r\n display: flex;\r\n align-items: flex-start;\r\n justify-content: center;\r\n padding: max(12vh, var(--juxt-space-4)) var(--juxt-space-4) var(--juxt-space-4);\r\n}\r\n\r\n.j-command {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n max-width: 40rem;\r\n max-height: min(34rem, calc(100dvh - 12vh - var(--juxt-space-8)));\r\n overflow: hidden;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-lg);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n/* Search */\r\n.j-command__search {\r\n position: relative;\r\n display: flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-3);\r\n height: 3.25rem;\r\n padding: 0 var(--juxt-space-3) 0 var(--juxt-space-4);\r\n border-bottom: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-command__search,\r\n[data-theme='dark'] .j-command__search {\r\n border-bottom-color: var(--juxt-border);\r\n}\r\n\r\n.j-command__search-icon {\r\n flex: none;\r\n width: 1.125rem;\r\n height: 1.125rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-command__input {\r\n flex: 1;\r\n min-width: 0;\r\n height: 100%;\r\n padding: 0;\r\n border: 0;\r\n outline: none;\r\n background: transparent;\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-lg);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n caret-color: var(--juxt-accent);\r\n}\r\n\r\n.j-command__input::placeholder {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-command__esc {\r\n height: 1.375rem;\r\n padding: 0 var(--juxt-space-1-5);\r\n cursor: pointer;\r\n}\r\n\r\n.j-command__esc:hover {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n/* An indeterminate hairline: present, never loud. */\r\n.j-command__progress {\r\n position: absolute;\r\n right: 0;\r\n bottom: -1px;\r\n left: 0;\r\n height: 1px;\r\n overflow: hidden;\r\n}\r\n\r\n.j-command__progress::after {\r\n content: '';\r\n position: absolute;\r\n inset: 0;\r\n width: 40%;\r\n background: linear-gradient(90deg, transparent, var(--juxt-accent), transparent);\r\n animation: j-command-progress 1.1s var(--juxt-ease-standard) infinite;\r\n}\r\n\r\n@keyframes j-command-progress {\r\n from {\r\n transform: translateX(-100%);\r\n }\r\n\r\n to {\r\n transform: translateX(250%);\r\n }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-command__progress::after {\r\n width: 100%;\r\n animation: j-command-pulse 1.4s ease-in-out infinite;\r\n }\r\n\r\n @keyframes j-command-pulse {\r\n 50% {\r\n opacity: 0.3;\r\n }\r\n }\r\n}\r\n\r\n/* List */\r\n.j-command__list {\r\n flex: 1;\r\n min-height: 0;\r\n padding: var(--juxt-space-1-5);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n scroll-padding-block: var(--juxt-space-8) var(--juxt-space-1-5);\r\n}\r\n\r\n.j-command__group + .j-command__group {\r\n margin-top: var(--juxt-space-1);\r\n}\r\n\r\n.j-command__group-label {\r\n padding: var(--juxt-space-2) var(--juxt-space-2-5) var(--juxt-space-1-5);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n user-select: none;\r\n}\r\n\r\n.j-command__item {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2-5);\r\n height: 2.5rem;\r\n padding: 0 var(--juxt-space-2-5);\r\n border-radius: var(--juxt-radius-sm);\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n/* The one place the palette uses green: a hairline marking where you are. */\r\n.j-command__item::before {\r\n content: '';\r\n position: absolute;\r\n top: 0.625rem;\r\n bottom: 0.625rem;\r\n left: 0;\r\n width: 2px;\r\n border-radius: 0 2px 2px 0;\r\n background: var(--juxt-accent);\r\n opacity: 0;\r\n transform: scaleY(0.4);\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-command__item.is-active {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-command__item.is-active::before {\r\n opacity: 1;\r\n transform: none;\r\n}\r\n\r\n.j-command__item[aria-disabled='true'] {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-command__item-icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 1.25rem;\r\n height: 1.25rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-command__item-icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-command__item.is-active .j-command__item-icon {\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-command__item-text {\r\n display: flex;\r\n flex: 1;\r\n align-items: baseline;\r\n gap: var(--juxt-space-2);\r\n min-width: 0;\r\n overflow: hidden;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-command__item-label {\r\n flex: none;\r\n max-width: 100%;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.j-command__item-label.is-searching {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-command__item-label mark {\r\n background: none;\r\n color: var(--juxt-fg);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n\r\n.j-command__item-description {\r\n overflow: hidden;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.j-command__item-shortcut {\r\n display: inline-flex;\r\n flex: none;\r\n gap: 3px;\r\n}\r\n\r\n.j-command__empty {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: var(--juxt-space-1-5);\r\n min-height: 8rem;\r\n padding: var(--juxt-space-6) var(--juxt-space-4);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n text-align: center;\r\n}\r\n\r\n.j-command__empty-title {\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n\r\n/* Footer */\r\n.j-command__footer {\r\n display: flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-4);\r\n height: 2.25rem;\r\n padding: 0 var(--juxt-space-4);\r\n border-top: 1px solid var(--juxt-border-subtle);\r\n background: var(--juxt-surface-sunken);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n}\r\n\r\n.dark .j-command__footer,\r\n[data-theme='dark'] .j-command__footer {\r\n border-top-color: var(--juxt-border);\r\n background: var(--juxt-surface);\r\n}\r\n\r\n.j-command__hint {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1);\r\n}\r\n\r\n.j-command__hint .j-kbd + .j-kbd {\r\n margin-left: -1px;\r\n}\r\n\r\n/* Motion: quick in, quicker out. */\r\n.j-command-overlay-enter-active {\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-command-overlay-leave-active {\r\n transition: opacity var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-command-overlay-enter-from,\r\n.j-command-overlay-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-command-enter-active .j-command {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-command-leave-active .j-command {\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-in),\r\n transform var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-command-enter-from .j-command,\r\n.j-command-leave-to .j-command {\r\n opacity: 0;\r\n transform: translateY(calc(var(--juxt-motion-shift) * -1)) scale(0.985);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-command-enter-from .j-command,\r\n .j-command-leave-to .j-command {\r\n transform: none;\r\n }\r\n}\r\n\r\n@media (max-width: 639px) {\r\n .j-command__positioner {\r\n padding: var(--juxt-space-2);\r\n }\r\n\r\n .j-command {\r\n max-height: calc(100dvh - var(--juxt-space-4));\r\n }\r\n\r\n .j-command__footer {\r\n display: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\nimport type { MatchRange } from './search'\r\nimport { computed, nextTick, onMounted, ref, useId, watch } from 'vue'\r\nimport { useFocusTrap } from '../../composables/useFocusTrap'\r\nimport { formatHotkey, useHotkey } from '../../composables/useHotkey'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { useScrollLock } from '../../composables/useScrollLock'\r\nimport { IconSearch, Spinner } from '../../icons'\r\nimport { matchText, splitByRanges } from './search'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface CommandItem {\r\n id: string\r\n label: string\r\n description?: string\r\n /** An icon component, rendered at 16px. */\r\n icon?: Component\r\n /** Shortcut to display, in hotkey syntax: `mod+shift+p`. Display only. */\r\n shortcut?: string\r\n /** Extra terms that should find this item. */\r\n keywords?: string[]\r\n disabled?: boolean\r\n /** Called when the item is chosen, before `select` is emitted. */\r\n onSelect?: (item: CommandItem) => void\r\n}\r\n\r\nexport interface CommandGroup {\r\n id: string\r\n label?: string\r\n items: CommandItem[]\r\n}\r\n\r\nexport interface CommandPaletteProps {\r\n groups: CommandGroup[]\r\n /** Shown as a \"Recent\" group while the search is empty. */\r\n recent?: CommandItem[]\r\n placeholder?: string\r\n /** Show a loading indicator | for async results. */\r\n loading?: boolean\r\n /**\r\n * Built-in ranking and filtering. Set to `false` when you filter yourself\r\n * (e.g. server-side search driven by `v-model:search`).\r\n */\r\n filter?: boolean\r\n /** Global shortcut that toggles the palette. `false` to disable. */\r\n hotkey?: string | false\r\n /** Accessible name of the dialog. */\r\n label?: string\r\n emptyText?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<CommandPaletteProps>(), {\r\n placeholder: 'Type a command or search…',\r\n filter: true,\r\n hotkey: 'mod+k',\r\n label: 'Command palette',\r\n emptyText: 'No results',\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\nconst search = defineModel<string>('search', { default: '' })\r\n\r\nconst emit = defineEmits<{\r\n select: [item: CommandItem]\r\n}>()\r\n\r\ndefineSlots<{\r\n item?: (props: { item: CommandItem, active: boolean }) => unknown\r\n empty?: (props: { search: string }) => unknown\r\n footer?: () => unknown\r\n}>()\r\n\r\nconst id = `j-${useId()}`\r\nconst listboxId = `${id}-listbox`\r\n// Keys contain group and item ids; keep the DOM id selector-safe.\r\nconst optionId = (key: string) => `${id}-option-${key.replace(/[^\\w-]/g, '_')}`\r\n\r\n// --- Results ---------------------------------------------------------------\r\ninterface Row {\r\n key: string\r\n item: CommandItem\r\n ranges: MatchRange[]\r\n score: number\r\n}\r\n\r\ninterface Section {\r\n id: string\r\n label?: string\r\n rows: Row[]\r\n}\r\n\r\nconst sections = computed<Section[]>(() => {\r\n const query = search.value.trim()\r\n const toKey = (groupId: string, item: CommandItem) => `${groupId}:${item.id}`\r\n\r\n if (!query || !props.filter) {\r\n const base: Section[] = []\r\n if (!query && props.recent?.length)\r\n base.push({ id: 'recent', label: 'Recent', rows: props.recent.map(item => ({ key: toKey('recent', item), item, ranges: [], score: 1 })) })\r\n for (const group of props.groups) {\r\n if (group.items.length)\r\n base.push({ id: group.id, label: group.label, rows: group.items.map(item => ({ key: toKey(group.id, item), item, ranges: [], score: 1 })) })\r\n }\r\n return base\r\n }\r\n\r\n const ranked: Section[] = []\r\n for (const group of props.groups) {\r\n const rows: Row[] = []\r\n for (const item of group.items) {\r\n const label = matchText(query, item.label)\r\n let score = label.score\r\n if (item.keywords) {\r\n for (const keyword of item.keywords) score = Math.max(score, matchText(query, keyword).score * 0.9)\r\n }\r\n if (item.description) score = Math.max(score, matchText(query, item.description).score * 0.5)\r\n if (score > 0) rows.push({ key: toKey(group.id, item), item, ranges: label.ranges, score })\r\n }\r\n if (rows.length) {\r\n rows.sort((a, b) => b.score - a.score)\r\n ranked.push({ id: group.id, label: group.label, rows })\r\n }\r\n }\r\n // The group holding the best match comes first.\r\n return ranked.sort((a, b) => b.rows[0]!.score - a.rows[0]!.score)\r\n})\r\n\r\nconst rows = computed(() => sections.value.flatMap(section => section.rows))\r\nconst rowIndex = computed(() => new Map(rows.value.map((row, index) => [row.key, index])))\r\n\r\nconst activeIndex = ref(0)\r\nconst activeRow = computed(() => rows.value[activeIndex.value])\r\n\r\nfunction firstEnabled(from = 0, delta: 1 | -1 = 1): number {\r\n const count = rows.value.length\r\n for (let i = 0; i < count; i++) {\r\n const index = (from + i * delta + count * 2) % count\r\n if (!rows.value[index]!.item.disabled) return index\r\n }\r\n return -1\r\n}\r\n\r\n// Every keystroke puts the best match under the cursor.\r\nwatch(rows, () => (activeIndex.value = Math.max(0, firstEnabled())))\r\n\r\n// --- Opening & closing -----------------------------------------------------\r\nconst panel = ref<HTMLElement | null>(null)\r\nconst input = ref<HTMLInputElement | null>(null)\r\nconst list = ref<HTMLElement | null>(null)\r\n\r\nuseHotkey(() => props.hotkey, () => (open.value = !open.value))\r\nuseLayer({ active: open, elements: () => [panel.value], onEscape: () => close() })\r\nuseFocusTrap(panel, open, { initialFocus: () => input.value })\r\nuseScrollLock(open)\r\n\r\nwatch(open, (value) => {\r\n if (!value) return\r\n search.value = ''\r\n activeIndex.value = Math.max(0, firstEnabled())\r\n nextTick(() => list.value?.scrollTo({ top: 0 }))\r\n})\r\n\r\nfunction close() {\r\n open.value = false\r\n}\r\n\r\nlet pressedOutside = false\r\nfunction onPointerDown(event: PointerEvent) {\r\n pressedOutside = event.target === event.currentTarget\r\n}\r\nfunction onClick(event: MouseEvent) {\r\n if (pressedOutside && event.target === event.currentTarget) close()\r\n pressedOutside = false\r\n}\r\n\r\n// --- Selection & navigation ------------------------------------------------\r\nfunction choose(row: Row | undefined) {\r\n if (!row || row.item.disabled) return\r\n row.item.onSelect?.(row.item)\r\n emit('select', row.item)\r\n close()\r\n}\r\n\r\nfunction setActive(index: number) {\r\n if (index < 0) return\r\n activeIndex.value = index\r\n const row = rows.value[index]\r\n if (!row) return\r\n nextTick(() => {\r\n const el = document.getElementById(optionId(row.key))\r\n // Keep a little context around the active row at the list edges.\r\n if (index === 0) list.value?.scrollTo({ top: 0 })\r\n else el?.scrollIntoView({ block: 'nearest' })\r\n })\r\n}\r\n\r\nfunction move(delta: 1 | -1, distance = 1) {\r\n const count = rows.value.length\r\n if (!count) return\r\n let index = activeIndex.value\r\n for (let step = 0; step < distance; step++) {\r\n const next = firstEnabled((index + delta + count) % count, delta)\r\n if (next === -1) return\r\n // Page jumps stop at the ends instead of wrapping.\r\n if (distance > 1 && ((delta === 1 && next < index) || (delta === -1 && next > index))) break\r\n index = next\r\n }\r\n setActive(index)\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.isComposing) return\r\n // Emacs-style Ctrl+N / Ctrl+P for people who never leave the home row.\r\n const ctrl = event.ctrlKey && !event.metaKey && !event.altKey\r\n const key = event.key\r\n if (key === 'ArrowDown' || (ctrl && key === 'n')) move(1)\r\n else if (key === 'ArrowUp' || (ctrl && key === 'p')) move(-1)\r\n else if (key === 'PageDown') move(1, 5)\r\n else if (key === 'PageUp') move(-1, 5)\r\n else if (key === 'Enter') choose(activeRow.value)\r\n else return\r\n event.preventDefault()\r\n}\r\n\r\n// Hover follows the pointer only when it moves | scrolling with the keyboard\r\n// under a resting cursor must not steal the selection.\r\nfunction onRowPointerMove(index: number) {\r\n if (activeIndex.value !== index && !rows.value[index]?.item.disabled) activeIndex.value = index\r\n}\r\n\r\n// --- Announcements ---------------------------------------------------------\r\nconst announcement = ref('')\r\nlet announceTimer: ReturnType<typeof setTimeout> | undefined\r\nwatch([rows, () => props.loading], () => {\r\n clearTimeout(announceTimer)\r\n announceTimer = setTimeout(() => {\r\n if (!open.value) return\r\n if (props.loading) announcement.value = 'Loading results'\r\n else announcement.value = rows.value.length === 1 ? '1 result' : `${rows.value.length} results`\r\n }, 350)\r\n})\r\n\r\n// Shortcut glyphs depend on the platform | resolve them on the client only.\r\nconst mounted = ref(false)\r\nonMounted(() => (mounted.value = true))\r\nconst keys = (hotkey: string) => (mounted.value ? formatHotkey(hotkey) : [])\r\n\r\ndefineExpose({ close, focus: () => input.value?.focus() })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-command-overlay\">\r\n <div v-if=\"open\" class=\"j-command__overlay\" aria-hidden=\"true\" />\r\n </Transition>\r\n <Transition name=\"j-command\" :duration=\"{ enter: 180, leave: 110 }\">\r\n <div v-if=\"open\" class=\"j-command__positioner\" @pointerdown=\"onPointerDown\" @click=\"onClick\">\r\n <div\r\n ref=\"panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n :aria-label=\"label\"\r\n class=\"j-command\"\r\n >\r\n <div class=\"j-command__search\">\r\n <IconSearch class=\"j-command__search-icon\" />\r\n <input\r\n ref=\"input\"\r\n v-model=\"search\"\r\n class=\"j-command__input\"\r\n type=\"text\"\r\n role=\"combobox\"\r\n aria-expanded=\"true\"\r\n aria-autocomplete=\"list\"\r\n :aria-controls=\"listboxId\"\r\n :aria-activedescendant=\"activeRow ? optionId(activeRow.key) : undefined\"\r\n :aria-label=\"label\"\r\n :placeholder=\"placeholder\"\r\n autocomplete=\"off\"\r\n autocorrect=\"off\"\r\n autocapitalize=\"off\"\r\n spellcheck=\"false\"\r\n enterkeyhint=\"go\"\r\n @keydown=\"onKeydown\"\r\n />\r\n <button type=\"button\" class=\"j-command__esc j-kbd\" tabindex=\"-1\" aria-label=\"Close\" @click=\"close\">\r\n Esc\r\n </button>\r\n <span v-if=\"loading\" class=\"j-command__progress\" aria-hidden=\"true\" />\r\n </div>\r\n\r\n <div\r\n :id=\"listboxId\"\r\n ref=\"list\"\r\n role=\"listbox\"\r\n :aria-label=\"label\"\r\n class=\"j-command__list\"\r\n @pointerdown.prevent\r\n >\r\n <div\r\n v-for=\"section in sections\"\r\n :key=\"section.id\"\r\n role=\"group\"\r\n class=\"j-command__group\"\r\n :aria-labelledby=\"section.label ? `${id}-group-${section.id}` : undefined\"\r\n >\r\n <div v-if=\"section.label\" :id=\"`${id}-group-${section.id}`\" class=\"j-command__group-label\" role=\"presentation\">\r\n {{ section.label }}\r\n </div>\r\n <div\r\n v-for=\"row in section.rows\"\r\n :id=\"optionId(row.key)\"\r\n :key=\"row.key\"\r\n role=\"option\"\r\n class=\"j-command__item\"\r\n :class=\"{ 'is-active': row.key === activeRow?.key }\"\r\n :aria-selected=\"row.key === activeRow?.key\"\r\n :aria-disabled=\"row.item.disabled || undefined\"\r\n @pointermove=\"onRowPointerMove(rowIndex.get(row.key)!)\"\r\n @click=\"choose(row)\"\r\n >\r\n <slot name=\"item\" :item=\"row.item\" :active=\"row.key === activeRow?.key\">\r\n <span v-if=\"row.item.icon\" class=\"j-command__item-icon\">\r\n <component :is=\"row.item.icon\" />\r\n </span>\r\n <span class=\"j-command__item-text\">\r\n <span class=\"j-command__item-label\" :class=\"{ 'is-searching': row.ranges.length }\">\r\n <template v-for=\"(part, i) in splitByRanges(row.item.label, row.ranges)\" :key=\"i\">\r\n <mark v-if=\"part.match\">{{ part.text }}</mark>\r\n <template v-else>{{ part.text }}</template>\r\n </template>\r\n </span>\r\n <span v-if=\"row.item.description\" class=\"j-command__item-description\">{{ row.item.description }}</span>\r\n </span>\r\n <span v-if=\"row.item.shortcut\" class=\"j-command__item-shortcut\" aria-hidden=\"true\">\r\n <kbd v-for=\"key in keys(row.item.shortcut)\" :key=\"key\" class=\"j-kbd\">{{ key }}</kbd>\r\n </span>\r\n </slot>\r\n </div>\r\n </div>\r\n\r\n <div v-if=\"!rows.length\" class=\"j-command__empty\" role=\"presentation\">\r\n <template v-if=\"loading\">\r\n <Spinner :size=\"16\" />\r\n <span>Searching…</span>\r\n </template>\r\n <slot v-else name=\"empty\" :search=\"search\">\r\n <span class=\"j-command__empty-title\">{{ emptyText }}</span>\r\n <span v-if=\"search\" class=\"j-command__empty-hint\">Nothing matches “{{ search }}”. Try fewer words.</span>\r\n </slot>\r\n </div>\r\n </div>\r\n\r\n <div class=\"j-command__footer\">\r\n <slot name=\"footer\">\r\n <span class=\"j-command__hint\"><kbd class=\"j-kbd\">↑</kbd><kbd class=\"j-kbd\">↓</kbd> Navigate</span>\r\n <span class=\"j-command__hint\"><kbd class=\"j-kbd\">↵</kbd> Select</span>\r\n <span class=\"j-command__hint\"><kbd class=\"j-kbd\">Esc</kbd> Close</span>\r\n </slot>\r\n </div>\r\n\r\n <span class=\"j-sr-only\" aria-live=\"polite\" aria-atomic=\"true\">{{ announcement }}</span>\r\n </div>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-command__overlay {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-overlay);\r\n background: var(--juxt-overlay);\r\n}\r\n\r\n.j-command__positioner {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-modal);\r\n display: flex;\r\n align-items: flex-start;\r\n justify-content: center;\r\n padding: max(12vh, var(--juxt-space-4)) var(--juxt-space-4) var(--juxt-space-4);\r\n}\r\n\r\n.j-command {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n max-width: 40rem;\r\n max-height: min(34rem, calc(100dvh - 12vh - var(--juxt-space-8)));\r\n overflow: hidden;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-lg);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n/* Search */\r\n.j-command__search {\r\n position: relative;\r\n display: flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-3);\r\n height: 3.25rem;\r\n padding: 0 var(--juxt-space-3) 0 var(--juxt-space-4);\r\n border-bottom: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-command__search,\r\n[data-theme='dark'] .j-command__search {\r\n border-bottom-color: var(--juxt-border);\r\n}\r\n\r\n.j-command__search-icon {\r\n flex: none;\r\n width: 1.125rem;\r\n height: 1.125rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-command__input {\r\n flex: 1;\r\n min-width: 0;\r\n height: 100%;\r\n padding: 0;\r\n border: 0;\r\n outline: none;\r\n background: transparent;\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-lg);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n caret-color: var(--juxt-accent);\r\n}\r\n\r\n.j-command__input::placeholder {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-command__esc {\r\n height: 1.375rem;\r\n padding: 0 var(--juxt-space-1-5);\r\n cursor: pointer;\r\n}\r\n\r\n.j-command__esc:hover {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n/* An indeterminate hairline: present, never loud. */\r\n.j-command__progress {\r\n position: absolute;\r\n right: 0;\r\n bottom: -1px;\r\n left: 0;\r\n height: 1px;\r\n overflow: hidden;\r\n}\r\n\r\n.j-command__progress::after {\r\n content: '';\r\n position: absolute;\r\n inset: 0;\r\n width: 40%;\r\n background: linear-gradient(90deg, transparent, var(--juxt-accent), transparent);\r\n animation: j-command-progress 1.1s var(--juxt-ease-standard) infinite;\r\n}\r\n\r\n@keyframes j-command-progress {\r\n from {\r\n transform: translateX(-100%);\r\n }\r\n\r\n to {\r\n transform: translateX(250%);\r\n }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-command__progress::after {\r\n width: 100%;\r\n animation: j-command-pulse 1.4s ease-in-out infinite;\r\n }\r\n\r\n @keyframes j-command-pulse {\r\n 50% {\r\n opacity: 0.3;\r\n }\r\n }\r\n}\r\n\r\n/* List */\r\n.j-command__list {\r\n flex: 1;\r\n min-height: 0;\r\n padding: var(--juxt-space-1-5);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n scroll-padding-block: var(--juxt-space-8) var(--juxt-space-1-5);\r\n}\r\n\r\n.j-command__group + .j-command__group {\r\n margin-top: var(--juxt-space-1);\r\n}\r\n\r\n.j-command__group-label {\r\n padding: var(--juxt-space-2) var(--juxt-space-2-5) var(--juxt-space-1-5);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n user-select: none;\r\n}\r\n\r\n.j-command__item {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2-5);\r\n height: 2.5rem;\r\n padding: 0 var(--juxt-space-2-5);\r\n border-radius: var(--juxt-radius-sm);\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n/* The one place the palette uses green: a hairline marking where you are. */\r\n.j-command__item::before {\r\n content: '';\r\n position: absolute;\r\n top: 0.625rem;\r\n bottom: 0.625rem;\r\n left: 0;\r\n width: 2px;\r\n border-radius: 0 2px 2px 0;\r\n background: var(--juxt-accent);\r\n opacity: 0;\r\n transform: scaleY(0.4);\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-command__item.is-active {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-command__item.is-active::before {\r\n opacity: 1;\r\n transform: none;\r\n}\r\n\r\n.j-command__item[aria-disabled='true'] {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-command__item-icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 1.25rem;\r\n height: 1.25rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-command__item-icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-command__item.is-active .j-command__item-icon {\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-command__item-text {\r\n display: flex;\r\n flex: 1;\r\n align-items: baseline;\r\n gap: var(--juxt-space-2);\r\n min-width: 0;\r\n overflow: hidden;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-command__item-label {\r\n flex: none;\r\n max-width: 100%;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.j-command__item-label.is-searching {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-command__item-label mark {\r\n background: none;\r\n color: var(--juxt-fg);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n\r\n.j-command__item-description {\r\n overflow: hidden;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.j-command__item-shortcut {\r\n display: inline-flex;\r\n flex: none;\r\n gap: 3px;\r\n}\r\n\r\n.j-command__empty {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: var(--juxt-space-1-5);\r\n min-height: 8rem;\r\n padding: var(--juxt-space-6) var(--juxt-space-4);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n text-align: center;\r\n}\r\n\r\n.j-command__empty-title {\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n\r\n/* Footer */\r\n.j-command__footer {\r\n display: flex;\r\n flex: none;\r\n align-items: center;\r\n gap: var(--juxt-space-4);\r\n height: 2.25rem;\r\n padding: 0 var(--juxt-space-4);\r\n border-top: 1px solid var(--juxt-border-subtle);\r\n background: var(--juxt-surface-sunken);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n}\r\n\r\n.dark .j-command__footer,\r\n[data-theme='dark'] .j-command__footer {\r\n border-top-color: var(--juxt-border);\r\n background: var(--juxt-surface);\r\n}\r\n\r\n.j-command__hint {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1);\r\n}\r\n\r\n.j-command__hint .j-kbd + .j-kbd {\r\n margin-left: -1px;\r\n}\r\n\r\n/* Motion: quick in, quicker out. */\r\n.j-command-overlay-enter-active {\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-command-overlay-leave-active {\r\n transition: opacity var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-command-overlay-enter-from,\r\n.j-command-overlay-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-command-enter-active .j-command {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-command-leave-active .j-command {\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-in),\r\n transform var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-command-enter-from .j-command,\r\n.j-command-leave-to .j-command {\r\n opacity: 0;\r\n transform: translateY(calc(var(--juxt-motion-shift) * -1)) scale(0.985);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-command-enter-from .j-command,\r\n .j-command-leave-to .j-command {\r\n transform: none;\r\n }\r\n}\r\n\r\n@media (max-width: 639px) {\r\n .j-command__positioner {\r\n padding: var(--juxt-space-2);\r\n }\r\n\r\n .j-command {\r\n max-height: calc(100dvh - var(--juxt-space-4));\r\n }\r\n\r\n .j-command__footer {\r\n display: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","import type { Ref } from 'vue'\r\nimport { ref } from 'vue'\r\nimport { prefersReducedMotion } from '../utils/dom'\r\nimport { useFocusTrap } from './useFocusTrap'\r\nimport { useLayer } from './useLayer'\r\nimport { useScrollLock } from './useScrollLock'\r\n\r\nexport interface ModalOptions {\r\n closeOnEscape: () => boolean\r\n closeOnOverlay: () => boolean\r\n close: () => void\r\n /** Selector for the element to focus first, inside the panel. */\r\n focusSelector?: string\r\n}\r\n\r\n/**\r\n * Internal: everything a modal surface (Dialog, Sheet) shares | layering,\r\n * focus trap and restoration, scroll lock, backdrop clicks and the \"nudge\"\r\n * when dismissal is refused.\r\n */\r\nexport function useModal(panel: Ref<HTMLElement | null>, open: Ref<boolean>, options: ModalOptions) {\r\n const nudging = ref(false)\r\n\r\n // A modal that refuses to close gives a small, polite nudge instead.\r\n function nudge() {\r\n if (prefersReducedMotion()) return\r\n nudging.value = false\r\n requestAnimationFrame(() => (nudging.value = true))\r\n }\r\n\r\n const { isTopLayer } = useLayer({\r\n active: open,\r\n elements: () => [panel.value],\r\n onEscape: () => (options.closeOnEscape() ? options.close() : nudge()),\r\n })\r\n\r\n useFocusTrap(panel, open, {\r\n // Prefer an explicit autofocus, then the first form field. Otherwise focus the\r\n // panel itself so screen readers announce it without jumping to a button.\r\n initialFocus: () =>\r\n panel.value?.querySelector<HTMLElement>('[autofocus], [data-autofocus]')\r\n ?? (options.focusSelector ? panel.value?.querySelector<HTMLElement>(options.focusSelector) : null)\r\n ?? panel.value,\r\n })\r\n\r\n useScrollLock(open)\r\n\r\n // Close on a click that both starts and ends on the backdrop, so dragging a\r\n // text selection out of the panel never dismisses it.\r\n let pressedOutside = false\r\n function onBackdropPointerDown(event: PointerEvent) {\r\n pressedOutside = event.target === event.currentTarget && isTopLayer()\r\n }\r\n function onBackdropClick(event: MouseEvent) {\r\n const outside = pressedOutside && event.target === event.currentTarget\r\n pressedOutside = false\r\n if (!outside) return\r\n if (options.closeOnOverlay()) options.close()\r\n else nudge()\r\n }\r\n\r\n return { nudging, onBackdropPointerDown, onBackdropClick }\r\n}\r\n\r\n/** Form fields worth focusing first in a modal body. */\r\nexport const FIELD_SELECTOR = ':is(input:not([type=hidden]), textarea, select, [role=combobox]):not(:disabled)'\r\n","<script setup lang=\"ts\">\r\nimport { ref, useId } from 'vue'\r\nimport { FIELD_SELECTOR, useModal } from '../../composables/useModal'\r\nimport { IconX } from '../../icons'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface DialogProps {\r\n title?: string\r\n description?: string\r\n size?: 'sm' | 'md' | 'lg'\r\n /** Close when the backdrop is clicked. Defaults to true. */\r\n closeOnOverlay?: boolean\r\n /** Close when Escape is pressed. Defaults to true. */\r\n closeOnEscape?: boolean\r\n /** Hide the close button in the corner. */\r\n hideClose?: boolean\r\n /** Accessible label when there is no visible title. */\r\n ariaLabel?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<DialogProps>(), {\r\n size: 'md',\r\n closeOnOverlay: true,\r\n closeOnEscape: true,\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\n\r\nconst emit = defineEmits<{\r\n /** Fired after the leave transition finishes | a good moment to reset form state. */\r\n afterClose: []\r\n}>()\r\n\r\ndefineSlots<{\r\n default?: (props: { close: () => void }) => unknown\r\n /** Custom header. Put `titleId` on your heading so the dialog stays labelled. */\r\n header?: (props: { titleId: string, descriptionId: string }) => unknown\r\n footer?: (props: { close: () => void }) => unknown\r\n}>()\r\n\r\nconst id = useId()\r\nconst titleId = `j-${id}-title`\r\nconst descriptionId = `j-${id}-description`\r\n\r\nconst panel = ref<HTMLElement | null>(null)\r\n\r\nfunction close() {\r\n open.value = false\r\n}\r\n\r\nconst { nudging, onBackdropPointerDown, onBackdropClick } = useModal(panel, open, {\r\n closeOnEscape: () => props.closeOnEscape,\r\n closeOnOverlay: () => props.closeOnOverlay,\r\n close,\r\n focusSelector: `.j-dialog__body ${FIELD_SELECTOR}`,\r\n})\r\n\r\ndefineExpose({ close })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-dialog-overlay\">\r\n <div v-if=\"open\" class=\"j-dialog__overlay\" aria-hidden=\"true\" />\r\n </Transition>\r\n <Transition name=\"j-dialog\" :duration=\"{ enter: 240, leave: 150 }\" @after-leave=\"emit('afterClose')\">\r\n <div v-if=\"open\" class=\"j-dialog__positioner\" @pointerdown=\"onBackdropPointerDown\" @click=\"onBackdropClick\">\r\n <div\r\n ref=\"panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n class=\"j-dialog__panel\"\r\n :class=\"[`j-dialog__panel--${size}`, { 'is-nudging': nudging }]\"\r\n :aria-labelledby=\"title || $slots.header ? titleId : undefined\"\r\n :aria-label=\"ariaLabel\"\r\n :aria-describedby=\"description ? descriptionId : undefined\"\r\n @animationend=\"nudging = false\"\r\n >\r\n <header v-if=\"title || description || $slots.header\" class=\"j-dialog__header\">\r\n <slot name=\"header\" :title-id=\"titleId\" :description-id=\"descriptionId\">\r\n <h2 :id=\"titleId\" class=\"j-dialog__title\">\r\n {{ title }}\r\n </h2>\r\n <p v-if=\"description\" :id=\"descriptionId\" class=\"j-dialog__description\">\r\n {{ description }}\r\n </p>\r\n </slot>\r\n </header>\r\n <div v-if=\"$slots.default\" class=\"j-dialog__body\">\r\n <slot :close=\"close\" />\r\n </div>\r\n <footer v-if=\"$slots.footer\" class=\"j-dialog__footer\">\r\n <slot name=\"footer\" :close=\"close\" />\r\n </footer>\r\n <button\r\n v-if=\"!hideClose\"\r\n type=\"button\"\r\n class=\"j-dialog__close j-focusable\"\r\n aria-label=\"Close\"\r\n @click=\"close\"\r\n >\r\n <IconX />\r\n </button>\r\n </div>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-dialog__overlay {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-overlay);\r\n background: var(--juxt-overlay);\r\n}\r\n\r\n.j-dialog__positioner {\r\n --j-dialog-shift: calc(var(--juxt-motion-shift) * 2);\r\n\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-modal);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: var(--juxt-space-4);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n}\r\n\r\n.j-dialog__panel {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n max-height: calc(100dvh - var(--juxt-space-8));\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-lg);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n outline: none;\r\n}\r\n\r\n.j-dialog__panel--sm {\r\n max-width: 25rem;\r\n}\r\n\r\n.j-dialog__panel--md {\r\n max-width: 30rem;\r\n}\r\n\r\n.j-dialog__panel--lg {\r\n max-width: 40rem;\r\n}\r\n\r\n.j-dialog__header {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n padding: var(--juxt-space-5) var(--juxt-space-12) 0 var(--juxt-space-5);\r\n}\r\n\r\n.j-dialog__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-lg);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-dialog__description {\r\n margin: 0;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-dialog__body {\r\n flex: 1;\r\n min-height: 0;\r\n padding: var(--juxt-space-4) var(--juxt-space-5) 0;\r\n overflow-y: auto;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-dialog__header + .j-dialog__footer {\r\n margin-top: var(--juxt-space-1);\r\n}\r\n\r\n.j-dialog__footer {\r\n display: flex;\r\n justify-content: flex-end;\r\n gap: var(--juxt-space-2);\r\n padding: var(--juxt-space-5);\r\n}\r\n\r\n.j-dialog__body:last-child {\r\n padding-bottom: var(--juxt-space-5);\r\n}\r\n\r\n.j-dialog__close {\r\n position: absolute;\r\n top: var(--juxt-space-3);\r\n right: var(--juxt-space-3);\r\n display: grid;\r\n place-items: center;\r\n width: var(--juxt-control-sm);\r\n height: var(--juxt-control-sm);\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-dialog__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-dialog__close > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n/* Motion: the backdrop fades; the panel fades, lifts and settles. */\r\n.j-dialog-overlay-enter-active {\r\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-dialog-overlay-leave-active {\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-dialog-overlay-enter-from,\r\n.j-dialog-overlay-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-dialog-enter-active .j-dialog__panel {\r\n transition:\r\n opacity var(--juxt-duration-normal) var(--juxt-ease-out),\r\n transform var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-dialog-leave-active .j-dialog__panel {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-in),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-dialog-enter-from .j-dialog__panel,\r\n.j-dialog-leave-to .j-dialog__panel {\r\n opacity: 0;\r\n transform: translateY(var(--j-dialog-shift)) scale(var(--juxt-motion-scale));\r\n}\r\n\r\n.j-dialog__panel.is-nudging {\r\n animation: j-dialog-nudge 220ms var(--juxt-ease-out);\r\n}\r\n\r\n@keyframes j-dialog-nudge {\r\n 40% {\r\n transform: scale(1.012);\r\n }\r\n}\r\n\r\n/* Small screens: a sheet anchored to the bottom, within thumb reach. */\r\n@media (max-width: 639px) {\r\n .j-dialog__positioner {\r\n --j-dialog-shift: calc(var(--juxt-motion-shift) * 6);\r\n\r\n align-items: flex-end;\r\n padding: var(--juxt-space-2);\r\n padding-bottom: max(var(--juxt-space-2), env(safe-area-inset-bottom));\r\n }\r\n\r\n .j-dialog__panel {\r\n max-width: none;\r\n max-height: calc(100dvh - var(--juxt-space-12));\r\n }\r\n\r\n .j-dialog-enter-from .j-dialog__panel,\r\n .j-dialog-leave-to .j-dialog__panel {\r\n transform: translateY(var(--j-dialog-shift));\r\n }\r\n\r\n .j-dialog__footer {\r\n flex-direction: column-reverse;\r\n }\r\n\r\n .j-dialog__footer > * {\r\n width: 100%;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { ref, useId } from 'vue'\r\nimport { FIELD_SELECTOR, useModal } from '../../composables/useModal'\r\nimport { IconX } from '../../icons'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface DialogProps {\r\n title?: string\r\n description?: string\r\n size?: 'sm' | 'md' | 'lg'\r\n /** Close when the backdrop is clicked. Defaults to true. */\r\n closeOnOverlay?: boolean\r\n /** Close when Escape is pressed. Defaults to true. */\r\n closeOnEscape?: boolean\r\n /** Hide the close button in the corner. */\r\n hideClose?: boolean\r\n /** Accessible label when there is no visible title. */\r\n ariaLabel?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<DialogProps>(), {\r\n size: 'md',\r\n closeOnOverlay: true,\r\n closeOnEscape: true,\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\n\r\nconst emit = defineEmits<{\r\n /** Fired after the leave transition finishes | a good moment to reset form state. */\r\n afterClose: []\r\n}>()\r\n\r\ndefineSlots<{\r\n default?: (props: { close: () => void }) => unknown\r\n /** Custom header. Put `titleId` on your heading so the dialog stays labelled. */\r\n header?: (props: { titleId: string, descriptionId: string }) => unknown\r\n footer?: (props: { close: () => void }) => unknown\r\n}>()\r\n\r\nconst id = useId()\r\nconst titleId = `j-${id}-title`\r\nconst descriptionId = `j-${id}-description`\r\n\r\nconst panel = ref<HTMLElement | null>(null)\r\n\r\nfunction close() {\r\n open.value = false\r\n}\r\n\r\nconst { nudging, onBackdropPointerDown, onBackdropClick } = useModal(panel, open, {\r\n closeOnEscape: () => props.closeOnEscape,\r\n closeOnOverlay: () => props.closeOnOverlay,\r\n close,\r\n focusSelector: `.j-dialog__body ${FIELD_SELECTOR}`,\r\n})\r\n\r\ndefineExpose({ close })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-dialog-overlay\">\r\n <div v-if=\"open\" class=\"j-dialog__overlay\" aria-hidden=\"true\" />\r\n </Transition>\r\n <Transition name=\"j-dialog\" :duration=\"{ enter: 240, leave: 150 }\" @after-leave=\"emit('afterClose')\">\r\n <div v-if=\"open\" class=\"j-dialog__positioner\" @pointerdown=\"onBackdropPointerDown\" @click=\"onBackdropClick\">\r\n <div\r\n ref=\"panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n class=\"j-dialog__panel\"\r\n :class=\"[`j-dialog__panel--${size}`, { 'is-nudging': nudging }]\"\r\n :aria-labelledby=\"title || $slots.header ? titleId : undefined\"\r\n :aria-label=\"ariaLabel\"\r\n :aria-describedby=\"description ? descriptionId : undefined\"\r\n @animationend=\"nudging = false\"\r\n >\r\n <header v-if=\"title || description || $slots.header\" class=\"j-dialog__header\">\r\n <slot name=\"header\" :title-id=\"titleId\" :description-id=\"descriptionId\">\r\n <h2 :id=\"titleId\" class=\"j-dialog__title\">\r\n {{ title }}\r\n </h2>\r\n <p v-if=\"description\" :id=\"descriptionId\" class=\"j-dialog__description\">\r\n {{ description }}\r\n </p>\r\n </slot>\r\n </header>\r\n <div v-if=\"$slots.default\" class=\"j-dialog__body\">\r\n <slot :close=\"close\" />\r\n </div>\r\n <footer v-if=\"$slots.footer\" class=\"j-dialog__footer\">\r\n <slot name=\"footer\" :close=\"close\" />\r\n </footer>\r\n <button\r\n v-if=\"!hideClose\"\r\n type=\"button\"\r\n class=\"j-dialog__close j-focusable\"\r\n aria-label=\"Close\"\r\n @click=\"close\"\r\n >\r\n <IconX />\r\n </button>\r\n </div>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-dialog__overlay {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-overlay);\r\n background: var(--juxt-overlay);\r\n}\r\n\r\n.j-dialog__positioner {\r\n --j-dialog-shift: calc(var(--juxt-motion-shift) * 2);\r\n\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-modal);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: var(--juxt-space-4);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n}\r\n\r\n.j-dialog__panel {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n max-height: calc(100dvh - var(--juxt-space-8));\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-lg);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n outline: none;\r\n}\r\n\r\n.j-dialog__panel--sm {\r\n max-width: 25rem;\r\n}\r\n\r\n.j-dialog__panel--md {\r\n max-width: 30rem;\r\n}\r\n\r\n.j-dialog__panel--lg {\r\n max-width: 40rem;\r\n}\r\n\r\n.j-dialog__header {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n padding: var(--juxt-space-5) var(--juxt-space-12) 0 var(--juxt-space-5);\r\n}\r\n\r\n.j-dialog__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-lg);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-dialog__description {\r\n margin: 0;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-dialog__body {\r\n flex: 1;\r\n min-height: 0;\r\n padding: var(--juxt-space-4) var(--juxt-space-5) 0;\r\n overflow-y: auto;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-dialog__header + .j-dialog__footer {\r\n margin-top: var(--juxt-space-1);\r\n}\r\n\r\n.j-dialog__footer {\r\n display: flex;\r\n justify-content: flex-end;\r\n gap: var(--juxt-space-2);\r\n padding: var(--juxt-space-5);\r\n}\r\n\r\n.j-dialog__body:last-child {\r\n padding-bottom: var(--juxt-space-5);\r\n}\r\n\r\n.j-dialog__close {\r\n position: absolute;\r\n top: var(--juxt-space-3);\r\n right: var(--juxt-space-3);\r\n display: grid;\r\n place-items: center;\r\n width: var(--juxt-control-sm);\r\n height: var(--juxt-control-sm);\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-dialog__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-dialog__close > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n/* Motion: the backdrop fades; the panel fades, lifts and settles. */\r\n.j-dialog-overlay-enter-active {\r\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-dialog-overlay-leave-active {\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-dialog-overlay-enter-from,\r\n.j-dialog-overlay-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-dialog-enter-active .j-dialog__panel {\r\n transition:\r\n opacity var(--juxt-duration-normal) var(--juxt-ease-out),\r\n transform var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-dialog-leave-active .j-dialog__panel {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-in),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-dialog-enter-from .j-dialog__panel,\r\n.j-dialog-leave-to .j-dialog__panel {\r\n opacity: 0;\r\n transform: translateY(var(--j-dialog-shift)) scale(var(--juxt-motion-scale));\r\n}\r\n\r\n.j-dialog__panel.is-nudging {\r\n animation: j-dialog-nudge 220ms var(--juxt-ease-out);\r\n}\r\n\r\n@keyframes j-dialog-nudge {\r\n 40% {\r\n transform: scale(1.012);\r\n }\r\n}\r\n\r\n/* Small screens: a sheet anchored to the bottom, within thumb reach. */\r\n@media (max-width: 639px) {\r\n .j-dialog__positioner {\r\n --j-dialog-shift: calc(var(--juxt-motion-shift) * 6);\r\n\r\n align-items: flex-end;\r\n padding: var(--juxt-space-2);\r\n padding-bottom: max(var(--juxt-space-2), env(safe-area-inset-bottom));\r\n }\r\n\r\n .j-dialog__panel {\r\n max-width: none;\r\n max-height: calc(100dvh - var(--juxt-space-12));\r\n }\r\n\r\n .j-dialog-enter-from .j-dialog__panel,\r\n .j-dialog-leave-to .j-dialog__panel {\r\n transform: translateY(var(--j-dialog-shift));\r\n }\r\n\r\n .j-dialog__footer {\r\n flex-direction: column-reverse;\r\n }\r\n\r\n .j-dialog__footer > * {\r\n width: 100%;\r\n }\r\n}\r\n}\r\n</style>\r\n","import type { VNode } from 'vue'\r\nimport { cloneVNode, Comment, defineComponent, Fragment, h, Text } from 'vue'\r\n\r\nfunction firstElement(nodes: VNode[] | undefined): VNode | undefined {\r\n if (!nodes) return undefined\r\n for (const node of nodes) {\r\n if (node.type === Comment) continue\r\n if (node.type === Fragment && Array.isArray(node.children)) {\r\n const found = firstElement(node.children as VNode[])\r\n if (found) return found\r\n continue\r\n }\r\n if (node.type === Text) {\r\n if (typeof node.children === 'string' && node.children.trim() === '') continue\r\n // Bare text can't hold attributes; give it an element.\r\n return h('span', null, node.children as string)\r\n }\r\n return node\r\n }\r\n return undefined\r\n}\r\n\r\n/**\r\n * Internal: renders its first child element and merges its own attributes\r\n * (ARIA, listeners, ids) onto it | no wrapper element.\r\n */\r\nexport const JSlot = defineComponent({\r\n name: 'JSlot',\r\n inheritAttrs: false,\r\n setup(_, { slots, attrs }) {\r\n return () => {\r\n const child = firstElement(slots.default?.())\r\n if (!child) return null\r\n // The consumer's own id wins; everything else is merged.\r\n const { id, ...rest } = attrs\r\n return cloneVNode(child, child.props?.id ? rest : { id, ...rest }, true)\r\n }\r\n },\r\n})\r\n","import type { InjectionKey, Ref } from 'vue'\r\n\r\nexport interface MenuRootContext {\r\n /** Close the whole menu tree. */\r\n close: (restoreFocus?: boolean) => void\r\n}\r\n\r\nexport interface MenuLevelContext {\r\n content: Ref<HTMLElement | null>\r\n /** Id of the submenu currently open at this level. */\r\n openSub: Ref<string | null>\r\n setOpenSub: (id: string | null) => void\r\n /** Close the open submenu shortly, unless the pointer is on its way into it. */\r\n scheduleSubClose: () => void\r\n cancelSubClose: () => void\r\n}\r\n\r\nexport const MENU_ROOT: InjectionKey<MenuRootContext> = Symbol('JDropdownMenu')\r\nexport const MENU_LEVEL: InjectionKey<MenuLevelContext> = Symbol('JDropdownMenuLevel')\r\n\r\nexport function getMenuItems(content: HTMLElement | null): HTMLElement[] {\r\n if (!content) return []\r\n return Array.from(content.querySelectorAll<HTMLElement>('[role^=\"menuitem\"]:not([aria-disabled=\"true\"])'))\r\n}\r\n","import type { Placement } from '@floating-ui/dom'\r\nimport type { CSSProperties, Ref } from 'vue'\r\nimport { autoUpdate, computePosition, flip, offset, shift, size } from '@floating-ui/dom'\r\nimport { computed, onBeforeUnmount, ref, shallowRef, watch } from 'vue'\r\n\r\nexport type { Placement }\r\n\r\nexport interface FloatingOptions {\r\n placement?: Placement\r\n /** Gap between reference and floating element, in px. */\r\n offset?: number | { mainAxis?: number, crossAxis?: number }\r\n /** Make the floating element at least as wide as the reference. */\r\n matchWidth?: boolean\r\n /** Padding kept from the viewport edge, in px. */\r\n padding?: number\r\n}\r\n\r\nconst ORIGIN: Record<string, string> = {\r\n top: 'bottom',\r\n bottom: 'top',\r\n left: 'right',\r\n right: 'left',\r\n}\r\n\r\n/**\r\n * Position a floating element next to a reference element and keep it there\r\n * through scroll and resize. Coordinates are written to `left`/`top` so\r\n * `transform` stays free for enter/leave motion.\r\n */\r\nexport function useFloating(\r\n reference: Ref<HTMLElement | null>,\r\n floating: Ref<HTMLElement | null>,\r\n options: FloatingOptions | (() => FloatingOptions) = {},\r\n) {\r\n const read = () => (typeof options === 'function' ? options() : options)\r\n const x = ref(0)\r\n const y = ref(0)\r\n const placement = ref<Placement>(read().placement ?? 'bottom-start')\r\n const availableHeight = ref<number>()\r\n const referenceWidth = ref<number>()\r\n const stop = shallowRef<() => void>()\r\n\r\n async function update() {\r\n const referenceEl = reference.value\r\n const floatingEl = floating.value\r\n if (!referenceEl || !floatingEl) return\r\n const opts = read()\r\n const padding = opts.padding ?? 8\r\n const result = await computePosition(referenceEl, floatingEl, {\r\n strategy: 'fixed',\r\n placement: opts.placement ?? 'bottom-start',\r\n middleware: [\r\n offset(opts.offset ?? 6),\r\n flip({ padding }),\r\n shift({ padding }),\r\n size({\r\n padding,\r\n apply({ availableHeight: height, rects }) {\r\n availableHeight.value = Math.max(120, Math.floor(height))\r\n referenceWidth.value = rects.reference.width\r\n },\r\n }),\r\n ],\r\n })\r\n x.value = Math.round(result.x)\r\n y.value = Math.round(result.y)\r\n placement.value = result.placement\r\n }\r\n\r\n watch(\r\n [reference, floating],\r\n ([referenceEl, floatingEl]) => {\r\n stop.value?.()\r\n stop.value = undefined\r\n if (referenceEl && floatingEl) stop.value = autoUpdate(referenceEl, floatingEl, update)\r\n },\r\n { flush: 'post' },\r\n )\r\n\r\n onBeforeUnmount(() => stop.value?.())\r\n\r\n const side = computed(() => placement.value.split('-')[0] as 'top' | 'bottom' | 'left' | 'right')\r\n\r\n const styles = computed<CSSProperties>(() => {\r\n const align = placement.value.split('-')[1]\r\n const cross = side.value === 'top' || side.value === 'bottom'\r\n ? (align === 'start' ? 'left' : align === 'end' ? 'right' : 'center')\r\n : (align === 'start' ? 'top' : align === 'end' ? 'bottom' : 'center')\r\n const origin = side.value === 'top' || side.value === 'bottom'\r\n ? `${cross} ${ORIGIN[side.value]}`\r\n : `${ORIGIN[side.value]} ${cross}`\r\n const dir = side.value === 'top' ? 1 : side.value === 'bottom' ? -1 : 0\r\n return {\r\n 'position': 'fixed',\r\n 'left': `${x.value}px`,\r\n 'top': `${y.value}px`,\r\n 'transformOrigin': origin,\r\n 'minWidth': read().matchWidth && referenceWidth.value ? `${referenceWidth.value}px` : undefined,\r\n '--j-available-height': availableHeight.value ? `${availableHeight.value}px` : undefined,\r\n '--j-pop-dir': String(dir),\r\n '--j-pop-x': String(side.value === 'left' ? 1 : side.value === 'right' ? -1 : 0),\r\n } as CSSProperties\r\n })\r\n\r\n return { x, y, placement, side, styles, update }\r\n}\r\n","/**\r\n * Type-to-jump for lists. Characters typed in quick succession form a query;\r\n * repeating the same letter cycles through matches.\r\n */\r\nexport function useTypeahead(timeout = 500) {\r\n let query = ''\r\n let timer: ReturnType<typeof setTimeout> | undefined\r\n\r\n function search<T>(key: string, items: T[], getLabel: (item: T) => string, currentIndex: number): number {\r\n if (key.length !== 1 || (key === ' ' && query === '')) return -1\r\n clearTimeout(timer)\r\n timer = setTimeout(() => (query = ''), timeout)\r\n query += key.toLowerCase()\r\n\r\n const repeated = query.length > 1 && [...query].every(char => char === query[0])\r\n const needle = repeated ? query[0]! : query\r\n // A fresh search starts after the current item so repeated keys cycle.\r\n const start = repeated || query.length === 1 ? currentIndex + 1 : currentIndex\r\n for (let i = 0; i < items.length; i++) {\r\n const index = (start + i + items.length) % items.length\r\n if (getLabel(items[index]!).trim().toLowerCase().startsWith(needle)) return index\r\n }\r\n return -1\r\n }\r\n\r\n const isSearching = () => query.length > 0\r\n\r\n return { search, isSearching }\r\n}\r\n","<script setup lang=\"ts\">\r\n/**\r\n * Internal: the floating list shared by the root menu and submenus.\r\n * Owns positioning, keyboard navigation, typeahead and the layer.\r\n */\r\nimport type { Placement } from '../../composables/useFloating'\r\nimport type { MenuLevelContext } from './context'\r\nimport { inject, nextTick, provide, ref, toRef, watch } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { useTypeahead } from '../../composables/useTypeahead'\r\nimport { focusElement } from '../../utils/dom'\r\nimport JPortal from '../primitives/JPortal.vue'\r\nimport { getMenuItems, MENU_LEVEL, MENU_ROOT } from './context'\r\n\r\nconst props = defineProps<{\r\n open: boolean\r\n reference: HTMLElement | null\r\n id: string\r\n labelledby?: string\r\n placement: Placement\r\n offset: number | { mainAxis?: number, crossAxis?: number }\r\n /** What to focus once open. */\r\n initialFocus: 'first' | 'last' | 'content' | 'none'\r\n isSub?: boolean\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n escape: []\r\n outside: [event: PointerEvent]\r\n /** Submenus only: ArrowLeft asks to go back to the parent. */\r\n back: []\r\n pointerenter: []\r\n}>()\r\n\r\nconst root = inject(MENU_ROOT)!\r\nconst content = ref<HTMLElement | null>(null)\r\nconst reference = toRef(props, 'reference')\r\n\r\nconst { styles } = useFloating(reference, content, () => ({ placement: props.placement, offset: props.offset }))\r\nconst typeahead = useTypeahead()\r\n\r\nuseLayer({\r\n active: toRef(props, 'open'),\r\n elements: () => [content.value, props.reference],\r\n onEscape: () => emit('escape'),\r\n onPointerDownOutside: event => emit('outside', event),\r\n})\r\n\r\n// Level context for the items inside this list.\r\nconst openSub = ref<string | null>(null)\r\nlet subTimer: ReturnType<typeof setTimeout> | undefined\r\nconst level: MenuLevelContext = {\r\n content,\r\n openSub,\r\n setOpenSub: (id) => {\r\n clearTimeout(subTimer)\r\n openSub.value = id\r\n },\r\n scheduleSubClose: () => {\r\n if (!openSub.value) return\r\n clearTimeout(subTimer)\r\n subTimer = setTimeout(() => (openSub.value = null), 180)\r\n },\r\n cancelSubClose: () => clearTimeout(subTimer),\r\n}\r\nprovide(MENU_LEVEL, level)\r\n\r\nwatch(\r\n () => props.open,\r\n async (value) => {\r\n if (!value) {\r\n openSub.value = null\r\n return\r\n }\r\n if (props.initialFocus === 'none') return\r\n await nextTick()\r\n const items = getMenuItems(content.value)\r\n if (props.initialFocus === 'first') focusElement(items[0] ?? content.value)\r\n else if (props.initialFocus === 'last') focusElement(items[items.length - 1] ?? content.value)\r\n else focusElement(content.value)\r\n },\r\n { flush: 'post' },\r\n)\r\n\r\nfunction move(delta: 1 | -1 | 'first' | 'last') {\r\n const items = getMenuItems(content.value)\r\n if (items.length === 0) return\r\n const current = items.indexOf(document.activeElement as HTMLElement)\r\n let next: number\r\n if (delta === 'first') next = 0\r\n else if (delta === 'last') next = items.length - 1\r\n else if (current === -1) next = delta === 1 ? 0 : items.length - 1\r\n else next = (current + delta + items.length) % items.length\r\n focusElement(items[next])\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const { key } = event\r\n switch (key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n move(1)\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n move(-1)\r\n break\r\n case 'Home':\r\n case 'PageUp':\r\n event.preventDefault()\r\n move('first')\r\n break\r\n case 'End':\r\n case 'PageDown':\r\n event.preventDefault()\r\n move('last')\r\n break\r\n case 'ArrowLeft':\r\n if (props.isSub) {\r\n event.preventDefault()\r\n emit('back')\r\n }\r\n break\r\n case 'Tab':\r\n event.preventDefault()\r\n root.close(true)\r\n break\r\n case 'Enter':\r\n case ' ': {\r\n const active = document.activeElement as HTMLElement | null\r\n if (active && active !== content.value && content.value?.contains(active)) {\r\n event.preventDefault()\r\n active.click()\r\n }\r\n break\r\n }\r\n default:\r\n if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\r\n const items = getMenuItems(content.value)\r\n const index = typeahead.search(key, items, item => item.textContent ?? '', items.indexOf(document.activeElement as HTMLElement))\r\n if (index >= 0) focusElement(items[index])\r\n }\r\n }\r\n}\r\n\r\ndefineExpose({ content })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-pop\">\r\n <div\r\n v-if=\"open\"\r\n :id=\"id\"\r\n ref=\"content\"\r\n role=\"menu\"\r\n tabindex=\"-1\"\r\n class=\"j-menu j-popover-surface\"\r\n :class=\"{ 'j-menu--sub': isSub }\"\r\n :style=\"styles\"\r\n :aria-labelledby=\"labelledby\"\r\n aria-orientation=\"vertical\"\r\n @keydown=\"onKeydown\"\r\n @pointerenter=\"emit('pointerenter')\"\r\n >\r\n <slot />\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-menu {\r\n z-index: var(--juxt-z-popover);\r\n box-sizing: border-box;\r\n min-width: 11rem;\r\n max-width: calc(100vw - 16px);\r\n max-height: var(--j-available-height, 24rem);\r\n padding: var(--juxt-space-1);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n outline: none;\r\n}\r\n\r\n.j-menu__item {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2);\r\n min-height: var(--juxt-control-sm);\r\n padding: 0 var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-xs);\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n text-decoration: none;\r\n cursor: pointer;\r\n outline: none;\r\n user-select: none;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.j-menu__item:focus,\r\n.j-menu__item[aria-expanded='true'] {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-menu__item[aria-disabled='true'] {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-menu__item--destructive {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-menu__item--destructive:focus {\r\n background: var(--juxt-danger-soft);\r\n}\r\n\r\n.j-menu__icon {\r\n display: inline-flex;\r\n flex: none;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-menu__icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-menu__item:focus .j-menu__icon {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-menu__item--destructive .j-menu__icon {\r\n color: inherit;\r\n}\r\n\r\n.j-menu__text {\r\n flex: 1;\r\n min-width: 0;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-menu__shortcut {\r\n margin-left: var(--juxt-space-4);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n letter-spacing: 0.04em;\r\n}\r\n\r\n.j-menu__check {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n margin-left: var(--juxt-space-4);\r\n color: var(--juxt-accent-text);\r\n opacity: 0;\r\n}\r\n\r\n.j-menu__check.is-checked {\r\n opacity: 1;\r\n}\r\n\r\n.j-menu__chevron {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n margin-right: -0.125rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-menu__separator {\r\n height: 1px;\r\n margin: var(--juxt-space-1) calc(var(--juxt-space-1) * -1);\r\n background: var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-menu__separator,\r\n[data-theme='dark'] .j-menu__separator {\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-menu__label {\r\n padding: var(--juxt-space-1-5) var(--juxt-space-2) var(--juxt-space-1);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\n/**\r\n * Internal: the floating list shared by the root menu and submenus.\r\n * Owns positioning, keyboard navigation, typeahead and the layer.\r\n */\r\nimport type { Placement } from '../../composables/useFloating'\r\nimport type { MenuLevelContext } from './context'\r\nimport { inject, nextTick, provide, ref, toRef, watch } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { useTypeahead } from '../../composables/useTypeahead'\r\nimport { focusElement } from '../../utils/dom'\r\nimport JPortal from '../primitives/JPortal.vue'\r\nimport { getMenuItems, MENU_LEVEL, MENU_ROOT } from './context'\r\n\r\nconst props = defineProps<{\r\n open: boolean\r\n reference: HTMLElement | null\r\n id: string\r\n labelledby?: string\r\n placement: Placement\r\n offset: number | { mainAxis?: number, crossAxis?: number }\r\n /** What to focus once open. */\r\n initialFocus: 'first' | 'last' | 'content' | 'none'\r\n isSub?: boolean\r\n}>()\r\n\r\nconst emit = defineEmits<{\r\n escape: []\r\n outside: [event: PointerEvent]\r\n /** Submenus only: ArrowLeft asks to go back to the parent. */\r\n back: []\r\n pointerenter: []\r\n}>()\r\n\r\nconst root = inject(MENU_ROOT)!\r\nconst content = ref<HTMLElement | null>(null)\r\nconst reference = toRef(props, 'reference')\r\n\r\nconst { styles } = useFloating(reference, content, () => ({ placement: props.placement, offset: props.offset }))\r\nconst typeahead = useTypeahead()\r\n\r\nuseLayer({\r\n active: toRef(props, 'open'),\r\n elements: () => [content.value, props.reference],\r\n onEscape: () => emit('escape'),\r\n onPointerDownOutside: event => emit('outside', event),\r\n})\r\n\r\n// Level context for the items inside this list.\r\nconst openSub = ref<string | null>(null)\r\nlet subTimer: ReturnType<typeof setTimeout> | undefined\r\nconst level: MenuLevelContext = {\r\n content,\r\n openSub,\r\n setOpenSub: (id) => {\r\n clearTimeout(subTimer)\r\n openSub.value = id\r\n },\r\n scheduleSubClose: () => {\r\n if (!openSub.value) return\r\n clearTimeout(subTimer)\r\n subTimer = setTimeout(() => (openSub.value = null), 180)\r\n },\r\n cancelSubClose: () => clearTimeout(subTimer),\r\n}\r\nprovide(MENU_LEVEL, level)\r\n\r\nwatch(\r\n () => props.open,\r\n async (value) => {\r\n if (!value) {\r\n openSub.value = null\r\n return\r\n }\r\n if (props.initialFocus === 'none') return\r\n await nextTick()\r\n const items = getMenuItems(content.value)\r\n if (props.initialFocus === 'first') focusElement(items[0] ?? content.value)\r\n else if (props.initialFocus === 'last') focusElement(items[items.length - 1] ?? content.value)\r\n else focusElement(content.value)\r\n },\r\n { flush: 'post' },\r\n)\r\n\r\nfunction move(delta: 1 | -1 | 'first' | 'last') {\r\n const items = getMenuItems(content.value)\r\n if (items.length === 0) return\r\n const current = items.indexOf(document.activeElement as HTMLElement)\r\n let next: number\r\n if (delta === 'first') next = 0\r\n else if (delta === 'last') next = items.length - 1\r\n else if (current === -1) next = delta === 1 ? 0 : items.length - 1\r\n else next = (current + delta + items.length) % items.length\r\n focusElement(items[next])\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const { key } = event\r\n switch (key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n move(1)\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n move(-1)\r\n break\r\n case 'Home':\r\n case 'PageUp':\r\n event.preventDefault()\r\n move('first')\r\n break\r\n case 'End':\r\n case 'PageDown':\r\n event.preventDefault()\r\n move('last')\r\n break\r\n case 'ArrowLeft':\r\n if (props.isSub) {\r\n event.preventDefault()\r\n emit('back')\r\n }\r\n break\r\n case 'Tab':\r\n event.preventDefault()\r\n root.close(true)\r\n break\r\n case 'Enter':\r\n case ' ': {\r\n const active = document.activeElement as HTMLElement | null\r\n if (active && active !== content.value && content.value?.contains(active)) {\r\n event.preventDefault()\r\n active.click()\r\n }\r\n break\r\n }\r\n default:\r\n if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\r\n const items = getMenuItems(content.value)\r\n const index = typeahead.search(key, items, item => item.textContent ?? '', items.indexOf(document.activeElement as HTMLElement))\r\n if (index >= 0) focusElement(items[index])\r\n }\r\n }\r\n}\r\n\r\ndefineExpose({ content })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-pop\">\r\n <div\r\n v-if=\"open\"\r\n :id=\"id\"\r\n ref=\"content\"\r\n role=\"menu\"\r\n tabindex=\"-1\"\r\n class=\"j-menu j-popover-surface\"\r\n :class=\"{ 'j-menu--sub': isSub }\"\r\n :style=\"styles\"\r\n :aria-labelledby=\"labelledby\"\r\n aria-orientation=\"vertical\"\r\n @keydown=\"onKeydown\"\r\n @pointerenter=\"emit('pointerenter')\"\r\n >\r\n <slot />\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-menu {\r\n z-index: var(--juxt-z-popover);\r\n box-sizing: border-box;\r\n min-width: 11rem;\r\n max-width: calc(100vw - 16px);\r\n max-height: var(--j-available-height, 24rem);\r\n padding: var(--juxt-space-1);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n outline: none;\r\n}\r\n\r\n.j-menu__item {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2);\r\n min-height: var(--juxt-control-sm);\r\n padding: 0 var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-xs);\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n text-decoration: none;\r\n cursor: pointer;\r\n outline: none;\r\n user-select: none;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.j-menu__item:focus,\r\n.j-menu__item[aria-expanded='true'] {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-menu__item[aria-disabled='true'] {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-menu__item--destructive {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-menu__item--destructive:focus {\r\n background: var(--juxt-danger-soft);\r\n}\r\n\r\n.j-menu__icon {\r\n display: inline-flex;\r\n flex: none;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-menu__icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-menu__item:focus .j-menu__icon {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-menu__item--destructive .j-menu__icon {\r\n color: inherit;\r\n}\r\n\r\n.j-menu__text {\r\n flex: 1;\r\n min-width: 0;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-menu__shortcut {\r\n margin-left: var(--juxt-space-4);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n letter-spacing: 0.04em;\r\n}\r\n\r\n.j-menu__check {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n margin-left: var(--juxt-space-4);\r\n color: var(--juxt-accent-text);\r\n opacity: 0;\r\n}\r\n\r\n.j-menu__check.is-checked {\r\n opacity: 1;\r\n}\r\n\r\n.j-menu__chevron {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n margin-right: -0.125rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-menu__separator {\r\n height: 1px;\r\n margin: var(--juxt-space-1) calc(var(--juxt-space-1) * -1);\r\n background: var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-menu__separator,\r\n[data-theme='dark'] .j-menu__separator {\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-menu__label {\r\n padding: var(--juxt-space-1-5) var(--juxt-space-2) var(--juxt-space-1);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Placement } from '../../composables/useFloating'\r\nimport { computed, nextTick, provide, ref, useId } from 'vue'\r\nimport { focusElement, unrefElement } from '../../utils/dom'\r\nimport { JSlot } from '../primitives/JSlot'\r\nimport { MENU_ROOT } from './context'\r\nimport JMenuContent from './JMenuContent.vue'\r\n\r\nexport interface DropdownMenuProps {\r\n placement?: Placement\r\n /** Distance from the trigger, in px. */\r\n offset?: number\r\n disabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<DropdownMenuProps>(), {\r\n placement: 'bottom-start',\r\n offset: 6,\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\n\r\ndefineSlots<{\r\n /** The element that opens the menu | usually a `JButton`. */\r\n trigger?: (props: { open: boolean }) => unknown\r\n default?: () => unknown\r\n}>()\r\n\r\nconst id = useId()\r\nconst triggerId = `j-${id}-trigger`\r\nconst menuId = `j-${id}-menu`\r\n\r\nconst triggerRef = ref()\r\nconst triggerEl = computed(() => unrefElement(triggerRef.value))\r\nconst initialFocus = ref<'first' | 'last' | 'content'>('content')\r\n\r\nfunction show(focus: 'first' | 'last' | 'content') {\r\n if (props.disabled) return\r\n initialFocus.value = focus\r\n open.value = true\r\n}\r\n\r\nfunction close(restoreFocus = true) {\r\n if (!open.value) return\r\n open.value = false\r\n if (restoreFocus) nextTick(() => focusElement(triggerEl.value))\r\n}\r\n\r\nprovide(MENU_ROOT, { close })\r\n\r\nfunction onClick(event: MouseEvent) {\r\n if (open.value) return close(false)\r\n // `detail` is 0 when a click came from the keyboard (Enter / Space).\r\n show(event.detail === 0 ? 'first' : 'content')\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\r\n event.preventDefault()\r\n show(event.key === 'ArrowDown' ? 'first' : 'last')\r\n }\r\n}\r\n\r\nconst triggerProps = computed(() => ({\r\n 'id': triggerId,\r\n 'aria-haspopup': 'menu',\r\n 'aria-expanded': open.value,\r\n 'aria-controls': open.value ? menuId : undefined,\r\n 'data-state': open.value ? 'open' : 'closed',\r\n 'onClick': onClick,\r\n 'onKeydown': onKeydown,\r\n}))\r\n\r\ndefineExpose({ open: () => show('content'), close })\r\n</script>\r\n\r\n<template>\r\n <JSlot ref=\"triggerRef\" v-bind=\"triggerProps\">\r\n <slot name=\"trigger\" :open=\"open\" />\r\n </JSlot>\r\n <JMenuContent\r\n :id=\"menuId\"\r\n :open=\"open\"\r\n :reference=\"triggerEl\"\r\n :labelledby=\"triggerEl?.id || triggerId\"\r\n :placement=\"placement\"\r\n :offset=\"offset\"\r\n :initial-focus=\"initialFocus\"\r\n @escape=\"close(true)\"\r\n @outside=\"close(false)\"\r\n >\r\n <slot />\r\n </JMenuContent>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport type { Placement } from '../../composables/useFloating'\r\nimport { computed, nextTick, provide, ref, useId } from 'vue'\r\nimport { focusElement, unrefElement } from '../../utils/dom'\r\nimport { JSlot } from '../primitives/JSlot'\r\nimport { MENU_ROOT } from './context'\r\nimport JMenuContent from './JMenuContent.vue'\r\n\r\nexport interface DropdownMenuProps {\r\n placement?: Placement\r\n /** Distance from the trigger, in px. */\r\n offset?: number\r\n disabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<DropdownMenuProps>(), {\r\n placement: 'bottom-start',\r\n offset: 6,\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\n\r\ndefineSlots<{\r\n /** The element that opens the menu | usually a `JButton`. */\r\n trigger?: (props: { open: boolean }) => unknown\r\n default?: () => unknown\r\n}>()\r\n\r\nconst id = useId()\r\nconst triggerId = `j-${id}-trigger`\r\nconst menuId = `j-${id}-menu`\r\n\r\nconst triggerRef = ref()\r\nconst triggerEl = computed(() => unrefElement(triggerRef.value))\r\nconst initialFocus = ref<'first' | 'last' | 'content'>('content')\r\n\r\nfunction show(focus: 'first' | 'last' | 'content') {\r\n if (props.disabled) return\r\n initialFocus.value = focus\r\n open.value = true\r\n}\r\n\r\nfunction close(restoreFocus = true) {\r\n if (!open.value) return\r\n open.value = false\r\n if (restoreFocus) nextTick(() => focusElement(triggerEl.value))\r\n}\r\n\r\nprovide(MENU_ROOT, { close })\r\n\r\nfunction onClick(event: MouseEvent) {\r\n if (open.value) return close(false)\r\n // `detail` is 0 when a click came from the keyboard (Enter / Space).\r\n show(event.detail === 0 ? 'first' : 'content')\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\r\n event.preventDefault()\r\n show(event.key === 'ArrowDown' ? 'first' : 'last')\r\n }\r\n}\r\n\r\nconst triggerProps = computed(() => ({\r\n 'id': triggerId,\r\n 'aria-haspopup': 'menu',\r\n 'aria-expanded': open.value,\r\n 'aria-controls': open.value ? menuId : undefined,\r\n 'data-state': open.value ? 'open' : 'closed',\r\n 'onClick': onClick,\r\n 'onKeydown': onKeydown,\r\n}))\r\n\r\ndefineExpose({ open: () => show('content'), close })\r\n</script>\r\n\r\n<template>\r\n <JSlot ref=\"triggerRef\" v-bind=\"triggerProps\">\r\n <slot name=\"trigger\" :open=\"open\" />\r\n </JSlot>\r\n <JMenuContent\r\n :id=\"menuId\"\r\n :open=\"open\"\r\n :reference=\"triggerEl\"\r\n :labelledby=\"triggerEl?.id || triggerId\"\r\n :placement=\"placement\"\r\n :offset=\"offset\"\r\n :initial-focus=\"initialFocus\"\r\n @escape=\"close(true)\"\r\n @outside=\"close(false)\"\r\n >\r\n <slot />\r\n </JMenuContent>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\nimport { inject, ref } from 'vue'\r\nimport { IconCheck } from '../../icons'\r\nimport { focusElement } from '../../utils/dom'\r\nimport { MENU_LEVEL, MENU_ROOT } from './context'\r\n\r\nexport interface DropdownMenuItemProps {\r\n disabled?: boolean\r\n /** Styles the item as a destructive action. */\r\n destructive?: boolean\r\n /** A keyboard shortcut to display, e.g. `⌘D`. Display only. */\r\n shortcut?: string\r\n /** Render as another element, e.g. `a` or `NuxtLink` for navigation items. */\r\n as?: string | Component\r\n /**\r\n * Makes the item one choice in a set (e.g. a theme picker). `true` shows a\r\n * check at the end of the row. Leave undefined for a plain action.\r\n */\r\n checked?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<DropdownMenuItemProps>(), { as: 'div', checked: undefined })\r\n\r\nconst emit = defineEmits<{\r\n /** Fired when the item is chosen. Call `event.preventDefault()` to keep the menu open. */\r\n select: [event: Event]\r\n}>()\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n\r\nconst root = inject(MENU_ROOT)!\r\nconst level = inject(MENU_LEVEL)!\r\nconst el = ref<HTMLElement | null>(null)\r\n\r\nfunction onClick(event: MouseEvent) {\r\n if (props.disabled) {\r\n event.preventDefault()\r\n return\r\n }\r\n const selectEvent = new Event('select', { cancelable: true })\r\n emit('select', selectEvent)\r\n if (!selectEvent.defaultPrevented) root.close(true)\r\n}\r\n\r\nfunction resolve(target: unknown): HTMLElement | null {\r\n const value = target as { $el?: HTMLElement } | HTMLElement | null\r\n return value instanceof HTMLElement ? value : (value?.$el ?? null)\r\n}\r\n\r\n// Hover moves real focus, so pointer and keyboard always agree on one item.\r\nfunction onPointerMove() {\r\n level.scheduleSubClose()\r\n const node = resolve(el.value)\r\n if (props.disabled || !node || document.activeElement === node) return\r\n focusElement(node)\r\n}\r\n\r\nfunction onPointerLeave() {\r\n const node = resolve(el.value)\r\n if (node && document.activeElement === node) focusElement(level.content.value)\r\n}\r\n</script>\r\n\r\n<template>\r\n <component\r\n :is=\"as\"\r\n ref=\"el\"\r\n :role=\"checked === undefined ? 'menuitem' : 'menuitemradio'\"\r\n :aria-checked=\"checked === undefined ? undefined : checked\"\r\n tabindex=\"-1\"\r\n class=\"j-menu__item\"\r\n :class=\"{ 'j-menu__item--destructive': destructive }\"\r\n :aria-disabled=\"disabled || undefined\"\r\n @click=\"onClick\"\r\n @pointermove=\"onPointerMove\"\r\n @pointerleave=\"onPointerLeave\"\r\n >\r\n <span v-if=\"$slots.leading\" class=\"j-menu__icon\"><slot name=\"leading\" /></span>\r\n <span class=\"j-menu__text\"><slot /></span>\r\n <span v-if=\"shortcut\" class=\"j-menu__shortcut\">{{ shortcut }}</span>\r\n <IconCheck v-if=\"checked !== undefined\" class=\"j-menu__check\" :class=\"{ 'is-checked': checked }\" />\r\n </component>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\nimport { inject, ref } from 'vue'\r\nimport { IconCheck } from '../../icons'\r\nimport { focusElement } from '../../utils/dom'\r\nimport { MENU_LEVEL, MENU_ROOT } from './context'\r\n\r\nexport interface DropdownMenuItemProps {\r\n disabled?: boolean\r\n /** Styles the item as a destructive action. */\r\n destructive?: boolean\r\n /** A keyboard shortcut to display, e.g. `⌘D`. Display only. */\r\n shortcut?: string\r\n /** Render as another element, e.g. `a` or `NuxtLink` for navigation items. */\r\n as?: string | Component\r\n /**\r\n * Makes the item one choice in a set (e.g. a theme picker). `true` shows a\r\n * check at the end of the row. Leave undefined for a plain action.\r\n */\r\n checked?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<DropdownMenuItemProps>(), { as: 'div', checked: undefined })\r\n\r\nconst emit = defineEmits<{\r\n /** Fired when the item is chosen. Call `event.preventDefault()` to keep the menu open. */\r\n select: [event: Event]\r\n}>()\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n\r\nconst root = inject(MENU_ROOT)!\r\nconst level = inject(MENU_LEVEL)!\r\nconst el = ref<HTMLElement | null>(null)\r\n\r\nfunction onClick(event: MouseEvent) {\r\n if (props.disabled) {\r\n event.preventDefault()\r\n return\r\n }\r\n const selectEvent = new Event('select', { cancelable: true })\r\n emit('select', selectEvent)\r\n if (!selectEvent.defaultPrevented) root.close(true)\r\n}\r\n\r\nfunction resolve(target: unknown): HTMLElement | null {\r\n const value = target as { $el?: HTMLElement } | HTMLElement | null\r\n return value instanceof HTMLElement ? value : (value?.$el ?? null)\r\n}\r\n\r\n// Hover moves real focus, so pointer and keyboard always agree on one item.\r\nfunction onPointerMove() {\r\n level.scheduleSubClose()\r\n const node = resolve(el.value)\r\n if (props.disabled || !node || document.activeElement === node) return\r\n focusElement(node)\r\n}\r\n\r\nfunction onPointerLeave() {\r\n const node = resolve(el.value)\r\n if (node && document.activeElement === node) focusElement(level.content.value)\r\n}\r\n</script>\r\n\r\n<template>\r\n <component\r\n :is=\"as\"\r\n ref=\"el\"\r\n :role=\"checked === undefined ? 'menuitem' : 'menuitemradio'\"\r\n :aria-checked=\"checked === undefined ? undefined : checked\"\r\n tabindex=\"-1\"\r\n class=\"j-menu__item\"\r\n :class=\"{ 'j-menu__item--destructive': destructive }\"\r\n :aria-disabled=\"disabled || undefined\"\r\n @click=\"onClick\"\r\n @pointermove=\"onPointerMove\"\r\n @pointerleave=\"onPointerLeave\"\r\n >\r\n <span v-if=\"$slots.leading\" class=\"j-menu__icon\"><slot name=\"leading\" /></span>\r\n <span class=\"j-menu__text\"><slot /></span>\r\n <span v-if=\"shortcut\" class=\"j-menu__shortcut\">{{ shortcut }}</span>\r\n <IconCheck v-if=\"checked !== undefined\" class=\"j-menu__check\" :class=\"{ 'is-checked': checked }\" />\r\n </component>\r\n</template>\r\n","<script setup lang=\"ts\">\r\ndefineSlots<{ default?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-menu__label\" role=\"presentation\">\r\n <slot />\r\n </div>\r\n</template>\r\n","<script setup lang=\"ts\">\r\ndefineSlots<{ default?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-menu__label\" role=\"presentation\">\r\n <slot />\r\n </div>\r\n</template>\r\n","<template>\r\n <div role=\"separator\" aria-orientation=\"horizontal\" class=\"j-menu__separator\" />\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport { computed, inject, nextTick, ref, useId } from 'vue'\r\nimport { IconChevronRight } from '../../icons'\r\nimport { focusElement } from '../../utils/dom'\r\nimport { getMenuItems, MENU_LEVEL, MENU_ROOT } from './context'\r\nimport JMenuContent from './JMenuContent.vue'\r\n\r\nexport interface DropdownMenuSubProps {\r\n label: string\r\n disabled?: boolean\r\n}\r\n\r\nconst props = defineProps<DropdownMenuSubProps>()\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n\r\nconst root = inject(MENU_ROOT)!\r\nconst level = inject(MENU_LEVEL)!\r\n\r\nconst id = useId()\r\nconst itemId = `j-${id}-subtrigger`\r\nconst menuId = `j-${id}-submenu`\r\nconst item = ref<HTMLElement | null>(null)\r\nconst sub = ref<InstanceType<typeof JMenuContent> | null>(null)\r\nconst initialFocus = ref<'first' | 'none'>('none')\r\nconst isOpen = computed(() => level.openSub.value === menuId)\r\n\r\nlet openTimer: ReturnType<typeof setTimeout> | undefined\r\n\r\nfunction openSub(focus: 'first' | 'none') {\r\n if (props.disabled) return\r\n initialFocus.value = focus\r\n level.setOpenSub(menuId)\r\n}\r\n\r\nfunction closeSub(returnFocus: boolean) {\r\n if (level.openSub.value === menuId) level.setOpenSub(null)\r\n if (returnFocus) nextTick(() => focusElement(item.value))\r\n}\r\n\r\nfunction onPointerMove() {\r\n level.cancelSubClose()\r\n if (props.disabled) return\r\n if (document.activeElement !== item.value) focusElement(item.value)\r\n if (isOpen.value) return\r\n clearTimeout(openTimer)\r\n openTimer = setTimeout(() => openSub('none'), 90)\r\n}\r\n\r\nfunction onPointerLeave() {\r\n clearTimeout(openTimer)\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key === 'ArrowRight' || event.key === 'Enter' || event.key === ' ') {\r\n event.preventDefault()\r\n event.stopPropagation()\r\n if (isOpen.value) focusElement(getMenuItems(sub.value?.content ?? null)[0])\r\n else openSub('first')\r\n }\r\n}\r\n\r\nfunction onClick() {\r\n openSub('first')\r\n}\r\n\r\n// A press outside the submenu either lands back in the parent (close just the\r\n// submenu) or somewhere else entirely (close the whole menu).\r\nfunction onOutside(event: PointerEvent) {\r\n const target = event.target as Node\r\n if (level.content.value?.contains(target)) closeSub(false)\r\n else root.close(false)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div\r\n :id=\"itemId\"\r\n ref=\"item\"\r\n role=\"menuitem\"\r\n tabindex=\"-1\"\r\n class=\"j-menu__item\"\r\n aria-haspopup=\"menu\"\r\n :aria-expanded=\"isOpen\"\r\n :aria-controls=\"isOpen ? menuId : undefined\"\r\n :aria-disabled=\"disabled || undefined\"\r\n @pointermove=\"onPointerMove\"\r\n @pointerleave=\"onPointerLeave\"\r\n @keydown=\"onKeydown\"\r\n @click=\"onClick\"\r\n >\r\n <span v-if=\"$slots.leading\" class=\"j-menu__icon\"><slot name=\"leading\" /></span>\r\n <span class=\"j-menu__text\">{{ label }}</span>\r\n <IconChevronRight class=\"j-menu__chevron\" />\r\n </div>\r\n <JMenuContent\r\n :id=\"menuId\"\r\n ref=\"sub\"\r\n :open=\"isOpen\"\r\n :reference=\"item\"\r\n :labelledby=\"itemId\"\r\n placement=\"right-start\"\r\n :offset=\"{ mainAxis: 2, crossAxis: -5 }\"\r\n :initial-focus=\"initialFocus\"\r\n is-sub\r\n @escape=\"closeSub(true)\"\r\n @back=\"closeSub(true)\"\r\n @outside=\"onOutside\"\r\n @pointerenter=\"level.cancelSubClose()\"\r\n >\r\n <slot />\r\n </JMenuContent>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport { computed, inject, nextTick, ref, useId } from 'vue'\r\nimport { IconChevronRight } from '../../icons'\r\nimport { focusElement } from '../../utils/dom'\r\nimport { getMenuItems, MENU_LEVEL, MENU_ROOT } from './context'\r\nimport JMenuContent from './JMenuContent.vue'\r\n\r\nexport interface DropdownMenuSubProps {\r\n label: string\r\n disabled?: boolean\r\n}\r\n\r\nconst props = defineProps<DropdownMenuSubProps>()\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n\r\nconst root = inject(MENU_ROOT)!\r\nconst level = inject(MENU_LEVEL)!\r\n\r\nconst id = useId()\r\nconst itemId = `j-${id}-subtrigger`\r\nconst menuId = `j-${id}-submenu`\r\nconst item = ref<HTMLElement | null>(null)\r\nconst sub = ref<InstanceType<typeof JMenuContent> | null>(null)\r\nconst initialFocus = ref<'first' | 'none'>('none')\r\nconst isOpen = computed(() => level.openSub.value === menuId)\r\n\r\nlet openTimer: ReturnType<typeof setTimeout> | undefined\r\n\r\nfunction openSub(focus: 'first' | 'none') {\r\n if (props.disabled) return\r\n initialFocus.value = focus\r\n level.setOpenSub(menuId)\r\n}\r\n\r\nfunction closeSub(returnFocus: boolean) {\r\n if (level.openSub.value === menuId) level.setOpenSub(null)\r\n if (returnFocus) nextTick(() => focusElement(item.value))\r\n}\r\n\r\nfunction onPointerMove() {\r\n level.cancelSubClose()\r\n if (props.disabled) return\r\n if (document.activeElement !== item.value) focusElement(item.value)\r\n if (isOpen.value) return\r\n clearTimeout(openTimer)\r\n openTimer = setTimeout(() => openSub('none'), 90)\r\n}\r\n\r\nfunction onPointerLeave() {\r\n clearTimeout(openTimer)\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key === 'ArrowRight' || event.key === 'Enter' || event.key === ' ') {\r\n event.preventDefault()\r\n event.stopPropagation()\r\n if (isOpen.value) focusElement(getMenuItems(sub.value?.content ?? null)[0])\r\n else openSub('first')\r\n }\r\n}\r\n\r\nfunction onClick() {\r\n openSub('first')\r\n}\r\n\r\n// A press outside the submenu either lands back in the parent (close just the\r\n// submenu) or somewhere else entirely (close the whole menu).\r\nfunction onOutside(event: PointerEvent) {\r\n const target = event.target as Node\r\n if (level.content.value?.contains(target)) closeSub(false)\r\n else root.close(false)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div\r\n :id=\"itemId\"\r\n ref=\"item\"\r\n role=\"menuitem\"\r\n tabindex=\"-1\"\r\n class=\"j-menu__item\"\r\n aria-haspopup=\"menu\"\r\n :aria-expanded=\"isOpen\"\r\n :aria-controls=\"isOpen ? menuId : undefined\"\r\n :aria-disabled=\"disabled || undefined\"\r\n @pointermove=\"onPointerMove\"\r\n @pointerleave=\"onPointerLeave\"\r\n @keydown=\"onKeydown\"\r\n @click=\"onClick\"\r\n >\r\n <span v-if=\"$slots.leading\" class=\"j-menu__icon\"><slot name=\"leading\" /></span>\r\n <span class=\"j-menu__text\">{{ label }}</span>\r\n <IconChevronRight class=\"j-menu__chevron\" />\r\n </div>\r\n <JMenuContent\r\n :id=\"menuId\"\r\n ref=\"sub\"\r\n :open=\"isOpen\"\r\n :reference=\"item\"\r\n :labelledby=\"itemId\"\r\n placement=\"right-start\"\r\n :offset=\"{ mainAxis: 2, crossAxis: -5 }\"\r\n :initial-focus=\"initialFocus\"\r\n is-sub\r\n @escape=\"closeSub(true)\"\r\n @back=\"closeSub(true)\"\r\n @outside=\"onOutside\"\r\n @pointerenter=\"level.cancelSubClose()\"\r\n >\r\n <slot />\r\n </JMenuContent>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nexport interface EmptyStateProps {\r\n title?: string\r\n description?: string\r\n size?: 'sm' | 'md' | 'lg'\r\n}\r\n\r\nwithDefaults(defineProps<EmptyStateProps>(), { size: 'md' })\r\n\r\ndefineSlots<{\r\n /** Icon or illustration shown above the title. */\r\n icon?: () => unknown\r\n default?: () => unknown\r\n /** Call to action, typically a `JButton`. */\r\n actions?: () => unknown\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-empty-state\" :class=\"`j-empty-state--${size}`\">\r\n <div v-if=\"$slots.icon\" class=\"j-empty-state__icon\">\r\n <slot name=\"icon\" />\r\n </div>\r\n <p v-if=\"title\" class=\"j-empty-state__title\">\r\n {{ title }}\r\n </p>\r\n <div v-if=\"description || $slots.default\" class=\"j-empty-state__description\">\r\n <slot>{{ description }}</slot>\r\n </div>\r\n <div v-if=\"$slots.actions\" class=\"j-empty-state__actions\">\r\n <slot name=\"actions\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-empty-state {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n max-width: 24rem;\r\n margin: 0 auto;\r\n padding: var(--juxt-space-8) var(--juxt-space-4);\r\n text-align: center;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-empty-state--sm {\r\n max-width: 18rem;\r\n padding: var(--juxt-space-6) var(--juxt-space-3);\r\n}\r\n\r\n.j-empty-state--lg {\r\n max-width: 28rem;\r\n padding: var(--juxt-space-12) var(--juxt-space-4);\r\n}\r\n\r\n.j-empty-state__icon {\r\n display: grid;\r\n place-items: center;\r\n width: 2.75rem;\r\n height: 2.75rem;\r\n margin-bottom: var(--juxt-space-4);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-surface-sunken);\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-empty-state--lg .j-empty-state__icon {\r\n width: 3.5rem;\r\n height: 3.5rem;\r\n}\r\n\r\n.j-empty-state__icon > svg {\r\n width: 1.25rem;\r\n height: 1.25rem;\r\n}\r\n\r\n.j-empty-state--lg .j-empty-state__icon > svg {\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n}\r\n\r\n.j-empty-state__title {\r\n margin: 0;\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-lg);\r\n font-weight: var(--juxt-weight-semibold);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-empty-state__description {\r\n margin-top: var(--juxt-space-1-5);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-empty-state__description > p {\r\n margin: 0;\r\n}\r\n\r\n.j-empty-state__actions {\r\n display: flex;\r\n flex-wrap: wrap;\r\n justify-content: center;\r\n gap: var(--juxt-space-2);\r\n margin-top: var(--juxt-space-5);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nexport interface EmptyStateProps {\r\n title?: string\r\n description?: string\r\n size?: 'sm' | 'md' | 'lg'\r\n}\r\n\r\nwithDefaults(defineProps<EmptyStateProps>(), { size: 'md' })\r\n\r\ndefineSlots<{\r\n /** Icon or illustration shown above the title. */\r\n icon?: () => unknown\r\n default?: () => unknown\r\n /** Call to action, typically a `JButton`. */\r\n actions?: () => unknown\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-empty-state\" :class=\"`j-empty-state--${size}`\">\r\n <div v-if=\"$slots.icon\" class=\"j-empty-state__icon\">\r\n <slot name=\"icon\" />\r\n </div>\r\n <p v-if=\"title\" class=\"j-empty-state__title\">\r\n {{ title }}\r\n </p>\r\n <div v-if=\"description || $slots.default\" class=\"j-empty-state__description\">\r\n <slot>{{ description }}</slot>\r\n </div>\r\n <div v-if=\"$slots.actions\" class=\"j-empty-state__actions\">\r\n <slot name=\"actions\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-empty-state {\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n max-width: 24rem;\r\n margin: 0 auto;\r\n padding: var(--juxt-space-8) var(--juxt-space-4);\r\n text-align: center;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-empty-state--sm {\r\n max-width: 18rem;\r\n padding: var(--juxt-space-6) var(--juxt-space-3);\r\n}\r\n\r\n.j-empty-state--lg {\r\n max-width: 28rem;\r\n padding: var(--juxt-space-12) var(--juxt-space-4);\r\n}\r\n\r\n.j-empty-state__icon {\r\n display: grid;\r\n place-items: center;\r\n width: 2.75rem;\r\n height: 2.75rem;\r\n margin-bottom: var(--juxt-space-4);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-surface-sunken);\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-empty-state--lg .j-empty-state__icon {\r\n width: 3.5rem;\r\n height: 3.5rem;\r\n}\r\n\r\n.j-empty-state__icon > svg {\r\n width: 1.25rem;\r\n height: 1.25rem;\r\n}\r\n\r\n.j-empty-state--lg .j-empty-state__icon > svg {\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n}\r\n\r\n.j-empty-state__title {\r\n margin: 0;\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-lg);\r\n font-weight: var(--juxt-weight-semibold);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-empty-state__description {\r\n margin-top: var(--juxt-space-1-5);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-empty-state__description > p {\r\n margin: 0;\r\n}\r\n\r\n.j-empty-state__actions {\r\n display: flex;\r\n flex-wrap: wrap;\r\n justify-content: center;\r\n gap: var(--juxt-space-2);\r\n margin-top: var(--juxt-space-5);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\n/**\r\n * Internal: lays out label, control, description and error for form fields.\r\n * Not exported | every field component composes it.\r\n */\r\ndefineProps<{\r\n id: string\r\n labelId: string\r\n descriptionId: string\r\n errorId: string\r\n label?: string\r\n description?: string\r\n errorMessage?: string\r\n showDescription?: boolean\r\n required?: boolean\r\n disabled?: boolean\r\n /** Render the label as a plain element when the control isn't labelable (e.g. a button). */\r\n labelFor?: string | null\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-field\" :class=\"{ 'is-disabled': disabled }\">\r\n <component\r\n :is=\"labelFor === null ? 'span' : 'label'\"\r\n v-if=\"label || $slots.label\"\r\n :id=\"labelId\"\r\n class=\"j-field__label\"\r\n :for=\"labelFor === null ? undefined : labelFor ?? id\"\r\n >\r\n <slot name=\"label\">{{ label }}</slot>\r\n <span v-if=\"required\" class=\"j-field__required\" aria-hidden=\"true\">*</span>\r\n </component>\r\n <slot />\r\n <div class=\"j-field__messages\">\r\n <Transition name=\"j-field-message\" mode=\"out-in\">\r\n <p v-if=\"errorMessage\" :id=\"errorId\" :key=\"errorMessage\" class=\"j-field__error\">\r\n {{ errorMessage }}\r\n </p>\r\n <p v-else-if=\"showDescription\" :id=\"descriptionId\" class=\"j-field__description\">\r\n {{ description }}\r\n </p>\r\n </Transition>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-field {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n min-width: 0;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-field__label {\r\n display: inline-flex;\r\n align-items: baseline;\r\n gap: var(--juxt-space-0-5);\r\n width: fit-content;\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-field.is-disabled .j-field__label {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-field__required {\r\n color: var(--juxt-fg-muted);\r\n font-weight: var(--juxt-weight-regular);\r\n}\r\n\r\n.j-field__messages:empty {\r\n display: none;\r\n}\r\n\r\n.j-field__description,\r\n.j-field__error {\r\n margin: 0;\r\n font-size: var(--juxt-text-xs);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-field__description {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-field__error {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-field-message-enter-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-field-message-leave-active {\r\n transition: opacity var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-field-message-enter-from {\r\n opacity: 0;\r\n transform: translateY(calc(var(--juxt-motion-shift) * -0.5));\r\n}\r\n\r\n.j-field-message-leave-to {\r\n opacity: 0;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\n/**\r\n * Internal: lays out label, control, description and error for form fields.\r\n * Not exported | every field component composes it.\r\n */\r\ndefineProps<{\r\n id: string\r\n labelId: string\r\n descriptionId: string\r\n errorId: string\r\n label?: string\r\n description?: string\r\n errorMessage?: string\r\n showDescription?: boolean\r\n required?: boolean\r\n disabled?: boolean\r\n /** Render the label as a plain element when the control isn't labelable (e.g. a button). */\r\n labelFor?: string | null\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-field\" :class=\"{ 'is-disabled': disabled }\">\r\n <component\r\n :is=\"labelFor === null ? 'span' : 'label'\"\r\n v-if=\"label || $slots.label\"\r\n :id=\"labelId\"\r\n class=\"j-field__label\"\r\n :for=\"labelFor === null ? undefined : labelFor ?? id\"\r\n >\r\n <slot name=\"label\">{{ label }}</slot>\r\n <span v-if=\"required\" class=\"j-field__required\" aria-hidden=\"true\">*</span>\r\n </component>\r\n <slot />\r\n <div class=\"j-field__messages\">\r\n <Transition name=\"j-field-message\" mode=\"out-in\">\r\n <p v-if=\"errorMessage\" :id=\"errorId\" :key=\"errorMessage\" class=\"j-field__error\">\r\n {{ errorMessage }}\r\n </p>\r\n <p v-else-if=\"showDescription\" :id=\"descriptionId\" class=\"j-field__description\">\r\n {{ description }}\r\n </p>\r\n </Transition>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-field {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n min-width: 0;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-field__label {\r\n display: inline-flex;\r\n align-items: baseline;\r\n gap: var(--juxt-space-0-5);\r\n width: fit-content;\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-field.is-disabled .j-field__label {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-field__required {\r\n color: var(--juxt-fg-muted);\r\n font-weight: var(--juxt-weight-regular);\r\n}\r\n\r\n.j-field__messages:empty {\r\n display: none;\r\n}\r\n\r\n.j-field__description,\r\n.j-field__error {\r\n margin: 0;\r\n font-size: var(--juxt-text-xs);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-field__description {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-field__error {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-field-message-enter-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-field-message-leave-active {\r\n transition: opacity var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-field-message-enter-from {\r\n opacity: 0;\r\n transform: translateY(calc(var(--juxt-motion-shift) * -0.5));\r\n}\r\n\r\n.j-field-message-leave-to {\r\n opacity: 0;\r\n}\r\n}\r\n</style>\r\n","import type { MaybeRefOrGetter } from 'vue'\r\nimport { computed, toValue, useId } from 'vue'\r\n\r\nexport interface FieldProps {\r\n /** Visible label. Also used as the accessible name. */\r\n label?: string\r\n /** Supporting text under the control. Hidden while an error message is shown. */\r\n description?: string\r\n /** An error message, or `true` to mark the field invalid without a message. */\r\n error?: string | boolean\r\n required?: boolean\r\n disabled?: boolean\r\n /** Id for the control. Generated when omitted. */\r\n id?: string\r\n}\r\n\r\n/** Ids and ARIA wiring shared by every form control. */\r\nexport function useField(props: MaybeRefOrGetter<FieldProps>) {\r\n const generated = useId()\r\n const p = () => toValue(props)\r\n\r\n const id = computed(() => p().id ?? `j-${generated}`)\r\n const labelId = computed(() => `${id.value}-label`)\r\n const descriptionId = computed(() => `${id.value}-description`)\r\n const errorId = computed(() => `${id.value}-error`)\r\n\r\n const invalid = computed(() => !!p().error)\r\n const errorMessage = computed(() => (typeof p().error === 'string' ? (p().error as string) : ''))\r\n const showDescription = computed(() => !!p().description && !errorMessage.value)\r\n\r\n const describedBy = computed(() => {\r\n const ids: string[] = []\r\n if (errorMessage.value) ids.push(errorId.value)\r\n else if (p().description) ids.push(descriptionId.value)\r\n return ids.length ? ids.join(' ') : undefined\r\n })\r\n\r\n return { id, labelId, descriptionId, errorId, invalid, errorMessage, showDescription, describedBy }\r\n}\r\n","<script setup lang=\"ts\">\r\nimport type { FieldProps } from '../field/useField'\r\nimport { ref } from 'vue'\r\nimport JFieldShell from '../field/JFieldShell.vue'\r\nimport { useField } from '../field/useField'\r\n\r\nexport type InputSize = 'sm' | 'md' | 'lg'\r\n\r\nexport interface InputProps extends FieldProps {\r\n type?: string\r\n placeholder?: string\r\n size?: InputSize\r\n readonly?: boolean\r\n name?: string\r\n autocomplete?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<InputProps>(), {\r\n type: 'text',\r\n size: 'md',\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<string | number | null>({ default: '' })\r\n\r\ndefineSlots<{\r\n leading?: () => unknown\r\n trailing?: () => unknown\r\n label?: () => unknown\r\n}>()\r\n\r\nconst field = useField(props)\r\nconst input = ref<HTMLInputElement | null>(null)\r\n\r\n// Clicking the frame (padding, icons) focuses the input without a blur flicker.\r\nfunction onFramePointerDown(event: PointerEvent) {\r\n if (event.target === input.value || props.disabled) return\r\n if ((event.target as HTMLElement).closest('button, a, input, select, textarea, [tabindex]')) return\r\n event.preventDefault()\r\n input.value?.focus()\r\n}\r\n\r\ndefineExpose({\r\n /** The underlying `<input>` element. */\r\n input,\r\n focus: () => input.value?.focus(),\r\n select: () => input.value?.select(),\r\n})\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n <div\r\n class=\"j-control j-input\"\r\n :class=\"[`j-control--${size}`, { 'is-invalid': field.invalid.value, 'is-disabled': disabled, 'is-readonly': readonly }]\"\r\n @pointerdown=\"onFramePointerDown\"\r\n >\r\n <span v-if=\"$slots.leading\" class=\"j-control__adornment j-control__adornment--leading\">\r\n <slot name=\"leading\" />\r\n </span>\r\n <input\r\n :id=\"field.id.value\"\r\n ref=\"input\"\r\n v-model=\"model\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n class=\"j-input__native\"\r\n :type=\"type\"\r\n :name=\"name\"\r\n :placeholder=\"placeholder\"\r\n :disabled=\"disabled\"\r\n :readonly=\"readonly\"\r\n :required=\"required\"\r\n :autocomplete=\"autocomplete\"\r\n :aria-invalid=\"field.invalid.value || undefined\"\r\n :aria-describedby=\"field.describedBy.value\"\r\n />\r\n <span v-if=\"$slots.trailing\" class=\"j-control__adornment j-control__adornment--trailing\">\r\n <slot name=\"trailing\" />\r\n </span>\r\n </div>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-input__native {\r\n flex: 1;\r\n width: 100%;\r\n min-width: 0;\r\n height: calc(var(--j-control-height) - 2px);\r\n padding: 0;\r\n border: 0;\r\n outline: 0;\r\n background: transparent;\r\n color: inherit;\r\n font: inherit;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-input__native::placeholder {\r\n color: var(--juxt-fg-muted);\r\n opacity: 1;\r\n}\r\n\r\n.j-input__native:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-input__native::-webkit-search-cancel-button {\r\n display: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { FieldProps } from '../field/useField'\r\nimport { ref } from 'vue'\r\nimport JFieldShell from '../field/JFieldShell.vue'\r\nimport { useField } from '../field/useField'\r\n\r\nexport type InputSize = 'sm' | 'md' | 'lg'\r\n\r\nexport interface InputProps extends FieldProps {\r\n type?: string\r\n placeholder?: string\r\n size?: InputSize\r\n readonly?: boolean\r\n name?: string\r\n autocomplete?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<InputProps>(), {\r\n type: 'text',\r\n size: 'md',\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<string | number | null>({ default: '' })\r\n\r\ndefineSlots<{\r\n leading?: () => unknown\r\n trailing?: () => unknown\r\n label?: () => unknown\r\n}>()\r\n\r\nconst field = useField(props)\r\nconst input = ref<HTMLInputElement | null>(null)\r\n\r\n// Clicking the frame (padding, icons) focuses the input without a blur flicker.\r\nfunction onFramePointerDown(event: PointerEvent) {\r\n if (event.target === input.value || props.disabled) return\r\n if ((event.target as HTMLElement).closest('button, a, input, select, textarea, [tabindex]')) return\r\n event.preventDefault()\r\n input.value?.focus()\r\n}\r\n\r\ndefineExpose({\r\n /** The underlying `<input>` element. */\r\n input,\r\n focus: () => input.value?.focus(),\r\n select: () => input.value?.select(),\r\n})\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n <div\r\n class=\"j-control j-input\"\r\n :class=\"[`j-control--${size}`, { 'is-invalid': field.invalid.value, 'is-disabled': disabled, 'is-readonly': readonly }]\"\r\n @pointerdown=\"onFramePointerDown\"\r\n >\r\n <span v-if=\"$slots.leading\" class=\"j-control__adornment j-control__adornment--leading\">\r\n <slot name=\"leading\" />\r\n </span>\r\n <input\r\n :id=\"field.id.value\"\r\n ref=\"input\"\r\n v-model=\"model\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n class=\"j-input__native\"\r\n :type=\"type\"\r\n :name=\"name\"\r\n :placeholder=\"placeholder\"\r\n :disabled=\"disabled\"\r\n :readonly=\"readonly\"\r\n :required=\"required\"\r\n :autocomplete=\"autocomplete\"\r\n :aria-invalid=\"field.invalid.value || undefined\"\r\n :aria-describedby=\"field.describedBy.value\"\r\n />\r\n <span v-if=\"$slots.trailing\" class=\"j-control__adornment j-control__adornment--trailing\">\r\n <slot name=\"trailing\" />\r\n </span>\r\n </div>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-input__native {\r\n flex: 1;\r\n width: 100%;\r\n min-width: 0;\r\n height: calc(var(--j-control-height) - 2px);\r\n padding: 0;\r\n border: 0;\r\n outline: 0;\r\n background: transparent;\r\n color: inherit;\r\n font: inherit;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-input__native::placeholder {\r\n color: var(--juxt-fg-muted);\r\n opacity: 1;\r\n}\r\n\r\n.j-input__native:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-input__native::-webkit-search-cancel-button {\r\n display: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, onMounted, ref } from 'vue'\r\nimport { formatHotkey } from '../../composables/useHotkey'\r\n\r\nexport interface KbdProps {\r\n /**\r\n * A shortcut in hotkey syntax, e.g. `mod+k` or `shift+enter`. `mod` renders\r\n * as ⌘ on Apple platforms and Ctrl elsewhere. Use the slot for literal keys.\r\n */\r\n keys?: string\r\n size?: 'sm' | 'md'\r\n}\r\n\r\nconst props = withDefaults(defineProps<KbdProps>(), { size: 'md' })\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\n// Platform glyphs are only known in the browser; resolve after mount so\r\n// server and client render the same markup.\r\nconst mounted = ref(false)\r\nonMounted(() => (mounted.value = true))\r\n\r\nconst parts = computed(() => {\r\n if (!props.keys) return []\r\n if (!mounted.value) return props.keys.split('+').map(part => (part === 'mod' ? 'Ctrl' : part.length === 1 ? part.toUpperCase() : part))\r\n return formatHotkey(props.keys)\r\n})\r\n</script>\r\n\r\n<template>\r\n <span v-if=\"keys\" class=\"j-kbd-group\" :class=\"`j-kbd-group--${size}`\">\r\n <kbd v-for=\"(part, i) in parts\" :key=\"i\" class=\"j-kbd\">{{ part }}</kbd>\r\n </span>\r\n <kbd v-else class=\"j-kbd\" :class=\"{ 'j-kbd--sm': size === 'sm' }\"><slot /></kbd>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-kbd-group {\r\n display: inline-flex;\r\n gap: 3px;\r\n vertical-align: middle;\r\n}\r\n\r\n.j-kbd-group--sm .j-kbd,\r\n.j-kbd--sm {\r\n min-width: 1.0625rem;\r\n height: 1.0625rem;\r\n font-size: 0.625rem;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, onMounted, ref } from 'vue'\r\nimport { formatHotkey } from '../../composables/useHotkey'\r\n\r\nexport interface KbdProps {\r\n /**\r\n * A shortcut in hotkey syntax, e.g. `mod+k` or `shift+enter`. `mod` renders\r\n * as ⌘ on Apple platforms and Ctrl elsewhere. Use the slot for literal keys.\r\n */\r\n keys?: string\r\n size?: 'sm' | 'md'\r\n}\r\n\r\nconst props = withDefaults(defineProps<KbdProps>(), { size: 'md' })\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\n// Platform glyphs are only known in the browser; resolve after mount so\r\n// server and client render the same markup.\r\nconst mounted = ref(false)\r\nonMounted(() => (mounted.value = true))\r\n\r\nconst parts = computed(() => {\r\n if (!props.keys) return []\r\n if (!mounted.value) return props.keys.split('+').map(part => (part === 'mod' ? 'Ctrl' : part.length === 1 ? part.toUpperCase() : part))\r\n return formatHotkey(props.keys)\r\n})\r\n</script>\r\n\r\n<template>\r\n <span v-if=\"keys\" class=\"j-kbd-group\" :class=\"`j-kbd-group--${size}`\">\r\n <kbd v-for=\"(part, i) in parts\" :key=\"i\" class=\"j-kbd\">{{ part }}</kbd>\r\n </span>\r\n <kbd v-else class=\"j-kbd\" :class=\"{ 'j-kbd--sm': size === 'sm' }\"><slot /></kbd>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-kbd-group {\r\n display: inline-flex;\r\n gap: 3px;\r\n vertical-align: middle;\r\n}\r\n\r\n.j-kbd-group--sm .j-kbd,\r\n.j-kbd--sm {\r\n min-width: 1.0625rem;\r\n height: 1.0625rem;\r\n font-size: 0.625rem;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, nextTick, ref } from 'vue'\r\nimport { IconChevronLeft, IconChevronRight, IconMoreHorizontal } from '../../icons'\r\nimport { focusElement } from '../../utils/dom'\r\n\r\nexport interface PaginationProps {\r\n /** Total number of pages. */\r\n total: number\r\n /** Pages shown on each side of the current page before collapsing to an ellipsis. */\r\n siblingCount?: number\r\n /** Accessible name for the nav landmark. */\r\n label?: string\r\n size?: 'sm' | 'md'\r\n disabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<PaginationProps>(), {\r\n siblingCount: 1,\r\n size: 'md',\r\n})\r\n\r\nconst model = defineModel<number>({ default: 1 })\r\n\r\nconst list = ref<HTMLElement | null>(null)\r\n\r\nconst totalPages = computed(() => Math.max(1, Math.floor(props.total)))\r\nconst current = computed(() => Math.min(Math.max(1, model.value), totalPages.value))\r\n\r\ntype PageItem = number | 'ellipsis'\r\n\r\nconst pageItems = computed<PageItem[]>(() => {\r\n const total = totalPages.value\r\n const siblings = Math.max(0, props.siblingCount)\r\n const totalSlots = siblings * 2 + 5\r\n if (total <= totalSlots) return Array.from({ length: total }, (_, i) => i + 1)\r\n\r\n const left = Math.max(current.value - siblings, 1)\r\n const right = Math.min(current.value + siblings, total)\r\n const showLeftEllipsis = left > 2\r\n const showRightEllipsis = right < total - 1\r\n\r\n const items: PageItem[] = [1]\r\n if (showLeftEllipsis) items.push('ellipsis')\r\n else for (let p = 2; p < left; p++) items.push(p)\r\n for (let p = Math.max(left, 2); p <= Math.min(right, total - 1); p++) items.push(p)\r\n if (showRightEllipsis) items.push('ellipsis')\r\n else for (let p = right + 1; p < total; p++) items.push(p)\r\n if (total > 1) items.push(total)\r\n return items\r\n})\r\n\r\nfunction goTo(page: number, focus = false) {\r\n if (props.disabled) return\r\n model.value = Math.min(Math.max(1, page), totalPages.value)\r\n if (focus) nextTick(() => focusElement(list.value?.querySelector<HTMLElement>('[aria-current=\"page\"]')))\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const target = event.target as HTMLElement\r\n if (!target.classList.contains('j-pagination__page')) return\r\n if (event.key === 'Home') {\r\n event.preventDefault()\r\n goTo(1, true)\r\n }\r\n else if (event.key === 'End') {\r\n event.preventDefault()\r\n goTo(totalPages.value, true)\r\n }\r\n}\r\n</script>\r\n\r\n<template>\r\n <nav class=\"j-pagination\" :class=\"`j-pagination--${size}`\" :aria-label=\"label || 'Pagination'\">\r\n <button\r\n type=\"button\"\r\n class=\"j-pagination__nav j-focusable\"\r\n aria-label=\"Previous page\"\r\n :disabled=\"disabled || current === 1\"\r\n @click=\"goTo(current - 1, true)\"\r\n >\r\n <IconChevronLeft />\r\n </button>\r\n <ul ref=\"list\" class=\"j-pagination__list\" @keydown=\"onKeydown\">\r\n <li v-for=\"(item, index) in pageItems\" :key=\"index\">\r\n <span v-if=\"item === 'ellipsis'\" class=\"j-pagination__ellipsis\" aria-hidden=\"true\">\r\n <IconMoreHorizontal />\r\n </span>\r\n <button\r\n v-else\r\n type=\"button\"\r\n class=\"j-pagination__page j-focusable\"\r\n :class=\"{ 'is-current': item === current }\"\r\n :aria-current=\"item === current ? 'page' : undefined\"\r\n :aria-label=\"`Page ${item}`\"\r\n :disabled=\"disabled\"\r\n @click=\"goTo(item)\"\r\n >\r\n {{ item }}\r\n </button>\r\n </li>\r\n </ul>\r\n <button\r\n type=\"button\"\r\n class=\"j-pagination__nav j-focusable\"\r\n aria-label=\"Next page\"\r\n :disabled=\"disabled || current === totalPages\"\r\n @click=\"goTo(current + 1, true)\"\r\n >\r\n <IconChevronRight />\r\n </button>\r\n </nav>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-pagination {\r\n --j-pagination-size: var(--juxt-control-md);\r\n\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-pagination--sm {\r\n --j-pagination-size: var(--juxt-control-sm);\r\n}\r\n\r\n.j-pagination__list {\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1);\r\n margin: 0;\r\n padding: 0;\r\n list-style: none;\r\n}\r\n\r\n.j-pagination__nav,\r\n.j-pagination__page {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: var(--j-pagination-size);\r\n height: var(--j-pagination-size);\r\n border: 1px solid transparent;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg-secondary);\r\n font-family: inherit;\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n font-variant-numeric: tabular-nums;\r\n cursor: pointer;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-pagination__nav > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-pagination__nav:hover:not(:disabled),\r\n.j-pagination__page:hover:not(:disabled) {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-pagination__nav:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-pagination__page.is-current {\r\n border-color: var(--juxt-border);\r\n background: var(--juxt-surface-active);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-pagination__page:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-pagination__ellipsis {\r\n display: grid;\r\n place-items: center;\r\n width: var(--j-pagination-size);\r\n height: var(--j-pagination-size);\r\n color: var(--juxt-fg-disabled);\r\n}\r\n\r\n.j-pagination__ellipsis > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, nextTick, ref } from 'vue'\r\nimport { IconChevronLeft, IconChevronRight, IconMoreHorizontal } from '../../icons'\r\nimport { focusElement } from '../../utils/dom'\r\n\r\nexport interface PaginationProps {\r\n /** Total number of pages. */\r\n total: number\r\n /** Pages shown on each side of the current page before collapsing to an ellipsis. */\r\n siblingCount?: number\r\n /** Accessible name for the nav landmark. */\r\n label?: string\r\n size?: 'sm' | 'md'\r\n disabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<PaginationProps>(), {\r\n siblingCount: 1,\r\n size: 'md',\r\n})\r\n\r\nconst model = defineModel<number>({ default: 1 })\r\n\r\nconst list = ref<HTMLElement | null>(null)\r\n\r\nconst totalPages = computed(() => Math.max(1, Math.floor(props.total)))\r\nconst current = computed(() => Math.min(Math.max(1, model.value), totalPages.value))\r\n\r\ntype PageItem = number | 'ellipsis'\r\n\r\nconst pageItems = computed<PageItem[]>(() => {\r\n const total = totalPages.value\r\n const siblings = Math.max(0, props.siblingCount)\r\n const totalSlots = siblings * 2 + 5\r\n if (total <= totalSlots) return Array.from({ length: total }, (_, i) => i + 1)\r\n\r\n const left = Math.max(current.value - siblings, 1)\r\n const right = Math.min(current.value + siblings, total)\r\n const showLeftEllipsis = left > 2\r\n const showRightEllipsis = right < total - 1\r\n\r\n const items: PageItem[] = [1]\r\n if (showLeftEllipsis) items.push('ellipsis')\r\n else for (let p = 2; p < left; p++) items.push(p)\r\n for (let p = Math.max(left, 2); p <= Math.min(right, total - 1); p++) items.push(p)\r\n if (showRightEllipsis) items.push('ellipsis')\r\n else for (let p = right + 1; p < total; p++) items.push(p)\r\n if (total > 1) items.push(total)\r\n return items\r\n})\r\n\r\nfunction goTo(page: number, focus = false) {\r\n if (props.disabled) return\r\n model.value = Math.min(Math.max(1, page), totalPages.value)\r\n if (focus) nextTick(() => focusElement(list.value?.querySelector<HTMLElement>('[aria-current=\"page\"]')))\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const target = event.target as HTMLElement\r\n if (!target.classList.contains('j-pagination__page')) return\r\n if (event.key === 'Home') {\r\n event.preventDefault()\r\n goTo(1, true)\r\n }\r\n else if (event.key === 'End') {\r\n event.preventDefault()\r\n goTo(totalPages.value, true)\r\n }\r\n}\r\n</script>\r\n\r\n<template>\r\n <nav class=\"j-pagination\" :class=\"`j-pagination--${size}`\" :aria-label=\"label || 'Pagination'\">\r\n <button\r\n type=\"button\"\r\n class=\"j-pagination__nav j-focusable\"\r\n aria-label=\"Previous page\"\r\n :disabled=\"disabled || current === 1\"\r\n @click=\"goTo(current - 1, true)\"\r\n >\r\n <IconChevronLeft />\r\n </button>\r\n <ul ref=\"list\" class=\"j-pagination__list\" @keydown=\"onKeydown\">\r\n <li v-for=\"(item, index) in pageItems\" :key=\"index\">\r\n <span v-if=\"item === 'ellipsis'\" class=\"j-pagination__ellipsis\" aria-hidden=\"true\">\r\n <IconMoreHorizontal />\r\n </span>\r\n <button\r\n v-else\r\n type=\"button\"\r\n class=\"j-pagination__page j-focusable\"\r\n :class=\"{ 'is-current': item === current }\"\r\n :aria-current=\"item === current ? 'page' : undefined\"\r\n :aria-label=\"`Page ${item}`\"\r\n :disabled=\"disabled\"\r\n @click=\"goTo(item)\"\r\n >\r\n {{ item }}\r\n </button>\r\n </li>\r\n </ul>\r\n <button\r\n type=\"button\"\r\n class=\"j-pagination__nav j-focusable\"\r\n aria-label=\"Next page\"\r\n :disabled=\"disabled || current === totalPages\"\r\n @click=\"goTo(current + 1, true)\"\r\n >\r\n <IconChevronRight />\r\n </button>\r\n </nav>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-pagination {\r\n --j-pagination-size: var(--juxt-control-md);\r\n\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-pagination--sm {\r\n --j-pagination-size: var(--juxt-control-sm);\r\n}\r\n\r\n.j-pagination__list {\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1);\r\n margin: 0;\r\n padding: 0;\r\n list-style: none;\r\n}\r\n\r\n.j-pagination__nav,\r\n.j-pagination__page {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: var(--j-pagination-size);\r\n height: var(--j-pagination-size);\r\n border: 1px solid transparent;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg-secondary);\r\n font-family: inherit;\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n font-variant-numeric: tabular-nums;\r\n cursor: pointer;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-pagination__nav > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-pagination__nav:hover:not(:disabled),\r\n.j-pagination__page:hover:not(:disabled) {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-pagination__nav:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-pagination__page.is-current {\r\n border-color: var(--juxt-border);\r\n background: var(--juxt-surface-active);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-pagination__page:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-pagination__ellipsis {\r\n display: grid;\r\n place-items: center;\r\n width: var(--j-pagination-size);\r\n height: var(--j-pagination-size);\r\n color: var(--juxt-fg-disabled);\r\n}\r\n\r\n.j-pagination__ellipsis > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Placement } from '../../composables/useFloating'\r\nimport { computed, nextTick, ref, useId, watch } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { focusElement, getFocusableElements, unrefElement } from '../../utils/dom'\r\nimport JPortal from '../primitives/JPortal.vue'\r\nimport { JSlot } from '../primitives/JSlot'\r\n\r\nexport interface PopoverProps {\r\n placement?: Placement\r\n /** Distance from the trigger in px. */\r\n offset?: number\r\n /** Accessible name of the popover content. */\r\n ariaLabel?: string\r\n /** Close when focus or a click leaves the popover. Defaults to true. */\r\n closeOnOutside?: boolean\r\n disabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<PopoverProps>(), {\r\n placement: 'bottom',\r\n offset: 8,\r\n closeOnOutside: true,\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\n\r\ndefineSlots<{\r\n trigger?: (props: { open: boolean }) => unknown\r\n default?: (props: { close: () => void }) => unknown\r\n}>()\r\n\r\nconst id = `j-${useId()}-popover`\r\nconst triggerRef = ref()\r\nconst triggerEl = computed(() => unrefElement(triggerRef.value))\r\nconst content = ref<HTMLElement | null>(null)\r\n\r\nconst { styles } = useFloating(triggerEl, content, () => ({ placement: props.placement, offset: props.offset }))\r\n\r\nfunction close(restoreFocus = false) {\r\n if (!open.value) return\r\n open.value = false\r\n if (restoreFocus) nextTick(() => focusElement(triggerEl.value))\r\n}\r\n\r\nuseLayer({\r\n active: open,\r\n elements: () => [triggerEl.value, content.value],\r\n onEscape: () => close(true),\r\n onPointerDownOutside: () => props.closeOnOutside && close(false),\r\n})\r\n\r\n// Moving focus in makes the content reachable without a trap: this is a\r\n// non-modal surface, so Tab is allowed to leave (and that closes it).\r\nwatch(open, async (value) => {\r\n if (!value) return\r\n await nextTick()\r\n const el = content.value\r\n if (!el) return\r\n focusElement(el.querySelector<HTMLElement>('[autofocus], [data-autofocus]') ?? getFocusableElements(el)[0] ?? el)\r\n}, { flush: 'post' })\r\n\r\nfunction onFocusOut(event: FocusEvent) {\r\n if (!props.closeOnOutside) return\r\n const next = event.relatedTarget as Node | null\r\n if (!next) return\r\n if (content.value?.contains(next) || triggerEl.value?.contains(next)) return\r\n close(false)\r\n}\r\n\r\nfunction toggle() {\r\n if (props.disabled) return\r\n open.value = !open.value\r\n}\r\n\r\nconst triggerProps = computed(() => ({\r\n 'aria-haspopup': 'dialog',\r\n 'aria-expanded': open.value,\r\n 'aria-controls': open.value ? id : undefined,\r\n 'data-state': open.value ? 'open' : 'closed',\r\n 'onClick': toggle,\r\n}))\r\n\r\ndefineExpose({ close: () => close(true) })\r\n</script>\r\n\r\n<template>\r\n <JSlot ref=\"triggerRef\" v-bind=\"triggerProps\">\r\n <slot name=\"trigger\" :open=\"open\" />\r\n </JSlot>\r\n <JPortal>\r\n <Transition name=\"j-pop\">\r\n <div\r\n v-if=\"open\"\r\n :id=\"id\"\r\n ref=\"content\"\r\n role=\"dialog\"\r\n tabindex=\"-1\"\r\n class=\"j-popover j-popover-surface\"\r\n :style=\"styles\"\r\n :aria-label=\"ariaLabel\"\r\n @focusout=\"onFocusOut\"\r\n >\r\n <slot :close=\"() => close(true)\" />\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-popover {\r\n z-index: var(--juxt-z-popover);\r\n box-sizing: border-box;\r\n max-width: calc(100vw - 16px);\r\n max-height: var(--j-available-height, 32rem);\r\n padding: var(--juxt-space-4);\r\n overflow: auto;\r\n outline: none;\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Placement } from '../../composables/useFloating'\r\nimport { computed, nextTick, ref, useId, watch } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { focusElement, getFocusableElements, unrefElement } from '../../utils/dom'\r\nimport JPortal from '../primitives/JPortal.vue'\r\nimport { JSlot } from '../primitives/JSlot'\r\n\r\nexport interface PopoverProps {\r\n placement?: Placement\r\n /** Distance from the trigger in px. */\r\n offset?: number\r\n /** Accessible name of the popover content. */\r\n ariaLabel?: string\r\n /** Close when focus or a click leaves the popover. Defaults to true. */\r\n closeOnOutside?: boolean\r\n disabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<PopoverProps>(), {\r\n placement: 'bottom',\r\n offset: 8,\r\n closeOnOutside: true,\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\n\r\ndefineSlots<{\r\n trigger?: (props: { open: boolean }) => unknown\r\n default?: (props: { close: () => void }) => unknown\r\n}>()\r\n\r\nconst id = `j-${useId()}-popover`\r\nconst triggerRef = ref()\r\nconst triggerEl = computed(() => unrefElement(triggerRef.value))\r\nconst content = ref<HTMLElement | null>(null)\r\n\r\nconst { styles } = useFloating(triggerEl, content, () => ({ placement: props.placement, offset: props.offset }))\r\n\r\nfunction close(restoreFocus = false) {\r\n if (!open.value) return\r\n open.value = false\r\n if (restoreFocus) nextTick(() => focusElement(triggerEl.value))\r\n}\r\n\r\nuseLayer({\r\n active: open,\r\n elements: () => [triggerEl.value, content.value],\r\n onEscape: () => close(true),\r\n onPointerDownOutside: () => props.closeOnOutside && close(false),\r\n})\r\n\r\n// Moving focus in makes the content reachable without a trap: this is a\r\n// non-modal surface, so Tab is allowed to leave (and that closes it).\r\nwatch(open, async (value) => {\r\n if (!value) return\r\n await nextTick()\r\n const el = content.value\r\n if (!el) return\r\n focusElement(el.querySelector<HTMLElement>('[autofocus], [data-autofocus]') ?? getFocusableElements(el)[0] ?? el)\r\n}, { flush: 'post' })\r\n\r\nfunction onFocusOut(event: FocusEvent) {\r\n if (!props.closeOnOutside) return\r\n const next = event.relatedTarget as Node | null\r\n if (!next) return\r\n if (content.value?.contains(next) || triggerEl.value?.contains(next)) return\r\n close(false)\r\n}\r\n\r\nfunction toggle() {\r\n if (props.disabled) return\r\n open.value = !open.value\r\n}\r\n\r\nconst triggerProps = computed(() => ({\r\n 'aria-haspopup': 'dialog',\r\n 'aria-expanded': open.value,\r\n 'aria-controls': open.value ? id : undefined,\r\n 'data-state': open.value ? 'open' : 'closed',\r\n 'onClick': toggle,\r\n}))\r\n\r\ndefineExpose({ close: () => close(true) })\r\n</script>\r\n\r\n<template>\r\n <JSlot ref=\"triggerRef\" v-bind=\"triggerProps\">\r\n <slot name=\"trigger\" :open=\"open\" />\r\n </JSlot>\r\n <JPortal>\r\n <Transition name=\"j-pop\">\r\n <div\r\n v-if=\"open\"\r\n :id=\"id\"\r\n ref=\"content\"\r\n role=\"dialog\"\r\n tabindex=\"-1\"\r\n class=\"j-popover j-popover-surface\"\r\n :style=\"styles\"\r\n :aria-label=\"ariaLabel\"\r\n @focusout=\"onFocusOut\"\r\n >\r\n <slot :close=\"() => close(true)\" />\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-popover {\r\n z-index: var(--juxt-z-popover);\r\n box-sizing: border-box;\r\n max-width: calc(100vw - 16px);\r\n max-height: var(--j-available-height, 32rem);\r\n padding: var(--juxt-space-4);\r\n overflow: auto;\r\n outline: none;\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, useId } from 'vue'\r\n\r\nexport interface ProgressProps {\r\n /** Current value. `null` shows an indeterminate bar. */\r\n value?: number | null\r\n max?: number\r\n size?: 'sm' | 'md'\r\n /** Visible label above the bar; also its accessible name. */\r\n label?: string\r\n /** Show the percentage next to the label. */\r\n showValue?: boolean\r\n /** `accent` for progress toward a goal, `neutral` for plain measurement. */\r\n variant?: 'accent' | 'neutral'\r\n}\r\n\r\nconst props = withDefaults(defineProps<ProgressProps>(), {\r\n value: null,\r\n max: 100,\r\n size: 'md',\r\n variant: 'accent',\r\n})\r\n\r\nconst id = `j-${useId()}`\r\nconst indeterminate = computed(() => props.value === null || props.value === undefined)\r\nconst ratio = computed(() => (indeterminate.value ? 0 : Math.min(1, Math.max(0, Number(props.value) / props.max))))\r\nconst percent = computed(() => Math.round(ratio.value * 100))\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-progress\" :class=\"[`j-progress--${size}`, `j-progress--${variant}`, { 'is-indeterminate': indeterminate }]\">\r\n <div v-if=\"label || showValue\" class=\"j-progress__header\">\r\n <span v-if=\"label\" :id=\"`${id}-label`\" class=\"j-progress__label\">{{ label }}</span>\r\n <span v-if=\"showValue && !indeterminate\" class=\"j-progress__value\">{{ percent }}%</span>\r\n </div>\r\n <div\r\n class=\"j-progress__track\"\r\n role=\"progressbar\"\r\n :aria-labelledby=\"label ? `${id}-label` : undefined\"\r\n aria-valuemin=\"0\"\r\n :aria-valuemax=\"max\"\r\n :aria-valuenow=\"indeterminate ? undefined : value!\"\r\n :aria-valuetext=\"indeterminate ? 'Loading' : `${percent}%`\"\r\n >\r\n <span class=\"j-progress__fill\" :style=\"indeterminate ? undefined : { transform: `scaleX(${ratio})` }\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-progress {\r\n --j-progress-height: 0.375rem;\r\n --j-progress-fill: var(--juxt-accent);\r\n\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-2);\r\n width: 100%;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-progress--sm {\r\n --j-progress-height: 0.25rem;\r\n}\r\n\r\n.j-progress--neutral {\r\n --j-progress-fill: var(--juxt-fg);\r\n}\r\n\r\n.j-progress__header {\r\n display: flex;\r\n align-items: baseline;\r\n justify-content: space-between;\r\n gap: var(--juxt-space-3);\r\n font-size: var(--juxt-text-sm);\r\n}\r\n\r\n.j-progress__label {\r\n color: var(--juxt-fg);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-progress__value {\r\n color: var(--juxt-fg-muted);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-progress__track {\r\n position: relative;\r\n height: var(--j-progress-height);\r\n overflow: hidden;\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n/* Width changes are transforms, so updates glide without reflow. */\r\n.j-progress__fill {\r\n position: absolute;\r\n inset: 0;\r\n border-radius: inherit;\r\n background: var(--j-progress-fill);\r\n transform: scaleX(0);\r\n transform-origin: left;\r\n transition: transform var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-progress.is-indeterminate .j-progress__fill {\r\n width: 40%;\r\n transform: none;\r\n animation: j-progress-indeterminate 1.4s var(--juxt-ease-standard) infinite;\r\n}\r\n\r\n@keyframes j-progress-indeterminate {\r\n from {\r\n transform: translateX(-100%);\r\n }\r\n\r\n to {\r\n transform: translateX(250%);\r\n }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-progress.is-indeterminate .j-progress__fill {\r\n width: 100%;\r\n animation: j-progress-pulse 1.6s ease-in-out infinite;\r\n }\r\n\r\n @keyframes j-progress-pulse {\r\n 50% {\r\n opacity: 0.35;\r\n }\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, useId } from 'vue'\r\n\r\nexport interface ProgressProps {\r\n /** Current value. `null` shows an indeterminate bar. */\r\n value?: number | null\r\n max?: number\r\n size?: 'sm' | 'md'\r\n /** Visible label above the bar; also its accessible name. */\r\n label?: string\r\n /** Show the percentage next to the label. */\r\n showValue?: boolean\r\n /** `accent` for progress toward a goal, `neutral` for plain measurement. */\r\n variant?: 'accent' | 'neutral'\r\n}\r\n\r\nconst props = withDefaults(defineProps<ProgressProps>(), {\r\n value: null,\r\n max: 100,\r\n size: 'md',\r\n variant: 'accent',\r\n})\r\n\r\nconst id = `j-${useId()}`\r\nconst indeterminate = computed(() => props.value === null || props.value === undefined)\r\nconst ratio = computed(() => (indeterminate.value ? 0 : Math.min(1, Math.max(0, Number(props.value) / props.max))))\r\nconst percent = computed(() => Math.round(ratio.value * 100))\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-progress\" :class=\"[`j-progress--${size}`, `j-progress--${variant}`, { 'is-indeterminate': indeterminate }]\">\r\n <div v-if=\"label || showValue\" class=\"j-progress__header\">\r\n <span v-if=\"label\" :id=\"`${id}-label`\" class=\"j-progress__label\">{{ label }}</span>\r\n <span v-if=\"showValue && !indeterminate\" class=\"j-progress__value\">{{ percent }}%</span>\r\n </div>\r\n <div\r\n class=\"j-progress__track\"\r\n role=\"progressbar\"\r\n :aria-labelledby=\"label ? `${id}-label` : undefined\"\r\n aria-valuemin=\"0\"\r\n :aria-valuemax=\"max\"\r\n :aria-valuenow=\"indeterminate ? undefined : value!\"\r\n :aria-valuetext=\"indeterminate ? 'Loading' : `${percent}%`\"\r\n >\r\n <span class=\"j-progress__fill\" :style=\"indeterminate ? undefined : { transform: `scaleX(${ratio})` }\" />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-progress {\r\n --j-progress-height: 0.375rem;\r\n --j-progress-fill: var(--juxt-accent);\r\n\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-2);\r\n width: 100%;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-progress--sm {\r\n --j-progress-height: 0.25rem;\r\n}\r\n\r\n.j-progress--neutral {\r\n --j-progress-fill: var(--juxt-fg);\r\n}\r\n\r\n.j-progress__header {\r\n display: flex;\r\n align-items: baseline;\r\n justify-content: space-between;\r\n gap: var(--juxt-space-3);\r\n font-size: var(--juxt-text-sm);\r\n}\r\n\r\n.j-progress__label {\r\n color: var(--juxt-fg);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-progress__value {\r\n color: var(--juxt-fg-muted);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-progress__track {\r\n position: relative;\r\n height: var(--j-progress-height);\r\n overflow: hidden;\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n/* Width changes are transforms, so updates glide without reflow. */\r\n.j-progress__fill {\r\n position: absolute;\r\n inset: 0;\r\n border-radius: inherit;\r\n background: var(--j-progress-fill);\r\n transform: scaleX(0);\r\n transform-origin: left;\r\n transition: transform var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-progress.is-indeterminate .j-progress__fill {\r\n width: 40%;\r\n transform: none;\r\n animation: j-progress-indeterminate 1.4s var(--juxt-ease-standard) infinite;\r\n}\r\n\r\n@keyframes j-progress-indeterminate {\r\n from {\r\n transform: translateX(-100%);\r\n }\r\n\r\n to {\r\n transform: translateX(250%);\r\n }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-progress.is-indeterminate .j-progress__fill {\r\n width: 100%;\r\n animation: j-progress-pulse 1.6s ease-in-out infinite;\r\n }\r\n\r\n @keyframes j-progress-pulse {\r\n 50% {\r\n opacity: 0.35;\r\n }\r\n }\r\n}\r\n}\r\n</style>\r\n","import type { ComputedRef, InjectionKey } from 'vue'\n\nexport type RadioValue = string | number\n\nexport interface RadioGroupContext {\n name: ComputedRef<string>\n value: ComputedRef<RadioValue | null | undefined>\n disabled: ComputedRef<boolean>\n required: ComputedRef<boolean>\n invalid: ComputedRef<boolean>\n select: (value: RadioValue) => void\n}\n\nexport const RADIO_GROUP: InjectionKey<RadioGroupContext> = Symbol('JRadioGroup')\n","<script setup lang=\"ts\">\r\nimport type { RadioValue } from './context'\r\nimport { computed, inject, useId } from 'vue'\r\nimport { RADIO_GROUP } from './context'\r\n\r\nexport interface RadioProps {\r\n value: RadioValue\r\n label?: string\r\n description?: string\r\n disabled?: boolean\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = defineProps<RadioProps>()\r\n\r\ndefineSlots<{ default?: () => unknown, description?: () => unknown }>()\r\n\r\nconst group = inject(RADIO_GROUP, null)\r\n\r\nconst id = `j-${useId()}`\r\nconst descriptionId = `${id}-description`\r\nconst checked = computed(() => group?.value.value === props.value)\r\nconst isDisabled = computed(() => props.disabled || !!group?.disabled.value)\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-radio\"\r\n :class=\"[$attrs.class, { 'is-disabled': isDisabled }]\"\r\n :style=\"$attrs.style as any\"\r\n :data-state=\"checked ? 'checked' : 'unchecked'\"\r\n >\r\n <span class=\"j-radio__control\">\r\n <input\r\n :id=\"id\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"radio\"\r\n class=\"j-radio__input\"\r\n :name=\"group?.name.value\"\r\n :value=\"value\"\r\n :checked=\"checked\"\r\n :disabled=\"isDisabled\"\r\n :required=\"group?.required.value\"\r\n :aria-describedby=\"description || $slots.description ? descriptionId : undefined\"\r\n @change=\"group?.select(value)\"\r\n />\r\n <span class=\"j-radio__circle\" aria-hidden=\"true\" />\r\n </span>\r\n <span v-if=\"label || $slots.default || description || $slots.description\" class=\"j-radio__text\">\r\n <label v-if=\"label || $slots.default\" :for=\"id\" class=\"j-radio__label\">\r\n <slot>{{ label }}</slot>\r\n </label>\r\n <span v-if=\"description || $slots.description\" :id=\"descriptionId\" class=\"j-radio__description\">\r\n <slot name=\"description\">{{ description }}</slot>\r\n </span>\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-radio {\r\n --j-radio-size: 1rem;\r\n\r\n display: inline-flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-radio__control {\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n margin-top: calc((var(--juxt-text-md) * var(--juxt-leading-snug) - var(--j-radio-size)) / 2);\r\n}\r\n\r\n.j-radio__input {\r\n position: absolute;\r\n inset: -4px;\r\n z-index: 1;\r\n width: calc(100% + 8px);\r\n height: calc(100% + 8px);\r\n margin: 0;\r\n opacity: 0;\r\n cursor: pointer;\r\n}\r\n\r\n.j-radio__input:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-radio__circle {\r\n position: relative;\r\n display: block;\r\n width: var(--j-radio-size);\r\n height: var(--j-radio-size);\r\n border: 1px solid var(--juxt-border-strong);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-xs);\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n/* The inner dot grows out of the centre once the fill lands. */\r\n.j-radio__circle::after {\r\n content: '';\r\n position: absolute;\r\n inset: 0;\r\n margin: auto;\r\n width: 0.375rem;\r\n height: 0.375rem;\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-accent-fg);\r\n opacity: 0;\r\n transform: scale(0.3);\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-radio__input:hover:not(:disabled) + .j-radio__circle {\r\n border-color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-radio__input:active:not(:disabled) + .j-radio__circle {\r\n transform: scale(0.9);\r\n}\r\n\r\n.j-radio__input:focus-visible + .j-radio__circle {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-radio[data-state='checked'] .j-radio__circle {\r\n border-color: var(--juxt-accent);\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-radio[data-state='checked'] .j-radio__input:hover:not(:disabled) + .j-radio__circle {\r\n border-color: var(--juxt-accent-hover);\r\n background: var(--juxt-accent-hover);\r\n}\r\n\r\n.j-radio[data-state='checked'] .j-radio__circle::after {\r\n opacity: 1;\r\n transform: none;\r\n transition-delay: 30ms;\r\n}\r\n\r\n.j-radio-group.is-invalid .j-radio:not([data-state='checked']) .j-radio__circle {\r\n border-color: var(--juxt-danger-border);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-radio__input:active:not(:disabled) + .j-radio__circle {\r\n transform: none;\r\n }\r\n}\r\n\r\n.j-radio__text {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-radio__label {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-radio__description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-radio.is-disabled .j-radio__circle {\r\n opacity: 0.5;\r\n box-shadow: none;\r\n}\r\n\r\n.j-radio.is-disabled .j-radio__label {\r\n color: var(--juxt-fg-muted);\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { RadioValue } from './context'\r\nimport { computed, inject, useId } from 'vue'\r\nimport { RADIO_GROUP } from './context'\r\n\r\nexport interface RadioProps {\r\n value: RadioValue\r\n label?: string\r\n description?: string\r\n disabled?: boolean\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = defineProps<RadioProps>()\r\n\r\ndefineSlots<{ default?: () => unknown, description?: () => unknown }>()\r\n\r\nconst group = inject(RADIO_GROUP, null)\r\n\r\nconst id = `j-${useId()}`\r\nconst descriptionId = `${id}-description`\r\nconst checked = computed(() => group?.value.value === props.value)\r\nconst isDisabled = computed(() => props.disabled || !!group?.disabled.value)\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-radio\"\r\n :class=\"[$attrs.class, { 'is-disabled': isDisabled }]\"\r\n :style=\"$attrs.style as any\"\r\n :data-state=\"checked ? 'checked' : 'unchecked'\"\r\n >\r\n <span class=\"j-radio__control\">\r\n <input\r\n :id=\"id\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"radio\"\r\n class=\"j-radio__input\"\r\n :name=\"group?.name.value\"\r\n :value=\"value\"\r\n :checked=\"checked\"\r\n :disabled=\"isDisabled\"\r\n :required=\"group?.required.value\"\r\n :aria-describedby=\"description || $slots.description ? descriptionId : undefined\"\r\n @change=\"group?.select(value)\"\r\n />\r\n <span class=\"j-radio__circle\" aria-hidden=\"true\" />\r\n </span>\r\n <span v-if=\"label || $slots.default || description || $slots.description\" class=\"j-radio__text\">\r\n <label v-if=\"label || $slots.default\" :for=\"id\" class=\"j-radio__label\">\r\n <slot>{{ label }}</slot>\r\n </label>\r\n <span v-if=\"description || $slots.description\" :id=\"descriptionId\" class=\"j-radio__description\">\r\n <slot name=\"description\">{{ description }}</slot>\r\n </span>\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-radio {\r\n --j-radio-size: 1rem;\r\n\r\n display: inline-flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-radio__control {\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n margin-top: calc((var(--juxt-text-md) * var(--juxt-leading-snug) - var(--j-radio-size)) / 2);\r\n}\r\n\r\n.j-radio__input {\r\n position: absolute;\r\n inset: -4px;\r\n z-index: 1;\r\n width: calc(100% + 8px);\r\n height: calc(100% + 8px);\r\n margin: 0;\r\n opacity: 0;\r\n cursor: pointer;\r\n}\r\n\r\n.j-radio__input:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-radio__circle {\r\n position: relative;\r\n display: block;\r\n width: var(--j-radio-size);\r\n height: var(--j-radio-size);\r\n border: 1px solid var(--juxt-border-strong);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-xs);\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n border-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n/* The inner dot grows out of the centre once the fill lands. */\r\n.j-radio__circle::after {\r\n content: '';\r\n position: absolute;\r\n inset: 0;\r\n margin: auto;\r\n width: 0.375rem;\r\n height: 0.375rem;\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-accent-fg);\r\n opacity: 0;\r\n transform: scale(0.3);\r\n transition:\r\n opacity var(--juxt-duration-instant) var(--juxt-ease-standard),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-radio__input:hover:not(:disabled) + .j-radio__circle {\r\n border-color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-radio__input:active:not(:disabled) + .j-radio__circle {\r\n transform: scale(0.9);\r\n}\r\n\r\n.j-radio__input:focus-visible + .j-radio__circle {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-radio[data-state='checked'] .j-radio__circle {\r\n border-color: var(--juxt-accent);\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-radio[data-state='checked'] .j-radio__input:hover:not(:disabled) + .j-radio__circle {\r\n border-color: var(--juxt-accent-hover);\r\n background: var(--juxt-accent-hover);\r\n}\r\n\r\n.j-radio[data-state='checked'] .j-radio__circle::after {\r\n opacity: 1;\r\n transform: none;\r\n transition-delay: 30ms;\r\n}\r\n\r\n.j-radio-group.is-invalid .j-radio:not([data-state='checked']) .j-radio__circle {\r\n border-color: var(--juxt-danger-border);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-radio__input:active:not(:disabled) + .j-radio__circle {\r\n transform: none;\r\n }\r\n}\r\n\r\n.j-radio__text {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-radio__label {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-radio__description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-radio.is-disabled .j-radio__circle {\r\n opacity: 0.5;\r\n box-shadow: none;\r\n}\r\n\r\n.j-radio.is-disabled .j-radio__label {\r\n color: var(--juxt-fg-muted);\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { RadioValue } from './context'\r\nimport { computed, provide, useId } from 'vue'\r\nimport { RADIO_GROUP } from './context'\r\n\r\nexport interface RadioGroupProps {\r\n /** Group label, rendered as the fieldset legend. */\r\n label?: string\r\n description?: string\r\n /** An error message, or `true` to mark the group invalid without one. */\r\n error?: string | boolean\r\n orientation?: 'vertical' | 'horizontal'\r\n disabled?: boolean\r\n required?: boolean\r\n /** Form field name. Generated when omitted. */\r\n name?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<RadioGroupProps>(), { orientation: 'vertical', error: undefined })\r\n\r\nconst model = defineModel<RadioValue | null>({ default: null })\r\n\r\ndefineSlots<{ default?: () => unknown, label?: () => unknown }>()\r\n\r\nconst id = `j-${useId()}`\r\nconst descriptionId = `${id}-description`\r\nconst errorId = `${id}-error`\r\nconst errorMessage = computed(() => (typeof props.error === 'string' ? props.error : ''))\r\n\r\nprovide(RADIO_GROUP, {\r\n name: computed(() => props.name ?? id),\r\n value: computed(() => model.value),\r\n disabled: computed(() => props.disabled),\r\n required: computed(() => props.required),\r\n invalid: computed(() => !!props.error),\r\n select: (value) => {\r\n if (!props.disabled) model.value = value\r\n },\r\n})\r\n\r\nconst describedBy = computed(() => (errorMessage.value ? errorId : props.description ? descriptionId : undefined))\r\n</script>\r\n\r\n<template>\r\n <fieldset\r\n class=\"j-radio-group\"\r\n :class=\"{ 'is-disabled': disabled, 'is-invalid': !!error }\"\r\n :disabled=\"disabled\"\r\n :aria-describedby=\"describedBy\"\r\n :aria-invalid=\"!!error || undefined\"\r\n :aria-required=\"required || undefined\"\r\n >\r\n <legend v-if=\"label || $slots.label\" class=\"j-radio-group__legend\">\r\n <slot name=\"label\">{{ label }}</slot>\r\n <span v-if=\"required\" class=\"j-field__required\" aria-hidden=\"true\">*</span>\r\n </legend>\r\n <p v-if=\"description && !errorMessage\" :id=\"descriptionId\" class=\"j-radio-group__description\">\r\n {{ description }}\r\n </p>\r\n <div class=\"j-radio-group__items\" :class=\"`j-radio-group__items--${orientation}`\">\r\n <slot />\r\n </div>\r\n <p v-if=\"errorMessage\" :id=\"errorId\" class=\"j-radio-group__error\">\r\n {{ errorMessage }}\r\n </p>\r\n </fieldset>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-radio-group {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n min-width: 0;\r\n margin: 0;\r\n padding: 0;\r\n border: 0;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-radio-group__legend {\r\n display: inline-flex;\r\n gap: var(--juxt-space-0-5);\r\n margin-bottom: var(--juxt-space-1-5);\r\n padding: 0;\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-radio-group__description,\r\n.j-radio-group__error {\r\n margin: calc(var(--juxt-space-1) * -1) 0 var(--juxt-space-1-5);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-radio-group__error {\r\n margin: var(--juxt-space-1) 0 0;\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-radio-group__items {\r\n display: flex;\r\n}\r\n\r\n.j-radio-group__items--vertical {\r\n flex-direction: column;\r\n gap: var(--juxt-space-3);\r\n}\r\n\r\n.j-radio-group__items--horizontal {\r\n flex-wrap: wrap;\r\n gap: var(--juxt-space-3) var(--juxt-space-6);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { RadioValue } from './context'\r\nimport { computed, provide, useId } from 'vue'\r\nimport { RADIO_GROUP } from './context'\r\n\r\nexport interface RadioGroupProps {\r\n /** Group label, rendered as the fieldset legend. */\r\n label?: string\r\n description?: string\r\n /** An error message, or `true` to mark the group invalid without one. */\r\n error?: string | boolean\r\n orientation?: 'vertical' | 'horizontal'\r\n disabled?: boolean\r\n required?: boolean\r\n /** Form field name. Generated when omitted. */\r\n name?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<RadioGroupProps>(), { orientation: 'vertical', error: undefined })\r\n\r\nconst model = defineModel<RadioValue | null>({ default: null })\r\n\r\ndefineSlots<{ default?: () => unknown, label?: () => unknown }>()\r\n\r\nconst id = `j-${useId()}`\r\nconst descriptionId = `${id}-description`\r\nconst errorId = `${id}-error`\r\nconst errorMessage = computed(() => (typeof props.error === 'string' ? props.error : ''))\r\n\r\nprovide(RADIO_GROUP, {\r\n name: computed(() => props.name ?? id),\r\n value: computed(() => model.value),\r\n disabled: computed(() => props.disabled),\r\n required: computed(() => props.required),\r\n invalid: computed(() => !!props.error),\r\n select: (value) => {\r\n if (!props.disabled) model.value = value\r\n },\r\n})\r\n\r\nconst describedBy = computed(() => (errorMessage.value ? errorId : props.description ? descriptionId : undefined))\r\n</script>\r\n\r\n<template>\r\n <fieldset\r\n class=\"j-radio-group\"\r\n :class=\"{ 'is-disabled': disabled, 'is-invalid': !!error }\"\r\n :disabled=\"disabled\"\r\n :aria-describedby=\"describedBy\"\r\n :aria-invalid=\"!!error || undefined\"\r\n :aria-required=\"required || undefined\"\r\n >\r\n <legend v-if=\"label || $slots.label\" class=\"j-radio-group__legend\">\r\n <slot name=\"label\">{{ label }}</slot>\r\n <span v-if=\"required\" class=\"j-field__required\" aria-hidden=\"true\">*</span>\r\n </legend>\r\n <p v-if=\"description && !errorMessage\" :id=\"descriptionId\" class=\"j-radio-group__description\">\r\n {{ description }}\r\n </p>\r\n <div class=\"j-radio-group__items\" :class=\"`j-radio-group__items--${orientation}`\">\r\n <slot />\r\n </div>\r\n <p v-if=\"errorMessage\" :id=\"errorId\" class=\"j-radio-group__error\">\r\n {{ errorMessage }}\r\n </p>\r\n </fieldset>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-radio-group {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n min-width: 0;\r\n margin: 0;\r\n padding: 0;\r\n border: 0;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-radio-group__legend {\r\n display: inline-flex;\r\n gap: var(--juxt-space-0-5);\r\n margin-bottom: var(--juxt-space-1-5);\r\n padding: 0;\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-radio-group__description,\r\n.j-radio-group__error {\r\n margin: calc(var(--juxt-space-1) * -1) 0 var(--juxt-space-1-5);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-radio-group__error {\r\n margin: var(--juxt-space-1) 0 0;\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-radio-group__items {\r\n display: flex;\r\n}\r\n\r\n.j-radio-group__items--vertical {\r\n flex-direction: column;\r\n gap: var(--juxt-space-3);\r\n}\r\n\r\n.j-radio-group__items--horizontal {\r\n flex-wrap: wrap;\r\n gap: var(--juxt-space-3) var(--juxt-space-6);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, ref } from 'vue'\r\n\r\nexport interface RatingProps {\r\n /** Number of stars. */\r\n max?: number\r\n /** Static display mode | no interaction, ideal for showing an average. */\r\n readonly?: boolean\r\n disabled?: boolean\r\n size?: 'sm' | 'md' | 'lg'\r\n /** Accessible name for the group, e.g. \"Rate this product\". */\r\n label?: string\r\n /** Allow half-star precision. Only applies in `readonly` mode. */\r\n allowHalf?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<RatingProps>(), { max: 5, size: 'md' })\r\n\r\nconst model = defineModel<number>({ default: 0 })\r\n\r\nconst hovered = ref<number | null>(null)\r\nconst stars = computed(() => Array.from({ length: props.max }, (_, i) => i + 1))\r\nconst display = computed(() => hovered.value ?? model.value)\r\n\r\nfunction fillFor(star: number) {\r\n const value = display.value\r\n if (value >= star) return 1\r\n if (props.readonly && props.allowHalf && value >= star - 0.5) return 0.5\r\n return 0\r\n}\r\n\r\nfunction select(star: number) {\r\n if (props.readonly || props.disabled) return\r\n model.value = star\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (props.readonly || props.disabled) return\r\n let next: number | undefined\r\n if (event.key === 'ArrowRight' || event.key === 'ArrowUp') next = Math.min(props.max, model.value + 1)\r\n else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') next = Math.max(0, model.value - 1)\r\n else if (event.key === 'Home') next = 0\r\n else if (event.key === 'End') next = props.max\r\n if (next === undefined) return\r\n event.preventDefault()\r\n model.value = next\r\n}\r\n\r\nconst groupLabel = computed(() => props.label ?? (props.readonly ? `${model.value} out of ${props.max} stars` : 'Rating'))\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-rating\"\r\n :class=\"[`j-rating--${size}`, { 'is-readonly': readonly, 'is-disabled': disabled }]\"\r\n :role=\"readonly ? 'img' : 'radiogroup'\"\r\n :aria-label=\"groupLabel\"\r\n :aria-disabled=\"disabled || undefined\"\r\n @mouseleave=\"hovered = null\"\r\n >\r\n <template v-if=\"readonly\">\r\n <span v-for=\"star in stars\" :key=\"star\" class=\"j-rating__star\" aria-hidden=\"true\">\r\n <svg viewBox=\"0 0 16 16\" class=\"j-rating__star-bg\"><path d=\"M8 1.5l1.98 4.01 4.42.64-3.2 3.12.76 4.4L8 11.6l-3.96 2.08.76-4.4-3.2-3.12 4.42-.64z\" /></svg>\r\n <span class=\"j-rating__star-fill\" :style=\"{ width: `${fillFor(star) * 100}%` }\">\r\n <svg viewBox=\"0 0 16 16\"><path d=\"M8 1.5l1.98 4.01 4.42.64-3.2 3.12.76 4.4L8 11.6l-3.96 2.08.76-4.4-3.2-3.12 4.42-.64z\" /></svg>\r\n </span>\r\n </span>\r\n </template>\r\n <template v-else>\r\n <button\r\n v-for=\"star in stars\"\r\n :key=\"star\"\r\n type=\"button\"\r\n role=\"radio\"\r\n class=\"j-rating__button j-focusable\"\r\n :aria-checked=\"model === star\"\r\n :aria-label=\"`${star} ${star === 1 ? 'star' : 'stars'}`\"\r\n :tabindex=\"disabled ? -1 : (model === star || (model === 0 && star === 1)) ? 0 : -1\"\r\n :disabled=\"disabled\"\r\n @click=\"select(star)\"\r\n @mouseenter=\"hovered = star\"\r\n @focus=\"hovered = star\"\r\n @blur=\"hovered = null\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <svg viewBox=\"0 0 16 16\" class=\"j-rating__star-bg\"><path d=\"M8 1.5l1.98 4.01 4.42.64-3.2 3.12.76 4.4L8 11.6l-3.96 2.08.76-4.4-3.2-3.12 4.42-.64z\" /></svg>\r\n <span class=\"j-rating__star-fill\" :style=\"{ width: `${fillFor(star) * 100}%` }\">\r\n <svg viewBox=\"0 0 16 16\"><path d=\"M8 1.5l1.98 4.01 4.42.64-3.2 3.12.76 4.4L8 11.6l-3.96 2.08.76-4.4-3.2-3.12 4.42-.64z\" /></svg>\r\n </span>\r\n </button>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-rating {\r\n --j-rating-size: 1.25rem;\r\n\r\n display: inline-flex;\r\n align-items: center;\r\n gap: var(--juxt-space-0-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-rating--sm {\r\n --j-rating-size: 1rem;\r\n}\r\n\r\n.j-rating--lg {\r\n --j-rating-size: 1.5rem;\r\n}\r\n\r\n.j-rating__star,\r\n.j-rating__button {\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n width: var(--j-rating-size);\r\n height: var(--j-rating-size);\r\n padding: 0;\r\n border: 0;\r\n background: transparent;\r\n color: var(--juxt-fg-disabled);\r\n cursor: default;\r\n}\r\n\r\n.j-rating__button {\r\n border-radius: var(--juxt-radius-xs);\r\n cursor: pointer;\r\n transition: transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-rating__button:hover:not(:disabled) {\r\n transform: scale(1.1);\r\n}\r\n\r\n.j-rating__button:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-rating.is-disabled .j-rating__button {\r\n opacity: 0.5;\r\n}\r\n\r\n.j-rating__star-bg,\r\n.j-rating__star-fill > svg {\r\n position: absolute;\r\n inset: 0;\r\n width: 100%;\r\n height: 100%;\r\n fill: currentColor;\r\n stroke: none;\r\n}\r\n\r\n.j-rating__star-fill {\r\n position: absolute;\r\n inset: 0;\r\n overflow: hidden;\r\n color: var(--juxt-warning);\r\n transition: width var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-rating__button:hover:not(:disabled) {\r\n transform: none;\r\n }\r\n\r\n .j-rating__star-fill {\r\n transition: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, ref } from 'vue'\r\n\r\nexport interface RatingProps {\r\n /** Number of stars. */\r\n max?: number\r\n /** Static display mode | no interaction, ideal for showing an average. */\r\n readonly?: boolean\r\n disabled?: boolean\r\n size?: 'sm' | 'md' | 'lg'\r\n /** Accessible name for the group, e.g. \"Rate this product\". */\r\n label?: string\r\n /** Allow half-star precision. Only applies in `readonly` mode. */\r\n allowHalf?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<RatingProps>(), { max: 5, size: 'md' })\r\n\r\nconst model = defineModel<number>({ default: 0 })\r\n\r\nconst hovered = ref<number | null>(null)\r\nconst stars = computed(() => Array.from({ length: props.max }, (_, i) => i + 1))\r\nconst display = computed(() => hovered.value ?? model.value)\r\n\r\nfunction fillFor(star: number) {\r\n const value = display.value\r\n if (value >= star) return 1\r\n if (props.readonly && props.allowHalf && value >= star - 0.5) return 0.5\r\n return 0\r\n}\r\n\r\nfunction select(star: number) {\r\n if (props.readonly || props.disabled) return\r\n model.value = star\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (props.readonly || props.disabled) return\r\n let next: number | undefined\r\n if (event.key === 'ArrowRight' || event.key === 'ArrowUp') next = Math.min(props.max, model.value + 1)\r\n else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') next = Math.max(0, model.value - 1)\r\n else if (event.key === 'Home') next = 0\r\n else if (event.key === 'End') next = props.max\r\n if (next === undefined) return\r\n event.preventDefault()\r\n model.value = next\r\n}\r\n\r\nconst groupLabel = computed(() => props.label ?? (props.readonly ? `${model.value} out of ${props.max} stars` : 'Rating'))\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-rating\"\r\n :class=\"[`j-rating--${size}`, { 'is-readonly': readonly, 'is-disabled': disabled }]\"\r\n :role=\"readonly ? 'img' : 'radiogroup'\"\r\n :aria-label=\"groupLabel\"\r\n :aria-disabled=\"disabled || undefined\"\r\n @mouseleave=\"hovered = null\"\r\n >\r\n <template v-if=\"readonly\">\r\n <span v-for=\"star in stars\" :key=\"star\" class=\"j-rating__star\" aria-hidden=\"true\">\r\n <svg viewBox=\"0 0 16 16\" class=\"j-rating__star-bg\"><path d=\"M8 1.5l1.98 4.01 4.42.64-3.2 3.12.76 4.4L8 11.6l-3.96 2.08.76-4.4-3.2-3.12 4.42-.64z\" /></svg>\r\n <span class=\"j-rating__star-fill\" :style=\"{ width: `${fillFor(star) * 100}%` }\">\r\n <svg viewBox=\"0 0 16 16\"><path d=\"M8 1.5l1.98 4.01 4.42.64-3.2 3.12.76 4.4L8 11.6l-3.96 2.08.76-4.4-3.2-3.12 4.42-.64z\" /></svg>\r\n </span>\r\n </span>\r\n </template>\r\n <template v-else>\r\n <button\r\n v-for=\"star in stars\"\r\n :key=\"star\"\r\n type=\"button\"\r\n role=\"radio\"\r\n class=\"j-rating__button j-focusable\"\r\n :aria-checked=\"model === star\"\r\n :aria-label=\"`${star} ${star === 1 ? 'star' : 'stars'}`\"\r\n :tabindex=\"disabled ? -1 : (model === star || (model === 0 && star === 1)) ? 0 : -1\"\r\n :disabled=\"disabled\"\r\n @click=\"select(star)\"\r\n @mouseenter=\"hovered = star\"\r\n @focus=\"hovered = star\"\r\n @blur=\"hovered = null\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <svg viewBox=\"0 0 16 16\" class=\"j-rating__star-bg\"><path d=\"M8 1.5l1.98 4.01 4.42.64-3.2 3.12.76 4.4L8 11.6l-3.96 2.08.76-4.4-3.2-3.12 4.42-.64z\" /></svg>\r\n <span class=\"j-rating__star-fill\" :style=\"{ width: `${fillFor(star) * 100}%` }\">\r\n <svg viewBox=\"0 0 16 16\"><path d=\"M8 1.5l1.98 4.01 4.42.64-3.2 3.12.76 4.4L8 11.6l-3.96 2.08.76-4.4-3.2-3.12 4.42-.64z\" /></svg>\r\n </span>\r\n </button>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-rating {\r\n --j-rating-size: 1.25rem;\r\n\r\n display: inline-flex;\r\n align-items: center;\r\n gap: var(--juxt-space-0-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-rating--sm {\r\n --j-rating-size: 1rem;\r\n}\r\n\r\n.j-rating--lg {\r\n --j-rating-size: 1.5rem;\r\n}\r\n\r\n.j-rating__star,\r\n.j-rating__button {\r\n position: relative;\r\n display: inline-flex;\r\n flex: none;\r\n width: var(--j-rating-size);\r\n height: var(--j-rating-size);\r\n padding: 0;\r\n border: 0;\r\n background: transparent;\r\n color: var(--juxt-fg-disabled);\r\n cursor: default;\r\n}\r\n\r\n.j-rating__button {\r\n border-radius: var(--juxt-radius-xs);\r\n cursor: pointer;\r\n transition: transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-rating__button:hover:not(:disabled) {\r\n transform: scale(1.1);\r\n}\r\n\r\n.j-rating__button:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-rating.is-disabled .j-rating__button {\r\n opacity: 0.5;\r\n}\r\n\r\n.j-rating__star-bg,\r\n.j-rating__star-fill > svg {\r\n position: absolute;\r\n inset: 0;\r\n width: 100%;\r\n height: 100%;\r\n fill: currentColor;\r\n stroke: none;\r\n}\r\n\r\n.j-rating__star-fill {\r\n position: absolute;\r\n inset: 0;\r\n overflow: hidden;\r\n color: var(--juxt-warning);\r\n transition: width var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-rating__button:hover:not(:disabled) {\r\n transform: none;\r\n }\r\n\r\n .j-rating__star-fill {\r\n transition: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\" generic=\"T extends string | number = string\">\r\nimport type { FieldProps } from '../field/useField'\r\nimport { computed, nextTick, ref, watch } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { useTypeahead } from '../../composables/useTypeahead'\r\nimport { IconCheck, IconChevronUpDown } from '../../icons'\r\nimport JFieldShell from '../field/JFieldShell.vue'\r\nimport { useField } from '../field/useField'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface SelectOption<V = string | number> {\r\n value: V\r\n label: string\r\n description?: string\r\n disabled?: boolean\r\n}\r\n\r\nexport interface SelectProps<V> extends FieldProps {\r\n /** Options as objects, or plain strings when value and label are the same. */\r\n options: Array<SelectOption<V> | V>\r\n placeholder?: string\r\n size?: 'sm' | 'md' | 'lg'\r\n /** Submits the value under this name in a form. */\r\n name?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<SelectProps<T>>(), {\r\n placeholder: 'Select…',\r\n size: 'md',\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<T | null>({ default: null })\r\n\r\nconst emit = defineEmits<{\r\n open: []\r\n close: []\r\n}>()\r\n\r\ndefineSlots<{\r\n option?: (props: { option: SelectOption<T>, selected: boolean, active: boolean }) => unknown\r\n value?: (props: { option: SelectOption<T> }) => unknown\r\n label?: () => unknown\r\n}>()\r\n\r\nconst field = useField(props)\r\nconst listboxId = computed(() => `${field.id.value}-listbox`)\r\nconst valueId = computed(() => `${field.id.value}-value`)\r\nconst optionId = (index: number) => `${field.id.value}-option-${index}`\r\n\r\nconst normalized = computed<SelectOption<T>[]>(() =>\r\n props.options.map(option =>\r\n typeof option === 'object' && option !== null ? option : { value: option as T, label: String(option) },\r\n ),\r\n)\r\n\r\n/*\r\n * The list the user sees and navigates. Today it's every option; a search\r\n * input only needs to set `query` for this to become a filtered view.\r\n */\r\nconst query = ref('')\r\nconst visibleOptions = computed(() => {\r\n const q = query.value.trim().toLowerCase()\r\n if (!q) return normalized.value\r\n return normalized.value.filter(option => option.label.toLowerCase().includes(q))\r\n})\r\n\r\nconst selected = computed(() => normalized.value.find(option => option.value === model.value))\r\n\r\nconst open = ref(false)\r\nconst activeIndex = ref(-1)\r\nconst trigger = ref<HTMLButtonElement | null>(null)\r\nconst listbox = ref<HTMLElement | null>(null)\r\nconst pointerActive = ref(false)\r\n\r\nconst { styles } = useFloating(trigger, listbox, { placement: 'bottom-start', offset: 4, matchWidth: true })\r\nconst typeahead = useTypeahead()\r\n\r\nuseLayer({\r\n active: open,\r\n elements: () => [trigger.value, listbox.value],\r\n onEscape: () => close(),\r\n onPointerDownOutside: () => close(),\r\n})\r\n\r\nconst isEnabled = (index: number) => {\r\n const option = visibleOptions.value[index]\r\n return !!option && !option.disabled\r\n}\r\n\r\nfunction step(from: number, delta: 1 | -1): number {\r\n const count = visibleOptions.value.length\r\n for (let i = from + delta; i >= 0 && i < count; i += delta) {\r\n if (isEnabled(i)) return i\r\n }\r\n return from\r\n}\r\n\r\nconst first = () => step(-1, 1)\r\nconst last = () => step(visibleOptions.value.length, -1)\r\n\r\nfunction setActive(index: number, scroll = true) {\r\n activeIndex.value = index\r\n if (!scroll) return\r\n nextTick(() => {\r\n document.getElementById(optionId(index))?.scrollIntoView({ block: 'nearest' })\r\n })\r\n}\r\n\r\nfunction openList(focus: 'selected' | 'first' | 'last' = 'selected') {\r\n if (props.disabled || open.value) return\r\n open.value = true\r\n emit('open')\r\n const selectedIndex = visibleOptions.value.findIndex(option => option.value === model.value)\r\n const target = focus === 'last' ? last() : focus === 'first' ? first() : selectedIndex >= 0 ? selectedIndex : first()\r\n setActive(target)\r\n}\r\n\r\nfunction close() {\r\n if (!open.value) return\r\n open.value = false\r\n pointerActive.value = false\r\n emit('close')\r\n}\r\n\r\nfunction commit(index: number) {\r\n const option = visibleOptions.value[index]\r\n if (!option || option.disabled) return\r\n model.value = option.value\r\n close()\r\n}\r\n\r\nfunction onTriggerClick() {\r\n if (open.value) close()\r\n else openList()\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (props.disabled) return\r\n const { key } = event\r\n\r\n if (!open.value) {\r\n if (key === 'ArrowDown' || key === 'Enter' || key === ' ') {\r\n event.preventDefault()\r\n openList()\r\n } else if (key === 'ArrowUp') {\r\n event.preventDefault()\r\n openList()\r\n } else if (key === 'Home') {\r\n event.preventDefault()\r\n openList('first')\r\n } else if (key === 'End') {\r\n event.preventDefault()\r\n openList('last')\r\n } else if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\r\n const index = typeahead.search(key, visibleOptions.value, option => option.label, activeIndex.value)\r\n if (index >= 0) {\r\n openList()\r\n setActive(index)\r\n }\r\n }\r\n return\r\n }\r\n\r\n switch (key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n if (event.altKey) return\r\n setActive(step(activeIndex.value, 1))\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n if (event.altKey) return commit(activeIndex.value)\r\n setActive(step(activeIndex.value, -1))\r\n break\r\n case 'Home':\r\n event.preventDefault()\r\n setActive(first())\r\n break\r\n case 'End':\r\n event.preventDefault()\r\n setActive(last())\r\n break\r\n case 'PageDown':\r\n event.preventDefault()\r\n setActive(Math.min(last(), step(Math.min(activeIndex.value + 9, visibleOptions.value.length - 1), 1)))\r\n break\r\n case 'PageUp':\r\n event.preventDefault()\r\n setActive(Math.max(first(), step(Math.max(activeIndex.value - 9, 0), -1)))\r\n break\r\n case 'Enter':\r\n event.preventDefault()\r\n commit(activeIndex.value)\r\n break\r\n case 'Tab':\r\n commit(activeIndex.value)\r\n close()\r\n break\r\n case ' ':\r\n event.preventDefault()\r\n if (typeahead.isSearching()) typeahead.search(key, visibleOptions.value, option => option.label, activeIndex.value)\r\n else commit(activeIndex.value)\r\n break\r\n default:\r\n if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\r\n const index = typeahead.search(key, visibleOptions.value, option => option.label, activeIndex.value)\r\n if (index >= 0) setActive(index)\r\n }\r\n }\r\n}\r\n\r\n// Only let the pointer steer the active option once it actually moves, so a\r\n// list opening under a resting cursor doesn't hijack the keyboard position.\r\nfunction onOptionPointerMove(index: number) {\r\n pointerActive.value = true\r\n if (activeIndex.value !== index && isEnabled(index)) setActive(index, false)\r\n}\r\n\r\nwatch(() => props.disabled, value => value && close())\r\n\r\ndefineExpose({ focus: () => trigger.value?.focus(), open: () => openList(), close })\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n <button\r\n :id=\"field.id.value\"\r\n ref=\"trigger\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"button\"\r\n role=\"combobox\"\r\n class=\"j-control j-select__trigger\"\r\n :class=\"[`j-control--${size}`, { 'is-open': open, 'is-invalid': field.invalid.value, 'is-disabled': disabled }]\"\r\n aria-haspopup=\"listbox\"\r\n :aria-expanded=\"open\"\r\n :aria-controls=\"open ? listboxId : undefined\"\r\n :aria-activedescendant=\"open && activeIndex >= 0 ? optionId(activeIndex) : undefined\"\r\n :aria-labelledby=\"label || $slots.label ? `${field.labelId.value} ${valueId}` : undefined\"\r\n :aria-describedby=\"field.describedBy.value\"\r\n :aria-invalid=\"field.invalid.value || undefined\"\r\n :aria-required=\"required || undefined\"\r\n :disabled=\"disabled\"\r\n @click=\"onTriggerClick\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <span :id=\"valueId\" class=\"j-select__value\" :class=\"{ 'is-placeholder': !selected }\">\r\n <slot v-if=\"selected\" name=\"value\" :option=\"selected\">{{ selected.label }}</slot>\r\n <template v-else>{{ placeholder }}</template>\r\n </span>\r\n <IconChevronUpDown class=\"j-select__chevron\" />\r\n </button>\r\n <input v-if=\"name\" type=\"hidden\" :name=\"name\" :value=\"model ?? ''\" />\r\n\r\n <JPortal>\r\n <Transition name=\"j-pop\">\r\n <ul\r\n v-if=\"open\"\r\n :id=\"listboxId\"\r\n ref=\"listbox\"\r\n role=\"listbox\"\r\n class=\"j-select__listbox j-popover-surface\"\r\n :class=\"{ 'is-pointer': pointerActive }\"\r\n :style=\"styles\"\r\n :aria-labelledby=\"label ? field.labelId.value : undefined\"\r\n tabindex=\"-1\"\r\n @pointerdown.prevent\r\n >\r\n <li\r\n v-for=\"(option, index) in visibleOptions\"\r\n :id=\"optionId(index)\"\r\n :key=\"String(option.value)\"\r\n role=\"option\"\r\n class=\"j-select__option\"\r\n :class=\"{ 'is-active': index === activeIndex, 'is-selected': option.value === model }\"\r\n :aria-selected=\"option.value === model\"\r\n :aria-disabled=\"option.disabled || undefined\"\r\n @pointermove=\"onOptionPointerMove(index)\"\r\n @click=\"commit(index)\"\r\n >\r\n <span class=\"j-select__option-text\">\r\n <slot name=\"option\" :option=\"option\" :selected=\"option.value === model\" :active=\"index === activeIndex\">\r\n <span class=\"j-select__option-label\">{{ option.label }}</span>\r\n <span v-if=\"option.description\" class=\"j-select__option-description\">{{ option.description }}</span>\r\n </slot>\r\n </span>\r\n <IconCheck class=\"j-select__check\" />\r\n </li>\r\n <li v-if=\"visibleOptions.length === 0\" class=\"j-select__empty\" role=\"presentation\">\r\n No options\r\n </li>\r\n </ul>\r\n </Transition>\r\n </JPortal>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-select__trigger {\r\n width: 100%;\r\n justify-content: space-between;\r\n text-align: left;\r\n cursor: pointer;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.j-select__trigger:focus-visible {\r\n outline: none;\r\n}\r\n\r\n.j-select__trigger:focus-visible:not(.is-invalid) {\r\n border-color: var(--juxt-accent);\r\n box-shadow: 0 0 0 3px var(--juxt-ring-soft);\r\n}\r\n\r\n.j-select__trigger:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-select__value {\r\n overflow: hidden;\r\n min-width: 0;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-select__value.is-placeholder {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-select__trigger.is-disabled .j-select__value {\r\n color: var(--juxt-fg-disabled);\r\n}\r\n\r\n.j-select__chevron {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n margin-right: -0.125rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-select__listbox {\r\n z-index: var(--juxt-z-popover);\r\n box-sizing: border-box;\r\n max-width: calc(100vw - 16px);\r\n max-height: min(18rem, var(--j-available-height, 18rem));\r\n margin: 0;\r\n padding: var(--juxt-space-1);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n list-style: none;\r\n outline: none;\r\n scroll-padding-block: var(--juxt-space-1);\r\n}\r\n\r\n.j-select__option {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2);\r\n min-height: var(--juxt-control-sm);\r\n padding: var(--juxt-space-1-5) var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-xs);\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-select__option.is-active {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-select__option[aria-disabled='true'] {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-select__option-text {\r\n display: flex;\r\n flex: 1;\r\n flex-direction: column;\r\n gap: 1px;\r\n min-width: 0;\r\n}\r\n\r\n.j-select__option-label {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-select__option-description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n}\r\n\r\n.j-select__check {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n color: var(--juxt-accent-text);\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-select__option.is-selected .j-select__check {\r\n opacity: 1;\r\n transform: none;\r\n}\r\n\r\n.j-select__empty {\r\n padding: var(--juxt-space-3) var(--juxt-space-2);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n text-align: center;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\" generic=\"T extends string | number = string\">\r\nimport type { FieldProps } from '../field/useField'\r\nimport { computed, nextTick, ref, watch } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { useTypeahead } from '../../composables/useTypeahead'\r\nimport { IconCheck, IconChevronUpDown } from '../../icons'\r\nimport JFieldShell from '../field/JFieldShell.vue'\r\nimport { useField } from '../field/useField'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface SelectOption<V = string | number> {\r\n value: V\r\n label: string\r\n description?: string\r\n disabled?: boolean\r\n}\r\n\r\nexport interface SelectProps<V> extends FieldProps {\r\n /** Options as objects, or plain strings when value and label are the same. */\r\n options: Array<SelectOption<V> | V>\r\n placeholder?: string\r\n size?: 'sm' | 'md' | 'lg'\r\n /** Submits the value under this name in a form. */\r\n name?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<SelectProps<T>>(), {\r\n placeholder: 'Select…',\r\n size: 'md',\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<T | null>({ default: null })\r\n\r\nconst emit = defineEmits<{\r\n open: []\r\n close: []\r\n}>()\r\n\r\ndefineSlots<{\r\n option?: (props: { option: SelectOption<T>, selected: boolean, active: boolean }) => unknown\r\n value?: (props: { option: SelectOption<T> }) => unknown\r\n label?: () => unknown\r\n}>()\r\n\r\nconst field = useField(props)\r\nconst listboxId = computed(() => `${field.id.value}-listbox`)\r\nconst valueId = computed(() => `${field.id.value}-value`)\r\nconst optionId = (index: number) => `${field.id.value}-option-${index}`\r\n\r\nconst normalized = computed<SelectOption<T>[]>(() =>\r\n props.options.map(option =>\r\n typeof option === 'object' && option !== null ? option : { value: option as T, label: String(option) },\r\n ),\r\n)\r\n\r\n/*\r\n * The list the user sees and navigates. Today it's every option; a search\r\n * input only needs to set `query` for this to become a filtered view.\r\n */\r\nconst query = ref('')\r\nconst visibleOptions = computed(() => {\r\n const q = query.value.trim().toLowerCase()\r\n if (!q) return normalized.value\r\n return normalized.value.filter(option => option.label.toLowerCase().includes(q))\r\n})\r\n\r\nconst selected = computed(() => normalized.value.find(option => option.value === model.value))\r\n\r\nconst open = ref(false)\r\nconst activeIndex = ref(-1)\r\nconst trigger = ref<HTMLButtonElement | null>(null)\r\nconst listbox = ref<HTMLElement | null>(null)\r\nconst pointerActive = ref(false)\r\n\r\nconst { styles } = useFloating(trigger, listbox, { placement: 'bottom-start', offset: 4, matchWidth: true })\r\nconst typeahead = useTypeahead()\r\n\r\nuseLayer({\r\n active: open,\r\n elements: () => [trigger.value, listbox.value],\r\n onEscape: () => close(),\r\n onPointerDownOutside: () => close(),\r\n})\r\n\r\nconst isEnabled = (index: number) => {\r\n const option = visibleOptions.value[index]\r\n return !!option && !option.disabled\r\n}\r\n\r\nfunction step(from: number, delta: 1 | -1): number {\r\n const count = visibleOptions.value.length\r\n for (let i = from + delta; i >= 0 && i < count; i += delta) {\r\n if (isEnabled(i)) return i\r\n }\r\n return from\r\n}\r\n\r\nconst first = () => step(-1, 1)\r\nconst last = () => step(visibleOptions.value.length, -1)\r\n\r\nfunction setActive(index: number, scroll = true) {\r\n activeIndex.value = index\r\n if (!scroll) return\r\n nextTick(() => {\r\n document.getElementById(optionId(index))?.scrollIntoView({ block: 'nearest' })\r\n })\r\n}\r\n\r\nfunction openList(focus: 'selected' | 'first' | 'last' = 'selected') {\r\n if (props.disabled || open.value) return\r\n open.value = true\r\n emit('open')\r\n const selectedIndex = visibleOptions.value.findIndex(option => option.value === model.value)\r\n const target = focus === 'last' ? last() : focus === 'first' ? first() : selectedIndex >= 0 ? selectedIndex : first()\r\n setActive(target)\r\n}\r\n\r\nfunction close() {\r\n if (!open.value) return\r\n open.value = false\r\n pointerActive.value = false\r\n emit('close')\r\n}\r\n\r\nfunction commit(index: number) {\r\n const option = visibleOptions.value[index]\r\n if (!option || option.disabled) return\r\n model.value = option.value\r\n close()\r\n}\r\n\r\nfunction onTriggerClick() {\r\n if (open.value) close()\r\n else openList()\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (props.disabled) return\r\n const { key } = event\r\n\r\n if (!open.value) {\r\n if (key === 'ArrowDown' || key === 'Enter' || key === ' ') {\r\n event.preventDefault()\r\n openList()\r\n } else if (key === 'ArrowUp') {\r\n event.preventDefault()\r\n openList()\r\n } else if (key === 'Home') {\r\n event.preventDefault()\r\n openList('first')\r\n } else if (key === 'End') {\r\n event.preventDefault()\r\n openList('last')\r\n } else if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\r\n const index = typeahead.search(key, visibleOptions.value, option => option.label, activeIndex.value)\r\n if (index >= 0) {\r\n openList()\r\n setActive(index)\r\n }\r\n }\r\n return\r\n }\r\n\r\n switch (key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n if (event.altKey) return\r\n setActive(step(activeIndex.value, 1))\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n if (event.altKey) return commit(activeIndex.value)\r\n setActive(step(activeIndex.value, -1))\r\n break\r\n case 'Home':\r\n event.preventDefault()\r\n setActive(first())\r\n break\r\n case 'End':\r\n event.preventDefault()\r\n setActive(last())\r\n break\r\n case 'PageDown':\r\n event.preventDefault()\r\n setActive(Math.min(last(), step(Math.min(activeIndex.value + 9, visibleOptions.value.length - 1), 1)))\r\n break\r\n case 'PageUp':\r\n event.preventDefault()\r\n setActive(Math.max(first(), step(Math.max(activeIndex.value - 9, 0), -1)))\r\n break\r\n case 'Enter':\r\n event.preventDefault()\r\n commit(activeIndex.value)\r\n break\r\n case 'Tab':\r\n commit(activeIndex.value)\r\n close()\r\n break\r\n case ' ':\r\n event.preventDefault()\r\n if (typeahead.isSearching()) typeahead.search(key, visibleOptions.value, option => option.label, activeIndex.value)\r\n else commit(activeIndex.value)\r\n break\r\n default:\r\n if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {\r\n const index = typeahead.search(key, visibleOptions.value, option => option.label, activeIndex.value)\r\n if (index >= 0) setActive(index)\r\n }\r\n }\r\n}\r\n\r\n// Only let the pointer steer the active option once it actually moves, so a\r\n// list opening under a resting cursor doesn't hijack the keyboard position.\r\nfunction onOptionPointerMove(index: number) {\r\n pointerActive.value = true\r\n if (activeIndex.value !== index && isEnabled(index)) setActive(index, false)\r\n}\r\n\r\nwatch(() => props.disabled, value => value && close())\r\n\r\ndefineExpose({ focus: () => trigger.value?.focus(), open: () => openList(), close })\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n <button\r\n :id=\"field.id.value\"\r\n ref=\"trigger\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"button\"\r\n role=\"combobox\"\r\n class=\"j-control j-select__trigger\"\r\n :class=\"[`j-control--${size}`, { 'is-open': open, 'is-invalid': field.invalid.value, 'is-disabled': disabled }]\"\r\n aria-haspopup=\"listbox\"\r\n :aria-expanded=\"open\"\r\n :aria-controls=\"open ? listboxId : undefined\"\r\n :aria-activedescendant=\"open && activeIndex >= 0 ? optionId(activeIndex) : undefined\"\r\n :aria-labelledby=\"label || $slots.label ? `${field.labelId.value} ${valueId}` : undefined\"\r\n :aria-describedby=\"field.describedBy.value\"\r\n :aria-invalid=\"field.invalid.value || undefined\"\r\n :aria-required=\"required || undefined\"\r\n :disabled=\"disabled\"\r\n @click=\"onTriggerClick\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <span :id=\"valueId\" class=\"j-select__value\" :class=\"{ 'is-placeholder': !selected }\">\r\n <slot v-if=\"selected\" name=\"value\" :option=\"selected\">{{ selected.label }}</slot>\r\n <template v-else>{{ placeholder }}</template>\r\n </span>\r\n <IconChevronUpDown class=\"j-select__chevron\" />\r\n </button>\r\n <input v-if=\"name\" type=\"hidden\" :name=\"name\" :value=\"model ?? ''\" />\r\n\r\n <JPortal>\r\n <Transition name=\"j-pop\">\r\n <ul\r\n v-if=\"open\"\r\n :id=\"listboxId\"\r\n ref=\"listbox\"\r\n role=\"listbox\"\r\n class=\"j-select__listbox j-popover-surface\"\r\n :class=\"{ 'is-pointer': pointerActive }\"\r\n :style=\"styles\"\r\n :aria-labelledby=\"label ? field.labelId.value : undefined\"\r\n tabindex=\"-1\"\r\n @pointerdown.prevent\r\n >\r\n <li\r\n v-for=\"(option, index) in visibleOptions\"\r\n :id=\"optionId(index)\"\r\n :key=\"String(option.value)\"\r\n role=\"option\"\r\n class=\"j-select__option\"\r\n :class=\"{ 'is-active': index === activeIndex, 'is-selected': option.value === model }\"\r\n :aria-selected=\"option.value === model\"\r\n :aria-disabled=\"option.disabled || undefined\"\r\n @pointermove=\"onOptionPointerMove(index)\"\r\n @click=\"commit(index)\"\r\n >\r\n <span class=\"j-select__option-text\">\r\n <slot name=\"option\" :option=\"option\" :selected=\"option.value === model\" :active=\"index === activeIndex\">\r\n <span class=\"j-select__option-label\">{{ option.label }}</span>\r\n <span v-if=\"option.description\" class=\"j-select__option-description\">{{ option.description }}</span>\r\n </slot>\r\n </span>\r\n <IconCheck class=\"j-select__check\" />\r\n </li>\r\n <li v-if=\"visibleOptions.length === 0\" class=\"j-select__empty\" role=\"presentation\">\r\n No options\r\n </li>\r\n </ul>\r\n </Transition>\r\n </JPortal>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-select__trigger {\r\n width: 100%;\r\n justify-content: space-between;\r\n text-align: left;\r\n cursor: pointer;\r\n -webkit-tap-highlight-color: transparent;\r\n}\r\n\r\n.j-select__trigger:focus-visible {\r\n outline: none;\r\n}\r\n\r\n.j-select__trigger:focus-visible:not(.is-invalid) {\r\n border-color: var(--juxt-accent);\r\n box-shadow: 0 0 0 3px var(--juxt-ring-soft);\r\n}\r\n\r\n.j-select__trigger:disabled {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-select__value {\r\n overflow: hidden;\r\n min-width: 0;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-select__value.is-placeholder {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-select__trigger.is-disabled .j-select__value {\r\n color: var(--juxt-fg-disabled);\r\n}\r\n\r\n.j-select__chevron {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n margin-right: -0.125rem;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-select__listbox {\r\n z-index: var(--juxt-z-popover);\r\n box-sizing: border-box;\r\n max-width: calc(100vw - 16px);\r\n max-height: min(18rem, var(--j-available-height, 18rem));\r\n margin: 0;\r\n padding: var(--juxt-space-1);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n list-style: none;\r\n outline: none;\r\n scroll-padding-block: var(--juxt-space-1);\r\n}\r\n\r\n.j-select__option {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-2);\r\n min-height: var(--juxt-control-sm);\r\n padding: var(--juxt-space-1-5) var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-xs);\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-select__option.is-active {\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-select__option[aria-disabled='true'] {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-select__option-text {\r\n display: flex;\r\n flex: 1;\r\n flex-direction: column;\r\n gap: 1px;\r\n min-width: 0;\r\n}\r\n\r\n.j-select__option-label {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-select__option-description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n}\r\n\r\n.j-select__check {\r\n flex: none;\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n color: var(--juxt-accent-text);\r\n opacity: 0;\r\n transform: scale(0.8);\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-select__option.is-selected .j-select__check {\r\n opacity: 1;\r\n transform: none;\r\n}\r\n\r\n.j-select__empty {\r\n padding: var(--juxt-space-3) var(--juxt-space-2);\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n text-align: center;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nexport interface SeparatorProps {\r\n orientation?: 'horizontal' | 'vertical'\r\n /** Text in the middle of a horizontal separator, e.g. \"or\". */\r\n label?: string\r\n /**\r\n * Purely visual (the default). Set to false when the separator divides\r\n * meaningful groups and should be announced.\r\n */\r\n decorative?: boolean\r\n}\r\n\r\nwithDefaults(defineProps<SeparatorProps>(), { orientation: 'horizontal', decorative: true })\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-separator\"\r\n :class=\"[`j-separator--${orientation}`, { 'j-separator--labelled': label || $slots.default }]\"\r\n :role=\"decorative ? 'none' : 'separator'\"\r\n :aria-orientation=\"decorative ? undefined : orientation\"\r\n >\r\n <span v-if=\"label || $slots.default\" class=\"j-separator__label\"><slot>{{ label }}</slot></span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-separator {\r\n flex: none;\r\n background: var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-separator,\r\n[data-theme='dark'] .j-separator {\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-separator--horizontal {\r\n width: 100%;\r\n height: 1px;\r\n}\r\n\r\n.j-separator--vertical {\r\n align-self: stretch;\r\n width: 1px;\r\n min-height: 1rem;\r\n}\r\n\r\n.j-separator--labelled {\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-3);\r\n height: auto;\r\n background: none !important;\r\n color: var(--juxt-fg-muted);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--juxt-text-xs);\r\n}\r\n\r\n.j-separator--labelled::before,\r\n.j-separator--labelled::after {\r\n content: '';\r\n flex: 1;\r\n height: 1px;\r\n background: var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-separator--labelled::before,\r\n.dark .j-separator--labelled::after,\r\n[data-theme='dark'] .j-separator--labelled::before,\r\n[data-theme='dark'] .j-separator--labelled::after {\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-separator__label {\r\n flex: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nexport interface SeparatorProps {\r\n orientation?: 'horizontal' | 'vertical'\r\n /** Text in the middle of a horizontal separator, e.g. \"or\". */\r\n label?: string\r\n /**\r\n * Purely visual (the default). Set to false when the separator divides\r\n * meaningful groups and should be announced.\r\n */\r\n decorative?: boolean\r\n}\r\n\r\nwithDefaults(defineProps<SeparatorProps>(), { orientation: 'horizontal', decorative: true })\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-separator\"\r\n :class=\"[`j-separator--${orientation}`, { 'j-separator--labelled': label || $slots.default }]\"\r\n :role=\"decorative ? 'none' : 'separator'\"\r\n :aria-orientation=\"decorative ? undefined : orientation\"\r\n >\r\n <span v-if=\"label || $slots.default\" class=\"j-separator__label\"><slot>{{ label }}</slot></span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-separator {\r\n flex: none;\r\n background: var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-separator,\r\n[data-theme='dark'] .j-separator {\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-separator--horizontal {\r\n width: 100%;\r\n height: 1px;\r\n}\r\n\r\n.j-separator--vertical {\r\n align-self: stretch;\r\n width: 1px;\r\n min-height: 1rem;\r\n}\r\n\r\n.j-separator--labelled {\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-3);\r\n height: auto;\r\n background: none !important;\r\n color: var(--juxt-fg-muted);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--juxt-text-xs);\r\n}\r\n\r\n.j-separator--labelled::before,\r\n.j-separator--labelled::after {\r\n content: '';\r\n flex: 1;\r\n height: 1px;\r\n background: var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-separator--labelled::before,\r\n.dark .j-separator--labelled::after,\r\n[data-theme='dark'] .j-separator--labelled::before,\r\n[data-theme='dark'] .j-separator--labelled::after {\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-separator__label {\r\n flex: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { ref, useId } from 'vue'\r\nimport { FIELD_SELECTOR, useModal } from '../../composables/useModal'\r\nimport { IconX } from '../../icons'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface SheetProps {\r\n title?: string\r\n description?: string\r\n /** Edge the sheet slides in from. */\r\n side?: 'right' | 'left' | 'bottom'\r\n /** Width (or height for `bottom`): 320, 400 or 560px. */\r\n size?: 'sm' | 'md' | 'lg'\r\n closeOnOverlay?: boolean\r\n closeOnEscape?: boolean\r\n hideClose?: boolean\r\n /** Accessible label when there is no visible title. */\r\n ariaLabel?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<SheetProps>(), {\r\n side: 'right',\r\n size: 'md',\r\n closeOnOverlay: true,\r\n closeOnEscape: true,\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\n\r\nconst emit = defineEmits<{\r\n /** After the leave transition finishes. */\r\n afterClose: []\r\n}>()\r\n\r\ndefineSlots<{\r\n default?: (props: { close: () => void }) => unknown\r\n header?: (props: { titleId: string, descriptionId: string }) => unknown\r\n footer?: (props: { close: () => void }) => unknown\r\n}>()\r\n\r\nconst id = useId()\r\nconst titleId = `j-${id}-title`\r\nconst descriptionId = `j-${id}-description`\r\nconst panel = ref<HTMLElement | null>(null)\r\n\r\nfunction close() {\r\n open.value = false\r\n}\r\n\r\nconst { nudging, onBackdropPointerDown, onBackdropClick } = useModal(panel, open, {\r\n closeOnEscape: () => props.closeOnEscape,\r\n closeOnOverlay: () => props.closeOnOverlay,\r\n close,\r\n focusSelector: `.j-sheet__body ${FIELD_SELECTOR}`,\r\n})\r\n\r\ndefineExpose({ close })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-sheet-overlay\">\r\n <div v-if=\"open\" class=\"j-sheet__overlay\" aria-hidden=\"true\" />\r\n </Transition>\r\n <Transition name=\"j-sheet\" :duration=\"{ enter: 280, leave: 180 }\" @after-leave=\"emit('afterClose')\">\r\n <div\r\n v-if=\"open\"\r\n class=\"j-sheet__positioner\"\r\n :class=\"`j-sheet__positioner--${side}`\"\r\n @pointerdown=\"onBackdropPointerDown\"\r\n @click=\"onBackdropClick\"\r\n >\r\n <div\r\n ref=\"panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n class=\"j-sheet\"\r\n :class=\"[`j-sheet--${side}`, `j-sheet--${size}`, { 'is-nudging': nudging }]\"\r\n :aria-labelledby=\"title || $slots.header ? titleId : undefined\"\r\n :aria-label=\"ariaLabel\"\r\n :aria-describedby=\"description ? descriptionId : undefined\"\r\n @animationend=\"nudging = false\"\r\n >\r\n <header v-if=\"title || description || $slots.header\" class=\"j-sheet__header\">\r\n <slot name=\"header\" :title-id=\"titleId\" :description-id=\"descriptionId\">\r\n <h2 :id=\"titleId\" class=\"j-sheet__title\">\r\n {{ title }}\r\n </h2>\r\n <p v-if=\"description\" :id=\"descriptionId\" class=\"j-sheet__description\">\r\n {{ description }}\r\n </p>\r\n </slot>\r\n </header>\r\n <div class=\"j-sheet__body\">\r\n <slot :close=\"close\" />\r\n </div>\r\n <footer v-if=\"$slots.footer\" class=\"j-sheet__footer\">\r\n <slot name=\"footer\" :close=\"close\" />\r\n </footer>\r\n <button v-if=\"!hideClose\" type=\"button\" class=\"j-sheet__close j-focusable\" aria-label=\"Close\" @click=\"close\">\r\n <IconX />\r\n </button>\r\n </div>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-sheet__overlay {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-overlay);\r\n background: var(--juxt-overlay);\r\n}\r\n\r\n.j-sheet__positioner {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-modal);\r\n display: flex;\r\n padding: var(--juxt-space-2);\r\n}\r\n\r\n.j-sheet__positioner--right {\r\n justify-content: flex-end;\r\n}\r\n\r\n.j-sheet__positioner--left {\r\n justify-content: flex-start;\r\n}\r\n\r\n.j-sheet__positioner--bottom {\r\n align-items: flex-end;\r\n padding-bottom: max(var(--juxt-space-2), env(safe-area-inset-bottom));\r\n}\r\n\r\n/* A floating panel with a small inset from the edges | calmer than edge-to-edge. */\r\n.j-sheet {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-lg);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n outline: none;\r\n}\r\n\r\n.j-sheet--right,\r\n.j-sheet--left {\r\n height: 100%;\r\n}\r\n\r\n.j-sheet--right.j-sheet--sm,\r\n.j-sheet--left.j-sheet--sm {\r\n max-width: 20rem;\r\n}\r\n\r\n.j-sheet--right.j-sheet--md,\r\n.j-sheet--left.j-sheet--md {\r\n max-width: 25rem;\r\n}\r\n\r\n.j-sheet--right.j-sheet--lg,\r\n.j-sheet--left.j-sheet--lg {\r\n max-width: 35rem;\r\n}\r\n\r\n.j-sheet--bottom.j-sheet--sm {\r\n max-height: 20rem;\r\n}\r\n\r\n.j-sheet--bottom.j-sheet--md {\r\n max-height: 25rem;\r\n}\r\n\r\n.j-sheet--bottom.j-sheet--lg {\r\n max-height: min(35rem, calc(100dvh - var(--juxt-space-12)));\r\n}\r\n\r\n.j-sheet__header {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n padding: var(--juxt-space-5) var(--juxt-space-12) var(--juxt-space-4) var(--juxt-space-5);\r\n border-bottom: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-sheet__header,\r\n[data-theme='dark'] .j-sheet__header {\r\n border-bottom-color: var(--juxt-border);\r\n}\r\n\r\n.j-sheet__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-lg);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-sheet__description {\r\n margin: 0;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-sheet__body {\r\n flex: 1;\r\n min-height: 0;\r\n padding: var(--juxt-space-5);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-sheet__footer {\r\n display: flex;\r\n justify-content: flex-end;\r\n gap: var(--juxt-space-2);\r\n padding: var(--juxt-space-4) var(--juxt-space-5);\r\n border-top: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-sheet__footer,\r\n[data-theme='dark'] .j-sheet__footer {\r\n border-top-color: var(--juxt-border);\r\n}\r\n\r\n.j-sheet__close {\r\n position: absolute;\r\n top: var(--juxt-space-3);\r\n right: var(--juxt-space-3);\r\n display: grid;\r\n place-items: center;\r\n width: var(--juxt-control-sm);\r\n height: var(--juxt-control-sm);\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-sheet__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-sheet__close > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-sheet.is-nudging {\r\n animation: j-sheet-nudge 220ms var(--juxt-ease-out);\r\n}\r\n\r\n@keyframes j-sheet-nudge {\r\n 40% {\r\n transform: scale(1.006);\r\n }\r\n}\r\n\r\n/* Motion: the backdrop fades; the sheet slides in from its edge. */\r\n.j-sheet-overlay-enter-active {\r\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-sheet-overlay-leave-active {\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-sheet-overlay-enter-from,\r\n.j-sheet-overlay-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-sheet-enter-active .j-sheet {\r\n transition: transform 280ms var(--juxt-ease-out);\r\n}\r\n\r\n.j-sheet-leave-active .j-sheet {\r\n transition: transform var(--juxt-duration-normal) var(--juxt-ease-in);\r\n}\r\n\r\n.j-sheet-enter-from .j-sheet--right,\r\n.j-sheet-leave-to .j-sheet--right {\r\n transform: translateX(calc(100% + var(--juxt-space-4)));\r\n}\r\n\r\n.j-sheet-enter-from .j-sheet--left,\r\n.j-sheet-leave-to .j-sheet--left {\r\n transform: translateX(calc(-100% - var(--juxt-space-4)));\r\n}\r\n\r\n.j-sheet-enter-from .j-sheet--bottom,\r\n.j-sheet-leave-to .j-sheet--bottom {\r\n transform: translateY(calc(100% + var(--juxt-space-4)));\r\n}\r\n\r\n/* Reduced motion: the sheet fades in place instead of travelling. */\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-sheet-enter-active .j-sheet,\r\n .j-sheet-leave-active .j-sheet {\r\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-standard);\r\n }\r\n\r\n .j-sheet-enter-from .j-sheet,\r\n .j-sheet-leave-to .j-sheet {\r\n opacity: 0;\r\n transform: none !important;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { ref, useId } from 'vue'\r\nimport { FIELD_SELECTOR, useModal } from '../../composables/useModal'\r\nimport { IconX } from '../../icons'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface SheetProps {\r\n title?: string\r\n description?: string\r\n /** Edge the sheet slides in from. */\r\n side?: 'right' | 'left' | 'bottom'\r\n /** Width (or height for `bottom`): 320, 400 or 560px. */\r\n size?: 'sm' | 'md' | 'lg'\r\n closeOnOverlay?: boolean\r\n closeOnEscape?: boolean\r\n hideClose?: boolean\r\n /** Accessible label when there is no visible title. */\r\n ariaLabel?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<SheetProps>(), {\r\n side: 'right',\r\n size: 'md',\r\n closeOnOverlay: true,\r\n closeOnEscape: true,\r\n})\r\n\r\nconst open = defineModel<boolean>('open', { default: false })\r\n\r\nconst emit = defineEmits<{\r\n /** After the leave transition finishes. */\r\n afterClose: []\r\n}>()\r\n\r\ndefineSlots<{\r\n default?: (props: { close: () => void }) => unknown\r\n header?: (props: { titleId: string, descriptionId: string }) => unknown\r\n footer?: (props: { close: () => void }) => unknown\r\n}>()\r\n\r\nconst id = useId()\r\nconst titleId = `j-${id}-title`\r\nconst descriptionId = `j-${id}-description`\r\nconst panel = ref<HTMLElement | null>(null)\r\n\r\nfunction close() {\r\n open.value = false\r\n}\r\n\r\nconst { nudging, onBackdropPointerDown, onBackdropClick } = useModal(panel, open, {\r\n closeOnEscape: () => props.closeOnEscape,\r\n closeOnOverlay: () => props.closeOnOverlay,\r\n close,\r\n focusSelector: `.j-sheet__body ${FIELD_SELECTOR}`,\r\n})\r\n\r\ndefineExpose({ close })\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <Transition name=\"j-sheet-overlay\">\r\n <div v-if=\"open\" class=\"j-sheet__overlay\" aria-hidden=\"true\" />\r\n </Transition>\r\n <Transition name=\"j-sheet\" :duration=\"{ enter: 280, leave: 180 }\" @after-leave=\"emit('afterClose')\">\r\n <div\r\n v-if=\"open\"\r\n class=\"j-sheet__positioner\"\r\n :class=\"`j-sheet__positioner--${side}`\"\r\n @pointerdown=\"onBackdropPointerDown\"\r\n @click=\"onBackdropClick\"\r\n >\r\n <div\r\n ref=\"panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n tabindex=\"-1\"\r\n class=\"j-sheet\"\r\n :class=\"[`j-sheet--${side}`, `j-sheet--${size}`, { 'is-nudging': nudging }]\"\r\n :aria-labelledby=\"title || $slots.header ? titleId : undefined\"\r\n :aria-label=\"ariaLabel\"\r\n :aria-describedby=\"description ? descriptionId : undefined\"\r\n @animationend=\"nudging = false\"\r\n >\r\n <header v-if=\"title || description || $slots.header\" class=\"j-sheet__header\">\r\n <slot name=\"header\" :title-id=\"titleId\" :description-id=\"descriptionId\">\r\n <h2 :id=\"titleId\" class=\"j-sheet__title\">\r\n {{ title }}\r\n </h2>\r\n <p v-if=\"description\" :id=\"descriptionId\" class=\"j-sheet__description\">\r\n {{ description }}\r\n </p>\r\n </slot>\r\n </header>\r\n <div class=\"j-sheet__body\">\r\n <slot :close=\"close\" />\r\n </div>\r\n <footer v-if=\"$slots.footer\" class=\"j-sheet__footer\">\r\n <slot name=\"footer\" :close=\"close\" />\r\n </footer>\r\n <button v-if=\"!hideClose\" type=\"button\" class=\"j-sheet__close j-focusable\" aria-label=\"Close\" @click=\"close\">\r\n <IconX />\r\n </button>\r\n </div>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-sheet__overlay {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-overlay);\r\n background: var(--juxt-overlay);\r\n}\r\n\r\n.j-sheet__positioner {\r\n position: fixed;\r\n inset: 0;\r\n z-index: var(--juxt-z-modal);\r\n display: flex;\r\n padding: var(--juxt-space-2);\r\n}\r\n\r\n.j-sheet__positioner--right {\r\n justify-content: flex-end;\r\n}\r\n\r\n.j-sheet__positioner--left {\r\n justify-content: flex-start;\r\n}\r\n\r\n.j-sheet__positioner--bottom {\r\n align-items: flex-end;\r\n padding-bottom: max(var(--juxt-space-2), env(safe-area-inset-bottom));\r\n}\r\n\r\n/* A floating panel with a small inset from the edges | calmer than edge-to-edge. */\r\n.j-sheet {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-lg);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-lg);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n outline: none;\r\n}\r\n\r\n.j-sheet--right,\r\n.j-sheet--left {\r\n height: 100%;\r\n}\r\n\r\n.j-sheet--right.j-sheet--sm,\r\n.j-sheet--left.j-sheet--sm {\r\n max-width: 20rem;\r\n}\r\n\r\n.j-sheet--right.j-sheet--md,\r\n.j-sheet--left.j-sheet--md {\r\n max-width: 25rem;\r\n}\r\n\r\n.j-sheet--right.j-sheet--lg,\r\n.j-sheet--left.j-sheet--lg {\r\n max-width: 35rem;\r\n}\r\n\r\n.j-sheet--bottom.j-sheet--sm {\r\n max-height: 20rem;\r\n}\r\n\r\n.j-sheet--bottom.j-sheet--md {\r\n max-height: 25rem;\r\n}\r\n\r\n.j-sheet--bottom.j-sheet--lg {\r\n max-height: min(35rem, calc(100dvh - var(--juxt-space-12)));\r\n}\r\n\r\n.j-sheet__header {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n padding: var(--juxt-space-5) var(--juxt-space-12) var(--juxt-space-4) var(--juxt-space-5);\r\n border-bottom: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-sheet__header,\r\n[data-theme='dark'] .j-sheet__header {\r\n border-bottom-color: var(--juxt-border);\r\n}\r\n\r\n.j-sheet__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-lg);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-sheet__description {\r\n margin: 0;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-sheet__body {\r\n flex: 1;\r\n min-height: 0;\r\n padding: var(--juxt-space-5);\r\n overflow-y: auto;\r\n overscroll-behavior: contain;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-sheet__footer {\r\n display: flex;\r\n justify-content: flex-end;\r\n gap: var(--juxt-space-2);\r\n padding: var(--juxt-space-4) var(--juxt-space-5);\r\n border-top: 1px solid var(--juxt-border-subtle);\r\n}\r\n\r\n.dark .j-sheet__footer,\r\n[data-theme='dark'] .j-sheet__footer {\r\n border-top-color: var(--juxt-border);\r\n}\r\n\r\n.j-sheet__close {\r\n position: absolute;\r\n top: var(--juxt-space-3);\r\n right: var(--juxt-space-3);\r\n display: grid;\r\n place-items: center;\r\n width: var(--juxt-control-sm);\r\n height: var(--juxt-control-sm);\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-sheet__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-sheet__close > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-sheet.is-nudging {\r\n animation: j-sheet-nudge 220ms var(--juxt-ease-out);\r\n}\r\n\r\n@keyframes j-sheet-nudge {\r\n 40% {\r\n transform: scale(1.006);\r\n }\r\n}\r\n\r\n/* Motion: the backdrop fades; the sheet slides in from its edge. */\r\n.j-sheet-overlay-enter-active {\r\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-sheet-overlay-leave-active {\r\n transition: opacity var(--juxt-duration-fast) var(--juxt-ease-in);\r\n}\r\n\r\n.j-sheet-overlay-enter-from,\r\n.j-sheet-overlay-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-sheet-enter-active .j-sheet {\r\n transition: transform 280ms var(--juxt-ease-out);\r\n}\r\n\r\n.j-sheet-leave-active .j-sheet {\r\n transition: transform var(--juxt-duration-normal) var(--juxt-ease-in);\r\n}\r\n\r\n.j-sheet-enter-from .j-sheet--right,\r\n.j-sheet-leave-to .j-sheet--right {\r\n transform: translateX(calc(100% + var(--juxt-space-4)));\r\n}\r\n\r\n.j-sheet-enter-from .j-sheet--left,\r\n.j-sheet-leave-to .j-sheet--left {\r\n transform: translateX(calc(-100% - var(--juxt-space-4)));\r\n}\r\n\r\n.j-sheet-enter-from .j-sheet--bottom,\r\n.j-sheet-leave-to .j-sheet--bottom {\r\n transform: translateY(calc(100% + var(--juxt-space-4)));\r\n}\r\n\r\n/* Reduced motion: the sheet fades in place instead of travelling. */\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-sheet-enter-active .j-sheet,\r\n .j-sheet-leave-active .j-sheet {\r\n transition: opacity var(--juxt-duration-normal) var(--juxt-ease-standard);\r\n }\r\n\r\n .j-sheet-enter-from .j-sheet,\r\n .j-sheet-leave-to .j-sheet {\r\n opacity: 0;\r\n transform: none !important;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\n\r\nexport interface SkeletonProps {\r\n /** `text` renders one or more lines; `circle` suits avatars. */\r\n shape?: 'rect' | 'text' | 'circle'\r\n /** Number of lines for `text`. The last one is shorter, like a real paragraph. */\r\n lines?: number\r\n /** Any CSS length, e.g. `12rem` or `100%`. */\r\n width?: string\r\n height?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<SkeletonProps>(), { shape: 'rect', lines: 1 })\r\n\r\nconst style = computed(() => ({ width: props.width, height: props.height }))\r\n</script>\r\n\r\n<template>\r\n <span v-if=\"shape === 'text'\" class=\"j-skeleton-lines\" :style=\"{ width }\" aria-hidden=\"true\">\r\n <span\r\n v-for=\"i in lines\"\r\n :key=\"i\"\r\n class=\"j-skeleton j-skeleton--text\"\r\n :style=\"{ width: i === lines && lines > 1 ? '62%' : undefined, height }\"\r\n />\r\n </span>\r\n <span v-else class=\"j-skeleton\" :class=\"`j-skeleton--${shape}`\" :style=\"style\" aria-hidden=\"true\" />\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-skeleton {\r\n display: block;\r\n width: 100%;\r\n height: 1rem;\r\n border-radius: var(--juxt-radius-sm);\r\n background: var(--juxt-surface-active);\r\n animation: j-skeleton-pulse 1.8s var(--juxt-ease-standard) infinite;\r\n}\r\n\r\n.j-skeleton--circle {\r\n width: 2rem;\r\n height: 2rem;\r\n border-radius: var(--juxt-radius-full);\r\n}\r\n\r\n.j-skeleton--text {\r\n height: 0.75rem;\r\n border-radius: var(--juxt-radius-xs);\r\n}\r\n\r\n.j-skeleton-lines {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-2);\r\n width: 100%;\r\n}\r\n\r\n/* A slow breath, not a shimmer | loading shouldn't compete for attention. */\r\n@keyframes j-skeleton-pulse {\r\n 50% {\r\n opacity: 0.5;\r\n }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-skeleton {\r\n animation: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\n\r\nexport interface SkeletonProps {\r\n /** `text` renders one or more lines; `circle` suits avatars. */\r\n shape?: 'rect' | 'text' | 'circle'\r\n /** Number of lines for `text`. The last one is shorter, like a real paragraph. */\r\n lines?: number\r\n /** Any CSS length, e.g. `12rem` or `100%`. */\r\n width?: string\r\n height?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<SkeletonProps>(), { shape: 'rect', lines: 1 })\r\n\r\nconst style = computed(() => ({ width: props.width, height: props.height }))\r\n</script>\r\n\r\n<template>\r\n <span v-if=\"shape === 'text'\" class=\"j-skeleton-lines\" :style=\"{ width }\" aria-hidden=\"true\">\r\n <span\r\n v-for=\"i in lines\"\r\n :key=\"i\"\r\n class=\"j-skeleton j-skeleton--text\"\r\n :style=\"{ width: i === lines && lines > 1 ? '62%' : undefined, height }\"\r\n />\r\n </span>\r\n <span v-else class=\"j-skeleton\" :class=\"`j-skeleton--${shape}`\" :style=\"style\" aria-hidden=\"true\" />\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-skeleton {\r\n display: block;\r\n width: 100%;\r\n height: 1rem;\r\n border-radius: var(--juxt-radius-sm);\r\n background: var(--juxt-surface-active);\r\n animation: j-skeleton-pulse 1.8s var(--juxt-ease-standard) infinite;\r\n}\r\n\r\n.j-skeleton--circle {\r\n width: 2rem;\r\n height: 2rem;\r\n border-radius: var(--juxt-radius-full);\r\n}\r\n\r\n.j-skeleton--text {\r\n height: 0.75rem;\r\n border-radius: var(--juxt-radius-xs);\r\n}\r\n\r\n.j-skeleton-lines {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-2);\r\n width: 100%;\r\n}\r\n\r\n/* A slow breath, not a shimmer | loading shouldn't compete for attention. */\r\n@keyframes j-skeleton-pulse {\r\n 50% {\r\n opacity: 0.5;\r\n }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-skeleton {\r\n animation: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, ref } from 'vue'\r\n\r\nexport interface SliderProps {\r\n min?: number\r\n max?: number\r\n step?: number\r\n disabled?: boolean\r\n /** Accessible name for the thumb(s) when no visible label is associated. */\r\n label?: string\r\n size?: 'sm' | 'md'\r\n /** Show the current value in a floating bubble above the active thumb. */\r\n showValue?: boolean\r\n /** Format the displayed and announced value. */\r\n format?: (value: number) => string\r\n}\r\n\r\nconst props = withDefaults(defineProps<SliderProps>(), {\r\n min: 0,\r\n max: 100,\r\n step: 1,\r\n size: 'md',\r\n})\r\n\r\n/** A single value, or a `[low, high]` tuple for a range slider. */\r\nconst model = defineModel<number | [number, number]>({ default: 0 })\r\n\r\nconst track = ref<HTMLElement | null>(null)\r\nconst activeThumb = ref<number | null>(null)\r\n\r\nconst isRange = computed(() => Array.isArray(model.value))\r\nconst values = computed<number[]>(() => (Array.isArray(model.value) ? model.value : [model.value as number]))\r\n\r\nfunction clamp(value: number) {\r\n const stepped = Math.round((value - props.min) / props.step) * props.step + props.min\r\n const rounded = Number(stepped.toFixed(6))\r\n return Math.min(props.max, Math.max(props.min, rounded))\r\n}\r\n\r\nfunction setValue(index: number, raw: number) {\r\n if (props.disabled) return\r\n const next = [...values.value]\r\n let value = clamp(raw)\r\n if (isRange.value) {\r\n const lower = index === 0 ? props.min : next[index - 1]!\r\n const upper = index === next.length - 1 ? props.max : next[index + 1]!\r\n value = Math.min(upper, Math.max(lower, value))\r\n }\r\n next[index] = value\r\n model.value = isRange.value ? ([next[0]!, next[1]!] as [number, number]) : next[0]!\r\n}\r\n\r\nfunction percent(value: number) {\r\n if (props.max === props.min) return 0\r\n return ((value - props.min) / (props.max - props.min)) * 100\r\n}\r\n\r\nfunction valueFromClientX(clientX: number): number {\r\n const rect = track.value!.getBoundingClientRect()\r\n const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width))\r\n return props.min + ratio * (props.max - props.min)\r\n}\r\n\r\nfunction nearestThumb(value: number) {\r\n let nearest = 0\r\n let best = Infinity\r\n values.value.forEach((v, i) => {\r\n const dist = Math.abs(v - value)\r\n if (dist < best) {\r\n best = dist\r\n nearest = i\r\n }\r\n })\r\n return nearest\r\n}\r\n\r\nfunction onPointerMove(event: PointerEvent) {\r\n if (activeThumb.value === null) return\r\n setValue(activeThumb.value, valueFromClientX(event.clientX))\r\n}\r\n\r\nfunction onPointerUp() {\r\n activeThumb.value = null\r\n window.removeEventListener('pointermove', onPointerMove)\r\n window.removeEventListener('pointerup', onPointerUp)\r\n}\r\n\r\nfunction startDrag(index: number) {\r\n activeThumb.value = index\r\n window.addEventListener('pointermove', onPointerMove)\r\n window.addEventListener('pointerup', onPointerUp)\r\n}\r\n\r\nfunction onTrackPointerDown(event: PointerEvent) {\r\n if (props.disabled) return\r\n const value = valueFromClientX(event.clientX)\r\n const index = nearestThumb(value)\r\n setValue(index, value)\r\n startDrag(index)\r\n}\r\n\r\nfunction onThumbPointerDown(index: number, event: PointerEvent) {\r\n if (props.disabled) return\r\n event.stopPropagation()\r\n ;(event.currentTarget as HTMLElement).focus()\r\n startDrag(index)\r\n}\r\n\r\nfunction onKeydown(index: number, event: KeyboardEvent) {\r\n if (props.disabled) return\r\n const big = props.step * 10 || (props.max - props.min) / 10\r\n const current = values.value[index]!\r\n let next: number | undefined\r\n if (event.key === 'ArrowRight' || event.key === 'ArrowUp') next = current + props.step\r\n else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') next = current - props.step\r\n else if (event.key === 'PageUp') next = current + big\r\n else if (event.key === 'PageDown') next = current - big\r\n else if (event.key === 'Home') next = props.min\r\n else if (event.key === 'End') next = props.max\r\n if (next === undefined) return\r\n event.preventDefault()\r\n setValue(index, next)\r\n}\r\n\r\nfunction display(value: number) {\r\n return props.format ? props.format(value) : String(Math.round(value * 100) / 100)\r\n}\r\n\r\nconst fillStyle = computed(() => {\r\n if (isRange.value) {\r\n const [a, b] = values.value\r\n return { left: `${percent(a!)}%`, width: `${Math.max(0, percent(b!) - percent(a!))}%` }\r\n }\r\n return { left: '0%', width: `${percent(values.value[0]!)}%` }\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-slider\" :class=\"[`j-slider--${size}`, { 'is-disabled': disabled }]\">\r\n <div ref=\"track\" class=\"j-slider__track\" @pointerdown=\"onTrackPointerDown\">\r\n <span class=\"j-slider__range\" :style=\"fillStyle\" />\r\n <span\r\n v-for=\"(value, index) in values\"\r\n :key=\"index\"\r\n class=\"j-slider__thumb j-focusable\"\r\n :class=\"{ 'is-dragging': activeThumb === index }\"\r\n role=\"slider\"\r\n :tabindex=\"disabled ? -1 : 0\"\r\n :aria-label=\"label\"\r\n :aria-orientation=\"'horizontal'\"\r\n :aria-valuemin=\"min\"\r\n :aria-valuemax=\"max\"\r\n :aria-valuenow=\"value\"\r\n :aria-valuetext=\"display(value)\"\r\n :aria-disabled=\"disabled || undefined\"\r\n :style=\"{ left: `${percent(value)}%` }\"\r\n @pointerdown=\"onThumbPointerDown(index, $event)\"\r\n @keydown=\"onKeydown(index, $event)\"\r\n >\r\n <span v-if=\"showValue\" class=\"j-slider__bubble\">{{ display(value) }}</span>\r\n </span>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-slider {\r\n --j-slider-track: 0.25rem;\r\n --j-slider-thumb: 1rem;\r\n\r\n display: flex;\r\n align-items: center;\r\n width: 100%;\r\n padding: calc(var(--j-slider-thumb) / 2) 0;\r\n font-family: var(--juxt-font-sans);\r\n touch-action: none;\r\n}\r\n\r\n.j-slider--sm {\r\n --j-slider-track: 0.1875rem;\r\n --j-slider-thumb: 0.875rem;\r\n}\r\n\r\n.j-slider__track {\r\n position: relative;\r\n width: 100%;\r\n height: var(--j-slider-track);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-surface-active);\r\n cursor: pointer;\r\n}\r\n\r\n.j-slider.is-disabled .j-slider__track {\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n}\r\n\r\n.j-slider__range {\r\n position: absolute;\r\n top: 0;\r\n bottom: 0;\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-slider__thumb {\r\n position: absolute;\r\n top: 50%;\r\n width: var(--j-slider-thumb);\r\n height: var(--j-slider-thumb);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-surface);\r\n box-shadow: 0 0 0 1px var(--juxt-border-strong), var(--juxt-shadow-sm);\r\n cursor: grab;\r\n transform: translate(-50%, -50%);\r\n transition:\r\n box-shadow var(--juxt-duration-fast) var(--juxt-ease-out),\r\n left var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-slider__thumb.is-dragging {\r\n cursor: grabbing;\r\n transition: none;\r\n}\r\n\r\n.j-slider__thumb:hover {\r\n box-shadow: 0 0 0 1px var(--juxt-accent), var(--juxt-shadow-sm);\r\n}\r\n\r\n.j-slider__thumb:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 3px;\r\n}\r\n\r\n.j-slider.is-disabled .j-slider__thumb {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-slider__bubble {\r\n position: absolute;\r\n bottom: calc(100% + var(--juxt-space-2));\r\n left: 50%;\r\n padding: var(--juxt-space-0-5) var(--juxt-space-1-5);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-tooltip-bg);\r\n color: var(--juxt-tooltip-fg);\r\n font-size: var(--juxt-text-2xs);\r\n font-weight: var(--juxt-weight-medium);\r\n font-variant-numeric: tabular-nums;\r\n white-space: nowrap;\r\n pointer-events: none;\r\n transform: translateX(-50%);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-slider__thumb {\r\n transition: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, ref } from 'vue'\r\n\r\nexport interface SliderProps {\r\n min?: number\r\n max?: number\r\n step?: number\r\n disabled?: boolean\r\n /** Accessible name for the thumb(s) when no visible label is associated. */\r\n label?: string\r\n size?: 'sm' | 'md'\r\n /** Show the current value in a floating bubble above the active thumb. */\r\n showValue?: boolean\r\n /** Format the displayed and announced value. */\r\n format?: (value: number) => string\r\n}\r\n\r\nconst props = withDefaults(defineProps<SliderProps>(), {\r\n min: 0,\r\n max: 100,\r\n step: 1,\r\n size: 'md',\r\n})\r\n\r\n/** A single value, or a `[low, high]` tuple for a range slider. */\r\nconst model = defineModel<number | [number, number]>({ default: 0 })\r\n\r\nconst track = ref<HTMLElement | null>(null)\r\nconst activeThumb = ref<number | null>(null)\r\n\r\nconst isRange = computed(() => Array.isArray(model.value))\r\nconst values = computed<number[]>(() => (Array.isArray(model.value) ? model.value : [model.value as number]))\r\n\r\nfunction clamp(value: number) {\r\n const stepped = Math.round((value - props.min) / props.step) * props.step + props.min\r\n const rounded = Number(stepped.toFixed(6))\r\n return Math.min(props.max, Math.max(props.min, rounded))\r\n}\r\n\r\nfunction setValue(index: number, raw: number) {\r\n if (props.disabled) return\r\n const next = [...values.value]\r\n let value = clamp(raw)\r\n if (isRange.value) {\r\n const lower = index === 0 ? props.min : next[index - 1]!\r\n const upper = index === next.length - 1 ? props.max : next[index + 1]!\r\n value = Math.min(upper, Math.max(lower, value))\r\n }\r\n next[index] = value\r\n model.value = isRange.value ? ([next[0]!, next[1]!] as [number, number]) : next[0]!\r\n}\r\n\r\nfunction percent(value: number) {\r\n if (props.max === props.min) return 0\r\n return ((value - props.min) / (props.max - props.min)) * 100\r\n}\r\n\r\nfunction valueFromClientX(clientX: number): number {\r\n const rect = track.value!.getBoundingClientRect()\r\n const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width))\r\n return props.min + ratio * (props.max - props.min)\r\n}\r\n\r\nfunction nearestThumb(value: number) {\r\n let nearest = 0\r\n let best = Infinity\r\n values.value.forEach((v, i) => {\r\n const dist = Math.abs(v - value)\r\n if (dist < best) {\r\n best = dist\r\n nearest = i\r\n }\r\n })\r\n return nearest\r\n}\r\n\r\nfunction onPointerMove(event: PointerEvent) {\r\n if (activeThumb.value === null) return\r\n setValue(activeThumb.value, valueFromClientX(event.clientX))\r\n}\r\n\r\nfunction onPointerUp() {\r\n activeThumb.value = null\r\n window.removeEventListener('pointermove', onPointerMove)\r\n window.removeEventListener('pointerup', onPointerUp)\r\n}\r\n\r\nfunction startDrag(index: number) {\r\n activeThumb.value = index\r\n window.addEventListener('pointermove', onPointerMove)\r\n window.addEventListener('pointerup', onPointerUp)\r\n}\r\n\r\nfunction onTrackPointerDown(event: PointerEvent) {\r\n if (props.disabled) return\r\n const value = valueFromClientX(event.clientX)\r\n const index = nearestThumb(value)\r\n setValue(index, value)\r\n startDrag(index)\r\n}\r\n\r\nfunction onThumbPointerDown(index: number, event: PointerEvent) {\r\n if (props.disabled) return\r\n event.stopPropagation()\r\n ;(event.currentTarget as HTMLElement).focus()\r\n startDrag(index)\r\n}\r\n\r\nfunction onKeydown(index: number, event: KeyboardEvent) {\r\n if (props.disabled) return\r\n const big = props.step * 10 || (props.max - props.min) / 10\r\n const current = values.value[index]!\r\n let next: number | undefined\r\n if (event.key === 'ArrowRight' || event.key === 'ArrowUp') next = current + props.step\r\n else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') next = current - props.step\r\n else if (event.key === 'PageUp') next = current + big\r\n else if (event.key === 'PageDown') next = current - big\r\n else if (event.key === 'Home') next = props.min\r\n else if (event.key === 'End') next = props.max\r\n if (next === undefined) return\r\n event.preventDefault()\r\n setValue(index, next)\r\n}\r\n\r\nfunction display(value: number) {\r\n return props.format ? props.format(value) : String(Math.round(value * 100) / 100)\r\n}\r\n\r\nconst fillStyle = computed(() => {\r\n if (isRange.value) {\r\n const [a, b] = values.value\r\n return { left: `${percent(a!)}%`, width: `${Math.max(0, percent(b!) - percent(a!))}%` }\r\n }\r\n return { left: '0%', width: `${percent(values.value[0]!)}%` }\r\n})\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-slider\" :class=\"[`j-slider--${size}`, { 'is-disabled': disabled }]\">\r\n <div ref=\"track\" class=\"j-slider__track\" @pointerdown=\"onTrackPointerDown\">\r\n <span class=\"j-slider__range\" :style=\"fillStyle\" />\r\n <span\r\n v-for=\"(value, index) in values\"\r\n :key=\"index\"\r\n class=\"j-slider__thumb j-focusable\"\r\n :class=\"{ 'is-dragging': activeThumb === index }\"\r\n role=\"slider\"\r\n :tabindex=\"disabled ? -1 : 0\"\r\n :aria-label=\"label\"\r\n :aria-orientation=\"'horizontal'\"\r\n :aria-valuemin=\"min\"\r\n :aria-valuemax=\"max\"\r\n :aria-valuenow=\"value\"\r\n :aria-valuetext=\"display(value)\"\r\n :aria-disabled=\"disabled || undefined\"\r\n :style=\"{ left: `${percent(value)}%` }\"\r\n @pointerdown=\"onThumbPointerDown(index, $event)\"\r\n @keydown=\"onKeydown(index, $event)\"\r\n >\r\n <span v-if=\"showValue\" class=\"j-slider__bubble\">{{ display(value) }}</span>\r\n </span>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-slider {\r\n --j-slider-track: 0.25rem;\r\n --j-slider-thumb: 1rem;\r\n\r\n display: flex;\r\n align-items: center;\r\n width: 100%;\r\n padding: calc(var(--j-slider-thumb) / 2) 0;\r\n font-family: var(--juxt-font-sans);\r\n touch-action: none;\r\n}\r\n\r\n.j-slider--sm {\r\n --j-slider-track: 0.1875rem;\r\n --j-slider-thumb: 0.875rem;\r\n}\r\n\r\n.j-slider__track {\r\n position: relative;\r\n width: 100%;\r\n height: var(--j-slider-track);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-surface-active);\r\n cursor: pointer;\r\n}\r\n\r\n.j-slider.is-disabled .j-slider__track {\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n}\r\n\r\n.j-slider__range {\r\n position: absolute;\r\n top: 0;\r\n bottom: 0;\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-slider__thumb {\r\n position: absolute;\r\n top: 50%;\r\n width: var(--j-slider-thumb);\r\n height: var(--j-slider-thumb);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-surface);\r\n box-shadow: 0 0 0 1px var(--juxt-border-strong), var(--juxt-shadow-sm);\r\n cursor: grab;\r\n transform: translate(-50%, -50%);\r\n transition:\r\n box-shadow var(--juxt-duration-fast) var(--juxt-ease-out),\r\n left var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-slider__thumb.is-dragging {\r\n cursor: grabbing;\r\n transition: none;\r\n}\r\n\r\n.j-slider__thumb:hover {\r\n box-shadow: 0 0 0 1px var(--juxt-accent), var(--juxt-shadow-sm);\r\n}\r\n\r\n.j-slider__thumb:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 3px;\r\n}\r\n\r\n.j-slider.is-disabled .j-slider__thumb {\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-slider__bubble {\r\n position: absolute;\r\n bottom: calc(100% + var(--juxt-space-2));\r\n left: 50%;\r\n padding: var(--juxt-space-0-5) var(--juxt-space-1-5);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-tooltip-bg);\r\n color: var(--juxt-tooltip-fg);\r\n font-size: var(--juxt-text-2xs);\r\n font-weight: var(--juxt-weight-medium);\r\n font-variant-numeric: tabular-nums;\r\n white-space: nowrap;\r\n pointer-events: none;\r\n transform: translateX(-50%);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-slider__thumb {\r\n transition: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { Spinner } from '../../icons'\r\n\r\nexport interface SpinnerProps {\r\n size?: 'sm' | 'md' | 'lg'\r\n /** Announced to screen readers. Set to an empty string when the context already says it. */\r\n label?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<SpinnerProps>(), { size: 'md', label: 'Loading' })\r\n\r\nconst pixels = computed(() => ({ sm: 14, md: 16, lg: 20 })[props.size])\r\n</script>\r\n\r\n<template>\r\n <span class=\"j-spinner-wrap\" :role=\"label ? 'status' : undefined\">\r\n <Spinner :size=\"pixels\" />\r\n <span v-if=\"label\" class=\"j-sr-only\">{{ label }}</span>\r\n </span>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-spinner-wrap {\r\n display: inline-flex;\r\n color: var(--juxt-fg-muted);\r\n vertical-align: middle;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { Spinner } from '../../icons'\r\n\r\nexport interface SpinnerProps {\r\n size?: 'sm' | 'md' | 'lg'\r\n /** Announced to screen readers. Set to an empty string when the context already says it. */\r\n label?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<SpinnerProps>(), { size: 'md', label: 'Loading' })\r\n\r\nconst pixels = computed(() => ({ sm: 14, md: 16, lg: 20 })[props.size])\r\n</script>\r\n\r\n<template>\r\n <span class=\"j-spinner-wrap\" :role=\"label ? 'status' : undefined\">\r\n <Spinner :size=\"pixels\" />\r\n <span v-if=\"label\" class=\"j-sr-only\">{{ label }}</span>\r\n </span>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-spinner-wrap {\r\n display: inline-flex;\r\n color: var(--juxt-fg-muted);\r\n vertical-align: middle;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { IconArrowDownRight, IconArrowUpRight } from '../../icons'\r\n\r\nexport type StatTrend = 'up' | 'down' | 'neutral'\r\n\r\nexport interface StatProps {\r\n label: string\r\n value: string | number\r\n /** Delta shown next to the value, e.g. `\"+12%\"`. */\r\n delta?: string\r\n trend?: StatTrend\r\n description?: string\r\n}\r\n\r\nconst props = defineProps<StatProps>()\r\n\r\ndefineSlots<{\r\n /** Icon shown next to the label. */\r\n icon?: () => unknown\r\n}>()\r\n\r\nconst trendIcon = computed(() => (props.trend === 'up' ? IconArrowUpRight : props.trend === 'down' ? IconArrowDownRight : null))\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-stat\">\r\n <div class=\"j-stat__header\">\r\n <span v-if=\"$slots.icon\" class=\"j-stat__icon\" aria-hidden=\"true\"><slot name=\"icon\" /></span>\r\n <span class=\"j-stat__label\">{{ label }}</span>\r\n </div>\r\n <div class=\"j-stat__row\">\r\n <span class=\"j-stat__value\">{{ value }}</span>\r\n <span v-if=\"delta\" class=\"j-stat__delta\" :class=\"trend ? `j-stat__delta--${trend}` : undefined\">\r\n <component :is=\"trendIcon\" v-if=\"trendIcon\" aria-hidden=\"true\" />\r\n {{ delta }}\r\n </span>\r\n </div>\r\n <p v-if=\"description\" class=\"j-stat__description\">\r\n {{ description }}\r\n </p>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-stat {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-stat__header {\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1-5);\r\n}\r\n\r\n.j-stat__icon {\r\n display: grid;\r\n place-items: center;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-stat__icon > svg {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n\r\n.j-stat__label {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-stat__row {\r\n display: flex;\r\n align-items: baseline;\r\n gap: var(--juxt-space-2);\r\n}\r\n\r\n.j-stat__value {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-3xl);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: var(--juxt-leading-tight);\r\n letter-spacing: var(--juxt-tracking-tighter);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-stat__delta {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 0.125rem;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-stat__delta > svg {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n\r\n.j-stat__delta--up {\r\n color: var(--juxt-success-text);\r\n}\r\n\r\n.j-stat__delta--down {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-stat__description {\r\n margin: 0;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed } from 'vue'\r\nimport { IconArrowDownRight, IconArrowUpRight } from '../../icons'\r\n\r\nexport type StatTrend = 'up' | 'down' | 'neutral'\r\n\r\nexport interface StatProps {\r\n label: string\r\n value: string | number\r\n /** Delta shown next to the value, e.g. `\"+12%\"`. */\r\n delta?: string\r\n trend?: StatTrend\r\n description?: string\r\n}\r\n\r\nconst props = defineProps<StatProps>()\r\n\r\ndefineSlots<{\r\n /** Icon shown next to the label. */\r\n icon?: () => unknown\r\n}>()\r\n\r\nconst trendIcon = computed(() => (props.trend === 'up' ? IconArrowUpRight : props.trend === 'down' ? IconArrowDownRight : null))\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-stat\">\r\n <div class=\"j-stat__header\">\r\n <span v-if=\"$slots.icon\" class=\"j-stat__icon\" aria-hidden=\"true\"><slot name=\"icon\" /></span>\r\n <span class=\"j-stat__label\">{{ label }}</span>\r\n </div>\r\n <div class=\"j-stat__row\">\r\n <span class=\"j-stat__value\">{{ value }}</span>\r\n <span v-if=\"delta\" class=\"j-stat__delta\" :class=\"trend ? `j-stat__delta--${trend}` : undefined\">\r\n <component :is=\"trendIcon\" v-if=\"trendIcon\" aria-hidden=\"true\" />\r\n {{ delta }}\r\n </span>\r\n </div>\r\n <p v-if=\"description\" class=\"j-stat__description\">\r\n {{ description }}\r\n </p>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-stat {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-1-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-stat__header {\r\n display: flex;\r\n align-items: center;\r\n gap: var(--juxt-space-1-5);\r\n}\r\n\r\n.j-stat__icon {\r\n display: grid;\r\n place-items: center;\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-stat__icon > svg {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n\r\n.j-stat__label {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-stat__row {\r\n display: flex;\r\n align-items: baseline;\r\n gap: var(--juxt-space-2);\r\n}\r\n\r\n.j-stat__value {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-3xl);\r\n font-weight: var(--juxt-weight-semibold);\r\n line-height: var(--juxt-leading-tight);\r\n letter-spacing: var(--juxt-tracking-tighter);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-stat__delta {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 0.125rem;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-stat__delta > svg {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n\r\n.j-stat__delta--up {\r\n color: var(--juxt-success-text);\r\n}\r\n\r\n.j-stat__delta--down {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-stat__description {\r\n margin: 0;\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, ref, useId } from 'vue'\r\n\r\nexport interface SwitchProps {\r\n label?: string\r\n description?: string\r\n disabled?: boolean\r\n size?: 'sm' | 'md'\r\n /** Submits `value` under `name` in a form when on. */\r\n name?: string\r\n value?: string\r\n id?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<SwitchProps>(), { size: 'md', value: 'on' })\r\nconst model = defineModel<boolean>({ default: false })\r\n\r\ndefineSlots<{ default?: () => unknown, description?: () => unknown }>()\r\n\r\nconst generated = useId()\r\nconst id = computed(() => props.id ?? `j-${generated}`)\r\nconst labelId = computed(() => `${id.value}-label`)\r\nconst descriptionId = computed(() => `${id.value}-description`)\r\nconst button = ref<HTMLButtonElement | null>(null)\r\n\r\nfunction toggle() {\r\n if (props.disabled) return\r\n model.value = !model.value\r\n}\r\n\r\ndefineExpose({ focus: () => button.value?.focus() })\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-switch\"\r\n :class=\"[$attrs.class, `j-switch--${size}`, { 'is-disabled': disabled }]\"\r\n :style=\"$attrs.style as any\"\r\n :data-state=\"model ? 'on' : 'off'\"\r\n >\r\n <button\r\n :id=\"id\"\r\n ref=\"button\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"button\"\r\n role=\"switch\"\r\n class=\"j-switch__track\"\r\n :aria-checked=\"model\"\r\n :aria-labelledby=\"label || $slots.default ? labelId : undefined\"\r\n :aria-describedby=\"description || $slots.description ? descriptionId : undefined\"\r\n :disabled=\"disabled\"\r\n @click=\"toggle\"\r\n >\r\n <span class=\"j-switch__thumb\" />\r\n </button>\r\n <input v-if=\"name && model\" type=\"hidden\" :name=\"name\" :value=\"value\" />\r\n <span v-if=\"label || $slots.default || description || $slots.description\" class=\"j-switch__text\">\r\n <label v-if=\"label || $slots.default\" :id=\"labelId\" :for=\"id\" class=\"j-switch__label\">\r\n <slot>{{ label }}</slot>\r\n </label>\r\n <span v-if=\"description || $slots.description\" :id=\"descriptionId\" class=\"j-switch__description\">\r\n <slot name=\"description\">{{ description }}</slot>\r\n </span>\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-switch {\r\n --j-switch-w: 2rem;\r\n --j-switch-h: 1.125rem;\r\n --j-switch-pad: 2px;\r\n --j-switch-thumb: calc(var(--j-switch-h) - var(--j-switch-pad) * 2);\r\n\r\n display: inline-flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-switch--sm {\r\n --j-switch-w: 1.625rem;\r\n --j-switch-h: 0.9375rem;\r\n}\r\n\r\n.j-switch__track {\r\n position: relative;\r\n flex: none;\r\n width: var(--j-switch-w);\r\n height: var(--j-switch-h);\r\n margin: calc((var(--juxt-text-md) * var(--juxt-leading-snug) - var(--j-switch-h)) / 2) 0 0;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-border-strong);\r\n box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.08);\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n background-color var(--juxt-duration-normal) var(--juxt-ease-standard),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-switch__track:hover:not(:disabled) {\r\n background: var(--juxt-fg-disabled);\r\n}\r\n\r\n.j-switch__track:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__track {\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__track:hover:not(:disabled) {\r\n background: var(--juxt-accent-hover);\r\n}\r\n\r\n.j-switch__thumb {\r\n position: absolute;\r\n top: var(--j-switch-pad);\r\n left: var(--j-switch-pad);\r\n width: var(--j-switch-thumb);\r\n height: var(--j-switch-thumb);\r\n border-radius: var(--juxt-radius-full);\r\n background: #fff;\r\n box-shadow: 0 1px 2px rgb(0 0 0 / 0.2), 0 0 0 0.5px rgb(0 0 0 / 0.04);\r\n transition:\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out),\r\n width var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__thumb {\r\n transform: translateX(calc(var(--j-switch-w) - var(--j-switch-thumb) - var(--j-switch-pad) * 2));\r\n}\r\n\r\n/* A whisper of stretch while pressed, so the thumb feels held. */\r\n.j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n width: calc(var(--j-switch-thumb) + 3px);\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n transform: translateX(calc(var(--j-switch-w) - var(--j-switch-thumb) - var(--j-switch-pad) * 2 - 3px));\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n width: var(--j-switch-thumb);\r\n }\r\n\r\n .j-switch[data-state='on'] .j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n transform: translateX(calc(var(--j-switch-w) - var(--j-switch-thumb) - var(--j-switch-pad) * 2));\r\n }\r\n}\r\n\r\n.j-switch__text {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-switch__label {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-switch__description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-switch.is-disabled .j-switch__track {\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n}\r\n\r\n.j-switch.is-disabled .j-switch__label {\r\n color: var(--juxt-fg-muted);\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, ref, useId } from 'vue'\r\n\r\nexport interface SwitchProps {\r\n label?: string\r\n description?: string\r\n disabled?: boolean\r\n size?: 'sm' | 'md'\r\n /** Submits `value` under `name` in a form when on. */\r\n name?: string\r\n value?: string\r\n id?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<SwitchProps>(), { size: 'md', value: 'on' })\r\nconst model = defineModel<boolean>({ default: false })\r\n\r\ndefineSlots<{ default?: () => unknown, description?: () => unknown }>()\r\n\r\nconst generated = useId()\r\nconst id = computed(() => props.id ?? `j-${generated}`)\r\nconst labelId = computed(() => `${id.value}-label`)\r\nconst descriptionId = computed(() => `${id.value}-description`)\r\nconst button = ref<HTMLButtonElement | null>(null)\r\n\r\nfunction toggle() {\r\n if (props.disabled) return\r\n model.value = !model.value\r\n}\r\n\r\ndefineExpose({ focus: () => button.value?.focus() })\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-switch\"\r\n :class=\"[$attrs.class, `j-switch--${size}`, { 'is-disabled': disabled }]\"\r\n :style=\"$attrs.style as any\"\r\n :data-state=\"model ? 'on' : 'off'\"\r\n >\r\n <button\r\n :id=\"id\"\r\n ref=\"button\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n type=\"button\"\r\n role=\"switch\"\r\n class=\"j-switch__track\"\r\n :aria-checked=\"model\"\r\n :aria-labelledby=\"label || $slots.default ? labelId : undefined\"\r\n :aria-describedby=\"description || $slots.description ? descriptionId : undefined\"\r\n :disabled=\"disabled\"\r\n @click=\"toggle\"\r\n >\r\n <span class=\"j-switch__thumb\" />\r\n </button>\r\n <input v-if=\"name && model\" type=\"hidden\" :name=\"name\" :value=\"value\" />\r\n <span v-if=\"label || $slots.default || description || $slots.description\" class=\"j-switch__text\">\r\n <label v-if=\"label || $slots.default\" :id=\"labelId\" :for=\"id\" class=\"j-switch__label\">\r\n <slot>{{ label }}</slot>\r\n </label>\r\n <span v-if=\"description || $slots.description\" :id=\"descriptionId\" class=\"j-switch__description\">\r\n <slot name=\"description\">{{ description }}</slot>\r\n </span>\r\n </span>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-switch {\r\n --j-switch-w: 2rem;\r\n --j-switch-h: 1.125rem;\r\n --j-switch-pad: 2px;\r\n --j-switch-thumb: calc(var(--j-switch-h) - var(--j-switch-pad) * 2);\r\n\r\n display: inline-flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-switch--sm {\r\n --j-switch-w: 1.625rem;\r\n --j-switch-h: 0.9375rem;\r\n}\r\n\r\n.j-switch__track {\r\n position: relative;\r\n flex: none;\r\n width: var(--j-switch-w);\r\n height: var(--j-switch-h);\r\n margin: calc((var(--juxt-text-md) * var(--juxt-leading-snug) - var(--j-switch-h)) / 2) 0 0;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-border-strong);\r\n box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.08);\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n background-color var(--juxt-duration-normal) var(--juxt-ease-standard),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-switch__track:hover:not(:disabled) {\r\n background: var(--juxt-fg-disabled);\r\n}\r\n\r\n.j-switch__track:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__track {\r\n background: var(--juxt-accent);\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__track:hover:not(:disabled) {\r\n background: var(--juxt-accent-hover);\r\n}\r\n\r\n.j-switch__thumb {\r\n position: absolute;\r\n top: var(--j-switch-pad);\r\n left: var(--j-switch-pad);\r\n width: var(--j-switch-thumb);\r\n height: var(--j-switch-thumb);\r\n border-radius: var(--juxt-radius-full);\r\n background: #fff;\r\n box-shadow: 0 1px 2px rgb(0 0 0 / 0.2), 0 0 0 0.5px rgb(0 0 0 / 0.04);\r\n transition:\r\n transform var(--juxt-duration-normal) var(--juxt-ease-out),\r\n width var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__thumb {\r\n transform: translateX(calc(var(--j-switch-w) - var(--j-switch-thumb) - var(--j-switch-pad) * 2));\r\n}\r\n\r\n/* A whisper of stretch while pressed, so the thumb feels held. */\r\n.j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n width: calc(var(--j-switch-thumb) + 3px);\r\n}\r\n\r\n.j-switch[data-state='on'] .j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n transform: translateX(calc(var(--j-switch-w) - var(--j-switch-thumb) - var(--j-switch-pad) * 2 - 3px));\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n width: var(--j-switch-thumb);\r\n }\r\n\r\n .j-switch[data-state='on'] .j-switch__track:active:not(:disabled) .j-switch__thumb {\r\n transform: translateX(calc(var(--j-switch-w) - var(--j-switch-thumb) - var(--j-switch-pad) * 2));\r\n }\r\n}\r\n\r\n.j-switch__text {\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-switch__label {\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n cursor: pointer;\r\n user-select: none;\r\n}\r\n\r\n.j-switch__description {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-switch.is-disabled .j-switch__track {\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n}\r\n\r\n.j-switch.is-disabled .j-switch__label {\r\n color: var(--juxt-fg-muted);\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch } from 'vue'\r\nimport { focusElement } from '../../utils/dom'\r\n\r\nexport interface TabItem {\r\n value: string\r\n label: string\r\n disabled?: boolean\r\n}\r\n\r\nexport interface TabsProps {\r\n items: TabItem[]\r\n /** `line` for page sections, `pill` for compact view switches. */\r\n variant?: 'line' | 'pill'\r\n size?: 'sm' | 'md'\r\n /** Accessible name for the tab list. */\r\n label?: string\r\n /** Stretch tabs to fill the width. */\r\n fill?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<TabsProps>(), { variant: 'line', size: 'md' })\r\n\r\nconst model = defineModel<string>()\r\n\r\ndefineSlots<{\r\n /** Custom tab label. */\r\n tab?: (props: { item: TabItem, selected: boolean }) => unknown\r\n /** Panels are named after each item's `value`. */\r\n [value: string]: ((props: { item: TabItem, selected: boolean }) => unknown) | undefined\r\n}>()\r\n\r\nconst id = `j-${useId()}`\r\nconst tabId = (value: string) => `${id}-tab-${value}`\r\nconst panelId = (value: string) => `${id}-panel-${value}`\r\n\r\nconst selected = computed(() => model.value ?? props.items.find(item => !item.disabled)?.value)\r\n\r\nconst list = ref<HTMLElement | null>(null)\r\nconst indicator = ref({ x: 0, width: 0, ready: false })\r\n// The indicator glides only for deliberate changes, never for layout shifts.\r\nconst animated = ref(false)\r\n\r\nfunction measure() {\r\n const el = list.value?.querySelector<HTMLElement>('[role=\"tab\"][aria-selected=\"true\"]')\r\n indicator.value = el ? { x: el.offsetLeft, width: el.offsetWidth, ready: true } : { ...indicator.value, ready: false }\r\n}\r\n\r\nfunction measureWithoutMotion() {\r\n animated.value = false\r\n measure()\r\n requestAnimationFrame(() => requestAnimationFrame(() => (animated.value = true)))\r\n}\r\n\r\nlet observer: ResizeObserver | undefined\r\nonMounted(() => {\r\n measureWithoutMotion()\r\n observer = new ResizeObserver(measureWithoutMotion)\r\n if (list.value) observer.observe(list.value)\r\n // Web fonts change tab widths once they arrive.\r\n document.fonts?.ready.then(measureWithoutMotion)\r\n})\r\nonBeforeUnmount(() => observer?.disconnect())\r\n\r\nwatch(selected, () => nextTick(measure), { flush: 'post' })\r\nwatch(() => props.items, () => nextTick(measure), { deep: true, flush: 'post' })\r\n\r\nfunction select(item: TabItem) {\r\n if (item.disabled) return\r\n model.value = item.value\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const enabled = props.items.filter(item => !item.disabled)\r\n if (!enabled.length) return\r\n const current = enabled.findIndex(item => item.value === selected.value)\r\n let next: number | undefined\r\n if (event.key === 'ArrowRight') next = (current + 1) % enabled.length\r\n else if (event.key === 'ArrowLeft') next = (current - 1 + enabled.length) % enabled.length\r\n else if (event.key === 'Home') next = 0\r\n else if (event.key === 'End') next = enabled.length - 1\r\n if (next === undefined) return\r\n event.preventDefault()\r\n const item = enabled[next]!\r\n select(item)\r\n nextTick(() => focusElement(document.getElementById(tabId(item.value))))\r\n}\r\n\r\nconst indicatorStyle = computed(() => ({\r\n transform: `translateX(${indicator.value.x}px)`,\r\n width: `${indicator.value.width}px`,\r\n}))\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-tabs\" :class=\"[`j-tabs--${variant}`, `j-tabs--${size}`, { 'j-tabs--fill': fill }]\">\r\n <div\r\n ref=\"list\"\r\n role=\"tablist\"\r\n aria-orientation=\"horizontal\"\r\n :aria-label=\"label\"\r\n class=\"j-tabs__list\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <span\r\n class=\"j-tabs__indicator\"\r\n :class=\"{ 'is-ready': indicator.ready, 'is-animated': animated }\"\r\n :style=\"indicatorStyle\"\r\n aria-hidden=\"true\"\r\n />\r\n <button\r\n v-for=\"item in items\"\r\n :id=\"tabId(item.value)\"\r\n :key=\"item.value\"\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"j-tabs__tab\"\r\n :aria-selected=\"item.value === selected\"\r\n :aria-controls=\"panelId(item.value)\"\r\n :tabindex=\"item.value === selected ? 0 : -1\"\r\n :disabled=\"item.disabled\"\r\n @click=\"select(item)\"\r\n >\r\n <slot name=\"tab\" :item=\"item\" :selected=\"item.value === selected\">\r\n {{ item.label }}\r\n </slot>\r\n </button>\r\n </div>\r\n <template v-for=\"item in items\" :key=\"item.value\">\r\n <div\r\n v-if=\"$slots[item.value]\"\r\n :id=\"panelId(item.value)\"\r\n role=\"tabpanel\"\r\n class=\"j-tabs__panel\"\r\n :aria-labelledby=\"tabId(item.value)\"\r\n :hidden=\"item.value !== selected\"\r\n tabindex=\"0\"\r\n >\r\n <slot :name=\"item.value\" :item=\"item\" :selected=\"item.value === selected\" />\r\n </div>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-tabs {\r\n --j-tab-height: var(--juxt-control-md);\r\n --j-tab-font: var(--juxt-text-md);\r\n\r\n display: flex;\r\n flex-direction: column;\r\n min-width: 0;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-tabs--sm {\r\n --j-tab-height: var(--juxt-control-sm);\r\n --j-tab-font: var(--juxt-text-sm);\r\n}\r\n\r\n.j-tabs__list {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n max-width: 100%;\r\n overflow-x: auto;\r\n scrollbar-width: none;\r\n}\r\n\r\n.j-tabs__list::-webkit-scrollbar {\r\n display: none;\r\n}\r\n\r\n.j-tabs__tab {\r\n position: relative;\r\n z-index: 1;\r\n display: inline-flex;\r\n flex: none;\r\n align-items: center;\r\n justify-content: center;\r\n gap: var(--juxt-space-1-5);\r\n height: var(--j-tab-height);\r\n padding: 0 var(--juxt-space-3);\r\n border: 0;\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n font: inherit;\r\n font-size: var(--j-tab-font);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n white-space: nowrap;\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: -2px;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-tabs__tab:hover:not(:disabled) {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-tabs__tab[aria-selected='true'] {\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-tabs__tab:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n}\r\n\r\n.j-tabs__tab:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-tabs--fill .j-tabs__tab {\r\n flex: 1;\r\n}\r\n\r\n.j-tabs__indicator {\r\n position: absolute;\r\n left: 0;\r\n opacity: 0;\r\n pointer-events: none;\r\n}\r\n\r\n.j-tabs__indicator.is-ready {\r\n opacity: 1;\r\n}\r\n\r\n.j-tabs__indicator.is-animated {\r\n transition:\r\n transform var(--juxt-duration-slow) var(--juxt-ease-out),\r\n width var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-tabs__indicator.is-animated {\r\n transition: none;\r\n }\r\n}\r\n\r\n/* Line */\r\n.j-tabs--line .j-tabs__list {\r\n gap: var(--juxt-space-1);\r\n box-shadow: inset 0 -1px 0 var(--juxt-border);\r\n}\r\n\r\n.j-tabs--line .j-tabs__tab {\r\n padding: 0 var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-sm) var(--juxt-radius-sm) 0 0;\r\n}\r\n\r\n.j-tabs--line .j-tabs__indicator {\r\n bottom: 0;\r\n height: 2px;\r\n border-radius: 2px 2px 0 0;\r\n background: var(--juxt-fg);\r\n}\r\n\r\n/* Pill */\r\n.j-tabs--pill .j-tabs__list {\r\n align-self: flex-start;\r\n padding: 3px;\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-sunken);\r\n box-shadow: inset 0 0 0 1px var(--juxt-border-subtle);\r\n}\r\n\r\n.j-tabs--pill.j-tabs--fill .j-tabs__list {\r\n align-self: stretch;\r\n}\r\n\r\n.j-tabs--pill .j-tabs__tab {\r\n --j-tab-height: calc(var(--juxt-control-md) - 6px);\r\n\r\n border-radius: var(--juxt-radius-sm);\r\n}\r\n\r\n.j-tabs--pill.j-tabs--sm .j-tabs__tab {\r\n --j-tab-height: calc(var(--juxt-control-sm) - 6px);\r\n\r\n padding: 0 var(--juxt-space-2-5);\r\n}\r\n\r\n.j-tabs--pill .j-tabs__indicator {\r\n top: 3px;\r\n bottom: 3px;\r\n border-radius: var(--juxt-radius-sm);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-sm);\r\n}\r\n\r\n.dark .j-tabs--pill .j-tabs__indicator,\r\n[data-theme='dark'] .j-tabs--pill .j-tabs__indicator {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n/* Panels */\r\n.j-tabs__panel {\r\n padding-top: var(--juxt-space-4);\r\n border-radius: var(--juxt-radius-sm);\r\n outline: 2px solid transparent;\r\n outline-offset: 2px;\r\n animation: j-tabs-panel-in var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-tabs__panel:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n}\r\n\r\n@keyframes j-tabs-panel-in {\r\n from {\r\n opacity: 0;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch } from 'vue'\r\nimport { focusElement } from '../../utils/dom'\r\n\r\nexport interface TabItem {\r\n value: string\r\n label: string\r\n disabled?: boolean\r\n}\r\n\r\nexport interface TabsProps {\r\n items: TabItem[]\r\n /** `line` for page sections, `pill` for compact view switches. */\r\n variant?: 'line' | 'pill'\r\n size?: 'sm' | 'md'\r\n /** Accessible name for the tab list. */\r\n label?: string\r\n /** Stretch tabs to fill the width. */\r\n fill?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<TabsProps>(), { variant: 'line', size: 'md' })\r\n\r\nconst model = defineModel<string>()\r\n\r\ndefineSlots<{\r\n /** Custom tab label. */\r\n tab?: (props: { item: TabItem, selected: boolean }) => unknown\r\n /** Panels are named after each item's `value`. */\r\n [value: string]: ((props: { item: TabItem, selected: boolean }) => unknown) | undefined\r\n}>()\r\n\r\nconst id = `j-${useId()}`\r\nconst tabId = (value: string) => `${id}-tab-${value}`\r\nconst panelId = (value: string) => `${id}-panel-${value}`\r\n\r\nconst selected = computed(() => model.value ?? props.items.find(item => !item.disabled)?.value)\r\n\r\nconst list = ref<HTMLElement | null>(null)\r\nconst indicator = ref({ x: 0, width: 0, ready: false })\r\n// The indicator glides only for deliberate changes, never for layout shifts.\r\nconst animated = ref(false)\r\n\r\nfunction measure() {\r\n const el = list.value?.querySelector<HTMLElement>('[role=\"tab\"][aria-selected=\"true\"]')\r\n indicator.value = el ? { x: el.offsetLeft, width: el.offsetWidth, ready: true } : { ...indicator.value, ready: false }\r\n}\r\n\r\nfunction measureWithoutMotion() {\r\n animated.value = false\r\n measure()\r\n requestAnimationFrame(() => requestAnimationFrame(() => (animated.value = true)))\r\n}\r\n\r\nlet observer: ResizeObserver | undefined\r\nonMounted(() => {\r\n measureWithoutMotion()\r\n observer = new ResizeObserver(measureWithoutMotion)\r\n if (list.value) observer.observe(list.value)\r\n // Web fonts change tab widths once they arrive.\r\n document.fonts?.ready.then(measureWithoutMotion)\r\n})\r\nonBeforeUnmount(() => observer?.disconnect())\r\n\r\nwatch(selected, () => nextTick(measure), { flush: 'post' })\r\nwatch(() => props.items, () => nextTick(measure), { deep: true, flush: 'post' })\r\n\r\nfunction select(item: TabItem) {\r\n if (item.disabled) return\r\n model.value = item.value\r\n}\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const enabled = props.items.filter(item => !item.disabled)\r\n if (!enabled.length) return\r\n const current = enabled.findIndex(item => item.value === selected.value)\r\n let next: number | undefined\r\n if (event.key === 'ArrowRight') next = (current + 1) % enabled.length\r\n else if (event.key === 'ArrowLeft') next = (current - 1 + enabled.length) % enabled.length\r\n else if (event.key === 'Home') next = 0\r\n else if (event.key === 'End') next = enabled.length - 1\r\n if (next === undefined) return\r\n event.preventDefault()\r\n const item = enabled[next]!\r\n select(item)\r\n nextTick(() => focusElement(document.getElementById(tabId(item.value))))\r\n}\r\n\r\nconst indicatorStyle = computed(() => ({\r\n transform: `translateX(${indicator.value.x}px)`,\r\n width: `${indicator.value.width}px`,\r\n}))\r\n</script>\r\n\r\n<template>\r\n <div class=\"j-tabs\" :class=\"[`j-tabs--${variant}`, `j-tabs--${size}`, { 'j-tabs--fill': fill }]\">\r\n <div\r\n ref=\"list\"\r\n role=\"tablist\"\r\n aria-orientation=\"horizontal\"\r\n :aria-label=\"label\"\r\n class=\"j-tabs__list\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <span\r\n class=\"j-tabs__indicator\"\r\n :class=\"{ 'is-ready': indicator.ready, 'is-animated': animated }\"\r\n :style=\"indicatorStyle\"\r\n aria-hidden=\"true\"\r\n />\r\n <button\r\n v-for=\"item in items\"\r\n :id=\"tabId(item.value)\"\r\n :key=\"item.value\"\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"j-tabs__tab\"\r\n :aria-selected=\"item.value === selected\"\r\n :aria-controls=\"panelId(item.value)\"\r\n :tabindex=\"item.value === selected ? 0 : -1\"\r\n :disabled=\"item.disabled\"\r\n @click=\"select(item)\"\r\n >\r\n <slot name=\"tab\" :item=\"item\" :selected=\"item.value === selected\">\r\n {{ item.label }}\r\n </slot>\r\n </button>\r\n </div>\r\n <template v-for=\"item in items\" :key=\"item.value\">\r\n <div\r\n v-if=\"$slots[item.value]\"\r\n :id=\"panelId(item.value)\"\r\n role=\"tabpanel\"\r\n class=\"j-tabs__panel\"\r\n :aria-labelledby=\"tabId(item.value)\"\r\n :hidden=\"item.value !== selected\"\r\n tabindex=\"0\"\r\n >\r\n <slot :name=\"item.value\" :item=\"item\" :selected=\"item.value === selected\" />\r\n </div>\r\n </template>\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-tabs {\r\n --j-tab-height: var(--juxt-control-md);\r\n --j-tab-font: var(--juxt-text-md);\r\n\r\n display: flex;\r\n flex-direction: column;\r\n min-width: 0;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-tabs--sm {\r\n --j-tab-height: var(--juxt-control-sm);\r\n --j-tab-font: var(--juxt-text-sm);\r\n}\r\n\r\n.j-tabs__list {\r\n position: relative;\r\n display: flex;\r\n align-items: center;\r\n max-width: 100%;\r\n overflow-x: auto;\r\n scrollbar-width: none;\r\n}\r\n\r\n.j-tabs__list::-webkit-scrollbar {\r\n display: none;\r\n}\r\n\r\n.j-tabs__tab {\r\n position: relative;\r\n z-index: 1;\r\n display: inline-flex;\r\n flex: none;\r\n align-items: center;\r\n justify-content: center;\r\n gap: var(--juxt-space-1-5);\r\n height: var(--j-tab-height);\r\n padding: 0 var(--juxt-space-3);\r\n border: 0;\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n font: inherit;\r\n font-size: var(--j-tab-font);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n white-space: nowrap;\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: -2px;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-tabs__tab:hover:not(:disabled) {\r\n color: var(--juxt-fg-secondary);\r\n}\r\n\r\n.j-tabs__tab[aria-selected='true'] {\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-tabs__tab:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n}\r\n\r\n.j-tabs__tab:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n\r\n.j-tabs--fill .j-tabs__tab {\r\n flex: 1;\r\n}\r\n\r\n.j-tabs__indicator {\r\n position: absolute;\r\n left: 0;\r\n opacity: 0;\r\n pointer-events: none;\r\n}\r\n\r\n.j-tabs__indicator.is-ready {\r\n opacity: 1;\r\n}\r\n\r\n.j-tabs__indicator.is-animated {\r\n transition:\r\n transform var(--juxt-duration-slow) var(--juxt-ease-out),\r\n width var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n .j-tabs__indicator.is-animated {\r\n transition: none;\r\n }\r\n}\r\n\r\n/* Line */\r\n.j-tabs--line .j-tabs__list {\r\n gap: var(--juxt-space-1);\r\n box-shadow: inset 0 -1px 0 var(--juxt-border);\r\n}\r\n\r\n.j-tabs--line .j-tabs__tab {\r\n padding: 0 var(--juxt-space-2);\r\n border-radius: var(--juxt-radius-sm) var(--juxt-radius-sm) 0 0;\r\n}\r\n\r\n.j-tabs--line .j-tabs__indicator {\r\n bottom: 0;\r\n height: 2px;\r\n border-radius: 2px 2px 0 0;\r\n background: var(--juxt-fg);\r\n}\r\n\r\n/* Pill */\r\n.j-tabs--pill .j-tabs__list {\r\n align-self: flex-start;\r\n padding: 3px;\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-sunken);\r\n box-shadow: inset 0 0 0 1px var(--juxt-border-subtle);\r\n}\r\n\r\n.j-tabs--pill.j-tabs--fill .j-tabs__list {\r\n align-self: stretch;\r\n}\r\n\r\n.j-tabs--pill .j-tabs__tab {\r\n --j-tab-height: calc(var(--juxt-control-md) - 6px);\r\n\r\n border-radius: var(--juxt-radius-sm);\r\n}\r\n\r\n.j-tabs--pill.j-tabs--sm .j-tabs__tab {\r\n --j-tab-height: calc(var(--juxt-control-sm) - 6px);\r\n\r\n padding: 0 var(--juxt-space-2-5);\r\n}\r\n\r\n.j-tabs--pill .j-tabs__indicator {\r\n top: 3px;\r\n bottom: 3px;\r\n border-radius: var(--juxt-radius-sm);\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-sm);\r\n}\r\n\r\n.dark .j-tabs--pill .j-tabs__indicator,\r\n[data-theme='dark'] .j-tabs--pill .j-tabs__indicator {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n/* Panels */\r\n.j-tabs__panel {\r\n padding-top: var(--juxt-space-4);\r\n border-radius: var(--juxt-radius-sm);\r\n outline: 2px solid transparent;\r\n outline-offset: 2px;\r\n animation: j-tabs-panel-in var(--juxt-duration-normal) var(--juxt-ease-out);\r\n}\r\n\r\n.j-tabs__panel:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n}\r\n\r\n@keyframes j-tabs-panel-in {\r\n from {\r\n opacity: 0;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, useSlots } from 'vue'\r\nimport { IconX } from '../../icons'\r\n\r\nexport type TagVariant = 'neutral' | 'success' | 'warning' | 'destructive' | 'accent'\r\n\r\nexport interface TagProps {\r\n variant?: TagVariant\r\n size?: 'sm' | 'md'\r\n disabled?: boolean\r\n /** Accessible label for the remove button, e.g. `\"Remove Engineering\"`. Defaults to \"Remove\". */\r\n removeLabel?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<TagProps>(), { variant: 'neutral', size: 'md' })\r\n\r\nconst emit = defineEmits<{ remove: [] }>()\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n\r\nconst slots = useSlots()\r\n// Falls back to the tag's own text so a list of tags without an explicit\r\n// `removeLabel` each still announce a distinct name, not several \"Remove\"s.\r\nconst text = computed(() => slots.default?.().map(vnode => (typeof vnode.children === 'string' ? vnode.children : '')).join('').trim())\r\nconst removeButtonLabel = computed(() => props.removeLabel || (text.value ? `Remove ${text.value}` : 'Remove'))\r\n\r\nfunction onRemove() {\r\n if (props.disabled) return\r\n emit('remove')\r\n}\r\n</script>\r\n\r\n<template>\r\n <span class=\"j-tag\" :class=\"[`j-tag--${variant}`, `j-tag--${size}`, { 'is-disabled': disabled }]\">\r\n <span v-if=\"$slots.leading\" class=\"j-tag__icon\"><slot name=\"leading\" /></span>\r\n <span class=\"j-tag__label\"><slot /></span>\r\n <button type=\"button\" class=\"j-tag__remove j-focusable\" :aria-label=\"removeButtonLabel\" :disabled=\"disabled\" @click=\"onRemove\">\r\n <IconX />\r\n </button>\r\n </span>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n/*\r\n * Tags are pills, not stamps | fully rounded and carrying their own remove\r\n * affordance, which is what sets them apart from the static JBadge.\r\n */\r\n.j-tag {\r\n --j-tag-fg: var(--juxt-fg-secondary);\r\n --j-tag-bg: var(--juxt-surface-active);\r\n\r\n display: inline-flex;\r\n flex: none;\r\n align-items: center;\r\n gap: 0.3125rem;\r\n height: 1.375rem;\r\n padding: 0 var(--juxt-space-1) 0 var(--juxt-space-2-5);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--j-tag-bg);\r\n color: var(--j-tag-fg);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-tag--sm {\r\n height: 1.125rem;\r\n padding: 0 0.1875rem 0 var(--juxt-space-2);\r\n font-size: var(--juxt-text-2xs);\r\n}\r\n\r\n.j-tag--success {\r\n --j-tag-fg: var(--juxt-success-text);\r\n --j-tag-bg: var(--juxt-success-soft);\r\n}\r\n\r\n.j-tag--warning {\r\n --j-tag-fg: var(--juxt-warning-text);\r\n --j-tag-bg: var(--juxt-warning-soft);\r\n}\r\n\r\n.j-tag--destructive {\r\n --j-tag-fg: var(--juxt-danger-text);\r\n --j-tag-bg: var(--juxt-danger-soft);\r\n}\r\n\r\n.j-tag--accent {\r\n --j-tag-fg: var(--juxt-accent-fg);\r\n --j-tag-bg: var(--juxt-accent-strong);\r\n}\r\n\r\n.j-tag__icon {\r\n display: inline-flex;\r\n margin-left: -0.0625rem;\r\n}\r\n\r\n.j-tag__icon > svg {\r\n width: 0.75rem;\r\n height: 0.75rem;\r\n}\r\n\r\n.j-tag__label {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.j-tag__remove {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 1.125rem;\r\n height: 1.125rem;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-full);\r\n background: transparent;\r\n color: inherit;\r\n cursor: pointer;\r\n opacity: 0.7;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-tag--sm .j-tag__remove {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n\r\n.j-tag__remove:hover:not(:disabled) {\r\n background: rgb(0 0 0 / 0.08);\r\n opacity: 1;\r\n}\r\n\r\n.dark .j-tag__remove:hover:not(:disabled),\r\n[data-theme='dark'] .j-tag__remove:hover:not(:disabled) {\r\n background: rgb(255 255 255 / 0.12);\r\n}\r\n\r\n.j-tag__remove > svg {\r\n width: 0.625rem;\r\n height: 0.625rem;\r\n}\r\n\r\n.j-tag--sm .j-tag__remove > svg {\r\n width: 0.5rem;\r\n height: 0.5rem;\r\n}\r\n\r\n.j-tag.is-disabled {\r\n opacity: 0.5;\r\n}\r\n\r\n.j-tag__remove:disabled {\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, useSlots } from 'vue'\r\nimport { IconX } from '../../icons'\r\n\r\nexport type TagVariant = 'neutral' | 'success' | 'warning' | 'destructive' | 'accent'\r\n\r\nexport interface TagProps {\r\n variant?: TagVariant\r\n size?: 'sm' | 'md'\r\n disabled?: boolean\r\n /** Accessible label for the remove button, e.g. `\"Remove Engineering\"`. Defaults to \"Remove\". */\r\n removeLabel?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<TagProps>(), { variant: 'neutral', size: 'md' })\r\n\r\nconst emit = defineEmits<{ remove: [] }>()\r\n\r\ndefineSlots<{ default?: () => unknown, leading?: () => unknown }>()\r\n\r\nconst slots = useSlots()\r\n// Falls back to the tag's own text so a list of tags without an explicit\r\n// `removeLabel` each still announce a distinct name, not several \"Remove\"s.\r\nconst text = computed(() => slots.default?.().map(vnode => (typeof vnode.children === 'string' ? vnode.children : '')).join('').trim())\r\nconst removeButtonLabel = computed(() => props.removeLabel || (text.value ? `Remove ${text.value}` : 'Remove'))\r\n\r\nfunction onRemove() {\r\n if (props.disabled) return\r\n emit('remove')\r\n}\r\n</script>\r\n\r\n<template>\r\n <span class=\"j-tag\" :class=\"[`j-tag--${variant}`, `j-tag--${size}`, { 'is-disabled': disabled }]\">\r\n <span v-if=\"$slots.leading\" class=\"j-tag__icon\"><slot name=\"leading\" /></span>\r\n <span class=\"j-tag__label\"><slot /></span>\r\n <button type=\"button\" class=\"j-tag__remove j-focusable\" :aria-label=\"removeButtonLabel\" :disabled=\"disabled\" @click=\"onRemove\">\r\n <IconX />\r\n </button>\r\n </span>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n/*\r\n * Tags are pills, not stamps | fully rounded and carrying their own remove\r\n * affordance, which is what sets them apart from the static JBadge.\r\n */\r\n.j-tag {\r\n --j-tag-fg: var(--juxt-fg-secondary);\r\n --j-tag-bg: var(--juxt-surface-active);\r\n\r\n display: inline-flex;\r\n flex: none;\r\n align-items: center;\r\n gap: 0.3125rem;\r\n height: 1.375rem;\r\n padding: 0 var(--juxt-space-1) 0 var(--juxt-space-2-5);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--j-tag-bg);\r\n color: var(--j-tag-fg);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1;\r\n white-space: nowrap;\r\n}\r\n\r\n.j-tag--sm {\r\n height: 1.125rem;\r\n padding: 0 0.1875rem 0 var(--juxt-space-2);\r\n font-size: var(--juxt-text-2xs);\r\n}\r\n\r\n.j-tag--success {\r\n --j-tag-fg: var(--juxt-success-text);\r\n --j-tag-bg: var(--juxt-success-soft);\r\n}\r\n\r\n.j-tag--warning {\r\n --j-tag-fg: var(--juxt-warning-text);\r\n --j-tag-bg: var(--juxt-warning-soft);\r\n}\r\n\r\n.j-tag--destructive {\r\n --j-tag-fg: var(--juxt-danger-text);\r\n --j-tag-bg: var(--juxt-danger-soft);\r\n}\r\n\r\n.j-tag--accent {\r\n --j-tag-fg: var(--juxt-accent-fg);\r\n --j-tag-bg: var(--juxt-accent-strong);\r\n}\r\n\r\n.j-tag__icon {\r\n display: inline-flex;\r\n margin-left: -0.0625rem;\r\n}\r\n\r\n.j-tag__icon > svg {\r\n width: 0.75rem;\r\n height: 0.75rem;\r\n}\r\n\r\n.j-tag__label {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n}\r\n\r\n.j-tag__remove {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 1.125rem;\r\n height: 1.125rem;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-full);\r\n background: transparent;\r\n color: inherit;\r\n cursor: pointer;\r\n opacity: 0.7;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-tag--sm .j-tag__remove {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n\r\n.j-tag__remove:hover:not(:disabled) {\r\n background: rgb(0 0 0 / 0.08);\r\n opacity: 1;\r\n}\r\n\r\n.dark .j-tag__remove:hover:not(:disabled),\r\n[data-theme='dark'] .j-tag__remove:hover:not(:disabled) {\r\n background: rgb(255 255 255 / 0.12);\r\n}\r\n\r\n.j-tag__remove > svg {\r\n width: 0.625rem;\r\n height: 0.625rem;\r\n}\r\n\r\n.j-tag--sm .j-tag__remove > svg {\r\n width: 0.5rem;\r\n height: 0.5rem;\r\n}\r\n\r\n.j-tag.is-disabled {\r\n opacity: 0.5;\r\n}\r\n\r\n.j-tag__remove:disabled {\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { FieldProps } from '../field/useField'\r\nimport { nextTick, onMounted, ref, watch } from 'vue'\r\nimport JFieldShell from '../field/JFieldShell.vue'\r\nimport { useField } from '../field/useField'\r\n\r\nexport interface TextareaProps extends FieldProps {\r\n placeholder?: string\r\n rows?: number\r\n /** Grow with the content, up to `maxRows`. */\r\n autoresize?: boolean\r\n maxRows?: number\r\n readonly?: boolean\r\n name?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<TextareaProps>(), {\r\n rows: 3,\r\n maxRows: 12,\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<string | null>({ default: '' })\r\n\r\ndefineSlots<{ label?: () => unknown }>()\r\n\r\nconst field = useField(props)\r\nconst textarea = ref<HTMLTextAreaElement | null>(null)\r\n\r\nfunction resize() {\r\n const el = textarea.value\r\n if (!props.autoresize || !el) return\r\n const style = getComputedStyle(el)\r\n const line = Number.parseFloat(style.lineHeight) || 20\r\n const chrome = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom)\r\n el.style.height = 'auto'\r\n const max = line * props.maxRows + chrome\r\n const next = Math.min(el.scrollHeight, max)\r\n el.style.height = `${next}px`\r\n el.style.overflowY = el.scrollHeight > max ? 'auto' : 'hidden'\r\n}\r\n\r\nonMounted(resize)\r\nwatch(model, () => nextTick(resize))\r\nwatch(() => props.autoresize, (value) => {\r\n if (value) return nextTick(resize)\r\n if (textarea.value) textarea.value.style.height = ''\r\n})\r\n\r\ndefineExpose({\r\n textarea,\r\n focus: () => textarea.value?.focus(),\r\n})\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n <div\r\n class=\"j-control j-textarea\"\r\n :class=\"{ 'is-invalid': field.invalid.value, 'is-disabled': disabled, 'is-readonly': readonly }\"\r\n >\r\n <textarea\r\n :id=\"field.id.value\"\r\n ref=\"textarea\"\r\n v-model=\"model\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n class=\"j-textarea__native\"\r\n :class=\"{ 'is-autoresize': autoresize }\"\r\n :rows=\"rows\"\r\n :name=\"name\"\r\n :placeholder=\"placeholder\"\r\n :disabled=\"disabled\"\r\n :readonly=\"readonly\"\r\n :required=\"required\"\r\n :aria-invalid=\"field.invalid.value || undefined\"\r\n :aria-describedby=\"field.describedBy.value\"\r\n />\r\n </div>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-textarea {\r\n align-items: stretch;\r\n padding: 0;\r\n cursor: auto;\r\n}\r\n\r\n.j-textarea__native {\r\n display: block;\r\n width: 100%;\r\n min-height: calc(var(--juxt-control-md) * 2);\r\n padding: var(--juxt-space-2) var(--juxt-space-2-5);\r\n border: 0;\r\n border-radius: inherit;\r\n outline: 0;\r\n background: transparent;\r\n color: inherit;\r\n font: inherit;\r\n line-height: var(--juxt-leading-normal);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n resize: vertical;\r\n}\r\n\r\n.j-textarea__native.is-autoresize {\r\n resize: none;\r\n overflow-y: hidden;\r\n}\r\n\r\n.j-textarea__native::placeholder {\r\n color: var(--juxt-fg-muted);\r\n opacity: 1;\r\n}\r\n\r\n.j-textarea__native:disabled {\r\n cursor: not-allowed;\r\n resize: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { FieldProps } from '../field/useField'\r\nimport { nextTick, onMounted, ref, watch } from 'vue'\r\nimport JFieldShell from '../field/JFieldShell.vue'\r\nimport { useField } from '../field/useField'\r\n\r\nexport interface TextareaProps extends FieldProps {\r\n placeholder?: string\r\n rows?: number\r\n /** Grow with the content, up to `maxRows`. */\r\n autoresize?: boolean\r\n maxRows?: number\r\n readonly?: boolean\r\n name?: string\r\n}\r\n\r\ndefineOptions({ inheritAttrs: false })\r\n\r\nconst props = withDefaults(defineProps<TextareaProps>(), {\r\n rows: 3,\r\n maxRows: 12,\r\n error: undefined,\r\n})\r\n\r\nconst model = defineModel<string | null>({ default: '' })\r\n\r\ndefineSlots<{ label?: () => unknown }>()\r\n\r\nconst field = useField(props)\r\nconst textarea = ref<HTMLTextAreaElement | null>(null)\r\n\r\nfunction resize() {\r\n const el = textarea.value\r\n if (!props.autoresize || !el) return\r\n const style = getComputedStyle(el)\r\n const line = Number.parseFloat(style.lineHeight) || 20\r\n const chrome = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom)\r\n el.style.height = 'auto'\r\n const max = line * props.maxRows + chrome\r\n const next = Math.min(el.scrollHeight, max)\r\n el.style.height = `${next}px`\r\n el.style.overflowY = el.scrollHeight > max ? 'auto' : 'hidden'\r\n}\r\n\r\nonMounted(resize)\r\nwatch(model, () => nextTick(resize))\r\nwatch(() => props.autoresize, (value) => {\r\n if (value) return nextTick(resize)\r\n if (textarea.value) textarea.value.style.height = ''\r\n})\r\n\r\ndefineExpose({\r\n textarea,\r\n focus: () => textarea.value?.focus(),\r\n})\r\n</script>\r\n\r\n<template>\r\n <JFieldShell\r\n :id=\"field.id.value\"\r\n :class=\"$attrs.class\"\r\n :style=\"$attrs.style\"\r\n :label-id=\"field.labelId.value\"\r\n :description-id=\"field.descriptionId.value\"\r\n :error-id=\"field.errorId.value\"\r\n :label=\"label\"\r\n :description=\"description\"\r\n :error-message=\"field.errorMessage.value\"\r\n :show-description=\"field.showDescription.value\"\r\n :required=\"required\"\r\n :disabled=\"disabled\"\r\n >\r\n <template v-if=\"$slots.label\" #label>\r\n <slot name=\"label\" />\r\n </template>\r\n <div\r\n class=\"j-control j-textarea\"\r\n :class=\"{ 'is-invalid': field.invalid.value, 'is-disabled': disabled, 'is-readonly': readonly }\"\r\n >\r\n <textarea\r\n :id=\"field.id.value\"\r\n ref=\"textarea\"\r\n v-model=\"model\"\r\n v-bind=\"{ ...$attrs, class: undefined, style: undefined }\"\r\n class=\"j-textarea__native\"\r\n :class=\"{ 'is-autoresize': autoresize }\"\r\n :rows=\"rows\"\r\n :name=\"name\"\r\n :placeholder=\"placeholder\"\r\n :disabled=\"disabled\"\r\n :readonly=\"readonly\"\r\n :required=\"required\"\r\n :aria-invalid=\"field.invalid.value || undefined\"\r\n :aria-describedby=\"field.describedBy.value\"\r\n />\r\n </div>\r\n </JFieldShell>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-textarea {\r\n align-items: stretch;\r\n padding: 0;\r\n cursor: auto;\r\n}\r\n\r\n.j-textarea__native {\r\n display: block;\r\n width: 100%;\r\n min-height: calc(var(--juxt-control-md) * 2);\r\n padding: var(--juxt-space-2) var(--juxt-space-2-5);\r\n border: 0;\r\n border-radius: inherit;\r\n outline: 0;\r\n background: transparent;\r\n color: inherit;\r\n font: inherit;\r\n line-height: var(--juxt-leading-normal);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n resize: vertical;\r\n}\r\n\r\n.j-textarea__native.is-autoresize {\r\n resize: none;\r\n overflow-y: hidden;\r\n}\r\n\r\n.j-textarea__native::placeholder {\r\n color: var(--juxt-fg-muted);\r\n opacity: 1;\r\n}\r\n\r\n.j-textarea__native:disabled {\r\n cursor: not-allowed;\r\n resize: none;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\ndefineSlots<{ default?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <ol class=\"j-timeline\">\r\n <slot />\r\n </ol>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-timeline {\r\n display: flex;\r\n flex-direction: column;\r\n margin: 0;\r\n padding: 0;\r\n list-style: none;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\ndefineSlots<{ default?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <ol class=\"j-timeline\">\r\n <slot />\r\n </ol>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-timeline {\r\n display: flex;\r\n flex-direction: column;\r\n margin: 0;\r\n padding: 0;\r\n list-style: none;\r\n font-family: var(--juxt-font-sans);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nexport type TimelineItemStatus = 'complete' | 'current' | 'upcoming'\r\n\r\nexport interface TimelineItemProps {\r\n title?: string\r\n /** A timestamp or relative time, e.g. \"2 hours ago\". */\r\n time?: string\r\n status?: TimelineItemStatus\r\n}\r\n\r\nwithDefaults(defineProps<TimelineItemProps>(), { status: 'complete' })\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n title?: () => unknown\r\n /** Replace the default dot marker. */\r\n marker?: () => unknown\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <li class=\"j-timeline-item\" :class=\"`j-timeline-item--${status}`\">\r\n <span class=\"j-timeline-item__rail\" aria-hidden=\"true\">\r\n <span class=\"j-timeline-item__marker\">\r\n <slot name=\"marker\" />\r\n </span>\r\n </span>\r\n <div class=\"j-timeline-item__content\">\r\n <div class=\"j-timeline-item__header\">\r\n <p v-if=\"title || $slots.title\" class=\"j-timeline-item__title\">\r\n <slot name=\"title\">{{ title }}</slot>\r\n </p>\r\n <time v-if=\"time\" class=\"j-timeline-item__time\">{{ time }}</time>\r\n </div>\r\n <div v-if=\"$slots.default\" class=\"j-timeline-item__body\">\r\n <slot />\r\n </div>\r\n </div>\r\n </li>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-timeline-item {\r\n position: relative;\r\n display: flex;\r\n gap: var(--juxt-space-3);\r\n padding-bottom: var(--juxt-space-5);\r\n}\r\n\r\n.j-timeline-item:last-child {\r\n padding-bottom: 0;\r\n}\r\n\r\n.j-timeline-item__rail {\r\n position: relative;\r\n display: flex;\r\n flex: none;\r\n justify-content: center;\r\n width: 1rem;\r\n}\r\n\r\n.j-timeline-item__rail::before {\r\n content: '';\r\n position: absolute;\r\n top: 1rem;\r\n bottom: calc(var(--juxt-space-5) * -1);\r\n width: 1px;\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-timeline-item:last-child .j-timeline-item__rail::before {\r\n display: none;\r\n}\r\n\r\n.j-timeline-item__marker {\r\n z-index: 1;\r\n display: grid;\r\n place-items: center;\r\n width: 0.625rem;\r\n height: 0.625rem;\r\n margin-top: 0.25rem;\r\n border: 2px solid var(--juxt-surface);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-fg-disabled);\r\n box-shadow: 0 0 0 1px var(--juxt-border);\r\n}\r\n\r\n.j-timeline-item--complete .j-timeline-item__marker {\r\n background: var(--juxt-accent);\r\n box-shadow: none;\r\n}\r\n\r\n.j-timeline-item--current .j-timeline-item__marker {\r\n background: var(--juxt-surface);\r\n box-shadow: 0 0 0 2px var(--juxt-accent);\r\n}\r\n\r\n.j-timeline-item__content {\r\n flex: 1;\r\n min-width: 0;\r\n padding-bottom: var(--juxt-space-0-5);\r\n}\r\n\r\n.j-timeline-item__header {\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-items: baseline;\r\n gap: var(--juxt-space-2);\r\n}\r\n\r\n.j-timeline-item__title {\r\n margin: 0;\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-timeline-item--upcoming .j-timeline-item__title {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-timeline-item__time {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-timeline-item__body {\r\n margin-top: var(--juxt-space-0-5);\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nexport type TimelineItemStatus = 'complete' | 'current' | 'upcoming'\r\n\r\nexport interface TimelineItemProps {\r\n title?: string\r\n /** A timestamp or relative time, e.g. \"2 hours ago\". */\r\n time?: string\r\n status?: TimelineItemStatus\r\n}\r\n\r\nwithDefaults(defineProps<TimelineItemProps>(), { status: 'complete' })\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n title?: () => unknown\r\n /** Replace the default dot marker. */\r\n marker?: () => unknown\r\n}>()\r\n</script>\r\n\r\n<template>\r\n <li class=\"j-timeline-item\" :class=\"`j-timeline-item--${status}`\">\r\n <span class=\"j-timeline-item__rail\" aria-hidden=\"true\">\r\n <span class=\"j-timeline-item__marker\">\r\n <slot name=\"marker\" />\r\n </span>\r\n </span>\r\n <div class=\"j-timeline-item__content\">\r\n <div class=\"j-timeline-item__header\">\r\n <p v-if=\"title || $slots.title\" class=\"j-timeline-item__title\">\r\n <slot name=\"title\">{{ title }}</slot>\r\n </p>\r\n <time v-if=\"time\" class=\"j-timeline-item__time\">{{ time }}</time>\r\n </div>\r\n <div v-if=\"$slots.default\" class=\"j-timeline-item__body\">\r\n <slot />\r\n </div>\r\n </div>\r\n </li>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-timeline-item {\r\n position: relative;\r\n display: flex;\r\n gap: var(--juxt-space-3);\r\n padding-bottom: var(--juxt-space-5);\r\n}\r\n\r\n.j-timeline-item:last-child {\r\n padding-bottom: 0;\r\n}\r\n\r\n.j-timeline-item__rail {\r\n position: relative;\r\n display: flex;\r\n flex: none;\r\n justify-content: center;\r\n width: 1rem;\r\n}\r\n\r\n.j-timeline-item__rail::before {\r\n content: '';\r\n position: absolute;\r\n top: 1rem;\r\n bottom: calc(var(--juxt-space-5) * -1);\r\n width: 1px;\r\n background: var(--juxt-border);\r\n}\r\n\r\n.j-timeline-item:last-child .j-timeline-item__rail::before {\r\n display: none;\r\n}\r\n\r\n.j-timeline-item__marker {\r\n z-index: 1;\r\n display: grid;\r\n place-items: center;\r\n width: 0.625rem;\r\n height: 0.625rem;\r\n margin-top: 0.25rem;\r\n border: 2px solid var(--juxt-surface);\r\n border-radius: var(--juxt-radius-full);\r\n background: var(--juxt-fg-disabled);\r\n box-shadow: 0 0 0 1px var(--juxt-border);\r\n}\r\n\r\n.j-timeline-item--complete .j-timeline-item__marker {\r\n background: var(--juxt-accent);\r\n box-shadow: none;\r\n}\r\n\r\n.j-timeline-item--current .j-timeline-item__marker {\r\n background: var(--juxt-surface);\r\n box-shadow: 0 0 0 2px var(--juxt-accent);\r\n}\r\n\r\n.j-timeline-item__content {\r\n flex: 1;\r\n min-width: 0;\r\n padding-bottom: var(--juxt-space-0-5);\r\n}\r\n\r\n.j-timeline-item__header {\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-items: baseline;\r\n gap: var(--juxt-space-2);\r\n}\r\n\r\n.j-timeline-item__title {\r\n margin: 0;\r\n color: var(--juxt-fg);\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-timeline-item--upcoming .j-timeline-item__title {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-timeline-item__time {\r\n color: var(--juxt-fg-muted);\r\n font-size: var(--juxt-text-xs);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n.j-timeline-item__body {\r\n margin-top: var(--juxt-space-0-5);\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n}\r\n</style>\r\n","import { reactive, readonly } from 'vue'\r\n\r\nexport type ToastType = 'default' | 'success' | 'error' | 'warning' | 'info' | 'loading'\r\n\r\nexport interface ToastAction {\r\n label: string\r\n onClick: () => void\r\n}\r\n\r\nexport interface ToastOptions {\r\n /** Reuse an id to replace an existing toast instead of stacking a new one. */\r\n id?: string\r\n description?: string\r\n type?: ToastType\r\n /** Time on screen in ms. `Infinity` keeps it until dismissed. Loading toasts never expire. */\r\n duration?: number\r\n action?: ToastAction\r\n /** Show the close button. Defaults to true. */\r\n dismissible?: boolean\r\n onDismiss?: () => void\r\n}\r\n\r\nexport interface Toast {\r\n id: string\r\n title: string\r\n description?: string\r\n type: ToastType\r\n duration: number\r\n action?: ToastAction\r\n dismissible: boolean\r\n onDismiss?: () => void\r\n /** Bumps whenever the toast changes, restarting its timer. */\r\n version: number\r\n}\r\n\r\nexport const DEFAULT_TOAST_DURATION = 5000\r\n\r\nconst state = reactive<{ toasts: Toast[] }>({ toasts: [] })\r\nlet counter = 0\r\n\r\nfunction resolveDuration(type: ToastType, duration?: number) {\r\n if (duration !== undefined) return duration\r\n if (type === 'loading') return Number.POSITIVE_INFINITY\r\n // Errors deserve a little longer to be read.\r\n if (type === 'error') return DEFAULT_TOAST_DURATION + 2000\r\n return DEFAULT_TOAST_DURATION\r\n}\r\n\r\nfunction create(title: string, options: ToastOptions = {}): string {\r\n const type = options.type ?? 'default'\r\n const existing = options.id ? state.toasts.find(t => t.id === options.id) : undefined\r\n if (existing) {\r\n update(existing.id, { title, ...options })\r\n return existing.id\r\n }\r\n const id = options.id ?? `toast-${++counter}`\r\n state.toasts.push({\r\n id,\r\n title,\r\n description: options.description,\r\n type,\r\n duration: resolveDuration(type, options.duration),\r\n action: options.action,\r\n dismissible: options.dismissible ?? true,\r\n onDismiss: options.onDismiss,\r\n version: 0,\r\n })\r\n return id\r\n}\r\n\r\nfunction update(id: string, patch: Partial<ToastOptions> & { title?: string }) {\r\n const item = state.toasts.find(t => t.id === id)\r\n if (!item) return\r\n const type = patch.type ?? item.type\r\n Object.assign(item, {\r\n ...patch,\r\n type,\r\n duration: patch.duration !== undefined || patch.type ? resolveDuration(type, patch.duration) : item.duration,\r\n version: item.version + 1,\r\n })\r\n}\r\n\r\nfunction dismiss(id?: string) {\r\n if (id === undefined) {\r\n const all = state.toasts.splice(0)\r\n all.forEach(t => t.onDismiss?.())\r\n return\r\n }\r\n const index = state.toasts.findIndex(t => t.id === id)\r\n if (index === -1) return\r\n const [removed] = state.toasts.splice(index, 1)\r\n removed?.onDismiss?.()\r\n}\r\n\r\ntype Message<T> = string | ((value: T) => string)\r\n\r\ninterface PromiseMessages<T> {\r\n loading: string\r\n success: Message<T>\r\n error: Message<unknown>\r\n description?: string\r\n}\r\n\r\nfunction promise<T>(input: Promise<T> | (() => Promise<T>), messages: PromiseMessages<T>): Promise<T> {\r\n const id = create(messages.loading, { type: 'loading', description: messages.description })\r\n const run = typeof input === 'function' ? input() : input\r\n run.then(\r\n (value) => {\r\n const title = typeof messages.success === 'function' ? messages.success(value) : messages.success\r\n update(id, { title, type: 'success', description: undefined })\r\n },\r\n (error: unknown) => {\r\n const title = typeof messages.error === 'function' ? messages.error(error) : messages.error\r\n update(id, { title, type: 'error', description: undefined })\r\n },\r\n )\r\n return run\r\n}\r\n\r\ntype Shortcut = (title: string, options?: Omit<ToastOptions, 'type'>) => string\r\nconst typed = (type: ToastType): Shortcut => (title, options) => create(title, { ...options, type })\r\n\r\nexport interface ToastFn {\r\n (title: string, options?: ToastOptions): string\r\n success: Shortcut\r\n error: Shortcut\r\n warning: Shortcut\r\n info: Shortcut\r\n loading: Shortcut\r\n promise: typeof promise\r\n update: typeof update\r\n dismiss: typeof dismiss\r\n}\r\n\r\n/** Show a toast. Works anywhere | inside or outside components. */\r\nexport const toast: ToastFn = Object.assign(create, {\r\n success: typed('success'),\r\n error: typed('error'),\r\n warning: typed('warning'),\r\n info: typed('info'),\r\n loading: typed('loading'),\r\n promise,\r\n update,\r\n dismiss,\r\n})\r\n\r\nexport function useToast() {\r\n return {\r\n toast,\r\n dismiss,\r\n update,\r\n toasts: readonly(state).toasts,\r\n }\r\n}\r\n\r\n/** @internal Mutable access for JToaster and tests. */\r\nexport const toastState = state\r\n","<script setup lang=\"ts\">\r\n/** Internal: one toast. Owns its timer, swipe gesture and keyboard dismissal. */\r\nimport type { Toast } from '../../composables/useToast'\r\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport { IconAlert, IconCircleCheck, IconCircleX, IconInfo, IconX, Spinner } from '../../icons'\r\n\r\nconst props = defineProps<{\r\n toast: Toast\r\n paused: boolean\r\n}>()\r\n\r\nconst emit = defineEmits<{ dismiss: [id: string] }>()\r\n\r\nconst el = ref<HTMLElement | null>(null)\r\nconst timed = computed(() => Number.isFinite(props.toast.duration) && props.toast.duration > 0)\r\n\r\n// --- Timer -----------------------------------------------------------------\r\nlet timer: ReturnType<typeof setTimeout> | undefined\r\nlet remaining = 0\r\nlet startedAt = 0\r\n\r\nfunction start() {\r\n clearTimeout(timer)\r\n if (!timed.value || props.paused) return\r\n startedAt = Date.now()\r\n timer = setTimeout(() => emit('dismiss', props.toast.id), remaining)\r\n}\r\n\r\nfunction pause() {\r\n if (!timer) return\r\n clearTimeout(timer)\r\n timer = undefined\r\n remaining -= Date.now() - startedAt\r\n}\r\n\r\nfunction reset() {\r\n remaining = props.toast.duration\r\n start()\r\n}\r\n\r\nonMounted(reset)\r\nonBeforeUnmount(() => clearTimeout(timer))\r\nwatch(() => props.toast.version, reset)\r\nwatch(() => props.paused, value => (value ? pause() : start()))\r\n\r\n// --- Swipe to dismiss ------------------------------------------------------\r\nconst offset = ref(0)\r\nconst swiping = ref(false)\r\nconst leaving = ref<0 | 1 | -1>(0)\r\nlet origin: { x: number, y: number, id: number } | null = null\r\n\r\nfunction onPointerDown(event: PointerEvent) {\r\n if (event.button !== 0 || (event.target as HTMLElement).closest('button, a')) return\r\n origin = { x: event.clientX, y: event.clientY, id: event.pointerId }\r\n}\r\n\r\nfunction onPointerMove(event: PointerEvent) {\r\n if (!origin || event.pointerId !== origin.id) return\r\n const dx = event.clientX - origin.x\r\n if (!swiping.value) {\r\n // Commit to a swipe only once the gesture is clearly horizontal.\r\n if (Math.abs(dx) < 6 || Math.abs(dx) < Math.abs(event.clientY - origin.y)) return\r\n swiping.value = true\r\n el.value?.setPointerCapture(event.pointerId)\r\n }\r\n offset.value = dx\r\n}\r\n\r\nfunction onPointerUp() {\r\n if (!origin) return\r\n origin = null\r\n if (!swiping.value) return\r\n swiping.value = false\r\n if (Math.abs(offset.value) > 64 && props.toast.dismissible) {\r\n leaving.value = offset.value > 0 ? 1 : -1\r\n emit('dismiss', props.toast.id)\r\n } else {\r\n offset.value = 0\r\n }\r\n}\r\n\r\nconst style = computed(() => {\r\n if (!offset.value) return undefined\r\n return {\r\n '--j-toast-swipe': `${offset.value}px`,\r\n '--j-toast-swipe-opacity': String(Math.max(0, 1 - Math.abs(offset.value) / 200)),\r\n }\r\n})\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key === 'Escape' && props.toast.dismissible) {\r\n event.preventDefault()\r\n event.stopPropagation()\r\n const next = (el.value?.nextElementSibling ?? el.value?.previousElementSibling) as HTMLElement | null\r\n emit('dismiss', props.toast.id)\r\n next?.focus()\r\n }\r\n}\r\n\r\nfunction runAction() {\r\n props.toast.action?.onClick()\r\n emit('dismiss', props.toast.id)\r\n}\r\n\r\nconst icons = {\r\n success: IconCircleCheck,\r\n error: IconCircleX,\r\n warning: IconAlert,\r\n info: IconInfo,\r\n}\r\n</script>\r\n\r\n<template>\r\n <li\r\n ref=\"el\"\r\n class=\"j-toast\"\r\n :class=\"[`j-toast--${toast.type}`, { 'is-swiping': swiping, 'is-swiped': leaving !== 0 }]\"\r\n :style=\"style\"\r\n :data-swipe-direction=\"leaving\"\r\n :role=\"toast.type === 'error' ? 'alert' : undefined\"\r\n tabindex=\"0\"\r\n @pointerdown=\"onPointerDown\"\r\n @pointermove=\"onPointerMove\"\r\n @pointerup=\"onPointerUp\"\r\n @pointercancel=\"onPointerUp\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <span v-if=\"toast.type !== 'default'\" class=\"j-toast__icon\" aria-hidden=\"true\">\r\n <Transition name=\"j-toast-icon\" mode=\"out-in\">\r\n <Spinner v-if=\"toast.type === 'loading'\" :size=\"16\" />\r\n <component :is=\"icons[toast.type]\" v-else :key=\"toast.type\" />\r\n </Transition>\r\n </span>\r\n <div class=\"j-toast__body\">\r\n <p class=\"j-toast__title\">\r\n {{ toast.title }}\r\n </p>\r\n <p v-if=\"toast.description\" class=\"j-toast__description\">\r\n {{ toast.description }}\r\n </p>\r\n <div v-if=\"toast.action\" class=\"j-toast__actions\">\r\n <button type=\"button\" class=\"j-toast__action j-focusable\" @click=\"runAction\">\r\n {{ toast.action.label }}\r\n </button>\r\n </div>\r\n </div>\r\n <button\r\n v-if=\"toast.dismissible\"\r\n type=\"button\"\r\n class=\"j-toast__close j-focusable\"\r\n aria-label=\"Dismiss notification\"\r\n @click=\"emit('dismiss', toast.id)\"\r\n >\r\n <IconX />\r\n </button>\r\n <span\r\n v-if=\"timed\"\r\n :key=\"toast.version\"\r\n class=\"j-toast__progress\"\r\n :class=\"{ 'is-paused': paused }\"\r\n :style=\"{ animationDuration: `${toast.duration}ms` }\"\r\n aria-hidden=\"true\"\r\n />\r\n </li>\r\n</template>\r\n","<script setup lang=\"ts\">\r\n/** Internal: one toast. Owns its timer, swipe gesture and keyboard dismissal. */\r\nimport type { Toast } from '../../composables/useToast'\r\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\r\nimport { IconAlert, IconCircleCheck, IconCircleX, IconInfo, IconX, Spinner } from '../../icons'\r\n\r\nconst props = defineProps<{\r\n toast: Toast\r\n paused: boolean\r\n}>()\r\n\r\nconst emit = defineEmits<{ dismiss: [id: string] }>()\r\n\r\nconst el = ref<HTMLElement | null>(null)\r\nconst timed = computed(() => Number.isFinite(props.toast.duration) && props.toast.duration > 0)\r\n\r\n// --- Timer -----------------------------------------------------------------\r\nlet timer: ReturnType<typeof setTimeout> | undefined\r\nlet remaining = 0\r\nlet startedAt = 0\r\n\r\nfunction start() {\r\n clearTimeout(timer)\r\n if (!timed.value || props.paused) return\r\n startedAt = Date.now()\r\n timer = setTimeout(() => emit('dismiss', props.toast.id), remaining)\r\n}\r\n\r\nfunction pause() {\r\n if (!timer) return\r\n clearTimeout(timer)\r\n timer = undefined\r\n remaining -= Date.now() - startedAt\r\n}\r\n\r\nfunction reset() {\r\n remaining = props.toast.duration\r\n start()\r\n}\r\n\r\nonMounted(reset)\r\nonBeforeUnmount(() => clearTimeout(timer))\r\nwatch(() => props.toast.version, reset)\r\nwatch(() => props.paused, value => (value ? pause() : start()))\r\n\r\n// --- Swipe to dismiss ------------------------------------------------------\r\nconst offset = ref(0)\r\nconst swiping = ref(false)\r\nconst leaving = ref<0 | 1 | -1>(0)\r\nlet origin: { x: number, y: number, id: number } | null = null\r\n\r\nfunction onPointerDown(event: PointerEvent) {\r\n if (event.button !== 0 || (event.target as HTMLElement).closest('button, a')) return\r\n origin = { x: event.clientX, y: event.clientY, id: event.pointerId }\r\n}\r\n\r\nfunction onPointerMove(event: PointerEvent) {\r\n if (!origin || event.pointerId !== origin.id) return\r\n const dx = event.clientX - origin.x\r\n if (!swiping.value) {\r\n // Commit to a swipe only once the gesture is clearly horizontal.\r\n if (Math.abs(dx) < 6 || Math.abs(dx) < Math.abs(event.clientY - origin.y)) return\r\n swiping.value = true\r\n el.value?.setPointerCapture(event.pointerId)\r\n }\r\n offset.value = dx\r\n}\r\n\r\nfunction onPointerUp() {\r\n if (!origin) return\r\n origin = null\r\n if (!swiping.value) return\r\n swiping.value = false\r\n if (Math.abs(offset.value) > 64 && props.toast.dismissible) {\r\n leaving.value = offset.value > 0 ? 1 : -1\r\n emit('dismiss', props.toast.id)\r\n } else {\r\n offset.value = 0\r\n }\r\n}\r\n\r\nconst style = computed(() => {\r\n if (!offset.value) return undefined\r\n return {\r\n '--j-toast-swipe': `${offset.value}px`,\r\n '--j-toast-swipe-opacity': String(Math.max(0, 1 - Math.abs(offset.value) / 200)),\r\n }\r\n})\r\n\r\nfunction onKeydown(event: KeyboardEvent) {\r\n if (event.key === 'Escape' && props.toast.dismissible) {\r\n event.preventDefault()\r\n event.stopPropagation()\r\n const next = (el.value?.nextElementSibling ?? el.value?.previousElementSibling) as HTMLElement | null\r\n emit('dismiss', props.toast.id)\r\n next?.focus()\r\n }\r\n}\r\n\r\nfunction runAction() {\r\n props.toast.action?.onClick()\r\n emit('dismiss', props.toast.id)\r\n}\r\n\r\nconst icons = {\r\n success: IconCircleCheck,\r\n error: IconCircleX,\r\n warning: IconAlert,\r\n info: IconInfo,\r\n}\r\n</script>\r\n\r\n<template>\r\n <li\r\n ref=\"el\"\r\n class=\"j-toast\"\r\n :class=\"[`j-toast--${toast.type}`, { 'is-swiping': swiping, 'is-swiped': leaving !== 0 }]\"\r\n :style=\"style\"\r\n :data-swipe-direction=\"leaving\"\r\n :role=\"toast.type === 'error' ? 'alert' : undefined\"\r\n tabindex=\"0\"\r\n @pointerdown=\"onPointerDown\"\r\n @pointermove=\"onPointerMove\"\r\n @pointerup=\"onPointerUp\"\r\n @pointercancel=\"onPointerUp\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <span v-if=\"toast.type !== 'default'\" class=\"j-toast__icon\" aria-hidden=\"true\">\r\n <Transition name=\"j-toast-icon\" mode=\"out-in\">\r\n <Spinner v-if=\"toast.type === 'loading'\" :size=\"16\" />\r\n <component :is=\"icons[toast.type]\" v-else :key=\"toast.type\" />\r\n </Transition>\r\n </span>\r\n <div class=\"j-toast__body\">\r\n <p class=\"j-toast__title\">\r\n {{ toast.title }}\r\n </p>\r\n <p v-if=\"toast.description\" class=\"j-toast__description\">\r\n {{ toast.description }}\r\n </p>\r\n <div v-if=\"toast.action\" class=\"j-toast__actions\">\r\n <button type=\"button\" class=\"j-toast__action j-focusable\" @click=\"runAction\">\r\n {{ toast.action.label }}\r\n </button>\r\n </div>\r\n </div>\r\n <button\r\n v-if=\"toast.dismissible\"\r\n type=\"button\"\r\n class=\"j-toast__close j-focusable\"\r\n aria-label=\"Dismiss notification\"\r\n @click=\"emit('dismiss', toast.id)\"\r\n >\r\n <IconX />\r\n </button>\r\n <span\r\n v-if=\"timed\"\r\n :key=\"toast.version\"\r\n class=\"j-toast__progress\"\r\n :class=\"{ 'is-paused': paused }\"\r\n :style=\"{ animationDuration: `${toast.duration}ms` }\"\r\n aria-hidden=\"true\"\r\n />\r\n </li>\r\n</template>\r\n","<script setup lang=\"ts\">\r\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\r\nimport { formatHotkey, useHotkey } from '../../composables/useHotkey'\r\nimport { toastState } from '../../composables/useToast'\r\nimport { focusElement, unrefElement } from '../../utils/dom'\r\nimport JToastItem from './JToastItem.vue'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport type ToasterPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'\r\n\r\nexport interface ToasterProps {\r\n position?: ToasterPosition\r\n /** How many toasts are visible at once. Extra toasts wait their turn. */\r\n max?: number\r\n /** Shortcut that moves focus to the newest toast. */\r\n hotkey?: string\r\n /** Accessible name for the notification region. */\r\n label?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<ToasterProps>(), {\r\n position: 'bottom-right',\r\n max: 4,\r\n hotkey: 'alt+t',\r\n label: 'Notifications',\r\n})\r\n\r\nconst list = ref()\r\nconst hovered = ref(false)\r\nconst focused = ref(false)\r\nconst hidden = ref(false)\r\nconst paused = computed(() => hovered.value || focused.value || hidden.value)\r\n\r\nconst isTop = computed(() => props.position.startsWith('top'))\r\n\r\n// Oldest first in the store; show the newest `max`, nearest the screen edge.\r\nconst visible = computed(() => {\r\n const items = toastState.toasts.slice(-props.max)\r\n return isTop.value ? items.reverse() : items\r\n})\r\n\r\nfunction dismiss(id: string) {\r\n const index = toastState.toasts.findIndex(t => t.id === id)\r\n if (index === -1) return\r\n const [removed] = toastState.toasts.splice(index, 1)\r\n removed?.onDismiss?.()\r\n}\r\n\r\nconst hotkeyLabel = ref('')\r\nonMounted(() => (hotkeyLabel.value = formatHotkey(props.hotkey).join('+')))\r\n\r\nuseHotkey(() => props.hotkey, () => {\r\n const items = unrefElement(list.value)?.querySelectorAll<HTMLElement>('.j-toast')\r\n if (!items?.length) return\r\n focusElement(isTop.value ? items[0] : items[items.length - 1])\r\n})\r\n\r\nfunction onVisibility() {\r\n hidden.value = document.visibilityState === 'hidden'\r\n}\r\nonMounted(() => document.addEventListener('visibilitychange', onVisibility))\r\nonBeforeUnmount(() => document.removeEventListener('visibilitychange', onVisibility))\r\n\r\nfunction onFocusOut(event: FocusEvent) {\r\n const current = event.currentTarget as HTMLElement\r\n if (!current.contains(event.relatedTarget as Node | null)) focused.value = false\r\n}\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <section\r\n class=\"j-toaster\"\r\n :class=\"`j-toaster--${position}`\"\r\n :aria-label=\"hotkeyLabel ? `${label} (${hotkeyLabel})` : label\"\r\n data-juxt-layer-ignore\r\n >\r\n <TransitionGroup\r\n ref=\"list\"\r\n tag=\"ol\"\r\n name=\"j-toast\"\r\n class=\"j-toaster__list\"\r\n aria-live=\"polite\"\r\n aria-relevant=\"additions text\"\r\n @pointerenter=\"hovered = true\"\r\n @pointerleave=\"hovered = false\"\r\n @focusin=\"focused = true\"\r\n @focusout=\"onFocusOut\"\r\n >\r\n <JToastItem\r\n v-for=\"item in visible\"\r\n :key=\"item.id\"\r\n :toast=\"item\"\r\n :paused=\"paused\"\r\n @dismiss=\"dismiss\"\r\n />\r\n </TransitionGroup>\r\n </section>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-toaster {\r\n --j-toast-width: 22.5rem;\r\n --j-toast-edge: var(--juxt-space-4);\r\n\r\n position: fixed;\r\n z-index: var(--juxt-z-toast);\r\n width: var(--j-toast-width);\r\n max-width: calc(100vw - var(--j-toast-edge) * 2);\r\n pointer-events: none;\r\n}\r\n\r\n.j-toaster--top-left,\r\n.j-toaster--top-center,\r\n.j-toaster--top-right {\r\n top: max(var(--j-toast-edge), env(safe-area-inset-top));\r\n}\r\n\r\n.j-toaster--bottom-left,\r\n.j-toaster--bottom-center,\r\n.j-toaster--bottom-right {\r\n bottom: max(var(--j-toast-edge), env(safe-area-inset-bottom));\r\n}\r\n\r\n.j-toaster--top-left,\r\n.j-toaster--bottom-left {\r\n left: var(--j-toast-edge);\r\n}\r\n\r\n.j-toaster--top-right,\r\n.j-toaster--bottom-right {\r\n right: calc(var(--j-toast-edge) + var(--juxt-scrollbar-width, 0px));\r\n}\r\n\r\n.j-toaster--top-center,\r\n.j-toaster--bottom-center {\r\n left: 50%;\r\n transform: translateX(-50%);\r\n}\r\n\r\n.j-toaster__list {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-2);\r\n margin: 0;\r\n padding: 0;\r\n list-style: none;\r\n}\r\n\r\n.j-toast {\r\n --j-toast-enter: calc(var(--juxt-motion-shift) * 3);\r\n\r\n position: relative;\r\n display: flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n box-sizing: border-box;\r\n width: 100%;\r\n padding: var(--juxt-space-3) var(--juxt-space-10) var(--juxt-space-3) var(--juxt-space-3);\r\n overflow: hidden;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-md);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n pointer-events: auto;\r\n opacity: var(--j-toast-swipe-opacity, 1);\r\n transform: translateX(var(--j-toast-swipe, 0));\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n touch-action: pan-y;\r\n transition:\r\n transform var(--juxt-duration-slow) var(--juxt-ease-out),\r\n opacity var(--juxt-duration-normal) var(--juxt-ease-out),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-toaster--top-left .j-toast,\r\n.j-toaster--top-center .j-toast,\r\n.j-toaster--top-right .j-toast {\r\n --j-toast-enter: calc(var(--juxt-motion-shift) * -3);\r\n}\r\n\r\n.j-toast.is-swiping {\r\n transition: none;\r\n user-select: none;\r\n}\r\n\r\n.j-toast:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-toast__icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 1rem;\r\n height: 1.25rem;\r\n}\r\n\r\n.j-toast__icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-toast--success .j-toast__icon {\r\n color: var(--juxt-success-text);\r\n}\r\n\r\n.j-toast--error .j-toast__icon {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-toast--warning .j-toast__icon {\r\n color: var(--juxt-warning-text);\r\n}\r\n\r\n.j-toast--info .j-toast__icon {\r\n color: var(--juxt-info-text);\r\n}\r\n\r\n.j-toast--loading .j-toast__icon {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-toast__body {\r\n display: flex;\r\n flex: 1;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-toast__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1.25rem;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-toast__description {\r\n margin: 0;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-toast__actions {\r\n display: flex;\r\n gap: var(--juxt-space-2);\r\n margin-top: var(--juxt-space-2);\r\n}\r\n\r\n.j-toast__action {\r\n height: var(--juxt-control-xs);\r\n padding: 0 var(--juxt-space-2);\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-surface);\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n cursor: pointer;\r\n transition-property: background-color, border-color, outline-color, outline-offset;\r\n}\r\n\r\n.j-toast__action:hover {\r\n border-color: var(--juxt-border-strong);\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-toast__close {\r\n position: absolute;\r\n top: var(--juxt-space-2-5);\r\n right: var(--juxt-space-2-5);\r\n display: grid;\r\n place-items: center;\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-xs);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-toast__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-toast__close > svg {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n\r\n/* Remaining time, drawn as a hairline that drains toward the start. */\r\n.j-toast__progress {\r\n position: absolute;\r\n right: 0;\r\n bottom: 0;\r\n left: 0;\r\n height: 2px;\r\n background: var(--juxt-border-strong);\r\n opacity: 0.6;\r\n transform-origin: left;\r\n animation: j-toast-progress linear forwards;\r\n}\r\n\r\n.j-toast--success .j-toast__progress {\r\n background: var(--juxt-accent-border);\r\n}\r\n\r\n.j-toast__progress.is-paused {\r\n animation-play-state: paused;\r\n}\r\n\r\n@keyframes j-toast-progress {\r\n from {\r\n transform: scaleX(1);\r\n }\r\n\r\n to {\r\n transform: scaleX(0);\r\n }\r\n}\r\n\r\n/* List motion */\r\n.j-toast-enter-from {\r\n opacity: 0;\r\n transform: translateY(var(--j-toast-enter)) scale(0.98);\r\n}\r\n\r\n.j-toast-leave-active {\r\n position: absolute;\r\n width: 100%;\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-in),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-in);\r\n}\r\n\r\n.j-toast-leave-to {\r\n opacity: 0;\r\n transform: scale(0.96);\r\n}\r\n\r\n.j-toast-leave-to.is-swiped {\r\n transform: translateX(calc(var(--j-toast-width) * var(--j-swipe-sign, 1)));\r\n}\r\n\r\n.j-toast-leave-to.is-swiped[data-swipe-direction='-1'] {\r\n --j-swipe-sign: -1;\r\n}\r\n\r\n.j-toast-move {\r\n transition: transform var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-toast-icon-enter-active,\r\n.j-toast-icon-leave-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-toast-icon-enter-from,\r\n.j-toast-icon-leave-to {\r\n opacity: 0;\r\n transform: scale(0.6);\r\n}\r\n\r\n@media (max-width: 639px) {\r\n .j-toaster {\r\n --j-toast-edge: var(--juxt-space-2);\r\n\r\n right: var(--j-toast-edge);\r\n left: var(--j-toast-edge);\r\n width: auto;\r\n max-width: none;\r\n transform: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\r\nimport { formatHotkey, useHotkey } from '../../composables/useHotkey'\r\nimport { toastState } from '../../composables/useToast'\r\nimport { focusElement, unrefElement } from '../../utils/dom'\r\nimport JToastItem from './JToastItem.vue'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport type ToasterPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'\r\n\r\nexport interface ToasterProps {\r\n position?: ToasterPosition\r\n /** How many toasts are visible at once. Extra toasts wait their turn. */\r\n max?: number\r\n /** Shortcut that moves focus to the newest toast. */\r\n hotkey?: string\r\n /** Accessible name for the notification region. */\r\n label?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<ToasterProps>(), {\r\n position: 'bottom-right',\r\n max: 4,\r\n hotkey: 'alt+t',\r\n label: 'Notifications',\r\n})\r\n\r\nconst list = ref()\r\nconst hovered = ref(false)\r\nconst focused = ref(false)\r\nconst hidden = ref(false)\r\nconst paused = computed(() => hovered.value || focused.value || hidden.value)\r\n\r\nconst isTop = computed(() => props.position.startsWith('top'))\r\n\r\n// Oldest first in the store; show the newest `max`, nearest the screen edge.\r\nconst visible = computed(() => {\r\n const items = toastState.toasts.slice(-props.max)\r\n return isTop.value ? items.reverse() : items\r\n})\r\n\r\nfunction dismiss(id: string) {\r\n const index = toastState.toasts.findIndex(t => t.id === id)\r\n if (index === -1) return\r\n const [removed] = toastState.toasts.splice(index, 1)\r\n removed?.onDismiss?.()\r\n}\r\n\r\nconst hotkeyLabel = ref('')\r\nonMounted(() => (hotkeyLabel.value = formatHotkey(props.hotkey).join('+')))\r\n\r\nuseHotkey(() => props.hotkey, () => {\r\n const items = unrefElement(list.value)?.querySelectorAll<HTMLElement>('.j-toast')\r\n if (!items?.length) return\r\n focusElement(isTop.value ? items[0] : items[items.length - 1])\r\n})\r\n\r\nfunction onVisibility() {\r\n hidden.value = document.visibilityState === 'hidden'\r\n}\r\nonMounted(() => document.addEventListener('visibilitychange', onVisibility))\r\nonBeforeUnmount(() => document.removeEventListener('visibilitychange', onVisibility))\r\n\r\nfunction onFocusOut(event: FocusEvent) {\r\n const current = event.currentTarget as HTMLElement\r\n if (!current.contains(event.relatedTarget as Node | null)) focused.value = false\r\n}\r\n</script>\r\n\r\n<template>\r\n <JPortal>\r\n <section\r\n class=\"j-toaster\"\r\n :class=\"`j-toaster--${position}`\"\r\n :aria-label=\"hotkeyLabel ? `${label} (${hotkeyLabel})` : label\"\r\n data-juxt-layer-ignore\r\n >\r\n <TransitionGroup\r\n ref=\"list\"\r\n tag=\"ol\"\r\n name=\"j-toast\"\r\n class=\"j-toaster__list\"\r\n aria-live=\"polite\"\r\n aria-relevant=\"additions text\"\r\n @pointerenter=\"hovered = true\"\r\n @pointerleave=\"hovered = false\"\r\n @focusin=\"focused = true\"\r\n @focusout=\"onFocusOut\"\r\n >\r\n <JToastItem\r\n v-for=\"item in visible\"\r\n :key=\"item.id\"\r\n :toast=\"item\"\r\n :paused=\"paused\"\r\n @dismiss=\"dismiss\"\r\n />\r\n </TransitionGroup>\r\n </section>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-toaster {\r\n --j-toast-width: 22.5rem;\r\n --j-toast-edge: var(--juxt-space-4);\r\n\r\n position: fixed;\r\n z-index: var(--juxt-z-toast);\r\n width: var(--j-toast-width);\r\n max-width: calc(100vw - var(--j-toast-edge) * 2);\r\n pointer-events: none;\r\n}\r\n\r\n.j-toaster--top-left,\r\n.j-toaster--top-center,\r\n.j-toaster--top-right {\r\n top: max(var(--j-toast-edge), env(safe-area-inset-top));\r\n}\r\n\r\n.j-toaster--bottom-left,\r\n.j-toaster--bottom-center,\r\n.j-toaster--bottom-right {\r\n bottom: max(var(--j-toast-edge), env(safe-area-inset-bottom));\r\n}\r\n\r\n.j-toaster--top-left,\r\n.j-toaster--bottom-left {\r\n left: var(--j-toast-edge);\r\n}\r\n\r\n.j-toaster--top-right,\r\n.j-toaster--bottom-right {\r\n right: calc(var(--j-toast-edge) + var(--juxt-scrollbar-width, 0px));\r\n}\r\n\r\n.j-toaster--top-center,\r\n.j-toaster--bottom-center {\r\n left: 50%;\r\n transform: translateX(-50%);\r\n}\r\n\r\n.j-toaster__list {\r\n position: relative;\r\n display: flex;\r\n flex-direction: column;\r\n gap: var(--juxt-space-2);\r\n margin: 0;\r\n padding: 0;\r\n list-style: none;\r\n}\r\n\r\n.j-toast {\r\n --j-toast-enter: calc(var(--juxt-motion-shift) * 3);\r\n\r\n position: relative;\r\n display: flex;\r\n align-items: flex-start;\r\n gap: var(--juxt-space-2-5);\r\n box-sizing: border-box;\r\n width: 100%;\r\n padding: var(--juxt-space-3) var(--juxt-space-10) var(--juxt-space-3) var(--juxt-space-3);\r\n overflow: hidden;\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-raised);\r\n box-shadow: var(--juxt-shadow-md);\r\n color: var(--juxt-fg);\r\n font-family: var(--juxt-font-sans);\r\n pointer-events: auto;\r\n opacity: var(--j-toast-swipe-opacity, 1);\r\n transform: translateX(var(--j-toast-swipe, 0));\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n touch-action: pan-y;\r\n transition:\r\n transform var(--juxt-duration-slow) var(--juxt-ease-out),\r\n opacity var(--juxt-duration-normal) var(--juxt-ease-out),\r\n outline-color var(--juxt-duration-fast) var(--juxt-ease-out),\r\n outline-offset var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-toaster--top-left .j-toast,\r\n.j-toaster--top-center .j-toast,\r\n.j-toaster--top-right .j-toast {\r\n --j-toast-enter: calc(var(--juxt-motion-shift) * -3);\r\n}\r\n\r\n.j-toast.is-swiping {\r\n transition: none;\r\n user-select: none;\r\n}\r\n\r\n.j-toast:focus-visible {\r\n outline-color: var(--juxt-ring);\r\n outline-offset: 2px;\r\n}\r\n\r\n.j-toast__icon {\r\n display: grid;\r\n flex: none;\r\n place-items: center;\r\n width: 1rem;\r\n height: 1.25rem;\r\n}\r\n\r\n.j-toast__icon > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-toast--success .j-toast__icon {\r\n color: var(--juxt-success-text);\r\n}\r\n\r\n.j-toast--error .j-toast__icon {\r\n color: var(--juxt-danger-text);\r\n}\r\n\r\n.j-toast--warning .j-toast__icon {\r\n color: var(--juxt-warning-text);\r\n}\r\n\r\n.j-toast--info .j-toast__icon {\r\n color: var(--juxt-info-text);\r\n}\r\n\r\n.j-toast--loading .j-toast__icon {\r\n color: var(--juxt-fg-muted);\r\n}\r\n\r\n.j-toast__body {\r\n display: flex;\r\n flex: 1;\r\n flex-direction: column;\r\n gap: var(--juxt-space-0-5);\r\n min-width: 0;\r\n}\r\n\r\n.j-toast__title {\r\n margin: 0;\r\n font-size: var(--juxt-text-md);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: 1.25rem;\r\n letter-spacing: var(--juxt-tracking-tight);\r\n}\r\n\r\n.j-toast__description {\r\n margin: 0;\r\n color: var(--juxt-fg-secondary);\r\n font-size: var(--juxt-text-sm);\r\n line-height: var(--juxt-leading-normal);\r\n}\r\n\r\n.j-toast__actions {\r\n display: flex;\r\n gap: var(--juxt-space-2);\r\n margin-top: var(--juxt-space-2);\r\n}\r\n\r\n.j-toast__action {\r\n height: var(--juxt-control-xs);\r\n padding: 0 var(--juxt-space-2);\r\n border: 1px solid var(--juxt-border);\r\n border-radius: var(--juxt-radius-xs);\r\n background: var(--juxt-surface);\r\n color: var(--juxt-fg);\r\n font: inherit;\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n cursor: pointer;\r\n transition-property: background-color, border-color, outline-color, outline-offset;\r\n}\r\n\r\n.j-toast__action:hover {\r\n border-color: var(--juxt-border-strong);\r\n background: var(--juxt-surface-hover);\r\n}\r\n\r\n.j-toast__close {\r\n position: absolute;\r\n top: var(--juxt-space-2-5);\r\n right: var(--juxt-space-2-5);\r\n display: grid;\r\n place-items: center;\r\n width: 1.5rem;\r\n height: 1.5rem;\r\n padding: 0;\r\n border: 0;\r\n border-radius: var(--juxt-radius-xs);\r\n background: transparent;\r\n color: var(--juxt-fg-muted);\r\n cursor: pointer;\r\n transition-property: background-color, color, outline-color, outline-offset;\r\n}\r\n\r\n.j-toast__close:hover {\r\n background: var(--juxt-surface-hover);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-toast__close > svg {\r\n width: 0.875rem;\r\n height: 0.875rem;\r\n}\r\n\r\n/* Remaining time, drawn as a hairline that drains toward the start. */\r\n.j-toast__progress {\r\n position: absolute;\r\n right: 0;\r\n bottom: 0;\r\n left: 0;\r\n height: 2px;\r\n background: var(--juxt-border-strong);\r\n opacity: 0.6;\r\n transform-origin: left;\r\n animation: j-toast-progress linear forwards;\r\n}\r\n\r\n.j-toast--success .j-toast__progress {\r\n background: var(--juxt-accent-border);\r\n}\r\n\r\n.j-toast__progress.is-paused {\r\n animation-play-state: paused;\r\n}\r\n\r\n@keyframes j-toast-progress {\r\n from {\r\n transform: scaleX(1);\r\n }\r\n\r\n to {\r\n transform: scaleX(0);\r\n }\r\n}\r\n\r\n/* List motion */\r\n.j-toast-enter-from {\r\n opacity: 0;\r\n transform: translateY(var(--j-toast-enter)) scale(0.98);\r\n}\r\n\r\n.j-toast-leave-active {\r\n position: absolute;\r\n width: 100%;\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-in),\r\n transform var(--juxt-duration-normal) var(--juxt-ease-in);\r\n}\r\n\r\n.j-toast-leave-to {\r\n opacity: 0;\r\n transform: scale(0.96);\r\n}\r\n\r\n.j-toast-leave-to.is-swiped {\r\n transform: translateX(calc(var(--j-toast-width) * var(--j-swipe-sign, 1)));\r\n}\r\n\r\n.j-toast-leave-to.is-swiped[data-swipe-direction='-1'] {\r\n --j-swipe-sign: -1;\r\n}\r\n\r\n.j-toast-move {\r\n transition: transform var(--juxt-duration-slow) var(--juxt-ease-out);\r\n}\r\n\r\n.j-toast-icon-enter-active,\r\n.j-toast-icon-leave-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-toast-icon-enter-from,\r\n.j-toast-icon-leave-to {\r\n opacity: 0;\r\n transform: scale(0.6);\r\n}\r\n\r\n@media (max-width: 639px) {\r\n .j-toaster {\r\n --j-toast-edge: var(--juxt-space-2);\r\n\r\n right: var(--j-toast-edge);\r\n left: var(--j-toast-edge);\r\n width: auto;\r\n max-width: none;\r\n transform: none;\r\n }\r\n}\r\n}\r\n</style>\r\n","import type { ComputedRef, InjectionKey } from 'vue'\r\n\r\nexport type ToggleGroupType = 'single' | 'multiple'\r\n\r\nexport interface ToggleGroupContext {\r\n type: ComputedRef<ToggleGroupType>\r\n disabled: ComputedRef<boolean>\r\n isSelected: (value: string) => boolean\r\n toggle: (value: string) => void\r\n}\r\n\r\nexport const TOGGLE_GROUP: InjectionKey<ToggleGroupContext> = Symbol('JToggleGroup')\r\n","<script setup lang=\"ts\">\r\nimport { computed, provide } from 'vue'\r\nimport { focusElement } from '../../utils/dom'\r\nimport { TOGGLE_GROUP } from './context'\r\n\r\nexport interface ToggleGroupProps {\r\n /** `single` keeps at most one item pressed; `multiple` lets any number stay pressed. */\r\n type?: 'single' | 'multiple'\r\n size?: 'sm' | 'md'\r\n disabled?: boolean\r\n /** Accessible name for the group. */\r\n label?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<ToggleGroupProps>(), {\r\n type: 'single',\r\n size: 'md',\r\n})\r\n\r\n/** The pressed value in `single` mode, or an array of values in `multiple` mode. */\r\nconst model = defineModel<string | string[] | null>({ default: null })\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\nprovide(TOGGLE_GROUP, {\r\n type: computed(() => props.type),\r\n disabled: computed(() => !!props.disabled),\r\n isSelected: (value) => {\r\n if (props.type === 'multiple') return Array.isArray(model.value) && model.value.includes(value)\r\n return model.value === value\r\n },\r\n toggle: (value) => {\r\n if (props.disabled) return\r\n if (props.type === 'multiple') {\r\n const current = Array.isArray(model.value) ? model.value : []\r\n model.value = current.includes(value) ? current.filter(v => v !== value) : [...current, value]\r\n return\r\n }\r\n model.value = value\r\n },\r\n})\r\n\r\n// Arrow keys move between items, per the WAI-ARIA toolbar pattern.\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const target = event.target as HTMLElement\r\n if (!target.classList.contains('j-toggle-group__item')) return\r\n const root = event.currentTarget as HTMLElement\r\n const items = Array.from(root.querySelectorAll<HTMLElement>('.j-toggle-group__item:not(:disabled)'))\r\n const index = items.indexOf(target)\r\n let next: HTMLElement | undefined\r\n if (event.key === 'ArrowRight' || event.key === 'ArrowDown') next = items[(index + 1) % items.length]\r\n else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') next = items[(index - 1 + items.length) % items.length]\r\n else if (event.key === 'Home') next = items[0]\r\n else if (event.key === 'End') next = items[items.length - 1]\r\n if (!next) return\r\n event.preventDefault()\r\n focusElement(next)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-toggle-group\"\r\n :class=\"[`j-toggle-group--${size}`, { 'is-disabled': disabled }]\"\r\n role=\"group\"\r\n :aria-label=\"label\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <slot />\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-toggle-group {\r\n --j-toggle-height: var(--juxt-control-md);\r\n\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 2px;\r\n padding: 2px;\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-sunken);\r\n box-shadow: inset 0 0 0 1px var(--juxt-border-subtle);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-toggle-group--sm {\r\n --j-toggle-height: var(--juxt-control-sm);\r\n}\r\n\r\n.j-toggle-group.is-disabled {\r\n opacity: 0.6;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, provide } from 'vue'\r\nimport { focusElement } from '../../utils/dom'\r\nimport { TOGGLE_GROUP } from './context'\r\n\r\nexport interface ToggleGroupProps {\r\n /** `single` keeps at most one item pressed; `multiple` lets any number stay pressed. */\r\n type?: 'single' | 'multiple'\r\n size?: 'sm' | 'md'\r\n disabled?: boolean\r\n /** Accessible name for the group. */\r\n label?: string\r\n}\r\n\r\nconst props = withDefaults(defineProps<ToggleGroupProps>(), {\r\n type: 'single',\r\n size: 'md',\r\n})\r\n\r\n/** The pressed value in `single` mode, or an array of values in `multiple` mode. */\r\nconst model = defineModel<string | string[] | null>({ default: null })\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\nprovide(TOGGLE_GROUP, {\r\n type: computed(() => props.type),\r\n disabled: computed(() => !!props.disabled),\r\n isSelected: (value) => {\r\n if (props.type === 'multiple') return Array.isArray(model.value) && model.value.includes(value)\r\n return model.value === value\r\n },\r\n toggle: (value) => {\r\n if (props.disabled) return\r\n if (props.type === 'multiple') {\r\n const current = Array.isArray(model.value) ? model.value : []\r\n model.value = current.includes(value) ? current.filter(v => v !== value) : [...current, value]\r\n return\r\n }\r\n model.value = value\r\n },\r\n})\r\n\r\n// Arrow keys move between items, per the WAI-ARIA toolbar pattern.\r\nfunction onKeydown(event: KeyboardEvent) {\r\n const target = event.target as HTMLElement\r\n if (!target.classList.contains('j-toggle-group__item')) return\r\n const root = event.currentTarget as HTMLElement\r\n const items = Array.from(root.querySelectorAll<HTMLElement>('.j-toggle-group__item:not(:disabled)'))\r\n const index = items.indexOf(target)\r\n let next: HTMLElement | undefined\r\n if (event.key === 'ArrowRight' || event.key === 'ArrowDown') next = items[(index + 1) % items.length]\r\n else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') next = items[(index - 1 + items.length) % items.length]\r\n else if (event.key === 'Home') next = items[0]\r\n else if (event.key === 'End') next = items[items.length - 1]\r\n if (!next) return\r\n event.preventDefault()\r\n focusElement(next)\r\n}\r\n</script>\r\n\r\n<template>\r\n <div\r\n class=\"j-toggle-group\"\r\n :class=\"[`j-toggle-group--${size}`, { 'is-disabled': disabled }]\"\r\n role=\"group\"\r\n :aria-label=\"label\"\r\n @keydown=\"onKeydown\"\r\n >\r\n <slot />\r\n </div>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-toggle-group {\r\n --j-toggle-height: var(--juxt-control-md);\r\n\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 2px;\r\n padding: 2px;\r\n border-radius: var(--juxt-radius-md);\r\n background: var(--juxt-surface-sunken);\r\n box-shadow: inset 0 0 0 1px var(--juxt-border-subtle);\r\n font-family: var(--juxt-font-sans);\r\n}\r\n\r\n.j-toggle-group--sm {\r\n --j-toggle-height: var(--juxt-control-sm);\r\n}\r\n\r\n.j-toggle-group.is-disabled {\r\n opacity: 0.6;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, inject } from 'vue'\r\nimport { TOGGLE_GROUP } from './context'\r\n\r\nexport interface ToggleGroupItemProps {\r\n /** Identifies the item in the group's `v-model`. */\r\n value: string\r\n disabled?: boolean\r\n /** Accessible name, when the slot content isn't descriptive enough (e.g. an icon-only item). */\r\n label?: string\r\n}\r\n\r\nconst props = defineProps<ToggleGroupItemProps>()\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\nconst group = inject(TOGGLE_GROUP)!\r\nconst selected = computed(() => group.isSelected(props.value))\r\nconst isDisabled = computed(() => props.disabled || group.disabled.value)\r\n</script>\r\n\r\n<template>\r\n <button\r\n type=\"button\"\r\n class=\"j-toggle-group__item j-focusable\"\r\n :class=\"{ 'is-selected': selected }\"\r\n :aria-pressed=\"selected\"\r\n :aria-label=\"label\"\r\n :disabled=\"isDisabled\"\r\n @click=\"group.toggle(value)\"\r\n >\r\n <slot />\r\n </button>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-toggle-group__item {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: var(--juxt-space-1-5);\r\n height: var(--j-toggle-height, var(--juxt-control-md));\r\n padding: 0 var(--juxt-space-3);\r\n border: 0;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg-secondary);\r\n font: inherit;\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n white-space: nowrap;\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n box-shadow var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-toggle-group__item > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-toggle-group__item:hover:not(:disabled) {\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-toggle-group__item.is-selected {\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-sm);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.dark .j-toggle-group__item.is-selected,\r\n[data-theme='dark'] .j-toggle-group__item.is-selected {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n.j-toggle-group__item:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport { computed, inject } from 'vue'\r\nimport { TOGGLE_GROUP } from './context'\r\n\r\nexport interface ToggleGroupItemProps {\r\n /** Identifies the item in the group's `v-model`. */\r\n value: string\r\n disabled?: boolean\r\n /** Accessible name, when the slot content isn't descriptive enough (e.g. an icon-only item). */\r\n label?: string\r\n}\r\n\r\nconst props = defineProps<ToggleGroupItemProps>()\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n\r\nconst group = inject(TOGGLE_GROUP)!\r\nconst selected = computed(() => group.isSelected(props.value))\r\nconst isDisabled = computed(() => props.disabled || group.disabled.value)\r\n</script>\r\n\r\n<template>\r\n <button\r\n type=\"button\"\r\n class=\"j-toggle-group__item j-focusable\"\r\n :class=\"{ 'is-selected': selected }\"\r\n :aria-pressed=\"selected\"\r\n :aria-label=\"label\"\r\n :disabled=\"isDisabled\"\r\n @click=\"group.toggle(value)\"\r\n >\r\n <slot />\r\n </button>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-toggle-group__item {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: var(--juxt-space-1-5);\r\n height: var(--j-toggle-height, var(--juxt-control-md));\r\n padding: 0 var(--juxt-space-3);\r\n border: 0;\r\n border-radius: var(--juxt-radius-sm);\r\n background: transparent;\r\n color: var(--juxt-fg-secondary);\r\n font: inherit;\r\n font-size: var(--juxt-text-sm);\r\n font-weight: var(--juxt-weight-medium);\r\n letter-spacing: var(--juxt-tracking-tight);\r\n white-space: nowrap;\r\n cursor: pointer;\r\n outline: 2px solid transparent;\r\n outline-offset: 0;\r\n -webkit-tap-highlight-color: transparent;\r\n transition:\r\n background-color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n color var(--juxt-duration-fast) var(--juxt-ease-standard),\r\n box-shadow var(--juxt-duration-fast) var(--juxt-ease-standard);\r\n}\r\n\r\n.j-toggle-group__item > svg {\r\n width: 1rem;\r\n height: 1rem;\r\n}\r\n\r\n.j-toggle-group__item:hover:not(:disabled) {\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.j-toggle-group__item.is-selected {\r\n background: var(--juxt-surface);\r\n box-shadow: var(--juxt-shadow-sm);\r\n color: var(--juxt-fg);\r\n}\r\n\r\n.dark .j-toggle-group__item.is-selected,\r\n[data-theme='dark'] .j-toggle-group__item.is-selected {\r\n background: var(--juxt-surface-active);\r\n}\r\n\r\n.j-toggle-group__item:disabled {\r\n color: var(--juxt-fg-disabled);\r\n cursor: not-allowed;\r\n}\r\n}\r\n</style>\r\n","<script lang=\"ts\">\r\n// Shared across every tooltip: once one has been shown, neighbours open\r\n// instantly for a moment. Scanning a toolbar shouldn't mean waiting at each icon.\r\nlet warmUntil = 0\r\nconst WARM_WINDOW = 400\r\n</script>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, onBeforeUnmount, ref, useId } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { unrefElement } from '../../utils/dom'\r\nimport { JSlot } from '../primitives/JSlot'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface TooltipProps {\r\n /** Tooltip text. Use the `content` slot for richer content. */\r\n content?: string\r\n side?: 'top' | 'right' | 'bottom' | 'left'\r\n align?: 'start' | 'center' | 'end'\r\n /** Hover delay before opening, in ms. Keyboard focus opens immediately. */\r\n delay?: number\r\n disabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<TooltipProps>(), {\r\n side: 'top',\r\n align: 'center',\r\n delay: 450,\r\n})\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n content?: () => unknown\r\n}>()\r\n\r\nconst id = `j-${useId()}-tooltip`\r\nconst open = ref(false)\r\nconst triggerRef = ref()\r\nconst triggerEl = computed(() => unrefElement(triggerRef.value))\r\nconst floating = ref<HTMLElement | null>(null)\r\n\r\nconst placement = computed(() => (props.align === 'center' ? props.side : `${props.side}-${props.align}` as const))\r\nconst { styles } = useFloating(triggerEl, floating, () => ({ placement: placement.value, offset: 6 }))\r\n\r\nuseLayer({\r\n active: open,\r\n elements: () => [triggerEl.value, floating.value],\r\n onEscape: () => hide(true),\r\n})\r\n\r\nlet openTimer: ReturnType<typeof setTimeout> | undefined\r\nlet closeTimer: ReturnType<typeof setTimeout> | undefined\r\n// After a click, stay quiet until the pointer leaves | the user is acting, not reading.\r\nlet suppressed = false\r\n\r\nfunction show(immediate = false) {\r\n if (props.disabled || suppressed) return\r\n clearTimeout(closeTimer)\r\n if (open.value) return\r\n clearTimeout(openTimer)\r\n if (immediate || Date.now() < warmUntil) open.value = true\r\n else openTimer = setTimeout(() => (open.value = true), props.delay)\r\n}\r\n\r\nfunction hide(immediate = false) {\r\n clearTimeout(openTimer)\r\n clearTimeout(closeTimer)\r\n const close = () => {\r\n if (open.value) warmUntil = Date.now() + WARM_WINDOW\r\n open.value = false\r\n }\r\n if (immediate) close()\r\n // A short grace period lets the pointer travel onto the tooltip itself.\r\n else closeTimer = setTimeout(close, 80)\r\n}\r\n\r\nonBeforeUnmount(() => {\r\n clearTimeout(openTimer)\r\n clearTimeout(closeTimer)\r\n})\r\n\r\nconst triggerProps = computed(() => ({\r\n 'aria-describedby': props.disabled ? undefined : id,\r\n 'onPointerenter': (event: PointerEvent) => event.pointerType === 'mouse' && show(),\r\n 'onPointerleave': (event: PointerEvent) => {\r\n suppressed = false\r\n if (event.pointerType === 'mouse') hide()\r\n },\r\n 'onPointerdown': () => {\r\n suppressed = true\r\n hide(true)\r\n },\r\n 'onFocus': (event: FocusEvent) => {\r\n const target = event.target as HTMLElement\r\n if (target.matches(':focus-visible')) show(true)\r\n },\r\n 'onBlur': () => hide(true),\r\n}))\r\n</script>\r\n\r\n<template>\r\n <JSlot ref=\"triggerRef\" v-bind=\"triggerProps\">\r\n <slot />\r\n </JSlot>\r\n <span v-if=\"!disabled\" :id=\"id\" class=\"j-sr-only\">\r\n <slot name=\"content\">{{ content }}</slot>\r\n </span>\r\n <JPortal>\r\n <Transition name=\"j-tooltip\">\r\n <div\r\n v-if=\"open\"\r\n ref=\"floating\"\r\n class=\"j-tooltip\"\r\n :style=\"styles\"\r\n aria-hidden=\"true\"\r\n data-juxt-layer-ignore\r\n @pointerenter=\"show(true)\"\r\n @pointerleave=\"hide()\"\r\n >\r\n <slot name=\"content\">\r\n {{ content }}\r\n </slot>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-tooltip {\r\n z-index: var(--juxt-z-tooltip);\r\n box-sizing: border-box;\r\n max-width: min(17.5rem, calc(100vw - 16px));\r\n padding: var(--juxt-space-1) var(--juxt-space-2);\r\n border: 1px solid var(--juxt-tooltip-border);\r\n border-radius: var(--juxt-radius-sm);\r\n background: var(--juxt-tooltip-bg);\r\n box-shadow: var(--juxt-shadow-sm);\r\n color: var(--juxt-tooltip-fg);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: 0;\r\n overflow-wrap: break-word;\r\n pointer-events: auto;\r\n}\r\n\r\n.j-tooltip .j-kbd {\r\n height: 1rem;\r\n min-width: 1rem;\r\n margin-left: var(--juxt-space-1);\r\n border-color: rgb(255 255 255 / 0.14);\r\n background: rgb(255 255 255 / 0.08);\r\n color: rgb(255 255 255 / 0.72);\r\n}\r\n\r\n.j-tooltip-enter-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-tooltip-leave-active {\r\n transition: opacity var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-tooltip-enter-from,\r\n.j-tooltip-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-tooltip-enter-from {\r\n transform: translate(\r\n calc(var(--juxt-motion-shift) * 0.5 * var(--j-pop-x, 0)),\r\n calc(var(--juxt-motion-shift) * 0.5 * var(--j-pop-dir, 0))\r\n );\r\n}\r\n}\r\n</style>\r\n","<script lang=\"ts\">\r\n// Shared across every tooltip: once one has been shown, neighbours open\r\n// instantly for a moment. Scanning a toolbar shouldn't mean waiting at each icon.\r\nlet warmUntil = 0\r\nconst WARM_WINDOW = 400\r\n</script>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed, onBeforeUnmount, ref, useId } from 'vue'\r\nimport { useFloating } from '../../composables/useFloating'\r\nimport { useLayer } from '../../composables/useLayer'\r\nimport { unrefElement } from '../../utils/dom'\r\nimport { JSlot } from '../primitives/JSlot'\r\nimport JPortal from '../primitives/JPortal.vue'\r\n\r\nexport interface TooltipProps {\r\n /** Tooltip text. Use the `content` slot for richer content. */\r\n content?: string\r\n side?: 'top' | 'right' | 'bottom' | 'left'\r\n align?: 'start' | 'center' | 'end'\r\n /** Hover delay before opening, in ms. Keyboard focus opens immediately. */\r\n delay?: number\r\n disabled?: boolean\r\n}\r\n\r\nconst props = withDefaults(defineProps<TooltipProps>(), {\r\n side: 'top',\r\n align: 'center',\r\n delay: 450,\r\n})\r\n\r\ndefineSlots<{\r\n default?: () => unknown\r\n content?: () => unknown\r\n}>()\r\n\r\nconst id = `j-${useId()}-tooltip`\r\nconst open = ref(false)\r\nconst triggerRef = ref()\r\nconst triggerEl = computed(() => unrefElement(triggerRef.value))\r\nconst floating = ref<HTMLElement | null>(null)\r\n\r\nconst placement = computed(() => (props.align === 'center' ? props.side : `${props.side}-${props.align}` as const))\r\nconst { styles } = useFloating(triggerEl, floating, () => ({ placement: placement.value, offset: 6 }))\r\n\r\nuseLayer({\r\n active: open,\r\n elements: () => [triggerEl.value, floating.value],\r\n onEscape: () => hide(true),\r\n})\r\n\r\nlet openTimer: ReturnType<typeof setTimeout> | undefined\r\nlet closeTimer: ReturnType<typeof setTimeout> | undefined\r\n// After a click, stay quiet until the pointer leaves | the user is acting, not reading.\r\nlet suppressed = false\r\n\r\nfunction show(immediate = false) {\r\n if (props.disabled || suppressed) return\r\n clearTimeout(closeTimer)\r\n if (open.value) return\r\n clearTimeout(openTimer)\r\n if (immediate || Date.now() < warmUntil) open.value = true\r\n else openTimer = setTimeout(() => (open.value = true), props.delay)\r\n}\r\n\r\nfunction hide(immediate = false) {\r\n clearTimeout(openTimer)\r\n clearTimeout(closeTimer)\r\n const close = () => {\r\n if (open.value) warmUntil = Date.now() + WARM_WINDOW\r\n open.value = false\r\n }\r\n if (immediate) close()\r\n // A short grace period lets the pointer travel onto the tooltip itself.\r\n else closeTimer = setTimeout(close, 80)\r\n}\r\n\r\nonBeforeUnmount(() => {\r\n clearTimeout(openTimer)\r\n clearTimeout(closeTimer)\r\n})\r\n\r\nconst triggerProps = computed(() => ({\r\n 'aria-describedby': props.disabled ? undefined : id,\r\n 'onPointerenter': (event: PointerEvent) => event.pointerType === 'mouse' && show(),\r\n 'onPointerleave': (event: PointerEvent) => {\r\n suppressed = false\r\n if (event.pointerType === 'mouse') hide()\r\n },\r\n 'onPointerdown': () => {\r\n suppressed = true\r\n hide(true)\r\n },\r\n 'onFocus': (event: FocusEvent) => {\r\n const target = event.target as HTMLElement\r\n if (target.matches(':focus-visible')) show(true)\r\n },\r\n 'onBlur': () => hide(true),\r\n}))\r\n</script>\r\n\r\n<template>\r\n <JSlot ref=\"triggerRef\" v-bind=\"triggerProps\">\r\n <slot />\r\n </JSlot>\r\n <span v-if=\"!disabled\" :id=\"id\" class=\"j-sr-only\">\r\n <slot name=\"content\">{{ content }}</slot>\r\n </span>\r\n <JPortal>\r\n <Transition name=\"j-tooltip\">\r\n <div\r\n v-if=\"open\"\r\n ref=\"floating\"\r\n class=\"j-tooltip\"\r\n :style=\"styles\"\r\n aria-hidden=\"true\"\r\n data-juxt-layer-ignore\r\n @pointerenter=\"show(true)\"\r\n @pointerleave=\"hide()\"\r\n >\r\n <slot name=\"content\">\r\n {{ content }}\r\n </slot>\r\n </div>\r\n </Transition>\r\n </JPortal>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n.j-tooltip {\r\n z-index: var(--juxt-z-tooltip);\r\n box-sizing: border-box;\r\n max-width: min(17.5rem, calc(100vw - 16px));\r\n padding: var(--juxt-space-1) var(--juxt-space-2);\r\n border: 1px solid var(--juxt-tooltip-border);\r\n border-radius: var(--juxt-radius-sm);\r\n background: var(--juxt-tooltip-bg);\r\n box-shadow: var(--juxt-shadow-sm);\r\n color: var(--juxt-tooltip-fg);\r\n font-family: var(--juxt-font-sans);\r\n font-size: var(--juxt-text-xs);\r\n font-weight: var(--juxt-weight-medium);\r\n line-height: var(--juxt-leading-snug);\r\n letter-spacing: 0;\r\n overflow-wrap: break-word;\r\n pointer-events: auto;\r\n}\r\n\r\n.j-tooltip .j-kbd {\r\n height: 1rem;\r\n min-width: 1rem;\r\n margin-left: var(--juxt-space-1);\r\n border-color: rgb(255 255 255 / 0.14);\r\n background: rgb(255 255 255 / 0.08);\r\n color: rgb(255 255 255 / 0.72);\r\n}\r\n\r\n.j-tooltip-enter-active {\r\n transition:\r\n opacity var(--juxt-duration-fast) var(--juxt-ease-out),\r\n transform var(--juxt-duration-fast) var(--juxt-ease-out);\r\n}\r\n\r\n.j-tooltip-leave-active {\r\n transition: opacity var(--juxt-duration-instant) var(--juxt-ease-in);\r\n}\r\n\r\n.j-tooltip-enter-from,\r\n.j-tooltip-leave-to {\r\n opacity: 0;\r\n}\r\n\r\n.j-tooltip-enter-from {\r\n transform: translate(\r\n calc(var(--juxt-motion-shift) * 0.5 * var(--j-pop-x, 0)),\r\n calc(var(--juxt-motion-shift) * 0.5 * var(--j-pop-dir, 0))\r\n );\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\n\r\nexport interface VisuallyHiddenProps {\r\n /** Render as another element or component, e.g. `label` or `h2`. */\r\n as?: string | Component\r\n}\r\n\r\nwithDefaults(defineProps<VisuallyHiddenProps>(), { as: 'span' })\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <component :is=\"as\" class=\"j-visually-hidden j-sr-only\">\r\n <slot />\r\n </component>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n/*\r\n * Visual hiding is handled entirely by the shared `.j-sr-only` utility in\r\n * base.css | this component just gives it a typed, composable wrapper so it\r\n * can be used as `<JVisuallyHidden as=\"label\">…</JVisuallyHidden>`.\r\n */\r\n.j-visually-hidden {\r\n display: block;\r\n}\r\n}\r\n</style>\r\n","<script setup lang=\"ts\">\r\nimport type { Component } from 'vue'\r\n\r\nexport interface VisuallyHiddenProps {\r\n /** Render as another element or component, e.g. `label` or `h2`. */\r\n as?: string | Component\r\n}\r\n\r\nwithDefaults(defineProps<VisuallyHiddenProps>(), { as: 'span' })\r\n\r\ndefineSlots<{ default?: () => unknown }>()\r\n</script>\r\n\r\n<template>\r\n <component :is=\"as\" class=\"j-visually-hidden j-sr-only\">\r\n <slot />\r\n </component>\r\n</template>\r\n\r\n<style>\r\n@layer theme, base, juxt, components, utilities;\r\n@layer juxt {\r\n/*\r\n * Visual hiding is handled entirely by the shared `.j-sr-only` utility in\r\n * base.css | this component just gives it a typed, composable wrapper so it\r\n * can be used as `<JVisuallyHidden as=\"label\">…</JVisuallyHidden>`.\r\n */\r\n.j-visually-hidden {\r\n display: block;\r\n}\r\n}\r\n</style>\r\n","export { default as JAccordion } from './components/accordion/JAccordion.vue'\r\nexport { default as JAccordionItem } from './components/accordion/JAccordionItem.vue'\r\nexport { default as JAlert } from './components/alert/JAlert.vue'\r\nexport { default as JAspectRatio } from './components/aspect-ratio/JAspectRatio.vue'\r\nexport { default as JAvatar } from './components/avatar/JAvatar.vue'\r\nexport { default as JAvatarGroup } from './components/avatar-group/JAvatarGroup.vue'\r\nexport { default as JBadge } from './components/badge/JBadge.vue'\r\nexport { default as JBanner } from './components/banner/JBanner.vue'\r\nexport { default as JBreadcrumb } from './components/breadcrumb/JBreadcrumb.vue'\r\nexport { default as JButton } from './components/button/JButton.vue'\r\nexport { default as JCard } from './components/card/JCard.vue'\r\nexport { default as JCheckbox } from './components/checkbox/JCheckbox.vue'\r\nexport { default as JCollapsible } from './components/collapsible/JCollapsible.vue'\r\nexport { default as JCommandPalette } from './components/command-palette/JCommandPalette.vue'\r\nexport { default as JDialog } from './components/dialog/JDialog.vue'\r\nexport { default as JDropdownMenu } from './components/dropdown-menu/JDropdownMenu.vue'\r\nexport { default as JDropdownMenuItem } from './components/dropdown-menu/JDropdownMenuItem.vue'\r\nexport { default as JDropdownMenuLabel } from './components/dropdown-menu/JDropdownMenuLabel.vue'\r\nexport { default as JDropdownMenuSeparator } from './components/dropdown-menu/JDropdownMenuSeparator.vue'\r\nexport { default as JDropdownMenuSub } from './components/dropdown-menu/JDropdownMenuSub.vue'\r\nexport { default as JEmptyState } from './components/empty-state/JEmptyState.vue'\r\nexport { default as JInput } from './components/input/JInput.vue'\r\nexport { default as JKbd } from './components/kbd/JKbd.vue'\r\nexport { default as JPagination } from './components/pagination/JPagination.vue'\r\nexport { default as JPopover } from './components/popover/JPopover.vue'\r\nexport { default as JProgress } from './components/progress/JProgress.vue'\r\nexport { default as JRadio } from './components/radio/JRadio.vue'\r\nexport { default as JRadioGroup } from './components/radio/JRadioGroup.vue'\r\nexport { default as JRating } from './components/rating/JRating.vue'\r\nexport { default as JSelect } from './components/select/JSelect.vue'\r\nexport { default as JSeparator } from './components/separator/JSeparator.vue'\r\nexport { default as JSheet } from './components/sheet/JSheet.vue'\r\nexport { default as JSkeleton } from './components/skeleton/JSkeleton.vue'\r\nexport { default as JSlider } from './components/slider/JSlider.vue'\r\nexport { default as JSpinner } from './components/spinner/JSpinner.vue'\r\nexport { default as JStat } from './components/stat/JStat.vue'\r\nexport { default as JSwitch } from './components/switch/JSwitch.vue'\r\nexport { default as JTabs } from './components/tabs/JTabs.vue'\r\nexport { default as JTag } from './components/tag/JTag.vue'\r\nexport { default as JTextarea } from './components/textarea/JTextarea.vue'\r\nexport { default as JTimeline } from './components/timeline/JTimeline.vue'\r\nexport { default as JTimelineItem } from './components/timeline/JTimelineItem.vue'\r\nexport { default as JToaster } from './components/toast/JToaster.vue'\r\nexport { default as JToggleGroup } from './components/toggle-group/JToggleGroup.vue'\r\nexport { default as JToggleGroupItem } from './components/toggle-group/JToggleGroupItem.vue'\r\nexport { default as JTooltip } from './components/tooltip/JTooltip.vue'\r\nexport { default as JVisuallyHidden } from './components/visually-hidden/JVisuallyHidden.vue'\r\n","import { onMounted, ref } from 'vue'\r\n\r\nexport interface RecentCommandsOptions {\r\n /** localStorage key. */\r\n key?: string\r\n /** How many ids to remember. */\r\n limit?: number\r\n}\r\n\r\n/**\r\n * Remembers which commands were used most recently, per browser.\r\n * Pair `record(item.id)` in your select handler with `resolve(allItems)`\r\n * to feed the palette's `recent` prop.\r\n */\r\nexport function useRecentCommands(options: RecentCommandsOptions = {}) {\r\n const key = options.key ?? 'juxt:recent-commands'\r\n const limit = options.limit ?? 5\r\n const ids = ref<string[]>([])\r\n\r\n function read(): string[] {\r\n try {\r\n const value = JSON.parse(localStorage.getItem(key) ?? '[]')\r\n return Array.isArray(value) ? value.filter((id): id is string => typeof id === 'string') : []\r\n } catch {\r\n return []\r\n }\r\n }\r\n\r\n function write() {\r\n try {\r\n localStorage.setItem(key, JSON.stringify(ids.value))\r\n } catch {\r\n // Private mode or storage disabled: recents simply won't persist.\r\n }\r\n }\r\n\r\n onMounted(() => (ids.value = read()))\r\n\r\n function record(id: string) {\r\n ids.value = [id, ...ids.value.filter(existing => existing !== id)].slice(0, limit)\r\n write()\r\n }\r\n\r\n function clear() {\r\n ids.value = []\r\n write()\r\n }\r\n\r\n /** Map remembered ids back to items, dropping any that no longer exist. */\r\n function resolve<T extends { id: string }>(items: T[]): T[] {\r\n const byId = new Map(items.map(item => [item.id, item]))\r\n return ids.value.map(id => byId.get(id)).filter((item): item is T => !!item)\r\n }\r\n\r\n return { ids, record, clear, resolve }\r\n}\r\n","import type { Component, Plugin } from 'vue'\r\nimport * as components from './components'\r\n\r\n/**\r\n * Registers every Juxt component globally.\r\n *\r\n * app.use(JuxtUI)\r\n *\r\n * Prefer direct imports (or the Nuxt module) if you want tree-shaking.\r\n */\r\nexport const JuxtUI: Plugin = {\r\n install(app) {\r\n for (const [name, component] of Object.entries(components)) app.component(name, component as Component)\r\n },\r\n}\r\n"],"mappings":"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"}
|