@toolbox-web/grid 3.9.2 → 3.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/all.js +1 -1
  2. package/all.js.map +1 -1
  3. package/index.js +1 -1
  4. package/index.js.map +1 -1
  5. package/lib/core/types.d.ts +6 -3
  6. package/lib/features/clipboard.js.map +1 -1
  7. package/lib/features/column-virtualization.js.map +1 -1
  8. package/lib/features/context-menu.js.map +1 -1
  9. package/lib/features/editing.js.map +1 -1
  10. package/lib/features/export.js.map +1 -1
  11. package/lib/features/filtering.js.map +1 -1
  12. package/lib/features/grouping-columns.js.map +1 -1
  13. package/lib/features/grouping-rows.js.map +1 -1
  14. package/lib/features/master-detail.js.map +1 -1
  15. package/lib/features/multi-sort.js.map +1 -1
  16. package/lib/features/pinned-columns.js.map +1 -1
  17. package/lib/features/pinned-rows.js.map +1 -1
  18. package/lib/features/pivot.js.map +1 -1
  19. package/lib/features/print.js.map +1 -1
  20. package/lib/features/registry.js +1 -1
  21. package/lib/features/registry.js.map +1 -1
  22. package/lib/features/reorder-columns.js.map +1 -1
  23. package/lib/features/responsive.js.map +1 -1
  24. package/lib/features/row-drag-drop.js.map +1 -1
  25. package/lib/features/selection.js.map +1 -1
  26. package/lib/features/server-side.js.map +1 -1
  27. package/lib/features/shell.js.map +1 -1
  28. package/lib/features/sticky-rows.js.map +1 -1
  29. package/lib/features/tooltip.js.map +1 -1
  30. package/lib/features/tree.js.map +1 -1
  31. package/lib/features/undo-redo.js.map +1 -1
  32. package/lib/features/visibility.js.map +1 -1
  33. package/lib/plugins/clipboard/index.js.map +1 -1
  34. package/lib/plugins/column-virtualization/index.js.map +1 -1
  35. package/lib/plugins/context-menu/index.js.map +1 -1
  36. package/lib/plugins/editing/editors.d.ts +15 -0
  37. package/lib/plugins/editing/index.js +1 -1
  38. package/lib/plugins/editing/index.js.map +1 -1
  39. package/lib/plugins/editing/internal/helpers.d.ts +23 -0
  40. package/lib/plugins/editing/types.d.ts +16 -1
  41. package/lib/plugins/export/index.js +1 -1
  42. package/lib/plugins/export/index.js.map +1 -1
  43. package/lib/plugins/filtering/filter-model.d.ts +20 -1
  44. package/lib/plugins/filtering/filter-panel-default.d.ts +15 -1
  45. package/lib/plugins/filtering/index.d.ts +1 -1
  46. package/lib/plugins/filtering/index.js +1 -1
  47. package/lib/plugins/filtering/index.js.map +1 -1
  48. package/lib/plugins/filtering/types.d.ts +58 -3
  49. package/lib/plugins/grouping-columns/index.js.map +1 -1
  50. package/lib/plugins/grouping-rows/index.js +1 -1
  51. package/lib/plugins/grouping-rows/index.js.map +1 -1
  52. package/lib/plugins/hierarchy/index.js.map +1 -1
  53. package/lib/plugins/master-detail/index.js +1 -1
  54. package/lib/plugins/master-detail/index.js.map +1 -1
  55. package/lib/plugins/master-detail/master-detail-plugin.d.ts +6 -1
  56. package/lib/plugins/multi-sort/index.js.map +1 -1
  57. package/lib/plugins/pinned-columns/index.js.map +1 -1
  58. package/lib/plugins/pinned-rows/index.js.map +1 -1
  59. package/lib/plugins/pivot/index.js +1 -1
  60. package/lib/plugins/pivot/index.js.map +1 -1
  61. package/lib/plugins/pivot/pivot-plugin.d.ts +2 -0
  62. package/lib/plugins/pivot/types.d.ts +2 -0
  63. package/lib/plugins/print/index.js.map +1 -1
  64. package/lib/plugins/reorder-columns/index.js.map +1 -1
  65. package/lib/plugins/responsive/index.js.map +1 -1
  66. package/lib/plugins/row-drag-drop/index.js.map +1 -1
  67. package/lib/plugins/selection/index.js +1 -1
  68. package/lib/plugins/selection/index.js.map +1 -1
  69. package/lib/plugins/server-side/index.js.map +1 -1
  70. package/lib/plugins/shared/form-field-target.d.ts +10 -0
  71. package/lib/plugins/shared/format-value.d.ts +12 -0
  72. package/lib/plugins/shared/structural-toggle.d.ts +14 -0
  73. package/lib/plugins/shell/index.js.map +1 -1
  74. package/lib/plugins/sticky-rows/index.js.map +1 -1
  75. package/lib/plugins/tooltip/index.js.map +1 -1
  76. package/lib/plugins/tree/index.js +1 -1
  77. package/lib/plugins/tree/index.js.map +1 -1
  78. package/lib/plugins/undo-redo/index.d.ts +1 -1
  79. package/lib/plugins/undo-redo/index.js +1 -1
  80. package/lib/plugins/undo-redo/index.js.map +1 -1
  81. package/lib/plugins/undo-redo/types.d.ts +20 -2
  82. package/lib/plugins/undo-redo/undo-redo-plugin.d.ts +8 -0
  83. package/lib/plugins/visibility/index.js.map +1 -1
  84. package/package.json +1 -1
  85. package/umd/grid.all.umd.js +1 -1
  86. package/umd/grid.all.umd.js.map +1 -1
  87. package/umd/grid.umd.js +1 -1
  88. package/umd/grid.umd.js.map +1 -1
  89. package/umd/plugins/clipboard.umd.js.map +1 -1
  90. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  91. package/umd/plugins/context-menu.umd.js.map +1 -1
  92. package/umd/plugins/editing.umd.js +1 -1
  93. package/umd/plugins/editing.umd.js.map +1 -1
  94. package/umd/plugins/export.umd.js +1 -1
  95. package/umd/plugins/export.umd.js.map +1 -1
  96. package/umd/plugins/filtering.umd.js +1 -1
  97. package/umd/plugins/filtering.umd.js.map +1 -1
  98. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  99. package/umd/plugins/grouping-rows.umd.js +1 -1
  100. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  101. package/umd/plugins/hierarchy.umd.js.map +1 -1
  102. package/umd/plugins/master-detail.umd.js +1 -1
  103. package/umd/plugins/master-detail.umd.js.map +1 -1
  104. package/umd/plugins/multi-sort.umd.js.map +1 -1
  105. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  106. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  107. package/umd/plugins/pivot.umd.js +1 -1
  108. package/umd/plugins/pivot.umd.js.map +1 -1
  109. package/umd/plugins/print.umd.js.map +1 -1
  110. package/umd/plugins/reorder-columns.umd.js.map +1 -1
  111. package/umd/plugins/responsive.umd.js.map +1 -1
  112. package/umd/plugins/row-drag-drop.umd.js.map +1 -1
  113. package/umd/plugins/selection.umd.js +1 -1
  114. package/umd/plugins/selection.umd.js.map +1 -1
  115. package/umd/plugins/server-side.umd.js.map +1 -1
  116. package/umd/plugins/shell.umd.js.map +1 -1
  117. package/umd/plugins/sticky-rows.umd.js.map +1 -1
  118. package/umd/plugins/tooltip.umd.js.map +1 -1
  119. package/umd/plugins/tree.umd.js +1 -1
  120. package/umd/plugins/tree.umd.js.map +1 -1
  121. package/umd/plugins/undo-redo.umd.js +1 -1
  122. package/umd/plugins/undo-redo.umd.js.map +1 -1
  123. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1,2 +1,2 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@toolbox-web/grid")):"function"==typeof define&&define.amd?define(["exports","@toolbox-web/grid"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TbwGridPlugin_undoRedo={},t.TbwGrid)}(this,function(t,e){Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});var o="editing";function n(t,e,o){const n=[...t.undoStack,e];for(;n.length>o;)n.shift();return{undoStack:n,redoStack:[]}}function i(t){if(0===t.undoStack.length)return{newState:t,action:null};const e=[...t.undoStack],o=e.pop();return o?{newState:{undoStack:e,redoStack:[...t.redoStack,o]},action:o}:{newState:t,action:null}}function r(t){if(0===t.redoStack.length)return{newState:t,action:null};const e=[...t.redoStack],o=e.pop();return o?{newState:{undoStack:[...t.undoStack,o],redoStack:e},action:o}:{newState:t,action:null}}var a=class extends e.BaseGridPlugin{static dependencies=[{name:"editing",required:!0,reason:"UndoRedoPlugin tracks cell edit history"}];name="undoRedo";get defaultConfig(){return{maxHistorySize:100}}undoStack=[];redoStack=[];#t=!1;#e=null;#o=0;#n=null;#i(t){const e=this.rows[t];if(e)try{return this.grid.getRowId(e)||void 0}catch{return}}#r(t,o){if(void 0!==t.rowId)return void this.grid.updateRow(t.rowId,{[t.field]:o},"history");const n=this.rows[t.rowIndex];n&&(0,e.writeCellField)(n,t.field,o)}#a(t){const e=this.grid,n=e._visibleColumns?.findIndex(e=>e.field===t.field)??-1;if(n<0)return;const i=void 0!==t.rowId?e._getRowEntry(t.rowId)?.index??-1:t.rowIndex;if(i<0)return;e._focusRow=i,e._focusCol=n;const r=e.findRenderedRowElement?.(i);if(!r)return;const a=r.querySelector(`.cell[data-col="${n}"]`);a?.classList.contains(o)&&a.querySelector('input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])')?.focus({preventScroll:!0})}#c(t,e){if(this.#t=!0,"compound"===t.type){const o="undo"===e?[...t.actions].reverse():t.actions;for(const t of o)this.#r(t,"undo"===e?t.oldValue:t.newValue)}else this.#r(t,"undo"===e?t.oldValue:t.newValue);this.#t=!1}#s(t){const e="compound"===t.type?t.actions[t.actions.length-1]:t;this.#n=e??null}afterRender(){const t=this.#n;t&&(this.#n=null,this.#a(t))}attach(t){super.attach(t),this.on("cell-edit-committed",t=>{this.#t||this.recordEdit(t.rowIndex,t.field,t.oldValue,t.newValue,t.rowId)})}detach(){this.undoStack=[],this.redoStack=[],this.#e=null,this.#o=0,this.#n=null}onKeyDown(t){const e=(t.ctrlKey||t.metaKey)&&"z"===t.key&&!t.shiftKey,o=(t.ctrlKey||t.metaKey)&&("y"===t.key||"z"===t.key&&t.shiftKey);if(!e&&!o)return!1;const n=e?"undo":"redo";return this.#d(t.target)?(this.#u(t.target,n),!0):(t.preventDefault(),this.#l(n),!0)}#l(t){const e={undoStack:this.undoStack,redoStack:this.redoStack},o="undo"===t?i(e):r(e);o.action&&(this.#c(o.action,t),this.undoStack=o.newState.undoStack,this.redoStack=o.newState.redoStack,this.emit(t,{action:o.action,type:t}),this.#s(o.action),this.requestRenderWithFocus())}#d(t){return t instanceof Element&&!!t.closest('input, textarea, [contenteditable=""], [contenteditable="true"]')}#u(t,e){const o="undo"===e?"historyUndo":"historyRedo";let n=!1;const i=t=>{t.inputType===o&&(n=!0)};t.addEventListener("beforeinput",i,{capture:!0}),queueMicrotask(()=>{t.removeEventListener("beforeinput",i,{capture:!0}),n||this.#l(e)})}recordEdit(t,e,o,i,r){const a=function(t,e,o,n,i){const r={type:"cell-edit",rowIndex:t,field:e,oldValue:o,newValue:n,timestamp:Date.now()};return void 0!==i&&(r.rowId=i),r}(t,e,o,i,r||this.#i(t));if(this.#e)return void this.#e.push(a);const c=n({undoStack:this.undoStack,redoStack:this.redoStack},a,this.config.maxHistorySize??100);this.undoStack=c.undoStack,this.redoStack=c.redoStack}beginTransaction(){0===this.#o&&(this.#e=[]),this.#o++}endTransaction(){if(0===this.#o&&function(t,e,o,n){throw new Error(function(t,e,o,n){return`${function(t,e){return`[tbw-grid${t?`#${t}`:""}${e?`:${e}`:""}]`}(o,n)} ${t}: ${e}\n\n → More info: ${function(t){return`https://toolboxjs.com/grid/errors#${t.toLowerCase()}`}(t)}`}(t,e,o,n))}("TBW112","No transaction in progress. Call beginTransaction() first."),this.#o--,this.#o>0)return;const t=this.#e;if(this.#e=null,!t||0===t.length)return;const e=1===t.length?t[0]:{type:"compound",actions:t,timestamp:Date.now()};const o=n({undoStack:this.undoStack,redoStack:this.redoStack},e,this.config.maxHistorySize??100);this.undoStack=o.undoStack,this.redoStack=o.redoStack}undo(){const t=i({undoStack:this.undoStack,redoStack:this.redoStack});return t.action&&(this.#c(t.action,"undo"),this.undoStack=t.newState.undoStack,this.redoStack=t.newState.redoStack,this.#s(t.action),this.requestRenderWithFocus()),t.action}redo(){const t=r({undoStack:this.undoStack,redoStack:this.redoStack});return t.action&&(this.#c(t.action,"redo"),this.undoStack=t.newState.undoStack,this.redoStack=t.newState.redoStack,this.#s(t.action),this.requestRenderWithFocus()),t.action}canUndo(){return{undoStack:this.undoStack,redoStack:this.redoStack}.undoStack.length>0}canRedo(){return{undoStack:this.undoStack,redoStack:this.redoStack}.redoStack.length>0}clearHistory(){const t={undoStack:[],redoStack:[]};this.undoStack=t.undoStack,this.redoStack=t.redoStack,this.#e=null,this.#o=0}getUndoStack(){return[...this.undoStack]}getRedoStack(){return[...this.redoStack]}};t.UndoRedoPlugin=a});
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@toolbox-web/grid")):"function"==typeof define&&define.amd?define(["exports","@toolbox-web/grid"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TbwGridPlugin_undoRedo={},t.TbwGrid)}(this,function(t,e){Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});var n="editing";function o(t,e,n){const o=[...t.undoStack,e];for(;o.length>n;)o.shift();return{undoStack:o,redoStack:[]}}var i=class extends e.BaseGridPlugin{static dependencies=[{name:"editing",required:!0,reason:"UndoRedoPlugin tracks cell edit history"}];name="undoRedo";get defaultConfig(){return{maxHistorySize:100}}undoStack=[];redoStack=[];#t=!1;#e=null;#n=0;#o=null;#i(t){const e=this.rows[t];if(e)try{return this.grid.getRowId(e)||void 0}catch{return}}#r(t,n){if(void 0!==t.rowId)return void this.grid.updateRow(t.rowId,{[t.field]:n},"history");const o=this.rows[t.rowIndex];o&&(0,e.writeCellField)(o,t.field,n)}#s(t){const e=this.grid,o=e._visibleColumns?.findIndex(e=>e.field===t.field)??-1;if(o<0)return;const i=void 0!==t.rowId?e._getRowEntry(t.rowId)?.index??-1:t.rowIndex;if(i<0)return;e._focusRow=i,e._focusCol=o;const r=e.findRenderedRowElement?.(i);if(!r)return;const s=r.querySelector(`.cell[data-col="${o}"]`);s?.classList.contains(n)&&s.querySelector('input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])')?.focus({preventScroll:!0})}#a(t,e){if(this.#t=!0,"compound"===t.type){const n="undo"===e?[...t.actions].reverse():t.actions;for(const t of n)this.#r(t,"undo"===e?t.oldValue:t.newValue)}else this.#r(t,"undo"===e?t.oldValue:t.newValue);this.#t=!1}#c(t){const e="compound"===t.type?t.actions[t.actions.length-1]:t;this.#o=e??null}afterRender(){const t=this.#o;t&&(this.#o=null,this.#s(t))}attach(t){super.attach(t),this.on("cell-edit-committed",t=>{this.#t||this.recordEdit(t.rowIndex,t.field,t.oldValue,t.newValue,t.rowId)})}detach(){this.undoStack=[],this.redoStack=[],this.#e=null,this.#n=0,this.#o=null}onKeyDown(t){const e=(t.ctrlKey||t.metaKey)&&"z"===t.key&&!t.shiftKey,n=(t.ctrlKey||t.metaKey)&&("y"===t.key||"z"===t.key&&t.shiftKey);if(!e&&!n)return!1;const o=e?"undo":"redo";return this.#d(t.target)?(this.#u(t.target,o),!0):(t.preventDefault(),this.#l(o),!0)}#l(t){const e={undoStack:this.undoStack,redoStack:this.redoStack},n="undo"===t?function(t){if(0===t.undoStack.length)return{newState:t,action:null};const e=[...t.undoStack],n=e.pop();return n?{newState:{undoStack:e,redoStack:[...t.redoStack,n]},action:n}:{newState:t,action:null}}(e):function(t){if(0===t.redoStack.length)return{newState:t,action:null};const e=[...t.redoStack],n=e.pop();return n?{newState:{undoStack:[...t.undoStack,n],redoStack:e},action:n}:{newState:t,action:null}}(e);return n.action?(this.#a(n.action,t),this.undoStack=n.newState.undoStack,this.redoStack=n.newState.redoStack,this.emit(t,{action:n.action,type:t}),this.#h(),this.#c(n.action),this.requestRenderWithFocus(),n.action):null}#h(){this.emit("history-change",{canUndo:this.canUndo(),canRedo:this.canRedo()})}#d(t){return t instanceof Element&&!!t.closest('input, textarea, [contenteditable=""], [contenteditable="true"]')}#u(t,e){const n="undo"===e?"historyUndo":"historyRedo";let o=!1;const i=t=>{t.inputType===n&&(o=!0)};t.addEventListener("beforeinput",i,{capture:!0}),queueMicrotask(()=>{t.removeEventListener("beforeinput",i,{capture:!0}),o||this.#l(e)})}recordEdit(t,e,n,i,r){const s=function(t,e,n,o,i){const r={type:"cell-edit",rowIndex:t,field:e,oldValue:n,newValue:o,timestamp:Date.now()};return void 0!==i&&(r.rowId=i),r}(t,e,n,i,r||this.#i(t));if(this.#e)return void this.#e.push(s);const a=o({undoStack:this.undoStack,redoStack:this.redoStack},s,this.config.maxHistorySize??100);this.undoStack=a.undoStack,this.redoStack=a.redoStack,this.#h()}beginTransaction(){0===this.#n&&(this.#e=[]),this.#n++}endTransaction(){if(0===this.#n&&function(t,e,n,o){throw new Error(function(t,e,n,o){return`${function(t,e){return`[tbw-grid${t?`#${t}`:""}${e?`:${e}`:""}]`}(n,o)} ${t}: ${e}\n\n → More info: ${function(t){return`https://toolboxjs.com/grid/errors#${t.toLowerCase()}`}(t)}`}(t,e,n,o))}("TBW112","No transaction in progress. Call beginTransaction() first."),this.#n--,this.#n>0)return;const t=this.#e;if(this.#e=null,!t||0===t.length)return;const e=1===t.length?t[0]:{type:"compound",actions:t,timestamp:Date.now()};const n=o({undoStack:this.undoStack,redoStack:this.redoStack},e,this.config.maxHistorySize??100);this.undoStack=n.undoStack,this.redoStack=n.redoStack,this.#h()}undo(){return this.#l("undo")}redo(){return this.#l("redo")}canUndo(){return{undoStack:this.undoStack,redoStack:this.redoStack}.undoStack.length>0}canRedo(){return{undoStack:this.undoStack,redoStack:this.redoStack}.redoStack.length>0}clearHistory(){const t=this.undoStack.length>0||this.redoStack.length>0,e={undoStack:[],redoStack:[]};this.undoStack=e.undoStack,this.redoStack=e.redoStack,this.#e=null,this.#n=0,t&&this.#h()}getUndoStack(){return[...this.undoStack]}getRedoStack(){return[...this.redoStack]}};t.UndoRedoPlugin=i});
2
2
  //# sourceMappingURL=undo-redo.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"undo-redo.umd.js","names":[],"sources":["../../../../../libs/grid/src/lib/core/constants.ts","../../../../../libs/grid/src/lib/core/internal/diagnostics.ts","../../../../../libs/grid/src/lib/plugins/undo-redo/history.ts","../../../../../libs/grid/src/lib/plugins/undo-redo/undo-redo-plugin.ts"],"sourcesContent":["/**\n * Grid DOM Constants\n *\n * Centralized constants for CSS classes, data attributes, and selectors\n * used throughout the grid. Use these instead of magic strings.\n *\n * Note: Some constants here are used by plugins but defined in core because:\n * 1. The core CSS needs to style these elements (e.g., sticky positioning)\n * 2. Multiple plugins may share the same class names\n *\n * Plugins can define their own additional constants and export them.\n * See each plugin's index.ts for plugin-specific exports.\n */\n\n/**\n * CSS selector for focusable editor elements within a cell.\n * Used by EditingPlugin, UndoRedoPlugin, and keyboard navigation. Defined here\n * (a leaf module) rather than in `rows.ts` so `keyboard.ts` can consume it\n * without creating a rows ↔ keyboard import cycle.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// #region CSS Classes\n\n/**\n * CSS class names used in the grid's DOM.\n * Use these when adding/removing classes or querying elements.\n *\n * Classes are organized by:\n * - Core: Used by the grid core for structure and basic functionality\n * - Shared: Used by multiple features/plugins, styled by core CSS\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridClasses = {\n // ─── Core Structure ───────────────────────────────────────────────\n ROOT: 'tbw-grid-root',\n HEADER: 'header',\n HEADER_ROW: 'header-row',\n HEADER_CELL: 'header-cell',\n\n // Body structure\n ROWS_VIEWPORT: 'rows-viewport',\n ROWS_SPACER: 'rows-spacer',\n ROWS_CONTAINER: 'rows',\n\n // Row elements\n DATA_ROW: 'data-grid-row',\n GROUP_ROW: 'group-row',\n\n // Cell elements\n DATA_CELL: 'cell',\n\n // ─── Core States ──────────────────────────────────────────────────\n SELECTED: 'selected',\n FOCUSED: 'focused',\n EDITING: 'editing',\n EXPANDED: 'expanded',\n COLLAPSED: 'collapsed',\n DRAGGING: 'dragging',\n RESIZING: 'resizing',\n\n // Sorting (core feature)\n SORTABLE: 'sortable',\n SORTED_ASC: 'sorted-asc',\n SORTED_DESC: 'sorted-desc',\n\n // Visibility\n HIDDEN: 'hidden',\n\n // ─── Shared Classes (used by plugins, styled by core CSS) ────────\n // These are defined here because core CSS provides the base styling.\n // Plugins apply these classes; core CSS defines how they look.\n\n // Sticky positioning (PinnedColumnsPlugin applies, core CSS styles)\n STICKY_LEFT: 'sticky-left',\n STICKY_RIGHT: 'sticky-right',\n\n // Pinned rows (PinnedRowsPlugin applies, core CSS styles)\n PINNED_TOP: 'pinned-top',\n PINNED_BOTTOM: 'pinned-bottom',\n\n // Tree structure (TreePlugin applies, core CSS styles)\n TREE_TOGGLE: 'tree-toggle',\n TREE_INDENT: 'tree-indent',\n\n // Grouping (GroupingRowsPlugin applies, core CSS styles)\n GROUP_TOGGLE: 'group-toggle',\n GROUP_LABEL: 'group-label',\n GROUP_COUNT: 'group-count',\n\n // Selection (SelectionPlugin applies, core CSS styles)\n RANGE_SELECTION: 'range-selection',\n SELECTION_OVERLAY: 'selection-overlay',\n} as const;\n\n// #endregion\n\n// #region Data Attributes\n\n/**\n * Data attribute names used on grid elements.\n * Use these when getting/setting data attributes.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridDataAttrs = {\n // ─── Core Attributes ──────────────────────────────────────────────\n FIELD: 'data-field',\n\n /**\n * Marks a `title` the grid itself put on a cell to reveal ellipsised text\n * (WCAG SC 1.4.12). Anything that sets its own `title` on a cell must clear\n * this, or the grid will later mistake that title for its own and replace it.\n *\n * @since 3.6.0\n */\n TRUNCATED: 'data-tbw-truncated',\n\n // ─── Shared Attributes (used by plugins) ──────────────────────────\n GROUP_KEY: 'data-group-key', // GroupingRowsPlugin\n} as const;\n\n// #endregion\n\n// #region Selectors\n\n/**\n * Common CSS selectors for querying grid elements.\n * Built from the class constants for consistency.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridSelectors = {\n ROOT: `.${GridClasses.ROOT}`,\n HEADER: `.${GridClasses.HEADER}`,\n HEADER_ROW: `.${GridClasses.HEADER_ROW}`,\n HEADER_CELL: `.${GridClasses.HEADER_CELL}`,\n ROWS_VIEWPORT: `.${GridClasses.ROWS_VIEWPORT}`,\n ROWS_CONTAINER: `.${GridClasses.ROWS_CONTAINER}`,\n DATA_ROW: `.${GridClasses.DATA_ROW}`,\n DATA_CELL: `.${GridClasses.DATA_CELL}`,\n GROUP_ROW: `.${GridClasses.GROUP_ROW}`,\n\n // By data attribute\n CELL_BY_FIELD: (field: string) => `.${GridClasses.DATA_CELL}[${GridDataAttrs.FIELD}=\"${field}\"]`,\n\n // State selectors\n SELECTED_ROWS: `.${GridClasses.DATA_ROW}.${GridClasses.SELECTED}`,\n EDITING_CELL: `.${GridClasses.DATA_CELL}.${GridClasses.EDITING}`,\n} as const;\n\n// #endregion\n\n// #region CSS Custom Properties\n\n/**\n * CSS custom property names for theming.\n * Use these when programmatically setting styles.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridCSSVars = {\n // Colors\n COLOR_BG: '--tbw-color-bg',\n COLOR_FG: '--tbw-color-fg',\n COLOR_FG_MUTED: '--tbw-color-fg-muted',\n COLOR_BORDER: '--tbw-color-border',\n COLOR_ACCENT: '--tbw-color-accent',\n COLOR_HEADER_BG: '--tbw-color-header-bg',\n COLOR_HEADER_FG: '--tbw-color-header-fg',\n COLOR_SELECTION: '--tbw-color-selection',\n COLOR_ROW_HOVER: '--tbw-color-row-hover',\n COLOR_ROW_ALT: '--tbw-color-row-alt',\n\n // Sizing\n ROW_HEIGHT: '--tbw-row-height',\n HEADER_HEIGHT: '--tbw-header-height',\n CELL_PADDING: '--tbw-cell-padding',\n\n // Typography\n FONT_FAMILY: '--tbw-font-family',\n FONT_SIZE: '--tbw-font-size',\n\n // Borders\n BORDER_RADIUS: '--tbw-border-radius',\n FOCUS_OUTLINE: '--tbw-focus-outline',\n} as const;\n\n// #endregion\n\n// Type helpers\n\n/**\n * Union of all CSS class names used in the grid's DOM.\n * Use `GridClasses` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridClassName = (typeof GridClasses)[keyof typeof GridClasses];\n\n/**\n * Union of all `data-*` attribute names used on grid elements.\n * Use `GridDataAttrs` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridDataAttr = (typeof GridDataAttrs)[keyof typeof GridDataAttrs];\n\n/**\n * Union of all CSS custom property names (`--tbw-*`) available for theming.\n * Use `GridCSSVars` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridCSSVar = (typeof GridCSSVars)[keyof typeof GridCSSVars];\n","/**\n * Centralized diagnostic messages for @toolbox-web/grid.\n *\n * Every user-facing warning, error, or info message in the grid has a unique\n * diagnostic code (e.g. `TBW001`). Each code maps to a section on the online\n * troubleshooting page, giving developers a direct link to resolution steps.\n *\n * ## Usage\n *\n * ```ts\n * import { MISSING_BREAKPOINT, warnDiagnostic, throwDiagnostic } from './diagnostics';\n *\n * // Warn with a code\n * warnDiagnostic(MISSING_BREAKPOINT, 'Set a breakpoint...', gridId);\n *\n * // Throw with a code\n * throwDiagnostic(MISSING_ROW_ID, 'Configure getRowId...', gridId);\n * ```\n *\n * Plugins should prefer `this.warn(MISSING_BREAKPOINT, message)` via BaseGridPlugin\n * instead of importing this module directly.\n *\n * @internal\n */\n\n// #region Grid Prefix\n\n/**\n * Build the `[tbw-grid]` or `[tbw-grid#my-id]` log prefix.\n * Pass `pluginName` for a scoped prefix like `[tbw-grid:SelectionPlugin]`.\n */\nexport function gridPrefix(gridId?: string, pluginName?: string): string {\n const id = gridId ? `#${gridId}` : '';\n const plugin = pluginName ? `:${pluginName}` : '';\n return `[tbw-grid${id}${plugin}]`;\n}\n\n// #endregion\n\n// #region Diagnostic Codes\n\n/**\n * Diagnostic codes used across the grid library.\n *\n * Each code is an individual export so that unused codes are tree-shaken\n * from bundles that don't reference them (esbuild can't tree-shake\n * properties from a single object).\n *\n * Naming: TBW + 3-digit number.\n * Ranges:\n * 001–019 Configuration validation (missing plugins, bad config)\n * 020–029 Plugin lifecycle (dependencies, incompatibilities, alias collapse)\n * 030–039 Feature registry\n * 040–049 Row operations (row ID, row mutations)\n * 050–059 Column operations (width, template)\n * 060–069 Rendering (callbacks, formatters, external views)\n * 070–079 Shell (tool panels, header/toolbar content)\n * 080–089 Editing & editors\n * 090–099 Print\n * 100–109 Clipboard\n * 110–119 Plugin-specific (responsive, undo-redo, grouping-columns)\n * 120–129 Style injection\n * 130–139 Attribute parsing\n * 140–149 DataSource / ServerSide\n */\n\n// --- Config validation (001–019) ---\n/** Column uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN = 'TBW001' as const;\n/** Grid config uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN_CONFIG = 'TBW002' as const;\n/** Plugin config rule violation (error severity). */\nexport const CONFIG_RULE_ERROR = 'TBW003' as const;\n/** Plugin config rule violation (warning severity). */\nexport const CONFIG_RULE_WARN = 'TBW004' as const;\n\n// --- Plugin lifecycle (020–029) ---\n/** Required plugin dependency is missing. */\nexport const MISSING_DEPENDENCY = 'TBW020' as const;\n/** Optional plugin dependency is missing. */\nexport const OPTIONAL_DEPENDENCY = 'TBW021' as const;\n/** Two loaded plugins are incompatible. */\nexport const INCOMPATIBLE_PLUGINS = 'TBW022' as const;\n/** Two plugin instances resolve to the same canonical name (alias collapse). */\nexport const PLUGIN_ALIAS_COLLAPSE = 'TBW023' as const;\n/** Error thrown inside a plugin event handler. */\nexport const PLUGIN_EVENT_ERROR = 'TBW024' as const;\n/** Conflicting config values found while merging alias-collapsed plugin instances. */\nexport const PLUGIN_ALIAS_CONFIG_CONFLICT = 'TBW025' as const;\n\n// --- Feature registry (030–039) ---\n/** Feature was re-registered (overwritten). */\nexport const FEATURE_REREGISTERED = 'TBW030' as const;\n/** Feature configured but not imported. */\nexport const FEATURE_NOT_IMPORTED = 'TBW031' as const;\n/** Feature depends on another feature that is not enabled. */\nexport const FEATURE_MISSING_DEP = 'TBW032' as const;\n\n// --- Row operations (040–049) ---\n/** Cannot determine row ID (no getRowId and no id property). */\nexport const MISSING_ROW_ID = 'TBW040' as const;\n/** Row with given ID not found. */\nexport const ROW_NOT_FOUND = 'TBW041' as const;\n\n// --- Column operations (050–059) ---\n/** Column has an invalid CSS width value. */\nexport const INVALID_COLUMN_WIDTH = 'TBW050' as const;\n\n// --- Rendering callbacks (060–069) ---\n/** rowClass callback threw an error. */\nexport const ROW_CLASS_ERROR = 'TBW060' as const;\n/** cellClass callback threw an error. */\nexport const CELL_CLASS_ERROR = 'TBW061' as const;\n/** Column format function threw an error. */\nexport const FORMAT_ERROR = 'TBW062' as const;\n/** External view mount() threw an error. */\nexport const VIEW_MOUNT_ERROR = 'TBW063' as const;\n/** External view event dispatch error. */\nexport const VIEW_DISPATCH_ERROR = 'TBW064' as const;\n\n// --- Shell (070–079) ---\n/** Tool panel missing required id. */\nexport const TOOL_PANEL_MISSING_ATTR = 'TBW070' as const;\n/** No tool panels registered. */\nexport const NO_TOOL_PANELS = 'TBW071' as const;\n/** Tool panel section not found. */\nexport const TOOL_PANEL_NOT_FOUND = 'TBW072' as const;\n/** Tool panel already registered. */\nexport const TOOL_PANEL_DUPLICATE = 'TBW073' as const;\n/** Header content already registered. */\nexport const HEADER_CONTENT_DUPLICATE = 'TBW074' as const;\n/** Toolbar content already registered. */\nexport const TOOLBAR_CONTENT_DUPLICATE = 'TBW075' as const;\n\n// --- Editing & editors (080–089) ---\n/** External editor mount() threw an error. */\nexport const EDITOR_MOUNT_ERROR = 'TBW080' as const;\n\n// --- Print (090–099) ---\n/** Print already in progress. */\nexport const PRINT_IN_PROGRESS = 'TBW090' as const;\n/** Grid not available for printing. */\nexport const PRINT_NO_GRID = 'TBW091' as const;\n/** Print operation failed. */\nexport const PRINT_FAILED = 'TBW092' as const;\n/** Multiple elements share the same grid ID (print isolation issue). */\nexport const PRINT_DUPLICATE_ID = 'TBW093' as const;\n\n// --- Clipboard (100–109) ---\n/** Clipboard API write failed. */\nexport const CLIPBOARD_FAILED = 'TBW100' as const;\n\n// --- Plugin-specific (110–119) ---\n/** ResponsivePlugin: no breakpoint configured. */\nexport const MISSING_BREAKPOINT = 'TBW110' as const;\n/** UndoRedoPlugin: transaction already in progress. */\nexport const TRANSACTION_IN_PROGRESS = 'TBW111' as const;\n/** UndoRedoPlugin: no transaction in progress. */\nexport const NO_TRANSACTION = 'TBW112' as const;\n/** GroupingColumnsPlugin: missing id or header on column group definition. */\nexport const COLUMN_GROUP_NO_ID = 'TBW113' as const;\n/** GroupingColumnsPlugin: conflicting columnGroups sources. */\nexport const COLUMN_GROUPS_CONFLICT = 'TBW114' as const;\n\n// --- Style injection (120–129) ---\n/** Failed to extract grid.css from document stylesheets. */\nexport const STYLE_EXTRACT_FAILED = 'TBW120' as const;\n/** Could not find grid.css in document.styleSheets. */\nexport const STYLE_NOT_FOUND = 'TBW121' as const;\n\n// --- Attribute parsing (130–139) ---\n/** Invalid JSON in an HTML attribute. */\nexport const INVALID_ATTRIBUTE_JSON = 'TBW130' as const;\n\n// --- DataSource / ServerSide (140–149) ---\n/** ServerSidePlugin: getRows() rejected. */\nexport const DATASOURCE_FETCH_ERROR = 'TBW140' as const;\n/** ServerSidePlugin: getChildRows() rejected. */\nexport const DATASOURCE_CHILD_FETCH_ERROR = 'TBW141' as const;\n/** ServerSidePlugin: getChildRows() not implemented but a plugin queried for children. */\nexport const DATASOURCE_NO_CHILD_HANDLER = 'TBW142' as const;\n/** ServerSidePlugin: concurrent request limit reached, request deferred. */\nexport const DATASOURCE_THROTTLED = 'TBW143' as const;\n\nexport type DiagnosticCode =\n | typeof MISSING_PLUGIN\n | typeof MISSING_PLUGIN_CONFIG\n | typeof CONFIG_RULE_ERROR\n | typeof CONFIG_RULE_WARN\n | typeof MISSING_DEPENDENCY\n | typeof OPTIONAL_DEPENDENCY\n | typeof INCOMPATIBLE_PLUGINS\n | typeof PLUGIN_ALIAS_COLLAPSE\n | typeof PLUGIN_EVENT_ERROR\n | typeof PLUGIN_ALIAS_CONFIG_CONFLICT\n | typeof FEATURE_REREGISTERED\n | typeof FEATURE_NOT_IMPORTED\n | typeof FEATURE_MISSING_DEP\n | typeof MISSING_ROW_ID\n | typeof ROW_NOT_FOUND\n | typeof INVALID_COLUMN_WIDTH\n | typeof ROW_CLASS_ERROR\n | typeof CELL_CLASS_ERROR\n | typeof FORMAT_ERROR\n | typeof VIEW_MOUNT_ERROR\n | typeof VIEW_DISPATCH_ERROR\n | typeof TOOL_PANEL_MISSING_ATTR\n | typeof NO_TOOL_PANELS\n | typeof TOOL_PANEL_NOT_FOUND\n | typeof TOOL_PANEL_DUPLICATE\n | typeof HEADER_CONTENT_DUPLICATE\n | typeof TOOLBAR_CONTENT_DUPLICATE\n | typeof EDITOR_MOUNT_ERROR\n | typeof PRINT_IN_PROGRESS\n | typeof PRINT_NO_GRID\n | typeof PRINT_FAILED\n | typeof PRINT_DUPLICATE_ID\n | typeof CLIPBOARD_FAILED\n | typeof MISSING_BREAKPOINT\n | typeof TRANSACTION_IN_PROGRESS\n | typeof NO_TRANSACTION\n | typeof COLUMN_GROUP_NO_ID\n | typeof COLUMN_GROUPS_CONFLICT\n | typeof STYLE_EXTRACT_FAILED\n | typeof STYLE_NOT_FOUND\n | typeof INVALID_ATTRIBUTE_JSON\n | typeof DATASOURCE_FETCH_ERROR\n | typeof DATASOURCE_CHILD_FETCH_ERROR\n | typeof DATASOURCE_NO_CHILD_HANDLER\n | typeof DATASOURCE_THROTTLED;\n\n// #endregion\n\n// #region Docs URL\n\nconst DOCS_BASE = 'https://toolboxjs.com/grid/errors';\n\n/** Build a direct link to the troubleshooting section for a code. */\nfunction docsUrl(code: DiagnosticCode): string {\n return `${DOCS_BASE}#${code.toLowerCase()}`;\n}\n\n// #endregion\n\n// #region Formatting\n\n/**\n * Format a diagnostic message with prefix, code, and docs link.\n *\n * Output format:\n * ```\n * [tbw-grid#my-id] TBW001: Your message here.\n *\n * → More info: https://toolboxjs.com/grid/errors#tbw001\n * ```\n */\nexport function formatDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): string {\n const prefix = gridPrefix(gridId, pluginName);\n return `${prefix} ${code}: ${message}\\n\\n → More info: ${docsUrl(code)}`;\n}\n\n// #endregion\n\n// #region Public API\n\n/**\n * Throw an error with a diagnostic code and docs link.\n * Use for configuration errors and API misuse that should halt execution.\n */\nexport function throwDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): never {\n throw new Error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a warning with a diagnostic code and docs link.\n * Use for recoverable issues the developer should fix.\n */\nexport function warnDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.warn(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a debug message with a diagnostic code and docs link.\n * Use for optional/soft dependency notifications — visible only when\n * the browser DevTools \"Verbose\" log level is enabled.\n */\nexport function debugDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.debug(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log an error with a diagnostic code and docs link.\n * Use for non-throwing errors (e.g., failed async operations).\n */\nexport function errorDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n// #endregion\n","/**\n * Undo/Redo History Management\n *\n * Pure functions for managing the undo/redo stacks.\n * These functions are stateless and return new state objects.\n */\n\nimport type { CompoundEditAction, EditAction, UndoRedoAction, UndoRedoState } from './types';\n\n/**\n * Push a new action onto the undo stack.\n * Clears the redo stack since new actions invalidate redo history.\n *\n * @param state - Current undo/redo state\n * @param action - The action to add\n * @param maxSize - Maximum history size\n * @returns New state with the action added\n */\nexport function pushAction(state: UndoRedoState, action: UndoRedoAction, maxSize: number): UndoRedoState {\n const undoStack = [...state.undoStack, action];\n\n // Trim oldest actions if over max size\n while (undoStack.length > maxSize) {\n undoStack.shift();\n }\n\n return {\n undoStack,\n redoStack: [], // Clear redo on new action\n };\n}\n\n/**\n * Undo the most recent action.\n * Moves the action from undo stack to redo stack.\n *\n * @param state - Current undo/redo state\n * @returns New state and the action that was undone (or null if nothing to undo)\n */\nexport function undo(state: UndoRedoState): {\n newState: UndoRedoState;\n action: UndoRedoAction | null;\n} {\n if (state.undoStack.length === 0) {\n return { newState: state, action: null };\n }\n\n const undoStack = [...state.undoStack];\n const action = undoStack.pop();\n\n // This should never happen due to the length check above,\n // but TypeScript needs the explicit check\n if (!action) {\n return { newState: state, action: null };\n }\n\n return {\n newState: {\n undoStack,\n redoStack: [...state.redoStack, action],\n },\n action,\n };\n}\n\n/**\n * Redo the most recently undone action.\n * Moves the action from redo stack back to undo stack.\n *\n * @param state - Current undo/redo state\n * @returns New state and the action that was redone (or null if nothing to redo)\n */\nexport function redo(state: UndoRedoState): {\n newState: UndoRedoState;\n action: UndoRedoAction | null;\n} {\n if (state.redoStack.length === 0) {\n return { newState: state, action: null };\n }\n\n const redoStack = [...state.redoStack];\n const action = redoStack.pop();\n\n // This should never happen due to the length check above,\n // but TypeScript needs the explicit check\n if (!action) {\n return { newState: state, action: null };\n }\n\n return {\n newState: {\n undoStack: [...state.undoStack, action],\n redoStack,\n },\n action,\n };\n}\n\n/**\n * Check if there are any actions that can be undone.\n *\n * @param state - Current undo/redo state\n * @returns True if undo is available\n */\nexport function canUndo(state: UndoRedoState): boolean {\n return state.undoStack.length > 0;\n}\n\n/**\n * Check if there are any actions that can be redone.\n *\n * @param state - Current undo/redo state\n * @returns True if redo is available\n */\nexport function canRedo(state: UndoRedoState): boolean {\n return state.redoStack.length > 0;\n}\n\n/**\n * Clear all history, returning an empty state.\n *\n * @returns Fresh empty state\n */\nexport function clearHistory(): UndoRedoState {\n return { undoStack: [], redoStack: [] };\n}\n\n/**\n * Create a new edit action with the current timestamp.\n *\n * @param rowIndex - The row index where the edit occurred\n * @param field - The field (column key) that was edited\n * @param oldValue - The value before the edit\n * @param newValue - The value after the edit\n * @param rowId - Stable ID of the edited row, when resolvable\n * @returns A new EditAction object\n */\nexport function createEditAction(\n rowIndex: number,\n field: string,\n oldValue: unknown,\n newValue: unknown,\n rowId?: string,\n): EditAction {\n const action: EditAction = {\n type: 'cell-edit',\n rowIndex,\n field,\n oldValue,\n newValue,\n timestamp: Date.now(),\n };\n if (rowId !== undefined) action.rowId = rowId;\n return action;\n}\n\n/**\n * Create a compound action grouping multiple edits into a single undo/redo unit.\n *\n * @param actions - The individual edit actions to group (in chronological order)\n * @returns A CompoundEditAction wrapping all provided edits\n */\nexport function createCompoundAction(actions: EditAction[]): CompoundEditAction {\n return {\n type: 'compound',\n actions,\n timestamp: Date.now(),\n };\n}\n","/**\n * Undo/Redo Plugin (Class-based)\n *\n * Provides undo/redo functionality for cell edits in tbw-grid.\n * Supports Ctrl+Z/Cmd+Z for undo and Ctrl+Y/Cmd+Y (or Ctrl+Shift+Z) for redo.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport { NO_TRANSACTION, throwDiagnostic } from '../../core/internal/diagnostics';\nimport { FOCUSABLE_EDITOR_SELECTOR } from '../../core/internal/rows';\nimport { writeCellField } from '../../core/internal/value-accessor';\nimport { BaseGridPlugin, type GridElement, type PluginDependency } from '../../core/plugin/base-plugin';\nimport type { GridHost } from '../../core/types';\nimport {\n canRedo,\n canUndo,\n clearHistory,\n createCompoundAction,\n createEditAction,\n pushAction,\n redo,\n undo,\n} from './history';\nimport type { EditAction, UndoRedoAction, UndoRedoConfig, UndoRedoDetail } from './types';\n\n/**\n * Undo/Redo Plugin for tbw-grid\n *\n * Tracks all cell edits and lets users revert or replay changes with familiar keyboard\n * shortcuts (Ctrl+Z / Ctrl+Y). Maintains an in-memory history stack with configurable\n * depth—perfect for data entry workflows where mistakes happen.\n *\n * > **Required Dependency:** This plugin requires EditingPlugin to be loaded first.\n * > UndoRedo tracks the edit history that EditingPlugin creates.\n *\n * ## Installation\n *\n * ```ts\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n * import { UndoRedoPlugin } from '@toolbox-web/grid/plugins/undo-redo';\n * ```\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Ctrl+Z` / `Cmd+Z` | Undo last edit |\n * | `Ctrl+Y` / `Cmd+Shift+Z` | Redo last undone edit |\n *\n * @example Basic Usage with EditingPlugin\n * ```ts\n * import { queryGrid } from '@toolbox-web/grid';\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n * import { UndoRedoPlugin } from '@toolbox-web/grid/plugins/undo-redo';\n *\n * const grid = queryGrid('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name', editable: true },\n * { field: 'price', header: 'Price', type: 'number', editable: true },\n * ],\n * plugins: [\n * new EditingPlugin({ editOn: 'dblclick' }), // Required - must be first\n * new UndoRedoPlugin({ maxHistorySize: 50 }),\n * ],\n * };\n * ```\n *\n * @see {@link UndoRedoConfig} for configuration options\n * @see EditingPlugin for the required dependency\n *\n * @internal Extends BaseGridPlugin\n * @since 0.1.1\n */\nexport class UndoRedoPlugin extends BaseGridPlugin<UndoRedoConfig> {\n /**\n * Plugin dependencies - UndoRedoPlugin requires EditingPlugin to track edits.\n *\n * The EditingPlugin must be loaded BEFORE this plugin in the plugins array.\n * @internal\n */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'editing', required: true, reason: 'UndoRedoPlugin tracks cell edit history' },\n ];\n\n /** @internal */\n readonly name = 'undoRedo';\n\n /** @internal */\n protected override get defaultConfig(): Partial<UndoRedoConfig> {\n return {\n maxHistorySize: 100,\n };\n }\n\n // State as class properties\n private undoStack: UndoRedoAction[] = [];\n private redoStack: UndoRedoAction[] = [];\n\n /** Suppresses recording during undo/redo to prevent feedback loops. */\n #suppressRecording = false;\n\n /** Accumulates edits during a transaction; `null` when no transaction is active. */\n #transactionBuffer: EditAction[] | null = null;\n\n /**\n * Open-transaction nesting depth. `beginTransaction` increments, `endTransaction`\n * decrements; the buffer is only finalized when it returns to 0. Nesting lets a\n * synchronous burst of commits (e.g. a multi-cell paste routed through\n * `updateRows`, where each cell fires `cell-commit` and consumers bracket it\n * with `beginTransaction()`/`queueMicrotask(endTransaction)`) coalesce into a\n * single compound undo entry instead of throwing \"already in progress\".\n */\n #transactionDepth = 0;\n\n /** Cell to focus after the render scheduled by the last undo/redo (see `afterRender`). */\n #pendingFocus: EditAction | null = null;\n\n /**\n * Resolve the stable ID of the row currently displayed at `rowIndex`.\n * Returns `undefined` when there is no such row or it has no resolvable ID.\n */\n #resolveRowId(rowIndex: number): string | undefined {\n const row = this.rows[rowIndex];\n if (!row) return undefined;\n try {\n return this.grid.getRowId(row) || undefined;\n } catch {\n return undefined;\n }\n }\n\n /**\n * Apply a value to a row cell, using `updateRow()` when possible so that\n * active editors (during row-edit mode) are notified via the `cell-change`\n * → `onValueChange` pipeline. Falls back to direct mutation when the row\n * has no ID.\n *\n * The row is resolved by `action.rowId` (searching the full source data, so\n * filtered-out rows are reachable) — never by `action.rowIndex` when an ID\n * exists, because the display index goes stale once the view is sorted or\n * filtered (#526).\n */\n #applyValue(action: EditAction, value: unknown): void {\n // Prefer updateRow() — it emits `cell-change` events which notify active\n // editors via their `onValueChange` callbacks. Without this, undo/redo\n // during row-edit mode is invisible because the render pipeline skips\n // cells that have active editors.\n //\n // The `'history'` source tells data plugins (editing) to apply the value\n // and recompute dirty state WITHOUT recording a fresh history entry or\n // re-marking the row changed — the undo stack owns this change. This is\n // what breaks the updateRow→commit→cell-edit-committed→record feedback loop.\n if (action.rowId !== undefined) {\n // updateRow resolves the id against the full source data (filtered-out rows\n // included); a row deleted since the edit is a silent no-op for 'history'.\n this.grid.updateRow(action.rowId, { [action.field]: value }, 'history');\n return;\n }\n\n // No row identity: fall back to the display index captured at edit time.\n const rows = this.rows as Record<string, unknown>[];\n const row = rows[action.rowIndex];\n if (!row) return;\n writeCellField(row, action.field, value);\n }\n\n /**\n * Move keyboard focus to the cell targeted by an undo/redo action.\n * If the grid is in row-edit mode and the cell has an active editor,\n * the editor input is focused so the user can continue editing.\n */\n #focusActionCell(action: EditAction): void {\n const internalGrid: GridHost = this.grid;\n\n // Map field name → visible column index\n const colIdx = internalGrid._visibleColumns?.findIndex((c) => c.field === action.field) ?? -1;\n if (colIdx < 0) return;\n\n // Resolve the row's CURRENT display index; skip focus when it is not in view.\n const rowIndex =\n action.rowId !== undefined ? (internalGrid._getRowEntry(action.rowId)?.index ?? -1) : action.rowIndex;\n if (rowIndex < 0) return;\n\n internalGrid._focusRow = rowIndex;\n internalGrid._focusCol = colIdx;\n\n // If we're in row-edit mode, focus the editor input in the target cell\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) return;\n\n const cellEl = rowEl.querySelector(`.cell[data-col=\"${colIdx}\"]`) as HTMLElement | null;\n if (cellEl?.classList.contains(GridClasses.EDITING)) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n editor?.focus({ preventScroll: true });\n }\n }\n\n /**\n * Apply value changes for a single or compound action.\n * Wraps `#applyValue` calls with `#suppressRecording` to prevent feedback loops.\n */\n #applyUndoRedoAction(action: UndoRedoAction, direction: 'undo' | 'redo'): void {\n this.#suppressRecording = true;\n if (action.type === 'compound') {\n const subActions = direction === 'undo' ? [...action.actions].reverse() : action.actions;\n for (const sub of subActions) {\n this.#applyValue(sub, direction === 'undo' ? sub.oldValue : sub.newValue);\n }\n } else {\n this.#applyValue(action, direction === 'undo' ? action.oldValue : action.newValue);\n }\n this.#suppressRecording = false;\n }\n\n /**\n * Focus the cell associated with an undo/redo action.\n * For compound actions, focuses the **last** action's cell. When consumers\n * use `beginTransaction()` + `recordEdit()` (cascaded fields) followed by\n * `queueMicrotask(() => endTransaction())`, the grid's auto-recorded\n * primary field edit is appended last. Focusing it ensures the cursor\n * lands on the field the user originally edited, not on a cascaded field\n * whose column may not even be visible.\n *\n * Focus is applied in `afterRender()`, not here: the change can re-sort or\n * re-filter the view, so the row's display index is only known after the\n * ROWS phase that `requestRenderWithFocus()` schedules.\n */\n #focusUndoRedoAction(action: UndoRedoAction): void {\n const target = action.type === 'compound' ? action.actions[action.actions.length - 1] : action;\n this.#pendingFocus = target ?? null;\n }\n\n /**\n * Apply the focus deferred by an undo/redo once the view reflects the change.\n * @internal\n */\n override afterRender(): void {\n const target = this.#pendingFocus;\n if (!target) return;\n this.#pendingFocus = null;\n this.#focusActionCell(target);\n }\n\n /**\n * Subscribe to cell-edit-committed events from EditingPlugin.\n * @internal\n */\n override attach(grid: GridElement): void {\n super.attach(grid);\n // Auto-record edits via Event Bus\n this.on(\n 'cell-edit-committed',\n (detail: { rowIndex: number; rowId?: string; field: string; oldValue: unknown; newValue: unknown }) => {\n // Skip recording during undo/redo operations. When undo/redo applies a\n // value via updateRow, two things can cause re-entry:\n // 1. updateRow → cell-change → onValueChange → editor triggers commit\n // 2. Browser native undo (if not fully suppressed) fires input event → commit\n // The suppress flag prevents these from corrupting the history stacks.\n if (this.#suppressRecording) return;\n this.recordEdit(detail.rowIndex, detail.field, detail.oldValue, detail.newValue, detail.rowId);\n },\n );\n }\n\n /**\n * Clean up state when plugin is detached.\n * @internal\n */\n override detach(): void {\n this.undoStack = [];\n this.redoStack = [];\n this.#transactionBuffer = null;\n this.#transactionDepth = 0;\n this.#pendingFocus = null;\n }\n\n /**\n * Handle keyboard shortcuts for undo/redo.\n * - Ctrl+Z / Cmd+Z: Undo\n * - Ctrl+Y / Cmd+Y / Ctrl+Shift+Z / Cmd+Shift+Z: Redo\n *\n * When the keystroke originates from a focusable form control (e.g. an\n * active cell editor input, a filter text box) we defer to the browser's\n * native undo/redo first. We watch for a `beforeinput` event with\n * `inputType === 'historyUndo'` / `'historyRedo'` on the target — if the\n * browser had something to undo, it consumes the keystroke and we do\n * nothing; if it didn't (history depleted), we fall back to the grid's\n * own undo/redo. This lets users undo their typing/paste inside an open\n * editor, then continue undoing committed cell edits with the same key.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean {\n const isUndo = (event.ctrlKey || event.metaKey) && event.key === 'z' && !event.shiftKey;\n const isRedo = (event.ctrlKey || event.metaKey) && (event.key === 'y' || (event.key === 'z' && event.shiftKey));\n\n if (!isUndo && !isRedo) return false;\n\n const kind: 'undo' | 'redo' = isUndo ? 'undo' : 'redo';\n\n if (this.#isNativeHistoryTarget(event.target)) {\n // Let the browser try its native undo/redo first. Only run ours if\n // the browser had nothing left in its own history.\n this.#deferToNativeHistory(event.target as HTMLElement, kind);\n return true;\n }\n\n // Prevent browser native undo on text inputs — it would conflict with\n // the grid's undo by mutating the input text independently, triggering\n // re-commits that cancel the grid undo.\n event.preventDefault();\n this.#performHistory(kind);\n return true;\n }\n\n /**\n * Apply an undo or redo against the grid's own history stack.\n */\n #performHistory(kind: 'undo' | 'redo'): void {\n const state = { undoStack: this.undoStack, redoStack: this.redoStack };\n const result = kind === 'undo' ? undo(state) : redo(state);\n if (!result.action) return;\n\n this.#applyUndoRedoAction(result.action, kind);\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n\n this.emit<UndoRedoDetail>(kind, { action: result.action, type: kind });\n this.#focusUndoRedoAction(result.action);\n this.requestRenderWithFocus();\n }\n\n /**\n * True when the keystroke target is a form control with its own native\n * undo history (text inputs, textareas, contenteditable). `<select>` is\n * intentionally excluded — it has no undo history.\n */\n #isNativeHistoryTarget(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false;\n return !!target.closest('input, textarea, [contenteditable=\"\"], [contenteditable=\"true\"]');\n }\n\n /**\n * Watch for the browser's native undo/redo on `target`. The browser fires\n * `beforeinput` with `inputType: 'historyUndo' | 'historyRedo'` when it\n * actually has something to undo/redo. If we don't observe that within a\n * microtask of the keydown returning, the native history is depleted and\n * we run the grid's own undo/redo to extend the chain.\n */\n #deferToNativeHistory(target: HTMLElement, kind: 'undo' | 'redo'): void {\n const wantedInputType = kind === 'undo' ? 'historyUndo' : 'historyRedo';\n let nativeHandled = false;\n\n const onBeforeInput = (e: Event) => {\n if ((e as InputEvent).inputType === wantedInputType) nativeHandled = true;\n };\n target.addEventListener('beforeinput', onBeforeInput, { capture: true });\n\n queueMicrotask(() => {\n target.removeEventListener('beforeinput', onBeforeInput, { capture: true } as EventListenerOptions);\n if (!nativeHandled) this.#performHistory(kind);\n });\n }\n\n // #region Public API Methods\n\n /**\n * Record a cell edit for undo/redo tracking.\n * Call this when a cell value changes.\n *\n * The row's stable ID is captured from the row currently displayed at `rowIndex`,\n * so call this while `rowIndex` still points at the edited row (e.g. synchronously\n * inside a `cell-commit` handler). Undo/redo then target that row by ID even after\n * the view is sorted or filtered. Pass `rowId` explicitly when the row is not in\n * the view (e.g. `cell-commit` from an `updateRow()` on a filtered-out row reports\n * `rowIndex: -1`) — `detail.rowId` from `cell-commit` is the natural source.\n *\n * @param rowIndex - The display (processed-row) index where the edit occurred\n * @param field - The field (column key) that was edited\n * @param oldValue - The value before the edit\n * @param newValue - The value after the edit\n * @param rowId - Stable ID of the edited row; resolved from `rowIndex` when omitted (since 3.9.2)\n */\n recordEdit(rowIndex: number, field: string, oldValue: unknown, newValue: unknown, rowId?: string): void {\n const action = createEditAction(rowIndex, field, oldValue, newValue, rowId || this.#resolveRowId(rowIndex));\n\n // Buffer during transactions instead of pushing to undo stack\n if (this.#transactionBuffer) {\n this.#transactionBuffer.push(action);\n return;\n }\n\n const newState = pushAction(\n { undoStack: this.undoStack, redoStack: this.redoStack },\n action,\n this.config.maxHistorySize ?? 100,\n );\n this.undoStack = newState.undoStack;\n this.redoStack = newState.redoStack;\n }\n\n /**\n * Begin grouping subsequent edits into a single compound action.\n *\n * While a transaction is active, all `recordEdit()` calls (both manual\n * and auto-recorded from `cell-edit-committed`) are buffered instead of\n * pushed to the undo stack. Call `endTransaction()` to finalize the group.\n *\n * **Nesting:** transactions are re-entrant. Calling `beginTransaction()` while\n * one is already open increments a depth counter rather than throwing; the\n * buffer is finalized only when a matching `endTransaction()` returns the depth\n * to 0. This lets a synchronous burst of commits (e.g. a multi-cell paste, where\n * each `cell-commit` handler brackets itself with `beginTransaction()` +\n * `queueMicrotask(endTransaction)`) coalesce into a single compound undo entry.\n *\n * **Typical usage** — group a user edit with its cascaded side-effects:\n *\n * ```ts\n * grid.on('cell-commit', () => {\n * const undoRedo = grid.getPluginByName('undoRedo');\n * undoRedo.beginTransaction();\n *\n * // Record cascaded updates (these won't auto-record)\n * const oldB = row.fieldB;\n * undoRedo.recordEdit(rowIndex, 'fieldB', oldB, computedB);\n * grid.updateRow(rowId, { fieldB: computedB });\n *\n * // End after the auto-recorded original edit is captured\n * queueMicrotask(() => undoRedo.endTransaction());\n * });\n * ```\n */\n beginTransaction(): void {\n if (this.#transactionDepth === 0) {\n this.#transactionBuffer = [];\n }\n this.#transactionDepth++;\n }\n\n /**\n * Finalize the current transaction, wrapping all buffered edits into a\n * single compound action on the undo stack.\n *\n * - If the buffer contains multiple edits, they are wrapped in a `CompoundEditAction`.\n * - If the buffer contains a single edit, it is pushed as a regular `EditAction`.\n * - If the buffer is empty, this is a no-op.\n *\n * Undoing a compound action reverts all edits in reverse order; redoing\n * replays them in forward order.\n *\n * When transactions are nested, only the outermost `endTransaction()` (the one\n * that returns the depth to 0) finalizes the buffer; inner calls just decrement.\n *\n * @throws Error if no transaction is in progress\n */\n endTransaction(): void {\n if (this.#transactionDepth === 0) {\n throwDiagnostic(NO_TRANSACTION, 'No transaction in progress. Call beginTransaction() first.');\n }\n\n this.#transactionDepth--;\n if (this.#transactionDepth > 0) return; // still inside an outer transaction\n\n const buffer = this.#transactionBuffer;\n this.#transactionBuffer = null;\n if (!buffer || buffer.length === 0) return;\n\n const action: UndoRedoAction = buffer.length === 1 ? buffer[0] : createCompoundAction(buffer);\n const newState = pushAction(\n { undoStack: this.undoStack, redoStack: this.redoStack },\n action,\n this.config.maxHistorySize ?? 100,\n );\n this.undoStack = newState.undoStack;\n this.redoStack = newState.redoStack;\n }\n\n /**\n * Programmatically undo the last action.\n *\n * @returns The undone action, or null if nothing to undo\n */\n undo(): UndoRedoAction | null {\n const result = undo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n this.#applyUndoRedoAction(result.action, 'undo');\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n this.#focusUndoRedoAction(result.action);\n this.requestRenderWithFocus();\n }\n return result.action;\n }\n\n /**\n * Programmatically redo the last undone action.\n *\n * @returns The redone action, or null if nothing to redo\n */\n redo(): UndoRedoAction | null {\n const result = redo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n this.#applyUndoRedoAction(result.action, 'redo');\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n this.#focusUndoRedoAction(result.action);\n this.requestRenderWithFocus();\n }\n return result.action;\n }\n\n /**\n * Check if there are any actions that can be undone.\n */\n canUndo(): boolean {\n return canUndo({ undoStack: this.undoStack, redoStack: this.redoStack });\n }\n\n /**\n * Check if there are any actions that can be redone.\n */\n canRedo(): boolean {\n return canRedo({ undoStack: this.undoStack, redoStack: this.redoStack });\n }\n\n /**\n * Clear all undo/redo history.\n */\n clearHistory(): void {\n const newState = clearHistory();\n this.undoStack = newState.undoStack;\n this.redoStack = newState.redoStack;\n this.#transactionBuffer = null;\n this.#transactionDepth = 0;\n }\n\n /**\n * Get a copy of the current undo stack.\n */\n getUndoStack(): UndoRedoAction[] {\n return [...this.undoStack];\n }\n\n /**\n * Get a copy of the current redo stack.\n */\n getRedoStack(): UndoRedoAction[] {\n return [...this.redoStack];\n }\n // #endregion\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"undo-redo.umd.js","names":["BaseGridPlugin","global","factory","exports","module","require","define","amd","globalThis","self","TbwGridPlugin_undoRedo","TbwGrid","this","_toolbox_web_grid","Object","defineProperty","Symbol","toStringTag","value","GridClasses","pushAction","state","action","maxSize","undoStack","length","shift","redoStack","UndoRedoPlugin","BaseGridPlugin","static","name","required","reason","defaultConfig","maxHistorySize","suppressRecording","transactionBuffer","transactionDepth","pendingFocus","resolveRowId","rowIndex","row","rows","grid","getRowId","applyValue","rowId","updateRow","field","writeCellField","focusActionCell","internalGrid","colIdx","_visibleColumns","findIndex","c","_getRowEntry","index","_focusRow","_focusCol","rowEl","findRenderedRowElement","cellEl","querySelector","classList","contains","focus","preventScroll","applyUndoRedoAction","direction","type","subActions","actions","reverse","sub","oldValue","newValue","focusUndoRedoAction","target","afterRender","attach","super","on","detail","recordEdit","detach","onKeyDown","event","isUndo","ctrlKey","metaKey","key","shiftKey","isRedo","kind","isNativeHistoryTarget","deferToNativeHistory","preventDefault","performHistory","result","newState","pop","undo","redo","emit","emitHistoryChange","requestRenderWithFocus","canUndo","canRedo","Element","closest","wantedInputType","nativeHandled","onBeforeInput","e","inputType","addEventListener","capture","queueMicrotask","removeEventListener","timestamp","Date","now","createEditAction","push","config","beginTransaction","endTransaction","code","message","gridId","pluginName","Error","gridPrefix","toLowerCase","docsUrl","formatDiagnostic","throwDiagnostic","buffer","clearHistory","hadHistory","getUndoStack","getRedoStack"],"sources":["../../../../../libs/grid/src/lib/core/constants.ts","../../../../../libs/grid/src/lib/core/internal/diagnostics.ts","../../../../../libs/grid/src/lib/plugins/undo-redo/history.ts","../../../../../libs/grid/src/lib/plugins/undo-redo/undo-redo-plugin.ts"],"sourcesContent":["/**\n * Grid DOM Constants\n *\n * Centralized constants for CSS classes, data attributes, and selectors\n * used throughout the grid. Use these instead of magic strings.\n *\n * Note: Some constants here are used by plugins but defined in core because:\n * 1. The core CSS needs to style these elements (e.g., sticky positioning)\n * 2. Multiple plugins may share the same class names\n *\n * Plugins can define their own additional constants and export them.\n * See each plugin's index.ts for plugin-specific exports.\n */\n\n/**\n * CSS selector for focusable editor elements within a cell.\n * Used by EditingPlugin, UndoRedoPlugin, and keyboard navigation. Defined here\n * (a leaf module) rather than in `rows.ts` so `keyboard.ts` can consume it\n * without creating a rows ↔ keyboard import cycle.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// #region CSS Classes\n\n/**\n * CSS class names used in the grid's DOM.\n * Use these when adding/removing classes or querying elements.\n *\n * Classes are organized by:\n * - Core: Used by the grid core for structure and basic functionality\n * - Shared: Used by multiple features/plugins, styled by core CSS\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridClasses = {\n // ─── Core Structure ───────────────────────────────────────────────\n ROOT: 'tbw-grid-root',\n HEADER: 'header',\n HEADER_ROW: 'header-row',\n HEADER_CELL: 'header-cell',\n\n // Body structure\n ROWS_VIEWPORT: 'rows-viewport',\n ROWS_SPACER: 'rows-spacer',\n ROWS_CONTAINER: 'rows',\n\n // Row elements\n DATA_ROW: 'data-grid-row',\n GROUP_ROW: 'group-row',\n\n // Cell elements\n DATA_CELL: 'cell',\n\n // ─── Core States ──────────────────────────────────────────────────\n SELECTED: 'selected',\n FOCUSED: 'focused',\n EDITING: 'editing',\n EXPANDED: 'expanded',\n COLLAPSED: 'collapsed',\n DRAGGING: 'dragging',\n RESIZING: 'resizing',\n\n // Sorting (core feature)\n SORTABLE: 'sortable',\n SORTED_ASC: 'sorted-asc',\n SORTED_DESC: 'sorted-desc',\n\n // Visibility\n HIDDEN: 'hidden',\n\n // ─── Shared Classes (used by plugins, styled by core CSS) ────────\n // These are defined here because core CSS provides the base styling.\n // Plugins apply these classes; core CSS defines how they look.\n\n // Sticky positioning (PinnedColumnsPlugin applies, core CSS styles)\n STICKY_LEFT: 'sticky-left',\n STICKY_RIGHT: 'sticky-right',\n\n // Pinned rows (PinnedRowsPlugin applies, core CSS styles)\n PINNED_TOP: 'pinned-top',\n PINNED_BOTTOM: 'pinned-bottom',\n\n // Tree structure (TreePlugin applies, core CSS styles)\n TREE_TOGGLE: 'tree-toggle',\n TREE_INDENT: 'tree-indent',\n\n // Grouping (GroupingRowsPlugin applies, core CSS styles)\n GROUP_TOGGLE: 'group-toggle',\n GROUP_LABEL: 'group-label',\n GROUP_COUNT: 'group-count',\n\n // Selection (SelectionPlugin applies, core CSS styles)\n RANGE_SELECTION: 'range-selection',\n SELECTION_OVERLAY: 'selection-overlay',\n} as const;\n\n// #endregion\n\n// #region Data Attributes\n\n/**\n * Data attribute names used on grid elements.\n * Use these when getting/setting data attributes.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridDataAttrs = {\n // ─── Core Attributes ──────────────────────────────────────────────\n FIELD: 'data-field',\n\n /**\n * Marks a `title` the grid itself put on a cell to reveal ellipsised text\n * (WCAG SC 1.4.12). Anything that sets its own `title` on a cell must clear\n * this, or the grid will later mistake that title for its own and replace it.\n *\n * @since 3.6.0\n */\n TRUNCATED: 'data-tbw-truncated',\n\n // ─── Shared Attributes (used by plugins) ──────────────────────────\n GROUP_KEY: 'data-group-key', // GroupingRowsPlugin\n} as const;\n\n// #endregion\n\n// #region Selectors\n\n/**\n * Common CSS selectors for querying grid elements.\n * Built from the class constants for consistency.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridSelectors = {\n ROOT: `.${GridClasses.ROOT}`,\n HEADER: `.${GridClasses.HEADER}`,\n HEADER_ROW: `.${GridClasses.HEADER_ROW}`,\n HEADER_CELL: `.${GridClasses.HEADER_CELL}`,\n ROWS_VIEWPORT: `.${GridClasses.ROWS_VIEWPORT}`,\n ROWS_CONTAINER: `.${GridClasses.ROWS_CONTAINER}`,\n DATA_ROW: `.${GridClasses.DATA_ROW}`,\n DATA_CELL: `.${GridClasses.DATA_CELL}`,\n GROUP_ROW: `.${GridClasses.GROUP_ROW}`,\n\n // By data attribute\n CELL_BY_FIELD: (field: string) => `.${GridClasses.DATA_CELL}[${GridDataAttrs.FIELD}=\"${field}\"]`,\n\n // State selectors\n SELECTED_ROWS: `.${GridClasses.DATA_ROW}.${GridClasses.SELECTED}`,\n EDITING_CELL: `.${GridClasses.DATA_CELL}.${GridClasses.EDITING}`,\n} as const;\n\n// #endregion\n\n// #region CSS Custom Properties\n\n/**\n * CSS custom property names for theming.\n * Use these when programmatically setting styles.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport const GridCSSVars = {\n // Colors\n COLOR_BG: '--tbw-color-bg',\n COLOR_FG: '--tbw-color-fg',\n COLOR_FG_MUTED: '--tbw-color-fg-muted',\n COLOR_BORDER: '--tbw-color-border',\n COLOR_ACCENT: '--tbw-color-accent',\n COLOR_HEADER_BG: '--tbw-color-header-bg',\n COLOR_HEADER_FG: '--tbw-color-header-fg',\n COLOR_SELECTION: '--tbw-color-selection',\n COLOR_ROW_HOVER: '--tbw-color-row-hover',\n COLOR_ROW_ALT: '--tbw-color-row-alt',\n\n // Sizing\n ROW_HEIGHT: '--tbw-row-height',\n HEADER_HEIGHT: '--tbw-header-height',\n CELL_PADDING: '--tbw-cell-padding',\n\n // Typography\n FONT_FAMILY: '--tbw-font-family',\n FONT_SIZE: '--tbw-font-size',\n\n // Borders\n BORDER_RADIUS: '--tbw-border-radius',\n FOCUS_OUTLINE: '--tbw-focus-outline',\n} as const;\n\n// #endregion\n\n// Type helpers\n\n/**\n * Union of all CSS class names used in the grid's DOM.\n * Use `GridClasses` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridClassName = (typeof GridClasses)[keyof typeof GridClasses];\n\n/**\n * Union of all `data-*` attribute names used on grid elements.\n * Use `GridDataAttrs` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridDataAttr = (typeof GridDataAttrs)[keyof typeof GridDataAttrs];\n\n/**\n * Union of all CSS custom property names (`--tbw-*`) available for theming.\n * Use `GridCSSVars` to access individual values by key.\n *\n * @category Plugin Development\n * @since 0.1.1\n */\nexport type GridCSSVar = (typeof GridCSSVars)[keyof typeof GridCSSVars];\n","/**\n * Centralized diagnostic messages for @toolbox-web/grid.\n *\n * Every user-facing warning, error, or info message in the grid has a unique\n * diagnostic code (e.g. `TBW001`). Each code maps to a section on the online\n * troubleshooting page, giving developers a direct link to resolution steps.\n *\n * ## Usage\n *\n * ```ts\n * import { MISSING_BREAKPOINT, warnDiagnostic, throwDiagnostic } from './diagnostics';\n *\n * // Warn with a code\n * warnDiagnostic(MISSING_BREAKPOINT, 'Set a breakpoint...', gridId);\n *\n * // Throw with a code\n * throwDiagnostic(MISSING_ROW_ID, 'Configure getRowId...', gridId);\n * ```\n *\n * Plugins should prefer `this.warn(MISSING_BREAKPOINT, message)` via BaseGridPlugin\n * instead of importing this module directly.\n *\n * @internal\n */\n\n// #region Grid Prefix\n\n/**\n * Build the `[tbw-grid]` or `[tbw-grid#my-id]` log prefix.\n * Pass `pluginName` for a scoped prefix like `[tbw-grid:SelectionPlugin]`.\n */\nexport function gridPrefix(gridId?: string, pluginName?: string): string {\n const id = gridId ? `#${gridId}` : '';\n const plugin = pluginName ? `:${pluginName}` : '';\n return `[tbw-grid${id}${plugin}]`;\n}\n\n// #endregion\n\n// #region Diagnostic Codes\n\n/**\n * Diagnostic codes used across the grid library.\n *\n * Each code is an individual export so that unused codes are tree-shaken\n * from bundles that don't reference them (esbuild can't tree-shake\n * properties from a single object).\n *\n * Naming: TBW + 3-digit number.\n * Ranges:\n * 001–019 Configuration validation (missing plugins, bad config)\n * 020–029 Plugin lifecycle (dependencies, incompatibilities, alias collapse)\n * 030–039 Feature registry\n * 040–049 Row operations (row ID, row mutations)\n * 050–059 Column operations (width, template)\n * 060–069 Rendering (callbacks, formatters, external views)\n * 070–079 Shell (tool panels, header/toolbar content)\n * 080–089 Editing & editors\n * 090–099 Print\n * 100–109 Clipboard\n * 110–119 Plugin-specific (responsive, undo-redo, grouping-columns)\n * 120–129 Style injection\n * 130–139 Attribute parsing\n * 140–149 DataSource / ServerSide\n */\n\n// --- Config validation (001–019) ---\n/** Column uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN = 'TBW001' as const;\n/** Grid config uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN_CONFIG = 'TBW002' as const;\n/** Plugin config rule violation (error severity). */\nexport const CONFIG_RULE_ERROR = 'TBW003' as const;\n/** Plugin config rule violation (warning severity). */\nexport const CONFIG_RULE_WARN = 'TBW004' as const;\n\n// --- Plugin lifecycle (020–029) ---\n/** Required plugin dependency is missing. */\nexport const MISSING_DEPENDENCY = 'TBW020' as const;\n/** Optional plugin dependency is missing. */\nexport const OPTIONAL_DEPENDENCY = 'TBW021' as const;\n/** Two loaded plugins are incompatible. */\nexport const INCOMPATIBLE_PLUGINS = 'TBW022' as const;\n/** Two plugin instances resolve to the same canonical name (alias collapse). */\nexport const PLUGIN_ALIAS_COLLAPSE = 'TBW023' as const;\n/** Error thrown inside a plugin event handler. */\nexport const PLUGIN_EVENT_ERROR = 'TBW024' as const;\n/** Conflicting config values found while merging alias-collapsed plugin instances. */\nexport const PLUGIN_ALIAS_CONFIG_CONFLICT = 'TBW025' as const;\n\n// --- Feature registry (030–039) ---\n/** Feature was re-registered (overwritten). */\nexport const FEATURE_REREGISTERED = 'TBW030' as const;\n/** Feature configured but not imported. */\nexport const FEATURE_NOT_IMPORTED = 'TBW031' as const;\n/** Feature depends on another feature that is not enabled. */\nexport const FEATURE_MISSING_DEP = 'TBW032' as const;\n\n// --- Row operations (040–049) ---\n/** Cannot determine row ID (no getRowId and no id property). */\nexport const MISSING_ROW_ID = 'TBW040' as const;\n/** Row with given ID not found. */\nexport const ROW_NOT_FOUND = 'TBW041' as const;\n\n// --- Column operations (050–059) ---\n/** Column has an invalid CSS width value. */\nexport const INVALID_COLUMN_WIDTH = 'TBW050' as const;\n\n// --- Rendering callbacks (060–069) ---\n/** rowClass callback threw an error. */\nexport const ROW_CLASS_ERROR = 'TBW060' as const;\n/** cellClass callback threw an error. */\nexport const CELL_CLASS_ERROR = 'TBW061' as const;\n/** Column format function threw an error. */\nexport const FORMAT_ERROR = 'TBW062' as const;\n/** External view mount() threw an error. */\nexport const VIEW_MOUNT_ERROR = 'TBW063' as const;\n/** External view event dispatch error. */\nexport const VIEW_DISPATCH_ERROR = 'TBW064' as const;\n\n// --- Shell (070–079) ---\n/** Tool panel missing required id. */\nexport const TOOL_PANEL_MISSING_ATTR = 'TBW070' as const;\n/** No tool panels registered. */\nexport const NO_TOOL_PANELS = 'TBW071' as const;\n/** Tool panel section not found. */\nexport const TOOL_PANEL_NOT_FOUND = 'TBW072' as const;\n/** Tool panel already registered. */\nexport const TOOL_PANEL_DUPLICATE = 'TBW073' as const;\n/** Header content already registered. */\nexport const HEADER_CONTENT_DUPLICATE = 'TBW074' as const;\n/** Toolbar content already registered. */\nexport const TOOLBAR_CONTENT_DUPLICATE = 'TBW075' as const;\n\n// --- Editing & editors (080–089) ---\n/** External editor mount() threw an error. */\nexport const EDITOR_MOUNT_ERROR = 'TBW080' as const;\n\n// --- Print (090–099) ---\n/** Print already in progress. */\nexport const PRINT_IN_PROGRESS = 'TBW090' as const;\n/** Grid not available for printing. */\nexport const PRINT_NO_GRID = 'TBW091' as const;\n/** Print operation failed. */\nexport const PRINT_FAILED = 'TBW092' as const;\n/** Multiple elements share the same grid ID (print isolation issue). */\nexport const PRINT_DUPLICATE_ID = 'TBW093' as const;\n\n// --- Clipboard (100–109) ---\n/** Clipboard API write failed. */\nexport const CLIPBOARD_FAILED = 'TBW100' as const;\n\n// --- Plugin-specific (110–119) ---\n/** ResponsivePlugin: no breakpoint configured. */\nexport const MISSING_BREAKPOINT = 'TBW110' as const;\n/** UndoRedoPlugin: transaction already in progress. */\nexport const TRANSACTION_IN_PROGRESS = 'TBW111' as const;\n/** UndoRedoPlugin: no transaction in progress. */\nexport const NO_TRANSACTION = 'TBW112' as const;\n/** GroupingColumnsPlugin: missing id or header on column group definition. */\nexport const COLUMN_GROUP_NO_ID = 'TBW113' as const;\n/** GroupingColumnsPlugin: conflicting columnGroups sources. */\nexport const COLUMN_GROUPS_CONFLICT = 'TBW114' as const;\n\n// --- Style injection (120–129) ---\n/** Failed to extract grid.css from document stylesheets. */\nexport const STYLE_EXTRACT_FAILED = 'TBW120' as const;\n/** Could not find grid.css in document.styleSheets. */\nexport const STYLE_NOT_FOUND = 'TBW121' as const;\n\n// --- Attribute parsing (130–139) ---\n/** Invalid JSON in an HTML attribute. */\nexport const INVALID_ATTRIBUTE_JSON = 'TBW130' as const;\n\n// --- DataSource / ServerSide (140–149) ---\n/** ServerSidePlugin: getRows() rejected. */\nexport const DATASOURCE_FETCH_ERROR = 'TBW140' as const;\n/** ServerSidePlugin: getChildRows() rejected. */\nexport const DATASOURCE_CHILD_FETCH_ERROR = 'TBW141' as const;\n/** ServerSidePlugin: getChildRows() not implemented but a plugin queried for children. */\nexport const DATASOURCE_NO_CHILD_HANDLER = 'TBW142' as const;\n/** ServerSidePlugin: concurrent request limit reached, request deferred. */\nexport const DATASOURCE_THROTTLED = 'TBW143' as const;\n\nexport type DiagnosticCode =\n | typeof MISSING_PLUGIN\n | typeof MISSING_PLUGIN_CONFIG\n | typeof CONFIG_RULE_ERROR\n | typeof CONFIG_RULE_WARN\n | typeof MISSING_DEPENDENCY\n | typeof OPTIONAL_DEPENDENCY\n | typeof INCOMPATIBLE_PLUGINS\n | typeof PLUGIN_ALIAS_COLLAPSE\n | typeof PLUGIN_EVENT_ERROR\n | typeof PLUGIN_ALIAS_CONFIG_CONFLICT\n | typeof FEATURE_REREGISTERED\n | typeof FEATURE_NOT_IMPORTED\n | typeof FEATURE_MISSING_DEP\n | typeof MISSING_ROW_ID\n | typeof ROW_NOT_FOUND\n | typeof INVALID_COLUMN_WIDTH\n | typeof ROW_CLASS_ERROR\n | typeof CELL_CLASS_ERROR\n | typeof FORMAT_ERROR\n | typeof VIEW_MOUNT_ERROR\n | typeof VIEW_DISPATCH_ERROR\n | typeof TOOL_PANEL_MISSING_ATTR\n | typeof NO_TOOL_PANELS\n | typeof TOOL_PANEL_NOT_FOUND\n | typeof TOOL_PANEL_DUPLICATE\n | typeof HEADER_CONTENT_DUPLICATE\n | typeof TOOLBAR_CONTENT_DUPLICATE\n | typeof EDITOR_MOUNT_ERROR\n | typeof PRINT_IN_PROGRESS\n | typeof PRINT_NO_GRID\n | typeof PRINT_FAILED\n | typeof PRINT_DUPLICATE_ID\n | typeof CLIPBOARD_FAILED\n | typeof MISSING_BREAKPOINT\n | typeof TRANSACTION_IN_PROGRESS\n | typeof NO_TRANSACTION\n | typeof COLUMN_GROUP_NO_ID\n | typeof COLUMN_GROUPS_CONFLICT\n | typeof STYLE_EXTRACT_FAILED\n | typeof STYLE_NOT_FOUND\n | typeof INVALID_ATTRIBUTE_JSON\n | typeof DATASOURCE_FETCH_ERROR\n | typeof DATASOURCE_CHILD_FETCH_ERROR\n | typeof DATASOURCE_NO_CHILD_HANDLER\n | typeof DATASOURCE_THROTTLED;\n\n// #endregion\n\n// #region Docs URL\n\nconst DOCS_BASE = 'https://toolboxjs.com/grid/errors';\n\n/** Build a direct link to the troubleshooting section for a code. */\nfunction docsUrl(code: DiagnosticCode): string {\n return `${DOCS_BASE}#${code.toLowerCase()}`;\n}\n\n// #endregion\n\n// #region Formatting\n\n/**\n * Format a diagnostic message with prefix, code, and docs link.\n *\n * Output format:\n * ```\n * [tbw-grid#my-id] TBW001: Your message here.\n *\n * → More info: https://toolboxjs.com/grid/errors#tbw001\n * ```\n */\nexport function formatDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): string {\n const prefix = gridPrefix(gridId, pluginName);\n return `${prefix} ${code}: ${message}\\n\\n → More info: ${docsUrl(code)}`;\n}\n\n// #endregion\n\n// #region Public API\n\n/**\n * Throw an error with a diagnostic code and docs link.\n * Use for configuration errors and API misuse that should halt execution.\n */\nexport function throwDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): never {\n throw new Error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a warning with a diagnostic code and docs link.\n * Use for recoverable issues the developer should fix.\n */\nexport function warnDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.warn(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a debug message with a diagnostic code and docs link.\n * Use for optional/soft dependency notifications — visible only when\n * the browser DevTools \"Verbose\" log level is enabled.\n */\nexport function debugDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.debug(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log an error with a diagnostic code and docs link.\n * Use for non-throwing errors (e.g., failed async operations).\n */\nexport function errorDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n// #endregion\n","/**\n * Undo/Redo History Management\n *\n * Pure functions for managing the undo/redo stacks.\n * These functions are stateless and return new state objects.\n */\n\nimport type { CompoundEditAction, EditAction, UndoRedoAction, UndoRedoState } from './types';\n\n/**\n * Push a new action onto the undo stack.\n * Clears the redo stack since new actions invalidate redo history.\n *\n * @param state - Current undo/redo state\n * @param action - The action to add\n * @param maxSize - Maximum history size\n * @returns New state with the action added\n */\nexport function pushAction(state: UndoRedoState, action: UndoRedoAction, maxSize: number): UndoRedoState {\n const undoStack = [...state.undoStack, action];\n\n // Trim oldest actions if over max size\n while (undoStack.length > maxSize) {\n undoStack.shift();\n }\n\n return {\n undoStack,\n redoStack: [], // Clear redo on new action\n };\n}\n\n/**\n * Undo the most recent action.\n * Moves the action from undo stack to redo stack.\n *\n * @param state - Current undo/redo state\n * @returns New state and the action that was undone (or null if nothing to undo)\n */\nexport function undo(state: UndoRedoState): {\n newState: UndoRedoState;\n action: UndoRedoAction | null;\n} {\n if (state.undoStack.length === 0) {\n return { newState: state, action: null };\n }\n\n const undoStack = [...state.undoStack];\n const action = undoStack.pop();\n\n // This should never happen due to the length check above,\n // but TypeScript needs the explicit check\n if (!action) {\n return { newState: state, action: null };\n }\n\n return {\n newState: {\n undoStack,\n redoStack: [...state.redoStack, action],\n },\n action,\n };\n}\n\n/**\n * Redo the most recently undone action.\n * Moves the action from redo stack back to undo stack.\n *\n * @param state - Current undo/redo state\n * @returns New state and the action that was redone (or null if nothing to redo)\n */\nexport function redo(state: UndoRedoState): {\n newState: UndoRedoState;\n action: UndoRedoAction | null;\n} {\n if (state.redoStack.length === 0) {\n return { newState: state, action: null };\n }\n\n const redoStack = [...state.redoStack];\n const action = redoStack.pop();\n\n // This should never happen due to the length check above,\n // but TypeScript needs the explicit check\n if (!action) {\n return { newState: state, action: null };\n }\n\n return {\n newState: {\n undoStack: [...state.undoStack, action],\n redoStack,\n },\n action,\n };\n}\n\n/**\n * Check if there are any actions that can be undone.\n *\n * @param state - Current undo/redo state\n * @returns True if undo is available\n */\nexport function canUndo(state: UndoRedoState): boolean {\n return state.undoStack.length > 0;\n}\n\n/**\n * Check if there are any actions that can be redone.\n *\n * @param state - Current undo/redo state\n * @returns True if redo is available\n */\nexport function canRedo(state: UndoRedoState): boolean {\n return state.redoStack.length > 0;\n}\n\n/**\n * Clear all history, returning an empty state.\n *\n * @returns Fresh empty state\n */\nexport function clearHistory(): UndoRedoState {\n return { undoStack: [], redoStack: [] };\n}\n\n/**\n * Create a new edit action with the current timestamp.\n *\n * @param rowIndex - The row index where the edit occurred\n * @param field - The field (column key) that was edited\n * @param oldValue - The value before the edit\n * @param newValue - The value after the edit\n * @param rowId - Stable ID of the edited row, when resolvable\n * @returns A new EditAction object\n */\nexport function createEditAction(\n rowIndex: number,\n field: string,\n oldValue: unknown,\n newValue: unknown,\n rowId?: string,\n): EditAction {\n const action: EditAction = {\n type: 'cell-edit',\n rowIndex,\n field,\n oldValue,\n newValue,\n timestamp: Date.now(),\n };\n if (rowId !== undefined) action.rowId = rowId;\n return action;\n}\n\n/**\n * Create a compound action grouping multiple edits into a single undo/redo unit.\n *\n * @param actions - The individual edit actions to group (in chronological order)\n * @returns A CompoundEditAction wrapping all provided edits\n */\nexport function createCompoundAction(actions: EditAction[]): CompoundEditAction {\n return {\n type: 'compound',\n actions,\n timestamp: Date.now(),\n };\n}\n","/**\n * Undo/Redo Plugin (Class-based)\n *\n * Provides undo/redo functionality for cell edits in tbw-grid.\n * Supports Ctrl+Z/Cmd+Z for undo and Ctrl+Y/Cmd+Y (or Ctrl+Shift+Z) for redo.\n */\n\nimport { GridClasses } from '../../core/constants';\nimport { NO_TRANSACTION, throwDiagnostic } from '../../core/internal/diagnostics';\nimport { FOCUSABLE_EDITOR_SELECTOR } from '../../core/internal/rows';\nimport { writeCellField } from '../../core/internal/value-accessor';\nimport { BaseGridPlugin, type GridElement, type PluginDependency } from '../../core/plugin/base-plugin';\nimport type { GridHost } from '../../core/types';\nimport {\n canRedo,\n canUndo,\n clearHistory,\n createCompoundAction,\n createEditAction,\n pushAction,\n redo,\n undo,\n} from './history';\nimport type { EditAction, HistoryChangeDetail, UndoRedoAction, UndoRedoConfig, UndoRedoDetail } from './types';\n\n/**\n * Undo/Redo Plugin for tbw-grid\n *\n * Tracks all cell edits and lets users revert or replay changes with familiar keyboard\n * shortcuts (Ctrl+Z / Ctrl+Y). Maintains an in-memory history stack with configurable\n * depth—perfect for data entry workflows where mistakes happen.\n *\n * > **Required Dependency:** This plugin requires EditingPlugin to be loaded first.\n * > UndoRedo tracks the edit history that EditingPlugin creates.\n *\n * ## Installation\n *\n * ```ts\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n * import { UndoRedoPlugin } from '@toolbox-web/grid/plugins/undo-redo';\n * ```\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Ctrl+Z` / `Cmd+Z` | Undo last edit |\n * | `Ctrl+Y` / `Cmd+Shift+Z` | Redo last undone edit |\n *\n * @example Basic Usage with EditingPlugin\n * ```ts\n * import { queryGrid } from '@toolbox-web/grid';\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n * import { UndoRedoPlugin } from '@toolbox-web/grid/plugins/undo-redo';\n *\n * const grid = queryGrid('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name', editable: true },\n * { field: 'price', header: 'Price', type: 'number', editable: true },\n * ],\n * plugins: [\n * new EditingPlugin({ editOn: 'dblclick' }), // Required - must be first\n * new UndoRedoPlugin({ maxHistorySize: 50 }),\n * ],\n * };\n * ```\n *\n * @see {@link UndoRedoConfig} for configuration options\n * @see EditingPlugin for the required dependency\n *\n * @internal Extends BaseGridPlugin\n * @since 0.1.1\n */\nexport class UndoRedoPlugin extends BaseGridPlugin<UndoRedoConfig> {\n /**\n * Plugin dependencies - UndoRedoPlugin requires EditingPlugin to track edits.\n *\n * The EditingPlugin must be loaded BEFORE this plugin in the plugins array.\n * @internal\n */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'editing', required: true, reason: 'UndoRedoPlugin tracks cell edit history' },\n ];\n\n /** @internal */\n readonly name = 'undoRedo';\n\n /** @internal */\n protected override get defaultConfig(): Partial<UndoRedoConfig> {\n return {\n maxHistorySize: 100,\n };\n }\n\n // State as class properties\n private undoStack: UndoRedoAction[] = [];\n private redoStack: UndoRedoAction[] = [];\n\n /** Suppresses recording during undo/redo to prevent feedback loops. */\n #suppressRecording = false;\n\n /** Accumulates edits during a transaction; `null` when no transaction is active. */\n #transactionBuffer: EditAction[] | null = null;\n\n /**\n * Open-transaction nesting depth. `beginTransaction` increments, `endTransaction`\n * decrements; the buffer is only finalized when it returns to 0. Nesting lets a\n * synchronous burst of commits (e.g. a multi-cell paste routed through\n * `updateRows`, where each cell fires `cell-commit` and consumers bracket it\n * with `beginTransaction()`/`queueMicrotask(endTransaction)`) coalesce into a\n * single compound undo entry instead of throwing \"already in progress\".\n */\n #transactionDepth = 0;\n\n /** Cell to focus after the render scheduled by the last undo/redo (see `afterRender`). */\n #pendingFocus: EditAction | null = null;\n\n /**\n * Resolve the stable ID of the row currently displayed at `rowIndex`.\n * Returns `undefined` when there is no such row or it has no resolvable ID.\n */\n #resolveRowId(rowIndex: number): string | undefined {\n const row = this.rows[rowIndex];\n if (!row) return undefined;\n try {\n return this.grid.getRowId(row) || undefined;\n } catch {\n return undefined;\n }\n }\n\n /**\n * Apply a value to a row cell, using `updateRow()` when possible so that\n * active editors (during row-edit mode) are notified via the `cell-change`\n * → `onValueChange` pipeline. Falls back to direct mutation when the row\n * has no ID.\n *\n * The row is resolved by `action.rowId` (searching the full source data, so\n * filtered-out rows are reachable) — never by `action.rowIndex` when an ID\n * exists, because the display index goes stale once the view is sorted or\n * filtered (#526).\n */\n #applyValue(action: EditAction, value: unknown): void {\n // Prefer updateRow() — it emits `cell-change` events which notify active\n // editors via their `onValueChange` callbacks. Without this, undo/redo\n // during row-edit mode is invisible because the render pipeline skips\n // cells that have active editors.\n //\n // The `'history'` source tells data plugins (editing) to apply the value\n // and recompute dirty state WITHOUT recording a fresh history entry or\n // re-marking the row changed — the undo stack owns this change. This is\n // what breaks the updateRow→commit→cell-edit-committed→record feedback loop.\n if (action.rowId !== undefined) {\n // updateRow resolves the id against the full source data (filtered-out rows\n // included); a row deleted since the edit is a silent no-op for 'history'.\n this.grid.updateRow(action.rowId, { [action.field]: value }, 'history');\n return;\n }\n\n // No row identity: fall back to the display index captured at edit time.\n const rows = this.rows as Record<string, unknown>[];\n const row = rows[action.rowIndex];\n if (!row) return;\n writeCellField(row, action.field, value);\n }\n\n /**\n * Move keyboard focus to the cell targeted by an undo/redo action.\n * If the grid is in row-edit mode and the cell has an active editor,\n * the editor input is focused so the user can continue editing.\n */\n #focusActionCell(action: EditAction): void {\n const internalGrid: GridHost = this.grid;\n\n // Map field name → visible column index\n const colIdx = internalGrid._visibleColumns?.findIndex((c) => c.field === action.field) ?? -1;\n if (colIdx < 0) return;\n\n // Resolve the row's CURRENT display index; skip focus when it is not in view.\n const rowIndex =\n action.rowId !== undefined ? (internalGrid._getRowEntry(action.rowId)?.index ?? -1) : action.rowIndex;\n if (rowIndex < 0) return;\n\n internalGrid._focusRow = rowIndex;\n internalGrid._focusCol = colIdx;\n\n // If we're in row-edit mode, focus the editor input in the target cell\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) return;\n\n const cellEl = rowEl.querySelector(`.cell[data-col=\"${colIdx}\"]`) as HTMLElement | null;\n if (cellEl?.classList.contains(GridClasses.EDITING)) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n editor?.focus({ preventScroll: true });\n }\n }\n\n /**\n * Apply value changes for a single or compound action.\n * Wraps `#applyValue` calls with `#suppressRecording` to prevent feedback loops.\n */\n #applyUndoRedoAction(action: UndoRedoAction, direction: 'undo' | 'redo'): void {\n this.#suppressRecording = true;\n if (action.type === 'compound') {\n const subActions = direction === 'undo' ? [...action.actions].reverse() : action.actions;\n for (const sub of subActions) {\n this.#applyValue(sub, direction === 'undo' ? sub.oldValue : sub.newValue);\n }\n } else {\n this.#applyValue(action, direction === 'undo' ? action.oldValue : action.newValue);\n }\n this.#suppressRecording = false;\n }\n\n /**\n * Focus the cell associated with an undo/redo action.\n * For compound actions, focuses the **last** action's cell. When consumers\n * use `beginTransaction()` + `recordEdit()` (cascaded fields) followed by\n * `queueMicrotask(() => endTransaction())`, the grid's auto-recorded\n * primary field edit is appended last. Focusing it ensures the cursor\n * lands on the field the user originally edited, not on a cascaded field\n * whose column may not even be visible.\n *\n * Focus is applied in `afterRender()`, not here: the change can re-sort or\n * re-filter the view, so the row's display index is only known after the\n * ROWS phase that `requestRenderWithFocus()` schedules.\n */\n #focusUndoRedoAction(action: UndoRedoAction): void {\n const target = action.type === 'compound' ? action.actions[action.actions.length - 1] : action;\n this.#pendingFocus = target ?? null;\n }\n\n /**\n * Apply the focus deferred by an undo/redo once the view reflects the change.\n * @internal\n */\n override afterRender(): void {\n const target = this.#pendingFocus;\n if (!target) return;\n this.#pendingFocus = null;\n this.#focusActionCell(target);\n }\n\n /**\n * Subscribe to cell-edit-committed events from EditingPlugin.\n * @internal\n */\n override attach(grid: GridElement): void {\n super.attach(grid);\n // Auto-record edits via Event Bus\n this.on(\n 'cell-edit-committed',\n (detail: { rowIndex: number; rowId?: string; field: string; oldValue: unknown; newValue: unknown }) => {\n // Skip recording during undo/redo operations. When undo/redo applies a\n // value via updateRow, two things can cause re-entry:\n // 1. updateRow → cell-change → onValueChange → editor triggers commit\n // 2. Browser native undo (if not fully suppressed) fires input event → commit\n // The suppress flag prevents these from corrupting the history stacks.\n if (this.#suppressRecording) return;\n this.recordEdit(detail.rowIndex, detail.field, detail.oldValue, detail.newValue, detail.rowId);\n },\n );\n }\n\n /**\n * Clean up state when plugin is detached.\n * @internal\n */\n override detach(): void {\n this.undoStack = [];\n this.redoStack = [];\n this.#transactionBuffer = null;\n this.#transactionDepth = 0;\n this.#pendingFocus = null;\n }\n\n /**\n * Handle keyboard shortcuts for undo/redo.\n * - Ctrl+Z / Cmd+Z: Undo\n * - Ctrl+Y / Cmd+Y / Ctrl+Shift+Z / Cmd+Shift+Z: Redo\n *\n * When the keystroke originates from a focusable form control (e.g. an\n * active cell editor input, a filter text box) we defer to the browser's\n * native undo/redo first. We watch for a `beforeinput` event with\n * `inputType === 'historyUndo'` / `'historyRedo'` on the target — if the\n * browser had something to undo, it consumes the keystroke and we do\n * nothing; if it didn't (history depleted), we fall back to the grid's\n * own undo/redo. This lets users undo their typing/paste inside an open\n * editor, then continue undoing committed cell edits with the same key.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean {\n const isUndo = (event.ctrlKey || event.metaKey) && event.key === 'z' && !event.shiftKey;\n const isRedo = (event.ctrlKey || event.metaKey) && (event.key === 'y' || (event.key === 'z' && event.shiftKey));\n\n if (!isUndo && !isRedo) return false;\n\n const kind: 'undo' | 'redo' = isUndo ? 'undo' : 'redo';\n\n if (this.#isNativeHistoryTarget(event.target)) {\n // Let the browser try its native undo/redo first. Only run ours if\n // the browser had nothing left in its own history.\n this.#deferToNativeHistory(event.target as HTMLElement, kind);\n return true;\n }\n\n // Prevent browser native undo on text inputs — it would conflict with\n // the grid's undo by mutating the input text independently, triggering\n // re-commits that cancel the grid undo.\n event.preventDefault();\n this.#performHistory(kind);\n return true;\n }\n\n /**\n * Apply an undo or redo against the grid's own history stack.\n * Shared by the keyboard path and the public `undo()` / `redo()` so listeners\n * cannot tell them apart (#563).\n */\n #performHistory(kind: 'undo' | 'redo'): UndoRedoAction | null {\n const state = { undoStack: this.undoStack, redoStack: this.redoStack };\n const result = kind === 'undo' ? undo(state) : redo(state);\n if (!result.action) return null;\n\n this.#applyUndoRedoAction(result.action, kind);\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n\n this.emit<UndoRedoDetail>(kind, { action: result.action, type: kind });\n this.#emitHistoryChange();\n this.#focusUndoRedoAction(result.action);\n this.requestRenderWithFocus();\n return result.action;\n }\n\n /** Notify listeners that the undo/redo stacks changed. */\n #emitHistoryChange(): void {\n this.emit<HistoryChangeDetail>('history-change', { canUndo: this.canUndo(), canRedo: this.canRedo() });\n }\n\n /**\n * True when the keystroke target is a form control with its own native\n * undo history (text inputs, textareas, contenteditable). `<select>` is\n * intentionally excluded — it has no undo history.\n */\n #isNativeHistoryTarget(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false;\n return !!target.closest('input, textarea, [contenteditable=\"\"], [contenteditable=\"true\"]');\n }\n\n /**\n * Watch for the browser's native undo/redo on `target`. The browser fires\n * `beforeinput` with `inputType: 'historyUndo' | 'historyRedo'` when it\n * actually has something to undo/redo. If we don't observe that within a\n * microtask of the keydown returning, the native history is depleted and\n * we run the grid's own undo/redo to extend the chain.\n */\n #deferToNativeHistory(target: HTMLElement, kind: 'undo' | 'redo'): void {\n const wantedInputType = kind === 'undo' ? 'historyUndo' : 'historyRedo';\n let nativeHandled = false;\n\n const onBeforeInput = (e: Event) => {\n if ((e as InputEvent).inputType === wantedInputType) nativeHandled = true;\n };\n target.addEventListener('beforeinput', onBeforeInput, { capture: true });\n\n queueMicrotask(() => {\n target.removeEventListener('beforeinput', onBeforeInput, { capture: true } as EventListenerOptions);\n if (!nativeHandled) this.#performHistory(kind);\n });\n }\n\n // #region Public API Methods\n\n /**\n * Record a cell edit for undo/redo tracking.\n * Call this when a cell value changes.\n *\n * The row's stable ID is captured from the row currently displayed at `rowIndex`,\n * so call this while `rowIndex` still points at the edited row (e.g. synchronously\n * inside a `cell-commit` handler). Undo/redo then target that row by ID even after\n * the view is sorted or filtered. Pass `rowId` explicitly when the row is not in\n * the view (e.g. `cell-commit` from an `updateRow()` on a filtered-out row reports\n * `rowIndex: -1`) — `detail.rowId` from `cell-commit` is the natural source.\n *\n * @param rowIndex - The display (processed-row) index where the edit occurred\n * @param field - The field (column key) that was edited\n * @param oldValue - The value before the edit\n * @param newValue - The value after the edit\n * @param rowId - Stable ID of the edited row; resolved from `rowIndex` when omitted (since 3.9.2)\n */\n recordEdit(rowIndex: number, field: string, oldValue: unknown, newValue: unknown, rowId?: string): void {\n const action = createEditAction(rowIndex, field, oldValue, newValue, rowId || this.#resolveRowId(rowIndex));\n\n // Buffer during transactions instead of pushing to undo stack\n if (this.#transactionBuffer) {\n this.#transactionBuffer.push(action);\n return;\n }\n\n const newState = pushAction(\n { undoStack: this.undoStack, redoStack: this.redoStack },\n action,\n this.config.maxHistorySize ?? 100,\n );\n this.undoStack = newState.undoStack;\n this.redoStack = newState.redoStack;\n this.#emitHistoryChange();\n }\n\n /**\n * Begin grouping subsequent edits into a single compound action.\n *\n * While a transaction is active, all `recordEdit()` calls (both manual\n * and auto-recorded from `cell-edit-committed`) are buffered instead of\n * pushed to the undo stack. Call `endTransaction()` to finalize the group.\n *\n * **Nesting:** transactions are re-entrant. Calling `beginTransaction()` while\n * one is already open increments a depth counter rather than throwing; the\n * buffer is finalized only when a matching `endTransaction()` returns the depth\n * to 0. This lets a synchronous burst of commits (e.g. a multi-cell paste, where\n * each `cell-commit` handler brackets itself with `beginTransaction()` +\n * `queueMicrotask(endTransaction)`) coalesce into a single compound undo entry.\n *\n * **Typical usage** — group a user edit with its cascaded side-effects:\n *\n * ```ts\n * grid.on('cell-commit', () => {\n * const undoRedo = grid.getPluginByName('undoRedo');\n * undoRedo.beginTransaction();\n *\n * // Record cascaded updates (these won't auto-record)\n * const oldB = row.fieldB;\n * undoRedo.recordEdit(rowIndex, 'fieldB', oldB, computedB);\n * grid.updateRow(rowId, { fieldB: computedB });\n *\n * // End after the auto-recorded original edit is captured\n * queueMicrotask(() => undoRedo.endTransaction());\n * });\n * ```\n */\n beginTransaction(): void {\n if (this.#transactionDepth === 0) {\n this.#transactionBuffer = [];\n }\n this.#transactionDepth++;\n }\n\n /**\n * Finalize the current transaction, wrapping all buffered edits into a\n * single compound action on the undo stack.\n *\n * - If the buffer contains multiple edits, they are wrapped in a `CompoundEditAction`.\n * - If the buffer contains a single edit, it is pushed as a regular `EditAction`.\n * - If the buffer is empty, this is a no-op.\n *\n * Undoing a compound action reverts all edits in reverse order; redoing\n * replays them in forward order.\n *\n * When transactions are nested, only the outermost `endTransaction()` (the one\n * that returns the depth to 0) finalizes the buffer; inner calls just decrement.\n *\n * @throws Error if no transaction is in progress\n */\n endTransaction(): void {\n if (this.#transactionDepth === 0) {\n throwDiagnostic(NO_TRANSACTION, 'No transaction in progress. Call beginTransaction() first.');\n }\n\n this.#transactionDepth--;\n if (this.#transactionDepth > 0) return; // still inside an outer transaction\n\n const buffer = this.#transactionBuffer;\n this.#transactionBuffer = null;\n if (!buffer || buffer.length === 0) return;\n\n const action: UndoRedoAction = buffer.length === 1 ? buffer[0] : createCompoundAction(buffer);\n const newState = pushAction(\n { undoStack: this.undoStack, redoStack: this.redoStack },\n action,\n this.config.maxHistorySize ?? 100,\n );\n this.undoStack = newState.undoStack;\n this.redoStack = newState.redoStack;\n this.#emitHistoryChange();\n }\n\n /**\n * Programmatically undo the last action.\n *\n * Behaves exactly like Ctrl+Z: emits `undo` (with the undone action) and\n * `history-change`, so app state that replays actions from the `undo` event\n * stays in sync.\n *\n * @returns The undone action, or null if nothing to undo\n */\n undo(): UndoRedoAction | null {\n return this.#performHistory('undo');\n }\n\n /**\n * Programmatically redo the last undone action.\n *\n * Behaves exactly like Ctrl+Y: emits `redo` (with the redone action) and\n * `history-change`.\n *\n * @returns The redone action, or null if nothing to redo\n */\n redo(): UndoRedoAction | null {\n return this.#performHistory('redo');\n }\n\n /**\n * Check if there are any actions that can be undone.\n */\n canUndo(): boolean {\n return canUndo({ undoStack: this.undoStack, redoStack: this.redoStack });\n }\n\n /**\n * Check if there are any actions that can be redone.\n */\n canRedo(): boolean {\n return canRedo({ undoStack: this.undoStack, redoStack: this.redoStack });\n }\n\n /**\n * Clear all undo/redo history.\n * Emits `history-change` when there was history to clear.\n */\n clearHistory(): void {\n const hadHistory = this.undoStack.length > 0 || this.redoStack.length > 0;\n const newState = clearHistory();\n this.undoStack = newState.undoStack;\n this.redoStack = newState.redoStack;\n this.#transactionBuffer = null;\n this.#transactionDepth = 0;\n if (hadHistory) this.#emitHistoryChange();\n }\n\n /**\n * Get a copy of the current undo stack.\n */\n getUndoStack(): UndoRedoAction[] {\n return [...this.undoStack];\n }\n\n /**\n * Get a copy of the current redo stack.\n */\n getRedoStack(): UndoRedoAction[] {\n return [...this.redoStack];\n }\n // #endregion\n}\n"],"mappings":"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"}