@toolbox-web/grid 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all.d.ts +1 -0
- package/all.d.ts.map +1 -1
- package/all.js +1655 -1444
- package/all.js.map +1 -1
- package/index.js +438 -401
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts.map +1 -1
- package/lib/core/internal/rows.d.ts.map +1 -1
- package/lib/core/internal/validate-config.d.ts +10 -0
- package/lib/core/internal/validate-config.d.ts.map +1 -1
- package/lib/core/plugin/base-plugin.d.ts +101 -0
- package/lib/core/plugin/base-plugin.d.ts.map +1 -1
- package/lib/core/plugin/index.d.ts +1 -1
- package/lib/core/plugin/index.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.js +22 -0
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js +48 -26
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js +64 -42
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/editing/EditingPlugin.d.ts +6 -1
- package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
- package/lib/plugins/editing/index.js +50 -4
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/export/index.js +36 -14
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/index.js +63 -41
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts +6 -1
- package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts.map +1 -1
- package/lib/plugins/grouping-columns/index.js +47 -6
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js +22 -0
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js +22 -0
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js +22 -0
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts +6 -1
- package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts.map +1 -1
- package/lib/plugins/pinned-columns/index.js +36 -0
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/index.js +22 -0
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js +22 -0
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/reorder/index.js +22 -0
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/responsive/ResponsivePlugin.d.ts +123 -0
- package/lib/plugins/responsive/ResponsivePlugin.d.ts.map +1 -0
- package/lib/plugins/responsive/index.d.ts +11 -0
- package/lib/plugins/responsive/index.d.ts.map +1 -0
- package/lib/plugins/responsive/index.js +589 -0
- package/lib/plugins/responsive/index.js.map +1 -0
- package/lib/plugins/responsive/types.d.ts +133 -0
- package/lib/plugins/responsive/types.d.ts.map +1 -0
- package/lib/plugins/selection/SelectionPlugin.d.ts +6 -1
- package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
- package/lib/plugins/selection/index.d.ts +1 -1
- package/lib/plugins/selection/index.d.ts.map +1 -1
- package/lib/plugins/selection/index.js +105 -63
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/selection/types.d.ts +26 -0
- package/lib/plugins/selection/types.d.ts.map +1 -1
- package/lib/plugins/server-side/index.js +22 -0
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/index.js +22 -0
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js +27 -5
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js +22 -0
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/umd/grid.all.umd.js +28 -22
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +18 -14
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/editing.umd.js +1 -1
- package/umd/plugins/editing.umd.js.map +1 -1
- package/umd/plugins/grouping-columns.umd.js +1 -1
- package/umd/plugins/grouping-columns.umd.js.map +1 -1
- package/umd/plugins/pinned-columns.umd.js +1 -1
- package/umd/plugins/pinned-columns.umd.js.map +1 -1
- package/umd/plugins/responsive.umd.js +2 -0
- package/umd/plugins/responsive.umd.js.map +1 -0
- package/umd/plugins/selection.umd.js +3 -1
- package/umd/plugins/selection.umd.js.map +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(E,v){typeof exports=="object"&&typeof module<"u"?v(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],v):(E=typeof globalThis<"u"?globalThis:E||self,v(E.TbwGridPlugin_editing={},E.TbwGrid))})(this,(function(E,v){"use strict";const P="@layer tbw-plugins{tbw-grid{--tbw-editing-bg: var(--tbw-color-selection);--tbw-editing-row-bg: var(--tbw-editing-bg);--tbw-editing-border: var(--tbw-border-input, 1px solid var(--tbw-color-border-strong));--tbw-padding-editing-input: var(--tbw-cell-padding-input, 2px 6px);--tbw-font-size-editor: inherit;--tbw-editing-row-outline-color: var(--tbw-color-accent);--tbw-editing-row-outline-width: 1px}tbw-grid .data-grid-row:has(.editing){background:var(--tbw-editing-row-bg);outline:var(--tbw-editing-row-outline-width) solid var(--tbw-editing-row-outline-color);outline-offset:calc(-1 * var(--tbw-editing-row-outline-width))}tbw-grid .data-grid-row>.cell.editing{overflow:hidden;padding:0;display:flex;min-height:calc(var(--tbw-row-height) + 2px);align-items:center;justify-content:center}tbw-grid .data-grid-row>.cell.editing input:not([type=checkbox]),tbw-grid .data-grid-row>.cell.editing select,tbw-grid .data-grid-row>.cell.editing textarea{width:100%;height:100%;flex:1 1 auto;min-width:0;border:var(--tbw-editing-border);padding:var(--tbw-padding-editing-input);font-size:var(--tbw-font-size-editor)}tbw-grid .tbw-editor-host{display:contents}}";function A(s){const e=s.options;return e?typeof e=="function"?e():e:[]}function q(s){return e=>{const i=s.editorParams,t=document.createElement("input");t.type="number",t.value=e.value!=null?String(e.value):"",i?.min!==void 0&&(t.min=String(i.min)),i?.max!==void 0&&(t.max=String(i.max)),i?.step!==void 0&&(t.step=String(i.step)),i?.placeholder&&(t.placeholder=i.placeholder);const r=()=>e.commit(t.value===""?null:Number(t.value));return t.addEventListener("blur",r),t.addEventListener("keydown",n=>{n.key==="Enter"&&r(),n.key==="Escape"&&e.cancel()}),t}}function O(){return s=>{const e=document.createElement("input");return e.type="checkbox",e.checked=!!s.value,e.addEventListener("change",()=>s.commit(e.checked)),e}}function D(s){return e=>{const i=s.editorParams,t=document.createElement("input");return t.type="date",e.value instanceof Date&&(t.valueAsDate=e.value),i?.min&&(t.min=i.min),i?.max&&(t.max=i.max),i?.placeholder&&(t.placeholder=i.placeholder),t.addEventListener("change",()=>e.commit(t.valueAsDate)),t.addEventListener("keydown",r=>{r.key==="Escape"&&e.cancel()}),t}}function I(s){return e=>{const i=s.editorParams,t=document.createElement("select");if(s.multi&&(t.multiple=!0),i?.includeEmpty){const o=document.createElement("option");o.value="",o.textContent=i.emptyLabel??"",t.appendChild(o)}A(s).forEach(o=>{const a=document.createElement("option");a.value=String(o.value),a.textContent=o.label,(s.multi&&Array.isArray(e.value)&&e.value.includes(o.value)||!s.multi&&e.value===o.value)&&(a.selected=!0),t.appendChild(a)});const n=()=>{if(s.multi){const o=Array.from(t.selectedOptions).map(a=>a.value);e.commit(o)}else e.commit(t.value)};return t.addEventListener("change",n),t.addEventListener("blur",n),t.addEventListener("keydown",o=>{o.key==="Escape"&&e.cancel()}),t}}function x(s){return e=>{const i=s.editorParams,t=document.createElement("input");return t.type="text",t.value=e.value!=null?String(e.value):"",i?.maxLength!==void 0&&(t.maxLength=i.maxLength),i?.pattern&&(t.pattern=i.pattern),i?.placeholder&&(t.placeholder=i.placeholder),t.addEventListener("blur",()=>e.commit(t.value)),t.addEventListener("keydown",r=>{r.key==="Enter"&&e.commit(t.value),r.key==="Escape"&&e.cancel()}),t}}function k(s){switch(s.type){case"number":return q(s);case"boolean":return O();case"date":return D(s);case"select":return I(s);default:return x(s)}}const _='input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])';function G(s,e){if(e.editor)return e.editor;if(e.__editorTemplate)return"template";if(!e.type)return;const t=s.effectiveConfig?.typeDefaults;if(t?.[e.type]?.editor)return t[e.type].editor;const r=s.__frameworkAdapter;if(r?.getTypeDefault){const n=r.getTypeDefault(e.type);if(n?.editor)return n.editor}}function y(s){return!(typeof s!="string"||s==="__proto__"||s==="constructor"||s==="prototype")}function M(s){const e=(s.__editingCellCount??0)+1;s.__editingCellCount=e,s.setAttribute("data-has-editing","")}function H(s){s.__editingCellCount=0,s.removeAttribute("data-has-editing")}function C(s,e){return s instanceof HTMLInputElement?s.type==="checkbox"?s.checked:s.type==="number"?s.value===""?null:Number(s.value):s.type==="date"?s.valueAsDate:s.value:e?.type==="number"&&s.value!==""?Number(s.value):s.value}function L(s){}function N(s,e,i){const t=s.querySelector("input,textarea,select");t&&(t.addEventListener("blur",()=>{i(C(t,e))}),t instanceof HTMLInputElement&&t.type==="checkbox"?t.addEventListener("change",()=>i(t.checked)):t instanceof HTMLSelectElement&&t.addEventListener("change",()=>i(C(t,e))))}class $ extends v.BaseGridPlugin{name="editing";styles=P;get defaultConfig(){return{editOn:"click"}}#e=-1;#s=-1;#r=new Map;#t=new Set;#n=new Set;#o=!1;attach(e){super.attach(e);const i=this.disconnectSignal,t=e;t._activeEditRows=-1,t._rowEditSnapshots=new Map,Object.defineProperty(e,"changedRows",{get:()=>this.changedRows,configurable:!0}),Object.defineProperty(e,"changedRowIds",{get:()=>this.changedRowIds,configurable:!0}),e.resetChangedRows=r=>this.resetChangedRows(r),e.beginBulkEdit=(r,n)=>{n&&this.beginCellEdit(r,n)},document.addEventListener("keydown",r=>{r.key==="Escape"&&this.#e!==-1&&this.#i(this.#e,!0)},{capture:!0,signal:i}),document.addEventListener("mousedown",r=>{if(this.#e===-1)return;const n=t.findRenderedRowElement?.(this.#e);!n||(r.composedPath&&r.composedPath()||[]).includes(n)||this.#i(this.#e,!1)},{signal:i})}detach(){this.#e=-1,this.#s=-1,this.#r.clear(),this.#t.clear(),this.#n.clear(),super.detach()}onCellClick(e){const i=this.grid,t=this.config.editOn??i.effectiveConfig?.editOn;if(t===!1||t==="manual"||t!=="click"&&t!=="dblclick")return!1;const r=e.originalEvent.type==="dblclick";if(t==="click"&&r||t==="dblclick"&&!r)return!1;const{rowIndex:n}=e;return i._columns?.some(a=>a.editable)?(e.originalEvent.stopPropagation(),this.beginBulkEdit(n),!0):!1}onKeyDown(e){const i=this.grid;if(e.key==="Escape"&&this.#e!==-1)return this.#i(this.#e,!0),!0;if(e.key===" "||e.key==="Spacebar"){const t=i._focusRow,r=i._focusCol;if(t>=0&&r>=0){const n=i._visibleColumns[r],o=i._rows[t];if(n?.editable&&n.type==="boolean"&&o){const a=n.field;if(y(a)){const c=!o[a];return this.#l(t,n,c,o),e.preventDefault(),this.requestRender(),!0}}}return!1}if(e.key==="Enter"&&!e.shiftKey){if(this.#e!==-1)return!1;const t=this.config.editOn??i.effectiveConfig?.editOn;if(t===!1||t==="manual")return!1;const r=i._focusRow,n=i._focusCol;if(r>=0&&i._columns?.some(a=>a.editable)){const a=i._visibleColumns[n],l=i._rows[r],c=a?.field??"",f=c&&l?l[c]:void 0,p=this.gridElement.querySelector(`[data-row="${r}"][data-col="${n}"]`),d=new CustomEvent("cell-activate",{cancelable:!0,bubbles:!0,detail:{rowIndex:r,colIndex:n,field:c,value:f,row:l,cellEl:p,trigger:"keyboard",originalEvent:e}});this.gridElement.dispatchEvent(d);const h=new CustomEvent("activate-cell",{cancelable:!0,bubbles:!0,detail:{row:r,col:n}});return this.gridElement.dispatchEvent(h),d.defaultPrevented||h.defaultPrevented?(e.preventDefault(),!0):(this.beginBulkEdit(r),!0)}return!1}return!1}processColumns(e){const i=this.grid,t=i.effectiveConfig?.typeDefaults,r=i.__frameworkAdapter;return!t&&!r?.getTypeDefault?e:e.map(n=>{if(!n.type)return n;let o;if(t?.[n.type]?.editorParams&&(o=t[n.type].editorParams),!o&&r?.getTypeDefault){const a=r.getTypeDefault(n.type);a?.editorParams&&(o=a.editorParams)}return o?{...n,editorParams:{...o,...n.editorParams}}:n})}afterRender(){const e=this.grid;if(this.#o&&(this.#o=!1,this.#u(e)),this.#n.size!==0)for(const i of this.#n){const[t,r]=i.split(":"),n=parseInt(t,10),o=parseInt(r,10),a=e.findRenderedRowElement?.(n);if(!a)continue;const l=a.querySelector(`.cell[data-col="${o}"]`);if(!l||l.classList.contains("editing"))continue;const c=e._rows[n],f=e._visibleColumns[o];c&&f&&this.#d(c,n,f,o,l,!0)}}onScrollRender(){this.afterRender()}get changedRows(){const e=[];for(const i of this.#t){const t=this.grid.getRow(i);t&&e.push(t)}return e}get changedRowIds(){return Array.from(this.#t)}get activeEditRow(){return this.#e}get activeEditCol(){return this.#s}isRowEditing(e){return this.#e===e}isCellEditing(e,i){return this.#n.has(`${e}:${i}`)}isRowChanged(e){const i=this.grid,t=i._rows[e];if(!t)return!1;try{const r=i.getRowId?.(t);return r?this.#t.has(r):!1}catch{return!1}}isRowChangedById(e){return this.#t.has(e)}resetChangedRows(e){const i=this.changedRows,t=this.changedRowIds;this.#t.clear(),this.#a(),e||this.emit("changed-rows-reset",{rows:i,ids:t}),this.grid._rowPool?.forEach(n=>n.classList.remove("changed"))}beginCellEdit(e,i){const t=this.grid,r=t._visibleColumns.findIndex(l=>l.field===i);if(r===-1||!t._visibleColumns[r]?.editable)return;const a=t.findRenderedRowElement?.(e)?.querySelector(`.cell[data-col="${r}"]`);a&&this.#f(e,r,a)}beginBulkEdit(e){const i=this.grid;if((this.config.editOn??i.effectiveConfig?.editOn)===!1||!i._columns?.some(a=>a.editable))return;const n=i.findRenderedRowElement?.(e);if(!n)return;const o=i._rows[e];this.#c(e,o),Array.from(n.children).forEach((a,l)=>{const c=i._visibleColumns[l];if(c?.editable){const f=a;f.classList.contains("editing")||this.#d(o,e,c,l,f,!0)}}),setTimeout(()=>{let a=n.querySelector(`.cell[data-col="${i._focusCol}"]`);if(a?.classList.contains("editing")||(a=n.querySelector(".cell.editing")),a?.classList.contains("editing")){const l=a.querySelector(_);try{l?.focus({preventScroll:!0})}catch{}}},0)}commitActiveRowEdit(){this.#e!==-1&&this.#i(this.#e,!1)}cancelActiveRowEdit(){this.#e!==-1&&this.#i(this.#e,!0)}#f(e,i,t){const r=this.grid,n=r._rows[e],o=r._visibleColumns[i];!n||!o?.editable||t.classList.contains("editing")||(this.#e!==e&&this.#c(e,n),this.#s=i,this.#d(n,e,o,i,t,!1))}#a(){const e=this.grid;e._activeEditRows=this.#e,e._rowEditSnapshots=this.#r}#c(e,i){this.#e!==e&&(this.#r.set(e,{...i}),this.#e=e,this.#a())}#i(e,i){if(this.#e!==e)return;const t=this.grid,r=this.#r.get(e),n=t._rows[e],o=t.findRenderedRowElement?.(e);let a;if(n)try{a=t.getRowId?.(n)}catch{}if(!i&&o&&n&&o.querySelectorAll(".cell.editing").forEach(c=>{const f=Number(c.getAttribute("data-col"));if(isNaN(f))return;const p=t._visibleColumns[f];if(!p)return;const d=c.querySelector("input,textarea,select");if(d){const h=C(d,p);n[p.field]!==h&&this.#l(e,p,h,n)}}),i&&r&&n)Object.keys(r).forEach(l=>{n[l]=r[l]}),a&&this.#t.delete(a);else if(!i&&n){const l=a?this.#t.has(a):!1;this.emit("row-commit",{rowIndex:e,rowId:a??"",row:n,changed:l,changedRows:this.changedRows,changedRowIds:this.changedRowIds})}this.#r.delete(e),this.#e=-1,this.#s=-1,this.#a();for(const l of this.#n)l.startsWith(`${e}:`)&&this.#n.delete(l);o&&(o.querySelectorAll(".cell.editing").forEach(l=>{l.classList.remove("editing"),H(l.parentElement)}),this.requestRender()),this.#o=!0,o||(this.#u(t),this.#o=!1)}#l(e,i,t,r){const n=i.field;if(!y(n))return;const o=r[n];if(o===t)return;const a=this.grid;let l;try{l=this.grid.getRowId(r)}catch{}const c=l?!this.#t.has(l):!0,f=l?h=>this.grid.updateRow(l,h,"cascade"):L;if(this.emitCancelable("cell-commit",{row:r,rowId:l??"",field:n,oldValue:o,value:t,rowIndex:e,changedRows:this.changedRows,changedRowIds:this.changedRowIds,firstTimeForRow:c,updateRow:f}))return;r[n]=t,l&&this.#t.add(l),this.#a();const d=a.findRenderedRowElement?.(e);d&&d.classList.add("changed")}#d(e,i,t,r,n,o){if(!t.editable||n.classList.contains("editing"))return;let a;try{a=this.grid.getRowId(e)}catch{}const l=a?u=>this.grid.updateRow(a,u,"cascade"):L,c=y(t.field)?e[t.field]:void 0;n.classList.add("editing"),this.#n.add(`${i}:${r}`);const f=n.parentElement;f&&M(f);let p=!1;const d=u=>{p||this.#e===-1||this.#l(i,t,u,e)},h=()=>{p=!0,y(t.field)&&(e[t.field]=c)},g=document.createElement("div");g.className="tbw-editor-host",n.innerHTML="",n.appendChild(g),g.addEventListener("keydown",u=>{u.key==="Enter"&&(u.stopPropagation(),u.preventDefault(),p=!0,this.#i(i,!1)),u.key==="Escape"&&(u.stopPropagation(),u.preventDefault(),h(),this.#i(i,!0))});const m=t,R=m.__editorTemplate,w=G(this.grid,m)??k(t),S=c;if(w==="template"&&R)this.#h(g,m,e,c,d,h,o,i);else if(typeof w=="string"){const u=document.createElement(w);u.value=S,u.addEventListener("change",()=>d(u.value)),g.appendChild(u),o||queueMicrotask(()=>{g.querySelector(_)?.focus({preventScroll:!0})})}else if(typeof w=="function"){const u={row:e,rowId:a??"",value:S,field:t.field,column:t,commit:d,cancel:h,updateRow:l},b=w(u);typeof b=="string"?(g.innerHTML=b,N(g,t,d)):b instanceof Node&&g.appendChild(b),o||queueMicrotask(()=>{g.querySelector(_)?.focus({preventScroll:!0})})}else if(w&&typeof w=="object"){const u=document.createElement("div");u.setAttribute("data-external-editor",""),u.setAttribute("data-field",t.field),g.appendChild(u);const b={row:e,rowId:a??"",value:S,field:t.field,column:t,commit:d,cancel:h,updateRow:l};if(w.mount)try{w.mount({placeholder:u,context:b,spec:w})}catch(T){console.warn(`[tbw-grid] External editor mount error for column '${t.field}':`,T)}else this.grid.dispatchEvent(new CustomEvent("mount-external-editor",{detail:{placeholder:u,spec:w,context:b}}))}}#h(e,i,t,r,n,o,a,l){const c=i.__editorTemplate;if(!c)return;const f=c.cloneNode(!0),p=i.__compiledEditor;p?f.innerHTML=p({row:t,value:r,field:i.field,column:i,commit:n,cancel:o}):f.querySelectorAll("*").forEach(h=>{h.childNodes.length===1&&h.firstChild?.nodeType===Node.TEXT_NODE&&(h.textContent=h.textContent?.replace(/{{\s*value\s*}}/g,r==null?"":String(r)).replace(/{{\s*row\.([a-zA-Z0-9_]+)\s*}}/g,(g,m)=>{if(!y(m))return"";const R=t[m];return R==null?"":String(R)})||"")});const d=f.querySelector("input,textarea,select");if(d){d instanceof HTMLInputElement&&d.type==="checkbox"?d.checked=!!r:d.value=String(r??"");let h=!1;d.addEventListener("blur",()=>{h||n(C(d,i))}),d.addEventListener("keydown",g=>{const m=g;m.key==="Enter"&&(m.stopPropagation(),m.preventDefault(),h=!0,n(C(d,i)),this.#i(l,!1)),m.key==="Escape"&&(m.stopPropagation(),m.preventDefault(),o(),this.#i(l,!0))}),d instanceof HTMLInputElement&&d.type==="checkbox"&&d.addEventListener("change",()=>n(d.checked)),a||setTimeout(()=>d.focus({preventScroll:!0}),0)}e.appendChild(f)}#u(e){queueMicrotask(()=>{try{const i=e._focusRow,t=e._focusCol,r=e.findRenderedRowElement?.(i);if(r){Array.from(e._bodyEl.querySelectorAll(".cell-focus")).forEach(o=>o.classList.remove("cell-focus"));const n=r.querySelector(`.cell[data-row="${i}"][data-col="${t}"]`);n&&(n.classList.add("cell-focus"),n.setAttribute("aria-selected","true"),n.hasAttribute("tabindex")||n.setAttribute("tabindex","-1"),n.focus({preventScroll:!0}))}}catch{}})}}E.EditingPlugin=$,E.defaultEditorFor=k,Object.defineProperty(E,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(E,v){typeof exports=="object"&&typeof module<"u"?v(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],v):(E=typeof globalThis<"u"?globalThis:E||self,v(E.TbwGridPlugin_editing={},E.TbwGrid))})(this,(function(E,v){"use strict";const P="@layer tbw-plugins{tbw-grid{--tbw-editing-bg: var(--tbw-color-selection);--tbw-editing-row-bg: var(--tbw-editing-bg);--tbw-editing-border: var(--tbw-border-input, 1px solid var(--tbw-color-border-strong));--tbw-padding-editing-input: var(--tbw-cell-padding-input, 2px 6px);--tbw-font-size-editor: inherit;--tbw-editing-row-outline-color: var(--tbw-color-accent);--tbw-editing-row-outline-width: 1px}tbw-grid .data-grid-row:has(.editing){background:var(--tbw-editing-row-bg);outline:var(--tbw-editing-row-outline-width) solid var(--tbw-editing-row-outline-color);outline-offset:calc(-1 * var(--tbw-editing-row-outline-width))}tbw-grid .data-grid-row>.cell.editing{overflow:hidden;padding:0;display:flex;min-height:calc(var(--tbw-row-height) + 2px);align-items:center;justify-content:center}tbw-grid .data-grid-row>.cell.editing input:not([type=checkbox]),tbw-grid .data-grid-row>.cell.editing select,tbw-grid .data-grid-row>.cell.editing textarea{width:100%;height:100%;flex:1 1 auto;min-width:0;border:var(--tbw-editing-border);padding:var(--tbw-padding-editing-input);font-size:var(--tbw-font-size-editor)}tbw-grid .tbw-editor-host{display:contents}}";function A(s){const e=s.options;return e?typeof e=="function"?e():e:[]}function q(s){return e=>{const i=s.editorParams,t=document.createElement("input");t.type="number",t.value=e.value!=null?String(e.value):"",i?.min!==void 0&&(t.min=String(i.min)),i?.max!==void 0&&(t.max=String(i.max)),i?.step!==void 0&&(t.step=String(i.step)),i?.placeholder&&(t.placeholder=i.placeholder);const r=()=>e.commit(t.value===""?null:Number(t.value));return t.addEventListener("blur",r),t.addEventListener("keydown",n=>{n.key==="Enter"&&r(),n.key==="Escape"&&e.cancel()}),t}}function O(){return s=>{const e=document.createElement("input");return e.type="checkbox",e.checked=!!s.value,e.addEventListener("change",()=>s.commit(e.checked)),e}}function D(s){return e=>{const i=s.editorParams,t=document.createElement("input");return t.type="date",e.value instanceof Date&&(t.valueAsDate=e.value),i?.min&&(t.min=i.min),i?.max&&(t.max=i.max),i?.placeholder&&(t.placeholder=i.placeholder),t.addEventListener("change",()=>e.commit(t.valueAsDate)),t.addEventListener("keydown",r=>{r.key==="Escape"&&e.cancel()}),t}}function I(s){return e=>{const i=s.editorParams,t=document.createElement("select");if(s.multi&&(t.multiple=!0),i?.includeEmpty){const o=document.createElement("option");o.value="",o.textContent=i.emptyLabel??"",t.appendChild(o)}A(s).forEach(o=>{const a=document.createElement("option");a.value=String(o.value),a.textContent=o.label,(s.multi&&Array.isArray(e.value)&&e.value.includes(o.value)||!s.multi&&e.value===o.value)&&(a.selected=!0),t.appendChild(a)});const n=()=>{if(s.multi){const o=Array.from(t.selectedOptions).map(a=>a.value);e.commit(o)}else e.commit(t.value)};return t.addEventListener("change",n),t.addEventListener("blur",n),t.addEventListener("keydown",o=>{o.key==="Escape"&&e.cancel()}),t}}function x(s){return e=>{const i=s.editorParams,t=document.createElement("input");return t.type="text",t.value=e.value!=null?String(e.value):"",i?.maxLength!==void 0&&(t.maxLength=i.maxLength),i?.pattern&&(t.pattern=i.pattern),i?.placeholder&&(t.placeholder=i.placeholder),t.addEventListener("blur",()=>e.commit(t.value)),t.addEventListener("keydown",r=>{r.key==="Enter"&&e.commit(t.value),r.key==="Escape"&&e.cancel()}),t}}function k(s){switch(s.type){case"number":return q(s);case"boolean":return O();case"date":return D(s);case"select":return I(s);default:return x(s)}}const _='input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])';function G(s,e){if(e.editor)return e.editor;if(e.__editorTemplate)return"template";if(!e.type)return;const t=s.effectiveConfig?.typeDefaults;if(t?.[e.type]?.editor)return t[e.type].editor;const r=s.__frameworkAdapter;if(r?.getTypeDefault){const n=r.getTypeDefault(e.type);if(n?.editor)return n.editor}}function y(s){return!(typeof s!="string"||s==="__proto__"||s==="constructor"||s==="prototype")}function M(s){const e=(s.__editingCellCount??0)+1;s.__editingCellCount=e,s.setAttribute("data-has-editing","")}function H(s){s.__editingCellCount=0,s.removeAttribute("data-has-editing")}function C(s,e){return s instanceof HTMLInputElement?s.type==="checkbox"?s.checked:s.type==="number"?s.value===""?null:Number(s.value):s.type==="date"?s.valueAsDate:s.value:e?.type==="number"&&s.value!==""?Number(s.value):s.value}function L(s){}function N(s,e,i){const t=s.querySelector("input,textarea,select");t&&(t.addEventListener("blur",()=>{i(C(t,e))}),t instanceof HTMLInputElement&&t.type==="checkbox"?t.addEventListener("change",()=>i(t.checked)):t instanceof HTMLSelectElement&&t.addEventListener("change",()=>i(C(t,e))))}class $ extends v.BaseGridPlugin{static manifest={ownedProperties:[{property:"editable",level:"column",description:'the "editable" column property',isUsed:e=>e===!0},{property:"editor",level:"column",description:'the "editor" column property'},{property:"editorParams",level:"column",description:'the "editorParams" column property'}]};name="editing";styles=P;get defaultConfig(){return{editOn:"click"}}#e=-1;#s=-1;#r=new Map;#t=new Set;#n=new Set;#o=!1;attach(e){super.attach(e);const i=this.disconnectSignal,t=e;t._activeEditRows=-1,t._rowEditSnapshots=new Map,Object.defineProperty(e,"changedRows",{get:()=>this.changedRows,configurable:!0}),Object.defineProperty(e,"changedRowIds",{get:()=>this.changedRowIds,configurable:!0}),e.resetChangedRows=r=>this.resetChangedRows(r),e.beginBulkEdit=(r,n)=>{n&&this.beginCellEdit(r,n)},document.addEventListener("keydown",r=>{r.key==="Escape"&&this.#e!==-1&&this.#i(this.#e,!0)},{capture:!0,signal:i}),document.addEventListener("mousedown",r=>{if(this.#e===-1)return;const n=t.findRenderedRowElement?.(this.#e);!n||(r.composedPath&&r.composedPath()||[]).includes(n)||this.#i(this.#e,!1)},{signal:i})}detach(){this.#e=-1,this.#s=-1,this.#r.clear(),this.#t.clear(),this.#n.clear(),super.detach()}onCellClick(e){const i=this.grid,t=this.config.editOn??i.effectiveConfig?.editOn;if(t===!1||t==="manual"||t!=="click"&&t!=="dblclick")return!1;const r=e.originalEvent.type==="dblclick";if(t==="click"&&r||t==="dblclick"&&!r)return!1;const{rowIndex:n}=e;return i._columns?.some(a=>a.editable)?(e.originalEvent.stopPropagation(),this.beginBulkEdit(n),!0):!1}onKeyDown(e){const i=this.grid;if(e.key==="Escape"&&this.#e!==-1)return this.#i(this.#e,!0),!0;if(e.key===" "||e.key==="Spacebar"){const t=i._focusRow,r=i._focusCol;if(t>=0&&r>=0){const n=i._visibleColumns[r],o=i._rows[t];if(n?.editable&&n.type==="boolean"&&o){const a=n.field;if(y(a)){const c=!o[a];return this.#l(t,n,c,o),e.preventDefault(),this.requestRender(),!0}}}return!1}if(e.key==="Enter"&&!e.shiftKey){if(this.#e!==-1)return!1;const t=this.config.editOn??i.effectiveConfig?.editOn;if(t===!1||t==="manual")return!1;const r=i._focusRow,n=i._focusCol;if(r>=0&&i._columns?.some(a=>a.editable)){const a=i._visibleColumns[n],l=i._rows[r],c=a?.field??"",f=c&&l?l[c]:void 0,p=this.gridElement.querySelector(`[data-row="${r}"][data-col="${n}"]`),d=new CustomEvent("cell-activate",{cancelable:!0,bubbles:!0,detail:{rowIndex:r,colIndex:n,field:c,value:f,row:l,cellEl:p,trigger:"keyboard",originalEvent:e}});this.gridElement.dispatchEvent(d);const h=new CustomEvent("activate-cell",{cancelable:!0,bubbles:!0,detail:{row:r,col:n}});return this.gridElement.dispatchEvent(h),d.defaultPrevented||h.defaultPrevented?(e.preventDefault(),!0):(this.beginBulkEdit(r),!0)}return!1}return!1}processColumns(e){const i=this.grid,t=i.effectiveConfig?.typeDefaults,r=i.__frameworkAdapter;return!t&&!r?.getTypeDefault?e:e.map(n=>{if(!n.type)return n;let o;if(t?.[n.type]?.editorParams&&(o=t[n.type].editorParams),!o&&r?.getTypeDefault){const a=r.getTypeDefault(n.type);a?.editorParams&&(o=a.editorParams)}return o?{...n,editorParams:{...o,...n.editorParams}}:n})}afterRender(){const e=this.grid;if(this.#o&&(this.#o=!1,this.#u(e)),this.#n.size!==0)for(const i of this.#n){const[t,r]=i.split(":"),n=parseInt(t,10),o=parseInt(r,10),a=e.findRenderedRowElement?.(n);if(!a)continue;const l=a.querySelector(`.cell[data-col="${o}"]`);if(!l||l.classList.contains("editing"))continue;const c=e._rows[n],f=e._visibleColumns[o];c&&f&&this.#d(c,n,f,o,l,!0)}}onScrollRender(){this.afterRender()}get changedRows(){const e=[];for(const i of this.#t){const t=this.grid.getRow(i);t&&e.push(t)}return e}get changedRowIds(){return Array.from(this.#t)}get activeEditRow(){return this.#e}get activeEditCol(){return this.#s}isRowEditing(e){return this.#e===e}isCellEditing(e,i){return this.#n.has(`${e}:${i}`)}isRowChanged(e){const i=this.grid,t=i._rows[e];if(!t)return!1;try{const r=i.getRowId?.(t);return r?this.#t.has(r):!1}catch{return!1}}isRowChangedById(e){return this.#t.has(e)}resetChangedRows(e){const i=this.changedRows,t=this.changedRowIds;this.#t.clear(),this.#a(),e||this.emit("changed-rows-reset",{rows:i,ids:t}),this.grid._rowPool?.forEach(n=>n.classList.remove("changed"))}beginCellEdit(e,i){const t=this.grid,r=t._visibleColumns.findIndex(l=>l.field===i);if(r===-1||!t._visibleColumns[r]?.editable)return;const a=t.findRenderedRowElement?.(e)?.querySelector(`.cell[data-col="${r}"]`);a&&this.#f(e,r,a)}beginBulkEdit(e){const i=this.grid;if((this.config.editOn??i.effectiveConfig?.editOn)===!1||!i._columns?.some(a=>a.editable))return;const n=i.findRenderedRowElement?.(e);if(!n)return;const o=i._rows[e];this.#c(e,o),Array.from(n.children).forEach((a,l)=>{const c=i._visibleColumns[l];if(c?.editable){const f=a;f.classList.contains("editing")||this.#d(o,e,c,l,f,!0)}}),setTimeout(()=>{let a=n.querySelector(`.cell[data-col="${i._focusCol}"]`);if(a?.classList.contains("editing")||(a=n.querySelector(".cell.editing")),a?.classList.contains("editing")){const l=a.querySelector(_);try{l?.focus({preventScroll:!0})}catch{}}},0)}commitActiveRowEdit(){this.#e!==-1&&this.#i(this.#e,!1)}cancelActiveRowEdit(){this.#e!==-1&&this.#i(this.#e,!0)}#f(e,i,t){const r=this.grid,n=r._rows[e],o=r._visibleColumns[i];!n||!o?.editable||t.classList.contains("editing")||(this.#e!==e&&this.#c(e,n),this.#s=i,this.#d(n,e,o,i,t,!1))}#a(){const e=this.grid;e._activeEditRows=this.#e,e._rowEditSnapshots=this.#r}#c(e,i){this.#e!==e&&(this.#r.set(e,{...i}),this.#e=e,this.#a())}#i(e,i){if(this.#e!==e)return;const t=this.grid,r=this.#r.get(e),n=t._rows[e],o=t.findRenderedRowElement?.(e);let a;if(n)try{a=t.getRowId?.(n)}catch{}if(!i&&o&&n&&o.querySelectorAll(".cell.editing").forEach(c=>{const f=Number(c.getAttribute("data-col"));if(isNaN(f))return;const p=t._visibleColumns[f];if(!p)return;const d=c.querySelector("input,textarea,select");if(d){const h=C(d,p);n[p.field]!==h&&this.#l(e,p,h,n)}}),i&&r&&n)Object.keys(r).forEach(l=>{n[l]=r[l]}),a&&this.#t.delete(a);else if(!i&&n){const l=a?this.#t.has(a):!1;this.emit("row-commit",{rowIndex:e,rowId:a??"",row:n,changed:l,changedRows:this.changedRows,changedRowIds:this.changedRowIds})}this.#r.delete(e),this.#e=-1,this.#s=-1,this.#a();for(const l of this.#n)l.startsWith(`${e}:`)&&this.#n.delete(l);o&&(o.querySelectorAll(".cell.editing").forEach(l=>{l.classList.remove("editing"),H(l.parentElement)}),this.requestRender()),this.#o=!0,o||(this.#u(t),this.#o=!1)}#l(e,i,t,r){const n=i.field;if(!y(n))return;const o=r[n];if(o===t)return;const a=this.grid;let l;try{l=this.grid.getRowId(r)}catch{}const c=l?!this.#t.has(l):!0,f=l?h=>this.grid.updateRow(l,h,"cascade"):L;if(this.emitCancelable("cell-commit",{row:r,rowId:l??"",field:n,oldValue:o,value:t,rowIndex:e,changedRows:this.changedRows,changedRowIds:this.changedRowIds,firstTimeForRow:c,updateRow:f}))return;r[n]=t,l&&this.#t.add(l),this.#a();const d=a.findRenderedRowElement?.(e);d&&d.classList.add("changed")}#d(e,i,t,r,n,o){if(!t.editable||n.classList.contains("editing"))return;let a;try{a=this.grid.getRowId(e)}catch{}const l=a?u=>this.grid.updateRow(a,u,"cascade"):L,c=y(t.field)?e[t.field]:void 0;n.classList.add("editing"),this.#n.add(`${i}:${r}`);const f=n.parentElement;f&&M(f);let p=!1;const d=u=>{p||this.#e===-1||this.#l(i,t,u,e)},h=()=>{p=!0,y(t.field)&&(e[t.field]=c)},g=document.createElement("div");g.className="tbw-editor-host",n.innerHTML="",n.appendChild(g),g.addEventListener("keydown",u=>{u.key==="Enter"&&(u.stopPropagation(),u.preventDefault(),p=!0,this.#i(i,!1)),u.key==="Escape"&&(u.stopPropagation(),u.preventDefault(),h(),this.#i(i,!0))});const m=t,R=m.__editorTemplate,w=G(this.grid,m)??k(t),S=c;if(w==="template"&&R)this.#h(g,m,e,c,d,h,o,i);else if(typeof w=="string"){const u=document.createElement(w);u.value=S,u.addEventListener("change",()=>d(u.value)),g.appendChild(u),o||queueMicrotask(()=>{g.querySelector(_)?.focus({preventScroll:!0})})}else if(typeof w=="function"){const u={row:e,rowId:a??"",value:S,field:t.field,column:t,commit:d,cancel:h,updateRow:l},b=w(u);typeof b=="string"?(g.innerHTML=b,N(g,t,d)):b instanceof Node&&g.appendChild(b),o||queueMicrotask(()=>{g.querySelector(_)?.focus({preventScroll:!0})})}else if(w&&typeof w=="object"){const u=document.createElement("div");u.setAttribute("data-external-editor",""),u.setAttribute("data-field",t.field),g.appendChild(u);const b={row:e,rowId:a??"",value:S,field:t.field,column:t,commit:d,cancel:h,updateRow:l};if(w.mount)try{w.mount({placeholder:u,context:b,spec:w})}catch(T){console.warn(`[tbw-grid] External editor mount error for column '${t.field}':`,T)}else this.grid.dispatchEvent(new CustomEvent("mount-external-editor",{detail:{placeholder:u,spec:w,context:b}}))}}#h(e,i,t,r,n,o,a,l){const c=i.__editorTemplate;if(!c)return;const f=c.cloneNode(!0),p=i.__compiledEditor;p?f.innerHTML=p({row:t,value:r,field:i.field,column:i,commit:n,cancel:o}):f.querySelectorAll("*").forEach(h=>{h.childNodes.length===1&&h.firstChild?.nodeType===Node.TEXT_NODE&&(h.textContent=h.textContent?.replace(/{{\s*value\s*}}/g,r==null?"":String(r)).replace(/{{\s*row\.([a-zA-Z0-9_]+)\s*}}/g,(g,m)=>{if(!y(m))return"";const R=t[m];return R==null?"":String(R)})||"")});const d=f.querySelector("input,textarea,select");if(d){d instanceof HTMLInputElement&&d.type==="checkbox"?d.checked=!!r:d.value=String(r??"");let h=!1;d.addEventListener("blur",()=>{h||n(C(d,i))}),d.addEventListener("keydown",g=>{const m=g;m.key==="Enter"&&(m.stopPropagation(),m.preventDefault(),h=!0,n(C(d,i)),this.#i(l,!1)),m.key==="Escape"&&(m.stopPropagation(),m.preventDefault(),o(),this.#i(l,!0))}),d instanceof HTMLInputElement&&d.type==="checkbox"&&d.addEventListener("change",()=>n(d.checked)),a||setTimeout(()=>d.focus({preventScroll:!0}),0)}e.appendChild(f)}#u(e){queueMicrotask(()=>{try{const i=e._focusRow,t=e._focusCol,r=e.findRenderedRowElement?.(i);if(r){Array.from(e._bodyEl.querySelectorAll(".cell-focus")).forEach(o=>o.classList.remove("cell-focus"));const n=r.querySelector(`.cell[data-row="${i}"][data-col="${t}"]`);n&&(n.classList.add("cell-focus"),n.setAttribute("aria-selected","true"),n.hasAttribute("tabindex")||n.setAttribute("tabindex","-1"),n.focus({preventScroll:!0}))}}catch{}})}}E.EditingPlugin=$,E.defaultEditorFor=k,Object.defineProperty(E,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=editing.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editing.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/editing/editors.ts","../../../../../libs/grid/src/lib/plugins/editing/EditingPlugin.ts"],"sourcesContent":["/**\n * Default Editors Module\n *\n * Provides built-in editor factories for different column types.\n * Each editor type has its own factory function for consistency and readability.\n *\n * IMPORTANT: Editor factories should NOT call focus() on elements - they are called\n * before the element is appended to the DOM. The calling code (beginBulkEdit,\n * inlineEnterEdit) is responsible for focusing the correct editor after insertion.\n */\n\nimport type { ColumnConfig, EditorContext } from '../../core/types';\nimport type { DateEditorParams, NumberEditorParams, SelectEditorParams, TextEditorParams } from './types';\n\n// ============================================================================\n// Type Aliases\n// ============================================================================\n\n/** Option shape used by select editor (matches column.options) */\ntype ColumnOption = { label: string; value: unknown };\n\n/** Column with any row type (used for editor factories) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyColumn = ColumnConfig<any>;\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/** Resolve column.options (handles both array and function forms) */\nfunction resolveOptions(column: AnyColumn): ColumnOption[] {\n const raw = column.options;\n if (!raw) return [];\n return typeof raw === 'function' ? raw() : raw;\n}\n\n// ============================================================================\n// Editor Factories\n// ============================================================================\n\n/** Creates a number input editor */\nfunction createNumberEditor(column: AnyColumn): (ctx: EditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as NumberEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'number';\n input.value = ctx.value != null ? String(ctx.value) : '';\n\n if (params?.min !== undefined) input.min = String(params.min);\n if (params?.max !== undefined) input.max = String(params.max);\n if (params?.step !== undefined) input.step = String(params.step);\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n const commit = () => ctx.commit(input.value === '' ? null : Number(input.value));\n input.addEventListener('blur', commit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') commit();\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n/** Creates a checkbox editor for boolean values */\nfunction createBooleanEditor(): (ctx: EditorContext) => HTMLElement {\n return (ctx) => {\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = !!ctx.value;\n input.addEventListener('change', () => ctx.commit(input.checked));\n return input;\n };\n}\n\n/** Creates a date input editor */\nfunction createDateEditor(column: AnyColumn): (ctx: EditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as DateEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'date';\n\n if (ctx.value instanceof Date) input.valueAsDate = ctx.value;\n if (params?.min) input.min = params.min;\n if (params?.max) input.max = params.max;\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n input.addEventListener('change', () => ctx.commit(input.valueAsDate));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n/** Creates a select dropdown editor */\nfunction createSelectEditor(column: AnyColumn): (ctx: EditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as SelectEditorParams | undefined;\n const select = document.createElement('select');\n if (column.multi) select.multiple = true;\n\n // Add empty option if requested\n if (params?.includeEmpty) {\n const emptyOpt = document.createElement('option');\n emptyOpt.value = '';\n emptyOpt.textContent = params.emptyLabel ?? '';\n select.appendChild(emptyOpt);\n }\n\n // Populate options from column.options\n const options = resolveOptions(column);\n options.forEach((opt) => {\n const o = document.createElement('option');\n o.value = String(opt.value);\n o.textContent = opt.label;\n if (column.multi && Array.isArray(ctx.value) && ctx.value.includes(opt.value)) {\n o.selected = true;\n } else if (!column.multi && ctx.value === opt.value) {\n o.selected = true;\n }\n select.appendChild(o);\n });\n\n const commit = () => {\n if (column.multi) {\n const values = Array.from(select.selectedOptions).map((o) => o.value);\n ctx.commit(values);\n } else {\n ctx.commit(select.value);\n }\n };\n\n select.addEventListener('change', commit);\n select.addEventListener('blur', commit);\n select.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return select;\n };\n}\n\n/** Creates a text input editor (default) */\nfunction createTextEditor(column: AnyColumn): (ctx: EditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as TextEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'text';\n input.value = ctx.value != null ? String(ctx.value) : '';\n\n if (params?.maxLength !== undefined) input.maxLength = params.maxLength;\n if (params?.pattern) input.pattern = params.pattern;\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n input.addEventListener('blur', () => ctx.commit(input.value));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') ctx.commit(input.value);\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\n/**\n * Returns a default editor factory function for the given column type.\n * Each editor handles commit on blur/Enter, and cancel on Escape.\n *\n * Note: Focus is NOT called here - the calling code handles focusing after DOM insertion.\n */\nexport function defaultEditorFor(column: AnyColumn): (ctx: EditorContext) => HTMLElement | string {\n switch (column.type) {\n case 'number':\n return createNumberEditor(column);\n case 'boolean':\n return createBooleanEditor();\n case 'date':\n return createDateEditor(column);\n case 'select':\n return createSelectEditor(column);\n default:\n return createTextEditor(column);\n }\n}\n\n// ============================================================================\n// Utility Export (used by EditingPlugin)\n// ============================================================================\n\n/**\n * Gets the current value from an input element, with type coercion based on column type.\n */\nexport function getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n col: AnyColumn,\n): unknown {\n if (input instanceof HTMLSelectElement) {\n if (col.multi) {\n return Array.from(input.selectedOptions).map((o) => o.value);\n }\n return input.value;\n }\n if (input instanceof HTMLInputElement) {\n if (input.type === 'checkbox') return input.checked;\n if (input.type === 'number') return input.value === '' ? null : Number(input.value);\n if (input.type === 'date') return input.valueAsDate;\n }\n return input.value;\n}\n","/**\n * Editing Plugin\n *\n * Provides complete editing functionality for tbw-grid.\n * This plugin is FULLY SELF-CONTAINED - the grid has ZERO editing knowledge.\n *\n * The plugin:\n * - Owns all editing state (active cell, snapshots, changed rows)\n * - Uses event distribution (onCellClick, onKeyDown) to handle edit lifecycle\n * - Uses afterRender() hook to inject editors into cells\n * - Uses processColumns() to augment columns with editing metadata\n * - Emits its own events (cell-commit, row-commit, changed-rows-reset)\n *\n * Without this plugin, the grid cannot edit. With this plugin, editing\n * is fully functional without any core changes.\n */\n\nimport { BaseGridPlugin, type CellClickEvent, type GridElement } from '../../core/plugin/base-plugin';\nimport type {\n ColumnConfig,\n ColumnEditorSpec,\n ColumnInternal,\n InternalGrid,\n RowElementInternal,\n} from '../../core/types';\nimport styles from './editing.css?inline';\nimport { defaultEditorFor } from './editors';\nimport type { CellCommitDetail, ChangedRowsResetDetail, EditingConfig, EditorContext, RowCommitDetail } from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * CSS selector for focusable editor elements within a cell.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Resolves the editor for a column using the priority chain:\n * 1. Column-level (`column.editor`)\n * 2. Light DOM template (`__editorTemplate` → returns 'template')\n * 3. Grid-level (`gridConfig.typeDefaults[column.type]`)\n * 4. App-level (framework adapter's `getTypeDefault`)\n * 5. Returns undefined (caller uses built-in defaultEditorFor)\n */\nfunction resolveEditor<TRow>(\n grid: InternalGrid<TRow>,\n col: ColumnInternal<TRow>,\n): ColumnEditorSpec<TRow, unknown> | 'template' | undefined {\n // 1. Column-level editor (highest priority)\n if (col.editor) return col.editor;\n\n // 2. Light DOM template\n const tplHolder = col.__editorTemplate;\n if (tplHolder) return 'template';\n\n // No type specified - no type defaults to check\n if (!col.type) return undefined;\n\n // 3. Grid-level typeDefaults (access via effectiveConfig)\n const gridTypeDefaults = (grid as any).effectiveConfig?.typeDefaults;\n if (gridTypeDefaults?.[col.type]?.editor) {\n return gridTypeDefaults[col.type].editor as ColumnEditorSpec<TRow, unknown>;\n }\n\n // 4. App-level registry (via framework adapter)\n const adapter = grid.__frameworkAdapter;\n if (adapter?.getTypeDefault) {\n const appDefault = adapter.getTypeDefault<TRow>(col.type);\n if (appDefault?.editor) {\n return appDefault.editor as ColumnEditorSpec<TRow, unknown>;\n }\n }\n\n // 5. No custom editor - caller uses built-in defaultEditorFor\n return undefined;\n}\n\n/**\n * Returns true if the given property key is safe to use on a plain object.\n */\nfunction isSafePropertyKey(key: unknown): key is string {\n if (typeof key !== 'string') return false;\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') return false;\n return true;\n}\n\n/**\n * Check if a row element has any cells in editing mode.\n */\nexport function hasEditingCells(rowEl: RowElementInternal): boolean {\n return (rowEl.__editingCellCount ?? 0) > 0;\n}\n\n/**\n * Increment the editing cell count on a row element.\n */\nfunction incrementEditingCount(rowEl: RowElementInternal): void {\n const count = (rowEl.__editingCellCount ?? 0) + 1;\n rowEl.__editingCellCount = count;\n rowEl.setAttribute('data-has-editing', '');\n}\n\n/**\n * Clear all editing state from a row element.\n */\nexport function clearEditingState(rowEl: RowElementInternal): void {\n rowEl.__editingCellCount = 0;\n rowEl.removeAttribute('data-has-editing');\n}\n\n/**\n * Get the typed value from an input element based on its type and column config.\n */\nfunction getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n column?: ColumnConfig<any>,\n): unknown {\n if (input instanceof HTMLInputElement) {\n if (input.type === 'checkbox') return input.checked;\n if (input.type === 'number') return input.value === '' ? null : Number(input.value);\n if (input.type === 'date') return input.valueAsDate;\n return input.value;\n }\n if (column?.type === 'number' && input.value !== '') {\n return Number(input.value);\n }\n return input.value;\n}\n\n/**\n * No-op updateRow function for rows without IDs.\n * Extracted to a named function to satisfy eslint no-empty-function.\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction noopUpdateRow(_changes: unknown): void {\n // Row has no ID - cannot update\n}\n\n/**\n * Auto-wire commit/cancel lifecycle for input elements in string-returned editors.\n */\nfunction wireEditorInputs(\n editorHost: HTMLElement,\n column: ColumnConfig<unknown>,\n commit: (value: unknown) => void,\n): void {\n const input = editorHost.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (!input) return;\n\n input.addEventListener('blur', () => {\n commit(getInputValue(input, column));\n });\n\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n } else if (input instanceof HTMLSelectElement) {\n input.addEventListener('change', () => commit(getInputValue(input, column)));\n }\n}\n\n// ============================================================================\n// EditingPlugin\n// ============================================================================\n\n/**\n * Editing Plugin for tbw-grid\n *\n * Enables inline cell editing in the grid. Provides built-in editors for common data types\n * and supports custom editor functions for specialized input scenarios.\n *\n * ## Why Opt-In?\n *\n * Editing is delivered as a plugin rather than built into the core grid:\n *\n * - **Smaller bundle** — Apps that only display data don't pay for editing code\n * - **Clear intent** — Explicit plugin registration makes editing capability obvious\n * - **Runtime validation** — Using `editable: true` without the plugin throws a helpful error\n *\n * ## Installation\n *\n * ```ts\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n * ```\n *\n * ## Edit Triggers\n *\n * Configure how editing is triggered with the `editOn` option:\n *\n * | Value | Behavior |\n * |-------|----------|\n * | `'click'` | Single click enters edit mode (default) |\n * | `'dblclick'` | Double-click enters edit mode |\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Enter` | Commit edit and move down |\n * | `Tab` | Commit edit and move right |\n * | `Escape` | Cancel edit, restore original value |\n * | `Arrow Keys` | Navigate between cells (when not editing) |\n *\n * @example Basic editing with double-click trigger\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', editable: true },\n * { field: 'price', type: 'number', editable: true },\n * { field: 'active', type: 'boolean', editable: true },\n * ],\n * plugins: [new EditingPlugin({ editOn: 'dblclick' })],\n * };\n *\n * grid.addEventListener('cell-commit', (e) => {\n * const { field, oldValue, newValue } = e.detail;\n * console.log(`${field}: ${oldValue} → ${newValue}`);\n * });\n * ```\n *\n * @example Custom editor function\n * ```ts\n * columns: [\n * {\n * field: 'status',\n * editable: true,\n * editor: (ctx) => {\n * const select = document.createElement('select');\n * ['pending', 'active', 'completed'].forEach(opt => {\n * const option = document.createElement('option');\n * option.value = opt;\n * option.textContent = opt;\n * option.selected = ctx.value === opt;\n * select.appendChild(option);\n * });\n * select.addEventListener('change', () => ctx.commit(select.value));\n * return select;\n * },\n * },\n * ]\n * ```\n *\n * @see {@link EditingConfig} for configuration options\n * @see {@link EditorContext} for custom editor context\n * @see [Live Demos](?path=/docs/grid-plugins-editing--docs) for interactive examples\n */\nexport class EditingPlugin<T = unknown> extends BaseGridPlugin<EditingConfig> {\n /** @internal */\n readonly name = 'editing';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<EditingConfig> {\n return {\n editOn: 'click',\n };\n }\n\n // #region Editing State (fully owned by plugin)\n\n /** Currently active edit row index, or -1 if not editing */\n #activeEditRow = -1;\n\n /** Currently active edit column index, or -1 if not editing */\n #activeEditCol = -1;\n\n /** Snapshots of row data before editing started */\n #rowEditSnapshots = new Map<number, T>();\n\n /** Set of row IDs that have been modified (ID-based for stability) */\n #changedRowIds = new Set<string>();\n\n /** Set of cells currently in edit mode: \"rowIndex:colIndex\" */\n #editingCells = new Set<string>();\n\n /** Flag to restore focus after next render (used when exiting edit mode) */\n #pendingFocusRestore = false;\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n const signal = this.disconnectSignal;\n const internalGrid = grid as unknown as InternalGrid<T>;\n\n // Inject editing state and methods onto grid for backward compatibility\n internalGrid._activeEditRows = -1;\n internalGrid._rowEditSnapshots = new Map();\n\n // Inject changedRows getter\n Object.defineProperty(grid, 'changedRows', {\n get: () => this.changedRows,\n configurable: true,\n });\n\n // Inject changedRowIds getter (new ID-based API)\n Object.defineProperty(grid, 'changedRowIds', {\n get: () => this.changedRowIds,\n configurable: true,\n });\n\n // Inject resetChangedRows method\n (grid as any).resetChangedRows = (silent?: boolean) => this.resetChangedRows(silent);\n\n // Inject beginBulkEdit method (for backward compatibility)\n (grid as any).beginBulkEdit = (rowIndex: number, field?: string) => {\n if (field) {\n this.beginCellEdit(rowIndex, field);\n }\n // If no field specified, we can't start editing without a specific cell\n };\n\n // Document-level Escape to cancel editing\n document.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n },\n { capture: true, signal },\n );\n\n // Click outside to commit editing\n document.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\n if (this.#activeEditRow === -1) return;\n const rowEl = internalGrid.findRenderedRowElement?.(this.#activeEditRow);\n if (!rowEl) return;\n const path = (e.composedPath && e.composedPath()) || [];\n if (path.includes(rowEl)) return;\n this.#exitRowEdit(this.#activeEditRow, false);\n },\n { signal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#rowEditSnapshots.clear();\n this.#changedRowIds.clear();\n this.#editingCells.clear();\n super.detach();\n }\n\n // #endregion\n\n // #region Event Handlers (event distribution)\n\n /**\n * Handle cell clicks - start editing if configured for click mode.\n * Both click and dblclick events come through this handler.\n * Starts row-based editing (all editable cells in the row get editors).\n * @internal\n */\n override onCellClick(event: CellClickEvent): boolean | void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n\n // Check if editing is disabled\n if (editOn === false || editOn === 'manual') return false;\n\n // Check if this is click or dblclick mode\n if (editOn !== 'click' && editOn !== 'dblclick') return false;\n\n // Check if the event type matches the edit mode\n const isDoubleClick = event.originalEvent.type === 'dblclick';\n if (editOn === 'click' && isDoubleClick) return false; // In click mode, only handle single clicks\n if (editOn === 'dblclick' && !isDoubleClick) return false; // In dblclick mode, only handle double clicks\n\n const { rowIndex } = event;\n\n // Check if any column in the row is editable\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (!hasEditableColumn) return false;\n\n // Start row-based editing (all editable cells get editors)\n event.originalEvent.stopPropagation();\n this.beginBulkEdit(rowIndex);\n return true; // Handled\n }\n\n /**\n * Handle keyboard events for edit lifecycle.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n\n // Escape: cancel current edit\n if (event.key === 'Escape' && this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n return true;\n }\n\n // Space: toggle boolean cells\n if (event.key === ' ' || event.key === 'Spacebar') {\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0 && focusCol >= 0) {\n const column = internalGrid._visibleColumns[focusCol];\n const rowData = internalGrid._rows[focusRow];\n if (column?.editable && column.type === 'boolean' && rowData) {\n const field = column.field;\n if (isSafePropertyKey(field)) {\n const currentValue = (rowData as Record<string, unknown>)[field];\n const newValue = !currentValue;\n this.#commitCellValue(focusRow, column, newValue, rowData);\n event.preventDefault();\n // Re-render to update the UI\n this.requestRender();\n return true;\n }\n }\n }\n // Space on non-boolean cell - don't block keyboard navigation\n return false;\n }\n\n // Enter: start row edit or commit\n if (event.key === 'Enter' && !event.shiftKey) {\n if (this.#activeEditRow !== -1) {\n // Already editing - let cell handlers deal with it\n return false;\n }\n\n // Start row-based editing (not just the focused cell)\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false || editOn === 'manual') return false;\n\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0) {\n // Check if ANY column in the row is editable\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (hasEditableColumn) {\n // Emit cell-activate event BEFORE starting edit\n // This ensures consumers always get the activation event\n const column = internalGrid._visibleColumns[focusCol];\n const row = internalGrid._rows[focusRow];\n const field = column?.field ?? '';\n const value = field && row ? (row as Record<string, unknown>)[field] : undefined;\n const cellEl = this.gridElement.querySelector(`[data-row=\"${focusRow}\"][data-col=\"${focusCol}\"]`) as\n | HTMLElement\n | undefined;\n\n const activateEvent = new CustomEvent('cell-activate', {\n cancelable: true,\n bubbles: true,\n detail: {\n rowIndex: focusRow,\n colIndex: focusCol,\n field,\n value,\n row,\n cellEl,\n trigger: 'keyboard' as const,\n originalEvent: event,\n },\n });\n this.gridElement.dispatchEvent(activateEvent);\n\n // Also emit deprecated activate-cell for backwards compatibility\n const legacyEvent = new CustomEvent('activate-cell', {\n cancelable: true,\n bubbles: true,\n detail: { row: focusRow, col: focusCol },\n });\n this.gridElement.dispatchEvent(legacyEvent);\n\n // If consumer canceled the activation, don't start editing\n if (activateEvent.defaultPrevented || legacyEvent.defaultPrevented) {\n event.preventDefault();\n return true;\n }\n\n this.beginBulkEdit(focusRow);\n return true;\n }\n }\n // No editable columns - don't block keyboard navigation\n return false;\n }\n\n // Don't block other keyboard events\n return false;\n }\n\n // #endregion\n\n // #region Render Hooks\n\n /**\n * Process columns to merge type-level editorParams with column-level.\n * Column-level params take precedence.\n * @internal\n */\n override processColumns(columns: ColumnConfig<T>[]): ColumnConfig<T>[] {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const typeDefaults = (internalGrid as any).effectiveConfig?.typeDefaults;\n const adapter = internalGrid.__frameworkAdapter;\n\n // If no type defaults configured anywhere, skip processing\n if (!typeDefaults && !adapter?.getTypeDefault) return columns;\n\n return columns.map((col) => {\n if (!col.type) return col;\n\n // Get type-level editorParams\n let typeEditorParams: Record<string, unknown> | undefined;\n\n // Check grid-level typeDefaults first\n if (typeDefaults?.[col.type]?.editorParams) {\n typeEditorParams = typeDefaults[col.type].editorParams;\n }\n\n // Then check app-level (adapter) typeDefaults\n if (!typeEditorParams && adapter?.getTypeDefault) {\n const appDefault = adapter.getTypeDefault<T>(col.type);\n if (appDefault?.editorParams) {\n typeEditorParams = appDefault.editorParams;\n }\n }\n\n // No type-level params to merge\n if (!typeEditorParams) return col;\n\n // Merge: type-level as base, column-level wins on conflicts\n return {\n ...col,\n editorParams: { ...typeEditorParams, ...col.editorParams },\n };\n });\n }\n\n /**\n * After render, reapply editors to cells in edit mode.\n * This handles virtualization - when a row scrolls back into view,\n * we need to re-inject the editor.\n * @internal\n */\n override afterRender(): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n\n // Restore focus after exiting edit mode\n if (this.#pendingFocusRestore) {\n this.#pendingFocusRestore = false;\n this.#restoreCellFocus(internalGrid);\n }\n\n if (this.#editingCells.size === 0) return;\n\n // Re-inject editors for any editing cells that are visible\n for (const cellKey of this.#editingCells) {\n const [rowStr, colStr] = cellKey.split(':');\n const rowIndex = parseInt(rowStr, 10);\n const colIndex = parseInt(colStr, 10);\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) continue;\n\n const cellEl = rowEl.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl || cellEl.classList.contains('editing')) continue;\n\n // Cell is visible but not in editing mode - reinject editor\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n if (rowData && column) {\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, true);\n }\n }\n }\n\n /**\n * On scroll render, reapply editors to recycled cells.\n * @internal\n */\n override onScrollRender(): void {\n this.afterRender();\n }\n\n // #endregion\n\n // #region Public API\n\n /**\n * Get all rows that have been modified.\n * Uses ID-based lookup for stability when rows are reordered.\n */\n get changedRows(): T[] {\n const rows: T[] = [];\n for (const id of this.#changedRowIds) {\n const row = this.grid.getRow(id) as T | undefined;\n if (row) rows.push(row);\n }\n return rows;\n }\n\n /**\n * Get IDs of all modified rows.\n */\n get changedRowIds(): string[] {\n return Array.from(this.#changedRowIds);\n }\n\n /**\n * Get the currently active edit row index, or -1 if not editing.\n */\n get activeEditRow(): number {\n return this.#activeEditRow;\n }\n\n /**\n * Get the currently active edit column index, or -1 if not editing.\n */\n get activeEditCol(): number {\n return this.#activeEditCol;\n }\n\n /**\n * Check if a specific row is currently being edited.\n */\n isRowEditing(rowIndex: number): boolean {\n return this.#activeEditRow === rowIndex;\n }\n\n /**\n * Check if a specific cell is currently being edited.\n */\n isCellEditing(rowIndex: number, colIndex: number): boolean {\n return this.#editingCells.has(`${rowIndex}:${colIndex}`);\n }\n\n /**\n * Check if a specific row has been modified.\n * @param rowIndex - Row index to check (will be converted to ID internally)\n */\n isRowChanged(rowIndex: number): boolean {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const row = internalGrid._rows[rowIndex];\n if (!row) return false;\n try {\n const rowId = internalGrid.getRowId?.(row);\n return rowId ? this.#changedRowIds.has(rowId) : false;\n } catch {\n return false;\n }\n }\n\n /**\n * Check if a row with the given ID has been modified.\n * @param rowId - Row ID to check\n */\n isRowChangedById(rowId: string): boolean {\n return this.#changedRowIds.has(rowId);\n }\n\n /**\n * Reset all change tracking.\n * @param silent - If true, suppresses the `changed-rows-reset` event\n * @fires changed-rows-reset - Emitted when tracking is reset (unless silent)\n */\n resetChangedRows(silent?: boolean): void {\n const rows = this.changedRows;\n const ids = this.changedRowIds;\n this.#changedRowIds.clear();\n this.#syncGridEditState();\n\n if (!silent) {\n this.emit<ChangedRowsResetDetail<T>>('changed-rows-reset', { rows, ids });\n }\n\n // Clear visual indicators\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n internalGrid._rowPool?.forEach((r) => r.classList.remove('changed'));\n }\n\n /**\n * Programmatically begin editing a cell.\n * @param rowIndex - Index of the row to edit\n * @param field - Field name of the column to edit\n * @fires cell-commit - Emitted when the cell value is committed (on blur or Enter)\n */\n beginCellEdit(rowIndex: number, field: string): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const colIndex = internalGrid._visibleColumns.findIndex((c) => c.field === field);\n if (colIndex === -1) return;\n\n const column = internalGrid._visibleColumns[colIndex];\n if (!column?.editable) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl) return;\n\n this.#beginCellEdit(rowIndex, colIndex, cellEl);\n }\n\n /**\n * Programmatically begin editing all editable cells in a row.\n * @param rowIndex - Index of the row to edit\n * @fires cell-commit - Emitted for each cell value that is committed\n * @fires row-commit - Emitted when focus leaves the row\n */\n beginBulkEdit(rowIndex: number): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false) return;\n\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (!hasEditableColumn) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) return;\n\n // Start row edit\n const rowData = internalGrid._rows[rowIndex];\n this.#startRowEdit(rowIndex, rowData);\n\n // Enter edit mode on all editable cells\n Array.from(rowEl.children).forEach((cell, i) => {\n const col = internalGrid._visibleColumns[i];\n if (col?.editable) {\n const cellEl = cell as HTMLElement;\n if (!cellEl.classList.contains('editing')) {\n this.#injectEditor(rowData, rowIndex, col, i, cellEl, true);\n }\n }\n });\n\n // Focus the first editable cell\n setTimeout(() => {\n let targetCell = rowEl.querySelector(`.cell[data-col=\"${internalGrid._focusCol}\"]`);\n if (!targetCell?.classList.contains('editing')) {\n targetCell = rowEl.querySelector('.cell.editing');\n }\n if (targetCell?.classList.contains('editing')) {\n const editor = (targetCell as HTMLElement).querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n try {\n editor?.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n }\n }, 0);\n }\n\n /**\n * Commit the currently active row edit.\n * @fires row-commit - Emitted after the row edit is committed\n */\n commitActiveRowEdit(): void {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, false);\n }\n }\n\n /**\n * Cancel the currently active row edit.\n */\n cancelActiveRowEdit(): void {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n }\n\n // #endregion\n\n // #region Internal Methods\n\n /**\n * Begin editing a single cell.\n */\n #beginCellEdit(rowIndex: number, colIndex: number, cellEl: HTMLElement): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n\n if (!rowData || !column?.editable) return;\n if (cellEl.classList.contains('editing')) return;\n\n // Start row edit if not already\n if (this.#activeEditRow !== rowIndex) {\n this.#startRowEdit(rowIndex, rowData);\n }\n\n this.#activeEditCol = colIndex;\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, false);\n }\n\n /**\n * Sync the internal grid state with the plugin's editing state.\n */\n #syncGridEditState(): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n internalGrid._activeEditRows = this.#activeEditRow;\n internalGrid._rowEditSnapshots = this.#rowEditSnapshots;\n }\n\n /**\n * Snapshot original row data and mark as editing.\n */\n #startRowEdit(rowIndex: number, rowData: T): void {\n if (this.#activeEditRow !== rowIndex) {\n this.#rowEditSnapshots.set(rowIndex, { ...rowData });\n this.#activeEditRow = rowIndex;\n this.#syncGridEditState();\n }\n }\n\n /**\n * Exit editing for a row.\n */\n #exitRowEdit(rowIndex: number, revert: boolean): void {\n if (this.#activeEditRow !== rowIndex) return;\n\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const snapshot = this.#rowEditSnapshots.get(rowIndex);\n const current = internalGrid._rows[rowIndex];\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n\n // Get row ID for change tracking\n let rowId: string | undefined;\n if (current) {\n try {\n rowId = internalGrid.getRowId?.(current);\n } catch {\n // Row has no ID - skip ID-based tracking\n }\n }\n\n // Collect and commit values from active editors before re-rendering\n if (!revert && rowEl && current) {\n const editingCells = rowEl.querySelectorAll('.cell.editing');\n editingCells.forEach((cell) => {\n const colIndex = Number((cell as HTMLElement).getAttribute('data-col'));\n if (isNaN(colIndex)) return;\n const col = internalGrid._visibleColumns[colIndex];\n if (!col) return;\n const input = cell.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n const val = getInputValue(input, col);\n if (current[col.field as keyof T] !== val) {\n this.#commitCellValue(rowIndex, col, val, current);\n }\n }\n });\n }\n\n // Revert if requested\n if (revert && snapshot && current) {\n Object.keys(snapshot as object).forEach((k) => {\n (current as Record<string, unknown>)[k] = (snapshot as Record<string, unknown>)[k];\n });\n if (rowId) {\n this.#changedRowIds.delete(rowId);\n }\n } else if (!revert && current) {\n const changed = rowId ? this.#changedRowIds.has(rowId) : false;\n this.emit<RowCommitDetail<T>>('row-commit', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n changed,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n });\n }\n\n // Clear editing state\n this.#rowEditSnapshots.delete(rowIndex);\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#syncGridEditState();\n\n // Remove all editing cells for this row\n for (const cellKey of this.#editingCells) {\n if (cellKey.startsWith(`${rowIndex}:`)) {\n this.#editingCells.delete(cellKey);\n }\n }\n\n // Re-render the row to remove editors\n if (rowEl) {\n // Remove editing class and re-render cells\n rowEl.querySelectorAll('.cell.editing').forEach((cell) => {\n cell.classList.remove('editing');\n clearEditingState(cell.parentElement as RowElementInternal);\n });\n\n // Request grid re-render to restore cell content\n this.requestRender();\n }\n\n // Mark that focus should be restored after render completes\n this.#pendingFocusRestore = true;\n\n // If no render was scheduled (row not visible), restore focus immediately\n if (!rowEl) {\n this.#restoreCellFocus(internalGrid);\n this.#pendingFocusRestore = false;\n }\n }\n\n /**\n * Commit a single cell value change.\n * Uses ID-based change tracking for stability when rows are reordered.\n */\n #commitCellValue(rowIndex: number, column: ColumnConfig<T>, newValue: unknown, rowData: T): void {\n const field = column.field;\n if (!isSafePropertyKey(field)) return;\n const oldValue = (rowData as Record<string, unknown>)[field];\n if (oldValue === newValue) return;\n\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n\n // Get row ID for change tracking (may not exist if getRowId not configured)\n let rowId: string | undefined;\n try {\n rowId = this.grid.getRowId(rowData);\n } catch {\n // Row has no ID - will still work but won't be tracked in changedRowIds\n }\n\n const firstTime = rowId ? !this.#changedRowIds.has(rowId) : true;\n\n // Create updateRow helper for cascade updates (noop if row has no ID)\n const updateRow: (changes: Partial<T>) => void = rowId\n ? (changes) => this.grid.updateRow(rowId!, changes as Record<string, unknown>, 'cascade')\n : noopUpdateRow;\n\n // Emit cancelable event BEFORE applying the value\n const cancelled = this.emitCancelable<CellCommitDetail<T>>('cell-commit', {\n row: rowData,\n rowId: rowId ?? '',\n field,\n oldValue,\n value: newValue,\n rowIndex,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n firstTimeForRow: firstTime,\n updateRow,\n });\n\n // If consumer called preventDefault(), abort the commit\n if (cancelled) return;\n\n // Apply the value and mark row as changed\n (rowData as Record<string, unknown>)[field] = newValue;\n if (rowId) {\n this.#changedRowIds.add(rowId);\n }\n this.#syncGridEditState();\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (rowEl) rowEl.classList.add('changed');\n }\n\n /**\n * Inject an editor into a cell.\n */\n #injectEditor(\n rowData: T,\n rowIndex: number,\n column: ColumnConfig<T>,\n colIndex: number,\n cell: HTMLElement,\n skipFocus: boolean,\n ): void {\n if (!column.editable) return;\n if (cell.classList.contains('editing')) return;\n\n // Get row ID for updateRow helper (may not exist)\n let rowId: string | undefined;\n try {\n rowId = this.grid.getRowId(rowData);\n } catch {\n // Row has no ID\n }\n\n // Create updateRow helper for cascade updates (noop if row has no ID)\n const updateRow: (changes: Partial<T>) => void = rowId\n ? (changes) => this.grid.updateRow(rowId!, changes as Record<string, unknown>, 'cascade')\n : noopUpdateRow;\n\n const originalValue = isSafePropertyKey(column.field)\n ? (rowData as Record<string, unknown>)[column.field]\n : undefined;\n\n cell.classList.add('editing');\n this.#editingCells.add(`${rowIndex}:${colIndex}`);\n\n const rowEl = cell.parentElement as RowElementInternal | null;\n if (rowEl) incrementEditingCount(rowEl);\n\n let editFinalized = false;\n const commit = (newValue: unknown) => {\n if (editFinalized || this.#activeEditRow === -1) return;\n this.#commitCellValue(rowIndex, column, newValue, rowData);\n };\n const cancel = () => {\n editFinalized = true;\n if (isSafePropertyKey(column.field)) {\n (rowData as Record<string, unknown>)[column.field] = originalValue;\n }\n };\n\n const editorHost = document.createElement('div');\n editorHost.className = 'tbw-editor-host';\n cell.innerHTML = '';\n cell.appendChild(editorHost);\n\n // Keydown handler for Enter/Escape\n editorHost.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter') {\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n e.preventDefault();\n cancel();\n this.#exitRowEdit(rowIndex, true);\n }\n });\n\n const colInternal = column as ColumnInternal<T>;\n const tplHolder = colInternal.__editorTemplate;\n // Resolve editor using priority chain: column → template → typeDefaults → adapter → built-in\n const editorSpec = resolveEditor(this.grid as unknown as InternalGrid<T>, colInternal) ?? defaultEditorFor(column);\n const value = originalValue;\n\n if (editorSpec === 'template' && tplHolder) {\n this.#renderTemplateEditor(editorHost, colInternal, rowData, originalValue, commit, cancel, skipFocus, rowIndex);\n } else if (typeof editorSpec === 'string') {\n const el = document.createElement(editorSpec) as HTMLElement & { value?: unknown };\n el.value = value;\n el.addEventListener('change', () => commit(el.value));\n editorHost.appendChild(el);\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (typeof editorSpec === 'function') {\n const ctx: EditorContext<T> = {\n row: rowData,\n rowId: rowId ?? '',\n value,\n field: column.field,\n column,\n commit,\n cancel,\n updateRow,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const produced = (editorSpec as any)(ctx);\n if (typeof produced === 'string') {\n editorHost.innerHTML = produced;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n wireEditorInputs(editorHost, column as any, commit);\n } else if (produced instanceof Node) {\n editorHost.appendChild(produced);\n }\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (editorSpec && typeof editorSpec === 'object') {\n const placeholder = document.createElement('div');\n placeholder.setAttribute('data-external-editor', '');\n placeholder.setAttribute('data-field', column.field);\n editorHost.appendChild(placeholder);\n const context: EditorContext<T> = {\n row: rowData,\n rowId: rowId ?? '',\n value,\n field: column.field,\n column,\n commit,\n cancel,\n updateRow,\n };\n if (editorSpec.mount) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n editorSpec.mount({ placeholder, context: context as any, spec: editorSpec });\n } catch (e) {\n console.warn(`[tbw-grid] External editor mount error for column '${column.field}':`, e);\n }\n } else {\n (this.grid as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('mount-external-editor', { detail: { placeholder, spec: editorSpec, context } }),\n );\n }\n }\n }\n\n /**\n * Render a template-based editor.\n */\n #renderTemplateEditor(\n editorHost: HTMLElement,\n column: ColumnInternal<T>,\n rowData: T,\n originalValue: unknown,\n commit: (value: unknown) => void,\n cancel: () => void,\n skipFocus: boolean,\n rowIndex: number,\n ): void {\n const tplHolder = column.__editorTemplate;\n if (!tplHolder) return;\n\n const clone = tplHolder.cloneNode(true) as HTMLElement;\n const compiledEditor = column.__compiledEditor;\n\n if (compiledEditor) {\n clone.innerHTML = compiledEditor({\n row: rowData,\n value: originalValue,\n field: column.field,\n column,\n commit,\n cancel,\n });\n } else {\n clone.querySelectorAll<HTMLElement>('*').forEach((node) => {\n if (node.childNodes.length === 1 && node.firstChild?.nodeType === Node.TEXT_NODE) {\n node.textContent =\n node.textContent\n ?.replace(/{{\\s*value\\s*}}/g, originalValue == null ? '' : String(originalValue))\n .replace(/{{\\s*row\\.([a-zA-Z0-9_]+)\\s*}}/g, (_m, g: string) => {\n if (!isSafePropertyKey(g)) return '';\n const v = (rowData as Record<string, unknown>)[g];\n return v == null ? '' : String(v);\n }) || '';\n }\n });\n }\n\n const input = clone.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!originalValue;\n } else {\n input.value = String(originalValue ?? '');\n }\n\n let editFinalized = false;\n input.addEventListener('blur', () => {\n if (editFinalized) return;\n commit(getInputValue(input, column));\n });\n input.addEventListener('keydown', (evt) => {\n const e = evt as KeyboardEvent;\n if (e.key === 'Enter') {\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n commit(getInputValue(input, column));\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n e.preventDefault();\n cancel();\n this.#exitRowEdit(rowIndex, true);\n }\n });\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n }\n if (!skipFocus) {\n setTimeout(() => input.focus({ preventScroll: true }), 0);\n }\n }\n editorHost.appendChild(clone);\n }\n\n /**\n * Restore focus to cell after exiting edit mode.\n */\n #restoreCellFocus(internalGrid: InternalGrid<T>): void {\n queueMicrotask(() => {\n try {\n const rowIdx = internalGrid._focusRow;\n const colIdx = internalGrid._focusCol;\n const rowEl = internalGrid.findRenderedRowElement?.(rowIdx);\n if (rowEl) {\n Array.from(internalGrid._bodyEl.querySelectorAll('.cell-focus')).forEach((el) =>\n el.classList.remove('cell-focus'),\n );\n const cell = rowEl.querySelector(`.cell[data-row=\"${rowIdx}\"][data-col=\"${colIdx}\"]`) as HTMLElement | null;\n if (cell) {\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n cell.focus({ preventScroll: true });\n }\n }\n } catch {\n /* empty */\n }\n });\n }\n\n // #endregion\n}\n"],"names":["resolveOptions","column","raw","createNumberEditor","ctx","params","input","commit","e","createBooleanEditor","createDateEditor","createSelectEditor","select","emptyOpt","opt","o","values","createTextEditor","defaultEditorFor","FOCUSABLE_EDITOR_SELECTOR","resolveEditor","grid","col","gridTypeDefaults","adapter","appDefault","isSafePropertyKey","key","incrementEditingCount","rowEl","count","clearEditingState","getInputValue","noopUpdateRow","_changes","wireEditorInputs","editorHost","EditingPlugin","BaseGridPlugin","styles","#activeEditRow","#activeEditCol","#rowEditSnapshots","#changedRowIds","#editingCells","#pendingFocusRestore","signal","internalGrid","silent","rowIndex","field","#exitRowEdit","event","editOn","isDoubleClick","focusRow","focusCol","rowData","newValue","#commitCellValue","row","value","cellEl","activateEvent","legacyEvent","columns","typeDefaults","typeEditorParams","#restoreCellFocus","cellKey","rowStr","colStr","colIndex","#injectEditor","rows","id","rowId","ids","#syncGridEditState","r","c","#beginCellEdit","#startRowEdit","cell","i","targetCell","editor","revert","snapshot","current","val","k","changed","oldValue","firstTime","updateRow","changes","skipFocus","originalValue","editFinalized","cancel","colInternal","tplHolder","editorSpec","#renderTemplateEditor","el","produced","placeholder","context","clone","compiledEditor","node","_m","g","v","evt","rowIdx","colIdx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"editing.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/editing/editors.ts","../../../../../libs/grid/src/lib/plugins/editing/EditingPlugin.ts"],"sourcesContent":["/**\n * Default Editors Module\n *\n * Provides built-in editor factories for different column types.\n * Each editor type has its own factory function for consistency and readability.\n *\n * IMPORTANT: Editor factories should NOT call focus() on elements - they are called\n * before the element is appended to the DOM. The calling code (beginBulkEdit,\n * inlineEnterEdit) is responsible for focusing the correct editor after insertion.\n */\n\nimport type { ColumnConfig, EditorContext } from '../../core/types';\nimport type { DateEditorParams, NumberEditorParams, SelectEditorParams, TextEditorParams } from './types';\n\n// ============================================================================\n// Type Aliases\n// ============================================================================\n\n/** Option shape used by select editor (matches column.options) */\ntype ColumnOption = { label: string; value: unknown };\n\n/** Column with any row type (used for editor factories) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyColumn = ColumnConfig<any>;\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/** Resolve column.options (handles both array and function forms) */\nfunction resolveOptions(column: AnyColumn): ColumnOption[] {\n const raw = column.options;\n if (!raw) return [];\n return typeof raw === 'function' ? raw() : raw;\n}\n\n// ============================================================================\n// Editor Factories\n// ============================================================================\n\n/** Creates a number input editor */\nfunction createNumberEditor(column: AnyColumn): (ctx: EditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as NumberEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'number';\n input.value = ctx.value != null ? String(ctx.value) : '';\n\n if (params?.min !== undefined) input.min = String(params.min);\n if (params?.max !== undefined) input.max = String(params.max);\n if (params?.step !== undefined) input.step = String(params.step);\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n const commit = () => ctx.commit(input.value === '' ? null : Number(input.value));\n input.addEventListener('blur', commit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') commit();\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n/** Creates a checkbox editor for boolean values */\nfunction createBooleanEditor(): (ctx: EditorContext) => HTMLElement {\n return (ctx) => {\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = !!ctx.value;\n input.addEventListener('change', () => ctx.commit(input.checked));\n return input;\n };\n}\n\n/** Creates a date input editor */\nfunction createDateEditor(column: AnyColumn): (ctx: EditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as DateEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'date';\n\n if (ctx.value instanceof Date) input.valueAsDate = ctx.value;\n if (params?.min) input.min = params.min;\n if (params?.max) input.max = params.max;\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n input.addEventListener('change', () => ctx.commit(input.valueAsDate));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n/** Creates a select dropdown editor */\nfunction createSelectEditor(column: AnyColumn): (ctx: EditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as SelectEditorParams | undefined;\n const select = document.createElement('select');\n if (column.multi) select.multiple = true;\n\n // Add empty option if requested\n if (params?.includeEmpty) {\n const emptyOpt = document.createElement('option');\n emptyOpt.value = '';\n emptyOpt.textContent = params.emptyLabel ?? '';\n select.appendChild(emptyOpt);\n }\n\n // Populate options from column.options\n const options = resolveOptions(column);\n options.forEach((opt) => {\n const o = document.createElement('option');\n o.value = String(opt.value);\n o.textContent = opt.label;\n if (column.multi && Array.isArray(ctx.value) && ctx.value.includes(opt.value)) {\n o.selected = true;\n } else if (!column.multi && ctx.value === opt.value) {\n o.selected = true;\n }\n select.appendChild(o);\n });\n\n const commit = () => {\n if (column.multi) {\n const values = Array.from(select.selectedOptions).map((o) => o.value);\n ctx.commit(values);\n } else {\n ctx.commit(select.value);\n }\n };\n\n select.addEventListener('change', commit);\n select.addEventListener('blur', commit);\n select.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return select;\n };\n}\n\n/** Creates a text input editor (default) */\nfunction createTextEditor(column: AnyColumn): (ctx: EditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as TextEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'text';\n input.value = ctx.value != null ? String(ctx.value) : '';\n\n if (params?.maxLength !== undefined) input.maxLength = params.maxLength;\n if (params?.pattern) input.pattern = params.pattern;\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n input.addEventListener('blur', () => ctx.commit(input.value));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') ctx.commit(input.value);\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\n/**\n * Returns a default editor factory function for the given column type.\n * Each editor handles commit on blur/Enter, and cancel on Escape.\n *\n * Note: Focus is NOT called here - the calling code handles focusing after DOM insertion.\n */\nexport function defaultEditorFor(column: AnyColumn): (ctx: EditorContext) => HTMLElement | string {\n switch (column.type) {\n case 'number':\n return createNumberEditor(column);\n case 'boolean':\n return createBooleanEditor();\n case 'date':\n return createDateEditor(column);\n case 'select':\n return createSelectEditor(column);\n default:\n return createTextEditor(column);\n }\n}\n\n// ============================================================================\n// Utility Export (used by EditingPlugin)\n// ============================================================================\n\n/**\n * Gets the current value from an input element, with type coercion based on column type.\n */\nexport function getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n col: AnyColumn,\n): unknown {\n if (input instanceof HTMLSelectElement) {\n if (col.multi) {\n return Array.from(input.selectedOptions).map((o) => o.value);\n }\n return input.value;\n }\n if (input instanceof HTMLInputElement) {\n if (input.type === 'checkbox') return input.checked;\n if (input.type === 'number') return input.value === '' ? null : Number(input.value);\n if (input.type === 'date') return input.valueAsDate;\n }\n return input.value;\n}\n","/**\n * Editing Plugin\n *\n * Provides complete editing functionality for tbw-grid.\n * This plugin is FULLY SELF-CONTAINED - the grid has ZERO editing knowledge.\n *\n * The plugin:\n * - Owns all editing state (active cell, snapshots, changed rows)\n * - Uses event distribution (onCellClick, onKeyDown) to handle edit lifecycle\n * - Uses afterRender() hook to inject editors into cells\n * - Uses processColumns() to augment columns with editing metadata\n * - Emits its own events (cell-commit, row-commit, changed-rows-reset)\n *\n * Without this plugin, the grid cannot edit. With this plugin, editing\n * is fully functional without any core changes.\n */\n\nimport type { PluginManifest } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin, type CellClickEvent, type GridElement } from '../../core/plugin/base-plugin';\nimport type {\n ColumnConfig,\n ColumnEditorSpec,\n ColumnInternal,\n InternalGrid,\n RowElementInternal,\n} from '../../core/types';\nimport styles from './editing.css?inline';\nimport { defaultEditorFor } from './editors';\nimport type { CellCommitDetail, ChangedRowsResetDetail, EditingConfig, EditorContext, RowCommitDetail } from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * CSS selector for focusable editor elements within a cell.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Resolves the editor for a column using the priority chain:\n * 1. Column-level (`column.editor`)\n * 2. Light DOM template (`__editorTemplate` → returns 'template')\n * 3. Grid-level (`gridConfig.typeDefaults[column.type]`)\n * 4. App-level (framework adapter's `getTypeDefault`)\n * 5. Returns undefined (caller uses built-in defaultEditorFor)\n */\nfunction resolveEditor<TRow>(\n grid: InternalGrid<TRow>,\n col: ColumnInternal<TRow>,\n): ColumnEditorSpec<TRow, unknown> | 'template' | undefined {\n // 1. Column-level editor (highest priority)\n if (col.editor) return col.editor;\n\n // 2. Light DOM template\n const tplHolder = col.__editorTemplate;\n if (tplHolder) return 'template';\n\n // No type specified - no type defaults to check\n if (!col.type) return undefined;\n\n // 3. Grid-level typeDefaults (access via effectiveConfig)\n const gridTypeDefaults = (grid as any).effectiveConfig?.typeDefaults;\n if (gridTypeDefaults?.[col.type]?.editor) {\n return gridTypeDefaults[col.type].editor as ColumnEditorSpec<TRow, unknown>;\n }\n\n // 4. App-level registry (via framework adapter)\n const adapter = grid.__frameworkAdapter;\n if (adapter?.getTypeDefault) {\n const appDefault = adapter.getTypeDefault<TRow>(col.type);\n if (appDefault?.editor) {\n return appDefault.editor as ColumnEditorSpec<TRow, unknown>;\n }\n }\n\n // 5. No custom editor - caller uses built-in defaultEditorFor\n return undefined;\n}\n\n/**\n * Returns true if the given property key is safe to use on a plain object.\n */\nfunction isSafePropertyKey(key: unknown): key is string {\n if (typeof key !== 'string') return false;\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') return false;\n return true;\n}\n\n/**\n * Check if a row element has any cells in editing mode.\n */\nexport function hasEditingCells(rowEl: RowElementInternal): boolean {\n return (rowEl.__editingCellCount ?? 0) > 0;\n}\n\n/**\n * Increment the editing cell count on a row element.\n */\nfunction incrementEditingCount(rowEl: RowElementInternal): void {\n const count = (rowEl.__editingCellCount ?? 0) + 1;\n rowEl.__editingCellCount = count;\n rowEl.setAttribute('data-has-editing', '');\n}\n\n/**\n * Clear all editing state from a row element.\n */\nexport function clearEditingState(rowEl: RowElementInternal): void {\n rowEl.__editingCellCount = 0;\n rowEl.removeAttribute('data-has-editing');\n}\n\n/**\n * Get the typed value from an input element based on its type and column config.\n */\nfunction getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n column?: ColumnConfig<any>,\n): unknown {\n if (input instanceof HTMLInputElement) {\n if (input.type === 'checkbox') return input.checked;\n if (input.type === 'number') return input.value === '' ? null : Number(input.value);\n if (input.type === 'date') return input.valueAsDate;\n return input.value;\n }\n if (column?.type === 'number' && input.value !== '') {\n return Number(input.value);\n }\n return input.value;\n}\n\n/**\n * No-op updateRow function for rows without IDs.\n * Extracted to a named function to satisfy eslint no-empty-function.\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction noopUpdateRow(_changes: unknown): void {\n // Row has no ID - cannot update\n}\n\n/**\n * Auto-wire commit/cancel lifecycle for input elements in string-returned editors.\n */\nfunction wireEditorInputs(\n editorHost: HTMLElement,\n column: ColumnConfig<unknown>,\n commit: (value: unknown) => void,\n): void {\n const input = editorHost.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (!input) return;\n\n input.addEventListener('blur', () => {\n commit(getInputValue(input, column));\n });\n\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n } else if (input instanceof HTMLSelectElement) {\n input.addEventListener('change', () => commit(getInputValue(input, column)));\n }\n}\n\n// ============================================================================\n// EditingPlugin\n// ============================================================================\n\n/**\n * Editing Plugin for tbw-grid\n *\n * Enables inline cell editing in the grid. Provides built-in editors for common data types\n * and supports custom editor functions for specialized input scenarios.\n *\n * ## Why Opt-In?\n *\n * Editing is delivered as a plugin rather than built into the core grid:\n *\n * - **Smaller bundle** — Apps that only display data don't pay for editing code\n * - **Clear intent** — Explicit plugin registration makes editing capability obvious\n * - **Runtime validation** — Using `editable: true` without the plugin throws a helpful error\n *\n * ## Installation\n *\n * ```ts\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n * ```\n *\n * ## Edit Triggers\n *\n * Configure how editing is triggered with the `editOn` option:\n *\n * | Value | Behavior |\n * |-------|----------|\n * | `'click'` | Single click enters edit mode (default) |\n * | `'dblclick'` | Double-click enters edit mode |\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Enter` | Commit edit and move down |\n * | `Tab` | Commit edit and move right |\n * | `Escape` | Cancel edit, restore original value |\n * | `Arrow Keys` | Navigate between cells (when not editing) |\n *\n * @example Basic editing with double-click trigger\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', editable: true },\n * { field: 'price', type: 'number', editable: true },\n * { field: 'active', type: 'boolean', editable: true },\n * ],\n * plugins: [new EditingPlugin({ editOn: 'dblclick' })],\n * };\n *\n * grid.addEventListener('cell-commit', (e) => {\n * const { field, oldValue, newValue } = e.detail;\n * console.log(`${field}: ${oldValue} → ${newValue}`);\n * });\n * ```\n *\n * @example Custom editor function\n * ```ts\n * columns: [\n * {\n * field: 'status',\n * editable: true,\n * editor: (ctx) => {\n * const select = document.createElement('select');\n * ['pending', 'active', 'completed'].forEach(opt => {\n * const option = document.createElement('option');\n * option.value = opt;\n * option.textContent = opt;\n * option.selected = ctx.value === opt;\n * select.appendChild(option);\n * });\n * select.addEventListener('change', () => ctx.commit(select.value));\n * return select;\n * },\n * },\n * ]\n * ```\n *\n * @see {@link EditingConfig} for configuration options\n * @see {@link EditorContext} for custom editor context\n * @see [Live Demos](?path=/docs/grid-plugins-editing--docs) for interactive examples\n */\nexport class EditingPlugin<T = unknown> extends BaseGridPlugin<EditingConfig> {\n /**\n * Plugin manifest - declares owned properties for configuration validation.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'editable',\n level: 'column',\n description: 'the \"editable\" column property',\n isUsed: (v) => v === true,\n },\n {\n property: 'editor',\n level: 'column',\n description: 'the \"editor\" column property',\n },\n {\n property: 'editorParams',\n level: 'column',\n description: 'the \"editorParams\" column property',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'editing';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<EditingConfig> {\n return {\n editOn: 'click',\n };\n }\n\n // #region Editing State (fully owned by plugin)\n\n /** Currently active edit row index, or -1 if not editing */\n #activeEditRow = -1;\n\n /** Currently active edit column index, or -1 if not editing */\n #activeEditCol = -1;\n\n /** Snapshots of row data before editing started */\n #rowEditSnapshots = new Map<number, T>();\n\n /** Set of row IDs that have been modified (ID-based for stability) */\n #changedRowIds = new Set<string>();\n\n /** Set of cells currently in edit mode: \"rowIndex:colIndex\" */\n #editingCells = new Set<string>();\n\n /** Flag to restore focus after next render (used when exiting edit mode) */\n #pendingFocusRestore = false;\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n const signal = this.disconnectSignal;\n const internalGrid = grid as unknown as InternalGrid<T>;\n\n // Inject editing state and methods onto grid for backward compatibility\n internalGrid._activeEditRows = -1;\n internalGrid._rowEditSnapshots = new Map();\n\n // Inject changedRows getter\n Object.defineProperty(grid, 'changedRows', {\n get: () => this.changedRows,\n configurable: true,\n });\n\n // Inject changedRowIds getter (new ID-based API)\n Object.defineProperty(grid, 'changedRowIds', {\n get: () => this.changedRowIds,\n configurable: true,\n });\n\n // Inject resetChangedRows method\n (grid as any).resetChangedRows = (silent?: boolean) => this.resetChangedRows(silent);\n\n // Inject beginBulkEdit method (for backward compatibility)\n (grid as any).beginBulkEdit = (rowIndex: number, field?: string) => {\n if (field) {\n this.beginCellEdit(rowIndex, field);\n }\n // If no field specified, we can't start editing without a specific cell\n };\n\n // Document-level Escape to cancel editing\n document.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n },\n { capture: true, signal },\n );\n\n // Click outside to commit editing\n document.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\n if (this.#activeEditRow === -1) return;\n const rowEl = internalGrid.findRenderedRowElement?.(this.#activeEditRow);\n if (!rowEl) return;\n const path = (e.composedPath && e.composedPath()) || [];\n if (path.includes(rowEl)) return;\n this.#exitRowEdit(this.#activeEditRow, false);\n },\n { signal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#rowEditSnapshots.clear();\n this.#changedRowIds.clear();\n this.#editingCells.clear();\n super.detach();\n }\n\n // #endregion\n\n // #region Event Handlers (event distribution)\n\n /**\n * Handle cell clicks - start editing if configured for click mode.\n * Both click and dblclick events come through this handler.\n * Starts row-based editing (all editable cells in the row get editors).\n * @internal\n */\n override onCellClick(event: CellClickEvent): boolean | void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n\n // Check if editing is disabled\n if (editOn === false || editOn === 'manual') return false;\n\n // Check if this is click or dblclick mode\n if (editOn !== 'click' && editOn !== 'dblclick') return false;\n\n // Check if the event type matches the edit mode\n const isDoubleClick = event.originalEvent.type === 'dblclick';\n if (editOn === 'click' && isDoubleClick) return false; // In click mode, only handle single clicks\n if (editOn === 'dblclick' && !isDoubleClick) return false; // In dblclick mode, only handle double clicks\n\n const { rowIndex } = event;\n\n // Check if any column in the row is editable\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (!hasEditableColumn) return false;\n\n // Start row-based editing (all editable cells get editors)\n event.originalEvent.stopPropagation();\n this.beginBulkEdit(rowIndex);\n return true; // Handled\n }\n\n /**\n * Handle keyboard events for edit lifecycle.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n\n // Escape: cancel current edit\n if (event.key === 'Escape' && this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n return true;\n }\n\n // Space: toggle boolean cells\n if (event.key === ' ' || event.key === 'Spacebar') {\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0 && focusCol >= 0) {\n const column = internalGrid._visibleColumns[focusCol];\n const rowData = internalGrid._rows[focusRow];\n if (column?.editable && column.type === 'boolean' && rowData) {\n const field = column.field;\n if (isSafePropertyKey(field)) {\n const currentValue = (rowData as Record<string, unknown>)[field];\n const newValue = !currentValue;\n this.#commitCellValue(focusRow, column, newValue, rowData);\n event.preventDefault();\n // Re-render to update the UI\n this.requestRender();\n return true;\n }\n }\n }\n // Space on non-boolean cell - don't block keyboard navigation\n return false;\n }\n\n // Enter: start row edit or commit\n if (event.key === 'Enter' && !event.shiftKey) {\n if (this.#activeEditRow !== -1) {\n // Already editing - let cell handlers deal with it\n return false;\n }\n\n // Start row-based editing (not just the focused cell)\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false || editOn === 'manual') return false;\n\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0) {\n // Check if ANY column in the row is editable\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (hasEditableColumn) {\n // Emit cell-activate event BEFORE starting edit\n // This ensures consumers always get the activation event\n const column = internalGrid._visibleColumns[focusCol];\n const row = internalGrid._rows[focusRow];\n const field = column?.field ?? '';\n const value = field && row ? (row as Record<string, unknown>)[field] : undefined;\n const cellEl = this.gridElement.querySelector(`[data-row=\"${focusRow}\"][data-col=\"${focusCol}\"]`) as\n | HTMLElement\n | undefined;\n\n const activateEvent = new CustomEvent('cell-activate', {\n cancelable: true,\n bubbles: true,\n detail: {\n rowIndex: focusRow,\n colIndex: focusCol,\n field,\n value,\n row,\n cellEl,\n trigger: 'keyboard' as const,\n originalEvent: event,\n },\n });\n this.gridElement.dispatchEvent(activateEvent);\n\n // Also emit deprecated activate-cell for backwards compatibility\n const legacyEvent = new CustomEvent('activate-cell', {\n cancelable: true,\n bubbles: true,\n detail: { row: focusRow, col: focusCol },\n });\n this.gridElement.dispatchEvent(legacyEvent);\n\n // If consumer canceled the activation, don't start editing\n if (activateEvent.defaultPrevented || legacyEvent.defaultPrevented) {\n event.preventDefault();\n return true;\n }\n\n this.beginBulkEdit(focusRow);\n return true;\n }\n }\n // No editable columns - don't block keyboard navigation\n return false;\n }\n\n // Don't block other keyboard events\n return false;\n }\n\n // #endregion\n\n // #region Render Hooks\n\n /**\n * Process columns to merge type-level editorParams with column-level.\n * Column-level params take precedence.\n * @internal\n */\n override processColumns(columns: ColumnConfig<T>[]): ColumnConfig<T>[] {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const typeDefaults = (internalGrid as any).effectiveConfig?.typeDefaults;\n const adapter = internalGrid.__frameworkAdapter;\n\n // If no type defaults configured anywhere, skip processing\n if (!typeDefaults && !adapter?.getTypeDefault) return columns;\n\n return columns.map((col) => {\n if (!col.type) return col;\n\n // Get type-level editorParams\n let typeEditorParams: Record<string, unknown> | undefined;\n\n // Check grid-level typeDefaults first\n if (typeDefaults?.[col.type]?.editorParams) {\n typeEditorParams = typeDefaults[col.type].editorParams;\n }\n\n // Then check app-level (adapter) typeDefaults\n if (!typeEditorParams && adapter?.getTypeDefault) {\n const appDefault = adapter.getTypeDefault<T>(col.type);\n if (appDefault?.editorParams) {\n typeEditorParams = appDefault.editorParams;\n }\n }\n\n // No type-level params to merge\n if (!typeEditorParams) return col;\n\n // Merge: type-level as base, column-level wins on conflicts\n return {\n ...col,\n editorParams: { ...typeEditorParams, ...col.editorParams },\n };\n });\n }\n\n /**\n * After render, reapply editors to cells in edit mode.\n * This handles virtualization - when a row scrolls back into view,\n * we need to re-inject the editor.\n * @internal\n */\n override afterRender(): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n\n // Restore focus after exiting edit mode\n if (this.#pendingFocusRestore) {\n this.#pendingFocusRestore = false;\n this.#restoreCellFocus(internalGrid);\n }\n\n if (this.#editingCells.size === 0) return;\n\n // Re-inject editors for any editing cells that are visible\n for (const cellKey of this.#editingCells) {\n const [rowStr, colStr] = cellKey.split(':');\n const rowIndex = parseInt(rowStr, 10);\n const colIndex = parseInt(colStr, 10);\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) continue;\n\n const cellEl = rowEl.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl || cellEl.classList.contains('editing')) continue;\n\n // Cell is visible but not in editing mode - reinject editor\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n if (rowData && column) {\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, true);\n }\n }\n }\n\n /**\n * On scroll render, reapply editors to recycled cells.\n * @internal\n */\n override onScrollRender(): void {\n this.afterRender();\n }\n\n // #endregion\n\n // #region Public API\n\n /**\n * Get all rows that have been modified.\n * Uses ID-based lookup for stability when rows are reordered.\n */\n get changedRows(): T[] {\n const rows: T[] = [];\n for (const id of this.#changedRowIds) {\n const row = this.grid.getRow(id) as T | undefined;\n if (row) rows.push(row);\n }\n return rows;\n }\n\n /**\n * Get IDs of all modified rows.\n */\n get changedRowIds(): string[] {\n return Array.from(this.#changedRowIds);\n }\n\n /**\n * Get the currently active edit row index, or -1 if not editing.\n */\n get activeEditRow(): number {\n return this.#activeEditRow;\n }\n\n /**\n * Get the currently active edit column index, or -1 if not editing.\n */\n get activeEditCol(): number {\n return this.#activeEditCol;\n }\n\n /**\n * Check if a specific row is currently being edited.\n */\n isRowEditing(rowIndex: number): boolean {\n return this.#activeEditRow === rowIndex;\n }\n\n /**\n * Check if a specific cell is currently being edited.\n */\n isCellEditing(rowIndex: number, colIndex: number): boolean {\n return this.#editingCells.has(`${rowIndex}:${colIndex}`);\n }\n\n /**\n * Check if a specific row has been modified.\n * @param rowIndex - Row index to check (will be converted to ID internally)\n */\n isRowChanged(rowIndex: number): boolean {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const row = internalGrid._rows[rowIndex];\n if (!row) return false;\n try {\n const rowId = internalGrid.getRowId?.(row);\n return rowId ? this.#changedRowIds.has(rowId) : false;\n } catch {\n return false;\n }\n }\n\n /**\n * Check if a row with the given ID has been modified.\n * @param rowId - Row ID to check\n */\n isRowChangedById(rowId: string): boolean {\n return this.#changedRowIds.has(rowId);\n }\n\n /**\n * Reset all change tracking.\n * @param silent - If true, suppresses the `changed-rows-reset` event\n * @fires changed-rows-reset - Emitted when tracking is reset (unless silent)\n */\n resetChangedRows(silent?: boolean): void {\n const rows = this.changedRows;\n const ids = this.changedRowIds;\n this.#changedRowIds.clear();\n this.#syncGridEditState();\n\n if (!silent) {\n this.emit<ChangedRowsResetDetail<T>>('changed-rows-reset', { rows, ids });\n }\n\n // Clear visual indicators\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n internalGrid._rowPool?.forEach((r) => r.classList.remove('changed'));\n }\n\n /**\n * Programmatically begin editing a cell.\n * @param rowIndex - Index of the row to edit\n * @param field - Field name of the column to edit\n * @fires cell-commit - Emitted when the cell value is committed (on blur or Enter)\n */\n beginCellEdit(rowIndex: number, field: string): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const colIndex = internalGrid._visibleColumns.findIndex((c) => c.field === field);\n if (colIndex === -1) return;\n\n const column = internalGrid._visibleColumns[colIndex];\n if (!column?.editable) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl) return;\n\n this.#beginCellEdit(rowIndex, colIndex, cellEl);\n }\n\n /**\n * Programmatically begin editing all editable cells in a row.\n * @param rowIndex - Index of the row to edit\n * @fires cell-commit - Emitted for each cell value that is committed\n * @fires row-commit - Emitted when focus leaves the row\n */\n beginBulkEdit(rowIndex: number): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false) return;\n\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (!hasEditableColumn) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) return;\n\n // Start row edit\n const rowData = internalGrid._rows[rowIndex];\n this.#startRowEdit(rowIndex, rowData);\n\n // Enter edit mode on all editable cells\n Array.from(rowEl.children).forEach((cell, i) => {\n const col = internalGrid._visibleColumns[i];\n if (col?.editable) {\n const cellEl = cell as HTMLElement;\n if (!cellEl.classList.contains('editing')) {\n this.#injectEditor(rowData, rowIndex, col, i, cellEl, true);\n }\n }\n });\n\n // Focus the first editable cell\n setTimeout(() => {\n let targetCell = rowEl.querySelector(`.cell[data-col=\"${internalGrid._focusCol}\"]`);\n if (!targetCell?.classList.contains('editing')) {\n targetCell = rowEl.querySelector('.cell.editing');\n }\n if (targetCell?.classList.contains('editing')) {\n const editor = (targetCell as HTMLElement).querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n try {\n editor?.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n }\n }, 0);\n }\n\n /**\n * Commit the currently active row edit.\n * @fires row-commit - Emitted after the row edit is committed\n */\n commitActiveRowEdit(): void {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, false);\n }\n }\n\n /**\n * Cancel the currently active row edit.\n */\n cancelActiveRowEdit(): void {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n }\n\n // #endregion\n\n // #region Internal Methods\n\n /**\n * Begin editing a single cell.\n */\n #beginCellEdit(rowIndex: number, colIndex: number, cellEl: HTMLElement): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n\n if (!rowData || !column?.editable) return;\n if (cellEl.classList.contains('editing')) return;\n\n // Start row edit if not already\n if (this.#activeEditRow !== rowIndex) {\n this.#startRowEdit(rowIndex, rowData);\n }\n\n this.#activeEditCol = colIndex;\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, false);\n }\n\n /**\n * Sync the internal grid state with the plugin's editing state.\n */\n #syncGridEditState(): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n internalGrid._activeEditRows = this.#activeEditRow;\n internalGrid._rowEditSnapshots = this.#rowEditSnapshots;\n }\n\n /**\n * Snapshot original row data and mark as editing.\n */\n #startRowEdit(rowIndex: number, rowData: T): void {\n if (this.#activeEditRow !== rowIndex) {\n this.#rowEditSnapshots.set(rowIndex, { ...rowData });\n this.#activeEditRow = rowIndex;\n this.#syncGridEditState();\n }\n }\n\n /**\n * Exit editing for a row.\n */\n #exitRowEdit(rowIndex: number, revert: boolean): void {\n if (this.#activeEditRow !== rowIndex) return;\n\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const snapshot = this.#rowEditSnapshots.get(rowIndex);\n const current = internalGrid._rows[rowIndex];\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n\n // Get row ID for change tracking\n let rowId: string | undefined;\n if (current) {\n try {\n rowId = internalGrid.getRowId?.(current);\n } catch {\n // Row has no ID - skip ID-based tracking\n }\n }\n\n // Collect and commit values from active editors before re-rendering\n if (!revert && rowEl && current) {\n const editingCells = rowEl.querySelectorAll('.cell.editing');\n editingCells.forEach((cell) => {\n const colIndex = Number((cell as HTMLElement).getAttribute('data-col'));\n if (isNaN(colIndex)) return;\n const col = internalGrid._visibleColumns[colIndex];\n if (!col) return;\n const input = cell.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n const val = getInputValue(input, col);\n if (current[col.field as keyof T] !== val) {\n this.#commitCellValue(rowIndex, col, val, current);\n }\n }\n });\n }\n\n // Revert if requested\n if (revert && snapshot && current) {\n Object.keys(snapshot as object).forEach((k) => {\n (current as Record<string, unknown>)[k] = (snapshot as Record<string, unknown>)[k];\n });\n if (rowId) {\n this.#changedRowIds.delete(rowId);\n }\n } else if (!revert && current) {\n const changed = rowId ? this.#changedRowIds.has(rowId) : false;\n this.emit<RowCommitDetail<T>>('row-commit', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n changed,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n });\n }\n\n // Clear editing state\n this.#rowEditSnapshots.delete(rowIndex);\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#syncGridEditState();\n\n // Remove all editing cells for this row\n for (const cellKey of this.#editingCells) {\n if (cellKey.startsWith(`${rowIndex}:`)) {\n this.#editingCells.delete(cellKey);\n }\n }\n\n // Re-render the row to remove editors\n if (rowEl) {\n // Remove editing class and re-render cells\n rowEl.querySelectorAll('.cell.editing').forEach((cell) => {\n cell.classList.remove('editing');\n clearEditingState(cell.parentElement as RowElementInternal);\n });\n\n // Request grid re-render to restore cell content\n this.requestRender();\n }\n\n // Mark that focus should be restored after render completes\n this.#pendingFocusRestore = true;\n\n // If no render was scheduled (row not visible), restore focus immediately\n if (!rowEl) {\n this.#restoreCellFocus(internalGrid);\n this.#pendingFocusRestore = false;\n }\n }\n\n /**\n * Commit a single cell value change.\n * Uses ID-based change tracking for stability when rows are reordered.\n */\n #commitCellValue(rowIndex: number, column: ColumnConfig<T>, newValue: unknown, rowData: T): void {\n const field = column.field;\n if (!isSafePropertyKey(field)) return;\n const oldValue = (rowData as Record<string, unknown>)[field];\n if (oldValue === newValue) return;\n\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n\n // Get row ID for change tracking (may not exist if getRowId not configured)\n let rowId: string | undefined;\n try {\n rowId = this.grid.getRowId(rowData);\n } catch {\n // Row has no ID - will still work but won't be tracked in changedRowIds\n }\n\n const firstTime = rowId ? !this.#changedRowIds.has(rowId) : true;\n\n // Create updateRow helper for cascade updates (noop if row has no ID)\n const updateRow: (changes: Partial<T>) => void = rowId\n ? (changes) => this.grid.updateRow(rowId!, changes as Record<string, unknown>, 'cascade')\n : noopUpdateRow;\n\n // Emit cancelable event BEFORE applying the value\n const cancelled = this.emitCancelable<CellCommitDetail<T>>('cell-commit', {\n row: rowData,\n rowId: rowId ?? '',\n field,\n oldValue,\n value: newValue,\n rowIndex,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n firstTimeForRow: firstTime,\n updateRow,\n });\n\n // If consumer called preventDefault(), abort the commit\n if (cancelled) return;\n\n // Apply the value and mark row as changed\n (rowData as Record<string, unknown>)[field] = newValue;\n if (rowId) {\n this.#changedRowIds.add(rowId);\n }\n this.#syncGridEditState();\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (rowEl) rowEl.classList.add('changed');\n }\n\n /**\n * Inject an editor into a cell.\n */\n #injectEditor(\n rowData: T,\n rowIndex: number,\n column: ColumnConfig<T>,\n colIndex: number,\n cell: HTMLElement,\n skipFocus: boolean,\n ): void {\n if (!column.editable) return;\n if (cell.classList.contains('editing')) return;\n\n // Get row ID for updateRow helper (may not exist)\n let rowId: string | undefined;\n try {\n rowId = this.grid.getRowId(rowData);\n } catch {\n // Row has no ID\n }\n\n // Create updateRow helper for cascade updates (noop if row has no ID)\n const updateRow: (changes: Partial<T>) => void = rowId\n ? (changes) => this.grid.updateRow(rowId!, changes as Record<string, unknown>, 'cascade')\n : noopUpdateRow;\n\n const originalValue = isSafePropertyKey(column.field)\n ? (rowData as Record<string, unknown>)[column.field]\n : undefined;\n\n cell.classList.add('editing');\n this.#editingCells.add(`${rowIndex}:${colIndex}`);\n\n const rowEl = cell.parentElement as RowElementInternal | null;\n if (rowEl) incrementEditingCount(rowEl);\n\n let editFinalized = false;\n const commit = (newValue: unknown) => {\n if (editFinalized || this.#activeEditRow === -1) return;\n this.#commitCellValue(rowIndex, column, newValue, rowData);\n };\n const cancel = () => {\n editFinalized = true;\n if (isSafePropertyKey(column.field)) {\n (rowData as Record<string, unknown>)[column.field] = originalValue;\n }\n };\n\n const editorHost = document.createElement('div');\n editorHost.className = 'tbw-editor-host';\n cell.innerHTML = '';\n cell.appendChild(editorHost);\n\n // Keydown handler for Enter/Escape\n editorHost.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter') {\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n e.preventDefault();\n cancel();\n this.#exitRowEdit(rowIndex, true);\n }\n });\n\n const colInternal = column as ColumnInternal<T>;\n const tplHolder = colInternal.__editorTemplate;\n // Resolve editor using priority chain: column → template → typeDefaults → adapter → built-in\n const editorSpec = resolveEditor(this.grid as unknown as InternalGrid<T>, colInternal) ?? defaultEditorFor(column);\n const value = originalValue;\n\n if (editorSpec === 'template' && tplHolder) {\n this.#renderTemplateEditor(editorHost, colInternal, rowData, originalValue, commit, cancel, skipFocus, rowIndex);\n } else if (typeof editorSpec === 'string') {\n const el = document.createElement(editorSpec) as HTMLElement & { value?: unknown };\n el.value = value;\n el.addEventListener('change', () => commit(el.value));\n editorHost.appendChild(el);\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (typeof editorSpec === 'function') {\n const ctx: EditorContext<T> = {\n row: rowData,\n rowId: rowId ?? '',\n value,\n field: column.field,\n column,\n commit,\n cancel,\n updateRow,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const produced = (editorSpec as any)(ctx);\n if (typeof produced === 'string') {\n editorHost.innerHTML = produced;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n wireEditorInputs(editorHost, column as any, commit);\n } else if (produced instanceof Node) {\n editorHost.appendChild(produced);\n }\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (editorSpec && typeof editorSpec === 'object') {\n const placeholder = document.createElement('div');\n placeholder.setAttribute('data-external-editor', '');\n placeholder.setAttribute('data-field', column.field);\n editorHost.appendChild(placeholder);\n const context: EditorContext<T> = {\n row: rowData,\n rowId: rowId ?? '',\n value,\n field: column.field,\n column,\n commit,\n cancel,\n updateRow,\n };\n if (editorSpec.mount) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n editorSpec.mount({ placeholder, context: context as any, spec: editorSpec });\n } catch (e) {\n console.warn(`[tbw-grid] External editor mount error for column '${column.field}':`, e);\n }\n } else {\n (this.grid as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('mount-external-editor', { detail: { placeholder, spec: editorSpec, context } }),\n );\n }\n }\n }\n\n /**\n * Render a template-based editor.\n */\n #renderTemplateEditor(\n editorHost: HTMLElement,\n column: ColumnInternal<T>,\n rowData: T,\n originalValue: unknown,\n commit: (value: unknown) => void,\n cancel: () => void,\n skipFocus: boolean,\n rowIndex: number,\n ): void {\n const tplHolder = column.__editorTemplate;\n if (!tplHolder) return;\n\n const clone = tplHolder.cloneNode(true) as HTMLElement;\n const compiledEditor = column.__compiledEditor;\n\n if (compiledEditor) {\n clone.innerHTML = compiledEditor({\n row: rowData,\n value: originalValue,\n field: column.field,\n column,\n commit,\n cancel,\n });\n } else {\n clone.querySelectorAll<HTMLElement>('*').forEach((node) => {\n if (node.childNodes.length === 1 && node.firstChild?.nodeType === Node.TEXT_NODE) {\n node.textContent =\n node.textContent\n ?.replace(/{{\\s*value\\s*}}/g, originalValue == null ? '' : String(originalValue))\n .replace(/{{\\s*row\\.([a-zA-Z0-9_]+)\\s*}}/g, (_m, g: string) => {\n if (!isSafePropertyKey(g)) return '';\n const v = (rowData as Record<string, unknown>)[g];\n return v == null ? '' : String(v);\n }) || '';\n }\n });\n }\n\n const input = clone.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!originalValue;\n } else {\n input.value = String(originalValue ?? '');\n }\n\n let editFinalized = false;\n input.addEventListener('blur', () => {\n if (editFinalized) return;\n commit(getInputValue(input, column));\n });\n input.addEventListener('keydown', (evt) => {\n const e = evt as KeyboardEvent;\n if (e.key === 'Enter') {\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n commit(getInputValue(input, column));\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n e.preventDefault();\n cancel();\n this.#exitRowEdit(rowIndex, true);\n }\n });\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n }\n if (!skipFocus) {\n setTimeout(() => input.focus({ preventScroll: true }), 0);\n }\n }\n editorHost.appendChild(clone);\n }\n\n /**\n * Restore focus to cell after exiting edit mode.\n */\n #restoreCellFocus(internalGrid: InternalGrid<T>): void {\n queueMicrotask(() => {\n try {\n const rowIdx = internalGrid._focusRow;\n const colIdx = internalGrid._focusCol;\n const rowEl = internalGrid.findRenderedRowElement?.(rowIdx);\n if (rowEl) {\n Array.from(internalGrid._bodyEl.querySelectorAll('.cell-focus')).forEach((el) =>\n el.classList.remove('cell-focus'),\n );\n const cell = rowEl.querySelector(`.cell[data-row=\"${rowIdx}\"][data-col=\"${colIdx}\"]`) as HTMLElement | null;\n if (cell) {\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n cell.focus({ preventScroll: true });\n }\n }\n } catch {\n /* empty */\n }\n });\n }\n\n // #endregion\n}\n"],"names":["resolveOptions","column","raw","createNumberEditor","ctx","params","input","commit","e","createBooleanEditor","createDateEditor","createSelectEditor","select","emptyOpt","opt","o","values","createTextEditor","defaultEditorFor","FOCUSABLE_EDITOR_SELECTOR","resolveEditor","grid","col","gridTypeDefaults","adapter","appDefault","isSafePropertyKey","key","incrementEditingCount","rowEl","count","clearEditingState","getInputValue","noopUpdateRow","_changes","wireEditorInputs","editorHost","EditingPlugin","BaseGridPlugin","v","styles","#activeEditRow","#activeEditCol","#rowEditSnapshots","#changedRowIds","#editingCells","#pendingFocusRestore","signal","internalGrid","silent","rowIndex","field","#exitRowEdit","event","editOn","isDoubleClick","focusRow","focusCol","rowData","newValue","#commitCellValue","row","value","cellEl","activateEvent","legacyEvent","columns","typeDefaults","typeEditorParams","#restoreCellFocus","cellKey","rowStr","colStr","colIndex","#injectEditor","rows","id","rowId","ids","#syncGridEditState","r","c","#beginCellEdit","#startRowEdit","cell","i","targetCell","editor","revert","snapshot","current","val","k","changed","oldValue","firstTime","updateRow","changes","skipFocus","originalValue","editFinalized","cancel","colInternal","tplHolder","editorSpec","#renderTemplateEditor","el","produced","placeholder","context","clone","compiledEditor","node","_m","g","evt","rowIdx","colIdx"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(d,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],c):(d=typeof globalThis<"u"?globalThis:d||self,c(d.TbwGridPlugin_groupingColumns={},d.TbwGrid))})(this,(function(d,c){"use strict";function
|
|
1
|
+
(function(d,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],c):(d=typeof globalThis<"u"?globalThis:d||self,c(d.TbwGridPlugin_groupingColumns={},d.TbwGrid))})(this,(function(d,c){"use strict";function g(u){if(!u.length)return[];const n=new Map,r=[],t=(e,l)=>{if(!l.length)return;const s=r[r.length-1];if(s&&s.implicit&&s.firstIndex+s.columns.length===e){s.columns.push(...l);return}r.push({id:"__implicit__"+e,label:void 0,columns:l,firstIndex:e,implicit:!0})};let i=[],o=0;return u.forEach((e,l)=>{const s=e.group;if(!s){i.length===0&&(o=l),i.push(e);return}i.length&&(t(o,i.slice()),i=[]);const p=typeof s=="string"?s:s.id;let a=n.get(p);a||(a={id:p,label:typeof s=="string"?void 0:s.label,columns:[],firstIndex:l},n.set(p,a),r.push(a)),a.columns.push(e)}),i.length&&t(o,i),r.length===1&&r[0].implicit&&r[0].columns.length===u.length?[]:r}function f(u,n,r){if(!n.length||!u)return;const t=new Map;for(const o of n)for(const e of o.columns)e.field&&t.set(e.field,o.id);const i=Array.from(u.querySelectorAll(".cell[data-field]"));i.forEach(o=>{const e=o.getAttribute("data-field")||"",l=t.get(e);l&&(o.classList.add("grouped"),o.getAttribute("data-group")||o.setAttribute("data-group",l))});for(const o of n){const e=o.columns[o.columns.length-1],l=i.find(s=>s.getAttribute("data-field")===e.field);l&&l.classList.add("group-end")}}function h(u,n){if(u.length===0)return null;const r=document.createElement("div");r.className="header-group-row",r.setAttribute("role","row");for(const t of u){const i=t.columns[0],o=i?n.findIndex(p=>p.field===i.field):-1;if(o===-1)continue;const e=String(t.id).startsWith("__implicit__"),l=e?"":t.label||t.id,s=document.createElement("div");s.className="cell header-group-cell",e&&s.classList.add("implicit-group"),s.setAttribute("data-group",String(t.id)),s.style.gridColumn=`${o+1} / span ${t.columns.length}`,s.textContent=l,r.appendChild(s)}return r}function m(u){return u.some(n=>n.group!=null)}const b="@layer tbw-plugins{.header-group-row{display:grid;grid-auto-flow:column;background:var(--tbw-grouping-columns-header-bg, var(--tbw-color-header-bg));border-bottom:1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border))}.header-group-cell{display:flex;align-items:center;justify-content:center;padding:var(--tbw-button-padding-sm, .25rem .5rem);font-weight:600;font-size:var(--tbw-font-size-sm, .9em);text-transform:uppercase;letter-spacing:.5px;border-right:2px solid var(--tbw-grouping-columns-separator, var(--tbw-color-border-strong, var(--tbw-color-border)))}.header-group-cell:last-child{border-right:none}.header-row .cell.grouped{border-top:none}.header-row .cell.group-end{border-right:2px solid var(--tbw-grouping-columns-separator, var(--tbw-color-border-strong, var(--tbw-color-border)))}.header-row .cell.group-end:last-child{border-right:none}.rows .cell.group-end{border-right:2px solid var(--tbw-grouping-columns-separator, var(--tbw-color-border-strong, var(--tbw-color-border)))}.rows .cell.group-end:last-child{border-right:none}.header-group-row.no-borders{border-bottom:none}.header-group-row.no-borders .header-group-cell{border-right:none}.header-row.no-group-borders .cell.group-end{border-right:1px solid var(--tbw-color-border)}}";class w extends c.BaseGridPlugin{static manifest={ownedProperties:[{property:"group",level:"column",description:'the "group" column property'},{property:"columnGroups",level:"config",description:'the "columnGroups" config property',isUsed:n=>Array.isArray(n)&&n.length>0}]};name="groupingColumns";styles=b;get defaultConfig(){return{showGroupBorders:!0}}groups=[];isActive=!1;detach(){this.groups=[],this.isActive=!1}static detect(n,r){if(r?.columnGroups&&Array.isArray(r.columnGroups)&&r.columnGroups.length>0)return!0;const t=r?.columns;return Array.isArray(t)?m(t):!1}processColumns(n){const r=this.grid?.gridConfig?.columnGroups;let t;if(r&&Array.isArray(r)&&r.length>0){const o=new Map;for(const e of r)for(const l of e.children)o.set(l,{id:e.id,label:e.header});t=n.map(e=>{const l=o.get(e.field);return l&&!e.group?{...e,group:l}:e})}else t=[...n];const i=g(t);return i.length===0?(this.isActive=!1,this.groups=[],t):(this.isActive=!0,this.groups=i,t)}afterRender(){if(!this.isActive){const s=this.gridElement?.querySelector(".header")?.querySelector(".header-group-row");s&&s.remove();return}const n=this.gridElement?.querySelector(".header");if(!n)return;const r=n.querySelector(".header-group-row");r&&r.remove();const t=this.columns,i=g(t);if(i.length===0)return;const o=h(i,t);if(o){o.classList.toggle("no-borders",!this.config.showGroupBorders);const l=n.querySelector(".header-row");l?n.insertBefore(o,l):n.appendChild(o)}const e=n.querySelector(".header-row");e&&(e.classList.toggle("no-group-borders",!this.config.showGroupBorders),f(e,i)),this.#e(i)}#e(n){if(!this.config.showGroupBorders)return;const r=this.gridElement;if(!r)return;const t=new Set;for(const o of n){const e=o.columns[o.columns.length-1];e?.field&&t.add(e.field)}const i=r.querySelectorAll(".rows .cell[data-field]");for(const o of i){const e=o.getAttribute("data-field");e&&t.has(e)?o.classList.add("group-end"):o.classList.remove("group-end")}}isGroupingActive(){return this.isActive}getGroups(){return this.groups}getGroupColumns(n){const r=this.groups.find(t=>t.id===n);return r?r.columns:[]}refresh(){this.requestRender()}}d.GroupingColumnsPlugin=w,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=grouping-columns.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grouping-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/grouping-columns/grouping-columns.ts","../../../../../libs/grid/src/lib/plugins/grouping-columns/GroupingColumnsPlugin.ts"],"sourcesContent":["/**\n * Column Groups Core Logic\n *\n * Pure functions for computing and managing column header groups.\n */\n\n// Import types to enable module augmentation\nimport type { ColumnConfig } from '../../core/types';\nimport './types';\nimport type { ColumnGroup, ColumnGroupInternal } from './types';\n\n/**\n * Compute column groups from column configuration.\n * Handles explicit groups (via column.group) and creates implicit groups for ungrouped columns.\n *\n * @param columns - Array of column configurations\n * @returns Array of column groups, or empty if no meaningful groups\n */\nexport function computeColumnGroups<T>(columns: ColumnConfig<T>[]): ColumnGroup<T>[] {\n if (!columns.length) return [];\n\n const explicitMap = new Map<string, ColumnGroupInternal<T>>();\n const groupsOrdered: ColumnGroupInternal<T>[] = [];\n\n // Helper to push unnamed implicit group for a run of ungrouped columns\n const pushImplicit = (startIdx: number, cols: ColumnConfig<T>[]) => {\n if (!cols.length) return;\n // Merge with previous implicit group if adjacent to reduce noise\n const prev = groupsOrdered[groupsOrdered.length - 1];\n if (prev && prev.implicit && prev.firstIndex + prev.columns.length === startIdx) {\n prev.columns.push(...cols);\n return;\n }\n groupsOrdered.push({\n id: '__implicit__' + startIdx,\n label: undefined,\n columns: cols,\n firstIndex: startIdx,\n implicit: true,\n });\n };\n\n let run: ColumnConfig<T>[] = [];\n let runStart = 0;\n\n columns.forEach((col, idx) => {\n const g = col.group;\n if (!g) {\n if (run.length === 0) runStart = idx;\n run.push(col);\n return;\n }\n // Close any pending implicit run\n if (run.length) {\n pushImplicit(runStart, run.slice());\n run = [];\n }\n const id = typeof g === 'string' ? g : g.id;\n let group = explicitMap.get(id);\n if (!group) {\n group = {\n id,\n label: typeof g === 'string' ? undefined : g.label,\n columns: [],\n firstIndex: idx,\n };\n explicitMap.set(id, group);\n groupsOrdered.push(group);\n }\n group.columns.push(col);\n });\n\n // Trailing implicit run\n if (run.length) pushImplicit(runStart, run);\n\n // If we only have a single implicit group covering all columns, treat as no groups\n if (groupsOrdered.length === 1 && groupsOrdered[0].implicit && groupsOrdered[0].columns.length === columns.length) {\n return [];\n }\n\n return groupsOrdered as ColumnGroup<T>[];\n}\n\n/**\n * Apply CSS classes to header cells based on their group membership.\n *\n * @param headerRowEl - The header row element\n * @param groups - The computed column groups\n * @param columns - The column configurations\n */\nexport function applyGroupedHeaderCellClasses(\n headerRowEl: HTMLElement | null,\n groups: ColumnGroup[],\n columns: ColumnConfig[],\n): void {\n if (!groups.length || !headerRowEl) return;\n\n const fieldToGroup = new Map<string, string>();\n for (const g of groups) {\n for (const c of g.columns) {\n if (c.field) {\n fieldToGroup.set(c.field, g.id);\n }\n }\n }\n\n const headerCells = Array.from(headerRowEl.querySelectorAll('.cell[data-field]')) as HTMLElement[];\n headerCells.forEach((cell) => {\n const f = cell.getAttribute('data-field') || '';\n const gid = fieldToGroup.get(f);\n if (gid) {\n cell.classList.add('grouped');\n if (!cell.getAttribute('data-group')) {\n cell.setAttribute('data-group', gid);\n }\n }\n });\n\n // Mark group end cells for styling\n for (const g of groups) {\n const last = g.columns[g.columns.length - 1];\n const cell = headerCells.find((c) => c.getAttribute('data-field') === last.field);\n if (cell) cell.classList.add('group-end');\n }\n}\n\n/**\n * Build the group header row element.\n *\n * @param groups - The computed column groups\n * @param columns - The column configurations (final array including any plugin-added columns)\n * @returns The group header row element, or null if no groups\n */\nexport function buildGroupHeaderRow(groups: ColumnGroup[], columns: ColumnConfig[]): HTMLElement | null {\n if (groups.length === 0) return null;\n\n const groupRow = document.createElement('div');\n groupRow.className = 'header-group-row';\n groupRow.setAttribute('role', 'row');\n\n for (const g of groups) {\n // Always compute start index from the current columns array, not stored firstIndex.\n // This accounts for plugin-added columns (e.g., expander) that weren't present\n // when the groups were initially computed during processColumns.\n const firstGroupCol = g.columns[0];\n const startIndex = firstGroupCol ? columns.findIndex((c) => c.field === firstGroupCol.field) : -1;\n if (startIndex === -1) continue; // Group columns not in final column list\n\n const isImplicit = String(g.id).startsWith('__implicit__');\n const label = isImplicit ? '' : g.label || g.id;\n\n const cell = document.createElement('div');\n cell.className = 'cell header-group-cell';\n if (isImplicit) cell.classList.add('implicit-group');\n cell.setAttribute('data-group', String(g.id));\n cell.style.gridColumn = `${startIndex + 1} / span ${g.columns.length}`;\n cell.textContent = label;\n groupRow.appendChild(cell);\n }\n\n return groupRow;\n}\n\n/**\n * Check if any columns have group configuration.\n *\n * @param columns - The column configurations\n * @returns True if at least one column has a group\n */\nexport function hasColumnGroups(columns: ColumnConfig[]): boolean {\n return columns.some((col) => col.group != null);\n}\n\n/**\n * Get group ID for a specific column.\n *\n * @param column - The column configuration\n * @returns The group ID, or undefined if not grouped\n */\nexport function getColumnGroupId(column: ColumnConfig): string | undefined {\n const g = column.group;\n if (!g) return undefined;\n return typeof g === 'string' ? g : g.id;\n}\n","/**\n * Column Groups Plugin (Class-based)\n *\n * Enables multi-level column header grouping.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n applyGroupedHeaderCellClasses,\n buildGroupHeaderRow,\n computeColumnGroups,\n hasColumnGroups,\n} from './grouping-columns';\nimport styles from './grouping-columns.css?inline';\nimport type { ColumnGroup, GroupingColumnsConfig } from './types';\n\n/**\n * Column Grouping Plugin for tbw-grid\n *\n * Enables visual grouping of columns under shared headers. Supports two approaches:\n * declarative `columnGroups` at the grid level, or inline `group` property on columns.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingColumnsPlugin } from '@toolbox-web/grid/plugins/grouping-columns';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `showGroupBorders` | `boolean` | `true` | Show borders between groups |\n * | `groupHeaderRenderer` | `function` | - | Custom renderer for group header content |\n *\n * ## Grid Config: `columnGroups`\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `id` | `string` | Unique group identifier |\n * | `header` | `string` | Display label for the group header |\n * | `children` | `string[]` | Array of column field names in this group |\n *\n * ## Column Config: `group`\n *\n * | Type | Description |\n * |------|-------------|\n * | `string` | Simple group ID (used as both id and label) |\n * | `{ id: string; label?: string }` | Group object with explicit id and optional label |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `isGroupingActive` | `() => boolean` | Check if grouping is active |\n * | `getGroups` | `() => ColumnGroup[]` | Get all computed groups |\n * | `getGroupColumns` | `(groupId) => ColumnConfig[]` | Get columns in a specific group |\n * | `refresh` | `() => void` | Force refresh of column groups |\n *\n * @example Declarative columnGroups (Recommended)\n * ```ts\n * import '@toolbox-web/grid';\n * import { GroupingColumnsPlugin } from '@toolbox-web/grid/plugins/grouping-columns';\n *\n * grid.gridConfig = {\n * columnGroups: [\n * { id: 'personal', header: 'Personal Info', children: ['firstName', 'lastName', 'email'] },\n * { id: 'work', header: 'Work Info', children: ['department', 'title', 'salary'] },\n * ],\n * columns: [\n * { field: 'firstName', header: 'First Name' },\n * { field: 'lastName', header: 'Last Name' },\n * // ...\n * ],\n * plugins: [new GroupingColumnsPlugin()],\n * };\n * ```\n *\n * @example Inline group Property\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'firstName', header: 'First Name', group: { id: 'personal', label: 'Personal Info' } },\n * { field: 'lastName', header: 'Last Name', group: 'personal' }, // string shorthand\n * ],\n * plugins: [new GroupingColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link GroupingColumnsConfig} for all configuration options\n * @see {@link ColumnGroup} for the group structure\n * @see {@link ReorderPlugin} for drag-to-reorder within groups\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingColumnsPlugin extends BaseGridPlugin<GroupingColumnsConfig> {\n /** @internal */\n readonly name = 'groupingColumns';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingColumnsConfig> {\n return {\n showGroupBorders: true,\n };\n }\n\n // #region Internal State\n private groups: ColumnGroup[] = [];\n private isActive = false;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.groups = [];\n this.isActive = false;\n }\n // #endregion\n\n // #region Static Detection\n\n /**\n * Auto-detect column groups from column configuration.\n * Detects both inline `column.group` properties and declarative `columnGroups` config.\n */\n static detect(rows: readonly any[], config: any): boolean {\n // Check for declarative columnGroups in config\n if (config?.columnGroups && Array.isArray(config.columnGroups) && config.columnGroups.length > 0) {\n return true;\n }\n // Check for inline group properties on columns\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasColumnGroups(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // First, check if gridConfig.columnGroups is defined and apply to columns\n const columnGroups = this.grid?.gridConfig?.columnGroups;\n let processedColumns: ColumnConfig[];\n\n if (columnGroups && Array.isArray(columnGroups) && columnGroups.length > 0) {\n // Build a map of field -> group info from the declarative config\n const fieldToGroup = new Map<string, { id: string; label: string }>();\n for (const group of columnGroups) {\n for (const field of group.children) {\n fieldToGroup.set(field, { id: group.id, label: group.header });\n }\n }\n\n // Apply group property to columns that don't already have one\n processedColumns = columns.map((col) => {\n const groupInfo = fieldToGroup.get(col.field);\n if (groupInfo && !col.group) {\n return { ...col, group: groupInfo };\n }\n return col;\n });\n } else {\n processedColumns = [...columns];\n }\n\n // Compute groups from column definitions (now including applied groups)\n const groups = computeColumnGroups(processedColumns);\n\n if (groups.length === 0) {\n this.isActive = false;\n this.groups = [];\n return processedColumns;\n }\n\n this.isActive = true;\n this.groups = groups;\n\n // Return columns with group info applied\n return processedColumns;\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isActive) {\n // Remove any existing group header\n const header = this.gridElement?.querySelector('.header');\n const existingGroupRow = header?.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n return;\n }\n\n const header = this.gridElement?.querySelector('.header');\n if (!header) return;\n\n // Remove existing group row if present\n const existingGroupRow = header.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n\n // Recompute groups from the final column list (which includes plugin-added columns like expander).\n // The groups computed during processColumns may be stale if other plugins added columns.\n const finalColumns = this.columns as ColumnConfig[];\n const groups = computeColumnGroups(finalColumns);\n if (groups.length === 0) return;\n\n // Build and insert group header row\n const groupRow = buildGroupHeaderRow(groups, finalColumns);\n if (groupRow) {\n // Toggle border visibility class\n groupRow.classList.toggle('no-borders', !this.config.showGroupBorders);\n\n const headerRow = header.querySelector('.header-row');\n if (headerRow) {\n header.insertBefore(groupRow, headerRow);\n } else {\n header.appendChild(groupRow);\n }\n }\n\n // Apply classes to header cells\n const headerRow = header.querySelector('.header-row') as HTMLElement;\n if (headerRow) {\n // Toggle border visibility on header cells\n headerRow.classList.toggle('no-group-borders', !this.config.showGroupBorders);\n applyGroupedHeaderCellClasses(headerRow, groups, finalColumns);\n }\n\n // Apply group-end class to data cells for continuous border styling\n this.#applyGroupEndToDataCells(groups);\n }\n\n /**\n * Apply group-end class to all data cells in the last column of each group.\n * This extends the strong border separator through all data rows.\n */\n #applyGroupEndToDataCells(groups: ColumnGroup[]): void {\n if (!this.config.showGroupBorders) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Collect the field names of all group-end columns\n const groupEndFields = new Set<string>();\n for (const g of groups) {\n const lastCol = g.columns[g.columns.length - 1];\n if (lastCol?.field) {\n groupEndFields.add(lastCol.field);\n }\n }\n\n // Apply group-end class to all data cells with those fields\n const allDataCells = gridEl.querySelectorAll('.rows .cell[data-field]');\n for (const cell of allDataCells) {\n const field = cell.getAttribute('data-field');\n if (field && groupEndFields.has(field)) {\n cell.classList.add('group-end');\n } else {\n cell.classList.remove('group-end');\n }\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Check if column groups are active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Get the computed column groups.\n * @returns Array of column groups\n */\n getGroups(): ColumnGroup[] {\n return this.groups;\n }\n\n /**\n * Get columns in a specific group.\n * @param groupId - The group ID to find\n * @returns Array of columns in the group\n */\n getGroupColumns(groupId: string): ColumnConfig[] {\n const group = this.groups.find((g) => g.id === groupId);\n return group ? group.columns : [];\n }\n\n /**\n * Refresh column groups (recompute from current columns).\n */\n refresh(): void {\n this.requestRender();\n }\n // #endregion\n}\n"],"names":["computeColumnGroups","columns","explicitMap","groupsOrdered","pushImplicit","startIdx","cols","prev","run","runStart","col","idx","g","id","group","applyGroupedHeaderCellClasses","headerRowEl","groups","fieldToGroup","c","headerCells","cell","f","gid","last","buildGroupHeaderRow","groupRow","firstGroupCol","startIndex","isImplicit","label","hasColumnGroups","GroupingColumnsPlugin","BaseGridPlugin","styles","rows","config","columnGroups","processedColumns","field","groupInfo","existingGroupRow","header","finalColumns","headerRow","#applyGroupEndToDataCells","gridEl","groupEndFields","lastCol","allDataCells","groupId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"grouping-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/grouping-columns/grouping-columns.ts","../../../../../libs/grid/src/lib/plugins/grouping-columns/GroupingColumnsPlugin.ts"],"sourcesContent":["/**\n * Column Groups Core Logic\n *\n * Pure functions for computing and managing column header groups.\n */\n\n// Import types to enable module augmentation\nimport type { ColumnConfig } from '../../core/types';\nimport './types';\nimport type { ColumnGroup, ColumnGroupInternal } from './types';\n\n/**\n * Compute column groups from column configuration.\n * Handles explicit groups (via column.group) and creates implicit groups for ungrouped columns.\n *\n * @param columns - Array of column configurations\n * @returns Array of column groups, or empty if no meaningful groups\n */\nexport function computeColumnGroups<T>(columns: ColumnConfig<T>[]): ColumnGroup<T>[] {\n if (!columns.length) return [];\n\n const explicitMap = new Map<string, ColumnGroupInternal<T>>();\n const groupsOrdered: ColumnGroupInternal<T>[] = [];\n\n // Helper to push unnamed implicit group for a run of ungrouped columns\n const pushImplicit = (startIdx: number, cols: ColumnConfig<T>[]) => {\n if (!cols.length) return;\n // Merge with previous implicit group if adjacent to reduce noise\n const prev = groupsOrdered[groupsOrdered.length - 1];\n if (prev && prev.implicit && prev.firstIndex + prev.columns.length === startIdx) {\n prev.columns.push(...cols);\n return;\n }\n groupsOrdered.push({\n id: '__implicit__' + startIdx,\n label: undefined,\n columns: cols,\n firstIndex: startIdx,\n implicit: true,\n });\n };\n\n let run: ColumnConfig<T>[] = [];\n let runStart = 0;\n\n columns.forEach((col, idx) => {\n const g = col.group;\n if (!g) {\n if (run.length === 0) runStart = idx;\n run.push(col);\n return;\n }\n // Close any pending implicit run\n if (run.length) {\n pushImplicit(runStart, run.slice());\n run = [];\n }\n const id = typeof g === 'string' ? g : g.id;\n let group = explicitMap.get(id);\n if (!group) {\n group = {\n id,\n label: typeof g === 'string' ? undefined : g.label,\n columns: [],\n firstIndex: idx,\n };\n explicitMap.set(id, group);\n groupsOrdered.push(group);\n }\n group.columns.push(col);\n });\n\n // Trailing implicit run\n if (run.length) pushImplicit(runStart, run);\n\n // If we only have a single implicit group covering all columns, treat as no groups\n if (groupsOrdered.length === 1 && groupsOrdered[0].implicit && groupsOrdered[0].columns.length === columns.length) {\n return [];\n }\n\n return groupsOrdered as ColumnGroup<T>[];\n}\n\n/**\n * Apply CSS classes to header cells based on their group membership.\n *\n * @param headerRowEl - The header row element\n * @param groups - The computed column groups\n * @param columns - The column configurations\n */\nexport function applyGroupedHeaderCellClasses(\n headerRowEl: HTMLElement | null,\n groups: ColumnGroup[],\n columns: ColumnConfig[],\n): void {\n if (!groups.length || !headerRowEl) return;\n\n const fieldToGroup = new Map<string, string>();\n for (const g of groups) {\n for (const c of g.columns) {\n if (c.field) {\n fieldToGroup.set(c.field, g.id);\n }\n }\n }\n\n const headerCells = Array.from(headerRowEl.querySelectorAll('.cell[data-field]')) as HTMLElement[];\n headerCells.forEach((cell) => {\n const f = cell.getAttribute('data-field') || '';\n const gid = fieldToGroup.get(f);\n if (gid) {\n cell.classList.add('grouped');\n if (!cell.getAttribute('data-group')) {\n cell.setAttribute('data-group', gid);\n }\n }\n });\n\n // Mark group end cells for styling\n for (const g of groups) {\n const last = g.columns[g.columns.length - 1];\n const cell = headerCells.find((c) => c.getAttribute('data-field') === last.field);\n if (cell) cell.classList.add('group-end');\n }\n}\n\n/**\n * Build the group header row element.\n *\n * @param groups - The computed column groups\n * @param columns - The column configurations (final array including any plugin-added columns)\n * @returns The group header row element, or null if no groups\n */\nexport function buildGroupHeaderRow(groups: ColumnGroup[], columns: ColumnConfig[]): HTMLElement | null {\n if (groups.length === 0) return null;\n\n const groupRow = document.createElement('div');\n groupRow.className = 'header-group-row';\n groupRow.setAttribute('role', 'row');\n\n for (const g of groups) {\n // Always compute start index from the current columns array, not stored firstIndex.\n // This accounts for plugin-added columns (e.g., expander) that weren't present\n // when the groups were initially computed during processColumns.\n const firstGroupCol = g.columns[0];\n const startIndex = firstGroupCol ? columns.findIndex((c) => c.field === firstGroupCol.field) : -1;\n if (startIndex === -1) continue; // Group columns not in final column list\n\n const isImplicit = String(g.id).startsWith('__implicit__');\n const label = isImplicit ? '' : g.label || g.id;\n\n const cell = document.createElement('div');\n cell.className = 'cell header-group-cell';\n if (isImplicit) cell.classList.add('implicit-group');\n cell.setAttribute('data-group', String(g.id));\n cell.style.gridColumn = `${startIndex + 1} / span ${g.columns.length}`;\n cell.textContent = label;\n groupRow.appendChild(cell);\n }\n\n return groupRow;\n}\n\n/**\n * Check if any columns have group configuration.\n *\n * @param columns - The column configurations\n * @returns True if at least one column has a group\n */\nexport function hasColumnGroups(columns: ColumnConfig[]): boolean {\n return columns.some((col) => col.group != null);\n}\n\n/**\n * Get group ID for a specific column.\n *\n * @param column - The column configuration\n * @returns The group ID, or undefined if not grouped\n */\nexport function getColumnGroupId(column: ColumnConfig): string | undefined {\n const g = column.group;\n if (!g) return undefined;\n return typeof g === 'string' ? g : g.id;\n}\n","/**\n * Column Groups Plugin (Class-based)\n *\n * Enables multi-level column header grouping.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { PluginManifest } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n applyGroupedHeaderCellClasses,\n buildGroupHeaderRow,\n computeColumnGroups,\n hasColumnGroups,\n} from './grouping-columns';\nimport styles from './grouping-columns.css?inline';\nimport type { ColumnGroup, GroupingColumnsConfig } from './types';\n\n/**\n * Column Grouping Plugin for tbw-grid\n *\n * Enables visual grouping of columns under shared headers. Supports two approaches:\n * declarative `columnGroups` at the grid level, or inline `group` property on columns.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingColumnsPlugin } from '@toolbox-web/grid/plugins/grouping-columns';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `showGroupBorders` | `boolean` | `true` | Show borders between groups |\n * | `groupHeaderRenderer` | `function` | - | Custom renderer for group header content |\n *\n * ## Grid Config: `columnGroups`\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `id` | `string` | Unique group identifier |\n * | `header` | `string` | Display label for the group header |\n * | `children` | `string[]` | Array of column field names in this group |\n *\n * ## Column Config: `group`\n *\n * | Type | Description |\n * |------|-------------|\n * | `string` | Simple group ID (used as both id and label) |\n * | `{ id: string; label?: string }` | Group object with explicit id and optional label |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `isGroupingActive` | `() => boolean` | Check if grouping is active |\n * | `getGroups` | `() => ColumnGroup[]` | Get all computed groups |\n * | `getGroupColumns` | `(groupId) => ColumnConfig[]` | Get columns in a specific group |\n * | `refresh` | `() => void` | Force refresh of column groups |\n *\n * @example Declarative columnGroups (Recommended)\n * ```ts\n * import '@toolbox-web/grid';\n * import { GroupingColumnsPlugin } from '@toolbox-web/grid/plugins/grouping-columns';\n *\n * grid.gridConfig = {\n * columnGroups: [\n * { id: 'personal', header: 'Personal Info', children: ['firstName', 'lastName', 'email'] },\n * { id: 'work', header: 'Work Info', children: ['department', 'title', 'salary'] },\n * ],\n * columns: [\n * { field: 'firstName', header: 'First Name' },\n * { field: 'lastName', header: 'Last Name' },\n * // ...\n * ],\n * plugins: [new GroupingColumnsPlugin()],\n * };\n * ```\n *\n * @example Inline group Property\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'firstName', header: 'First Name', group: { id: 'personal', label: 'Personal Info' } },\n * { field: 'lastName', header: 'Last Name', group: 'personal' }, // string shorthand\n * ],\n * plugins: [new GroupingColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link GroupingColumnsConfig} for all configuration options\n * @see {@link ColumnGroup} for the group structure\n * @see {@link ReorderPlugin} for drag-to-reorder within groups\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingColumnsPlugin extends BaseGridPlugin<GroupingColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties for configuration validation.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'group',\n level: 'column',\n description: 'the \"group\" column property',\n },\n {\n property: 'columnGroups',\n level: 'config',\n description: 'the \"columnGroups\" config property',\n isUsed: (v) => Array.isArray(v) && v.length > 0,\n },\n ],\n };\n\n /** @internal */\n readonly name = 'groupingColumns';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingColumnsConfig> {\n return {\n showGroupBorders: true,\n };\n }\n\n // #region Internal State\n private groups: ColumnGroup[] = [];\n private isActive = false;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.groups = [];\n this.isActive = false;\n }\n // #endregion\n\n // #region Static Detection\n\n /**\n * Auto-detect column groups from column configuration.\n * Detects both inline `column.group` properties and declarative `columnGroups` config.\n */\n static detect(rows: readonly any[], config: any): boolean {\n // Check for declarative columnGroups in config\n if (config?.columnGroups && Array.isArray(config.columnGroups) && config.columnGroups.length > 0) {\n return true;\n }\n // Check for inline group properties on columns\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasColumnGroups(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // First, check if gridConfig.columnGroups is defined and apply to columns\n const columnGroups = this.grid?.gridConfig?.columnGroups;\n let processedColumns: ColumnConfig[];\n\n if (columnGroups && Array.isArray(columnGroups) && columnGroups.length > 0) {\n // Build a map of field -> group info from the declarative config\n const fieldToGroup = new Map<string, { id: string; label: string }>();\n for (const group of columnGroups) {\n for (const field of group.children) {\n fieldToGroup.set(field, { id: group.id, label: group.header });\n }\n }\n\n // Apply group property to columns that don't already have one\n processedColumns = columns.map((col) => {\n const groupInfo = fieldToGroup.get(col.field);\n if (groupInfo && !col.group) {\n return { ...col, group: groupInfo };\n }\n return col;\n });\n } else {\n processedColumns = [...columns];\n }\n\n // Compute groups from column definitions (now including applied groups)\n const groups = computeColumnGroups(processedColumns);\n\n if (groups.length === 0) {\n this.isActive = false;\n this.groups = [];\n return processedColumns;\n }\n\n this.isActive = true;\n this.groups = groups;\n\n // Return columns with group info applied\n return processedColumns;\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isActive) {\n // Remove any existing group header\n const header = this.gridElement?.querySelector('.header');\n const existingGroupRow = header?.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n return;\n }\n\n const header = this.gridElement?.querySelector('.header');\n if (!header) return;\n\n // Remove existing group row if present\n const existingGroupRow = header.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n\n // Recompute groups from the final column list (which includes plugin-added columns like expander).\n // The groups computed during processColumns may be stale if other plugins added columns.\n const finalColumns = this.columns as ColumnConfig[];\n const groups = computeColumnGroups(finalColumns);\n if (groups.length === 0) return;\n\n // Build and insert group header row\n const groupRow = buildGroupHeaderRow(groups, finalColumns);\n if (groupRow) {\n // Toggle border visibility class\n groupRow.classList.toggle('no-borders', !this.config.showGroupBorders);\n\n const headerRow = header.querySelector('.header-row');\n if (headerRow) {\n header.insertBefore(groupRow, headerRow);\n } else {\n header.appendChild(groupRow);\n }\n }\n\n // Apply classes to header cells\n const headerRow = header.querySelector('.header-row') as HTMLElement;\n if (headerRow) {\n // Toggle border visibility on header cells\n headerRow.classList.toggle('no-group-borders', !this.config.showGroupBorders);\n applyGroupedHeaderCellClasses(headerRow, groups, finalColumns);\n }\n\n // Apply group-end class to data cells for continuous border styling\n this.#applyGroupEndToDataCells(groups);\n }\n\n /**\n * Apply group-end class to all data cells in the last column of each group.\n * This extends the strong border separator through all data rows.\n */\n #applyGroupEndToDataCells(groups: ColumnGroup[]): void {\n if (!this.config.showGroupBorders) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Collect the field names of all group-end columns\n const groupEndFields = new Set<string>();\n for (const g of groups) {\n const lastCol = g.columns[g.columns.length - 1];\n if (lastCol?.field) {\n groupEndFields.add(lastCol.field);\n }\n }\n\n // Apply group-end class to all data cells with those fields\n const allDataCells = gridEl.querySelectorAll('.rows .cell[data-field]');\n for (const cell of allDataCells) {\n const field = cell.getAttribute('data-field');\n if (field && groupEndFields.has(field)) {\n cell.classList.add('group-end');\n } else {\n cell.classList.remove('group-end');\n }\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Check if column groups are active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Get the computed column groups.\n * @returns Array of column groups\n */\n getGroups(): ColumnGroup[] {\n return this.groups;\n }\n\n /**\n * Get columns in a specific group.\n * @param groupId - The group ID to find\n * @returns Array of columns in the group\n */\n getGroupColumns(groupId: string): ColumnConfig[] {\n const group = this.groups.find((g) => g.id === groupId);\n return group ? group.columns : [];\n }\n\n /**\n * Refresh column groups (recompute from current columns).\n */\n refresh(): void {\n this.requestRender();\n }\n // #endregion\n}\n"],"names":["computeColumnGroups","columns","explicitMap","groupsOrdered","pushImplicit","startIdx","cols","prev","run","runStart","col","idx","g","id","group","applyGroupedHeaderCellClasses","headerRowEl","groups","fieldToGroup","c","headerCells","cell","f","gid","last","buildGroupHeaderRow","groupRow","firstGroupCol","startIndex","isImplicit","label","hasColumnGroups","GroupingColumnsPlugin","BaseGridPlugin","v","styles","rows","config","columnGroups","processedColumns","field","groupInfo","existingGroupRow","header","finalColumns","headerRow","#applyGroupEndToDataCells","gridEl","groupEndFields","lastCol","allDataCells","groupId"],"mappings":"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"}
|