Refactor DeesTable component: modularize data handling and styles
- Moved column computation and data retrieval logic to a new data.ts file for better separation of concerns. - Created a styles.ts file to encapsulate all CSS styles related to the DeesTable component. - Updated the DeesTable class to utilize the new data handling functions and styles. - Introduced selection and filtering features, allowing for single and multi-row selection. - Enhanced rendering logic to accommodate selection checkboxes and filtering capabilities. - Re-exported types from types.ts for better type management and clarity.
This commit is contained in:
		
							
								
								
									
										78
									
								
								ts_web/elements/dees-table/data.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								ts_web/elements/dees-table/data.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| import type { Column, TDisplayFunction } from './types.js'; | ||||
|  | ||||
| export function computeColumnsFromDisplayFunction<T>( | ||||
|   displayFunction: TDisplayFunction<T>, | ||||
|   data: T[] | ||||
| ): Column<T>[] { | ||||
|   if (!data || data.length === 0) return []; | ||||
|   const firstTransformedItem = displayFunction(data[0]); | ||||
|   const keys: string[] = Object.keys(firstTransformedItem); | ||||
|   return keys.map((key) => ({ | ||||
|     key, | ||||
|     header: key, | ||||
|     value: (row: T) => displayFunction(row)[key], | ||||
|   })); | ||||
| } | ||||
|  | ||||
| export function computeEffectiveColumns<T>( | ||||
|   columns: Column<T>[] | undefined, | ||||
|   augmentFromDisplayFunction: boolean, | ||||
|   displayFunction: TDisplayFunction<T>, | ||||
|   data: T[] | ||||
| ): Column<T>[] { | ||||
|   const base = (columns || []).slice(); | ||||
|   if (!augmentFromDisplayFunction) return base; | ||||
|   const fromDisplay = computeColumnsFromDisplayFunction(displayFunction, data); | ||||
|   const existingKeys = new Set(base.map((c) => String(c.key))); | ||||
|   for (const col of fromDisplay) { | ||||
|     if (!existingKeys.has(String(col.key))) { | ||||
|       base.push(col); | ||||
|     } | ||||
|   } | ||||
|   return base; | ||||
| } | ||||
|  | ||||
| export function getCellValue<T>(row: T, col: Column<T>, displayFunction?: TDisplayFunction<T>): any { | ||||
|   return col.value ? col.value(row) : (row as any)[col.key as any]; | ||||
| } | ||||
|  | ||||
| export function getViewData<T>( | ||||
|   data: T[], | ||||
|   effectiveColumns: Column<T>[], | ||||
|   sortKey?: string, | ||||
|   sortDir?: 'asc' | 'desc' | null, | ||||
|   filterText?: string | ||||
| ): T[] { | ||||
|   let arr = data.slice(); | ||||
|   const ft = (filterText || '').trim().toLowerCase(); | ||||
|   if (ft) { | ||||
|     arr = arr.filter((row) => { | ||||
|       for (const col of effectiveColumns) { | ||||
|         if (col.hidden) continue; | ||||
|         const val = getCellValue(row, col); | ||||
|         const s = String(val ?? '').toLowerCase(); | ||||
|         if (s.includes(ft)) return true; | ||||
|       } | ||||
|       return false; | ||||
|     }); | ||||
|   } | ||||
|   if (!sortKey || !sortDir) return arr; | ||||
|   const col = effectiveColumns.find((c) => String(c.key) === sortKey); | ||||
|   if (!col) return arr; | ||||
|   const dir = sortDir === 'asc' ? 1 : -1; | ||||
|   arr.sort((a, b) => { | ||||
|     const va = getCellValue(a, col); | ||||
|     const vb = getCellValue(b, col); | ||||
|     if (va == null && vb == null) return 0; | ||||
|     if (va == null) return -1 * dir; | ||||
|     if (vb == null) return 1 * dir; | ||||
|     if (typeof va === 'number' && typeof vb === 'number') return (va - vb) * dir; | ||||
|     const sa = String(va).toLowerCase(); | ||||
|     const sb = String(vb).toLowerCase(); | ||||
|     if (sa < sb) return -1 * dir; | ||||
|     if (sa > sb) return 1 * dir; | ||||
|     return 0; | ||||
|   }); | ||||
|   return arr; | ||||
| } | ||||
|  | ||||
| @@ -467,6 +467,44 @@ export const demoFunc = () => html` | ||||
|           dataName="users" | ||||
|         ></dees-table> | ||||
|       </div> | ||||
|  | ||||
|       <div class="demo-section"  | ||||
|            @selectionChange=${(e: CustomEvent) => { console.log('Selection changed', e.detail); }} | ||||
|            @search-changed=${(e: CustomEvent) => { | ||||
|              const tbl = document.getElementById('tableFilterSelectDemo') as any; | ||||
|              if (tbl) tbl.setFilterText(e.detail.value); | ||||
|            }} | ||||
|            @search-submit=${(e: CustomEvent) => { | ||||
|              const tbl = document.getElementById('tableFilterSelectDemo') as any; | ||||
|              if (tbl) tbl.setFilterText(e.detail.value); | ||||
|            }} | ||||
|       > | ||||
|         <h2 class="demo-title">Filtering + Multi-Selection (New)</h2> | ||||
|         <p class="demo-description">Use the search bar to filter rows; toggle selection via checkboxes. Click headers to sort.</p> | ||||
|         <dees-searchbar></dees-searchbar> | ||||
|         <div style="height: 12px"></div> | ||||
|         <dees-table | ||||
|           id="tableFilterSelectDemo" | ||||
|           heading1="Inventory (Filter + Select)" | ||||
|           heading2="Try typing to filter and selecting multiple rows" | ||||
|           .selectionMode=${'multi'} | ||||
|           .rowKey=${'sku'} | ||||
|           .columns=${[ | ||||
|             { key: 'sku', header: 'SKU', sortable: true }, | ||||
|             { key: 'name', header: 'Name', sortable: true }, | ||||
|             { key: 'stock', header: 'Stock', sortable: true }, | ||||
|           ]} | ||||
|           .data=${[ | ||||
|             { sku: 'A-100', name: 'USB-C Cable', stock: 120 }, | ||||
|             { sku: 'A-101', name: 'Wireless Mouse', stock: 55 }, | ||||
|             { sku: 'A-102', name: 'Laptop Stand', stock: 18 }, | ||||
|             { sku: 'B-200', name: 'Keyboard (ISO)', stock: 89 }, | ||||
|             { sku: 'B-201', name: 'HDMI Adapter', stock: 0 }, | ||||
|             { sku: 'C-300', name: 'Webcam 1080p', stock: 42 }, | ||||
|           ]} | ||||
|           dataName="items" | ||||
|         ></dees-table> | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| `; | ||||
|   | ||||
| @@ -1,21 +1,21 @@ | ||||
| import * as plugins from '../00plugins.js'; | ||||
| import { demoFunc } from './dees-table.demo.js'; | ||||
| import { cssGeistFontFamily } from '../00fonts.js'; | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   DeesElement, | ||||
|   property, | ||||
|   type TemplateResult, | ||||
|   cssManager, | ||||
|   css, | ||||
|   directives, | ||||
| } from '@design.estate/dees-element'; | ||||
| import { customElement, html, DeesElement, property, type TemplateResult, directives } from '@design.estate/dees-element'; | ||||
|  | ||||
| import { DeesContextmenu } from '../dees-contextmenu.js'; | ||||
|  | ||||
| import * as plugins from '../00plugins.js'; | ||||
| import * as domtools from '@design.estate/dees-domtools'; | ||||
| import { type TIconKey } from '../dees-icon.js'; | ||||
| 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'; | ||||
|  | ||||
| export type { Column, ITableAction, ITableActionDataArg, TDisplayFunction } from './types.js'; | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
| @@ -23,62 +23,7 @@ declare global { | ||||
|   } | ||||
| } | ||||
|  | ||||
| // interfaces | ||||
| export interface ITableAction<T = any> { | ||||
|   name: string; | ||||
|   iconName: TIconKey; | ||||
|   /** | ||||
|    * the table behaviour to use for this action | ||||
|    * e.g. upload: allows to upload files to the table | ||||
|    */ | ||||
|   useTableBehaviour?: 'upload' | 'cancelUpload' | 'none'; | ||||
|   /** | ||||
|    * the type of the action | ||||
|    */ | ||||
|   type: ( | ||||
|     | 'inRow' | ||||
|     | 'contextmenu' | ||||
|     | 'doubleClick' | ||||
|     | 'footer' | ||||
|     | 'header' | ||||
|     | 'preview' | ||||
|     | 'keyCombination' | ||||
|   )[]; | ||||
|   /** | ||||
|    * allows to check if the action is relevant for the given item | ||||
|    * @param itemArg | ||||
|    * @returns | ||||
|    */ | ||||
|   actionRelevancyCheckFunc?: (itemArg: T) => boolean; | ||||
|   /** | ||||
|    * the actual action function implementation | ||||
|    * @param itemArg | ||||
|    * @returns | ||||
|    */ | ||||
|   actionFunc: (actionDataArg: ITableActionDataArg<T>) => Promise<any>; | ||||
| } | ||||
|  | ||||
| export interface ITableActionDataArg<T> { | ||||
|   item: T; | ||||
|   table: DeesTable<T>; | ||||
| } | ||||
|  | ||||
| // schema-first columns API (Phase 1) | ||||
| export interface Column<T = any> { | ||||
|   /** key in the raw item or a computed key name */ | ||||
|   key: keyof T | string; | ||||
|   /** header label or template; defaults to key */ | ||||
|   header?: string | TemplateResult; | ||||
|   /** compute the cell value when not reading directly by key */ | ||||
|   value?: (row: T) => any; | ||||
|   /** optional cell renderer */ | ||||
|   renderer?: (value: any, row: T, ctx: { rowIndex: number; colIndex: number; column: Column<T> }) => TemplateResult | string; | ||||
|   /** reserved for future phases; present to sketch intent */ | ||||
|   sortable?: boolean; | ||||
|   hidden?: boolean; | ||||
| } | ||||
|  | ||||
| export type TDisplayFunction<T = any> = (itemArg: T) => object; | ||||
| // interfaces moved to ./types.ts and re-exported above | ||||
|  | ||||
| // the table implementation | ||||
| @customElement('dees-table') | ||||
| @@ -219,374 +164,29 @@ export class DeesTable<T> extends DeesElement { | ||||
|   @property({ attribute: false }) | ||||
|   private sortDir: 'asc' | 'desc' | null = null; | ||||
|  | ||||
|   // simple client-side filtering (Phase 1) | ||||
|   @property({ type: String }) | ||||
|   public filterText: string = ''; | ||||
|    | ||||
|   // 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; | ||||
|  | ||||
|   constructor() { | ||||
|     super(); | ||||
|   } | ||||
|  | ||||
|   public static styles = [ | ||||
|     cssManager.defaultStyles, | ||||
|     css` | ||||
|       :host { | ||||
|         display: block; | ||||
|         width: 100%; | ||||
|       } | ||||
|  | ||||
|       .mainbox { | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')}; | ||||
|         font-family: ${cssGeistFontFamily}; | ||||
|         font-weight: 400; | ||||
|         font-size: 14px; | ||||
|         display: block; | ||||
|         width: 100%; | ||||
|         background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')}; | ||||
|         border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-radius: 8px; | ||||
|         overflow: hidden; | ||||
|         cursor: default; | ||||
|       } | ||||
|  | ||||
|       .header { | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|         align-items: center; | ||||
|         padding: 16px 24px; | ||||
|         min-height: 64px; | ||||
|         border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|  | ||||
|       .headingContainer { | ||||
|         flex: 1; | ||||
|       } | ||||
|  | ||||
|       .heading { | ||||
|         line-height: 1.5; | ||||
|       } | ||||
|  | ||||
|       .heading1 { | ||||
|         font-size: 18px; | ||||
|         font-weight: 600; | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')}; | ||||
|         letter-spacing: -0.025em; | ||||
|       } | ||||
|        | ||||
|       .heading2 { | ||||
|         font-size: 14px; | ||||
|         color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')}; | ||||
|         margin-top: 2px; | ||||
|       } | ||||
|  | ||||
|       .headingSeparation { | ||||
|         display: none; | ||||
|       } | ||||
|  | ||||
|       .headerActions { | ||||
|         user-select: none; | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         gap: 8px; | ||||
|       } | ||||
|        | ||||
|       .headerAction { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         gap: 6px; | ||||
|         padding: 6px 12px; | ||||
|         font-size: 14px; | ||||
|         font-weight: 500; | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')}; | ||||
|         background: transparent; | ||||
|         border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-radius: 6px; | ||||
|         cursor: pointer; | ||||
|         transition: all 0.15s ease; | ||||
|       } | ||||
|  | ||||
|       .headerAction:hover { | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')}; | ||||
|         background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')}; | ||||
|       } | ||||
|  | ||||
|       .headerAction dees-icon { | ||||
|         width: 14px; | ||||
|         height: 14px; | ||||
|       } | ||||
|  | ||||
|       .searchGrid { | ||||
|         display: grid; | ||||
|         grid-gap: 16px; | ||||
|         grid-template-columns: 1fr 200px; | ||||
|         padding: 16px 24px; | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 98%)', 'hsl(0 0% 3.9%)')}; | ||||
|         border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         transition: all 0.2s ease; | ||||
|       } | ||||
|  | ||||
|       .searchGrid.hidden { | ||||
|         height: 0px; | ||||
|         opacity: 0; | ||||
|         overflow: hidden; | ||||
|         padding: 0px 24px; | ||||
|         border-bottom-width: 0px; | ||||
|       } | ||||
|  | ||||
|       table { | ||||
|         width: 100%; | ||||
|         caption-side: bottom; | ||||
|         font-size: 14px; | ||||
|         border-collapse: separate; | ||||
|         border-spacing: 0; | ||||
|       } | ||||
|        | ||||
|       .noDataSet { | ||||
|         padding: 48px 24px; | ||||
|         text-align: center; | ||||
|         color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')}; | ||||
|       } | ||||
|        | ||||
|       thead { | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 9%)')}; | ||||
|         border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')}; | ||||
|       } | ||||
|        | ||||
|       tbody tr { | ||||
|         transition: background-color 0.15s ease; | ||||
|         position: relative; | ||||
|       } | ||||
|        | ||||
|       /* Default horizontal lines (bottom border only) */ | ||||
|       tbody tr { | ||||
|         border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|        | ||||
|       tbody tr:last-child { | ||||
|         border-bottom: none; | ||||
|       } | ||||
|        | ||||
|       /* Full horizontal lines when enabled */ | ||||
|       :host([show-horizontal-lines]) tbody tr { | ||||
|         border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|        | ||||
|       :host([show-horizontal-lines]) tbody tr:first-child { | ||||
|         border-top: none; | ||||
|       } | ||||
|        | ||||
|       :host([show-horizontal-lines]) tbody tr:last-child { | ||||
|         border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|        | ||||
|       tbody tr:hover { | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1% / 0.5)', 'hsl(0 0% 14.9% / 0.5)')}; | ||||
|       } | ||||
|        | ||||
|       /* Column hover effect for better traceability */ | ||||
|       td { | ||||
|         position: relative; | ||||
|       } | ||||
|        | ||||
|       td::after { | ||||
|         content: ''; | ||||
|         position: absolute; | ||||
|         top: -1000px; | ||||
|         bottom: -1000px; | ||||
|         left: 0; | ||||
|         right: 0; | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1% / 0.3)', 'hsl(0 0% 14.9% / 0.3)')}; | ||||
|         opacity: 0; | ||||
|         pointer-events: none; | ||||
|         transition: opacity 0.15s ease; | ||||
|         z-index: -1; | ||||
|       } | ||||
|        | ||||
|       td:hover::after { | ||||
|         opacity: 1; | ||||
|       } | ||||
|        | ||||
|       /* Grid mode - shows both vertical and horizontal lines */ | ||||
|       :host([show-grid]) th { | ||||
|         border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-left: none; | ||||
|         border-top: none; | ||||
|       } | ||||
|        | ||||
|       :host([show-grid]) td { | ||||
|         border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-left: none; | ||||
|         border-top: none; | ||||
|       } | ||||
|        | ||||
|       :host([show-grid]) th:first-child, | ||||
|       :host([show-grid]) td:first-child { | ||||
|         border-left: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|        | ||||
|       :host([show-grid]) tbody tr:first-child td { | ||||
|         border-top: none; | ||||
|       } | ||||
|        | ||||
|       tbody tr.selected { | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|  | ||||
|       tbody tr.hasAttachment { | ||||
|         background: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3% / 0.1)', 'hsl(142.1 76.2% 36.3% / 0.1)')}; | ||||
|       } | ||||
|  | ||||
|       th { | ||||
|         height: 48px; | ||||
|         padding: 12px 24px; | ||||
|         text-align: left; | ||||
|         font-weight: 500; | ||||
|         color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; | ||||
|         letter-spacing: -0.01em; | ||||
|       } | ||||
|        | ||||
|       :host([show-vertical-lines]) th { | ||||
|         border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|        | ||||
|       td { | ||||
|         padding: 12px 24px; | ||||
|         vertical-align: middle; | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')}; | ||||
|       } | ||||
|        | ||||
|       :host([show-vertical-lines]) td { | ||||
|         border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|        | ||||
|       th:first-child, | ||||
|       td:first-child { | ||||
|         padding-left: 24px; | ||||
|       } | ||||
|        | ||||
|       th:last-child, | ||||
|       td:last-child { | ||||
|         padding-right: 24px; | ||||
|       } | ||||
|        | ||||
|       :host([show-vertical-lines]) th:last-child, | ||||
|       :host([show-vertical-lines]) td:last-child { | ||||
|         border-right: none; | ||||
|       } | ||||
|        | ||||
|       .innerCellContainer { | ||||
|         position: relative; | ||||
|         min-height: 24px; | ||||
|         line-height: 24px; | ||||
|       } | ||||
|       td input { | ||||
|         position: absolute; | ||||
|         top: 4px; | ||||
|         bottom: 4px; | ||||
|         left: 20px; | ||||
|         right: 20px; | ||||
|         width: calc(100% - 40px); | ||||
|         height: calc(100% - 8px); | ||||
|         padding: 0 12px; | ||||
|         outline: none; | ||||
|         border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-radius: 6px; | ||||
|         background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')}; | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')}; | ||||
|         font-family: inherit; | ||||
|         font-size: inherit; | ||||
|         font-weight: inherit; | ||||
|         transition: all 0.15s ease; | ||||
|         box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); | ||||
|       } | ||||
|        | ||||
|       td input:focus { | ||||
|         border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')}; | ||||
|         outline: 2px solid transparent; | ||||
|         outline-offset: 2px; | ||||
|         box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.2)', 'hsl(217.2 91.2% 59.8% / 0.2)')}; | ||||
|       } | ||||
|       .actionsContainer { | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         gap: 4px; | ||||
|       } | ||||
|        | ||||
|       .action { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|         width: 32px; | ||||
|         height: 32px; | ||||
|         border-radius: 6px; | ||||
|         color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; | ||||
|         cursor: pointer; | ||||
|         transition: all 0.15s ease; | ||||
|       } | ||||
|  | ||||
|       .action:hover { | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 14.9%)')}; | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')}; | ||||
|       } | ||||
|  | ||||
|       .action:active { | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 11.8%)')}; | ||||
|       } | ||||
|        | ||||
|       .action dees-icon { | ||||
|         width: 16px; | ||||
|         height: 16px; | ||||
|       } | ||||
|  | ||||
|       .footer { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: space-between; | ||||
|         height: 52px; | ||||
|         padding: 0 24px; | ||||
|         font-size: 14px; | ||||
|         color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 9%)')}; | ||||
|         border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|  | ||||
|       .tableStatistics { | ||||
|         font-weight: 500; | ||||
|       } | ||||
|  | ||||
|       .footerActions { | ||||
|         display: flex; | ||||
|         gap: 8px; | ||||
|       } | ||||
|  | ||||
|       .footerActions .footerAction { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         gap: 6px; | ||||
|         padding: 6px 12px; | ||||
|         font-weight: 500; | ||||
|         color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; | ||||
|         border-radius: 6px; | ||||
|         cursor: pointer; | ||||
|         user-select: none; | ||||
|         transition: all 0.15s ease; | ||||
|       } | ||||
|  | ||||
|       .footerActions .footerAction:hover { | ||||
|         background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')}; | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')}; | ||||
|       } | ||||
|  | ||||
|       .footerActions .footerAction dees-icon { | ||||
|         width: 14px; | ||||
|         height: 14px; | ||||
|       } | ||||
|     `, | ||||
|   ]; | ||||
|   public static styles = tableStyles; | ||||
|  | ||||
|   public render(): TemplateResult { | ||||
|     const usingColumns = Array.isArray(this.columns) && this.columns.length > 0; | ||||
|     const effectiveColumns: Column<T>[] = usingColumns | ||||
|       ? this.computeEffectiveColumns() | ||||
|       : this.computeColumnsFromDisplayFunction(); | ||||
|       ? computeEffectiveColumnsFn(this.columns, this.augmentFromDisplayFunction, this.displayFunction, this.data) | ||||
|       : computeColumnsFromDisplayFunctionFn(this.displayFunction, this.data); | ||||
|  | ||||
|     return html` | ||||
|       <div class="mainbox"> | ||||
| @@ -657,6 +257,20 @@ export class DeesTable<T> extends DeesElement { | ||||
|               <table> | ||||
|                 <thead> | ||||
|                   <tr> | ||||
|                     ${this.selectionMode !== 'none' | ||||
|                       ? html` | ||||
|                           <th style="width:42px; text-align:center;"> | ||||
|                             ${this.selectionMode === 'multi' | ||||
|                               ? html`<input type="checkbox" | ||||
|                                   .checked=${this.areAllSelected()} | ||||
|                                   @click=${(e: Event) => { | ||||
|                                     e.stopPropagation(); | ||||
|                                     this.toggleSelectAll(); | ||||
|                                   }} />` | ||||
|                               : html``} | ||||
|                           </th> | ||||
|                         ` | ||||
|                       : html``} | ||||
|                     ${effectiveColumns | ||||
|                       .filter((c) => !c.hidden) | ||||
|                       .map((col) => { | ||||
| @@ -681,7 +295,7 @@ export class DeesTable<T> extends DeesElement { | ||||
|                   </tr> | ||||
|                 </thead> | ||||
|                 <tbody> | ||||
|                   ${this.getViewData(effectiveColumns).map((itemArg, rowIndex) => { | ||||
|                   ${getViewDataFn(this.data, effectiveColumns, this.sortKey, this.sortDir, this.filterText).map((itemArg, rowIndex) => { | ||||
|                     const getTr = (elementArg: HTMLElement): HTMLElement => { | ||||
|                       if (elementArg.tagName === 'TR') { | ||||
|                         return elementArg; | ||||
| @@ -693,6 +307,13 @@ export class DeesTable<T> extends DeesElement { | ||||
|                       <tr | ||||
|                         @click=${() => { | ||||
|                           this.selectedDataRow = itemArg; | ||||
|                           if (this.selectionMode === 'single') { | ||||
|                             const id = this.getRowId(itemArg); | ||||
|                             this.selectedIds.clear(); | ||||
|                             this.selectedIds.add(id); | ||||
|                             this.emitSelectionChange(); | ||||
|                             this.requestUpdate(); | ||||
|                           } | ||||
|                         }} | ||||
|                         @dragenter=${async (eventArg: DragEvent) => { | ||||
|                           eventArg.preventDefault(); | ||||
| @@ -747,10 +368,22 @@ export class DeesTable<T> extends DeesElement { | ||||
|                         }} | ||||
|                         class="${itemArg === this.selectedDataRow ? 'selected' : ''}" | ||||
|                       > | ||||
|                         ${this.selectionMode !== 'none' | ||||
|                           ? html`<td style="width:42px; text-align:center;"> | ||||
|                               <input | ||||
|                                 type="checkbox" | ||||
|                                 .checked=${this.isRowSelected(itemArg)} | ||||
|                                 @click=${(e: Event) => { | ||||
|                                   e.stopPropagation(); | ||||
|                                   this.toggleRowSelected(itemArg); | ||||
|                                 }} | ||||
|                               /> | ||||
|                             </td>` | ||||
|                           : html``} | ||||
|                         ${effectiveColumns | ||||
|                           .filter((c) => !c.hidden) | ||||
|                           .map((col, colIndex) => { | ||||
|                             const value = this.getCellValue(itemArg, col); | ||||
|                             const value = getCellValueFn(itemArg, col, this.displayFunction); | ||||
|                             const content = col.renderer | ||||
|                               ? col.renderer(value, itemArg, { rowIndex, colIndex, column: col }) | ||||
|                               : value; | ||||
| @@ -910,55 +543,7 @@ export class DeesTable<T> extends DeesElement { | ||||
|     table.style.tableLayout = 'fixed'; | ||||
|   } | ||||
|  | ||||
|   private computeColumnsFromDisplayFunction(): Column<T>[] { | ||||
|     if (!this.data || this.data.length === 0) return []; | ||||
|     const firstTransformedItem = this.displayFunction(this.data[0]); | ||||
|     const keys: string[] = Object.keys(firstTransformedItem); | ||||
|     return keys.map((key) => ({ | ||||
|       key, | ||||
|       header: key, | ||||
|       value: (row: T) => this.displayFunction(row)[key], | ||||
|     })); | ||||
|   } | ||||
|  | ||||
|   private computeEffectiveColumns(): Column<T>[] { | ||||
|     const base = (this.columns || []).slice(); | ||||
|     if (!this.augmentFromDisplayFunction) return base; | ||||
|     const fromDisplay = this.computeColumnsFromDisplayFunction(); | ||||
|     const existingKeys = new Set(base.map((c) => String(c.key))); | ||||
|     for (const col of fromDisplay) { | ||||
|       if (!existingKeys.has(String(col.key))) { | ||||
|         base.push(col); | ||||
|       } | ||||
|     } | ||||
|     return base; | ||||
|   } | ||||
|  | ||||
|   private getCellValue(row: T, col: Column<T>): any { | ||||
|     return col.value ? col.value(row) : (row as any)[col.key as any]; | ||||
|   } | ||||
|  | ||||
|   private getViewData(effectiveColumns: Column<T>[]): T[] { | ||||
|     if (!this.sortKey || !this.sortDir) return this.data; | ||||
|     const col = effectiveColumns.find((c) => String(c.key) === this.sortKey); | ||||
|     if (!col) return this.data; | ||||
|     const arr = this.data.slice(); | ||||
|     const dir = this.sortDir === 'asc' ? 1 : -1; | ||||
|     arr.sort((a, b) => { | ||||
|       const va = this.getCellValue(a, col); | ||||
|       const vb = this.getCellValue(b, col); | ||||
|       if (va == null && vb == null) return 0; | ||||
|       if (va == null) return -1 * dir; | ||||
|       if (vb == null) return 1 * dir; | ||||
|       if (typeof va === 'number' && typeof vb === 'number') return (va - vb) * dir; | ||||
|       const sa = String(va).toLowerCase(); | ||||
|       const sb = String(vb).toLowerCase(); | ||||
|       if (sa < sb) return -1 * dir; | ||||
|       if (sa > sb) return 1 * dir; | ||||
|       return 0; | ||||
|     }); | ||||
|     return arr; | ||||
|   } | ||||
|   // compute helpers moved to ./data.ts | ||||
|  | ||||
|   private toggleSort(col: Column<T>) { | ||||
|     const key = String(col.key); | ||||
| @@ -991,6 +576,76 @@ export class DeesTable<T> extends DeesElement { | ||||
|     return html`<span style="margin-left:6px; opacity:0.7;">${this.sortDir === 'asc' ? '▲' : '▼'}</span>`; | ||||
|   } | ||||
|  | ||||
|   // filtering helpers | ||||
|   public setFilterText(value: string) { | ||||
|     const prev = this.filterText; | ||||
|     this.filterText = value ?? ''; | ||||
|     if (prev !== this.filterText) { | ||||
|       this.dispatchEvent( | ||||
|         new CustomEvent('filterChange', { | ||||
|           detail: { text: this.filterText }, | ||||
|           bubbles: true, | ||||
|         }) | ||||
|       ); | ||||
|       this.requestUpdate(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // 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(); | ||||
|   } | ||||
|  | ||||
|   private areAllSelected(): boolean { | ||||
|     return this.data.length > 0 && this.selectedIds.size === this.data.length; | ||||
|   } | ||||
|  | ||||
|   private toggleSelectAll() { | ||||
|     if (this.areAllSelected()) { | ||||
|       this.selectedIds.clear(); | ||||
|     } else { | ||||
|       this.selectedIds = new Set(this.data.map((r) => this.getRowId(r))); | ||||
|     } | ||||
|     this.emitSelectionChange(); | ||||
|     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, | ||||
|       }) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   getActionsForType(typeArg: ITableAction['type'][0]) { | ||||
|     const actions: ITableAction[] = []; | ||||
|     for (const action of this.dataActions) { | ||||
|   | ||||
							
								
								
									
										362
									
								
								ts_web/elements/dees-table/styles.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										362
									
								
								ts_web/elements/dees-table/styles.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,362 @@ | ||||
| import { cssManager, css, type CSSResult } from '@design.estate/dees-element'; | ||||
| import { cssGeistFontFamily } from '../00fonts.js'; | ||||
|  | ||||
| export const tableStyles: CSSResult[] = [ | ||||
|   cssManager.defaultStyles, | ||||
|   css` | ||||
|       :host { | ||||
|         display: block; | ||||
|         width: 100%; | ||||
|       } | ||||
|  | ||||
|       .mainbox { | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')}; | ||||
|         font-family: ${cssGeistFontFamily}; | ||||
|         font-weight: 400; | ||||
|         font-size: 14px; | ||||
|         display: block; | ||||
|         width: 100%; | ||||
|         background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')}; | ||||
|         border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-radius: 8px; | ||||
|         overflow: hidden; | ||||
|         cursor: default; | ||||
|       } | ||||
|  | ||||
|       .header { | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|         align-items: center; | ||||
|         padding: 16px 24px; | ||||
|         min-height: 64px; | ||||
|         border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|  | ||||
|       .headingContainer { | ||||
|         flex: 1; | ||||
|       } | ||||
|  | ||||
|       .heading { | ||||
|         line-height: 1.5; | ||||
|       } | ||||
|  | ||||
|       .heading1 { | ||||
|         font-size: 18px; | ||||
|         font-weight: 600; | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')}; | ||||
|         letter-spacing: -0.025em; | ||||
|       } | ||||
|        | ||||
|       .heading2 { | ||||
|         font-size: 14px; | ||||
|         color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')}; | ||||
|         margin-top: 2px; | ||||
|       } | ||||
|  | ||||
|       .headingSeparation { | ||||
|         display: none; | ||||
|       } | ||||
|  | ||||
|       .headerActions { | ||||
|         user-select: none; | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         gap: 8px; | ||||
|       } | ||||
|        | ||||
|       .headerAction { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         gap: 6px; | ||||
|         padding: 6px 12px; | ||||
|         font-size: 14px; | ||||
|         font-weight: 500; | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')}; | ||||
|         background: transparent; | ||||
|         border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-radius: 6px; | ||||
|         cursor: pointer; | ||||
|         transition: all 0.15s ease; | ||||
|       } | ||||
|  | ||||
|       .headerAction:hover { | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')}; | ||||
|         background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')}; | ||||
|       } | ||||
|  | ||||
|       .headerAction dees-icon { | ||||
|         width: 14px; | ||||
|         height: 14px; | ||||
|       } | ||||
|  | ||||
|       .searchGrid { | ||||
|         display: grid; | ||||
|         grid-gap: 16px; | ||||
|         grid-template-columns: 1fr 200px; | ||||
|         padding: 16px 24px; | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 98%)', 'hsl(0 0% 3.9%)')}; | ||||
|         border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         transition: all 0.2s ease; | ||||
|       } | ||||
|  | ||||
|       .searchGrid.hidden { | ||||
|         height: 0px; | ||||
|         opacity: 0; | ||||
|         overflow: hidden; | ||||
|         padding: 0px 24px; | ||||
|         border-bottom-width: 0px; | ||||
|       } | ||||
|  | ||||
|       table { | ||||
|         width: 100%; | ||||
|         caption-side: bottom; | ||||
|         font-size: 14px; | ||||
|         border-collapse: separate; | ||||
|         border-spacing: 0; | ||||
|       } | ||||
|        | ||||
|       .noDataSet { | ||||
|         padding: 48px 24px; | ||||
|         text-align: center; | ||||
|         color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')}; | ||||
|       } | ||||
|        | ||||
|       thead { | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 9%)')}; | ||||
|         border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')}; | ||||
|       } | ||||
|        | ||||
|       tbody tr { | ||||
|         transition: background-color 0.15s ease; | ||||
|         position: relative; | ||||
|       } | ||||
|        | ||||
|       /* Default horizontal lines (bottom border only) */ | ||||
|       tbody tr { | ||||
|         border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|        | ||||
|       tbody tr:last-child { | ||||
|         border-bottom: none; | ||||
|       } | ||||
|        | ||||
|       /* Full horizontal lines when enabled */ | ||||
|       :host([show-horizontal-lines]) tbody tr { | ||||
|         border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|        | ||||
|       :host([show-horizontal-lines]) tbody tr:first-child { | ||||
|         border-top: none; | ||||
|       } | ||||
|        | ||||
|       :host([show-horizontal-lines]) tbody tr:last-child { | ||||
|         border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|        | ||||
|       tbody tr:hover { | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1% / 0.5)', 'hsl(0 0% 14.9% / 0.5)')}; | ||||
|       } | ||||
|        | ||||
|       /* Column hover effect for better traceability */ | ||||
|       td { | ||||
|         position: relative; | ||||
|       } | ||||
|        | ||||
|       td::after { | ||||
|         content: ''; | ||||
|         position: absolute; | ||||
|         top: -1000px; | ||||
|         bottom: -1000px; | ||||
|         left: 0; | ||||
|         right: 0; | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1% / 0.3)', 'hsl(0 0% 14.9% / 0.3)')}; | ||||
|         opacity: 0; | ||||
|         pointer-events: none; | ||||
|         transition: opacity 0.15s ease; | ||||
|         z-index: -1; | ||||
|       } | ||||
|        | ||||
|       td:hover::after { | ||||
|         opacity: 1; | ||||
|       } | ||||
|        | ||||
|       /* Grid mode - shows both vertical and horizontal lines */ | ||||
|       :host([show-grid]) th { | ||||
|         border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-left: none; | ||||
|         border-top: none; | ||||
|       } | ||||
|        | ||||
|       :host([show-grid]) td { | ||||
|         border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-left: none; | ||||
|         border-top: none; | ||||
|       } | ||||
|        | ||||
|       :host([show-grid]) th:first-child, | ||||
|       :host([show-grid]) td:first-child { | ||||
|         border-left: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|        | ||||
|       :host([show-grid]) tbody tr:first-child td { | ||||
|         border-top: none; | ||||
|       } | ||||
|        | ||||
|       tbody tr.selected { | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|  | ||||
|       tbody tr.hasAttachment { | ||||
|         background: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3% / 0.1)', 'hsl(142.1 76.2% 36.3% / 0.1)')}; | ||||
|       } | ||||
|  | ||||
|       th { | ||||
|         height: 48px; | ||||
|         padding: 12px 24px; | ||||
|         text-align: left; | ||||
|         font-weight: 500; | ||||
|         color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; | ||||
|         letter-spacing: -0.01em; | ||||
|       } | ||||
|        | ||||
|       :host([show-vertical-lines]) th { | ||||
|         border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|        | ||||
|       td { | ||||
|         padding: 12px 24px; | ||||
|         vertical-align: middle; | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')}; | ||||
|       } | ||||
|        | ||||
|       :host([show-vertical-lines]) td { | ||||
|         border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|        | ||||
|       th:first-child, | ||||
|       td:first-child { | ||||
|         padding-left: 24px; | ||||
|       } | ||||
|        | ||||
|       th:last-child, | ||||
|       td:last-child { | ||||
|         padding-right: 24px; | ||||
|       } | ||||
|        | ||||
|       :host([show-vertical-lines]) th:last-child, | ||||
|       :host([show-vertical-lines]) td:last-child { | ||||
|         border-right: none; | ||||
|       } | ||||
|        | ||||
|       .innerCellContainer { | ||||
|         position: relative; | ||||
|         min-height: 24px; | ||||
|         line-height: 24px; | ||||
|       } | ||||
|       td input { | ||||
|         position: absolute; | ||||
|         top: 4px; | ||||
|         bottom: 4px; | ||||
|         left: 20px; | ||||
|         right: 20px; | ||||
|         width: calc(100% - 40px); | ||||
|         height: calc(100% - 8px); | ||||
|         padding: 0 12px; | ||||
|         outline: none; | ||||
|         border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|         border-radius: 6px; | ||||
|         background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')}; | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')}; | ||||
|         font-family: inherit; | ||||
|         font-size: inherit; | ||||
|         font-weight: inherit; | ||||
|         transition: all 0.15s ease; | ||||
|         box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); | ||||
|       } | ||||
|        | ||||
|       td input:focus { | ||||
|         border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')}; | ||||
|         outline: 2px solid transparent; | ||||
|         outline-offset: 2px; | ||||
|         box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.2)', 'hsl(217.2 91.2% 59.8% / 0.2)')}; | ||||
|       } | ||||
|       .actionsContainer { | ||||
|         display: flex; | ||||
|         flex-direction: row; | ||||
|         gap: 4px; | ||||
|       } | ||||
|        | ||||
|       .action { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: center; | ||||
|         width: 32px; | ||||
|         height: 32px; | ||||
|         border-radius: 6px; | ||||
|         color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; | ||||
|         cursor: pointer; | ||||
|         transition: all 0.15s ease; | ||||
|       } | ||||
|  | ||||
|       .action:hover { | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 14.9%)')}; | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')}; | ||||
|       } | ||||
|  | ||||
|       .action:active { | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 11.8%)')}; | ||||
|       } | ||||
|        | ||||
|       .action dees-icon { | ||||
|         width: 16px; | ||||
|         height: 16px; | ||||
|       } | ||||
|  | ||||
|       .footer { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         justify-content: space-between; | ||||
|         height: 52px; | ||||
|         padding: 0 24px; | ||||
|         font-size: 14px; | ||||
|         color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; | ||||
|         background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 9%)')}; | ||||
|         border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; | ||||
|       } | ||||
|  | ||||
|       .tableStatistics { | ||||
|         font-weight: 500; | ||||
|       } | ||||
|  | ||||
|       .footerActions { | ||||
|         display: flex; | ||||
|         gap: 8px; | ||||
|       } | ||||
|  | ||||
|       .footerActions .footerAction { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         gap: 6px; | ||||
|         padding: 6px 12px; | ||||
|         font-weight: 500; | ||||
|         color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')}; | ||||
|         border-radius: 6px; | ||||
|         cursor: pointer; | ||||
|         user-select: none; | ||||
|         transition: all 0.15s ease; | ||||
|       } | ||||
|  | ||||
|       .footerActions .footerAction:hover { | ||||
|         background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')}; | ||||
|         color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')}; | ||||
|       } | ||||
|  | ||||
|       .footerActions .footerAction dees-icon { | ||||
|         width: 14px; | ||||
|         height: 14px; | ||||
|       } | ||||
|   `, | ||||
| ]; | ||||
|  | ||||
							
								
								
									
										28
									
								
								ts_web/elements/dees-table/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								ts_web/elements/dees-table/types.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| import type { TemplateResult } from '@design.estate/dees-element'; | ||||
| import type { TIconKey } from '../dees-icon.js'; | ||||
|  | ||||
| export interface ITableActionDataArg<T> { | ||||
|   item: T; | ||||
|   table: any; // avoid circular typing with DeesTable; consumers rely on shape only | ||||
| } | ||||
|  | ||||
| export interface ITableAction<T = any> { | ||||
|   name: string; | ||||
|   iconName: TIconKey; | ||||
|   useTableBehaviour?: 'upload' | 'cancelUpload' | 'none'; | ||||
|   type: ('inRow' | 'contextmenu' | 'doubleClick' | 'footer' | 'header' | 'preview' | 'keyCombination')[]; | ||||
|   actionRelevancyCheckFunc?: (itemArg: T) => boolean; | ||||
|   actionFunc: (actionDataArg: ITableActionDataArg<T>) => Promise<any>; | ||||
| } | ||||
|  | ||||
| export interface Column<T = any> { | ||||
|   key: keyof T | string; | ||||
|   header?: string | TemplateResult; | ||||
|   value?: (row: T) => any; | ||||
|   renderer?: (value: any, row: T, ctx: { rowIndex: number; colIndex: number; column: Column<T> }) => TemplateResult | string; | ||||
|   sortable?: boolean; | ||||
|   hidden?: boolean; | ||||
| } | ||||
|  | ||||
| export type TDisplayFunction<T = any> = (itemArg: T) => Record<string, any>; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user