@dashfoo/react 0.5.0 → 0.5.1
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/dist/index.js +2530 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,9 +1,2537 @@
|
|
|
1
|
-
"use client";
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { canRedo, canUndo, dashfooMachine, dragDockMachine, findTabset, fromJSON, normalize, reducer, resolveDockTarget, snapEnabled, snapSizes, stackModel, tabNodeSchema, toJSON, zoneRect } from "@dashfoo/core";
|
|
4
|
+
import { Fragment, createContext, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { Accessibility, DragDropManager, Draggable, Feedback, PointerSensor } from "@dnd-kit/dom";
|
|
6
|
+
import { createStore, useStore } from "zustand";
|
|
7
|
+
import { useActorRef, useSelector } from "@xstate/react";
|
|
8
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { Group, Panel as Panel$1, Separator } from "react-resizable-panels";
|
|
10
|
+
|
|
11
|
+
//#region src/hooks/drag-hooks.tsx
|
|
12
|
+
const INTERACTIVE_SELECTOR = `
|
|
2
13
|
input:not([disabled]),
|
|
3
14
|
select:not([disabled]),
|
|
4
15
|
textarea:not([disabled]),
|
|
5
16
|
button:not([disabled]),
|
|
6
17
|
a[href],
|
|
7
18
|
[contenteditable]:not([contenteditable="false"])
|
|
8
|
-
`);return r!==null&&r!==t.element},V=()=>new k({plugins:e=>e.filter(e=>e!==O&&e!==j),sensors:()=>[ee.configure({preventActivation:B})]}),re=g(null),H=g(null),ie=()=>M(()=>({intent:null,intentOwner:null,subject:null})),ae=(e,t)=>e===t||e!==null&&t!==null&&e.index===t.index&&e.location===t.location&&e.targetId===t.targetId,oe=(e,t)=>e===t||e!==null&&t!==null&&e.id===t.id&&e.kind===t.kind,se=ie(),U=g(null),ce=()=>{let e=E(null),t=E(null);return{draggableRef:t,elementRef:e,ref:v(n=>{e.current=n,t.current&&(t.current.element=n??void 0)},[])}},le=(e,t,n)=>{let r=y(re),i=y(H),a=r?.manager??i,{draggableRef:o,elementRef:s,ref:c}=ce(),{data:l,id:u}=e;return b(()=>{if(!a||t)return;let e=new A({data:{...l,label:``},id:u},a);return e.element=s.current??void 0,o.current=e,()=>{e.destroy(),o.current=null}},[l,t,o,s,u,a]),b(()=>{let e=o.current;e&&(e.data={...l,label:n})},[l,o,n]),{ref:c}},ue=(e,t=!1,n=``)=>le({data:T(()=>({type:`tab`}),[]),id:e},t,n),de=(e,t=!1,n=``)=>le({data:T(()=>({tabsetId:e,type:`tabset`}),[e]),id:`grip-${e}`},t,n),fe=e=>{let t=y(re);return{ref:v(n=>{t?.registerTabset(e,n)},[t,e])}},pe=({createTab:e,disabled:t=!1,label:n=``})=>{let r=x(),i=E(e);return b(()=>{i.current=e}),le({data:T(()=>({createTab:()=>i.current(),type:`external`}),[]),id:`external-${r}`},t,n)},me=()=>N(y(U)??se,e=>e.subject),he=()=>N(y(U)??se,e=>e.intent),ge=()=>{let e=new Map;return{getItem:t=>e.get(t)??null,removeItem:t=>{e.delete(t)},setItem:(t,n)=>{e.set(t,n)}}},_e={getItem:e=>{if(typeof window>`u`)return null;try{return window.localStorage.getItem(e)}catch{return null}},removeItem:e=>{if(!(typeof window>`u`))try{window.localStorage.removeItem(e)}catch{}},setItem:(e,t)=>{if(!(typeof window>`u`))try{window.localStorage.setItem(e,t)}catch(e){console.warn(`[dashfoo] failed to persist layout`,e)}}},ve=(e,t)=>{let n=E(e);b(()=>{n.current=e});let[r]=D(()=>{if(e===null||t===void 0)return t;let n=e.storage.getItem(e.key);if(n===null)return t;try{return a(n)}catch{return t}});b(()=>{let e=n.current;if(e===null)return;let t=e.storage.getItem(e.key);if(t!==null)try{a(t)}catch(t){console.warn(`[dashfoo] discarding unreadable persisted layout`,t),e.storage.removeItem(e.key)}},[]);let i=E(e?.key);b(()=>{e!==null&&e.key!==i.current&&(console.warn(`[dashfoo] persist key changed for a mounted layout; the displayed layout still reflects the previous key — remount (key={persistKey}) to load the new key`),i.current=e.key)},[e]);let o=E(null),s=E(null),c=v(()=>{o.current!==null&&(clearTimeout(o.current),o.current=null),s.current!==null&&(s.current.storage.setItem(s.current.key,s.current.value),s.current=null)},[]),l=v(e=>{let t=n.current;t!==null&&(s.current={key:t.key,storage:t.storage,value:p(e)},o.current!==null&&clearTimeout(o.current),o.current=setTimeout(c,t.debounceMs))},[c]);b(()=>{let e=()=>{c()},t=()=>{document.visibilityState===`hidden`&&c()};return window.addEventListener(`pagehide`,e),document.addEventListener(`visibilitychange`,t),()=>{window.removeEventListener(`pagehide`,e),document.removeEventListener(`visibilitychange`,t),c()}},[c]);let u=v(()=>{o.current!==null&&(clearTimeout(o.current),o.current=null),s.current=null;let e=n.current;e!==null&&e.storage.removeItem(e.key)},[]);return T(()=>({clear:u,initialModel:r,save:l}),[u,r,l])},ye=()=>typeof window<`u`&&typeof window.matchMedia==`function`,be=(e,t)=>{let{query:n}=e;return n?`maxWidth`in n?t<=n.maxWidth:ye()&&window.matchMedia(n.media).matches:!0},xe=(e,t)=>{let n=e.at(-1);if(!n)throw Error(`useResponsiveModel requires at least one breakpoint.`);return e.find(e=>be(e,t))??n},Se=()=>{let[e,t]=D(1/0),n=E(null),r=v(e=>{if(n.current?.disconnect(),!e)return;let r=new ResizeObserver(e=>{let n=e[0];n&&t(n.contentRect.width)});r.observe(e),n.current=r},[]);return b(()=>()=>{n.current?.disconnect()},[]),[r,e]},Ce=({breakpoints:e})=>{let[t,n]=Se(),[,r]=D(0);b(()=>{if(!ye())return;let t=e.flatMap(e=>e.query&&`media`in e.query?[window.matchMedia(e.query.media)]:[]);if(t.length===0)return;let n=()=>{r(e=>e+1)};for(let e of t)e.addEventListener(`change`,n);return()=>{for(let e of t)e.removeEventListener(`change`,n)}},[e]);let i=xe(e,n),a=i.compact===!0;return{breakpoint:i.id,containerRef:t,draggableTabs:!a,draggableTabsets:!a,isCompact:a,model:i.model,resizableSplits:!a}},we=r=>{let{defaultModel:i,model:a,onAction:c,onActiveTabsetChange:l,onMaximizedTabsetChange:u,onModelChange:d}=r,f=a??i;if(f===void 0)throw Error("useDashfooStore requires either a `model` or a `defaultModel`.");let p=te(n,{input:{model:o(f)}}),m=P(p,e=>e.context.history);b(()=>{a!==void 0&&p.send({model:o(a),type:`SET_MODEL`})},[p,a]);let h=a??m.present,g=v((e,t,n)=>{Object.is(e,t)||(d?.(t,n),t.activeTabsetId!==e.activeTabsetId&&l?.(t.activeTabsetId),t.maximizedTabsetId!==e.maximizedTabsetId&&u?.(t.maximizedTabsetId))},[d,l,u]),_=v(e=>{let t=c?c(e):e;if(!t)return;let n=(()=>{if(a===void 0){let e=p.getSnapshot().context.history.present;return p.send({action:t,type:`DISPATCH`}),{after:p.getSnapshot().context.history.present,before:e}}return{after:s(a,t),before:o(a)}})();g(n.before,n.after,t)},[p,a,g,c]),y=v(()=>{if(a!==void 0)return;let e=p.getSnapshot().context.history.present;p.send({type:`UNDO`});let t=p.getSnapshot().context.history.present;g(e,t)},[p,a,g]),x=v(()=>{if(a!==void 0)return;let e=p.getSnapshot().context.history.present;p.send({type:`REDO`});let t=p.getSnapshot().context.history.present;g(e,t)},[p,a,g]),S=v(e=>{p.send({model:o(e),type:`SET_MODEL`})},[p]);return T(()=>({canRedo:()=>e(p.getSnapshot().context.history),canUndo:()=>t(p.getSnapshot().context.history),dispatch:_,model:h,redo:x,setModel:S,undo:y}),[p,_,h,x,S,y])},Te=({children:e})=>{let[t]=D(V);C(()=>()=>t.destroy(),[t]);let[n]=D(ie);return I(H.Provider,{value:t,children:I(U.Provider,{value:n,children:e})})},Ee=e=>M(()=>e),De=g(null),W=e=>{let t=y(De);if(t===null)throw Error(`dashfoo components must be rendered inside <DashfooLayout> or <Layout.Root>.`);return N(t,e)},Oe=(e,t)=>e.x>=t.x&&e.x<=t.x+t.width&&e.y>=t.y&&e.y<=t.y+t.height,ke=(e,t)=>{let n=e.findIndex(e=>t<e.x+e.width/2);return n===-1?e.length:n},Ae=(e,t,n)=>{let r=t[n],i=t.at(-1),a=(r?.x??(i?i.x+i.width:e.x))-4/2;return{height:e.height,width:4,x:Math.max(e.x,Math.min(a,e.x+e.width-4)),y:e.y}},je=(e,t,n)=>!(e===`grip-${t}`||n.length===1&&n[0]===e),Me=new Set,G=(e,t)=>{Me.has(e)||(Me.add(e),console.warn(`[dashfoo] ${t}`))},Ne={boxSizing:`border-box`,pointerEvents:`none`,position:`fixed`,transition:`var(--dashfoo-dock-transition, left 60ms, top 60ms, width 60ms, height 60ms)`,zIndex:9999},Pe=e=>({...Ne,background:`var(--dashfoo-dock-fill, oklch(0.556 0 0 / 0.18))`,border:`var(--dashfoo-dock-border-width, 1px) solid var(--dashfoo-dock-border, oklch(0.708 0 0 / 0.75))`,borderRadius:`var(--dashfoo-dock-radius, 6px)`,height:e.height,left:e.x,top:e.y,width:e.width}),Fe=e=>({...Pe(e),borderRadius:`var(--dashfoo-dock-line-radius, 2px)`}),Ie=(e,t)=>[...e.querySelectorAll(`[data-dashfoo="tab-item"]`)].flatMap(e=>e.querySelector(`[data-dashfoo="tab"]`)?.dataset.tabId===t?[]:[e.getBoundingClientRect()]),Le=({actorRef:e,getTabsetElement:t})=>{let n=P(e,e=>e.context.intent),r=P(e,e=>e.context.subject?.id);if(!n)return null;let i=t(n.targetId);if(!i)return null;if(n.location===`center`){let e=i.querySelector(`[data-dashfoo="tabstrip"]`);if(e)return I(`div`,{"data-dashfoo":`dock-indicator`,style:Fe(Ae(e.getBoundingClientRect(),Ie(e,r),n.index??0))})}return I(`div`,{"data-dashfoo":`dock-indicator`,style:Pe(m(i.getBoundingClientRect(),n.location))})},Re={left:0,position:`fixed`,top:0,zIndex:9999},ze=({overlayRef:e,preview:t})=>t?I(`div`,{"data-dashfoo":`drag-preview`,ref:e,style:{...Re,transform:`translate(${t.x}px, ${t.y}px)`},children:t.label}):null,Be=(e,t)=>[...e.querySelectorAll(`[data-dashfoo="tab"]`)].flatMap(e=>e.dataset.tabId===t?[]:[e.getBoundingClientRect()]),Ve=(e,t,n,r)=>{let i=t.querySelector(`[data-dashfoo="tabstrip"]`);if(i&&Oe(n,i.getBoundingClientRect()))return{index:ke(Be(i,r),n.x),location:`center`,targetId:e};let a=c(n,t.getBoundingClientRect()),o=a.kind===`tab`?`center`:`split-${a.edge}`;return o===`center`&&i?{index:Be(i,r).length,location:o,targetId:e}:{location:o,targetId:e}},K={x:12,y:8},He=e=>{let t=e?.data?.label;return typeof t==`string`?t:``},Ue=e=>typeof e==`function`,We=e=>{let t=e.data;if(t?.type===`tabset`)return{id:String(t.tabsetId),kind:`tabset`};if(t?.type===`external`){if(!Ue(t.createTab))return console.warn(`[dashfoo] external drag source is missing its createTab function`),null;let n;try{n=t.createTab()}catch(e){return console.warn(`[dashfoo] external drag source createTab threw`,e),null}let r=f.safeParse(n);return r.success?{id:String(e.id),kind:`external`,tab:r.data}:(console.warn(`[dashfoo] external drag source returned an invalid tab`,r.error),null)}return{id:String(e.id),kind:`tab`}},Ge=({children:e,onCommit:t,splitDock:n})=>{let i=te(r),a=y(De),[o]=D(()=>new Map),s=E(null),[c,l]=D(null),u=y(H),[d]=D(()=>u?null:V());C(()=>()=>d?.destroy(),[d]);let f=u??d;if(f===null)throw Error(`[dashfoo] DashfooDragProvider unmounted while its layout is still mounted; keep the provider above the layout or remount the layout`);let p=y(U),[m]=D(()=>p??ie()),h=x();b(()=>{let e=i.subscribe(e=>{let{intent:t,subject:n}=e.context,r=m.getState();t===null?r.intent!==null&&r.intentOwner===h&&m.setState({intent:null,intentOwner:null}):(!ae(r.intent,t)||r.intentOwner!==h)&&m.setState({intent:t,intentOwner:h}),oe(m.getState().subject,n)||m.setState({subject:n})});return()=>{e.unsubscribe()}},[i,h,m]);let g=E(t),_=E(n);b(()=>{g.current=t,_.current=n}),b(()=>{let e=i.on(`COMMIT`,e=>{if(a?.getState().editable===!1)return;let t=g.current??a?.getState().dispatch;if(!t){G(`drag-layer-no-commit`,`Layout.DragLayer has neither an onCommit prop nor a <Layout.Root> above it; drops are ignored`);return}t(e.action)});return()=>{e.unsubscribe()}},[i,a]);let S=v((e,t)=>{t?o.set(e,t):o.delete(e)},[o]),w=v(e=>o.get(e),[o]),O=v(e=>{s.current=e},[]);b(()=>{let e=e=>{let t=document.elementFromPoint(e.x,e.y);if(t){for(let[e,n]of o)if(n.contains(t))return{element:n,id:e}}},t=(e,t,n,r)=>{if(a?.getState().editable===!1||!je(r,e,[...t.querySelectorAll(`[data-dashfoo="tab"]`)].map(e=>e.dataset.tabId??``)))return null;let i=Ve(e,t,n,r);return!(_.current??a?.getState().splitDock??!0)&&i.location.startsWith(`split-`)?{location:`center`,targetId:e}:i},n=e=>{let t=s.current;t&&(t.style.transform=`translate(${e.x+K.x}px, ${e.y+K.y}px)`)},r=e=>{let t=e.operation.source;if(!t)return;let n=We(t);if(!n)return;let r=e.operation.position.current;l({label:He(t),x:r.x+K.x,y:r.y+K.y}),i.send({subject:n,type:`START`})},c=r=>{let a=r.operation,o=a.position.current;n(o);let s=a.source?String(a.source.id):void 0,c=e(o);i.send({intent:c?t(c.id,c.element,o,s):null,type:`OVER`})},u=n=>{if(l(null),n.canceled){i.send({type:`CANCEL`});return}let r=n.operation,a=r.position.current,o=r.source?String(r.source.id):void 0,s=e(a);s&&i.send({intent:t(s.id,s.element,a,o),type:`OVER`}),i.send({type:`DROP`})},d=f.monitor.addEventListener(`dragstart`,r),p=f.monitor.addEventListener(`dragmove`,c),m=f.monitor.addEventListener(`dragend`,u);return()=>{d(),p(),m()}},[i,a,f,o]);let k=T(()=>({manager:f,registerTabset:S}),[f,S]);return I(re.Provider,{value:k,children:L(U.Provider,{value:m,children:[e,I(Le,{actorRef:i,getTabsetElement:w}),I(ze,{overlayRef:O,preview:c})]})})},Ke=g(!1),qe=()=>y(Ke),Je=(e,t,n=e)=>({height:e.height,left:Math.max(0,Math.min(e.left,Math.max(0,t.width-n.width))),top:Math.max(0,Math.min(e.top,Math.max(0,t.height-n.height))),width:e.width}),Ye=e=>{let t=(e.closest(`[data-dashfoo="tabset"]`)??e).getBoundingClientRect();return Je({height:Math.max(120,Math.min(Math.round(t.height),340)),left:Math.round(t.left)+16,top:Math.round(t.top)+16,width:Math.max(200,Math.min(Math.round(t.width),480))},{height:window.innerHeight,width:window.innerWidth})},Xe=(e,t,n,r)=>{let{height:i,left:a,top:o,width:s}=e;return t.x===1?s=Math.max(200,e.width+n):t.x===-1&&(s=Math.max(200,e.width-n),a=Math.max(0,e.left+(e.width-s))),t.y===1?i=Math.max(120,e.height+r):t.y===-1&&(i=Math.max(120,e.height-r),o=Math.max(0,e.top+(e.height-i))),{height:i,left:a,top:o,width:s}},Ze={display:`flex`,height:`100%`,position:`relative`,width:`100%`},Qe=({children:e,closableTabs:t=!0,dispatch:n,draggableTabs:r=!0,draggableTabsets:i=!0,editable:a=!0,floatable:o=!1,keepMounted:s=!1,maximizable:c=!0,model:l,renamableTabs:u=!0,renderTab:d,renderTabLabel:f,renderTabsetToolbar:p,resizableSplits:m=!0,rootRef:h,snap:g,style:_,...v})=>{let y=l.global,b={closableTabs:a&&t&&y.tabEnableClose!==!1,dispatch:n,draggableTabs:a&&r&&y.tabEnableDrag!==!1,draggableTabsets:a&&i,editable:a,floatable:a&&o,keepMounted:s,maximizable:c&&y.tabSetEnableMaximize!==!1,maximizedTabsetId:l.maximizedTabsetId,renamableTabs:a&&u&&y.tabEnableRename!==!1,renderTab:d,renderTabLabel:f,renderTabsetToolbar:p,resizableSplits:a&&m&&y.enableSplitResize!==!1,snap:g??y.snap??null,splitDock:y.enableSplitDock!==!1,tabLocation:y.tabLocation??`top`,tabsetMinSize:y.tabSetMinSize??320,tabStripEnabled:y.tabSetEnableTabStrip!==!1},[x]=D(()=>Ee(b));w(()=>{x.setState(b)});let S=y.splitterSize===void 0?{...Ze,..._}:{...Ze,"--dashfoo-splitter-size":`${y.splitterSize}px`,..._};return I(De.Provider,{value:x,children:I(`div`,{...v,"data-dashfoo":`layout`,ref:h,style:S,children:e})})},q=(e,t)=>`dashfoo-tab-${e}-${t}`,$e=e=>`dashfoo-panel-${e}`,et=(e,t,n)=>e?.querySelector(`#${CSS.escape(q(t,n))}`)??null,tt=e=>M((t,n)=>({...e,cancelRename:()=>t({editingTabId:null}),closeTab:e=>{t({restoreFocus:!0}),n().dispatch({tabId:e,type:`deleteTab`})},commitRename:(e,r)=>{t({editingTabId:null});let{node:i,renameTab:a}=n(),o=r.trim(),s=i.children.find(t=>t.id===e)?.name;o&&o!==s&&a(e,o)},editingTabId:null,overflowItems:[],registerRenameInput:()=>(t(e=>({renameInputCount:e.renameInputCount+1})),()=>t(e=>({renameInputCount:e.renameInputCount-1}))),registerTablist:e=>{if(e){let r=n().tablistElement;r&&r!==e&&G(`duplicate-tablist-${n().node.id}`,`more than one <Tabset.Tablist> under the same <Tabset.Root>; the last one wins for keyboard focus and overflow`),t({tablistElement:e});return}t({tablistElement:null})},renameInputCount:0,renameTab:(e,t)=>n().dispatch({name:t,tabId:e,type:`renameTab`}),restoreFocus:!1,selectOverflowTab:e=>{let{dispatch:t,node:r,tablistElement:i}=n(),a=r.children.findIndex(t=>t.id===e);a!==-1&&(t({index:a,tabsetId:r.id,type:`selectTab`}),et(i,r.id,e)?.scrollIntoView({block:`nearest`,inline:`nearest`}))},selectTab:(e,t)=>{let{dispatch:r,node:i,tablistElement:a}=n();if(r({index:e,tabsetId:i.id,type:`selectTab`}),t?.focus){let t=i.children[e]?.id;t&&et(a,i.id,t)?.focus()}},startEditing:e=>{let{node:r,renameInputCount:i,tabsRenamable:a}=n(),o=r.children.find(t=>t.id===e);if(!(!a||o?.enableRename===!1)){if(i===0){G(`missing-rename-input-${r.id}`,`startEditing needs a <Tabset.RenameInput> in the composition; ignoring`);return}t({editingTabId:e})}},tablistElement:null,toggleMaximize:()=>{let{dispatch:e,isMaximized:t,node:r}=n();e({tabsetId:t?null:r.id,type:`setMaximizedTabset`})}})),nt=g(null),J=e=>{let t=y(nt);if(t===null)throw Error(`dashfoo Tabset parts must be rendered inside <Tabset.Root>.`);return N(t,e)},rt=g(null),Y=()=>{let e=y(rt);if(e===null)throw Error(`dashfoo tab parts must be rendered inside <Tabset.Tab>.`);return e},it={flex:1,minHeight:0,overflow:`auto`},at=({render:e,tab:t})=>{let n=W(e=>e.renderTab);return e?e(t):n(t)},ot=({children:e,style:t,...n})=>{let r=J(e=>e.node),i=J(e=>e.visualSelected),a=W(e=>e.keepMounted),o={...it,...t};if(a&&r.children.length>0)return r.children.map((t,a)=>I(`div`,{...n,"aria-labelledby":q(r.id,t.id),"data-dashfoo":`tabcontent`,hidden:a!==i||void 0,id:a===i?$e(r.id):void 0,role:a===i?`tabpanel`:void 0,style:o,tabIndex:a===i?0:void 0,children:I(at,{render:e,tab:t})},t.id));let s=r.children[i];return s?I(`div`,{...n,"aria-labelledby":q(r.id,s.id),"data-dashfoo":`tabcontent`,id:$e(r.id),role:`tabpanel`,style:o,tabIndex:0,children:I(at,{render:e,tab:s})}):I(`div`,{...n,"data-dashfoo":`tabcontent`,style:o})},st=()=>L(`svg`,{"aria-hidden":`true`,fill:`currentColor`,height:`10`,viewBox:`0 0 10 10`,width:`10`,children:[I(`circle`,{cx:`2`,cy:`5`,r:`1`}),I(`circle`,{cx:`5`,cy:`5`,r:`1`}),I(`circle`,{cx:`8`,cy:`5`,r:`1`})]}),ct={position:`absolute`,right:0,top:`100%`,zIndex:10},lt=({items:e,onSelect:t})=>{let[n,r]=D(!1),[i,a]=D(0),o=E(null),s=E(null),c=E([]);b(()=>{if(!n)return;let e=e=>{e.target instanceof Node&&!o.current?.contains(e.target)&&r(!1)};return document.addEventListener(`pointerdown`,e),()=>{document.removeEventListener(`pointerdown`,e)}},[n]),b(()=>{n&&c.current[0]?.focus()},[n]);let l=()=>{r(!1),s.current?.focus()},u=e=>{a(e),c.current[e]?.focus()},d=()=>{a(0),r(e=>!e)},f=e=>{t(e),l()};return L(`div`,{"data-dashfoo":`tab-overflow-root`,ref:o,style:{position:`relative`},children:[I(`button`,{"aria-expanded":n,"aria-haspopup":`menu`,"aria-label":`More tabs`,"data-dashfoo":`tab-overflow`,onClick:d,ref:s,type:`button`,children:I(st,{})}),n?I(`div`,{"data-dashfoo":`tab-overflow-menu`,onBlur:e=>{e.relatedTarget instanceof Node&&o.current?.contains(e.relatedTarget)||r(!1)},onKeyDown:t=>{let n=e.length-1;switch(t.key){case`ArrowDown`:t.preventDefault(),u(i>=n?0:i+1);break;case`ArrowUp`:t.preventDefault(),u(i<=0?n:i-1);break;case`Home`:t.preventDefault(),u(0);break;case`End`:t.preventDefault(),u(n);break;case`Escape`:t.preventDefault(),l();break;default:break}},role:`menu`,style:ct,tabIndex:-1,children:e.map((e,t)=>I(`button`,{"data-dashfoo":`tab-overflow-item`,onClick:()=>f(e.id),ref:e=>{c.current[t]=e},role:`menuitem`,tabIndex:t===i?0:-1,type:`button`,children:e.name},e.id))}):null]})},ut=()=>{let e=J(e=>e.overflowItems),t=J(e=>e.selectOverflowTab);return e.length===0?null:I(lt,{items:e,onSelect:t})},dt=e=>{let t=e.getBoundingClientRect();return[...e.querySelectorAll(`[data-dashfoo="tab"]`)].flatMap(e=>{let n=e.getBoundingClientRect();return(n.right>t.right+1||n.left<t.left-1)&&e.dataset.tabId?[e.dataset.tabId]:[]})},ft=(e,t)=>{let[n,r]=D([]);return b(()=>{let t=()=>{r(t=>{let n=e?dt(e):[];return n.length===0&&t.length===0?t:n})};if(t(),!e)return;let n=new ResizeObserver(t);return n.observe(e),e.addEventListener(`scroll`,t,{passive:!0}),()=>{n.disconnect(),e.removeEventListener(`scroll`,t)}},[e,t]),n},pt=(e,t)=>t+1<e?t+1:t-1,mt={display:`flex`,flexDirection:`column`,height:`100%`,minHeight:0,minWidth:0,width:`100%`},ht=({children:e,node:t,ref:n,style:r,...i})=>{let a=W(e=>e.closableTabs),o=W(e=>e.dispatch),s=W(e=>e.maximizable),c=W(e=>e.maximizedTabsetId),l=W(e=>e.renamableTabs),u=W(e=>e.tabLocation),{ref:d}=fe(t.id),f=me(),p=E(null),m=c===t.id,h=s&&t.enableMaximize!==!1,g=a&&t.enableClose!==!1,_=l,v=f?.kind===`tab`?t.children.findIndex(e=>e.id===f.id):-1,y=v!==-1&&v===t.selected?pt(t.children.length,v):t.selected,x=t.children[y],S=f?.kind===`tabset`&&f.id===t.id,C={activeTab:x,dispatch:o,isMaximized:m,node:t,showMaximize:h,tabsClosable:g,tabsRenamable:_,visualSelected:y},[O]=D(()=>tt(C));w(()=>{O.setState(C)});let k=ft(N(O,e=>e.tablistElement),t.children.map(e=>`${e.id}:${e.name}`).join(`|`)),A=T(()=>{let e=new Map(t.children.map(e=>[e.id,e.name]));return k.map(t=>({id:t,name:e.get(t)??t}))},[k,t.children]);return w(()=>{O.setState({overflowItems:A})},[A,O]),b(()=>{if(!O.getState().restoreFocus)return;O.setState({restoreFocus:!1});let e=t.children[t.selected]?.id;e?p.current?.querySelector(`#${CSS.escape(q(t.id,e))}`)?.focus():p.current?.focus()},[t.children,t.selected,t.id,O]),I(nt.Provider,{value:O,children:I(`div`,{...i,"data-dashfoo":`tabset`,"data-dragging-source":S||void 0,"data-tab-location":u,ref:e=>{d(e),p.current=e,typeof n==`function`?n(e):n&&(n.current=e)},style:{...mt,...r},tabIndex:-1,children:e})})},gt=(e,t)=>{typeof e==`function`?e(t):e&&(e.current=t)},X=(...e)=>t=>{for(let n of e)gt(n,t)},_t=({children:e,ref:t,tab:n,...r})=>{let i=y(nt),a=J(e=>e.node),o=J(e=>e.editingTabId===n.id),s=me(),c=s?.kind===`tab`&&s.id===n.id,l=E(null),u=E(!1),d=a.children.findIndex(e=>e.id===n.id);b(()=>{i?.getState().node.children.some(e=>e.id===n.id)===!1&&G(`orphan-tab-${n.id}`,`<Tabset.Tab> for "${n.id}" is not a child of its Tabset.Root's node; it renders but can never be selected`)},[d===-1,i,n.id]),b(()=>{u.current&&!o&&l.current?.querySelector(`[data-dashfoo="tab"]`)?.focus(),u.current=o},[o]);let f=T(()=>({index:d,tab:n}),[d,n]),p=T(()=>X(l,t),[t]);return I(rt.Provider,{value:f,children:I(`span`,{...r,"data-dashfoo":`tab-item`,"data-dragging":c||void 0,ref:p,children:e})})},vt=({children:e,onClick:t,onDoubleClick:n,ref:r,...i})=>{let{index:a,tab:o}=Y(),s=J(e=>e.node),c=J(e=>e.editingTabId===o.id),l=J(e=>e.selectTab),u=J(e=>e.startEditing),d=J(e=>e.tabsRenamable),f=J(e=>e.visualSelected),p=W(e=>e.draggableTabs),m=W(e=>e.renderTabLabel),{ref:h}=ue(o.id,!p||o.enableDrag===!1,o.name),g=T(()=>X(h,r),[h,r]);if(c)return null;let _=a===f,v=d&&o.enableRename!==!1,y=e=>{t?.(e),l(a)},b=e=>{n?.(e),v&&u(o.id)};return I(`button`,{"aria-label":m?o.name:void 0,...i,"aria-controls":$e(s.id),"aria-selected":_,"data-dashfoo":`tab`,"data-tab-id":o.id,id:q(s.id,o.id),onClick:y,onDoubleClick:b,ref:g,role:`tab`,tabIndex:_?0:-1,type:`button`,children:e??(m?m(o):o.name)})},yt={pointerEvents:`none`},Z=({children:e})=>I(`svg`,{"aria-hidden":`true`,fill:`none`,height:`12`,stroke:`currentColor`,strokeLinecap:`round`,strokeLinejoin:`round`,strokeWidth:`2`,style:yt,viewBox:`0 0 24 24`,width:`12`,children:e}),bt=()=>L(Z,{children:[I(`path`,{d:`M18 6 6 18`}),I(`path`,{d:`m6 6 12 12`})]}),xt=()=>L(Z,{children:[I(`circle`,{cx:`12`,cy:`5`,r:`1`}),I(`circle`,{cx:`19`,cy:`5`,r:`1`}),I(`circle`,{cx:`5`,cy:`5`,r:`1`}),I(`circle`,{cx:`12`,cy:`12`,r:`1`}),I(`circle`,{cx:`19`,cy:`12`,r:`1`}),I(`circle`,{cx:`5`,cy:`12`,r:`1`}),I(`circle`,{cx:`12`,cy:`19`,r:`1`}),I(`circle`,{cx:`19`,cy:`19`,r:`1`}),I(`circle`,{cx:`5`,cy:`19`,r:`1`})]}),St=({maximized:e})=>e?L(Z,{children:[I(`path`,{d:`m14 10 7-7`}),I(`path`,{d:`M20 10h-6V4`}),I(`path`,{d:`m3 21 7-7`}),I(`path`,{d:`M4 14h6v6`})]}):L(Z,{children:[I(`path`,{d:`M15 3h6v6`}),I(`path`,{d:`m21 3-7 7`}),I(`path`,{d:`m3 21 7-7`}),I(`path`,{d:`M9 21H3v-6`})]}),Ct=()=>L(Z,{children:[I(`rect`,{height:`14`,rx:`2`,width:`18`,x:`3`,y:`6`}),I(`path`,{d:`M3 10h18`})]}),wt=()=>I(Z,{children:I(`path`,{d:`M5 12h14`})}),Tt=()=>L(Z,{children:[I(`path`,{d:`M3 7V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2`}),I(`path`,{d:`M11 8 7 12l4 4`}),I(`path`,{d:`M7 12h10`})]}),Et=({defaultValue:e,onBlur:t,onKeyDown:n,ref:r,...i})=>{let{tab:a}=Y(),o=J(e=>e.cancelRename),s=J(e=>e.commitRename),c=E(null),l=E(!1),u=T(()=>X(c,r),[r]);b(()=>{c.current?.focus(),c.current?.select()},[]);let d=e=>{n?.(e),e.key===`Enter`?(l.current=!0,s(a.id,e.currentTarget.value)):e.key===`Escape`&&(l.current=!0,o())},f=e=>{t?.(e),!l.current&&(l.current=!0,s(a.id,e.currentTarget.value))};return I(`input`,{"aria-label":`Rename ${a.name}`,...i,"data-dashfoo":`tab-rename`,defaultValue:e??a.name,onBlur:f,onKeyDown:d,ref:u,type:`text`})},Dt=e=>{let{tab:t}=Y(),n=J(e=>e.editingTabId===t.id),r=J(e=>e.registerRenameInput);return b(()=>r(),[r]),n?I(Et,{...e}):null},Ot=({children:e,onClick:t,...n})=>{let{index:r,tab:i}=Y(),a=J(e=>e.closeTab),o=J(e=>e.editingTabId===i.id),s=J(e=>e.tabsClosable),c=J(e=>e.visualSelected);if(!(s&&i.enableClose!==!1)||o)return null;let l=e=>{t?.(e),e.stopPropagation(),a(i.id)};return I(`button`,{"aria-label":`Close ${i.name}`,...n,"data-dashfoo":`tab-close`,onClick:l,tabIndex:r===c?0:-1,type:`button`,children:e??I(bt,{})})},kt={display:`flex`,flexShrink:0},At={display:`flex`,minWidth:0,overflowX:`auto`},jt=({style:e,...t})=>I(`div`,{...t,"data-dashfoo":`tabstrip`,style:{...kt,...e}}),Mt=({onKeyDown:e,ref:t,style:n,...r})=>{let i=J(e=>e.node),a=J(e=>e.registerTablist),o=J(e=>e.selectTab),s=t=>{if(e?.(t),t.defaultPrevented||!(t.target instanceof HTMLElement)||!t.target.closest(`[role="tab"]`))return;let n=i.children.length;if(n===0)return;let r={ArrowLeft:(i.selected-1+n)%n,ArrowRight:(i.selected+1)%n,End:n-1,Home:0}[t.key];r!==void 0&&(t.preventDefault(),o(r,{focus:!0}))},c=T(()=>X(a,t),[a,t]);return I(`div`,{"aria-label":i.name??`Tabs`,...r,"aria-orientation":`horizontal`,"data-dashfoo":`tablist`,onKeyDown:s,ref:c,role:`tablist`,style:{...At,...n},tabIndex:-1})},Nt={alignItems:`center`,display:`flex`,flexShrink:0,marginInlineStart:`auto`},Pt=({style:e,...t})=>I(`div`,{...t,"data-dashfoo":`tabset-toolbar`,style:{...Nt,...e}}),Ft=({children:e,ref:t,...n})=>{let r=J(e=>e.activeTab?.name??``),i=J(e=>e.isMaximized),a=J(e=>e.node),o=!W(e=>e.draggableTabsets)||i,{ref:s}=de(a.id,o,r),c=T(()=>X(s,t),[s,t]);return o?null:I(`button`,{"aria-label":`Move tabset`,...n,"data-dashfoo":`tabset-grip`,ref:c,type:`button`,children:e??I(xt,{})})},It=({children:e,onClick:t,...n})=>{let r=J(e=>e.isMaximized),i=J(e=>e.showMaximize),a=J(e=>e.toggleMaximize);if(!i)return null;let o=e=>{t?.(e),a()};return I(`button`,{"aria-label":r?`Restore`:`Maximize`,...n,"aria-pressed":r,"data-dashfoo":`tabset-maximize`,onClick:o,type:`button`,children:e??I(St,{maximized:r})})},Lt=({children:e,onClick:t,...n})=>{let r=W(e=>e.floatable),i=W(e=>e.dispatch),a=J(e=>e.isMaximized),o=J(e=>e.node),s=qe();if(!r||a)return null;if(!s)return G(`float-no-layer`,`Tabset.FloatButton needs a <Layout.FloatLayer> around the layout (DashfooLayout adds one); the float control is hidden`),null;let c=e=>{t?.(e),i({geometry:Ye(e.currentTarget),tabsetId:o.id,type:`floatTabset`})};return I(`button`,{"aria-label":`Float panel`,...n,"data-dashfoo":`tabset-float`,onClick:c,type:`button`,children:e??I(Ct,{})})},Rt=I(ot,{}),zt=()=>{let e=W(e=>e.draggableTabsets),t=W(e=>e.floatable),n=W(e=>e.renderTabsetToolbar),r=W(e=>e.tabLocation),i=W(e=>e.tabStripEnabled),a=J(e=>e.isMaximized),o=J(e=>e.node),s=J(e=>e.overflowItems.length),c=J(e=>e.showMaximize)||n!==void 0||s>0||t&&!a||e&&!a,l=i?L(jt,{children:[I(Mt,{children:o.children.map(e=>L(_t,{tab:e,children:[I(vt,{}),I(Dt,{}),I(Ot,{})]},e.id))}),c?L(Pt,{children:[I(ut,{}),I(Ft,{}),n?.(o),I(Lt,{}),I(It,{})]}):null]}):null;return r===`bottom`?L(F,{children:[Rt,l]}):L(F,{children:[l,Rt]})},Bt=({node:e})=>I(ht,{node:e,children:I(zt,{})}),Vt=e=>`${e.value}${e.unit}`,Ht=e=>e?.unit===`px`?e.value:void 0,Ut={display:`flex`,flex:1,minHeight:0,minWidth:0},Wt=.01,Gt=(e,t)=>e.reduce((e,n)=>(e[n.id]=(n.weight??1)/t*100,e),{}),Kt=(e,t)=>t.map(t=>e[t.id]??0),qt=(e,t)=>t.reduce((t,n,r)=>(t[n.id]=e[r]??0,t),{}),Jt=(e,t)=>{let n=Object.keys(e),r=Object.keys(t);return n.length===r.length?r.every(n=>Math.abs((e[n]??NaN)-t[n])<=Wt):!1},Yt=(e,t,n)=>{let r=Ht(e.min);if(r!==void 0)return r;if(e.min!==void 0)return;if(e.type===`tabset`)return n;let i=[];for(let r of e.children){let e=Yt(r,t,n);if(e===void 0)return;i.push(e)}if(i.length!==0)return e.orientation===t?i.reduce((e,t)=>e+t,0):Math.max(...i)},Xt=({node:e,renderTabset:t})=>{let n=W(e=>e.dispatch),r=W(e=>e.resizableSplits),i=W(e=>e.tabsetMinSize),a=W(e=>e.snap),o=e.snap??a,s=l(o),c=e.orientation===`row`?`horizontal`:`vertical`,d=e.children.reduce((e,t)=>e+(t.weight??1),0),f=T(()=>Gt(e.children,d),[e.children,d]),p=E(!1),m=E(null),g=E(null),_=E(!1),v=E(null),y=E(!1),b=E(null),[x,S]=D(null),C=e=>{e!==b.current&&(b.current=e,S(e))},O=e=>{let t=g.current;t&&(e?t.dataset.dashfooSnapping=`true`:delete t.dataset.dashfooSnapping)};return w(()=>{let e=m.current;if(!e)return;let t=e.getLayout();if(!(Object.keys(t).length===Object.keys(f).length&&Object.keys(f).every(e=>e in t))||Jt(t,f))return;_.current=!0,e.setLayout(f);let n=requestAnimationFrame(()=>{_.current=!1});return()=>{cancelAnimationFrame(n),_.current=!1}},[f]),I(R,{"data-dashfoo":`row`,disabled:!r,elementRef:g,groupRef:m,onLayoutChange:s?t=>{if(y.current||_.current||!r||o===null||!s)return;let n=v.current;if(n===null)return;let{sizes:i,snapped:a}=u(Kt(t,e.children),n,o);if(C(a?n:null),!a){O(!1);return}O(!0);let c=m.current,l=qt(i,e.children);!c||Jt(t,l)||(y.current=!0,c.setLayout(l),y.current=!1)}:void 0,onLayoutChanged:t=>{if(!p.current){p.current=!0;return}if(_.current)return;let i=v.current;if(v.current=null,C(null),O(!1),!r)return;let a=e.children.map(e=>t[e.id]??e.weight??1);i!==null&&o!==null&&s&&(a=u(a,i,o).sizes),a.some((t,n)=>Math.abs(t-(e.children[n]?.weight??1))>Wt)&&n({rowId:e.id,type:`adjustSplit`,weights:a})},orientation:c,style:Ut,children:e.children.map((n,a)=>{let o=(n.weight??1)/d*100,c=n.min?Vt(n.min):void 0;if(c===void 0){let t=Yt(n,e.orientation,i);c=t===void 0?void 0:`${t}px`}let l=n.max?Vt(n.max):void 0;return L(h,{children:[a>0?I(ne,{"data-dashfoo":`splitter`,"data-dashfoo-snapped":x===a-1?`true`:void 0,disabled:!r,onPointerDown:s&&r?()=>{v.current=a-1}:void 0}):null,I(z,{defaultSize:`${o}%`,id:n.id,maxSize:l,minSize:c,children:n.type===`row`?I(Xt,{node:n,renderTabset:t}):t?.(n)??I(Bt,{node:n})})]},n.id)})},e.id)},Q=e=>e.name??`Panel`,Zt={alignItems:`center`,display:`flex`,flexShrink:0,gap:`0.375rem`,touchAction:`none`},Qt={flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`},$t={alignItems:`center`,cursor:`pointer`,display:`inline-flex`,flexShrink:0},en={flex:1,minHeight:0,position:`relative`},tn=[{edges:{x:0,y:-1},key:`n`,style:{cursor:`ns-resize`,height:6,insetInline:10,top:-3}},{edges:{x:0,y:1},key:`s`,style:{bottom:-3,cursor:`ns-resize`,height:6,insetInline:10}},{edges:{x:1,y:0},key:`e`,style:{cursor:`ew-resize`,insetBlock:10,right:-3,width:6}},{edges:{x:-1,y:0},key:`w`,style:{cursor:`ew-resize`,insetBlock:10,left:-3,width:6}},{edges:{x:1,y:-1},key:`ne`,style:{cursor:`nesw-resize`,height:14,right:-4,top:-4,width:14}},{edges:{x:-1,y:-1},key:`nw`,style:{cursor:`nwse-resize`,height:14,left:-4,top:-4,width:14}},{edges:{x:1,y:1},key:`se`,style:{bottom:-4,cursor:`nwse-resize`,height:14,right:-4,width:14}},{edges:{x:-1,y:1},key:`sw`,style:{bottom:-4,cursor:`nesw-resize`,height:14,left:-4,width:14}}],nn=new Map(tn.map(e=>[e.key,e.edges])),rn={height:34,width:168},an=({dispatch:e,node:t,onDone:n})=>{let r=E(null),i=E(!1);b(()=>{r.current?.focus(),r.current?.select()},[]);let a=r=>{let i=r.trim();i&&i!==Q(t)&&e({floatId:t.id,name:i,type:`renameFloat`}),n()};return I(`input`,{"aria-label":`Rename ${Q(t)}`,"data-dashfoo":`float-rename`,defaultValue:Q(t),onBlur:e=>{i.current||(i.current=!0,a(e.currentTarget.value))},onKeyDown:e=>{e.key===`Enter`?(i.current=!0,a(e.currentTarget.value)):e.key===`Escape`&&(i.current=!0,n())},onPointerDown:e=>e.stopPropagation(),ref:r,style:Qt,type:`text`})},on=({dispatch:e,node:t,renamable:n})=>{let[r,i]=D(!1);return n&&r?I(an,{dispatch:e,node:t,onDone:()=>i(!1)}):I(`span`,{"data-dashfoo":`float-title`,onDoubleClick:n?()=>i(!0):void 0,style:Qt,title:n?`Double-click to rename`:void 0,children:Q(t)})},sn=({global:e,node:t,onFocus:n,zIndex:r})=>{let i=W(e=>e.dispatch),a=W(e=>e.closableTabs),o=W(e=>e.draggableTabs),s=W(e=>e.editable),c=W(e=>e.renamableTabs),l=W(e=>e.resizableSplits),u=W(e=>e.keepMounted),d=W(e=>e.renderTab),f=W(e=>e.renderTabLabel),p=W(e=>e.renderTabsetToolbar),m=W(e=>e.snap),h=E(null),g=E(null),_=E(t.geometry),v=E(!1),y=e=>{h.current=e},b=()=>{s&&i({floatId:t.id,minimized:!1,type:`setFloatMinimized`})},x=e=>{let n=h.current;if(!n||!s)return;v.current=!1;let r=e.currentTarget.dataset.edge,i=n.offsetParent;g.current={bounds:{height:i?.clientHeight??0,width:i?.clientWidth??0},edges:r?nn.get(r)??null:null,pointerId:e.pointerId,start:t.geometry,startX:e.clientX,startY:e.clientY},_.current=t.geometry},S=e=>{let n=g.current,r=h.current;if(!n||!r||e.pointerId!==n.pointerId)return;let i=e.clientX-n.startX,a=e.clientY-n.startY;if(!v.current){if(Math.hypot(i,a)<=4)return;v.current=!0,r.setPointerCapture(n.pointerId)}let o=n.edges?Xe(n.start,n.edges,i,a):Je({...n.start,left:n.start.left+i,top:n.start.top+a},n.bounds,t.minimized?rn:void 0);_.current=o,r.style.left=`${o.left}px`,r.style.top=`${o.top}px`,t.minimized||(r.style.width=`${o.width}px`,r.style.height=`${o.height}px`)},C=e=>{let n=g.current;if(!(!n||e.pointerId!==n.pointerId)){if(g.current=null,!v.current){t.minimized&&b();return}h.current?.releasePointerCapture(e.pointerId),i({floatId:t.id,geometry:_.current,type:`moveFloat`})}};if(t.minimized)return L(`button`,{"aria-label":`Restore ${Q(t)} panel`,"data-dashfoo":`float-chip`,onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),b())},onPointerDown:x,onPointerDownCapture:n,onPointerMove:S,onPointerUp:C,ref:y,style:{left:t.geometry.left,pointerEvents:`auto`,position:`absolute`,top:t.geometry.top,zIndex:r},title:Q(t),type:`button`,children:[I(Ct,{}),I(`span`,{"data-dashfoo":`float-chip-label`,children:Q(t)})]});let w={global:e,layout:t.layout,version:1};return L(`div`,{"data-dashfoo":`float`,onPointerDownCapture:n,onPointerMove:S,onPointerUp:C,ref:y,style:{height:t.geometry.height,left:t.geometry.left,pointerEvents:`auto`,position:`absolute`,top:t.geometry.top,width:t.geometry.width,zIndex:r},children:[L(`div`,{"data-dashfoo":`float-titlebar`,onPointerDown:x,style:s?Zt:{...Zt,cursor:`default`},children:[I(`span`,{"aria-hidden":`true`,"data-dashfoo":`float-grip`,children:I(xt,{})}),I(on,{dispatch:i,node:t,renamable:s}),s&&L(F,{children:[I(`button`,{"aria-label":`Minimize panel`,"data-dashfoo":`float-minimize`,onClick:()=>i({floatId:t.id,minimized:!0,type:`setFloatMinimized`}),onPointerDown:e=>e.stopPropagation(),style:$t,type:`button`,children:I(wt,{})}),I(`button`,{"aria-label":`Dock panel back into the main layout`,"data-dashfoo":`float-dock`,onClick:()=>i({floatId:t.id,type:`dockFloat`}),onPointerDown:e=>e.stopPropagation(),style:$t,type:`button`,children:I(Tt,{})})]})]}),I(`div`,{"data-dashfoo":`float-body`,style:en,children:I(Qe,{closableTabs:a,dispatch:i,draggableTabs:o,draggableTabsets:!1,editable:s,floatable:!1,keepMounted:u,maximizable:!1,model:w,renamableTabs:c,renderTab:d,renderTabLabel:f,renderTabsetToolbar:p,resizableSplits:l,snap:m??void 0,children:I(Ge,{children:I(Xt,{node:t.layout})})})}),s&&tn.map(e=>I(`div`,{"data-dashfoo":`float-resize`,"data-edge":e.key,onPointerDown:x,style:{position:`absolute`,touchAction:`none`,zIndex:1,...e.style}},e.key))]})},cn={inset:0,pointerEvents:`none`,position:`fixed`},ln=({floats:e,global:t})=>{let[n,r]=D(null);return e.length===0?null:I(`div`,{"data-dashfoo":`float-overlay`,style:cn,children:e.map(e=>I(sn,{global:t,node:e,onFocus:()=>r(e.id),zIndex:e.id===n?2:1},e.id))})},$={DragLayer:Ge,FloatLayer:({children:e,floats:t,global:n})=>L(Ke.Provider,{value:!0,children:[e,I(ln,{floats:t,global:n})]}),Root:Qe,Rows:Xt,Tabset:Bt},un=(e,t)=>e===void 0||!t?null:typeof e==`string`?{debounceMs:300,key:e,storage:_e}:{debounceMs:e.debounceMs??300,key:e.key,storage:e.storage??_e},dn=new Set,fn=_((e,t)=>{let{closableTabs:n=!0,components:r,defaultModel:a,draggableTabs:o=!0,draggableTabsets:s=!0,editable:c=!0,factory:l,floatable:u=!1,keepMounted:f=!1,maximizable:p=!0,model:m,onAction:h,onActiveTabsetChange:g,onMaximizedTabsetChange:_,onModelChange:b,persist:x,renamableTabs:C=!0,renderTabLabel:w,renderTabsetToolbar:D,resizableSplits:O=!0,responsive:k,snap:A}=e,j=ve(T(()=>un(x,a!==void 0),[x,a]),a),ee=v((e,t)=>{j.save(e),b?.(e,t)},[b,j]),M=we({defaultModel:j.initialModel??a,model:m,onAction:h,onActiveTabsetChange:g,onMaximizedTabsetChange:_,onModelChange:ee}),N=y(H)!==null;S(t,()=>({addTab:(e,t)=>M.dispatch({index:t.index,location:t.location??`center`,tab:e,targetId:t.targetId,type:`addNode`}),canRedo:M.canRedo,canUndo:M.canUndo,closeTab:e=>M.dispatch({tabId:e,type:`deleteTab`}),dispatch:M.dispatch,dockFloat:e=>M.dispatch({floatId:e,type:`dockFloat`}),floatTab:e=>M.dispatch({tabId:e,type:`floatTab`}),getModel:()=>M.model,maximizeTabset:e=>M.dispatch({tabsetId:e,type:`setMaximizedTabset`}),redo:M.redo,renameTab:(e,t)=>M.dispatch({name:t,tabId:e,type:`renameTab`}),resetLayout:()=>{j.clear(),a!==void 0&&M.setModel(a)},selectTab:(e,t)=>M.dispatch({index:t,tabsetId:e,type:`selectTab`}),undo:M.undo}),[a,j,M]);let te=v(e=>{if(l)return l(e);let t=r?.[e.component];return t?I(t,{node:e}):(r&&!dn.has(e.component)&&(dn.add(e.component),console.warn(`[dashfoo] no component registered for "${e.component}"`)),null)},[r,l]),[P,F]=Se(),L=k!==void 0&&F<=k.maxWidth,R=T(()=>L?d(M.model,k?.orientation):M.model,[L,k?.orientation,M.model]),z=E(L);z.current=L;let ne=v(e=>{z.current&&e.type===`adjustSplit`||M.dispatch(e)},[M]),B=R.maximizedTabsetId?i(R,R.maximizedTabsetId):void 0,V=I($.FloatLayer,{floats:M.model.floats??[],global:M.model.global,children:I($.DragLayer,{children:B?I($.Tabset,{node:B}):I($.Rows,{node:R.layout})})});return I($.Root,{closableTabs:n,dispatch:ne,draggableTabs:L?!1:o,draggableTabsets:L?!1:s,editable:c,floatable:L?!1:u,keepMounted:f,maximizable:p,model:R,renamableTabs:C,renderTab:te,renderTabLabel:w,renderTabsetToolbar:D,resizableSplits:L?!1:O,rootRef:P,snap:A,children:N?V:I(Te,{children:V})})});fn.displayName=`DashfooLayout`;const pn={Badge:e=>I(`span`,{...e,"data-dashfoo":`panel-badge`}),Body:e=>I(`div`,{...e,"data-dashfoo":`panel-body`}),Header:e=>I(`div`,{...e,"data-dashfoo":`panel-header`}),Icon:e=>I(`span`,{...e,"data-dashfoo":`panel-icon`}),Root:e=>I(`div`,{...e,"data-dashfoo":`panel`}),Title:e=>I(`span`,{...e,"data-dashfoo":`panel-title`})},mn={CloseButton:Ot,Content:ot,FloatButton:Lt,Grip:Ft,MaximizeButton:It,OverflowMenu:ut,RenameInput:Dt,Root:ht,Tab:_t,Tablist:Mt,TabStrip:jt,Toolbar:Pt,Trigger:vt};export{Te as DashfooDragProvider,fn as DashfooLayout,$ as Layout,pn as Panel,mn as Tabset,xe as activeBreakpoint,_e as localStorageAdapter,be as matchBreakpoint,ge as memoryStorageAdapter,Se as useContainerWidth,we as useDashfooStore,me as useDragSubject,he as useDropIntent,pe as useExternalTabSource,W as useLayout,ve as usePersistence,Ce as useResponsiveModel,Y as useTab,J as useTabset};
|
|
19
|
+
`;
|
|
20
|
+
const preventActivation = (event, source) => {
|
|
21
|
+
const { target } = event;
|
|
22
|
+
if (!(target instanceof Element)) return false;
|
|
23
|
+
const interactive = target.closest(INTERACTIVE_SELECTOR);
|
|
24
|
+
return interactive !== null && interactive !== source.element;
|
|
25
|
+
};
|
|
26
|
+
const createDragManager = () => new DragDropManager({
|
|
27
|
+
plugins: (defaults) => defaults.filter((plugin) => plugin !== Accessibility && plugin !== Feedback),
|
|
28
|
+
sensors: () => [PointerSensor.configure({ preventActivation })]
|
|
29
|
+
});
|
|
30
|
+
const DragContext = createContext(null);
|
|
31
|
+
const SharedDragManagerContext = createContext(null);
|
|
32
|
+
const createDragSubjectStore = () => createStore(() => ({
|
|
33
|
+
intent: null,
|
|
34
|
+
intentOwner: null,
|
|
35
|
+
subject: null
|
|
36
|
+
}));
|
|
37
|
+
const sameDropIntent = (a, b) => a === b || a !== null && b !== null && a.index === b.index && a.location === b.location && a.targetId === b.targetId;
|
|
38
|
+
const sameDragSubject = (a, b) => a === b || a !== null && b !== null && a.id === b.id && a.kind === b.kind;
|
|
39
|
+
const nullSubjectStore = createDragSubjectStore();
|
|
40
|
+
const DragSubjectStoreContext = createContext(null);
|
|
41
|
+
const useDraggableHandle = () => {
|
|
42
|
+
const elementRef = useRef(null);
|
|
43
|
+
const draggableRef = useRef(null);
|
|
44
|
+
return {
|
|
45
|
+
draggableRef,
|
|
46
|
+
elementRef,
|
|
47
|
+
ref: useCallback((element) => {
|
|
48
|
+
elementRef.current = element;
|
|
49
|
+
if (draggableRef.current) draggableRef.current.element = element ?? void 0;
|
|
50
|
+
}, [])
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
const useDraggableEntity = (descriptor, disabled, label) => {
|
|
54
|
+
const context = useContext(DragContext);
|
|
55
|
+
const sharedManager = useContext(SharedDragManagerContext);
|
|
56
|
+
const manager = context?.manager ?? sharedManager;
|
|
57
|
+
const { draggableRef, elementRef, ref } = useDraggableHandle();
|
|
58
|
+
const { data, id } = descriptor;
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!manager || disabled) return;
|
|
61
|
+
const draggable = new Draggable({
|
|
62
|
+
data: {
|
|
63
|
+
...data,
|
|
64
|
+
label: ""
|
|
65
|
+
},
|
|
66
|
+
id
|
|
67
|
+
}, manager);
|
|
68
|
+
draggable.element = elementRef.current ?? void 0;
|
|
69
|
+
draggableRef.current = draggable;
|
|
70
|
+
return () => {
|
|
71
|
+
draggable.destroy();
|
|
72
|
+
draggableRef.current = null;
|
|
73
|
+
};
|
|
74
|
+
}, [
|
|
75
|
+
data,
|
|
76
|
+
disabled,
|
|
77
|
+
draggableRef,
|
|
78
|
+
elementRef,
|
|
79
|
+
id,
|
|
80
|
+
manager
|
|
81
|
+
]);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const draggable = draggableRef.current;
|
|
84
|
+
if (draggable) draggable.data = {
|
|
85
|
+
...data,
|
|
86
|
+
label
|
|
87
|
+
};
|
|
88
|
+
}, [
|
|
89
|
+
data,
|
|
90
|
+
draggableRef,
|
|
91
|
+
label
|
|
92
|
+
]);
|
|
93
|
+
return { ref };
|
|
94
|
+
};
|
|
95
|
+
const useTabDraggable = (tabId, disabled = false, label = "") => {
|
|
96
|
+
return useDraggableEntity({
|
|
97
|
+
data: useMemo(() => ({ type: "tab" }), []),
|
|
98
|
+
id: tabId
|
|
99
|
+
}, disabled, label);
|
|
100
|
+
};
|
|
101
|
+
const useTabsetDraggable = (tabsetId, disabled = false, label = "") => {
|
|
102
|
+
return useDraggableEntity({
|
|
103
|
+
data: useMemo(() => ({
|
|
104
|
+
tabsetId,
|
|
105
|
+
type: "tabset"
|
|
106
|
+
}), [tabsetId]),
|
|
107
|
+
id: `grip-${tabsetId}`
|
|
108
|
+
}, disabled, label);
|
|
109
|
+
};
|
|
110
|
+
const useTabsetDroppable = (tabsetId) => {
|
|
111
|
+
const context = useContext(DragContext);
|
|
112
|
+
return { ref: useCallback((element) => {
|
|
113
|
+
context?.registerTabset(tabsetId, element);
|
|
114
|
+
}, [context, tabsetId]) };
|
|
115
|
+
};
|
|
116
|
+
const useExternalTabSource = ({ createTab, disabled = false, label = "" }) => {
|
|
117
|
+
const id = useId();
|
|
118
|
+
const createTabRef = useRef(createTab);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
createTabRef.current = createTab;
|
|
121
|
+
});
|
|
122
|
+
return useDraggableEntity({
|
|
123
|
+
data: useMemo(() => ({
|
|
124
|
+
createTab: () => createTabRef.current(),
|
|
125
|
+
type: "external"
|
|
126
|
+
}), []),
|
|
127
|
+
id: `external-${id}`
|
|
128
|
+
}, disabled, label);
|
|
129
|
+
};
|
|
130
|
+
const useDragSubject = () => {
|
|
131
|
+
return useStore(useContext(DragSubjectStoreContext) ?? nullSubjectStore, (state) => state.subject);
|
|
132
|
+
};
|
|
133
|
+
const useDropIntent = () => {
|
|
134
|
+
return useStore(useContext(DragSubjectStoreContext) ?? nullSubjectStore, (state) => state.intent);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/hooks/persistence.ts
|
|
139
|
+
const memoryStorageAdapter = () => {
|
|
140
|
+
const store = /* @__PURE__ */ new Map();
|
|
141
|
+
return {
|
|
142
|
+
getItem: (key) => store.get(key) ?? null,
|
|
143
|
+
removeItem: (key) => {
|
|
144
|
+
store.delete(key);
|
|
145
|
+
},
|
|
146
|
+
setItem: (key, value) => {
|
|
147
|
+
store.set(key, value);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
const localStorageAdapter = {
|
|
152
|
+
getItem: (key) => {
|
|
153
|
+
if (typeof window === "undefined") return null;
|
|
154
|
+
try {
|
|
155
|
+
return window.localStorage.getItem(key);
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
removeItem: (key) => {
|
|
161
|
+
if (typeof window === "undefined") return;
|
|
162
|
+
try {
|
|
163
|
+
window.localStorage.removeItem(key);
|
|
164
|
+
} catch {}
|
|
165
|
+
},
|
|
166
|
+
setItem: (key, value) => {
|
|
167
|
+
if (typeof window === "undefined") return;
|
|
168
|
+
try {
|
|
169
|
+
window.localStorage.setItem(key, value);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.warn("[dashfoo] failed to persist layout", error);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const usePersistence = (config, defaultModel) => {
|
|
176
|
+
const configRef = useRef(config);
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
configRef.current = config;
|
|
179
|
+
});
|
|
180
|
+
const [initialModel] = useState(() => {
|
|
181
|
+
if (config === null || defaultModel === void 0) return defaultModel;
|
|
182
|
+
const raw = config.storage.getItem(config.key);
|
|
183
|
+
if (raw === null) return defaultModel;
|
|
184
|
+
try {
|
|
185
|
+
return fromJSON(raw);
|
|
186
|
+
} catch {
|
|
187
|
+
return defaultModel;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
const current = configRef.current;
|
|
192
|
+
if (current === null) return;
|
|
193
|
+
const raw = current.storage.getItem(current.key);
|
|
194
|
+
if (raw === null) return;
|
|
195
|
+
try {
|
|
196
|
+
fromJSON(raw);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.warn("[dashfoo] discarding unreadable persisted layout", error);
|
|
199
|
+
current.storage.removeItem(current.key);
|
|
200
|
+
}
|
|
201
|
+
}, []);
|
|
202
|
+
const loadedKey = useRef(config?.key);
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
if (config !== null && config.key !== loadedKey.current) {
|
|
205
|
+
console.warn("[dashfoo] persist key changed for a mounted layout; the displayed layout still reflects the previous key — remount (key={persistKey}) to load the new key");
|
|
206
|
+
loadedKey.current = config.key;
|
|
207
|
+
}
|
|
208
|
+
}, [config]);
|
|
209
|
+
const timer = useRef(null);
|
|
210
|
+
const pending = useRef(null);
|
|
211
|
+
const flush = useCallback(() => {
|
|
212
|
+
if (timer.current !== null) {
|
|
213
|
+
clearTimeout(timer.current);
|
|
214
|
+
timer.current = null;
|
|
215
|
+
}
|
|
216
|
+
if (pending.current !== null) {
|
|
217
|
+
pending.current.storage.setItem(pending.current.key, pending.current.value);
|
|
218
|
+
pending.current = null;
|
|
219
|
+
}
|
|
220
|
+
}, []);
|
|
221
|
+
const save = useCallback((next) => {
|
|
222
|
+
const current = configRef.current;
|
|
223
|
+
if (current === null) return;
|
|
224
|
+
pending.current = {
|
|
225
|
+
key: current.key,
|
|
226
|
+
storage: current.storage,
|
|
227
|
+
value: toJSON(next)
|
|
228
|
+
};
|
|
229
|
+
if (timer.current !== null) clearTimeout(timer.current);
|
|
230
|
+
timer.current = setTimeout(flush, current.debounceMs);
|
|
231
|
+
}, [flush]);
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
const handlePageHide = () => {
|
|
234
|
+
flush();
|
|
235
|
+
};
|
|
236
|
+
const handleVisibilityChange = () => {
|
|
237
|
+
if (document.visibilityState === "hidden") flush();
|
|
238
|
+
};
|
|
239
|
+
window.addEventListener("pagehide", handlePageHide);
|
|
240
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
241
|
+
return () => {
|
|
242
|
+
window.removeEventListener("pagehide", handlePageHide);
|
|
243
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
244
|
+
flush();
|
|
245
|
+
};
|
|
246
|
+
}, [flush]);
|
|
247
|
+
const clear = useCallback(() => {
|
|
248
|
+
if (timer.current !== null) {
|
|
249
|
+
clearTimeout(timer.current);
|
|
250
|
+
timer.current = null;
|
|
251
|
+
}
|
|
252
|
+
pending.current = null;
|
|
253
|
+
const current = configRef.current;
|
|
254
|
+
if (current !== null) current.storage.removeItem(current.key);
|
|
255
|
+
}, []);
|
|
256
|
+
return useMemo(() => ({
|
|
257
|
+
clear,
|
|
258
|
+
initialModel,
|
|
259
|
+
save
|
|
260
|
+
}), [
|
|
261
|
+
clear,
|
|
262
|
+
initialModel,
|
|
263
|
+
save
|
|
264
|
+
]);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region src/hooks/responsive.ts
|
|
269
|
+
const hasMatchMedia = () => typeof window !== "undefined" && typeof window.matchMedia === "function";
|
|
270
|
+
const matchBreakpoint = (breakpoint, width) => {
|
|
271
|
+
const { query } = breakpoint;
|
|
272
|
+
if (!query) return true;
|
|
273
|
+
if ("maxWidth" in query) return width <= query.maxWidth;
|
|
274
|
+
return hasMatchMedia() && window.matchMedia(query.media).matches;
|
|
275
|
+
};
|
|
276
|
+
const activeBreakpoint = (breakpoints, width) => {
|
|
277
|
+
const fallback = breakpoints.at(-1);
|
|
278
|
+
if (!fallback) throw new Error("useResponsiveModel requires at least one breakpoint.");
|
|
279
|
+
return breakpoints.find((breakpoint) => matchBreakpoint(breakpoint, width)) ?? fallback;
|
|
280
|
+
};
|
|
281
|
+
const useContainerWidth = () => {
|
|
282
|
+
const [width, setWidth] = useState(Number.POSITIVE_INFINITY);
|
|
283
|
+
const observerRef = useRef(null);
|
|
284
|
+
const containerRef = useCallback((element) => {
|
|
285
|
+
observerRef.current?.disconnect();
|
|
286
|
+
if (!element) return;
|
|
287
|
+
const observer = new ResizeObserver((entries) => {
|
|
288
|
+
const entry = entries[0];
|
|
289
|
+
if (entry) setWidth(entry.contentRect.width);
|
|
290
|
+
});
|
|
291
|
+
observer.observe(element);
|
|
292
|
+
observerRef.current = observer;
|
|
293
|
+
}, []);
|
|
294
|
+
useEffect(() => () => {
|
|
295
|
+
observerRef.current?.disconnect();
|
|
296
|
+
}, []);
|
|
297
|
+
return [containerRef, width];
|
|
298
|
+
};
|
|
299
|
+
const useResponsiveModel = ({ breakpoints }) => {
|
|
300
|
+
const [containerRef, width] = useContainerWidth();
|
|
301
|
+
const [, setMediaTick] = useState(0);
|
|
302
|
+
useEffect(() => {
|
|
303
|
+
if (!hasMatchMedia()) return;
|
|
304
|
+
const lists = breakpoints.flatMap((breakpoint) => breakpoint.query && "media" in breakpoint.query ? [window.matchMedia(breakpoint.query.media)] : []);
|
|
305
|
+
if (lists.length === 0) return;
|
|
306
|
+
const handleChange = () => {
|
|
307
|
+
setMediaTick((tick) => tick + 1);
|
|
308
|
+
};
|
|
309
|
+
for (const list of lists) list.addEventListener("change", handleChange);
|
|
310
|
+
return () => {
|
|
311
|
+
for (const list of lists) list.removeEventListener("change", handleChange);
|
|
312
|
+
};
|
|
313
|
+
}, [breakpoints]);
|
|
314
|
+
const active = activeBreakpoint(breakpoints, width);
|
|
315
|
+
const isCompact = active.compact === true;
|
|
316
|
+
return {
|
|
317
|
+
breakpoint: active.id,
|
|
318
|
+
containerRef,
|
|
319
|
+
draggableTabs: !isCompact,
|
|
320
|
+
draggableTabsets: !isCompact,
|
|
321
|
+
isCompact,
|
|
322
|
+
model: active.model,
|
|
323
|
+
resizableSplits: !isCompact
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
//#endregion
|
|
328
|
+
//#region src/hooks/store.ts
|
|
329
|
+
const useDashfooStore = (options) => {
|
|
330
|
+
const { defaultModel, model: controlledModel, onAction, onActiveTabsetChange, onMaximizedTabsetChange, onModelChange } = options;
|
|
331
|
+
const initialModel = controlledModel ?? defaultModel;
|
|
332
|
+
if (initialModel === void 0) throw new Error("useDashfooStore requires either a `model` or a `defaultModel`.");
|
|
333
|
+
const actorRef = useActorRef(dashfooMachine, { input: { model: normalize(initialModel) } });
|
|
334
|
+
const history = useSelector(actorRef, (snapshot) => snapshot.context.history);
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
if (controlledModel !== void 0) actorRef.send({
|
|
337
|
+
model: normalize(controlledModel),
|
|
338
|
+
type: "SET_MODEL"
|
|
339
|
+
});
|
|
340
|
+
}, [actorRef, controlledModel]);
|
|
341
|
+
const model = controlledModel ?? history.present;
|
|
342
|
+
const notify = useCallback((before, after, action) => {
|
|
343
|
+
if (Object.is(before, after)) return;
|
|
344
|
+
onModelChange?.(after, action);
|
|
345
|
+
if (after.activeTabsetId !== before.activeTabsetId) onActiveTabsetChange?.(after.activeTabsetId);
|
|
346
|
+
if (after.maximizedTabsetId !== before.maximizedTabsetId) onMaximizedTabsetChange?.(after.maximizedTabsetId);
|
|
347
|
+
}, [
|
|
348
|
+
onModelChange,
|
|
349
|
+
onActiveTabsetChange,
|
|
350
|
+
onMaximizedTabsetChange
|
|
351
|
+
]);
|
|
352
|
+
const dispatch = useCallback((action) => {
|
|
353
|
+
const resolved = onAction ? onAction(action) : action;
|
|
354
|
+
if (!resolved) return;
|
|
355
|
+
const transition = (() => {
|
|
356
|
+
if (controlledModel === void 0) {
|
|
357
|
+
const before = actorRef.getSnapshot().context.history.present;
|
|
358
|
+
actorRef.send({
|
|
359
|
+
action: resolved,
|
|
360
|
+
type: "DISPATCH"
|
|
361
|
+
});
|
|
362
|
+
return {
|
|
363
|
+
after: actorRef.getSnapshot().context.history.present,
|
|
364
|
+
before
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
after: reducer(controlledModel, resolved),
|
|
369
|
+
before: normalize(controlledModel)
|
|
370
|
+
};
|
|
371
|
+
})();
|
|
372
|
+
notify(transition.before, transition.after, resolved);
|
|
373
|
+
}, [
|
|
374
|
+
actorRef,
|
|
375
|
+
controlledModel,
|
|
376
|
+
notify,
|
|
377
|
+
onAction
|
|
378
|
+
]);
|
|
379
|
+
const undo = useCallback(() => {
|
|
380
|
+
if (controlledModel !== void 0) return;
|
|
381
|
+
const before = actorRef.getSnapshot().context.history.present;
|
|
382
|
+
actorRef.send({ type: "UNDO" });
|
|
383
|
+
const after = actorRef.getSnapshot().context.history.present;
|
|
384
|
+
notify(before, after);
|
|
385
|
+
}, [
|
|
386
|
+
actorRef,
|
|
387
|
+
controlledModel,
|
|
388
|
+
notify
|
|
389
|
+
]);
|
|
390
|
+
const redo = useCallback(() => {
|
|
391
|
+
if (controlledModel !== void 0) return;
|
|
392
|
+
const before = actorRef.getSnapshot().context.history.present;
|
|
393
|
+
actorRef.send({ type: "REDO" });
|
|
394
|
+
const after = actorRef.getSnapshot().context.history.present;
|
|
395
|
+
notify(before, after);
|
|
396
|
+
}, [
|
|
397
|
+
actorRef,
|
|
398
|
+
controlledModel,
|
|
399
|
+
notify
|
|
400
|
+
]);
|
|
401
|
+
const setModel = useCallback((next) => {
|
|
402
|
+
actorRef.send({
|
|
403
|
+
model: normalize(next),
|
|
404
|
+
type: "SET_MODEL"
|
|
405
|
+
});
|
|
406
|
+
}, [actorRef]);
|
|
407
|
+
return useMemo(() => ({
|
|
408
|
+
canRedo: () => canRedo(actorRef.getSnapshot().context.history),
|
|
409
|
+
canUndo: () => canUndo(actorRef.getSnapshot().context.history),
|
|
410
|
+
dispatch,
|
|
411
|
+
model,
|
|
412
|
+
redo,
|
|
413
|
+
setModel,
|
|
414
|
+
undo
|
|
415
|
+
}), [
|
|
416
|
+
actorRef,
|
|
417
|
+
dispatch,
|
|
418
|
+
model,
|
|
419
|
+
redo,
|
|
420
|
+
setModel,
|
|
421
|
+
undo
|
|
422
|
+
]);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region src/components/drag-root.tsx
|
|
427
|
+
const DashfooDragProvider = ({ children }) => {
|
|
428
|
+
const [manager] = useState(createDragManager);
|
|
429
|
+
useInsertionEffect(() => () => manager.destroy(), [manager]);
|
|
430
|
+
const [subjectStore] = useState(createDragSubjectStore);
|
|
431
|
+
return /* @__PURE__ */ jsx(SharedDragManagerContext.Provider, {
|
|
432
|
+
value: manager,
|
|
433
|
+
children: /* @__PURE__ */ jsx(DragSubjectStoreContext.Provider, {
|
|
434
|
+
value: subjectStore,
|
|
435
|
+
children
|
|
436
|
+
})
|
|
437
|
+
});
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
//#endregion
|
|
441
|
+
//#region src/hooks/layout-store.ts
|
|
442
|
+
const createLayoutStore = (initial) => createStore(() => initial);
|
|
443
|
+
const LayoutStoreContext = createContext(null);
|
|
444
|
+
const useLayout = (selector) => {
|
|
445
|
+
const store = useContext(LayoutStoreContext);
|
|
446
|
+
if (store === null) throw new Error("dashfoo components must be rendered inside <DashfooLayout> or <Layout.Root>.");
|
|
447
|
+
return useStore(store, selector);
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/lib/tab-insertion.ts
|
|
452
|
+
const pointInRect = (point, rect) => point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
|
|
453
|
+
const insertionIndex = (rects, pointerX) => {
|
|
454
|
+
const found = rects.findIndex((rect) => pointerX < rect.x + rect.width / 2);
|
|
455
|
+
return found === -1 ? rects.length : found;
|
|
456
|
+
};
|
|
457
|
+
const INSERTION_LINE_WIDTH = 4;
|
|
458
|
+
const insertionLineRect = (stripRect, itemRects, index) => {
|
|
459
|
+
const at = itemRects[index];
|
|
460
|
+
const last = itemRects.at(-1);
|
|
461
|
+
const left = (at?.x ?? (last ? last.x + last.width : stripRect.x)) - INSERTION_LINE_WIDTH / 2;
|
|
462
|
+
return {
|
|
463
|
+
height: stripRect.height,
|
|
464
|
+
width: INSERTION_LINE_WIDTH,
|
|
465
|
+
x: Math.max(stripRect.x, Math.min(left, stripRect.x + stripRect.width - INSERTION_LINE_WIDTH)),
|
|
466
|
+
y: stripRect.y
|
|
467
|
+
};
|
|
468
|
+
};
|
|
469
|
+
const shouldAllowDrop = (draggedId, targetId, tabIds) => {
|
|
470
|
+
if (draggedId === `grip-${targetId}`) return false;
|
|
471
|
+
if (tabIds.length === 1 && tabIds[0] === draggedId) return false;
|
|
472
|
+
return true;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
//#endregion
|
|
476
|
+
//#region src/lib/warn-once.ts
|
|
477
|
+
const warned = /* @__PURE__ */ new Set();
|
|
478
|
+
const warnOnce = (key, message) => {
|
|
479
|
+
if (warned.has(key)) return;
|
|
480
|
+
warned.add(key);
|
|
481
|
+
console.warn(`[dashfoo] ${message}`);
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region src/components/drag-overlays.tsx
|
|
486
|
+
const overlayBase = {
|
|
487
|
+
boxSizing: "border-box",
|
|
488
|
+
pointerEvents: "none",
|
|
489
|
+
position: "fixed",
|
|
490
|
+
transition: "var(--dashfoo-dock-transition, left 60ms, top 60ms, width 60ms, height 60ms)",
|
|
491
|
+
zIndex: 9999
|
|
492
|
+
};
|
|
493
|
+
const paneStyle = (zone) => ({
|
|
494
|
+
...overlayBase,
|
|
495
|
+
background: "var(--dashfoo-dock-fill, oklch(0.556 0 0 / 0.18))",
|
|
496
|
+
border: "var(--dashfoo-dock-border-width, 1px) solid var(--dashfoo-dock-border, oklch(0.708 0 0 / 0.75))",
|
|
497
|
+
borderRadius: "var(--dashfoo-dock-radius, 6px)",
|
|
498
|
+
height: zone.height,
|
|
499
|
+
left: zone.x,
|
|
500
|
+
top: zone.y,
|
|
501
|
+
width: zone.width
|
|
502
|
+
});
|
|
503
|
+
const lineStyle = (zone) => ({
|
|
504
|
+
...paneStyle(zone),
|
|
505
|
+
borderRadius: "var(--dashfoo-dock-line-radius, 2px)"
|
|
506
|
+
});
|
|
507
|
+
const tabItemRects = (strip, excludeId) => [...strip.querySelectorAll("[data-dashfoo=\"tab-item\"]")].flatMap((item) => item.querySelector("[data-dashfoo=\"tab\"]")?.dataset.tabId === excludeId ? [] : [item.getBoundingClientRect()]);
|
|
508
|
+
const DockIndicator = ({ actorRef, getTabsetElement }) => {
|
|
509
|
+
const intent = useSelector(actorRef, (snapshot) => snapshot.context.intent);
|
|
510
|
+
const draggedId = useSelector(actorRef, (snapshot) => snapshot.context.subject?.id);
|
|
511
|
+
if (!intent) return null;
|
|
512
|
+
const element = getTabsetElement(intent.targetId);
|
|
513
|
+
if (!element) return null;
|
|
514
|
+
if (intent.location === "center") {
|
|
515
|
+
const strip = element.querySelector("[data-dashfoo=\"tabstrip\"]");
|
|
516
|
+
if (strip) return /* @__PURE__ */ jsx("div", {
|
|
517
|
+
"data-dashfoo": "dock-indicator",
|
|
518
|
+
style: lineStyle(insertionLineRect(strip.getBoundingClientRect(), tabItemRects(strip, draggedId), intent.index ?? 0))
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
return /* @__PURE__ */ jsx("div", {
|
|
522
|
+
"data-dashfoo": "dock-indicator",
|
|
523
|
+
style: paneStyle(zoneRect(element.getBoundingClientRect(), intent.location))
|
|
524
|
+
});
|
|
525
|
+
};
|
|
526
|
+
const previewStyle = {
|
|
527
|
+
left: 0,
|
|
528
|
+
position: "fixed",
|
|
529
|
+
top: 0,
|
|
530
|
+
zIndex: 9999
|
|
531
|
+
};
|
|
532
|
+
const DragPreview = ({ overlayRef, preview }) => {
|
|
533
|
+
if (!preview) return null;
|
|
534
|
+
return /* @__PURE__ */ jsx("div", {
|
|
535
|
+
"data-dashfoo": "drag-preview",
|
|
536
|
+
ref: overlayRef,
|
|
537
|
+
style: {
|
|
538
|
+
...previewStyle,
|
|
539
|
+
transform: `translate(${preview.x}px, ${preview.y}px)`
|
|
540
|
+
},
|
|
541
|
+
children: preview.label
|
|
542
|
+
});
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
//#endregion
|
|
546
|
+
//#region src/components/drag-adapter.tsx
|
|
547
|
+
const tabRects = (strip, excludeId) => [...strip.querySelectorAll("[data-dashfoo=\"tab\"]")].flatMap((tab) => tab.dataset.tabId === excludeId ? [] : [tab.getBoundingClientRect()]);
|
|
548
|
+
const intentForTabset = (id, element, point, draggedId) => {
|
|
549
|
+
const strip = element.querySelector("[data-dashfoo=\"tabstrip\"]");
|
|
550
|
+
if (strip && pointInRect(point, strip.getBoundingClientRect())) return {
|
|
551
|
+
index: insertionIndex(tabRects(strip, draggedId), point.x),
|
|
552
|
+
location: "center",
|
|
553
|
+
targetId: id
|
|
554
|
+
};
|
|
555
|
+
const target = resolveDockTarget(point, element.getBoundingClientRect());
|
|
556
|
+
const location = target.kind === "tab" ? "center" : `split-${target.edge}`;
|
|
557
|
+
if (location === "center" && strip) return {
|
|
558
|
+
index: tabRects(strip, draggedId).length,
|
|
559
|
+
location,
|
|
560
|
+
targetId: id
|
|
561
|
+
};
|
|
562
|
+
return {
|
|
563
|
+
location,
|
|
564
|
+
targetId: id
|
|
565
|
+
};
|
|
566
|
+
};
|
|
567
|
+
const PREVIEW_OFFSET = {
|
|
568
|
+
x: 12,
|
|
569
|
+
y: 8
|
|
570
|
+
};
|
|
571
|
+
const labelOf = (source) => {
|
|
572
|
+
const raw = source?.data?.label;
|
|
573
|
+
return typeof raw === "string" ? raw : "";
|
|
574
|
+
};
|
|
575
|
+
const isTabFactory = (value) => typeof value === "function";
|
|
576
|
+
const subjectFor = (source) => {
|
|
577
|
+
const data = source.data;
|
|
578
|
+
if (data?.type === "tabset") return {
|
|
579
|
+
id: String(data.tabsetId),
|
|
580
|
+
kind: "tabset"
|
|
581
|
+
};
|
|
582
|
+
if (data?.type === "external") {
|
|
583
|
+
if (!isTabFactory(data.createTab)) {
|
|
584
|
+
console.warn("[dashfoo] external drag source is missing its createTab function");
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
let candidate;
|
|
588
|
+
try {
|
|
589
|
+
candidate = data.createTab();
|
|
590
|
+
} catch (error) {
|
|
591
|
+
console.warn("[dashfoo] external drag source createTab threw", error);
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
const parsed = tabNodeSchema.safeParse(candidate);
|
|
595
|
+
if (!parsed.success) {
|
|
596
|
+
console.warn("[dashfoo] external drag source returned an invalid tab", parsed.error);
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
id: String(source.id),
|
|
601
|
+
kind: "external",
|
|
602
|
+
tab: parsed.data
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
id: String(source.id),
|
|
607
|
+
kind: "tab"
|
|
608
|
+
};
|
|
609
|
+
};
|
|
610
|
+
const DragProvider = ({ children, onCommit, splitDock }) => {
|
|
611
|
+
const actorRef = useActorRef(dragDockMachine);
|
|
612
|
+
const layoutStore = useContext(LayoutStoreContext);
|
|
613
|
+
const [tabsets] = useState(() => /* @__PURE__ */ new Map());
|
|
614
|
+
const overlayRef = useRef(null);
|
|
615
|
+
const [preview, setPreview] = useState(null);
|
|
616
|
+
const sharedManager = useContext(SharedDragManagerContext);
|
|
617
|
+
const [ownManager] = useState(() => sharedManager ? null : createDragManager());
|
|
618
|
+
useInsertionEffect(() => () => ownManager?.destroy(), [ownManager]);
|
|
619
|
+
const manager = sharedManager ?? ownManager;
|
|
620
|
+
if (manager === null) throw new Error("[dashfoo] DashfooDragProvider unmounted while its layout is still mounted; keep the provider above the layout or remount the layout");
|
|
621
|
+
const inheritedStore = useContext(DragSubjectStoreContext);
|
|
622
|
+
const [subjectStore] = useState(() => inheritedStore ?? createDragSubjectStore());
|
|
623
|
+
const ownerId = useId();
|
|
624
|
+
useEffect(() => {
|
|
625
|
+
const subscription = actorRef.subscribe((snapshot) => {
|
|
626
|
+
const { intent, subject } = snapshot.context;
|
|
627
|
+
const state = subjectStore.getState();
|
|
628
|
+
if (intent !== null) {
|
|
629
|
+
if (!sameDropIntent(state.intent, intent) || state.intentOwner !== ownerId) subjectStore.setState({
|
|
630
|
+
intent,
|
|
631
|
+
intentOwner: ownerId
|
|
632
|
+
});
|
|
633
|
+
} else if (state.intent !== null && state.intentOwner === ownerId) subjectStore.setState({
|
|
634
|
+
intent: null,
|
|
635
|
+
intentOwner: null
|
|
636
|
+
});
|
|
637
|
+
if (!sameDragSubject(subjectStore.getState().subject, subject)) subjectStore.setState({ subject });
|
|
638
|
+
});
|
|
639
|
+
return () => {
|
|
640
|
+
subscription.unsubscribe();
|
|
641
|
+
};
|
|
642
|
+
}, [
|
|
643
|
+
actorRef,
|
|
644
|
+
ownerId,
|
|
645
|
+
subjectStore
|
|
646
|
+
]);
|
|
647
|
+
const onCommitRef = useRef(onCommit);
|
|
648
|
+
const splitDockRef = useRef(splitDock);
|
|
649
|
+
useEffect(() => {
|
|
650
|
+
onCommitRef.current = onCommit;
|
|
651
|
+
splitDockRef.current = splitDock;
|
|
652
|
+
});
|
|
653
|
+
useEffect(() => {
|
|
654
|
+
const subscription = actorRef.on("COMMIT", (emitted) => {
|
|
655
|
+
if (layoutStore?.getState().editable === false) return;
|
|
656
|
+
const commit = onCommitRef.current ?? layoutStore?.getState().dispatch;
|
|
657
|
+
if (!commit) {
|
|
658
|
+
warnOnce("drag-layer-no-commit", "Layout.DragLayer has neither an onCommit prop nor a <Layout.Root> above it; drops are ignored");
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
commit(emitted.action);
|
|
662
|
+
});
|
|
663
|
+
return () => {
|
|
664
|
+
subscription.unsubscribe();
|
|
665
|
+
};
|
|
666
|
+
}, [actorRef, layoutStore]);
|
|
667
|
+
const registerTabset = useCallback((id, element) => {
|
|
668
|
+
if (element) tabsets.set(id, element);
|
|
669
|
+
else tabsets.delete(id);
|
|
670
|
+
}, [tabsets]);
|
|
671
|
+
const getTabsetElement = useCallback((id) => tabsets.get(id), [tabsets]);
|
|
672
|
+
const attachOverlay = useCallback((element) => {
|
|
673
|
+
overlayRef.current = element;
|
|
674
|
+
}, []);
|
|
675
|
+
useEffect(() => {
|
|
676
|
+
const tabsetAt = (point) => {
|
|
677
|
+
const top = document.elementFromPoint(point.x, point.y);
|
|
678
|
+
if (!top) return;
|
|
679
|
+
for (const [id, element] of tabsets) if (element.contains(top)) return {
|
|
680
|
+
element,
|
|
681
|
+
id
|
|
682
|
+
};
|
|
683
|
+
};
|
|
684
|
+
const resolveIntent = (targetId, element, point, draggedId) => {
|
|
685
|
+
if (layoutStore?.getState().editable === false) return null;
|
|
686
|
+
if (!shouldAllowDrop(draggedId, targetId, [...element.querySelectorAll("[data-dashfoo=\"tab\"]")].map((tab) => tab.dataset.tabId ?? ""))) return null;
|
|
687
|
+
const intent = intentForTabset(targetId, element, point, draggedId);
|
|
688
|
+
if (!(splitDockRef.current ?? layoutStore?.getState().splitDock ?? true) && intent.location.startsWith("split-")) return {
|
|
689
|
+
location: "center",
|
|
690
|
+
targetId
|
|
691
|
+
};
|
|
692
|
+
return intent;
|
|
693
|
+
};
|
|
694
|
+
const positionOverlay = (point) => {
|
|
695
|
+
const element = overlayRef.current;
|
|
696
|
+
if (element) element.style.transform = `translate(${point.x + PREVIEW_OFFSET.x}px, ${point.y + PREVIEW_OFFSET.y}px)`;
|
|
697
|
+
};
|
|
698
|
+
const handleStart = (event) => {
|
|
699
|
+
const source = event.operation.source;
|
|
700
|
+
if (!source) return;
|
|
701
|
+
const subject = subjectFor(source);
|
|
702
|
+
if (!subject) return;
|
|
703
|
+
const point = event.operation.position.current;
|
|
704
|
+
setPreview({
|
|
705
|
+
label: labelOf(source),
|
|
706
|
+
x: point.x + PREVIEW_OFFSET.x,
|
|
707
|
+
y: point.y + PREVIEW_OFFSET.y
|
|
708
|
+
});
|
|
709
|
+
actorRef.send({
|
|
710
|
+
subject,
|
|
711
|
+
type: "START"
|
|
712
|
+
});
|
|
713
|
+
};
|
|
714
|
+
const handleMove = (event) => {
|
|
715
|
+
const op = event.operation;
|
|
716
|
+
const point = op.position.current;
|
|
717
|
+
positionOverlay(point);
|
|
718
|
+
const draggedId = op.source ? String(op.source.id) : void 0;
|
|
719
|
+
const hit = tabsetAt(point);
|
|
720
|
+
actorRef.send({
|
|
721
|
+
intent: hit ? resolveIntent(hit.id, hit.element, point, draggedId) : null,
|
|
722
|
+
type: "OVER"
|
|
723
|
+
});
|
|
724
|
+
};
|
|
725
|
+
const handleEnd = (event) => {
|
|
726
|
+
setPreview(null);
|
|
727
|
+
if (event.canceled) {
|
|
728
|
+
actorRef.send({ type: "CANCEL" });
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const op = event.operation;
|
|
732
|
+
const point = op.position.current;
|
|
733
|
+
const draggedId = op.source ? String(op.source.id) : void 0;
|
|
734
|
+
const hit = tabsetAt(point);
|
|
735
|
+
if (hit) actorRef.send({
|
|
736
|
+
intent: resolveIntent(hit.id, hit.element, point, draggedId),
|
|
737
|
+
type: "OVER"
|
|
738
|
+
});
|
|
739
|
+
actorRef.send({ type: "DROP" });
|
|
740
|
+
};
|
|
741
|
+
const offStart = manager.monitor.addEventListener("dragstart", handleStart);
|
|
742
|
+
const offMove = manager.monitor.addEventListener("dragmove", handleMove);
|
|
743
|
+
const offEnd = manager.monitor.addEventListener("dragend", handleEnd);
|
|
744
|
+
return () => {
|
|
745
|
+
offStart();
|
|
746
|
+
offMove();
|
|
747
|
+
offEnd();
|
|
748
|
+
};
|
|
749
|
+
}, [
|
|
750
|
+
actorRef,
|
|
751
|
+
layoutStore,
|
|
752
|
+
manager,
|
|
753
|
+
tabsets
|
|
754
|
+
]);
|
|
755
|
+
const contextValue = useMemo(() => ({
|
|
756
|
+
manager,
|
|
757
|
+
registerTabset
|
|
758
|
+
}), [manager, registerTabset]);
|
|
759
|
+
return /* @__PURE__ */ jsx(DragContext.Provider, {
|
|
760
|
+
value: contextValue,
|
|
761
|
+
children: /* @__PURE__ */ jsxs(DragSubjectStoreContext.Provider, {
|
|
762
|
+
value: subjectStore,
|
|
763
|
+
children: [
|
|
764
|
+
children,
|
|
765
|
+
/* @__PURE__ */ jsx(DockIndicator, {
|
|
766
|
+
actorRef,
|
|
767
|
+
getTabsetElement
|
|
768
|
+
}),
|
|
769
|
+
/* @__PURE__ */ jsx(DragPreview, {
|
|
770
|
+
overlayRef: attachOverlay,
|
|
771
|
+
preview
|
|
772
|
+
})
|
|
773
|
+
]
|
|
774
|
+
})
|
|
775
|
+
});
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
//#endregion
|
|
779
|
+
//#region src/components/float-context.ts
|
|
780
|
+
const FloatLayerContext = createContext(false);
|
|
781
|
+
const useHasFloatLayer = () => useContext(FloatLayerContext);
|
|
782
|
+
|
|
783
|
+
//#endregion
|
|
784
|
+
//#region src/lib/float-geometry.ts
|
|
785
|
+
const MIN_WIDTH = 200;
|
|
786
|
+
const MIN_HEIGHT = 120;
|
|
787
|
+
const DEFAULT_WIDTH = 480;
|
|
788
|
+
const DEFAULT_HEIGHT = 340;
|
|
789
|
+
const LIFT_OFFSET = 16;
|
|
790
|
+
const clampToBounds = (rect, bounds, size = rect) => ({
|
|
791
|
+
height: rect.height,
|
|
792
|
+
left: Math.max(0, Math.min(rect.left, Math.max(0, bounds.width - size.width))),
|
|
793
|
+
top: Math.max(0, Math.min(rect.top, Math.max(0, bounds.height - size.height))),
|
|
794
|
+
width: rect.width
|
|
795
|
+
});
|
|
796
|
+
const measureFloatRect = (element) => {
|
|
797
|
+
const rect = (element.closest("[data-dashfoo=\"tabset\"]") ?? element).getBoundingClientRect();
|
|
798
|
+
return clampToBounds({
|
|
799
|
+
height: Math.max(MIN_HEIGHT, Math.min(Math.round(rect.height), DEFAULT_HEIGHT)),
|
|
800
|
+
left: Math.round(rect.left) + LIFT_OFFSET,
|
|
801
|
+
top: Math.round(rect.top) + LIFT_OFFSET,
|
|
802
|
+
width: Math.max(MIN_WIDTH, Math.min(Math.round(rect.width), DEFAULT_WIDTH))
|
|
803
|
+
}, {
|
|
804
|
+
height: window.innerHeight,
|
|
805
|
+
width: window.innerWidth
|
|
806
|
+
});
|
|
807
|
+
};
|
|
808
|
+
const resizeRect = (start, edges, dx, dy) => {
|
|
809
|
+
let { height, left, top, width } = start;
|
|
810
|
+
if (edges.x === 1) width = Math.max(MIN_WIDTH, start.width + dx);
|
|
811
|
+
else if (edges.x === -1) {
|
|
812
|
+
width = Math.max(MIN_WIDTH, start.width - dx);
|
|
813
|
+
left = Math.max(0, start.left + (start.width - width));
|
|
814
|
+
}
|
|
815
|
+
if (edges.y === 1) height = Math.max(MIN_HEIGHT, start.height + dy);
|
|
816
|
+
else if (edges.y === -1) {
|
|
817
|
+
height = Math.max(MIN_HEIGHT, start.height - dy);
|
|
818
|
+
top = Math.max(0, start.top + (start.height - height));
|
|
819
|
+
}
|
|
820
|
+
return {
|
|
821
|
+
height,
|
|
822
|
+
left,
|
|
823
|
+
top,
|
|
824
|
+
width
|
|
825
|
+
};
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
//#endregion
|
|
829
|
+
//#region src/components/layout-root.tsx
|
|
830
|
+
const DEFAULT_TABSET_MIN_SIZE = 320;
|
|
831
|
+
const rootStyle = {
|
|
832
|
+
display: "flex",
|
|
833
|
+
height: "100%",
|
|
834
|
+
position: "relative",
|
|
835
|
+
width: "100%"
|
|
836
|
+
};
|
|
837
|
+
const LayoutRoot = ({ children, closableTabs = true, dispatch, draggableTabs = true, draggableTabsets = true, editable = true, floatable = false, keepMounted = false, maximizable = true, model, renamableTabs = true, renderTab, renderTabLabel, renderTabsetToolbar, resizableSplits = true, rootRef, snap, style, ...props }) => {
|
|
838
|
+
const global = model.global;
|
|
839
|
+
const snapshot = {
|
|
840
|
+
closableTabs: editable && closableTabs && global.tabEnableClose !== false,
|
|
841
|
+
dispatch,
|
|
842
|
+
draggableTabs: editable && draggableTabs && global.tabEnableDrag !== false,
|
|
843
|
+
draggableTabsets: editable && draggableTabsets,
|
|
844
|
+
editable,
|
|
845
|
+
floatable: editable && floatable,
|
|
846
|
+
keepMounted,
|
|
847
|
+
maximizable: maximizable && global.tabSetEnableMaximize !== false,
|
|
848
|
+
maximizedTabsetId: model.maximizedTabsetId,
|
|
849
|
+
renamableTabs: editable && renamableTabs && global.tabEnableRename !== false,
|
|
850
|
+
renderTab,
|
|
851
|
+
renderTabLabel,
|
|
852
|
+
renderTabsetToolbar,
|
|
853
|
+
resizableSplits: editable && resizableSplits && global.enableSplitResize !== false,
|
|
854
|
+
snap: snap ?? global.snap ?? null,
|
|
855
|
+
splitDock: global.enableSplitDock !== false,
|
|
856
|
+
tabLocation: global.tabLocation ?? "top",
|
|
857
|
+
tabsetMinSize: global.tabSetMinSize ?? DEFAULT_TABSET_MIN_SIZE,
|
|
858
|
+
tabStripEnabled: global.tabSetEnableTabStrip !== false
|
|
859
|
+
};
|
|
860
|
+
const [store] = useState(() => createLayoutStore(snapshot));
|
|
861
|
+
useLayoutEffect(() => {
|
|
862
|
+
store.setState(snapshot);
|
|
863
|
+
});
|
|
864
|
+
const layoutStyle = global.splitterSize === void 0 ? {
|
|
865
|
+
...rootStyle,
|
|
866
|
+
...style
|
|
867
|
+
} : {
|
|
868
|
+
...rootStyle,
|
|
869
|
+
"--dashfoo-splitter-size": `${global.splitterSize}px`,
|
|
870
|
+
...style
|
|
871
|
+
};
|
|
872
|
+
return /* @__PURE__ */ jsx(LayoutStoreContext.Provider, {
|
|
873
|
+
value: store,
|
|
874
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
875
|
+
...props,
|
|
876
|
+
"data-dashfoo": "layout",
|
|
877
|
+
ref: rootRef,
|
|
878
|
+
style: layoutStyle,
|
|
879
|
+
children
|
|
880
|
+
})
|
|
881
|
+
});
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
//#endregion
|
|
885
|
+
//#region src/lib/tab-ids.ts
|
|
886
|
+
const tabDomId = (tabsetId, tabId) => `dashfoo-tab-${tabsetId}-${tabId}`;
|
|
887
|
+
const panelDomId = (tabsetId) => `dashfoo-panel-${tabsetId}`;
|
|
888
|
+
|
|
889
|
+
//#endregion
|
|
890
|
+
//#region src/components/tabset/tabset-store.ts
|
|
891
|
+
const tabButtonIn = (scope, tabsetId, tabId) => scope?.querySelector(`#${CSS.escape(tabDomId(tabsetId, tabId))}`) ?? null;
|
|
892
|
+
const createTabsetStore = (initial) => createStore((set, get) => ({
|
|
893
|
+
...initial,
|
|
894
|
+
cancelRename: () => set({ editingTabId: null }),
|
|
895
|
+
closeTab: (tabId) => {
|
|
896
|
+
set({ restoreFocus: true });
|
|
897
|
+
get().dispatch({
|
|
898
|
+
tabId,
|
|
899
|
+
type: "deleteTab"
|
|
900
|
+
});
|
|
901
|
+
},
|
|
902
|
+
commitRename: (tabId, value) => {
|
|
903
|
+
set({ editingTabId: null });
|
|
904
|
+
const { node, renameTab } = get();
|
|
905
|
+
const trimmed = value.trim();
|
|
906
|
+
const current = node.children.find((tab) => tab.id === tabId)?.name;
|
|
907
|
+
if (trimmed && trimmed !== current) renameTab(tabId, trimmed);
|
|
908
|
+
},
|
|
909
|
+
editingTabId: null,
|
|
910
|
+
overflowItems: [],
|
|
911
|
+
registerRenameInput: () => {
|
|
912
|
+
set((state) => ({ renameInputCount: state.renameInputCount + 1 }));
|
|
913
|
+
return () => set((state) => ({ renameInputCount: state.renameInputCount - 1 }));
|
|
914
|
+
},
|
|
915
|
+
registerTablist: (element) => {
|
|
916
|
+
if (element) {
|
|
917
|
+
const current = get().tablistElement;
|
|
918
|
+
if (current && current !== element) warnOnce(`duplicate-tablist-${get().node.id}`, "more than one <Tabset.Tablist> under the same <Tabset.Root>; the last one wins for keyboard focus and overflow");
|
|
919
|
+
set({ tablistElement: element });
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
set({ tablistElement: null });
|
|
923
|
+
},
|
|
924
|
+
renameInputCount: 0,
|
|
925
|
+
renameTab: (tabId, name) => get().dispatch({
|
|
926
|
+
name,
|
|
927
|
+
tabId,
|
|
928
|
+
type: "renameTab"
|
|
929
|
+
}),
|
|
930
|
+
restoreFocus: false,
|
|
931
|
+
selectOverflowTab: (tabId) => {
|
|
932
|
+
const { dispatch, node, tablistElement } = get();
|
|
933
|
+
const index = node.children.findIndex((tab) => tab.id === tabId);
|
|
934
|
+
if (index === -1) return;
|
|
935
|
+
dispatch({
|
|
936
|
+
index,
|
|
937
|
+
tabsetId: node.id,
|
|
938
|
+
type: "selectTab"
|
|
939
|
+
});
|
|
940
|
+
tabButtonIn(tablistElement, node.id, tabId)?.scrollIntoView({
|
|
941
|
+
block: "nearest",
|
|
942
|
+
inline: "nearest"
|
|
943
|
+
});
|
|
944
|
+
},
|
|
945
|
+
selectTab: (index, options) => {
|
|
946
|
+
const { dispatch, node, tablistElement } = get();
|
|
947
|
+
dispatch({
|
|
948
|
+
index,
|
|
949
|
+
tabsetId: node.id,
|
|
950
|
+
type: "selectTab"
|
|
951
|
+
});
|
|
952
|
+
if (options?.focus) {
|
|
953
|
+
const tabId = node.children[index]?.id;
|
|
954
|
+
if (tabId) tabButtonIn(tablistElement, node.id, tabId)?.focus();
|
|
955
|
+
}
|
|
956
|
+
},
|
|
957
|
+
startEditing: (tabId) => {
|
|
958
|
+
const { node, renameInputCount, tabsRenamable } = get();
|
|
959
|
+
const tab = node.children.find((child) => child.id === tabId);
|
|
960
|
+
if (!tabsRenamable || tab?.enableRename === false) return;
|
|
961
|
+
if (renameInputCount === 0) {
|
|
962
|
+
warnOnce(`missing-rename-input-${node.id}`, "startEditing needs a <Tabset.RenameInput> in the composition; ignoring");
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
set({ editingTabId: tabId });
|
|
966
|
+
},
|
|
967
|
+
tablistElement: null,
|
|
968
|
+
toggleMaximize: () => {
|
|
969
|
+
const { dispatch, isMaximized, node } = get();
|
|
970
|
+
dispatch({
|
|
971
|
+
tabsetId: isMaximized ? null : node.id,
|
|
972
|
+
type: "setMaximizedTabset"
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
}));
|
|
976
|
+
const TabsetStoreContext = createContext(null);
|
|
977
|
+
const useTabset = (selector) => {
|
|
978
|
+
const store = useContext(TabsetStoreContext);
|
|
979
|
+
if (store === null) throw new Error("dashfoo Tabset parts must be rendered inside <Tabset.Root>.");
|
|
980
|
+
return useStore(store, selector);
|
|
981
|
+
};
|
|
982
|
+
const TabContext = createContext(null);
|
|
983
|
+
const useTab = () => {
|
|
984
|
+
const value = useContext(TabContext);
|
|
985
|
+
if (value === null) throw new Error("dashfoo tab parts must be rendered inside <Tabset.Tab>.");
|
|
986
|
+
return value;
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
//#endregion
|
|
990
|
+
//#region src/components/tabset/tabset-content.tsx
|
|
991
|
+
const contentStyle = {
|
|
992
|
+
flex: 1,
|
|
993
|
+
minHeight: 0,
|
|
994
|
+
overflow: "auto"
|
|
995
|
+
};
|
|
996
|
+
const TabPanel = ({ render, tab }) => {
|
|
997
|
+
const renderTab = useLayout((state) => state.renderTab);
|
|
998
|
+
return render ? render(tab) : renderTab(tab);
|
|
999
|
+
};
|
|
1000
|
+
const TabsetContent = ({ children, style, ...props }) => {
|
|
1001
|
+
const node = useTabset((state) => state.node);
|
|
1002
|
+
const visualSelected = useTabset((state) => state.visualSelected);
|
|
1003
|
+
const keepMounted = useLayout((state) => state.keepMounted);
|
|
1004
|
+
const mergedStyle = {
|
|
1005
|
+
...contentStyle,
|
|
1006
|
+
...style
|
|
1007
|
+
};
|
|
1008
|
+
if (keepMounted && node.children.length > 0) return node.children.map((tab, index) => /* @__PURE__ */ jsx("div", {
|
|
1009
|
+
...props,
|
|
1010
|
+
"aria-labelledby": tabDomId(node.id, tab.id),
|
|
1011
|
+
"data-dashfoo": "tabcontent",
|
|
1012
|
+
hidden: index !== visualSelected || void 0,
|
|
1013
|
+
id: index === visualSelected ? panelDomId(node.id) : void 0,
|
|
1014
|
+
role: index === visualSelected ? "tabpanel" : void 0,
|
|
1015
|
+
style: mergedStyle,
|
|
1016
|
+
tabIndex: index === visualSelected ? 0 : void 0,
|
|
1017
|
+
children: /* @__PURE__ */ jsx(TabPanel, {
|
|
1018
|
+
render: children,
|
|
1019
|
+
tab
|
|
1020
|
+
})
|
|
1021
|
+
}, tab.id));
|
|
1022
|
+
const active = node.children[visualSelected];
|
|
1023
|
+
if (active) return /* @__PURE__ */ jsx("div", {
|
|
1024
|
+
...props,
|
|
1025
|
+
"aria-labelledby": tabDomId(node.id, active.id),
|
|
1026
|
+
"data-dashfoo": "tabcontent",
|
|
1027
|
+
id: panelDomId(node.id),
|
|
1028
|
+
role: "tabpanel",
|
|
1029
|
+
style: mergedStyle,
|
|
1030
|
+
tabIndex: 0,
|
|
1031
|
+
children: /* @__PURE__ */ jsx(TabPanel, {
|
|
1032
|
+
render: children,
|
|
1033
|
+
tab: active
|
|
1034
|
+
})
|
|
1035
|
+
});
|
|
1036
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1037
|
+
...props,
|
|
1038
|
+
"data-dashfoo": "tabcontent",
|
|
1039
|
+
style: mergedStyle
|
|
1040
|
+
});
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
//#endregion
|
|
1044
|
+
//#region src/components/tab-overflow.tsx
|
|
1045
|
+
const OverflowIcon = () => /* @__PURE__ */ jsxs("svg", {
|
|
1046
|
+
"aria-hidden": "true",
|
|
1047
|
+
fill: "currentColor",
|
|
1048
|
+
height: "10",
|
|
1049
|
+
viewBox: "0 0 10 10",
|
|
1050
|
+
width: "10",
|
|
1051
|
+
children: [
|
|
1052
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1053
|
+
cx: "2",
|
|
1054
|
+
cy: "5",
|
|
1055
|
+
r: "1"
|
|
1056
|
+
}),
|
|
1057
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1058
|
+
cx: "5",
|
|
1059
|
+
cy: "5",
|
|
1060
|
+
r: "1"
|
|
1061
|
+
}),
|
|
1062
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1063
|
+
cx: "8",
|
|
1064
|
+
cy: "5",
|
|
1065
|
+
r: "1"
|
|
1066
|
+
})
|
|
1067
|
+
]
|
|
1068
|
+
});
|
|
1069
|
+
const menuStyle = {
|
|
1070
|
+
position: "absolute",
|
|
1071
|
+
right: 0,
|
|
1072
|
+
top: "100%",
|
|
1073
|
+
zIndex: 10
|
|
1074
|
+
};
|
|
1075
|
+
const TabOverflowMenu = ({ items, onSelect }) => {
|
|
1076
|
+
const [open, setOpen] = useState(false);
|
|
1077
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
1078
|
+
const rootRef = useRef(null);
|
|
1079
|
+
const triggerRef = useRef(null);
|
|
1080
|
+
const itemRefs = useRef([]);
|
|
1081
|
+
useEffect(() => {
|
|
1082
|
+
if (!open) return;
|
|
1083
|
+
const handlePointerDown = (event) => {
|
|
1084
|
+
if (event.target instanceof Node && !rootRef.current?.contains(event.target)) setOpen(false);
|
|
1085
|
+
};
|
|
1086
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
1087
|
+
return () => {
|
|
1088
|
+
document.removeEventListener("pointerdown", handlePointerDown);
|
|
1089
|
+
};
|
|
1090
|
+
}, [open]);
|
|
1091
|
+
useEffect(() => {
|
|
1092
|
+
if (open) itemRefs.current[0]?.focus();
|
|
1093
|
+
}, [open]);
|
|
1094
|
+
const closeAndReturnFocus = () => {
|
|
1095
|
+
setOpen(false);
|
|
1096
|
+
triggerRef.current?.focus();
|
|
1097
|
+
};
|
|
1098
|
+
const focusItem = (index) => {
|
|
1099
|
+
setActiveIndex(index);
|
|
1100
|
+
itemRefs.current[index]?.focus();
|
|
1101
|
+
};
|
|
1102
|
+
const handleToggle = () => {
|
|
1103
|
+
setActiveIndex(0);
|
|
1104
|
+
setOpen((value) => !value);
|
|
1105
|
+
};
|
|
1106
|
+
const handleSelect = (id) => {
|
|
1107
|
+
onSelect(id);
|
|
1108
|
+
closeAndReturnFocus();
|
|
1109
|
+
};
|
|
1110
|
+
const handleMenuKeyDown = (event) => {
|
|
1111
|
+
const last = items.length - 1;
|
|
1112
|
+
switch (event.key) {
|
|
1113
|
+
case "ArrowDown":
|
|
1114
|
+
event.preventDefault();
|
|
1115
|
+
focusItem(activeIndex >= last ? 0 : activeIndex + 1);
|
|
1116
|
+
break;
|
|
1117
|
+
case "ArrowUp":
|
|
1118
|
+
event.preventDefault();
|
|
1119
|
+
focusItem(activeIndex <= 0 ? last : activeIndex - 1);
|
|
1120
|
+
break;
|
|
1121
|
+
case "Home":
|
|
1122
|
+
event.preventDefault();
|
|
1123
|
+
focusItem(0);
|
|
1124
|
+
break;
|
|
1125
|
+
case "End":
|
|
1126
|
+
event.preventDefault();
|
|
1127
|
+
focusItem(last);
|
|
1128
|
+
break;
|
|
1129
|
+
case "Escape":
|
|
1130
|
+
event.preventDefault();
|
|
1131
|
+
closeAndReturnFocus();
|
|
1132
|
+
break;
|
|
1133
|
+
default: break;
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
const handleFocusOut = (event) => {
|
|
1137
|
+
if (event.relatedTarget instanceof Node && rootRef.current?.contains(event.relatedTarget)) return;
|
|
1138
|
+
setOpen(false);
|
|
1139
|
+
};
|
|
1140
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1141
|
+
"data-dashfoo": "tab-overflow-root",
|
|
1142
|
+
ref: rootRef,
|
|
1143
|
+
style: { position: "relative" },
|
|
1144
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
1145
|
+
"aria-expanded": open,
|
|
1146
|
+
"aria-haspopup": "menu",
|
|
1147
|
+
"aria-label": "More tabs",
|
|
1148
|
+
"data-dashfoo": "tab-overflow",
|
|
1149
|
+
onClick: handleToggle,
|
|
1150
|
+
ref: triggerRef,
|
|
1151
|
+
type: "button",
|
|
1152
|
+
children: /* @__PURE__ */ jsx(OverflowIcon, {})
|
|
1153
|
+
}), open ? /* @__PURE__ */ jsx("div", {
|
|
1154
|
+
"data-dashfoo": "tab-overflow-menu",
|
|
1155
|
+
onBlur: handleFocusOut,
|
|
1156
|
+
onKeyDown: handleMenuKeyDown,
|
|
1157
|
+
role: "menu",
|
|
1158
|
+
style: menuStyle,
|
|
1159
|
+
tabIndex: -1,
|
|
1160
|
+
children: items.map((item, index) => /* @__PURE__ */ jsx("button", {
|
|
1161
|
+
"data-dashfoo": "tab-overflow-item",
|
|
1162
|
+
onClick: () => handleSelect(item.id),
|
|
1163
|
+
ref: (node) => {
|
|
1164
|
+
itemRefs.current[index] = node;
|
|
1165
|
+
},
|
|
1166
|
+
role: "menuitem",
|
|
1167
|
+
tabIndex: index === activeIndex ? 0 : -1,
|
|
1168
|
+
type: "button",
|
|
1169
|
+
children: item.name
|
|
1170
|
+
}, item.id))
|
|
1171
|
+
}) : null]
|
|
1172
|
+
});
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
//#endregion
|
|
1176
|
+
//#region src/components/tabset/tabset-overflow.tsx
|
|
1177
|
+
const TabsetOverflowMenu = () => {
|
|
1178
|
+
const overflowItems = useTabset((state) => state.overflowItems);
|
|
1179
|
+
const selectOverflowTab = useTabset((state) => state.selectOverflowTab);
|
|
1180
|
+
if (overflowItems.length === 0) return null;
|
|
1181
|
+
return /* @__PURE__ */ jsx(TabOverflowMenu, {
|
|
1182
|
+
items: overflowItems,
|
|
1183
|
+
onSelect: selectOverflowTab
|
|
1184
|
+
});
|
|
1185
|
+
};
|
|
1186
|
+
|
|
1187
|
+
//#endregion
|
|
1188
|
+
//#region src/hooks/use-tab-overflow.ts
|
|
1189
|
+
const overflowingIds = (tablist) => {
|
|
1190
|
+
const box = tablist.getBoundingClientRect();
|
|
1191
|
+
return [...tablist.querySelectorAll("[data-dashfoo=\"tab\"]")].flatMap((tab) => {
|
|
1192
|
+
const rect = tab.getBoundingClientRect();
|
|
1193
|
+
return (rect.right > box.right + 1 || rect.left < box.left - 1) && tab.dataset.tabId ? [tab.dataset.tabId] : [];
|
|
1194
|
+
});
|
|
1195
|
+
};
|
|
1196
|
+
const useTabOverflow = (tablist, signature) => {
|
|
1197
|
+
const [overflow, setOverflow] = useState([]);
|
|
1198
|
+
useEffect(() => {
|
|
1199
|
+
const recompute = () => {
|
|
1200
|
+
setOverflow((previous) => {
|
|
1201
|
+
const next = tablist ? overflowingIds(tablist) : [];
|
|
1202
|
+
return next.length === 0 && previous.length === 0 ? previous : next;
|
|
1203
|
+
});
|
|
1204
|
+
};
|
|
1205
|
+
recompute();
|
|
1206
|
+
if (!tablist) return;
|
|
1207
|
+
const observer = new ResizeObserver(recompute);
|
|
1208
|
+
observer.observe(tablist);
|
|
1209
|
+
tablist.addEventListener("scroll", recompute, { passive: true });
|
|
1210
|
+
return () => {
|
|
1211
|
+
observer.disconnect();
|
|
1212
|
+
tablist.removeEventListener("scroll", recompute);
|
|
1213
|
+
};
|
|
1214
|
+
}, [tablist, signature]);
|
|
1215
|
+
return overflow;
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
//#endregion
|
|
1219
|
+
//#region src/lib/tab-selection.ts
|
|
1220
|
+
const fallbackSelectedIndex = (count, removedIndex) => removedIndex + 1 < count ? removedIndex + 1 : removedIndex - 1;
|
|
1221
|
+
|
|
1222
|
+
//#endregion
|
|
1223
|
+
//#region src/components/tabset/tabset-root.tsx
|
|
1224
|
+
const tabsetStyle = {
|
|
1225
|
+
display: "flex",
|
|
1226
|
+
flexDirection: "column",
|
|
1227
|
+
height: "100%",
|
|
1228
|
+
minHeight: 0,
|
|
1229
|
+
minWidth: 0,
|
|
1230
|
+
width: "100%"
|
|
1231
|
+
};
|
|
1232
|
+
const TabsetRoot = ({ children, node, ref: userRef, style, ...props }) => {
|
|
1233
|
+
const closableTabs = useLayout((state) => state.closableTabs);
|
|
1234
|
+
const dispatch = useLayout((state) => state.dispatch);
|
|
1235
|
+
const maximizable = useLayout((state) => state.maximizable);
|
|
1236
|
+
const maximizedTabsetId = useLayout((state) => state.maximizedTabsetId);
|
|
1237
|
+
const renamableTabs = useLayout((state) => state.renamableTabs);
|
|
1238
|
+
const tabLocation = useLayout((state) => state.tabLocation);
|
|
1239
|
+
const { ref } = useTabsetDroppable(node.id);
|
|
1240
|
+
const dragSubject = useDragSubject();
|
|
1241
|
+
const tabsetRef = useRef(null);
|
|
1242
|
+
const isMaximized = maximizedTabsetId === node.id;
|
|
1243
|
+
const showMaximize = maximizable && node.enableMaximize !== false;
|
|
1244
|
+
const tabsClosable = closableTabs && node.enableClose !== false;
|
|
1245
|
+
const tabsRenamable = renamableTabs;
|
|
1246
|
+
const draggingTabIndex = dragSubject?.kind === "tab" ? node.children.findIndex((tab) => tab.id === dragSubject.id) : -1;
|
|
1247
|
+
const visualSelected = draggingTabIndex !== -1 && draggingTabIndex === node.selected ? fallbackSelectedIndex(node.children.length, draggingTabIndex) : node.selected;
|
|
1248
|
+
const activeTab = node.children[visualSelected];
|
|
1249
|
+
const isDragSource = dragSubject?.kind === "tabset" && dragSubject.id === node.id;
|
|
1250
|
+
const snapshot = {
|
|
1251
|
+
activeTab,
|
|
1252
|
+
dispatch,
|
|
1253
|
+
isMaximized,
|
|
1254
|
+
node,
|
|
1255
|
+
showMaximize,
|
|
1256
|
+
tabsClosable,
|
|
1257
|
+
tabsRenamable,
|
|
1258
|
+
visualSelected
|
|
1259
|
+
};
|
|
1260
|
+
const [store] = useState(() => createTabsetStore(snapshot));
|
|
1261
|
+
useLayoutEffect(() => {
|
|
1262
|
+
store.setState(snapshot);
|
|
1263
|
+
});
|
|
1264
|
+
const overflow = useTabOverflow(useStore(store, (state) => state.tablistElement), node.children.map((tab) => `${tab.id}:${tab.name}`).join("|"));
|
|
1265
|
+
const overflowItems = useMemo(() => {
|
|
1266
|
+
const names = new Map(node.children.map((tab) => [tab.id, tab.name]));
|
|
1267
|
+
return overflow.map((id) => ({
|
|
1268
|
+
id,
|
|
1269
|
+
name: names.get(id) ?? id
|
|
1270
|
+
}));
|
|
1271
|
+
}, [overflow, node.children]);
|
|
1272
|
+
useLayoutEffect(() => {
|
|
1273
|
+
store.setState({ overflowItems });
|
|
1274
|
+
}, [overflowItems, store]);
|
|
1275
|
+
useEffect(() => {
|
|
1276
|
+
if (!store.getState().restoreFocus) return;
|
|
1277
|
+
store.setState({ restoreFocus: false });
|
|
1278
|
+
const activeTabId = node.children[node.selected]?.id;
|
|
1279
|
+
if (activeTabId) tabsetRef.current?.querySelector(`#${CSS.escape(tabDomId(node.id, activeTabId))}`)?.focus();
|
|
1280
|
+
else tabsetRef.current?.focus();
|
|
1281
|
+
}, [
|
|
1282
|
+
node.children,
|
|
1283
|
+
node.selected,
|
|
1284
|
+
node.id,
|
|
1285
|
+
store
|
|
1286
|
+
]);
|
|
1287
|
+
return /* @__PURE__ */ jsx(TabsetStoreContext.Provider, {
|
|
1288
|
+
value: store,
|
|
1289
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1290
|
+
...props,
|
|
1291
|
+
"data-dashfoo": "tabset",
|
|
1292
|
+
"data-dragging-source": isDragSource || void 0,
|
|
1293
|
+
"data-tab-location": tabLocation,
|
|
1294
|
+
ref: (element) => {
|
|
1295
|
+
ref(element);
|
|
1296
|
+
tabsetRef.current = element;
|
|
1297
|
+
if (typeof userRef === "function") userRef(element);
|
|
1298
|
+
else if (userRef) userRef.current = element;
|
|
1299
|
+
},
|
|
1300
|
+
style: {
|
|
1301
|
+
...tabsetStyle,
|
|
1302
|
+
...style
|
|
1303
|
+
},
|
|
1304
|
+
tabIndex: -1,
|
|
1305
|
+
children
|
|
1306
|
+
})
|
|
1307
|
+
});
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
//#endregion
|
|
1311
|
+
//#region src/lib/merge-refs.ts
|
|
1312
|
+
const applyRef = (ref, value) => {
|
|
1313
|
+
if (typeof ref === "function") ref(value);
|
|
1314
|
+
else if (ref) ref.current = value;
|
|
1315
|
+
};
|
|
1316
|
+
const mergeRefs = (...refs) => (value) => {
|
|
1317
|
+
for (const ref of refs) applyRef(ref, value);
|
|
1318
|
+
};
|
|
1319
|
+
|
|
1320
|
+
//#endregion
|
|
1321
|
+
//#region src/components/tabset/tabset-tab.tsx
|
|
1322
|
+
const TabsetTab = ({ children, ref: userRef, tab, ...props }) => {
|
|
1323
|
+
const store = useContext(TabsetStoreContext);
|
|
1324
|
+
const node = useTabset((state) => state.node);
|
|
1325
|
+
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
1326
|
+
const dragSubject = useDragSubject();
|
|
1327
|
+
const isDragging = dragSubject?.kind === "tab" && dragSubject.id === tab.id;
|
|
1328
|
+
const itemRef = useRef(null);
|
|
1329
|
+
const wasEditing = useRef(false);
|
|
1330
|
+
const index = node.children.findIndex((child) => child.id === tab.id);
|
|
1331
|
+
useEffect(() => {
|
|
1332
|
+
if (store?.getState().node.children.some((child) => child.id === tab.id) === false) warnOnce(`orphan-tab-${tab.id}`, `<Tabset.Tab> for "${tab.id}" is not a child of its Tabset.Root's node; it renders but can never be selected`);
|
|
1333
|
+
}, [
|
|
1334
|
+
index === -1,
|
|
1335
|
+
store,
|
|
1336
|
+
tab.id
|
|
1337
|
+
]);
|
|
1338
|
+
useEffect(() => {
|
|
1339
|
+
if (wasEditing.current && !editing) itemRef.current?.querySelector("[data-dashfoo=\"tab\"]")?.focus();
|
|
1340
|
+
wasEditing.current = editing;
|
|
1341
|
+
}, [editing]);
|
|
1342
|
+
const contextValue = useMemo(() => ({
|
|
1343
|
+
index,
|
|
1344
|
+
tab
|
|
1345
|
+
}), [index, tab]);
|
|
1346
|
+
const refCallback = useMemo(() => mergeRefs(itemRef, userRef), [userRef]);
|
|
1347
|
+
return /* @__PURE__ */ jsx(TabContext.Provider, {
|
|
1348
|
+
value: contextValue,
|
|
1349
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
1350
|
+
...props,
|
|
1351
|
+
"data-dashfoo": "tab-item",
|
|
1352
|
+
"data-dragging": isDragging || void 0,
|
|
1353
|
+
ref: refCallback,
|
|
1354
|
+
children
|
|
1355
|
+
})
|
|
1356
|
+
});
|
|
1357
|
+
};
|
|
1358
|
+
const TabsetTrigger = ({ children, onClick, onDoubleClick, ref: userRef, ...props }) => {
|
|
1359
|
+
const { index, tab } = useTab();
|
|
1360
|
+
const node = useTabset((state) => state.node);
|
|
1361
|
+
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
1362
|
+
const selectTab = useTabset((state) => state.selectTab);
|
|
1363
|
+
const startEditing = useTabset((state) => state.startEditing);
|
|
1364
|
+
const tabsRenamable = useTabset((state) => state.tabsRenamable);
|
|
1365
|
+
const visualSelected = useTabset((state) => state.visualSelected);
|
|
1366
|
+
const draggableTabs = useLayout((state) => state.draggableTabs);
|
|
1367
|
+
const renderTabLabel = useLayout((state) => state.renderTabLabel);
|
|
1368
|
+
const { ref } = useTabDraggable(tab.id, !draggableTabs || tab.enableDrag === false, tab.name);
|
|
1369
|
+
const refCallback = useMemo(() => mergeRefs(ref, userRef), [ref, userRef]);
|
|
1370
|
+
if (editing) return null;
|
|
1371
|
+
const selected = index === visualSelected;
|
|
1372
|
+
const renamable = tabsRenamable && tab.enableRename !== false;
|
|
1373
|
+
const handleClick = (event) => {
|
|
1374
|
+
onClick?.(event);
|
|
1375
|
+
selectTab(index);
|
|
1376
|
+
};
|
|
1377
|
+
const handleDoubleClick = (event) => {
|
|
1378
|
+
onDoubleClick?.(event);
|
|
1379
|
+
if (renamable) startEditing(tab.id);
|
|
1380
|
+
};
|
|
1381
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1382
|
+
"aria-label": renderTabLabel ? tab.name : void 0,
|
|
1383
|
+
...props,
|
|
1384
|
+
"aria-controls": panelDomId(node.id),
|
|
1385
|
+
"aria-selected": selected,
|
|
1386
|
+
"data-dashfoo": "tab",
|
|
1387
|
+
"data-tab-id": tab.id,
|
|
1388
|
+
id: tabDomId(node.id, tab.id),
|
|
1389
|
+
onClick: handleClick,
|
|
1390
|
+
onDoubleClick: handleDoubleClick,
|
|
1391
|
+
ref: refCallback,
|
|
1392
|
+
role: "tab",
|
|
1393
|
+
tabIndex: selected ? 0 : -1,
|
|
1394
|
+
type: "button",
|
|
1395
|
+
children: children ?? (renderTabLabel ? renderTabLabel(tab) : tab.name)
|
|
1396
|
+
});
|
|
1397
|
+
};
|
|
1398
|
+
|
|
1399
|
+
//#endregion
|
|
1400
|
+
//#region src/components/tabset-icons.tsx
|
|
1401
|
+
const iconStyle = { pointerEvents: "none" };
|
|
1402
|
+
const Icon = ({ children }) => /* @__PURE__ */ jsx("svg", {
|
|
1403
|
+
"aria-hidden": "true",
|
|
1404
|
+
fill: "none",
|
|
1405
|
+
height: "12",
|
|
1406
|
+
stroke: "currentColor",
|
|
1407
|
+
strokeLinecap: "round",
|
|
1408
|
+
strokeLinejoin: "round",
|
|
1409
|
+
strokeWidth: "2",
|
|
1410
|
+
style: iconStyle,
|
|
1411
|
+
viewBox: "0 0 24 24",
|
|
1412
|
+
width: "12",
|
|
1413
|
+
children
|
|
1414
|
+
});
|
|
1415
|
+
const CloseIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [/* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }), /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })] });
|
|
1416
|
+
const GripIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1417
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1418
|
+
cx: "12",
|
|
1419
|
+
cy: "5",
|
|
1420
|
+
r: "1"
|
|
1421
|
+
}),
|
|
1422
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1423
|
+
cx: "19",
|
|
1424
|
+
cy: "5",
|
|
1425
|
+
r: "1"
|
|
1426
|
+
}),
|
|
1427
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1428
|
+
cx: "5",
|
|
1429
|
+
cy: "5",
|
|
1430
|
+
r: "1"
|
|
1431
|
+
}),
|
|
1432
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1433
|
+
cx: "12",
|
|
1434
|
+
cy: "12",
|
|
1435
|
+
r: "1"
|
|
1436
|
+
}),
|
|
1437
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1438
|
+
cx: "19",
|
|
1439
|
+
cy: "12",
|
|
1440
|
+
r: "1"
|
|
1441
|
+
}),
|
|
1442
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1443
|
+
cx: "5",
|
|
1444
|
+
cy: "12",
|
|
1445
|
+
r: "1"
|
|
1446
|
+
}),
|
|
1447
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1448
|
+
cx: "12",
|
|
1449
|
+
cy: "19",
|
|
1450
|
+
r: "1"
|
|
1451
|
+
}),
|
|
1452
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1453
|
+
cx: "19",
|
|
1454
|
+
cy: "19",
|
|
1455
|
+
r: "1"
|
|
1456
|
+
}),
|
|
1457
|
+
/* @__PURE__ */ jsx("circle", {
|
|
1458
|
+
cx: "5",
|
|
1459
|
+
cy: "19",
|
|
1460
|
+
r: "1"
|
|
1461
|
+
})
|
|
1462
|
+
] });
|
|
1463
|
+
const MaximizeIcon = ({ maximized }) => maximized ? /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1464
|
+
/* @__PURE__ */ jsx("path", { d: "m14 10 7-7" }),
|
|
1465
|
+
/* @__PURE__ */ jsx("path", { d: "M20 10h-6V4" }),
|
|
1466
|
+
/* @__PURE__ */ jsx("path", { d: "m3 21 7-7" }),
|
|
1467
|
+
/* @__PURE__ */ jsx("path", { d: "M4 14h6v6" })
|
|
1468
|
+
] }) : /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1469
|
+
/* @__PURE__ */ jsx("path", { d: "M15 3h6v6" }),
|
|
1470
|
+
/* @__PURE__ */ jsx("path", { d: "m21 3-7 7" }),
|
|
1471
|
+
/* @__PURE__ */ jsx("path", { d: "m3 21 7-7" }),
|
|
1472
|
+
/* @__PURE__ */ jsx("path", { d: "M9 21H3v-6" })
|
|
1473
|
+
] });
|
|
1474
|
+
const FloatIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [/* @__PURE__ */ jsx("rect", {
|
|
1475
|
+
height: "14",
|
|
1476
|
+
rx: "2",
|
|
1477
|
+
width: "18",
|
|
1478
|
+
x: "3",
|
|
1479
|
+
y: "6"
|
|
1480
|
+
}), /* @__PURE__ */ jsx("path", { d: "M3 10h18" })] });
|
|
1481
|
+
const MinimizeIcon = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M5 12h14" }) });
|
|
1482
|
+
const DockIcon = () => /* @__PURE__ */ jsxs(Icon, { children: [
|
|
1483
|
+
/* @__PURE__ */ jsx("path", { d: "M3 7V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2" }),
|
|
1484
|
+
/* @__PURE__ */ jsx("path", { d: "M11 8 7 12l4 4" }),
|
|
1485
|
+
/* @__PURE__ */ jsx("path", { d: "M7 12h10" })
|
|
1486
|
+
] });
|
|
1487
|
+
|
|
1488
|
+
//#endregion
|
|
1489
|
+
//#region src/components/tabset/tabset-tab-controls.tsx
|
|
1490
|
+
const RenameEditor = ({ defaultValue, onBlur, onKeyDown, ref: userRef, ...props }) => {
|
|
1491
|
+
const { tab } = useTab();
|
|
1492
|
+
const cancelRename = useTabset((state) => state.cancelRename);
|
|
1493
|
+
const commitRename = useTabset((state) => state.commitRename);
|
|
1494
|
+
const ref = useRef(null);
|
|
1495
|
+
const done = useRef(false);
|
|
1496
|
+
const refCallback = useMemo(() => mergeRefs(ref, userRef), [userRef]);
|
|
1497
|
+
useEffect(() => {
|
|
1498
|
+
ref.current?.focus();
|
|
1499
|
+
ref.current?.select();
|
|
1500
|
+
}, []);
|
|
1501
|
+
const handleKeyDown = (event) => {
|
|
1502
|
+
onKeyDown?.(event);
|
|
1503
|
+
if (event.key === "Enter") {
|
|
1504
|
+
done.current = true;
|
|
1505
|
+
commitRename(tab.id, event.currentTarget.value);
|
|
1506
|
+
} else if (event.key === "Escape") {
|
|
1507
|
+
done.current = true;
|
|
1508
|
+
cancelRename();
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
const handleCommitOnBlur = (event) => {
|
|
1512
|
+
onBlur?.(event);
|
|
1513
|
+
if (done.current) return;
|
|
1514
|
+
done.current = true;
|
|
1515
|
+
commitRename(tab.id, event.currentTarget.value);
|
|
1516
|
+
};
|
|
1517
|
+
return /* @__PURE__ */ jsx("input", {
|
|
1518
|
+
"aria-label": `Rename ${tab.name}`,
|
|
1519
|
+
...props,
|
|
1520
|
+
"data-dashfoo": "tab-rename",
|
|
1521
|
+
defaultValue: defaultValue ?? tab.name,
|
|
1522
|
+
onBlur: handleCommitOnBlur,
|
|
1523
|
+
onKeyDown: handleKeyDown,
|
|
1524
|
+
ref: refCallback,
|
|
1525
|
+
type: "text"
|
|
1526
|
+
});
|
|
1527
|
+
};
|
|
1528
|
+
const TabsetRenameInput = (props) => {
|
|
1529
|
+
const { tab } = useTab();
|
|
1530
|
+
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
1531
|
+
const registerRenameInput = useTabset((state) => state.registerRenameInput);
|
|
1532
|
+
useEffect(() => registerRenameInput(), [registerRenameInput]);
|
|
1533
|
+
if (!editing) return null;
|
|
1534
|
+
return /* @__PURE__ */ jsx(RenameEditor, { ...props });
|
|
1535
|
+
};
|
|
1536
|
+
const TabsetCloseButton = ({ children, onClick, ...props }) => {
|
|
1537
|
+
const { index, tab } = useTab();
|
|
1538
|
+
const closeTab = useTabset((state) => state.closeTab);
|
|
1539
|
+
const editing = useTabset((state) => state.editingTabId === tab.id);
|
|
1540
|
+
const tabsClosable = useTabset((state) => state.tabsClosable);
|
|
1541
|
+
const visualSelected = useTabset((state) => state.visualSelected);
|
|
1542
|
+
if (!(tabsClosable && tab.enableClose !== false) || editing) return null;
|
|
1543
|
+
const handleClick = (event) => {
|
|
1544
|
+
onClick?.(event);
|
|
1545
|
+
event.stopPropagation();
|
|
1546
|
+
closeTab(tab.id);
|
|
1547
|
+
};
|
|
1548
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1549
|
+
"aria-label": `Close ${tab.name}`,
|
|
1550
|
+
...props,
|
|
1551
|
+
"data-dashfoo": "tab-close",
|
|
1552
|
+
onClick: handleClick,
|
|
1553
|
+
tabIndex: index === visualSelected ? 0 : -1,
|
|
1554
|
+
type: "button",
|
|
1555
|
+
children: children ?? /* @__PURE__ */ jsx(CloseIcon, {})
|
|
1556
|
+
});
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
//#endregion
|
|
1560
|
+
//#region src/components/tabset/tabset-tablist.tsx
|
|
1561
|
+
const stripStyle = {
|
|
1562
|
+
display: "flex",
|
|
1563
|
+
flexShrink: 0
|
|
1564
|
+
};
|
|
1565
|
+
const tablistStyle = {
|
|
1566
|
+
display: "flex",
|
|
1567
|
+
minWidth: 0,
|
|
1568
|
+
overflowX: "auto"
|
|
1569
|
+
};
|
|
1570
|
+
const TabsetTabStrip = ({ style, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
1571
|
+
...props,
|
|
1572
|
+
"data-dashfoo": "tabstrip",
|
|
1573
|
+
style: {
|
|
1574
|
+
...stripStyle,
|
|
1575
|
+
...style
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
const TabsetTablist = ({ onKeyDown, ref: userRef, style, ...props }) => {
|
|
1579
|
+
const node = useTabset((state) => state.node);
|
|
1580
|
+
const registerTablist = useTabset((state) => state.registerTablist);
|
|
1581
|
+
const selectTab = useTabset((state) => state.selectTab);
|
|
1582
|
+
const handleKeyDown = (event) => {
|
|
1583
|
+
onKeyDown?.(event);
|
|
1584
|
+
if (event.defaultPrevented) return;
|
|
1585
|
+
if (!(event.target instanceof HTMLElement) || !event.target.closest("[role=\"tab\"]")) return;
|
|
1586
|
+
const count = node.children.length;
|
|
1587
|
+
if (count === 0) return;
|
|
1588
|
+
const next = {
|
|
1589
|
+
ArrowLeft: (node.selected - 1 + count) % count,
|
|
1590
|
+
ArrowRight: (node.selected + 1) % count,
|
|
1591
|
+
End: count - 1,
|
|
1592
|
+
Home: 0
|
|
1593
|
+
}[event.key];
|
|
1594
|
+
if (next === void 0) return;
|
|
1595
|
+
event.preventDefault();
|
|
1596
|
+
selectTab(next, { focus: true });
|
|
1597
|
+
};
|
|
1598
|
+
const refCallback = useMemo(() => mergeRefs(registerTablist, userRef), [registerTablist, userRef]);
|
|
1599
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1600
|
+
"aria-label": node.name ?? "Tabs",
|
|
1601
|
+
...props,
|
|
1602
|
+
"aria-orientation": "horizontal",
|
|
1603
|
+
"data-dashfoo": "tablist",
|
|
1604
|
+
onKeyDown: handleKeyDown,
|
|
1605
|
+
ref: refCallback,
|
|
1606
|
+
role: "tablist",
|
|
1607
|
+
style: {
|
|
1608
|
+
...tablistStyle,
|
|
1609
|
+
...style
|
|
1610
|
+
},
|
|
1611
|
+
tabIndex: -1
|
|
1612
|
+
});
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
//#endregion
|
|
1616
|
+
//#region src/components/tabset/tabset-toolbar.tsx
|
|
1617
|
+
const toolbarStyle = {
|
|
1618
|
+
alignItems: "center",
|
|
1619
|
+
display: "flex",
|
|
1620
|
+
flexShrink: 0,
|
|
1621
|
+
marginInlineStart: "auto"
|
|
1622
|
+
};
|
|
1623
|
+
const TabsetToolbar = ({ style, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
1624
|
+
...props,
|
|
1625
|
+
"data-dashfoo": "tabset-toolbar",
|
|
1626
|
+
style: {
|
|
1627
|
+
...toolbarStyle,
|
|
1628
|
+
...style
|
|
1629
|
+
}
|
|
1630
|
+
});
|
|
1631
|
+
const TabsetGrip = ({ children, ref: userRef, ...props }) => {
|
|
1632
|
+
const activeTabName = useTabset((state) => state.activeTab?.name ?? "");
|
|
1633
|
+
const isMaximized = useTabset((state) => state.isMaximized);
|
|
1634
|
+
const node = useTabset((state) => state.node);
|
|
1635
|
+
const hidden = !useLayout((state) => state.draggableTabsets) || isMaximized;
|
|
1636
|
+
const { ref } = useTabsetDraggable(node.id, hidden, activeTabName);
|
|
1637
|
+
const refCallback = useMemo(() => mergeRefs(ref, userRef), [ref, userRef]);
|
|
1638
|
+
if (hidden) return null;
|
|
1639
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1640
|
+
"aria-label": "Move tabset",
|
|
1641
|
+
...props,
|
|
1642
|
+
"data-dashfoo": "tabset-grip",
|
|
1643
|
+
ref: refCallback,
|
|
1644
|
+
type: "button",
|
|
1645
|
+
children: children ?? /* @__PURE__ */ jsx(GripIcon, {})
|
|
1646
|
+
});
|
|
1647
|
+
};
|
|
1648
|
+
const TabsetMaximizeButton = ({ children, onClick, ...props }) => {
|
|
1649
|
+
const isMaximized = useTabset((state) => state.isMaximized);
|
|
1650
|
+
const showMaximize = useTabset((state) => state.showMaximize);
|
|
1651
|
+
const toggleMaximize = useTabset((state) => state.toggleMaximize);
|
|
1652
|
+
if (!showMaximize) return null;
|
|
1653
|
+
const handleClick = (event) => {
|
|
1654
|
+
onClick?.(event);
|
|
1655
|
+
toggleMaximize();
|
|
1656
|
+
};
|
|
1657
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1658
|
+
"aria-label": isMaximized ? "Restore" : "Maximize",
|
|
1659
|
+
...props,
|
|
1660
|
+
"aria-pressed": isMaximized,
|
|
1661
|
+
"data-dashfoo": "tabset-maximize",
|
|
1662
|
+
onClick: handleClick,
|
|
1663
|
+
type: "button",
|
|
1664
|
+
children: children ?? /* @__PURE__ */ jsx(MaximizeIcon, { maximized: isMaximized })
|
|
1665
|
+
});
|
|
1666
|
+
};
|
|
1667
|
+
const TabsetFloatButton = ({ children, onClick, ...props }) => {
|
|
1668
|
+
const floatable = useLayout((state) => state.floatable);
|
|
1669
|
+
const dispatch = useLayout((state) => state.dispatch);
|
|
1670
|
+
const isMaximized = useTabset((state) => state.isMaximized);
|
|
1671
|
+
const node = useTabset((state) => state.node);
|
|
1672
|
+
const hasFloatLayer = useHasFloatLayer();
|
|
1673
|
+
if (!floatable || isMaximized) return null;
|
|
1674
|
+
if (!hasFloatLayer) {
|
|
1675
|
+
warnOnce("float-no-layer", "Tabset.FloatButton needs a <Layout.FloatLayer> around the layout (DashfooLayout adds one); the float control is hidden");
|
|
1676
|
+
return null;
|
|
1677
|
+
}
|
|
1678
|
+
const handleClick = (event) => {
|
|
1679
|
+
onClick?.(event);
|
|
1680
|
+
dispatch({
|
|
1681
|
+
geometry: measureFloatRect(event.currentTarget),
|
|
1682
|
+
tabsetId: node.id,
|
|
1683
|
+
type: "floatTabset"
|
|
1684
|
+
});
|
|
1685
|
+
};
|
|
1686
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1687
|
+
"aria-label": "Float panel",
|
|
1688
|
+
...props,
|
|
1689
|
+
"data-dashfoo": "tabset-float",
|
|
1690
|
+
onClick: handleClick,
|
|
1691
|
+
type: "button",
|
|
1692
|
+
children: children ?? /* @__PURE__ */ jsx(FloatIcon, {})
|
|
1693
|
+
});
|
|
1694
|
+
};
|
|
1695
|
+
|
|
1696
|
+
//#endregion
|
|
1697
|
+
//#region src/components/tabset/tabset-view.tsx
|
|
1698
|
+
const tabsetContent = /* @__PURE__ */ jsx(TabsetContent, {});
|
|
1699
|
+
const DefaultTabsetLayout = () => {
|
|
1700
|
+
const draggableTabsets = useLayout((state) => state.draggableTabsets);
|
|
1701
|
+
const floatable = useLayout((state) => state.floatable);
|
|
1702
|
+
const renderTabsetToolbar = useLayout((state) => state.renderTabsetToolbar);
|
|
1703
|
+
const tabLocation = useLayout((state) => state.tabLocation);
|
|
1704
|
+
const tabStripEnabled = useLayout((state) => state.tabStripEnabled);
|
|
1705
|
+
const isMaximized = useTabset((state) => state.isMaximized);
|
|
1706
|
+
const node = useTabset((state) => state.node);
|
|
1707
|
+
const overflowCount = useTabset((state) => state.overflowItems.length);
|
|
1708
|
+
const showToolbar = useTabset((state) => state.showMaximize) || renderTabsetToolbar !== void 0 || overflowCount > 0 || floatable && !isMaximized || draggableTabsets && !isMaximized;
|
|
1709
|
+
const strip = tabStripEnabled ? /* @__PURE__ */ jsxs(TabsetTabStrip, { children: [/* @__PURE__ */ jsx(TabsetTablist, { children: node.children.map((tab) => /* @__PURE__ */ jsxs(TabsetTab, {
|
|
1710
|
+
tab,
|
|
1711
|
+
children: [
|
|
1712
|
+
/* @__PURE__ */ jsx(TabsetTrigger, {}),
|
|
1713
|
+
/* @__PURE__ */ jsx(TabsetRenameInput, {}),
|
|
1714
|
+
/* @__PURE__ */ jsx(TabsetCloseButton, {})
|
|
1715
|
+
]
|
|
1716
|
+
}, tab.id)) }), showToolbar ? /* @__PURE__ */ jsxs(TabsetToolbar, { children: [
|
|
1717
|
+
/* @__PURE__ */ jsx(TabsetOverflowMenu, {}),
|
|
1718
|
+
/* @__PURE__ */ jsx(TabsetGrip, {}),
|
|
1719
|
+
renderTabsetToolbar?.(node),
|
|
1720
|
+
/* @__PURE__ */ jsx(TabsetFloatButton, {}),
|
|
1721
|
+
/* @__PURE__ */ jsx(TabsetMaximizeButton, {})
|
|
1722
|
+
] }) : null] }) : null;
|
|
1723
|
+
return tabLocation === "bottom" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [tabsetContent, strip] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [strip, tabsetContent] });
|
|
1724
|
+
};
|
|
1725
|
+
const TabsetView = ({ node }) => /* @__PURE__ */ jsx(TabsetRoot, {
|
|
1726
|
+
node,
|
|
1727
|
+
children: /* @__PURE__ */ jsx(DefaultTabsetLayout, {})
|
|
1728
|
+
});
|
|
1729
|
+
|
|
1730
|
+
//#endregion
|
|
1731
|
+
//#region src/components/row-view.tsx
|
|
1732
|
+
const dimensionToSize = (dimension) => `${dimension.value}${dimension.unit}`;
|
|
1733
|
+
const dimensionToPixels = (dimension) => dimension?.unit === "px" ? dimension.value : void 0;
|
|
1734
|
+
const groupStyle = {
|
|
1735
|
+
display: "flex",
|
|
1736
|
+
flex: 1,
|
|
1737
|
+
minHeight: 0,
|
|
1738
|
+
minWidth: 0
|
|
1739
|
+
};
|
|
1740
|
+
const WEIGHT_EPSILON = .01;
|
|
1741
|
+
const layoutFromWeights = (children, total) => children.reduce((layout, child) => {
|
|
1742
|
+
layout[child.id] = (child.weight ?? 1) / total * 100;
|
|
1743
|
+
return layout;
|
|
1744
|
+
}, {});
|
|
1745
|
+
const orderedFromLayout = (layout, children) => children.map((child) => layout[child.id] ?? 0);
|
|
1746
|
+
const layoutFromOrdered = (sizes, children) => children.reduce((layout, child, index) => {
|
|
1747
|
+
layout[child.id] = sizes[index] ?? 0;
|
|
1748
|
+
return layout;
|
|
1749
|
+
}, {});
|
|
1750
|
+
const layoutsMatch = (current, expected) => {
|
|
1751
|
+
const currentIds = Object.keys(current);
|
|
1752
|
+
const expectedIds = Object.keys(expected);
|
|
1753
|
+
if (currentIds.length !== expectedIds.length) return false;
|
|
1754
|
+
return expectedIds.every((id) => Math.abs((current[id] ?? NaN) - expected[id]) <= WEIGHT_EPSILON);
|
|
1755
|
+
};
|
|
1756
|
+
const descendantMinSize = (child, axis, tabsetMinSize) => {
|
|
1757
|
+
const explicit = dimensionToPixels(child.min);
|
|
1758
|
+
if (explicit !== void 0) return explicit;
|
|
1759
|
+
if (child.min !== void 0) return;
|
|
1760
|
+
if (child.type === "tabset") return tabsetMinSize;
|
|
1761
|
+
const childMinimums = [];
|
|
1762
|
+
for (const grandchild of child.children) {
|
|
1763
|
+
const minimum = descendantMinSize(grandchild, axis, tabsetMinSize);
|
|
1764
|
+
if (minimum === void 0) return;
|
|
1765
|
+
childMinimums.push(minimum);
|
|
1766
|
+
}
|
|
1767
|
+
if (childMinimums.length === 0) return;
|
|
1768
|
+
return child.orientation === axis ? childMinimums.reduce((sum, value) => sum + value, 0) : Math.max(...childMinimums);
|
|
1769
|
+
};
|
|
1770
|
+
const RowView = ({ node, renderTabset }) => {
|
|
1771
|
+
const dispatch = useLayout((state) => state.dispatch);
|
|
1772
|
+
const resizableSplits = useLayout((state) => state.resizableSplits);
|
|
1773
|
+
const tabsetMinSize = useLayout((state) => state.tabsetMinSize);
|
|
1774
|
+
const globalSnap = useLayout((state) => state.snap);
|
|
1775
|
+
const effectiveSnap = node.snap ?? globalSnap;
|
|
1776
|
+
const snapActive = snapEnabled(effectiveSnap);
|
|
1777
|
+
const orientation = node.orientation === "row" ? "horizontal" : "vertical";
|
|
1778
|
+
const total = node.children.reduce((sum, child) => sum + (child.weight ?? 1), 0);
|
|
1779
|
+
const desiredLayout = useMemo(() => layoutFromWeights(node.children, total), [node.children, total]);
|
|
1780
|
+
const measured = useRef(false);
|
|
1781
|
+
const groupRef = useRef(null);
|
|
1782
|
+
const groupElement = useRef(null);
|
|
1783
|
+
const syncing = useRef(false);
|
|
1784
|
+
const activeBoundary = useRef(null);
|
|
1785
|
+
const snapping = useRef(false);
|
|
1786
|
+
const snappedBoundaryRef = useRef(null);
|
|
1787
|
+
const [snappedBoundary, setSnappedBoundary] = useState(null);
|
|
1788
|
+
const setHighlight = (boundary) => {
|
|
1789
|
+
if (boundary !== snappedBoundaryRef.current) {
|
|
1790
|
+
snappedBoundaryRef.current = boundary;
|
|
1791
|
+
setSnappedBoundary(boundary);
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
const setSnapTransition = (active) => {
|
|
1795
|
+
const element = groupElement.current;
|
|
1796
|
+
if (!element) return;
|
|
1797
|
+
if (active) element.dataset.dashfooSnapping = "true";
|
|
1798
|
+
else delete element.dataset.dashfooSnapping;
|
|
1799
|
+
};
|
|
1800
|
+
useLayoutEffect(() => {
|
|
1801
|
+
const group = groupRef.current;
|
|
1802
|
+
if (!group) return;
|
|
1803
|
+
const current = group.getLayout();
|
|
1804
|
+
if (!(Object.keys(current).length === Object.keys(desiredLayout).length && Object.keys(desiredLayout).every((id) => id in current)) || layoutsMatch(current, desiredLayout)) return;
|
|
1805
|
+
syncing.current = true;
|
|
1806
|
+
group.setLayout(desiredLayout);
|
|
1807
|
+
const frame = requestAnimationFrame(() => {
|
|
1808
|
+
syncing.current = false;
|
|
1809
|
+
});
|
|
1810
|
+
return () => {
|
|
1811
|
+
cancelAnimationFrame(frame);
|
|
1812
|
+
syncing.current = false;
|
|
1813
|
+
};
|
|
1814
|
+
}, [desiredLayout]);
|
|
1815
|
+
const handleLayoutChange = (layout) => {
|
|
1816
|
+
if (snapping.current || syncing.current || !resizableSplits) return;
|
|
1817
|
+
if (effectiveSnap === null || !snapActive) return;
|
|
1818
|
+
const boundary = activeBoundary.current;
|
|
1819
|
+
if (boundary === null) return;
|
|
1820
|
+
const { sizes, snapped } = snapSizes(orderedFromLayout(layout, node.children), boundary, effectiveSnap);
|
|
1821
|
+
setHighlight(snapped ? boundary : null);
|
|
1822
|
+
if (!snapped) {
|
|
1823
|
+
setSnapTransition(false);
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
setSnapTransition(true);
|
|
1827
|
+
const group = groupRef.current;
|
|
1828
|
+
const target = layoutFromOrdered(sizes, node.children);
|
|
1829
|
+
if (!group || layoutsMatch(layout, target)) return;
|
|
1830
|
+
snapping.current = true;
|
|
1831
|
+
group.setLayout(target);
|
|
1832
|
+
snapping.current = false;
|
|
1833
|
+
};
|
|
1834
|
+
const handleLayoutChanged = (layout) => {
|
|
1835
|
+
if (!measured.current) {
|
|
1836
|
+
measured.current = true;
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
if (syncing.current) return;
|
|
1840
|
+
const boundary = activeBoundary.current;
|
|
1841
|
+
activeBoundary.current = null;
|
|
1842
|
+
setHighlight(null);
|
|
1843
|
+
setSnapTransition(false);
|
|
1844
|
+
if (!resizableSplits) return;
|
|
1845
|
+
let weights = node.children.map((child) => layout[child.id] ?? child.weight ?? 1);
|
|
1846
|
+
if (boundary !== null && effectiveSnap !== null && snapActive) weights = snapSizes(weights, boundary, effectiveSnap).sizes;
|
|
1847
|
+
if (!weights.some((weight, index) => Math.abs(weight - (node.children[index]?.weight ?? 1)) > WEIGHT_EPSILON)) return;
|
|
1848
|
+
dispatch({
|
|
1849
|
+
rowId: node.id,
|
|
1850
|
+
type: "adjustSplit",
|
|
1851
|
+
weights
|
|
1852
|
+
});
|
|
1853
|
+
};
|
|
1854
|
+
return /* @__PURE__ */ jsx(Group, {
|
|
1855
|
+
"data-dashfoo": "row",
|
|
1856
|
+
disabled: !resizableSplits,
|
|
1857
|
+
elementRef: groupElement,
|
|
1858
|
+
groupRef,
|
|
1859
|
+
onLayoutChange: snapActive ? handleLayoutChange : void 0,
|
|
1860
|
+
onLayoutChanged: handleLayoutChanged,
|
|
1861
|
+
orientation,
|
|
1862
|
+
style: groupStyle,
|
|
1863
|
+
children: node.children.map((child, index) => {
|
|
1864
|
+
const percent = (child.weight ?? 1) / total * 100;
|
|
1865
|
+
let min = child.min ? dimensionToSize(child.min) : void 0;
|
|
1866
|
+
if (min === void 0) {
|
|
1867
|
+
const minimum = descendantMinSize(child, node.orientation, tabsetMinSize);
|
|
1868
|
+
min = minimum === void 0 ? void 0 : `${minimum}px`;
|
|
1869
|
+
}
|
|
1870
|
+
const max = child.max ? dimensionToSize(child.max) : void 0;
|
|
1871
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 ? /* @__PURE__ */ jsx(Separator, {
|
|
1872
|
+
"data-dashfoo": "splitter",
|
|
1873
|
+
"data-dashfoo-snapped": snappedBoundary === index - 1 ? "true" : void 0,
|
|
1874
|
+
disabled: !resizableSplits,
|
|
1875
|
+
onPointerDown: snapActive && resizableSplits ? () => {
|
|
1876
|
+
activeBoundary.current = index - 1;
|
|
1877
|
+
} : void 0
|
|
1878
|
+
}) : null, /* @__PURE__ */ jsx(Panel$1, {
|
|
1879
|
+
defaultSize: `${percent}%`,
|
|
1880
|
+
id: child.id,
|
|
1881
|
+
maxSize: max,
|
|
1882
|
+
minSize: min,
|
|
1883
|
+
children: child.type === "row" ? /* @__PURE__ */ jsx(RowView, {
|
|
1884
|
+
node: child,
|
|
1885
|
+
renderTabset
|
|
1886
|
+
}) : renderTabset?.(child) ?? /* @__PURE__ */ jsx(TabsetView, { node: child })
|
|
1887
|
+
})] }, child.id);
|
|
1888
|
+
})
|
|
1889
|
+
}, node.id);
|
|
1890
|
+
};
|
|
1891
|
+
|
|
1892
|
+
//#endregion
|
|
1893
|
+
//#region src/components/float-panel.tsx
|
|
1894
|
+
const floatTitle = (node) => node.name ?? "Panel";
|
|
1895
|
+
const titleBarStyle = {
|
|
1896
|
+
alignItems: "center",
|
|
1897
|
+
display: "flex",
|
|
1898
|
+
flexShrink: 0,
|
|
1899
|
+
gap: "0.375rem",
|
|
1900
|
+
touchAction: "none"
|
|
1901
|
+
};
|
|
1902
|
+
const titleStyle = {
|
|
1903
|
+
flex: 1,
|
|
1904
|
+
minWidth: 0,
|
|
1905
|
+
overflow: "hidden",
|
|
1906
|
+
textOverflow: "ellipsis",
|
|
1907
|
+
whiteSpace: "nowrap"
|
|
1908
|
+
};
|
|
1909
|
+
const dockButtonStyle = {
|
|
1910
|
+
alignItems: "center",
|
|
1911
|
+
cursor: "pointer",
|
|
1912
|
+
display: "inline-flex",
|
|
1913
|
+
flexShrink: 0
|
|
1914
|
+
};
|
|
1915
|
+
const bodyStyle = {
|
|
1916
|
+
flex: 1,
|
|
1917
|
+
minHeight: 0,
|
|
1918
|
+
position: "relative"
|
|
1919
|
+
};
|
|
1920
|
+
const RESIZE_HANDLES = [
|
|
1921
|
+
{
|
|
1922
|
+
edges: {
|
|
1923
|
+
x: 0,
|
|
1924
|
+
y: -1
|
|
1925
|
+
},
|
|
1926
|
+
key: "n",
|
|
1927
|
+
style: {
|
|
1928
|
+
cursor: "ns-resize",
|
|
1929
|
+
height: 6,
|
|
1930
|
+
insetInline: 10,
|
|
1931
|
+
top: -3
|
|
1932
|
+
}
|
|
1933
|
+
},
|
|
1934
|
+
{
|
|
1935
|
+
edges: {
|
|
1936
|
+
x: 0,
|
|
1937
|
+
y: 1
|
|
1938
|
+
},
|
|
1939
|
+
key: "s",
|
|
1940
|
+
style: {
|
|
1941
|
+
bottom: -3,
|
|
1942
|
+
cursor: "ns-resize",
|
|
1943
|
+
height: 6,
|
|
1944
|
+
insetInline: 10
|
|
1945
|
+
}
|
|
1946
|
+
},
|
|
1947
|
+
{
|
|
1948
|
+
edges: {
|
|
1949
|
+
x: 1,
|
|
1950
|
+
y: 0
|
|
1951
|
+
},
|
|
1952
|
+
key: "e",
|
|
1953
|
+
style: {
|
|
1954
|
+
cursor: "ew-resize",
|
|
1955
|
+
insetBlock: 10,
|
|
1956
|
+
right: -3,
|
|
1957
|
+
width: 6
|
|
1958
|
+
}
|
|
1959
|
+
},
|
|
1960
|
+
{
|
|
1961
|
+
edges: {
|
|
1962
|
+
x: -1,
|
|
1963
|
+
y: 0
|
|
1964
|
+
},
|
|
1965
|
+
key: "w",
|
|
1966
|
+
style: {
|
|
1967
|
+
cursor: "ew-resize",
|
|
1968
|
+
insetBlock: 10,
|
|
1969
|
+
left: -3,
|
|
1970
|
+
width: 6
|
|
1971
|
+
}
|
|
1972
|
+
},
|
|
1973
|
+
{
|
|
1974
|
+
edges: {
|
|
1975
|
+
x: 1,
|
|
1976
|
+
y: -1
|
|
1977
|
+
},
|
|
1978
|
+
key: "ne",
|
|
1979
|
+
style: {
|
|
1980
|
+
cursor: "nesw-resize",
|
|
1981
|
+
height: 14,
|
|
1982
|
+
right: -4,
|
|
1983
|
+
top: -4,
|
|
1984
|
+
width: 14
|
|
1985
|
+
}
|
|
1986
|
+
},
|
|
1987
|
+
{
|
|
1988
|
+
edges: {
|
|
1989
|
+
x: -1,
|
|
1990
|
+
y: -1
|
|
1991
|
+
},
|
|
1992
|
+
key: "nw",
|
|
1993
|
+
style: {
|
|
1994
|
+
cursor: "nwse-resize",
|
|
1995
|
+
height: 14,
|
|
1996
|
+
left: -4,
|
|
1997
|
+
top: -4,
|
|
1998
|
+
width: 14
|
|
1999
|
+
}
|
|
2000
|
+
},
|
|
2001
|
+
{
|
|
2002
|
+
edges: {
|
|
2003
|
+
x: 1,
|
|
2004
|
+
y: 1
|
|
2005
|
+
},
|
|
2006
|
+
key: "se",
|
|
2007
|
+
style: {
|
|
2008
|
+
bottom: -4,
|
|
2009
|
+
cursor: "nwse-resize",
|
|
2010
|
+
height: 14,
|
|
2011
|
+
right: -4,
|
|
2012
|
+
width: 14
|
|
2013
|
+
}
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
edges: {
|
|
2017
|
+
x: -1,
|
|
2018
|
+
y: 1
|
|
2019
|
+
},
|
|
2020
|
+
key: "sw",
|
|
2021
|
+
style: {
|
|
2022
|
+
bottom: -4,
|
|
2023
|
+
cursor: "nesw-resize",
|
|
2024
|
+
height: 14,
|
|
2025
|
+
left: -4,
|
|
2026
|
+
width: 14
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
];
|
|
2030
|
+
const EDGE_BY_KEY = new Map(RESIZE_HANDLES.map((h) => [h.key, h.edges]));
|
|
2031
|
+
const CHIP_SIZE = {
|
|
2032
|
+
height: 34,
|
|
2033
|
+
width: 168
|
|
2034
|
+
};
|
|
2035
|
+
const TAP_SLOP = 4;
|
|
2036
|
+
const FloatTitleEditor = ({ dispatch, node, onDone }) => {
|
|
2037
|
+
const ref = useRef(null);
|
|
2038
|
+
const done = useRef(false);
|
|
2039
|
+
useEffect(() => {
|
|
2040
|
+
ref.current?.focus();
|
|
2041
|
+
ref.current?.select();
|
|
2042
|
+
}, []);
|
|
2043
|
+
const commit = (value) => {
|
|
2044
|
+
const trimmed = value.trim();
|
|
2045
|
+
if (trimmed && trimmed !== floatTitle(node)) dispatch({
|
|
2046
|
+
floatId: node.id,
|
|
2047
|
+
name: trimmed,
|
|
2048
|
+
type: "renameFloat"
|
|
2049
|
+
});
|
|
2050
|
+
onDone();
|
|
2051
|
+
};
|
|
2052
|
+
const handleKeyDown = (event) => {
|
|
2053
|
+
if (event.key === "Enter") {
|
|
2054
|
+
done.current = true;
|
|
2055
|
+
commit(event.currentTarget.value);
|
|
2056
|
+
} else if (event.key === "Escape") {
|
|
2057
|
+
done.current = true;
|
|
2058
|
+
onDone();
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
2061
|
+
const handleBlur = (event) => {
|
|
2062
|
+
if (done.current) return;
|
|
2063
|
+
done.current = true;
|
|
2064
|
+
commit(event.currentTarget.value);
|
|
2065
|
+
};
|
|
2066
|
+
return /* @__PURE__ */ jsx("input", {
|
|
2067
|
+
"aria-label": `Rename ${floatTitle(node)}`,
|
|
2068
|
+
"data-dashfoo": "float-rename",
|
|
2069
|
+
defaultValue: floatTitle(node),
|
|
2070
|
+
onBlur: handleBlur,
|
|
2071
|
+
onKeyDown: handleKeyDown,
|
|
2072
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
2073
|
+
ref,
|
|
2074
|
+
style: titleStyle,
|
|
2075
|
+
type: "text"
|
|
2076
|
+
});
|
|
2077
|
+
};
|
|
2078
|
+
const FloatTitle = ({ dispatch, node, renamable }) => {
|
|
2079
|
+
const [editing, setEditing] = useState(false);
|
|
2080
|
+
if (renamable && editing) return /* @__PURE__ */ jsx(FloatTitleEditor, {
|
|
2081
|
+
dispatch,
|
|
2082
|
+
node,
|
|
2083
|
+
onDone: () => setEditing(false)
|
|
2084
|
+
});
|
|
2085
|
+
return /* @__PURE__ */ jsx("span", {
|
|
2086
|
+
"data-dashfoo": "float-title",
|
|
2087
|
+
onDoubleClick: renamable ? () => setEditing(true) : void 0,
|
|
2088
|
+
style: titleStyle,
|
|
2089
|
+
title: renamable ? "Double-click to rename" : void 0,
|
|
2090
|
+
children: floatTitle(node)
|
|
2091
|
+
});
|
|
2092
|
+
};
|
|
2093
|
+
const FloatPanel = ({ global, node, onFocus, zIndex }) => {
|
|
2094
|
+
const dispatch = useLayout((state) => state.dispatch);
|
|
2095
|
+
const closableTabs = useLayout((state) => state.closableTabs);
|
|
2096
|
+
const draggableTabs = useLayout((state) => state.draggableTabs);
|
|
2097
|
+
const editable = useLayout((state) => state.editable);
|
|
2098
|
+
const renamableTabs = useLayout((state) => state.renamableTabs);
|
|
2099
|
+
const resizableSplits = useLayout((state) => state.resizableSplits);
|
|
2100
|
+
const keepMounted = useLayout((state) => state.keepMounted);
|
|
2101
|
+
const renderTab = useLayout((state) => state.renderTab);
|
|
2102
|
+
const renderTabLabel = useLayout((state) => state.renderTabLabel);
|
|
2103
|
+
const renderTabsetToolbar = useLayout((state) => state.renderTabsetToolbar);
|
|
2104
|
+
const snap = useLayout((state) => state.snap);
|
|
2105
|
+
const panelRef = useRef(null);
|
|
2106
|
+
const gestureRef = useRef(null);
|
|
2107
|
+
const latestRef = useRef(node.geometry);
|
|
2108
|
+
const movedRef = useRef(false);
|
|
2109
|
+
const setPanel = (element) => {
|
|
2110
|
+
panelRef.current = element;
|
|
2111
|
+
};
|
|
2112
|
+
const restore = () => {
|
|
2113
|
+
if (!editable) return;
|
|
2114
|
+
dispatch({
|
|
2115
|
+
floatId: node.id,
|
|
2116
|
+
minimized: false,
|
|
2117
|
+
type: "setFloatMinimized"
|
|
2118
|
+
});
|
|
2119
|
+
};
|
|
2120
|
+
const handlePointerDown = (event) => {
|
|
2121
|
+
const panel = panelRef.current;
|
|
2122
|
+
if (!panel || !editable) return;
|
|
2123
|
+
movedRef.current = false;
|
|
2124
|
+
const edgeKey = event.currentTarget.dataset.edge;
|
|
2125
|
+
const parent = panel.offsetParent;
|
|
2126
|
+
gestureRef.current = {
|
|
2127
|
+
bounds: {
|
|
2128
|
+
height: parent?.clientHeight ?? 0,
|
|
2129
|
+
width: parent?.clientWidth ?? 0
|
|
2130
|
+
},
|
|
2131
|
+
edges: edgeKey ? EDGE_BY_KEY.get(edgeKey) ?? null : null,
|
|
2132
|
+
pointerId: event.pointerId,
|
|
2133
|
+
start: node.geometry,
|
|
2134
|
+
startX: event.clientX,
|
|
2135
|
+
startY: event.clientY
|
|
2136
|
+
};
|
|
2137
|
+
latestRef.current = node.geometry;
|
|
2138
|
+
};
|
|
2139
|
+
const handlePointerMove = (event) => {
|
|
2140
|
+
const gesture = gestureRef.current;
|
|
2141
|
+
const panel = panelRef.current;
|
|
2142
|
+
if (!gesture || !panel || event.pointerId !== gesture.pointerId) return;
|
|
2143
|
+
const dx = event.clientX - gesture.startX;
|
|
2144
|
+
const dy = event.clientY - gesture.startY;
|
|
2145
|
+
if (!movedRef.current) {
|
|
2146
|
+
if (Math.hypot(dx, dy) <= TAP_SLOP) return;
|
|
2147
|
+
movedRef.current = true;
|
|
2148
|
+
panel.setPointerCapture(gesture.pointerId);
|
|
2149
|
+
}
|
|
2150
|
+
const next = gesture.edges ? resizeRect(gesture.start, gesture.edges, dx, dy) : clampToBounds({
|
|
2151
|
+
...gesture.start,
|
|
2152
|
+
left: gesture.start.left + dx,
|
|
2153
|
+
top: gesture.start.top + dy
|
|
2154
|
+
}, gesture.bounds, node.minimized ? CHIP_SIZE : void 0);
|
|
2155
|
+
latestRef.current = next;
|
|
2156
|
+
panel.style.left = `${next.left}px`;
|
|
2157
|
+
panel.style.top = `${next.top}px`;
|
|
2158
|
+
if (!node.minimized) {
|
|
2159
|
+
panel.style.width = `${next.width}px`;
|
|
2160
|
+
panel.style.height = `${next.height}px`;
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
const handlePointerUp = (event) => {
|
|
2164
|
+
const gesture = gestureRef.current;
|
|
2165
|
+
if (!gesture || event.pointerId !== gesture.pointerId) return;
|
|
2166
|
+
gestureRef.current = null;
|
|
2167
|
+
if (!movedRef.current) {
|
|
2168
|
+
if (node.minimized) restore();
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
panelRef.current?.releasePointerCapture(event.pointerId);
|
|
2172
|
+
dispatch({
|
|
2173
|
+
floatId: node.id,
|
|
2174
|
+
geometry: latestRef.current,
|
|
2175
|
+
type: "moveFloat"
|
|
2176
|
+
});
|
|
2177
|
+
};
|
|
2178
|
+
if (node.minimized) return /* @__PURE__ */ jsxs("button", {
|
|
2179
|
+
"aria-label": `Restore ${floatTitle(node)} panel`,
|
|
2180
|
+
"data-dashfoo": "float-chip",
|
|
2181
|
+
onKeyDown: (event) => {
|
|
2182
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
2183
|
+
event.preventDefault();
|
|
2184
|
+
restore();
|
|
2185
|
+
}
|
|
2186
|
+
},
|
|
2187
|
+
onPointerDown: handlePointerDown,
|
|
2188
|
+
onPointerDownCapture: onFocus,
|
|
2189
|
+
onPointerMove: handlePointerMove,
|
|
2190
|
+
onPointerUp: handlePointerUp,
|
|
2191
|
+
ref: setPanel,
|
|
2192
|
+
style: {
|
|
2193
|
+
left: node.geometry.left,
|
|
2194
|
+
pointerEvents: "auto",
|
|
2195
|
+
position: "absolute",
|
|
2196
|
+
top: node.geometry.top,
|
|
2197
|
+
zIndex
|
|
2198
|
+
},
|
|
2199
|
+
title: floatTitle(node),
|
|
2200
|
+
type: "button",
|
|
2201
|
+
children: [/* @__PURE__ */ jsx(FloatIcon, {}), /* @__PURE__ */ jsx("span", {
|
|
2202
|
+
"data-dashfoo": "float-chip-label",
|
|
2203
|
+
children: floatTitle(node)
|
|
2204
|
+
})]
|
|
2205
|
+
});
|
|
2206
|
+
const floatModel = {
|
|
2207
|
+
global,
|
|
2208
|
+
layout: node.layout,
|
|
2209
|
+
version: 1
|
|
2210
|
+
};
|
|
2211
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2212
|
+
"data-dashfoo": "float",
|
|
2213
|
+
onPointerDownCapture: onFocus,
|
|
2214
|
+
onPointerMove: handlePointerMove,
|
|
2215
|
+
onPointerUp: handlePointerUp,
|
|
2216
|
+
ref: setPanel,
|
|
2217
|
+
style: {
|
|
2218
|
+
height: node.geometry.height,
|
|
2219
|
+
left: node.geometry.left,
|
|
2220
|
+
pointerEvents: "auto",
|
|
2221
|
+
position: "absolute",
|
|
2222
|
+
top: node.geometry.top,
|
|
2223
|
+
width: node.geometry.width,
|
|
2224
|
+
zIndex
|
|
2225
|
+
},
|
|
2226
|
+
children: [
|
|
2227
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2228
|
+
"data-dashfoo": "float-titlebar",
|
|
2229
|
+
onPointerDown: handlePointerDown,
|
|
2230
|
+
style: editable ? titleBarStyle : {
|
|
2231
|
+
...titleBarStyle,
|
|
2232
|
+
cursor: "default"
|
|
2233
|
+
},
|
|
2234
|
+
children: [
|
|
2235
|
+
/* @__PURE__ */ jsx("span", {
|
|
2236
|
+
"aria-hidden": "true",
|
|
2237
|
+
"data-dashfoo": "float-grip",
|
|
2238
|
+
children: /* @__PURE__ */ jsx(GripIcon, {})
|
|
2239
|
+
}),
|
|
2240
|
+
/* @__PURE__ */ jsx(FloatTitle, {
|
|
2241
|
+
dispatch,
|
|
2242
|
+
node,
|
|
2243
|
+
renamable: editable
|
|
2244
|
+
}),
|
|
2245
|
+
editable && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
|
|
2246
|
+
"aria-label": "Minimize panel",
|
|
2247
|
+
"data-dashfoo": "float-minimize",
|
|
2248
|
+
onClick: () => dispatch({
|
|
2249
|
+
floatId: node.id,
|
|
2250
|
+
minimized: true,
|
|
2251
|
+
type: "setFloatMinimized"
|
|
2252
|
+
}),
|
|
2253
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
2254
|
+
style: dockButtonStyle,
|
|
2255
|
+
type: "button",
|
|
2256
|
+
children: /* @__PURE__ */ jsx(MinimizeIcon, {})
|
|
2257
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
2258
|
+
"aria-label": "Dock panel back into the main layout",
|
|
2259
|
+
"data-dashfoo": "float-dock",
|
|
2260
|
+
onClick: () => dispatch({
|
|
2261
|
+
floatId: node.id,
|
|
2262
|
+
type: "dockFloat"
|
|
2263
|
+
}),
|
|
2264
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
2265
|
+
style: dockButtonStyle,
|
|
2266
|
+
type: "button",
|
|
2267
|
+
children: /* @__PURE__ */ jsx(DockIcon, {})
|
|
2268
|
+
})] })
|
|
2269
|
+
]
|
|
2270
|
+
}),
|
|
2271
|
+
/* @__PURE__ */ jsx("div", {
|
|
2272
|
+
"data-dashfoo": "float-body",
|
|
2273
|
+
style: bodyStyle,
|
|
2274
|
+
children: /* @__PURE__ */ jsx(LayoutRoot, {
|
|
2275
|
+
closableTabs,
|
|
2276
|
+
dispatch,
|
|
2277
|
+
draggableTabs,
|
|
2278
|
+
draggableTabsets: false,
|
|
2279
|
+
editable,
|
|
2280
|
+
floatable: false,
|
|
2281
|
+
keepMounted,
|
|
2282
|
+
maximizable: false,
|
|
2283
|
+
model: floatModel,
|
|
2284
|
+
renamableTabs,
|
|
2285
|
+
renderTab,
|
|
2286
|
+
renderTabLabel,
|
|
2287
|
+
renderTabsetToolbar,
|
|
2288
|
+
resizableSplits,
|
|
2289
|
+
snap: snap ?? void 0,
|
|
2290
|
+
children: /* @__PURE__ */ jsx(DragProvider, { children: /* @__PURE__ */ jsx(RowView, { node: node.layout }) })
|
|
2291
|
+
})
|
|
2292
|
+
}),
|
|
2293
|
+
editable && RESIZE_HANDLES.map((handle) => /* @__PURE__ */ jsx("div", {
|
|
2294
|
+
"data-dashfoo": "float-resize",
|
|
2295
|
+
"data-edge": handle.key,
|
|
2296
|
+
onPointerDown: handlePointerDown,
|
|
2297
|
+
style: {
|
|
2298
|
+
position: "absolute",
|
|
2299
|
+
touchAction: "none",
|
|
2300
|
+
zIndex: 1,
|
|
2301
|
+
...handle.style
|
|
2302
|
+
}
|
|
2303
|
+
}, handle.key))
|
|
2304
|
+
]
|
|
2305
|
+
});
|
|
2306
|
+
};
|
|
2307
|
+
|
|
2308
|
+
//#endregion
|
|
2309
|
+
//#region src/components/float-layer.tsx
|
|
2310
|
+
const overlayStyle = {
|
|
2311
|
+
inset: 0,
|
|
2312
|
+
pointerEvents: "none",
|
|
2313
|
+
position: "fixed"
|
|
2314
|
+
};
|
|
2315
|
+
const FloatOverlay = ({ floats, global }) => {
|
|
2316
|
+
const [topId, setTopId] = useState(null);
|
|
2317
|
+
if (floats.length === 0) return null;
|
|
2318
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2319
|
+
"data-dashfoo": "float-overlay",
|
|
2320
|
+
style: overlayStyle,
|
|
2321
|
+
children: floats.map((node) => /* @__PURE__ */ jsx(FloatPanel, {
|
|
2322
|
+
global,
|
|
2323
|
+
node,
|
|
2324
|
+
onFocus: () => setTopId(node.id),
|
|
2325
|
+
zIndex: node.id === topId ? 2 : 1
|
|
2326
|
+
}, node.id))
|
|
2327
|
+
});
|
|
2328
|
+
};
|
|
2329
|
+
const FloatLayer = ({ children, floats, global }) => /* @__PURE__ */ jsxs(FloatLayerContext.Provider, {
|
|
2330
|
+
value: true,
|
|
2331
|
+
children: [children, /* @__PURE__ */ jsx(FloatOverlay, {
|
|
2332
|
+
floats,
|
|
2333
|
+
global
|
|
2334
|
+
})]
|
|
2335
|
+
});
|
|
2336
|
+
|
|
2337
|
+
//#endregion
|
|
2338
|
+
//#region src/components/layout.tsx
|
|
2339
|
+
const Layout = {
|
|
2340
|
+
DragLayer: DragProvider,
|
|
2341
|
+
FloatLayer,
|
|
2342
|
+
Root: LayoutRoot,
|
|
2343
|
+
Rows: RowView,
|
|
2344
|
+
Tabset: TabsetView
|
|
2345
|
+
};
|
|
2346
|
+
|
|
2347
|
+
//#endregion
|
|
2348
|
+
//#region src/components/dashfoo-layout.tsx
|
|
2349
|
+
const DEFAULT_PERSIST_DEBOUNCE_MS = 300;
|
|
2350
|
+
const resolvePersist = (persist, hasDefaultModel) => {
|
|
2351
|
+
if (persist === void 0 || !hasDefaultModel) return null;
|
|
2352
|
+
if (typeof persist === "string") return {
|
|
2353
|
+
debounceMs: DEFAULT_PERSIST_DEBOUNCE_MS,
|
|
2354
|
+
key: persist,
|
|
2355
|
+
storage: localStorageAdapter
|
|
2356
|
+
};
|
|
2357
|
+
return {
|
|
2358
|
+
debounceMs: persist.debounceMs ?? DEFAULT_PERSIST_DEBOUNCE_MS,
|
|
2359
|
+
key: persist.key,
|
|
2360
|
+
storage: persist.storage ?? localStorageAdapter
|
|
2361
|
+
};
|
|
2362
|
+
};
|
|
2363
|
+
const warnedComponents = /* @__PURE__ */ new Set();
|
|
2364
|
+
const DashfooLayout = forwardRef((props, ref) => {
|
|
2365
|
+
const { closableTabs = true, components, defaultModel, draggableTabs = true, draggableTabsets = true, editable = true, factory, floatable = false, keepMounted = false, maximizable = true, model, onAction, onActiveTabsetChange, onMaximizedTabsetChange, onModelChange, persist, renamableTabs = true, renderTabLabel, renderTabsetToolbar, resizableSplits = true, responsive, snap } = props;
|
|
2366
|
+
const persistence = usePersistence(useMemo(() => resolvePersist(persist, defaultModel !== void 0), [persist, defaultModel]), defaultModel);
|
|
2367
|
+
const handleModelChange = useCallback((next, action) => {
|
|
2368
|
+
persistence.save(next);
|
|
2369
|
+
onModelChange?.(next, action);
|
|
2370
|
+
}, [onModelChange, persistence]);
|
|
2371
|
+
const store = useDashfooStore({
|
|
2372
|
+
defaultModel: persistence.initialModel ?? defaultModel,
|
|
2373
|
+
model,
|
|
2374
|
+
onAction,
|
|
2375
|
+
onActiveTabsetChange,
|
|
2376
|
+
onMaximizedTabsetChange,
|
|
2377
|
+
onModelChange: handleModelChange
|
|
2378
|
+
});
|
|
2379
|
+
const hasSharedManager = useContext(SharedDragManagerContext) !== null;
|
|
2380
|
+
useImperativeHandle(ref, () => ({
|
|
2381
|
+
addTab: (tab, target) => store.dispatch({
|
|
2382
|
+
index: target.index,
|
|
2383
|
+
location: target.location ?? "center",
|
|
2384
|
+
tab,
|
|
2385
|
+
targetId: target.targetId,
|
|
2386
|
+
type: "addNode"
|
|
2387
|
+
}),
|
|
2388
|
+
canRedo: store.canRedo,
|
|
2389
|
+
canUndo: store.canUndo,
|
|
2390
|
+
closeTab: (tabId) => store.dispatch({
|
|
2391
|
+
tabId,
|
|
2392
|
+
type: "deleteTab"
|
|
2393
|
+
}),
|
|
2394
|
+
dispatch: store.dispatch,
|
|
2395
|
+
dockFloat: (floatId) => store.dispatch({
|
|
2396
|
+
floatId,
|
|
2397
|
+
type: "dockFloat"
|
|
2398
|
+
}),
|
|
2399
|
+
floatTab: (tabId) => store.dispatch({
|
|
2400
|
+
tabId,
|
|
2401
|
+
type: "floatTab"
|
|
2402
|
+
}),
|
|
2403
|
+
getModel: () => store.model,
|
|
2404
|
+
maximizeTabset: (tabsetId) => store.dispatch({
|
|
2405
|
+
tabsetId,
|
|
2406
|
+
type: "setMaximizedTabset"
|
|
2407
|
+
}),
|
|
2408
|
+
redo: store.redo,
|
|
2409
|
+
renameTab: (tabId, name) => store.dispatch({
|
|
2410
|
+
name,
|
|
2411
|
+
tabId,
|
|
2412
|
+
type: "renameTab"
|
|
2413
|
+
}),
|
|
2414
|
+
resetLayout: () => {
|
|
2415
|
+
persistence.clear();
|
|
2416
|
+
if (defaultModel !== void 0) store.setModel(defaultModel);
|
|
2417
|
+
},
|
|
2418
|
+
selectTab: (tabsetId, index) => store.dispatch({
|
|
2419
|
+
index,
|
|
2420
|
+
tabsetId,
|
|
2421
|
+
type: "selectTab"
|
|
2422
|
+
}),
|
|
2423
|
+
undo: store.undo
|
|
2424
|
+
}), [
|
|
2425
|
+
defaultModel,
|
|
2426
|
+
persistence,
|
|
2427
|
+
store
|
|
2428
|
+
]);
|
|
2429
|
+
const renderTab = useCallback((tab) => {
|
|
2430
|
+
if (factory) return factory(tab);
|
|
2431
|
+
const Component = components?.[tab.component];
|
|
2432
|
+
if (!Component) {
|
|
2433
|
+
if (components && !warnedComponents.has(tab.component)) {
|
|
2434
|
+
warnedComponents.add(tab.component);
|
|
2435
|
+
console.warn(`[dashfoo] no component registered for "${tab.component}"`);
|
|
2436
|
+
}
|
|
2437
|
+
return null;
|
|
2438
|
+
}
|
|
2439
|
+
return /* @__PURE__ */ jsx(Component, { node: tab });
|
|
2440
|
+
}, [components, factory]);
|
|
2441
|
+
const [containerRef, width] = useContainerWidth();
|
|
2442
|
+
const isCompact = responsive !== void 0 && width <= responsive.maxWidth;
|
|
2443
|
+
const view = useMemo(() => isCompact ? stackModel(store.model, responsive?.orientation) : store.model, [
|
|
2444
|
+
isCompact,
|
|
2445
|
+
responsive?.orientation,
|
|
2446
|
+
store.model
|
|
2447
|
+
]);
|
|
2448
|
+
const isCompactRef = useRef(isCompact);
|
|
2449
|
+
isCompactRef.current = isCompact;
|
|
2450
|
+
const dispatch = useCallback((action) => {
|
|
2451
|
+
if (isCompactRef.current && action.type === "adjustSplit") return;
|
|
2452
|
+
store.dispatch(action);
|
|
2453
|
+
}, [store]);
|
|
2454
|
+
const maximized = view.maximizedTabsetId ? findTabset(view, view.maximizedTabsetId) : void 0;
|
|
2455
|
+
const tree = /* @__PURE__ */ jsx(Layout.FloatLayer, {
|
|
2456
|
+
floats: store.model.floats ?? [],
|
|
2457
|
+
global: store.model.global,
|
|
2458
|
+
children: /* @__PURE__ */ jsx(Layout.DragLayer, { children: maximized ? /* @__PURE__ */ jsx(Layout.Tabset, { node: maximized }) : /* @__PURE__ */ jsx(Layout.Rows, { node: view.layout }) })
|
|
2459
|
+
});
|
|
2460
|
+
return /* @__PURE__ */ jsx(Layout.Root, {
|
|
2461
|
+
closableTabs,
|
|
2462
|
+
dispatch,
|
|
2463
|
+
draggableTabs: isCompact ? false : draggableTabs,
|
|
2464
|
+
draggableTabsets: isCompact ? false : draggableTabsets,
|
|
2465
|
+
editable,
|
|
2466
|
+
floatable: isCompact ? false : floatable,
|
|
2467
|
+
keepMounted,
|
|
2468
|
+
maximizable,
|
|
2469
|
+
model: view,
|
|
2470
|
+
renamableTabs,
|
|
2471
|
+
renderTab,
|
|
2472
|
+
renderTabLabel,
|
|
2473
|
+
renderTabsetToolbar,
|
|
2474
|
+
resizableSplits: isCompact ? false : resizableSplits,
|
|
2475
|
+
rootRef: containerRef,
|
|
2476
|
+
snap,
|
|
2477
|
+
children: hasSharedManager ? tree : /* @__PURE__ */ jsx(DashfooDragProvider, { children: tree })
|
|
2478
|
+
});
|
|
2479
|
+
});
|
|
2480
|
+
DashfooLayout.displayName = "DashfooLayout";
|
|
2481
|
+
|
|
2482
|
+
//#endregion
|
|
2483
|
+
//#region src/components/panel.tsx
|
|
2484
|
+
const PanelRoot = (props) => /* @__PURE__ */ jsx("div", {
|
|
2485
|
+
...props,
|
|
2486
|
+
"data-dashfoo": "panel"
|
|
2487
|
+
});
|
|
2488
|
+
const PanelHeader = (props) => /* @__PURE__ */ jsx("div", {
|
|
2489
|
+
...props,
|
|
2490
|
+
"data-dashfoo": "panel-header"
|
|
2491
|
+
});
|
|
2492
|
+
const PanelBody = (props) => /* @__PURE__ */ jsx("div", {
|
|
2493
|
+
...props,
|
|
2494
|
+
"data-dashfoo": "panel-body"
|
|
2495
|
+
});
|
|
2496
|
+
const PanelTitle = (props) => /* @__PURE__ */ jsx("span", {
|
|
2497
|
+
...props,
|
|
2498
|
+
"data-dashfoo": "panel-title"
|
|
2499
|
+
});
|
|
2500
|
+
const PanelIcon = (props) => /* @__PURE__ */ jsx("span", {
|
|
2501
|
+
...props,
|
|
2502
|
+
"data-dashfoo": "panel-icon"
|
|
2503
|
+
});
|
|
2504
|
+
const PanelBadge = (props) => /* @__PURE__ */ jsx("span", {
|
|
2505
|
+
...props,
|
|
2506
|
+
"data-dashfoo": "panel-badge"
|
|
2507
|
+
});
|
|
2508
|
+
const Panel = {
|
|
2509
|
+
Badge: PanelBadge,
|
|
2510
|
+
Body: PanelBody,
|
|
2511
|
+
Header: PanelHeader,
|
|
2512
|
+
Icon: PanelIcon,
|
|
2513
|
+
Root: PanelRoot,
|
|
2514
|
+
Title: PanelTitle
|
|
2515
|
+
};
|
|
2516
|
+
|
|
2517
|
+
//#endregion
|
|
2518
|
+
//#region src/components/tabset/tabset.ts
|
|
2519
|
+
const Tabset = {
|
|
2520
|
+
CloseButton: TabsetCloseButton,
|
|
2521
|
+
Content: TabsetContent,
|
|
2522
|
+
FloatButton: TabsetFloatButton,
|
|
2523
|
+
Grip: TabsetGrip,
|
|
2524
|
+
MaximizeButton: TabsetMaximizeButton,
|
|
2525
|
+
OverflowMenu: TabsetOverflowMenu,
|
|
2526
|
+
RenameInput: TabsetRenameInput,
|
|
2527
|
+
Root: TabsetRoot,
|
|
2528
|
+
Tab: TabsetTab,
|
|
2529
|
+
Tablist: TabsetTablist,
|
|
2530
|
+
TabStrip: TabsetTabStrip,
|
|
2531
|
+
Toolbar: TabsetToolbar,
|
|
2532
|
+
Trigger: TabsetTrigger
|
|
2533
|
+
};
|
|
2534
|
+
|
|
2535
|
+
//#endregion
|
|
2536
|
+
export { DashfooDragProvider, DashfooLayout, Layout, Panel, Tabset, activeBreakpoint, localStorageAdapter, matchBreakpoint, memoryStorageAdapter, useContainerWidth, useDashfooStore, useDragSubject, useDropIntent, useExternalTabSource, useLayout, usePersistence, useResponsiveModel, useTab, useTabset };
|
|
9
2537
|
//# sourceMappingURL=index.js.map
|