rei-kit 4.3.0 → 4.4.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.
@@ -9,14 +9,25 @@
9
9
  */
10
10
  type __VLS_Props = {
11
11
  title: string;
12
+ /**
13
+ * The element the title is, when `h1` is not it.
14
+ *
15
+ * A screen's title is the page's one `h1`, which is the default. It is the
16
+ * wrong answer in two places: a screen rendered inside another page — a
17
+ * documentation site, a preview, a phone frame beside three others — and a
18
+ * header that sits under a heading that already owns the rank. Both put a
19
+ * second `h1` in the document, which is how this kit's own showcase ended
20
+ * up with three.
21
+ */
22
+ as?: 'h1' | 'h2' | 'h3' | 'p' | undefined;
12
23
  };
13
- declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {};
24
+ declare var __VLS_1: {}, __VLS_9: {}, __VLS_11: {};
14
25
  type __VLS_Slots = {} & {
15
26
  left?: (props: typeof __VLS_1) => any;
16
27
  } & {
17
- title?: (props: typeof __VLS_3) => any;
28
+ title?: (props: typeof __VLS_9) => any;
18
29
  } & {
19
- right?: (props: typeof __VLS_5) => any;
30
+ right?: (props: typeof __VLS_11) => any;
20
31
  };
21
32
  declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
22
33
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,5 +1,5 @@
1
1
  import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BOaGB7Aw.js";
2
- import { t as BaseButton_default } from "./BaseButton-BD2HKB1L.js";
2
+ import { t as BaseButton_default } from "./BaseButton-C3VGgIuR.js";
3
3
  import { Teleport, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeModels, nextTick, normalizeClass, normalizeStyle, onScopeDispose, onUnmounted, openBlock, readonly, ref, renderSlot, toDisplayString, unref, useId, useModel, vModelCheckbox, watch, withCtx, withDirectives } from "vue";
4
4
  import { X } from "lucide-vue-next";
5
5
  //#region src/utils/sheet-root.ts
@@ -458,4 +458,4 @@ function focusTarget(target) {
458
458
  //#endregion
459
459
  export { BaseCheckbox_default as a, useBoundValue as c, ensureSheetRoot as d, textDirection as i, useVisualViewport as l, elementDirection as n, BaseSheet_default as o, horizontalStep as r, inertOutside as s, focusTarget as t, SHEET_ROOT_ID as u };
460
460
 
461
- //# sourceMappingURL=focus-target-BtcoJdmz.js.map
461
+ //# sourceMappingURL=focus-target-DENXwVQ3.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"focus-target-BtcoJdmz.js","names":[],"sources":["../src/utils/sheet-root.ts","../src/composables/use-visual-viewport.ts","../src/composables/use-bound-value.ts","../src/utils/inert.ts","../src/components/BaseSheet.vue","../src/components/BaseSheet.vue","../src/components/BaseCheckbox.vue","../src/components/BaseCheckbox.vue","../src/utils/direction.ts","../src/utils/focus-target.ts"],"sourcesContent":["/** The node every sheet teleports into. */\nexport const SHEET_ROOT_ID = 'sheet-root'\n\n/**\n * Returns the sheet root, creating it if the page has none.\n *\n * `BaseSheet` teleports out of the app's tree so the app behind it can be\n * marked `inert` — a sheet inside the element it is disabling would disable\n * itself. That means it needs a mount point outside `#app`.\n *\n * It used to need the consuming app to put `<div id=\"sheet-root\">` in its\n * `index.html`, and nothing said so. A page without it got a sheet that opened,\n * blocked the page, and rendered nothing: Vue warns to the console about a\n * missing teleport target and carries on, so the build is green, the types are\n * fine, and the screen is wrong. The kit's own showcase had exactly that bug,\n * which is how it was found.\n *\n * So the node is made on demand. An app that already declares one keeps it —\n * this only fills a gap, it never replaces.\n */\nexport function ensureSheetRoot(): string {\n const selector = `#${SHEET_ROOT_ID}`\n\n // Server-rendered: there is no document, and Teleport is skipped anyway.\n if (typeof document === 'undefined') return selector\n if (document.getElementById(SHEET_ROOT_ID)) return selector\n\n const root = document.createElement('div')\n root.id = SHEET_ROOT_ID\n document.body.appendChild(root)\n\n return selector\n}\n","import { onScopeDispose, readonly, ref } from 'vue'\n\n/** The visible area, once the on-screen keyboard has taken its share. */\nexport interface VisualViewportRect {\n height: number\n offsetTop: number\n}\n\n/**\n * Tracks the visual viewport.\n *\n * Chrome and Android browsers honour `interactive-widget=resizes-content`, so\n * the layout viewport already shrinks for the keyboard there. Safari on iOS\n * does not implement it: it shrinks only the *visual* viewport, leaving a sheet\n * sized in `dvh` sitting partly underneath the keyboard.\n *\n * `null` means the API is unavailable, which callers should read as \"trust the\n * layout viewport\" rather than as zero. A server has no viewport at all, so it\n * gets that same `null` — this runs during `setup`, and a component using it\n * has to survive being rendered there.\n *\n * @example\n * ```ts\n * const viewport = useVisualViewport()\n * // :style=\"viewport ? { height: `${viewport.height}px` } : undefined\"\n * ```\n */\nexport function useVisualViewport() {\n const rect = ref<VisualViewportRect | null>(null)\n\n const viewport = typeof window === 'undefined' ? undefined : window.visualViewport\n if (!viewport) return readonly(rect)\n\n function read() {\n if (!viewport) return\n\n rect.value = { height: viewport.height, offsetTop: viewport.offsetTop }\n }\n\n read()\n\n // `scroll` matters as much as `resize`: iOS shifts the visual viewport up to\n // keep the focused field visible, without changing its height.\n viewport.addEventListener('resize', read)\n viewport.addEventListener('scroll', read)\n\n onScopeDispose(() => {\n viewport.removeEventListener('resize', read)\n viewport.removeEventListener('scroll', read)\n })\n\n return readonly(rect)\n}\n","import { computed, ref, watch } from 'vue'\nimport type { Ref, WritableComputedRef } from 'vue'\n\n/**\n * A `v-model` that accepts `undefined` and emits only what a field produces.\n *\n * `defineModel` could not be typed both ways. Without a default it declares\n * that it may emit `undefined`, so under vue-tsc's strictTemplates a\n * `ref('')` cannot be bound to it. With a default it drops `undefined` from\n * what it accepts, so under `exactOptionalPropertyTypes` a form library's\n * `string | undefined` cannot be. Hibi binds exactly that, and 2.4.0 as first\n * written broke it — the consumer check caught it before the tag.\n *\n * So the prop and the event are declared by hand — the prop as\n * `T | undefined`, the event as `T` — and this is the rest of what\n * `defineModel` did: follow the prop, and keep a value of its own when\n * nothing is bound. Vue applies `.trim` and `.number` to the event itself, so\n * modifiers behave as they did.\n *\n * Internal: not exported from the package.\n */\nexport function useBoundValue<T>(\n read: () => T | undefined,\n write: (value: T) => void,\n): WritableComputedRef<T | undefined> {\n const local = ref(read()) as Ref<T | undefined>\n\n watch(read, (next) => {\n local.value = next\n })\n\n return computed({\n get: () => local.value,\n set: (next) => {\n local.value = next\n write(next as T)\n },\n })\n}\n","/**\n * How many open layers hold each element inert. An element set inert by the\n * app itself is never in here, so releasing never undoes the app's own choice.\n */\nconst holds = new Map<Element, number>()\n\n/**\n * Takes everything on the page except the branch holding `keep` out of tab\n * order and pointer events, and returns the function that gives it back.\n *\n * `inert` is a real focus trap without keydown bookkeeping, and the only one\n * that also stops a screen reader's virtual cursor. It used to be applied to\n * `document.getElementById('app')` — the id Vite's template happens to use —\n * so an app mounted on `#root` got a dialog whose background was still\n * reachable with Tab, and nothing said so. Working from `<body>`'s children\n * instead holds whatever the app is mounted on, and anything else teleported\n * there too.\n *\n * Counted per element, so a sheet opened over a sheet, or a tour over either,\n * does not hand the page back when the inner one closes.\n */\nexport function inertOutside(keep: Element): () => void {\n if (typeof document === 'undefined') return () => {}\n\n const taken: Element[] = []\n\n for (const element of Array.from(document.body.children)) {\n if (element.contains(keep)) continue\n\n const count = holds.get(element)\n if (count === undefined) {\n if (element.hasAttribute('inert')) continue\n element.setAttribute('inert', '')\n }\n holds.set(element, (count ?? 0) + 1)\n taken.push(element)\n }\n\n let released = false\n\n return () => {\n if (released) return\n released = true\n\n for (const element of taken) {\n const count = (holds.get(element) ?? 1) - 1\n if (count > 0) {\n holds.set(element, count)\n continue\n }\n holds.delete(element)\n element.removeAttribute('inert')\n }\n }\n}\n","<script setup lang=\"ts\">\nimport BaseButton from './BaseButton.vue'\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\nimport { X } from 'lucide-vue-next'\n\nimport { useVisualViewport } from '../composables/use-visual-viewport'\nimport { inertOutside } from '../utils/inert'\nimport { ensureSheetRoot } from '../utils/sheet-root'\n\nconst open = defineModel<boolean>({ required: true })\nconst {\n title,\n subtitle = '',\n closeLabel = 'Close',\n} = defineProps<{\n title: string\n subtitle?: string | undefined\n /**\n * Accessible name for the close button.\n *\n * A prop rather than a translation: a component that calls t() forces every\n * consumer onto one i18n setup, and this is the package's only visible string.\n */\n closeLabel?: string | undefined\n}>()\n\n/* Resolved in setup, before the first render, so the Teleport always has a\n target to find. */\nconst sheetRoot = ensureSheetRoot()\n\nconst viewport = useVisualViewport()\n\n/**\n * Pins the sheet to the area the keyboard has left visible.\n *\n * Only needed where the layout viewport does not shrink on its own — iOS. On\n * Android the numbers already agree, so this is a no-op there rather than a\n * second, competing adjustment.\n */\nconst viewportStyle = computed(() =>\n viewport.value\n ? { height: `${viewport.value.height}px`, top: `${viewport.value.offsetTop}px` }\n : undefined,\n)\n\nconst panel = ref<HTMLElement | null>(null)\nlet lastFocused: HTMLElement | null = null\n\nfunction close() {\n open.value = false\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') close()\n}\n\nwatch(open, async (isOpen) => {\n if (isOpen) {\n setBackgroundInert(true)\n lastFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null\n window.addEventListener('keydown', onKeydown)\n await nextTick()\n panel.value?.focus()\n } else {\n window.removeEventListener('keydown', onKeydown)\n // Released before focus is handed back: an inert element cannot take\n // focus, so the other order quietly dropped it on the body.\n setBackgroundInert(false)\n lastFocused?.focus()\n lastFocused = null\n }\n})\n\n/* The whole page but the sheet root goes inert while this is open. Held per\n sheet, so the unmount below can only release what this sheet took. */\nlet releaseInert: (() => void) | null = null\n\nfunction setBackgroundInert(isInert: boolean) {\n releaseInert?.()\n releaseInert = null\n\n const root = isInert ? document.querySelector(sheetRoot) : null\n if (root) releaseInert = inertOutside(root)\n}\n\nonUnmounted(() => {\n window.removeEventListener('keydown', onKeydown)\n // Unmounting while open would otherwise leave the whole app inert forever.\n setBackgroundInert(false)\n})\n</script>\n\n<template>\n <Teleport :to=\"sheetRoot\">\n <Transition name=\"sheet\">\n <div\n v-if=\"open\"\n class=\"fixed inset-x-0 top-0 bottom-0 z-50 flex items-center justify-center\"\n :style=\"viewportStyle\"\n >\n <div\n class=\"shell-frame md:rounded-shell relative flex max-h-full flex-col justify-end overflow-hidden\"\n >\n <div class=\"bg-ink/45 absolute inset-0 backdrop-blur-[2px]\" @click=\"close\" />\n\n <!-- Header and footer stay put; only the slot scrolls. Sized in dvh so\n the on-screen keyboard shrinks the sheet instead of pushing its\n content out of reach. -->\n <section\n ref=\"panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"title\"\n tabindex=\"-1\"\n class=\"sheet-panel surface-overlay relative flex max-h-[94%] min-h-[56dvh] flex-col rounded-t-[calc(var(--radius-shell)-2px)] outline-none\"\n >\n <div class=\"flex shrink-0 justify-center pt-3\" aria-hidden=\"true\">\n <span class=\"bg-hair h-1.5 w-10 rounded-full\" />\n </div>\n\n <header class=\"flex shrink-0 items-start gap-3 px-6 pt-4 pb-5\">\n <div class=\"min-w-0 flex-1\">\n <h2 class=\"text-ink text-xl leading-tight font-semibold\">{{ title }}</h2>\n <p v-if=\"subtitle\" class=\"text-ink-soft mt-1 text-sm leading-snug\">\n {{ subtitle }}\n </p>\n </div>\n\n <!-- `unstyled`, so the sheet keeps the exact button it had. What it\n gains is the focus ring it never had: this was a raw\n `<button>` with no `focus-visible` rule, so closing a sheet\n from the keyboard was invisible. -->\n <BaseButton\n variant=\"unstyled\"\n class=\"text-ink-soft hover:bg-muted hover:text-ink -mt-1 flex size-10 shrink-0 items-center justify-center rounded-full transition-colors active:scale-90\"\n :aria-label=\"closeLabel\"\n @click=\"close\"\n >\n <X class=\"size-5\" />\n </BaseButton>\n </header>\n\n <div\n class=\"min-h-0 flex-1 overflow-y-auto px-6 pb-[calc(2rem+env(safe-area-inset-bottom,0px))]\"\n >\n <slot />\n </div>\n </section>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style scoped>\n.sheet-enter-active,\n.sheet-leave-active {\n transition: opacity var(--duration-base) ease;\n}\n.sheet-enter-from,\n.sheet-leave-to {\n opacity: 0;\n}\n\n/* The panel travels further than the scrim fades, which is what makes the\n sheet read as rising rather than appearing. */\n.sheet-enter-active .sheet-panel,\n.sheet-leave-active .sheet-panel {\n transition: transform var(--duration-slow) var(--ease-sheet);\n}\n.sheet-enter-from .sheet-panel,\n.sheet-leave-to .sheet-panel {\n transform: translateY(6%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sheet-enter-from .sheet-panel,\n .sheet-leave-to .sheet-panel {\n transform: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport BaseButton from './BaseButton.vue'\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\nimport { X } from 'lucide-vue-next'\n\nimport { useVisualViewport } from '../composables/use-visual-viewport'\nimport { inertOutside } from '../utils/inert'\nimport { ensureSheetRoot } from '../utils/sheet-root'\n\nconst open = defineModel<boolean>({ required: true })\nconst {\n title,\n subtitle = '',\n closeLabel = 'Close',\n} = defineProps<{\n title: string\n subtitle?: string | undefined\n /**\n * Accessible name for the close button.\n *\n * A prop rather than a translation: a component that calls t() forces every\n * consumer onto one i18n setup, and this is the package's only visible string.\n */\n closeLabel?: string | undefined\n}>()\n\n/* Resolved in setup, before the first render, so the Teleport always has a\n target to find. */\nconst sheetRoot = ensureSheetRoot()\n\nconst viewport = useVisualViewport()\n\n/**\n * Pins the sheet to the area the keyboard has left visible.\n *\n * Only needed where the layout viewport does not shrink on its own — iOS. On\n * Android the numbers already agree, so this is a no-op there rather than a\n * second, competing adjustment.\n */\nconst viewportStyle = computed(() =>\n viewport.value\n ? { height: `${viewport.value.height}px`, top: `${viewport.value.offsetTop}px` }\n : undefined,\n)\n\nconst panel = ref<HTMLElement | null>(null)\nlet lastFocused: HTMLElement | null = null\n\nfunction close() {\n open.value = false\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') close()\n}\n\nwatch(open, async (isOpen) => {\n if (isOpen) {\n setBackgroundInert(true)\n lastFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null\n window.addEventListener('keydown', onKeydown)\n await nextTick()\n panel.value?.focus()\n } else {\n window.removeEventListener('keydown', onKeydown)\n // Released before focus is handed back: an inert element cannot take\n // focus, so the other order quietly dropped it on the body.\n setBackgroundInert(false)\n lastFocused?.focus()\n lastFocused = null\n }\n})\n\n/* The whole page but the sheet root goes inert while this is open. Held per\n sheet, so the unmount below can only release what this sheet took. */\nlet releaseInert: (() => void) | null = null\n\nfunction setBackgroundInert(isInert: boolean) {\n releaseInert?.()\n releaseInert = null\n\n const root = isInert ? document.querySelector(sheetRoot) : null\n if (root) releaseInert = inertOutside(root)\n}\n\nonUnmounted(() => {\n window.removeEventListener('keydown', onKeydown)\n // Unmounting while open would otherwise leave the whole app inert forever.\n setBackgroundInert(false)\n})\n</script>\n\n<template>\n <Teleport :to=\"sheetRoot\">\n <Transition name=\"sheet\">\n <div\n v-if=\"open\"\n class=\"fixed inset-x-0 top-0 bottom-0 z-50 flex items-center justify-center\"\n :style=\"viewportStyle\"\n >\n <div\n class=\"shell-frame md:rounded-shell relative flex max-h-full flex-col justify-end overflow-hidden\"\n >\n <div class=\"bg-ink/45 absolute inset-0 backdrop-blur-[2px]\" @click=\"close\" />\n\n <!-- Header and footer stay put; only the slot scrolls. Sized in dvh so\n the on-screen keyboard shrinks the sheet instead of pushing its\n content out of reach. -->\n <section\n ref=\"panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"title\"\n tabindex=\"-1\"\n class=\"sheet-panel surface-overlay relative flex max-h-[94%] min-h-[56dvh] flex-col rounded-t-[calc(var(--radius-shell)-2px)] outline-none\"\n >\n <div class=\"flex shrink-0 justify-center pt-3\" aria-hidden=\"true\">\n <span class=\"bg-hair h-1.5 w-10 rounded-full\" />\n </div>\n\n <header class=\"flex shrink-0 items-start gap-3 px-6 pt-4 pb-5\">\n <div class=\"min-w-0 flex-1\">\n <h2 class=\"text-ink text-xl leading-tight font-semibold\">{{ title }}</h2>\n <p v-if=\"subtitle\" class=\"text-ink-soft mt-1 text-sm leading-snug\">\n {{ subtitle }}\n </p>\n </div>\n\n <!-- `unstyled`, so the sheet keeps the exact button it had. What it\n gains is the focus ring it never had: this was a raw\n `<button>` with no `focus-visible` rule, so closing a sheet\n from the keyboard was invisible. -->\n <BaseButton\n variant=\"unstyled\"\n class=\"text-ink-soft hover:bg-muted hover:text-ink -mt-1 flex size-10 shrink-0 items-center justify-center rounded-full transition-colors active:scale-90\"\n :aria-label=\"closeLabel\"\n @click=\"close\"\n >\n <X class=\"size-5\" />\n </BaseButton>\n </header>\n\n <div\n class=\"min-h-0 flex-1 overflow-y-auto px-6 pb-[calc(2rem+env(safe-area-inset-bottom,0px))]\"\n >\n <slot />\n </div>\n </section>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style scoped>\n.sheet-enter-active,\n.sheet-leave-active {\n transition: opacity var(--duration-base) ease;\n}\n.sheet-enter-from,\n.sheet-leave-to {\n opacity: 0;\n}\n\n/* The panel travels further than the scrim fades, which is what makes the\n sheet read as rising rather than appearing. */\n.sheet-enter-active .sheet-panel,\n.sheet-leave-active .sheet-panel {\n transition: transform var(--duration-slow) var(--ease-sheet);\n}\n.sheet-enter-from .sheet-panel,\n.sheet-leave-to .sheet-panel {\n transform: translateY(6%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sheet-enter-from .sheet-panel,\n .sheet-leave-to .sheet-panel {\n transform: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\n/**\n * A single checkbox, with its label beside it.\n *\n * Deliberately not built on `FormField`. That component stacks a label above\n * its control, which is right for every field where the control is a box you\n * type into and wrong here: a checkbox is read as one sentence with a mark in\n * front of it, and putting the words above the box breaks the association a\n * sighted reader makes before they get to the accessible name.\n *\n * The whole row is the label, so the words are part of the hit target. On a\n * phone that is the difference between a control and a coin toss.\n */\nconst {\n label,\n error = '',\n hint = '',\n disabled = false,\n size = 'md',\n indeterminate = false,\n labelHidden = false,\n} = defineProps<{\n label: string\n error?: string | undefined\n hint?: string | undefined\n disabled?: boolean | undefined\n /**\n * `md` is a setting: a line the reader came here to change, in ink.\n * `sm` is an aside — \"remember me\" under a sign-in form, \"show the ones I\n * have learned\" above a list — quieter and tighter.\n *\n * The two are not a guess. Of the five hand-written checkboxes across the\n * three consuming apps, four were the aside and one was the setting, and\n * they differed in exactly these two ways.\n */\n size?: 'sm' | 'md' | undefined\n /**\n * Neither checked nor unchecked: some of what this box stands for is.\n *\n * It is a DOM property and not an attribute, so it cannot be written in\n * markup and there is no `indeterminate=\"\"` to set — which is why a\n * \"select all\" box on top of a list is the one checkbox every app ends up\n * writing by hand. The kit's own `DataTable` did, for exactly this\n * reason.\n *\n * It is presentation only: the value underneath is still `true` or\n * `false`, and pressing the box resolves it.\n */\n indeterminate?: boolean | undefined\n /**\n * Hides the words but keeps them as the accessible name. For a box in a\n * table header or a list row whose surroundings already say what it is —\n * dropping the label would leave the control named nothing at all.\n */\n labelHidden?: boolean | undefined\n}>()\n\nconst model = defineModel<boolean>({ default: false })\n\nconst id = useId()\nconst errorId = `${id}-error`\nconst hintId = `${id}-hint`\n\nconst describedBy = computed(() => {\n if (error) return errorId\n if (hint) return hintId\n return undefined\n})\n</script>\n\n<template>\n <div class=\"flex flex-col gap-1.5\">\n <label\n :for=\"id\"\n class=\"flex items-center\"\n :class=\"[\n labelHidden ? '' : size === 'sm' ? 'gap-2' : 'gap-3',\n disabled ? 'opacity-50' : 'cursor-pointer',\n ]\"\n >\n <input\n :id=\"id\"\n v-model=\"model\"\n type=\"checkbox\"\n :disabled=\"disabled\"\n :indeterminate=\"indeterminate\"\n :aria-invalid=\"Boolean(error)\"\n :aria-describedby=\"describedBy\"\n class=\"accent-primary focus-visible:outline-primary size-4 shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2\"\n />\n <span\n class=\"text-sm\"\n :class=\"[labelHidden ? 'sr-only' : '', size === 'sm' ? 'text-ink-soft' : 'text-ink']\"\n >\n {{ label }}\n </span>\n </label>\n\n <p v-if=\"error\" :id=\"errorId\" class=\"text-negative text-xs\">{{ error }}</p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"text-ink-soft text-xs\">{{ hint }}</p>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\n/**\n * A single checkbox, with its label beside it.\n *\n * Deliberately not built on `FormField`. That component stacks a label above\n * its control, which is right for every field where the control is a box you\n * type into and wrong here: a checkbox is read as one sentence with a mark in\n * front of it, and putting the words above the box breaks the association a\n * sighted reader makes before they get to the accessible name.\n *\n * The whole row is the label, so the words are part of the hit target. On a\n * phone that is the difference between a control and a coin toss.\n */\nconst {\n label,\n error = '',\n hint = '',\n disabled = false,\n size = 'md',\n indeterminate = false,\n labelHidden = false,\n} = defineProps<{\n label: string\n error?: string | undefined\n hint?: string | undefined\n disabled?: boolean | undefined\n /**\n * `md` is a setting: a line the reader came here to change, in ink.\n * `sm` is an aside — \"remember me\" under a sign-in form, \"show the ones I\n * have learned\" above a list — quieter and tighter.\n *\n * The two are not a guess. Of the five hand-written checkboxes across the\n * three consuming apps, four were the aside and one was the setting, and\n * they differed in exactly these two ways.\n */\n size?: 'sm' | 'md' | undefined\n /**\n * Neither checked nor unchecked: some of what this box stands for is.\n *\n * It is a DOM property and not an attribute, so it cannot be written in\n * markup and there is no `indeterminate=\"\"` to set — which is why a\n * \"select all\" box on top of a list is the one checkbox every app ends up\n * writing by hand. The kit's own `DataTable` did, for exactly this\n * reason.\n *\n * It is presentation only: the value underneath is still `true` or\n * `false`, and pressing the box resolves it.\n */\n indeterminate?: boolean | undefined\n /**\n * Hides the words but keeps them as the accessible name. For a box in a\n * table header or a list row whose surroundings already say what it is —\n * dropping the label would leave the control named nothing at all.\n */\n labelHidden?: boolean | undefined\n}>()\n\nconst model = defineModel<boolean>({ default: false })\n\nconst id = useId()\nconst errorId = `${id}-error`\nconst hintId = `${id}-hint`\n\nconst describedBy = computed(() => {\n if (error) return errorId\n if (hint) return hintId\n return undefined\n})\n</script>\n\n<template>\n <div class=\"flex flex-col gap-1.5\">\n <label\n :for=\"id\"\n class=\"flex items-center\"\n :class=\"[\n labelHidden ? '' : size === 'sm' ? 'gap-2' : 'gap-3',\n disabled ? 'opacity-50' : 'cursor-pointer',\n ]\"\n >\n <input\n :id=\"id\"\n v-model=\"model\"\n type=\"checkbox\"\n :disabled=\"disabled\"\n :indeterminate=\"indeterminate\"\n :aria-invalid=\"Boolean(error)\"\n :aria-describedby=\"describedBy\"\n class=\"accent-primary focus-visible:outline-primary size-4 shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2\"\n />\n <span\n class=\"text-sm\"\n :class=\"[labelHidden ? 'sr-only' : '', size === 'sm' ? 'text-ink-soft' : 'text-ink']\"\n >\n {{ label }}\n </span>\n </label>\n\n <p v-if=\"error\" :id=\"errorId\" class=\"text-negative text-xs\">{{ error }}</p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"text-ink-soft text-xs\">{{ hint }}</p>\n </div>\n</template>\n","/** Which way a language is written. */\nexport type TextDirection = 'ltr' | 'rtl'\n\n/*\n * The language subtags written right to left.\n *\n * A list rather than `Intl.Locale.prototype.getTextInfo()`, which is recent,\n * was a property before it was a method, and is missing or differently\n * spelled depending on the engine. Feature-detecting three spellings to\n * answer a question with twelve possible answers is more code and more ways\n * to be wrong than the list, and the list does not change: a language does\n * not switch direction.\n */\nconst RTL = new Set([\n 'ar', // Arabic\n 'arc', // Aramaic\n 'ckb', // Central Kurdish\n 'dv', // Divehi\n 'fa', // Persian\n 'he', // Hebrew\n 'ks', // Kashmiri\n 'ku', // Kurdish\n 'nqo', // N'Ko\n 'ps', // Pashto\n 'sd', // Sindhi\n 'syr', // Syriac\n 'ug', // Uyghur\n 'ur', // Urdu\n 'yi', // Yiddish\n])\n\n/**\n * Which way a locale tag is written.\n *\n * The kit is built on logical properties and `start`/`end` props all the\n * way through — `BaseDrawer`'s side, every inset, every margin — and none\n * of that does anything at all until `dir` is set on the document. So the\n * direction has to come from somewhere, and a locale tag is the only thing\n * that knows it.\n *\n * Only the language subtag is read, so `ar-EG` and `ar` answer the same.\n *\n * @example\n * ```ts\n * textDirection('he-IL') // 'rtl'\n * textDirection('tr') // 'ltr'\n * ```\n */\nexport function textDirection(locale: string): TextDirection {\n // `und-Arab` and the like are a real spelling, so the script subtag is\n // worth the two lines it costs.\n const parts = locale.toLowerCase().split(/[-_]/)\n const script = parts[1]\n\n if (script === 'arab' || script === 'hebr' || script === 'thaa' || script === 'syrc') return 'rtl'\n\n return RTL.has(parts[0] ?? '') ? 'rtl' : 'ltr'\n}\n\n/**\n * Which way the text around an element runs.\n *\n * The companion to `textDirection`, and the other end of it: that one asks\n * a locale, this one asks the page. An element inside a quoted passage may\n * run the other way from the document it is in, so the nearest `dir`\n * attribute wins and the document is the fallback.\n *\n * The attribute rather than `getComputedStyle().direction`: the computed\n * value is the real answer, but it is inherited through the cascade, and\n * jsdom does not inherit it — so the tests that exist to prove the\n * right-to-left behaviour would all pass with the behaviour removed. What\n * actually sets the direction in this kit is the attribute, in one place,\n * on `<html>`, written by `createI18nRuntime`.\n */\nexport function elementDirection(from: Element | null | undefined): TextDirection {\n const declared = from?.closest('[dir]')?.getAttribute('dir') ?? document.dir\n\n return declared === 'rtl' ? 'rtl' : 'ltr'\n}\n\n/**\n * `ArrowLeft` and `ArrowRight` as a direction of travel: `-1` back through\n * a list, `1` on through it, `0` for any other key.\n *\n * They are mirrored where the writing is, which is not a nicety — it is the\n * whole of what the arrows mean. A row of tabs in Arabic runs right to\n * left, so the tab to the *left* of the current one is the next one, and a\n * handler that reads `ArrowRight` as \"forward\" walks backwards through\n * every roving-tabindex control in the kit at once. WAI-ARIA says so\n * explicitly, and it is invisible to every check: the code type-checks, the\n * styles are all logical, and the keys are the same keys.\n *\n * @example\n * ```ts\n * function onKeydown(event: KeyboardEvent) {\n * const step = horizontalStep(event.key, event.currentTarget as Element)\n * if (step === 0) return\n * event.preventDefault()\n * moveTo(index + step)\n * }\n * ```\n */\nexport function horizontalStep(key: string, from: Element | null | undefined): -1 | 0 | 1 {\n if (key !== 'ArrowLeft' && key !== 'ArrowRight') return 0\n\n const forward = elementDirection(from) === 'rtl' ? 'ArrowLeft' : 'ArrowRight'\n\n return key === forward ? 1 : -1\n}\n","/**\n * Moves focus to an element that was never meant to take it.\n *\n * A `<main>`, a heading, the top of a view: none of them are focusable, and\n * both places the kit needs this — `SkipLink` and `createRouteAnnouncer` —\n * fail in the same invisible way without it. A skip link scrolls the page\n * and leaves focus in the header; a route change swaps the content and\n * leaves focus on the link that is no longer there.\n *\n * `tabindex=\"-1\"` makes the element focusable by script and never by Tab,\n * which is the whole trick. It is left behind rather than cleaned up: a\n * reader that moves out and comes back would otherwise find an unfocusable\n * target the second time.\n *\n * @param target - The element, or the `id` of one. A missing one is a no-op.\n * @returns Whether focus moved, so a caller can fall back.\n */\nexport function focusTarget(target: Element | string | null): boolean {\n const element = typeof target === 'string' ? document.getElementById(target) : target\n if (!(element instanceof HTMLElement)) return false\n\n if (!element.hasAttribute('tabindex')) element.setAttribute('tabindex', '-1')\n\n // `focus()` scrolls it into view on its own, which is why there is no\n // `scrollIntoView` beside it.\n element.focus()\n\n return true\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"focus-target-DENXwVQ3.js","names":[],"sources":["../src/utils/sheet-root.ts","../src/composables/use-visual-viewport.ts","../src/composables/use-bound-value.ts","../src/utils/inert.ts","../src/components/BaseSheet.vue","../src/components/BaseSheet.vue","../src/components/BaseCheckbox.vue","../src/components/BaseCheckbox.vue","../src/utils/direction.ts","../src/utils/focus-target.ts"],"sourcesContent":["/** The node every sheet teleports into. */\nexport const SHEET_ROOT_ID = 'sheet-root'\n\n/**\n * Returns the sheet root, creating it if the page has none.\n *\n * `BaseSheet` teleports out of the app's tree so the app behind it can be\n * marked `inert` — a sheet inside the element it is disabling would disable\n * itself. That means it needs a mount point outside `#app`.\n *\n * It used to need the consuming app to put `<div id=\"sheet-root\">` in its\n * `index.html`, and nothing said so. A page without it got a sheet that opened,\n * blocked the page, and rendered nothing: Vue warns to the console about a\n * missing teleport target and carries on, so the build is green, the types are\n * fine, and the screen is wrong. The kit's own showcase had exactly that bug,\n * which is how it was found.\n *\n * So the node is made on demand. An app that already declares one keeps it —\n * this only fills a gap, it never replaces.\n */\nexport function ensureSheetRoot(): string {\n const selector = `#${SHEET_ROOT_ID}`\n\n // Server-rendered: there is no document, and Teleport is skipped anyway.\n if (typeof document === 'undefined') return selector\n if (document.getElementById(SHEET_ROOT_ID)) return selector\n\n const root = document.createElement('div')\n root.id = SHEET_ROOT_ID\n document.body.appendChild(root)\n\n return selector\n}\n","import { onScopeDispose, readonly, ref } from 'vue'\n\n/** The visible area, once the on-screen keyboard has taken its share. */\nexport interface VisualViewportRect {\n height: number\n offsetTop: number\n}\n\n/**\n * Tracks the visual viewport.\n *\n * Chrome and Android browsers honour `interactive-widget=resizes-content`, so\n * the layout viewport already shrinks for the keyboard there. Safari on iOS\n * does not implement it: it shrinks only the *visual* viewport, leaving a sheet\n * sized in `dvh` sitting partly underneath the keyboard.\n *\n * `null` means the API is unavailable, which callers should read as \"trust the\n * layout viewport\" rather than as zero. A server has no viewport at all, so it\n * gets that same `null` — this runs during `setup`, and a component using it\n * has to survive being rendered there.\n *\n * @example\n * ```ts\n * const viewport = useVisualViewport()\n * // :style=\"viewport ? { height: `${viewport.height}px` } : undefined\"\n * ```\n */\nexport function useVisualViewport() {\n const rect = ref<VisualViewportRect | null>(null)\n\n const viewport = typeof window === 'undefined' ? undefined : window.visualViewport\n if (!viewport) return readonly(rect)\n\n function read() {\n if (!viewport) return\n\n rect.value = { height: viewport.height, offsetTop: viewport.offsetTop }\n }\n\n read()\n\n // `scroll` matters as much as `resize`: iOS shifts the visual viewport up to\n // keep the focused field visible, without changing its height.\n viewport.addEventListener('resize', read)\n viewport.addEventListener('scroll', read)\n\n onScopeDispose(() => {\n viewport.removeEventListener('resize', read)\n viewport.removeEventListener('scroll', read)\n })\n\n return readonly(rect)\n}\n","import { computed, ref, watch } from 'vue'\nimport type { Ref, WritableComputedRef } from 'vue'\n\n/**\n * A `v-model` that accepts `undefined` and emits only what a field produces.\n *\n * `defineModel` could not be typed both ways. Without a default it declares\n * that it may emit `undefined`, so under vue-tsc's strictTemplates a\n * `ref('')` cannot be bound to it. With a default it drops `undefined` from\n * what it accepts, so under `exactOptionalPropertyTypes` a form library's\n * `string | undefined` cannot be. Hibi binds exactly that, and 2.4.0 as first\n * written broke it — the consumer check caught it before the tag.\n *\n * So the prop and the event are declared by hand — the prop as\n * `T | undefined`, the event as `T` — and this is the rest of what\n * `defineModel` did: follow the prop, and keep a value of its own when\n * nothing is bound. Vue applies `.trim` and `.number` to the event itself, so\n * modifiers behave as they did.\n *\n * Internal: not exported from the package.\n */\nexport function useBoundValue<T>(\n read: () => T | undefined,\n write: (value: T) => void,\n): WritableComputedRef<T | undefined> {\n const local = ref(read()) as Ref<T | undefined>\n\n watch(read, (next) => {\n local.value = next\n })\n\n return computed({\n get: () => local.value,\n set: (next) => {\n local.value = next\n write(next as T)\n },\n })\n}\n","/**\n * How many open layers hold each element inert. An element set inert by the\n * app itself is never in here, so releasing never undoes the app's own choice.\n */\nconst holds = new Map<Element, number>()\n\n/**\n * Takes everything on the page except the branch holding `keep` out of tab\n * order and pointer events, and returns the function that gives it back.\n *\n * `inert` is a real focus trap without keydown bookkeeping, and the only one\n * that also stops a screen reader's virtual cursor. It used to be applied to\n * `document.getElementById('app')` — the id Vite's template happens to use —\n * so an app mounted on `#root` got a dialog whose background was still\n * reachable with Tab, and nothing said so. Working from `<body>`'s children\n * instead holds whatever the app is mounted on, and anything else teleported\n * there too.\n *\n * Counted per element, so a sheet opened over a sheet, or a tour over either,\n * does not hand the page back when the inner one closes.\n */\nexport function inertOutside(keep: Element): () => void {\n if (typeof document === 'undefined') return () => {}\n\n const taken: Element[] = []\n\n for (const element of Array.from(document.body.children)) {\n if (element.contains(keep)) continue\n\n const count = holds.get(element)\n if (count === undefined) {\n if (element.hasAttribute('inert')) continue\n element.setAttribute('inert', '')\n }\n holds.set(element, (count ?? 0) + 1)\n taken.push(element)\n }\n\n let released = false\n\n return () => {\n if (released) return\n released = true\n\n for (const element of taken) {\n const count = (holds.get(element) ?? 1) - 1\n if (count > 0) {\n holds.set(element, count)\n continue\n }\n holds.delete(element)\n element.removeAttribute('inert')\n }\n }\n}\n","<script setup lang=\"ts\">\nimport BaseButton from './BaseButton.vue'\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\nimport { X } from 'lucide-vue-next'\n\nimport { useVisualViewport } from '../composables/use-visual-viewport'\nimport { inertOutside } from '../utils/inert'\nimport { ensureSheetRoot } from '../utils/sheet-root'\n\nconst open = defineModel<boolean>({ required: true })\nconst {\n title,\n subtitle = '',\n closeLabel = 'Close',\n} = defineProps<{\n title: string\n subtitle?: string | undefined\n /**\n * Accessible name for the close button.\n *\n * A prop rather than a translation: a component that calls t() forces every\n * consumer onto one i18n setup, and this is the package's only visible string.\n */\n closeLabel?: string | undefined\n}>()\n\n/* Resolved in setup, before the first render, so the Teleport always has a\n target to find. */\nconst sheetRoot = ensureSheetRoot()\n\nconst viewport = useVisualViewport()\n\n/**\n * Pins the sheet to the area the keyboard has left visible.\n *\n * Only needed where the layout viewport does not shrink on its own — iOS. On\n * Android the numbers already agree, so this is a no-op there rather than a\n * second, competing adjustment.\n */\nconst viewportStyle = computed(() =>\n viewport.value\n ? { height: `${viewport.value.height}px`, top: `${viewport.value.offsetTop}px` }\n : undefined,\n)\n\nconst panel = ref<HTMLElement | null>(null)\nlet lastFocused: HTMLElement | null = null\n\nfunction close() {\n open.value = false\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') close()\n}\n\nwatch(open, async (isOpen) => {\n if (isOpen) {\n setBackgroundInert(true)\n lastFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null\n window.addEventListener('keydown', onKeydown)\n await nextTick()\n panel.value?.focus()\n } else {\n window.removeEventListener('keydown', onKeydown)\n // Released before focus is handed back: an inert element cannot take\n // focus, so the other order quietly dropped it on the body.\n setBackgroundInert(false)\n lastFocused?.focus()\n lastFocused = null\n }\n})\n\n/* The whole page but the sheet root goes inert while this is open. Held per\n sheet, so the unmount below can only release what this sheet took. */\nlet releaseInert: (() => void) | null = null\n\nfunction setBackgroundInert(isInert: boolean) {\n releaseInert?.()\n releaseInert = null\n\n const root = isInert ? document.querySelector(sheetRoot) : null\n if (root) releaseInert = inertOutside(root)\n}\n\nonUnmounted(() => {\n window.removeEventListener('keydown', onKeydown)\n // Unmounting while open would otherwise leave the whole app inert forever.\n setBackgroundInert(false)\n})\n</script>\n\n<template>\n <Teleport :to=\"sheetRoot\">\n <Transition name=\"sheet\">\n <div\n v-if=\"open\"\n class=\"fixed inset-x-0 top-0 bottom-0 z-50 flex items-center justify-center\"\n :style=\"viewportStyle\"\n >\n <div\n class=\"shell-frame md:rounded-shell relative flex max-h-full flex-col justify-end overflow-hidden\"\n >\n <div class=\"bg-ink/45 absolute inset-0 backdrop-blur-[2px]\" @click=\"close\" />\n\n <!-- Header and footer stay put; only the slot scrolls. Sized in dvh so\n the on-screen keyboard shrinks the sheet instead of pushing its\n content out of reach. -->\n <section\n ref=\"panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"title\"\n tabindex=\"-1\"\n class=\"sheet-panel surface-overlay relative flex max-h-[94%] min-h-[56dvh] flex-col rounded-t-[calc(var(--radius-shell)-2px)] outline-none\"\n >\n <div class=\"flex shrink-0 justify-center pt-3\" aria-hidden=\"true\">\n <span class=\"bg-hair h-1.5 w-10 rounded-full\" />\n </div>\n\n <header class=\"flex shrink-0 items-start gap-3 px-6 pt-4 pb-5\">\n <div class=\"min-w-0 flex-1\">\n <h2 class=\"text-ink text-xl leading-tight font-semibold\">{{ title }}</h2>\n <p v-if=\"subtitle\" class=\"text-ink-soft mt-1 text-sm leading-snug\">\n {{ subtitle }}\n </p>\n </div>\n\n <!-- `unstyled`, so the sheet keeps the exact button it had. What it\n gains is the focus ring it never had: this was a raw\n `<button>` with no `focus-visible` rule, so closing a sheet\n from the keyboard was invisible. -->\n <BaseButton\n variant=\"unstyled\"\n class=\"text-ink-soft hover:bg-muted hover:text-ink -mt-1 flex size-10 shrink-0 items-center justify-center rounded-full transition-colors active:scale-90\"\n :aria-label=\"closeLabel\"\n @click=\"close\"\n >\n <X class=\"size-5\" />\n </BaseButton>\n </header>\n\n <div\n class=\"min-h-0 flex-1 overflow-y-auto px-6 pb-[calc(2rem+env(safe-area-inset-bottom,0px))]\"\n >\n <slot />\n </div>\n </section>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style scoped>\n.sheet-enter-active,\n.sheet-leave-active {\n transition: opacity var(--duration-base) ease;\n}\n.sheet-enter-from,\n.sheet-leave-to {\n opacity: 0;\n}\n\n/* The panel travels further than the scrim fades, which is what makes the\n sheet read as rising rather than appearing. */\n.sheet-enter-active .sheet-panel,\n.sheet-leave-active .sheet-panel {\n transition: transform var(--duration-slow) var(--ease-sheet);\n}\n.sheet-enter-from .sheet-panel,\n.sheet-leave-to .sheet-panel {\n transform: translateY(6%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sheet-enter-from .sheet-panel,\n .sheet-leave-to .sheet-panel {\n transform: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport BaseButton from './BaseButton.vue'\nimport { computed, nextTick, onUnmounted, ref, watch } from 'vue'\nimport { X } from 'lucide-vue-next'\n\nimport { useVisualViewport } from '../composables/use-visual-viewport'\nimport { inertOutside } from '../utils/inert'\nimport { ensureSheetRoot } from '../utils/sheet-root'\n\nconst open = defineModel<boolean>({ required: true })\nconst {\n title,\n subtitle = '',\n closeLabel = 'Close',\n} = defineProps<{\n title: string\n subtitle?: string | undefined\n /**\n * Accessible name for the close button.\n *\n * A prop rather than a translation: a component that calls t() forces every\n * consumer onto one i18n setup, and this is the package's only visible string.\n */\n closeLabel?: string | undefined\n}>()\n\n/* Resolved in setup, before the first render, so the Teleport always has a\n target to find. */\nconst sheetRoot = ensureSheetRoot()\n\nconst viewport = useVisualViewport()\n\n/**\n * Pins the sheet to the area the keyboard has left visible.\n *\n * Only needed where the layout viewport does not shrink on its own — iOS. On\n * Android the numbers already agree, so this is a no-op there rather than a\n * second, competing adjustment.\n */\nconst viewportStyle = computed(() =>\n viewport.value\n ? { height: `${viewport.value.height}px`, top: `${viewport.value.offsetTop}px` }\n : undefined,\n)\n\nconst panel = ref<HTMLElement | null>(null)\nlet lastFocused: HTMLElement | null = null\n\nfunction close() {\n open.value = false\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') close()\n}\n\nwatch(open, async (isOpen) => {\n if (isOpen) {\n setBackgroundInert(true)\n lastFocused = document.activeElement instanceof HTMLElement ? document.activeElement : null\n window.addEventListener('keydown', onKeydown)\n await nextTick()\n panel.value?.focus()\n } else {\n window.removeEventListener('keydown', onKeydown)\n // Released before focus is handed back: an inert element cannot take\n // focus, so the other order quietly dropped it on the body.\n setBackgroundInert(false)\n lastFocused?.focus()\n lastFocused = null\n }\n})\n\n/* The whole page but the sheet root goes inert while this is open. Held per\n sheet, so the unmount below can only release what this sheet took. */\nlet releaseInert: (() => void) | null = null\n\nfunction setBackgroundInert(isInert: boolean) {\n releaseInert?.()\n releaseInert = null\n\n const root = isInert ? document.querySelector(sheetRoot) : null\n if (root) releaseInert = inertOutside(root)\n}\n\nonUnmounted(() => {\n window.removeEventListener('keydown', onKeydown)\n // Unmounting while open would otherwise leave the whole app inert forever.\n setBackgroundInert(false)\n})\n</script>\n\n<template>\n <Teleport :to=\"sheetRoot\">\n <Transition name=\"sheet\">\n <div\n v-if=\"open\"\n class=\"fixed inset-x-0 top-0 bottom-0 z-50 flex items-center justify-center\"\n :style=\"viewportStyle\"\n >\n <div\n class=\"shell-frame md:rounded-shell relative flex max-h-full flex-col justify-end overflow-hidden\"\n >\n <div class=\"bg-ink/45 absolute inset-0 backdrop-blur-[2px]\" @click=\"close\" />\n\n <!-- Header and footer stay put; only the slot scrolls. Sized in dvh so\n the on-screen keyboard shrinks the sheet instead of pushing its\n content out of reach. -->\n <section\n ref=\"panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n :aria-label=\"title\"\n tabindex=\"-1\"\n class=\"sheet-panel surface-overlay relative flex max-h-[94%] min-h-[56dvh] flex-col rounded-t-[calc(var(--radius-shell)-2px)] outline-none\"\n >\n <div class=\"flex shrink-0 justify-center pt-3\" aria-hidden=\"true\">\n <span class=\"bg-hair h-1.5 w-10 rounded-full\" />\n </div>\n\n <header class=\"flex shrink-0 items-start gap-3 px-6 pt-4 pb-5\">\n <div class=\"min-w-0 flex-1\">\n <h2 class=\"text-ink text-xl leading-tight font-semibold\">{{ title }}</h2>\n <p v-if=\"subtitle\" class=\"text-ink-soft mt-1 text-sm leading-snug\">\n {{ subtitle }}\n </p>\n </div>\n\n <!-- `unstyled`, so the sheet keeps the exact button it had. What it\n gains is the focus ring it never had: this was a raw\n `<button>` with no `focus-visible` rule, so closing a sheet\n from the keyboard was invisible. -->\n <BaseButton\n variant=\"unstyled\"\n class=\"text-ink-soft hover:bg-muted hover:text-ink -mt-1 flex size-10 shrink-0 items-center justify-center rounded-full transition-colors active:scale-90\"\n :aria-label=\"closeLabel\"\n @click=\"close\"\n >\n <X class=\"size-5\" />\n </BaseButton>\n </header>\n\n <div\n class=\"min-h-0 flex-1 overflow-y-auto px-6 pb-[calc(2rem+env(safe-area-inset-bottom,0px))]\"\n >\n <slot />\n </div>\n </section>\n </div>\n </div>\n </Transition>\n </Teleport>\n</template>\n\n<style scoped>\n.sheet-enter-active,\n.sheet-leave-active {\n transition: opacity var(--duration-base) ease;\n}\n.sheet-enter-from,\n.sheet-leave-to {\n opacity: 0;\n}\n\n/* The panel travels further than the scrim fades, which is what makes the\n sheet read as rising rather than appearing. */\n.sheet-enter-active .sheet-panel,\n.sheet-leave-active .sheet-panel {\n transition: transform var(--duration-slow) var(--ease-sheet);\n}\n.sheet-enter-from .sheet-panel,\n.sheet-leave-to .sheet-panel {\n transform: translateY(6%);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sheet-enter-from .sheet-panel,\n .sheet-leave-to .sheet-panel {\n transform: none;\n }\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\n/**\n * A single checkbox, with its label beside it.\n *\n * Deliberately not built on `FormField`. That component stacks a label above\n * its control, which is right for every field where the control is a box you\n * type into and wrong here: a checkbox is read as one sentence with a mark in\n * front of it, and putting the words above the box breaks the association a\n * sighted reader makes before they get to the accessible name.\n *\n * The whole row is the label, so the words are part of the hit target. On a\n * phone that is the difference between a control and a coin toss.\n */\nconst {\n label,\n error = '',\n hint = '',\n disabled = false,\n size = 'md',\n indeterminate = false,\n labelHidden = false,\n} = defineProps<{\n label: string\n error?: string | undefined\n hint?: string | undefined\n disabled?: boolean | undefined\n /**\n * `md` is a setting: a line the reader came here to change, in ink.\n * `sm` is an aside — \"remember me\" under a sign-in form, \"show the ones I\n * have learned\" above a list — quieter and tighter.\n *\n * The two are not a guess. Of the five hand-written checkboxes across the\n * three consuming apps, four were the aside and one was the setting, and\n * they differed in exactly these two ways.\n */\n size?: 'sm' | 'md' | undefined\n /**\n * Neither checked nor unchecked: some of what this box stands for is.\n *\n * It is a DOM property and not an attribute, so it cannot be written in\n * markup and there is no `indeterminate=\"\"` to set — which is why a\n * \"select all\" box on top of a list is the one checkbox every app ends up\n * writing by hand. The kit's own `DataTable` did, for exactly this\n * reason.\n *\n * It is presentation only: the value underneath is still `true` or\n * `false`, and pressing the box resolves it.\n */\n indeterminate?: boolean | undefined\n /**\n * Hides the words but keeps them as the accessible name. For a box in a\n * table header or a list row whose surroundings already say what it is —\n * dropping the label would leave the control named nothing at all.\n */\n labelHidden?: boolean | undefined\n}>()\n\nconst model = defineModel<boolean>({ default: false })\n\nconst id = useId()\nconst errorId = `${id}-error`\nconst hintId = `${id}-hint`\n\nconst describedBy = computed(() => {\n if (error) return errorId\n if (hint) return hintId\n return undefined\n})\n</script>\n\n<template>\n <div class=\"flex flex-col gap-1.5\">\n <label\n :for=\"id\"\n class=\"flex items-center\"\n :class=\"[\n labelHidden ? '' : size === 'sm' ? 'gap-2' : 'gap-3',\n disabled ? 'opacity-50' : 'cursor-pointer',\n ]\"\n >\n <input\n :id=\"id\"\n v-model=\"model\"\n type=\"checkbox\"\n :disabled=\"disabled\"\n :indeterminate=\"indeterminate\"\n :aria-invalid=\"Boolean(error)\"\n :aria-describedby=\"describedBy\"\n class=\"accent-primary focus-visible:outline-primary size-4 shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2\"\n />\n <span\n class=\"text-sm\"\n :class=\"[labelHidden ? 'sr-only' : '', size === 'sm' ? 'text-ink-soft' : 'text-ink']\"\n >\n {{ label }}\n </span>\n </label>\n\n <p v-if=\"error\" :id=\"errorId\" class=\"text-negative text-xs\">{{ error }}</p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"text-ink-soft text-xs\">{{ hint }}</p>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\n/**\n * A single checkbox, with its label beside it.\n *\n * Deliberately not built on `FormField`. That component stacks a label above\n * its control, which is right for every field where the control is a box you\n * type into and wrong here: a checkbox is read as one sentence with a mark in\n * front of it, and putting the words above the box breaks the association a\n * sighted reader makes before they get to the accessible name.\n *\n * The whole row is the label, so the words are part of the hit target. On a\n * phone that is the difference between a control and a coin toss.\n */\nconst {\n label,\n error = '',\n hint = '',\n disabled = false,\n size = 'md',\n indeterminate = false,\n labelHidden = false,\n} = defineProps<{\n label: string\n error?: string | undefined\n hint?: string | undefined\n disabled?: boolean | undefined\n /**\n * `md` is a setting: a line the reader came here to change, in ink.\n * `sm` is an aside — \"remember me\" under a sign-in form, \"show the ones I\n * have learned\" above a list — quieter and tighter.\n *\n * The two are not a guess. Of the five hand-written checkboxes across the\n * three consuming apps, four were the aside and one was the setting, and\n * they differed in exactly these two ways.\n */\n size?: 'sm' | 'md' | undefined\n /**\n * Neither checked nor unchecked: some of what this box stands for is.\n *\n * It is a DOM property and not an attribute, so it cannot be written in\n * markup and there is no `indeterminate=\"\"` to set — which is why a\n * \"select all\" box on top of a list is the one checkbox every app ends up\n * writing by hand. The kit's own `DataTable` did, for exactly this\n * reason.\n *\n * It is presentation only: the value underneath is still `true` or\n * `false`, and pressing the box resolves it.\n */\n indeterminate?: boolean | undefined\n /**\n * Hides the words but keeps them as the accessible name. For a box in a\n * table header or a list row whose surroundings already say what it is —\n * dropping the label would leave the control named nothing at all.\n */\n labelHidden?: boolean | undefined\n}>()\n\nconst model = defineModel<boolean>({ default: false })\n\nconst id = useId()\nconst errorId = `${id}-error`\nconst hintId = `${id}-hint`\n\nconst describedBy = computed(() => {\n if (error) return errorId\n if (hint) return hintId\n return undefined\n})\n</script>\n\n<template>\n <div class=\"flex flex-col gap-1.5\">\n <label\n :for=\"id\"\n class=\"flex items-center\"\n :class=\"[\n labelHidden ? '' : size === 'sm' ? 'gap-2' : 'gap-3',\n disabled ? 'opacity-50' : 'cursor-pointer',\n ]\"\n >\n <input\n :id=\"id\"\n v-model=\"model\"\n type=\"checkbox\"\n :disabled=\"disabled\"\n :indeterminate=\"indeterminate\"\n :aria-invalid=\"Boolean(error)\"\n :aria-describedby=\"describedBy\"\n class=\"accent-primary focus-visible:outline-primary size-4 shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2\"\n />\n <span\n class=\"text-sm\"\n :class=\"[labelHidden ? 'sr-only' : '', size === 'sm' ? 'text-ink-soft' : 'text-ink']\"\n >\n {{ label }}\n </span>\n </label>\n\n <p v-if=\"error\" :id=\"errorId\" class=\"text-negative text-xs\">{{ error }}</p>\n <p v-else-if=\"hint\" :id=\"hintId\" class=\"text-ink-soft text-xs\">{{ hint }}</p>\n </div>\n</template>\n","/** Which way a language is written. */\nexport type TextDirection = 'ltr' | 'rtl'\n\n/*\n * The language subtags written right to left.\n *\n * A list rather than `Intl.Locale.prototype.getTextInfo()`, which is recent,\n * was a property before it was a method, and is missing or differently\n * spelled depending on the engine. Feature-detecting three spellings to\n * answer a question with twelve possible answers is more code and more ways\n * to be wrong than the list, and the list does not change: a language does\n * not switch direction.\n */\nconst RTL = new Set([\n 'ar', // Arabic\n 'arc', // Aramaic\n 'ckb', // Central Kurdish\n 'dv', // Divehi\n 'fa', // Persian\n 'he', // Hebrew\n 'ks', // Kashmiri\n 'ku', // Kurdish\n 'nqo', // N'Ko\n 'ps', // Pashto\n 'sd', // Sindhi\n 'syr', // Syriac\n 'ug', // Uyghur\n 'ur', // Urdu\n 'yi', // Yiddish\n])\n\n/**\n * Which way a locale tag is written.\n *\n * The kit is built on logical properties and `start`/`end` props all the\n * way through — `BaseDrawer`'s side, every inset, every margin — and none\n * of that does anything at all until `dir` is set on the document. So the\n * direction has to come from somewhere, and a locale tag is the only thing\n * that knows it.\n *\n * Only the language subtag is read, so `ar-EG` and `ar` answer the same.\n *\n * @example\n * ```ts\n * textDirection('he-IL') // 'rtl'\n * textDirection('tr') // 'ltr'\n * ```\n */\nexport function textDirection(locale: string): TextDirection {\n // `und-Arab` and the like are a real spelling, so the script subtag is\n // worth the two lines it costs.\n const parts = locale.toLowerCase().split(/[-_]/)\n const script = parts[1]\n\n if (script === 'arab' || script === 'hebr' || script === 'thaa' || script === 'syrc') return 'rtl'\n\n return RTL.has(parts[0] ?? '') ? 'rtl' : 'ltr'\n}\n\n/**\n * Which way the text around an element runs.\n *\n * The companion to `textDirection`, and the other end of it: that one asks\n * a locale, this one asks the page. An element inside a quoted passage may\n * run the other way from the document it is in, so the nearest `dir`\n * attribute wins and the document is the fallback.\n *\n * The attribute rather than `getComputedStyle().direction`: the computed\n * value is the real answer, but it is inherited through the cascade, and\n * jsdom does not inherit it — so the tests that exist to prove the\n * right-to-left behaviour would all pass with the behaviour removed. What\n * actually sets the direction in this kit is the attribute, in one place,\n * on `<html>`, written by `createI18nRuntime`.\n */\nexport function elementDirection(from: Element | null | undefined): TextDirection {\n const declared = from?.closest('[dir]')?.getAttribute('dir') ?? document.dir\n\n return declared === 'rtl' ? 'rtl' : 'ltr'\n}\n\n/**\n * `ArrowLeft` and `ArrowRight` as a direction of travel: `-1` back through\n * a list, `1` on through it, `0` for any other key.\n *\n * They are mirrored where the writing is, which is not a nicety — it is the\n * whole of what the arrows mean. A row of tabs in Arabic runs right to\n * left, so the tab to the *left* of the current one is the next one, and a\n * handler that reads `ArrowRight` as \"forward\" walks backwards through\n * every roving-tabindex control in the kit at once. WAI-ARIA says so\n * explicitly, and it is invisible to every check: the code type-checks, the\n * styles are all logical, and the keys are the same keys.\n *\n * @example\n * ```ts\n * function onKeydown(event: KeyboardEvent) {\n * const step = horizontalStep(event.key, event.currentTarget as Element)\n * if (step === 0) return\n * event.preventDefault()\n * moveTo(index + step)\n * }\n * ```\n */\nexport function horizontalStep(key: string, from: Element | null | undefined): -1 | 0 | 1 {\n if (key !== 'ArrowLeft' && key !== 'ArrowRight') return 0\n\n const forward = elementDirection(from) === 'rtl' ? 'ArrowLeft' : 'ArrowRight'\n\n return key === forward ? 1 : -1\n}\n","/**\n * Moves focus to an element that was never meant to take it.\n *\n * A `<main>`, a heading, the top of a view: none of them are focusable, and\n * both places the kit needs this — `SkipLink` and `createRouteAnnouncer` —\n * fail in the same invisible way without it. A skip link scrolls the page\n * and leaves focus in the header; a route change swaps the content and\n * leaves focus on the link that is no longer there.\n *\n * `tabindex=\"-1\"` makes the element focusable by script and never by Tab,\n * which is the whole trick. It is left behind rather than cleaned up: a\n * reader that moves out and comes back would otherwise find an unfocusable\n * target the second time.\n *\n * @param target - The element, or the `id` of one. A missing one is a no-op.\n * @returns Whether focus moved, so a caller can fall back.\n */\nexport function focusTarget(target: Element | string | null): boolean {\n const element = typeof target === 'string' ? document.getElementById(target) : target\n if (!(element instanceof HTMLElement)) return false\n\n if (!element.hasAttribute('tabindex')) element.setAttribute('tabindex', '-1')\n\n // `focus()` scrolls it into view on its own, which is why there is no\n // `scrollIntoView` beside it.\n element.focus()\n\n return true\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { a as addDays, c as lastNDays, d as toDateKey, f as todayKey, i as needsIosInstall, l as leadingBlanks, n as isApplePortable, o as eachDayOfYear, r as isInstalled, s as fromDateKey, t as SettingsGroup_default, u as startOfWeek } from "./SettingsGroup-BXkajKGl.js";
2
2
  import { a as formatNumber, i as formatDate, o as setFormatLocale, r as watchVisibility } from "./environment-Cz5KiC1t.js";
3
- import { a as BaseCheckbox_default, c as useBoundValue, d as ensureSheetRoot, i as textDirection, l as useVisualViewport, n as elementDirection, o as BaseSheet_default, r as horizontalStep, t as focusTarget, u as SHEET_ROOT_ID } from "./focus-target-BtcoJdmz.js";
4
- import { _ as toRedirectPath, a as useFieldAttrs, c as useToast, d as isThemePreference, f as readStoredTheme, g as safeRedirect, h as tapFeedback, i as BaseInput_default, l as useOnline, m as useTheme, n as useAnnounce, o as FormField_default, p as setThemeStorageKey, r as GoogleButton_default, s as BaseAlert_default, t as announce, u as applyTheme } from "./use-announce-B8Ln-LsE.js";
3
+ import { a as BaseCheckbox_default, c as useBoundValue, d as ensureSheetRoot, i as textDirection, l as useVisualViewport, n as elementDirection, o as BaseSheet_default, r as horizontalStep, t as focusTarget, u as SHEET_ROOT_ID } from "./focus-target-DENXwVQ3.js";
4
+ import { _ as toRedirectPath, a as useFieldAttrs, c as useToast, d as isThemePreference, f as readStoredTheme, g as safeRedirect, h as tapFeedback, i as BaseInput_default, l as useOnline, m as useTheme, n as useAnnounce, o as FormField_default, p as setThemeStorageKey, r as GoogleButton_default, s as BaseAlert_default, t as announce, u as applyTheme } from "./use-announce-COM3S_ER.js";
5
5
  import { n as registerErrorMapper, r as toAppError, t as AppError } from "./app-error-DF9cijE0.js";
6
- import { a as BaseSkeleton_default, c as useMediaQuery, l as useDragScroll, n as ScrollArea_default, o as useMenuKeys, s as useAnchoredPanel, t as BaseListbox_default } from "./BaseListbox-D-NQ6nDd.js";
6
+ import { a as BaseSkeleton_default, c as useMediaQuery, l as useDragScroll, n as ScrollArea_default, o as useMenuKeys, s as useAnchoredPanel, t as BaseListbox_default } from "./BaseListbox-tS7ZpcoF.js";
7
7
  import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BOaGB7Aw.js";
8
- import { t as BaseButton_default } from "./BaseButton-BD2HKB1L.js";
8
+ import { t as BaseButton_default } from "./BaseButton-C3VGgIuR.js";
9
9
  import { t as SettingsRow_default } from "./SettingsRow-C6Spi7Np.js";
10
10
  import { Fragment, Teleport, Transition, TransitionGroup, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, guardReactiveProps, isRef, mergeModels, mergeProps, nextTick, normalizeClass, normalizeProps, normalizeStyle, onBeforeUnmount, onErrorCaptured, onMounted, onScopeDispose, openBlock, readonly, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref, useId, useModel, useTemplateRef, vModelRadio, vModelSelect, vModelText, vShow, watch, watchEffect, withCtx, withDirectives, withKeys, withModifiers } from "vue";
11
11
  import { ArrowDown, ArrowRight, ArrowUp, ArrowUpRight, CalendarDays, Check, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, Clock, Copy, Eye, EyeOff, Info, Minus, Paperclip, Plus, Star, TriangleAlert, Upload, X, XCircle } from "lucide-vue-next";
@@ -590,7 +590,7 @@ var _hoisted_3$30 = [
590
590
  "aria-describedby"
591
591
  ];
592
592
  var _hoisted_4$21 = ["disabled"];
593
- var _hoisted_5$12 = [
593
+ var _hoisted_5$11 = [
594
594
  "aria-label",
595
595
  "aria-multiselectable",
596
596
  "aria-busy"
@@ -904,7 +904,7 @@ var BaseCombobox_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @_
904
904
  height: "0.75rem"
905
905
  });
906
906
  }), 64))])])) : matches.value.length === 0 ? (openBlock(), createElementBlock("li", _hoisted_10$3, toDisplayString(__props.emptyLabel), 1)) : createCommentVNode("", true)
907
- ], 46, _hoisted_5$12), [[vShow, open.value]]),
907
+ ], 46, _hoisted_5$11), [[vShow, open.value]]),
908
908
  __props.error ? (openBlock(), createElementBlock("p", {
909
909
  key: 0,
910
910
  id: errorId,
@@ -1191,7 +1191,7 @@ var _hoisted_1$46 = { class: "rk-slider" };
1191
1191
  var _hoisted_2$40 = { class: "rk-slider-head" };
1192
1192
  var _hoisted_3$27 = ["for"];
1193
1193
  var _hoisted_4$19 = ["for"];
1194
- var _hoisted_5$11 = [
1194
+ var _hoisted_5$10 = [
1195
1195
  "id",
1196
1196
  "min",
1197
1197
  "max",
@@ -1256,7 +1256,7 @@ var BaseSlider_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
1256
1256
  "aria-valuetext": text.value,
1257
1257
  "aria-describedby": __props.hint ? hintId : void 0,
1258
1258
  style: normalizeStyle({ "--rk-slider-filled": `${filled.value}%` })
1259
- }, null, 12, _hoisted_5$11), [[
1259
+ }, null, 12, _hoisted_5$10), [[
1260
1260
  vModelText,
1261
1261
  model.value,
1262
1262
  void 0,
@@ -1795,7 +1795,7 @@ var _hoisted_4$16 = {
1795
1795
  key: 1,
1796
1796
  class: "text-ink-soft max-w-[36ch] text-sm"
1797
1797
  };
1798
- var _hoisted_5$10 = {
1798
+ var _hoisted_5$9 = {
1799
1799
  key: 2,
1800
1800
  class: "mt-2 flex w-full flex-col gap-2"
1801
1801
  };
@@ -1821,7 +1821,7 @@ var EmptyState_default = /* @__PURE__ */ defineComponent({
1821
1821
  _ctx.$slots.icon ? (openBlock(), createElementBlock("div", _hoisted_2$35, [renderSlot(_ctx.$slots, "icon")])) : createCommentVNode("", true),
1822
1822
  createElementVNode("h3", _hoisted_3$23, toDisplayString(__props.title), 1),
1823
1823
  __props.description ? (openBlock(), createElementBlock("p", _hoisted_4$16, toDisplayString(__props.description), 1)) : createCommentVNode("", true),
1824
- _ctx.$slots.action ? (openBlock(), createElementBlock("div", _hoisted_5$10, [renderSlot(_ctx.$slots, "action")])) : createCommentVNode("", true)
1824
+ _ctx.$slots.action ? (openBlock(), createElementBlock("div", _hoisted_5$9, [renderSlot(_ctx.$slots, "action")])) : createCommentVNode("", true)
1825
1825
  ]);
1826
1826
  };
1827
1827
  }
@@ -1890,14 +1890,16 @@ var PageContainer_default = /* @__PURE__ */ defineComponent({
1890
1890
  //#region src/components/PageHeader.vue?vue&type=script&setup=true&lang.ts
1891
1891
  var _hoisted_1$38 = { class: "grid h-12 shrink-0 grid-cols-[2.5rem_1fr_2.5rem] items-center" };
1892
1892
  var _hoisted_2$34 = { class: "justify-self-start" };
1893
- var _hoisted_3$22 = { class: "text-ink flex min-w-0 justify-center text-base font-semibold tabular-nums" };
1894
- var _hoisted_4$15 = { class: "truncate" };
1895
- var _hoisted_5$9 = { class: "justify-self-end" };
1893
+ var _hoisted_3$22 = { class: "truncate" };
1894
+ var _hoisted_4$15 = { class: "justify-self-end" };
1896
1895
  //#endregion
1897
1896
  //#region src/components/PageHeader.vue
1898
1897
  var PageHeader_default = /* @__PURE__ */ defineComponent({
1899
1898
  __name: "PageHeader",
1900
- props: { title: {} },
1899
+ props: {
1900
+ title: {},
1901
+ as: { default: "h1" }
1902
+ },
1901
1903
  setup(__props) {
1902
1904
  /**
1903
1905
  * The bar at the top of a screen: a title with room either side of it.
@@ -1911,8 +1913,11 @@ var PageHeader_default = /* @__PURE__ */ defineComponent({
1911
1913
  return (_ctx, _cache) => {
1912
1914
  return openBlock(), createElementBlock("header", _hoisted_1$38, [
1913
1915
  createElementVNode("div", _hoisted_2$34, [renderSlot(_ctx.$slots, "left")]),
1914
- createElementVNode("h1", _hoisted_3$22, [renderSlot(_ctx.$slots, "title", {}, () => [createElementVNode("span", _hoisted_4$15, toDisplayString(__props.title), 1)])]),
1915
- createElementVNode("div", _hoisted_5$9, [renderSlot(_ctx.$slots, "right")])
1916
+ (openBlock(), createBlock(resolveDynamicComponent(__props.as), { class: "text-ink flex min-w-0 justify-center text-base font-semibold tabular-nums" }, {
1917
+ default: withCtx(() => [renderSlot(_ctx.$slots, "title", {}, () => [createElementVNode("span", _hoisted_3$22, toDisplayString(__props.title), 1)])]),
1918
+ _: 3
1919
+ })),
1920
+ createElementVNode("div", _hoisted_4$15, [renderSlot(_ctx.$slots, "right")])
1916
1921
  ]);
1917
1922
  };
1918
1923
  }
@@ -2163,7 +2168,7 @@ var SkeletonList_default = /* @__PURE__ */ defineComponent({
2163
2168
  return openBlock(), createElementBlock("div", _hoisted_1$32, [createElementVNode("span", _hoisted_2$30, toDisplayString(__props.label), 1), (openBlock(true), createElementBlock(Fragment, null, renderList(__props.rows, (row) => {
2164
2169
  return openBlock(), createElementBlock("div", {
2165
2170
  key: row,
2166
- class: normalizeClass(["bg-muted rounded-card animate-pulse", isLength.value ? void 0 : __props.rowHeight]),
2171
+ class: normalizeClass(["bg-muted rounded-card motion-safe:animate-pulse", isLength.value ? void 0 : __props.rowHeight]),
2167
2172
  style: normalizeStyle(isLength.value ? { height: __props.rowHeight } : void 0),
2168
2173
  "aria-hidden": "true"
2169
2174
  }, null, 6);
@@ -5294,7 +5299,7 @@ function createI18nRuntime(options) {
5294
5299
  *
5295
5300
  * The fallback keeps `vitest` and `vite dev` honest, where no define runs.
5296
5301
  */
5297
- var VERSION = "4.3.0";
5302
+ var VERSION = "4.4.0";
5298
5303
  //#endregion
5299
5304
  export { ActivityGrid_default as ActivityGrid, AnnounceHost_default as AnnounceHost, AppError, AvatarStack_default as AvatarStack, BarChart_default as BarChart, BaseAlert_default as BaseAlert, BaseAvatar_default as BaseAvatar, BaseBadge_default as BaseBadge, BaseButton_default as BaseButton, BaseCalendar_default as BaseCalendar, BaseCard_default as BaseCard, BaseCheckbox_default as BaseCheckbox, BaseCheckboxGroup_default as BaseCheckboxGroup, BaseChip_default as BaseChip, BaseCombobox_default as BaseCombobox, BaseDatePicker_default as BaseDatePicker, BaseInput_default as BaseInput, BaseKbd_default as BaseKbd, BaseLink_default as BaseLink, BaseListbox_default as BaseListbox, BaseMenu_default as BaseMenu, BasePopconfirm_default as BasePopconfirm, BasePopover_default as BasePopover, BaseRadioGroup_default as BaseRadioGroup, BaseRating_default as BaseRating, BaseSelect_default as BaseSelect, BaseSeparator_default as BaseSeparator, BaseSheet_default as BaseSheet, BaseSkeleton_default as BaseSkeleton, BaseSlider_default as BaseSlider, BaseSpinner_default as BaseSpinner, BaseStepper_default as BaseStepper, BaseSwitch_default as BaseSwitch, BaseTable_default as BaseTable, BaseTextarea_default as BaseTextarea, BaseTimeline_default as BaseTimeline, CircularProgress_default as CircularProgress, CodeBlock_default as CodeBlock, ColorPicker_default as ColorPicker, CopyButton_default as CopyButton, DescriptionList_default as DescriptionList, DonutChart_default as DonutChart, EmptyState_default as EmptyState, ErrorBoundary_default as ErrorBoundary, ErrorSummary_default as ErrorSummary, FileDrop_default as FileDrop, FormField_default as FormField, GoogleButton_default as GoogleButton, LocaleLinks_default as LocaleLinks, MATERIALS, NumberInput_default as NumberInput, PALETTES, PageContainer_default as PageContainer, PageHeader_default as PageHeader, PasswordInput_default as PasswordInput, PinInput_default as PinInput, PriceCard_default as PriceCard, ProgressBar_default as ProgressBar, SHEET_ROOT_ID, ScrollArea_default as ScrollArea, SectionHeading_default as SectionHeading, SegmentedControl_default as SegmentedControl, SettingsGroup_default as SettingsGroup, SettingsRow_default as SettingsRow, SkeletonList_default as SkeletonList, SliderField_default as SliderField, StatCard_default as StatCard, TabBar_default as TabBar, TagsInput_default as TagsInput, TimePicker_default as TimePicker, ToastHost_default as ToastHost, ToggleGroup_default as ToggleGroup, ToneDot_default as ToneDot, VERSION, VirtualList_default as VirtualList, addDays, announce, applyMaterial, applyPalette, applyTheme, createI18nRuntime, downloadJson, eachDayOfYear, elementDirection, ensureSheetRoot, formatDate, formatNumber, fromDateKey, horizontalStep, isApplePortable, isInstalled, isMaterial, isPaletteName, isThemePreference, lastNDays, leadingBlanks, needsIosInstall, readStoredTheme, registerErrorMapper, relativeDayLabel, safeRedirect, setFormatLocale, setMaterialStorageKey, setPaletteStorageKey, setThemeStorageKey, startOfWeek, tapFeedback, textDirection, toAppError, toDateKey, toRedirectPath, todayKey, useAnnounce, useDebouncedCallback, useDragScroll, useMaterial, useMediaQuery, useOnline, usePalette, useTheme, useToast, useToday, useVisualViewport };
5300
5305