@vmscloud/moz-ui-grid-react 0.1.23 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/MozGrid.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ReactNode, ComponentType } from 'react';
2
2
  import { GridChrome, WrapperExtras, MozGridCoreProps, WrapperToolItemsProps } from '@vmscloud/moz-ui-grid-wrapper';
3
- import { PureSheet, ScrollEndReachedPayload, ScrollPayload } from '@vmscloud/moz-ui-grid-core';
3
+ import { DataMutationEvent, PureSheet, ScrollEndReachedPayload, ScrollPayload } from '@vmscloud/moz-ui-grid-core';
4
4
  import { CellSlotProps, HeaderSlotProps, EditorSlotProps } from './types/slots';
5
5
  /**
6
6
  * MozGrid — vanilla GridChrome(@vmscloud/moz-ui-grid-wrapper) 위임 React 어댑터.
@@ -43,6 +43,7 @@ export type MozGridProps = WrapperExtras & {
43
43
  onRowReordered?: (payload: unknown) => void;
44
44
  onCellClick?: (payload: unknown) => void;
45
45
  onCellDblclick?: (payload: unknown) => void;
46
+ onDataChanged?: (payload: DataMutationEvent) => void;
46
47
  onCellChange?: (payload: unknown) => void;
47
48
  onCellBeforeEdit?: (payload: unknown) => void;
48
49
  onCellEditCommitting?: (payload: unknown) => void;
@@ -108,6 +109,7 @@ export declare const MozGrid: import('react').ForwardRefExoticComponent<WrapperE
108
109
  onRowReordered?: (payload: unknown) => void;
109
110
  onCellClick?: (payload: unknown) => void;
110
111
  onCellDblclick?: (payload: unknown) => void;
112
+ onDataChanged?: (payload: DataMutationEvent) => void;
111
113
  onCellChange?: (payload: unknown) => void;
112
114
  onCellBeforeEdit?: (payload: unknown) => void;
113
115
  onCellEditCommitting?: (payload: unknown) => void;
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var e=Object.defineProperty,t=(t,o,n)=>((t,o,n)=>o in t?e(t,o,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[o]=n)(t,"symbol"!=typeof o?o+"":o,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react/jsx-runtime"),n=require("react"),r=require("@vmscloud/moz-ui-grid-wrapper"),u=require("react-dom/client");class s{constructor(){t(this,"instances",new Map),t(this,"contextWrapper",null),t(this,"lifecycleRoots",new WeakMap)}setContextWrapper(e){this.contextWrapper=e}mount(e,t,o,n={}){let r=this.instances.get(e);if(r&&r.container!==t){const t=r;queueMicrotask(()=>t.root.unmount()),this.instances.delete(e),r=void 0}if(r)return r.component=o,r.props={...n},void this.render(r);r={root:u.createRoot(t),container:t,component:o,props:{...n}},this.instances.set(e,r),this.render(r)}mountInstance(e,t,o){const r=this.lifecycleRoots.get(e);if(r)try{r.unmount()}catch{}const s=u.createRoot(e);this.lifecycleRoots.set(e,s);const i=e=>{const o=n.createElement(t,e);s.render(this.contextWrapper?this.contextWrapper(o):o)};return i(o),{update:e=>i(e),destroy:()=>{queueMicrotask(()=>{this.lifecycleRoots.get(e)===s&&(s.unmount(),this.lifecycleRoots.delete(e))})}}}updateProps(e,t){const o=this.instances.get(e);o&&(o.props={...o.props,...t},this.render(o))}unmount(e){const t=this.instances.get(e);t&&(queueMicrotask(()=>t.root.unmount()),this.instances.delete(e))}unmountByPrefix(e){for(const t of Array.from(this.instances.keys()))t.startsWith(e)&&this.unmount(t)}unmountAll(){for(const[,e]of this.instances)queueMicrotask(()=>e.root.unmount());this.instances.clear()}get size(){return this.instances.size}has(e){return this.instances.has(e)}render(e){const t=n.createElement(e.component,e.props),o=this.contextWrapper?this.contextWrapper(t):t;e.root.render(o)}}const i=n.createContext(null);function c(){return n.useContext(i)}const l=n.createContext(void 0);function a(){return n.useContext(l)}function d(e,t,o){return e.map(e=>{const n={...e},r=t.get("cell",e.id);(null==r?void 0:r.component)&&(n.cellRenderer=function(e,t,o){const n=e=>({value:e.value,rowData:e.rowData,columnId:e.columnId,rowId:e.rowId}),r=(e,r)=>{const u=o.mountInstance(e,t,n(r));return{update:e=>(u.update(n(e)),!0),destroy(){u.destroy()}}};return r.__cellRendererKind="lifecycle",r}(e.id,r.component,o));const u=t.get("header",e.id);(null==u?void 0:u.component)&&(n.headerRenderer=function(e,t,o){return n=>{const r=document.createElement("div");r.style.display="contents";const u=`header:${e}`,s={columnId:n.columnId,header:n.header};return o.mount(u,r,t,s),r}}(e.id,u.component,o));const s=t.get("editor",e.id);return(null==s?void 0:s.component)&&(n.editor=function(e,t,o){return{type:"custom",renderer:n=>{const r=document.createElement("div");r.style.display="contents";const u=`editor:${e}`,s={value:n.value,rowData:n.rowData,columnId:e,onCommit:n.onCommit,onCancel:n.onCancel};return o.mount(u,r,t,s),r},destroy:()=>{o.unmount(`editor:${e}`)}}}(e.id,s.component,o)),n})}const p=[["row:click","onRowClick"],["row:dblclick","onRowDblclick"],["row:pinned","onRowPinned"],["row:unpinned","onRowUnpinned"],["row:reordered","onRowReordered"],["cell:click","onCellClick"],["cell:dblclick","onCellDblclick"],["cell:change","onCellChange"],["cell:beforeEdit","onCellBeforeEdit"],["cell:editCommitting","onCellEditCommitting"],["cell:editCommitted","onCellEditCommitted"],["cell:tooltip","onCellTooltip"],["selection:changed","onSelectionChanged"],["column:resize","onColumnResize"],["column:reorder","onColumnReorder"],["column:pin","onColumnPin"],["column:visibility","onColumnVisibility"],["sort:changed","onSortChanged"],["header:click","onHeaderClick"],["header:sortClick","onHeaderSortClick"],["header:filterClick","onHeaderFilterClick"],["filter:changed","onFilterChanged"],["scroll","onScroll"],["scroll:endReached","onEndReached"],["undo:stateChange","onUndoStateChange"],["clipboard:copy","onClipboardCopy"],["clipboard:paste","onClipboardPaste"],["validation:changed","onValidationChanged"],["columnGroup:toggle","onColumnGroupToggle"],["contextmenu","onContextMenu"],["rowGroup:toggle","onGroupToggle"],["rowGroup:batchToggle","onGroupBatchToggle"]],g=[["tool-left","toolLeft"],["tool-items","toolItems"],["option-popup","optionPopup"],["tool-bottom","toolBottom"],["notification","notification"],["footer","footer"],["empty-state","emptyStateContent"]],m=n.forwardRef(function(e,t){const u=n.useRef(null),i=n.useRef(null),l=n.useRef(null),m=c(),f=n.useRef(m);f.current=m;const h=a(),C=e.locale??h,x=n.useRef(C);x.current=C;const y=n.useRef([]),S=n.useRef({disabled:!0,hasItem:!1}),v=n.useRef(new Set),R=e=>{S.current=e,v.current.forEach(e=>e())},w=n.useRef(e);w.current=e;const M=n.useRef(void 0),T=n.useRef(void 0),b=n.useRef(null),[I,F]=n.useState(0),B=()=>{var e;const t=w.current,o=l.current,n=null==(e=t.coreConfig)?void 0:e.fields;if(o&&n!==M.current){const e=function(){const e=[];function t(e,t){return t?`${e}:${t}`:e}return{register(o){const n=e.findIndex(e=>t(e.type,e.fieldId)===t(o.type,o.fieldId));n>=0?e[n]=o:e.push(o)},unregister(o,n){const r=t(o,n),u=e.findIndex(e=>t(e.type,e.fieldId)===r);u>=0&&e.splice(u,1)},get(o,n){const r=t(o,n);return e.find(e=>t(e.type,e.fieldId)===r)},getAll:t=>e.filter(e=>e.type===t)}}();for(const[o,n]of Object.entries(t.cellTemplates??{}))e.register({type:"cell",fieldId:o,component:n});for(const[o,n]of Object.entries(t.headerTemplates??{}))e.register({type:"header",fieldId:o,component:n});for(const[o,n]of Object.entries(t.editorTemplates??{}))e.register({type:"editor",fieldId:o,component:n});M.current=n,T.current=n?d(n,e,o):void 0}const r={...t.coreConfig??{}},u=T.current;return u&&(r.fields=u),void 0!==x.current&&(r.locale=x.current),r},P=()=>{const e=u.current;if(!e)return;const t=new s;t.setContextWrapper(f.current),l.current=t,M.current=void 0,T.current=void 0;const c=new r.GridChrome(e,B(),(e=>{const t=w.current,r={};for(const[u,s]of g)null!=t[s]&&(r[u]=t=>{const r=`slot:${u}`,i="tool-items"===u?()=>{const[,e]=n.useState(0);n.useEffect(()=>{const t=()=>e(e=>e+1);return v.current.add(t),()=>{v.current.delete(t)}},[]);const t=w.current.toolItems;return o.jsx(o.Fragment,{children:("function"==typeof t?t(S.current):t)??null})}:()=>o.jsx(o.Fragment,{children:w.current[s]??null});return e.mount(r,t,i,{}),()=>e.unmount(r)});return{useToolBox:t.useToolBox,useToolBoxSetting:t.useToolBoxSetting,useExtendFooter:t.useExtendFooter,useSummaryFooter:t.useSummaryFooter,useSort:t.useSort,useCustomCell:t.useCustomCell,useContextMenu:t.useContextMenu,useBuiltinTooltip:t.useBuiltinTooltip,usePivot:t.usePivot,usePivotBar:t.usePivotBar,useRowGroupSettings:t.useRowGroupSettings,useFieldSettings:t.useFieldSettings,useFormatSettings:t.useFormatSettings,useLayoutSettings:t.useLayoutSettings,useAggregateSettings:t.useAggregateSettings,useChart:t.useChart,useMultipleChart:t.useMultipleChart,chartIncludeGrandTotal:t.chartIncludeGrandTotal,chartMap:t.chartMap,loading:t.loading,locale:x.current,sourceFields:t.sourceFields,contextMenuConfig:t.contextMenuConfig,emptyState:t.emptyState,slots:r,onToolItemsPropsChange:e=>R(e)}})(t));i.current=c,c.on("data:loaded",()=>{var e,t;return null==(t=(e=w.current).onDataLoaded)?void 0:t.call(e)});const a=c.on.bind(c);for(const[o,n]of p)a(o,e=>{var t,o;return null==(o=(t=w.current)[n])?void 0:o.call(t,e)});y.current.push(c.onConfigChange(()=>{var e,t;const o=w.current.coreConfig;null==(t=(e=w.current).onCoreConfigChange)||t.call(e,c.getCoreConfig(o))}));const d=b.current;b.current=null,c.ready().then(async()=>{var e,t;d&&await c.restoreReinitState(d),R(c.getToolItemsProps()),null==(t=(e=w.current).onReady)||t.call(e,c.grid,c)})};n.useEffect(()=>{var e;null==(e=l.current)||e.setContextWrapper(m)},[m]),n.useEffect(()=>(P(),()=>{var e,t;for(const o of y.current)try{o()}catch{}y.current=[],null==(e=i.current)||e.destroy(),i.current=null,null==(t=l.current)||t.unmountAll(),l.current=null}),[I]),n.useEffect(()=>{const e=i.current;if(!e)return;const{reinitRequired:t}=e.reconcileCoreConfig(B());t&&(b.current=e.captureReinitState(),F(e=>e+1))}),n.useEffect(()=>{var t;null==(t=i.current)||t.setUIOptions({useToolBox:e.useToolBox,useToolBoxSetting:e.useToolBoxSetting,useExtendFooter:e.useExtendFooter,useSummaryFooter:e.useSummaryFooter,useSort:e.useSort,useCustomCell:e.useCustomCell,useContextMenu:e.useContextMenu,useBuiltinTooltip:e.useBuiltinTooltip,usePivot:e.usePivot,usePivotBar:e.usePivotBar,useRowGroupSettings:e.useRowGroupSettings,useFieldSettings:e.useFieldSettings,useFormatSettings:e.useFormatSettings,useLayoutSettings:e.useLayoutSettings,useAggregateSettings:e.useAggregateSettings,loading:e.loading})},[e.useToolBox,e.useToolBoxSetting,e.useExtendFooter,e.useSummaryFooter,e.useSort,e.useCustomCell,e.useContextMenu,e.useBuiltinTooltip,e.usePivot,e.usePivotBar,e.useRowGroupSettings,e.useFieldSettings,e.useFormatSettings,e.useLayoutSettings,e.useAggregateSettings,e.loading]),n.useEffect(()=>{var t;null==(t=i.current)||t.setContextMenuConfig(e.contextMenuConfig)},[e.contextMenuConfig]),n.useImperativeHandle(t,()=>new Proxy({},{get(e,t){if("chrome"===t)return i.current;const o=i.current;if(!o)return;const n=o[t];return"function"==typeof n?n.bind(o):n},has:(e,t)=>"chrome"===t||!!i.current&&t in i.current}),[]);const E=e.height,k={height:"number"==typeof E?`${E}px`:E??"100%",width:"100%",overflow:"hidden"};return o.jsx("div",{ref:u,className:"moz-grid-chrome",style:k})});exports.MozContextWrapperContext=i,exports.MozGrid=m,exports.MozGridLocaleContext=l,exports.MozGridLocaleProvider=function({locale:e,children:t}){return o.jsx(l.Provider,{value:e,children:t})},exports.useMozContextWrapper=c,exports.useMozGridLocale=a;
1
+ "use strict";var e=Object.defineProperty,t=(t,o,n)=>((t,o,n)=>o in t?e(t,o,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[o]=n)(t,"symbol"!=typeof o?o+"":o,n);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react/jsx-runtime"),n=require("react"),r=require("@vmscloud/moz-ui-grid-wrapper"),u=require("react-dom/client");class s{constructor(){t(this,"instances",new Map),t(this,"contextWrapper",null),t(this,"lifecycleRoots",new WeakMap)}setContextWrapper(e){this.contextWrapper=e}mount(e,t,o,n={}){let r=this.instances.get(e);if(r&&r.container!==t){const t=r;queueMicrotask(()=>t.root.unmount()),this.instances.delete(e),r=void 0}if(r)return r.component=o,r.props={...n},void this.render(r);r={root:u.createRoot(t),container:t,component:o,props:{...n}},this.instances.set(e,r),this.render(r)}mountInstance(e,t,o){const r=this.lifecycleRoots.get(e);if(r)try{r.unmount()}catch{}const s=u.createRoot(e);this.lifecycleRoots.set(e,s);const i=e=>{const o=n.createElement(t,e);s.render(this.contextWrapper?this.contextWrapper(o):o)};return i(o),{update:e=>i(e),destroy:()=>{queueMicrotask(()=>{this.lifecycleRoots.get(e)===s&&(s.unmount(),this.lifecycleRoots.delete(e))})}}}updateProps(e,t){const o=this.instances.get(e);o&&(o.props={...o.props,...t},this.render(o))}unmount(e){const t=this.instances.get(e);t&&(queueMicrotask(()=>t.root.unmount()),this.instances.delete(e))}unmountByPrefix(e){for(const t of Array.from(this.instances.keys()))t.startsWith(e)&&this.unmount(t)}unmountAll(){for(const[,e]of this.instances)queueMicrotask(()=>e.root.unmount());this.instances.clear()}get size(){return this.instances.size}has(e){return this.instances.has(e)}render(e){const t=n.createElement(e.component,e.props),o=this.contextWrapper?this.contextWrapper(t):t;e.root.render(o)}}const i=n.createContext(null);function c(){return n.useContext(i)}const l=n.createContext({});function a(){const e=n.useContext(l),t=n.useContext(p);return{...e,locale:t??e.locale}}function d({config:e={},children:t}){const n=a(),r={locale:e.locale??n.locale,timeZone:e.timeZone??n.timeZone};return void 0!==r.timeZone&&new Intl.DateTimeFormat("en",{timeZone:r.timeZone}),o.jsx(l.Provider,{value:r,children:o.jsx(p.Provider,{value:r.locale,children:t})})}const p=n.createContext(void 0);function m(e,t,o){return e.map(e=>{const n={...e},r=t.get("cell",e.id);(null==r?void 0:r.component)&&(n.cellRenderer=function(e,t,o){const n=e=>({value:e.value,rowData:e.rowData,columnId:e.columnId,rowId:e.rowId}),r=(e,r)=>{const u=o.mountInstance(e,t,n(r));return{update:e=>(u.update(n(e)),!0),destroy(){u.destroy()}}};return r.__cellRendererKind="lifecycle",r}(e.id,r.component,o));const u=t.get("header",e.id);(null==u?void 0:u.component)&&(n.headerRenderer=function(e,t,o){return n=>{const r=document.createElement("div");r.style.display="contents";const u=`header:${e}`,s={columnId:n.columnId,header:n.header};return o.mount(u,r,t,s),r}}(e.id,u.component,o));const s=t.get("editor",e.id);return(null==s?void 0:s.component)&&(n.editor=function(e,t,o){return{type:"custom",renderer:n=>{const r=document.createElement("div");r.style.display="contents";const u=`editor:${e}`,s={value:n.value,rowData:n.rowData,columnId:e,onCommit:n.onCommit,onCancel:n.onCancel};return o.mount(u,r,t,s),r},destroy:()=>{o.unmount(`editor:${e}`)}}}(e.id,s.component,o)),n})}const g=[["data:changed","onDataChanged"],["row:click","onRowClick"],["row:dblclick","onRowDblclick"],["row:pinned","onRowPinned"],["row:unpinned","onRowUnpinned"],["row:reordered","onRowReordered"],["cell:click","onCellClick"],["cell:dblclick","onCellDblclick"],["cell:change","onCellChange"],["cell:beforeEdit","onCellBeforeEdit"],["cell:editCommitting","onCellEditCommitting"],["cell:editCommitted","onCellEditCommitted"],["cell:tooltip","onCellTooltip"],["selection:changed","onSelectionChanged"],["column:resize","onColumnResize"],["column:reorder","onColumnReorder"],["column:pin","onColumnPin"],["column:visibility","onColumnVisibility"],["sort:changed","onSortChanged"],["header:click","onHeaderClick"],["header:sortClick","onHeaderSortClick"],["header:filterClick","onHeaderFilterClick"],["filter:changed","onFilterChanged"],["scroll","onScroll"],["scroll:endReached","onEndReached"],["undo:stateChange","onUndoStateChange"],["clipboard:copy","onClipboardCopy"],["clipboard:paste","onClipboardPaste"],["validation:changed","onValidationChanged"],["columnGroup:toggle","onColumnGroupToggle"],["contextmenu","onContextMenu"],["rowGroup:toggle","onGroupToggle"],["rowGroup:batchToggle","onGroupBatchToggle"]],f=[["tool-left","toolLeft"],["tool-items","toolItems"],["option-popup","optionPopup"],["tool-bottom","toolBottom"],["notification","notification"],["footer","footer"],["empty-state","emptyStateContent"]],C=n.forwardRef(function(e,t){var u,i;const l=n.useRef(null),d=n.useRef(null),p=n.useRef(null),C=c(),h=n.useRef(C);h.current=C;const x=a(),y=e.locale??(null==(u=e.coreConfig)?void 0:u.locale)??x.locale,v=n.useRef();v.current=(null==(i=e.coreConfig)?void 0:i.timeZone)??x.timeZone;const S=n.useRef(y);S.current=y;const R=n.useRef([]),w=n.useRef({disabled:!0,hasItem:!1}),M=n.useRef(new Set),T=e=>{w.current=e,M.current.forEach(e=>e())},I=n.useRef(e);I.current=e;const b=n.useRef(void 0),F=n.useRef(void 0),P=n.useRef(null),[B,E]=n.useState(0),G=()=>{var e;const t=I.current,o=p.current,n=null==(e=t.coreConfig)?void 0:e.fields;if(o&&n!==b.current){const e=function(){const e=[];function t(e,t){return t?`${e}:${t}`:e}return{register(o){const n=e.findIndex(e=>t(e.type,e.fieldId)===t(o.type,o.fieldId));n>=0?e[n]=o:e.push(o)},unregister(o,n){const r=t(o,n),u=e.findIndex(e=>t(e.type,e.fieldId)===r);u>=0&&e.splice(u,1)},get(o,n){const r=t(o,n);return e.find(e=>t(e.type,e.fieldId)===r)},getAll:t=>e.filter(e=>e.type===t)}}();for(const[o,n]of Object.entries(t.cellTemplates??{}))e.register({type:"cell",fieldId:o,component:n});for(const[o,n]of Object.entries(t.headerTemplates??{}))e.register({type:"header",fieldId:o,component:n});for(const[o,n]of Object.entries(t.editorTemplates??{}))e.register({type:"editor",fieldId:o,component:n});b.current=n,F.current=n?m(n,e,o):void 0}const r={...t.coreConfig??{}},u=F.current;return u&&(r.fields=u),void 0!==S.current&&(r.locale=S.current),r.timeZone=v.current,r},k=()=>{const e=l.current;if(!e)return;const t=new s;t.setContextWrapper(h.current),p.current=t,b.current=void 0,F.current=void 0;const u=new r.GridChrome(e,G(),(e=>{const t=I.current,r={};for(const[u,s]of f)null!=t[s]&&(r[u]=t=>{const r=`slot:${u}`,i="tool-items"===u?()=>{const[,e]=n.useState(0);n.useEffect(()=>{const t=()=>e(e=>e+1);return M.current.add(t),()=>{M.current.delete(t)}},[]);const t=I.current.toolItems;return o.jsx(o.Fragment,{children:("function"==typeof t?t(w.current):t)??null})}:()=>o.jsx(o.Fragment,{children:I.current[s]??null});return e.mount(r,t,i,{}),()=>e.unmount(r)});return{useToolBox:t.useToolBox,useToolBoxSetting:t.useToolBoxSetting,useExtendFooter:t.useExtendFooter,useSummaryFooter:t.useSummaryFooter,useSort:t.useSort,useCustomCell:t.useCustomCell,useContextMenu:t.useContextMenu,useBuiltinTooltip:t.useBuiltinTooltip,usePivot:t.usePivot,usePivotBar:t.usePivotBar,useRowGroupSettings:t.useRowGroupSettings,useFieldSettings:t.useFieldSettings,useFormatSettings:t.useFormatSettings,useLayoutSettings:t.useLayoutSettings,useAggregateSettings:t.useAggregateSettings,useChart:t.useChart,useMultipleChart:t.useMultipleChart,chartIncludeGrandTotal:t.chartIncludeGrandTotal,chartMap:t.chartMap,loading:t.loading,locale:S.current,sourceFields:t.sourceFields,contextMenuConfig:t.contextMenuConfig,emptyState:t.emptyState,slots:r,onToolItemsPropsChange:e=>T(e)}})(t));d.current=u,u.on("data:loaded",()=>{var e,t;return null==(t=(e=I.current).onDataLoaded)?void 0:t.call(e)});const i=u.on.bind(u);for(const[o,n]of g)i(o,e=>{var t,o;return null==(o=(t=I.current)[n])?void 0:o.call(t,e)});R.current.push(u.onConfigChange(()=>{var e,t;const o=I.current.coreConfig;null==(t=(e=I.current).onCoreConfigChange)||t.call(e,u.getCoreConfig(o))}));const c=P.current;P.current=null,u.ready().then(async()=>{var e,t;d.current===u&&(c&&await u.restoreReinitState(c),d.current===u&&(T(u.getToolItemsProps()),null==(t=(e=I.current).onReady)||t.call(e,u.grid,u)))}).catch(e=>{d.current})};n.useEffect(()=>{var e;null==(e=p.current)||e.setContextWrapper(C)},[C]),n.useEffect(()=>(k(),()=>{var e,t;for(const o of R.current)try{o()}catch{}R.current=[],null==(e=d.current)||e.destroy(),d.current=null,null==(t=p.current)||t.unmountAll(),p.current=null}),[B]),n.useEffect(()=>{const e=d.current;if(!e)return;const{reinitRequired:t}=e.reconcileCoreConfig(G());t&&(P.current=e.captureReinitState(),E(e=>e+1))}),n.useEffect(()=>{var t;null==(t=d.current)||t.setUIOptions({useToolBox:e.useToolBox,useToolBoxSetting:e.useToolBoxSetting,useExtendFooter:e.useExtendFooter,useSummaryFooter:e.useSummaryFooter,useSort:e.useSort,useCustomCell:e.useCustomCell,useContextMenu:e.useContextMenu,useBuiltinTooltip:e.useBuiltinTooltip,usePivot:e.usePivot,usePivotBar:e.usePivotBar,useRowGroupSettings:e.useRowGroupSettings,useFieldSettings:e.useFieldSettings,useFormatSettings:e.useFormatSettings,useLayoutSettings:e.useLayoutSettings,useAggregateSettings:e.useAggregateSettings,loading:e.loading})},[e.useToolBox,e.useToolBoxSetting,e.useExtendFooter,e.useSummaryFooter,e.useSort,e.useCustomCell,e.useContextMenu,e.useBuiltinTooltip,e.usePivot,e.usePivotBar,e.useRowGroupSettings,e.useFieldSettings,e.useFormatSettings,e.useLayoutSettings,e.useAggregateSettings,e.loading]),n.useEffect(()=>{var t;null==(t=d.current)||t.setContextMenuConfig(e.contextMenuConfig)},[e.contextMenuConfig]),n.useImperativeHandle(t,()=>new Proxy({},{get(e,t){if("chrome"===t)return d.current;const o=d.current;if(!o)return;const n=o[t];return"function"==typeof n?n.bind(o):n},has:(e,t)=>"chrome"===t||!!d.current&&t in d.current}),[]);const z=e.height,W={height:"number"==typeof z?`${z}px`:z??"100%",width:"100%",overflow:"hidden"};return o.jsx("div",{ref:l,className:"moz-grid-chrome",style:W})});exports.MozContextWrapperContext=i,exports.MozGrid=C,exports.MozGridConfigContext=l,exports.MozGridConfigProvider=d,exports.MozGridLocaleContext=p,exports.MozGridLocaleProvider=function({locale:e,children:t}){return o.jsx(d,{config:{locale:e},children:t})},exports.useMozContextWrapper=c,exports.useMozGridConfig=a,exports.useMozGridLocale=function(){return n.useContext(p)};
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { MozGrid, type MozGridProps, type MozGridHandle } from './MozGrid';
2
2
  export { MozGridLocaleContext, MozGridLocaleProvider, useMozGridLocale } from './locale/MozGridLocaleContext';
3
3
  export type { MozGridLocaleProviderProps } from './locale/MozGridLocaleContext';
4
+ export { MozGridConfigContext, MozGridConfigProvider, useMozGridConfig } from './locale/MozGridLocaleContext';
5
+ export type { MozGridConfig, MozGridConfigProviderProps } from './locale/MozGridLocaleContext';
4
6
  export { MozContextWrapperContext, useMozContextWrapper, type ContextWrapper } from './contextWrapper';
5
7
  export type { GridChrome } from '@vmscloud/moz-ui-grid-wrapper';
6
8
  export type { PureSheet } from '@vmscloud/moz-ui-grid-core';
package/dist/index.js CHANGED
@@ -123,9 +123,21 @@ const MozContextWrapperContext = createContext(null);
123
123
  function useMozContextWrapper() {
124
124
  return useContext(MozContextWrapperContext);
125
125
  }
126
+ const MozGridConfigContext = createContext({});
127
+ function useMozGridConfig() {
128
+ const config = useContext(MozGridConfigContext);
129
+ const locale = useContext(MozGridLocaleContext);
130
+ return { ...config, locale: locale ?? config.locale };
131
+ }
132
+ function MozGridConfigProvider({ config = {}, children }) {
133
+ const parent = useMozGridConfig();
134
+ const value = { locale: config.locale ?? parent.locale, timeZone: config.timeZone ?? parent.timeZone };
135
+ if (value.timeZone !== void 0) new Intl.DateTimeFormat("en", { timeZone: value.timeZone });
136
+ return /* @__PURE__ */ jsx(MozGridConfigContext.Provider, { value, children: /* @__PURE__ */ jsx(MozGridLocaleContext.Provider, { value: value.locale, children }) });
137
+ }
126
138
  const MozGridLocaleContext = createContext(void 0);
127
139
  function MozGridLocaleProvider({ locale, children }) {
128
- return /* @__PURE__ */ jsx(MozGridLocaleContext.Provider, { value: locale, children });
140
+ return /* @__PURE__ */ jsx(MozGridConfigProvider, { config: { locale }, children });
129
141
  }
130
142
  function useMozGridLocale() {
131
143
  return useContext(MozGridLocaleContext);
@@ -236,6 +248,7 @@ function createEditorConfig(fieldId, Component, renderer) {
236
248
  };
237
249
  }
238
250
  const EVENT_CALLBACKS = [
251
+ ["data:changed", "onDataChanged"],
239
252
  ["row:click", "onRowClick"],
240
253
  ["row:dblclick", "onRowDblclick"],
241
254
  ["row:pinned", "onRowPinned"],
@@ -279,14 +292,17 @@ const SLOT_PROP_MAP = [
279
292
  ["empty-state", "emptyStateContent"]
280
293
  ];
281
294
  const MozGrid = forwardRef(function MozGrid2(props, ref) {
295
+ var _a, _b;
282
296
  const hostRef = useRef(null);
283
297
  const chromeRef = useRef(null);
284
298
  const rendererRef = useRef(null);
285
299
  const consumerWrapper = useMozContextWrapper();
286
300
  const consumerWrapperRef = useRef(consumerWrapper);
287
301
  consumerWrapperRef.current = consumerWrapper;
288
- const contextLocale = useMozGridLocale();
289
- const resolvedLocale = props.locale ?? contextLocale;
302
+ const contextConfig = useMozGridConfig();
303
+ const resolvedLocale = props.locale ?? ((_a = props.coreConfig) == null ? void 0 : _a.locale) ?? contextConfig.locale;
304
+ const resolvedTimeZoneRef = useRef();
305
+ resolvedTimeZoneRef.current = ((_b = props.coreConfig) == null ? void 0 : _b.timeZone) ?? contextConfig.timeZone;
290
306
  const resolvedLocaleRef = useRef(resolvedLocale);
291
307
  resolvedLocaleRef.current = resolvedLocale;
292
308
  const chromeUnsubsRef = useRef([]);
@@ -303,10 +319,10 @@ const MozGrid = forwardRef(function MozGrid2(props, ref) {
303
319
  const pendingRestoreRef = useRef(null);
304
320
  const [mountTick, setMountTick] = useState(0);
305
321
  const buildCoreConfig = () => {
306
- var _a;
322
+ var _a2;
307
323
  const pp = propsRef.current;
308
324
  const renderer = rendererRef.current;
309
- const raw = (_a = pp.coreConfig) == null ? void 0 : _a.fields;
325
+ const raw = (_a2 = pp.coreConfig) == null ? void 0 : _a2.fields;
310
326
  if (renderer && raw !== lastRawFieldsRef.current) {
311
327
  const registry = createTemplateRegistry();
312
328
  for (const [fieldId, component] of Object.entries(pp.cellTemplates ?? {})) {
@@ -325,6 +341,7 @@ const MozGrid = forwardRef(function MozGrid2(props, ref) {
325
341
  const built = lastBuiltFieldsRef.current;
326
342
  if (built) core.fields = built;
327
343
  if (resolvedLocaleRef.current !== void 0) core.locale = resolvedLocaleRef.current;
344
+ core.timeZone = resolvedTimeZoneRef.current;
328
345
  return core;
329
346
  };
330
347
  const buildChromeOptions = (renderer) => {
@@ -391,41 +408,45 @@ const MozGrid = forwardRef(function MozGrid2(props, ref) {
391
408
  const chrome = new GridChrome(host, buildCoreConfig(), buildChromeOptions(renderer));
392
409
  chromeRef.current = chrome;
393
410
  chrome.on("data:loaded", () => {
394
- var _a, _b;
395
- return (_b = (_a = propsRef.current).onDataLoaded) == null ? void 0 : _b.call(_a);
411
+ var _a2, _b2;
412
+ return (_b2 = (_a2 = propsRef.current).onDataLoaded) == null ? void 0 : _b2.call(_a2);
396
413
  });
397
414
  const onAny = chrome.on.bind(chrome);
398
415
  for (const [ev, cbKey] of EVENT_CALLBACKS) {
399
416
  onAny(ev, (p) => {
400
- var _a, _b;
401
- return (_b = (_a = propsRef.current)[cbKey]) == null ? void 0 : _b.call(_a, p);
417
+ var _a2, _b2;
418
+ return (_b2 = (_a2 = propsRef.current)[cbKey]) == null ? void 0 : _b2.call(_a2, p);
402
419
  });
403
420
  }
404
421
  const unsubs = chromeUnsubsRef.current;
405
422
  unsubs.push(
406
423
  chrome.onConfigChange(() => {
407
- var _a, _b;
424
+ var _a2, _b2;
408
425
  const base = propsRef.current.coreConfig;
409
- (_b = (_a = propsRef.current).onCoreConfigChange) == null ? void 0 : _b.call(_a, chrome.getCoreConfig(base));
426
+ (_b2 = (_a2 = propsRef.current).onCoreConfigChange) == null ? void 0 : _b2.call(_a2, chrome.getCoreConfig(base));
410
427
  })
411
428
  );
412
429
  const restore = pendingRestoreRef.current;
413
430
  pendingRestoreRef.current = null;
414
431
  void chrome.ready().then(async () => {
415
- var _a, _b;
432
+ var _a2, _b2;
433
+ if (chromeRef.current !== chrome) return;
416
434
  if (restore) await chrome.restoreReinitState(restore);
435
+ if (chromeRef.current !== chrome) return;
417
436
  setToolItemsProps(chrome.getToolItemsProps());
418
- (_b = (_a = propsRef.current).onReady) == null ? void 0 : _b.call(_a, chrome.grid, chrome);
437
+ (_b2 = (_a2 = propsRef.current).onReady) == null ? void 0 : _b2.call(_a2, chrome.grid, chrome);
438
+ }).catch((error) => {
439
+ if (chromeRef.current === chrome) console.error("[MozGrid] initialization failed", error);
419
440
  });
420
441
  };
421
442
  useEffect(() => {
422
- var _a;
423
- (_a = rendererRef.current) == null ? void 0 : _a.setContextWrapper(consumerWrapper);
443
+ var _a2;
444
+ (_a2 = rendererRef.current) == null ? void 0 : _a2.setContextWrapper(consumerWrapper);
424
445
  }, [consumerWrapper]);
425
446
  useEffect(() => {
426
447
  createChromeInstance();
427
448
  return () => {
428
- var _a, _b;
449
+ var _a2, _b2;
429
450
  for (const u of chromeUnsubsRef.current) {
430
451
  try {
431
452
  u();
@@ -433,9 +454,9 @@ const MozGrid = forwardRef(function MozGrid2(props, ref) {
433
454
  }
434
455
  }
435
456
  chromeUnsubsRef.current = [];
436
- (_a = chromeRef.current) == null ? void 0 : _a.destroy();
457
+ (_a2 = chromeRef.current) == null ? void 0 : _a2.destroy();
437
458
  chromeRef.current = null;
438
- (_b = rendererRef.current) == null ? void 0 : _b.unmountAll();
459
+ (_b2 = rendererRef.current) == null ? void 0 : _b2.unmountAll();
439
460
  rendererRef.current = null;
440
461
  };
441
462
  }, [mountTick]);
@@ -449,8 +470,8 @@ const MozGrid = forwardRef(function MozGrid2(props, ref) {
449
470
  }
450
471
  });
451
472
  useEffect(() => {
452
- var _a;
453
- (_a = chromeRef.current) == null ? void 0 : _a.setUIOptions({
473
+ var _a2;
474
+ (_a2 = chromeRef.current) == null ? void 0 : _a2.setUIOptions({
454
475
  useToolBox: props.useToolBox,
455
476
  useToolBoxSetting: props.useToolBoxSetting,
456
477
  useExtendFooter: props.useExtendFooter,
@@ -487,8 +508,8 @@ const MozGrid = forwardRef(function MozGrid2(props, ref) {
487
508
  props.loading
488
509
  ]);
489
510
  useEffect(() => {
490
- var _a;
491
- (_a = chromeRef.current) == null ? void 0 : _a.setContextMenuConfig(props.contextMenuConfig);
511
+ var _a2;
512
+ (_a2 = chromeRef.current) == null ? void 0 : _a2.setContextMenuConfig(props.contextMenuConfig);
492
513
  }, [props.contextMenuConfig]);
493
514
  useImperativeHandle(ref, () => new Proxy({}, {
494
515
  get(_t, p) {
@@ -513,8 +534,11 @@ const MozGrid = forwardRef(function MozGrid2(props, ref) {
513
534
  export {
514
535
  MozContextWrapperContext,
515
536
  MozGrid,
537
+ MozGridConfigContext,
538
+ MozGridConfigProvider,
516
539
  MozGridLocaleContext,
517
540
  MozGridLocaleProvider,
518
541
  useMozContextWrapper,
542
+ useMozGridConfig,
519
543
  useMozGridLocale
520
544
  };
@@ -1,4 +1,13 @@
1
1
  import { ReactNode } from 'react';
2
+ import { MozGridConfig } from '@vmscloud/moz-ui-grid-core';
3
+ export type { MozGridConfig } from '@vmscloud/moz-ui-grid-core';
4
+ export declare const MozGridConfigContext: import('react').Context<MozGridConfig>;
5
+ export interface MozGridConfigProviderProps {
6
+ config?: MozGridConfig;
7
+ children?: ReactNode;
8
+ }
9
+ export declare function useMozGridConfig(): MozGridConfig;
10
+ export declare function MozGridConfigProvider({ config, children }: MozGridConfigProviderProps): JSX.Element;
2
11
  /**
3
12
  * MozGridLocaleContext / MozGridLocaleProvider / useMozGridLocale
4
13
  *
@@ -19,5 +28,6 @@ export interface MozGridLocaleProviderProps {
19
28
  locale?: string;
20
29
  children?: ReactNode;
21
30
  }
31
+ /** @deprecated Use MozGridConfigProvider config={{ locale }}. */
22
32
  export declare function MozGridLocaleProvider({ locale, children }: MozGridLocaleProviderProps): JSX.Element;
23
33
  export declare function useMozGridLocale(): string | undefined;
@@ -0,0 +1,5 @@
1
+ import { Meta, StoryObj } from '@storybook/react';
2
+ import { MozGrid } from '..';
3
+ declare const meta: Meta<typeof MozGrid>;
4
+ export default meta;
5
+ export declare const TimeZone: StoryObj<typeof MozGrid>;
@@ -6,6 +6,23 @@
6
6
 
7
7
  > 📋 **사용 예제 코드는 별도 파일**: [examples.md](examples.md) — Sub-API별 사용, Reactive React 통합, 이벤트, 선택 모드, Excel I/O 등 약 30개 시나리오
8
8
 
9
+ ## 날짜 시간대 계약 (`timeZone`)
10
+
11
+ ```ts
12
+ const grid = new PureSheet(container, { fields, data, timeZone: 'Asia/Seoul' });
13
+ await grid.io.saveExcelToFile({ suggestedName: 'seoul.xlsx' });
14
+ await grid.io.fromExcel(legacyFile, { timeZone: 'UTC' });
15
+ ```
16
+
17
+ - 미지정: 기존 브라우저 로컬 날짜 표시와 UTC Excel serial IO를 유지합니다. `locale`로 시간대를 추론하지 않습니다.
18
+ - 명시: 내장 날짜 mask와 날짜 기본 표시, XLSX serial export/import/preview가 같은 IANA 시간대를 사용합니다. 브라우저/OS 시간대와 무관합니다. 사용자 함수 mask/커스텀 렌더러의 출력은 변경하지 않습니다.
19
+ - `Date`, 숫자 timestamp, `Z` 또는 offset이 있는 ISO 문자열은 **절대 시각**입니다. Offset 없는 ISO 날짜/시각(`2026-09-01 00:00:00`, `2026-09-01T00:00:00`), 날짜만(`2026-09-01`), compact 문자열(`20260901`, `20260901000000`)은 **설정 시간대의 벽시계 값**입니다. 호스트 로컬 `new Date(string)`로 먼저 해석하지 않습니다.
20
+ - Excel은 시간대 정보를 저장하지 않습니다. 같은 파일을 왕복하려면 같은 zone을 사용해야 합니다. `2026-08-31T15:00:00Z`는 Seoul에서 `2026-09-01 00:00:00`, serial `46266`입니다. 미지정/UTC export는 기존 `46265.625`입니다.
21
+ - 날짜만 표시하는 mask는 **시간 값을 자르지 않습니다**. XLSX 소수부에 시·분·초·밀리초가 유지됩니다. 임의의 날짜 문자열은 호스트 의존 파싱을 하지 않고 원문으로 남깁니다.
22
+ - DST 해석은 Temporal `compatible`: 중복 시각은 **더 이른 instant**, 존재하지 않는 시각은 **gap 길이만큼 앞으로** 이동합니다. 따라서 DST 중복 시각의 두 instant를 Excel serial만으로 구분할 수 없습니다. 필요하면 UTC IO override를 사용하세요.
23
+ - 잘못된 zone(빈 문자열 포함)은 `RangeError`입니다. UTC나 브라우저 zone으로 조용히 폴백하지 않습니다.
24
+ - 코어에서는 생성 설정입니다. React/Vue provider 및 `coreConfig.timeZone` 변경은 기존 상태보존 재생성 경로로 반영됩니다.
25
+
9
26
  ## 목차
10
27
 
11
28
  - [개요](#개요)
@@ -221,6 +238,7 @@ grid.on('row:click', ({ rowIndex }) => {
221
238
  | `undoStack` | `UndoStack` | — | 외부 UndoStack 주입 |
222
239
  | **국제화** | | | |
223
240
  | `locale` | `string` | 자동 감지 | 로케일 (예: `'ko-KR'`) |
241
+ | `timeZone` | `string` | 미지정 | IANA 시간대(예: `'Asia/Seoul'`). 날짜 표시와 XLSX import/export에 공통 적용. 미지정은 기존 로컬 표시·UTC IO 유지 |
224
242
  | `translations` | `PartialTranslationMap` | — | 번역 오버라이드 |
225
243
  | **포맷팅** | | | |
226
244
  | `formatRow` | `FormatRowCallback` | — | 행 포맷팅 콜백 (config에서 설정 시 `_config` 키로 자동 등록) |
@@ -858,7 +876,7 @@ PureSheet 인스턴스는 기능별 Sub-API 네임스페이스를 제공합니
858
876
 
859
877
  ### grid.cells — 셀 선택 API
860
878
 
861
- 셀 = 행(rowId/viewIndex) + 열(columnId)의 교차점. 쓰기(select)와 조회(selected)를 제공한다.
879
+ 셀 = 행(rowId/viewIndex) + 열(columnId)의 교차점. 쓰기(select*)와 조회(getSelected*)를 제공한다.
862
880
 
863
881
  #### 선택 (셀 — 쓰기)
864
882
 
@@ -874,7 +892,9 @@ PureSheet 인스턴스는 기능별 Sub-API 네임스페이스를 제공합니
874
892
 
875
893
  | 메서드 | 반환 | 설명 |
876
894
  |--------|------|------|
877
- | `selected()` | `{rowId, columnId}[]` | 선택된 셀 목록 |
895
+ | `getSelectedCellIds()` | `{rowId, columnId}[]` | 선택된 셀 목록 (셀 API 에는 `selected()` 가 없다 — 행 API `grid.rows.selected()` 와 다름) |
896
+ | `getSelectedCells()` | `SelectedCell[]` | 선택된 셀 상세 (행 핸들·값 포함) |
897
+ | `getActiveCell()` | `SelectedCell \| null` | 현재 활성 셀 |
878
898
  | `getSelectedCellRowIds()` | `string[]` | 선택된 셀이 걸친 행 rowId 목록(중복 제거) |
879
899
  | `getSelectedCellColumnIds()` | `string[]` | 선택된 셀이 걸친 컬럼 columnId 목록(중복 제거) |
880
900
  | `getSelectedCellIndices()` | `number[]` | 선택된 셀이 걸친 행 sourceIndex 목록(중복 제거) |
@@ -1274,8 +1294,13 @@ await grid.rowGroup.setConfig({
1274
1294
  저장본 0.4 → 0.5 마이그레이션은 옛 그룹 정렬 `column`/객체 `by`를 새 모델로 변환하고,
1275
1295
  피벗 `SortState.byValue`는 동일한 scalar 값 그대로 `by`로 개명합니다. 피벗 집계/정렬 의미는 변하지 않습니다.
1276
1296
 
1277
- 그룹 내부 데이터 행의 일반 정렬은 별도로 유지됩니다. null/undefined는 기존 정렬 규약대로
1278
- 오름차순에서는 뒤, 내림차순에서는 앞에 위치합니다. wrapper 정렬 패널의 **그룹 정렬 → 정렬 기준**에서도
1297
+ 그룹 내부 데이터 행의 일반 정렬은 별도로 유지됩니다. 일반·계산 필드·그룹·트리·피벗 정렬에서
1298
+ null/undefined는 **오름차순(ASC)에서는 앞, 내림차순(DESC)에서는 뒤**에 위치합니다.
1299
+ 두 값이 모두 null/undefined이면 동률이며, 다음 정렬 키를 비교하고 모든 키가 같으면 기존 순서를 유지합니다.
1300
+ 빈 문자열(`''`), 공백 문자열, `0`, `false`는 null/undefined로 취급하지 않습니다.
1301
+ `sortKey`가 있으면 원본 값이 아닌 변환된 키를 비교합니다.
1302
+ 이는 이전 ASC 뒤/DESC 앞 기본정책의 변경이며, 저장된 정렬 방향에도 새 기본정책이 적용됩니다.
1303
+ wrapper 정렬 패널의 **그룹 정렬 → 정렬 기준**에서도
1279
1304
  ‘그룹 이름’을 선택할 수 있습니다. 정렬 규칙이 없을 때와 새 규칙을 추가할 때 모두
1280
1305
  **그룹 이름 오름차순**이 기본입니다. 기존에 저장한 하위 행 개수·집계 정렬은 그대로 유지됩니다.
1281
1306
 
@@ -1626,6 +1651,8 @@ interface InvalidCell {
1626
1651
 
1627
1652
  #### Export 옵션 — `ExcelExportOptions`
1628
1653
 
1654
+ `timeZone?: string`은 해당 작업만 코어 시간대보다 우선합니다. `saveExcelToFile`과 Worker 경로에도 적용됩니다.
1655
+
1629
1656
  | 필드 | 타입 | 기본값 | 설명 |
1630
1657
  |------|------|--------|------|
1631
1658
  | `scope` | `'all' \| 'visible' \| 'selected'` | `'all'` | 데이터 범위 |
@@ -1699,6 +1726,8 @@ interface InvalidCell {
1699
1726
 
1700
1727
  #### Import 옵션 — `ExcelImportOptions`
1701
1728
 
1729
+ `timeZone?: string`은 해당 파일의 날짜 serial 해석 시간대를 재정의합니다. 예전 UTC 파일은 `{ timeZone: 'UTC' }`로 가져오세요. `previewExcel(file, { timeZone })`도 같은 옵션을 지원합니다.
1730
+
1702
1731
  ##### 기본 옵션
1703
1732
 
1704
1733
  | 필드 | 타입 | 기본값 | 설명 |
@@ -2244,12 +2273,37 @@ grid.off('cell:click', handler);
2244
2273
  |--------|---------|------|
2245
2274
  | `cell:click` | `{ row, columnId, value, event }` | 셀 클릭 |
2246
2275
  | `cell:dblclick` | `{ row, columnId, value, event }` | 셀 더블클릭 |
2247
- | `cell:change` | `{ rowIndex, rowId, columnId, oldValue, newValue }` | 셀 값 변경. `rowId`는 정렬/필터 무관 안정 식별자(정규화된 문자열) — cascade 등 rowId 기반 로직의 트리거로 사용 |
2276
+ | `cell:change` | `{ rowIndex, rowId, columnId, oldValue, newValue }` | 에디터 편집에 따른 셀 값 변경. API·붙여넣기·undo/redo를 모두 관찰하는 이벤트가 아님. `rowId`는 정렬/필터 무관 안정 식별자 |
2277
+ | `data:changed` | `DataMutationEvent` | ChangeTracker에 반영된 셀 데이터 변경. 에디터 생명주기와 독립적인 추가 이벤트 (아래 계약 참조) |
2278
+ | `changes:changed` | `{ hasChanges, addedCount, deletedCount }` | 기존 dirty 상태 요약. 신규 행 셀 수정도 발행하며 상세 값은 `data:changed`로 구독 |
2248
2279
  | `cell:beforeEdit` | `{ ..., cancel() }` | 편집 시작 전 (취소 가능) |
2249
2280
  | `cell:editCommitting` | `{ ..., cancel() }` | 편집 커밋 전 (취소 가능) |
2250
2281
  | `cell:editCommitted` | `{ rowIndex, rowId, dataIndex, columnId, oldValue, newValue }` | 편집 커밋 완료. `rowId` 포함 (위와 동일) — boolean 셀 즉시 토글도 이 이벤트로 발화되어 체크박스 cascade 트리거로 쓰인다 |
2251
2282
  | `cell:tooltip` | `CellTooltipPayload` (아래 참조) | 셀·컬럼 헤더 호버 시 툴팁 표시 요청 (`tooltipMode` 로 발행 제어) |
2252
2283
 
2284
+ **`DataMutationEvent` — 셀 데이터 변경 계약**
2285
+
2286
+ ```ts
2287
+ interface DataMutationEvent {
2288
+ source: 'api' | 'edit' | 'paste' | 'undo' | 'redo' | 'import';
2289
+ changes: Array<{
2290
+ rowId: string | number;
2291
+ field: string;
2292
+ oldValue: CellValue;
2293
+ newValue: CellValue;
2294
+ sourceIndex?: number;
2295
+ }>;
2296
+ }
2297
+ ```
2298
+
2299
+ - `oldValue`는 최초 원본이 아니라 **바로 이전 유효 값**, `newValue`는 이번 작업이 반영한 값입니다. Undo는 이전 값으로 되돌아가는 방향으로 전달합니다.
2300
+ - `field`는 데이터 바인딩 필드입니다. 표시 컬럼 ID·행 view index와 혼동하지 마세요. `sourceIndex`는 호출자가 아는 경우만 포함합니다.
2301
+ - 일반 셀 명령은 변경 1개를 발행합니다. Undo batch는 하나의 undo 단위이지만 셀 이벤트를 하나로 합치지는 않습니다. `beginBulkImport`/`endBulkImport` 구간은 실제 변경 순서를 유지한 `source: 'import'` 변경 묶음을 종료 시 한 번 발행합니다.
2302
+ - 현재 값과 `Object.is`로 같은 쓰기는 이벤트·dirty 버전·undo 기록을 만들지 않습니다. 삭제 예정 행에 대한 쓰기도 데이터 이벤트를 만들지 않습니다.
2303
+ - 이 이벤트는 **셀 값 쓰기** 계약이며 행 추가/삭제/재정렬, commit/discard, 데이터 전체 교체를 설명하는 이벤트가 아닙니다. 해당 상태는 기존 행 이벤트·`changes:changed`·undo/redo 이벤트로 구독합니다.
2304
+ - 신규 행의 임시 식별자는 key 필드를 수정해도 유지됩니다. 기존 행 검증은 설정된 단일/복합 key resolver를 사용하며, 다른 행 데이터·잘못된 originalData·partial seed는 여전히 거부합니다.
2305
+ - 알림은 ChangeTracker 반영 시점입니다. **processor 동기화보다 먼저 동기적으로 발행될 수 있으므로**, 핸들러에서는 payload의 `oldValue`/`newValue`를 사용하세요. API 호출의 완료 후 상태가 필요하면 해당 mutation API의 Promise를 기다리세요. 엔진 재조회·비동기 렌더 완료 신호가 아니며 취소할 수 없습니다. `cell:beforeEdit`/`cell:editCommitting`/`cell:editCommitted`의 의미는 바뀌지 않습니다.
2306
+
2253
2307
  **`CellTooltipPayload`** (26cd68a 커밋에서 validation 정보 통합)
2254
2308
 
2255
2309
  | 필드 | 타입 | 설명 |
@@ -4011,7 +4065,7 @@ const grid = new PureSheet(el, {
4011
4065
 
4012
4066
  | 요소 | 표기 |
4013
4067
  |---|---|
4014
- | 컬럼 참조 | `[컬럼헤더]`(표시) / `$id`(저장) — 공백·한글 헤더 지원 |
4068
+ | 컬럼 참조 | `[컬럼헤더]`(표시) / `$id` 또는 `$["변동 상태"]`(저장) — 공백·특수문자 ID 지원 |
4015
4069
  | 논리 | `AND` / `OR` / `NOT` 또는 `&&` / `\|\|` / `!` |
4016
4070
  | 비교 | `==` `!=` `>` `>=` `<` `<=` |
4017
4071
  | 문자열 | `CONTAINS` `NOT_CONTAINS` `STARTS_WITH` `ENDS_WITH` `LIKE` `IS_EMPTY` `IS_NOT_EMPTY` |
@@ -4043,6 +4097,16 @@ const grid = new PureSheet(el, {
4043
4097
 
4044
4098
  > 라벨이 바뀌어도(다국어·헤더 편집) **같은 저장본이 늘 현재 라벨로 보입니다.** 필터 평가는 저장 표기(`$id`)를 그대로 쓰므로 라벨 변경이 조건을 깨뜨리지 않습니다.
4045
4099
 
4100
+ **공백·특수문자 ID (ITSM-2026-002900)**
4101
+
4102
+ - 기존 영문·숫자·한글·밑줄 ID는 `$simple_id`를 유지합니다. 그 외 ID는 `$[JSON 문자열]`로 저장합니다. ID 자체를 trim/치환/정규화하지 않습니다.
4103
+ - 예: ID가 `변동 상태`면 `$["변동 상태"] CONTAINS "증가"`, ID가 `a"b]c`면 `$["a\"b]c"] IS_NOT_EMPTY`입니다. JSON 큰따옴표와 이스케이프 규칙을 쓰며 빈 ID는 허용하지 않습니다.
4104
+ - Lexer/검증/평가와 `astToFormula`는 같은 바인딩 AST를 사용합니다. 문자열 리터럴 안의 `$`나 `[]`는 변환하지 않습니다. `$a-$b`는 계속 두 컬럼의 뺄셈입니다.
4105
+ - 현재 라벨이 중복이거나 `]`/앞뒤 공백/빈 문자열 때문에 라벨 표기로 왕복할 수 없으면 화면에도 명시적 ID 표기를 유지합니다.
4106
+ - `FormulaRefIssue.reason`은 `unknown`, `ambiguous`, `syntax`입니다. 토큰화 실패는 원문과 `syntax` 이슈를 반환합니다. 변환 성공 여부만으로 수식 유효성을 판단하지 말고 **변환 후 `validateFormula`**도 확인하세요.
4107
+ - 기존의 잘못된 저장본 `$변동 상태 …`는 자동으로 추측 복구하지 않습니다. 원문을 보존하고 편집기에서 오류를 표시합니다. 사용자가 컬럼을 다시 선택하거나 `$["변동 상태"]`로 명시적으로 고쳐야 합니다. 조건부 서식에서 오류가 있으면 규칙 전체 적용을 막고 이전 적용본을 유지합니다.
4108
+ - 새 표기를 읽지 못하는 구버전 Core로 되돌리면 해당 수식은 동작하지 않습니다. Core와 공용 wrapper를 함께 배포하고, 소비처 업그레이드 전에는 새 저장본을 공유하지 마세요.
4109
+
4046
4110
  ---
4047
4111
 
4048
4112
  ### 값 목록 위젯 — ValueCheckList
@@ -6,6 +6,25 @@
6
6
  > 📋 이 문서는 **React 래퍼 표면만** 다룹니다. 그리드 본체(코어) 설정·타입·이벤트 페이로드는 [reference.md](reference.md)를 참조하세요.
7
7
  > 📋 사용 예제 코드는 [examples.md](examples.md)를 참조하세요.
8
8
 
9
+ ## 공통 설정 Provider
10
+
11
+ ```tsx
12
+ import { MozGrid, MozGridConfigProvider, useMozGridConfig, type MozGridConfig } from '@vmscloud/moz-ui-grid-react';
13
+
14
+ <MozGridConfigProvider config={{ locale: 'ko', timeZone: 'Asia/Seoul' }}>
15
+ <MozGrid coreConfig={{ fields, data }} />
16
+ <MozGrid coreConfig={{ fields, data, timeZone: 'UTC' }} locale="en" />
17
+ </MozGridConfigProvider>
18
+ ```
19
+
20
+ `MozGridConfig`는 `{ locale?: string; timeZone?: string }`입니다. `useMozGridConfig()`는 가장 가까운 provider들의 병합 설정을 반환합니다. `MozGridConfigContext`와 `MozGridConfigProviderProps`도 공개 export입니다.
21
+
22
+ 시간대는 **개별 `coreConfig.timeZone` > provider > 미지정 기본값**입니다. 언어는 **개별 `locale` prop > `coreConfig.locale` > provider > 기존 기본값**입니다. 중첩 provider는 지정한 필드만 덮어쓰고 undefined 필드는 상위에서 상속합니다. Provider 값 변경은 하위 그리드에 반영되며 명시된 개별 설정은 유지됩니다.
23
+
24
+ `MozGridLocaleProvider`는 deprecated 호환 API입니다. 기존 `locale` prop과 `useMozGridLocale`/`MozGridLocaleContext` export는 유지되며 새 provider와 혼용할 수 있습니다. 신규 코드는 `MozGridConfigProvider config={{ locale, timeZone }}`를 사용하세요.
25
+
26
+ 미지정은 기존 로컬 표시/UTC IO이며, 명시는 같은 zone으로 날짜 표시와 XLSX IO를 맞춥니다. Offsetless 문자열·DST·IO override 계약은 [코어 날짜 시간대 계약](reference.md#날짜-시간대-계약-timezone)을 참고하세요.
27
+
9
28
  ## 목차
10
29
 
11
30
  - [개요](#개요)
@@ -275,6 +294,15 @@ function StatusCell({ value, rowData, columnId, rowId }: CellSlotProps) {
275
294
 
276
295
  ## 이벤트 콜백 props
277
296
 
297
+ ### 준비 완료·재생성·서식 콜백
298
+
299
+ - `onReady(grid, chrome)`는 해당 인스턴스의 코어 초기화와 reinit 상태 복원 뒤에 호출됩니다. React StrictMode 정리, unmount, 다음 reinit으로 폐기된 인스턴스의 대기 작업은 복원·콜백을 이어서 실행하지 않습니다. 현재 인스턴스의 초기화 실패는 콘솔 오류로 보고하고 `onReady`를 호출하지 않습니다.
300
+ - `ready()`는 코어 `waitForReady()`의 **초기화 완료**를 기다립니다. `coreConfig.data`로 시작된 모든 비동기 데이터 로딩 완료를 뜻하지 않습니다. 데이터 완료는 `onDataLoaded` 또는 직접 호출한 `await grid.loadData(rows)`로 확인하세요.
301
+ - `coreConfig.formatRow` / `formatHeader`는 첫 렌더 전에 등록됩니다. 함수 참조 교체·추가·제거는 재생성 없이 `_config` 핸들러만 갱신하고 `grid.refresh()`를 요청합니다. 다른 네임드 핸들러는 유지됩니다. React에서는 `useCallback`으로 불필요한 참조 변경·refresh를 줄이세요.
302
+ - `onReady` 안에서 `grid.formatRow.addHandler('my-format', handler)`로 **직접** 등록한 핸들러는 다음 렌더부터 적용됩니다. 이미 그려진 행에도 바로 적용하려면 등록 뒤 `await grid.refresh()`를 실행하세요. 등록 자체는 자동 repaint를 하지 않습니다. `formatHeader`도 동일하며 스크롤만으로 헤더가 다시 그려지지는 않습니다.
303
+ - 핸들러는 인스턴스별입니다. 직접 등록한 핸들러는 각 `onReady`에서 다시 등록하세요. 제거는 이후 호출만 멈추며, 소비자가 DOM에 남긴 임의 텍스트·스타일을 역으로 복원하지 않습니다. 상태에 따라 스타일을 적용/해제하는 대칭적인 콜백을 사용하세요.
304
+
305
+
278
306
  모든 상호작용 콜백은 `on*` props입니다. `onReady`/`onDataLoaded`를 제외한 **32개**는 코어 PureSheet 이벤트를 `EVENT_CALLBACKS` 표를 통해 `chrome.on(event, ...)`으로 일반 포워딩합니다(총 **34개 이벤트 콜백** props). `onDataLoaded` 는 `chrome.on('data:loaded', …)` 로 별도 배선되고, `onReady` 는 어댑터가 직접 호출합니다.
279
307
 
280
308
  > **페이로드 형태는 모두 코어 이벤트 페이로드입니다 — [reference.md 이벤트](reference.md#이벤트) 참조.** props 시그니처상 `(payload: unknown) => void`로 노출되지만 실제 페이로드는 해당 코어 이벤트의 형태를 따릅니다.
@@ -290,7 +318,8 @@ function StatusCell({ value, rowData, columnId, rowId }: CellSlotProps) {
290
318
  | `onRowReordered` | `(payload: unknown) => void` | `row:reordered` | 행 순서 변경 |
291
319
  | `onCellClick` | `(payload: unknown) => void` | `cell:click` | 셀 클릭 |
292
320
  | `onCellDblclick` | `(payload: unknown) => void` | `cell:dblclick` | 셀 더블클릭 |
293
- | `onCellChange` | `(payload: unknown) => void` | `cell:change` | 셀 값 변경 |
321
+ | `onCellChange` | `(payload: unknown) => void` | `cell:change` | 에디터 셀 값 변경 |
322
+ | `onDataChanged` | `(payload: DataMutationEvent) => void` | `data:changed` | API·편집·붙여넣기·undo/redo의 실제 셀 변경. ChangeTracker 반영 시점이며 processor 동기화/렌더 완료 신호가 아님 |
294
323
  | `onCellBeforeEdit` | `(payload: unknown) => void` | `cell:beforeEdit` | 셀 편집 시작 전 |
295
324
  | `onCellEditCommitting` | `(payload: unknown) => void` | `cell:editCommitting` | 셀 편집 커밋 중 |
296
325
  | `onCellEditCommitted` | `(payload: unknown) => void` | `cell:editCommitted` | 셀 편집 커밋 완료 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vmscloud/moz-ui-grid-react",
3
- "version": "0.1.23",
3
+ "version": "0.1.26",
4
4
  "description": "MozGrid - React 18+ wrapping library for @vmscloud/moz-ui-grid-core",
5
5
  "license": "ISC",
6
6
  "type": "module",
@@ -36,12 +36,11 @@
36
36
  "directory": "components/frontend/moz-ui-grid/moz-ui-grid-react"
37
37
  },
38
38
  "scripts": {
39
- "preinstall": "node ../../../scripts/enforce-pm.cjs",
40
- "build": "tsc --noEmit && vite build --config vite.config.lib.ts",
39
+ "build": "tsc --noEmit && vite build --config vite.config.lib.ts && node scripts/copy-docs.mjs",
41
40
  "build:lib": "vite build --config vite.config.lib.ts && node scripts/copy-docs.mjs",
42
41
  "docs:bundle": "node scripts/copy-docs.mjs",
43
42
  "prepack": "node scripts/copy-docs.mjs",
44
- "prepublishOnly": "bun run build:lib",
43
+ "prepublishOnly": "bun run build",
45
44
  "dev": "vite --config vite.config.lib.ts",
46
45
  "test": "vitest run",
47
46
  "test:watch": "vitest",
@@ -58,8 +57,8 @@
58
57
  "@radix-ui/react-popover": "^1.1.6",
59
58
  "@radix-ui/react-switch": "^1.1.3",
60
59
  "@tanstack/react-virtual": "^3.13.0",
61
- "@vmscloud/moz-ui-grid-core": "^0.1.45",
62
- "@vmscloud/moz-ui-grid-wrapper": "^0.1.16",
60
+ "@vmscloud/moz-ui-grid-core": "^0.1.47",
61
+ "@vmscloud/moz-ui-grid-wrapper": "^0.1.21",
63
62
  "clsx": "^2.1.0",
64
63
  "echarts": "^5.5.0",
65
64
  "echarts-for-react": "^3.0.2",