@dashfoo/core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/index.d.ts +210 -884
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/state/actions.ts","../src/model/schema.ts","../src/model/builders.ts","../src/geometry/geometry.ts","../src/state/history.ts","../src/model/ids.ts","../src/model/invariants.ts","../src/machines/dashfoo-machine.ts","../src/machines/drag-dock-machine.ts","../src/state/reducer.ts","../src/model/serialize.ts","../src/model/stack.ts","../src/model/tree.ts"],"mappings":";;;cAwCM,sBAAA,EAAsB,CAAA,CAAA,QAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/state/actions.ts","../src/model/schema.ts","../src/model/builders.ts","../src/geometry/geometry.ts","../src/state/history.ts","../src/model/ids.ts","../src/model/invariants.ts","../src/machines/dashfoo-machine.ts","../src/machines/drag-dock-machine.ts","../src/state/reducer.ts","../src/model/serialize.ts","../src/model/stack.ts","../src/model/tree.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{z as e}from"zod";import{assign as t,emit as n,setup as r}from"xstate";const i=e.lazy(()=>e.union([e.string(),e.number(),e.boolean(),e.null(),e.array(i),e.record(e.string(),i)])),a=e.enum([`top`,`bottom`,`left`,`right`]),o=e.enum([`px`,`%`,`em`,`rem`,`vh`,`vw`]),s=e.enum([`row`,`column`]),c=e.object({unit:o,value:e.number()}),l=e.object({component:e.string(),config:i.optional(),enableClose:e.boolean().optional(),enableDrag:e.boolean().optional(),enableRename:e.boolean().optional(),id:e.string(),name:e.string(),type:e.literal(`tab`)}),u=e.object({children:e.array(l),enableClose:e.boolean().optional(),enableMaximize:e.boolean().optional(),id:e.string(),max:c.optional(),min:c.optional(),name:e.string().optional(),selected:e.number().int(),type:e.literal(`tabset`),weight:e.number().optional()}),d=e.lazy(()=>e.object({children:e.array(e.union([d,u])),id:e.string(),max:c.optional(),min:c.optional(),orientation:s,type:e.literal(`row`),weight:e.number().optional()})),f=e.object({enableSplitDock:e.boolean().optional(),splitterSize:e.number().optional(),tabEnableClose:e.boolean().optional(),tabEnableRename:e.boolean().optional(),tabLocation:e.enum([`top`,`bottom`]).optional(),tabSetEnableMaximize:e.boolean().optional(),tabSetEnableTabStrip:e.boolean().optional(),tabSetMinSize:e.number().optional()}),p=e.object({activeTabsetId:e.string().optional(),global:f,layout:d,maximizedTabsetId:e.string().optional(),version:e.literal(1)}),m=l.pick({config:!0,enableClose:!0,enableDrag:!0,enableRename:!0,name:!0}).partial(),h=u.pick({enableClose:!0,enableMaximize:!0,max:!0,min:!0,selected:!0,weight:!0}).partial(),ee=e.object({max:c.optional(),min:c.optional(),orientation:s,weight:e.number()}).partial(),g=e.union([m,h,ee]),_=e.enum([`center`,`split-bottom`,`split-left`,`split-right`,`split-top`]),v=e.discriminatedUnion(`type`,[e.object({index:e.number().int().optional(),location:_,tab:l,targetId:e.string(),type:e.literal(`addNode`)}),e.object({index:e.number().int().optional(),location:_,sourceId:e.string(),targetId:e.string(),type:e.literal(`moveNode`)}),e.object({index:e.number().int().optional(),location:_,sourceId:e.string(),targetId:e.string(),type:e.literal(`moveTabset`)}),e.object({index:e.number().int(),tabsetId:e.string(),type:e.literal(`selectTab`)}),e.object({tabsetId:e.string(),type:e.literal(`setActiveTabset`)}),e.object({tabsetId:e.string().nullable(),type:e.literal(`setMaximizedTabset`)}),e.object({name:e.string(),tabId:e.string(),type:e.literal(`renameTab`)}),e.object({tabId:e.string(),type:e.literal(`deleteTab`)}),e.object({tabsetId:e.string(),type:e.literal(`deleteTabset`)}),e.object({rowId:e.string(),type:e.literal(`adjustSplit`),weights:e.array(e.number())}),e.object({attrs:g,nodeId:e.string(),type:e.literal(`updateNodeAttributes`)}),e.object({attrs:f.partial(),type:e.literal(`updateGlobalAttributes`)})]),y=(e=`node`)=>`${e}-${crypto.randomUUID()}`,b=()=>y(`tab`),x=(e,t)=>{for(let n of e.children)n.type===`tabset`?t.push(n):x(n,t)},S=e=>{let t=[];return x(e.layout,t),t},te=e=>S(e)[0],C=(e,t)=>S(e).find(e=>e.id===t),w=(e,t)=>{let n=e.children.findIndex(e=>e.id===t);if(n===-1)return;let r=e.children[n];if(r)return{container:e,index:n,tab:r}},T=(e,t)=>{for(let n of S(e)){let e=w(n,t);if(e)return e}},E=(e,t)=>{if(e.id===t)return e;for(let n of e.children)if(n.type===`row`){let e=E(n,t);if(e)return e}},D=(e,t)=>{if(e.id===t)return e;for(let n of e.children){if(n.type===`row`){let e=D(n,t);if(e)return e;continue}if(n.id===t)return n;let e=n.children.find(e=>e.id===t);if(e)return e}},O=(e,t)=>D(e.layout,t),k=(e,t)=>{let n=e.children.findIndex(e=>e.type===`tabset`&&e.id===t);if(n!==-1)return{index:n,parent:e};for(let n of e.children)if(n.type===`row`){let e=k(n,t);if(e)return e}},A=(e,t)=>{t.push(e.id);for(let n of e.children)if(n.type===`row`)A(n,t);else{t.push(n.id);for(let e of n.children)t.push(e.id)}},j=e=>{let t=[];A(e.layout,t);let n=new Set,r=new Set;for(let e of t)n.has(e)&&r.add(e),n.add(e);return[...r]},M=e=>typeof e==`number`?{unit:`px`,value:e}:e,N=(e,t,n={})=>({component:e,name:t,type:`tab`,...n,id:n.id??e}),ne=(e,t={})=>{let{max:n,min:r,...i}=t;return{children:e,type:`tabset`,...i,...n===void 0?{}:{max:M(n)},...r===void 0?{}:{min:M(r)},id:t.id??y(`tabset`),selected:t.selected??0}},re=(e,t={})=>{let{max:n,min:r,...i}=t;return{children:e,type:`row`,...i,...n===void 0?{}:{max:M(n)},...r===void 0?{}:{min:M(r)},id:t.id??y(`row`),orientation:t.orientation??`row`}},ie=(e,t={})=>{let n={layout:e,version:1,...t,global:t.global??{}},r=j(n);return r.length>0&&console.warn(`[dashfoo] builder produced duplicate node ids: ${r.join(`, `)} — pass explicit ids when reusing a component`),n},ae=(e,t)=>{let n=(e.x-t.x)/t.width,r=(e.y-t.y)/t.height;return{bottom:1-r,left:n,right:1-n,top:r}},oe=e=>{let t=Math.min(e.left,e.right,e.top,e.bottom);return t===e.left?`left`:t===e.right?`right`:t===e.top?`top`:`bottom`},P=(e,t,n)=>{let r=n?.bandFraction??.22;if(t.width<=0||t.height<=0)return{kind:`tab`};let i=ae(e,t);return Math.min(i.left,i.right,i.top,i.bottom)>r?{kind:`tab`}:{edge:oe(i),kind:`split`}},F=(e,t)=>{let n=e.width/2,r=e.height/2;switch(t){case`split-bottom`:return{height:r,width:e.width,x:e.x,y:e.y+r};case`split-left`:return{height:e.height,width:n,x:e.x,y:e.y};case`split-right`:return{height:e.height,width:n,x:e.x+n,y:e.y};case`split-top`:return{height:r,width:e.width,x:e.x,y:e.y};default:return e}},I=(e,t)=>{if(e===0)return 0;let n=Number.isFinite(t)?Math.trunc(t):0;return n<0?0:n>e-1?e-1:n},L=e=>({...e,selected:I(e.children.length,e.selected)}),R=e=>{let t=[];for(let n of e){if(n.type===`tabset`){n.children.length>0&&t.push(L(n));continue}let e=R(n.children);if(e.length!==0){if(e.length===1){let r=e[0];r!==void 0&&t.push(n.weight===void 0?r:{...r,weight:n.weight});continue}t.push({...n,children:e})}}return t},z=e=>{let t=R(e.children),n=e.orientation,r=t[0];for(;t.length===1&&r?.type===`row`;)t=r.children,n=r.orientation,r=t[0];return{...e,children:t,orientation:n}},B=e=>{let t=z(e.layout),n={...e,layout:t},r=new Set(S(n).map(e=>e.id)),i=S(n)[0]?.id,a=e.activeTabsetId!==void 0&&r.has(e.activeTabsetId)?e.activeTabsetId:i,o=e.maximizedTabsetId!==void 0&&r.has(e.maximizedTabsetId)?e.maximizedTabsetId:void 0;return{...n,activeTabsetId:a,maximizedTabsetId:o}},V=e=>{throw Error(`Unhandled action: ${JSON.stringify(e)}`)},H=(e,t)=>{let n=e.children.findIndex(e=>e.type===`tabset`&&e.id===t);if(n!==-1)return e.children.splice(n,1),!0;for(let n of e.children)if(n.type===`row`&&H(n,t))return!0;return!1},U=(e,t)=>{let n=e.children.findIndex(e=>e.type===`tabset`&&e.id===t);if(n!==-1){let[t]=e.children.splice(n,1);return t?.type===`tabset`?t:void 0}for(let n of e.children)if(n.type===`row`){let e=U(n,t);if(e)return e}},W=e=>e===`split-left`||e===`split-right`?`row`:`column`,G=e=>e===`split-left`||e===`split-top`,K=(e,t,n,r)=>{let i=C(e,n),a=k(e.layout,n);if(!i||!a)return;let o=W(r),s=G(r);if(a.parent.orientation===o){let e=i.weight??100;i.weight=e/2,t.weight=e/2,a.parent.children.splice(s?a.index:a.index+1,0,t)}else{let e=i.weight;i.weight=50,t.weight=50;let n={children:s?[t,i]:[i,t],id:y(`row`),orientation:o,type:`row`};e!==void 0&&(n.weight=e),a.parent.children.splice(a.index,1,n)}e.activeTabsetId=t.id},q=(e,t,n)=>{let{id:r,index:i,location:a}=n,o=C(e,r);if(o){if(a===`center`){let e=i??o.children.length;o.children.splice(e,0,t),o.selected=e;return}K(e,{children:[t],id:y(`tabset`),selected:0,type:`tabset`,weight:50},r,a)}},se=(e,t)=>{switch(t.type){case`addNode`:q(e,t.tab,{id:t.targetId,index:t.index,location:t.location});return;case`adjustSplit`:{let n=E(e.layout,t.rowId);if(!n)return;for(let e=0;e<t.weights.length;e++){let r=n.children[e],i=t.weights[e];!r||i===void 0||(r.weight=i)}return}case`deleteTab`:{let n=T(e,t.tabId);n&&n.container.children.splice(n.index,1);return}case`deleteTabset`:H(e.layout,t.tabsetId);return;case`moveNode`:{let n=T(e,t.sourceId);if(!n)return;let[r]=n.container.children.splice(n.index,1);if(!r)return;q(e,r,{id:t.targetId,index:t.index,location:t.location});return}case`moveTabset`:{if(t.sourceId===t.targetId)return;let n=C(e,t.sourceId);if(!n)return;if(t.location===`center`){let r=C(e,t.targetId);r&&(r.children.push(...n.children),H(e.layout,t.sourceId));return}if(t.location.startsWith(`split-`)){let n=U(e.layout,t.sourceId);n&&K(e,n,t.targetId,t.location)}return}case`renameTab`:{let n=T(e,t.tabId);n&&(n.tab.name=t.name);return}case`selectTab`:{let n=C(e,t.tabsetId);n&&(n.selected=t.index);return}case`setActiveTabset`:C(e,t.tabsetId)&&(e.activeTabsetId=t.tabsetId);return;case`setMaximizedTabset`:e.maximizedTabsetId=t.tabsetId??void 0;return;case`updateGlobalAttributes`:Object.assign(e.global,t.attrs);return;case`updateNodeAttributes`:{let n=O(e,t.nodeId);n&&Object.assign(n,t.attrs);return}default:V(t)}},J=(e,t)=>{let n=structuredClone(e);return se(n,t),B(n)},Y=e=>({future:[],past:[],present:e}),ce=e=>e.past.length>0,le=e=>e.future.length>0,X=(e,t)=>{let n=J(e.present,t);return{future:[],past:[...e.past,e.present],present:n}},Z=e=>{let t=e.past.at(-1);return t===void 0?e:{future:[e.present,...e.future],past:e.past.slice(0,-1),present:t}},Q=e=>{let[t,...n]=e.future;return t===void 0?e:{future:n,past:[...e.past,e.present],present:t}},ue=r({types:{context:{},events:{},input:{}}}).createMachine({context:({input:e})=>({history:Y(e.model)}),id:`dashfoo`,initial:`ready`,states:{ready:{on:{DISPATCH:{actions:t({history:({context:e,event:t})=>X(e.history,t.action)})},REDO:{actions:t({history:({context:e})=>Q(e.history)})},SET_MODEL:{actions:t({history:({event:e})=>Y(e.model)})},UNDO:{actions:t({history:({context:e})=>Z(e.history)})}}}}}),de=e=>{if(e.subject===null||e.intent===null)throw Error(`drop requires a subject and a drop intent`);return{intent:e.intent,subject:e.subject}},fe=r({guards:{hasValidDrop:({context:e})=>e.subject!==null&&e.intent!==null},types:{context:{},emitted:{},events:{}}}).createMachine({context:{intent:null,subject:null},id:`dragDock`,initial:`idle`,states:{dragging:{on:{CANCEL:{target:`idle`},DROP:[{actions:n(({context:e})=>{let{intent:t,subject:n}=de(e);return{action:{index:t.index,location:t.location,sourceId:n.id,targetId:t.targetId,type:n.kind===`tabset`?`moveTabset`:`moveNode`},type:`COMMIT`}}),guard:`hasValidDrop`,target:`idle`},{target:`idle`}],OVER:{actions:t({intent:({event:e})=>e.intent})}}},idle:{entry:t({intent:null,subject:null}),on:{START:{actions:t({subject:({event:e})=>e.subject}),target:`dragging`}}}}}),$=e=>{let t=B(p.parse(e)),n=j(t);return n.length>0&&console.warn(`[dashfoo] duplicate node ids in the loaded layout: ${n.join(`, `)}`),t},pe=e=>JSON.stringify(e),me=e=>$(JSON.parse(e)),he=(e,t=`column`)=>{let n=[];for(let t of S(e))n.push({...t,weight:1});return B({...e,layout:{children:n,id:e.layout.id,orientation:t,type:`row`},maximizedTabsetId:void 0})};export{v as actionSchema,le as canRedo,ce as canUndo,S as collectTabsets,Y as createHistory,y as createNodeId,b as createTabId,ue as dashfooMachine,p as dashfooSchema,c as dimensionSchema,X as dispatch,_ as dockLocationSchema,fe as dragDockMachine,a as edgeSchema,O as findAttributedNode,j as findDuplicateIds,E as findRow,T as findTab,C as findTabset,k as findTabsetParent,me as fromJSON,te as getFirstTabset,f as globalAttributesSchema,i as jsonValueSchema,ie as model,g as mutableNodeAttrsSchema,B as normalize,s as orientationSchema,$ as parseModel,Q as redo,J as reducer,P as resolveDockTarget,re as row,d as rowNodeSchema,he as stackModel,N as tab,l as tabNodeSchema,ne as tabset,u as tabsetNodeSchema,pe as toJSON,Z as undo,o as unitSchema,F as zoneRect};
|
|
1
|
+
import{z as e}from"zod";import{assign as t,emit as n,setup as r}from"xstate";const i=e.lazy(()=>e.union([e.string(),e.number(),e.boolean(),e.null(),e.array(i),e.record(e.string(),i)])),a=e.enum([`top`,`bottom`,`left`,`right`]),o=e.enum([`px`,`%`,`em`,`rem`,`vh`,`vw`]),s=e.enum([`row`,`column`]),c=e.object({unit:o,value:e.number()}),l=e.object({component:e.string(),config:i.optional(),enableClose:e.boolean().optional(),enableDrag:e.boolean().optional(),enableRename:e.boolean().optional(),id:e.string(),name:e.string(),type:e.literal(`tab`)}),u=e.object({children:e.array(l),enableClose:e.boolean().optional(),enableMaximize:e.boolean().optional(),id:e.string(),max:c.optional(),min:c.optional(),name:e.string().optional(),selected:e.number().int(),type:e.literal(`tabset`),weight:e.number().optional()}),d=e.lazy(()=>e.object({children:e.array(e.union([d,u])),id:e.string(),max:c.optional(),min:c.optional(),orientation:s,type:e.literal(`row`),weight:e.number().optional()})),f=e.object({enableSplitDock:e.boolean().optional(),splitterSize:e.number().optional(),tabEnableClose:e.boolean().optional(),tabEnableRename:e.boolean().optional(),tabLocation:e.enum([`top`,`bottom`]).optional(),tabSetEnableMaximize:e.boolean().optional(),tabSetEnableTabStrip:e.boolean().optional(),tabSetMinSize:e.number().optional()}),p=e.object({activeTabsetId:e.string().optional(),global:f,layout:d,maximizedTabsetId:e.string().optional(),version:e.literal(1)}),m=l.pick({config:!0,enableClose:!0,enableDrag:!0,enableRename:!0,name:!0}).partial(),h=u.pick({enableClose:!0,enableMaximize:!0,max:!0,min:!0,selected:!0,weight:!0}).partial(),ee=e.object({max:c.optional(),min:c.optional(),orientation:s,weight:e.number()}).partial(),g=e.union([m,h,ee]),_=e.enum([`center`,`split-bottom`,`split-left`,`split-right`,`split-top`]),v=e.discriminatedUnion(`type`,[e.object({index:e.number().int().optional(),location:_,tab:l,targetId:e.string(),type:e.literal(`addNode`)}),e.object({index:e.number().int().optional(),location:_,sourceId:e.string(),targetId:e.string(),type:e.literal(`moveNode`)}),e.object({index:e.number().int().optional(),location:_,sourceId:e.string(),targetId:e.string(),type:e.literal(`moveTabset`)}),e.object({index:e.number().int(),tabsetId:e.string(),type:e.literal(`selectTab`)}),e.object({tabsetId:e.string(),type:e.literal(`setActiveTabset`)}),e.object({tabsetId:e.string().nullable(),type:e.literal(`setMaximizedTabset`)}),e.object({name:e.string(),tabId:e.string(),type:e.literal(`renameTab`)}),e.object({tabId:e.string(),type:e.literal(`deleteTab`)}),e.object({tabsetId:e.string(),type:e.literal(`deleteTabset`)}),e.object({rowId:e.string(),type:e.literal(`adjustSplit`),weights:e.array(e.number())}),e.object({attrs:g,nodeId:e.string(),type:e.literal(`updateNodeAttributes`)}),e.object({attrs:f.partial(),type:e.literal(`updateGlobalAttributes`)})]),y=(e=`node`)=>`${e}-${crypto.randomUUID()}`,b=()=>y(`tab`),x=(e,t)=>{for(let n of e.children)n.type===`tabset`?t.push(n):x(n,t)},S=e=>{let t=[];return x(e.layout,t),t},te=e=>S(e)[0],C=(e,t)=>S(e).find(e=>e.id===t),w=(e,t)=>{let n=e.children.findIndex(e=>e.id===t);if(n===-1)return;let r=e.children[n];if(r)return{container:e,index:n,tab:r}},T=(e,t)=>{for(let n of S(e)){let e=w(n,t);if(e)return e}},E=(e,t)=>{if(e.id===t)return e;for(let n of e.children)if(n.type===`row`){let e=E(n,t);if(e)return e}},D=(e,t)=>{if(e.id===t)return e;for(let n of e.children){if(n.type===`row`){let e=D(n,t);if(e)return e;continue}if(n.id===t)return n;let e=n.children.find(e=>e.id===t);if(e)return e}},O=(e,t)=>D(e.layout,t),k=(e,t)=>{let n=e.children.findIndex(e=>e.type===`tabset`&&e.id===t);if(n!==-1)return{index:n,parent:e};for(let n of e.children)if(n.type===`row`){let e=k(n,t);if(e)return e}},A=(e,t)=>{t.push(e.id);for(let n of e.children)if(n.type===`row`)A(n,t);else{t.push(n.id);for(let e of n.children)t.push(e.id)}},j=e=>{let t=[];A(e.layout,t);let n=new Set,r=new Set;for(let e of t)n.has(e)&&r.add(e),n.add(e);return[...r]},M=e=>typeof e==`number`?{unit:`px`,value:e}:e,N=(e,t,n={})=>({component:e,name:t,type:`tab`,...n,id:n.id??e}),ne=(e,t={})=>{let{max:n,min:r,...i}=t;return{children:e,type:`tabset`,...i,...n===void 0?{}:{max:M(n)},...r===void 0?{}:{min:M(r)},id:t.id??y(`tabset`),selected:t.selected??0}},re=(e,t={})=>{let{max:n,min:r,...i}=t;return{children:e,type:`row`,...i,...n===void 0?{}:{max:M(n)},...r===void 0?{}:{min:M(r)},id:t.id??y(`row`),orientation:t.orientation??`row`}},ie=(e,t={})=>{let n={layout:e,version:1,...t,global:t.global??{}},r=j(n);return r.length>0&&console.warn(`[dashfoo] builder produced duplicate node ids: ${r.join(`, `)} — pass explicit ids when reusing a component`),n},ae=(e,t)=>{let n=(e.x-t.x)/t.width,r=(e.y-t.y)/t.height;return{bottom:1-r,left:n,right:1-n,top:r}},oe=e=>{let t=Math.min(e.left,e.right,e.top,e.bottom);return t===e.left?`left`:t===e.right?`right`:t===e.top?`top`:`bottom`},P=(e,t,n)=>{let r=n?.bandFraction??.22;if(t.width<=0||t.height<=0)return{kind:`tab`};let i=ae(e,t);return Math.min(i.left,i.right,i.top,i.bottom)>r?{kind:`tab`}:{edge:oe(i),kind:`split`}},F=(e,t)=>{let n=e.width/2,r=e.height/2;switch(t){case`split-bottom`:return{height:r,width:e.width,x:e.x,y:e.y+r};case`split-left`:return{height:e.height,width:n,x:e.x,y:e.y};case`split-right`:return{height:e.height,width:n,x:e.x+n,y:e.y};case`split-top`:return{height:r,width:e.width,x:e.x,y:e.y};default:return e}},I=(e,t)=>{if(e===0)return 0;let n=Number.isFinite(t)?Math.trunc(t):0;return n<0?0:n>e-1?e-1:n},L=e=>({...e,selected:I(e.children.length,e.selected)}),R=e=>{let t=[];for(let n of e){if(n.type===`tabset`){n.children.length>0&&t.push(L(n));continue}let e=R(n.children);if(e.length!==0){if(e.length===1){let r=e[0];r!==void 0&&t.push(n.weight===void 0?r:{...r,weight:n.weight});continue}t.push({...n,children:e})}}return t},z=e=>{let t=R(e.children),n=e.orientation,r=t[0];for(;t.length===1&&r?.type===`row`;)t=r.children,n=r.orientation,r=t[0];return{...e,children:t,orientation:n}},B=e=>{let t=z(e.layout),n={...e,layout:t},r=new Set(S(n).map(e=>e.id)),i=S(n)[0]?.id,a=e.activeTabsetId!==void 0&&r.has(e.activeTabsetId)?e.activeTabsetId:i,o=e.maximizedTabsetId!==void 0&&r.has(e.maximizedTabsetId)?e.maximizedTabsetId:void 0;return{...n,activeTabsetId:a,maximizedTabsetId:o}},V=e=>{throw Error(`Unhandled action: ${JSON.stringify(e)}`)},H=(e,t)=>{let n=e.children.findIndex(e=>e.type===`tabset`&&e.id===t);if(n!==-1)return e.children.splice(n,1),!0;for(let n of e.children)if(n.type===`row`&&H(n,t))return!0;return!1},U=(e,t)=>{let n=e.children.findIndex(e=>e.type===`tabset`&&e.id===t);if(n!==-1){let[t]=e.children.splice(n,1);return t?.type===`tabset`?t:void 0}for(let n of e.children)if(n.type===`row`){let e=U(n,t);if(e)return e}},W=e=>e===`split-left`||e===`split-right`?`row`:`column`,G=e=>e===`split-left`||e===`split-top`,K=(e,t,n,r)=>{let i=C(e,n),a=k(e.layout,n);if(!i||!a)return;let o=W(r),s=G(r);if(a.parent.orientation===o){let e=i.weight??100;i.weight=e/2,t.weight=e/2,a.parent.children.splice(s?a.index:a.index+1,0,t)}else{let e=i.weight;i.weight=50,t.weight=50;let n={children:s?[t,i]:[i,t],id:y(`row`),orientation:o,type:`row`};e!==void 0&&(n.weight=e),a.parent.children.splice(a.index,1,n)}e.activeTabsetId=t.id},q=(e,t,n)=>{let{id:r,index:i,location:a}=n,o=C(e,r);if(o){if(a===`center`){let e=i??o.children.length;o.children.splice(e,0,t),o.selected=e;return}K(e,{children:[t],id:y(`tabset`),selected:0,type:`tabset`,weight:50},r,a)}},se=(e,t)=>{switch(t.type){case`addNode`:q(e,t.tab,{id:t.targetId,index:t.index,location:t.location});return;case`adjustSplit`:{let n=E(e.layout,t.rowId);if(!n)return;for(let e=0;e<t.weights.length;e++){let r=n.children[e],i=t.weights[e];!r||i===void 0||(r.weight=i)}return}case`deleteTab`:{let n=T(e,t.tabId);n&&n.container.children.splice(n.index,1);return}case`deleteTabset`:H(e.layout,t.tabsetId);return;case`moveNode`:{let n=T(e,t.sourceId);if(!n)return;let[r]=n.container.children.splice(n.index,1);if(!r)return;q(e,r,{id:t.targetId,index:t.index,location:t.location});return}case`moveTabset`:{if(t.sourceId===t.targetId)return;let n=C(e,t.sourceId);if(!n)return;if(t.location===`center`){let r=C(e,t.targetId);r&&(r.children.push(...n.children),H(e.layout,t.sourceId));return}if(t.location.startsWith(`split-`)){let n=U(e.layout,t.sourceId);n&&K(e,n,t.targetId,t.location)}return}case`renameTab`:{let n=T(e,t.tabId);n&&(n.tab.name=t.name);return}case`selectTab`:{let n=C(e,t.tabsetId);n&&(n.selected=t.index);return}case`setActiveTabset`:C(e,t.tabsetId)&&(e.activeTabsetId=t.tabsetId);return;case`setMaximizedTabset`:e.maximizedTabsetId=t.tabsetId??void 0;return;case`updateGlobalAttributes`:Object.assign(e.global,t.attrs);return;case`updateNodeAttributes`:{let n=O(e,t.nodeId);n&&Object.assign(n,t.attrs);return}default:V(t)}},J=(e,t)=>{let n=structuredClone(e);return se(n,t),B(n)},ce=100,Y=e=>({future:[],past:[],present:e}),le=e=>e.past.length>0,ue=e=>e.future.length>0,X=(e,t)=>{let n=J(e.present,t);return{future:[],past:[...e.past,e.present].slice(-100),present:n}},Z=e=>{let t=e.past.at(-1);return t===void 0?e:{future:[e.present,...e.future],past:e.past.slice(0,-1),present:t}},Q=e=>{let[t,...n]=e.future;return t===void 0?e:{future:n,past:[...e.past,e.present].slice(-100),present:t}},de=r({types:{context:{},events:{},input:{}}}).createMachine({context:({input:e})=>({history:Y(e.model)}),id:`dashfoo`,initial:`ready`,states:{ready:{on:{DISPATCH:{actions:t({history:({context:e,event:t})=>X(e.history,t.action)})},REDO:{actions:t({history:({context:e})=>Q(e.history)})},SET_MODEL:{actions:t({history:({event:e})=>Y(e.model)})},UNDO:{actions:t({history:({context:e})=>Z(e.history)})}}}}}),fe=e=>{if(e.subject===null||e.intent===null)throw Error(`drop requires a subject and a drop intent`);return{intent:e.intent,subject:e.subject}},pe=r({guards:{hasValidDrop:({context:e})=>e.subject!==null&&e.intent!==null},types:{context:{},emitted:{},events:{}}}).createMachine({context:{intent:null,subject:null},id:`dragDock`,initial:`idle`,states:{dragging:{on:{CANCEL:{target:`idle`},DROP:[{actions:n(({context:e})=>{let{intent:t,subject:n}=fe(e);return{action:n.kind===`external`?{index:t.index,location:t.location,tab:n.tab,targetId:t.targetId,type:`addNode`}:{index:t.index,location:t.location,sourceId:n.id,targetId:t.targetId,type:n.kind===`tabset`?`moveTabset`:`moveNode`},type:`COMMIT`}}),guard:`hasValidDrop`,target:`idle`},{target:`idle`}],OVER:{actions:t({intent:({event:e})=>e.intent})}}},idle:{entry:t({intent:null,subject:null}),on:{START:{actions:t({subject:({event:e})=>e.subject}),target:`dragging`}}}}}),$=e=>{let t=B(p.parse(e)),n=j(t);return n.length>0&&console.warn(`[dashfoo] duplicate node ids in the loaded layout: ${n.join(`, `)}`),t},me=e=>JSON.stringify(e),he=e=>$(JSON.parse(e)),ge=(e,t=`column`)=>{let n=[];for(let t of S(e))n.push({...t,weight:1});return B({...e,layout:{children:n,id:e.layout.id,orientation:t,type:`row`},maximizedTabsetId:void 0})};export{ce as HISTORY_LIMIT,v as actionSchema,ue as canRedo,le as canUndo,S as collectTabsets,Y as createHistory,y as createNodeId,b as createTabId,de as dashfooMachine,p as dashfooSchema,c as dimensionSchema,X as dispatch,_ as dockLocationSchema,pe as dragDockMachine,a as edgeSchema,O as findAttributedNode,j as findDuplicateIds,E as findRow,T as findTab,C as findTabset,k as findTabsetParent,he as fromJSON,te as getFirstTabset,f as globalAttributesSchema,i as jsonValueSchema,ie as model,g as mutableNodeAttrsSchema,B as normalize,s as orientationSchema,$ as parseModel,Q as redo,J as reducer,P as resolveDockTarget,re as row,d as rowNodeSchema,ge as stackModel,N as tab,l as tabNodeSchema,ne as tabset,u as tabsetNodeSchema,me as toJSON,Z as undo,o as unitSchema,F as zoneRect};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/model/schema.ts","../src/state/actions.ts","../src/model/ids.ts","../src/model/tree.ts","../src/model/builders.ts","../src/geometry/geometry.ts","../src/model/invariants.ts","../src/state/reducer.ts","../src/state/history.ts","../src/machines/dashfoo-machine.ts","../src/machines/drag-dock-machine.ts","../src/model/serialize.ts","../src/model/stack.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// A JSON-serializable value. Per-tab `config` is validated against this so the\n// model stays losslessly serializable (a function or symbol in config fails).\ntype JsonValue = string | number | boolean | null | Array<JsonValue> | { [key: string]: JsonValue };\n\nconst jsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>\n z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n z.array(jsonValueSchema),\n z.record(z.string(), jsonValueSchema),\n ]),\n);\n\nconst edgeSchema = z.enum([\"top\", \"bottom\", \"left\", \"right\"]);\nconst unitSchema = z.enum([\"px\", \"%\", \"em\", \"rem\", \"vh\", \"vw\"]);\nconst orientationSchema = z.enum([\"row\", \"column\"]);\n\nconst dimensionSchema = z.object({\n unit: unitSchema,\n value: z.number(),\n});\n\nconst tabNodeSchema = z.object({\n component: z.string(),\n config: jsonValueSchema.optional(),\n enableClose: z.boolean().optional(),\n enableDrag: z.boolean().optional(),\n enableRename: z.boolean().optional(),\n id: z.string(),\n name: z.string(),\n type: z.literal(\"tab\"),\n});\n\nconst tabsetNodeSchema = z.object({\n children: z.array(tabNodeSchema),\n enableClose: z.boolean().optional(),\n enableMaximize: z.boolean().optional(),\n id: z.string(),\n max: dimensionSchema.optional(),\n min: dimensionSchema.optional(),\n name: z.string().optional(),\n selected: z.number().int(),\n type: z.literal(\"tabset\"),\n weight: z.number().optional(),\n});\n\n// RowNode is recursive (a row may contain rows), so its type is declared\n// explicitly and the schema is built with z.lazy.\ntype RowNode = {\n children: Array<RowNode | z.infer<typeof tabsetNodeSchema>>;\n id: string;\n max?: z.infer<typeof dimensionSchema>;\n min?: z.infer<typeof dimensionSchema>;\n orientation: z.infer<typeof orientationSchema>;\n type: \"row\";\n weight?: number;\n};\n\nconst rowNodeSchema: z.ZodType<RowNode> = z.lazy(() =>\n z.object({\n children: z.array(z.union([rowNodeSchema, tabsetNodeSchema])),\n id: z.string(),\n max: dimensionSchema.optional(),\n min: dimensionSchema.optional(),\n orientation: orientationSchema,\n type: z.literal(\"row\"),\n weight: z.number().optional(),\n }),\n);\n\nconst globalAttributesSchema = z.object({\n enableSplitDock: z.boolean().optional(),\n splitterSize: z.number().optional(),\n tabEnableClose: z.boolean().optional(),\n tabEnableRename: z.boolean().optional(),\n tabLocation: z.enum([\"top\", \"bottom\"]).optional(),\n tabSetEnableMaximize: z.boolean().optional(),\n tabSetEnableTabStrip: z.boolean().optional(),\n tabSetMinSize: z.number().optional(),\n});\n\nconst dashfooSchema = z.object({\n activeTabsetId: z.string().optional(),\n global: globalAttributesSchema,\n layout: rowNodeSchema,\n maximizedTabsetId: z.string().optional(),\n // The persisted-payload format version, pinned by the schema itself: any\n // future format change bumps the literal, so foreign payloads fail validation.\n version: z.literal(1),\n});\n\ntype Json = JsonValue;\ntype Edge = z.infer<typeof edgeSchema>;\ntype Unit = z.infer<typeof unitSchema>;\ntype Orientation = z.infer<typeof orientationSchema>;\ntype Dimension = z.infer<typeof dimensionSchema>;\ntype TabNode = z.infer<typeof tabNodeSchema>;\ntype TabsetNode = z.infer<typeof tabsetNodeSchema>;\ntype GlobalAttributes = z.infer<typeof globalAttributesSchema>;\ntype Dashfoo = z.infer<typeof dashfooSchema>;\ntype Node = RowNode | TabsetNode | TabNode;\n\nexport {\n dashfooSchema,\n dimensionSchema,\n edgeSchema,\n globalAttributesSchema,\n jsonValueSchema,\n orientationSchema,\n rowNodeSchema,\n tabNodeSchema,\n tabsetNodeSchema,\n unitSchema,\n};\n\nexport type {\n Dashfoo,\n Dimension,\n Edge,\n GlobalAttributes,\n Json,\n Node,\n Orientation,\n RowNode,\n TabNode,\n TabsetNode,\n Unit,\n};\n","import { z } from \"zod\";\n\nimport {\n dimensionSchema,\n globalAttributesSchema,\n orientationSchema,\n tabNodeSchema,\n tabsetNodeSchema,\n} from \"../model/schema\";\n\nconst mutableTabAttrsSchema = tabNodeSchema\n .pick({\n config: true,\n enableClose: true,\n enableDrag: true,\n enableRename: true,\n name: true,\n })\n .partial();\n\nconst mutableTabsetAttrsSchema = tabsetNodeSchema\n .pick({\n enableClose: true,\n enableMaximize: true,\n max: true,\n min: true,\n selected: true,\n weight: true,\n })\n .partial();\n\nconst mutableRowAttrsSchema = z\n .object({\n max: dimensionSchema.optional(),\n min: dimensionSchema.optional(),\n orientation: orientationSchema,\n weight: z.number(),\n })\n .partial();\n\nconst mutableNodeAttrsSchema = z.union([\n mutableTabAttrsSchema,\n mutableTabsetAttrsSchema,\n mutableRowAttrsSchema,\n]);\n\n// Every document mutation is one of these immutable, discriminated actions. The\n// reducer is exhaustive over this union; the React layer validates untrusted\n// payloads against actionSchema before dispatch.\nconst dockLocationSchema = z.enum([\n \"center\",\n \"split-bottom\",\n \"split-left\",\n \"split-right\",\n \"split-top\",\n]);\n\nconst actionSchema = z.discriminatedUnion(\"type\", [\n z.object({\n index: z.number().int().optional(),\n location: dockLocationSchema,\n tab: tabNodeSchema,\n targetId: z.string(),\n type: z.literal(\"addNode\"),\n }),\n z.object({\n index: z.number().int().optional(),\n location: dockLocationSchema,\n sourceId: z.string(),\n targetId: z.string(),\n type: z.literal(\"moveNode\"),\n }),\n z.object({\n index: z.number().int().optional(),\n location: dockLocationSchema,\n sourceId: z.string(),\n targetId: z.string(),\n type: z.literal(\"moveTabset\"),\n }),\n z.object({ index: z.number().int(), tabsetId: z.string(), type: z.literal(\"selectTab\") }),\n z.object({ tabsetId: z.string(), type: z.literal(\"setActiveTabset\") }),\n z.object({ tabsetId: z.string().nullable(), type: z.literal(\"setMaximizedTabset\") }),\n z.object({ name: z.string(), tabId: z.string(), type: z.literal(\"renameTab\") }),\n z.object({ tabId: z.string(), type: z.literal(\"deleteTab\") }),\n z.object({ tabsetId: z.string(), type: z.literal(\"deleteTabset\") }),\n z.object({\n rowId: z.string(),\n type: z.literal(\"adjustSplit\"),\n weights: z.array(z.number()),\n }),\n z.object({\n attrs: mutableNodeAttrsSchema,\n nodeId: z.string(),\n type: z.literal(\"updateNodeAttributes\"),\n }),\n z.object({ attrs: globalAttributesSchema.partial(), type: z.literal(\"updateGlobalAttributes\") }),\n]);\n\ntype DockLocation = z.infer<typeof dockLocationSchema>;\ntype MutableNodeAttrs = z.infer<typeof mutableNodeAttrsSchema>;\ntype Action = z.infer<typeof actionSchema>;\n\n// Where a drag will land: the dock location, the target tabset, and (for a tab\n// stack) the slot index. Co-located with DockLocation; the drag machine and the\n// React adapter both consume it.\ntype DropIntent = { index?: number; location: DockLocation; targetId: string };\n\nexport { actionSchema, dockLocationSchema, mutableNodeAttrsSchema };\nexport type { Action, DockLocation, DropIntent, MutableNodeAttrs };\n","// Unique ids for nodes and tabs. The reducer uses these for split/dock surgery;\n// they are public so consumers building addNode/moveNode payloads (or seed\n// models) have one blessed generator instead of rolling their own.\nconst createNodeId = (prefix = \"node\"): string => `${prefix}-${crypto.randomUUID()}`;\n\nconst createTabId = (): string => createNodeId(\"tab\");\n\nexport { createNodeId, createTabId };\n","import type { Dashfoo, RowNode, TabNode, TabsetNode } from \"./schema\";\n\n// A tab lives inside a tabset.\ntype TabContainer = TabsetNode;\ntype TabLocation = { container: TabContainer; index: number; tab: TabNode };\n\nconst collectTabsetsInRow = (row: RowNode, acc: Array<TabsetNode>): void => {\n for (const child of row.children) {\n if (child.type === \"tabset\") {\n acc.push(child);\n } else {\n collectTabsetsInRow(child, acc);\n }\n }\n};\n\n// Every tabset in the layout tree, depth-first.\nconst collectTabsets = (model: Dashfoo): Array<TabsetNode> => {\n const acc: Array<TabsetNode> = [];\n collectTabsetsInRow(model.layout, acc);\n return acc;\n};\n\nconst getFirstTabset = (model: Dashfoo): TabsetNode | undefined => collectTabsets(model)[0];\n\nconst findTabset = (model: Dashfoo, tabsetId: string): TabsetNode | undefined =>\n collectTabsets(model).find((tabset) => tabset.id === tabsetId);\n\nconst findTabInContainer = (container: TabContainer, tabId: string): TabLocation | undefined => {\n const index = container.children.findIndex((tab) => tab.id === tabId);\n if (index === -1) {\n return undefined;\n }\n\n const tab = container.children[index];\n if (!tab) {\n return undefined;\n }\n\n return { container, index, tab };\n};\n\n// Locate a tab anywhere in the model — searching every tabset — returning the\n// tab, its container, and its index within that container.\nconst findTab = (model: Dashfoo, tabId: string): TabLocation | undefined => {\n for (const tabset of collectTabsets(model)) {\n const found = findTabInContainer(tabset, tabId);\n if (found) {\n return found;\n }\n }\n\n return undefined;\n};\n\ntype AttributedNode = RowNode | TabNode | TabsetNode;\n\n// Find a row anywhere in the layout tree by id.\nconst findRow = (row: RowNode, id: string): RowNode | undefined => {\n if (row.id === id) {\n return row;\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = findRow(child, id);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n};\n\nconst findAttributedNodeInRow = (row: RowNode, id: string): AttributedNode | undefined => {\n if (row.id === id) {\n return row;\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = findAttributedNodeInRow(child, id);\n if (found) {\n return found;\n }\n continue;\n }\n if (child.id === id) {\n return child;\n }\n const tab = child.children.find((candidate) => candidate.id === id);\n if (tab) {\n return tab;\n }\n }\n return undefined;\n};\n\n// Locate any attributed node (row, tabset, or tab) by id — backs updateNodeAttributes.\nconst findAttributedNode = (model: Dashfoo, id: string): AttributedNode | undefined =>\n findAttributedNodeInRow(model.layout, id);\n\n// The row holding a tabset, and the tabset's index within it — backs split placement.\nconst findTabsetParent = (\n row: RowNode,\n tabsetId: string,\n): { index: number; parent: RowNode } | undefined => {\n const index = row.children.findIndex((child) => child.type === \"tabset\" && child.id === tabsetId);\n if (index !== -1) {\n return { index, parent: row };\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = findTabsetParent(child, tabsetId);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n};\n\nconst collectIdsInRow = (row: RowNode, acc: Array<string>): void => {\n acc.push(row.id);\n for (const child of row.children) {\n if (child.type === \"row\") {\n collectIdsInRow(child, acc);\n } else {\n acc.push(child.id);\n for (const tab of child.children) {\n acc.push(tab.id);\n }\n }\n }\n};\n\n// Ids that appear more than once across the whole model (rows, tabsets, tabs).\n// Duplicate ids produce duplicate React keys and corrupt the resize/drag\n// plumbing, so this backs a load-time diagnostic.\nconst findDuplicateIds = (model: Dashfoo): Array<string> => {\n const ids: Array<string> = [];\n collectIdsInRow(model.layout, ids);\n const seen = new Set<string>();\n const duplicates = new Set<string>();\n for (const id of ids) {\n if (seen.has(id)) {\n duplicates.add(id);\n }\n seen.add(id);\n }\n return [...duplicates];\n};\n\nexport {\n collectTabsets,\n findAttributedNode,\n findDuplicateIds,\n findRow,\n findTab,\n findTabset,\n findTabsetParent,\n getFirstTabset,\n};\nexport type { AttributedNode, TabContainer, TabLocation };\n","// Ergonomic constructors for the layout model, so consumers seed a Dashfoo\n// without hand-writing `type`/`version`/`selected` boilerplate or nested\n// literals. Every builder fills the required-but-mechanical fields and leaves\n// the meaningful ones (ids that get referenced, weights) to the caller. Output\n// is schema-valid by construction; id uniqueness across reused components is\n// the caller's responsibility (tabs default their id to the component name), so\n// `model` warns once if it detects duplicate node ids.\n\nimport { createNodeId } from \"./ids\";\nimport type {\n Dashfoo,\n Dimension,\n GlobalAttributes,\n Json,\n RowNode,\n TabNode,\n TabsetNode,\n} from \"./schema\";\nimport { findDuplicateIds } from \"./tree\";\n\nconst toDimension = (value: number | Dimension): Dimension =>\n typeof value === \"number\" ? { unit: \"px\", value } : value;\n\ntype TabOptions = {\n config?: Json;\n enableClose?: boolean;\n enableDrag?: boolean;\n enableRename?: boolean;\n id?: string;\n};\n\n// The id defaults to the component name — the common case where a layout holds\n// one tab per component and references it by that name.\nconst tab = (component: string, name: string, options: TabOptions = {}): TabNode => ({\n component,\n name,\n type: \"tab\",\n ...options,\n id: options.id ?? component,\n});\n\ntype TabsetOptions = {\n enableClose?: boolean;\n enableMaximize?: boolean;\n id?: string;\n max?: number | Dimension;\n min?: number | Dimension;\n name?: string;\n selected?: number;\n weight?: number;\n};\n\nconst tabset = (children: Array<TabNode>, options: TabsetOptions = {}): TabsetNode => {\n const { max, min, ...rest } = options;\n return {\n children,\n type: \"tabset\",\n ...rest,\n ...(max === undefined ? {} : { max: toDimension(max) }),\n ...(min === undefined ? {} : { min: toDimension(min) }),\n id: options.id ?? createNodeId(\"tabset\"),\n selected: options.selected ?? 0,\n };\n};\n\ntype RowOptions = {\n id?: string;\n max?: number | Dimension;\n min?: number | Dimension;\n orientation?: RowNode[\"orientation\"];\n weight?: number;\n};\n\nconst row = (children: RowNode[\"children\"], options: RowOptions = {}): RowNode => {\n const { max, min, ...rest } = options;\n return {\n children,\n type: \"row\",\n ...rest,\n ...(max === undefined ? {} : { max: toDimension(max) }),\n ...(min === undefined ? {} : { min: toDimension(min) }),\n id: options.id ?? createNodeId(\"row\"),\n orientation: options.orientation ?? \"row\",\n };\n};\n\ntype ModelOptions = {\n activeTabsetId?: string;\n global?: GlobalAttributes;\n maximizedTabsetId?: string;\n};\n\nconst model = (layout: RowNode, options: ModelOptions = {}): Dashfoo => {\n const built: Dashfoo = {\n layout,\n version: 1,\n ...options,\n global: options.global ?? {},\n };\n const duplicates = findDuplicateIds(built);\n if (duplicates.length > 0) {\n // oxlint-disable-next-line no-console\n console.warn(\n `[dashfoo] builder produced duplicate node ids: ${duplicates.join(\", \")} — pass explicit ids when reusing a component`,\n );\n }\n return built;\n};\n\nexport { model, row, tab, tabset };\nexport type { ModelOptions, RowOptions, TabOptions, TabsetOptions };\n","import type { Edge } from \"../model/schema\";\nimport type { DockLocation } from \"../state/actions\";\n\ntype Point = { x: number; y: number };\ntype Rect = { height: number; width: number; x: number; y: number };\ntype DockTarget = { kind: \"tab\" } | { edge: Edge; kind: \"split\" };\ntype BandOptions = { bandFraction?: number };\n\n// Fractional distance of the pointer from each of the four edges of a rect.\nconst edgeDistances = (\n pointer: Point,\n rect: Rect,\n): { bottom: number; left: number; right: number; top: number } => {\n const fx = (pointer.x - rect.x) / rect.width;\n const fy = (pointer.y - rect.y) / rect.height;\n return { bottom: 1 - fy, left: fx, right: 1 - fx, top: fy };\n};\n\nconst closestEdge = (d: { bottom: number; left: number; right: number; top: number }): Edge => {\n const min = Math.min(d.left, d.right, d.top, d.bottom);\n if (min === d.left) {\n return \"left\";\n }\n if (min === d.right) {\n return \"right\";\n }\n if (min === d.top) {\n return \"top\";\n }\n return \"bottom\";\n};\n\n// Where a drag dropped over a tabset should land: stacked as a tab when the\n// pointer is in the center, or splitting the tabset when it is within an outer\n// band (default 22%) of one of the four edges — the closer edge wins in corners.\nconst resolveDockTarget = (pointer: Point, rect: Rect, opts?: BandOptions): DockTarget => {\n const band = opts?.bandFraction ?? 0.22;\n // a zero-size tabset has no meaningful edges; dividing by its width/height\n // yields NaN distances that would silently resolve to a bogus split.\n if (rect.width <= 0 || rect.height <= 0) {\n return { kind: \"tab\" };\n }\n const distances = edgeDistances(pointer, rect);\n const min = Math.min(distances.left, distances.right, distances.top, distances.bottom);\n\n if (min > band) {\n return { kind: \"tab\" };\n }\n return { edge: closestEdge(distances), kind: \"split\" };\n};\n\n// The region the dock indicator highlights for a given location over a tabset:\n// the whole tabset for a center stack, the matching half for a split.\nconst zoneRect = (rect: Rect, location: DockLocation): Rect => {\n const halfW = rect.width / 2;\n const halfH = rect.height / 2;\n switch (location) {\n case \"split-bottom\": {\n return { height: halfH, width: rect.width, x: rect.x, y: rect.y + halfH };\n }\n case \"split-left\": {\n return { height: rect.height, width: halfW, x: rect.x, y: rect.y };\n }\n case \"split-right\": {\n return { height: rect.height, width: halfW, x: rect.x + halfW, y: rect.y };\n }\n case \"split-top\": {\n return { height: halfH, width: rect.width, x: rect.x, y: rect.y };\n }\n default: {\n return rect;\n }\n }\n};\n\nexport { resolveDockTarget, zoneRect };\nexport type { BandOptions, DockTarget, Point, Rect };\n","import type { Dashfoo, RowNode, TabsetNode } from \"./schema\";\nimport { collectTabsets } from \"./tree\";\n\nconst clampSelected = (length: number, selected: number): number => {\n if (length === 0) {\n return 0;\n }\n // coerce to a non-negative integer first to match the schema's z.number().int():\n // a persisted 1.5 or NaN must not survive as a fractional/NaN index.\n const base = Number.isFinite(selected) ? Math.trunc(selected) : 0;\n if (base < 0) {\n return 0;\n }\n if (base > length - 1) {\n return length - 1;\n }\n return base;\n};\n\nconst normalizeTabset = (tabset: TabsetNode): TabsetNode => ({\n ...tabset,\n selected: clampSelected(tabset.children.length, tabset.selected),\n});\n\n// Drop empty tabsets and empty rows, recurse into child rows, and simplify a\n// single-child row by lifting its lone child (which inherits the lifted row's\n// weight so sizing is preserved).\nconst normalizeRowChildren = (children: RowNode[\"children\"]): RowNode[\"children\"] => {\n const out: RowNode[\"children\"] = [];\n\n for (const child of children) {\n if (child.type === \"tabset\") {\n if (child.children.length > 0) {\n out.push(normalizeTabset(child));\n }\n continue;\n }\n\n const grandchildren = normalizeRowChildren(child.children);\n if (grandchildren.length === 0) {\n continue;\n }\n if (grandchildren.length === 1) {\n const only = grandchildren[0];\n if (only !== undefined) {\n out.push(child.weight === undefined ? only : { ...only, weight: child.weight });\n }\n continue;\n }\n out.push({ ...child, children: grandchildren });\n }\n\n return out;\n};\n\n// The root is always a row; if it reduces to a single child that is itself a\n// row, absorb that row (adopt its children + orientation) to avoid redundant\n// nesting.\nconst normalizeLayout = (root: RowNode): RowNode => {\n let children = normalizeRowChildren(root.children);\n let orientation = root.orientation;\n\n let inner = children[0];\n while (children.length === 1 && inner?.type === \"row\") {\n children = inner.children;\n orientation = inner.orientation;\n inner = children[0];\n }\n\n return { ...root, children, orientation };\n};\n\n// Self-healing pass run after every action so the tree stays canonical: no empty\n// tabsets or rows, no redundant single-child rows, selected indices in range,\n// and active/maximized ids that always point at an existing tabset.\nconst normalize = (model: Dashfoo): Dashfoo => {\n const layout = normalizeLayout(model.layout);\n const withLayout: Dashfoo = { ...model, layout };\n\n const tabsetIds = new Set(collectTabsets(withLayout).map((tabset) => tabset.id));\n const firstTabsetId = collectTabsets(withLayout)[0]?.id;\n\n const activeTabsetId =\n model.activeTabsetId !== undefined && tabsetIds.has(model.activeTabsetId)\n ? model.activeTabsetId\n : firstTabsetId;\n\n const maximizedTabsetId =\n model.maximizedTabsetId !== undefined && tabsetIds.has(model.maximizedTabsetId)\n ? model.maximizedTabsetId\n : undefined;\n\n return { ...withLayout, activeTabsetId, maximizedTabsetId };\n};\n\nexport { normalize };\n","import { createNodeId } from \"../model/ids\";\nimport { normalize } from \"../model/invariants\";\nimport type { Dashfoo, RowNode, TabNode, TabsetNode } from \"../model/schema\";\nimport { findAttributedNode, findRow, findTab, findTabset, findTabsetParent } from \"../model/tree\";\n\nimport type { Action, DockLocation } from \"./actions\";\n\nconst assertNever = (value: never): never => {\n throw new Error(`Unhandled action: ${JSON.stringify(value)}`);\n};\n\nconst removeTabset = (row: RowNode, tabsetId: string): boolean => {\n const index = row.children.findIndex((child) => child.type === \"tabset\" && child.id === tabsetId);\n if (index !== -1) {\n row.children.splice(index, 1);\n return true;\n }\n for (const child of row.children) {\n if (child.type === \"row\" && removeTabset(child, tabsetId)) {\n return true;\n }\n }\n return false;\n};\n\n// Detach a tabset from the tree and return it (for moving a whole tabset).\nconst removeTabsetReturning = (row: RowNode, tabsetId: string): TabsetNode | undefined => {\n const index = row.children.findIndex((child) => child.type === \"tabset\" && child.id === tabsetId);\n if (index !== -1) {\n const [removed] = row.children.splice(index, 1);\n return removed?.type === \"tabset\" ? removed : undefined;\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = removeTabsetReturning(child, tabsetId);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n};\n\nconst splitOrientation = (location: DockLocation): \"column\" | \"row\" =>\n location === \"split-left\" || location === \"split-right\" ? \"row\" : \"column\";\n\nconst splitsBefore = (location: DockLocation): boolean =>\n location === \"split-left\" || location === \"split-top\";\n\n// Place an (already-detached) tabset beside the target for a split-* location:\n// reuse the parent row when the orientation already matches, else wrap the target\n// in a new row. Shared by insertTab (a fresh tabset) and moveTabset (an existing\n// one). Splits the target's space in half.\nconst placeBesideTarget = (\n draft: Dashfoo,\n placed: TabsetNode,\n targetId: string,\n location: DockLocation,\n): void => {\n const targetTabset = findTabset(draft, targetId);\n const found = findTabsetParent(draft.layout, targetId);\n if (!targetTabset || !found) {\n return;\n }\n\n const orientation = splitOrientation(location);\n const before = splitsBefore(location);\n\n if (found.parent.orientation === orientation) {\n const targetWeight = targetTabset.weight ?? 100;\n targetTabset.weight = targetWeight / 2;\n placed.weight = targetWeight / 2;\n found.parent.children.splice(before ? found.index : found.index + 1, 0, placed);\n } else {\n const targetWeight = targetTabset.weight;\n targetTabset.weight = 50;\n placed.weight = 50;\n const newRow: RowNode = {\n children: before ? [placed, targetTabset] : [targetTabset, placed],\n id: createNodeId(\"row\"),\n orientation,\n type: \"row\",\n };\n if (targetWeight !== undefined) {\n newRow.weight = targetWeight;\n }\n found.parent.children.splice(found.index, 1, newRow);\n }\n\n draft.activeTabsetId = placed.id;\n};\n\n// Insert a tab into the model at a dock target. center stacks it into the target\n// tabset; split-* creates a new tabset beside the target.\ntype DropTarget = { id: string; index?: number; location: DockLocation };\n\nconst insertTab = (draft: Dashfoo, tab: TabNode, target: DropTarget): void => {\n const { id: targetId, index, location } = target;\n const targetTabset = findTabset(draft, targetId);\n if (!targetTabset) {\n return;\n }\n\n if (location === \"center\") {\n const at = index ?? targetTabset.children.length;\n targetTabset.children.splice(at, 0, tab);\n targetTabset.selected = at;\n return;\n }\n\n const newTabset: TabsetNode = {\n children: [tab],\n id: createNodeId(\"tabset\"),\n selected: 0,\n type: \"tabset\",\n weight: 50,\n };\n placeBesideTarget(draft, newTabset, targetId, location);\n};\n\nconst applyAction = (draft: Dashfoo, action: Action): void => {\n switch (action.type) {\n case \"addNode\": {\n insertTab(draft, action.tab, {\n id: action.targetId,\n index: action.index,\n location: action.location,\n });\n return;\n }\n case \"adjustSplit\": {\n const row = findRow(draft.layout, action.rowId);\n if (!row) {\n return;\n }\n for (let index = 0; index < action.weights.length; index++) {\n const child = row.children[index];\n const weight = action.weights[index];\n if (!child || weight === undefined) {\n continue;\n }\n child.weight = weight;\n }\n return;\n }\n case \"deleteTab\": {\n const location = findTab(draft, action.tabId);\n if (location) {\n location.container.children.splice(location.index, 1);\n }\n return;\n }\n case \"deleteTabset\": {\n removeTabset(draft.layout, action.tabsetId);\n return;\n }\n case \"moveNode\": {\n const source = findTab(draft, action.sourceId);\n if (!source) {\n return;\n }\n // Remove the source first; action.index is the destination slot measured\n // against the tabs that remain (the drag adapter excludes the dragged tab),\n // so it indexes the post-removal array directly — no reorder adjustment.\n const [removed] = source.container.children.splice(source.index, 1);\n if (!removed) {\n return;\n }\n insertTab(draft, removed, {\n id: action.targetId,\n index: action.index,\n location: action.location,\n });\n return;\n }\n case \"moveTabset\": {\n if (action.sourceId === action.targetId) {\n return;\n }\n const source = findTabset(draft, action.sourceId);\n if (!source) {\n return;\n }\n if (action.location === \"center\") {\n const target = findTabset(draft, action.targetId);\n if (target) {\n target.children.push(...source.children);\n removeTabset(draft.layout, action.sourceId);\n }\n return;\n }\n // split-* moves the whole tabset beside the target.\n if (action.location.startsWith(\"split-\")) {\n const detached = removeTabsetReturning(draft.layout, action.sourceId);\n if (detached) {\n placeBesideTarget(draft, detached, action.targetId, action.location);\n }\n }\n return;\n }\n case \"renameTab\": {\n const location = findTab(draft, action.tabId);\n if (location) {\n location.tab.name = action.name;\n }\n return;\n }\n case \"selectTab\": {\n const tabset = findTabset(draft, action.tabsetId);\n if (tabset) {\n tabset.selected = action.index;\n }\n return;\n }\n case \"setActiveTabset\": {\n if (findTabset(draft, action.tabsetId)) {\n draft.activeTabsetId = action.tabsetId;\n }\n return;\n }\n case \"setMaximizedTabset\": {\n draft.maximizedTabsetId = action.tabsetId ?? undefined;\n return;\n }\n case \"updateGlobalAttributes\": {\n Object.assign(draft.global, action.attrs);\n return;\n }\n case \"updateNodeAttributes\": {\n const node = findAttributedNode(draft, action.nodeId);\n if (node) {\n Object.assign(node, action.attrs);\n }\n return;\n }\n default: {\n assertNever(action);\n }\n }\n};\n\n// The canonical engine: deep-copy the model (so the input is never mutated),\n// apply one action to the copy, then run the self-healing invariants so the\n// result is always a valid, canonical model.\nconst reducer = (model: Dashfoo, action: Action): Dashfoo => {\n const draft = structuredClone(model);\n applyAction(draft, action);\n return normalize(draft);\n};\n\nexport { reducer };\n","import type { Dashfoo } from \"../model/schema\";\n\nimport type { Action } from \"./actions\";\nimport { reducer } from \"./reducer\";\n\n// past/present/future snapshots. Each committed action is its own undo step:\n// rrp v4's onLayoutChanged fires one adjustSplit per release, so there is no\n// per-frame burst to coalesce.\ntype History = {\n future: Array<Dashfoo>;\n past: Array<Dashfoo>;\n present: Dashfoo;\n};\n\nconst createHistory = (present: Dashfoo): History => ({\n future: [],\n past: [],\n present,\n});\n\nconst canUndo = (history: History): boolean => history.past.length > 0;\nconst canRedo = (history: History): boolean => history.future.length > 0;\n\nconst dispatch = (history: History, action: Action): History => {\n const present = reducer(history.present, action);\n return { future: [], past: [...history.past, history.present], present };\n};\n\nconst undo = (history: History): History => {\n const previous = history.past.at(-1);\n if (previous === undefined) {\n return history;\n }\n return {\n future: [history.present, ...history.future],\n past: history.past.slice(0, -1),\n present: previous,\n };\n};\n\nconst redo = (history: History): History => {\n const [next, ...rest] = history.future;\n if (next === undefined) {\n return history;\n }\n return {\n future: rest,\n past: [...history.past, history.present],\n present: next,\n };\n};\n\nexport { canRedo, canUndo, createHistory, dispatch, redo, undo };\nexport type { History };\n","import { assign, setup } from \"xstate\";\n\nimport type { Dashfoo } from \"../model/schema\";\nimport type { Action } from \"../state/actions\";\nimport type { History } from \"../state/history\";\nimport { createHistory, dispatch, redo, undo } from \"../state/history\";\n\ntype DashfooContext = { history: History };\n\ntype DashfooEvent =\n | { action: Action; type: \"DISPATCH\" }\n | { model: Dashfoo; type: \"SET_MODEL\" }\n | { type: \"REDO\" }\n | { type: \"UNDO\" };\n\ntype DashfooInput = { model: Dashfoo };\n\n// The document actor: it owns the undo/redo history (whose `present` is the live\n// model) and processes document mutations by invoking the pure reducer via the\n// history helpers. It has no finite states beyond `ready` — the document is data,\n// not a lifecycle — so every mutation is an event handled with assign.\nconst dashfooMachine = setup({\n types: {\n context: {} as DashfooContext,\n events: {} as DashfooEvent,\n input: {} as DashfooInput,\n },\n}).createMachine({\n context: ({ input }) => ({ history: createHistory(input.model) }),\n id: \"dashfoo\",\n initial: \"ready\",\n states: {\n ready: {\n on: {\n DISPATCH: {\n actions: assign({\n history: ({ context, event }) => dispatch(context.history, event.action),\n }),\n },\n REDO: {\n actions: assign({ history: ({ context }) => redo(context.history) }),\n },\n SET_MODEL: {\n actions: assign({ history: ({ event }) => createHistory(event.model) }),\n },\n UNDO: {\n actions: assign({ history: ({ context }) => undo(context.history) }),\n },\n },\n },\n },\n});\n\nexport { dashfooMachine };\nexport type { DashfooContext, DashfooEvent, DashfooInput };\n","import { assign, emit, setup } from \"xstate\";\n\nimport type { Action, DropIntent } from \"../state/actions\";\n\ntype DragSubject = { id: string; kind: \"tab\" | \"tabset\" };\n\ntype DragContext = { intent: DropIntent | null; subject: DragSubject | null };\n\ntype DragEvent =\n | { intent: DropIntent | null; type: \"OVER\" }\n | { subject: DragSubject; type: \"START\" }\n | { type: \"CANCEL\" }\n | { type: \"DROP\" };\n\ntype DragEmitted = { action: Action; type: \"COMMIT\" };\n\nconst requireDrop = (context: DragContext): { intent: DropIntent; subject: DragSubject } => {\n if (context.subject === null || context.intent === null) {\n throw new Error(\"drop requires a subject and a drop intent\");\n }\n return { intent: context.intent, subject: context.subject };\n};\n\n// The drag/dock interaction lifecycle, driven by abstract events the dnd-kit\n// adapter maps from pointer/keyboard input. It owns transient drag state only and\n// never touches the document — on a valid drop it emits a COMMIT carrying the\n// moveNode action, which the React layer forwards to the document machine.\nconst dragDockMachine = setup({\n guards: {\n hasValidDrop: ({ context }) => context.subject !== null && context.intent !== null,\n },\n types: {\n context: {} as DragContext,\n emitted: {} as DragEmitted,\n events: {} as DragEvent,\n },\n}).createMachine({\n context: { intent: null, subject: null },\n id: \"dragDock\",\n initial: \"idle\",\n states: {\n dragging: {\n on: {\n CANCEL: { target: \"idle\" },\n DROP: [\n {\n actions: emit(({ context }) => {\n const { intent, subject } = requireDrop(context);\n return {\n action: {\n index: intent.index,\n location: intent.location,\n sourceId: subject.id,\n targetId: intent.targetId,\n type: subject.kind === \"tabset\" ? \"moveTabset\" : \"moveNode\",\n },\n type: \"COMMIT\",\n };\n }),\n guard: \"hasValidDrop\",\n target: \"idle\",\n },\n { target: \"idle\" },\n ],\n OVER: { actions: assign({ intent: ({ event }) => event.intent }) },\n },\n },\n idle: {\n entry: assign({ intent: null, subject: null }),\n on: {\n START: { actions: assign({ subject: ({ event }) => event.subject }), target: \"dragging\" },\n },\n },\n },\n});\n\nexport { dragDockMachine };\nexport type { DragContext, DragEmitted, DragEvent, DragSubject };\n","import { normalize } from \"./invariants\";\nimport type { Dashfoo } from \"./schema\";\nimport { dashfooSchema } from \"./schema\";\nimport { findDuplicateIds } from \"./tree\";\n\n// Validate an untrusted value against the schema and return a normalized,\n// canonical model. Throws if the value is not a valid model — including a\n// payload whose `version` is not the pinned `1`; warns (does not throw) on\n// duplicate ids, which corrupt React keys and the plumbing.\nconst parseModel = (value: unknown): Dashfoo => {\n const model = normalize(dashfooSchema.parse(value));\n const duplicates = findDuplicateIds(model);\n if (duplicates.length > 0) {\n // oxlint-disable-next-line no-console\n console.warn(`[dashfoo] duplicate node ids in the loaded layout: ${duplicates.join(\", \")}`);\n }\n return model;\n};\n\nconst toJSON = (model: Dashfoo): string => JSON.stringify(model);\n\nconst fromJSON = (json: string): Dashfoo => parseModel(JSON.parse(json));\n\nexport { fromJSON, parseModel, toJSON };\n","import { normalize } from \"./invariants\";\nimport type { Dashfoo, Orientation, TabsetNode } from \"./schema\";\nimport { collectTabsets } from \"./tree\";\n\n// Flatten any layout into a single row (default a column) of all its tabsets, in\n// document order, each with equal weight, so nothing is lost on a narrow screen;\n// maximize is cleared. The building block for a mobile breakpoint:\n// `stackModel(desktopModel)`.\nconst stackModel = (model: Dashfoo, orientation: Orientation = \"column\"): Dashfoo => {\n // Equal weight so stacked panels share the space evenly. A loop (not map) so we\n // create fresh nodes without mutating the shared input tabsets.\n const children: Array<TabsetNode> = [];\n for (const tabset of collectTabsets(model)) {\n children.push({ ...tabset, weight: 1 });\n }\n\n return normalize({\n ...model,\n layout: { children, id: model.layout.id, orientation, type: \"row\" },\n maximizedTabsetId: undefined,\n });\n};\n\nexport { stackModel };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/model/schema.ts","../src/state/actions.ts","../src/model/ids.ts","../src/model/tree.ts","../src/model/builders.ts","../src/geometry/geometry.ts","../src/model/invariants.ts","../src/state/reducer.ts","../src/state/history.ts","../src/machines/dashfoo-machine.ts","../src/machines/drag-dock-machine.ts","../src/model/serialize.ts","../src/model/stack.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// A JSON-serializable value. Per-tab `config` is validated against this so the\n// model stays losslessly serializable (a function or symbol in config fails).\ntype JsonValue = string | number | boolean | null | Array<JsonValue> | { [key: string]: JsonValue };\n\nconst jsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>\n z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n z.array(jsonValueSchema),\n z.record(z.string(), jsonValueSchema),\n ]),\n);\n\nconst edgeSchema = z.enum([\"top\", \"bottom\", \"left\", \"right\"]);\nconst unitSchema = z.enum([\"px\", \"%\", \"em\", \"rem\", \"vh\", \"vw\"]);\nconst orientationSchema = z.enum([\"row\", \"column\"]);\n\nconst dimensionSchema = z.object({\n unit: unitSchema,\n value: z.number(),\n});\n\nconst tabNodeSchema = z.object({\n component: z.string(),\n config: jsonValueSchema.optional(),\n enableClose: z.boolean().optional(),\n enableDrag: z.boolean().optional(),\n enableRename: z.boolean().optional(),\n id: z.string(),\n name: z.string(),\n type: z.literal(\"tab\"),\n});\n\nconst tabsetNodeSchema = z.object({\n children: z.array(tabNodeSchema),\n enableClose: z.boolean().optional(),\n enableMaximize: z.boolean().optional(),\n id: z.string(),\n max: dimensionSchema.optional(),\n min: dimensionSchema.optional(),\n name: z.string().optional(),\n selected: z.number().int(),\n type: z.literal(\"tabset\"),\n weight: z.number().optional(),\n});\n\n// RowNode is recursive (a row may contain rows), so its type is declared\n// explicitly and the schema is built with z.lazy.\ntype RowNode = {\n children: Array<RowNode | z.infer<typeof tabsetNodeSchema>>;\n id: string;\n max?: z.infer<typeof dimensionSchema>;\n min?: z.infer<typeof dimensionSchema>;\n orientation: z.infer<typeof orientationSchema>;\n type: \"row\";\n weight?: number;\n};\n\nconst rowNodeSchema: z.ZodType<RowNode> = z.lazy(() =>\n z.object({\n children: z.array(z.union([rowNodeSchema, tabsetNodeSchema])),\n id: z.string(),\n max: dimensionSchema.optional(),\n min: dimensionSchema.optional(),\n orientation: orientationSchema,\n type: z.literal(\"row\"),\n weight: z.number().optional(),\n }),\n);\n\nconst globalAttributesSchema = z.object({\n enableSplitDock: z.boolean().optional(),\n splitterSize: z.number().optional(),\n tabEnableClose: z.boolean().optional(),\n tabEnableRename: z.boolean().optional(),\n tabLocation: z.enum([\"top\", \"bottom\"]).optional(),\n tabSetEnableMaximize: z.boolean().optional(),\n tabSetEnableTabStrip: z.boolean().optional(),\n tabSetMinSize: z.number().optional(),\n});\n\nconst dashfooSchema = z.object({\n activeTabsetId: z.string().optional(),\n global: globalAttributesSchema,\n layout: rowNodeSchema,\n maximizedTabsetId: z.string().optional(),\n // The persisted-payload format version, pinned by the schema itself: any\n // future format change bumps the literal, so foreign payloads fail validation.\n version: z.literal(1),\n});\n\ntype Json = JsonValue;\ntype Edge = z.infer<typeof edgeSchema>;\ntype Unit = z.infer<typeof unitSchema>;\ntype Orientation = z.infer<typeof orientationSchema>;\ntype Dimension = z.infer<typeof dimensionSchema>;\ntype TabNode = z.infer<typeof tabNodeSchema>;\ntype TabsetNode = z.infer<typeof tabsetNodeSchema>;\ntype GlobalAttributes = z.infer<typeof globalAttributesSchema>;\ntype Dashfoo = z.infer<typeof dashfooSchema>;\ntype Node = RowNode | TabsetNode | TabNode;\n\nexport {\n dashfooSchema,\n dimensionSchema,\n edgeSchema,\n globalAttributesSchema,\n jsonValueSchema,\n orientationSchema,\n rowNodeSchema,\n tabNodeSchema,\n tabsetNodeSchema,\n unitSchema,\n};\n\nexport type {\n Dashfoo,\n Dimension,\n Edge,\n GlobalAttributes,\n Json,\n Node,\n Orientation,\n RowNode,\n TabNode,\n TabsetNode,\n Unit,\n};\n","import { z } from \"zod\";\n\nimport {\n dimensionSchema,\n globalAttributesSchema,\n orientationSchema,\n tabNodeSchema,\n tabsetNodeSchema,\n} from \"../model/schema\";\n\nconst mutableTabAttrsSchema = tabNodeSchema\n .pick({\n config: true,\n enableClose: true,\n enableDrag: true,\n enableRename: true,\n name: true,\n })\n .partial();\n\nconst mutableTabsetAttrsSchema = tabsetNodeSchema\n .pick({\n enableClose: true,\n enableMaximize: true,\n max: true,\n min: true,\n selected: true,\n weight: true,\n })\n .partial();\n\nconst mutableRowAttrsSchema = z\n .object({\n max: dimensionSchema.optional(),\n min: dimensionSchema.optional(),\n orientation: orientationSchema,\n weight: z.number(),\n })\n .partial();\n\nconst mutableNodeAttrsSchema = z.union([\n mutableTabAttrsSchema,\n mutableTabsetAttrsSchema,\n mutableRowAttrsSchema,\n]);\n\n// Every document mutation is one of these immutable, discriminated actions. The\n// reducer is exhaustive over this union; the React layer validates untrusted\n// payloads against actionSchema before dispatch.\nconst dockLocationSchema = z.enum([\n \"center\",\n \"split-bottom\",\n \"split-left\",\n \"split-right\",\n \"split-top\",\n]);\n\nconst actionSchema = z.discriminatedUnion(\"type\", [\n z.object({\n index: z.number().int().optional(),\n location: dockLocationSchema,\n tab: tabNodeSchema,\n targetId: z.string(),\n type: z.literal(\"addNode\"),\n }),\n z.object({\n index: z.number().int().optional(),\n location: dockLocationSchema,\n sourceId: z.string(),\n targetId: z.string(),\n type: z.literal(\"moveNode\"),\n }),\n z.object({\n index: z.number().int().optional(),\n location: dockLocationSchema,\n sourceId: z.string(),\n targetId: z.string(),\n type: z.literal(\"moveTabset\"),\n }),\n z.object({ index: z.number().int(), tabsetId: z.string(), type: z.literal(\"selectTab\") }),\n z.object({ tabsetId: z.string(), type: z.literal(\"setActiveTabset\") }),\n z.object({ tabsetId: z.string().nullable(), type: z.literal(\"setMaximizedTabset\") }),\n z.object({ name: z.string(), tabId: z.string(), type: z.literal(\"renameTab\") }),\n z.object({ tabId: z.string(), type: z.literal(\"deleteTab\") }),\n z.object({ tabsetId: z.string(), type: z.literal(\"deleteTabset\") }),\n z.object({\n rowId: z.string(),\n type: z.literal(\"adjustSplit\"),\n weights: z.array(z.number()),\n }),\n z.object({\n attrs: mutableNodeAttrsSchema,\n nodeId: z.string(),\n type: z.literal(\"updateNodeAttributes\"),\n }),\n z.object({ attrs: globalAttributesSchema.partial(), type: z.literal(\"updateGlobalAttributes\") }),\n]);\n\ntype DockLocation = z.infer<typeof dockLocationSchema>;\ntype MutableNodeAttrs = z.infer<typeof mutableNodeAttrsSchema>;\ntype Action = z.infer<typeof actionSchema>;\n\n// Where a drag will land: the dock location, the target tabset, and (for a tab\n// stack) the slot index. Co-located with DockLocation; the drag machine and the\n// React adapter both consume it.\ntype DropIntent = { index?: number; location: DockLocation; targetId: string };\n\nexport { actionSchema, dockLocationSchema, mutableNodeAttrsSchema };\nexport type { Action, DockLocation, DropIntent, MutableNodeAttrs };\n","// Unique ids for nodes and tabs. The reducer uses these for split/dock surgery;\n// they are public so consumers building addNode/moveNode payloads (or seed\n// models) have one blessed generator instead of rolling their own.\nconst createNodeId = (prefix = \"node\"): string => `${prefix}-${crypto.randomUUID()}`;\n\nconst createTabId = (): string => createNodeId(\"tab\");\n\nexport { createNodeId, createTabId };\n","import type { Dashfoo, RowNode, TabNode, TabsetNode } from \"./schema\";\n\n// A tab lives inside a tabset.\ntype TabContainer = TabsetNode;\ntype TabLocation = { container: TabContainer; index: number; tab: TabNode };\n\nconst collectTabsetsInRow = (row: RowNode, acc: Array<TabsetNode>): void => {\n for (const child of row.children) {\n if (child.type === \"tabset\") {\n acc.push(child);\n } else {\n collectTabsetsInRow(child, acc);\n }\n }\n};\n\n// Every tabset in the layout tree, depth-first.\nconst collectTabsets = (model: Dashfoo): Array<TabsetNode> => {\n const acc: Array<TabsetNode> = [];\n collectTabsetsInRow(model.layout, acc);\n return acc;\n};\n\nconst getFirstTabset = (model: Dashfoo): TabsetNode | undefined => collectTabsets(model)[0];\n\nconst findTabset = (model: Dashfoo, tabsetId: string): TabsetNode | undefined =>\n collectTabsets(model).find((tabset) => tabset.id === tabsetId);\n\nconst findTabInContainer = (container: TabContainer, tabId: string): TabLocation | undefined => {\n const index = container.children.findIndex((tab) => tab.id === tabId);\n if (index === -1) {\n return undefined;\n }\n\n const tab = container.children[index];\n if (!tab) {\n return undefined;\n }\n\n return { container, index, tab };\n};\n\n// Locate a tab anywhere in the model — searching every tabset — returning the\n// tab, its container, and its index within that container.\nconst findTab = (model: Dashfoo, tabId: string): TabLocation | undefined => {\n for (const tabset of collectTabsets(model)) {\n const found = findTabInContainer(tabset, tabId);\n if (found) {\n return found;\n }\n }\n\n return undefined;\n};\n\ntype AttributedNode = RowNode | TabNode | TabsetNode;\n\n// Find a row anywhere in the layout tree by id.\nconst findRow = (row: RowNode, id: string): RowNode | undefined => {\n if (row.id === id) {\n return row;\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = findRow(child, id);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n};\n\nconst findAttributedNodeInRow = (row: RowNode, id: string): AttributedNode | undefined => {\n if (row.id === id) {\n return row;\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = findAttributedNodeInRow(child, id);\n if (found) {\n return found;\n }\n continue;\n }\n if (child.id === id) {\n return child;\n }\n const tab = child.children.find((candidate) => candidate.id === id);\n if (tab) {\n return tab;\n }\n }\n return undefined;\n};\n\n// Locate any attributed node (row, tabset, or tab) by id — backs updateNodeAttributes.\nconst findAttributedNode = (model: Dashfoo, id: string): AttributedNode | undefined =>\n findAttributedNodeInRow(model.layout, id);\n\n// The row holding a tabset, and the tabset's index within it — backs split placement.\nconst findTabsetParent = (\n row: RowNode,\n tabsetId: string,\n): { index: number; parent: RowNode } | undefined => {\n const index = row.children.findIndex((child) => child.type === \"tabset\" && child.id === tabsetId);\n if (index !== -1) {\n return { index, parent: row };\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = findTabsetParent(child, tabsetId);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n};\n\nconst collectIdsInRow = (row: RowNode, acc: Array<string>): void => {\n acc.push(row.id);\n for (const child of row.children) {\n if (child.type === \"row\") {\n collectIdsInRow(child, acc);\n } else {\n acc.push(child.id);\n for (const tab of child.children) {\n acc.push(tab.id);\n }\n }\n }\n};\n\n// Ids that appear more than once across the whole model (rows, tabsets, tabs).\n// Duplicate ids produce duplicate React keys and corrupt the resize/drag\n// plumbing, so this backs a load-time diagnostic.\nconst findDuplicateIds = (model: Dashfoo): Array<string> => {\n const ids: Array<string> = [];\n collectIdsInRow(model.layout, ids);\n const seen = new Set<string>();\n const duplicates = new Set<string>();\n for (const id of ids) {\n if (seen.has(id)) {\n duplicates.add(id);\n }\n seen.add(id);\n }\n return [...duplicates];\n};\n\nexport {\n collectTabsets,\n findAttributedNode,\n findDuplicateIds,\n findRow,\n findTab,\n findTabset,\n findTabsetParent,\n getFirstTabset,\n};\nexport type { AttributedNode, TabContainer, TabLocation };\n","// Ergonomic constructors for the layout model, so consumers seed a Dashfoo\n// without hand-writing `type`/`version`/`selected` boilerplate or nested\n// literals. Every builder fills the required-but-mechanical fields and leaves\n// the meaningful ones (ids that get referenced, weights) to the caller. Output\n// is schema-valid by construction; id uniqueness across reused components is\n// the caller's responsibility (tabs default their id to the component name), so\n// `model` warns once if it detects duplicate node ids.\n\nimport { createNodeId } from \"./ids\";\nimport type {\n Dashfoo,\n Dimension,\n GlobalAttributes,\n Json,\n RowNode,\n TabNode,\n TabsetNode,\n} from \"./schema\";\nimport { findDuplicateIds } from \"./tree\";\n\nconst toDimension = (value: number | Dimension): Dimension =>\n typeof value === \"number\" ? { unit: \"px\", value } : value;\n\ntype TabOptions = {\n config?: Json;\n enableClose?: boolean;\n enableDrag?: boolean;\n enableRename?: boolean;\n id?: string;\n};\n\n// The id defaults to the component name — the common case where a layout holds\n// one tab per component and references it by that name.\nconst tab = (component: string, name: string, options: TabOptions = {}): TabNode => ({\n component,\n name,\n type: \"tab\",\n ...options,\n id: options.id ?? component,\n});\n\ntype TabsetOptions = {\n enableClose?: boolean;\n enableMaximize?: boolean;\n id?: string;\n max?: number | Dimension;\n min?: number | Dimension;\n name?: string;\n selected?: number;\n weight?: number;\n};\n\nconst tabset = (children: Array<TabNode>, options: TabsetOptions = {}): TabsetNode => {\n const { max, min, ...rest } = options;\n return {\n children,\n type: \"tabset\",\n ...rest,\n ...(max === undefined ? {} : { max: toDimension(max) }),\n ...(min === undefined ? {} : { min: toDimension(min) }),\n id: options.id ?? createNodeId(\"tabset\"),\n selected: options.selected ?? 0,\n };\n};\n\ntype RowOptions = {\n id?: string;\n max?: number | Dimension;\n min?: number | Dimension;\n orientation?: RowNode[\"orientation\"];\n weight?: number;\n};\n\nconst row = (children: RowNode[\"children\"], options: RowOptions = {}): RowNode => {\n const { max, min, ...rest } = options;\n return {\n children,\n type: \"row\",\n ...rest,\n ...(max === undefined ? {} : { max: toDimension(max) }),\n ...(min === undefined ? {} : { min: toDimension(min) }),\n id: options.id ?? createNodeId(\"row\"),\n orientation: options.orientation ?? \"row\",\n };\n};\n\ntype ModelOptions = {\n activeTabsetId?: string;\n global?: GlobalAttributes;\n maximizedTabsetId?: string;\n};\n\nconst model = (layout: RowNode, options: ModelOptions = {}): Dashfoo => {\n const built: Dashfoo = {\n layout,\n version: 1,\n ...options,\n global: options.global ?? {},\n };\n const duplicates = findDuplicateIds(built);\n if (duplicates.length > 0) {\n // oxlint-disable-next-line no-console\n console.warn(\n `[dashfoo] builder produced duplicate node ids: ${duplicates.join(\", \")} — pass explicit ids when reusing a component`,\n );\n }\n return built;\n};\n\nexport { model, row, tab, tabset };\nexport type { ModelOptions, RowOptions, TabOptions, TabsetOptions };\n","import type { Edge } from \"../model/schema\";\nimport type { DockLocation } from \"../state/actions\";\n\ntype Point = { x: number; y: number };\ntype Rect = { height: number; width: number; x: number; y: number };\ntype DockTarget = { kind: \"tab\" } | { edge: Edge; kind: \"split\" };\ntype BandOptions = { bandFraction?: number };\n\n// Fractional distance of the pointer from each of the four edges of a rect.\nconst edgeDistances = (\n pointer: Point,\n rect: Rect,\n): { bottom: number; left: number; right: number; top: number } => {\n const fx = (pointer.x - rect.x) / rect.width;\n const fy = (pointer.y - rect.y) / rect.height;\n return { bottom: 1 - fy, left: fx, right: 1 - fx, top: fy };\n};\n\nconst closestEdge = (d: { bottom: number; left: number; right: number; top: number }): Edge => {\n const min = Math.min(d.left, d.right, d.top, d.bottom);\n if (min === d.left) {\n return \"left\";\n }\n if (min === d.right) {\n return \"right\";\n }\n if (min === d.top) {\n return \"top\";\n }\n return \"bottom\";\n};\n\n// Where a drag dropped over a tabset should land: stacked as a tab when the\n// pointer is in the center, or splitting the tabset when it is within an outer\n// band (default 22%) of one of the four edges — the closer edge wins in corners.\nconst resolveDockTarget = (pointer: Point, rect: Rect, opts?: BandOptions): DockTarget => {\n const band = opts?.bandFraction ?? 0.22;\n // a zero-size tabset has no meaningful edges; dividing by its width/height\n // yields NaN distances that would silently resolve to a bogus split.\n if (rect.width <= 0 || rect.height <= 0) {\n return { kind: \"tab\" };\n }\n const distances = edgeDistances(pointer, rect);\n const min = Math.min(distances.left, distances.right, distances.top, distances.bottom);\n\n if (min > band) {\n return { kind: \"tab\" };\n }\n return { edge: closestEdge(distances), kind: \"split\" };\n};\n\n// The region the dock indicator highlights for a given location over a tabset:\n// the whole tabset for a center stack, the matching half for a split.\nconst zoneRect = (rect: Rect, location: DockLocation): Rect => {\n const halfW = rect.width / 2;\n const halfH = rect.height / 2;\n switch (location) {\n case \"split-bottom\": {\n return { height: halfH, width: rect.width, x: rect.x, y: rect.y + halfH };\n }\n case \"split-left\": {\n return { height: rect.height, width: halfW, x: rect.x, y: rect.y };\n }\n case \"split-right\": {\n return { height: rect.height, width: halfW, x: rect.x + halfW, y: rect.y };\n }\n case \"split-top\": {\n return { height: halfH, width: rect.width, x: rect.x, y: rect.y };\n }\n default: {\n return rect;\n }\n }\n};\n\nexport { resolveDockTarget, zoneRect };\nexport type { BandOptions, DockTarget, Point, Rect };\n","import type { Dashfoo, RowNode, TabsetNode } from \"./schema\";\nimport { collectTabsets } from \"./tree\";\n\nconst clampSelected = (length: number, selected: number): number => {\n if (length === 0) {\n return 0;\n }\n // coerce to a non-negative integer first to match the schema's z.number().int():\n // a persisted 1.5 or NaN must not survive as a fractional/NaN index.\n const base = Number.isFinite(selected) ? Math.trunc(selected) : 0;\n if (base < 0) {\n return 0;\n }\n if (base > length - 1) {\n return length - 1;\n }\n return base;\n};\n\nconst normalizeTabset = (tabset: TabsetNode): TabsetNode => ({\n ...tabset,\n selected: clampSelected(tabset.children.length, tabset.selected),\n});\n\n// Drop empty tabsets and empty rows, recurse into child rows, and simplify a\n// single-child row by lifting its lone child (which inherits the lifted row's\n// weight so sizing is preserved).\nconst normalizeRowChildren = (children: RowNode[\"children\"]): RowNode[\"children\"] => {\n const out: RowNode[\"children\"] = [];\n\n for (const child of children) {\n if (child.type === \"tabset\") {\n if (child.children.length > 0) {\n out.push(normalizeTabset(child));\n }\n continue;\n }\n\n const grandchildren = normalizeRowChildren(child.children);\n if (grandchildren.length === 0) {\n continue;\n }\n if (grandchildren.length === 1) {\n const only = grandchildren[0];\n if (only !== undefined) {\n out.push(child.weight === undefined ? only : { ...only, weight: child.weight });\n }\n continue;\n }\n out.push({ ...child, children: grandchildren });\n }\n\n return out;\n};\n\n// The root is always a row; if it reduces to a single child that is itself a\n// row, absorb that row (adopt its children + orientation) to avoid redundant\n// nesting.\nconst normalizeLayout = (root: RowNode): RowNode => {\n let children = normalizeRowChildren(root.children);\n let orientation = root.orientation;\n\n let inner = children[0];\n while (children.length === 1 && inner?.type === \"row\") {\n children = inner.children;\n orientation = inner.orientation;\n inner = children[0];\n }\n\n return { ...root, children, orientation };\n};\n\n// Self-healing pass run after every action so the tree stays canonical: no empty\n// tabsets or rows, no redundant single-child rows, selected indices in range,\n// and active/maximized ids that always point at an existing tabset.\nconst normalize = (model: Dashfoo): Dashfoo => {\n const layout = normalizeLayout(model.layout);\n const withLayout: Dashfoo = { ...model, layout };\n\n const tabsetIds = new Set(collectTabsets(withLayout).map((tabset) => tabset.id));\n const firstTabsetId = collectTabsets(withLayout)[0]?.id;\n\n const activeTabsetId =\n model.activeTabsetId !== undefined && tabsetIds.has(model.activeTabsetId)\n ? model.activeTabsetId\n : firstTabsetId;\n\n const maximizedTabsetId =\n model.maximizedTabsetId !== undefined && tabsetIds.has(model.maximizedTabsetId)\n ? model.maximizedTabsetId\n : undefined;\n\n return { ...withLayout, activeTabsetId, maximizedTabsetId };\n};\n\nexport { normalize };\n","import { createNodeId } from \"../model/ids\";\nimport { normalize } from \"../model/invariants\";\nimport type { Dashfoo, RowNode, TabNode, TabsetNode } from \"../model/schema\";\nimport { findAttributedNode, findRow, findTab, findTabset, findTabsetParent } from \"../model/tree\";\n\nimport type { Action, DockLocation } from \"./actions\";\n\nconst assertNever = (value: never): never => {\n throw new Error(`Unhandled action: ${JSON.stringify(value)}`);\n};\n\nconst removeTabset = (row: RowNode, tabsetId: string): boolean => {\n const index = row.children.findIndex((child) => child.type === \"tabset\" && child.id === tabsetId);\n if (index !== -1) {\n row.children.splice(index, 1);\n return true;\n }\n for (const child of row.children) {\n if (child.type === \"row\" && removeTabset(child, tabsetId)) {\n return true;\n }\n }\n return false;\n};\n\n// Detach a tabset from the tree and return it (for moving a whole tabset).\nconst removeTabsetReturning = (row: RowNode, tabsetId: string): TabsetNode | undefined => {\n const index = row.children.findIndex((child) => child.type === \"tabset\" && child.id === tabsetId);\n if (index !== -1) {\n const [removed] = row.children.splice(index, 1);\n return removed?.type === \"tabset\" ? removed : undefined;\n }\n for (const child of row.children) {\n if (child.type === \"row\") {\n const found = removeTabsetReturning(child, tabsetId);\n if (found) {\n return found;\n }\n }\n }\n return undefined;\n};\n\nconst splitOrientation = (location: DockLocation): \"column\" | \"row\" =>\n location === \"split-left\" || location === \"split-right\" ? \"row\" : \"column\";\n\nconst splitsBefore = (location: DockLocation): boolean =>\n location === \"split-left\" || location === \"split-top\";\n\n// Place an (already-detached) tabset beside the target for a split-* location:\n// reuse the parent row when the orientation already matches, else wrap the target\n// in a new row. Shared by insertTab (a fresh tabset) and moveTabset (an existing\n// one). Splits the target's space in half.\nconst placeBesideTarget = (\n draft: Dashfoo,\n placed: TabsetNode,\n targetId: string,\n location: DockLocation,\n): void => {\n const targetTabset = findTabset(draft, targetId);\n const found = findTabsetParent(draft.layout, targetId);\n if (!targetTabset || !found) {\n return;\n }\n\n const orientation = splitOrientation(location);\n const before = splitsBefore(location);\n\n if (found.parent.orientation === orientation) {\n const targetWeight = targetTabset.weight ?? 100;\n targetTabset.weight = targetWeight / 2;\n placed.weight = targetWeight / 2;\n found.parent.children.splice(before ? found.index : found.index + 1, 0, placed);\n } else {\n const targetWeight = targetTabset.weight;\n targetTabset.weight = 50;\n placed.weight = 50;\n const newRow: RowNode = {\n children: before ? [placed, targetTabset] : [targetTabset, placed],\n id: createNodeId(\"row\"),\n orientation,\n type: \"row\",\n };\n if (targetWeight !== undefined) {\n newRow.weight = targetWeight;\n }\n found.parent.children.splice(found.index, 1, newRow);\n }\n\n draft.activeTabsetId = placed.id;\n};\n\n// Insert a tab into the model at a dock target. center stacks it into the target\n// tabset; split-* creates a new tabset beside the target.\ntype DropTarget = { id: string; index?: number; location: DockLocation };\n\nconst insertTab = (draft: Dashfoo, tab: TabNode, target: DropTarget): void => {\n const { id: targetId, index, location } = target;\n const targetTabset = findTabset(draft, targetId);\n if (!targetTabset) {\n return;\n }\n\n if (location === \"center\") {\n const at = index ?? targetTabset.children.length;\n targetTabset.children.splice(at, 0, tab);\n targetTabset.selected = at;\n return;\n }\n\n const newTabset: TabsetNode = {\n children: [tab],\n id: createNodeId(\"tabset\"),\n selected: 0,\n type: \"tabset\",\n weight: 50,\n };\n placeBesideTarget(draft, newTabset, targetId, location);\n};\n\nconst applyAction = (draft: Dashfoo, action: Action): void => {\n switch (action.type) {\n case \"addNode\": {\n insertTab(draft, action.tab, {\n id: action.targetId,\n index: action.index,\n location: action.location,\n });\n return;\n }\n case \"adjustSplit\": {\n const row = findRow(draft.layout, action.rowId);\n if (!row) {\n return;\n }\n for (let index = 0; index < action.weights.length; index++) {\n const child = row.children[index];\n const weight = action.weights[index];\n if (!child || weight === undefined) {\n continue;\n }\n child.weight = weight;\n }\n return;\n }\n case \"deleteTab\": {\n const location = findTab(draft, action.tabId);\n if (location) {\n location.container.children.splice(location.index, 1);\n }\n return;\n }\n case \"deleteTabset\": {\n removeTabset(draft.layout, action.tabsetId);\n return;\n }\n case \"moveNode\": {\n const source = findTab(draft, action.sourceId);\n if (!source) {\n return;\n }\n // Remove the source first; action.index is the destination slot measured\n // against the tabs that remain (the drag adapter excludes the dragged tab),\n // so it indexes the post-removal array directly — no reorder adjustment.\n const [removed] = source.container.children.splice(source.index, 1);\n if (!removed) {\n return;\n }\n insertTab(draft, removed, {\n id: action.targetId,\n index: action.index,\n location: action.location,\n });\n return;\n }\n case \"moveTabset\": {\n if (action.sourceId === action.targetId) {\n return;\n }\n const source = findTabset(draft, action.sourceId);\n if (!source) {\n return;\n }\n if (action.location === \"center\") {\n const target = findTabset(draft, action.targetId);\n if (target) {\n target.children.push(...source.children);\n removeTabset(draft.layout, action.sourceId);\n }\n return;\n }\n // split-* moves the whole tabset beside the target.\n if (action.location.startsWith(\"split-\")) {\n const detached = removeTabsetReturning(draft.layout, action.sourceId);\n if (detached) {\n placeBesideTarget(draft, detached, action.targetId, action.location);\n }\n }\n return;\n }\n case \"renameTab\": {\n const location = findTab(draft, action.tabId);\n if (location) {\n location.tab.name = action.name;\n }\n return;\n }\n case \"selectTab\": {\n const tabset = findTabset(draft, action.tabsetId);\n if (tabset) {\n tabset.selected = action.index;\n }\n return;\n }\n case \"setActiveTabset\": {\n if (findTabset(draft, action.tabsetId)) {\n draft.activeTabsetId = action.tabsetId;\n }\n return;\n }\n case \"setMaximizedTabset\": {\n draft.maximizedTabsetId = action.tabsetId ?? undefined;\n return;\n }\n case \"updateGlobalAttributes\": {\n Object.assign(draft.global, action.attrs);\n return;\n }\n case \"updateNodeAttributes\": {\n const node = findAttributedNode(draft, action.nodeId);\n if (node) {\n Object.assign(node, action.attrs);\n }\n return;\n }\n default: {\n assertNever(action);\n }\n }\n};\n\n// The canonical engine: deep-copy the model (so the input is never mutated),\n// apply one action to the copy, then run the self-healing invariants so the\n// result is always a valid, canonical model.\nconst reducer = (model: Dashfoo, action: Action): Dashfoo => {\n const draft = structuredClone(model);\n applyAction(draft, action);\n return normalize(draft);\n};\n\nexport { reducer };\n","import type { Dashfoo } from \"../model/schema\";\n\nimport type { Action } from \"./actions\";\nimport { reducer } from \"./reducer\";\n\n// past/present/future snapshots. Each committed action is its own undo step:\n// rrp v4's onLayoutChanged fires one adjustSplit per release, so there is no\n// per-frame burst to coalesce.\ntype History = {\n future: Array<Dashfoo>;\n past: Array<Dashfoo>;\n present: Dashfoo;\n};\n\n// Snapshots are full model clones, so an unbounded past grows memory for the\n// life of the session (every action — even a tab click — is one step). 100\n// steps is far beyond practical undo depth while keeping memory flat.\nconst HISTORY_LIMIT = 100;\n\nconst createHistory = (present: Dashfoo): History => ({\n future: [],\n past: [],\n present,\n});\n\nconst canUndo = (history: History): boolean => history.past.length > 0;\nconst canRedo = (history: History): boolean => history.future.length > 0;\n\nconst dispatch = (history: History, action: Action): History => {\n const present = reducer(history.present, action);\n return { future: [], past: [...history.past, history.present].slice(-HISTORY_LIMIT), present };\n};\n\nconst undo = (history: History): History => {\n const previous = history.past.at(-1);\n if (previous === undefined) {\n return history;\n }\n return {\n future: [history.present, ...history.future],\n past: history.past.slice(0, -1),\n present: previous,\n };\n};\n\nconst redo = (history: History): History => {\n const [next, ...rest] = history.future;\n if (next === undefined) {\n return history;\n }\n return {\n future: rest,\n past: [...history.past, history.present].slice(-HISTORY_LIMIT),\n present: next,\n };\n};\n\nexport { canRedo, canUndo, createHistory, dispatch, HISTORY_LIMIT, redo, undo };\nexport type { History };\n","import { assign, setup } from \"xstate\";\n\nimport type { Dashfoo } from \"../model/schema\";\nimport type { Action } from \"../state/actions\";\nimport type { History } from \"../state/history\";\nimport { createHistory, dispatch, redo, undo } from \"../state/history\";\n\ntype DashfooContext = { history: History };\n\ntype DashfooEvent =\n | { action: Action; type: \"DISPATCH\" }\n | { model: Dashfoo; type: \"SET_MODEL\" }\n | { type: \"REDO\" }\n | { type: \"UNDO\" };\n\ntype DashfooInput = { model: Dashfoo };\n\n// The document actor: it owns the undo/redo history (whose `present` is the live\n// model) and processes document mutations by invoking the pure reducer via the\n// history helpers. It has no finite states beyond `ready` — the document is data,\n// not a lifecycle — so every mutation is an event handled with assign.\nconst dashfooMachine = setup({\n types: {\n context: {} as DashfooContext,\n events: {} as DashfooEvent,\n input: {} as DashfooInput,\n },\n}).createMachine({\n context: ({ input }) => ({ history: createHistory(input.model) }),\n id: \"dashfoo\",\n initial: \"ready\",\n states: {\n ready: {\n on: {\n DISPATCH: {\n actions: assign({\n history: ({ context, event }) => dispatch(context.history, event.action),\n }),\n },\n REDO: {\n actions: assign({ history: ({ context }) => redo(context.history) }),\n },\n SET_MODEL: {\n actions: assign({ history: ({ event }) => createHistory(event.model) }),\n },\n UNDO: {\n actions: assign({ history: ({ context }) => undo(context.history) }),\n },\n },\n },\n },\n});\n\nexport { dashfooMachine };\nexport type { DashfooContext, DashfooEvent, DashfooInput };\n","import { assign, emit, setup } from \"xstate\";\n\nimport type { TabNode } from \"../model/schema\";\nimport type { Action, DropIntent } from \"../state/actions\";\n\n// Internal subjects reference a node already in the document; an external\n// subject carries the TabNode to insert on drop (a widget dragged in from\n// outside the layout), so its id never has to exist in the model.\ntype DragSubject =\n | { id: string; kind: \"tab\" | \"tabset\" }\n | { id: string; kind: \"external\"; tab: TabNode };\n\ntype DragContext = { intent: DropIntent | null; subject: DragSubject | null };\n\ntype DragEvent =\n | { intent: DropIntent | null; type: \"OVER\" }\n | { subject: DragSubject; type: \"START\" }\n | { type: \"CANCEL\" }\n | { type: \"DROP\" };\n\ntype DragEmitted = { action: Action; type: \"COMMIT\" };\n\nconst requireDrop = (context: DragContext): { intent: DropIntent; subject: DragSubject } => {\n if (context.subject === null || context.intent === null) {\n throw new Error(\"drop requires a subject and a drop intent\");\n }\n return { intent: context.intent, subject: context.subject };\n};\n\n// The drag/dock interaction lifecycle, driven by abstract events the dnd-kit\n// adapter maps from pointer/keyboard input. It owns transient drag state only and\n// never touches the document — on a valid drop it emits a COMMIT carrying the\n// resulting action (moveNode/moveTabset, or addNode for an external subject),\n// which the React layer forwards to the document machine.\nconst dragDockMachine = setup({\n guards: {\n hasValidDrop: ({ context }) => context.subject !== null && context.intent !== null,\n },\n types: {\n context: {} as DragContext,\n emitted: {} as DragEmitted,\n events: {} as DragEvent,\n },\n}).createMachine({\n context: { intent: null, subject: null },\n id: \"dragDock\",\n initial: \"idle\",\n states: {\n dragging: {\n on: {\n CANCEL: { target: \"idle\" },\n DROP: [\n {\n actions: emit(({ context }) => {\n const { intent, subject } = requireDrop(context);\n const action: Action =\n subject.kind === \"external\"\n ? {\n index: intent.index,\n location: intent.location,\n tab: subject.tab,\n targetId: intent.targetId,\n type: \"addNode\",\n }\n : {\n index: intent.index,\n location: intent.location,\n sourceId: subject.id,\n targetId: intent.targetId,\n type: subject.kind === \"tabset\" ? \"moveTabset\" : \"moveNode\",\n };\n return { action, type: \"COMMIT\" };\n }),\n guard: \"hasValidDrop\",\n target: \"idle\",\n },\n { target: \"idle\" },\n ],\n OVER: { actions: assign({ intent: ({ event }) => event.intent }) },\n },\n },\n idle: {\n entry: assign({ intent: null, subject: null }),\n on: {\n START: { actions: assign({ subject: ({ event }) => event.subject }), target: \"dragging\" },\n },\n },\n },\n});\n\nexport { dragDockMachine };\nexport type { DragContext, DragEmitted, DragEvent, DragSubject };\n","import { normalize } from \"./invariants\";\nimport type { Dashfoo } from \"./schema\";\nimport { dashfooSchema } from \"./schema\";\nimport { findDuplicateIds } from \"./tree\";\n\n// Validate an untrusted value against the schema and return a normalized,\n// canonical model. Throws if the value is not a valid model — including a\n// payload whose `version` is not the pinned `1`; warns (does not throw) on\n// duplicate ids, which corrupt React keys and the plumbing.\nconst parseModel = (value: unknown): Dashfoo => {\n const model = normalize(dashfooSchema.parse(value));\n const duplicates = findDuplicateIds(model);\n if (duplicates.length > 0) {\n // oxlint-disable-next-line no-console\n console.warn(`[dashfoo] duplicate node ids in the loaded layout: ${duplicates.join(\", \")}`);\n }\n return model;\n};\n\nconst toJSON = (model: Dashfoo): string => JSON.stringify(model);\n\nconst fromJSON = (json: string): Dashfoo => parseModel(JSON.parse(json));\n\nexport { fromJSON, parseModel, toJSON };\n","import { normalize } from \"./invariants\";\nimport type { Dashfoo, Orientation, TabsetNode } from \"./schema\";\nimport { collectTabsets } from \"./tree\";\n\n// Flatten any layout into a single row (default a column) of all its tabsets, in\n// document order, each with equal weight, so nothing is lost on a narrow screen;\n// maximize is cleared. The building block for a mobile breakpoint:\n// `stackModel(desktopModel)`.\nconst stackModel = (model: Dashfoo, orientation: Orientation = \"column\"): Dashfoo => {\n // Equal weight so stacked panels share the space evenly. A loop (not map) so we\n // create fresh nodes without mutating the shared input tabsets.\n const children: Array<TabsetNode> = [];\n for (const tabset of collectTabsets(model)) {\n children.push({ ...tabset, weight: 1 });\n }\n\n return normalize({\n ...model,\n layout: { children, id: model.layout.id, orientation, type: \"row\" },\n maximizedTabsetId: undefined,\n });\n};\n\nexport { stackModel };\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dashfoo/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Headless docking-layout engine: model, actions, reducer, serialization, geometry, and the XState actor system. No React.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Pedro Filho <pedro@filho.me>",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"xstate": "^5.0.0",
|
|
29
|
-
"zod": "^
|
|
29
|
+
"zod": "^4.0.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"typescript": "^6.0.3",
|