@object-ui/plugin-grid 0.5.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +14 -5
- package/CHANGELOG.md +15 -0
- package/README.md +97 -0
- package/dist/index.js +622 -531
- package/dist/index.umd.cjs +3 -3
- package/dist/packages/plugin-grid/src/ListColumnExtensions.test.d.ts +0 -0
- package/dist/packages/plugin-grid/src/ListColumnSchema.test.d.ts +1 -0
- package/dist/packages/plugin-grid/src/ObjectGrid.d.ts +7 -1
- package/dist/packages/plugin-grid/src/__tests__/VirtualGrid.test.d.ts +0 -0
- package/package.json +8 -8
- package/src/ListColumnExtensions.test.tsx +374 -0
- package/src/ListColumnSchema.test.ts +88 -0
- package/src/ObjectGrid.msw.test.tsx +24 -1
- package/src/ObjectGrid.tsx +273 -109
- package/src/__tests__/VirtualGrid.test.tsx +438 -0
- package/src/index.tsx +12 -2
package/dist/index.umd.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
(function(A,
|
|
1
|
+
(function(A,R){typeof exports=="object"&&typeof module<"u"?R(exports,require("react"),require("@object-ui/core"),require("@object-ui/react"),require("@object-ui/fields"),require("@object-ui/components"),require("lucide-react"),require("react-dom")):typeof define=="function"&&define.amd?define(["exports","react","@object-ui/core","@object-ui/react","@object-ui/fields","@object-ui/components","lucide-react","react-dom"],R):(A=typeof globalThis<"u"?globalThis:A||self,R(A.ObjectUIPluginGrid={},A.React,A.core,A.react,A.fields,A.components,A.lucideReact,A.ReactDOM))})(this,(function(A,R,he,B,me,q,de,Ce){"use strict";function _e(n){const a=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(n){for(const e in n)if(e!=="default"){const s=Object.getOwnPropertyDescriptor(n,e);Object.defineProperty(a,e,s.get?s:{enumerable:!0,get:()=>n[e]})}}return a.default=n,Object.freeze(a)}const le=_e(R);var ae={exports:{}},Q={};var pe;function Ae(){if(pe)return Q;pe=1;var n=Symbol.for("react.transitional.element"),a=Symbol.for("react.fragment");function e(s,t,i){var o=null;if(i!==void 0&&(o=""+i),t.key!==void 0&&(o=""+t.key),"key"in t){i={};for(var l in t)l!=="key"&&(i[l]=t[l])}else i=t;return t=i.ref,{$$typeof:n,type:s,key:o,ref:t!==void 0?t:null,props:i}}return Q.Fragment=a,Q.jsx=e,Q.jsxs=e,Q}var ee={};var ge;function Re(){return ge||(ge=1,process.env.NODE_ENV!=="production"&&(function(){function n(r){if(r==null)return null;if(typeof r=="function")return r.$$typeof===U?null:r.displayName||r.name||null;if(typeof r=="string")return r;switch(r){case w:return"Fragment";case O:return"Profiler";case z:return"StrictMode";case Y:return"Suspense";case $:return"SuspenseList";case J:return"Activity"}if(typeof r=="object")switch(typeof r.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),r.$$typeof){case v:return"Portal";case M:return r.displayName||"Context";case F:return(r._context.displayName||"Context")+".Consumer";case C:var h=r.render;return r=r.displayName,r||(r=h.displayName||h.name||"",r=r!==""?"ForwardRef("+r+")":"ForwardRef"),r;case L:return h=r.displayName||null,h!==null?h:n(r.type)||"Memo";case I:h=r._payload,r=r._init;try{return n(r(h))}catch{}}return null}function a(r){return""+r}function e(r){try{a(r);var h=!1}catch{h=!0}if(h){h=console;var y=h.error,S=typeof Symbol=="function"&&Symbol.toStringTag&&r[Symbol.toStringTag]||r.constructor.name||"Object";return y.call(h,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",S),a(r)}}function s(r){if(r===w)return"<>";if(typeof r=="object"&&r!==null&&r.$$typeof===I)return"<...>";try{var h=n(r);return h?"<"+h+">":"<...>"}catch{return"<...>"}}function t(){var r=K.A;return r===null?null:r.getOwner()}function i(){return Error("react-stack-top-frame")}function o(r){if(te.call(r,"key")){var h=Object.getOwnPropertyDescriptor(r,"key").get;if(h&&h.isReactWarning)return!1}return r.key!==void 0}function l(r,h){function y(){ce||(ce=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",h))}y.isReactWarning=!0,Object.defineProperty(r,"key",{get:y,configurable:!0})}function p(){var r=n(this.type);return ne[r]||(ne[r]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),r=this.props.ref,r!==void 0?r:null}function g(r,h,y,S,G,Z){var f=y.ref;return r={$$typeof:_,type:r,key:h,props:y,_owner:S},(f!==void 0?f:null)!==null?Object.defineProperty(r,"ref",{enumerable:!1,get:p}):Object.defineProperty(r,"ref",{enumerable:!1,value:null}),r._store={},Object.defineProperty(r._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(r,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(r,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:G}),Object.defineProperty(r,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:Z}),Object.freeze&&(Object.freeze(r.props),Object.freeze(r)),r}function m(r,h,y,S,G,Z){var f=h.children;if(f!==void 0)if(S)if(D(f)){for(S=0;S<f.length;S++)E(f[S]);Object.freeze&&Object.freeze(f)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else E(f);if(te.call(h,"key")){f=n(r);var x=Object.keys(h).filter(function(j){return j!=="key"});S=0<x.length?"{key: someKey, "+x.join(": ..., ")+": ...}":"{key: someKey}",ue[f+S]||(x=0<x.length?"{"+x.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
|
|
2
2
|
let props = %s;
|
|
3
3
|
<%s {...props} />
|
|
4
4
|
React keys must be passed directly to JSX without using spread:
|
|
5
5
|
let props = %s;
|
|
6
|
-
<%s key={someKey} {...props} />`,
|
|
6
|
+
<%s key={someKey} {...props} />`,S,f,x,f),ue[f+S]=!0)}if(f=null,y!==void 0&&(e(y),f=""+y),o(h)&&(e(h.key),f=""+h.key),"key"in h){y={};for(var N in h)N!=="key"&&(y[N]=h[N])}else y=h;return f&&l(y,typeof r=="function"?r.displayName||r.name||"Unknown":r),g(r,f,y,t(),G,Z)}function E(r){u(r)?r._store&&(r._store.validated=1):typeof r=="object"&&r!==null&&r.$$typeof===I&&(r._payload.status==="fulfilled"?u(r._payload.value)&&r._payload.value._store&&(r._payload.value._store.validated=1):r._store&&(r._store.validated=1))}function u(r){return typeof r=="object"&&r!==null&&r.$$typeof===_}var b=R,_=Symbol.for("react.transitional.element"),v=Symbol.for("react.portal"),w=Symbol.for("react.fragment"),z=Symbol.for("react.strict_mode"),O=Symbol.for("react.profiler"),F=Symbol.for("react.consumer"),M=Symbol.for("react.context"),C=Symbol.for("react.forward_ref"),Y=Symbol.for("react.suspense"),$=Symbol.for("react.suspense_list"),L=Symbol.for("react.memo"),I=Symbol.for("react.lazy"),J=Symbol.for("react.activity"),U=Symbol.for("react.client.reference"),K=b.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,te=Object.prototype.hasOwnProperty,D=Array.isArray,X=console.createTask?console.createTask:function(){return null};b={react_stack_bottom_frame:function(r){return r()}};var ce,ne={},V=b.react_stack_bottom_frame.bind(b,i)(),se=X(s(i)),ue={};ee.Fragment=w,ee.jsx=function(r,h,y){var S=1e4>K.recentlyCreatedOwnerStacks++;return m(r,h,y,!1,S?Error("react-stack-top-frame"):V,S?X(s(r)):se)},ee.jsxs=function(r,h,y){var S=1e4>K.recentlyCreatedOwnerStacks++;return m(r,h,y,!0,S?Error("react-stack-top-frame"):V,S?X(s(r)):se)}})()),ee}var be;function Te(){return be||(be=1,process.env.NODE_ENV==="production"?ae.exports=Ae():ae.exports=Re()),ae.exports}var c=Te();function Ne(n){return n.data?Array.isArray(n.data)?{provider:"value",items:n.data}:n.data:n.staticData?{provider:"value",items:n.staticData}:n.objectName?{provider:"object",object:n.objectName}:null}function ve(n){if(!(!n||n.length===0))return typeof n[0]=="object"&&n[0]!==null,n}const ye=({schema:n,dataSource:a,onEdit:e,onDelete:s,onRowSelect:t,onRowClick:i,onCellChange:o,onRowSave:l,onBatchSave:p,...g})=>{const[m,E]=R.useState([]),[u,b]=R.useState(!0),[_,v]=R.useState(null),[w,z]=R.useState(null),O=g.data,F=B.useDataScope(n.bind),M=Ne(n),C=R.useMemo(()=>O&&Array.isArray(O)?{provider:"value",items:O}:F&&Array.isArray(F)?{provider:"value",items:F}:M,[JSON.stringify(M),F,O]),Y=C?.provider==="value",$=C?.provider==="object"&&C&&"object"in C?C.object:n.objectName,L=n.fields,I=n.columns,J=n.filter,U=n.sort,K=n.pagination,te=n.pageSize;R.useEffect(()=>{Y&&C?.provider==="value"&&(E(f=>{const x=C.items;return JSON.stringify(f)!==JSON.stringify(x)?x:f}),b(!1))},[Y,C]),R.useEffect(()=>{if(Y)return;let f=!1;return(async()=>{b(!0),v(null);try{let N=null;if((ve(I)||L)&&$)N={name:$,fields:{}};else if($&&a){const d=await a.getObjectSchema($);if(f)return;N=d}else throw $?new Error("DataSource required"):new Error("Object name required for data fetching");if(f||z(N),a&&$){const k={$select:(()=>{if(L)return L;if(I&&Array.isArray(I))return I.map(T=>typeof T=="string"?T:T.field)})(),$top:K?.pageSize||te||50};J&&Array.isArray(J)?k.$filter=J:n.defaultFilters&&(k.$filter=n.defaultFilters),U?typeof U=="string"?k.$orderby=U:Array.isArray(U)&&(k.$orderby=U.map(T=>`${T.field} ${T.order}`).join(", ")):n.defaultSort&&(k.$orderby=`${n.defaultSort.field} ${n.defaultSort.order}`);const W=await a.find($,k);if(f)return;E(W.data||[])}}catch(N){f||v(N)}finally{f||b(!1)}})(),()=>{f=!0}},[$,L,I,J,U,K,te,a,Y,C]);const D=B.useNavigationOverlay({navigation:n.navigation,objectName:n.objectName,onNavigate:n.onNavigate,onRowClick:i}),{execute:X}=B.useAction(),ce=R.useCallback(()=>{const f=ve(I);if(f){if(f.length>0&&typeof f[0]=="object"&&f[0]!==null){const j=f[0];if("accessorKey"in j)return f;if("field"in j)return f.filter(d=>d?.field&&typeof d.field=="string"&&!d.hidden).map(d=>{const k=d.label||d.field.charAt(0).toUpperCase()+d.field.slice(1).replace(/_/g," ");let W;const T=d.type?me.getCellRenderer(d.type):null;return d.link&&d.action?W=(P,re)=>{const ie=T?c.jsx(T,{value:P,field:{name:d.field,type:d.type||"text"}}):String(P??"");return c.jsx("button",{type:"button",className:"text-primary underline-offset-4 hover:underline cursor-pointer bg-transparent border-none p-0 text-left font-inherit",onClick:oe=>{oe.stopPropagation(),D.handleClick(re)},children:ie})}:d.link?W=(P,re)=>{const ie=T?c.jsx(T,{value:P,field:{name:d.field,type:d.type||"text"}}):String(P??"");return c.jsx("button",{type:"button",className:"text-primary underline-offset-4 hover:underline cursor-pointer bg-transparent border-none p-0 text-left font-inherit",onClick:oe=>{oe.stopPropagation(),D.handleClick(re)},children:ie})}:d.action?W=(P,re)=>{const ie=T?c.jsx(T,{value:P,field:{name:d.field,type:d.type||"text"}}):String(P??"");return c.jsx("button",{type:"button",className:"text-primary underline-offset-4 hover:underline cursor-pointer bg-transparent border-none p-0 text-left font-inherit",onClick:oe=>{oe.stopPropagation(),X({type:d.action,params:{record:re,field:d.field,value:P}})},children:ie})}:T&&(W=P=>c.jsx(T,{value:P,field:{name:d.field,type:d.type||"text"}})),{header:k,accessorKey:d.field,...d.width&&{width:d.width},...d.align&&{align:d.align},sortable:d.sortable!==!1,...d.resizable!==void 0&&{resizable:d.resizable},...d.wrap!==void 0&&{wrap:d.wrap},...W&&{cell:W}}})}return f.filter(j=>typeof j=="string"&&j.trim().length>0).map(j=>({header:w?.fields?.[j]?.label||j.charAt(0).toUpperCase()+j.slice(1).replace(/_/g," "),accessorKey:j}))}if(Y){const j=C?.provider==="value"?C.items:[];if(j.length>0)return(L||Object.keys(j[0])).map(k=>({header:k.charAt(0).toUpperCase()+k.slice(1).replace(/_/g," "),accessorKey:k}))}if(!w)return[];const x=[];return(L||Object.keys(w.fields||{})).forEach(j=>{const d=w.fields?.[j];if(!d||d.permissions&&d.permissions.read===!1)return;const k=me.getCellRenderer(d.type);x.push({header:d.label||j,accessorKey:j,cell:W=>c.jsx(k,{value:W,field:d}),sortable:d.sortable!==!1})}),x},[w,L,I,C,Y,D.handleClick,X]);if(_)return c.jsxs("div",{className:"p-4 border border-red-300 bg-red-50 rounded-md",children:[c.jsx("h3",{className:"text-red-800 font-semibold",children:"Error loading grid"}),c.jsx("p",{className:"text-red-600 text-sm mt-1",children:_.message})]});if(u&&m.length===0)return c.jsxs("div",{className:"p-8 text-center",children:[c.jsx("div",{className:"inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"}),c.jsx("p",{className:"mt-2 text-sm text-gray-600",children:"Loading grid..."})]});const ne=ce(),V="operations"in n?n.operations:void 0,se=V&&(V.update||V.delete),ue=se?[...ne,{header:"Actions",accessorKey:"_actions",cell:(f,x)=>c.jsxs(q.DropdownMenu,{children:[c.jsx(q.DropdownMenuTrigger,{asChild:!0,children:c.jsxs(q.Button,{variant:"ghost",size:"icon",className:"h-8 w-8",children:[c.jsx(de.MoreVertical,{className:"h-4 w-4"}),c.jsx("span",{className:"sr-only",children:"Open menu"})]})}),c.jsxs(q.DropdownMenuContent,{align:"end",children:[V?.update&&e&&c.jsxs(q.DropdownMenuItem,{onClick:()=>e(x),children:[c.jsx(de.Edit,{className:"mr-2 h-4 w-4"}),"Edit"]}),V?.delete&&s&&c.jsxs(q.DropdownMenuItem,{onClick:()=>s(x),children:[c.jsx(de.Trash2,{className:"mr-2 h-4 w-4"}),"Delete"]})]})]}),sortable:!1}]:ne;let r=!1;n.selection?.type?r=n.selection.type==="none"?!1:n.selection.type:n.selectable!==void 0&&(r=n.selectable);const h=n.pagination!==void 0?!0:n.showPagination!==void 0?n.showPagination:!0,y=n.pagination?.pageSize||n.pageSize||10,S=n.searchableFields!==void 0?n.searchableFields.length>0:n.showSearch!==void 0?n.showSearch:!0,G={type:"data-table",caption:n.label||n.title,columns:ue,data:m,pagination:h,pageSize:y,searchable:S,selectable:r,sortable:!0,exportable:V?.export,rowActions:se,resizableColumns:n.resizable??n.resizableColumns??!0,reorderableColumns:n.reorderableColumns??!1,editable:n.editable??!1,className:n.className,onSelectionChange:t,onRowClick:D.handleClick,onCellChange:o,onRowSave:l,onBatchSave:p},Z=n.label?`${n.label} Detail`:n.objectName?`${n.objectName.charAt(0).toUpperCase()+n.objectName.slice(1)} Detail`:"Record Detail";return D.isOverlay&&D.mode==="split"?c.jsx(q.NavigationOverlay,{...D,title:Z,mainContent:c.jsx(B.SchemaRenderer,{schema:G}),children:f=>c.jsx("div",{className:"space-y-3",children:Object.entries(f).map(([x,N])=>c.jsxs("div",{className:"flex flex-col",children:[c.jsx("span",{className:"text-xs font-medium text-muted-foreground uppercase tracking-wide",children:x.replace(/_/g," ")}),c.jsx("span",{className:"text-sm",children:String(N??"—")})]},x))})}):c.jsxs(c.Fragment,{children:[c.jsx(B.SchemaRenderer,{schema:G}),D.isOverlay&&c.jsx(q.NavigationOverlay,{...D,title:Z,children:f=>c.jsx("div",{className:"space-y-3",children:Object.entries(f).map(([x,N])=>c.jsxs("div",{className:"flex flex-col",children:[c.jsx("span",{className:"text-xs font-medium text-muted-foreground uppercase tracking-wide",children:x.replace(/_/g," ")}),c.jsx("span",{className:"text-sm",children:String(N??"—")})]},x))})})]})};function H(n,a,e){let s=e.initialDeps??[],t,i=!0;function o(){var l,p,g;let m;e.key&&((l=e.debug)!=null&&l.call(e))&&(m=Date.now());const E=n();if(!(E.length!==s.length||E.some((_,v)=>s[v]!==_)))return t;s=E;let b;if(e.key&&((p=e.debug)!=null&&p.call(e))&&(b=Date.now()),t=a(...E),e.key&&((g=e.debug)!=null&&g.call(e))){const _=Math.round((Date.now()-m)*100)/100,v=Math.round((Date.now()-b)*100)/100,w=v/16,z=(O,F)=>{for(O=String(O);O.length<F;)O=" "+O;return O};console.info(`%c⏱ ${z(v,5)} /${z(_,5)} ms`,`
|
|
7
7
|
font-size: .6rem;
|
|
8
8
|
font-weight: bold;
|
|
9
|
-
color: hsl(${Math.max(0,Math.min(120-120*_,120))}deg 100% 31%);`,e?.key)}return e?.onChange&&!(i&&e.skipInitialOnChange)&&e.onChange(n),i=!1,n}return o.updateDeps=l=>{s=l},o}function ue(t,a){if(t===void 0)throw new Error("Unexpected undefined");return t}const we=(t,a)=>Math.abs(t-a)<1.01,Oe=(t,a,e)=>{let s;return function(...n){t.clearTimeout(s),s=t.setTimeout(()=>a.apply(this,n),e)}},de=t=>{const{offsetWidth:a,offsetHeight:e}=t;return{width:a,height:e}},je=t=>t,_e=t=>{const a=Math.max(t.startIndex-t.overscan,0),e=Math.min(t.endIndex+t.overscan,t.count-1),s=[];for(let n=a;n<=e;n++)s.push(n);return s},Ce=(t,a)=>{const e=t.scrollElement;if(!e)return;const s=t.targetWindow;if(!s)return;const n=o=>{const{width:l,height:f}=o;a({width:Math.round(l),height:Math.round(f)})};if(n(de(e)),!s.ResizeObserver)return()=>{};const i=new s.ResizeObserver(o=>{const l=()=>{const f=o[0];if(f?.borderBoxSize){const v=f.borderBoxSize[0];if(v){n({width:v.inlineSize,height:v.blockSize});return}}n(de(e))};t.options.useAnimationFrameWithResizeObserver?requestAnimationFrame(l):l()});return i.observe(e,{box:"border-box"}),()=>{i.unobserve(e)}},fe={passive:!0},he=typeof window>"u"?!0:"onscrollend"in window,Te=(t,a)=>{const e=t.scrollElement;if(!e)return;const s=t.targetWindow;if(!s)return;let n=0;const i=t.options.useScrollendEvent&&he?()=>{}:Oe(s,()=>{a(n,!1)},t.options.isScrollingResetDelay),o=d=>()=>{const{horizontal:S,isRtl:c}=t.options;n=S?e.scrollLeft*(c&&-1||1):e.scrollTop,i(),a(n,d)},l=o(!0),f=o(!1);e.addEventListener("scroll",l,fe);const v=t.options.useScrollendEvent&&he;return v&&e.addEventListener("scrollend",f,fe),()=>{e.removeEventListener("scroll",l),v&&e.removeEventListener("scrollend",f)}},Ae=(t,a,e)=>{if(a?.borderBoxSize){const s=a.borderBoxSize[0];if(s)return Math.round(s[e.options.horizontal?"inlineSize":"blockSize"])}return t[e.options.horizontal?"offsetWidth":"offsetHeight"]},Re=(t,{adjustments:a=0,behavior:e},s)=>{var n,i;const o=t+a;(i=(n=s.scrollElement)==null?void 0:n.scrollTo)==null||i.call(n,{[s.options.horizontal?"left":"top"]:o,behavior:e})};class Me{constructor(a){this.unsubs=[],this.scrollElement=null,this.targetWindow=null,this.isScrolling=!1,this.currentScrollToIndex=null,this.measurementsCache=[],this.itemSizeCache=new Map,this.laneAssignments=new Map,this.pendingMeasuredCacheIndexes=[],this.prevLanes=void 0,this.lanesChangedFlag=!1,this.lanesSettling=!1,this.scrollRect=null,this.scrollOffset=null,this.scrollDirection=null,this.scrollAdjustments=0,this.elementsCache=new Map,this.observer=(()=>{let e=null;const s=()=>e||(!this.targetWindow||!this.targetWindow.ResizeObserver?null:e=new this.targetWindow.ResizeObserver(n=>{n.forEach(i=>{const o=()=>{this._measureElement(i.target,i)};this.options.useAnimationFrameWithResizeObserver?requestAnimationFrame(o):o()})}));return{disconnect:()=>{var n;(n=s())==null||n.disconnect(),e=null},observe:n=>{var i;return(i=s())==null?void 0:i.observe(n,{box:"border-box"})},unobserve:n=>{var i;return(i=s())==null?void 0:i.unobserve(n)}}})(),this.range=null,this.setOptions=e=>{Object.entries(e).forEach(([s,n])=>{typeof n>"u"&&delete e[s]}),this.options={debug:!1,initialOffset:0,overscan:1,paddingStart:0,paddingEnd:0,scrollPaddingStart:0,scrollPaddingEnd:0,horizontal:!1,getItemKey:je,rangeExtractor:_e,onChange:()=>{},measureElement:Ae,initialRect:{width:0,height:0},scrollMargin:0,gap:0,indexAttribute:"data-index",initialMeasurementsCache:[],lanes:1,isScrollingResetDelay:150,enabled:!0,isRtl:!1,useScrollendEvent:!1,useAnimationFrameWithResizeObserver:!1,...e}},this.notify=e=>{var s,n;(n=(s=this.options).onChange)==null||n.call(s,this,e)},this.maybeNotify=F(()=>(this.calculateRange(),[this.isScrolling,this.range?this.range.startIndex:null,this.range?this.range.endIndex:null]),e=>{this.notify(e)},{key:process.env.NODE_ENV!=="production"&&"maybeNotify",debug:()=>this.options.debug,initialDeps:[this.isScrolling,this.range?this.range.startIndex:null,this.range?this.range.endIndex:null]}),this.cleanup=()=>{this.unsubs.filter(Boolean).forEach(e=>e()),this.unsubs=[],this.observer.disconnect(),this.scrollElement=null,this.targetWindow=null},this._didMount=()=>()=>{this.cleanup()},this._willUpdate=()=>{var e;const s=this.options.enabled?this.options.getScrollElement():null;if(this.scrollElement!==s){if(this.cleanup(),!s){this.maybeNotify();return}this.scrollElement=s,this.scrollElement&&"ownerDocument"in this.scrollElement?this.targetWindow=this.scrollElement.ownerDocument.defaultView:this.targetWindow=((e=this.scrollElement)==null?void 0:e.window)??null,this.elementsCache.forEach(n=>{this.observer.observe(n)}),this.unsubs.push(this.options.observeElementRect(this,n=>{this.scrollRect=n,this.maybeNotify()})),this.unsubs.push(this.options.observeElementOffset(this,(n,i)=>{this.scrollAdjustments=0,this.scrollDirection=i?this.getScrollOffset()<n?"forward":"backward":null,this.scrollOffset=n,this.isScrolling=i,this.maybeNotify()})),this._scrollToOffset(this.getScrollOffset(),{adjustments:void 0,behavior:void 0})}},this.getSize=()=>this.options.enabled?(this.scrollRect=this.scrollRect??this.options.initialRect,this.scrollRect[this.options.horizontal?"width":"height"]):(this.scrollRect=null,0),this.getScrollOffset=()=>this.options.enabled?(this.scrollOffset=this.scrollOffset??(typeof this.options.initialOffset=="function"?this.options.initialOffset():this.options.initialOffset),this.scrollOffset):(this.scrollOffset=null,0),this.getFurthestMeasurement=(e,s)=>{const n=new Map,i=new Map;for(let o=s-1;o>=0;o--){const l=e[o];if(n.has(l.lane))continue;const f=i.get(l.lane);if(f==null||l.end>f.end?i.set(l.lane,l):l.end<f.end&&n.set(l.lane,!0),n.size===this.options.lanes)break}return i.size===this.options.lanes?Array.from(i.values()).sort((o,l)=>o.end===l.end?o.index-l.index:o.end-l.end)[0]:void 0},this.getMeasurementOptions=F(()=>[this.options.count,this.options.paddingStart,this.options.scrollMargin,this.options.getItemKey,this.options.enabled,this.options.lanes],(e,s,n,i,o,l)=>(this.prevLanes!==void 0&&this.prevLanes!==l&&(this.lanesChangedFlag=!0),this.prevLanes=l,this.pendingMeasuredCacheIndexes=[],{count:e,paddingStart:s,scrollMargin:n,getItemKey:i,enabled:o,lanes:l}),{key:!1}),this.getMeasurements=F(()=>[this.getMeasurementOptions(),this.itemSizeCache],({count:e,paddingStart:s,scrollMargin:n,getItemKey:i,enabled:o,lanes:l},f)=>{if(!o)return this.measurementsCache=[],this.itemSizeCache.clear(),this.laneAssignments.clear(),[];if(this.laneAssignments.size>e)for(const c of this.laneAssignments.keys())c>=e&&this.laneAssignments.delete(c);this.lanesChangedFlag&&(this.lanesChangedFlag=!1,this.lanesSettling=!0,this.measurementsCache=[],this.itemSizeCache.clear(),this.laneAssignments.clear(),this.pendingMeasuredCacheIndexes=[]),this.measurementsCache.length===0&&!this.lanesSettling&&(this.measurementsCache=this.options.initialMeasurementsCache,this.measurementsCache.forEach(c=>{this.itemSizeCache.set(c.key,c.size)}));const v=this.lanesSettling?0:this.pendingMeasuredCacheIndexes.length>0?Math.min(...this.pendingMeasuredCacheIndexes):0;this.pendingMeasuredCacheIndexes=[],this.lanesSettling&&this.measurementsCache.length===e&&(this.lanesSettling=!1);const d=this.measurementsCache.slice(0,v),S=new Array(l).fill(void 0);for(let c=0;c<v;c++){const m=d[c];m&&(S[m.lane]=c)}for(let c=v;c<e;c++){const m=i(c),T=this.laneAssignments.get(c);let g,_;if(T!==void 0&&this.options.lanes>1){g=T;const k=S[g],D=k!==void 0?d[k]:void 0;_=D?D.end+this.options.gap:s+n}else{const k=this.options.lanes===1?d[c-1]:this.getFurthestMeasurement(d,c);_=k?k.end+this.options.gap:s+n,g=k?k.lane:c%this.options.lanes,this.options.lanes>1&&this.laneAssignments.set(c,g)}const z=f.get(m),p=typeof z=="number"?z:this.options.estimateSize(c),C=_+p;d[c]={index:c,start:_,size:p,end:C,key:m,lane:g},S[g]=c}return this.measurementsCache=d,d},{key:process.env.NODE_ENV!=="production"&&"getMeasurements",debug:()=>this.options.debug}),this.calculateRange=F(()=>[this.getMeasurements(),this.getSize(),this.getScrollOffset(),this.options.lanes],(e,s,n,i)=>this.range=e.length>0&&s>0?ze({measurements:e,outerSize:s,scrollOffset:n,lanes:i}):null,{key:process.env.NODE_ENV!=="production"&&"calculateRange",debug:()=>this.options.debug}),this.getVirtualIndexes=F(()=>{let e=null,s=null;const n=this.calculateRange();return n&&(e=n.startIndex,s=n.endIndex),this.maybeNotify.updateDeps([this.isScrolling,e,s]),[this.options.rangeExtractor,this.options.overscan,this.options.count,e,s]},(e,s,n,i,o)=>i===null||o===null?[]:e({startIndex:i,endIndex:o,overscan:s,count:n}),{key:process.env.NODE_ENV!=="production"&&"getVirtualIndexes",debug:()=>this.options.debug}),this.indexFromElement=e=>{const s=this.options.indexAttribute,n=e.getAttribute(s);return n?parseInt(n,10):(console.warn(`Missing attribute name '${s}={index}' on measured element.`),-1)},this._measureElement=(e,s)=>{const n=this.indexFromElement(e),i=this.measurementsCache[n];if(!i)return;const o=i.key,l=this.elementsCache.get(o);l!==e&&(l&&this.observer.unobserve(l),this.observer.observe(e),this.elementsCache.set(o,e)),e.isConnected&&this.resizeItem(n,this.options.measureElement(e,s,this))},this.resizeItem=(e,s)=>{const n=this.measurementsCache[e];if(!n)return;const i=this.itemSizeCache.get(n.key)??n.size,o=s-i;o!==0&&((this.shouldAdjustScrollPositionOnItemSizeChange!==void 0?this.shouldAdjustScrollPositionOnItemSizeChange(n,o,this):n.start<this.getScrollOffset()+this.scrollAdjustments)&&(process.env.NODE_ENV!=="production"&&this.options.debug&&console.info("correction",o),this._scrollToOffset(this.getScrollOffset(),{adjustments:this.scrollAdjustments+=o,behavior:void 0})),this.pendingMeasuredCacheIndexes.push(n.index),this.itemSizeCache=new Map(this.itemSizeCache.set(n.key,s)),this.notify(!1))},this.measureElement=e=>{if(!e){this.elementsCache.forEach((s,n)=>{s.isConnected||(this.observer.unobserve(s),this.elementsCache.delete(n))});return}this._measureElement(e,void 0)},this.getVirtualItems=F(()=>[this.getVirtualIndexes(),this.getMeasurements()],(e,s)=>{const n=[];for(let i=0,o=e.length;i<o;i++){const l=e[i],f=s[l];n.push(f)}return n},{key:process.env.NODE_ENV!=="production"&&"getVirtualItems",debug:()=>this.options.debug}),this.getVirtualItemForOffset=e=>{const s=this.getMeasurements();if(s.length!==0)return ue(s[me(0,s.length-1,n=>ue(s[n]).start,e)])},this.getMaxScrollOffset=()=>{if(!this.scrollElement)return 0;if("scrollHeight"in this.scrollElement)return this.options.horizontal?this.scrollElement.scrollWidth-this.scrollElement.clientWidth:this.scrollElement.scrollHeight-this.scrollElement.clientHeight;{const e=this.scrollElement.document.documentElement;return this.options.horizontal?e.scrollWidth-this.scrollElement.innerWidth:e.scrollHeight-this.scrollElement.innerHeight}},this.getOffsetForAlignment=(e,s,n=0)=>{if(!this.scrollElement)return 0;const i=this.getSize(),o=this.getScrollOffset();s==="auto"&&(s=e>=o+i?"end":"start"),s==="center"?e+=(n-i)/2:s==="end"&&(e-=i);const l=this.getMaxScrollOffset();return Math.max(Math.min(l,e),0)},this.getOffsetForIndex=(e,s="auto")=>{e=Math.max(0,Math.min(e,this.options.count-1));const n=this.measurementsCache[e];if(!n)return;const i=this.getSize(),o=this.getScrollOffset();if(s==="auto")if(n.end>=o+i-this.options.scrollPaddingEnd)s="end";else if(n.start<=o+this.options.scrollPaddingStart)s="start";else return[o,s];if(s==="end"&&e===this.options.count-1)return[this.getMaxScrollOffset(),s];const l=s==="end"?n.end+this.options.scrollPaddingEnd:n.start-this.options.scrollPaddingStart;return[this.getOffsetForAlignment(l,s,n.size),s]},this.isDynamicMode=()=>this.elementsCache.size>0,this.scrollToOffset=(e,{align:s="start",behavior:n}={})=>{n==="smooth"&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),this._scrollToOffset(this.getOffsetForAlignment(e,s),{adjustments:void 0,behavior:n})},this.scrollToIndex=(e,{align:s="auto",behavior:n}={})=>{n==="smooth"&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),e=Math.max(0,Math.min(e,this.options.count-1)),this.currentScrollToIndex=e;let i=0;const o=10,l=v=>{if(!this.targetWindow)return;const d=this.getOffsetForIndex(e,v);if(!d){console.warn("Failed to get offset for index:",e);return}const[S,c]=d;this._scrollToOffset(S,{adjustments:void 0,behavior:n}),this.targetWindow.requestAnimationFrame(()=>{const m=()=>{if(this.currentScrollToIndex!==e)return;const T=this.getScrollOffset(),g=this.getOffsetForIndex(e,c);if(!g){console.warn("Failed to get offset for index:",e);return}we(g[0],T)||f(c)};this.isDynamicMode()?this.targetWindow.requestAnimationFrame(m):m()})},f=v=>{this.targetWindow&&this.currentScrollToIndex===e&&(i++,i<o?(process.env.NODE_ENV!=="production"&&this.options.debug&&console.info("Schedule retry",i,o),this.targetWindow.requestAnimationFrame(()=>l(v))):console.warn(`Failed to scroll to index ${e} after ${o} attempts.`))};l(s)},this.scrollBy=(e,{behavior:s}={})=>{s==="smooth"&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),this._scrollToOffset(this.getScrollOffset()+e,{adjustments:void 0,behavior:s})},this.getTotalSize=()=>{var e;const s=this.getMeasurements();let n;if(s.length===0)n=this.options.paddingStart;else if(this.options.lanes===1)n=((e=s[s.length-1])==null?void 0:e.end)??0;else{const i=Array(this.options.lanes).fill(null);let o=s.length-1;for(;o>=0&&i.some(l=>l===null);){const l=s[o];i[l.lane]===null&&(i[l.lane]=l.end),o--}n=Math.max(...i.filter(l=>l!==null))}return Math.max(n-this.options.scrollMargin+this.options.paddingEnd,0)},this._scrollToOffset=(e,{adjustments:s,behavior:n})=>{this.options.scrollToFn(e,{behavior:n,adjustments:s},this)},this.measure=()=>{this.itemSizeCache=new Map,this.laneAssignments=new Map,this.notify(!1)},this.setOptions(a)}}const me=(t,a,e,s)=>{for(;t<=a;){const n=(t+a)/2|0,i=e(n);if(i<s)t=n+1;else if(i>s)a=n-1;else return n}return t>0?t-1:0};function ze({measurements:t,outerSize:a,scrollOffset:e,lanes:s}){const n=t.length-1,i=f=>t[f].start;if(t.length<=s)return{startIndex:0,endIndex:n};let o=me(0,n,i,e),l=o;if(s===1)for(;l<n&&t[l].end<e+a;)l++;else if(s>1){const f=Array(s).fill(0);for(;l<n&&f.some(d=>d<e+a);){const d=t[l];f[d.lane]=d.end,l++}const v=Array(s).fill(e+a);for(;o>=0&&v.some(d=>d>=e);){const d=t[o];v[d.lane]=d.start,o--}o=Math.max(0,o-o%s),l=Math.min(n,l+(s-1-l%s))}return{startIndex:o,endIndex:l}}const ge=typeof document<"u"?U.useLayoutEffect:U.useEffect;function ke({useFlushSync:t=!0,...a}){const e=U.useReducer(()=>({}),{})[1],s={...a,onChange:(i,o)=>{var l;t&&o?be.flushSync(e):e(),(l=a.onChange)==null||l.call(a,i,o)}},[n]=U.useState(()=>new Me(s));return n.setOptions(s),ge(()=>n._didMount(),[]),ge(()=>n._willUpdate()),n}function Ne(t){return ke({observeElementRect:Ce,observeElementOffset:Te,scrollToFn:Re,...t})}const Ie=({data:t,columns:a,rowHeight:e=40,height:s=600,className:n="",headerClassName:i="",rowClassName:o,onRowClick:l,overscan:f=5})=>{const v=j.useRef(null),d=Ne({count:t.length,getScrollElement:()=>v.current,estimateSize:()=>e,overscan:f}),S=d.getVirtualItems();return b.jsxs("div",{className:n,children:[b.jsx("div",{className:`grid border-b sticky top-0 bg-background z-10 ${i}`,style:{gridTemplateColumns:a.map(c=>c.width||"1fr").join(" ")},children:a.map((c,m)=>b.jsx("div",{className:`px-4 py-2 font-semibold text-sm ${c.align==="center"?"text-center":c.align==="right"?"text-right":"text-left"}`,children:c.header},m))}),b.jsx("div",{ref:v,className:"overflow-auto",style:{height:typeof s=="number"?`${s}px`:s,contain:"strict"},children:b.jsx("div",{style:{height:`${d.getTotalSize()}px`,width:"100%",position:"relative"},children:S.map(c=>{const m=t[c.index],T=typeof o=="function"?o(m,c.index):o||"";return b.jsx("div",{className:`grid border-b hover:bg-muted/50 cursor-pointer ${T}`,style:{position:"absolute",top:0,left:0,width:"100%",height:`${c.size}px`,transform:`translateY(${c.start}px)`,gridTemplateColumns:a.map(g=>g.width||"1fr").join(" ")},onClick:()=>l?.(m,c.index),children:a.map((g,_)=>{const z=m[g.accessorKey],p=g.cell?g.cell(z,m):z;return b.jsx("div",{className:`px-4 py-2 text-sm flex items-center ${g.align==="center"?"text-center justify-center":g.align==="right"?"text-right justify-end":"text-left justify-start"}`,children:p},_)})},c.key)})})}),b.jsxs("div",{className:"px-4 py-2 text-xs text-muted-foreground border-t",children:["Showing ",S.length," of ",t.length," rows (virtual scrolling enabled)"]})]})},Q=({schema:t,...a})=>{const{dataSource:e}=X.useSchemaContext()||{};return b.jsx(ce,{schema:t,dataSource:e,...a})};re.ComponentRegistry.register("object-grid",Q,{namespace:"plugin-grid",label:"Object Grid",category:"plugin"}),re.ComponentRegistry.register("grid",Q,{namespace:"view",label:"Data Grid",category:"view"}),A.ObjectGrid=ce,A.ObjectGridRenderer=Q,A.VirtualGrid=Ie,Object.defineProperty(A,Symbol.toStringTag,{value:"Module"})}));
|
|
9
|
+
color: hsl(${Math.max(0,Math.min(120-120*w,120))}deg 100% 31%);`,e?.key)}return e?.onChange&&!(i&&e.skipInitialOnChange)&&e.onChange(t),i=!1,t}return o.updateDeps=l=>{s=l},o}function xe(n,a){if(n===void 0)throw new Error("Unexpected undefined");return n}const ke=(n,a)=>Math.abs(n-a)<1.01,ze=(n,a,e)=>{let s;return function(...t){n.clearTimeout(s),s=n.setTimeout(()=>a.apply(this,t),e)}},Ee=n=>{const{offsetWidth:a,offsetHeight:e}=n;return{width:a,height:e}},Me=n=>n,Ie=n=>{const a=Math.max(n.startIndex-n.overscan,0),e=Math.min(n.endIndex+n.overscan,n.count-1),s=[];for(let t=a;t<=e;t++)s.push(t);return s},De=(n,a)=>{const e=n.scrollElement;if(!e)return;const s=n.targetWindow;if(!s)return;const t=o=>{const{width:l,height:p}=o;a({width:Math.round(l),height:Math.round(p)})};if(t(Ee(e)),!s.ResizeObserver)return()=>{};const i=new s.ResizeObserver(o=>{const l=()=>{const p=o[0];if(p?.borderBoxSize){const g=p.borderBoxSize[0];if(g){t({width:g.inlineSize,height:g.blockSize});return}}t(Ee(e))};n.options.useAnimationFrameWithResizeObserver?requestAnimationFrame(l):l()});return i.observe(e,{box:"border-box"}),()=>{i.unobserve(e)}},Se={passive:!0},je=typeof window>"u"?!0:"onscrollend"in window,Pe=(n,a)=>{const e=n.scrollElement;if(!e)return;const s=n.targetWindow;if(!s)return;let t=0;const i=n.options.useScrollendEvent&&je?()=>{}:ze(s,()=>{a(t,!1)},n.options.isScrollingResetDelay),o=m=>()=>{const{horizontal:E,isRtl:u}=n.options;t=E?e.scrollLeft*(u&&-1||1):e.scrollTop,i(),a(t,m)},l=o(!0),p=o(!1);e.addEventListener("scroll",l,Se);const g=n.options.useScrollendEvent&&je;return g&&e.addEventListener("scrollend",p,Se),()=>{e.removeEventListener("scroll",l),g&&e.removeEventListener("scrollend",p)}},Fe=(n,a,e)=>{if(a?.borderBoxSize){const s=a.borderBoxSize[0];if(s)return Math.round(s[e.options.horizontal?"inlineSize":"blockSize"])}return n[e.options.horizontal?"offsetWidth":"offsetHeight"]},$e=(n,{adjustments:a=0,behavior:e},s)=>{var t,i;const o=n+a;(i=(t=s.scrollElement)==null?void 0:t.scrollTo)==null||i.call(t,{[s.options.horizontal?"left":"top"]:o,behavior:e})};class We{constructor(a){this.unsubs=[],this.scrollElement=null,this.targetWindow=null,this.isScrolling=!1,this.currentScrollToIndex=null,this.measurementsCache=[],this.itemSizeCache=new Map,this.laneAssignments=new Map,this.pendingMeasuredCacheIndexes=[],this.prevLanes=void 0,this.lanesChangedFlag=!1,this.lanesSettling=!1,this.scrollRect=null,this.scrollOffset=null,this.scrollDirection=null,this.scrollAdjustments=0,this.elementsCache=new Map,this.observer=(()=>{let e=null;const s=()=>e||(!this.targetWindow||!this.targetWindow.ResizeObserver?null:e=new this.targetWindow.ResizeObserver(t=>{t.forEach(i=>{const o=()=>{this._measureElement(i.target,i)};this.options.useAnimationFrameWithResizeObserver?requestAnimationFrame(o):o()})}));return{disconnect:()=>{var t;(t=s())==null||t.disconnect(),e=null},observe:t=>{var i;return(i=s())==null?void 0:i.observe(t,{box:"border-box"})},unobserve:t=>{var i;return(i=s())==null?void 0:i.unobserve(t)}}})(),this.range=null,this.setOptions=e=>{Object.entries(e).forEach(([s,t])=>{typeof t>"u"&&delete e[s]}),this.options={debug:!1,initialOffset:0,overscan:1,paddingStart:0,paddingEnd:0,scrollPaddingStart:0,scrollPaddingEnd:0,horizontal:!1,getItemKey:Me,rangeExtractor:Ie,onChange:()=>{},measureElement:Fe,initialRect:{width:0,height:0},scrollMargin:0,gap:0,indexAttribute:"data-index",initialMeasurementsCache:[],lanes:1,isScrollingResetDelay:150,enabled:!0,isRtl:!1,useScrollendEvent:!1,useAnimationFrameWithResizeObserver:!1,...e}},this.notify=e=>{var s,t;(t=(s=this.options).onChange)==null||t.call(s,this,e)},this.maybeNotify=H(()=>(this.calculateRange(),[this.isScrolling,this.range?this.range.startIndex:null,this.range?this.range.endIndex:null]),e=>{this.notify(e)},{key:process.env.NODE_ENV!=="production"&&"maybeNotify",debug:()=>this.options.debug,initialDeps:[this.isScrolling,this.range?this.range.startIndex:null,this.range?this.range.endIndex:null]}),this.cleanup=()=>{this.unsubs.filter(Boolean).forEach(e=>e()),this.unsubs=[],this.observer.disconnect(),this.scrollElement=null,this.targetWindow=null},this._didMount=()=>()=>{this.cleanup()},this._willUpdate=()=>{var e;const s=this.options.enabled?this.options.getScrollElement():null;if(this.scrollElement!==s){if(this.cleanup(),!s){this.maybeNotify();return}this.scrollElement=s,this.scrollElement&&"ownerDocument"in this.scrollElement?this.targetWindow=this.scrollElement.ownerDocument.defaultView:this.targetWindow=((e=this.scrollElement)==null?void 0:e.window)??null,this.elementsCache.forEach(t=>{this.observer.observe(t)}),this.unsubs.push(this.options.observeElementRect(this,t=>{this.scrollRect=t,this.maybeNotify()})),this.unsubs.push(this.options.observeElementOffset(this,(t,i)=>{this.scrollAdjustments=0,this.scrollDirection=i?this.getScrollOffset()<t?"forward":"backward":null,this.scrollOffset=t,this.isScrolling=i,this.maybeNotify()})),this._scrollToOffset(this.getScrollOffset(),{adjustments:void 0,behavior:void 0})}},this.getSize=()=>this.options.enabled?(this.scrollRect=this.scrollRect??this.options.initialRect,this.scrollRect[this.options.horizontal?"width":"height"]):(this.scrollRect=null,0),this.getScrollOffset=()=>this.options.enabled?(this.scrollOffset=this.scrollOffset??(typeof this.options.initialOffset=="function"?this.options.initialOffset():this.options.initialOffset),this.scrollOffset):(this.scrollOffset=null,0),this.getFurthestMeasurement=(e,s)=>{const t=new Map,i=new Map;for(let o=s-1;o>=0;o--){const l=e[o];if(t.has(l.lane))continue;const p=i.get(l.lane);if(p==null||l.end>p.end?i.set(l.lane,l):l.end<p.end&&t.set(l.lane,!0),t.size===this.options.lanes)break}return i.size===this.options.lanes?Array.from(i.values()).sort((o,l)=>o.end===l.end?o.index-l.index:o.end-l.end)[0]:void 0},this.getMeasurementOptions=H(()=>[this.options.count,this.options.paddingStart,this.options.scrollMargin,this.options.getItemKey,this.options.enabled,this.options.lanes],(e,s,t,i,o,l)=>(this.prevLanes!==void 0&&this.prevLanes!==l&&(this.lanesChangedFlag=!0),this.prevLanes=l,this.pendingMeasuredCacheIndexes=[],{count:e,paddingStart:s,scrollMargin:t,getItemKey:i,enabled:o,lanes:l}),{key:!1}),this.getMeasurements=H(()=>[this.getMeasurementOptions(),this.itemSizeCache],({count:e,paddingStart:s,scrollMargin:t,getItemKey:i,enabled:o,lanes:l},p)=>{if(!o)return this.measurementsCache=[],this.itemSizeCache.clear(),this.laneAssignments.clear(),[];if(this.laneAssignments.size>e)for(const u of this.laneAssignments.keys())u>=e&&this.laneAssignments.delete(u);this.lanesChangedFlag&&(this.lanesChangedFlag=!1,this.lanesSettling=!0,this.measurementsCache=[],this.itemSizeCache.clear(),this.laneAssignments.clear(),this.pendingMeasuredCacheIndexes=[]),this.measurementsCache.length===0&&!this.lanesSettling&&(this.measurementsCache=this.options.initialMeasurementsCache,this.measurementsCache.forEach(u=>{this.itemSizeCache.set(u.key,u.size)}));const g=this.lanesSettling?0:this.pendingMeasuredCacheIndexes.length>0?Math.min(...this.pendingMeasuredCacheIndexes):0;this.pendingMeasuredCacheIndexes=[],this.lanesSettling&&this.measurementsCache.length===e&&(this.lanesSettling=!1);const m=this.measurementsCache.slice(0,g),E=new Array(l).fill(void 0);for(let u=0;u<g;u++){const b=m[u];b&&(E[b.lane]=u)}for(let u=g;u<e;u++){const b=i(u),_=this.laneAssignments.get(u);let v,w;if(_!==void 0&&this.options.lanes>1){v=_;const M=E[v],C=M!==void 0?m[M]:void 0;w=C?C.end+this.options.gap:s+t}else{const M=this.options.lanes===1?m[u-1]:this.getFurthestMeasurement(m,u);w=M?M.end+this.options.gap:s+t,v=M?M.lane:u%this.options.lanes,this.options.lanes>1&&this.laneAssignments.set(u,v)}const z=p.get(b),O=typeof z=="number"?z:this.options.estimateSize(u),F=w+O;m[u]={index:u,start:w,size:O,end:F,key:b,lane:v},E[v]=u}return this.measurementsCache=m,m},{key:process.env.NODE_ENV!=="production"&&"getMeasurements",debug:()=>this.options.debug}),this.calculateRange=H(()=>[this.getMeasurements(),this.getSize(),this.getScrollOffset(),this.options.lanes],(e,s,t,i)=>this.range=e.length>0&&s>0?Le({measurements:e,outerSize:s,scrollOffset:t,lanes:i}):null,{key:process.env.NODE_ENV!=="production"&&"calculateRange",debug:()=>this.options.debug}),this.getVirtualIndexes=H(()=>{let e=null,s=null;const t=this.calculateRange();return t&&(e=t.startIndex,s=t.endIndex),this.maybeNotify.updateDeps([this.isScrolling,e,s]),[this.options.rangeExtractor,this.options.overscan,this.options.count,e,s]},(e,s,t,i,o)=>i===null||o===null?[]:e({startIndex:i,endIndex:o,overscan:s,count:t}),{key:process.env.NODE_ENV!=="production"&&"getVirtualIndexes",debug:()=>this.options.debug}),this.indexFromElement=e=>{const s=this.options.indexAttribute,t=e.getAttribute(s);return t?parseInt(t,10):(console.warn(`Missing attribute name '${s}={index}' on measured element.`),-1)},this._measureElement=(e,s)=>{const t=this.indexFromElement(e),i=this.measurementsCache[t];if(!i)return;const o=i.key,l=this.elementsCache.get(o);l!==e&&(l&&this.observer.unobserve(l),this.observer.observe(e),this.elementsCache.set(o,e)),e.isConnected&&this.resizeItem(t,this.options.measureElement(e,s,this))},this.resizeItem=(e,s)=>{const t=this.measurementsCache[e];if(!t)return;const i=this.itemSizeCache.get(t.key)??t.size,o=s-i;o!==0&&((this.shouldAdjustScrollPositionOnItemSizeChange!==void 0?this.shouldAdjustScrollPositionOnItemSizeChange(t,o,this):t.start<this.getScrollOffset()+this.scrollAdjustments)&&(process.env.NODE_ENV!=="production"&&this.options.debug&&console.info("correction",o),this._scrollToOffset(this.getScrollOffset(),{adjustments:this.scrollAdjustments+=o,behavior:void 0})),this.pendingMeasuredCacheIndexes.push(t.index),this.itemSizeCache=new Map(this.itemSizeCache.set(t.key,s)),this.notify(!1))},this.measureElement=e=>{if(!e){this.elementsCache.forEach((s,t)=>{s.isConnected||(this.observer.unobserve(s),this.elementsCache.delete(t))});return}this._measureElement(e,void 0)},this.getVirtualItems=H(()=>[this.getVirtualIndexes(),this.getMeasurements()],(e,s)=>{const t=[];for(let i=0,o=e.length;i<o;i++){const l=e[i],p=s[l];t.push(p)}return t},{key:process.env.NODE_ENV!=="production"&&"getVirtualItems",debug:()=>this.options.debug}),this.getVirtualItemForOffset=e=>{const s=this.getMeasurements();if(s.length!==0)return xe(s[we(0,s.length-1,t=>xe(s[t]).start,e)])},this.getMaxScrollOffset=()=>{if(!this.scrollElement)return 0;if("scrollHeight"in this.scrollElement)return this.options.horizontal?this.scrollElement.scrollWidth-this.scrollElement.clientWidth:this.scrollElement.scrollHeight-this.scrollElement.clientHeight;{const e=this.scrollElement.document.documentElement;return this.options.horizontal?e.scrollWidth-this.scrollElement.innerWidth:e.scrollHeight-this.scrollElement.innerHeight}},this.getOffsetForAlignment=(e,s,t=0)=>{if(!this.scrollElement)return 0;const i=this.getSize(),o=this.getScrollOffset();s==="auto"&&(s=e>=o+i?"end":"start"),s==="center"?e+=(t-i)/2:s==="end"&&(e-=i);const l=this.getMaxScrollOffset();return Math.max(Math.min(l,e),0)},this.getOffsetForIndex=(e,s="auto")=>{e=Math.max(0,Math.min(e,this.options.count-1));const t=this.measurementsCache[e];if(!t)return;const i=this.getSize(),o=this.getScrollOffset();if(s==="auto")if(t.end>=o+i-this.options.scrollPaddingEnd)s="end";else if(t.start<=o+this.options.scrollPaddingStart)s="start";else return[o,s];if(s==="end"&&e===this.options.count-1)return[this.getMaxScrollOffset(),s];const l=s==="end"?t.end+this.options.scrollPaddingEnd:t.start-this.options.scrollPaddingStart;return[this.getOffsetForAlignment(l,s,t.size),s]},this.isDynamicMode=()=>this.elementsCache.size>0,this.scrollToOffset=(e,{align:s="start",behavior:t}={})=>{t==="smooth"&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),this._scrollToOffset(this.getOffsetForAlignment(e,s),{adjustments:void 0,behavior:t})},this.scrollToIndex=(e,{align:s="auto",behavior:t}={})=>{t==="smooth"&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),e=Math.max(0,Math.min(e,this.options.count-1)),this.currentScrollToIndex=e;let i=0;const o=10,l=g=>{if(!this.targetWindow)return;const m=this.getOffsetForIndex(e,g);if(!m){console.warn("Failed to get offset for index:",e);return}const[E,u]=m;this._scrollToOffset(E,{adjustments:void 0,behavior:t}),this.targetWindow.requestAnimationFrame(()=>{const b=()=>{if(this.currentScrollToIndex!==e)return;const _=this.getScrollOffset(),v=this.getOffsetForIndex(e,u);if(!v){console.warn("Failed to get offset for index:",e);return}ke(v[0],_)||p(u)};this.isDynamicMode()?this.targetWindow.requestAnimationFrame(b):b()})},p=g=>{this.targetWindow&&this.currentScrollToIndex===e&&(i++,i<o?(process.env.NODE_ENV!=="production"&&this.options.debug&&console.info("Schedule retry",i,o),this.targetWindow.requestAnimationFrame(()=>l(g))):console.warn(`Failed to scroll to index ${e} after ${o} attempts.`))};l(s)},this.scrollBy=(e,{behavior:s}={})=>{s==="smooth"&&this.isDynamicMode()&&console.warn("The `smooth` scroll behavior is not fully supported with dynamic size."),this._scrollToOffset(this.getScrollOffset()+e,{adjustments:void 0,behavior:s})},this.getTotalSize=()=>{var e;const s=this.getMeasurements();let t;if(s.length===0)t=this.options.paddingStart;else if(this.options.lanes===1)t=((e=s[s.length-1])==null?void 0:e.end)??0;else{const i=Array(this.options.lanes).fill(null);let o=s.length-1;for(;o>=0&&i.some(l=>l===null);){const l=s[o];i[l.lane]===null&&(i[l.lane]=l.end),o--}t=Math.max(...i.filter(l=>l!==null))}return Math.max(t-this.options.scrollMargin+this.options.paddingEnd,0)},this._scrollToOffset=(e,{adjustments:s,behavior:t})=>{this.options.scrollToFn(e,{behavior:t,adjustments:s},this)},this.measure=()=>{this.itemSizeCache=new Map,this.laneAssignments=new Map,this.notify(!1)},this.setOptions(a)}}const we=(n,a,e,s)=>{for(;n<=a;){const t=(n+a)/2|0,i=e(t);if(i<s)n=t+1;else if(i>s)a=t-1;else return t}return n>0?n-1:0};function Le({measurements:n,outerSize:a,scrollOffset:e,lanes:s}){const t=n.length-1,i=p=>n[p].start;if(n.length<=s)return{startIndex:0,endIndex:t};let o=we(0,t,i,e),l=o;if(s===1)for(;l<t&&n[l].end<e+a;)l++;else if(s>1){const p=Array(s).fill(0);for(;l<t&&p.some(m=>m<e+a);){const m=n[l];p[m.lane]=m.end,l++}const g=Array(s).fill(e+a);for(;o>=0&&g.some(m=>m>=e);){const m=n[o];g[m.lane]=m.start,o--}o=Math.max(0,o-o%s),l=Math.min(t,l+(s-1-l%s))}return{startIndex:o,endIndex:l}}const Oe=typeof document<"u"?le.useLayoutEffect:le.useEffect;function Ve({useFlushSync:n=!0,...a}){const e=le.useReducer(()=>({}),{})[1],s={...a,onChange:(i,o)=>{var l;n&&o?Ce.flushSync(e):e(),(l=a.onChange)==null||l.call(a,i,o)}},[t]=le.useState(()=>new We(s));return t.setOptions(s),Oe(()=>t._didMount(),[]),Oe(()=>t._willUpdate()),t}function qe(n){return Ve({observeElementRect:De,observeElementOffset:Pe,scrollToFn:$e,...n})}const Ye=({data:n,columns:a,rowHeight:e=40,height:s=600,className:t="",headerClassName:i="",rowClassName:o,onRowClick:l,overscan:p=5})=>{const g=R.useRef(null),m=qe({count:n.length,getScrollElement:()=>g.current,estimateSize:()=>e,overscan:p}),E=m.getVirtualItems();return c.jsxs("div",{className:t,children:[c.jsx("div",{className:`grid border-b sticky top-0 bg-background z-10 ${i}`,style:{gridTemplateColumns:a.map(u=>u.width||"1fr").join(" ")},children:a.map((u,b)=>c.jsx("div",{className:`px-4 py-2 font-semibold text-sm ${u.align==="center"?"text-center":u.align==="right"?"text-right":"text-left"}`,children:u.header},b))}),c.jsx("div",{ref:g,className:"overflow-auto",style:{height:typeof s=="number"?`${s}px`:s,contain:"strict"},children:c.jsx("div",{style:{height:`${m.getTotalSize()}px`,width:"100%",position:"relative"},children:E.map(u=>{const b=n[u.index],_=typeof o=="function"?o(b,u.index):o||"";return c.jsx("div",{className:`grid border-b hover:bg-muted/50 cursor-pointer ${_}`,style:{position:"absolute",top:0,left:0,width:"100%",height:`${u.size}px`,transform:`translateY(${u.start}px)`,gridTemplateColumns:a.map(v=>v.width||"1fr").join(" ")},onClick:()=>l?.(b,u.index),children:a.map((v,w)=>{const z=b[v.accessorKey],O=v.cell?v.cell(z,b):z;return c.jsx("div",{className:`px-4 py-2 text-sm flex items-center ${v.align==="center"?"text-center justify-center":v.align==="right"?"text-right justify-end":"text-left justify-start"}`,children:O},w)})},u.key)})})}),c.jsxs("div",{className:"px-4 py-2 text-xs text-muted-foreground border-t",children:["Showing ",E.length," of ",n.length," rows (virtual scrolling enabled)"]})]})},fe=({schema:n,...a})=>{const{dataSource:e}=B.useSchemaContext()||{};return c.jsx(ye,{schema:n,dataSource:e,...a})};he.ComponentRegistry.register("object-grid",fe,{namespace:"plugin-grid",label:"Object Grid",category:"plugin",inputs:[{name:"objectName",type:"string",label:"Object Name",required:!0},{name:"columns",type:"array",label:"Columns"},{name:"filters",type:"array",label:"Filters"}]}),he.ComponentRegistry.register("grid",fe,{namespace:"view",label:"Data Grid",category:"view",inputs:[{name:"objectName",type:"string",label:"Object Name",required:!0},{name:"columns",type:"array",label:"Columns"},{name:"filters",type:"array",label:"Filters"}]}),A.ObjectGrid=ye,A.ObjectGridRenderer=fe,A.VirtualGrid=Ye,Object.defineProperty(A,Symbol.toStringTag,{value:"Module"})}));
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -8,7 +8,13 @@ export interface ObjectGridProps {
|
|
|
8
8
|
onEdit?: (record: any) => void;
|
|
9
9
|
onDelete?: (record: any) => void;
|
|
10
10
|
onBulkDelete?: (records: any[]) => void;
|
|
11
|
-
onCellChange?: (rowIndex: number, columnKey: string, newValue: any) => void;
|
|
11
|
+
onCellChange?: (rowIndex: number, columnKey: string, newValue: any, row: any) => void;
|
|
12
|
+
onRowSave?: (rowIndex: number, changes: Record<string, any>, row: any) => void | Promise<void>;
|
|
13
|
+
onBatchSave?: (changes: Array<{
|
|
14
|
+
rowIndex: number;
|
|
15
|
+
changes: Record<string, any>;
|
|
16
|
+
row: any;
|
|
17
|
+
}>) => void | Promise<void>;
|
|
12
18
|
onRowSelect?: (selectedRows: any[]) => void;
|
|
13
19
|
}
|
|
14
20
|
export declare const ObjectGrid: React.FC<ObjectGridProps>;
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/plugin-grid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Grid plugin for Object UI",
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@tanstack/react-virtual": "^3.11.3",
|
|
19
19
|
"lucide-react": "^0.563.0",
|
|
20
|
-
"@object-ui/components": "0.
|
|
21
|
-
"@object-ui/core": "0.
|
|
22
|
-
"@object-ui/fields": "0.
|
|
23
|
-
"@object-ui/react": "0.
|
|
24
|
-
"@object-ui/types": "0.
|
|
20
|
+
"@object-ui/components": "2.0.0",
|
|
21
|
+
"@object-ui/core": "2.0.0",
|
|
22
|
+
"@object-ui/fields": "2.0.0",
|
|
23
|
+
"@object-ui/react": "2.0.0",
|
|
24
|
+
"@object-ui/types": "2.0.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@vitejs/plugin-react": "^5.1.3",
|
|
32
|
-
"msw": "^2.12.
|
|
32
|
+
"msw": "^2.12.9",
|
|
33
33
|
"typescript": "^5.9.3",
|
|
34
34
|
"vite": "^7.3.1",
|
|
35
35
|
"vite-plugin-dts": "^4.5.4",
|
|
36
|
-
"@object-ui/data-objectstack": "0.
|
|
36
|
+
"@object-ui/data-objectstack": "2.0.0"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "vite build",
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListColumn Extensions Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for link, action, hidden, type, wrap, and resizable properties
|
|
5
|
+
* on ListColumn when rendered through ObjectGrid → data-table.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
8
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
9
|
+
import '@testing-library/jest-dom';
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { ObjectGrid } from './ObjectGrid';
|
|
12
|
+
import { registerAllFields } from '@object-ui/fields';
|
|
13
|
+
import { ActionProvider } from '@object-ui/react';
|
|
14
|
+
import type { ListColumn } from '@object-ui/types';
|
|
15
|
+
|
|
16
|
+
registerAllFields();
|
|
17
|
+
|
|
18
|
+
// --- Mock Data ---
|
|
19
|
+
const mockData = [
|
|
20
|
+
{ _id: '1', name: 'Alice', email: 'alice@test.com', amount: 1500, status: 'active' },
|
|
21
|
+
{ _id: '2', name: 'Bob', email: 'bob@test.com', amount: 2300, status: 'inactive' },
|
|
22
|
+
{ _id: '3', name: 'Charlie', email: 'charlie@test.com', amount: 800, status: 'active' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
// --- Helper: Render ObjectGrid with static data and ListColumn[] ---
|
|
26
|
+
function renderGrid(columns: ListColumn[], opts?: { onNavigate?: any; navigation?: any }) {
|
|
27
|
+
const schema: any = {
|
|
28
|
+
type: 'object-grid' as const,
|
|
29
|
+
objectName: 'test_object',
|
|
30
|
+
columns,
|
|
31
|
+
data: { provider: 'value', items: mockData },
|
|
32
|
+
navigation: opts?.navigation,
|
|
33
|
+
onNavigate: opts?.onNavigate,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return render(
|
|
37
|
+
<ActionProvider>
|
|
38
|
+
<ObjectGrid schema={schema} />
|
|
39
|
+
</ActionProvider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// =========================================================================
|
|
44
|
+
// 1. Hidden columns
|
|
45
|
+
// =========================================================================
|
|
46
|
+
describe('ListColumn: hidden', () => {
|
|
47
|
+
it('should not render hidden columns', async () => {
|
|
48
|
+
renderGrid([
|
|
49
|
+
{ field: 'name', label: 'Name' },
|
|
50
|
+
{ field: 'email', label: 'Email', hidden: true },
|
|
51
|
+
{ field: 'amount', label: 'Amount' },
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
await waitFor(() => {
|
|
55
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
56
|
+
});
|
|
57
|
+
expect(screen.getByText('Amount')).toBeInTheDocument();
|
|
58
|
+
// Email column should NOT be rendered
|
|
59
|
+
expect(screen.queryByText('Email')).not.toBeInTheDocument();
|
|
60
|
+
expect(screen.queryByText('alice@test.com')).not.toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should render non-hidden columns normally', async () => {
|
|
64
|
+
renderGrid([
|
|
65
|
+
{ field: 'name', label: 'Name', hidden: false },
|
|
66
|
+
{ field: 'email', label: 'Email' },
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
await waitFor(() => {
|
|
70
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
expect(screen.getByText('Email')).toBeInTheDocument();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// =========================================================================
|
|
77
|
+
// 2. Link columns
|
|
78
|
+
// =========================================================================
|
|
79
|
+
describe('ListColumn: link', () => {
|
|
80
|
+
it('should render link columns as clickable text', async () => {
|
|
81
|
+
renderGrid([
|
|
82
|
+
{ field: 'name', label: 'Name', link: true },
|
|
83
|
+
{ field: 'email', label: 'Email' },
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
await waitFor(() => {
|
|
87
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// The name cells should be rendered as buttons (clickable links)
|
|
91
|
+
const aliceLink = screen.getByRole('button', { name: 'Alice' });
|
|
92
|
+
expect(aliceLink).toBeInTheDocument();
|
|
93
|
+
expect(aliceLink).toHaveClass('text-primary');
|
|
94
|
+
|
|
95
|
+
const bobLink = screen.getByRole('button', { name: 'Bob' });
|
|
96
|
+
expect(bobLink).toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should trigger navigation when link column is clicked', async () => {
|
|
100
|
+
const onNavigate = vi.fn();
|
|
101
|
+
|
|
102
|
+
renderGrid(
|
|
103
|
+
[
|
|
104
|
+
{ field: 'name', label: 'Name', link: true },
|
|
105
|
+
{ field: 'email', label: 'Email' },
|
|
106
|
+
],
|
|
107
|
+
{
|
|
108
|
+
navigation: { mode: 'page' },
|
|
109
|
+
onNavigate,
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
await waitFor(() => {
|
|
114
|
+
expect(screen.getByRole('button', { name: 'Alice' })).toBeInTheDocument();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
fireEvent.click(screen.getByRole('button', { name: 'Alice' }));
|
|
118
|
+
|
|
119
|
+
expect(onNavigate).toHaveBeenCalledTimes(1);
|
|
120
|
+
expect(onNavigate).toHaveBeenCalledWith('1', 'view');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should not render non-link columns as buttons', async () => {
|
|
124
|
+
renderGrid([
|
|
125
|
+
{ field: 'name', label: 'Name', link: true },
|
|
126
|
+
{ field: 'email', label: 'Email' },
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
await waitFor(() => {
|
|
130
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Email values should NOT be buttons
|
|
134
|
+
expect(screen.queryByRole('button', { name: 'alice@test.com' })).not.toBeInTheDocument();
|
|
135
|
+
// But the text should still render
|
|
136
|
+
expect(screen.getByText('alice@test.com')).toBeInTheDocument();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// =========================================================================
|
|
141
|
+
// 3. Action columns
|
|
142
|
+
// =========================================================================
|
|
143
|
+
describe('ListColumn: action', () => {
|
|
144
|
+
it('should render action columns as clickable text', async () => {
|
|
145
|
+
renderGrid([
|
|
146
|
+
{ field: 'name', label: 'Name' },
|
|
147
|
+
{ field: 'status', label: 'Status', action: 'toggleStatus' },
|
|
148
|
+
]);
|
|
149
|
+
|
|
150
|
+
await waitFor(() => {
|
|
151
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Status cells should be buttons
|
|
155
|
+
const activeBtn = screen.getAllByRole('button', { name: 'active' });
|
|
156
|
+
expect(activeBtn.length).toBeGreaterThanOrEqual(1);
|
|
157
|
+
expect(activeBtn[0]).toHaveClass('text-primary');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should execute action when action column is clicked', async () => {
|
|
161
|
+
const actionHandler = vi.fn().mockResolvedValue({ success: true });
|
|
162
|
+
|
|
163
|
+
const schema: any = {
|
|
164
|
+
type: 'object-grid' as const,
|
|
165
|
+
objectName: 'test_object',
|
|
166
|
+
columns: [
|
|
167
|
+
{ field: 'name', label: 'Name' },
|
|
168
|
+
{ field: 'status', label: 'Status', action: 'toggleStatus' },
|
|
169
|
+
],
|
|
170
|
+
data: { provider: 'value', items: mockData },
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
render(
|
|
174
|
+
<ActionProvider handlers={{ toggleStatus: actionHandler }}>
|
|
175
|
+
<ObjectGrid schema={schema} />
|
|
176
|
+
</ActionProvider>
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
await waitFor(() => {
|
|
180
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const statusBtns = screen.getAllByRole('button', { name: 'active' });
|
|
184
|
+
fireEvent.click(statusBtns[0]);
|
|
185
|
+
|
|
186
|
+
await waitFor(() => {
|
|
187
|
+
expect(actionHandler).toHaveBeenCalledTimes(1);
|
|
188
|
+
});
|
|
189
|
+
expect(actionHandler).toHaveBeenCalledWith(
|
|
190
|
+
expect.objectContaining({
|
|
191
|
+
type: 'toggleStatus',
|
|
192
|
+
params: expect.objectContaining({
|
|
193
|
+
field: 'status',
|
|
194
|
+
value: 'active',
|
|
195
|
+
record: expect.objectContaining({ _id: '1', name: 'Alice' }),
|
|
196
|
+
}),
|
|
197
|
+
}),
|
|
198
|
+
expect.any(Object) // ActionCtx
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// =========================================================================
|
|
204
|
+
// 4. Type-based cell rendering
|
|
205
|
+
// =========================================================================
|
|
206
|
+
describe('ListColumn: type', () => {
|
|
207
|
+
it('should use getCellRenderer for typed columns', async () => {
|
|
208
|
+
renderGrid([
|
|
209
|
+
{ field: 'name', label: 'Name' },
|
|
210
|
+
{ field: 'email', label: 'Email', type: 'email' },
|
|
211
|
+
]);
|
|
212
|
+
|
|
213
|
+
await waitFor(() => {
|
|
214
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Email type should render as a mailto link
|
|
218
|
+
const emailLink = screen.getByText('alice@test.com');
|
|
219
|
+
expect(emailLink).toBeInTheDocument();
|
|
220
|
+
// The email cell renderer wraps in an anchor
|
|
221
|
+
expect(emailLink.closest('a')).toHaveAttribute('href', 'mailto:alice@test.com');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should render boolean type columns correctly', async () => {
|
|
225
|
+
const boolData = [
|
|
226
|
+
{ _id: '1', name: 'Alice', active: true },
|
|
227
|
+
{ _id: '2', name: 'Bob', active: false },
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
const schema: any = {
|
|
231
|
+
type: 'object-grid' as const,
|
|
232
|
+
objectName: 'test_object',
|
|
233
|
+
columns: [
|
|
234
|
+
{ field: 'name', label: 'Name' },
|
|
235
|
+
{ field: 'active', label: 'Active', type: 'boolean' },
|
|
236
|
+
],
|
|
237
|
+
data: { provider: 'value', items: boolData },
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
render(
|
|
241
|
+
<ActionProvider>
|
|
242
|
+
<ObjectGrid schema={schema} />
|
|
243
|
+
</ActionProvider>
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
await waitFor(() => {
|
|
247
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Boolean renderer should show check/x icons or text representation
|
|
251
|
+
expect(screen.getByText('Active')).toBeInTheDocument();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// =========================================================================
|
|
256
|
+
// 5. Combined: link + type
|
|
257
|
+
// =========================================================================
|
|
258
|
+
describe('ListColumn: link + type', () => {
|
|
259
|
+
it('should render typed content inside a clickable link', async () => {
|
|
260
|
+
const onNavigate = vi.fn();
|
|
261
|
+
|
|
262
|
+
renderGrid(
|
|
263
|
+
[
|
|
264
|
+
{ field: 'email', label: 'Email', link: true, type: 'email' },
|
|
265
|
+
{ field: 'name', label: 'Name' },
|
|
266
|
+
],
|
|
267
|
+
{
|
|
268
|
+
navigation: { mode: 'page' },
|
|
269
|
+
onNavigate,
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
await waitFor(() => {
|
|
274
|
+
expect(screen.getByText('Email')).toBeInTheDocument();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Should be a button wrapping the email content
|
|
278
|
+
const emailBtn = screen.getByRole('button', { name: /alice@test.com/ });
|
|
279
|
+
expect(emailBtn).toBeInTheDocument();
|
|
280
|
+
expect(emailBtn).toHaveClass('text-primary');
|
|
281
|
+
|
|
282
|
+
fireEvent.click(emailBtn);
|
|
283
|
+
expect(onNavigate).toHaveBeenCalledWith('1', 'view');
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// =========================================================================
|
|
288
|
+
// 6. Column properties passthrough
|
|
289
|
+
// =========================================================================
|
|
290
|
+
describe('ListColumn: property passthrough', () => {
|
|
291
|
+
it('should auto-generate header from field name if no label', async () => {
|
|
292
|
+
renderGrid([
|
|
293
|
+
{ field: 'first_name' },
|
|
294
|
+
]);
|
|
295
|
+
|
|
296
|
+
await waitFor(() => {
|
|
297
|
+
// Should convert snake_case to title case
|
|
298
|
+
expect(screen.getByText('First name')).toBeInTheDocument();
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should use label when provided', async () => {
|
|
303
|
+
renderGrid([
|
|
304
|
+
{ field: 'name', label: 'Full Name' },
|
|
305
|
+
]);
|
|
306
|
+
|
|
307
|
+
await waitFor(() => {
|
|
308
|
+
expect(screen.getByText('Full Name')).toBeInTheDocument();
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should handle all columns hidden gracefully', async () => {
|
|
313
|
+
const { container } = renderGrid([
|
|
314
|
+
{ field: 'name', hidden: true },
|
|
315
|
+
{ field: 'email', hidden: true },
|
|
316
|
+
]);
|
|
317
|
+
|
|
318
|
+
// Should render without error, just no columns
|
|
319
|
+
await waitFor(() => {
|
|
320
|
+
expect(container).toBeInTheDocument();
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// =========================================================================
|
|
326
|
+
// 7. Type definitions alignment
|
|
327
|
+
// =========================================================================
|
|
328
|
+
describe('ListColumn type definitions', () => {
|
|
329
|
+
it('should accept link property on ListColumn', () => {
|
|
330
|
+
const col: ListColumn = {
|
|
331
|
+
field: 'name',
|
|
332
|
+
link: true,
|
|
333
|
+
};
|
|
334
|
+
expect(col.link).toBe(true);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should accept action property on ListColumn', () => {
|
|
338
|
+
const col: ListColumn = {
|
|
339
|
+
field: 'status',
|
|
340
|
+
action: 'toggleStatus',
|
|
341
|
+
};
|
|
342
|
+
expect(col.action).toBe('toggleStatus');
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should accept both link and action together', () => {
|
|
346
|
+
const col: ListColumn = {
|
|
347
|
+
field: 'name',
|
|
348
|
+
link: true,
|
|
349
|
+
action: 'viewDetail',
|
|
350
|
+
};
|
|
351
|
+
expect(col.link).toBe(true);
|
|
352
|
+
expect(col.action).toBe('viewDetail');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should accept all ListColumn properties', () => {
|
|
356
|
+
const col: ListColumn = {
|
|
357
|
+
field: 'amount',
|
|
358
|
+
label: 'Total Amount',
|
|
359
|
+
width: 150,
|
|
360
|
+
align: 'right',
|
|
361
|
+
hidden: false,
|
|
362
|
+
sortable: true,
|
|
363
|
+
resizable: true,
|
|
364
|
+
wrap: false,
|
|
365
|
+
type: 'currency',
|
|
366
|
+
link: false,
|
|
367
|
+
action: 'editAmount',
|
|
368
|
+
};
|
|
369
|
+
expect(col.field).toBe('amount');
|
|
370
|
+
expect(col.type).toBe('currency');
|
|
371
|
+
expect(col.link).toBe(false);
|
|
372
|
+
expect(col.action).toBe('editAmount');
|
|
373
|
+
});
|
|
374
|
+
});
|