rei-kit 2.22.0 → 2.24.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.
@@ -0,0 +1,48 @@
1
+ import { Ref } from 'vue';
2
+ /** Above the anchor or below it. */
3
+ export type PanelSide = 'top' | 'bottom';
4
+ export interface AnchoredPanelOptions {
5
+ /** The element the panel is positioned against. */
6
+ root: Ref<HTMLElement | null>;
7
+ /** The panel itself, measured once it is on screen. */
8
+ panel: Ref<HTMLElement | null>;
9
+ /** Whether it is showing. */
10
+ open: Ref<boolean>;
11
+ /** Which way it opens before anything is measured. */
12
+ side?: PanelSide;
13
+ /** How close to the window edge it may come, in pixels. */
14
+ margin?: number;
15
+ }
16
+ /**
17
+ * Keeping a panel on screen.
18
+ *
19
+ * Two corrections, in this order, because they are not equal. **Flip**: a
20
+ * panel that does not fit below its anchor opens above it when there is
21
+ * more room there. **Shift**: whatever is left of it past the side of the
22
+ * window is slid back in.
23
+ *
24
+ * A panel taller than the window fits neither way, and then the rule is
25
+ * still "more room wins" — which shows more of it, and leaves the rest
26
+ * scrolled to rather than gone. `BasePopover` carried a comment saying it
27
+ * kept the side it asked for in that case; it never did, and nothing
28
+ * checked, because the arithmetic had never been given a layout to work
29
+ * on. The comment is gone and the behaviour is the one under test.
30
+ *
31
+ * This was written inside `BasePopover`, which is how the kit came to ship
32
+ * a popover that flips and a menu that does not. `BaseMenu`'s panel was
33
+ * pinned under its trigger with `top: 100%`, so a menu button near the
34
+ * bottom of a window — a row action, the `…` on a card, anything on a
35
+ * phone — opened a list that ran off the bottom of the screen. Nothing said
36
+ * so: it rendered, it passed axe, and the items were all there.
37
+ *
38
+ * Re-measured while open, because the page can scroll or the window change
39
+ * size underneath, and a panel that stays where it opened ends up attached
40
+ * to nothing. The scroll listener captures, so a scroll in any ancestor
41
+ * counts, and it is passive-by-omission: it only reads.
42
+ */
43
+ export declare function useAnchoredPanel({ root, panel, open, side, margin, }: AnchoredPanelOptions): {
44
+ placed: Ref<PanelSide, PanelSide>;
45
+ shift: Ref<number, number>;
46
+ place: () => void;
47
+ reset: () => void;
48
+ };
@@ -1,4 +1,21 @@
1
1
  import { Ref } from 'vue';
2
+ /**
3
+ * What every modal surface has to do, in one place.
4
+ *
5
+ * A dialog, a drawer and anything else that takes the whole page share a
6
+ * list that is easy to get nearly right: trap Tab, stop Escape, make the
7
+ * rest of the page inert, stop it scrolling underneath, and hand focus back
8
+ * to whatever opened it. Each of those has an order that matters, and the
9
+ * orders are not obvious — releasing `inert` *after* restoring focus drops
10
+ * it on the body, because an inert element cannot take focus. That was
11
+ * written once, correctly, in `BaseModal`; a drawer that copied it would be
12
+ * a second copy to keep correct.
13
+ *
14
+ * The Tab wrap has to be done by hand: the browser's own Tab order is the
15
+ * whole document, and a dialog is only part of it. `inert` is what also
16
+ * stops a screen reader's virtual cursor, which walks straight past a
17
+ * keydown handler.
18
+ */
2
19
  export interface DialogShellOptions {
3
20
  /** Whether Escape and the scrim close it. */
4
21
  dismissible?: () => boolean;
@@ -75,6 +75,49 @@ function formatNumber(value, options = {}, tag = locale.value) {
75
75
  return formatter.format(value);
76
76
  }
77
77
  //#endregion
78
- export { formatNumber as n, setFormatLocale as r, formatDate as t };
78
+ //#region src/motion/environment.ts
79
+ /**
80
+ * What every moving part asks before it moves.
81
+ *
82
+ * Kept in one place so the answers cannot drift apart: a counter that honoured
83
+ * reduced motion beside a marquee that did not would be worse than neither.
84
+ * Each is safe to call on a server, where the answer is "do not move" — the
85
+ * server renders the final state, and that is what a reader without
86
+ * JavaScript should see.
87
+ */
88
+ /** Whether the reader has asked the operating system for less motion. */
89
+ function prefersReducedMotion() {
90
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return true;
91
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
92
+ }
93
+ /** One frame from now, or the nearest thing to it where frames do not exist. */
94
+ function nextFrame(callback) {
95
+ if (typeof requestAnimationFrame === "function") {
96
+ const id = requestAnimationFrame(callback);
97
+ return () => cancelAnimationFrame(id);
98
+ }
99
+ const id = setTimeout(callback, 16);
100
+ return () => clearTimeout(id);
101
+ }
102
+ /**
103
+ * Calls `callback` with whether `element` is on screen, each time that
104
+ * changes. Returns the function that stops watching.
105
+ *
106
+ * Without `IntersectionObserver` the element counts as visible straight away:
107
+ * an animation that never starts is a missing number, not a missing flourish.
108
+ */
109
+ function watchVisibility(element, callback, threshold = .25) {
110
+ if (typeof IntersectionObserver === "undefined") {
111
+ callback(true);
112
+ return () => {};
113
+ }
114
+ const observer = new IntersectionObserver((entries) => {
115
+ for (const entry of entries) callback(entry.isIntersecting);
116
+ }, { threshold });
117
+ observer.observe(element);
118
+ return () => observer.disconnect();
119
+ }
120
+ //#endregion
121
+ export { formatNumber as a, formatDate as i, prefersReducedMotion as n, setFormatLocale as o, watchVisibility as r, nextFrame as t };
79
122
 
80
- //# sourceMappingURL=format-BEvBK1SJ.js.map
123
+ //# sourceMappingURL=environment-Cz5KiC1t.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment-Cz5KiC1t.js","names":[],"sources":["../src/utils/format.ts","../src/motion/environment.ts"],"sourcesContent":["import { ref } from 'vue'\n\n/**\n * The locale `Intl` formatting uses.\n *\n * Held here rather than imported from an i18n runtime so the utilities have no\n * i18n dependency at all: an app that never installs vue-i18n still gets dates\n * in the right language. `createI18nRuntime` sets this when it is used.\n */\nconst locale = ref<string>(typeof navigator === 'undefined' ? 'en' : (navigator.language ?? 'en'))\n\n/**\n * Points every formatter at a new locale.\n *\n * @example\n * ```ts\n * setFormatLocale('tr-TR')\n * ```\n */\nexport function setFormatLocale(next: string): void {\n locale.value = next\n}\n\n/**\n * `Intl.DateTimeFormat` is expensive to construct, so instances are cached per\n * locale and option set. The key includes the locale, which is what lets the\n * cache survive a language change instead of returning stale formatters.\n */\nconst cache = new Map<string, Intl.DateTimeFormat>()\n\n/**\n * Formats a date in the active locale.\n *\n * Reading the locale ref here is deliberate: called from a `computed`, the\n * result re-evaluates when the language changes.\n *\n * @param date - Date to format.\n * @param options - Passed straight to `Intl.DateTimeFormat`.\n *\n * @example\n * ```ts\n * formatDate(new Date(), { weekday: 'narrow' }) // 'T'\n * ```\n */\nexport function formatDate(date: Date, options: Intl.DateTimeFormatOptions): string {\n const tag = locale.value\n const key = `${tag}:${JSON.stringify(options)}`\n\n let formatter = cache.get(key)\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(tag, options)\n cache.set(key, formatter)\n }\n\n return formatter.format(date)\n}\n\nconst numberCache = new Map<string, Intl.NumberFormat>()\n\n/**\n * Formats a number in the active locale, or in `tag` when one is given.\n *\n * The same cache and the same locale as `formatDate`, so a counter and the\n * date beside it change language together.\n *\n * @param value - Number to format.\n * @param options - Passed straight to `Intl.NumberFormat`: currency, percent, decimals.\n * @param tag - A BCP 47 tag that overrides the active locale for this call.\n *\n * @example\n * ```ts\n * formatNumber(48200, { style: 'currency', currency: 'JPY' }) // '¥48,200'\n * ```\n */\nexport function formatNumber(\n value: number,\n options: Intl.NumberFormatOptions = {},\n tag: string = locale.value,\n): string {\n const key = `${tag}:${JSON.stringify(options)}`\n\n let formatter = numberCache.get(key)\n if (!formatter) {\n formatter = new Intl.NumberFormat(tag, options)\n numberCache.set(key, formatter)\n }\n\n return formatter.format(value)\n}\n","/**\n * What every moving part asks before it moves.\n *\n * Kept in one place so the answers cannot drift apart: a counter that honoured\n * reduced motion beside a marquee that did not would be worse than neither.\n * Each is safe to call on a server, where the answer is \"do not move\" — the\n * server renders the final state, and that is what a reader without\n * JavaScript should see.\n */\n\n/** Whether the reader has asked the operating system for less motion. */\nexport function prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return true\n\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches\n}\n\n/** One frame from now, or the nearest thing to it where frames do not exist. */\nexport function nextFrame(callback: () => void): () => void {\n if (typeof requestAnimationFrame === 'function') {\n const id = requestAnimationFrame(callback)\n\n return () => cancelAnimationFrame(id)\n }\n\n const id = setTimeout(callback, 16)\n\n return () => clearTimeout(id)\n}\n\n/**\n * Calls `callback` with whether `element` is on screen, each time that\n * changes. Returns the function that stops watching.\n *\n * Without `IntersectionObserver` the element counts as visible straight away:\n * an animation that never starts is a missing number, not a missing flourish.\n */\nexport function watchVisibility(\n element: Element,\n callback: (visible: boolean) => void,\n threshold = 0.25,\n): () => void {\n if (typeof IntersectionObserver === 'undefined') {\n callback(true)\n\n return () => {}\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) callback(entry.isIntersecting)\n },\n { threshold },\n )\n observer.observe(element)\n\n return () => observer.disconnect()\n}\n"],"mappings":";;;;;;;;;AASA,IAAM,SAAS,IAAY,OAAO,cAAc,cAAc,OAAQ,UAAU,YAAY,IAAK;;;;;;;;;AAUjG,SAAgB,gBAAgB,MAAoB;CAClD,OAAO,QAAQ;AACjB;;;;;;AAOA,IAAM,wBAAQ,IAAI,IAAiC;;;;;;;;;;;;;;;AAgBnD,SAAgB,WAAW,MAAY,SAA6C;CAClF,MAAM,MAAM,OAAO;CACnB,MAAM,MAAM,GAAG,IAAI,GAAG,KAAK,UAAU,OAAO;CAE5C,IAAI,YAAY,MAAM,IAAI,GAAG;CAC7B,IAAI,CAAC,WAAW;EACd,YAAY,IAAI,KAAK,eAAe,KAAK,OAAO;EAChD,MAAM,IAAI,KAAK,SAAS;CAC1B;CAEA,OAAO,UAAU,OAAO,IAAI;AAC9B;AAEA,IAAM,8BAAc,IAAI,IAA+B;;;;;;;;;;;;;;;;AAiBvD,SAAgB,aACd,OACA,UAAoC,CAAC,GACrC,MAAc,OAAO,OACb;CACR,MAAM,MAAM,GAAG,IAAI,GAAG,KAAK,UAAU,OAAO;CAE5C,IAAI,YAAY,YAAY,IAAI,GAAG;CACnC,IAAI,CAAC,WAAW;EACd,YAAY,IAAI,KAAK,aAAa,KAAK,OAAO;EAC9C,YAAY,IAAI,KAAK,SAAS;CAChC;CAEA,OAAO,UAAU,OAAO,KAAK;AAC/B;;;;;;;;;;;;;AC7EA,SAAgB,uBAAgC;CAC9C,IAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY,OAAO;CAErF,OAAO,OAAO,WAAW,kCAAkC,CAAC,CAAC;AAC/D;;AAGA,SAAgB,UAAU,UAAkC;CAC1D,IAAI,OAAO,0BAA0B,YAAY;EAC/C,MAAM,KAAK,sBAAsB,QAAQ;EAEzC,aAAa,qBAAqB,EAAE;CACtC;CAEA,MAAM,KAAK,WAAW,UAAU,EAAE;CAElC,aAAa,aAAa,EAAE;AAC9B;;;;;;;;AASA,SAAgB,gBACd,SACA,UACA,YAAY,KACA;CACZ,IAAI,OAAO,yBAAyB,aAAa;EAC/C,SAAS,IAAI;EAEb,aAAa,CAAC;CAChB;CAEA,MAAM,WAAW,IAAI,sBAClB,YAAY;EACX,KAAK,MAAM,SAAS,SAAS,SAAS,MAAM,cAAc;CAC5D,GACA,EAAE,UAAU,CACd;CACA,SAAS,QAAQ,OAAO;CAExB,aAAa,SAAS,WAAW;AACnC"}
@@ -344,6 +344,92 @@ var BaseCheckbox_default = /* @__PURE__ */ defineComponent({
344
344
  }
345
345
  });
346
346
  //#endregion
347
+ //#region src/utils/direction.ts
348
+ var RTL = /* @__PURE__ */ new Set([
349
+ "ar",
350
+ "arc",
351
+ "ckb",
352
+ "dv",
353
+ "fa",
354
+ "he",
355
+ "ks",
356
+ "ku",
357
+ "nqo",
358
+ "ps",
359
+ "sd",
360
+ "syr",
361
+ "ug",
362
+ "ur",
363
+ "yi"
364
+ ]);
365
+ /**
366
+ * Which way a locale tag is written.
367
+ *
368
+ * The kit is built on logical properties and `start`/`end` props all the
369
+ * way through — `BaseDrawer`'s side, every inset, every margin — and none
370
+ * of that does anything at all until `dir` is set on the document. So the
371
+ * direction has to come from somewhere, and a locale tag is the only thing
372
+ * that knows it.
373
+ *
374
+ * Only the language subtag is read, so `ar-EG` and `ar` answer the same.
375
+ *
376
+ * @example
377
+ * ```ts
378
+ * textDirection('he-IL') // 'rtl'
379
+ * textDirection('tr') // 'ltr'
380
+ * ```
381
+ */
382
+ function textDirection(locale) {
383
+ const parts = locale.toLowerCase().split(/[-_]/);
384
+ const script = parts[1];
385
+ if (script === "arab" || script === "hebr" || script === "thaa" || script === "syrc") return "rtl";
386
+ return RTL.has(parts[0] ?? "") ? "rtl" : "ltr";
387
+ }
388
+ /**
389
+ * Which way the text around an element runs.
390
+ *
391
+ * The companion to `textDirection`, and the other end of it: that one asks
392
+ * a locale, this one asks the page. An element inside a quoted passage may
393
+ * run the other way from the document it is in, so the nearest `dir`
394
+ * attribute wins and the document is the fallback.
395
+ *
396
+ * The attribute rather than `getComputedStyle().direction`: the computed
397
+ * value is the real answer, but it is inherited through the cascade, and
398
+ * jsdom does not inherit it — so the tests that exist to prove the
399
+ * right-to-left behaviour would all pass with the behaviour removed. What
400
+ * actually sets the direction in this kit is the attribute, in one place,
401
+ * on `<html>`, written by `createI18nRuntime`.
402
+ */
403
+ function elementDirection(from) {
404
+ return (from?.closest("[dir]")?.getAttribute("dir") ?? document.dir) === "rtl" ? "rtl" : "ltr";
405
+ }
406
+ /**
407
+ * `ArrowLeft` and `ArrowRight` as a direction of travel: `-1` back through
408
+ * a list, `1` on through it, `0` for any other key.
409
+ *
410
+ * They are mirrored where the writing is, which is not a nicety — it is the
411
+ * whole of what the arrows mean. A row of tabs in Arabic runs right to
412
+ * left, so the tab to the *left* of the current one is the next one, and a
413
+ * handler that reads `ArrowRight` as "forward" walks backwards through
414
+ * every roving-tabindex control in the kit at once. WAI-ARIA says so
415
+ * explicitly, and it is invisible to every check: the code type-checks, the
416
+ * styles are all logical, and the keys are the same keys.
417
+ *
418
+ * @example
419
+ * ```ts
420
+ * function onKeydown(event: KeyboardEvent) {
421
+ * const step = horizontalStep(event.key, event.currentTarget as Element)
422
+ * if (step === 0) return
423
+ * event.preventDefault()
424
+ * moveTo(index + step)
425
+ * }
426
+ * ```
427
+ */
428
+ function horizontalStep(key, from) {
429
+ if (key !== "ArrowLeft" && key !== "ArrowRight") return 0;
430
+ return key === (elementDirection(from) === "rtl" ? "ArrowLeft" : "ArrowRight") ? 1 : -1;
431
+ }
432
+ //#endregion
347
433
  //#region src/utils/focus-target.ts
348
434
  /**
349
435
  * Moves focus to an element that was never meant to take it.
@@ -370,6 +456,6 @@ function focusTarget(target) {
370
456
  return true;
371
457
  }
372
458
  //#endregion
373
- export { useBoundValue as a, ensureSheetRoot as c, inertOutside as i, BaseCheckbox_default as n, useVisualViewport as o, BaseSheet_default as r, SHEET_ROOT_ID as s, focusTarget as t };
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 };
374
460
 
375
- //# sourceMappingURL=focus-target-D0WsXwKh.js.map
461
+ //# sourceMappingURL=focus-target-DRgd8UnQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focus-target-DRgd8UnQ.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-[28px] 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-[28px] 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.d.ts CHANGED
@@ -95,6 +95,14 @@ export type { TabItem } from './components/TabBar.vue';
95
95
  * the picture's order, and one tab stop rather than three hundred odd.
96
96
  */
97
97
  export { default as ActivityGrid } from './components/ActivityGrid.vue';
98
+ /**
99
+ * `BarChart` and `DonutChart`: the two shapes the kit's own dashboard
100
+ * vocabulary — `StatCard`, `ProgressBar`, `ActivityGrid` — creates a need
101
+ * for and did not answer. Both are the data as text with a picture beside
102
+ * it, rather than a picture with a sentence describing it.
103
+ */
104
+ export { default as BarChart } from './components/BarChart.vue';
105
+ export { default as DonutChart } from './components/DonutChart.vue';
98
106
  /**
99
107
  * `ScrollArea`, a scrolling box with the two things a hand-written one
100
108
  * leaves out: a fade at whichever edge has more content past it, and a
@@ -126,6 +134,13 @@ export { useAnnounce, announce } from './composables/use-announce';
126
134
  * no i18n runtime and still has more than one direction.
127
135
  */
128
136
  export { textDirection } from './utils/direction';
137
+ /**
138
+ * `elementDirection` and `horizontalStep`, for an app writing the keyboard
139
+ * behaviour the kit does not cover. `ArrowLeft` means "back through the
140
+ * list" only where the language runs left to right; read as a step, it
141
+ * means the same thing in both.
142
+ */
143
+ export { elementDirection, horizontalStep } from './utils/direction';
129
144
  export type { TextDirection } from './utils/direction';
130
145
  export type { AnnounceOptions } from './composables/use-announce';
131
146
  export { default as AvatarStack } from './components/AvatarStack.vue';
@@ -162,6 +177,7 @@ export type { PopoverTriggerProps } from './components/BasePopover.vue';
162
177
  export { default as ToggleGroup } from './components/ToggleGroup.vue';
163
178
  export { default as NumberInput } from './components/NumberInput.vue';
164
179
  export { default as PinInput } from './components/PinInput.vue';
180
+ export { default as PasswordInput } from './components/PasswordInput.vue';
165
181
  export { default as CircularProgress } from './components/CircularProgress.vue';
166
182
  export { default as BaseStepper } from './components/BaseStepper.vue';
167
183
  export type { StepperStep } from './components/BaseStepper.vue';