sb-floating-panel-vue 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -153,7 +153,7 @@ The trigger the panel is anchored to.
153
153
 
154
154
  | Prop | Type | Default | Description |
155
155
  |----------------|-----------------------|----------------|--------------------------------------------------------------------------------------------------------------|
156
- | `as` | `string \| Component` | `'button'` | Tag or component to render. `button` gets `type="button"`; `a` with `href` and components keep their own semantics; any other tag (also `a` without `href`) gets `role="button"`, `tabindex="0"` and Enter/Space |
156
+ | `as` | `string \| Component` | `'button'` | Tag or component to render. `button` gets `type="button"`; form controls (`input`, `textarea`, `select`), `a` with `href` and components keep their own semantics; any other tag (also `a` without `href`) gets `role="button"`, `tabindex="0"` and Enter/Space. A text field opens the panel on click without closing it, and keeps focus (see [Text field references](#text-field-references)) |
157
157
  | `id` | `string` | generated | `id` of the element, used to name the panel |
158
158
  | `ariaHaspopup` | `'dialog' \| 'menu' \| 'listbox' \| 'tree' \| 'grid' \| 'true' \| 'false'` | follows `role` | Value of `aria-haspopup`. When unset it follows the `role` of the panel (`alertdialog` gives `dialog`) and is left out for other roles |
159
159
 
@@ -172,7 +172,7 @@ The panel, teleported to `body`. It is rendered while open and stays mounted unt
172
172
  | `transition` | `string \| TransitionProps` | — | Name of a Vue `<Transition>` defined in your CSS, or an object of `<Transition>` props (e.g. Tailwind classes). No animation when unset |
173
173
  | `teleport` | `string \| HTMLElement \| false` | `'body'` | Teleport target (selector or element); `false` renders the panel in place |
174
174
  | `zIndex` | `number \| string` | — | `z-index` of the positioned wrapper. Use this, not a `z-*` class on the panel (see [Stacking](#stacking)) |
175
- | `autoFocus` | `boolean` | `true`, `false` with `role="tooltip"` or the `hover` / `focus` trigger | Once positioned, focuses the first `[autofocus]` or focusable element inside, else the panel itself (a menu: its first item; a listbox: its selected option) |
175
+ | `autoFocus` | `boolean` | `true`, `false` with `role="tooltip"`, the `hover` / `focus` trigger or a text field reference | Once positioned, focuses the first `[autofocus]` or focusable element inside, else the panel itself (a menu: its first item; a listbox: its selected option) |
176
176
  | `keyboardNavigation` | `boolean` | `true` | Handles the keyboard model of `role="menu"` and `role="listbox"` panels (see [Keyboard](#keyboard)). No effect with other roles |
177
177
 
178
178
  | Event | Description |
@@ -227,6 +227,7 @@ import type {
227
227
  - Moving focus outside (e.g. tabbing past the last element) closes it.
228
228
  - A container nested inside a panel counts as part of it.
229
229
  - **What is up to you.** The library does not implement the keyboard model of `tree` and `grid` panels, nor selection: a listbox option or a menu item does what its `click` handler does (e.g. set `aria-selected` and close the panel). Use valid structure: items directly inside the `menu` / `listbox`, or inside `ul` / `li` with `role="none"`.
230
+ - <a id="text-field-references"></a>**Text field references.** `<SbReference as="input">` (or `textarea`) stays a text field: no `role="button"`, Enter and Space type as usual, a click opens the panel without closing it, and focus stays in the field when the panel opens. The [combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) (autocomplete with focus kept in the field and `aria-activedescendant`) is not supported: with a `menu` or `listbox` panel the arrow keys move focus into the panel, and the console warns about it in development.
230
231
 
231
232
  ---
232
233
 
@@ -356,7 +357,7 @@ SbContainer ── provides the state ──┬─▶ SbReference
356
357
  - The lookup follows the component tree, not the DOM, so it keeps working although `SbFloating` is teleported to `body`.
357
358
  - They can sit at any depth inside the container slot, also inside your own components.
358
359
  - Used outside an `SbContainer`, they throw: `[sb-floating-panel-vue] <SbFloating> must be used inside <SbContainer>.`
359
- - In development, the console warns about a second `SbReference` or `SbFloating` in the same container and about an `as` component that renders no single root element. The warnings depend on `process.env.NODE_ENV`, which your bundler replaces: they are left out of production builds.
360
+ - In development, the console warns about a second `SbReference` or `SbFloating` in the same container about an `as` component that renders no single root element and about a text field reference of a `menu` or `listbox` panel. The warnings depend on `process.env.NODE_ENV`, which your bundler replaces: they are left out of production builds.
360
361
 
361
362
  ### Nested containers
362
363
 
@@ -1,7 +1,7 @@
1
1
  import { InjectionKey, Ref } from 'vue';
2
- import { FloatingPanel } from './composables/useSbFloatingPanel';
3
- import { NavigableRole } from './focus';
4
- import { SbTrigger } from './types';
2
+ import { SbTrigger } from '../types';
3
+ import { NavigableRole } from '../utils/navigation';
4
+ import { FloatingPanel } from './useSbFloatingPanel';
5
5
  /**
6
6
  * State shared by `SbContainer` with `SbReference` and `SbFloating`.
7
7
  */
@@ -39,14 +39,7 @@ export interface SbFloatingContext extends FloatingPanel {
39
39
  /** Registers a nested container; returns the function that unregisters it. */
40
40
  registerChild: (child: SbFloatingContext) => () => void;
41
41
  }
42
- /**
43
- * Panel roles that make the reference a popup trigger, with their `aria-haspopup` value.
44
- * Panels with these roles are also named after the reference.
45
- */
46
- export declare const POPUP_ROLES: Record<string, 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid'>;
47
42
  export declare const sbFloatingContextKey: InjectionKey<SbFloatingContext>;
48
- /** Element of a template ref: a component gives its instance, whose root element is `$el`. */
49
- export declare function toElement(el: unknown): HTMLElement | null;
50
43
  /** Counts a mounted part of the container, warning about a second one; returns the release. */
51
44
  export declare function claimPart(context: SbFloatingContext, part: 'reference' | 'floating', component: string): () => void;
52
45
  export declare function useSbFloatingContext(component: string): SbFloatingContext;
@@ -1,11 +1,34 @@
1
1
  import { Teleport as e, Transition as t, computed as n, createBlock as r, createCommentVNode as i, createElementBlock as a, createVNode as o, defineComponent as s, inject as c, mergeModels as l, mergeProps as u, nextTick as d, normalizeStyle as f, onBeforeUnmount as p, onMounted as m, onScopeDispose as h, openBlock as g, provide as _, ref as v, renderSlot as y, resolveDynamicComponent as b, toRef as x, toValue as S, unref as C, useAttrs as w, useId as T, useModel as E, watch as D, watchEffect as O, withCtx as k } from "vue";
2
2
  import { autoUpdate as A, flip as j, hide as M, limitShift as N, offset as P, shift as F, size as ee, useFloating as te } from "@floating-ui/vue";
3
+ //#region src/utils/warn.ts
4
+ var I = (() => {
5
+ try {
6
+ return process.env.NODE_ENV !== "production";
7
+ } catch {
8
+ return !1;
9
+ }
10
+ })();
11
+ function L(e) {
12
+ I && console.warn(`[sb-floating-panel-vue] ${e}`);
13
+ }
14
+ //#endregion
15
+ //#region src/composables/useSbFloatingContext.ts
16
+ var R = Symbol("SbFloatingContext");
17
+ function ne(e, t, n) {
18
+ return ++e.parts[t] > 1 && L(`<SbContainer> has more than one <${n}>: they share one open state and position, so only the last one works. Use one SbContainer for each panel.`), () => e.parts[t]--;
19
+ }
20
+ function z(e) {
21
+ let t = c(R, null);
22
+ if (!t) throw Error(`[sb-floating-panel-vue] <${e}> must be used inside <SbContainer>.`);
23
+ return t;
24
+ }
25
+ //#endregion
3
26
  //#region src/composables/useSbFloatingPanel.ts
4
- var I = (e) => `${Math.max(0, e)}px`, L = (e, t) => {
27
+ var B = (e) => `${Math.max(0, e)}px`, V = (e, t) => {
5
28
  let [n, r] = e.split("-"), i = t && (n === "top" || n === "bottom"), a = r === "start" ? i ? "100%" : "0%" : r === "end" ? i ? "0%" : "100%" : "50%";
6
29
  return n === "top" ? `${a} 100%` : n === "bottom" ? `${a} 0%` : n === "left" ? `100% ${a}` : `0% ${a}`;
7
30
  };
8
- function R(e) {
31
+ function re(e) {
9
32
  let t = v(null), r = v(null), i = n(() => {
10
33
  let t = S(e.padding), n = S(e.matchWidth), r = S(e.placement).includes("-"), i = j({
11
34
  padding: t,
@@ -20,10 +43,10 @@ function R(e) {
20
43
  ee({
21
44
  padding: t,
22
45
  apply({ rects: e, elements: t, availableWidth: r, availableHeight: i, placement: a }) {
23
- let { style: o } = t.floating, s = I(e.reference.width);
46
+ let { style: o } = t.floating, s = B(e.reference.width);
24
47
  o.setProperty("--sbf-reference-width", s);
25
48
  let c = getComputedStyle(t.floating).direction === "rtl";
26
- o.setProperty("--sbf-transform-origin", L(a, c)), o.setProperty("--sbf-available-width", I(r)), o.setProperty("--sbf-available-height", I(i)), o.width = n === !0 ? s : "", o.minWidth = n === "min" ? s : "";
49
+ o.setProperty("--sbf-transform-origin", V(a, c)), o.setProperty("--sbf-available-width", B(r)), o.setProperty("--sbf-available-height", B(i)), o.width = n === !0 ? s : "", o.minWidth = n === "min" ? s : "";
27
50
  }
28
51
  })
29
52
  ];
@@ -51,42 +74,8 @@ function R(e) {
51
74
  };
52
75
  }
53
76
  //#endregion
54
- //#region src/dev.ts
55
- var z = (() => {
56
- try {
57
- return process.env.NODE_ENV !== "production";
58
- } catch {
59
- return !1;
60
- }
61
- })();
62
- function B(e) {
63
- z && console.warn(`[sb-floating-panel-vue] ${e}`);
64
- }
65
- //#endregion
66
- //#region src/context.ts
67
- var V = {
68
- dialog: "dialog",
69
- alertdialog: "dialog",
70
- menu: "menu",
71
- listbox: "listbox",
72
- tree: "tree",
73
- grid: "grid"
74
- }, H = Symbol("SbFloatingContext");
75
- function ne(e) {
76
- let t = e && typeof e == "object" && "$el" in e ? e.$el : e;
77
- return t instanceof HTMLElement ? t : null;
78
- }
79
- function re(e, t, n) {
80
- return ++e.parts[t] > 1 && B(`<SbContainer> has more than one <${n}>: they share one open state and position, so only the last one works. Use one SbContainer for each panel.`), () => e.parts[t]--;
81
- }
82
- function ie(e) {
83
- let t = c(H, null);
84
- if (!t) throw Error(`[sb-floating-panel-vue] <${e}> must be used inside <SbContainer>.`);
85
- return t;
86
- }
87
- //#endregion
88
- //#region src/focus.ts
89
- var ae = "data-sbf-focus-guard", U = [
77
+ //#region src/utils/dom.ts
78
+ var ie = "data-sbf-focus-guard", H = [
90
79
  "a[href]",
91
80
  "button:not([disabled])",
92
81
  "input:not([disabled]):not([type=\"hidden\"])",
@@ -97,34 +86,25 @@ var ae = "data-sbf-focus-guard", U = [
97
86
  "video[controls]",
98
87
  "[tabindex]",
99
88
  "[contenteditable=\"true\"]"
100
- ].join(","), W = (e) => e.tabIndex >= 0 && !e.hasAttribute("data-sbf-focus-guard") && !e.closest("[inert]") && (e.checkVisibility?.() ?? !0);
101
- function G(e) {
102
- let t = e instanceof Element ? e.closest(U) : null;
103
- return !!t && !t.closest("[inert]");
89
+ ].join(","), ae = (e) => e.tabIndex >= 0 && !e.hasAttribute("data-sbf-focus-guard") && !e.closest("[inert]") && (e.checkVisibility?.() ?? !0);
90
+ function U(e) {
91
+ let t = e && typeof e == "object" && "$el" in e ? e.$el : e;
92
+ return t instanceof HTMLElement ? t : null;
104
93
  }
105
- function K(e) {
106
- return [...e.querySelectorAll(U)].filter(W);
94
+ function oe(e) {
95
+ let t = e instanceof Element ? e.closest(H) : null;
96
+ return !!t && !t.closest("[inert]");
107
97
  }
108
- var q = {
109
- menu: "[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]",
110
- listbox: "[role=\"option\"]"
111
- }, oe = (e) => !!e && Object.hasOwn(q, e);
112
- function se(e, t) {
113
- return [...e.querySelectorAll(q[t])].filter((n) => n.closest(`[role="${t}"]`) === e && !n.matches(":disabled") && (n.checkVisibility?.() ?? !0));
98
+ var se = (e) => e.matches(H) && !e.closest("[inert]");
99
+ function W(e) {
100
+ return [...e.querySelectorAll(H)].filter(ae);
114
101
  }
115
- var ce = (e) => e instanceof Element && !!e.closest("input, textarea, select, [contenteditable]:not([contenteditable=\"false\"])"), le = (e) => getComputedStyle(e).direction === "rtl" ? {
116
- open: "ArrowLeft",
117
- close: "ArrowRight"
118
- } : {
119
- open: "ArrowRight",
120
- close: "ArrowLeft"
121
- };
122
- function ue(e, t) {
123
- return K(e.ownerDocument).find((n) => !!(e.compareDocumentPosition(n) & Node.DOCUMENT_POSITION_FOLLOWING) && !e.contains(n) && !t(n));
102
+ function G(e, t) {
103
+ return W(e.ownerDocument).find((n) => !!(e.compareDocumentPosition(n) & Node.DOCUMENT_POSITION_FOLLOWING) && !e.contains(n) && !t(n));
124
104
  }
125
105
  //#endregion
126
106
  //#region src/components/SbContainer.vue?vue&type=script&setup=true&lang.ts
127
- var J = [], Y = /* @__PURE__ */ s({
107
+ var K = [], q = /* @__PURE__ */ s({
128
108
  inheritAttrs: !1,
129
109
  __name: "SbContainer",
130
110
  props: /*@__PURE__*/ l({
@@ -179,15 +159,15 @@ var J = [], Y = /* @__PURE__ */ s({
179
159
  let d = (e) => {
180
160
  clearTimeout(u), !(e && i.disabled) && e !== l.value && (a.value = e);
181
161
  }, f = () => d(!0), m = () => d(!1), g = () => d(!l.value), b = (e) => {
182
- if (i.trigger !== "hover" || e.pointerType === "touch") return;
183
- let t = e.type === "pointerenter";
162
+ if (e.pointerType === "touch" || (O?.hover(e), i.trigger !== "hover")) return;
163
+ let t = e.type === "pointerenter" || k.contains(e.relatedTarget);
184
164
  if (clearTimeout(u), t === l.value) return;
185
165
  let { delay: n } = i, r = typeof n == "number" ? {
186
166
  open: n,
187
167
  close: n
188
168
  } : n, a = (t ? r.open : r.close) ?? (t ? 300 : 150);
189
169
  u = setTimeout(() => d(t), a);
190
- }, S = R({
170
+ }, S = re({
191
171
  isOpen: l,
192
172
  placement: () => i.placement,
193
173
  strategy: () => i.strategy,
@@ -195,7 +175,7 @@ var J = [], Y = /* @__PURE__ */ s({
195
175
  padding: () => i.padding,
196
176
  matchWidth: () => i.matchWidth,
197
177
  hideWhenDetached: () => i.hideWhenDetached
198
- }), w = /* @__PURE__ */ new Set(), O = c(H, null), k = {
178
+ }), w = /* @__PURE__ */ new Set(), O = c(R, null), k = {
199
179
  ...S,
200
180
  isOpen: l,
201
181
  disabled: x(i, "disabled"),
@@ -219,14 +199,14 @@ var J = [], Y = /* @__PURE__ */ s({
219
199
  contains: (e) => e instanceof Node ? S.reference.value?.contains(e) || S.floating.value?.contains(e) ? !0 : [...w].some((t) => t.contains(e)) : !1,
220
200
  registerChild: (e) => (w.add(e), () => w.delete(e))
221
201
  };
222
- O && h(O.registerChild(k)), _(H, k), p(() => k.isUnmounting = !0);
202
+ O && h(O.registerChild(k)), _(R, k), p(() => k.isUnmounting = !0);
223
203
  let A = (e) => {
224
204
  let t = e.composedPath()[0] ?? e.target;
225
- return J.findLast((e) => e.contains(t)) ?? J[J.length - 1];
205
+ return K.findLast((e) => e.contains(t)) ?? K[K.length - 1];
226
206
  }, j = (e) => {
227
207
  e.key === "Escape" && A(e) === k && (!i.closeOnEscape || e.defaultPrevented || e.isComposing || (e.preventDefault(), m()));
228
208
  }, M = (e) => {
229
- k.contains(e) || (k.skipFocusReturn = G(e), m());
209
+ k.contains(e) || (k.skipFocusReturn = oe(e), m());
230
210
  }, N, P = (e) => {
231
211
  if (N?.(), !i.closeOnClickOutside) return;
232
212
  let t = e.composedPath()[0] ?? e.target;
@@ -245,8 +225,8 @@ var J = [], Y = /* @__PURE__ */ s({
245
225
  i.closeOnFocusOut && (k.contains(e.composedPath()[0] ?? e.target) || m());
246
226
  };
247
227
  return D(l, (e, t, n) => {
248
- e && typeof document < "u" && (J.push(k), document.addEventListener("keydown", j), document.addEventListener("pointerdown", P, !0), document.addEventListener("focusin", F), n(() => {
249
- document.removeEventListener("keydown", j), document.removeEventListener("pointerdown", P, !0), document.removeEventListener("focusin", F), N?.(), J.splice(J.indexOf(k), 1);
228
+ e && typeof document < "u" && (K.push(k), document.addEventListener("keydown", j), document.addEventListener("pointerdown", P, !0), document.addEventListener("focusin", F), n(() => {
229
+ document.removeEventListener("keydown", j), document.removeEventListener("pointerdown", P, !0), document.removeEventListener("focusin", F), N?.(), K.splice(K.indexOf(k), 1);
250
230
  }));
251
231
  }, { immediate: !0 }), D(l, (e) => o(e ? "open" : "close")), D(S.referenceHidden, (e) => {
252
232
  e && i.hideWhenDetached === "close" && m();
@@ -272,7 +252,27 @@ var J = [], Y = /* @__PURE__ */ s({
272
252
  referenceId: k.referenceId.value
273
253
  });
274
254
  }
275
- }), X = /* @__PURE__ */ s({
255
+ }), J = {
256
+ dialog: "dialog",
257
+ alertdialog: "dialog",
258
+ menu: "menu",
259
+ listbox: "listbox",
260
+ tree: "tree",
261
+ grid: "grid"
262
+ }, Y = {
263
+ menu: "[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]",
264
+ listbox: "[role=\"option\"]"
265
+ }, ce = (e) => !!e && Object.hasOwn(Y, e);
266
+ function le(e, t) {
267
+ return [...e.querySelectorAll(Y[t])].filter((n) => n.closest(`[role="${t}"]`) === e && !n.matches(":disabled") && (n.checkVisibility?.() ?? !0));
268
+ }
269
+ var X = (e) => e instanceof Element && !!e.closest("input, textarea, select, [contenteditable]:not([contenteditable=\"false\"])"), ue = (e) => getComputedStyle(e).direction === "rtl" ? {
270
+ open: "ArrowLeft",
271
+ close: "ArrowRight"
272
+ } : {
273
+ open: "ArrowRight",
274
+ close: "ArrowLeft"
275
+ }, de = /* @__PURE__ */ s({
276
276
  __name: "SbReference",
277
277
  props: {
278
278
  as: { default: "button" },
@@ -280,59 +280,64 @@ var J = [], Y = /* @__PURE__ */ s({
280
280
  ariaHaspopup: { default: void 0 }
281
281
  },
282
282
  setup(e) {
283
- let t = e, i = w(), a = ie("SbReference"), { isOpen: o, disabled: s, floatingId: c, floatingRole: l } = a, u = re(a, "reference", "SbReference"), d = () => typeof t.as != "string" || t.as === "button" || t.as === "a" && i.href != null, f = `${T()}-reference`, h = n(() => t.id ?? f);
283
+ let t = e, i = w(), a = z("SbReference"), { isOpen: o, disabled: s, floatingId: c, floatingRole: l } = a, u = ne(a, "reference", "SbReference"), d = [
284
+ "button",
285
+ "input",
286
+ "textarea",
287
+ "select"
288
+ ], f = () => typeof t.as != "string" || d.includes(t.as) || t.as === "a" && i.href != null, h = `${T()}-reference`, _ = n(() => t.id ?? h);
284
289
  O(() => {
285
- a.referenceId.value = h.value;
290
+ a.referenceId.value = _.value;
286
291
  }), m(() => {
287
- a.reference.value || B("<SbReference> rendered no element, so the panel cannot be positioned: a component in `as` must render a single root element.");
292
+ let e = a.reference.value;
293
+ e ? X(e) && a.navigation.value && L(`<SbReference> is a text field with a role="${a.navigation.value}" panel: the arrow keys move focus into the panel, so typing stops there. The combobox pattern (focus kept in the field with aria-activedescendant) is not supported.`) : L("<SbReference> rendered no element, so the panel cannot be positioned: a component in `as` must render a single root element.");
288
294
  }), p(() => {
289
- a.referenceId.value === h.value && (a.referenceId.value = void 0), u(), !a.parts.reference && !a.isUnmounting && a.close();
295
+ a.referenceId.value === _.value && (a.referenceId.value = void 0), u(), !a.parts.reference && !a.isUnmounting && a.close();
290
296
  });
291
- let _ = n(() => t.ariaHaspopup ?? (l.value && Object.hasOwn(V, l.value) ? V[l.value] : void 0)), v = (e) => {
292
- a.reference.value = ne(e);
293
- }, x = () => {
294
- a.trigger.value === "click" && a.toggle();
295
- }, S = n(() => l.value === "tooltip"), E = () => a.trigger.value === "hover" || a.trigger.value === "focus", D = (e) => {
296
- E() && !a.contains(e.relatedTarget) && a.open();
297
- }, A = (e) => {
298
- E() && !a.contains(e.relatedTarget) && a.close();
299
- }, j = (e) => {
300
- let t = a.floating.value, n = t && K(t)[0];
301
- n && (e.preventDefault(), n.focus());
297
+ let v = n(() => t.ariaHaspopup ?? (l.value && Object.hasOwn(J, l.value) ? J[l.value] : void 0)), x = (e) => {
298
+ a.reference.value = U(e);
299
+ }, S = () => {
300
+ a.trigger.value === "click" && (X(a.reference.value) ? a.open() : a.toggle());
301
+ }, E = n(() => l.value === "tooltip"), D = () => a.trigger.value === "hover" || a.trigger.value === "focus", A = () => a.parent?.navigation.value === "menu", j = (e) => {
302
+ D() && !A() && !a.contains(e.relatedTarget) && a.open();
302
303
  }, M = (e) => {
304
+ D() && !a.contains(e.relatedTarget) && a.close();
305
+ }, N = (e) => {
306
+ let t = a.floating.value, n = t && W(t)[0];
307
+ n && (e.preventDefault(), n.focus());
308
+ }, P = (e) => {
303
309
  if (a.navigation.value && !s.value && a.trigger.value !== "manual") {
304
- if (a.parent?.navigation.value === "menu") {
310
+ if (A()) {
305
311
  let t = e.currentTarget;
306
- return e.key === le(t).open ? "first" : void 0;
312
+ return e.key === ue(t).open ? "first" : void 0;
307
313
  }
308
314
  return e.key === "ArrowDown" ? "first" : e.key === "ArrowUp" ? "last" : void 0;
309
315
  }
310
- }, N = (e) => {
311
- let t = a.parent?.navigation.value === "menu";
312
- if (e.key === "Tab" && !e.shiftKey && o.value && !t) {
313
- j(e);
316
+ }, F = (e) => {
317
+ if (e.key === "Tab" && !e.shiftKey && o.value && !A()) {
318
+ N(e);
314
319
  return;
315
320
  }
316
- let n = M(e);
317
- n ? (e.preventDefault(), a.focusRequest.value = n, a.open()) : d() || e.key !== "Enter" && e.key !== " " || (e.preventDefault(), x());
321
+ let t = P(e);
322
+ t ? (e.preventDefault(), a.focusRequest.value = t, a.open()) : f() || e.key !== "Enter" && e.key !== " " || (e.preventDefault(), S());
318
323
  };
319
324
  return (e, n) => (g(), r(b(t.as), {
320
- id: h.value,
321
- ref: v,
325
+ id: _.value,
326
+ ref: x,
322
327
  type: t.as === "button" ? "button" : void 0,
323
- role: d() ? void 0 : "button",
324
- tabindex: d() ? void 0 : C(s) ? -1 : 0,
328
+ role: f() ? void 0 : "button",
329
+ tabindex: f() ? void 0 : C(s) ? -1 : 0,
325
330
  disabled: t.as === "button" && C(s) ? !0 : void 0,
326
331
  "aria-disabled": t.as !== "button" && C(s) ? "true" : void 0,
327
- "aria-haspopup": _.value,
328
- "aria-expanded": S.value ? void 0 : C(o) ? "true" : "false",
329
- "aria-controls": C(o) && !S.value ? C(c) : void 0,
330
- "aria-describedby": C(o) && S.value ? C(c) : void 0,
332
+ "aria-haspopup": v.value,
333
+ "aria-expanded": E.value ? void 0 : C(o) ? "true" : "false",
334
+ "aria-controls": C(o) && !E.value ? C(c) : void 0,
335
+ "aria-describedby": C(o) && E.value ? C(c) : void 0,
331
336
  "data-sbf-state": C(o) ? "open" : "closed",
332
- onClick: x,
333
- onKeydown: N,
334
- onFocusin: D,
335
- onFocusout: A,
337
+ onClick: S,
338
+ onKeydown: F,
339
+ onFocusin: j,
340
+ onFocusout: M,
336
341
  onPointerenter: C(a).hover,
337
342
  onPointerleave: C(a).hover
338
343
  }, {
@@ -377,24 +382,26 @@ var J = [], Y = /* @__PURE__ */ s({
377
382
  },
378
383
  emits: ["after-enter", "after-leave"],
379
384
  setup(s, { emit: c }) {
380
- let l = s, m = c, _ = w(), x = ie("SbFloating");
381
- p(re(x, "floating", "SbFloating"));
382
- let { isOpen: S, floatingId: T, referenceId: E, side: A, align: j } = x;
385
+ let l = s, _ = c, x = w(), S = z("SbFloating");
386
+ p(ne(S, "floating", "SbFloating"));
387
+ let { isOpen: T, floatingId: E, referenceId: A, side: j, align: M } = S;
383
388
  O(() => {
384
- x.floatingRole.value = l.role, x.navigation.value = l.keyboardNavigation && oe(l.role) ? l.role : null;
389
+ S.floatingRole.value = l.role, S.navigation.value = l.keyboardNavigation && ce(l.role) ? l.role : null;
390
+ }), m(() => {
391
+ l.role === "tooltip" && S.trigger.value === "click" && L("<SbFloating role=\"tooltip\"> opens on click: a tooltip shows on hover and focus. Set trigger=\"hover\" or \"focus\" on SbContainer, or use another role for a panel opened by click.");
385
392
  });
386
- let M = v(S.value), N = v(null), P = (e) => {
387
- x.floating.value = e instanceof HTMLElement ? e : null;
388
- }, F = (e) => {
389
- N.value = ne(e);
390
- }, ee = n(() => ({
391
- ...x.floatingStyles.value,
393
+ let N = v(T.value), P = v(null), F = (e) => {
394
+ S.floating.value = e instanceof HTMLElement ? e : null;
395
+ }, ee = (e) => {
396
+ P.value = U(e);
397
+ }, te = n(() => ({
398
+ ...S.floatingStyles.value,
392
399
  ...l.zIndex === void 0 ? {} : { zIndex: l.zIndex }
393
- })), te = () => ({
394
- "aria-labelledby": l.role && Object.hasOwn(V, l.role) && _["aria-label"] == null ? E.value : void 0,
395
- ..._
396
- }), I = n(() => l.autoFocus ?? (l.role !== "tooltip" && x.trigger.value !== "hover" && x.trigger.value !== "focus")), L = n(() => S.value && l.teleport !== !1 && l.role !== "tooltip"), R = {
397
- [ae]: "",
400
+ })), I = () => ({
401
+ "aria-labelledby": l.role && Object.hasOwn(J, l.role) && x["aria-label"] == null ? A.value : void 0,
402
+ ...x
403
+ }), R = n(() => l.autoFocus ?? (l.role !== "tooltip" && S.trigger.value !== "hover" && S.trigger.value !== "focus" && !X(S.reference.value))), B = n(() => T.value && l.teleport !== !1 && l.role !== "tooltip"), V = {
404
+ [ie]: "",
398
405
  tabindex: 0,
399
406
  style: {
400
407
  position: "fixed",
@@ -402,63 +409,76 @@ var J = [], Y = /* @__PURE__ */ s({
402
409
  outline: "none",
403
410
  pointerEvents: "none"
404
411
  }
405
- }, z = n(() => {
412
+ }, re = n(() => {
406
413
  let { transition: e } = l;
407
414
  return e ? typeof e == "string" ? { name: e } : e : { css: !1 };
408
- }), B = (e) => e.setAttribute("data-sbf-state", "closed"), H = () => m("after-enter"), U = () => {
409
- S.value || (M.value = !1), m("after-leave");
410
- }, W = (e) => e instanceof Node && !!N.value?.contains(e), G = (e) => {
411
- let t = N.value;
415
+ }), H = (e) => e.setAttribute("data-sbf-state", "closed"), ae = () => _("after-enter"), oe = () => {
416
+ T.value || (N.value = !1), _("after-leave");
417
+ }, K = (e) => e instanceof Node && !!P.value?.contains(e), q = (e) => {
418
+ let t = P.value;
412
419
  if (!t) return;
413
- let n = K(t);
420
+ let n = W(t);
414
421
  ((e === "first" ? n[0] : n[n.length - 1]) ?? t).focus({ preventScroll: !0 });
415
- }, q = (e) => {
416
- W(e.relatedTarget) ? x.reference.value?.focus() : G("first");
417
- }, J = (e) => {
418
- if (!W(e.relatedTarget)) {
419
- G("last");
422
+ }, Y = (e) => {
423
+ K(e.relatedTarget) ? S.reference.value?.focus() : q("first");
424
+ }, de = (e) => {
425
+ if (!K(e.relatedTarget)) {
426
+ q("last");
420
427
  return;
421
428
  }
422
- let t = x.reference.value;
423
- ((t && ue(t, (e) => x.contains(e))) ?? t)?.focus();
424
- }, Y = () => {
425
- let e = x.navigation.value;
426
- return N.value && e ? se(N.value, e) : [];
427
- }, X = (e) => {
428
- let t = N.value;
429
- if (!t) return;
430
- let n = Y();
431
- (n.find((e) => e.getAttribute("aria-selected") === "true") ?? (e === "last" ? n[n.length - 1] : e ? n[0] : void 0) ?? t.querySelector("[autofocus]") ?? n[0] ?? K(t)[0] ?? t).focus({ preventScroll: !0 });
429
+ let t = S.reference.value;
430
+ ((t && G(t, (e) => S.contains(e))) ?? t)?.focus();
432
431
  }, Z = () => {
433
- let e = x.focusRequest.value;
434
- return x.focusRequest.value = null, e;
432
+ let e = S.navigation.value;
433
+ return P.value && e ? le(P.value, e) : [];
434
+ }, fe = {
435
+ menu: "role=\"menuitem\", \"menuitemcheckbox\" or \"menuitemradio\"",
436
+ listbox: "role=\"option\""
437
+ }, pe = !1, me = (e) => {
438
+ let t = S.navigation.value;
439
+ if (pe || !t) return;
440
+ let n = e.length ? e.every(se) ? null : "has items that cannot take focus, so the arrow keys cannot move to them: make each one a button, or give it tabindex=\"-1\"." : `has no items the arrow keys can move to: items need ${fe[t]} and must not be disabled.`;
441
+ n && (pe = !0, L(`<SbFloating role="${t}"> ${n}`));
442
+ }, he = (e) => {
443
+ let t = P.value;
444
+ if (!t) return;
445
+ let n = Z();
446
+ e && me(n), (n.find((e) => e.getAttribute("aria-selected") === "true") ?? (e === "last" ? n[n.length - 1] : e ? n[0] : void 0) ?? t.querySelector("[autofocus]") ?? n[0] ?? W(t)[0] ?? t).focus({ preventScroll: !0 });
447
+ }, ge = () => {
448
+ let e = S.focusRequest.value;
449
+ return S.focusRequest.value = null, e;
435
450
  }, Q = "", $;
436
451
  h(() => clearTimeout($));
437
- let de = (e, t, n) => {
452
+ let _e = (e, t, n) => {
438
453
  Q += n.toLowerCase(), clearTimeout($), $ = setTimeout(() => Q = "", 500);
439
454
  let r = [...Q].every((e) => e === Q[0]) ? Q[0] : Q, i = Math.max(t, 0), a = [...e.slice(i), ...e.slice(0, i)];
440
455
  return r.length === 1 && t >= 0 && a.push(a.shift()), a.find((e) => e.textContent?.trim().toLowerCase().startsWith(r));
441
- }, fe = (e) => {
442
- let t = x, n = [t];
456
+ }, ve = (e) => {
457
+ let t = S, n = [t];
443
458
  for (; t.parent?.navigation.value === "menu";) t = t.parent, n.push(t);
444
- let r = t.reference.value, i = e ? r : (r && ue(r, (e) => t.contains(e))) ?? r;
459
+ let r = t.reference.value, i = e ? r : (r && G(r, (e) => t.contains(e))) ?? r;
445
460
  for (let e of n) e.skipFocusReturn = !0, e.close();
446
461
  i?.focus();
447
- }, pe = (e) => {
448
- let t = x.navigation.value;
449
- if (!t || e.defaultPrevented || e.isComposing || ce(e.target)) return;
450
- let n = Y(), r = n.findIndex((t) => t.contains(e.target)), i = n.length - 1, a = t === "menu", o;
451
- if (e.key === "ArrowDown") o = n[r < i ? r + 1 : a ? 0 : i];
462
+ }, ye = (e) => {
463
+ let t = S.navigation.value;
464
+ if (!t || e.defaultPrevented || e.isComposing || X(e.target)) return;
465
+ let n = Z(), r = n.findIndex((t) => t.contains(e.target)), i = n.length - 1, a = t === "menu", o;
466
+ if ([
467
+ "ArrowDown",
468
+ "ArrowUp",
469
+ "Home",
470
+ "End"
471
+ ].includes(e.key) && me(n), e.key === "ArrowDown") o = n[r < i ? r + 1 : a ? 0 : i];
452
472
  else if (e.key === "ArrowUp") o = n[r > 0 ? r - 1 : a || r < 0 ? i : 0];
453
473
  else if (e.key === "Home") o = n[0];
454
474
  else if (e.key === "End") o = n[i];
455
475
  else if (e.key === "Tab" && t === "menu") {
456
- e.preventDefault(), fe(e.shiftKey);
476
+ e.preventDefault(), ve(e.shiftKey);
457
477
  return;
458
- } else if (t === "menu" && x.parent?.navigation.value === "menu" && N.value && e.key === le(N.value).close) {
459
- e.preventDefault(), x.close();
478
+ } else if (t === "menu" && S.parent?.navigation.value === "menu" && P.value && e.key === ue(P.value).close) {
479
+ e.preventDefault(), S.close();
460
480
  return;
461
- } else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey && (e.key !== " " || Q)) e.preventDefault(), o = de(n, r, e.key);
481
+ } else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey && (e.key !== " " || Q)) e.preventDefault(), o = _e(n, r, e.key);
462
482
  else if ((e.key === "Enter" || e.key === " ") && e.target === n[r]) {
463
483
  let t = n[r];
464
484
  if (t.matches("button, a[href], input, summary")) return;
@@ -467,46 +487,46 @@ var J = [], Y = /* @__PURE__ */ s({
467
487
  }
468
488
  o && (e.preventDefault(), o.focus());
469
489
  };
470
- return D(S, (e) => {
471
- let t = x.skipFocusReturn;
472
- if (x.skipFocusReturn = !1, e) {
473
- M.value = !0;
490
+ return D(T, (e) => {
491
+ let t = S.skipFocusReturn;
492
+ if (S.skipFocusReturn = !1, e) {
493
+ N.value = !0;
474
494
  return;
475
495
  }
476
496
  let n = document.activeElement;
477
- !t && n && x.floating.value?.contains(n) && x.reference.value?.focus({ preventScroll: !0 });
478
- }), D(() => S.value && x.isPositioned.value, async (e) => {
497
+ !t && n && S.floating.value?.contains(n) && S.reference.value?.focus({ preventScroll: !0 });
498
+ }), D(() => T.value && S.isPositioned.value, async (e) => {
479
499
  if (!e) return;
480
- let t = Z();
481
- (t || I.value) && (await d(), X(t));
482
- }), D(x.focusRequest, (e) => {
483
- e && S.value && x.isPositioned.value && X(Z());
500
+ let t = ge();
501
+ (t || R.value) && (await d(), he(t));
502
+ }), D(S.focusRequest, (e) => {
503
+ e && T.value && S.isPositioned.value && he(ge());
484
504
  }), (n, s) => (g(), r(e, {
485
505
  to: l.teleport || "body",
486
506
  disabled: l.teleport === !1
487
- }, [M.value ? (g(), a("div", {
507
+ }, [N.value ? (g(), a("div", {
488
508
  key: 0,
489
- ref: P,
490
- style: f(ee.value),
491
- onPointerenter: s[0] ||= (...e) => C(x).hover && C(x).hover(...e),
492
- onPointerleave: s[1] ||= (...e) => C(x).hover && C(x).hover(...e)
509
+ ref: F,
510
+ style: f(te.value),
511
+ onPointerenter: s[0] ||= (...e) => C(S).hover && C(S).hover(...e),
512
+ onPointerleave: s[1] ||= (...e) => C(S).hover && C(S).hover(...e)
493
513
  }, [
494
- L.value ? (g(), a("span", u({ key: 0 }, R, { onFocus: q }), null, 16)) : i("", !0),
495
- o(t, u(z.value, {
514
+ B.value ? (g(), a("span", u({ key: 0 }, V, { onFocus: Y }), null, 16)) : i("", !0),
515
+ o(t, u(re.value, {
496
516
  appear: "",
497
- onBeforeLeave: B,
498
- onAfterEnter: H,
499
- onAfterLeave: U
517
+ onBeforeLeave: H,
518
+ onAfterEnter: ae,
519
+ onAfterLeave: oe
500
520
  }), {
501
- default: k(() => [C(S) ? (g(), r(b(l.as), u({ key: 0 }, te(), {
502
- id: C(T),
503
- ref: F,
521
+ default: k(() => [C(T) ? (g(), r(b(l.as), u({ key: 0 }, I(), {
522
+ id: C(E),
523
+ ref: ee,
504
524
  role: l.role ?? void 0,
505
- tabindex: I.value ? -1 : void 0,
525
+ tabindex: R.value ? -1 : void 0,
506
526
  "data-sbf-state": "open",
507
- "data-sbf-side": C(A),
508
- "data-sbf-align": C(j),
509
- onKeydown: pe
527
+ "data-sbf-side": C(j),
528
+ "data-sbf-align": C(M),
529
+ onKeydown: ye
510
530
  }), {
511
531
  default: k(() => [y(n.$slots, "default")]),
512
532
  _: 3
@@ -519,9 +539,9 @@ var J = [], Y = /* @__PURE__ */ s({
519
539
  ])) : i("", !0)]),
520
540
  _: 3
521
541
  }, 16),
522
- L.value ? (g(), a("span", u({ key: 1 }, R, { onFocus: J }), null, 16)) : i("", !0)
542
+ B.value ? (g(), a("span", u({ key: 1 }, V, { onFocus: de }), null, 16)) : i("", !0)
523
543
  ], 36)) : i("", !0)], 8, ["to", "disabled"]));
524
544
  }
525
545
  });
526
546
  //#endregion
527
- export { Y as SbContainer, Z as SbFloating, X as SbReference };
547
+ export { q as SbContainer, Z as SbFloating, de as SbReference };
@@ -1 +1 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("vue"),require("@floating-ui/vue")):typeof define==`function`&&define.amd?define([`exports`,`vue`,`@floating-ui/vue`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.SbFloatingPanelVue={},e.Vue,e.FloatingUIVue))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=e=>`${Math.max(0,e)}px`,i=(e,t)=>{let[n,r]=e.split(`-`),i=t&&(n===`top`||n===`bottom`),a=r===`start`?i?`100%`:`0%`:r===`end`?i?`0%`:`100%`:`50%`;return n===`top`?`${a} 100%`:n===`bottom`?`${a} 0%`:n===`left`?`100% ${a}`:`0% ${a}`};function a(e){let a=(0,t.ref)(null),o=(0,t.ref)(null),s=(0,t.computed)(()=>{let a=(0,t.toValue)(e.padding),o=(0,t.toValue)(e.matchWidth),s=(0,t.toValue)(e.placement).includes(`-`),c=(0,n.flip)({padding:a,crossAxis:`alignment`,fallbackAxisSideDirection:`end`}),l=(0,n.shift)({padding:a,limiter:(0,n.limitShift)()}),u=[(0,n.offset)((0,t.toValue)(e.offset)),...s?[c,l]:[l,c],(0,n.size)({padding:a,apply({rects:e,elements:t,availableWidth:n,availableHeight:a,placement:s}){let{style:c}=t.floating,l=r(e.reference.width);c.setProperty(`--sbf-reference-width`,l);let u=getComputedStyle(t.floating).direction===`rtl`;c.setProperty(`--sbf-transform-origin`,i(s,u)),c.setProperty(`--sbf-available-width`,r(n)),c.setProperty(`--sbf-available-height`,r(a)),c.width=o===!0?l:``,c.minWidth=o===`min`?l:``}})];return(0,t.toValue)(e.hideWhenDetached)&&u.push((0,n.hide)({padding:a})),u}),{floatingStyles:c,middlewareData:l,placement:u,isPositioned:d,update:f}=(0,n.useFloating)(a,o,{open:()=>(0,t.toValue)(e.isOpen),placement:()=>(0,t.toValue)(e.placement),strategy:()=>(0,t.toValue)(e.strategy),middleware:s,whileElementsMounted:n.autoUpdate}),p=(0,t.computed)(()=>!!l.value.hide?.referenceHidden);return{reference:a,floating:o,floatingStyles:(0,t.computed)(()=>({...c.value,...p.value?{visibility:`hidden`}:{}})),placement:u,side:(0,t.computed)(()=>u.value.split(`-`)[0]),align:(0,t.computed)(()=>u.value.split(`-`)[1]),isPositioned:d,referenceHidden:p,update:f}}var o=(()=>{try{return process.env.NODE_ENV!==`production`}catch{return!1}})();function s(e){o&&console.warn(`[sb-floating-panel-vue] ${e}`)}var c={dialog:`dialog`,alertdialog:`dialog`,menu:`menu`,listbox:`listbox`,tree:`tree`,grid:`grid`},l=Symbol(`SbFloatingContext`);function u(e){let t=e&&typeof e==`object`&&`$el`in e?e.$el:e;return t instanceof HTMLElement?t:null}function d(e,t,n){return++e.parts[t]>1&&s(`<SbContainer> has more than one <${n}>: they share one open state and position, so only the last one works. Use one SbContainer for each panel.`),()=>e.parts[t]--}function f(e){let n=(0,t.inject)(l,null);if(!n)throw Error(`[sb-floating-panel-vue] <${e}> must be used inside <SbContainer>.`);return n}var p=`data-sbf-focus-guard`,m=[`a[href]`,`button:not([disabled])`,`input:not([disabled]):not([type="hidden"])`,`select:not([disabled])`,`textarea:not([disabled])`,`summary`,`audio[controls]`,`video[controls]`,`[tabindex]`,`[contenteditable="true"]`].join(`,`),h=e=>e.tabIndex>=0&&!e.hasAttribute(`data-sbf-focus-guard`)&&!e.closest(`[inert]`)&&(e.checkVisibility?.()??!0);function g(e){let t=e instanceof Element?e.closest(m):null;return!!t&&!t.closest(`[inert]`)}function _(e){return[...e.querySelectorAll(m)].filter(h)}var v={menu:`[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]`,listbox:`[role="option"]`},y=e=>!!e&&Object.hasOwn(v,e);function b(e,t){return[...e.querySelectorAll(v[t])].filter(n=>n.closest(`[role="${t}"]`)===e&&!n.matches(`:disabled`)&&(n.checkVisibility?.()??!0))}var x=e=>e instanceof Element&&!!e.closest(`input, textarea, select, [contenteditable]:not([contenteditable="false"])`),S=e=>getComputedStyle(e).direction===`rtl`?{open:`ArrowLeft`,close:`ArrowRight`}:{open:`ArrowRight`,close:`ArrowLeft`};function C(e,t){return _(e.ownerDocument).find(n=>!!(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)&&!e.contains(n)&&!t(n))}var w=[],T=(0,t.defineComponent)({inheritAttrs:!1,__name:`SbContainer`,props:(0,t.mergeModels)({placement:{default:`bottom-start`},strategy:{default:`absolute`},offset:{default:8},padding:{default:8},matchWidth:{type:[Boolean,String],default:!1},hideWhenDetached:{type:[Boolean,String],default:!1},defaultOpen:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},trigger:{default:`click`},delay:{default:()=>({open:300,close:150})},closeOnEscape:{type:Boolean,default:!0},closeOnClickOutside:{type:Boolean,default:!0},closeOnFocusOut:{type:Boolean,default:!0}},{open:{type:Boolean,default:void 0},openModifiers:{}}),emits:(0,t.mergeModels)([`open`,`close`],[`update:open`]),setup(e,{expose:n,emit:r}){let i=e,o=(0,t.useModel)(e,`open`),s=r,c=i.defaultOpen,u=(0,t.computed)(()=>!i.disabled&&(o.value??c)),d;(0,t.onScopeDispose)(()=>clearTimeout(d));let f=e=>{clearTimeout(d),!(e&&i.disabled)&&e!==u.value&&(o.value=e)},p=()=>f(!0),m=()=>f(!1),h=()=>f(!u.value),_=e=>{if(i.trigger!==`hover`||e.pointerType===`touch`)return;let t=e.type===`pointerenter`;if(clearTimeout(d),t===u.value)return;let{delay:n}=i,r=typeof n==`number`?{open:n,close:n}:n,a=(t?r.open:r.close)??(t?300:150);d=setTimeout(()=>f(t),a)},v=a({isOpen:u,placement:()=>i.placement,strategy:()=>i.strategy,offset:()=>i.offset,padding:()=>i.padding,matchWidth:()=>i.matchWidth,hideWhenDetached:()=>i.hideWhenDetached}),y=new Set,b=(0,t.inject)(l,null),x={...v,isOpen:u,disabled:(0,t.toRef)(i,`disabled`),trigger:(0,t.toRef)(i,`trigger`),floatingId:`${(0,t.useId)()}-floating`,referenceId:(0,t.ref)(),floatingRole:(0,t.ref)(`dialog`),navigation:(0,t.ref)(null),focusRequest:(0,t.ref)(null),parent:b,skipFocusReturn:!1,parts:{reference:0,floating:0},isUnmounting:!1,open:p,close:m,toggle:h,hover:_,contains:e=>e instanceof Node?v.reference.value?.contains(e)||v.floating.value?.contains(e)?!0:[...y].some(t=>t.contains(e)):!1,registerChild:e=>(y.add(e),()=>y.delete(e))};b&&(0,t.onScopeDispose)(b.registerChild(x)),(0,t.provide)(l,x),(0,t.onBeforeUnmount)(()=>x.isUnmounting=!0);let S=e=>{let t=e.composedPath()[0]??e.target;return w.findLast(e=>e.contains(t))??w[w.length-1]},C=e=>{e.key===`Escape`&&S(e)===x&&(!i.closeOnEscape||e.defaultPrevented||e.isComposing||(e.preventDefault(),m()))},T=e=>{x.contains(e)||(x.skipFocusReturn=g(e),m())},E,D=e=>{if(E?.(),!i.closeOnClickOutside)return;let t=e.composedPath()[0]??e.target;if(e.pointerType!==`touch`){T(t);return}if(x.contains(t))return;let n=e=>{E?.(),T(e.composedPath()[0]??e.target)};document.addEventListener(`click`,n,!0),E=()=>{document.removeEventListener(`click`,n,!0),E=void 0}},O=e=>{i.closeOnFocusOut&&(x.contains(e.composedPath()[0]??e.target)||m())};return(0,t.watch)(u,(e,t,n)=>{e&&typeof document<`u`&&(w.push(x),document.addEventListener(`keydown`,C),document.addEventListener(`pointerdown`,D,!0),document.addEventListener(`focusin`,O),n(()=>{document.removeEventListener(`keydown`,C),document.removeEventListener(`pointerdown`,D,!0),document.removeEventListener(`focusin`,O),E?.(),w.splice(w.indexOf(x),1)}))},{immediate:!0}),(0,t.watch)(u,e=>s(e?`open`:`close`)),(0,t.watch)(v.referenceHidden,e=>{e&&i.hideWhenDetached===`close`&&m()}),(0,t.watch)(()=>i.disabled,e=>{e&&(o.value??c)&&(o.value=!1)}),n({get isOpen(){return u.value},open:p,close:m,toggle:h,update:v.update}),(e,n)=>(0,t.renderSlot)(e.$slots,`default`,{isOpen:u.value,open:p,close:m,toggle:h,placement:(0,t.unref)(v).placement.value,side:(0,t.unref)(v).side.value,align:(0,t.unref)(v).align.value,floatingId:x.floatingId,referenceId:x.referenceId.value})}}),E=(0,t.defineComponent)({__name:`SbReference`,props:{as:{default:`button`},id:{default:void 0},ariaHaspopup:{default:void 0}},setup(e){let n=e,r=(0,t.useAttrs)(),i=f(`SbReference`),{isOpen:a,disabled:o,floatingId:l,floatingRole:p}=i,m=d(i,`reference`,`SbReference`),h=()=>typeof n.as!=`string`||n.as===`button`||n.as===`a`&&r.href!=null,g=`${(0,t.useId)()}-reference`,v=(0,t.computed)(()=>n.id??g);(0,t.watchEffect)(()=>{i.referenceId.value=v.value}),(0,t.onMounted)(()=>{i.reference.value||s("<SbReference> rendered no element, so the panel cannot be positioned: a component in `as` must render a single root element.")}),(0,t.onBeforeUnmount)(()=>{i.referenceId.value===v.value&&(i.referenceId.value=void 0),m(),!i.parts.reference&&!i.isUnmounting&&i.close()});let y=(0,t.computed)(()=>n.ariaHaspopup??(p.value&&Object.hasOwn(c,p.value)?c[p.value]:void 0)),b=e=>{i.reference.value=u(e)},x=()=>{i.trigger.value===`click`&&i.toggle()},C=(0,t.computed)(()=>p.value===`tooltip`),w=()=>i.trigger.value===`hover`||i.trigger.value===`focus`,T=e=>{w()&&!i.contains(e.relatedTarget)&&i.open()},E=e=>{w()&&!i.contains(e.relatedTarget)&&i.close()},D=e=>{let t=i.floating.value,n=t&&_(t)[0];n&&(e.preventDefault(),n.focus())},O=e=>{if(i.navigation.value&&!o.value&&i.trigger.value!==`manual`){if(i.parent?.navigation.value===`menu`){let t=e.currentTarget;return e.key===S(t).open?`first`:void 0}return e.key===`ArrowDown`?`first`:e.key===`ArrowUp`?`last`:void 0}},k=e=>{let t=i.parent?.navigation.value===`menu`;if(e.key===`Tab`&&!e.shiftKey&&a.value&&!t){D(e);return}let n=O(e);n?(e.preventDefault(),i.focusRequest.value=n,i.open()):h()||e.key!==`Enter`&&e.key!==` `||(e.preventDefault(),x())};return(e,r)=>((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(n.as),{id:v.value,ref:b,type:n.as===`button`?`button`:void 0,role:h()?void 0:`button`,tabindex:h()?void 0:(0,t.unref)(o)?-1:0,disabled:n.as===`button`&&(0,t.unref)(o)?!0:void 0,"aria-disabled":n.as!==`button`&&(0,t.unref)(o)?`true`:void 0,"aria-haspopup":y.value,"aria-expanded":C.value?void 0:(0,t.unref)(a)?`true`:`false`,"aria-controls":(0,t.unref)(a)&&!C.value?(0,t.unref)(l):void 0,"aria-describedby":(0,t.unref)(a)&&C.value?(0,t.unref)(l):void 0,"data-sbf-state":(0,t.unref)(a)?`open`:`closed`,onClick:x,onKeydown:k,onFocusin:T,onFocusout:E,onPointerenter:(0,t.unref)(i).hover,onPointerleave:(0,t.unref)(i).hover},{default:(0,t.withCtx)(()=>[(0,t.renderSlot)(e.$slots,`default`)]),_:3},40,[`id`,`type`,`role`,`tabindex`,`disabled`,`aria-disabled`,`aria-haspopup`,`aria-expanded`,`aria-controls`,`aria-describedby`,`data-sbf-state`,`onPointerenter`,`onPointerleave`]))}}),D=(0,t.defineComponent)({inheritAttrs:!1,__name:`SbFloating`,props:{as:{default:`div`},role:{default:`dialog`},transition:{default:void 0},teleport:{type:[String,Boolean],default:`body`},zIndex:{default:void 0},autoFocus:{type:Boolean,default:void 0},keyboardNavigation:{type:Boolean,default:!0}},emits:[`after-enter`,`after-leave`],setup(e,{emit:n}){let r=e,i=n,a=(0,t.useAttrs)(),o=f(`SbFloating`);(0,t.onBeforeUnmount)(d(o,`floating`,`SbFloating`));let{isOpen:s,floatingId:l,referenceId:m,side:h,align:g}=o;(0,t.watchEffect)(()=>{o.floatingRole.value=r.role,o.navigation.value=r.keyboardNavigation&&y(r.role)?r.role:null});let v=(0,t.ref)(s.value),w=(0,t.ref)(null),T=e=>{o.floating.value=e instanceof HTMLElement?e:null},E=e=>{w.value=u(e)},D=(0,t.computed)(()=>({...o.floatingStyles.value,...r.zIndex===void 0?{}:{zIndex:r.zIndex}})),O=()=>({"aria-labelledby":r.role&&Object.hasOwn(c,r.role)&&a[`aria-label`]==null?m.value:void 0,...a}),k=(0,t.computed)(()=>r.autoFocus??(r.role!==`tooltip`&&o.trigger.value!==`hover`&&o.trigger.value!==`focus`)),A=(0,t.computed)(()=>s.value&&r.teleport!==!1&&r.role!==`tooltip`),j={[p]:``,tabindex:0,style:{position:`fixed`,opacity:0,outline:`none`,pointerEvents:`none`}},M=(0,t.computed)(()=>{let{transition:e}=r;return e?typeof e==`string`?{name:e}:e:{css:!1}}),N=e=>e.setAttribute(`data-sbf-state`,`closed`),P=()=>i(`after-enter`),F=()=>{s.value||(v.value=!1),i(`after-leave`)},I=e=>e instanceof Node&&!!w.value?.contains(e),L=e=>{let t=w.value;if(!t)return;let n=_(t);((e===`first`?n[0]:n[n.length-1])??t).focus({preventScroll:!0})},R=e=>{I(e.relatedTarget)?o.reference.value?.focus():L(`first`)},z=e=>{if(!I(e.relatedTarget)){L(`last`);return}let t=o.reference.value;((t&&C(t,e=>o.contains(e)))??t)?.focus()},B=()=>{let e=o.navigation.value;return w.value&&e?b(w.value,e):[]},V=e=>{let t=w.value;if(!t)return;let n=B();(n.find(e=>e.getAttribute(`aria-selected`)===`true`)??(e===`last`?n[n.length-1]:e?n[0]:void 0)??t.querySelector(`[autofocus]`)??n[0]??_(t)[0]??t).focus({preventScroll:!0})},H=()=>{let e=o.focusRequest.value;return o.focusRequest.value=null,e},U=``,W;(0,t.onScopeDispose)(()=>clearTimeout(W));let G=(e,t,n)=>{U+=n.toLowerCase(),clearTimeout(W),W=setTimeout(()=>U=``,500);let r=[...U].every(e=>e===U[0])?U[0]:U,i=Math.max(t,0),a=[...e.slice(i),...e.slice(0,i)];return r.length===1&&t>=0&&a.push(a.shift()),a.find(e=>e.textContent?.trim().toLowerCase().startsWith(r))},K=e=>{let t=o,n=[t];for(;t.parent?.navigation.value===`menu`;)t=t.parent,n.push(t);let r=t.reference.value,i=e?r:(r&&C(r,e=>t.contains(e)))??r;for(let e of n)e.skipFocusReturn=!0,e.close();i?.focus()},q=e=>{let t=o.navigation.value;if(!t||e.defaultPrevented||e.isComposing||x(e.target))return;let n=B(),r=n.findIndex(t=>t.contains(e.target)),i=n.length-1,a=t===`menu`,s;if(e.key===`ArrowDown`)s=n[r<i?r+1:a?0:i];else if(e.key===`ArrowUp`)s=n[r>0?r-1:a||r<0?i:0];else if(e.key===`Home`)s=n[0];else if(e.key===`End`)s=n[i];else if(e.key===`Tab`&&t===`menu`){e.preventDefault(),K(e.shiftKey);return}else if(t===`menu`&&o.parent?.navigation.value===`menu`&&w.value&&e.key===S(w.value).close){e.preventDefault(),o.close();return}else if(e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&(e.key!==` `||U))e.preventDefault(),s=G(n,r,e.key);else if((e.key===`Enter`||e.key===` `)&&e.target===n[r]){let t=n[r];if(t.matches(`button, a[href], input, summary`))return;e.preventDefault(),t.getAttribute(`aria-disabled`)!==`true`&&t.click();return}s&&(e.preventDefault(),s.focus())};return(0,t.watch)(s,e=>{let t=o.skipFocusReturn;if(o.skipFocusReturn=!1,e){v.value=!0;return}let n=document.activeElement;!t&&n&&o.floating.value?.contains(n)&&o.reference.value?.focus({preventScroll:!0})}),(0,t.watch)(()=>s.value&&o.isPositioned.value,async e=>{if(!e)return;let n=H();(n||k.value)&&(await(0,t.nextTick)(),V(n))}),(0,t.watch)(o.focusRequest,e=>{e&&s.value&&o.isPositioned.value&&V(H())}),(e,n)=>((0,t.openBlock)(),(0,t.createBlock)(t.Teleport,{to:r.teleport||`body`,disabled:r.teleport===!1},[v.value?((0,t.openBlock)(),(0,t.createElementBlock)(`div`,{key:0,ref:T,style:(0,t.normalizeStyle)(D.value),onPointerenter:n[0]||=(...e)=>(0,t.unref)(o).hover&&(0,t.unref)(o).hover(...e),onPointerleave:n[1]||=(...e)=>(0,t.unref)(o).hover&&(0,t.unref)(o).hover(...e)},[A.value?((0,t.openBlock)(),(0,t.createElementBlock)(`span`,(0,t.mergeProps)({key:0},j,{onFocus:R}),null,16)):(0,t.createCommentVNode)(``,!0),(0,t.createVNode)(t.Transition,(0,t.mergeProps)(M.value,{appear:``,onBeforeLeave:N,onAfterEnter:P,onAfterLeave:F}),{default:(0,t.withCtx)(()=>[(0,t.unref)(s)?((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(r.as),(0,t.mergeProps)({key:0},O(),{id:(0,t.unref)(l),ref:E,role:r.role??void 0,tabindex:k.value?-1:void 0,"data-sbf-state":`open`,"data-sbf-side":(0,t.unref)(h),"data-sbf-align":(0,t.unref)(g),onKeydown:q}),{default:(0,t.withCtx)(()=>[(0,t.renderSlot)(e.$slots,`default`)]),_:3},16,[`id`,`role`,`tabindex`,`data-sbf-side`,`data-sbf-align`])):(0,t.createCommentVNode)(``,!0)]),_:3},16),A.value?((0,t.openBlock)(),(0,t.createElementBlock)(`span`,(0,t.mergeProps)({key:1},j,{onFocus:z}),null,16)):(0,t.createCommentVNode)(``,!0)],36)):(0,t.createCommentVNode)(``,!0)],8,[`to`,`disabled`]))}});e.SbContainer=T,e.SbFloating=D,e.SbReference=E});
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("vue"),require("@floating-ui/vue")):typeof define==`function`&&define.amd?define([`exports`,`vue`,`@floating-ui/vue`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.SbFloatingPanelVue={},e.Vue,e.FloatingUIVue))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=(()=>{try{return process.env.NODE_ENV!==`production`}catch{return!1}})();function i(e){r&&console.warn(`[sb-floating-panel-vue] ${e}`)}var a=Symbol(`SbFloatingContext`);function o(e,t,n){return++e.parts[t]>1&&i(`<SbContainer> has more than one <${n}>: they share one open state and position, so only the last one works. Use one SbContainer for each panel.`),()=>e.parts[t]--}function s(e){let n=(0,t.inject)(a,null);if(!n)throw Error(`[sb-floating-panel-vue] <${e}> must be used inside <SbContainer>.`);return n}var c=e=>`${Math.max(0,e)}px`,l=(e,t)=>{let[n,r]=e.split(`-`),i=t&&(n===`top`||n===`bottom`),a=r===`start`?i?`100%`:`0%`:r===`end`?i?`0%`:`100%`:`50%`;return n===`top`?`${a} 100%`:n===`bottom`?`${a} 0%`:n===`left`?`100% ${a}`:`0% ${a}`};function u(e){let r=(0,t.ref)(null),i=(0,t.ref)(null),a=(0,t.computed)(()=>{let r=(0,t.toValue)(e.padding),i=(0,t.toValue)(e.matchWidth),a=(0,t.toValue)(e.placement).includes(`-`),o=(0,n.flip)({padding:r,crossAxis:`alignment`,fallbackAxisSideDirection:`end`}),s=(0,n.shift)({padding:r,limiter:(0,n.limitShift)()}),u=[(0,n.offset)((0,t.toValue)(e.offset)),...a?[o,s]:[s,o],(0,n.size)({padding:r,apply({rects:e,elements:t,availableWidth:n,availableHeight:r,placement:a}){let{style:o}=t.floating,s=c(e.reference.width);o.setProperty(`--sbf-reference-width`,s);let u=getComputedStyle(t.floating).direction===`rtl`;o.setProperty(`--sbf-transform-origin`,l(a,u)),o.setProperty(`--sbf-available-width`,c(n)),o.setProperty(`--sbf-available-height`,c(r)),o.width=i===!0?s:``,o.minWidth=i===`min`?s:``}})];return(0,t.toValue)(e.hideWhenDetached)&&u.push((0,n.hide)({padding:r})),u}),{floatingStyles:o,middlewareData:s,placement:u,isPositioned:d,update:f}=(0,n.useFloating)(r,i,{open:()=>(0,t.toValue)(e.isOpen),placement:()=>(0,t.toValue)(e.placement),strategy:()=>(0,t.toValue)(e.strategy),middleware:a,whileElementsMounted:n.autoUpdate}),p=(0,t.computed)(()=>!!s.value.hide?.referenceHidden);return{reference:r,floating:i,floatingStyles:(0,t.computed)(()=>({...o.value,...p.value?{visibility:`hidden`}:{}})),placement:u,side:(0,t.computed)(()=>u.value.split(`-`)[0]),align:(0,t.computed)(()=>u.value.split(`-`)[1]),isPositioned:d,referenceHidden:p,update:f}}var d=`data-sbf-focus-guard`,f=[`a[href]`,`button:not([disabled])`,`input:not([disabled]):not([type="hidden"])`,`select:not([disabled])`,`textarea:not([disabled])`,`summary`,`audio[controls]`,`video[controls]`,`[tabindex]`,`[contenteditable="true"]`].join(`,`),p=e=>e.tabIndex>=0&&!e.hasAttribute(`data-sbf-focus-guard`)&&!e.closest(`[inert]`)&&(e.checkVisibility?.()??!0);function m(e){let t=e&&typeof e==`object`&&`$el`in e?e.$el:e;return t instanceof HTMLElement?t:null}function h(e){let t=e instanceof Element?e.closest(f):null;return!!t&&!t.closest(`[inert]`)}var g=e=>e.matches(f)&&!e.closest(`[inert]`);function _(e){return[...e.querySelectorAll(f)].filter(p)}function v(e,t){return _(e.ownerDocument).find(n=>!!(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)&&!e.contains(n)&&!t(n))}var y=[],b=(0,t.defineComponent)({inheritAttrs:!1,__name:`SbContainer`,props:(0,t.mergeModels)({placement:{default:`bottom-start`},strategy:{default:`absolute`},offset:{default:8},padding:{default:8},matchWidth:{type:[Boolean,String],default:!1},hideWhenDetached:{type:[Boolean,String],default:!1},defaultOpen:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},trigger:{default:`click`},delay:{default:()=>({open:300,close:150})},closeOnEscape:{type:Boolean,default:!0},closeOnClickOutside:{type:Boolean,default:!0},closeOnFocusOut:{type:Boolean,default:!0}},{open:{type:Boolean,default:void 0},openModifiers:{}}),emits:(0,t.mergeModels)([`open`,`close`],[`update:open`]),setup(e,{expose:n,emit:r}){let i=e,o=(0,t.useModel)(e,`open`),s=r,c=i.defaultOpen,l=(0,t.computed)(()=>!i.disabled&&(o.value??c)),d;(0,t.onScopeDispose)(()=>clearTimeout(d));let f=e=>{clearTimeout(d),!(e&&i.disabled)&&e!==l.value&&(o.value=e)},p=()=>f(!0),m=()=>f(!1),g=()=>f(!l.value),_=e=>{if(e.pointerType===`touch`||(x?.hover(e),i.trigger!==`hover`))return;let t=e.type===`pointerenter`||S.contains(e.relatedTarget);if(clearTimeout(d),t===l.value)return;let{delay:n}=i,r=typeof n==`number`?{open:n,close:n}:n,a=(t?r.open:r.close)??(t?300:150);d=setTimeout(()=>f(t),a)},v=u({isOpen:l,placement:()=>i.placement,strategy:()=>i.strategy,offset:()=>i.offset,padding:()=>i.padding,matchWidth:()=>i.matchWidth,hideWhenDetached:()=>i.hideWhenDetached}),b=new Set,x=(0,t.inject)(a,null),S={...v,isOpen:l,disabled:(0,t.toRef)(i,`disabled`),trigger:(0,t.toRef)(i,`trigger`),floatingId:`${(0,t.useId)()}-floating`,referenceId:(0,t.ref)(),floatingRole:(0,t.ref)(`dialog`),navigation:(0,t.ref)(null),focusRequest:(0,t.ref)(null),parent:x,skipFocusReturn:!1,parts:{reference:0,floating:0},isUnmounting:!1,open:p,close:m,toggle:g,hover:_,contains:e=>e instanceof Node?v.reference.value?.contains(e)||v.floating.value?.contains(e)?!0:[...b].some(t=>t.contains(e)):!1,registerChild:e=>(b.add(e),()=>b.delete(e))};x&&(0,t.onScopeDispose)(x.registerChild(S)),(0,t.provide)(a,S),(0,t.onBeforeUnmount)(()=>S.isUnmounting=!0);let C=e=>{let t=e.composedPath()[0]??e.target;return y.findLast(e=>e.contains(t))??y[y.length-1]},w=e=>{e.key===`Escape`&&C(e)===S&&(!i.closeOnEscape||e.defaultPrevented||e.isComposing||(e.preventDefault(),m()))},T=e=>{S.contains(e)||(S.skipFocusReturn=h(e),m())},E,D=e=>{if(E?.(),!i.closeOnClickOutside)return;let t=e.composedPath()[0]??e.target;if(e.pointerType!==`touch`){T(t);return}if(S.contains(t))return;let n=e=>{E?.(),T(e.composedPath()[0]??e.target)};document.addEventListener(`click`,n,!0),E=()=>{document.removeEventListener(`click`,n,!0),E=void 0}},O=e=>{i.closeOnFocusOut&&(S.contains(e.composedPath()[0]??e.target)||m())};return(0,t.watch)(l,(e,t,n)=>{e&&typeof document<`u`&&(y.push(S),document.addEventListener(`keydown`,w),document.addEventListener(`pointerdown`,D,!0),document.addEventListener(`focusin`,O),n(()=>{document.removeEventListener(`keydown`,w),document.removeEventListener(`pointerdown`,D,!0),document.removeEventListener(`focusin`,O),E?.(),y.splice(y.indexOf(S),1)}))},{immediate:!0}),(0,t.watch)(l,e=>s(e?`open`:`close`)),(0,t.watch)(v.referenceHidden,e=>{e&&i.hideWhenDetached===`close`&&m()}),(0,t.watch)(()=>i.disabled,e=>{e&&(o.value??c)&&(o.value=!1)}),n({get isOpen(){return l.value},open:p,close:m,toggle:g,update:v.update}),(e,n)=>(0,t.renderSlot)(e.$slots,`default`,{isOpen:l.value,open:p,close:m,toggle:g,placement:(0,t.unref)(v).placement.value,side:(0,t.unref)(v).side.value,align:(0,t.unref)(v).align.value,floatingId:S.floatingId,referenceId:S.referenceId.value})}}),x={dialog:`dialog`,alertdialog:`dialog`,menu:`menu`,listbox:`listbox`,tree:`tree`,grid:`grid`},S={menu:`[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]`,listbox:`[role="option"]`},C=e=>!!e&&Object.hasOwn(S,e);function w(e,t){return[...e.querySelectorAll(S[t])].filter(n=>n.closest(`[role="${t}"]`)===e&&!n.matches(`:disabled`)&&(n.checkVisibility?.()??!0))}var T=e=>e instanceof Element&&!!e.closest(`input, textarea, select, [contenteditable]:not([contenteditable="false"])`),E=e=>getComputedStyle(e).direction===`rtl`?{open:`ArrowLeft`,close:`ArrowRight`}:{open:`ArrowRight`,close:`ArrowLeft`},D=(0,t.defineComponent)({__name:`SbReference`,props:{as:{default:`button`},id:{default:void 0},ariaHaspopup:{default:void 0}},setup(e){let n=e,r=(0,t.useAttrs)(),a=s(`SbReference`),{isOpen:c,disabled:l,floatingId:u,floatingRole:d}=a,f=o(a,`reference`,`SbReference`),p=[`button`,`input`,`textarea`,`select`],h=()=>typeof n.as!=`string`||p.includes(n.as)||n.as===`a`&&r.href!=null,g=`${(0,t.useId)()}-reference`,v=(0,t.computed)(()=>n.id??g);(0,t.watchEffect)(()=>{a.referenceId.value=v.value}),(0,t.onMounted)(()=>{let e=a.reference.value;e?T(e)&&a.navigation.value&&i(`<SbReference> is a text field with a role="${a.navigation.value}" panel: the arrow keys move focus into the panel, so typing stops there. The combobox pattern (focus kept in the field with aria-activedescendant) is not supported.`):i("<SbReference> rendered no element, so the panel cannot be positioned: a component in `as` must render a single root element.")}),(0,t.onBeforeUnmount)(()=>{a.referenceId.value===v.value&&(a.referenceId.value=void 0),f(),!a.parts.reference&&!a.isUnmounting&&a.close()});let y=(0,t.computed)(()=>n.ariaHaspopup??(d.value&&Object.hasOwn(x,d.value)?x[d.value]:void 0)),b=e=>{a.reference.value=m(e)},S=()=>{a.trigger.value===`click`&&(T(a.reference.value)?a.open():a.toggle())},C=(0,t.computed)(()=>d.value===`tooltip`),w=()=>a.trigger.value===`hover`||a.trigger.value===`focus`,D=()=>a.parent?.navigation.value===`menu`,O=e=>{w()&&!D()&&!a.contains(e.relatedTarget)&&a.open()},k=e=>{w()&&!a.contains(e.relatedTarget)&&a.close()},A=e=>{let t=a.floating.value,n=t&&_(t)[0];n&&(e.preventDefault(),n.focus())},j=e=>{if(a.navigation.value&&!l.value&&a.trigger.value!==`manual`){if(D()){let t=e.currentTarget;return e.key===E(t).open?`first`:void 0}return e.key===`ArrowDown`?`first`:e.key===`ArrowUp`?`last`:void 0}},M=e=>{if(e.key===`Tab`&&!e.shiftKey&&c.value&&!D()){A(e);return}let t=j(e);t?(e.preventDefault(),a.focusRequest.value=t,a.open()):h()||e.key!==`Enter`&&e.key!==` `||(e.preventDefault(),S())};return(e,r)=>((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(n.as),{id:v.value,ref:b,type:n.as===`button`?`button`:void 0,role:h()?void 0:`button`,tabindex:h()?void 0:(0,t.unref)(l)?-1:0,disabled:n.as===`button`&&(0,t.unref)(l)?!0:void 0,"aria-disabled":n.as!==`button`&&(0,t.unref)(l)?`true`:void 0,"aria-haspopup":y.value,"aria-expanded":C.value?void 0:(0,t.unref)(c)?`true`:`false`,"aria-controls":(0,t.unref)(c)&&!C.value?(0,t.unref)(u):void 0,"aria-describedby":(0,t.unref)(c)&&C.value?(0,t.unref)(u):void 0,"data-sbf-state":(0,t.unref)(c)?`open`:`closed`,onClick:S,onKeydown:M,onFocusin:O,onFocusout:k,onPointerenter:(0,t.unref)(a).hover,onPointerleave:(0,t.unref)(a).hover},{default:(0,t.withCtx)(()=>[(0,t.renderSlot)(e.$slots,`default`)]),_:3},40,[`id`,`type`,`role`,`tabindex`,`disabled`,`aria-disabled`,`aria-haspopup`,`aria-expanded`,`aria-controls`,`aria-describedby`,`data-sbf-state`,`onPointerenter`,`onPointerleave`]))}}),O=(0,t.defineComponent)({inheritAttrs:!1,__name:`SbFloating`,props:{as:{default:`div`},role:{default:`dialog`},transition:{default:void 0},teleport:{type:[String,Boolean],default:`body`},zIndex:{default:void 0},autoFocus:{type:Boolean,default:void 0},keyboardNavigation:{type:Boolean,default:!0}},emits:[`after-enter`,`after-leave`],setup(e,{emit:n}){let r=e,a=n,c=(0,t.useAttrs)(),l=s(`SbFloating`);(0,t.onBeforeUnmount)(o(l,`floating`,`SbFloating`));let{isOpen:u,floatingId:f,referenceId:p,side:h,align:y}=l;(0,t.watchEffect)(()=>{l.floatingRole.value=r.role,l.navigation.value=r.keyboardNavigation&&C(r.role)?r.role:null}),(0,t.onMounted)(()=>{r.role===`tooltip`&&l.trigger.value===`click`&&i(`<SbFloating role="tooltip"> opens on click: a tooltip shows on hover and focus. Set trigger="hover" or "focus" on SbContainer, or use another role for a panel opened by click.`)});let b=(0,t.ref)(u.value),S=(0,t.ref)(null),D=e=>{l.floating.value=e instanceof HTMLElement?e:null},O=e=>{S.value=m(e)},k=(0,t.computed)(()=>({...l.floatingStyles.value,...r.zIndex===void 0?{}:{zIndex:r.zIndex}})),A=()=>({"aria-labelledby":r.role&&Object.hasOwn(x,r.role)&&c[`aria-label`]==null?p.value:void 0,...c}),j=(0,t.computed)(()=>r.autoFocus??(r.role!==`tooltip`&&l.trigger.value!==`hover`&&l.trigger.value!==`focus`&&!T(l.reference.value))),M=(0,t.computed)(()=>u.value&&r.teleport!==!1&&r.role!==`tooltip`),N={[d]:``,tabindex:0,style:{position:`fixed`,opacity:0,outline:`none`,pointerEvents:`none`}},P=(0,t.computed)(()=>{let{transition:e}=r;return e?typeof e==`string`?{name:e}:e:{css:!1}}),F=e=>e.setAttribute(`data-sbf-state`,`closed`),I=()=>a(`after-enter`),L=()=>{u.value||(b.value=!1),a(`after-leave`)},R=e=>e instanceof Node&&!!S.value?.contains(e),z=e=>{let t=S.value;if(!t)return;let n=_(t);((e===`first`?n[0]:n[n.length-1])??t).focus({preventScroll:!0})},B=e=>{R(e.relatedTarget)?l.reference.value?.focus():z(`first`)},V=e=>{if(!R(e.relatedTarget)){z(`last`);return}let t=l.reference.value;((t&&v(t,e=>l.contains(e)))??t)?.focus()},H=()=>{let e=l.navigation.value;return S.value&&e?w(S.value,e):[]},U={menu:`role="menuitem", "menuitemcheckbox" or "menuitemradio"`,listbox:`role="option"`},W=!1,G=e=>{let t=l.navigation.value;if(W||!t)return;let n=e.length?e.every(g)?null:`has items that cannot take focus, so the arrow keys cannot move to them: make each one a button, or give it tabindex="-1".`:`has no items the arrow keys can move to: items need ${U[t]} and must not be disabled.`;n&&(W=!0,i(`<SbFloating role="${t}"> ${n}`))},K=e=>{let t=S.value;if(!t)return;let n=H();e&&G(n),(n.find(e=>e.getAttribute(`aria-selected`)===`true`)??(e===`last`?n[n.length-1]:e?n[0]:void 0)??t.querySelector(`[autofocus]`)??n[0]??_(t)[0]??t).focus({preventScroll:!0})},q=()=>{let e=l.focusRequest.value;return l.focusRequest.value=null,e},J=``,Y;(0,t.onScopeDispose)(()=>clearTimeout(Y));let X=(e,t,n)=>{J+=n.toLowerCase(),clearTimeout(Y),Y=setTimeout(()=>J=``,500);let r=[...J].every(e=>e===J[0])?J[0]:J,i=Math.max(t,0),a=[...e.slice(i),...e.slice(0,i)];return r.length===1&&t>=0&&a.push(a.shift()),a.find(e=>e.textContent?.trim().toLowerCase().startsWith(r))},Z=e=>{let t=l,n=[t];for(;t.parent?.navigation.value===`menu`;)t=t.parent,n.push(t);let r=t.reference.value,i=e?r:(r&&v(r,e=>t.contains(e)))??r;for(let e of n)e.skipFocusReturn=!0,e.close();i?.focus()},Q=e=>{let t=l.navigation.value;if(!t||e.defaultPrevented||e.isComposing||T(e.target))return;let n=H(),r=n.findIndex(t=>t.contains(e.target)),i=n.length-1,a=t===`menu`,o;if([`ArrowDown`,`ArrowUp`,`Home`,`End`].includes(e.key)&&G(n),e.key===`ArrowDown`)o=n[r<i?r+1:a?0:i];else if(e.key===`ArrowUp`)o=n[r>0?r-1:a||r<0?i:0];else if(e.key===`Home`)o=n[0];else if(e.key===`End`)o=n[i];else if(e.key===`Tab`&&t===`menu`){e.preventDefault(),Z(e.shiftKey);return}else if(t===`menu`&&l.parent?.navigation.value===`menu`&&S.value&&e.key===E(S.value).close){e.preventDefault(),l.close();return}else if(e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&(e.key!==` `||J))e.preventDefault(),o=X(n,r,e.key);else if((e.key===`Enter`||e.key===` `)&&e.target===n[r]){let t=n[r];if(t.matches(`button, a[href], input, summary`))return;e.preventDefault(),t.getAttribute(`aria-disabled`)!==`true`&&t.click();return}o&&(e.preventDefault(),o.focus())};return(0,t.watch)(u,e=>{let t=l.skipFocusReturn;if(l.skipFocusReturn=!1,e){b.value=!0;return}let n=document.activeElement;!t&&n&&l.floating.value?.contains(n)&&l.reference.value?.focus({preventScroll:!0})}),(0,t.watch)(()=>u.value&&l.isPositioned.value,async e=>{if(!e)return;let n=q();(n||j.value)&&(await(0,t.nextTick)(),K(n))}),(0,t.watch)(l.focusRequest,e=>{e&&u.value&&l.isPositioned.value&&K(q())}),(e,n)=>((0,t.openBlock)(),(0,t.createBlock)(t.Teleport,{to:r.teleport||`body`,disabled:r.teleport===!1},[b.value?((0,t.openBlock)(),(0,t.createElementBlock)(`div`,{key:0,ref:D,style:(0,t.normalizeStyle)(k.value),onPointerenter:n[0]||=(...e)=>(0,t.unref)(l).hover&&(0,t.unref)(l).hover(...e),onPointerleave:n[1]||=(...e)=>(0,t.unref)(l).hover&&(0,t.unref)(l).hover(...e)},[M.value?((0,t.openBlock)(),(0,t.createElementBlock)(`span`,(0,t.mergeProps)({key:0},N,{onFocus:B}),null,16)):(0,t.createCommentVNode)(``,!0),(0,t.createVNode)(t.Transition,(0,t.mergeProps)(P.value,{appear:``,onBeforeLeave:F,onAfterEnter:I,onAfterLeave:L}),{default:(0,t.withCtx)(()=>[(0,t.unref)(u)?((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(r.as),(0,t.mergeProps)({key:0},A(),{id:(0,t.unref)(f),ref:O,role:r.role??void 0,tabindex:j.value?-1:void 0,"data-sbf-state":`open`,"data-sbf-side":(0,t.unref)(h),"data-sbf-align":(0,t.unref)(y),onKeydown:Q}),{default:(0,t.withCtx)(()=>[(0,t.renderSlot)(e.$slots,`default`)]),_:3},16,[`id`,`role`,`tabindex`,`data-sbf-side`,`data-sbf-align`])):(0,t.createCommentVNode)(``,!0)]),_:3},16),M.value?((0,t.openBlock)(),(0,t.createElementBlock)(`span`,(0,t.mergeProps)({key:1},N,{onFocus:V}),null,16)):(0,t.createCommentVNode)(``,!0)],36)):(0,t.createCommentVNode)(``,!0)],8,[`to`,`disabled`]))}});e.SbContainer=b,e.SbFloating=O,e.SbReference=D});
package/dist/types.d.ts CHANGED
@@ -153,11 +153,15 @@ export interface SbReferenceProps {
153
153
  /**
154
154
  * HTML tag or component to render.
155
155
  * - `button` gets `type="button"`.
156
- * - `a` with `href` and components are left as they are: a component is expected to render
157
- * its own focusable element (e.g. the `<Button>` of your design system).
156
+ * - Form controls (`input`, `textarea`, `select`), `a` with `href` and components are left as
157
+ * they are: a component is expected to render its own focusable element (e.g. the
158
+ * `<Button>` of your design system).
158
159
  * - Any other tag (including `a` without `href`) gets `role="button"`, `tabindex="0"` and
159
160
  * Enter/Space handling.
160
161
  *
162
+ * A text field (`input`, `textarea`) opens the panel on click without closing it, and keeps
163
+ * focus when the panel opens. The combobox pattern (`aria-activedescendant`) is not supported.
164
+ *
161
165
  * When disabled, `button` gets `disabled`; anything else gets `aria-disabled="true"`.
162
166
  * @default 'button'
163
167
  */
@@ -219,7 +223,8 @@ export interface SbFloatingProps {
219
223
  * Moves focus inside the panel once it is positioned: to the first element with
220
224
  * `autofocus`, otherwise to the first focusable element, otherwise to the panel itself.
221
225
  * With `role="menu"` or `listbox` the first item gets focus (a listbox: its selected option).
222
- * @default true, false with `role="tooltip"` and with the `hover` and `focus` triggers
226
+ * @default true, false with `role="tooltip"`, with the `hover` and `focus` triggers and with
227
+ * a text field reference
223
228
  */
224
229
  autoFocus?: boolean;
225
230
  /**
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Panel roles that make the reference a popup trigger, with their `aria-haspopup` value.
3
+ * Panels with these roles are also named after the reference.
4
+ */
5
+ export declare const POPUP_ROLES: Record<string, 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid'>;
@@ -0,0 +1,12 @@
1
+ /** Marks the hidden elements placed around a teleported panel to keep the Tab order. */
2
+ export declare const FOCUS_GUARD_ATTRIBUTE = "data-sbf-focus-guard";
3
+ /** Element of a template ref: a component gives its instance, whose root element is `$el`. */
4
+ export declare function toElement(el: unknown): HTMLElement | null;
5
+ /** Whether pressing `target` moves focus to it (or to a focusable ancestor). */
6
+ export declare function isFocusable(target: EventTarget | null): boolean;
7
+ /** Whether `el` itself can take focus, with Tab or from script. */
8
+ export declare const canFocus: (el: Element) => boolean;
9
+ /** Elements reachable with Tab inside `root`, in document order (focus guards excluded). */
10
+ export declare function getTabbables(root: ParentNode): HTMLElement[];
11
+ /** First element reachable with Tab after `anchor` in document order, skipping the excluded ones. */
12
+ export declare function getNextTabbable(anchor: Element, exclude: (el: HTMLElement) => boolean): HTMLElement | undefined;
@@ -1,9 +1,3 @@
1
- /** Marks the hidden elements placed around a teleported panel to keep the Tab order. */
2
- export declare const FOCUS_GUARD_ATTRIBUTE = "data-sbf-focus-guard";
3
- /** Whether pressing `target` moves focus to it (or to a focusable ancestor). */
4
- export declare function isFocusable(target: EventTarget | null): boolean;
5
- /** Elements reachable with Tab inside `root`, in document order (focus guards excluded). */
6
- export declare function getTabbables(root: ParentNode): HTMLElement[];
7
1
  /** Panel roles whose keyboard model the components handle, with the selector of their items. */
8
2
  export declare const ITEM_SELECTORS: {
9
3
  readonly menu: "[role=\"menuitem\"], [role=\"menuitemcheckbox\"], [role=\"menuitemradio\"]";
@@ -20,5 +14,3 @@ export declare const submenuKeys: (el: Element) => {
20
14
  open: string;
21
15
  close: string;
22
16
  };
23
- /** First element reachable with Tab after `anchor` in document order, skipping the excluded ones. */
24
- export declare function getNextTabbable(anchor: Element, exclude: (el: HTMLElement) => boolean): HTMLElement | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sb-floating-panel-vue",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
4
4
  "description": "Headless Vue 3 floating panel components (dropdowns, popovers, menus) powered by @floating-ui/vue: positioning, accessibility and open state handled, zero CSS shipped.",
5
5
  "keywords": [
6
6
  "vue",
File without changes