sb-floating-panel-vue 5.0.3 → 5.2.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
@@ -65,18 +65,16 @@ import { SbContainer, SbReference, SbFloating } from 'sb-floating-panel-vue';
65
65
 
66
66
  ### Menu
67
67
 
68
- With `role="menu"` the library handles the keyboard: ArrowDown on the trigger opens the menu on its first item, the arrow keys, Home, End and typing move between items, Tab leaves (see [Keyboard](#keyboard)). Activating an item is up to its `click` handler.
68
+ With `role="menu"` the library handles the keyboard: ArrowDown on the trigger opens the menu on its first item, the arrow keys, Home, End and typing move between items, Tab leaves (see [Keyboard](#keyboard)). Activating an item runs its `click` handler; with `close-on-select` the menu then closes and focus goes back to the trigger.
69
69
 
70
70
  ```vue
71
71
  <SbContainer match-width="min">
72
- <template #default="{ close }">
73
- <SbReference class="my-trigger">Account</SbReference>
74
- <SbFloating role="menu" class="my-panel">
75
- <button v-for="item in ['Profile', 'Settings', 'Sign out']" :key="item" type="button" role="menuitem" @click="close()">
76
- {{ item }}
77
- </button>
78
- </SbFloating>
79
- </template>
72
+ <SbReference class="my-trigger">Account</SbReference>
73
+ <SbFloating role="menu" close-on-select class="my-panel">
74
+ <button v-for="item in ['Profile', 'Settings', 'Sign out']" :key="item" type="button" role="menuitem" @click="go(item)">
75
+ {{ item }}
76
+ </button>
77
+ </SbFloating>
80
78
  </SbContainer>
81
79
  ```
82
80
 
@@ -103,45 +101,64 @@ Holds the state and the positioning. Renders no element of its own, only its slo
103
101
 
104
102
  **Positioning**
105
103
 
106
- | Prop | Type | Default | Description |
107
- |--------------------|-------------------------|------------------|--------------------------------------------------------------------------------------|
108
- | `placement` | `SbPlacement` | `'bottom-start'` | Preferred placement; the panel flips when there is not enough space |
109
- | `strategy` | `'absolute' \| 'fixed'` | `'absolute'` | CSS position strategy. Use `fixed` inside scroll containers or fixed layouts |
110
- | `offset` | `number` | `8` | Distance in px between reference and panel |
111
- | `padding` | `number` | `8` | Minimum distance in px between the panel and the viewport edges |
112
- | `matchWidth` | `boolean \| 'min'` | `false` | `true` sets `width`, `'min'` sets `min-width` to the reference width |
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 |
113
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 |
114
112
 
115
113
  **Open state**
116
114
 
117
- | Prop | Type | Default | Description |
118
- |----------------|-----------|---------|--------------------------------------------------------------|
119
- | `v-model:open` | `boolean` | — | Open state. Leave it unset to let the container manage it |
120
- | `defaultOpen` | `boolean` | `false` | Initial open state when `v-model:open` is not used |
121
- | `disabled` | `boolean` | `false` | Prevents opening; closes the panel if it is open |
122
- | `trigger` | `SbTrigger` | `'click'` | How the reference opens the panel, see below |
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 |
123
121
  | `delay` | `number \| { open?: number; close?: number }` | `{ open: 300, close: 150 }` | Milliseconds before the `hover` trigger opens and closes; a number sets both |
124
122
 
125
- | `trigger` | Opens | Closes |
126
- |------------|----------------------------------------------------------|------------------------------------------------------------------------|
127
- | `'click'` | Click (Enter/Space) on the reference toggles the panel | Same, or any dismissal below |
123
+ | `trigger` | Opens | Closes |
124
+ | ---------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
125
+ | `'click'` | Click (Enter/Space) on the reference toggles the panel | Same, or any dismissal below |
128
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 |
129
- | `'focus'` | Focus on the reference | Focus moving elsewhere |
130
- | `'manual'` | Only `v-model:open`, the slot props or the exposed methods | Same, or any dismissal below |
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 |
131
129
 
132
130
  **Dismissal**
133
131
 
134
132
  | Prop | Type | Default | Description |
135
- |-----------------------|-----------|---------|----------------------------------------------------------------------------------------------|
133
+ | --------------------- | --------- | ------- | -------------------------------------------------------------------------------------------- |
136
134
  | `closeOnEscape` | `boolean` | `true` | Closes on Escape |
137
135
  | `closeOnClickOutside` | `boolean` | `true` | Closes on a pointer press outside the reference and the panel (on touch, on the tap's click) |
138
136
  | `closeOnFocusOut` | `boolean` | `true` | Closes when keyboard focus moves outside the reference and the panel |
139
137
 
140
- | Event | Payload | Description |
141
- |---------------|-----------|--------------------------|
142
- | `update:open` | `boolean` | Used by `v-model:open` |
143
- | `open` | — | The panel has opened |
144
- | `close` | — | The panel has closed |
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 |
158
+
159
+ ```vue
160
+ <SbContainer @close="(reason) => reason === 'select' && track('menu-pick')">
161
+ ```
145
162
 
146
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).
147
164
 
@@ -151,11 +168,11 @@ Holds the state and the positioning. Renders no element of its own, only its slo
151
168
 
152
169
  The trigger the panel is anchored to.
153
170
 
154
- | Prop | Type | Default | Description |
155
- |----------------|-----------------------|----------------|--------------------------------------------------------------------------------------------------------------|
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
- | `id` | `string` | generated | `id` of the element, used to name the panel |
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 |
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 |
159
176
 
160
177
  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.
161
178
 
@@ -165,23 +182,43 @@ With a component (e.g. the `Button` of your design system), its root element bec
165
182
 
166
183
  The panel, teleported to `body`. It is rendered while open and stays mounted until its leave transition has finished.
167
184
 
168
- | Prop | Type | Default | Description |
169
- |----------------------|----------------------------------|------------|-------------|
170
- | `as` | `string \| Component` | `'div'` | Tag or component of the panel element; a component must render a single root element |
171
- | `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 |
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
- | `teleport` | `string \| HTMLElement \| false` | `'body'` | Teleport target (selector or element); `false` renders the panel in place |
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"`, 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
- | `keyboardNavigation` | `boolean` | `true` | Handles the keyboard model of `role="menu"` and `role="listbox"` panels (see [Keyboard](#keyboard)). No effect with other roles |
177
-
178
- | Event | Description |
179
- |---------------|----------------------------------------------------------------|
180
- | `after-enter` | The enter transition has finished |
181
- | `after-leave` | The leave transition has finished and the panel is removed |
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 |
195
+
196
+ | Event | Description |
197
+ | ------------- | ---------------------------------------------------------- |
198
+ | `after-enter` | The enter transition has finished |
199
+ | `after-leave` | The leave transition has finished and the panel is removed |
182
200
 
183
201
  With a popup role (`dialog`, `alertdialog`, `menu`, `listbox`, `tree`, `grid`), the panel is named after the reference with `aria-labelledby`, unless you pass `aria-label` or `aria-labelledby` yourself. When focus is inside the panel as it closes, it goes back to the reference.
184
202
 
203
+ ### `useSbFloating()`
204
+
205
+ The open state and actions of the closest `SbContainer`, for your own components inside it: a `MenuItem` of your design system can close the menu without `close` passed down from the slot.
206
+
207
+ ```ts
208
+ import { useSbFloating } from 'sb-floating-panel-vue';
209
+
210
+ const { isOpen, open, close, toggle } = useSbFloating();
211
+ ```
212
+
213
+ | Returns | Type | Description |
214
+ | -------- | ------------------------ | ------------------------- |
215
+ | `isOpen` | `Readonly<Ref<boolean>>` | Whether the panel is open |
216
+ | `open` | `() => void` | Opens the panel |
217
+ | `close` | `() => void` | Closes the panel |
218
+ | `toggle` | `() => void` | Toggles the panel |
219
+
220
+ Call it in `setup` of a component rendered inside an `SbContainer` (in the reference, the panel or anywhere in its slot); outside one it throws.
221
+
185
222
  ### Types
186
223
 
187
224
  ```ts
@@ -197,6 +234,9 @@ import type {
197
234
  SbAlignment,
198
235
  SbStrategy,
199
236
  SbFloatingRole,
237
+ SbOpenReason,
238
+ SbCloseReason,
239
+ UseSbFloatingReturn,
200
240
  } from 'sb-floating-panel-vue';
201
241
  ```
202
242
 
@@ -206,27 +246,28 @@ import type {
206
246
 
207
247
  - `SbReference` gets an `id`, `aria-expanded`, `aria-controls` (pointing to the panel `id`) and `aria-haspopup` (following the panel `role`); non-interactive tags get `role="button"`, `tabindex="0"` and Enter/Space.
208
248
  - `SbFloating` gets an `id` generated with Vue `useId()` (SSR safe), a `role` (`dialog` by default) and, with a popup role (`dialog`, `alertdialog`, `menu`, `listbox`, `tree`, `grid`), `aria-labelledby` pointing to the reference.
209
- - **Tab order.** The panel is teleported to the end of `body`, so the library keeps the order a reader expects: Tab on the open reference enters the panel, Shift+Tab from its first element returns to the reference, Tab from its last element moves on to what follows the reference. Two hidden focus guards (`data-sbf-focus-guard`) around the panel do this; they are not rendered with `:teleport="false"` nor around a tooltip.
210
- - **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.
249
+ - **Tab order.** The panel is teleported to the end of `body`, so the library keeps the order a reader expects: Tab on the open reference enters the panel, Shift+Tab from its first element returns to the reference, Tab from its last element moves on to what follows the reference, and Shift+Tab from there enters the panel again from its last element while it is still open (with `closeOnFocusOut: false`). Two hidden focus guards (`data-sbf-focus-guard`) around the panel do this; they are not rendered with `:teleport="false"` nor around a tooltip.
250
+ - **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.
211
251
  - <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"`.
212
252
 
213
- | Key | On the reference | In the panel |
214
- |-----|------------------|--------------|
215
- | 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 |
216
- | Home / End | — | First / last item |
217
- | Letters | — | Next item whose text starts with the typed letters |
218
- | Enter / Space | Toggles | Clicks items that are not buttons or links |
219
- | Tab / Shift+Tab | — | Menu: closes it (and the menus it opened from) and moves focus on, as from the reference |
220
- | ArrowRight / ArrowLeft | A submenu trigger: ArrowRight opens the submenu on its first item | A submenu: ArrowLeft closes it and focuses its trigger |
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 |
221
261
 
222
262
  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
+
223
264
  - <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.
224
265
  - **Dismissal.**
225
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.
226
267
  - A press outside closes the panel; on touch, only a tap does, so starting a scroll keeps it open.
227
268
  - Moving focus outside (e.g. tabbing past the last element) closes it.
228
269
  - A container nested inside a panel counts as part of it.
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"`.
270
+ - **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"`.
230
271
  - <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.
231
272
 
232
273
  ---
@@ -239,7 +280,13 @@ The library writes values on the elements; your CSS reads them. `SbFloating` ren
239
280
  <!-- wrapper: positioning only (Floating UI styles, z-index, CSS variables) -->
240
281
  <div style="position: absolute; transform: translate(120px, 40px); --sbf-reference-width: 180px; …">
241
282
  <!-- panel: your class, style and attributes -->
242
- <div class="my-panel" role="dialog" data-sbf-state="open" data-sbf-side="bottom" data-sbf-align="start">
283
+ <div
284
+ class="my-panel"
285
+ role="dialog"
286
+ data-sbf-state="open"
287
+ data-sbf-side="bottom"
288
+ data-sbf-align="start"
289
+ >
243
290
  …
244
291
  </div>
245
292
  </div>
@@ -249,22 +296,22 @@ Your styles, transitions and transforms go on the panel, so they never fight wit
249
296
 
250
297
  ### Data attributes
251
298
 
252
- | Attribute | Values | On | Use |
253
- |------------------|--------------------------------------|--------------------------|---------------------------------------------|
254
- | `data-sbf-state` | `open` / `closed` | reference | Open/closed styles (e.g. rotate an icon) |
255
- | `data-sbf-state` | `open` / `closed` | panel | `closed` from the start of the leave transition: style or animate the exit |
256
- | `data-sbf-side` | `top` / `bottom` / `left` / `right` | panel | Real side after flip: directional animation |
257
- | `data-sbf-align` | `start` / `end` / absent | panel | Alignment part of the placement |
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 |
258
305
 
259
306
  ### CSS variables
260
307
 
261
308
  Set on the wrapper and inherited by the panel:
262
309
 
263
- | Variable | Meaning |
264
- |--------------------------|------------------------------------------------------|
265
- | `--sbf-reference-width` | Width of the reference |
266
- | `--sbf-available-width` | Horizontal space left before overflowing the viewport |
267
- | `--sbf-available-height` | Vertical space left before overflowing the viewport |
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 |
268
315
  | `--sbf-transform-origin` | Corner or edge of the panel facing the reference, for `transform-origin` |
269
316
 
270
317
  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`).
@@ -294,7 +341,9 @@ Panels are teleported to `body`, so their styles cannot be `scoped` to the compo
294
341
  .pop-enter-active,
295
342
  .pop-leave-active {
296
343
  transform-origin: var(--sbf-transform-origin);
297
- transition: opacity 150ms ease, transform 150ms ease;
344
+ transition:
345
+ opacity 150ms ease,
346
+ transform 150ms ease;
298
347
  }
299
348
  .pop-enter-from,
300
349
  .pop-leave-to {
@@ -303,8 +352,12 @@ Panels are teleported to `body`, so their styles cannot be `scoped` to the compo
303
352
  }
304
353
 
305
354
  /* direction from the side resolved after flip */
306
- .pop-enter-from[data-sbf-side='bottom'] { transform: translateY(-6px); }
307
- .pop-enter-from[data-sbf-side='top'] { transform: translateY(6px); }
355
+ .pop-enter-from[data-sbf-side='bottom'] {
356
+ transform: translateY(-6px);
357
+ }
358
+ .pop-enter-from[data-sbf-side='top'] {
359
+ transform: translateY(6px);
360
+ }
308
361
 
309
362
  /* respect the user's motion setting */
310
363
  @media (prefers-reduced-motion: reduce) {
@@ -355,9 +408,9 @@ SbContainer ── provides the state ──┬─▶ SbReference
355
408
 
356
409
  - `SbReference` and `SbFloating` find their container on their own: you pass no refs, styles or handlers, and never write `.value`.
357
410
  - The lookup follows the component tree, not the DOM, so it keeps working although `SbFloating` is teleported to `body`.
358
- - They can sit at any depth inside the container slot, also inside your own components.
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).
359
412
  - Used outside an `SbContainer`, they throw: `[sb-floating-panel-vue] <SbFloating> must be used inside <SbContainer>.`
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, 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.
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.
361
414
 
362
415
  ### Nested containers
363
416
 
@@ -1,19 +1,21 @@
1
- import { SbContainerExposed, SbContainerProps, SbContainerSlotProps } from '../types';
1
+ import { SbCloseReason, SbContainerExposed, SbContainerProps, SbContainerSlotProps, SbOpenReason } from '../types';
2
2
  declare const _default: typeof __VLS_export;
3
3
  export default _default;
4
4
  declare const __VLS_export: __VLS_WithSlots<import('vue').DefineComponent<Omit<SbContainerProps, "open"> & {
5
5
  open?: boolean | undefined;
6
6
  }, SbContainerExposed, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
7
- close: () => any;
8
- open: () => any;
7
+ close: (reason: SbCloseReason) => any;
8
+ open: (reason: SbOpenReason) => any;
9
9
  "update:open": (value: boolean | undefined) => any;
10
10
  }, string, import('vue').PublicProps, Readonly<Omit<SbContainerProps, "open"> & {
11
11
  open?: boolean | undefined;
12
12
  }> & Readonly<{
13
- onClose?: (() => any) | undefined;
14
- onOpen?: (() => any) | undefined;
13
+ onClose?: ((reason: SbCloseReason) => any) | undefined;
14
+ onOpen?: ((reason: SbOpenReason) => any) | undefined;
15
15
  "onUpdate:open"?: ((value: boolean | undefined) => any) | undefined;
16
16
  }>, {
17
+ trigger: import('..').SbTrigger;
18
+ disabled: boolean;
17
19
  placement: import('..').SbPlacement;
18
20
  strategy: import('..').SbStrategy;
19
21
  padding: number;
@@ -21,8 +23,6 @@ declare const __VLS_export: __VLS_WithSlots<import('vue').DefineComponent<Omit<S
21
23
  matchWidth: boolean | "min";
22
24
  hideWhenDetached: boolean | "close";
23
25
  defaultOpen: boolean;
24
- disabled: boolean;
25
- trigger: import('..').SbTrigger;
26
26
  delay: number | {
27
27
  open?: number;
28
28
  close?: number;
@@ -1,8 +1,8 @@
1
1
  import { TransitionProps } from 'vue';
2
2
  import { SbFloatingProps } from '../types';
3
- declare var __VLS_25: {};
3
+ declare var __VLS_26: {};
4
4
  type __VLS_Slots = {} & {
5
- default?: (props: typeof __VLS_25) => any;
5
+ default?: (props: typeof __VLS_26) => any;
6
6
  };
7
7
  declare const __VLS_base: import('vue').DefineComponent<SbFloatingProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
8
8
  "after-enter": () => any;
@@ -18,6 +18,7 @@ declare const __VLS_base: import('vue').DefineComponent<SbFloatingProps, {}, {},
18
18
  zIndex: string | number;
19
19
  autoFocus: boolean;
20
20
  keyboardNavigation: boolean;
21
+ closeOnSelect: boolean;
21
22
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
22
23
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
23
24
  declare const _default: typeof __VLS_export;
@@ -0,0 +1,6 @@
1
+ import { UseSbFloatingReturn } from '../types';
2
+ /**
3
+ * Open state and actions of the closest `SbContainer`, for your own components inside it
4
+ * (e.g. a `MenuItem` that closes the menu), without passing `close` down from the slot.
5
+ */
6
+ export declare function useSbFloating(): UseSbFloatingReturn;
@@ -1,5 +1,5 @@
1
1
  import { InjectionKey, Ref } from 'vue';
2
- import { SbTrigger } from '../types';
2
+ import { SbCloseReason, SbOpenReason, SbTrigger } from '../types';
3
3
  import { NavigableRole } from '../utils/navigation';
4
4
  import { FloatingPanel } from './useSbFloatingPanel';
5
5
  /**
@@ -20,7 +20,10 @@ export interface SbFloatingContext extends FloatingPanel {
20
20
  focusRequest: Ref<'first' | 'last' | null>;
21
21
  /** Container of the panel this one is nested in (e.g. the menu of a submenu). */
22
22
  parent: SbFloatingContext | null;
23
- /** Set when an outside press closes the panel on a focusable element, which keeps focus. */
23
+ /**
24
+ * Set before closing when focus must not go back to the reference: an outside press on a
25
+ * focusable element keeps it, a menu closing with the menus it opened from moves it.
26
+ */
24
27
  skipFocusReturn: boolean;
25
28
  /** Mounted `SbReference` and `SbFloating` components, to warn about extra ones. */
26
29
  parts: {
@@ -29,9 +32,16 @@ export interface SbFloatingContext extends FloatingPanel {
29
32
  };
30
33
  /** Set while the container unmounts, when its parts must not change the open state. */
31
34
  isUnmounting: boolean;
35
+ /** Public actions: their reason is `api`. */
32
36
  open: () => void;
33
37
  close: () => void;
34
38
  toggle: () => void;
39
+ /** Opens or closes the panel from an interaction of the components, with its reason. */
40
+ setOpen: {
41
+ (value: true, reason: SbOpenReason): void;
42
+ (value: false, reason: SbCloseReason): void;
43
+ (value: boolean, reason: SbOpenReason & SbCloseReason): void;
44
+ };
35
45
  /** Pointer entering or leaving the reference or the panel, for the `hover` trigger. */
36
46
  hover: (event: PointerEvent) => void;
37
47
  /** Whether an event target is inside the reference, the panel or a nested container. */
@@ -39,6 +49,14 @@ export interface SbFloatingContext extends FloatingPanel {
39
49
  /** Registers a nested container; returns the function that unregisters it. */
40
50
  registerChild: (child: SbFloatingContext) => () => void;
41
51
  }
52
+ /** Whether the container is nested in a menu (a submenu): its reference is an item of it. */
53
+ export declare const inMenu: (context: SbFloatingContext) => boolean;
54
+ /**
55
+ * Moves focus out of the panel (or a panel nested in it) about to unmount, which would leave it on
56
+ * the body: to the reference, or to what follows the reference in the page when that goes too.
57
+ * Returns whether focus was in the panel.
58
+ */
59
+ export declare function moveFocusOut(context: SbFloatingContext, toReference: boolean): boolean;
42
60
  export declare const sbFloatingContextKey: InjectionKey<SbFloatingContext>;
43
61
  /** Counts a mounted part of the container, warning about a second one; returns the release. */
44
62
  export declare function claimPart(context: SbFloatingContext, part: 'reference' | 'floating', component: string): () => void;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { default as SbContainer } from './components/SbContainer.vue';
2
2
  export { default as SbReference } from './components/SbReference.vue';
3
3
  export { default as SbFloating } from './components/SbFloating.vue';
4
- export type { SbContainerProps, SbContainerSlotProps, SbContainerExposed, SbReferenceProps, SbFloatingProps, SbTrigger, SbPlacement, SbSide, SbAlignment, SbStrategy, SbFloatingRole, } from './types';
4
+ export { useSbFloating } from './composables/useSbFloating';
5
+ export type { SbContainerProps, SbContainerSlotProps, SbContainerExposed, SbReferenceProps, SbFloatingProps, SbTrigger, SbPlacement, SbSide, SbAlignment, SbStrategy, SbFloatingRole, SbOpenReason, SbCloseReason, UseSbFloatingReturn, } from './types';