| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  | import * as plugins from '../00plugins.js'; | 
					
						
							| 
									
										
										
										
											2023-09-12 13:42:55 +02:00
										 |  |  | import { demoFunc } from './dees-table.demo.js'; | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  | import { customElement, html, DeesElement, property, type TemplateResult, directives } from '@design.estate/dees-element'; | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  | import { DeesContextmenu } from '../dees-contextmenu.js'; | 
					
						
							| 
									
										
										
										
											2023-08-07 19:13:29 +02:00
										 |  |  | import * as domtools from '@design.estate/dees-domtools'; | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  | import { type TIconKey } from '../dees-icon.js'; | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  | import { tableStyles } from './styles.js'; | 
					
						
							|  |  |  | import type { Column, ITableAction, ITableActionDataArg, TDisplayFunction } from './types.js'; | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   computeColumnsFromDisplayFunction as computeColumnsFromDisplayFunctionFn, | 
					
						
							|  |  |  |   computeEffectiveColumns as computeEffectiveColumnsFn, | 
					
						
							|  |  |  |   getCellValue as getCellValueFn, | 
					
						
							|  |  |  |   getViewData as getViewDataFn, | 
					
						
							|  |  |  | } from './data.js'; | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  | import { compileLucenePredicate } from './lucene.js'; | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | export type { Column, ITableAction, ITableActionDataArg, TDisplayFunction } from './types.js'; | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | declare global { | 
					
						
							|  |  |  |   interface HTMLElementTagNameMap { | 
					
						
							|  |  |  |     'dees-table': DeesTable<any>; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  | // interfaces moved to ./types.ts and re-exported above
 | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | // the table implementation
 | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  | @customElement('dees-table') | 
					
						
							|  |  |  | export class DeesTable<T> extends DeesElement { | 
					
						
							| 
									
										
										
										
											2023-09-12 13:42:55 +02:00
										 |  |  |   public static demo = demoFunc; | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-07 02:28:31 +01:00
										 |  |  |   // INSTANCE
 | 
					
						
							| 
									
										
										
										
											2021-11-26 20:06:09 +01:00
										 |  |  |   @property({ | 
					
						
							|  |  |  |     type: String, | 
					
						
							|  |  |  |   }) | 
					
						
							|  |  |  |   public heading1: string = 'heading 1'; | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-26 20:06:09 +01:00
										 |  |  |   @property({ | 
					
						
							|  |  |  |     type: String, | 
					
						
							|  |  |  |   }) | 
					
						
							|  |  |  |   public heading2: string = 'heading 2'; | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @property({ | 
					
						
							| 
									
										
										
										
											2021-10-07 18:47:36 +02:00
										 |  |  |     type: Array, | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  |   }) | 
					
						
							|  |  |  |   public data: T[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-17 20:07:45 +02:00
										 |  |  |   // dees-form compatibility -----------------------------------------
 | 
					
						
							|  |  |  |   @property({ | 
					
						
							|  |  |  |     type: String, | 
					
						
							|  |  |  |   }) | 
					
						
							|  |  |  |   public key: string; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ | 
					
						
							|  |  |  |     type: String, | 
					
						
							|  |  |  |   }) | 
					
						
							|  |  |  |   public label: string; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ | 
					
						
							|  |  |  |     type: Boolean, | 
					
						
							|  |  |  |   }) | 
					
						
							|  |  |  |   public disabled: boolean = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ | 
					
						
							|  |  |  |     type: Boolean, | 
					
						
							|  |  |  |   }) | 
					
						
							|  |  |  |   public required: boolean = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   get value() { | 
					
						
							|  |  |  |     return this.data; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-06-27 17:50:54 +00:00
										 |  |  |   set value(_valueArg) {} | 
					
						
							| 
									
										
										
										
											2023-10-23 17:26:03 +02:00
										 |  |  |   public changeSubject = new domtools.plugins.smartrx.rxjs.Subject<DeesTable<T>>(); | 
					
						
							| 
									
										
										
										
											2023-10-17 20:07:45 +02:00
										 |  |  |   // end dees-form compatibility -----------------------------------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-21 22:37:39 +01:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * What does a row of data represent? | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2022-12-06 13:11:06 +01:00
										 |  |  |   @property({ | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |     type: String, | 
					
						
							|  |  |  |     reflect: true, | 
					
						
							| 
									
										
										
										
											2022-12-06 13:11:06 +01:00
										 |  |  |   }) | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |   public dataName: string; | 
					
						
							| 
									
										
										
										
											2022-12-06 13:11:06 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-21 22:37:39 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @property({ | 
					
						
							|  |  |  |     type: Boolean, | 
					
						
							|  |  |  |   }) | 
					
						
							|  |  |  |   searchable: boolean = true; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-26 20:06:09 +01:00
										 |  |  |   @property({ | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |     type: Array, | 
					
						
							| 
									
										
										
										
											2021-11-26 20:06:09 +01:00
										 |  |  |   }) | 
					
						
							| 
									
										
										
										
											2023-09-13 21:13:47 +02:00
										 |  |  |   public dataActions: ITableAction<T>[] = []; | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |   // schema-first columns API
 | 
					
						
							|  |  |  |   @property({ attribute: false }) | 
					
						
							|  |  |  |   public columns: Column<T>[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * Stable row identity for selection and updates. If provided as a function, | 
					
						
							|  |  |  |    * it is only usable as a property (not via attribute). | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   @property({ attribute: false }) | 
					
						
							|  |  |  |   public rowKey?: keyof T | ((row: T) => string); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** | 
					
						
							|  |  |  |    * When true and columns are provided, merge any missing columns discovered | 
					
						
							|  |  |  |    * via displayFunction into the effective schema. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   @property({ type: Boolean }) | 
					
						
							|  |  |  |   public augmentFromDisplayFunction: boolean = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  |   @property({ | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |     attribute: false, | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  |   }) | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |   public displayFunction: TDisplayFunction = (itemArg: T) => itemArg as any; | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  |   @property({ | 
					
						
							|  |  |  |     attribute: false, | 
					
						
							|  |  |  |   }) | 
					
						
							|  |  |  |   public reverseDisplayFunction: (itemArg: any) => T = (itemArg: any) => itemArg as T; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  |   @property({ | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |     type: Object, | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  |   }) | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |   public selectedDataRow: T; | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 19:03:18 +02:00
										 |  |  |   @property({ | 
					
						
							|  |  |  |     type: Array, | 
					
						
							|  |  |  |   }) | 
					
						
							|  |  |  |   public editableFields: string[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-06-27 17:50:54 +00:00
										 |  |  |   @property({ | 
					
						
							|  |  |  |     type: Boolean, | 
					
						
							|  |  |  |     reflect: true, | 
					
						
							|  |  |  |     attribute: 'show-vertical-lines' | 
					
						
							|  |  |  |   }) | 
					
						
							|  |  |  |   public showVerticalLines: boolean = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ | 
					
						
							|  |  |  |     type: Boolean, | 
					
						
							|  |  |  |     reflect: true, | 
					
						
							|  |  |  |     attribute: 'show-horizontal-lines' | 
					
						
							|  |  |  |   }) | 
					
						
							|  |  |  |   public showHorizontalLines: boolean = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @property({ | 
					
						
							|  |  |  |     type: Boolean, | 
					
						
							|  |  |  |     reflect: true, | 
					
						
							|  |  |  |     attribute: 'show-grid' | 
					
						
							|  |  |  |   }) | 
					
						
							|  |  |  |   public showGrid: boolean = true; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-07 02:28:31 +01:00
										 |  |  |   public files: File[] = []; | 
					
						
							|  |  |  |   public fileWeakMap = new WeakMap(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-22 20:02:48 +02:00
										 |  |  |   public dataChangeSubject = new domtools.plugins.smartrx.rxjs.Subject(); | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |   // simple client-side sorting (Phase 1)
 | 
					
						
							|  |  |  |   @property({ attribute: false }) | 
					
						
							|  |  |  |   private sortKey?: string; | 
					
						
							|  |  |  |   @property({ attribute: false }) | 
					
						
							|  |  |  |   private sortDir: 'asc' | 'desc' | null = null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |   // simple client-side filtering (Phase 1)
 | 
					
						
							|  |  |  |   @property({ type: String }) | 
					
						
							|  |  |  |   public filterText: string = ''; | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |   // per-column quick filters
 | 
					
						
							|  |  |  |   @property({ attribute: false }) | 
					
						
							|  |  |  |   public columnFilters: Record<string, string> = {}; | 
					
						
							|  |  |  |   @property({ type: Boolean, attribute: 'show-column-filters' }) | 
					
						
							|  |  |  |   public showColumnFilters: boolean = false; | 
					
						
							|  |  |  |   @property({ type: Boolean, reflect: true, attribute: 'sticky-header' }) | 
					
						
							|  |  |  |   public stickyHeader: boolean = false; | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // search row state
 | 
					
						
							|  |  |  |   @property({ type: String }) | 
					
						
							|  |  |  |   public searchMode: 'table' | 'data' | 'server' = 'table'; | 
					
						
							|  |  |  |   private __searchTextSub?: { unsubscribe?: () => void }; | 
					
						
							|  |  |  |   private __searchModeSub?: { unsubscribe?: () => void }; | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |    | 
					
						
							|  |  |  |   // selection (Phase 1)
 | 
					
						
							|  |  |  |   @property({ type: String }) | 
					
						
							|  |  |  |   public selectionMode: 'none' | 'single' | 'multi' = 'none'; | 
					
						
							|  |  |  |   @property({ attribute: false }) | 
					
						
							|  |  |  |   private selectedIds: Set<string> = new Set(); | 
					
						
							|  |  |  |   private _rowIdMap = new WeakMap<object, string>(); | 
					
						
							|  |  |  |   private _rowIdCounter = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  |   constructor() { | 
					
						
							|  |  |  |     super(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |   public static styles = tableStyles; | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   public render(): TemplateResult { | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |     const usingColumns = Array.isArray(this.columns) && this.columns.length > 0; | 
					
						
							|  |  |  |     const effectiveColumns: Column<T>[] = usingColumns | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |       ? computeEffectiveColumnsFn(this.columns, this.augmentFromDisplayFunction, this.displayFunction, this.data) | 
					
						
							|  |  |  |       : computeColumnsFromDisplayFunctionFn(this.displayFunction, this.data); | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  |     const lucenePred = compileLucenePredicate<T>( | 
					
						
							|  |  |  |       this.filterText, | 
					
						
							|  |  |  |       this.searchMode === 'data' ? 'data' : 'table', | 
					
						
							|  |  |  |       effectiveColumns | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const viewData = getViewDataFn( | 
					
						
							|  |  |  |       this.data, | 
					
						
							|  |  |  |       effectiveColumns, | 
					
						
							|  |  |  |       this.sortKey, | 
					
						
							|  |  |  |       this.sortDir, | 
					
						
							|  |  |  |       this.filterText, | 
					
						
							|  |  |  |       this.columnFilters, | 
					
						
							|  |  |  |       this.searchMode === 'data' ? 'data' : 'table', | 
					
						
							|  |  |  |       lucenePred || undefined | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     (this as any)._lastViewData = viewData; | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  |     return html`
 | 
					
						
							|  |  |  |       <div class="mainbox"> | 
					
						
							|  |  |  |         <!-- the heading part --> | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |         <div class="header"> | 
					
						
							|  |  |  |           <div class="headingContainer"> | 
					
						
							| 
									
										
										
										
											2023-10-17 20:07:45 +02:00
										 |  |  |             <div class="heading heading1">${this.label || this.heading1}</div> | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |             <div class="heading heading2">${this.heading2}</div> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |           <div class="headerActions"> | 
					
						
							| 
									
										
										
										
											2025-04-18 17:07:43 +00:00
										 |  |  |             ${directives.resolveExec(async () => { | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |               const resultArray: TemplateResult[] = []; | 
					
						
							|  |  |  |               for (const action of this.dataActions) { | 
					
						
							| 
									
										
										
										
											2023-09-12 13:42:55 +02:00
										 |  |  |                 if (!action.type.includes('header')) continue; | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                 resultArray.push( | 
					
						
							|  |  |  |                   html`<div
 | 
					
						
							|  |  |  |                     class="headerAction" | 
					
						
							|  |  |  |                     @click=${() => { | 
					
						
							| 
									
										
										
										
											2023-09-22 19:04:02 +02:00
										 |  |  |                       action.actionFunc({ | 
					
						
							|  |  |  |                         item: this.selectedDataRow, | 
					
						
							| 
									
										
										
										
											2023-09-22 20:02:48 +02:00
										 |  |  |                         table: this, | 
					
						
							| 
									
										
										
										
											2023-09-22 19:04:02 +02:00
										 |  |  |                       }); | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                     }} | 
					
						
							|  |  |  |                   > | 
					
						
							|  |  |  |                     ${action.iconName | 
					
						
							| 
									
										
										
										
											2025-06-30 12:57:13 +00:00
										 |  |  |                       ? html`<dees-icon .iconSize=${14} .icon=${action.iconName}></dees-icon>
 | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                           ${action.name}`
 | 
					
						
							|  |  |  |                       : action.name} | 
					
						
							|  |  |  |                   </div>`
 | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               return resultArray; | 
					
						
							|  |  |  |             })} | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |         </div> | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  |         <div class="headingSeparation"></div> | 
					
						
							| 
									
										
										
										
											2024-01-21 22:37:39 +01:00
										 |  |  |         <div class="searchGrid hidden"> | 
					
						
							| 
									
										
										
										
											2024-01-21 01:42:06 +01:00
										 |  |  |           <dees-input-text | 
					
						
							|  |  |  |             .label=${'lucene syntax search'} | 
					
						
							|  |  |  |             .description=${`
 | 
					
						
							|  |  |  |               You can use the lucene syntax to search for data, e.g.: | 
					
						
							|  |  |  |                | 
					
						
							|  |  |  |               \`\`\`
 | 
					
						
							|  |  |  |               name: "john" AND age: 18 | 
					
						
							|  |  |  |               \`\`\`
 | 
					
						
							|  |  |  |                | 
					
						
							| 
									
										
										
										
											2024-01-21 14:14:57 +01:00
										 |  |  |             `}
 | 
					
						
							|  |  |  |           ></dees-input-text> | 
					
						
							| 
									
										
										
										
											2024-01-21 01:12:57 +01:00
										 |  |  |           <dees-input-multitoggle | 
					
						
							|  |  |  |             .label=${'search mode'} | 
					
						
							|  |  |  |             .options=${['table', 'data', 'server']} | 
					
						
							| 
									
										
										
										
											2024-01-21 13:36:47 +01:00
										 |  |  |             .selectedOption=${'table'} | 
					
						
							| 
									
										
										
										
											2024-01-21 01:42:06 +01:00
										 |  |  |             .description=${`
 | 
					
						
							|  |  |  |               There are three basic modes: | 
					
						
							|  |  |  |                | 
					
						
							|  |  |  |               * table: only searches data already in the table | 
					
						
							|  |  |  |               * data: searches original data, ignoring table transforms | 
					
						
							|  |  |  |               * server: searches data on the server | 
					
						
							|  |  |  |                | 
					
						
							|  |  |  |             `}
 | 
					
						
							| 
									
										
										
										
											2024-01-21 01:12:57 +01:00
										 |  |  |           ></dees-input-multitoggle> | 
					
						
							|  |  |  |         </div> | 
					
						
							| 
									
										
										
										
											2023-10-24 14:18:03 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  |         <!-- the actual table --> | 
					
						
							|  |  |  |         <style></style> | 
					
						
							|  |  |  |         ${this.data.length > 0 | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |           ? html`
 | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |               <div class="tableScroll"> | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |               <table> | 
					
						
							|  |  |  |                 <thead> | 
					
						
							|  |  |  |                   <tr> | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |                     ${this.selectionMode !== 'none' | 
					
						
							|  |  |  |                       ? html`
 | 
					
						
							|  |  |  |                           <th style="width:42px; text-align:center;"> | 
					
						
							|  |  |  |                             ${this.selectionMode === 'multi' | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |                               ? html`
 | 
					
						
							|  |  |  |                                   <dees-input-checkbox | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  |                                     .value=${this.areAllVisibleSelected()} | 
					
						
							|  |  |  |                                     .indeterminate=${this.isVisibleSelectionIndeterminate()} | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |                                     @newValue=${(e: CustomEvent<boolean>) => { | 
					
						
							|  |  |  |                                       e.stopPropagation(); | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  |                                       this.setSelectVisible(e.detail === true); | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |                                     }} | 
					
						
							|  |  |  |                                   ></dees-input-checkbox> | 
					
						
							|  |  |  |                                 `
 | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |                               : html``} | 
					
						
							|  |  |  |                           </th> | 
					
						
							|  |  |  |                         `
 | 
					
						
							|  |  |  |                       : html``} | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |                     ${effectiveColumns | 
					
						
							|  |  |  |                       .filter((c) => !c.hidden) | 
					
						
							|  |  |  |                       .map((col) => { | 
					
						
							|  |  |  |                         const isSortable = !!col.sortable; | 
					
						
							|  |  |  |                         const ariaSort = this.getAriaSort(col); | 
					
						
							|  |  |  |                         return html`
 | 
					
						
							|  |  |  |                           <th | 
					
						
							|  |  |  |                             role="columnheader" | 
					
						
							|  |  |  |                             aria-sort=${ariaSort} | 
					
						
							|  |  |  |                             style="${isSortable ? 'cursor: pointer;' : ''}" | 
					
						
							|  |  |  |                             @click=${() => (isSortable ? this.toggleSort(col) : null)} | 
					
						
							|  |  |  |                           > | 
					
						
							|  |  |  |                             ${col.header ?? (col.key as any)} | 
					
						
							|  |  |  |                             ${this.renderSortIndicator(col)} | 
					
						
							|  |  |  |                           </th>`;
 | 
					
						
							|  |  |  |                       })} | 
					
						
							|  |  |  |                     ${(() => { | 
					
						
							|  |  |  |                       if (this.dataActions && this.dataActions.length > 0) { | 
					
						
							| 
									
										
										
										
											2025-09-16 16:12:13 +00:00
										 |  |  |                         return html` <th class="actionsCol">Actions</th> `; | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |                       } | 
					
						
							|  |  |  |                     })()} | 
					
						
							|  |  |  |                   </tr> | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |                   ${this.showColumnFilters | 
					
						
							|  |  |  |                     ? html`<tr class="filtersRow">
 | 
					
						
							|  |  |  |                         ${this.selectionMode !== 'none' | 
					
						
							|  |  |  |                           ? html`<th style="width:42px;"></th>` | 
					
						
							|  |  |  |                           : html``} | 
					
						
							|  |  |  |                         ${effectiveColumns | 
					
						
							|  |  |  |                           .filter((c) => !c.hidden) | 
					
						
							|  |  |  |                           .map((col) => { | 
					
						
							|  |  |  |                             const key = String(col.key); | 
					
						
							|  |  |  |                             if (col.filterable === false) return html`<th></th>`; | 
					
						
							|  |  |  |                             return html`<th>
 | 
					
						
							|  |  |  |                               <input type="text" placeholder="Filter..." .value=${this.columnFilters[key] || ''} | 
					
						
							|  |  |  |                                 @input=${(e: Event) => this.setColumnFilter(key, (e.target as HTMLInputElement).value)} /> | 
					
						
							|  |  |  |                             </th>`;
 | 
					
						
							|  |  |  |                           })} | 
					
						
							|  |  |  |                         ${(() => { | 
					
						
							|  |  |  |                           if (this.dataActions && this.dataActions.length > 0) { | 
					
						
							|  |  |  |                             return html` <th></th> `; | 
					
						
							|  |  |  |                           } | 
					
						
							|  |  |  |                         })()} | 
					
						
							|  |  |  |                       </tr>`
 | 
					
						
							|  |  |  |                     : html``} | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |                 </thead> | 
					
						
							|  |  |  |                 <tbody> | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  |                   ${viewData.map((itemArg, rowIndex) => { | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                     const getTr = (elementArg: HTMLElement): HTMLElement => { | 
					
						
							|  |  |  |                       if (elementArg.tagName === 'TR') { | 
					
						
							|  |  |  |                         return elementArg; | 
					
						
							|  |  |  |                       } else { | 
					
						
							|  |  |  |                         return getTr(elementArg.parentElement); | 
					
						
							|  |  |  |                       } | 
					
						
							|  |  |  |                     }; | 
					
						
							|  |  |  |                     return html`
 | 
					
						
							| 
									
										
										
										
											2021-10-07 18:47:36 +02:00
										 |  |  |                       <tr | 
					
						
							|  |  |  |                         @click=${() => { | 
					
						
							|  |  |  |                           this.selectedDataRow = itemArg; | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |                           if (this.selectionMode === 'single') { | 
					
						
							|  |  |  |                             const id = this.getRowId(itemArg); | 
					
						
							|  |  |  |                             this.selectedIds.clear(); | 
					
						
							|  |  |  |                             this.selectedIds.add(id); | 
					
						
							|  |  |  |                             this.emitSelectionChange(); | 
					
						
							|  |  |  |                             this.requestUpdate(); | 
					
						
							|  |  |  |                           } | 
					
						
							| 
									
										
										
										
											2021-10-07 18:47:36 +02:00
										 |  |  |                         }} | 
					
						
							| 
									
										
										
										
											2022-12-07 02:28:31 +01:00
										 |  |  |                         @dragenter=${async (eventArg: DragEvent) => { | 
					
						
							| 
									
										
										
										
											2022-12-06 13:11:06 +01:00
										 |  |  |                           eventArg.preventDefault(); | 
					
						
							|  |  |  |                           eventArg.stopPropagation(); | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                           const realTarget = getTr(eventArg.target as HTMLElement); | 
					
						
							|  |  |  |                           setTimeout(() => { | 
					
						
							| 
									
										
										
										
											2023-09-15 20:11:51 +02:00
										 |  |  |                             realTarget.classList.add('hasAttachment'); | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                           }, 0); | 
					
						
							| 
									
										
										
										
											2022-12-06 13:11:06 +01:00
										 |  |  |                         }} | 
					
						
							| 
									
										
										
										
											2022-12-07 02:28:31 +01:00
										 |  |  |                         @dragleave=${async (eventArg: DragEvent) => { | 
					
						
							| 
									
										
										
										
											2022-12-06 13:11:06 +01:00
										 |  |  |                           eventArg.preventDefault(); | 
					
						
							|  |  |  |                           eventArg.stopPropagation(); | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                           const realTarget = getTr(eventArg.target as HTMLElement); | 
					
						
							| 
									
										
										
										
											2023-09-15 20:11:51 +02:00
										 |  |  |                           realTarget.classList.remove('hasAttachment'); | 
					
						
							| 
									
										
										
										
											2022-12-06 13:11:06 +01:00
										 |  |  |                         }} | 
					
						
							| 
									
										
										
										
											2022-12-07 02:28:31 +01:00
										 |  |  |                         @dragover=${async (eventArg: DragEvent) => { | 
					
						
							| 
									
										
										
										
											2022-12-06 13:11:06 +01:00
										 |  |  |                           eventArg.preventDefault(); | 
					
						
							| 
									
										
										
										
											2022-12-07 02:28:31 +01:00
										 |  |  |                         }} | 
					
						
							|  |  |  |                         @drop=${async (eventArg: DragEvent) => { | 
					
						
							|  |  |  |                           eventArg.preventDefault(); | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                           const newFiles = []; | 
					
						
							| 
									
										
										
										
											2022-12-07 02:28:31 +01:00
										 |  |  |                           for (const file of Array.from(eventArg.dataTransfer.files)) { | 
					
						
							|  |  |  |                             this.files.push(file); | 
					
						
							|  |  |  |                             newFiles.push(file); | 
					
						
							|  |  |  |                             this.requestUpdate(); | 
					
						
							|  |  |  |                           } | 
					
						
							|  |  |  |                           const result: File[] = this.fileWeakMap.get(itemArg as object); | 
					
						
							|  |  |  |                           if (!result) { | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                             this.fileWeakMap.set(itemArg as object, newFiles); | 
					
						
							| 
									
										
										
										
											2022-12-07 02:28:31 +01:00
										 |  |  |                           } else { | 
					
						
							|  |  |  |                             result.push(...newFiles); | 
					
						
							|  |  |  |                           } | 
					
						
							| 
									
										
										
										
											2022-12-06 13:11:06 +01:00
										 |  |  |                         }} | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                         @contextmenu=${async (eventArg: MouseEvent) => { | 
					
						
							| 
									
										
										
										
											2023-09-15 19:03:18 +02:00
										 |  |  |                           DeesContextmenu.openContextMenuWithOptions( | 
					
						
							|  |  |  |                             eventArg, | 
					
						
							|  |  |  |                             this.getActionsForType('contextmenu').map((action) => { | 
					
						
							|  |  |  |                               const menuItem: plugins.tsclass.website.IMenuItem = { | 
					
						
							|  |  |  |                                 name: action.name, | 
					
						
							|  |  |  |                                 iconName: action.iconName as any, | 
					
						
							|  |  |  |                                 action: async () => { | 
					
						
							| 
									
										
										
										
											2023-09-22 19:04:02 +02:00
										 |  |  |                                   await action.actionFunc({ | 
					
						
							|  |  |  |                                     item: itemArg, | 
					
						
							| 
									
										
										
										
											2023-09-22 20:02:48 +02:00
										 |  |  |                                     table: this, | 
					
						
							| 
									
										
										
										
											2023-09-22 19:04:02 +02:00
										 |  |  |                                   }); | 
					
						
							| 
									
										
										
										
											2023-09-15 19:03:18 +02:00
										 |  |  |                                   return null; | 
					
						
							|  |  |  |                                 }, | 
					
						
							|  |  |  |                               }; | 
					
						
							|  |  |  |                               return menuItem; | 
					
						
							|  |  |  |                             }) | 
					
						
							|  |  |  |                           ); | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                         }} | 
					
						
							| 
									
										
										
										
											2021-10-07 18:47:36 +02:00
										 |  |  |                         class="${itemArg === this.selectedDataRow ? 'selected' : ''}" | 
					
						
							|  |  |  |                       > | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |                         ${this.selectionMode !== 'none' | 
					
						
							|  |  |  |                           ? html`<td style="width:42px; text-align:center;">
 | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |                               <dees-input-checkbox | 
					
						
							|  |  |  |                                 .value=${this.isRowSelected(itemArg)} | 
					
						
							|  |  |  |                                 @newValue=${(e: CustomEvent<boolean>) => { | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |                                   e.stopPropagation(); | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |                                   this.setRowSelected(itemArg, e.detail === true); | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |                                 }} | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |                               ></dees-input-checkbox> | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |                             </td>`
 | 
					
						
							|  |  |  |                           : html``} | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |                         ${effectiveColumns | 
					
						
							|  |  |  |                           .filter((c) => !c.hidden) | 
					
						
							|  |  |  |                           .map((col, colIndex) => { | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |                             const value = getCellValueFn(itemArg, col, this.displayFunction); | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |                             const content = col.renderer | 
					
						
							|  |  |  |                               ? col.renderer(value, itemArg, { rowIndex, colIndex, column: col }) | 
					
						
							|  |  |  |                               : value; | 
					
						
							|  |  |  |                             const editKey = String(col.key); | 
					
						
							|  |  |  |                             return html`
 | 
					
						
							|  |  |  |                               <td | 
					
						
							|  |  |  |                                 @dblclick=${(e: Event) => { | 
					
						
							|  |  |  |                                   const dblAction = this.dataActions.find((actionArg) => | 
					
						
							| 
									
										
										
										
											2023-09-15 19:03:18 +02:00
										 |  |  |                                     actionArg.type.includes('doubleClick') | 
					
						
							|  |  |  |                                   ); | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |                                   if (this.editableFields.includes(editKey)) { | 
					
						
							|  |  |  |                                     this.handleCellEditing(e, itemArg, editKey); | 
					
						
							|  |  |  |                                   } else if (dblAction) { | 
					
						
							|  |  |  |                                     dblAction.actionFunc({ item: itemArg, table: this }); | 
					
						
							| 
									
										
										
										
											2023-09-15 19:03:18 +02:00
										 |  |  |                                   } | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |                                 }} | 
					
						
							|  |  |  |                               > | 
					
						
							|  |  |  |                                 <div class="innerCellContainer">${content}</div> | 
					
						
							|  |  |  |                               </td> | 
					
						
							|  |  |  |                             `;
 | 
					
						
							|  |  |  |                           })} | 
					
						
							| 
									
										
										
										
											2022-12-06 13:11:06 +01:00
										 |  |  |                         ${(() => { | 
					
						
							| 
									
										
										
										
											2022-12-11 17:24:12 +01:00
										 |  |  |                           if (this.dataActions && this.dataActions.length > 0) { | 
					
						
							| 
									
										
										
										
											2022-12-06 13:11:06 +01:00
										 |  |  |                             return html`
 | 
					
						
							| 
									
										
										
										
											2025-09-16 16:12:13 +00:00
										 |  |  |                               <td class="actionsCol"> | 
					
						
							| 
									
										
										
										
											2025-06-27 17:50:54 +00:00
										 |  |  |                                 <div class="actionsContainer"> | 
					
						
							|  |  |  |                                   ${this.getActionsForType('inRow').map( | 
					
						
							|  |  |  |                                     (actionArg) => html`
 | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |                                       <div | 
					
						
							|  |  |  |                                         class="action" | 
					
						
							|  |  |  |                                         @click=${() => | 
					
						
							|  |  |  |                                           actionArg.actionFunc({ | 
					
						
							|  |  |  |                                             item: itemArg, | 
					
						
							|  |  |  |                                             table: this, | 
					
						
							|  |  |  |                                           })} | 
					
						
							|  |  |  |                                       > | 
					
						
							|  |  |  |                                         ${actionArg.iconName | 
					
						
							|  |  |  |                                           ? html` <dees-icon .icon=${actionArg.iconName}></dees-icon> ` | 
					
						
							|  |  |  |                                           : actionArg.name} | 
					
						
							|  |  |  |                                       </div> | 
					
						
							|  |  |  |                                     `
 | 
					
						
							|  |  |  |                                   )} | 
					
						
							| 
									
										
										
										
											2022-12-06 13:11:06 +01:00
										 |  |  |                                 </div> | 
					
						
							|  |  |  |                               </td> | 
					
						
							|  |  |  |                             `;
 | 
					
						
							|  |  |  |                           } | 
					
						
							|  |  |  |                         })()} | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |                       </tr>`;
 | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                   })} | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |                 </tbody> | 
					
						
							|  |  |  |               </table> | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |               </div> | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |             `
 | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  |           : html` <div class="noDataSet">No data set!</div> `} | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |         <div class="footer"> | 
					
						
							|  |  |  |           <div class="tableStatistics"> | 
					
						
							|  |  |  |             ${this.data.length} ${this.dataName || 'data rows'} (total) | | 
					
						
							|  |  |  |             ${this.selectedDataRow ? '# ' + `${this.data.indexOf(this.selectedDataRow) + 1}` : `No`} | 
					
						
							|  |  |  |             selected | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |           <div class="footerActions"> | 
					
						
							| 
									
										
										
										
											2025-04-18 17:07:43 +00:00
										 |  |  |             ${directives.resolveExec(async () => { | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |               const resultArray: TemplateResult[] = []; | 
					
						
							|  |  |  |               for (const action of this.dataActions) { | 
					
						
							| 
									
										
										
										
											2023-09-12 13:42:55 +02:00
										 |  |  |                 if (!action.type.includes('footer')) continue; | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                 resultArray.push( | 
					
						
							|  |  |  |                   html`<div
 | 
					
						
							|  |  |  |                     class="footerAction" | 
					
						
							|  |  |  |                     @click=${() => { | 
					
						
							| 
									
										
										
										
											2023-09-22 19:04:02 +02:00
										 |  |  |                       action.actionFunc({ | 
					
						
							|  |  |  |                         item: this.selectedDataRow, | 
					
						
							| 
									
										
										
										
											2023-09-22 20:02:48 +02:00
										 |  |  |                         table: this, | 
					
						
							| 
									
										
										
										
											2023-09-22 19:04:02 +02:00
										 |  |  |                       }); | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                     }} | 
					
						
							|  |  |  |                   > | 
					
						
							|  |  |  |                     ${action.iconName | 
					
						
							| 
									
										
										
										
											2025-06-30 12:57:13 +00:00
										 |  |  |                       ? html`<dees-icon .iconSize=${14} .icon=${action.iconName}></dees-icon>
 | 
					
						
							| 
									
										
										
										
											2023-09-04 19:28:50 +02:00
										 |  |  |                           ${action.name}`
 | 
					
						
							|  |  |  |                       : action.name} | 
					
						
							|  |  |  |                   </div>`
 | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |               return resultArray; | 
					
						
							|  |  |  |             })} | 
					
						
							|  |  |  |           </div> | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  |         </div> | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     `;
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-21 22:37:39 +01:00
										 |  |  |   public async firstUpdated() { | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-09-12 13:42:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-16 14:31:03 +02:00
										 |  |  |   public async updated(changedProperties: Map<string | number | symbol, unknown>): Promise<void> { | 
					
						
							|  |  |  |     super.updated(changedProperties); | 
					
						
							| 
									
										
										
										
											2023-10-20 10:47:53 +02:00
										 |  |  |     this.determineColumnWidths(); | 
					
						
							| 
									
										
										
										
											2024-01-21 22:37:39 +01:00
										 |  |  |     if (this.searchable) { | 
					
						
							|  |  |  |       const existing = this.dataActions.find((actionArg) => actionArg.type.includes('header') && actionArg.name === 'Search'); | 
					
						
							|  |  |  |       if (!existing) { | 
					
						
							|  |  |  |         this.dataActions.unshift({ | 
					
						
							|  |  |  |           name: 'Search', | 
					
						
							|  |  |  |           iconName: 'magnifyingGlass', | 
					
						
							|  |  |  |           type: ['header'], | 
					
						
							|  |  |  |           actionFunc: async () => { | 
					
						
							|  |  |  |             console.log('open search'); | 
					
						
							|  |  |  |             const searchGrid = this.shadowRoot.querySelector('.searchGrid'); | 
					
						
							|  |  |  |             searchGrid.classList.toggle('hidden'); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         console.log(this.dataActions); | 
					
						
							|  |  |  |         this.requestUpdate(); | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  |       // wire search inputs
 | 
					
						
							|  |  |  |       this.wireSearchInputs(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private __debounceTimer?: any; | 
					
						
							|  |  |  |   private debounceRun(fn: () => void, ms = 200) { | 
					
						
							|  |  |  |     if (this.__debounceTimer) clearTimeout(this.__debounceTimer); | 
					
						
							|  |  |  |     this.__debounceTimer = setTimeout(fn, ms); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private wireSearchInputs() { | 
					
						
							|  |  |  |     const searchTextEl: any = this.shadowRoot?.querySelector('.searchGrid dees-input-text'); | 
					
						
							|  |  |  |     const searchModeEl: any = this.shadowRoot?.querySelector('.searchGrid dees-input-multitoggle'); | 
					
						
							|  |  |  |     if (searchTextEl && !this.__searchTextSub) { | 
					
						
							|  |  |  |       this.__searchTextSub = searchTextEl.changeSubject.subscribe((el: any) => { | 
					
						
							|  |  |  |         const val: string = el?.value ?? ''; | 
					
						
							|  |  |  |         this.debounceRun(() => { | 
					
						
							|  |  |  |           if (this.searchMode === 'server') { | 
					
						
							|  |  |  |             this.dispatchEvent( | 
					
						
							|  |  |  |               new CustomEvent('searchRequest', { | 
					
						
							|  |  |  |                 detail: { query: val, mode: 'server' }, | 
					
						
							|  |  |  |                 bubbles: true, | 
					
						
							|  |  |  |               }) | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           } else { | 
					
						
							|  |  |  |             this.setFilterText(val); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (searchModeEl && !this.__searchModeSub) { | 
					
						
							|  |  |  |       this.__searchModeSub = searchModeEl.changeSubject.subscribe((el: any) => { | 
					
						
							|  |  |  |         const mode: string = el?.selectedOption || el?.value || 'table'; | 
					
						
							|  |  |  |         if (mode === 'table' || mode === 'data' || mode === 'server') { | 
					
						
							|  |  |  |           this.searchMode = mode as any; | 
					
						
							|  |  |  |           // When switching modes, re-apply current text input
 | 
					
						
							|  |  |  |           const val: string = searchTextEl?.value ?? ''; | 
					
						
							|  |  |  |           this.debounceRun(() => { | 
					
						
							|  |  |  |             if (this.searchMode === 'server') { | 
					
						
							|  |  |  |               this.dispatchEvent(new CustomEvent('searchRequest', { detail: { query: val, mode: 'server' }, bubbles: true })); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |               this.setFilterText(val); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2024-01-21 22:37:39 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-09-16 14:31:03 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-20 10:47:53 +02:00
										 |  |  |   public async determineColumnWidths() { | 
					
						
							|  |  |  |     const domtools = await this.domtoolsPromise; | 
					
						
							|  |  |  |     await domtools.convenience.smartdelay.delayFor(0); | 
					
						
							| 
									
										
										
										
											2023-09-16 14:31:03 +02:00
										 |  |  |     // Get the table element
 | 
					
						
							|  |  |  |     const table = this.shadowRoot.querySelector('table'); | 
					
						
							|  |  |  |     if (!table) return; | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-16 14:31:03 +02:00
										 |  |  |     // Get the first row's cells to measure the widths
 | 
					
						
							|  |  |  |     const cells = table.rows[0].cells; | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-20 10:47:53 +02:00
										 |  |  |     const handleColumnByIndex = async (i: number, waitForRenderArg: boolean = false) => { | 
					
						
							|  |  |  |       const done = plugins.smartpromise.defer(); | 
					
						
							| 
									
										
										
										
											2023-09-16 14:31:03 +02:00
										 |  |  |       const cell = cells[i]; | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-16 14:31:03 +02:00
										 |  |  |       // Get computed width
 | 
					
						
							|  |  |  |       const width = window.getComputedStyle(cell).width; | 
					
						
							| 
									
										
										
										
											2023-10-20 10:47:53 +02:00
										 |  |  |       if (cell.textContent.includes('Actions')) { | 
					
						
							| 
									
										
										
										
											2023-10-24 14:18:03 +02:00
										 |  |  |         const neededWidth = | 
					
						
							| 
									
										
										
										
											2024-01-21 13:57:26 +01:00
										 |  |  |           this.dataActions.filter((actionArg) => actionArg.type.includes('inRow')).length * 36; | 
					
						
							| 
									
										
										
										
											2023-10-20 11:17:42 +02:00
										 |  |  |         cell.style.width = `${Math.max(neededWidth, 68)}px`; | 
					
						
							| 
									
										
										
										
											2023-10-20 10:47:53 +02:00
										 |  |  |       } else { | 
					
						
							|  |  |  |         cell.style.width = width; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (waitForRenderArg) { | 
					
						
							|  |  |  |         requestAnimationFrame(() => { | 
					
						
							|  |  |  |           done.resolve(); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         await done.promise; | 
					
						
							| 
									
										
										
										
											2023-09-16 14:31:03 +02:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-10-24 14:18:03 +02:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2023-10-20 10:47:53 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (cells[cells.length - 1].textContent.includes('Actions')) { | 
					
						
							|  |  |  |       await handleColumnByIndex(cells.length - 1, true); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-20 10:47:53 +02:00
										 |  |  |     for (let i = 0; i < cells.length; i++) { | 
					
						
							|  |  |  |       if (cells[i].textContent.includes('Actions')) { | 
					
						
							|  |  |  |         continue; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       await handleColumnByIndex(i); | 
					
						
							| 
									
										
										
										
											2023-09-16 14:31:03 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-10-20 10:47:53 +02:00
										 |  |  |     table.style.tableLayout = 'fixed'; | 
					
						
							| 
									
										
										
										
											2023-09-16 14:31:03 +02:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |   // compute helpers moved to ./data.ts
 | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |   private toggleSort(col: Column<T>) { | 
					
						
							|  |  |  |     const key = String(col.key); | 
					
						
							|  |  |  |     if (this.sortKey !== key) { | 
					
						
							|  |  |  |       this.sortKey = key; | 
					
						
							|  |  |  |       this.sortDir = 'asc'; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       if (this.sortDir === 'asc') this.sortDir = 'desc'; | 
					
						
							|  |  |  |       else if (this.sortDir === 'desc') { | 
					
						
							|  |  |  |         this.sortDir = null; | 
					
						
							|  |  |  |         this.sortKey = undefined; | 
					
						
							|  |  |  |       } else this.sortDir = 'asc'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.dispatchEvent( | 
					
						
							|  |  |  |       new CustomEvent('sortChange', { | 
					
						
							|  |  |  |         detail: { key: this.sortKey, dir: this.sortDir }, | 
					
						
							|  |  |  |         bubbles: true, | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     this.requestUpdate(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private getAriaSort(col: Column<T>): 'none' | 'ascending' | 'descending' { | 
					
						
							|  |  |  |     if (String(col.key) !== this.sortKey || !this.sortDir) return 'none'; | 
					
						
							|  |  |  |     return this.sortDir === 'asc' ? 'ascending' : 'descending'; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private renderSortIndicator(col: Column<T>) { | 
					
						
							|  |  |  |     if (String(col.key) !== this.sortKey || !this.sortDir) return html``; | 
					
						
							|  |  |  |     return html`<span style="margin-left:6px; opacity:0.7;">${this.sortDir === 'asc' ? '▲' : '▼'}</span>`; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |   // filtering helpers
 | 
					
						
							|  |  |  |   public setFilterText(value: string) { | 
					
						
							|  |  |  |     const prev = this.filterText; | 
					
						
							|  |  |  |     this.filterText = value ?? ''; | 
					
						
							|  |  |  |     if (prev !== this.filterText) { | 
					
						
							|  |  |  |       this.dispatchEvent( | 
					
						
							|  |  |  |         new CustomEvent('filterChange', { | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |           detail: { text: this.filterText, columns: { ...this.columnFilters } }, | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |           bubbles: true, | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       this.requestUpdate(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |   public setColumnFilter(key: string, value: string) { | 
					
						
							|  |  |  |     this.columnFilters = { ...this.columnFilters, [key]: value }; | 
					
						
							|  |  |  |     this.dispatchEvent( | 
					
						
							|  |  |  |       new CustomEvent('filterChange', { | 
					
						
							|  |  |  |         detail: { text: this.filterText, columns: { ...this.columnFilters } }, | 
					
						
							|  |  |  |         bubbles: true, | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     this.requestUpdate(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |   // selection helpers
 | 
					
						
							|  |  |  |   private getRowId(row: T): string { | 
					
						
							|  |  |  |     if (this.rowKey) { | 
					
						
							|  |  |  |       if (typeof this.rowKey === 'function') return this.rowKey(row); | 
					
						
							|  |  |  |       return String((row as any)[this.rowKey]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const key = row as any as object; | 
					
						
							|  |  |  |     if (!this._rowIdMap.has(key)) { | 
					
						
							|  |  |  |       this._rowIdMap.set(key, String(++this._rowIdCounter)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return this._rowIdMap.get(key); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private isRowSelected(row: T): boolean { | 
					
						
							|  |  |  |     return this.selectedIds.has(this.getRowId(row)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private toggleRowSelected(row: T) { | 
					
						
							|  |  |  |     const id = this.getRowId(row); | 
					
						
							|  |  |  |     if (this.selectionMode === 'single') { | 
					
						
							|  |  |  |       this.selectedIds.clear(); | 
					
						
							|  |  |  |       this.selectedIds.add(id); | 
					
						
							|  |  |  |     } else if (this.selectionMode === 'multi') { | 
					
						
							|  |  |  |       if (this.selectedIds.has(id)) this.selectedIds.delete(id); | 
					
						
							|  |  |  |       else this.selectedIds.add(id); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.emitSelectionChange(); | 
					
						
							|  |  |  |     this.requestUpdate(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |   private setRowSelected(row: T, checked: boolean) { | 
					
						
							|  |  |  |     const id = this.getRowId(row); | 
					
						
							|  |  |  |     if (this.selectionMode === 'single') { | 
					
						
							|  |  |  |       this.selectedIds.clear(); | 
					
						
							|  |  |  |       if (checked) this.selectedIds.add(id); | 
					
						
							|  |  |  |     } else if (this.selectionMode === 'multi') { | 
					
						
							|  |  |  |       if (checked) this.selectedIds.add(id); | 
					
						
							|  |  |  |       else this.selectedIds.delete(id); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.emitSelectionChange(); | 
					
						
							|  |  |  |     this.requestUpdate(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  |   private areAllVisibleSelected(): boolean { | 
					
						
							|  |  |  |     const view: T[] = (this as any)._lastViewData || []; | 
					
						
							|  |  |  |     if (view.length === 0) return false; | 
					
						
							|  |  |  |     for (const r of view) { | 
					
						
							|  |  |  |       if (!this.selectedIds.has(this.getRowId(r))) return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return true; | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  |   private isVisibleSelectionIndeterminate(): boolean { | 
					
						
							|  |  |  |     const view: T[] = (this as any)._lastViewData || []; | 
					
						
							|  |  |  |     if (view.length === 0) return false; | 
					
						
							|  |  |  |     let count = 0; | 
					
						
							|  |  |  |     for (const r of view) { | 
					
						
							|  |  |  |       if (this.selectedIds.has(this.getRowId(r))) count++; | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  |     return count > 0 && count < view.length; | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  |   private setSelectVisible(checked: boolean) { | 
					
						
							|  |  |  |     const view: T[] = (this as any)._lastViewData || []; | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |     if (checked) { | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  |       for (const r of view) this.selectedIds.add(this.getRowId(r)); | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2025-09-16 15:46:44 +00:00
										 |  |  |       for (const r of view) this.selectedIds.delete(this.getRowId(r)); | 
					
						
							| 
									
										
										
										
											2025-09-16 15:17:33 +00:00
										 |  |  |     } | 
					
						
							|  |  |  |     this.emitSelectionChange(); | 
					
						
							| 
									
										
										
										
											2025-09-16 14:53:59 +00:00
										 |  |  |     this.requestUpdate(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private emitSelectionChange() { | 
					
						
							|  |  |  |     const selectedIds = Array.from(this.selectedIds); | 
					
						
							|  |  |  |     const selectedRows = this.data.filter((r) => this.selectedIds.has(this.getRowId(r))); | 
					
						
							|  |  |  |     this.dispatchEvent( | 
					
						
							|  |  |  |       new CustomEvent('selectionChange', { | 
					
						
							|  |  |  |         detail: { selectedIds, selectedRows }, | 
					
						
							|  |  |  |         bubbles: true, | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-12 13:42:55 +02:00
										 |  |  |   getActionsForType(typeArg: ITableAction['type'][0]) { | 
					
						
							|  |  |  |     const actions: ITableAction[] = []; | 
					
						
							|  |  |  |     for (const action of this.dataActions) { | 
					
						
							|  |  |  |       if (!action.type.includes(typeArg)) continue; | 
					
						
							|  |  |  |       actions.push(action); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return actions; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-09-15 19:03:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 19:33:04 +02:00
										 |  |  |   async handleCellEditing(event: Event, itemArg: T, key: string) { | 
					
						
							| 
									
										
										
										
											2025-06-27 17:50:54 +00:00
										 |  |  |     await this.domtoolsPromise; | 
					
						
							| 
									
										
										
										
											2023-09-15 19:03:18 +02:00
										 |  |  |     const target = event.target as HTMLElement; | 
					
						
							| 
									
										
										
										
											2023-10-07 20:01:49 +02:00
										 |  |  |     const originalColor = target.style.color; | 
					
						
							|  |  |  |     target.style.color = 'transparent'; | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  |     const transformedItem = this.displayFunction(itemArg); | 
					
						
							| 
									
										
										
										
											2025-09-14 19:57:50 +00:00
										 |  |  |     const initialValue = ((transformedItem as any)[key] ?? (itemArg as any)[key] ?? '') as string; | 
					
						
							| 
									
										
										
										
											2023-09-15 19:03:18 +02:00
										 |  |  |     // Create an input element
 | 
					
						
							|  |  |  |     const input = document.createElement('input'); | 
					
						
							|  |  |  |     input.type = 'text'; | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  |     input.value = initialValue; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 19:33:04 +02:00
										 |  |  |     const blurInput = async (blurArg = true, saveArg = false) => { | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  |       if (blurArg) { | 
					
						
							|  |  |  |         input.blur(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       if (saveArg) { | 
					
						
							|  |  |  |         itemArg[key] = input.value as any; // Convert string to T (you might need better type casting depending on your data structure)
 | 
					
						
							| 
									
										
										
										
											2023-10-18 15:18:49 +02:00
										 |  |  |         this.changeSubject.next(this); | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2023-10-07 19:33:04 +02:00
										 |  |  |       input.remove(); | 
					
						
							| 
									
										
										
										
											2023-10-07 20:01:49 +02:00
										 |  |  |       target.style.color = originalColor; | 
					
						
							| 
									
										
										
										
											2023-10-07 19:33:04 +02:00
										 |  |  |       this.requestUpdate(); | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2023-09-15 19:03:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // When the input loses focus or the Enter key is pressed, update the data
 | 
					
						
							|  |  |  |     input.addEventListener('blur', () => { | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  |       blurInput(false, false); | 
					
						
							| 
									
										
										
										
											2023-09-15 19:03:18 +02:00
										 |  |  |     }); | 
					
						
							|  |  |  |     input.addEventListener('keydown', (e: KeyboardEvent) => { | 
					
						
							|  |  |  |       if (e.key === 'Enter') { | 
					
						
							| 
									
										
										
										
											2023-09-17 21:38:02 +02:00
										 |  |  |         blurInput(true, true); // This will trigger the blur event handler above
 | 
					
						
							| 
									
										
										
										
											2023-09-15 19:03:18 +02:00
										 |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Replace the cell's content with the input
 | 
					
						
							|  |  |  |     target.appendChild(input); | 
					
						
							|  |  |  |     input.focus(); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-10-07 18:01:05 +02:00
										 |  |  | } |