sb-floating-panel-vue 3.0.0 → 5.0.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 +224 -107
- package/dist/components/SbContainer.vue.d.ts +8 -3
- package/dist/components/SbFloating.vue.d.ts +8 -7
- package/dist/components/SbReference.vue.d.ts +4 -3
- package/dist/composables/useSbFloatingPanel.d.ts +3 -1
- package/dist/context.d.ts +31 -2
- package/dist/dev.d.ts +2 -0
- package/dist/focus.d.ts +24 -0
- package/dist/index.d.ts +1 -1
- package/dist/sb-floating-panel.es.js +400 -143
- package/dist/sb-floating-panel.umd.js +1 -1
- package/dist/types.d.ts +98 -23
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("vue"),require("@floating-ui/vue")):typeof define==`function`&&define.amd?define([`exports`,`vue`,`@floating-ui/vue`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.SbFloatingPanelVue={},e.Vue,e.FloatingUIVue))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=e=>`${Math.max(0,e)}px`;function i(e){let i=(0,t.ref)(null),a=(0,t.ref)(null),o=(0,t.computed)(()=>{let i=(0,t.toValue)(e.padding),a=(0,t.toValue)(e.matchWidth),o=(0,t.toValue)(e.placement).includes(`-`),s=(0,n.flip)({padding:i,crossAxis:`alignment`,fallbackAxisSideDirection:`end`}),c=(0,n.shift)({padding:i,limiter:(0,n.limitShift)()}),l=[(0,n.offset)((0,t.toValue)(e.offset)),...o?[s,c]:[c,s],(0,n.size)({padding:i,apply({rects:e,elements:t,availableWidth:n,availableHeight:i}){let{style:o}=t.floating,s=r(e.reference.width);o.setProperty(`--sbf-reference-width`,s),o.setProperty(`--sbf-available-width`,r(n)),o.setProperty(`--sbf-available-height`,r(i)),o.width=a===!0?s:``,o.minWidth=a===`min`?s:``}})];return(0,t.toValue)(e.hideWhenDetached)&&l.push((0,n.hide)({padding:i})),l}),{floatingStyles:s,middlewareData:c,placement:l,isPositioned:u,update:d}=(0,n.useFloating)(i,a,{open:()=>(0,t.toValue)(e.isOpen),placement:()=>(0,t.toValue)(e.placement),strategy:()=>(0,t.toValue)(e.strategy),middleware:o,whileElementsMounted:n.autoUpdate});return{reference:i,floating:a,floatingStyles:(0,t.computed)(()=>({...s.value,...c.value.hide?.referenceHidden?{visibility:`hidden`}:{}})),placement:l,side:(0,t.computed)(()=>l.value.split(`-`)[0]),align:(0,t.computed)(()=>l.value.split(`-`)[1]),isPositioned:u,update:d}}var a=Symbol(`SbFloatingContext`);function o(e){let n=(0,t.inject)(a,null);if(!n)throw Error(`[sb-floating-panel-vue] <${e}> must be used inside <SbContainer>.`);return n}var s=new WeakSet,c=(0,t.defineComponent)({inheritAttrs:!1,__name:`SbContainer`,props:(0,t.mergeModels)({placement:{default:`bottom-start`},strategy:{default:`absolute`},offset:{default:8},padding:{default:8},matchWidth:{type:[Boolean,String],default:!1},hideWhenDetached:{type:Boolean,default:!1},defaultOpen:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},trigger:{default:`click`},closeOnEscape:{type:Boolean,default:!0},closeOnClickOutside:{type:Boolean,default:!0}},{open:{type:Boolean,default:void 0},openModifiers:{}}),emits:(0,t.mergeModels)([`open`,`close`],[`update:open`]),setup(e,{expose:n,emit:r}){let o=e,c=(0,t.useModel)(e,`open`),l=r,u=(0,t.computed)(()=>!o.disabled&&(c.value??o.defaultOpen)),d=e=>{e&&o.disabled||e!==u.value&&(c.value=e)},f=()=>d(!0),p=()=>d(!1),m=()=>d(!u.value),h=i({isOpen:u,placement:()=>o.placement,strategy:()=>o.strategy,offset:()=>o.offset,padding:()=>o.padding,matchWidth:()=>o.matchWidth,hideWhenDetached:()=>o.hideWhenDetached}),g=new Set,_={...h,isOpen:u,disabled:(0,t.toRef)(o,`disabled`),trigger:(0,t.toRef)(o,`trigger`),floatingId:`${(0,t.useId)()}-floating`,open:f,close:p,toggle:m,contains:e=>e instanceof Node?h.reference.value?.contains(e)||h.floating.value?.contains(e)?!0:[...g].some(t=>t.contains(e)):!1,hasOpenChild:()=>[...g].some(e=>e.isOpen.value||e.hasOpenChild()),registerChild:e=>(g.add(e),()=>g.delete(e))},v=(0,t.inject)(a,null);v&&(0,t.onScopeDispose)(v.registerChild(_)),(0,t.provide)(a,_);let y=e=>{e.key===`Escape`&&o.closeOnEscape&&!_.hasOpenChild()&&(s.has(e)||(s.add(e),p()))},b=e=>{o.closeOnClickOutside&&(_.contains(e.composedPath()[0]??e.target)||p())};return(0,t.watch)(u,(e,t,n)=>{e&&typeof document<`u`&&(document.addEventListener(`keydown`,y),document.addEventListener(`pointerdown`,b,!0),n(()=>{document.removeEventListener(`keydown`,y),document.removeEventListener(`pointerdown`,b,!0)}))},{immediate:!0}),(0,t.watch)(u,e=>l(e?`open`:`close`)),(0,t.watch)(()=>o.disabled,e=>{e&&c.value&&(c.value=!1)}),n({open:f,close:p,toggle:m,update:h.update}),(e,n)=>(0,t.renderSlot)(e.$slots,`default`,{isOpen:u.value,open:f,close:p,toggle:m,placement:(0,t.unref)(h).placement.value,side:(0,t.unref)(h).side.value})}}),l=(0,t.defineComponent)({__name:`SbReference`,props:{as:{default:`button`},ariaHaspopup:{default:`dialog`}},setup(e){let n=e,r=o(`SbReference`),{isOpen:i,disabled:a,floatingId:s}=r,c=(0,t.computed)(()=>n.as===`button`||n.as===`a`),l=e=>{r.reference.value=e instanceof HTMLElement?e:null},u=()=>{r.trigger.value===`click`&&r.toggle()},d=e=>{c.value||e.key!==`Enter`&&e.key!==` `||(e.preventDefault(),u())};return(e,r)=>((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(n.as),{ref:l,type:n.as===`button`?`button`:void 0,role:c.value?void 0:`button`,tabindex:c.value?void 0:(0,t.unref)(a)?-1:0,disabled:n.as===`button`&&(0,t.unref)(a)?!0:void 0,"aria-disabled":!c.value&&(0,t.unref)(a)?`true`:void 0,"aria-haspopup":n.ariaHaspopup,"aria-expanded":(0,t.unref)(i)?`true`:`false`,"aria-controls":(0,t.unref)(i)?(0,t.unref)(s):void 0,"data-sbf-state":(0,t.unref)(i)?`open`:`closed`,onClick:u,onKeydown:d},{default:(0,t.withCtx)(()=>[(0,t.renderSlot)(e.$slots,`default`)]),_:3},40,[`type`,`role`,`tabindex`,`disabled`,`aria-disabled`,`aria-haspopup`,`aria-expanded`,`aria-controls`,`data-sbf-state`]))}}),u=`a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]), [contenteditable="true"]`,d=(0,t.defineComponent)({inheritAttrs:!1,__name:`SbFloating`,props:{as:{default:`div`},role:{default:`dialog`},transition:{default:void 0},teleportTo:{default:`body`},teleportDisabled:{type:Boolean,default:!1},zIndex:{default:void 0},autoFocus:{type:Boolean,default:!1}},emits:[`after-enter`,`after-leave`],setup(e,{emit:n}){let r=e,i=n,a=o(`SbFloating`),{isOpen:s,floatingId:c,side:l,align:d}=a,f=(0,t.ref)(s.value),p=(0,t.ref)(null),m=e=>{a.floating.value=e instanceof HTMLElement?e:null},h=e=>{p.value=e instanceof HTMLElement?e:null},g=(0,t.computed)(()=>({...a.floatingStyles.value,...r.zIndex===void 0?{}:{zIndex:r.zIndex}})),_=()=>i(`after-enter`),v=()=>{s.value||(f.value=!1),i(`after-leave`)},y=()=>{let e=p.value;e&&(e.querySelector(`[autofocus]`)??e.querySelector(u)??e).focus({preventScroll:!0})};return(0,t.watch)(s,e=>{if(e){f.value=!0;return}let t=document.activeElement;t&&a.floating.value?.contains(t)&&a.reference.value?.focus()}),(0,t.watch)(()=>s.value&&a.isPositioned.value,async e=>{e&&r.autoFocus&&(await(0,t.nextTick)(),y())}),(e,n)=>((0,t.openBlock)(),(0,t.createBlock)(t.Teleport,{to:r.teleportTo,disabled:r.teleportDisabled},[f.value?((0,t.openBlock)(),(0,t.createElementBlock)(`div`,{key:0,ref:m,style:(0,t.normalizeStyle)(g.value)},[(0,t.createVNode)(t.Transition,{name:r.transition,css:!!r.transition,appear:``,onAfterEnter:_,onAfterLeave:v},{default:(0,t.withCtx)(()=>[(0,t.unref)(s)?((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(r.as),(0,t.mergeProps)({key:0},e.$attrs,{id:(0,t.unref)(c),ref:h,role:r.role,tabindex:r.autoFocus?-1:void 0,"data-sbf-state":(0,t.unref)(s)?`open`:`closed`,"data-sbf-side":(0,t.unref)(l),"data-sbf-align":(0,t.unref)(d)}),{default:(0,t.withCtx)(()=>[(0,t.renderSlot)(e.$slots,`default`)]),_:3},16,[`id`,`role`,`tabindex`,`data-sbf-state`,`data-sbf-side`,`data-sbf-align`])):(0,t.createCommentVNode)(``,!0)]),_:3},8,[`name`,`css`])],4)):(0,t.createCommentVNode)(``,!0)],8,[`to`,`disabled`]))}});e.SbContainer=c,e.SbFloating=d,e.SbReference=l});
|
|
1
|
+
(function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("vue"),require("@floating-ui/vue")):typeof define==`function`&&define.amd?define([`exports`,`vue`,`@floating-ui/vue`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.SbFloatingPanelVue={},e.Vue,e.FloatingUIVue))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=e=>`${Math.max(0,e)}px`,i=(e,t)=>{let[n,r]=e.split(`-`),i=t&&(n===`top`||n===`bottom`),a=r===`start`?i?`100%`:`0%`:r===`end`?i?`0%`:`100%`:`50%`;return n===`top`?`${a} 100%`:n===`bottom`?`${a} 0%`:n===`left`?`100% ${a}`:`0% ${a}`};function a(e){let a=(0,t.ref)(null),o=(0,t.ref)(null),s=(0,t.computed)(()=>{let a=(0,t.toValue)(e.padding),o=(0,t.toValue)(e.matchWidth),s=(0,t.toValue)(e.placement).includes(`-`),c=(0,n.flip)({padding:a,crossAxis:`alignment`,fallbackAxisSideDirection:`end`}),l=(0,n.shift)({padding:a,limiter:(0,n.limitShift)()}),u=[(0,n.offset)((0,t.toValue)(e.offset)),...s?[c,l]:[l,c],(0,n.size)({padding:a,apply({rects:e,elements:t,availableWidth:n,availableHeight:a,placement:s}){let{style:c}=t.floating,l=r(e.reference.width);c.setProperty(`--sbf-reference-width`,l);let u=getComputedStyle(t.floating).direction===`rtl`;c.setProperty(`--sbf-transform-origin`,i(s,u)),c.setProperty(`--sbf-available-width`,r(n)),c.setProperty(`--sbf-available-height`,r(a)),c.width=o===!0?l:``,c.minWidth=o===`min`?l:``}})];return(0,t.toValue)(e.hideWhenDetached)&&u.push((0,n.hide)({padding:a})),u}),{floatingStyles:c,middlewareData:l,placement:u,isPositioned:d,update:f}=(0,n.useFloating)(a,o,{open:()=>(0,t.toValue)(e.isOpen),placement:()=>(0,t.toValue)(e.placement),strategy:()=>(0,t.toValue)(e.strategy),middleware:s,whileElementsMounted:n.autoUpdate}),p=(0,t.computed)(()=>!!l.value.hide?.referenceHidden);return{reference:a,floating:o,floatingStyles:(0,t.computed)(()=>({...c.value,...p.value?{visibility:`hidden`}:{}})),placement:u,side:(0,t.computed)(()=>u.value.split(`-`)[0]),align:(0,t.computed)(()=>u.value.split(`-`)[1]),isPositioned:d,referenceHidden:p,update:f}}var o=(()=>{try{return process.env.NODE_ENV!==`production`}catch{return!1}})();function s(e){o&&console.warn(`[sb-floating-panel-vue] ${e}`)}var c={dialog:`dialog`,alertdialog:`dialog`,menu:`menu`,listbox:`listbox`,tree:`tree`,grid:`grid`},l=Symbol(`SbFloatingContext`);function u(e){let t=e&&typeof e==`object`&&`$el`in e?e.$el:e;return t instanceof HTMLElement?t:null}function d(e,t,n){return++e.parts[t]>1&&s(`<SbContainer> has more than one <${n}>: they share one open state and position, so only the last one works. Use one SbContainer for each panel.`),()=>e.parts[t]--}function f(e){let n=(0,t.inject)(l,null);if(!n)throw Error(`[sb-floating-panel-vue] <${e}> must be used inside <SbContainer>.`);return n}var p=`data-sbf-focus-guard`,m=[`a[href]`,`button:not([disabled])`,`input:not([disabled]):not([type="hidden"])`,`select:not([disabled])`,`textarea:not([disabled])`,`summary`,`audio[controls]`,`video[controls]`,`[tabindex]`,`[contenteditable="true"]`].join(`,`),h=e=>e.tabIndex>=0&&!e.hasAttribute(`data-sbf-focus-guard`)&&!e.closest(`[inert]`)&&(e.checkVisibility?.()??!0);function g(e){let t=e instanceof Element?e.closest(m):null;return!!t&&!t.closest(`[inert]`)}function _(e){return[...e.querySelectorAll(m)].filter(h)}var v={menu:`[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]`,listbox:`[role="option"]`},y=e=>!!e&&Object.hasOwn(v,e);function b(e,t){return[...e.querySelectorAll(v[t])].filter(n=>n.closest(`[role="${t}"]`)===e&&!n.matches(`:disabled`)&&(n.checkVisibility?.()??!0))}var x=e=>e instanceof Element&&!!e.closest(`input, textarea, select, [contenteditable]:not([contenteditable="false"])`),S=e=>getComputedStyle(e).direction===`rtl`?{open:`ArrowLeft`,close:`ArrowRight`}:{open:`ArrowRight`,close:`ArrowLeft`};function C(e,t){return _(e.ownerDocument).find(n=>!!(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)&&!e.contains(n)&&!t(n))}var w=[],T=(0,t.defineComponent)({inheritAttrs:!1,__name:`SbContainer`,props:(0,t.mergeModels)({placement:{default:`bottom-start`},strategy:{default:`absolute`},offset:{default:8},padding:{default:8},matchWidth:{type:[Boolean,String],default:!1},hideWhenDetached:{type:[Boolean,String],default:!1},defaultOpen:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},trigger:{default:`click`},delay:{default:()=>({open:300,close:150})},closeOnEscape:{type:Boolean,default:!0},closeOnClickOutside:{type:Boolean,default:!0},closeOnFocusOut:{type:Boolean,default:!0}},{open:{type:Boolean,default:void 0},openModifiers:{}}),emits:(0,t.mergeModels)([`open`,`close`],[`update:open`]),setup(e,{expose:n,emit:r}){let i=e,o=(0,t.useModel)(e,`open`),s=r,c=i.defaultOpen,u=(0,t.computed)(()=>!i.disabled&&(o.value??c)),d;(0,t.onScopeDispose)(()=>clearTimeout(d));let f=e=>{clearTimeout(d),!(e&&i.disabled)&&e!==u.value&&(o.value=e)},p=()=>f(!0),m=()=>f(!1),h=()=>f(!u.value),_=e=>{if(i.trigger!==`hover`||e.pointerType===`touch`)return;let t=e.type===`pointerenter`;if(clearTimeout(d),t===u.value)return;let{delay:n}=i,r=typeof n==`number`?{open:n,close:n}:n,a=(t?r.open:r.close)??(t?300:150);d=setTimeout(()=>f(t),a)},v=a({isOpen:u,placement:()=>i.placement,strategy:()=>i.strategy,offset:()=>i.offset,padding:()=>i.padding,matchWidth:()=>i.matchWidth,hideWhenDetached:()=>i.hideWhenDetached}),y=new Set,b=(0,t.inject)(l,null),x={...v,isOpen:u,disabled:(0,t.toRef)(i,`disabled`),trigger:(0,t.toRef)(i,`trigger`),floatingId:`${(0,t.useId)()}-floating`,referenceId:(0,t.ref)(),floatingRole:(0,t.ref)(`dialog`),navigation:(0,t.ref)(null),focusRequest:(0,t.ref)(null),parent:b,skipFocusReturn:!1,parts:{reference:0,floating:0},isUnmounting:!1,open:p,close:m,toggle:h,hover:_,contains:e=>e instanceof Node?v.reference.value?.contains(e)||v.floating.value?.contains(e)?!0:[...y].some(t=>t.contains(e)):!1,registerChild:e=>(y.add(e),()=>y.delete(e))};b&&(0,t.onScopeDispose)(b.registerChild(x)),(0,t.provide)(l,x),(0,t.onBeforeUnmount)(()=>x.isUnmounting=!0);let S=e=>{let t=e.composedPath()[0]??e.target;return w.findLast(e=>e.contains(t))??w[w.length-1]},C=e=>{e.key===`Escape`&&S(e)===x&&(!i.closeOnEscape||e.defaultPrevented||e.isComposing||(e.preventDefault(),m()))},T=e=>{x.contains(e)||(x.skipFocusReturn=g(e),m())},E,D=e=>{if(E?.(),!i.closeOnClickOutside)return;let t=e.composedPath()[0]??e.target;if(e.pointerType!==`touch`){T(t);return}if(x.contains(t))return;let n=e=>{E?.(),T(e.composedPath()[0]??e.target)};document.addEventListener(`click`,n,!0),E=()=>{document.removeEventListener(`click`,n,!0),E=void 0}},O=e=>{i.closeOnFocusOut&&(x.contains(e.composedPath()[0]??e.target)||m())};return(0,t.watch)(u,(e,t,n)=>{e&&typeof document<`u`&&(w.push(x),document.addEventListener(`keydown`,C),document.addEventListener(`pointerdown`,D,!0),document.addEventListener(`focusin`,O),n(()=>{document.removeEventListener(`keydown`,C),document.removeEventListener(`pointerdown`,D,!0),document.removeEventListener(`focusin`,O),E?.(),w.splice(w.indexOf(x),1)}))},{immediate:!0}),(0,t.watch)(u,e=>s(e?`open`:`close`)),(0,t.watch)(v.referenceHidden,e=>{e&&i.hideWhenDetached===`close`&&m()}),(0,t.watch)(()=>i.disabled,e=>{e&&(o.value??c)&&(o.value=!1)}),n({get isOpen(){return u.value},open:p,close:m,toggle:h,update:v.update}),(e,n)=>(0,t.renderSlot)(e.$slots,`default`,{isOpen:u.value,open:p,close:m,toggle:h,placement:(0,t.unref)(v).placement.value,side:(0,t.unref)(v).side.value,align:(0,t.unref)(v).align.value,floatingId:x.floatingId,referenceId:x.referenceId.value})}}),E=(0,t.defineComponent)({__name:`SbReference`,props:{as:{default:`button`},id:{default:void 0},ariaHaspopup:{default:void 0}},setup(e){let n=e,r=(0,t.useAttrs)(),i=f(`SbReference`),{isOpen:a,disabled:o,floatingId:l,floatingRole:p}=i,m=d(i,`reference`,`SbReference`),h=()=>typeof n.as!=`string`||n.as===`button`||n.as===`a`&&r.href!=null,g=`${(0,t.useId)()}-reference`,v=(0,t.computed)(()=>n.id??g);(0,t.watchEffect)(()=>{i.referenceId.value=v.value}),(0,t.onMounted)(()=>{i.reference.value||s("<SbReference> rendered no element, so the panel cannot be positioned: a component in `as` must render a single root element.")}),(0,t.onBeforeUnmount)(()=>{i.referenceId.value===v.value&&(i.referenceId.value=void 0),m(),!i.parts.reference&&!i.isUnmounting&&i.close()});let y=(0,t.computed)(()=>n.ariaHaspopup??(p.value&&Object.hasOwn(c,p.value)?c[p.value]:void 0)),b=e=>{i.reference.value=u(e)},x=()=>{i.trigger.value===`click`&&i.toggle()},C=(0,t.computed)(()=>p.value===`tooltip`),w=()=>i.trigger.value===`hover`||i.trigger.value===`focus`,T=e=>{w()&&!i.contains(e.relatedTarget)&&i.open()},E=e=>{w()&&!i.contains(e.relatedTarget)&&i.close()},D=e=>{let t=i.floating.value,n=t&&_(t)[0];n&&(e.preventDefault(),n.focus())},O=e=>{if(i.navigation.value&&!o.value&&i.trigger.value!==`manual`){if(i.parent?.navigation.value===`menu`){let t=e.currentTarget;return e.key===S(t).open?`first`:void 0}return e.key===`ArrowDown`?`first`:e.key===`ArrowUp`?`last`:void 0}},k=e=>{let t=i.parent?.navigation.value===`menu`;if(e.key===`Tab`&&!e.shiftKey&&a.value&&!t){D(e);return}let n=O(e);n?(e.preventDefault(),i.focusRequest.value=n,i.open()):h()||e.key!==`Enter`&&e.key!==` `||(e.preventDefault(),x())};return(e,r)=>((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(n.as),{id:v.value,ref:b,type:n.as===`button`?`button`:void 0,role:h()?void 0:`button`,tabindex:h()?void 0:(0,t.unref)(o)?-1:0,disabled:n.as===`button`&&(0,t.unref)(o)?!0:void 0,"aria-disabled":n.as!==`button`&&(0,t.unref)(o)?`true`:void 0,"aria-haspopup":y.value,"aria-expanded":C.value?void 0:(0,t.unref)(a)?`true`:`false`,"aria-controls":(0,t.unref)(a)&&!C.value?(0,t.unref)(l):void 0,"aria-describedby":(0,t.unref)(a)&&C.value?(0,t.unref)(l):void 0,"data-sbf-state":(0,t.unref)(a)?`open`:`closed`,onClick:x,onKeydown:k,onFocusin:T,onFocusout:E,onPointerenter:(0,t.unref)(i).hover,onPointerleave:(0,t.unref)(i).hover},{default:(0,t.withCtx)(()=>[(0,t.renderSlot)(e.$slots,`default`)]),_:3},40,[`id`,`type`,`role`,`tabindex`,`disabled`,`aria-disabled`,`aria-haspopup`,`aria-expanded`,`aria-controls`,`aria-describedby`,`data-sbf-state`,`onPointerenter`,`onPointerleave`]))}}),D=(0,t.defineComponent)({inheritAttrs:!1,__name:`SbFloating`,props:{as:{default:`div`},role:{default:`dialog`},transition:{default:void 0},teleport:{type:[String,Boolean],default:`body`},zIndex:{default:void 0},autoFocus:{type:Boolean,default:void 0},keyboardNavigation:{type:Boolean,default:!0}},emits:[`after-enter`,`after-leave`],setup(e,{emit:n}){let r=e,i=n,a=(0,t.useAttrs)(),o=f(`SbFloating`);(0,t.onBeforeUnmount)(d(o,`floating`,`SbFloating`));let{isOpen:s,floatingId:l,referenceId:m,side:h,align:g}=o;(0,t.watchEffect)(()=>{o.floatingRole.value=r.role,o.navigation.value=r.keyboardNavigation&&y(r.role)?r.role:null});let v=(0,t.ref)(s.value),w=(0,t.ref)(null),T=e=>{o.floating.value=e instanceof HTMLElement?e:null},E=e=>{w.value=u(e)},D=(0,t.computed)(()=>({...o.floatingStyles.value,...r.zIndex===void 0?{}:{zIndex:r.zIndex}})),O=()=>({"aria-labelledby":r.role&&Object.hasOwn(c,r.role)&&a[`aria-label`]==null?m.value:void 0,...a}),k=(0,t.computed)(()=>r.autoFocus??(r.role!==`tooltip`&&o.trigger.value!==`hover`&&o.trigger.value!==`focus`)),A=(0,t.computed)(()=>s.value&&r.teleport!==!1&&r.role!==`tooltip`),j={[p]:``,tabindex:0,style:{position:`fixed`,opacity:0,outline:`none`,pointerEvents:`none`}},M=(0,t.computed)(()=>{let{transition:e}=r;return e?typeof e==`string`?{name:e}:e:{css:!1}}),N=e=>e.setAttribute(`data-sbf-state`,`closed`),P=()=>i(`after-enter`),F=()=>{s.value||(v.value=!1),i(`after-leave`)},I=e=>e instanceof Node&&!!w.value?.contains(e),L=e=>{let t=w.value;if(!t)return;let n=_(t);((e===`first`?n[0]:n[n.length-1])??t).focus({preventScroll:!0})},R=e=>{I(e.relatedTarget)?o.reference.value?.focus():L(`first`)},z=e=>{if(!I(e.relatedTarget)){L(`last`);return}let t=o.reference.value;((t&&C(t,e=>o.contains(e)))??t)?.focus()},B=()=>{let e=o.navigation.value;return w.value&&e?b(w.value,e):[]},V=e=>{let t=w.value;if(!t)return;let n=B();(n.find(e=>e.getAttribute(`aria-selected`)===`true`)??(e===`last`?n[n.length-1]:e?n[0]:void 0)??t.querySelector(`[autofocus]`)??n[0]??_(t)[0]??t).focus({preventScroll:!0})},H=()=>{let e=o.focusRequest.value;return o.focusRequest.value=null,e},U=``,W;(0,t.onScopeDispose)(()=>clearTimeout(W));let G=(e,t,n)=>{U+=n.toLowerCase(),clearTimeout(W),W=setTimeout(()=>U=``,500);let r=[...U].every(e=>e===U[0])?U[0]:U,i=Math.max(t,0),a=[...e.slice(i),...e.slice(0,i)];return r.length===1&&t>=0&&a.push(a.shift()),a.find(e=>e.textContent?.trim().toLowerCase().startsWith(r))},K=e=>{let t=o,n=[t];for(;t.parent?.navigation.value===`menu`;)t=t.parent,n.push(t);let r=t.reference.value,i=e?r:(r&&C(r,e=>t.contains(e)))??r;for(let e of n)e.skipFocusReturn=!0,e.close();i?.focus()},q=e=>{let t=o.navigation.value;if(!t||e.defaultPrevented||e.isComposing||x(e.target))return;let n=B(),r=n.findIndex(t=>t.contains(e.target)),i=n.length-1,a=t===`menu`,s;if(e.key===`ArrowDown`)s=n[r<i?r+1:a?0:i];else if(e.key===`ArrowUp`)s=n[r>0?r-1:a||r<0?i:0];else if(e.key===`Home`)s=n[0];else if(e.key===`End`)s=n[i];else if(e.key===`Tab`&&t===`menu`){e.preventDefault(),K(e.shiftKey);return}else if(t===`menu`&&o.parent?.navigation.value===`menu`&&w.value&&e.key===S(w.value).close){e.preventDefault(),o.close();return}else if(e.key.length===1&&!e.ctrlKey&&!e.metaKey&&!e.altKey&&(e.key!==` `||U))e.preventDefault(),s=G(n,r,e.key);else if((e.key===`Enter`||e.key===` `)&&e.target===n[r]){let t=n[r];if(t.matches(`button, a[href], input, summary`))return;e.preventDefault(),t.getAttribute(`aria-disabled`)!==`true`&&t.click();return}s&&(e.preventDefault(),s.focus())};return(0,t.watch)(s,e=>{let t=o.skipFocusReturn;if(o.skipFocusReturn=!1,e){v.value=!0;return}let n=document.activeElement;!t&&n&&o.floating.value?.contains(n)&&o.reference.value?.focus({preventScroll:!0})}),(0,t.watch)(()=>s.value&&o.isPositioned.value,async e=>{if(!e)return;let n=H();(n||k.value)&&(await(0,t.nextTick)(),V(n))}),(0,t.watch)(o.focusRequest,e=>{e&&s.value&&o.isPositioned.value&&V(H())}),(e,n)=>((0,t.openBlock)(),(0,t.createBlock)(t.Teleport,{to:r.teleport||`body`,disabled:r.teleport===!1},[v.value?((0,t.openBlock)(),(0,t.createElementBlock)(`div`,{key:0,ref:T,style:(0,t.normalizeStyle)(D.value),onPointerenter:n[0]||=(...e)=>(0,t.unref)(o).hover&&(0,t.unref)(o).hover(...e),onPointerleave:n[1]||=(...e)=>(0,t.unref)(o).hover&&(0,t.unref)(o).hover(...e)},[A.value?((0,t.openBlock)(),(0,t.createElementBlock)(`span`,(0,t.mergeProps)({key:0},j,{onFocus:R}),null,16)):(0,t.createCommentVNode)(``,!0),(0,t.createVNode)(t.Transition,(0,t.mergeProps)(M.value,{appear:``,onBeforeLeave:N,onAfterEnter:P,onAfterLeave:F}),{default:(0,t.withCtx)(()=>[(0,t.unref)(s)?((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(r.as),(0,t.mergeProps)({key:0},O(),{id:(0,t.unref)(l),ref:E,role:r.role??void 0,tabindex:k.value?-1:void 0,"data-sbf-state":`open`,"data-sbf-side":(0,t.unref)(h),"data-sbf-align":(0,t.unref)(g),onKeydown:q}),{default:(0,t.withCtx)(()=>[(0,t.renderSlot)(e.$slots,`default`)]),_:3},16,[`id`,`role`,`tabindex`,`data-sbf-side`,`data-sbf-align`])):(0,t.createCommentVNode)(``,!0)]),_:3},16),A.value?((0,t.openBlock)(),(0,t.createElementBlock)(`span`,(0,t.mergeProps)({key:1},j,{onFocus:z}),null,16)):(0,t.createCommentVNode)(``,!0)],36)):(0,t.createCommentVNode)(``,!0)],8,[`to`,`disabled`]))}});e.SbContainer=T,e.SbFloating=D,e.SbReference=E});
|
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Component, TransitionProps } from 'vue';
|
|
1
2
|
/** Side of the reference the panel is placed on. */
|
|
2
3
|
export type SbSide = 'top' | 'right' | 'bottom' | 'left';
|
|
3
4
|
/** Alignment of the panel along the side. */
|
|
@@ -6,12 +7,21 @@ export type SbAlignment = 'start' | 'end';
|
|
|
6
7
|
export type SbPlacement = SbSide | `${SbSide}-${SbAlignment}`;
|
|
7
8
|
/** CSS position strategy of the panel. */
|
|
8
9
|
export type SbStrategy = 'absolute' | 'fixed';
|
|
10
|
+
/**
|
|
11
|
+
* ARIA role of the panel. The roles the components act on are suggested by your editor;
|
|
12
|
+
* any other role is accepted as a string.
|
|
13
|
+
*/
|
|
14
|
+
export type SbFloatingRole = 'dialog' | 'alertdialog' | 'menu' | 'listbox' | 'tree' | 'grid' | 'tooltip' | (string & {});
|
|
9
15
|
/**
|
|
10
16
|
* How the reference opens the floating panel.
|
|
11
17
|
* - `click`: clicking (or pressing Enter/Space on) `SbReference` toggles the panel.
|
|
18
|
+
* - `hover`: the pointer over `SbReference` opens the panel after `delay.open`, keyboard focus
|
|
19
|
+
* opens it at once. It closes `delay.close` after the pointer leaves both the reference and the
|
|
20
|
+
* panel, so the pointer can move onto the panel, and when focus leaves them. Touch has no hover.
|
|
21
|
+
* - `focus`: focus on `SbReference` opens the panel, focus moving elsewhere closes it.
|
|
12
22
|
* - `manual`: the panel opens only through `v-model:open`, the slot props or the exposed methods.
|
|
13
23
|
*/
|
|
14
|
-
export type SbTrigger = 'click' | 'manual';
|
|
24
|
+
export type SbTrigger = 'click' | 'hover' | 'focus' | 'manual';
|
|
15
25
|
/**
|
|
16
26
|
* Props for the `SbContainer` component: positioning, open state and interaction.
|
|
17
27
|
*/
|
|
@@ -47,10 +57,12 @@ export interface SbContainerProps {
|
|
|
47
57
|
*/
|
|
48
58
|
matchWidth?: boolean | 'min';
|
|
49
59
|
/**
|
|
50
|
-
*
|
|
60
|
+
* When the reference is scrolled out of view:
|
|
61
|
+
* - `true`: hides the floating panel (`visibility: hidden`) until the reference is back.
|
|
62
|
+
* - `'close'`: closes the panel, so focus never stays inside a hidden panel.
|
|
51
63
|
* @default false
|
|
52
64
|
*/
|
|
53
|
-
hideWhenDetached?: boolean;
|
|
65
|
+
hideWhenDetached?: boolean | 'close';
|
|
54
66
|
/**
|
|
55
67
|
* Open state, bound with `v-model:open`. Leave it unset to let the container manage it.
|
|
56
68
|
*/
|
|
@@ -70,6 +82,15 @@ export interface SbContainerProps {
|
|
|
70
82
|
* @default 'click'
|
|
71
83
|
*/
|
|
72
84
|
trigger?: SbTrigger;
|
|
85
|
+
/**
|
|
86
|
+
* Delay in milliseconds before the `hover` trigger opens and closes the panel: a number for
|
|
87
|
+
* both, or an object (missing keys keep their default). Focus opens the panel without delay.
|
|
88
|
+
* @default { open: 300, close: 150 }
|
|
89
|
+
*/
|
|
90
|
+
delay?: number | {
|
|
91
|
+
open?: number;
|
|
92
|
+
close?: number;
|
|
93
|
+
};
|
|
73
94
|
/**
|
|
74
95
|
* Closes the panel when the Escape key is pressed.
|
|
75
96
|
* @default true
|
|
@@ -80,6 +101,12 @@ export interface SbContainerProps {
|
|
|
80
101
|
* @default true
|
|
81
102
|
*/
|
|
82
103
|
closeOnClickOutside?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Closes the panel when keyboard focus moves outside the reference and the panel
|
|
106
|
+
* (e.g. tabbing past the last element of the panel).
|
|
107
|
+
* @default true
|
|
108
|
+
*/
|
|
109
|
+
closeOnFocusOut?: boolean;
|
|
83
110
|
}
|
|
84
111
|
/**
|
|
85
112
|
* Props passed to the default slot of `SbContainer`.
|
|
@@ -97,11 +124,19 @@ export interface SbContainerSlotProps {
|
|
|
97
124
|
placement: SbPlacement;
|
|
98
125
|
/** Side of the resolved placement. */
|
|
99
126
|
side: SbSide;
|
|
127
|
+
/** Alignment of the resolved placement, `undefined` when centered. */
|
|
128
|
+
align: SbAlignment | undefined;
|
|
129
|
+
/** `id` of the panel, e.g. for an `aria-describedby` of your own. */
|
|
130
|
+
floatingId: string;
|
|
131
|
+
/** `id` of the reference, `undefined` until `SbReference` is set up. */
|
|
132
|
+
referenceId: string | undefined;
|
|
100
133
|
}
|
|
101
134
|
/**
|
|
102
135
|
* Methods exposed by `SbContainer` through a template ref.
|
|
103
136
|
*/
|
|
104
137
|
export interface SbContainerExposed {
|
|
138
|
+
/** Whether the panel is open. */
|
|
139
|
+
readonly isOpen: boolean;
|
|
105
140
|
/** Opens the panel. */
|
|
106
141
|
open: () => void;
|
|
107
142
|
/** Closes the panel. */
|
|
@@ -116,14 +151,26 @@ export interface SbContainerExposed {
|
|
|
116
151
|
*/
|
|
117
152
|
export interface SbReferenceProps {
|
|
118
153
|
/**
|
|
119
|
-
* HTML tag to render.
|
|
120
|
-
*
|
|
154
|
+
* HTML tag or component to render.
|
|
155
|
+
* - `button` gets `type="button"`.
|
|
156
|
+
* - `a` with `href` and components are left as they are: a component is expected to render
|
|
157
|
+
* its own focusable element (e.g. the `<Button>` of your design system).
|
|
158
|
+
* - Any other tag (including `a` without `href`) gets `role="button"`, `tabindex="0"` and
|
|
159
|
+
* Enter/Space handling.
|
|
160
|
+
*
|
|
161
|
+
* When disabled, `button` gets `disabled`; anything else gets `aria-disabled="true"`.
|
|
121
162
|
* @default 'button'
|
|
122
163
|
*/
|
|
123
|
-
as?: string;
|
|
164
|
+
as?: string | Component;
|
|
124
165
|
/**
|
|
125
|
-
*
|
|
126
|
-
* @default
|
|
166
|
+
* `id` of the element. The panel is named after the reference with `aria-labelledby`.
|
|
167
|
+
* @default an id generated with `useId()`
|
|
168
|
+
*/
|
|
169
|
+
id?: string;
|
|
170
|
+
/**
|
|
171
|
+
* Value of `aria-haspopup`. When unset it follows the `role` of `SbFloating`
|
|
172
|
+
* (`dialog`, `menu`, `listbox`, `tree`, `grid`; `alertdialog` gives `dialog`)
|
|
173
|
+
* and is left out for any other role.
|
|
127
174
|
*/
|
|
128
175
|
ariaHaspopup?: 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid' | 'true' | 'false';
|
|
129
176
|
}
|
|
@@ -132,30 +179,38 @@ export interface SbReferenceProps {
|
|
|
132
179
|
*/
|
|
133
180
|
export interface SbFloatingProps {
|
|
134
181
|
/**
|
|
135
|
-
* HTML tag of the panel element (the one that receives your `class`, `style`
|
|
182
|
+
* HTML tag or component of the panel element (the one that receives your `class`, `style`
|
|
183
|
+
* and attributes). A component must render a single root element.
|
|
136
184
|
* @default 'div'
|
|
137
185
|
*/
|
|
138
|
-
as?: string;
|
|
186
|
+
as?: string | Component;
|
|
139
187
|
/**
|
|
140
|
-
* ARIA role of the panel.
|
|
188
|
+
* ARIA role of the panel. With a popup role (`dialog`, `alertdialog`, `menu`, `listbox`,
|
|
189
|
+
* `tree`, `grid`), the panel is named after the reference with `aria-labelledby`, unless
|
|
190
|
+
* you pass `aria-label` or `aria-labelledby` yourself.
|
|
191
|
+
* With `tooltip`, the open panel describes the reference with `aria-describedby` (unless you
|
|
192
|
+
* pass it on `SbReference`) instead of `aria-expanded` and `aria-controls`.
|
|
193
|
+
* `null` renders no role (disclosure pattern).
|
|
141
194
|
* @default 'dialog'
|
|
142
195
|
*/
|
|
143
|
-
role?:
|
|
196
|
+
role?: SbFloatingRole | null;
|
|
144
197
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
198
|
+
* Animation of the panel, through a Vue `<Transition>` on the panel element.
|
|
199
|
+
* - A name: the classes of your CSS (`<name>-enter-active`, …).
|
|
200
|
+
* - An object of `<Transition>` props: e.g. `enterActiveClass`, `leaveToClass` with
|
|
201
|
+
* Tailwind utilities, or JavaScript hooks.
|
|
202
|
+
*
|
|
203
|
+
* With any transition, Vue also waits for CSS animations keyed on
|
|
204
|
+
* `data-sbf-state="closed"`, set on the panel when it starts leaving.
|
|
205
|
+
* No animation when unset.
|
|
147
206
|
*/
|
|
148
|
-
transition?: string;
|
|
207
|
+
transition?: string | TransitionProps;
|
|
149
208
|
/**
|
|
150
|
-
*
|
|
209
|
+
* Where the panel is teleported: a CSS selector or an element. `false` renders it in place,
|
|
210
|
+
* next to the reference.
|
|
151
211
|
* @default 'body'
|
|
152
212
|
*/
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Renders the panel in place instead of teleporting it.
|
|
156
|
-
* @default false
|
|
157
|
-
*/
|
|
158
|
-
teleportDisabled?: boolean;
|
|
213
|
+
teleport?: string | HTMLElement | false;
|
|
159
214
|
/**
|
|
160
215
|
* `z-index` of the positioned wrapper.
|
|
161
216
|
*/
|
|
@@ -163,7 +218,27 @@ export interface SbFloatingProps {
|
|
|
163
218
|
/**
|
|
164
219
|
* Moves focus inside the panel once it is positioned: to the first element with
|
|
165
220
|
* `autofocus`, otherwise to the first focusable element, otherwise to the panel itself.
|
|
166
|
-
*
|
|
221
|
+
* With `role="menu"` or `listbox` the first item gets focus (a listbox: its selected option).
|
|
222
|
+
* @default true, false with `role="tooltip"` and with the `hover` and `focus` triggers
|
|
167
223
|
*/
|
|
168
224
|
autoFocus?: boolean;
|
|
225
|
+
/**
|
|
226
|
+
* Handles the keyboard model of `role="menu"` and `role="listbox"` panels
|
|
227
|
+
* (WAI-ARIA menu button and listbox patterns). Items are the elements with role `menuitem`,
|
|
228
|
+
* `menuitemcheckbox`, `menuitemradio` (menu) or `option` (listbox); they must be focusable,
|
|
229
|
+
* e.g. buttons or elements with `tabindex="-1"`.
|
|
230
|
+
* - ArrowDown / ArrowUp on the reference open the panel on the first / last item
|
|
231
|
+
* (not with the `manual` trigger).
|
|
232
|
+
* - ArrowDown / ArrowUp move between items (a menu wraps around), Home / End go to the ends,
|
|
233
|
+
* typing letters focuses the next item whose text starts with them.
|
|
234
|
+
* - Enter and Space activate items that are not buttons or links, with a click.
|
|
235
|
+
* - Menu only: Tab and Shift+Tab close it and move focus on, as from its reference.
|
|
236
|
+
* - Submenu (a menu inside a menu): ArrowRight on its reference opens it, ArrowLeft closes it
|
|
237
|
+
* (mirrored in right-to-left text).
|
|
238
|
+
*
|
|
239
|
+
* Keys whose `keydown` you already handled with `preventDefault()` are left to you.
|
|
240
|
+
* No effect with other roles.
|
|
241
|
+
* @default true
|
|
242
|
+
*/
|
|
243
|
+
keyboardNavigation?: boolean;
|
|
169
244
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sb-floating-panel-vue",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Headless Vue 3 floating panel components (dropdowns, popovers, menus) powered by @floating-ui/vue: positioning, accessibility and open state handled, zero CSS shipped.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vue",
|