@data-slot/popover 0.2.41 → 0.2.43
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
|
@@ -53,6 +53,7 @@ const popover = createPopover(element, {
|
|
|
53
53
|
alignOffset: 0,
|
|
54
54
|
avoidCollisions: true,
|
|
55
55
|
collisionPadding: 8,
|
|
56
|
+
portal: true,
|
|
56
57
|
closeOnClickOutside: true,
|
|
57
58
|
closeOnEscape: true,
|
|
58
59
|
onOpenChange: (open) => console.log(open),
|
|
@@ -70,6 +71,7 @@ const popover = createPopover(element, {
|
|
|
70
71
|
| `alignOffset` | `number` | `0` | Offset from alignment edge in pixels |
|
|
71
72
|
| `avoidCollisions` | `boolean` | `true` | Flip/shift to stay in viewport |
|
|
72
73
|
| `collisionPadding` | `number` | `8` | Viewport edge padding in pixels |
|
|
74
|
+
| `portal` | `boolean` | `true` | Portal content to `document.body` while open |
|
|
73
75
|
| `position` | `"top" \| "bottom" \| "left" \| "right"` | - | Deprecated alias for `side` |
|
|
74
76
|
| `closeOnClickOutside` | `boolean` | `true` | Close when clicking outside |
|
|
75
77
|
| `closeOnEscape` | `boolean` | `true` | Close when pressing Escape |
|
|
@@ -119,6 +121,7 @@ Options can also be set via data attributes on the root element. JS options take
|
|
|
119
121
|
| `data-align-offset` | number | `0` | Offset from alignment edge (px) |
|
|
120
122
|
| `data-avoid-collisions` | boolean | `true` | Flip/shift to stay in viewport |
|
|
121
123
|
| `data-collision-padding` | number | `8` | Viewport edge padding (px) |
|
|
124
|
+
| `data-portal` | boolean | `true` | Portal content to `document.body` while open |
|
|
122
125
|
| `data-close-on-click-outside` | boolean | `true` | Close when clicking outside |
|
|
123
126
|
| `data-close-on-escape` | boolean | `true` | Close when pressing Escape |
|
|
124
127
|
|
|
@@ -141,31 +144,66 @@ Placement can be set on root or content (content takes precedence):
|
|
|
141
144
|
|
|
142
145
|
## Styling
|
|
143
146
|
|
|
144
|
-
Popover position is computed in JavaScript and applied as `position:
|
|
145
|
-
|
|
147
|
+
Popover position is computed in JavaScript and applied as `position: absolute` + inline `transform: translate3d(...)`.
|
|
148
|
+
By default, content is portaled to `document.body` while open (document coordinates). In this mode, a generated `popover-positioner` wrapper receives the positioning transform. If `portal` is disabled, positioning is applied directly to `popover-content`.
|
|
149
|
+
Use `data-open`/`data-closed` and `data-side` for styling/animation.
|
|
150
|
+
When portaling is enabled (default), a transient wrapper with `data-slot="popover-positioner"` is created while open and receives the positioning transform. This keeps `popover-content` free for transform animations.
|
|
146
151
|
|
|
147
152
|
```css
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
[data-slot="popover-content"] {
|
|
154
|
+
transform-origin: var(--transform-origin, center);
|
|
155
|
+
--popover-slide-x: 0px;
|
|
156
|
+
--popover-slide-y: -4px;
|
|
151
157
|
}
|
|
152
158
|
|
|
153
|
-
[data-slot="popover-content"] {
|
|
154
|
-
|
|
155
|
-
|
|
159
|
+
[data-slot="popover-content"][data-side="top"] {
|
|
160
|
+
--popover-slide-y: 4px;
|
|
161
|
+
}
|
|
162
|
+
[data-slot="popover-content"][data-side="bottom"] {
|
|
163
|
+
--popover-slide-y: -4px;
|
|
164
|
+
}
|
|
165
|
+
[data-slot="popover-content"][data-side="left"] {
|
|
166
|
+
--popover-slide-x: 4px;
|
|
167
|
+
--popover-slide-y: 0px;
|
|
168
|
+
}
|
|
169
|
+
[data-slot="popover-content"][data-side="right"] {
|
|
170
|
+
--popover-slide-x: -4px;
|
|
171
|
+
--popover-slide-y: 0px;
|
|
156
172
|
}
|
|
157
173
|
|
|
158
|
-
[data-slot="popover-content"][
|
|
159
|
-
|
|
174
|
+
[data-slot="popover-content"][data-open] {
|
|
175
|
+
animation: popover-in 160ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
160
176
|
}
|
|
161
|
-
|
|
162
|
-
|
|
177
|
+
|
|
178
|
+
[data-slot="popover-content"][data-closed] {
|
|
179
|
+
pointer-events: none;
|
|
180
|
+
animation: popover-out 120ms ease-in forwards;
|
|
163
181
|
}
|
|
164
|
-
|
|
165
|
-
|
|
182
|
+
|
|
183
|
+
@keyframes popover-in {
|
|
184
|
+
from {
|
|
185
|
+
opacity: 0;
|
|
186
|
+
scale: 0.96;
|
|
187
|
+
translate: var(--popover-slide-x) var(--popover-slide-y);
|
|
188
|
+
}
|
|
189
|
+
to {
|
|
190
|
+
opacity: 1;
|
|
191
|
+
scale: 1;
|
|
192
|
+
translate: 0 0;
|
|
193
|
+
}
|
|
166
194
|
}
|
|
167
|
-
|
|
168
|
-
|
|
195
|
+
|
|
196
|
+
@keyframes popover-out {
|
|
197
|
+
from {
|
|
198
|
+
opacity: 1;
|
|
199
|
+
scale: 1;
|
|
200
|
+
translate: 0 0;
|
|
201
|
+
}
|
|
202
|
+
to {
|
|
203
|
+
opacity: 0;
|
|
204
|
+
scale: 0.96;
|
|
205
|
+
translate: var(--popover-slide-x) var(--popover-slide-y);
|
|
206
|
+
}
|
|
169
207
|
}
|
|
170
208
|
```
|
|
171
209
|
|
|
@@ -178,13 +216,15 @@ With Tailwind:
|
|
|
178
216
|
data-slot="popover-content"
|
|
179
217
|
data-side="bottom"
|
|
180
218
|
data-align="start"
|
|
181
|
-
class="
|
|
219
|
+
class="absolute bg-white shadow-lg rounded-lg p-4"
|
|
182
220
|
>
|
|
183
221
|
Content
|
|
184
222
|
</div>
|
|
185
223
|
</div>
|
|
186
224
|
```
|
|
187
225
|
|
|
226
|
+
Use Tailwind for layout/colors and keep the state selectors from the CSS snippet above for fade/zoom animation.
|
|
227
|
+
|
|
188
228
|
## Accessibility
|
|
189
229
|
|
|
190
230
|
The component automatically handles:
|
|
@@ -28,6 +28,8 @@ interface PopoverOptions {
|
|
|
28
28
|
collisionPadding?: number;
|
|
29
29
|
/** Callback when open state changes */
|
|
30
30
|
onOpenChange?: (open: boolean) => void;
|
|
31
|
+
/** Portal content to body while open. @default true */
|
|
32
|
+
portal?: boolean;
|
|
31
33
|
/** Close when clicking outside */
|
|
32
34
|
closeOnClickOutside?: boolean;
|
|
33
35
|
/** Close when pressing Escape */
|
|
@@ -28,6 +28,8 @@ interface PopoverOptions {
|
|
|
28
28
|
collisionPadding?: number;
|
|
29
29
|
/** Callback when open state changes */
|
|
30
30
|
onOpenChange?: (open: boolean) => void;
|
|
31
|
+
/** Portal content to body while open. @default true */
|
|
32
|
+
portal?: boolean;
|
|
31
33
|
/** Close when clicking outside */
|
|
32
34
|
closeOnClickOutside?: boolean;
|
|
33
35
|
/** Close when pressing Escape */
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@data-slot/core`);const t=[`top`,`right`,`bottom`,`left`],n=[`start`,`center`,`end`];function r(r,i={}){let a=(0,e.getPart)(r,`popover-trigger`),o=(0,e.getPart)(r,`popover-content`),s=(0,e.getPart)(r,`popover-close`);if(!a||!o)throw Error(`Popover requires trigger and content slots`);let c=i.defaultOpen??(0,e.getDataBool)(r,`defaultOpen`)??!1,l=i.onOpenChange,u=i.closeOnClickOutside??(0,e.getDataBool)(r,`closeOnClickOutside`)??!0,d=i.closeOnEscape??(0,e.getDataBool)(r,`closeOnEscape`)??!0,f=i.position??(0,e.getDataEnum)(o,`position`,t)??(0,e.getDataEnum)(r,`position`,t),
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@data-slot/core`);const t=[`top`,`right`,`bottom`,`left`],n=[`start`,`center`,`end`];function r(r,i={}){let a=(0,e.getPart)(r,`popover-trigger`),o=(0,e.getPart)(r,`popover-content`),s=(0,e.getPart)(r,`popover-close`);if(!a||!o)throw Error(`Popover requires trigger and content slots`);let c=i.defaultOpen??(0,e.getDataBool)(r,`defaultOpen`)??!1,l=i.onOpenChange,u=i.closeOnClickOutside??(0,e.getDataBool)(r,`closeOnClickOutside`)??!0,d=i.closeOnEscape??(0,e.getDataBool)(r,`closeOnEscape`)??!0,f=i.portal??(0,e.getDataBool)(o,`portal`)??(0,e.getDataBool)(r,`portal`)??!0,p=i.position??(0,e.getDataEnum)(o,`position`,t)??(0,e.getDataEnum)(r,`position`,t),m=i.side??(0,e.getDataEnum)(o,`side`,t)??(0,e.getDataEnum)(r,`side`,t)??p??`bottom`,h=i.align??(0,e.getDataEnum)(o,`align`,n)??(0,e.getDataEnum)(r,`align`,n)??`center`,g=i.sideOffset??(0,e.getDataNumber)(o,`sideOffset`)??(0,e.getDataNumber)(r,`sideOffset`)??4,_=i.alignOffset??(0,e.getDataNumber)(o,`alignOffset`)??(0,e.getDataNumber)(r,`alignOffset`)??0,v=i.avoidCollisions??(0,e.getDataBool)(o,`avoidCollisions`)??(0,e.getDataBool)(r,`avoidCollisions`)??!0,y=i.collisionPadding??(0,e.getDataNumber)(o,`collisionPadding`)??(0,e.getDataNumber)(r,`collisionPadding`)??8,b=c,x=[],S=(0,e.createPortalLifecycle)({content:o,root:r,enabled:f,wrapperSlot:`popover-positioner`}),C=!1,w=null,T=!1,E=()=>{T&&=(o.removeAttribute(`tabindex`),!1)},D=()=>{let e=o.querySelector(`[autofocus]`);if(e)return e.focus();let t=o.querySelector(`a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])`);if(t)return t.focus();o.getAttribute(`tabindex`)||(o.setAttribute(`tabindex`,`-1`),T=!0),o.focus()},O=(0,e.ensureId)(o,`popover-content`);a.setAttribute(`aria-haspopup`,`dialog`),a.setAttribute(`aria-controls`,O),o.setAttribute(`data-side`,m),o.setAttribute(`data-align`,h),o.setAttribute(`data-position`,m);let k=()=>{let t=S.container,n=r.ownerDocument.defaultView??window,i=(0,e.computeFloatingPosition)({anchorRect:a.getBoundingClientRect(),contentRect:o.getBoundingClientRect(),side:m,align:h,sideOffset:g,alignOffset:_,avoidCollisions:v,collisionPadding:y});t.style.position=`absolute`,t.style.top=`0px`,t.style.left=`0px`,t.style.transform=`translate3d(${i.x+n.scrollX}px, ${i.y+n.scrollY}px, 0)`,t.style.willChange=`transform`,t.style.margin=`0`,o.setAttribute(`data-side`,i.side),o.setAttribute(`data-align`,i.align),t!==o&&(t.setAttribute(`data-side`,i.side),t.setAttribute(`data-align`,i.align)),o.setAttribute(`data-position`,i.side)},A=e=>{let t=S.container;r.setAttribute(`data-state`,e),o.setAttribute(`data-state`,e),t!==o&&t.setAttribute(`data-state`,e),e===`open`?(r.setAttribute(`data-open`,``),o.setAttribute(`data-open`,``),t!==o&&t.setAttribute(`data-open`,``),r.removeAttribute(`data-closed`),o.removeAttribute(`data-closed`),t!==o&&t.removeAttribute(`data-closed`)):(r.setAttribute(`data-closed`,``),o.setAttribute(`data-closed`,``),t!==o&&t.setAttribute(`data-closed`,``),r.removeAttribute(`data-open`),o.removeAttribute(`data-open`),t!==o&&t.removeAttribute(`data-open`))},j=()=>{requestAnimationFrame(()=>{w&&w.isConnected?w.focus():a.focus(),w=null})},M=(0,e.createPresenceLifecycle)({element:o,onExitComplete:()=>{C||(S.restore(),o.hidden=!0,E(),j())}}),N=(0,e.createPositionSync)({observedElements:[a,o],isActive:()=>b,ancestorScroll:!1,onUpdate:k}),P=t=>{b!==t&&(t&&(w=document.activeElement),b=t,(0,e.setAria)(a,`expanded`,b),t?(S.mount(),o.hidden=!1,A(`open`),M.enter(),k(),N.start(),N.update(),requestAnimationFrame(D)):(A(`closed`),M.exit(),N.stop()),(0,e.emit)(r,`popover:change`,{open:b}),l?.(b))};return(0,e.setAria)(a,`expanded`,b),A(b?`open`:`closed`),o.hidden=!b,c&&(S.mount(),M.enter(),o.hidden=!1,k(),N.start(),N.update(),requestAnimationFrame(D)),x.push((0,e.on)(a,`click`,()=>P(!b))),s&&x.push((0,e.on)(s,`click`,()=>P(!1))),x.push((0,e.createDismissLayer)({root:r,isOpen:()=>b,onDismiss:()=>P(!1),closeOnClickOutside:u,closeOnEscape:d})),x.push((0,e.on)(r,`popover:set`,e=>{let t=e.detail,n;t?.open===void 0?t?.value!==void 0&&(n=t.value):n=t.open,typeof n==`boolean`&&P(n)})),{open:()=>P(!0),close:()=>P(!1),toggle:()=>P(!b),get isOpen(){return b},destroy:()=>{C=!0,N.stop(),M.cleanup(),S.cleanup(),x.forEach(e=>e()),x.length=0,E()}}}const i=new WeakSet;function a(t=document){let n=[];for(let a of(0,e.getRoots)(t,`popover`))i.has(a)||(i.add(a),n.push(r(a)));return n}exports.create=a,exports.createPopover=r;
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{computeFloatingPosition as e,createDismissLayer as t,
|
|
1
|
+
import{computeFloatingPosition as e,createDismissLayer as t,createPortalLifecycle as n,createPositionSync as r,createPresenceLifecycle as i,emit as a,ensureId as o,getDataBool as s,getDataEnum as c,getDataNumber as l,getPart as u,getRoots as d,on as f,setAria as p}from"@data-slot/core";const m=[`top`,`right`,`bottom`,`left`],h=[`start`,`center`,`end`];function g(d,g={}){let _=u(d,`popover-trigger`),v=u(d,`popover-content`),y=u(d,`popover-close`);if(!_||!v)throw Error(`Popover requires trigger and content slots`);let b=g.defaultOpen??s(d,`defaultOpen`)??!1,x=g.onOpenChange,S=g.closeOnClickOutside??s(d,`closeOnClickOutside`)??!0,C=g.closeOnEscape??s(d,`closeOnEscape`)??!0,w=g.portal??s(v,`portal`)??s(d,`portal`)??!0,T=g.position??c(v,`position`,m)??c(d,`position`,m),E=g.side??c(v,`side`,m)??c(d,`side`,m)??T??`bottom`,D=g.align??c(v,`align`,h)??c(d,`align`,h)??`center`,O=g.sideOffset??l(v,`sideOffset`)??l(d,`sideOffset`)??4,k=g.alignOffset??l(v,`alignOffset`)??l(d,`alignOffset`)??0,A=g.avoidCollisions??s(v,`avoidCollisions`)??s(d,`avoidCollisions`)??!0,j=g.collisionPadding??l(v,`collisionPadding`)??l(d,`collisionPadding`)??8,M=b,N=[],P=n({content:v,root:d,enabled:w,wrapperSlot:`popover-positioner`}),F=!1,I=null,L=!1,R=()=>{L&&=(v.removeAttribute(`tabindex`),!1)},z=()=>{let e=v.querySelector(`[autofocus]`);if(e)return e.focus();let t=v.querySelector(`a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])`);if(t)return t.focus();v.getAttribute(`tabindex`)||(v.setAttribute(`tabindex`,`-1`),L=!0),v.focus()},B=o(v,`popover-content`);_.setAttribute(`aria-haspopup`,`dialog`),_.setAttribute(`aria-controls`,B),v.setAttribute(`data-side`,E),v.setAttribute(`data-align`,D),v.setAttribute(`data-position`,E);let V=()=>{let t=P.container,n=d.ownerDocument.defaultView??window,r=e({anchorRect:_.getBoundingClientRect(),contentRect:v.getBoundingClientRect(),side:E,align:D,sideOffset:O,alignOffset:k,avoidCollisions:A,collisionPadding:j});t.style.position=`absolute`,t.style.top=`0px`,t.style.left=`0px`,t.style.transform=`translate3d(${r.x+n.scrollX}px, ${r.y+n.scrollY}px, 0)`,t.style.willChange=`transform`,t.style.margin=`0`,v.setAttribute(`data-side`,r.side),v.setAttribute(`data-align`,r.align),t!==v&&(t.setAttribute(`data-side`,r.side),t.setAttribute(`data-align`,r.align)),v.setAttribute(`data-position`,r.side)},H=e=>{let t=P.container;d.setAttribute(`data-state`,e),v.setAttribute(`data-state`,e),t!==v&&t.setAttribute(`data-state`,e),e===`open`?(d.setAttribute(`data-open`,``),v.setAttribute(`data-open`,``),t!==v&&t.setAttribute(`data-open`,``),d.removeAttribute(`data-closed`),v.removeAttribute(`data-closed`),t!==v&&t.removeAttribute(`data-closed`)):(d.setAttribute(`data-closed`,``),v.setAttribute(`data-closed`,``),t!==v&&t.setAttribute(`data-closed`,``),d.removeAttribute(`data-open`),v.removeAttribute(`data-open`),t!==v&&t.removeAttribute(`data-open`))},U=()=>{requestAnimationFrame(()=>{I&&I.isConnected?I.focus():_.focus(),I=null})},W=i({element:v,onExitComplete:()=>{F||(P.restore(),v.hidden=!0,R(),U())}}),G=r({observedElements:[_,v],isActive:()=>M,ancestorScroll:!1,onUpdate:V}),K=e=>{M!==e&&(e&&(I=document.activeElement),M=e,p(_,`expanded`,M),e?(P.mount(),v.hidden=!1,H(`open`),W.enter(),V(),G.start(),G.update(),requestAnimationFrame(z)):(H(`closed`),W.exit(),G.stop()),a(d,`popover:change`,{open:M}),x?.(M))};return p(_,`expanded`,M),H(M?`open`:`closed`),v.hidden=!M,b&&(P.mount(),W.enter(),v.hidden=!1,V(),G.start(),G.update(),requestAnimationFrame(z)),N.push(f(_,`click`,()=>K(!M))),y&&N.push(f(y,`click`,()=>K(!1))),N.push(t({root:d,isOpen:()=>M,onDismiss:()=>K(!1),closeOnClickOutside:S,closeOnEscape:C})),N.push(f(d,`popover:set`,e=>{let t=e.detail,n;t?.open===void 0?t?.value!==void 0&&(n=t.value):n=t.open,typeof n==`boolean`&&K(n)})),{open:()=>K(!0),close:()=>K(!1),toggle:()=>K(!M),get isOpen(){return M},destroy:()=>{F=!0,G.stop(),W.cleanup(),P.cleanup(),N.forEach(e=>e()),N.length=0,R()}}}const _=new WeakSet;function v(e=document){let t=[];for(let n of d(e,`popover`))_.has(n)||(_.add(n),t.push(g(n)));return t}export{v as create,g as createPopover};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@data-slot/popover",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.43",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -38,6 +38,6 @@
|
|
|
38
38
|
],
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@data-slot/core": "0.2.
|
|
41
|
+
"@data-slot/core": "0.2.43"
|
|
42
42
|
}
|
|
43
43
|
}
|