sb-floating-panel-vue 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,6 @@
1
1
  # sb-floating-panel-vue
2
2
 
3
- [![npm](https://img.shields.io/npm/v/sb-floating-panel-vue.svg)](https://www.npmjs.com/package/sb-floating-panel-vue)
4
- [![license](https://img.shields.io/npm/l/sb-floating-panel-vue.svg)](https://github.com/stefanBid/sb-floating-panel-vue/blob/main/LICENSE)
5
- [![Vue 3](https://img.shields.io/badge/vue-3.x-brightgreen.svg)](https://vuejs.org)
6
- [![CI](https://github.com/stefanBid/sb-floating-panel-vue/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/stefanBid/sb-floating-panel-vue/actions/workflows/ci.yml)
7
- [![coverage](https://raw.githubusercontent.com/stefanBid/sb-floating-panel-vue/badges/coverage.svg)](https://github.com/stefanBid/sb-floating-panel-vue/actions/workflows/ci.yml)
8
- [![branches](https://raw.githubusercontent.com/stefanBid/sb-floating-panel-vue/badges/coverage-branches.svg)](https://github.com/stefanBid/sb-floating-panel-vue/actions/workflows/ci.yml)
3
+ [![npm](https://img.shields.io/npm/v/sb-floating-panel-vue.svg)](https://www.npmjs.com/package/sb-floating-panel-vue) [![license](https://img.shields.io/npm/l/sb-floating-panel-vue.svg)](https://github.com/stefanBid/sb-floating-panel-vue/blob/main/LICENSE) [![Vue 3](https://img.shields.io/badge/vue-3.x-brightgreen.svg)](https://vuejs.org) [![CI](https://github.com/stefanBid/sb-floating-panel-vue/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/stefanBid/sb-floating-panel-vue/actions/workflows/ci.yml) [![coverage](https://raw.githubusercontent.com/stefanBid/sb-floating-panel-vue/badges/coverage.svg)](https://github.com/stefanBid/sb-floating-panel-vue/actions/workflows/ci.yml) [![branches](https://raw.githubusercontent.com/stefanBid/sb-floating-panel-vue/badges/coverage-branches.svg)](https://github.com/stefanBid/sb-floating-panel-vue/actions/workflows/ci.yml)
9
4
 
10
5
  Headless Vue 3 components for floating panels (dropdowns, menus, listboxes, popovers, tooltips) built on [`@floating-ui/vue`](https://floating-ui.com/). You write three components; the library handles placement, flipping, viewport collisions, open state, keyboard and accessibility. It never decides how your panel looks: no stylesheet, only data attributes and CSS variables your styles can read.
11
6
 
@@ -22,14 +17,7 @@ Headless Vue 3 components for floating panels (dropdowns, menus, listboxes, popo
22
17
  ## 📦 Installation
23
18
 
24
19
  ```bash
25
- # npm
26
20
  npm install sb-floating-panel-vue vue @floating-ui/vue
27
-
28
- # yarn
29
- yarn add sb-floating-panel-vue vue @floating-ui/vue
30
-
31
- # pnpm
32
- pnpm add sb-floating-panel-vue vue @floating-ui/vue
33
21
  ```
34
22
 
35
23
  `vue` (`^3.5.13`) and `@floating-ui/vue` (`^2.0.1`) are peer dependencies: your app provides them, so only one copy of each ends up in the bundle. Vue is usually already installed in your app.
@@ -101,78 +89,61 @@ Holds the state and the positioning. Renders no element of its own, only its slo
101
89
 
102
90
  **Positioning**
103
91
 
104
- | Prop | Type | Default | Description |
105
- | ------------------ | ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |
106
- | `placement` | `SbPlacement` | `'bottom-start'` | Preferred placement; the panel flips when there is not enough space |
107
- | `strategy` | `'absolute' \| 'fixed'` | `'absolute'` | CSS position strategy. Use `fixed` inside scroll containers or fixed layouts |
108
- | `offset` | `number` | `8` | Distance in px between reference and panel |
109
- | `padding` | `number` | `8` | Minimum distance in px between the panel and the viewport edges |
110
- | `matchWidth` | `boolean \| 'min'` | `false` | `true` sets `width`, `'min'` sets `min-width` to the reference width |
111
- | `hideWhenDetached` | `boolean \| 'close'` | `false` | When the reference is scrolled out of view: `true` hides the panel (`visibility: hidden`) until it is back, `'close'` closes it |
92
+ | Prop | Type | Default | Description |
93
+ | --- | --- | --- | --- |
94
+ | `placement` | `SbPlacement` | `'bottom-start'` | Preferred placement; the panel flips when there is not enough space |
95
+ | `strategy` | `'absolute' \| 'fixed'` | `'absolute'` | CSS position strategy. Use `fixed` inside scroll containers or fixed layouts |
96
+ | `offset` | `number` | `8` | Distance in px between reference and panel |
97
+ | `padding` | `number` | `8` | Minimum distance in px between the panel and the viewport edges |
98
+ | `matchWidth` | `boolean \| 'min'` | `false` | `true` sets `width`, `'min'` sets `min-width` to the reference width |
99
+ | `hideWhenDetached` | `boolean \| 'close'` | `false` | When the reference is scrolled out of view: `true` hides the panel (`visibility: hidden`) until it is back, `'close'` closes it |
112
100
 
113
101
  **Open state**
114
102
 
115
- | Prop | Type | Default | Description |
116
- | -------------- | --------------------------------------------- | --------------------------- | ---------------------------------------------------------------------------- |
117
- | `v-model:open` | `boolean` | — | Open state. Leave it unset to let the container manage it |
118
- | `defaultOpen` | `boolean` | `false` | Initial open state when `v-model:open` is not used |
119
- | `disabled` | `boolean` | `false` | Prevents opening; closes the panel if it is open |
120
- | `trigger` | `SbTrigger` | `'click'` | How the reference opens the panel, see below |
121
- | `delay` | `number \| { open?: number; close?: number }` | `{ open: 300, close: 150 }` | Milliseconds before the `hover` trigger opens and closes; a number sets both |
122
-
123
- | `trigger` | Opens | Closes |
124
- | ---------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
125
- | `'click'` | Click (Enter/Space) on the reference toggles the panel | Same, or any dismissal below |
126
- | `'hover'` | Pointer over the reference after `delay.open`; keyboard focus at once (touch has no hover) | `delay.close` after the pointer leaves the reference and the panel, or when focus moves elsewhere |
127
- | `'focus'` | Focus on the reference | Focus moving elsewhere |
128
- | `'manual'` | Only `v-model:open`, the slot props or the exposed methods | Same, or any dismissal below |
103
+ | Prop | Type | Default | Description |
104
+ | --- | --- | --- | --- |
105
+ | `v-model:open` | `boolean` | — | Open state. Leave it unset to let the container manage it |
106
+ | `defaultOpen` | `boolean` | `false` | Initial open state when `v-model:open` is not used |
107
+ | `disabled` | `boolean` | `false` | Prevents opening; closes the panel if it is open |
108
+ | `trigger` | `SbTrigger` | `'click'` | How the reference opens the panel, see below |
109
+ | `delay` | `number \| { open?: number; close?: number }` | `{ open: 300, close: 150 }` | Milliseconds before the `hover` trigger opens and closes; a number sets both |
110
+
111
+ | `trigger` | Opens | Closes |
112
+ | --- | --- | --- |
113
+ | `'click'` | Click (Enter/Space) on the reference toggles the panel | Same, or any dismissal below |
114
+ | `'hover'` | Pointer over the reference after `delay.open`; keyboard focus at once (touch has no hover) | `delay.close` after the pointer leaves the reference and the panel, or when focus moves elsewhere |
115
+ | `'focus'` | Focus on the reference | Focus moving elsewhere |
116
+ | `'manual'` | Only `v-model:open`, the slot props or the exposed methods | Same, or any dismissal below |
129
117
 
130
118
  **Dismissal**
131
119
 
132
- | Prop | Type | Default | Description |
133
- | --------------------- | --------- | ------- | -------------------------------------------------------------------------------------------- |
134
- | `closeOnEscape` | `boolean` | `true` | Closes on Escape |
135
- | `closeOnClickOutside` | `boolean` | `true` | Closes on a pointer press outside the reference and the panel (on touch, on the tap's click) |
136
- | `closeOnFocusOut` | `boolean` | `true` | Closes when keyboard focus moves outside the reference and the panel |
137
-
138
- | Event | Payload | Description |
139
- | ------------- | --------------- | ----------------------------- |
140
- | `update:open` | `boolean` | Used by `v-model:open` |
141
- | `open` | `SbOpenReason` | The panel has opened, and why |
142
- | `close` | `SbCloseReason` | The panel has closed, and why |
143
-
144
- The reason tells what opened or closed the panel, e.g. for analytics or to react only to a dismissal:
145
-
146
- | Reason | `open` | `close` |
147
- | ----------- | -------------------------------------------------------------- | ---------------------------------------------------------------- |
148
- | `trigger` | Click on the reference, Enter, Space or an arrow key on it | Click on the reference, Enter or Space on it |
149
- | `hover` | Pointer over the reference (`hover` trigger) | Pointer left the reference and the panel (`hover` trigger) |
150
- | `focus` | Focus on the reference (`hover` and `focus` triggers) | — |
151
- | `focus-out` | — | Focus moved outside, e.g. Tab out of a menu |
152
- | `escape` | — | Escape, or the arrow key that closes a submenu |
153
- | `outside` | — | Press outside the reference and the panel |
154
- | `select` | — | Item activated with `closeOnSelect` |
155
- | `detached` | — | Reference scrolled out of view, with `hideWhenDetached: 'close'` |
156
- | `disabled` | — | The container was disabled |
157
- | `api` | `v-model:open`, slot props, exposed methods, `useSbFloating()` | Same, or the reference unmounting |
120
+ | Prop | Type | Default | Description |
121
+ | --- | --- | --- | --- |
122
+ | `closeOnEscape` | `boolean` | `true` | Closes on Escape |
123
+ | `closeOnClickOutside` | `boolean` | `true` | Closes on a pointer press outside the reference and the panel (on touch, on the tap's click) |
124
+ | `closeOnFocusOut` | `boolean` | `true` | Closes when keyboard focus moves outside the reference and the panel |
158
125
 
159
- ```vue
160
- <SbContainer @close="(reason) => reason === 'select' && track('menu-pick')">
161
- ```
126
+ | Event | Payload | Description |
127
+ | ------------- | ---------------------------- | ---------------------- |
128
+ | `update:open` | `boolean` | Used by `v-model:open` |
129
+ | `open` | `SbOpenReason` (deprecated) | The panel has opened |
130
+ | `close` | `SbCloseReason` (deprecated) | The panel has closed |
131
+
132
+ The reason passed to `open` and `close` (what opened or closed the panel) is deprecated and removed in v6; the events stay. Its values are described on the `SbOpenReason` and `SbCloseReason` types.
162
133
 
163
- **Slot props**: `isOpen`, `open()`, `close()`, `toggle()`, `placement` (resolved after flip), `side`, `align`, `floatingId` and `referenceId` (the `id` of the panel and of the reference, for ARIA attributes of your own).
134
+ **Slot props**: `isOpen`, `open()`, `close()`, `toggle()`, `placement` (resolved after flip) and `floatingId` (the `id` of the panel, for ARIA attributes of your own). `side`, `align` and `referenceId` are deprecated: read the side and alignment from `placement`, and pass your own `id` to `SbReference`.
164
135
 
165
- **Exposed** (template ref): `isOpen`, `open()`, `close()`, `toggle()`, `update()` (recomputes the position).
136
+ **Exposed** (template ref): `update()` (recomputes the position). `isOpen`, `open()`, `close()` and `toggle()` are deprecated: use `v-model:open`.
166
137
 
167
138
  ### `<SbReference>`
168
139
 
169
140
  The trigger the panel is anchored to.
170
141
 
171
- | Prop | Type | Default | Description |
172
- | -------------- | -------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
173
- | `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)) |
174
- | `id` | `string` | generated | `id` of the element, used to name the panel |
175
- | `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 |
142
+ | Prop | Type | Default | Description |
143
+ | --- | --- | --- | --- |
144
+ | `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)) |
145
+ | `id` | `string` | generated | `id` of the element, used to name the panel |
146
+ | `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 |
176
147
 
177
148
  Sets `aria-expanded`, `aria-controls` (while open) and `data-sbf-state`; a tooltip reference gets `aria-describedby` instead (see [Tooltip](#tooltip-1)). When the container is `disabled`, a `button` gets `disabled`, anything else gets `aria-disabled="true"` (and non-interactive tags `tabindex="-1"`). Your own `class`, attributes and listeners (`@click`, `@mouseenter`, …) are applied to the element; the click is not stopped.
178
149
 
@@ -182,16 +153,16 @@ With a component (e.g. the `Button` of your design system), its root element bec
182
153
 
183
154
  The panel, teleported to `body`. It is rendered while open and stays mounted until its leave transition has finished.
184
155
 
185
- | Prop | Type | Default | Description |
186
- | -------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
187
- | `as` | `string \| Component` | `'div'` | Tag or component of the panel element; a component must render a single root element |
188
- | `role` | `SbFloatingRole \| null` | `'dialog'` | ARIA role of the panel; `null` renders no role (disclosure pattern). Known roles are suggested by your editor, any string is accepted |
189
- | `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 |
190
- | `teleport` | `string \| HTMLElement \| false` | `'body'` | Teleport target (selector or element); `false` renders the panel in place |
191
- | `zIndex` | `number \| string` | — | `z-index` of the positioned wrapper. Use this, not a `z-*` class on the panel (see [Stacking](#stacking)) |
192
- | `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) |
193
- | `keyboardNavigation` | `boolean` | `true` | Handles the keyboard model of `role="menu"` and `role="listbox"` panels (see [Keyboard](#keyboard)). No effect with other roles |
194
- | `closeOnSelect` | `boolean` | `false` | With `role="menu"` / `"listbox"`, activating a `menuitem` / `option` closes the panel (a submenu: with its parent menus) and gives focus back to the reference, after the item's `click` handler. Not for `menuitemcheckbox` / `menuitemradio`, submenu triggers, disabled items or a listbox with `aria-multiselectable="true"`; `event.preventDefault()` in the handler keeps it open |
156
+ | Prop | Type | Default | Description |
157
+ | --- | --- | --- | --- |
158
+ | `as` | `string \| Component` | `'div'` | Tag or component of the panel element; a component must render a single root element |
159
+ | `role` | `SbFloatingRole \| null` | `'dialog'` | ARIA role of the panel; `null` renders no role (disclosure pattern). Known roles are suggested by your editor, any string is accepted |
160
+ | `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 |
161
+ | `teleport` | `string \| HTMLElement \| false` | `'body'` | Teleport target (selector or element); `false` renders the panel in place |
162
+ | `zIndex` | `number \| string` | — | `z-index` of the positioned wrapper. Use this, not a `z-*` class on the panel (see [Stacking](#stacking)) |
163
+ | `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) |
164
+ | `keyboardNavigation` | `boolean` | `true` | Handles the keyboard model of `role="menu"` and `role="listbox"` panels (see [Keyboard](#keyboard)). No effect with other roles |
165
+ | `closeOnSelect` | `boolean` | `false` | With `role="menu"` / `"listbox"`, activating a `menuitem` / `option` closes the panel (a submenu: with its parent menus) and gives focus back to the reference, after the item's `click` handler. Not for `menuitemcheckbox` / `menuitemradio`, submenu triggers, disabled items or a listbox with `aria-multiselectable="true"`; `event.preventDefault()` in the handler keeps it open |
195
166
 
196
167
  | Event | Description |
197
168
  | ------------- | ---------------------------------------------------------- |
@@ -221,24 +192,7 @@ Call it in `setup` of a component rendered inside an `SbContainer` (in the refer
221
192
 
222
193
  ### Types
223
194
 
224
- ```ts
225
- import type {
226
- SbContainerProps,
227
- SbContainerSlotProps,
228
- SbContainerExposed,
229
- SbReferenceProps,
230
- SbFloatingProps,
231
- SbTrigger,
232
- SbPlacement,
233
- SbSide,
234
- SbAlignment,
235
- SbStrategy,
236
- SbFloatingRole,
237
- SbOpenReason,
238
- SbCloseReason,
239
- UseSbFloatingReturn,
240
- } from 'sb-floating-panel-vue';
241
- ```
195
+ Every prop, slot prop, event and option type is exported (`SbContainerProps`, `SbPlacement`, `SbFloatingRole`, …): import them with `import type { … } from 'sb-floating-panel-vue'`.
242
196
 
243
197
  ---
244
198
 
@@ -250,23 +204,23 @@ import type {
250
204
  - **Focus.** By default (`autoFocus`) focus moves inside the panel once it is positioned. When the panel closes with focus inside it, focus returns to the reference without scrolling the page. When it unmounts open with focus inside, focus is not left on the body: it goes back to the reference when only `SbFloating` is removed, and to what follows the reference in the page when the reference is removed too.
251
205
  - <a id="keyboard"></a>**Keyboard.** With `role="menu"` or `role="listbox"` the components follow the WAI-ARIA [menu button](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/) and [listbox](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/) patterns. Items are the elements with role `menuitem`, `menuitemcheckbox`, `menuitemradio` (menu) or `option` (listbox); they must be focusable (a `button`, or `tabindex="-1"`). Turn it off with `:keyboard-navigation="false"`.
252
206
 
253
- | Key | On the reference | In the panel |
254
- | ---------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
255
- | ArrowDown / ArrowUp | Opens on the first / last item (or the selected option), except with the `manual` trigger | Next / previous item; a menu wraps around, a listbox stops at its ends |
256
- | Home / End | — | First / last item |
257
- | Letters | — | Next item whose text starts with the typed letters |
258
- | Enter / Space | Toggles | Clicks items that are not buttons or links |
259
- | Tab / Shift+Tab | — | Menu: closes it (and the menus it opened from) and moves focus on, as from the reference |
260
- | ArrowRight / ArrowLeft | A submenu trigger: ArrowRight opens the submenu on its first item | A submenu: ArrowLeft closes it and focuses its trigger |
207
+ | Key | On the reference | In the panel |
208
+ | --- | --- | --- |
209
+ | ArrowDown / ArrowUp | Opens on the first / last item (or the selected option), except with the `manual` trigger | Next / previous item; a menu wraps around, a listbox stops at its ends |
210
+ | Home / End | — | First / last item |
211
+ | Letters | — | Next item whose text starts with the typed letters |
212
+ | Enter / Space | Toggles | Clicks items that are not buttons or links |
213
+ | Tab / Shift+Tab | — | Menu: closes it (and the menus it opened from) and moves focus on, as from the reference |
214
+ | ArrowRight / ArrowLeft | A submenu trigger: ArrowRight opens the submenu on its first item | A submenu: ArrowLeft closes it and focuses its trigger |
261
215
 
262
216
  A submenu is a menu `SbContainer` inside a menu panel; its arrows are mirrored in right-to-left text. Keys whose `keydown` you already handled with `event.preventDefault()`, and keys typed in text fields, are left alone.
263
217
 
264
218
  - <a id="tooltip-1"></a>**Tooltip.** With `role="tooltip"` the panel follows the WAI-ARIA [tooltip](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/) pattern: the reference gets no `aria-expanded` nor `aria-controls`, and while open `aria-describedby` points to the panel (pass your own `aria-describedby` on `SbReference` to replace it). Focus stays on the reference and no focus guards are rendered. Use it with `trigger="hover"`, which meets WCAG [1.4.13](https://www.w3.org/WAI/WCAG22/Understanding/content-on-hover-or-focus): it opens on keyboard focus too, the pointer can move onto the tooltip within `delay.close`, and Escape dismisses it.
265
219
  - **Dismissal.**
266
- - Escape closes the open panel that has focus, else the most recently opened one. It is ignored when a widget inside has already handled it (`event.preventDefault()`) and while an IME composition is in progress.
220
+ - Escape closes the open panel that has focus, else the most recently opened one, skipping panels with `closeOnEscape` set to `false`. It is ignored when a widget inside has already handled it (`event.preventDefault()`) and while an IME composition is in progress.
267
221
  - A press outside closes the panel; on touch, only a tap does, so starting a scroll keeps it open.
268
222
  - Moving focus outside (e.g. tabbing past the last element) closes it.
269
- - A container nested inside a panel counts as part of it.
223
+ - A container nested inside a panel counts as part of it. Anything else outside the reference and the panel does not: a datepicker, select or other popup of another library, teleported out of the panel, closes it when pressed or focused. Set `:close-on-click-outside="false"` and `:close-on-focus-out="false"` on that container and close it yourself.
270
224
  - **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`); add `closeOnSelect` to close the panel after it. Use valid structure: items directly inside the `menu` / `listbox`, or inside `ul` / `li` with `role="none"`.
271
225
  - <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.
272
226
 
@@ -296,22 +250,22 @@ Your styles, transitions and transforms go on the panel, so they never fight wit
296
250
 
297
251
  ### Data attributes
298
252
 
299
- | Attribute | Values | On | Use |
300
- | ---------------- | ----------------------------------- | --------- | -------------------------------------------------------------------------- |
301
- | `data-sbf-state` | `open` / `closed` | reference | Open/closed styles (e.g. rotate an icon) |
302
- | `data-sbf-state` | `open` / `closed` | panel | `closed` from the start of the leave transition: style or animate the exit |
303
- | `data-sbf-side` | `top` / `bottom` / `left` / `right` | panel | Real side after flip: directional animation |
304
- | `data-sbf-align` | `start` / `end` / absent | panel | Alignment part of the placement |
253
+ | Attribute | Values | On | Use |
254
+ | --- | --- | --- | --- |
255
+ | `data-sbf-state` | `open` / `closed` | reference | Open/closed styles (e.g. rotate an icon) |
256
+ | `data-sbf-state` | `open` / `closed` | panel | `closed` from the start of the leave transition: style or animate the exit |
257
+ | `data-sbf-side` | `top` / `bottom` / `left` / `right` | panel | Real side after flip: directional animation |
258
+ | `data-sbf-align` | `start` / `end` / absent | panel | Alignment part of the placement |
305
259
 
306
260
  ### CSS variables
307
261
 
308
262
  Set on the wrapper and inherited by the panel:
309
263
 
310
- | Variable | Meaning |
311
- | ------------------------ | ------------------------------------------------------------------------ |
312
- | `--sbf-reference-width` | Width of the reference |
313
- | `--sbf-available-width` | Horizontal space left before overflowing the viewport |
314
- | `--sbf-available-height` | Vertical space left before overflowing the viewport |
264
+ | Variable | Meaning |
265
+ | --- | --- |
266
+ | `--sbf-reference-width` | Width of the reference |
267
+ | `--sbf-available-width` | Horizontal space left before overflowing the viewport |
268
+ | `--sbf-available-height` | Vertical space left before overflowing the viewport |
315
269
  | `--sbf-transform-origin` | Corner or edge of the panel facing the reference, for `transform-origin` |
316
270
 
317
271
  All attributes and variables use the `sbf` prefix, so they do not clash with other libraries in the same app (e.g. Radix / Reka UI `data-state`).
@@ -397,20 +351,7 @@ Panels are teleported to `body`, so their styles cannot be `scoped` to the compo
397
351
 
398
352
  ## 🧭 How It Works
399
353
 
400
- ### Shared state
401
-
402
- `SbContainer` owns the state (open state, positioning, ARIA ids) and shares it with its descendants through Vue `provide`/`inject`:
403
-
404
- ```
405
- SbContainer ── provides the state ──┬─▶ SbReference
406
- └─▶ SbFloating
407
- ```
408
-
409
- - `SbReference` and `SbFloating` find their container on their own: you pass no refs, styles or handlers, and never write `.value`.
410
- - The lookup follows the component tree, not the DOM, so it keeps working although `SbFloating` is teleported to `body`.
411
- - They can sit at any depth inside the container slot, also inside your own components, which can read the same state with [`useSbFloating()`](#usesbfloating).
412
- - Used outside an `SbContainer`, they throw: `[sb-floating-panel-vue] <SbFloating> must be used inside <SbContainer>.`
413
- - 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, about a text field reference of a `menu` or `listbox` panel, about a `tooltip` opened by click, and, the first time the arrow keys look for them, about a `menu` or `listbox` with no items or with items that cannot take focus. The warnings depend on `process.env.NODE_ENV`, which your bundler replaces: they are left out of production builds.
354
+ `SbContainer` owns the state (open state, positioning, ARIA ids) and shares it with `SbReference` and `SbFloating` through `provide`/`inject`. The lookup follows the component tree, not the DOM, so it keeps working although `SbFloating` is teleported to `body`; used outside an `SbContainer`, they throw.
414
355
 
415
356
  ### Nested containers
416
357
 
@@ -420,92 +361,19 @@ An `SbContainer` placed inside another one (e.g. a submenu in a menu panel) regi
420
361
  - Escape closes the innermost open panel first, then the next one;
421
362
  - a press outside all of them closes them all.
422
363
 
423
- ### From click to open panel
364
+ ### Development warnings
424
365
 
425
- 1. **Click** on `SbReference` (or Enter/Space on a non-interactive tag). With `trigger="click"` and the container not `disabled`, the container toggles: it emits `update:open` and, without `v-model:open`, updates its own state.
426
- 2. **Open state changes.** `SbReference` gets `aria-expanded="true"`, `aria-controls` and `data-sbf-state="open"`; the container starts listening for Escape, outside presses and focus moving outside (only while open) and emits `open`.
427
- 3. **The panel mounts.** `SbFloating` renders the wrapper in the teleport target, with the panel and two hidden focus guards inside it; the enter classes of your `transition`, if any, are applied to the panel.
428
- 4. **Positioning.** Floating UI starts tracking the reference (scroll, resize, layout changes) and computes the position with `offset`, `flip`, `shift`, `size` and, with `hideWhenDetached`, `hide`. `size` writes the `--sbf-*` variables and applies `matchWidth`; the position is written on the wrapper.
429
- 5. **Positioned.** With `autoFocus` (the default), focus moves inside the panel now, not earlier, so the page does not jump to the panel's initial (0, 0) position.
366
+ 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, about a text field reference of a `menu` or `listbox` panel, about a `tooltip` opened by click, and, the first time the arrow keys look for them, about a `menu` or `listbox` with no items or with items that cannot take focus. The warnings depend on `process.env.NODE_ENV`, which your bundler replaces: they are left out of production builds.
430
367
 
431
- Closing (click on the reference, `close()`, Escape, press outside, focus moving outside, the pointer leaving with `trigger="hover"`, `v-model:open` set to `false`, `disabled`, or the reference unmounting):
368
+ ### Directives on `SbFloating`
432
369
 
433
- 1. If focus is inside the panel, it goes back to the reference without scrolling the page; it stays where it is when a press outside lands on another focusable element. The container emits `close`.
434
- 2. The panel gets `data-sbf-state="closed"` and the leave classes of your `transition`; the wrapper stays mounted and positioned until the animation ends (immediately without `transition`).
435
- 3. The wrapper is removed, Floating UI stops tracking, `SbFloating` emits `after-leave`.
370
+ `<SbFloating>` renders a `<teleport>` root, so a custom directive applied to the component is not inherited and Vue warns about it: put the directive on an element inside the slot.
436
371
 
437
372
  ---
438
373
 
439
- ## 🔀 Migrating from v4
440
-
441
- v5 is a breaking release.
442
-
443
- - **`teleportTo` and `teleportDisabled` are replaced by `teleport`**: `teleport-to="#overlays"` becomes `teleport="#overlays"`, `teleport-disabled` becomes `:teleport="false"`.
444
- - **Menus and listboxes handle the keyboard.** Remove your own arrow-key, Home/End and typeahead handlers on `role="menu"` and `role="listbox"` panels, or keep them and call `event.preventDefault()` on the keys they handle (the library then leaves those keys alone), or set `:keyboard-navigation="false"`. Tab now closes a menu.
445
- - **Tooltips.** With `role="tooltip"` the reference no longer gets `aria-expanded` nor `aria-controls`, and gets `aria-describedby` pointing to the panel while open; `autoFocus` defaults to `false`. Replace a `trigger="manual"` tooltip opened with your own `@mouseenter` / `@focus` handlers by `trigger="hover"`.
446
- - **`SbTrigger` also takes `'hover'` and `'focus'`**: a `switch` over it in your code may need the new cases.
447
- - **The panel closes when its reference unmounts** while open.
448
- - **Escape closes the open panel that has focus**, else the most recently opened one (it was always the most recently opened one).
374
+ ## 🔀 Migrating
449
375
 
450
- ## 🔀 Migrating from v3
451
-
452
- v4 changed these defaults:
453
-
454
- - **`autoFocus` is `true`.** Focus moves inside the panel when it opens. Set `:auto-focus="false"` on panels that must not take focus, such as tooltips.
455
- - **`ariaHaspopup` follows the panel `role`** instead of defaulting to `dialog`: drop the matching `aria-haspopup` you set by hand, keep it only where it differs from the role.
456
- - **Focus moving outside closes the panel.** Set `:close-on-focus-out="false"` to keep the previous behaviour.
457
- - **The panel is named after the reference** with `aria-labelledby`; pass `aria-label` or `aria-labelledby` to name it differently.
458
- - **Escape** no longer closes the panel when a widget inside called `preventDefault()` on it, and it now calls `preventDefault()` itself.
459
- - **`data-sbf-state` on the panel** becomes `closed` during the leave transition.
460
-
461
- ---
462
-
463
- ## 🔀 Migrating from v2
464
-
465
- v3 is a breaking release.
466
-
467
- - **The composable is no longer exported.** Use the components; for full control use `@floating-ui/vue` directly.
468
- - **No refs or styles in the slot.** Remove `reference-ref`, `floating-ref`, `is-open`, `floating-placement`, `floating-style` and `:on-click`: the components share their state.
469
- - **No arrow.** `hasArrow`, `floating-arrow-ref`, `floating-arrow-style` and `arrow-dimensions` are gone.
470
- - **No CSS.** Remove `import 'sb-floating-panel-vue/style.css'`. `animation` and `duration` are replaced by `transition`, the name of your own `<Transition>` classes.
471
- - **Renamed props:** `offsetValue` → `offset` (default `15` → `8`), `hasResize` → `matchWidth`.
472
- - **New defaults:** `padding: 8` from the viewport edges; Escape and click outside close the panel.
473
- - `SbContainer` no longer renders a `<div>`; `SbReference` no longer stops click propagation and sets `type="button"`.
474
-
475
- ```vue
476
- <!-- v2 -->
477
- <SbContainer :has-resize="true">
478
- <template #default="{ reference, floating, floatingPlacement, floatingStyle, isOpen, toggle }">
479
- <SbReference :reference-ref="reference" :on-click="toggle">Toggle</SbReference>
480
- <SbFloating
481
- :is-open="isOpen.value"
482
- :floating-ref="floating"
483
- :floating-placement="floatingPlacement.value"
484
- :floating-style="floatingStyle.value"
485
- >
486
- Hello Panel!
487
- </SbFloating>
488
- </template>
489
- </SbContainer>
490
-
491
- <!-- v3 -->
492
- <SbContainer match-width>
493
- <SbReference>Toggle</SbReference>
494
- <SbFloating>Hello Panel!</SbFloating>
495
- </SbContainer>
496
- ```
497
-
498
- ---
499
-
500
- ## ⚠️ Directive Note (Teleport Limitation)
501
-
502
- `<SbFloating>` renders a `<teleport>` root, so a custom directive applied directly to the component cannot be inherited and Vue warns about it. Move the directive to an element inside the slot:
503
-
504
- ```vue
505
- <SbFloating>
506
- <div v-my-directive>...</div>
507
- </SbFloating>
508
- ```
376
+ Breaking changes between major versions and how to update: [MIGRATING.md](MIGRATING.md).
509
377
 
510
378
  ---
511
379
 
@@ -18,8 +18,8 @@ declare const __VLS_export: __VLS_WithSlots<import('vue').DefineComponent<Omit<S
18
18
  disabled: boolean;
19
19
  placement: import('..').SbPlacement;
20
20
  strategy: import('..').SbStrategy;
21
- padding: number;
22
21
  offset: number;
22
+ padding: number;
23
23
  matchWidth: boolean | "min";
24
24
  hideWhenDetached: boolean | "close";
25
25
  defaultOpen: boolean;
@@ -1,4 +1,3 @@
1
- import { TransitionProps } from 'vue';
2
1
  import { SbFloatingProps } from '../types';
3
2
  declare var __VLS_26: {};
4
3
  type __VLS_Slots = {} & {
@@ -13,9 +12,7 @@ declare const __VLS_base: import('vue').DefineComponent<SbFloatingProps, {}, {},
13
12
  }>, {
14
13
  as: string | import('vue').Component;
15
14
  role: import('..').SbFloatingRole | null;
16
- transition: string | TransitionProps;
17
15
  teleport: string | false | HTMLElement;
18
- zIndex: string | number;
19
16
  autoFocus: boolean;
20
17
  keyboardNavigation: boolean;
21
18
  closeOnSelect: boolean;
@@ -5,8 +5,6 @@ type __VLS_Slots = {} & {
5
5
  };
6
6
  declare const __VLS_base: import('vue').DefineComponent<SbReferenceProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<SbReferenceProps> & Readonly<{}>, {
7
7
  as: string | import('vue').Component;
8
- id: string;
9
- ariaHaspopup: "dialog" | "menu" | "listbox" | "tree" | "grid" | "true" | "false";
10
8
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
11
9
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
12
10
  declare const _default: typeof __VLS_export;
@@ -1,18 +1,7 @@
1
- import { ComputedRef, CSSProperties, MaybeRefOrGetter, Ref } from 'vue';
2
- import { SbAlignment, SbPlacement, SbSide, SbStrategy } from '../types';
3
- /**
4
- * Options of the internal `useSbFloatingPanel` composable. Every option can be a value,
5
- * a ref or a getter, so `SbContainer` props stay reactive.
6
- */
7
- export interface FloatingPanelOptions {
8
- isOpen: MaybeRefOrGetter<boolean>;
9
- placement: MaybeRefOrGetter<SbPlacement>;
10
- strategy: MaybeRefOrGetter<SbStrategy>;
11
- offset: MaybeRefOrGetter<number>;
12
- padding: MaybeRefOrGetter<number>;
13
- matchWidth: MaybeRefOrGetter<boolean | 'min'>;
14
- hideWhenDetached: MaybeRefOrGetter<boolean | 'close'>;
15
- }
1
+ import { ComputedRef, CSSProperties, Ref } from 'vue';
2
+ import { SbAlignment, SbContainerProps, SbPlacement, SbSide } from '../types';
3
+ /** Positioning props of `SbContainer`, with their defaults: read reactively. */
4
+ type PositioningProps = Readonly<Required<Pick<SbContainerProps, 'placement' | 'strategy' | 'offset' | 'padding' | 'matchWidth' | 'hideWhenDetached'>>>;
16
5
  export interface FloatingPanel {
17
6
  reference: Ref<HTMLElement | null>;
18
7
  floating: Ref<HTMLElement | null>;
@@ -30,4 +19,5 @@ export interface FloatingPanel {
30
19
  /**
31
20
  * Wraps Floating UI for the components. Not part of the public API.
32
21
  */
33
- export declare function useSbFloatingPanel(options: FloatingPanelOptions): FloatingPanel;
22
+ export declare function useSbFloatingPanel(props: PositioningProps, isOpen: Readonly<Ref<boolean>>): FloatingPanel;
23
+ export {};