@toolbox-web/grid 1.14.1 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/all.js +2013 -1564
- package/all.js.map +1 -1
- package/index.js +918 -880
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts.map +1 -1
- package/lib/core/internal/columns.d.ts +0 -5
- package/lib/core/internal/columns.d.ts.map +1 -1
- package/lib/core/internal/rows.d.ts.map +1 -1
- package/lib/core/internal/validate-config.d.ts.map +1 -1
- package/lib/core/types.d.ts +12 -0
- package/lib/core/types.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.d.ts.map +1 -1
- package/lib/plugins/column-virtualization/index.js +1 -1
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/ContextMenuPlugin.d.ts +20 -1
- package/lib/plugins/context-menu/ContextMenuPlugin.d.ts.map +1 -1
- package/lib/plugins/context-menu/index.d.ts +1 -1
- package/lib/plugins/context-menu/index.d.ts.map +1 -1
- package/lib/plugins/context-menu/index.js +177 -84
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/context-menu/menu.d.ts +7 -0
- package/lib/plugins/context-menu/menu.d.ts.map +1 -1
- package/lib/plugins/context-menu/types.d.ts +48 -2
- package/lib/plugins/context-menu/types.d.ts.map +1 -1
- package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
- package/lib/plugins/editing/index.js +327 -298
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/FilteringPlugin.d.ts +7 -1
- package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
- package/lib/plugins/filtering/filter-model.d.ts.map +1 -1
- package/lib/plugins/filtering/index.js +173 -138
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts +5 -1
- package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts.map +1 -1
- package/lib/plugins/grouping-columns/index.js +242 -109
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-columns/types.d.ts +7 -0
- package/lib/plugins/grouping-columns/types.d.ts.map +1 -1
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts +11 -1
- package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts.map +1 -1
- package/lib/plugins/pinned-columns/index.d.ts +1 -1
- package/lib/plugins/pinned-columns/index.d.ts.map +1 -1
- package/lib/plugins/pinned-columns/index.js +174 -79
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-columns/pinned-columns.d.ts +23 -4
- package/lib/plugins/pinned-columns/pinned-columns.d.ts.map +1 -1
- package/lib/plugins/pinned-columns/types.d.ts +21 -9
- package/lib/plugins/pinned-columns/types.d.ts.map +1 -1
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/print/index.js.map +1 -1
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/row-reorder/index.js.map +1 -1
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/VisibilityPlugin.d.ts +42 -2
- package/lib/plugins/visibility/VisibilityPlugin.d.ts.map +1 -1
- package/lib/plugins/visibility/index.d.ts +1 -1
- package/lib/plugins/visibility/index.d.ts.map +1 -1
- package/lib/plugins/visibility/index.js +219 -59
- package/lib/plugins/visibility/index.js.map +1 -1
- package/lib/plugins/visibility/types.d.ts +25 -0
- package/lib/plugins/visibility/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/umd/grid.all.umd.js +27 -27
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +21 -21
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/column-virtualization.umd.js +1 -1
- package/umd/plugins/column-virtualization.umd.js.map +1 -1
- package/umd/plugins/context-menu.umd.js +1 -1
- package/umd/plugins/context-menu.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/filtering.umd.js +1 -1
- package/umd/plugins/filtering.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/visibility.umd.js +1 -1
- package/umd/plugins/visibility.umd.js.map +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(c,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],g):(c=typeof globalThis<"u"?globalThis:c||self,g(c.TbwGridPlugin_groupingColumns={},c.TbwGrid))})(this,(function(c,g){"use strict";function p(d){if(!d.length)return[];const o=new Map,i=[],s=(r,n)=>{if(!n.length)return;const l=i[i.length-1];if(l&&l.implicit&&l.firstIndex+l.columns.length===r){l.columns.push(...n);return}i.push({id:"__implicit__"+r,label:void 0,columns:n,firstIndex:r,implicit:!0})};let t=[],e=0;return d.forEach((r,n)=>{const l=r.group;if(!l){t.length===0&&(e=n),t.push(r);return}t.length&&(s(e,t.slice()),t=[]);const u=typeof l=="string"?l:l.id;let a=o.get(u);a||(a={id:u,label:typeof l=="string"?void 0:l.label,columns:[],firstIndex:n},o.set(u,a),i.push(a)),a.columns.push(r)}),t.length&&s(e,t),i.length===1&&i[0].implicit&&i[0].columns.length===d.length?[]:i}function f(d,o,i){if(!o.length||!d)return;const s=new Map;for(const e of o)for(const r of e.columns)r.field&&s.set(r.field,e.id);const t=Array.from(d.querySelectorAll(".cell[data-field]"));t.forEach(e=>{const r=e.getAttribute("data-field")||"",n=s.get(r);n&&(e.classList.add("grouped"),e.getAttribute("data-group")||e.setAttribute("data-group",n))});for(const e of o){const r=e.columns[e.columns.length-1],n=t.find(l=>l.getAttribute("data-field")===r.field);n&&n.classList.add("group-end")}}function h(d,o){if(d.length===0)return null;const i=document.createElement("div");i.className="header-group-row",i.setAttribute("role","row");for(const s of d){const t=s.columns[0],e=t?o.findIndex(u=>u.field===t.field):-1;if(e===-1)continue;const r=String(s.id).startsWith("__implicit__"),n=r?"":s.label||s.id,l=document.createElement("div");l.className="cell header-group-cell",r&&l.classList.add("implicit-group"),l.setAttribute("data-group",String(s.id)),l.style.gridColumn=`${e+1} / span ${s.columns.length}`,l.textContent=n,i.appendChild(l)}return i}function m(d){return d.some(o=>o.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 g.BaseGridPlugin{static manifest={ownedProperties:[{property:"group",level:"column",description:'the "group" column property'},{property:"columnGroups",level:"config",description:'the "columnGroups" config property',isUsed:o=>Array.isArray(o)&&o.length>0}],queries:[{type:"getColumnGrouping",description:"Returns column group metadata for the visibility panel"}]};name="groupingColumns";styles=b;get defaultConfig(){return{showGroupBorders:!0,lockGroupOrder:!1}}groups=[];isActive=!1;#e=new Set;attach(o){super.attach(o),o.addEventListener("column-move",this.#r,{signal:this.disconnectSignal})}detach(){this.groups=[],this.isActive=!1,this.#e.clear()}#r=o=>{if(!this.isActive)return;const i=o,{field:s,columnOrder:t}=i.detail;if(this.config.lockGroupOrder){for(const e of this.groups)if(!e.id.startsWith("__implicit__")&&!this.#i(e,t)){i.preventDefault(),this.#s(s);return}}this.#o(t)};#o(o){this.#e.clear();const i=this.#t(o);for(const s of this.groups){const t=new Set(s.columns.map(e=>e.field));for(let e=o.length-1;e>=0;e--)if(t.has(o[e])){const r=o[e];r!==i&&this.#e.add(r);break}}}#t(o){if(this.groups.length===0)return null;for(let i=o.length-1;i>=0;i--){const s=o[i];for(const t of this.groups)if(t.columns.some(e=>e.field===s)){const e=new Set(t.columns.map(r=>r.field));for(let r=o.length-1;r>=0;r--)if(e.has(o[r]))return o[r]}}return null}#i(o,i){const s=o.columns.map(t=>i.indexOf(t.field)).filter(t=>t!==-1).sort((t,e)=>t-e);return s.length<=1?!0:s.length===s[s.length-1]-s[0]+1}#s(o){const i=this.gridElement?.querySelector(`.header-row [part~="header-cell"][data-field="${o}"]`);i&&(i.style.setProperty("--_flash-color","var(--tbw-color-error)"),i.animate([{backgroundColor:"rgba(from var(--_flash-color) r g b / 30%)"},{backgroundColor:"transparent"}],{duration:400,easing:"ease-out"}))}handleQuery(o){if(o.type==="getColumnGrouping")return this.#n()}#n(){let o;const i=this.grid?.gridConfig?.columnGroups;if(i&&Array.isArray(i)&&i.length>0)o=i.filter(t=>t.children.length>0).map(t=>({id:t.id,label:t.header,fields:[...t.children]}));else if(this.isActive&&this.groups.length>0){o=this.groups.filter(e=>!e.id.startsWith("__implicit__")).map(e=>({id:e.id,label:e.label??e.id,fields:e.columns.map(r=>r.field)}));const t=this.columns;for(const e of t)if(e.hidden&&e.group){const r=typeof e.group=="string"?e.group:e.group.id,n=typeof e.group=="string"?e.group:e.group.label??e.group.id,l=o.find(u=>u.id===r);l?l.fields.includes(e.field)||l.fields.push(e.field):o.push({id:r,label:n,fields:[e.field]})}}else{const t=this.columns,e=new Map;for(const r of t){if(!r.group)continue;const n=typeof r.group=="string"?r.group:r.group.id,l=typeof r.group=="string"?r.group:r.group.label??r.group.id,u=e.get(n);u?u.fields.includes(r.field)||u.fields.push(r.field):e.set(n,{id:n,label:l,fields:[r.field]})}o=Array.from(e.values())}const s=this.grid?.getColumnOrder();if(s&&s.length>0){const t=new Map(s.map((e,r)=>[e,r]));for(const e of o)e.fields.sort((r,n)=>(t.get(r)??1/0)-(t.get(n)??1/0))}return o}static detect(o,i){if(i?.columnGroups&&Array.isArray(i.columnGroups)&&i.columnGroups.length>0)return!0;const s=i?.columns;return Array.isArray(s)?m(s):!1}processColumns(o){const i=this.grid?.gridConfig?.columnGroups;let s;if(i&&Array.isArray(i)&&i.length>0){const e=new Map;for(const r of i)for(const n of r.children)e.set(n,{id:r.id,label:r.header});s=o.map(r=>{const n=e.get(r.field);return n&&!r.group?{...r,group:n}:r})}else s=[...o];const t=p(s);if(t.length===0)return this.isActive=!1,this.groups=[],s;this.isActive=!0,this.groups=t,this.#e.clear();for(const e of t){const r=e.columns[e.columns.length-1];r?.field&&this.#e.add(r.field)}return s}afterRender(){if(!this.isActive){const l=this.gridElement?.querySelector(".header")?.querySelector(".header-group-row");l&&l.remove();return}const o=this.gridElement?.querySelector(".header");if(!o)return;const i=o.querySelector(".header-group-row");i&&i.remove();const s=this.visibleColumns,t=p(s);if(t.length===0)return;this.#e.clear();for(let n=0;n<t.length;n++){const l=t[n],u=l.columns[l.columns.length-1];u?.field&&n<t.length-1&&this.#e.add(u.field)}const e=h(t,s);if(e){e.classList.toggle("no-borders",!this.config.showGroupBorders);const n=o.querySelector(".header-row");n?o.insertBefore(e,n):o.appendChild(e)}const r=o.querySelector(".header-row");r&&(r.classList.toggle("no-group-borders",!this.config.showGroupBorders),f(r,t))}afterCellRender(o){!this.isActive||!this.config.showGroupBorders||o.cellElement.classList.toggle("group-end",this.#e.has(o.column.field))}isGroupingActive(){return this.isActive}getGroups(){return this.groups}getGroupColumns(o){const i=this.groups.find(s=>s.id===o);return i?i.columns:[]}refresh(){this.requestRender()}}c.GroupingColumnsPlugin=w,Object.defineProperty(c,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 type { AfterCellRenderContext, 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 /** Fields that are the last column in a group (for group-end border class). */\n #groupEndFields = new Set<string>();\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.groups = [];\n this.isActive = false;\n this.#groupEndFields.clear();\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 // Pre-compute group-end fields for the afterCellRender hook\n this.#groupEndFields.clear();\n for (const g of groups) {\n const lastCol = g.columns[g.columns.length - 1];\n if (lastCol?.field) {\n this.#groupEndFields.add(lastCol.field);\n }\n }\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\n /**\n * Apply group-end class to individual cells during render and scroll.\n * This is more efficient than querySelectorAll in afterRender and ensures\n * cells recycled during scroll also get the class applied.\n * @internal\n */\n override afterCellRender(context: AfterCellRenderContext): void {\n if (!this.isActive || !this.config.showGroupBorders) return;\n context.cellElement.classList.toggle('group-end', this.#groupEndFields.has(context.column.field));\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","#groupEndFields","rows","config","columnGroups","processedColumns","field","groupInfo","lastCol","existingGroupRow","header","finalColumns","headerRow","context","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 { AfterCellRenderContext, PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnGroupInfo } from '../visibility/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 queries: [{ type: 'getColumnGrouping', description: 'Returns column group metadata for the visibility panel' }],\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 lockGroupOrder: false,\n };\n }\n\n // #region Internal State\n private groups: ColumnGroup[] = [];\n private isActive = false;\n /** Fields that are the last column in a group (for group-end border class). */\n #groupEndFields = new Set<string>();\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for cancelable column-move events to enforce group contiguity\n (grid as unknown as HTMLElement).addEventListener('column-move', this.#onColumnMove, {\n signal: this.disconnectSignal,\n });\n }\n\n /** @internal */\n override detach(): void {\n this.groups = [];\n this.isActive = false;\n this.#groupEndFields.clear();\n }\n\n // #region Column Move Guard\n\n /**\n * Handle the cancelable column-move event.\n * - When lockGroupOrder is enabled, prevents moves that would break group contiguity.\n * - Always refreshes #groupEndFields after a successful move so that afterCellRender\n * applies group-end borders to the correct (reordered) last column.\n */\n #onColumnMove = (e: Event): void => {\n if (!this.isActive) return;\n\n const event = e as CustomEvent<{ field: string; columnOrder: string[] }>;\n const { field, columnOrder } = event.detail;\n\n if (this.config.lockGroupOrder) {\n // Check ALL explicit groups — moving any column (grouped or not) could break contiguity\n for (const group of this.groups) {\n if (group.id.startsWith('__implicit__')) continue;\n if (!this.#isGroupContiguous(group, columnOrder)) {\n event.preventDefault();\n this.#flashHeaderCell(field);\n return;\n }\n }\n }\n\n // Recompute group-end fields based on proposed column order.\n // setColumnOrder runs synchronously after this handler returns,\n // but afterCellRender (which reads #groupEndFields) fires during\n // the subsequent refreshVirtualWindow. Precompute using the\n // proposed columnOrder so the borders are correct immediately.\n this.#recomputeGroupEndFields(columnOrder);\n };\n\n /**\n * Recompute which fields are group-end based on a column order.\n * The last field of each explicit group in the order gets the group-end class.\n */\n #recomputeGroupEndFields(columnOrder: string[]): void {\n this.#groupEndFields.clear();\n // Find the last field of each group (including implicit groups between explicit ones).\n // Skip the very last group overall — no adjacent group follows it, so no separator needed.\n const lastGroupEndField = this.#findLastGroupEndField(columnOrder);\n for (const group of this.groups) {\n const groupFields = new Set(group.columns.map((c) => c.field));\n // Walk the column order in reverse to find the last member of this group\n for (let i = columnOrder.length - 1; i >= 0; i--) {\n if (groupFields.has(columnOrder[i])) {\n const field = columnOrder[i];\n // Don't mark the last group's trailing field — nothing follows it\n if (field !== lastGroupEndField) {\n this.#groupEndFields.add(field);\n }\n break;\n }\n }\n }\n }\n\n /**\n * Find the trailing field of the last group in column order (to exclude from group-end marking).\n */\n #findLastGroupEndField(columnOrder: string[]): string | null {\n if (this.groups.length === 0) return null;\n // Determine which group contains the last field in column order\n for (let i = columnOrder.length - 1; i >= 0; i--) {\n const field = columnOrder[i];\n for (const group of this.groups) {\n if (group.columns.some((c) => c.field === field)) {\n // This group is the last in display order — find its last field\n const groupFields = new Set(group.columns.map((c) => c.field));\n for (let j = columnOrder.length - 1; j >= 0; j--) {\n if (groupFields.has(columnOrder[j])) return columnOrder[j];\n }\n }\n }\n }\n return null;\n }\n\n /**\n * Check if all columns in a group are contiguous in the proposed column order.\n */\n #isGroupContiguous(group: ColumnGroup, columnOrder: string[]): boolean {\n const indices = group.columns\n .map((c) => columnOrder.indexOf(c.field))\n .filter((i) => i !== -1)\n .sort((a, b) => a - b);\n if (indices.length <= 1) return true;\n return indices.length === indices[indices.length - 1] - indices[0] + 1;\n }\n\n /**\n * Flash the header cell with an error color to indicate a blocked move.\n */\n #flashHeaderCell(field: string): void {\n const headerCell = this.gridElement?.querySelector(\n `.header-row [part~=\"header-cell\"][data-field=\"${field}\"]`,\n ) as HTMLElement;\n if (!headerCell) return;\n\n headerCell.style.setProperty('--_flash-color', 'var(--tbw-color-error)');\n headerCell.animate(\n [{ backgroundColor: 'rgba(from var(--_flash-color) r g b / 30%)' }, { backgroundColor: 'transparent' }],\n { duration: 400, easing: 'ease-out' },\n );\n }\n // #endregion\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'getColumnGrouping') {\n return this.#getStableColumnGrouping();\n }\n return undefined;\n }\n\n /**\n * Get stable column grouping info that includes ALL columns (visible and hidden).\n * Used by the visibility panel to maintain group structure regardless of visibility state.\n * Fields within each group are sorted by current display order.\n */\n #getStableColumnGrouping(): ColumnGroupInfo[] {\n let result: ColumnGroupInfo[];\n\n // 1. Prefer declarative columnGroups - always complete, visibility-independent\n const columnGroups = this.grid?.gridConfig?.columnGroups;\n if (columnGroups && Array.isArray(columnGroups) && columnGroups.length > 0) {\n result = columnGroups\n .filter((g) => g.children.length > 0)\n .map((g) => ({\n id: g.id,\n label: g.header,\n fields: [...g.children],\n }));\n } else if (this.isActive && this.groups.length > 0) {\n // 2. If active groups exist from processColumns, use them\n result = this.groups\n .filter((g) => !g.id.startsWith('__implicit__'))\n .map<ColumnGroupInfo>((g) => ({\n id: g.id,\n label: g.label ?? g.id,\n fields: g.columns.map((c) => c.field),\n }));\n\n // Also check hidden columns for inline group properties not in active groups\n const allCols = this.columns as ColumnConfig[];\n for (const col of allCols) {\n if ((col as any).hidden && col.group) {\n const gId = typeof col.group === 'string' ? col.group : col.group.id;\n const gLabel = typeof col.group === 'string' ? col.group : (col.group.label ?? col.group.id);\n const existing = result.find((g) => g.id === gId);\n if (existing) {\n if (!existing.fields.includes(col.field)) existing.fields.push(col.field);\n } else {\n result.push({ id: gId, label: gLabel, fields: [col.field] });\n }\n }\n }\n } else {\n // 3. Fall back: scan ALL columns (including hidden) for inline group properties\n const allCols = this.columns as ColumnConfig[];\n const groupMap = new Map<string, ColumnGroupInfo>();\n for (const col of allCols) {\n if (!col.group) continue;\n const gId = typeof col.group === 'string' ? col.group : col.group.id;\n const gLabel = typeof col.group === 'string' ? col.group : (col.group.label ?? col.group.id);\n const existing = groupMap.get(gId);\n if (existing) {\n if (!existing.fields.includes(col.field)) existing.fields.push(col.field);\n } else {\n groupMap.set(gId, { id: gId, label: gLabel, fields: [col.field] });\n }\n }\n result = Array.from(groupMap.values());\n }\n\n // Sort fields within each group by current display order so consumers\n // (e.g. the visibility panel) render columns in their reordered positions.\n const displayOrder = this.grid?.getColumnOrder();\n if (displayOrder && displayOrder.length > 0) {\n const orderIndex = new Map(displayOrder.map((f, i) => [f, i]));\n for (const group of result) {\n group.fields.sort((a, b) => (orderIndex.get(a) ?? Infinity) - (orderIndex.get(b) ?? Infinity));\n }\n }\n\n return result;\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 // Pre-compute group-end fields for the afterCellRender hook\n this.#groupEndFields.clear();\n for (const g of groups) {\n const lastCol = g.columns[g.columns.length - 1];\n if (lastCol?.field) {\n this.#groupEndFields.add(lastCol.field);\n }\n }\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 visible columns only (hidden columns have no CSS grid track).\n // This also picks up any plugin-added columns (e.g. expander) that weren't present\n // during processColumns.\n const finalColumns = this.visibleColumns as ColumnConfig[];\n const groups = computeColumnGroups(finalColumns);\n if (groups.length === 0) return;\n\n // Keep #groupEndFields in sync for afterCellRender (covers scheduler-driven renders)\n this.#groupEndFields.clear();\n for (let gi = 0; gi < groups.length; gi++) {\n const g = groups[gi];\n const lastCol = g.columns[g.columns.length - 1];\n // Don't mark the last group — no adjacent group follows it\n if (lastCol?.field && gi < groups.length - 1) {\n this.#groupEndFields.add(lastCol.field);\n }\n }\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\n /**\n * Apply group-end class to individual cells during render and scroll.\n * This is more efficient than querySelectorAll in afterRender and ensures\n * cells recycled during scroll also get the class applied.\n * @internal\n */\n override afterCellRender(context: AfterCellRenderContext): void {\n if (!this.isActive || !this.config.showGroupBorders) return;\n context.cellElement.classList.toggle('group-end', this.#groupEndFields.has(context.column.field));\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","#groupEndFields","grid","#onColumnMove","e","event","field","columnOrder","#isGroupContiguous","#flashHeaderCell","#recomputeGroupEndFields","lastGroupEndField","#findLastGroupEndField","groupFields","i","j","indices","a","b","headerCell","query","#getStableColumnGrouping","result","columnGroups","allCols","gId","gLabel","existing","groupMap","displayOrder","orderIndex","rows","config","processedColumns","groupInfo","lastCol","existingGroupRow","header","finalColumns","gi","headerRow","context","groupId"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(f
|
|
1
|
+
(function(d,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("../../core/internal/utils"),require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/utils","../../core/plugin/base-plugin"],f):(d=typeof globalThis<"u"?globalThis:d||self,f(d.TbwGridPlugin_pinnedColumns={},d.TbwGrid,d.TbwGrid))})(this,(function(d,f,A){"use strict";function u(o){return o.pinned??o.sticky??o.meta?.pinned??o.meta?.sticky}function p(o,t){return f.resolveInlinePosition(o,t)}function m(o,t){const e=u(o);return e?p(e,t)==="left":!1}function y(o,t){const e=u(o);return e?p(e,t)==="right":!1}function S(o,t="ltr"){return o.filter(e=>m(e,t))}function b(o,t="ltr"){return o.filter(e=>y(e,t))}function g(o){return o.some(t=>u(t)!=null)}function k(o,t){const e=Array.from(o.querySelectorAll(".header-row .cell"));if(!e.length)return;const s=f.getDirection(o);let l=0;for(const r of t)if(m(r,s)){const n=e.find(i=>i.getAttribute("data-field")===r.field);n&&(n.classList.add("sticky-left"),n.style.position="sticky",n.style.left=l+"px",o.querySelectorAll(`.data-grid-row .cell[data-field="${r.field}"]`).forEach(i=>{i.classList.add("sticky-left"),i.style.position="sticky",i.style.left=l+"px"}),l+=n.offsetWidth)}let c=0;for(const r of[...t].reverse())if(y(r,s)){const n=e.find(i=>i.getAttribute("data-field")===r.field);n&&(n.classList.add("sticky-right"),n.style.position="sticky",n.style.right=c+"px",o.querySelectorAll(`.data-grid-row .cell[data-field="${r.field}"]`).forEach(i=>{i.classList.add("sticky-right"),i.style.position="sticky",i.style.right=c+"px"}),c+=n.offsetWidth)}}function v(o,t="ltr"){const e=[],s=[],l=[];for(const c of o){const r=u(c);r?p(r,t)==="left"?e.push(c):l.push(c):s.push(c)}return[...e,...s,...l]}function P(o){o.querySelectorAll(".sticky-left, .sticky-right").forEach(e=>{e.classList.remove("sticky-left","sticky-right"),e.style.position="",e.style.left="",e.style.right=""})}const C="canMoveColumn";class x extends A.BaseGridPlugin{static manifest={ownedProperties:[{property:"pinned",level:"column",description:'the "pinned" column property',isUsed:t=>t==="left"||t==="right"||t==="start"||t==="end"},{property:"sticky",level:"column",description:'the "sticky" column property (deprecated, use "pinned")',isUsed:t=>t==="left"||t==="right"||t==="start"||t==="end"}],incompatibleWith:[{name:"groupingColumns",reason:"Pinning reorders columns to the grid edges, but moving a column out of its column group is not supported. The group header layout cannot accommodate members at different positions."}],queries:[{type:C,description:"Prevents pinned (sticky) columns from being moved/reordered"},{type:"getStickyOffsets",description:"Returns the sticky offsets for left/right pinned columns"},{type:"getContextMenuItems",description:"Contributes pin/unpin items to the header context menu"}]};name="pinnedColumns";get defaultConfig(){return{}}isApplied=!1;leftOffsets=new Map;rightOffsets=new Map;#t=[];detach(){this.leftOffsets.clear(),this.rightOffsets.clear(),this.isApplied=!1,this.#t=[]}static detect(t,e){const s=e?.columns;return Array.isArray(s)?g(s):!1}processColumns(t){const e=[...t];if(this.isApplied=g(e),!this.isApplied)return e;const s=this.gridElement,l=s?f.getDirection(s):"ltr";return v(e,l)}afterRender(){if(!this.isApplied)return;const t=this.grid,e=[...this.columns];if(!g(e)){P(t),this.isApplied=!1;return}queueMicrotask(()=>{k(t,e)})}handleQuery(t){switch(t.type){case C:{const e=t.context;return u(e)!=null?!1:void 0}case"getStickyOffsets":return{left:Object.fromEntries(this.leftOffsets),right:Object.fromEntries(this.rightOffsets)};case"getContextMenuItems":{const e=t.context;if(!e.isHeader)return;const s=e.column;if(!s?.field||s.meta?.lockPinning||this.grid?.getPluginByName("groupingColumns")?.isGroupingActive())return;const r=u(s)!=null,n=[];return r?n.push({id:"pinned/unpin",label:"Unpin Column",icon:"📌",order:40,action:()=>this.setPinPosition(s.field,void 0)}):(n.push({id:"pinned/pin-left",label:"Pin Left",icon:"⬅",order:40,action:()=>this.setPinPosition(s.field,"left")}),n.push({id:"pinned/pin-right",label:"Pin Right",icon:"➡",order:41,action:()=>this.setPinPosition(s.field,"right")})),n}default:return}}setPinPosition(t,e){const s=this.columns;if(!s?.length)return;const l=s.findIndex(r=>r.field===t);if(l===-1)return;const c=this.grid;if(e){this.#t.length===0&&(this.#t=s.map(n=>n.field));const r=s.map(n=>{if(n.field!==t)return n;const i={...n};return i.pinned=e,delete i.sticky,i});c.columns=r}else{const n={...s[l]};delete n.pinned,delete n.sticky;const i=[...s];i.splice(l,1);const O=this.#t.indexOf(t);if(O>=0){let h=i.length;for(let a=0;a<i.length;a++){if(u(i[a]))continue;if(this.#t.indexOf(i[a].field)>O){h=a;break}}i.splice(h,0,n)}else i.splice(Math.min(l,i.length),0,n);i.some(h=>u(h)!=null)||(this.#t=[]),c.columns=i}}refreshStickyOffsets(){const t=[...this.columns];k(this.grid,t)}getLeftPinnedColumns(){const t=[...this.columns],e=f.getDirection(this.grid);return S(t,e)}getRightPinnedColumns(){const t=[...this.columns],e=f.getDirection(this.grid);return b(t,e)}clearStickyPositions(){P(this.grid)}getHorizontalScrollOffsets(t,e){if(!this.isApplied)return;let s=0,l=0;if(t){const r=t.querySelectorAll(".sticky-left"),n=t.querySelectorAll(".sticky-right");r.forEach(i=>{s+=i.offsetWidth}),n.forEach(i=>{l+=i.offsetWidth})}else this.grid.querySelectorAll(".header-row .cell").forEach(i=>{i.classList.contains("sticky-left")?s+=i.offsetWidth:i.classList.contains("sticky-right")&&(l+=i.offsetWidth)});const c=e?.classList.contains("sticky-left")||e?.classList.contains("sticky-right");return{left:s,right:l,skipScroll:c}}}d.PinnedColumnsPlugin=x,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=pinned-columns.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinned-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pinned-columns/pinned-columns.ts","../../../../../libs/grid/src/lib/plugins/pinned-columns/PinnedColumnsPlugin.ts"],"sourcesContent":["/**\n * Sticky Columns Core Logic\n *\n * Pure functions for applying sticky (pinned) column positioning.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { getDirection, resolveInlinePosition, type TextDirection } from '../../core/internal/utils';\nimport type { ResolvedStickyPosition, StickyPosition } from './types';\n\n/**\n * Resolve a sticky position to a physical position based on text direction.\n *\n * - `'left'` / `'right'` → unchanged (physical values)\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n *\n * @param position - The sticky position (logical or physical)\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical sticky position ('left' or 'right')\n */\nexport function resolveStickyPosition(position: StickyPosition, direction: TextDirection): ResolvedStickyPosition {\n return resolveInlinePosition(position, direction);\n}\n\n/**\n * Check if a column is sticky on the left (after resolving logical positions).\n */\nfunction isResolvedLeft(col: any, direction: TextDirection): boolean {\n const sticky = col.sticky as StickyPosition | undefined;\n if (!sticky) return false;\n return resolveStickyPosition(sticky, direction) === 'left';\n}\n\n/**\n * Check if a column is sticky on the right (after resolving logical positions).\n */\nfunction isResolvedRight(col: any, direction: TextDirection): boolean {\n const sticky = col.sticky as StickyPosition | undefined;\n if (!sticky) return false;\n return resolveStickyPosition(sticky, direction) === 'right';\n}\n\n/**\n * Get columns that should be sticky on the left.\n *\n * @param columns - Array of column configurations\n * @param direction - Text direction (default: 'ltr')\n * @returns Array of columns with sticky='left' or sticky='start' (in LTR)\n */\nexport function getLeftStickyColumns(columns: any[], direction: TextDirection = 'ltr'): any[] {\n return columns.filter((col) => isResolvedLeft(col, direction));\n}\n\n/**\n * Get columns that should be sticky on the right.\n *\n * @param columns - Array of column configurations\n * @param direction - Text direction (default: 'ltr')\n * @returns Array of columns with sticky='right' or sticky='end' (in LTR)\n */\nexport function getRightStickyColumns(columns: any[], direction: TextDirection = 'ltr'): any[] {\n return columns.filter((col) => isResolvedRight(col, direction));\n}\n\n/**\n * Check if any columns have sticky positioning.\n *\n * @param columns - Array of column configurations\n * @returns True if any column has sticky position\n */\nexport function hasStickyColumns(columns: any[]): boolean {\n return columns.some(\n (col) => col.sticky === 'left' || col.sticky === 'right' || col.sticky === 'start' || col.sticky === 'end',\n );\n}\n\n/**\n * Get the sticky position of a column.\n *\n * @param column - Column configuration\n * @returns The sticky position or null if not sticky\n */\nexport function getColumnStickyPosition(column: any): StickyPosition | null {\n if (column.sticky === 'left') return 'left';\n if (column.sticky === 'right') return 'right';\n if (column.sticky === 'start') return 'start';\n if (column.sticky === 'end') return 'end';\n return null;\n}\n\n/**\n * Calculate left offsets for sticky-left columns.\n * Returns a map of field -> offset in pixels.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @param direction - Text direction (default: 'ltr')\n * @returns Map of field to left offset\n */\nexport function calculateLeftStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n direction: TextDirection = 'ltr',\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n for (const col of columns) {\n if (isResolvedLeft(col, direction)) {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Calculate right offsets for sticky-right columns.\n * Processes columns in reverse order.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @param direction - Text direction (default: 'ltr')\n * @returns Map of field to right offset\n */\nexport function calculateRightStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n direction: TextDirection = 'ltr',\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n // Process in reverse for right-sticky columns\n const reversed = [...columns].reverse();\n for (const col of reversed) {\n if (isResolvedRight(col, direction)) {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Apply sticky offsets to header and body cells.\n * This modifies the DOM elements in place.\n *\n * @param host - The grid host element (render root for DOM queries)\n * @param columns - Array of column configurations\n */\nexport function applyStickyOffsets(host: HTMLElement, columns: any[]): void {\n // With light DOM, query the host element directly\n const headerCells = Array.from(host.querySelectorAll('.header-row .cell')) as HTMLElement[];\n if (!headerCells.length) return;\n\n // Detect text direction from the host element\n const direction = getDirection(host);\n\n // Build column index map for matching body cells (which use data-col, not data-field)\n const fieldToIndex = new Map<string, number>();\n columns.forEach((col, i) => {\n if (col.field) fieldToIndex.set(col.field, i);\n });\n\n // Apply left sticky (includes 'start' in LTR, 'end' in RTL)\n let left = 0;\n for (const col of columns) {\n if (isResolvedLeft(col, direction)) {\n const colIndex = fieldToIndex.get(col.field);\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-left');\n cell.style.position = 'sticky';\n cell.style.left = left + 'px';\n // Body cells use data-col (column index), not data-field\n if (colIndex !== undefined) {\n host.querySelectorAll(`.data-grid-row .cell[data-col=\"${colIndex}\"]`).forEach((el) => {\n el.classList.add('sticky-left');\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.left = left + 'px';\n });\n }\n left += cell.offsetWidth;\n }\n }\n }\n\n // Apply right sticky (includes 'end' in LTR, 'start' in RTL) - process in reverse\n let right = 0;\n for (const col of [...columns].reverse()) {\n if (isResolvedRight(col, direction)) {\n const colIndex = fieldToIndex.get(col.field);\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-right');\n cell.style.position = 'sticky';\n cell.style.right = right + 'px';\n // Body cells use data-col (column index), not data-field\n if (colIndex !== undefined) {\n host.querySelectorAll(`.data-grid-row .cell[data-col=\"${colIndex}\"]`).forEach((el) => {\n el.classList.add('sticky-right');\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.right = right + 'px';\n });\n }\n right += cell.offsetWidth;\n }\n }\n }\n}\n\n/**\n * Clear sticky positioning from all cells.\n *\n * @param host - The grid host element (render root for DOM queries)\n */\nexport function clearStickyOffsets(host: HTMLElement): void {\n // With light DOM, query the host element directly\n const cells = host.querySelectorAll('.sticky-left, .sticky-right');\n cells.forEach((cell) => {\n cell.classList.remove('sticky-left', 'sticky-right');\n (cell as HTMLElement).style.position = '';\n (cell as HTMLElement).style.left = '';\n (cell as HTMLElement).style.right = '';\n });\n}\n","/**\n * Pinned Columns Plugin (Class-based)\n *\n * Enables column pinning (sticky left/right positioning).\n */\n\nimport { getDirection } from '../../core/internal/utils';\nimport type { PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n applyStickyOffsets,\n clearStickyOffsets,\n getLeftStickyColumns,\n getRightStickyColumns,\n hasStickyColumns,\n} from './pinned-columns';\nimport type { PinnedColumnsConfig, StickyPosition } from './types';\n\n/** Query type constant for checking if a column can be moved */\nconst QUERY_CAN_MOVE_COLUMN = 'canMoveColumn';\n\n/**\n * Pinned Columns Plugin for tbw-grid\n *\n * Freezes columns to the left or right edge of the grid—essential for keeping key\n * identifiers or action buttons visible while scrolling through wide datasets. Just set\n * `pinned: 'left'` or `pinned: 'right'` on your column definitions.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n * ```\n *\n * ## Column Configuration\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `pinned` | `'left' \\| 'right' \\| 'start' \\| 'end'` | Pin column to edge (logical or physical) |\n *\n * ### RTL Support\n *\n * Use logical values (`start`/`end`) for grids that work in both LTR and RTL layouts:\n * - `'start'` - Pins to left in LTR, right in RTL\n * - `'end'` - Pins to right in LTR, left in RTL\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-shadow` | `4px 0 8px rgba(0,0,0,0.1)` | Shadow on pinned column edge |\n * | `--tbw-pinned-border` | `var(--tbw-color-border)` | Border between pinned and scrollable |\n *\n * @example Pin ID Left and Actions Right\n * ```ts\n * import '@toolbox-web/grid';\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'left', width: 80 },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * { field: 'department', header: 'Department' },\n * { field: 'actions', header: 'Actions', pinned: 'right', width: 120 },\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @example RTL-Compatible Pinning\n * ```ts\n * // Same config works in LTR and RTL\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'start' }, // Left in LTR, Right in RTL\n * { field: 'name', header: 'Name' },\n * { field: 'actions', header: 'Actions', pinned: 'end' }, // Right in LTR, Left in RTL\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link PinnedColumnsConfig} for configuration options\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedColumnsPlugin extends BaseGridPlugin<PinnedColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties and handled queries.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'sticky',\n level: 'column',\n description: 'the \"sticky\" column property',\n isUsed: (v) => v === 'left' || v === 'right' || v === 'start' || v === 'end',\n },\n ],\n queries: [\n {\n type: QUERY_CAN_MOVE_COLUMN,\n description: 'Prevents pinned (sticky) columns from being moved/reordered',\n },\n {\n type: 'getStickyOffsets',\n description: 'Returns the sticky offsets for left/right pinned columns',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'pinnedColumns';\n\n /** @internal */\n protected override get defaultConfig(): Partial<PinnedColumnsConfig> {\n return {};\n }\n\n // #region Internal State\n private isApplied = false;\n private leftOffsets = new Map<string, number>();\n private rightOffsets = new Map<string, number>();\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.leftOffsets.clear();\n this.rightOffsets.clear();\n this.isApplied = false;\n }\n // #endregion\n\n // #region Detection\n\n /**\n * Auto-detect sticky columns from column configuration.\n */\n static detect(rows: readonly unknown[], config: { columns?: ColumnConfig[] }): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasStickyColumns(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // Mark that we have sticky columns to apply\n this.isApplied = hasStickyColumns([...columns]);\n return [...columns];\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isApplied) {\n return;\n }\n\n const host = this.grid as unknown as HTMLElement;\n const columns = [...this.columns];\n\n if (!hasStickyColumns(columns)) {\n clearStickyOffsets(host);\n this.isApplied = false;\n return;\n }\n\n // Apply sticky offsets after a microtask to ensure DOM is ready\n queueMicrotask(() => {\n applyStickyOffsets(host, columns);\n });\n }\n\n /**\n * Handle inter-plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n switch (query.type) {\n case QUERY_CAN_MOVE_COLUMN: {\n // Prevent pinned columns from being moved/reordered.\n // Pinned columns have fixed positions and should not be draggable.\n const column = query.context as ColumnConfig;\n const sticky = (column as ColumnConfig & { sticky?: StickyPosition }).sticky;\n if (sticky === 'left' || sticky === 'right' || sticky === 'start' || sticky === 'end') {\n return false;\n }\n // Also check meta.sticky for backwards compatibility\n const metaSticky = (column.meta as { sticky?: StickyPosition } | undefined)?.sticky;\n if (metaSticky === 'left' || metaSticky === 'right' || metaSticky === 'start' || metaSticky === 'end') {\n return false;\n }\n return undefined; // Let other plugins or default behavior decide\n }\n case 'getStickyOffsets': {\n // Return the calculated sticky offsets for column virtualization\n return {\n left: Object.fromEntries(this.leftOffsets),\n right: Object.fromEntries(this.rightOffsets),\n };\n }\n default:\n return undefined;\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Re-apply sticky offsets (e.g., after column resize).\n */\n refreshStickyOffsets(): void {\n const columns = [...this.columns];\n applyStickyOffsets(this.grid as unknown as HTMLElement, columns);\n }\n\n /**\n * Get columns pinned to the left (after resolving logical positions for current direction).\n */\n getLeftPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n const direction = getDirection(this.grid as unknown as HTMLElement);\n return getLeftStickyColumns(columns, direction);\n }\n\n /**\n * Get columns pinned to the right (after resolving logical positions for current direction).\n */\n getRightPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n const direction = getDirection(this.grid as unknown as HTMLElement);\n return getRightStickyColumns(columns, direction);\n }\n\n /**\n * Clear all sticky positioning.\n */\n clearStickyPositions(): void {\n clearStickyOffsets(this.grid as unknown as HTMLElement);\n }\n\n /**\n * Report horizontal scroll boundary offsets for pinned columns.\n * Used by keyboard navigation to ensure focused cells aren't hidden behind sticky columns.\n * @internal\n */\n override getHorizontalScrollOffsets(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } | undefined {\n if (!this.isApplied) {\n return undefined;\n }\n\n let left = 0;\n let right = 0;\n\n if (rowEl) {\n // Calculate from rendered cells in the row\n const stickyLeftCells = rowEl.querySelectorAll('.sticky-left');\n const stickyRightCells = rowEl.querySelectorAll('.sticky-right');\n stickyLeftCells.forEach((el) => {\n left += (el as HTMLElement).offsetWidth;\n });\n stickyRightCells.forEach((el) => {\n right += (el as HTMLElement).offsetWidth;\n });\n } else {\n // Fall back to header row if no row element provided\n const host = this.grid as unknown as HTMLElement;\n const headerCells = host.querySelectorAll('.header-row .cell');\n headerCells.forEach((cell) => {\n if (cell.classList.contains('sticky-left')) {\n left += (cell as HTMLElement).offsetWidth;\n } else if (cell.classList.contains('sticky-right')) {\n right += (cell as HTMLElement).offsetWidth;\n }\n });\n }\n\n // Skip horizontal scrolling if focused cell is pinned (it's always visible)\n const skipScroll =\n focusedCell?.classList.contains('sticky-left') || focusedCell?.classList.contains('sticky-right');\n\n return { left, right, skipScroll };\n }\n // #endregion\n}\n"],"names":["resolveStickyPosition","position","direction","resolveInlinePosition","isResolvedLeft","col","sticky","isResolvedRight","getLeftStickyColumns","columns","getRightStickyColumns","hasStickyColumns","applyStickyOffsets","host","headerCells","getDirection","fieldToIndex","i","left","colIndex","cell","c","el","right","clearStickyOffsets","QUERY_CAN_MOVE_COLUMN","PinnedColumnsPlugin","BaseGridPlugin","v","rows","config","query","column","metaSticky","rowEl","focusedCell","stickyLeftCells","stickyRightCells","skipScroll"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pinned-columns.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/pinned-columns/pinned-columns.ts","../../../../../libs/grid/src/lib/plugins/pinned-columns/PinnedColumnsPlugin.ts"],"sourcesContent":["/**\n * Pinned Columns Core Logic\n *\n * Pure functions for applying pinned (sticky) column positioning.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { getDirection, resolveInlinePosition, type TextDirection } from '../../core/internal/utils';\nimport type { PinnedPosition, ResolvedPinnedPosition } from './types';\n\n// Keep deprecated imports working (StickyPosition = PinnedPosition)\ntype StickyPosition = PinnedPosition;\ntype ResolvedStickyPosition = ResolvedPinnedPosition;\n\n/**\n * Get the effective pinned position from a column, checking `pinned` first then `sticky` (deprecated).\n *\n * @param col - Column configuration object\n * @returns The pinned position, or undefined if not pinned\n */\nexport function getColumnPinned(col: any): PinnedPosition | undefined {\n return col.pinned ?? col.sticky ?? col.meta?.pinned ?? col.meta?.sticky;\n}\n\n/**\n * Resolve a pinned position to a physical position based on text direction.\n *\n * - `'left'` / `'right'` → unchanged (physical values)\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n *\n * @param position - The pinned position (logical or physical)\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical pinned position ('left' or 'right')\n */\nexport function resolveStickyPosition(position: StickyPosition, direction: TextDirection): ResolvedStickyPosition {\n return resolveInlinePosition(position, direction);\n}\n\n/**\n * Check if a column is pinned on the left (after resolving logical positions).\n */\nfunction isResolvedLeft(col: any, direction: TextDirection): boolean {\n const pinned = getColumnPinned(col);\n if (!pinned) return false;\n return resolveStickyPosition(pinned, direction) === 'left';\n}\n\n/**\n * Check if a column is pinned on the right (after resolving logical positions).\n */\nfunction isResolvedRight(col: any, direction: TextDirection): boolean {\n const pinned = getColumnPinned(col);\n if (!pinned) return false;\n return resolveStickyPosition(pinned, direction) === 'right';\n}\n\n/**\n * Get columns that should be sticky on the left.\n *\n * @param columns - Array of column configurations\n * @param direction - Text direction (default: 'ltr')\n * @returns Array of columns with sticky='left' or sticky='start' (in LTR)\n */\nexport function getLeftStickyColumns(columns: any[], direction: TextDirection = 'ltr'): any[] {\n return columns.filter((col) => isResolvedLeft(col, direction));\n}\n\n/**\n * Get columns that should be sticky on the right.\n *\n * @param columns - Array of column configurations\n * @param direction - Text direction (default: 'ltr')\n * @returns Array of columns with sticky='right' or sticky='end' (in LTR)\n */\nexport function getRightStickyColumns(columns: any[], direction: TextDirection = 'ltr'): any[] {\n return columns.filter((col) => isResolvedRight(col, direction));\n}\n\n/**\n * Check if any columns have sticky positioning.\n *\n * @param columns - Array of column configurations\n * @returns True if any column has sticky position\n */\nexport function hasStickyColumns(columns: any[]): boolean {\n return columns.some((col) => getColumnPinned(col) != null);\n}\n\n/**\n * Get the sticky position of a column.\n *\n * @param column - Column configuration\n * @returns The sticky position or null if not sticky\n */\nexport function getColumnStickyPosition(column: any): StickyPosition | null {\n return getColumnPinned(column) ?? null;\n}\n\n\n/**\n * Calculate left offsets for sticky-left columns.\n * Returns a map of field -> offset in pixels.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @param direction - Text direction (default: 'ltr')\n * @returns Map of field to left offset\n */\nexport function calculateLeftStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n direction: TextDirection = 'ltr',\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n for (const col of columns) {\n if (isResolvedLeft(col, direction)) {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Calculate right offsets for sticky-right columns.\n * Processes columns in reverse order.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @param direction - Text direction (default: 'ltr')\n * @returns Map of field to right offset\n */\nexport function calculateRightStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n direction: TextDirection = 'ltr',\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n // Process in reverse for right-sticky columns\n const reversed = [...columns].reverse();\n for (const col of reversed) {\n if (isResolvedRight(col, direction)) {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Apply sticky offsets to header and body cells.\n * This modifies the DOM elements in place.\n *\n * @param host - The grid host element (render root for DOM queries)\n * @param columns - Array of column configurations\n */\nexport function applyStickyOffsets(host: HTMLElement, columns: any[]): void {\n // With light DOM, query the host element directly\n const headerCells = Array.from(host.querySelectorAll('.header-row .cell')) as HTMLElement[];\n if (!headerCells.length) return;\n\n // Detect text direction from the host element\n const direction = getDirection(host);\n\n // Apply left sticky (includes 'start' in LTR, 'end' in RTL)\n let left = 0;\n for (const col of columns) {\n if (isResolvedLeft(col, direction)) {\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-left');\n cell.style.position = 'sticky';\n cell.style.left = left + 'px';\n // Body cells: use data-field for reliable matching (data-col indices may differ\n // between _columns and _visibleColumns due to hidden/utility columns)\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${col.field}\"]`).forEach((el) => {\n el.classList.add('sticky-left');\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.left = left + 'px';\n });\n left += cell.offsetWidth;\n }\n }\n }\n\n // Apply right sticky (includes 'end' in LTR, 'start' in RTL) - process in reverse\n let right = 0;\n for (const col of [...columns].reverse()) {\n if (isResolvedRight(col, direction)) {\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-right');\n cell.style.position = 'sticky';\n cell.style.right = right + 'px';\n // Body cells: use data-field for reliable matching\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${col.field}\"]`).forEach((el) => {\n el.classList.add('sticky-right');\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.right = right + 'px';\n });\n right += cell.offsetWidth;\n }\n }\n }\n}\n\n/**\n * Reorder columns so that pinned-left columns come first and pinned-right columns come last.\n * Maintains the relative order within each group (left-pinned, unpinned, right-pinned).\n *\n * @param columns - Array of column configurations (in their current order)\n * @param direction - Text direction ('ltr' or 'rtl'), used to resolve logical positions\n * @returns New array with pinned columns moved to the edges\n */\nexport function reorderColumnsForPinning(columns: readonly any[], direction: TextDirection = 'ltr'): any[] {\n const left: any[] = [];\n const middle: any[] = [];\n const right: any[] = [];\n\n for (const col of columns) {\n const pinned = getColumnPinned(col);\n if (pinned) {\n const resolved = resolveStickyPosition(pinned, direction);\n if (resolved === 'left') left.push(col);\n else right.push(col);\n } else {\n middle.push(col);\n }\n }\n\n return [...left, ...middle, ...right];\n}\n\n/**\n * Clear sticky positioning from all cells.\n *\n * @param host - The grid host element (render root for DOM queries)\n */\nexport function clearStickyOffsets(host: HTMLElement): void {\n // With light DOM, query the host element directly\n const cells = host.querySelectorAll('.sticky-left, .sticky-right');\n cells.forEach((cell) => {\n cell.classList.remove('sticky-left', 'sticky-right');\n (cell as HTMLElement).style.position = '';\n (cell as HTMLElement).style.left = '';\n (cell as HTMLElement).style.right = '';\n });\n}\n","/**\n * Pinned Columns Plugin (Class-based)\n *\n * Enables column pinning (sticky left/right positioning).\n */\n\nimport { getDirection } from '../../core/internal/utils';\nimport type { PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ContextMenuParams, HeaderContextMenuItem } from '../context-menu/types';\nimport {\n applyStickyOffsets,\n clearStickyOffsets,\n getColumnPinned,\n getLeftStickyColumns,\n getRightStickyColumns,\n hasStickyColumns,\n reorderColumnsForPinning,\n} from './pinned-columns';\nimport type { PinnedColumnsConfig, PinnedPosition } from './types';\n\n/** Query type constant for checking if a column can be moved */\nconst QUERY_CAN_MOVE_COLUMN = 'canMoveColumn';\n\n/**\n * Pinned Columns Plugin for tbw-grid\n *\n * Freezes columns to the left or right edge of the grid—essential for keeping key\n * identifiers or action buttons visible while scrolling through wide datasets. Just set\n * `pinned: 'left'` or `pinned: 'right'` on your column definitions.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n * ```\n *\n * ## Column Configuration\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `pinned` | `'left' \\| 'right' \\| 'start' \\| 'end'` | Pin column to edge (logical or physical) |\n * | `meta.lockPinning` | `boolean` | `false` | Prevent user from pin/unpin via context menu |\n *\n * ### RTL Support\n *\n * Use logical values (`start`/`end`) for grids that work in both LTR and RTL layouts:\n * - `'start'` - Pins to left in LTR, right in RTL\n * - `'end'` - Pins to right in LTR, left in RTL\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-shadow` | `4px 0 8px rgba(0,0,0,0.1)` | Shadow on pinned column edge |\n * | `--tbw-pinned-border` | `var(--tbw-color-border)` | Border between pinned and scrollable |\n *\n * @example Pin ID Left and Actions Right\n * ```ts\n * import '@toolbox-web/grid';\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'left', width: 80 },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * { field: 'department', header: 'Department' },\n * { field: 'actions', header: 'Actions', pinned: 'right', width: 120 },\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @example RTL-Compatible Pinning\n * ```ts\n * // Same config works in LTR and RTL\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'start' }, // Left in LTR, Right in RTL\n * { field: 'name', header: 'Name' },\n * { field: 'actions', header: 'Actions', pinned: 'end' }, // Right in LTR, Left in RTL\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link PinnedColumnsConfig} for configuration options\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedColumnsPlugin extends BaseGridPlugin<PinnedColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties and handled queries.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'pinned',\n level: 'column',\n description: 'the \"pinned\" column property',\n isUsed: (v) => v === 'left' || v === 'right' || v === 'start' || v === 'end',\n },\n {\n property: 'sticky',\n level: 'column',\n description: 'the \"sticky\" column property (deprecated, use \"pinned\")',\n isUsed: (v) => v === 'left' || v === 'right' || v === 'start' || v === 'end',\n },\n ],\n incompatibleWith: [\n {\n name: 'groupingColumns',\n reason:\n 'Pinning reorders columns to the grid edges, but moving a column out of its column group ' +\n 'is not supported. The group header layout cannot accommodate members at different positions.',\n },\n ],\n queries: [\n {\n type: QUERY_CAN_MOVE_COLUMN,\n description: 'Prevents pinned (sticky) columns from being moved/reordered',\n },\n {\n type: 'getStickyOffsets',\n description: 'Returns the sticky offsets for left/right pinned columns',\n },\n {\n type: 'getContextMenuItems',\n description: 'Contributes pin/unpin items to the header context menu',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'pinnedColumns';\n\n /** @internal */\n protected override get defaultConfig(): Partial<PinnedColumnsConfig> {\n return {};\n }\n\n // #region Internal State\n private isApplied = false;\n private leftOffsets = new Map<string, number>();\n private rightOffsets = new Map<string, number>();\n /**\n * Snapshot of the column field order before the first context-menu pin.\n * Used to restore original positions when unpinning.\n */\n #originalColumnOrder: string[] = [];\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.leftOffsets.clear();\n this.rightOffsets.clear();\n this.isApplied = false;\n this.#originalColumnOrder = [];\n }\n // #endregion\n\n // #region Detection\n\n /**\n * Auto-detect sticky columns from column configuration.\n */\n static detect(rows: readonly unknown[], config: { columns?: ColumnConfig[] }): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasStickyColumns(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n const cols = [...columns];\n this.isApplied = hasStickyColumns(cols);\n if (!this.isApplied) return cols;\n\n const host = this.gridElement;\n const direction = host ? getDirection(host) : 'ltr';\n return reorderColumnsForPinning(cols, direction) as ColumnConfig[];\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isApplied) {\n return;\n }\n\n const host = this.grid as unknown as HTMLElement;\n const columns = [...this.columns];\n\n if (!hasStickyColumns(columns)) {\n clearStickyOffsets(host);\n this.isApplied = false;\n return;\n }\n\n // Apply sticky offsets after a microtask to ensure DOM is ready\n queueMicrotask(() => {\n applyStickyOffsets(host, columns);\n });\n }\n\n /**\n * Handle inter-plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n switch (query.type) {\n case QUERY_CAN_MOVE_COLUMN: {\n // Prevent pinned columns from being moved/reordered.\n // Pinned columns have fixed positions and should not be draggable.\n const column = query.context as ColumnConfig;\n if (getColumnPinned(column) != null) {\n return false;\n }\n return undefined; // Let other plugins or default behavior decide\n }\n case 'getStickyOffsets': {\n // Return the calculated sticky offsets for column virtualization\n return {\n left: Object.fromEntries(this.leftOffsets),\n right: Object.fromEntries(this.rightOffsets),\n };\n }\n case 'getContextMenuItems': {\n const params = query.context as ContextMenuParams;\n if (!params.isHeader) return undefined;\n\n const column = params.column as ColumnConfig;\n if (!column?.field) return undefined;\n\n // Don't offer pin/unpin for locked-pinning columns\n if (column.meta?.lockPinning) return undefined;\n\n // Don't offer pin/unpin when column grouping is active (incompatible)\n const groupingPlugin = this.grid?.getPluginByName('groupingColumns') as\n | { isGroupingActive(): boolean }\n | undefined;\n if (groupingPlugin?.isGroupingActive()) return undefined;\n\n const pinned = getColumnPinned(column);\n const isPinned = pinned != null;\n const items: HeaderContextMenuItem[] = [];\n\n if (isPinned) {\n items.push({\n id: 'pinned/unpin',\n label: 'Unpin Column',\n icon: '📌',\n order: 40,\n action: () => this.setPinPosition(column.field, undefined),\n });\n } else {\n items.push({\n id: 'pinned/pin-left',\n label: 'Pin Left',\n icon: '⬅',\n order: 40,\n action: () => this.setPinPosition(column.field, 'left'),\n });\n items.push({\n id: 'pinned/pin-right',\n label: 'Pin Right',\n icon: '➡',\n order: 41,\n action: () => this.setPinPosition(column.field, 'right'),\n });\n }\n\n return items;\n }\n default:\n return undefined;\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set the pin position for a column.\n * Updates the column's `pinned` property and triggers a full re-render.\n *\n * @param field - The field name of the column to pin/unpin\n * @param position - The pin position (`'left'`, `'right'`, `'start'`, `'end'`), or `undefined` to unpin\n */\n setPinPosition(field: string, position: PinnedPosition | undefined): void {\n // Read the currently-visible columns from the plugin accessor.\n // These are the post-processColumns result, which is the authoritative column set.\n const currentColumns = this.columns;\n if (!currentColumns?.length) return;\n\n const currentIndex = currentColumns.findIndex((col) => col.field === field);\n if (currentIndex === -1) return;\n\n const gridEl = this.grid as unknown as HTMLElement & { columns?: ColumnConfig[] };\n\n if (position) {\n // PINNING: snapshot original column order if this is the first context-menu pin.\n // The snapshot lets us restore columns to their original positions on unpin.\n if (this.#originalColumnOrder.length === 0) {\n this.#originalColumnOrder = currentColumns.map((c) => c.field);\n }\n\n // Set the pinned property; processColumns will reorder on next render\n const updated = currentColumns.map((col) => {\n if (col.field !== field) return col;\n const copy = { ...col };\n (copy as ColumnConfig & { pinned?: PinnedPosition }).pinned = position;\n delete (copy as ColumnConfig & { sticky?: PinnedPosition }).sticky;\n return copy;\n });\n\n gridEl.columns = updated;\n } else {\n // UNPINNING: restore column to its original position\n const col = currentColumns[currentIndex];\n const copy = { ...col };\n delete (copy as ColumnConfig & { pinned?: PinnedPosition }).pinned;\n delete (copy as ColumnConfig & { sticky?: PinnedPosition }).sticky;\n\n // Remove from current position\n const remaining = [...currentColumns];\n remaining.splice(currentIndex, 1);\n\n // Find the best insertion point using the original order snapshot\n const originalIndex = this.#originalColumnOrder.indexOf(field);\n if (originalIndex >= 0) {\n // Scan remaining non-pinned columns and find the first whose original\n // position is greater than this column's original position.\n let insertIndex = remaining.length;\n for (let i = 0; i < remaining.length; i++) {\n if (getColumnPinned(remaining[i])) continue; // skip pinned columns\n const otherOriginal = this.#originalColumnOrder.indexOf(remaining[i].field);\n if (otherOriginal > originalIndex) {\n insertIndex = i;\n break;\n }\n }\n remaining.splice(insertIndex, 0, copy);\n } else {\n // Original position unknown — keep at current index\n remaining.splice(Math.min(currentIndex, remaining.length), 0, copy);\n }\n\n // If no more pinned columns remain, clear the snapshot\n if (!remaining.some((c) => getColumnPinned(c) != null)) {\n this.#originalColumnOrder = [];\n }\n\n gridEl.columns = remaining;\n }\n }\n\n /**\n * Re-apply sticky offsets (e.g., after column resize).\n */\n refreshStickyOffsets(): void {\n const columns = [...this.columns];\n applyStickyOffsets(this.grid as unknown as HTMLElement, columns);\n }\n\n /**\n * Get columns pinned to the left (after resolving logical positions for current direction).\n */\n getLeftPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n const direction = getDirection(this.grid as unknown as HTMLElement);\n return getLeftStickyColumns(columns, direction);\n }\n\n /**\n * Get columns pinned to the right (after resolving logical positions for current direction).\n */\n getRightPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n const direction = getDirection(this.grid as unknown as HTMLElement);\n return getRightStickyColumns(columns, direction);\n }\n\n /**\n * Clear all sticky positioning.\n */\n clearStickyPositions(): void {\n clearStickyOffsets(this.grid as unknown as HTMLElement);\n }\n\n /**\n * Report horizontal scroll boundary offsets for pinned columns.\n * Used by keyboard navigation to ensure focused cells aren't hidden behind sticky columns.\n * @internal\n */\n override getHorizontalScrollOffsets(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } | undefined {\n if (!this.isApplied) {\n return undefined;\n }\n\n let left = 0;\n let right = 0;\n\n if (rowEl) {\n // Calculate from rendered cells in the row\n const stickyLeftCells = rowEl.querySelectorAll('.sticky-left');\n const stickyRightCells = rowEl.querySelectorAll('.sticky-right');\n stickyLeftCells.forEach((el) => {\n left += (el as HTMLElement).offsetWidth;\n });\n stickyRightCells.forEach((el) => {\n right += (el as HTMLElement).offsetWidth;\n });\n } else {\n // Fall back to header row if no row element provided\n const host = this.grid as unknown as HTMLElement;\n const headerCells = host.querySelectorAll('.header-row .cell');\n headerCells.forEach((cell) => {\n if (cell.classList.contains('sticky-left')) {\n left += (cell as HTMLElement).offsetWidth;\n } else if (cell.classList.contains('sticky-right')) {\n right += (cell as HTMLElement).offsetWidth;\n }\n });\n }\n\n // Skip horizontal scrolling if focused cell is pinned (it's always visible)\n const skipScroll =\n focusedCell?.classList.contains('sticky-left') || focusedCell?.classList.contains('sticky-right');\n\n return { left, right, skipScroll };\n }\n // #endregion\n}\n"],"names":["getColumnPinned","col","resolveStickyPosition","position","direction","resolveInlinePosition","isResolvedLeft","pinned","isResolvedRight","getLeftStickyColumns","columns","getRightStickyColumns","hasStickyColumns","applyStickyOffsets","host","headerCells","getDirection","left","cell","c","el","right","reorderColumnsForPinning","middle","clearStickyOffsets","QUERY_CAN_MOVE_COLUMN","PinnedColumnsPlugin","BaseGridPlugin","v","#originalColumnOrder","rows","config","cols","query","column","params","isPinned","items","field","currentColumns","currentIndex","gridEl","updated","copy","remaining","originalIndex","insertIndex","i","rowEl","focusedCell","stickyLeftCells","stickyRightCells","skipScroll"],"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_visibility={},d.TbwGrid))})(this,(function(d,c){"use strict";const v='@layer tbw-plugins{.tbw-visibility-content{display:flex;flex-direction:column;height:100%}.tbw-visibility-list{flex:1;overflow-y:auto;padding:var(--tbw-panel-padding, var(--tbw-spacing-md, .5rem))}.tbw-visibility-row{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));padding:var(--tbw-menu-item-padding, .375rem .25rem);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem);border-radius:var(--tbw-border-radius, .25rem);position:relative}.tbw-visibility-row:hover{background:var(--tbw-visibility-hover, var(--tbw-color-row-hover))}.tbw-visibility-row input[type=checkbox]{cursor:pointer}.tbw-visibility-row.locked span{color:var(--tbw-color-fg-muted)}.tbw-visibility-handle{cursor:grab;color:var(--tbw-color-fg-muted);font-size:var(--tbw-font-size-2xs, .625rem);letter-spacing:-2px;-webkit-user-select:none;user-select:none;flex-shrink:0}.tbw-visibility-row.reorderable:hover .tbw-visibility-handle{color:var(--tbw-color-fg)}.tbw-visibility-label{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));flex:1;cursor:pointer}.tbw-visibility-row.dragging{opacity:.5;cursor:grabbing}.tbw-visibility-row.drop-before:before{content:"";position:absolute;left:0;right:0;top:0;height:2px;background:var(--tbw-visibility-indicator, var(--tbw-color-accent))}.tbw-visibility-row.drop-after:after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--tbw-visibility-indicator, var(--tbw-color-accent))}.tbw-visibility-show-all{margin:var(--tbw-panel-padding, var(--tbw-spacing-md, .5rem));padding:var(--tbw-button-padding, .5rem .75rem);border:1px solid var(--tbw-visibility-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius, .25rem);background:var(--tbw-visibility-btn-bg, var(--tbw-color-header-bg));color:var(--tbw-color-fg);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem)}.tbw-visibility-show-all:hover{background:var(--tbw-visibility-hover, var(--tbw-color-row-hover))}}';function h(f){const e=f.meta??{};return e.lockPosition!==!0&&e.suppressMovable!==!0}class a extends c.BaseGridPlugin{static dependencies=[{name:"reorder",required:!1,reason:"Enables drag-to-reorder columns in visibility panel"}];name="visibility";static PANEL_ID="columns";styles=v;get defaultConfig(){return{allowHideAll:!1}}columnListElement=null;isDragging=!1;draggedField=null;draggedIndex=null;dropIndex=null;clearDragClasses(e){e.querySelectorAll(".tbw-visibility-row").forEach(l=>{l.classList.remove("dragging","drop-target","drop-before","drop-after")})}detach(){this.columnListElement=null,this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null}getToolPanel(){return{id:a.PANEL_ID,title:"Columns",icon:"☰",tooltip:"Column visibility",order:100,render:e=>this.renderPanelContent(e)}}show(){this.grid.openToolPanel(),this.grid.expandedToolPanelSections.includes(a.PANEL_ID)||this.grid.toggleToolPanelSection(a.PANEL_ID)}hide(){this.grid.closeToolPanel()}toggle(){this.grid.isToolPanelOpen||this.grid.openToolPanel(),this.grid.toggleToolPanelSection(a.PANEL_ID)}isColumnVisible(e){return this.grid.isColumnVisible(e)}setColumnVisible(e,l){this.grid.setColumnVisible(e,l)}getVisibleColumns(){return this.grid.getAllColumns().filter(e=>e.visible).map(e=>e.field)}getHiddenColumns(){return this.grid.getAllColumns().filter(e=>!e.visible).map(e=>e.field)}showAll(){this.grid.showAllColumns()}toggleColumn(e){this.grid.toggleColumnVisibility(e)}showColumn(e){this.setColumnVisible(e,!0)}hideColumn(e){this.setColumnVisible(e,!1)}getAllColumns(){return this.grid.getAllColumns()}isPanelVisible(){return this.grid.isToolPanelOpen&&this.grid.expandedToolPanelSections.includes(a.PANEL_ID)}renderPanelContent(e){const l=document.createElement("div");l.className="tbw-visibility-content";const n=document.createElement("div");n.className="tbw-visibility-list",l.appendChild(n);const r=document.createElement("button");return r.className="tbw-visibility-show-all",r.textContent="Show All",r.addEventListener("click",()=>{this.grid.showAllColumns(),this.rebuildToggles(n)}),l.appendChild(r),this.columnListElement=n,this.rebuildToggles(n),e.appendChild(l),()=>{this.columnListElement=null,l.remove()}}hasReorderPlugin(){const e=this.grid?.getPluginByName?.("reorder");return!!(e&&typeof e.moveColumn=="function")}rebuildToggles(e){const l=this.hasReorderPlugin();e.innerHTML="";const n=this.grid.getAllColumns().filter(r=>!r.utility);for(let r=0;r<n.length;r++){const t=n[r],g=t.header||t.field,i=document.createElement("div");i.className=t.lockVisible?"tbw-visibility-row locked":"tbw-visibility-row",i.setAttribute("data-field",t.field),i.setAttribute("data-index",String(r)),l&&h(t)&&(i.draggable=!0,i.classList.add("reorderable"),this.setupDragListeners(i,t.field,r,e));const s=document.createElement("label");s.className="tbw-visibility-label";const o=document.createElement("input");o.type="checkbox",o.checked=t.visible,o.disabled=t.lockVisible??!1,o.addEventListener("change",()=>{this.grid.toggleColumnVisibility(t.field),setTimeout(()=>this.rebuildToggles(e),0)});const b=document.createElement("span");if(b.textContent=g,s.appendChild(o),s.appendChild(b),l&&h(t)){const u=document.createElement("span");u.className="tbw-visibility-handle",this.setIcon(u,this.resolveIcon("dragHandle")),u.title="Drag to reorder",i.appendChild(u)}i.appendChild(s),e.appendChild(i)}}setupDragListeners(e,l,n,r){e.addEventListener("dragstart",t=>{this.isDragging=!0,this.draggedField=l,this.draggedIndex=n,t.dataTransfer&&(t.dataTransfer.effectAllowed="move",t.dataTransfer.setData("text/plain",l)),e.classList.add("dragging")}),e.addEventListener("dragend",()=>{this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null,this.clearDragClasses(r)}),e.addEventListener("dragover",t=>{if(t.preventDefault(),!this.isDragging||this.draggedField===l)return;const g=e.getBoundingClientRect(),i=g.top+g.height/2;this.dropIndex=t.clientY<i?n:n+1,r.querySelectorAll(".tbw-visibility-row").forEach(s=>{s!==e&&s.classList.remove("drop-target","drop-before","drop-after")}),e.classList.add("drop-target"),e.classList.toggle("drop-before",t.clientY<i),e.classList.toggle("drop-after",t.clientY>=i)}),e.addEventListener("dragleave",()=>{e.classList.remove("drop-target","drop-before","drop-after")}),e.addEventListener("drop",t=>{t.preventDefault();const g=this.draggedField,i=this.draggedIndex,s=this.dropIndex;if(!this.isDragging||g===null||i===null||s===null)return;const o=s>i?s-1:s;if(o!==i){const b=this.grid.getAllColumns(),m=b.filter(p=>!p.utility)[o]?.field,w=m?b.findIndex(p=>p.field===m):b.length,y={field:g,fromIndex:i,toIndex:w};this.emit("column-reorder-request",y),setTimeout(()=>{this.rebuildToggles(r)},0)}})}}d.VisibilityPlugin=a,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(u,h){typeof exports=="object"&&typeof module<"u"?h(exports,require("../../core/plugin/base-plugin")):typeof define=="function"&&define.amd?define(["exports","../../core/plugin/base-plugin"],h):(u=typeof globalThis<"u"?globalThis:u||self,h(u.TbwGridPlugin_visibility={},u.TbwGrid))})(this,(function(u,h){"use strict";const w='@layer tbw-plugins{.tbw-visibility-content{display:flex;flex-direction:column;height:100%}.tbw-visibility-list{flex:1;overflow-y:auto;padding:var(--tbw-panel-padding, var(--tbw-spacing-md, .5rem))}.tbw-visibility-row{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));padding:var(--tbw-menu-item-padding, .375rem .25rem);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem);border-radius:var(--tbw-border-radius, .25rem);position:relative}.tbw-visibility-row:hover{background:var(--tbw-visibility-hover, var(--tbw-color-row-hover))}.tbw-visibility-row input[type=checkbox]{cursor:pointer}.tbw-visibility-row.locked span{color:var(--tbw-color-fg-muted)}.tbw-visibility-handle{cursor:grab;color:var(--tbw-color-fg-muted);font-size:var(--tbw-font-size-2xs, .625rem);letter-spacing:-2px;-webkit-user-select:none;user-select:none;flex-shrink:0}.tbw-visibility-row.reorderable:hover .tbw-visibility-handle{color:var(--tbw-color-fg)}.tbw-visibility-label{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));flex:1;cursor:pointer}.tbw-visibility-row.dragging{opacity:.5;cursor:grabbing}.tbw-visibility-row.drop-before:before{content:"";position:absolute;left:0;right:0;top:0;height:2px;background:var(--tbw-visibility-indicator, var(--tbw-color-accent))}.tbw-visibility-row.drop-after:after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--tbw-visibility-indicator, var(--tbw-color-accent))}.tbw-visibility-show-all{margin:var(--tbw-panel-padding, var(--tbw-spacing-md, .5rem));padding:var(--tbw-button-padding, .5rem .75rem);border:1px solid var(--tbw-visibility-border, var(--tbw-color-border));border-radius:var(--tbw-border-radius, .25rem);background:var(--tbw-visibility-btn-bg, var(--tbw-color-header-bg));color:var(--tbw-color-fg);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem)}.tbw-visibility-show-all:hover{background:var(--tbw-visibility-hover, var(--tbw-color-row-hover))}.tbw-visibility-group-header{display:flex;align-items:center;padding:var(--tbw-menu-item-padding, .375rem .25rem);font-size:var(--tbw-font-size-sm, .8125rem);font-weight:600;color:var(--tbw-color-fg);border-bottom:1px solid var(--tbw-color-border);margin-top:var(--tbw-spacing-sm, .25rem);position:relative}.tbw-visibility-group-header:first-child{margin-top:0}.tbw-visibility-group-header .tbw-visibility-label{gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-visibility-group-header.reorderable{cursor:grab}.tbw-visibility-group-header.reorderable:hover{background:var(--tbw-visibility-hover, var(--tbw-color-row-hover))}.tbw-visibility-group-header .tbw-visibility-handle{cursor:grab;color:var(--tbw-color-fg-muted);font-size:var(--tbw-font-size-2xs, .625rem);letter-spacing:-2px;-webkit-user-select:none;user-select:none;flex-shrink:0}.tbw-visibility-group-header.reorderable:hover .tbw-visibility-handle{color:var(--tbw-color-fg)}.tbw-visibility-group-header.dragging{opacity:.5;cursor:grabbing}.tbw-visibility-group-header.drop-before:before{content:"";position:absolute;left:0;right:0;top:0;height:2px;background:var(--tbw-visibility-indicator, var(--tbw-color-accent))}.tbw-visibility-group-header.drop-after:after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--tbw-visibility-indicator, var(--tbw-color-accent))}.tbw-visibility-row--grouped{padding-left:calc(var(--tbw-panel-padding, var(--tbw-spacing-md, .5rem)) + .75rem)}}';function v(y){const e=y.meta??{};return e.lockPosition!==!0&&e.suppressMovable!==!0}class b extends h.BaseGridPlugin{static dependencies=[{name:"reorder",required:!1,reason:"Enables drag-to-reorder columns in visibility panel"}];static manifest={queries:[{type:"getContextMenuItems",description:'Contributes "Hide column" item to the header context menu'}]};name="visibility";static PANEL_ID="columns";styles=w;get defaultConfig(){return{allowHideAll:!1}}columnListElement=null;isDragging=!1;draggedField=null;draggedIndex=null;dropIndex=null;draggedGroupId=null;draggedGroupFields=[];clearDragClasses(e){e.querySelectorAll(".tbw-visibility-row, .tbw-visibility-group-header").forEach(t=>{t.classList.remove("dragging","drop-target","drop-before","drop-after")})}attach(e){super.attach(e),e.addEventListener("column-move",()=>{this.columnListElement&&requestAnimationFrame(()=>{this.columnListElement&&this.rebuildToggles(this.columnListElement)})},{signal:this.disconnectSignal})}detach(){this.columnListElement=null,this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null}handleQuery(e){if(e.type==="getContextMenuItems"){const t=e.context;if(!t.isHeader)return;const l=t.column;return!l?.field||l.meta?.lockVisibility?void 0:[{id:"visibility/hide-column",label:"Hide Column",icon:"👁",order:30,action:()=>this.hideColumn(l.field)}]}}getToolPanel(){return{id:b.PANEL_ID,title:"Columns",icon:"☰",tooltip:"Column visibility",order:100,render:e=>this.renderPanelContent(e)}}show(){this.grid.openToolPanel(),this.grid.expandedToolPanelSections.includes(b.PANEL_ID)||this.grid.toggleToolPanelSection(b.PANEL_ID)}hide(){this.grid.closeToolPanel()}toggle(){this.grid.isToolPanelOpen||this.grid.openToolPanel(),this.grid.toggleToolPanelSection(b.PANEL_ID)}isColumnVisible(e){return this.grid.isColumnVisible(e)}setColumnVisible(e,t){this.grid.setColumnVisible(e,t)}getVisibleColumns(){return this.grid.getAllColumns().filter(e=>e.visible).map(e=>e.field)}getHiddenColumns(){return this.grid.getAllColumns().filter(e=>!e.visible).map(e=>e.field)}showAll(){this.grid.showAllColumns()}toggleColumn(e){this.grid.toggleColumnVisibility(e)}showColumn(e){this.setColumnVisible(e,!0)}hideColumn(e){this.setColumnVisible(e,!1)}getAllColumns(){return this.grid.getAllColumns()}isPanelVisible(){return this.grid.isToolPanelOpen&&this.grid.expandedToolPanelSections.includes(b.PANEL_ID)}renderPanelContent(e){const t=document.createElement("div");t.className="tbw-visibility-content";const l=document.createElement("div");l.className="tbw-visibility-list",t.appendChild(l);const n=document.createElement("button");return n.className="tbw-visibility-show-all",n.textContent="Show All",n.addEventListener("click",()=>{this.grid.showAllColumns(),this.rebuildToggles(l)}),t.appendChild(n),this.columnListElement=l,this.rebuildToggles(l),e.appendChild(t),()=>{this.columnListElement=null,t.remove()}}hasReorderPlugin(){const e=this.grid?.getPluginByName?.("reorder");return!!(e&&typeof e.moveColumn=="function")}rebuildToggles(e){const t=this.hasReorderPlugin();e.innerHTML="";const l=this.grid.getAllColumns().filter(o=>!o.utility),i=this.grid.query("getColumnGrouping")?.flat().filter(o=>o&&o.fields.length>0)??[];if(i.length===0){this.renderFlatColumnList(l,t,e);return}const s=new Map;for(const o of i)for(const a of o.fields)s.set(a,o);const r=new Set;for(const o of l){const a=s.get(o.field);if(a){if(!r.has(a.id)){r.add(a.id);const g=new Set(a.fields),c=l.filter(d=>g.has(d.field));c.length>0&&this.renderGroupSection(a,c,t,e)}}else{const g=l.indexOf(o);e.appendChild(this.createColumnRow(o,g,t,e))}}}renderGroupSection(e,t,l,n){const i=document.createElement("div");i.className="tbw-visibility-group-header",i.setAttribute("data-group-id",e.id),l&&(i.draggable=!0,i.classList.add("reorderable"),this.setupGroupDragListeners(i,e,n));const s=document.createElement("label");s.className="tbw-visibility-label";const r=document.createElement("input");r.type="checkbox";const o=t.filter(d=>d.visible).length,a=t.every(d=>d.lockVisible);o===t.length?(r.checked=!0,r.indeterminate=!1):o===0?(r.checked=!1,r.indeterminate=!1):(r.checked=!1,r.indeterminate=!0),r.disabled=a,r.addEventListener("change",()=>{const d=r.checked;for(const p of t)p.lockVisible||this.grid.setColumnVisible(p.field,d);setTimeout(()=>this.rebuildToggles(n),0)});const g=document.createElement("span");if(g.textContent=e.label,s.appendChild(r),s.appendChild(g),i.appendChild(s),l){const d=document.createElement("span");d.className="tbw-visibility-handle",this.setIcon(d,this.resolveIcon("dragHandle")),d.title="Drag to reorder group",i.insertBefore(d,s)}n.appendChild(i);const c=this.grid.getAllColumns().filter(d=>!d.utility);for(const d of t){const p=c.findIndex(f=>f.field===d.field),m=this.createColumnRow(d,p,l,n);m.classList.add("tbw-visibility-row--grouped"),n.appendChild(m)}}renderFlatColumnList(e,t,l){const n=this.grid.getAllColumns().filter(i=>!i.utility);for(const i of e){const s=n.findIndex(r=>r.field===i.field);l.appendChild(this.createColumnRow(i,s,t,l))}}createColumnRow(e,t,l,n){const i=e.header||e.field,s=document.createElement("div");s.className=e.lockVisible?"tbw-visibility-row locked":"tbw-visibility-row",s.setAttribute("data-field",e.field),s.setAttribute("data-index",String(t)),l&&v(e)&&(s.draggable=!0,s.classList.add("reorderable"),this.setupDragListeners(s,e.field,t,n));const r=document.createElement("label");r.className="tbw-visibility-label";const o=document.createElement("input");o.type="checkbox",o.checked=e.visible,o.disabled=e.lockVisible??!1,o.addEventListener("change",()=>{this.grid.toggleColumnVisibility(e.field),setTimeout(()=>this.rebuildToggles(n),0)});const a=document.createElement("span");if(a.textContent=i,r.appendChild(o),r.appendChild(a),l&&v(e)){const g=document.createElement("span");g.className="tbw-visibility-handle",this.setIcon(g,this.resolveIcon("dragHandle")),g.title="Drag to reorder",s.appendChild(g)}return s.appendChild(r),s}setupGroupDragListeners(e,t,l){e.addEventListener("dragstart",n=>{this.isDragging=!0,this.draggedGroupId=t.id,this.draggedGroupFields=[...t.fields],this.draggedField=null,this.draggedIndex=null,n.dataTransfer&&(n.dataTransfer.effectAllowed="move",n.dataTransfer.setData("text/plain",`group:${t.id}`)),e.classList.add("dragging"),l.querySelectorAll(".tbw-visibility-row--grouped").forEach(i=>{const s=i.getAttribute("data-field");s&&this.draggedGroupFields.includes(s)&&i.classList.add("dragging")})}),e.addEventListener("dragend",()=>{this.isDragging=!1,this.draggedGroupId=null,this.draggedGroupFields=[],this.draggedField=null,this.draggedIndex=null,this.dropIndex=null,this.clearDragClasses(l)}),e.addEventListener("dragover",n=>{if(n.preventDefault(),!this.isDragging||this.draggedGroupId===t.id||!this.draggedGroupId)return;const i=e.getBoundingClientRect(),s=i.top+i.height/2,r=n.clientY<s;this.clearDragClasses(l),e.classList.add("drop-target"),e.classList.toggle("drop-before",r),e.classList.toggle("drop-after",!r)}),e.addEventListener("dragleave",()=>{e.classList.remove("drop-target","drop-before","drop-after")}),e.addEventListener("drop",n=>{if(n.preventDefault(),!this.isDragging||!this.draggedGroupId||this.draggedGroupId===t.id)return;const i=e.getBoundingClientRect(),s=n.clientY<i.top+i.height/2;this.executeGroupDrop(this.draggedGroupFields,t.fields,s,l)})}executeGroupDrop(e,t,l,n){const s=this.grid.getAllColumns().map(d=>d.field),r=s.filter(d=>!e.includes(d)),o=l?t[0]:t[t.length-1],a=r.indexOf(o);if(a===-1)return;const g=l?a:a+1,c=s.filter(d=>e.includes(d));r.splice(g,0,...c),this.grid.setColumnOrder(r),requestAnimationFrame(()=>{this.columnListElement&&this.rebuildToggles(this.columnListElement)})}setupDragListeners(e,t,l,n){e.addEventListener("dragstart",i=>{this.isDragging=!0,this.draggedField=t,this.draggedIndex=l,this.draggedGroupId=null,this.draggedGroupFields=[],i.dataTransfer&&(i.dataTransfer.effectAllowed="move",i.dataTransfer.setData("text/plain",t)),e.classList.add("dragging")}),e.addEventListener("dragend",()=>{this.isDragging=!1,this.draggedField=null,this.draggedIndex=null,this.dropIndex=null,this.clearDragClasses(n)}),e.addEventListener("dragover",i=>{if(i.preventDefault(),!this.isDragging)return;if(this.draggedGroupId){if(e.classList.contains("tbw-visibility-row--grouped"))return}else if(this.draggedField===t)return;const s=e.getBoundingClientRect(),r=s.top+s.height/2;this.dropIndex=i.clientY<r?l:l+1,this.clearDragClasses(n),this.draggedGroupId?(n.querySelector(`.tbw-visibility-group-header[data-group-id="${this.draggedGroupId}"]`)?.classList.add("dragging"),n.querySelectorAll(".tbw-visibility-row--grouped").forEach(o=>{const a=o.getAttribute("data-field");a&&this.draggedGroupFields.includes(a)&&o.classList.add("dragging")})):this.draggedField&&n.querySelector(`.tbw-visibility-row[data-field="${this.draggedField}"]`)?.classList.add("dragging"),e.classList.add("drop-target"),e.classList.toggle("drop-before",i.clientY<r),e.classList.toggle("drop-after",i.clientY>=r)}),e.addEventListener("dragleave",()=>{e.classList.remove("drop-target","drop-before","drop-after")}),e.addEventListener("drop",i=>{if(i.preventDefault(),!this.isDragging)return;if(this.draggedGroupId&&this.draggedGroupFields.length>0){if(e.classList.contains("tbw-visibility-row--grouped"))return;const g=e.getBoundingClientRect(),c=i.clientY<g.top+g.height/2;this.executeGroupDrop(this.draggedGroupFields,[t],c,n);return}const s=this.draggedField,r=this.draggedIndex,o=this.dropIndex;if(s===null||r===null||o===null)return;const a=o>r?o-1:o;if(a!==r){const g=this.grid.getAllColumns(),d=g.filter(f=>!f.utility)[a]?.field,p=d?g.findIndex(f=>f.field===d):g.length,m={field:s,fromIndex:r,toIndex:p};this.emit("column-reorder-request",m)}})}}u.VisibilityPlugin=b,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=visibility.umd.js.map
|