rei-kit 2.20.0 → 2.22.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.
Files changed (35) hide show
  1. package/README.md +3 -3
  2. package/dist/{BaseListbox-Btibgnaf.js → BaseListbox-DMR-Zn1u.js} +232 -4
  3. package/dist/BaseListbox-DMR-Zn1u.js.map +1 -0
  4. package/dist/app/guards.d.ts +43 -1
  5. package/dist/app/index.d.ts +1 -1
  6. package/dist/app.js +51 -5
  7. package/dist/app.js.map +1 -1
  8. package/dist/components/AnnounceHost.vue.d.ts +3 -0
  9. package/dist/components/BaseCheckbox.vue.d.ts +19 -0
  10. package/dist/components/ErrorSummary.vue.d.ts +60 -0
  11. package/dist/components/FormField.vue.d.ts +6 -0
  12. package/dist/components/ScrollArea.vue.d.ts +68 -0
  13. package/dist/components/VirtualList.vue.d.ts +37 -0
  14. package/dist/composables/use-announce.d.ts +65 -0
  15. package/dist/composables/use-virtual-window.d.ts +39 -0
  16. package/dist/{BaseSheet-B7401rS7.js → focus-target-D0WsXwKh.js} +109 -9
  17. package/dist/focus-target-D0WsXwKh.js.map +1 -0
  18. package/dist/index.d.ts +33 -0
  19. package/dist/index.js +443 -262
  20. package/dist/index.js.map +1 -1
  21. package/dist/motion.js +1 -1
  22. package/dist/motion.js.map +1 -1
  23. package/dist/styles.css +469 -323
  24. package/dist/{GoogleButton-mCqGbrXm.js → use-announce-B1LsfsZx.js} +59 -86
  25. package/dist/use-announce-B1LsfsZx.js.map +1 -0
  26. package/dist/utils/direction.d.ts +20 -0
  27. package/dist/utils/focus-target.d.ts +18 -0
  28. package/dist/web/SkipLink.vue.d.ts +43 -0
  29. package/dist/web/index.d.ts +6 -0
  30. package/dist/web.js +154 -133
  31. package/dist/web.js.map +1 -1
  32. package/package.json +2 -2
  33. package/dist/BaseListbox-Btibgnaf.js.map +0 -1
  34. package/dist/BaseSheet-B7401rS7.js.map +0 -1
  35. package/dist/GoogleButton-mCqGbrXm.js.map +0 -1
@@ -0,0 +1,37 @@
1
+ declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
2
+ props: import('vue').PublicProps & __VLS_PrettifyLocal<{
3
+ /** The whole list. Only the rows near the viewport are rendered. */
4
+ items: readonly T[];
5
+ /** Every row is this tall, in pixels. */
6
+ rowHeight: number;
7
+ /** Names the list, e.g. "Bütün kayıtlar". Already translated. */
8
+ label: string;
9
+ /** Below this many rows the list is not windowed at all. */
10
+ virtualizeAfter?: number | undefined;
11
+ /** Passed to the scroll area: `hidden` takes the native bar away. */
12
+ scrollbar?: "auto" | "hidden" | undefined;
13
+ }> & (typeof globalThis extends {
14
+ __VLS_PROPS_FALLBACK: infer P;
15
+ } ? P : {});
16
+ expose: (exposed: import('vue').ShallowUnwrapRef<{
17
+ scrollToIndex: (index: number) => void;
18
+ }>) => void;
19
+ attrs: any;
20
+ slots: {
21
+ /** One row. `index` is its place in the whole list, not in the window. */
22
+ default: (props: {
23
+ item: T;
24
+ index: number;
25
+ }) => unknown;
26
+ };
27
+ emit: {};
28
+ }>) => import('vue').VNode & {
29
+ __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
30
+ };
31
+ declare const _default: typeof __VLS_export;
32
+ export default _default;
33
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
34
+ [K in keyof T]: T[K];
35
+ } : {
36
+ [K in keyof T as K]: T[K];
37
+ }) & {};
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Saying something to a screen reader when nothing on screen has changed
3
+ * enough to say it.
4
+ *
5
+ * The kit already has a live region in a dozen places — `ToastHost`,
6
+ * `BaseSpinner`, `OfflineBanner`, `CopyButton` — but every one of them
7
+ * belongs to a component that happens to be on screen. What has no home is
8
+ * everything else: a filter that narrows a list to three results, a route
9
+ * that changed under a client-side navigation, a draft that saved itself.
10
+ * On screen all of those are obvious. To a reader they are silence.
11
+ *
12
+ * ── Three things this gets right that a hand-written region does not ──
13
+ *
14
+ * **The region exists before the text does.** A live region inserted into
15
+ * the page together with its first message is usually not announced at
16
+ * all: the reader has nothing to notice a change *to*. So `AnnounceHost`
17
+ * renders both regions empty, from mount, and only the text inside them
18
+ * ever changes. This is the single most common reason a hand-rolled
19
+ * announcement does nothing, and it fails silently on every platform.
20
+ *
21
+ * **The same sentence twice is still said twice.** "3 results" after
22
+ * "3 results" is not a mutation, so a reader says nothing — and the reader
23
+ * has no idea the filter did anything. The text is cleared and set again
24
+ * on the next tick, which is a change either way.
25
+ *
26
+ * **Polite by default.** `assertive` interrupts whatever is being read,
27
+ * mid-word. That is right for "connection lost" and rude for "saved", and
28
+ * a region that is always assertive trains the reader to resent it.
29
+ *
30
+ * ── What is deliberately not here ──
31
+ *
32
+ * **No text of its own.** Like everything else in the kit, the sentence is
33
+ * the app's.
34
+ *
35
+ * **Not for anything on screen.** If there is a visible message, it should
36
+ * be the live region — `BaseAlert`, `FormField`, a toast. This is only for
37
+ * what a sighted reader learns from the screen itself.
38
+ *
39
+ * **A singleton, on purpose.** Two hosts would be two regions, and a
40
+ * message would be read twice.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const { announce } = useAnnounce()
45
+ * watch(results, (rows) => announce(t('search.found', { count: rows.length })))
46
+ * ```
47
+ */
48
+ export interface AnnounceOptions {
49
+ /**
50
+ * Interrupt whatever is being read. For something the reader has to know
51
+ * now — a connection lost, a session about to expire — and nothing else.
52
+ */
53
+ assertive?: boolean | undefined;
54
+ }
55
+ /**
56
+ * Says something once. Render one `AnnounceHost` in the app for it to reach a
57
+ * reader at all.
58
+ */
59
+ export declare function announce(message: string, { assertive: urgent }?: AnnounceOptions): void;
60
+ export declare function useAnnounce(): {
61
+ announce: typeof announce;
62
+ /** What the regions are saying. `AnnounceHost` renders these; apps read them in tests. */
63
+ polite: Readonly<import('vue').Ref<string, string>>;
64
+ assertive: Readonly<import('vue').Ref<string, string>>;
65
+ };
@@ -0,0 +1,39 @@
1
+ import { Ref } from 'vue';
2
+ export interface VirtualWindowOptions {
3
+ /** How many rows there are in total. */
4
+ count: Ref<number>;
5
+ /** Every row is this tall, in pixels. Fixed — that is what makes the
6
+ * arithmetic possible without measuring each one. */
7
+ rowHeight: Ref<number>;
8
+ /**
9
+ * Below this many rows the window is the whole list and the padding is
10
+ * zero, so a short list renders exactly as it would without any of this.
11
+ */
12
+ threshold: Ref<number>;
13
+ }
14
+ /**
15
+ * The arithmetic behind a windowed list: which rows are near the viewport,
16
+ * and how much empty space stands in for the rest.
17
+ *
18
+ * This is the part `BaseCombobox` and `VirtualList` share. It was written
19
+ * inside the combobox first, and pulling it out here is the reason there is
20
+ * one copy of it rather than two — the second would have drifted, and the
21
+ * half that drifts silently is the padding, which is what keeps the
22
+ * scrollbar honest about how long the list is.
23
+ *
24
+ * There is no clock and no observer in here: it is a function of the scroll
25
+ * position, and the component that owns the element is what tells it where
26
+ * that is. That is also what makes it testable without a layout.
27
+ */
28
+ export declare function useVirtualWindow({ count, rowHeight, threshold }: VirtualWindowOptions): {
29
+ active: import('vue').ComputedRef<boolean>;
30
+ window: import('vue').ComputedRef<{
31
+ start: number;
32
+ end: number;
33
+ }>;
34
+ padTop: import('vue').ComputedRef<number>;
35
+ padBottom: import('vue').ComputedRef<number>;
36
+ scrollTop: Ref<number, number>;
37
+ viewportHeight: Ref<number, number>;
38
+ onScroll: (event: Event) => void;
39
+ };
@@ -1,6 +1,6 @@
1
1
  import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BOaGB7Aw.js";
2
2
  import { t as BaseButton_default } from "./BaseButton-RN2an975.js";
3
- import { Teleport, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeModels, nextTick, normalizeStyle, onScopeDispose, onUnmounted, openBlock, readonly, ref, renderSlot, toDisplayString, unref, useModel, watch, withCtx } from "vue";
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
6
6
  /** The node every sheet teleports into. */
@@ -157,9 +157,9 @@ function inertOutside(keep) {
157
157
  }
158
158
  //#endregion
159
159
  //#region src/components/BaseSheet.vue?vue&type=script&setup=true&lang.ts
160
- var _hoisted_1 = { class: "shell-frame md:rounded-shell relative flex max-h-full flex-col justify-end overflow-hidden" };
161
- var _hoisted_2 = ["aria-label"];
162
- var _hoisted_3 = { class: "flex shrink-0 items-start gap-3 px-6 pt-4 pb-5" };
160
+ var _hoisted_1$1 = { class: "shell-frame md:rounded-shell relative flex max-h-full flex-col justify-end overflow-hidden" };
161
+ var _hoisted_2$1 = ["aria-label"];
162
+ var _hoisted_3$1 = { class: "flex shrink-0 items-start gap-3 px-6 pt-4 pb-5" };
163
163
  var _hoisted_4 = { class: "min-w-0 flex-1" };
164
164
  var _hoisted_5 = { class: "text-ink text-xl leading-tight font-semibold" };
165
165
  var _hoisted_6 = {
@@ -237,7 +237,7 @@ var BaseSheet_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
237
237
  key: 0,
238
238
  class: "fixed inset-x-0 top-0 bottom-0 z-50 flex items-center justify-center",
239
239
  style: normalizeStyle(viewportStyle.value)
240
- }, [createElementVNode("div", _hoisted_1, [createElementVNode("div", {
240
+ }, [createElementVNode("div", _hoisted_1$1, [createElementVNode("div", {
241
241
  class: "bg-ink/45 absolute inset-0 backdrop-blur-[2px]",
242
242
  onClick: close
243
243
  }), createElementVNode("section", {
@@ -253,7 +253,7 @@ var BaseSheet_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
253
253
  class: "flex shrink-0 justify-center pt-3",
254
254
  "aria-hidden": "true"
255
255
  }, [createElementVNode("span", { class: "bg-hair h-1.5 w-10 rounded-full" })], -1)),
256
- createElementVNode("header", _hoisted_3, [createElementVNode("div", _hoisted_4, [createElementVNode("h2", _hoisted_5, toDisplayString(__props.title), 1), __props.subtitle ? (openBlock(), createElementBlock("p", _hoisted_6, toDisplayString(__props.subtitle), 1)) : createCommentVNode("", true)]), createVNode(BaseButton_default, {
256
+ createElementVNode("header", _hoisted_3$1, [createElementVNode("div", _hoisted_4, [createElementVNode("h2", _hoisted_5, toDisplayString(__props.title), 1), __props.subtitle ? (openBlock(), createElementBlock("p", _hoisted_6, toDisplayString(__props.subtitle), 1)) : createCommentVNode("", true)]), createVNode(BaseButton_default, {
257
257
  variant: "unstyled",
258
258
  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",
259
259
  "aria-label": __props.closeLabel,
@@ -263,13 +263,113 @@ var BaseSheet_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
263
263
  _: 1
264
264
  }, 8, ["aria-label"])]),
265
265
  createElementVNode("div", _hoisted_7, [renderSlot(_ctx.$slots, "default", {}, void 0, true)])
266
- ], 8, _hoisted_2)])], 4)) : createCommentVNode("", true)]),
266
+ ], 8, _hoisted_2$1)])], 4)) : createCommentVNode("", true)]),
267
267
  _: 3
268
268
  })], 8, ["to"]);
269
269
  };
270
270
  }
271
271
  }), [["__scopeId", "data-v-2855eea3"]]);
272
272
  //#endregion
273
- export { SHEET_ROOT_ID as a, useVisualViewport as i, inertOutside as n, ensureSheetRoot as o, useBoundValue as r, BaseSheet_default as t };
273
+ //#region src/components/BaseCheckbox.vue?vue&type=script&setup=true&lang.ts
274
+ var _hoisted_1 = { class: "flex flex-col gap-1.5" };
275
+ var _hoisted_2 = ["for"];
276
+ var _hoisted_3 = [
277
+ "id",
278
+ "disabled",
279
+ "indeterminate",
280
+ "aria-invalid",
281
+ "aria-describedby"
282
+ ];
283
+ //#endregion
284
+ //#region src/components/BaseCheckbox.vue
285
+ var BaseCheckbox_default = /* @__PURE__ */ defineComponent({
286
+ __name: "BaseCheckbox",
287
+ props: /*@__PURE__*/ mergeModels({
288
+ label: {},
289
+ error: { default: "" },
290
+ hint: { default: "" },
291
+ disabled: {
292
+ type: Boolean,
293
+ default: false
294
+ },
295
+ size: { default: "md" },
296
+ indeterminate: {
297
+ type: Boolean,
298
+ default: false
299
+ },
300
+ labelHidden: {
301
+ type: Boolean,
302
+ default: false
303
+ }
304
+ }, {
305
+ "modelValue": {
306
+ type: Boolean,
307
+ default: false
308
+ },
309
+ "modelModifiers": {}
310
+ }),
311
+ emits: ["update:modelValue"],
312
+ setup(__props) {
313
+ const model = useModel(__props, "modelValue");
314
+ const id = useId();
315
+ const errorId = `${id}-error`;
316
+ const hintId = `${id}-hint`;
317
+ const describedBy = computed(() => {
318
+ if (__props.error) return errorId;
319
+ if (__props.hint) return hintId;
320
+ });
321
+ return (_ctx, _cache) => {
322
+ return openBlock(), createElementBlock("div", _hoisted_1, [createElementVNode("label", {
323
+ for: unref(id),
324
+ class: normalizeClass(["flex items-center", [__props.labelHidden ? "" : __props.size === "sm" ? "gap-2" : "gap-3", __props.disabled ? "opacity-50" : "cursor-pointer"]])
325
+ }, [withDirectives(createElementVNode("input", {
326
+ id: unref(id),
327
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => model.value = $event),
328
+ type: "checkbox",
329
+ disabled: __props.disabled,
330
+ indeterminate: __props.indeterminate,
331
+ "aria-invalid": Boolean(__props.error),
332
+ "aria-describedby": describedBy.value,
333
+ class: "accent-primary focus-visible:outline-primary size-4 shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2"
334
+ }, null, 8, _hoisted_3), [[vModelCheckbox, model.value]]), createElementVNode("span", { class: normalizeClass(["text-sm", [__props.labelHidden ? "sr-only" : "", __props.size === "sm" ? "text-ink-soft" : "text-ink"]]) }, toDisplayString(__props.label), 3)], 10, _hoisted_2), __props.error ? (openBlock(), createElementBlock("p", {
335
+ key: 0,
336
+ id: errorId,
337
+ class: "text-negative text-xs"
338
+ }, toDisplayString(__props.error), 1)) : __props.hint ? (openBlock(), createElementBlock("p", {
339
+ key: 1,
340
+ id: hintId,
341
+ class: "text-ink-soft text-xs"
342
+ }, toDisplayString(__props.hint), 1)) : createCommentVNode("", true)]);
343
+ };
344
+ }
345
+ });
346
+ //#endregion
347
+ //#region src/utils/focus-target.ts
348
+ /**
349
+ * Moves focus to an element that was never meant to take it.
350
+ *
351
+ * A `<main>`, a heading, the top of a view: none of them are focusable, and
352
+ * both places the kit needs this — `SkipLink` and `createRouteAnnouncer` —
353
+ * fail in the same invisible way without it. A skip link scrolls the page
354
+ * and leaves focus in the header; a route change swaps the content and
355
+ * leaves focus on the link that is no longer there.
356
+ *
357
+ * `tabindex="-1"` makes the element focusable by script and never by Tab,
358
+ * which is the whole trick. It is left behind rather than cleaned up: a
359
+ * reader that moves out and comes back would otherwise find an unfocusable
360
+ * target the second time.
361
+ *
362
+ * @param target - The element, or the `id` of one. A missing one is a no-op.
363
+ * @returns Whether focus moved, so a caller can fall back.
364
+ */
365
+ function focusTarget(target) {
366
+ const element = typeof target === "string" ? document.getElementById(target) : target;
367
+ if (!(element instanceof HTMLElement)) return false;
368
+ if (!element.hasAttribute("tabindex")) element.setAttribute("tabindex", "-1");
369
+ element.focus();
370
+ return true;
371
+ }
372
+ //#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 };
274
374
 
275
- //# sourceMappingURL=BaseSheet-B7401rS7.js.map
375
+ //# sourceMappingURL=focus-target-D0WsXwKh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focus-target-D0WsXwKh.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/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","/**\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,39 @@ 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
+ * `ScrollArea`, a scrolling box with the two things a hand-written one
100
+ * leaves out: a fade at whichever edge has more content past it, and a
101
+ * focus stop — but only when nothing inside it can take focus.
102
+ */
103
+ export { default as ScrollArea } from './components/ScrollArea.vue';
104
+ /**
105
+ * `VirtualList`, a long list where only the rows near the viewport exist —
106
+ * and every one of them states its place in the whole, which is the half
107
+ * that is usually dropped.
108
+ */
109
+ export { default as VirtualList } from './components/VirtualList.vue';
110
+ /**
111
+ * `AnnounceHost` and `useAnnounce`, for saying something to a reader when
112
+ * nothing on screen has changed enough to say it: a filter that narrowed a
113
+ * list, a route that changed, a draft that saved itself.
114
+ */
115
+ export { default as AnnounceHost } from './components/AnnounceHost.vue';
116
+ /**
117
+ * `ErrorSummary`, the answer to "did that work?" at the top of a rejected
118
+ * form — and a way from there into each field in one press.
119
+ */
120
+ export { default as ErrorSummary } from './components/ErrorSummary.vue';
121
+ export { useAnnounce, announce } from './composables/use-announce';
122
+ /**
123
+ * `textDirection`, because every logical property and every `start`/`end`
124
+ * prop in the kit is inert until the document says which way the language
125
+ * runs. The i18n runtime sets `dir` from it; this is for an app that has
126
+ * no i18n runtime and still has more than one direction.
127
+ */
128
+ export { textDirection } from './utils/direction';
129
+ export type { TextDirection } from './utils/direction';
130
+ export type { AnnounceOptions } from './composables/use-announce';
98
131
  export { default as AvatarStack } from './components/AvatarStack.vue';
99
132
  export { default as BaseCalendar } from './components/BaseCalendar.vue';
100
133
  export { default as BaseChip } from './components/BaseChip.vue';