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"
|
dataName="users"
|
||||||
></dees-table>
|
></dees-table>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@@ -1,21 +1,21 @@
|
|||||||
import * as plugins from '../00plugins.js';
|
import * as plugins from '../00plugins.js';
|
||||||
import { demoFunc } from './dees-table.demo.js';
|
import { demoFunc } from './dees-table.demo.js';
|
||||||
import { cssGeistFontFamily } from '../00fonts.js';
|
import { customElement, html, DeesElement, property, type TemplateResult, directives } from '@design.estate/dees-element';
|
||||||
import {
|
|
||||||
customElement,
|
|
||||||
html,
|
|
||||||
DeesElement,
|
|
||||||
property,
|
|
||||||
type TemplateResult,
|
|
||||||
cssManager,
|
|
||||||
css,
|
|
||||||
directives,
|
|
||||||
} from '@design.estate/dees-element';
|
|
||||||
|
|
||||||
import { DeesContextmenu } from '../dees-contextmenu.js';
|
import { DeesContextmenu } from '../dees-contextmenu.js';
|
||||||
|
import * as plugins from '../00plugins.js';
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import { type TIconKey } from '../dees-icon.js';
|
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 {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -23,62 +23,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// interfaces
|
// interfaces moved to ./types.ts and re-exported above
|
||||||
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;
|
|
||||||
|
|
||||||
// the table implementation
|
// the table implementation
|
||||||
@customElement('dees-table')
|
@customElement('dees-table')
|
||||||
@@ -219,374 +164,29 @@ export class DeesTable<T> extends DeesElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
private sortDir: 'asc' | 'desc' | null = null;
|
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() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = tableStyles;
|
||||||
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 render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
const usingColumns = Array.isArray(this.columns) && this.columns.length > 0;
|
const usingColumns = Array.isArray(this.columns) && this.columns.length > 0;
|
||||||
const effectiveColumns: Column<T>[] = usingColumns
|
const effectiveColumns: Column<T>[] = usingColumns
|
||||||
? this.computeEffectiveColumns()
|
? computeEffectiveColumnsFn(this.columns, this.augmentFromDisplayFunction, this.displayFunction, this.data)
|
||||||
: this.computeColumnsFromDisplayFunction();
|
: computeColumnsFromDisplayFunctionFn(this.displayFunction, this.data);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="mainbox">
|
<div class="mainbox">
|
||||||
@@ -657,6 +257,20 @@ export class DeesTable<T> extends DeesElement {
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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
|
${effectiveColumns
|
||||||
.filter((c) => !c.hidden)
|
.filter((c) => !c.hidden)
|
||||||
.map((col) => {
|
.map((col) => {
|
||||||
@@ -681,7 +295,7 @@ export class DeesTable<T> extends DeesElement {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<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 => {
|
const getTr = (elementArg: HTMLElement): HTMLElement => {
|
||||||
if (elementArg.tagName === 'TR') {
|
if (elementArg.tagName === 'TR') {
|
||||||
return elementArg;
|
return elementArg;
|
||||||
@@ -693,6 +307,13 @@ export class DeesTable<T> extends DeesElement {
|
|||||||
<tr
|
<tr
|
||||||
@click=${() => {
|
@click=${() => {
|
||||||
this.selectedDataRow = itemArg;
|
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) => {
|
@dragenter=${async (eventArg: DragEvent) => {
|
||||||
eventArg.preventDefault();
|
eventArg.preventDefault();
|
||||||
@@ -747,10 +368,22 @@ export class DeesTable<T> extends DeesElement {
|
|||||||
}}
|
}}
|
||||||
class="${itemArg === this.selectedDataRow ? 'selected' : ''}"
|
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
|
${effectiveColumns
|
||||||
.filter((c) => !c.hidden)
|
.filter((c) => !c.hidden)
|
||||||
.map((col, colIndex) => {
|
.map((col, colIndex) => {
|
||||||
const value = this.getCellValue(itemArg, col);
|
const value = getCellValueFn(itemArg, col, this.displayFunction);
|
||||||
const content = col.renderer
|
const content = col.renderer
|
||||||
? col.renderer(value, itemArg, { rowIndex, colIndex, column: col })
|
? col.renderer(value, itemArg, { rowIndex, colIndex, column: col })
|
||||||
: value;
|
: value;
|
||||||
@@ -910,55 +543,7 @@ export class DeesTable<T> extends DeesElement {
|
|||||||
table.style.tableLayout = 'fixed';
|
table.style.tableLayout = 'fixed';
|
||||||
}
|
}
|
||||||
|
|
||||||
private computeColumnsFromDisplayFunction(): Column<T>[] {
|
// compute helpers moved to ./data.ts
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private toggleSort(col: Column<T>) {
|
private toggleSort(col: Column<T>) {
|
||||||
const key = String(col.key);
|
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>`;
|
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]) {
|
getActionsForType(typeArg: ITableAction['type'][0]) {
|
||||||
const actions: ITableAction[] = [];
|
const actions: ITableAction[] = [];
|
||||||
for (const action of this.dataActions) {
|
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