juxscript 1.1.239 → 1.1.243
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +7 -137
- package/lib/components/dataframe.ts +0 -569
- package/lib/components/tag.ts +68 -0
- package/lib/styles/shadcn.css +20 -7
- package/lib/utils/idgen.ts +6 -0
- package/package.json +5 -6
- package/index.d.ts +0 -239
- package/index.d.ts.map +0 -1
- package/lib/components/alert.d.ts +0 -36
- package/lib/components/alert.d.ts.map +0 -1
- package/lib/components/alert.js +0 -172
- package/lib/components/alert.ts +0 -219
- package/lib/components/app.d.ts +0 -89
- package/lib/components/app.d.ts.map +0 -1
- package/lib/components/app.js +0 -175
- package/lib/components/app.ts +0 -247
- package/lib/components/badge.d.ts +0 -26
- package/lib/components/badge.d.ts.map +0 -1
- package/lib/components/badge.js +0 -91
- package/lib/components/badge.ts +0 -118
- package/lib/components/base/Animations.d.ts +0 -36
- package/lib/components/base/Animations.d.ts.map +0 -1
- package/lib/components/base/Animations.js +0 -70
- package/lib/components/base/Animations.ts +0 -112
- package/lib/components/base/BaseComponent.d.ts +0 -294
- package/lib/components/base/BaseComponent.d.ts.map +0 -1
- package/lib/components/base/BaseComponent.js +0 -735
- package/lib/components/base/BaseComponent.ts +0 -884
- package/lib/components/base/FormInput.d.ts +0 -77
- package/lib/components/base/FormInput.d.ts.map +0 -1
- package/lib/components/base/FormInput.js +0 -171
- package/lib/components/base/FormInput.ts +0 -237
- package/lib/components/blueprint.d.ts +0 -40
- package/lib/components/blueprint.d.ts.map +0 -1
- package/lib/components/blueprint.js +0 -327
- package/lib/components/button.d.ts +0 -70
- package/lib/components/button.d.ts.map +0 -1
- package/lib/components/button.js +0 -177
- package/lib/components/button.ts +0 -237
- package/lib/components/card.d.ts +0 -35
- package/lib/components/card.d.ts.map +0 -1
- package/lib/components/card.js +0 -130
- package/lib/components/card.ts +0 -177
- package/lib/components/chart.d.ts +0 -49
- package/lib/components/chart.d.ts.map +0 -1
- package/lib/components/chart.js +0 -205
- package/lib/components/chart.ts +0 -254
- package/lib/components/checkbox.d.ts +0 -33
- package/lib/components/checkbox.d.ts.map +0 -1
- package/lib/components/checkbox.js +0 -202
- package/lib/components/checkbox.ts +0 -260
- package/lib/components/code.d.ts +0 -52
- package/lib/components/code.d.ts.map +0 -1
- package/lib/components/code.js +0 -201
- package/lib/components/code.ts +0 -260
- package/lib/components/container.d.ts +0 -60
- package/lib/components/container.d.ts.map +0 -1
- package/lib/components/container.js +0 -195
- package/lib/components/container.ts +0 -259
- package/lib/components/data.d.ts +0 -36
- package/lib/components/data.d.ts.map +0 -1
- package/lib/components/data.js +0 -110
- package/lib/components/data.ts +0 -135
- package/lib/components/dataframe/DataFrameSource.d.ts +0 -118
- package/lib/components/dataframe/DataFrameSource.d.ts.map +0 -1
- package/lib/components/dataframe/DataFrameSource.js +0 -421
- package/lib/components/dataframe/DataFrameSource.ts +0 -532
- package/lib/components/dataframe/ImportSettingsModal.d.ts +0 -60
- package/lib/components/dataframe/ImportSettingsModal.d.ts.map +0 -1
- package/lib/components/dataframe/ImportSettingsModal.js +0 -442
- package/lib/components/dataframe/ImportSettingsModal.ts +0 -531
- package/lib/components/dataframe.d.ts +0 -110
- package/lib/components/dataframe.d.ts.map +0 -1
- package/lib/components/dataframe.js +0 -470
- package/lib/components/datepicker.d.ts +0 -40
- package/lib/components/datepicker.d.ts.map +0 -1
- package/lib/components/datepicker.js +0 -193
- package/lib/components/datepicker.ts +0 -251
- package/lib/components/dialog.d.ts +0 -39
- package/lib/components/dialog.d.ts.map +0 -1
- package/lib/components/dialog.js +0 -131
- package/lib/components/dialog.ts +0 -178
- package/lib/components/divider.d.ts +0 -31
- package/lib/components/divider.d.ts.map +0 -1
- package/lib/components/divider.js +0 -72
- package/lib/components/divider.ts +0 -104
- package/lib/components/dropdown-menu.d.ts +0 -42
- package/lib/components/dropdown-menu.d.ts.map +0 -1
- package/lib/components/dropdown-menu.js +0 -177
- package/lib/components/dropdown-menu.ts +0 -214
- package/lib/components/dropdown.d.ts +0 -41
- package/lib/components/dropdown.d.ts.map +0 -1
- package/lib/components/dropdown.js +0 -136
- package/lib/components/dropdown.ts +0 -188
- package/lib/components/element.d.ts +0 -51
- package/lib/components/element.d.ts.map +0 -1
- package/lib/components/element.js +0 -209
- package/lib/components/element.ts +0 -271
- package/lib/components/event-chain.d.ts +0 -9
- package/lib/components/event-chain.d.ts.map +0 -1
- package/lib/components/event-chain.js +0 -33
- package/lib/components/fileupload.d.ts +0 -98
- package/lib/components/fileupload.d.ts.map +0 -1
- package/lib/components/fileupload.js +0 -351
- package/lib/components/fileupload.ts +0 -449
- package/lib/components/grid.d.ts +0 -88
- package/lib/components/grid.d.ts.map +0 -1
- package/lib/components/grid.js +0 -208
- package/lib/components/grid.ts +0 -295
- package/lib/components/heading.d.ts +0 -25
- package/lib/components/heading.d.ts.map +0 -1
- package/lib/components/heading.js +0 -83
- package/lib/components/heading.ts +0 -113
- package/lib/components/helpers.d.ts +0 -9
- package/lib/components/helpers.d.ts.map +0 -1
- package/lib/components/helpers.js +0 -30
- package/lib/components/helpers.ts +0 -41
- package/lib/components/hero.d.ts +0 -60
- package/lib/components/hero.d.ts.map +0 -1
- package/lib/components/hero.js +0 -239
- package/lib/components/hero.ts +0 -302
- package/lib/components/history/StateHistory.d.ts +0 -91
- package/lib/components/history/StateHistory.d.ts.map +0 -1
- package/lib/components/history/StateHistory.js +0 -154
- package/lib/components/history/StateHistory.ts +0 -200
- package/lib/components/icon.d.ts +0 -36
- package/lib/components/icon.d.ts.map +0 -1
- package/lib/components/icon.js +0 -135
- package/lib/components/icon.ts +0 -182
- package/lib/components/icons.d.ts +0 -25
- package/lib/components/icons.d.ts.map +0 -1
- package/lib/components/icons.js +0 -440
- package/lib/components/icons.ts +0 -464
- package/lib/components/image.d.ts +0 -42
- package/lib/components/image.d.ts.map +0 -1
- package/lib/components/image.js +0 -204
- package/lib/components/image.ts +0 -260
- package/lib/components/include.d.ts +0 -86
- package/lib/components/include.d.ts.map +0 -1
- package/lib/components/include.js +0 -238
- package/lib/components/include.ts +0 -281
- package/lib/components/input.d.ts +0 -85
- package/lib/components/input.d.ts.map +0 -1
- package/lib/components/input.js +0 -362
- package/lib/components/input.ts +0 -473
- package/lib/components/layer.d.ts +0 -72
- package/lib/components/layer.d.ts.map +0 -1
- package/lib/components/layer.js +0 -219
- package/lib/components/layer.ts +0 -304
- package/lib/components/link.d.ts +0 -41
- package/lib/components/link.d.ts.map +0 -1
- package/lib/components/link.js +0 -216
- package/lib/components/link.ts +0 -268
- package/lib/components/list.d.ts +0 -83
- package/lib/components/list.d.ts.map +0 -1
- package/lib/components/list.js +0 -314
- package/lib/components/list.ts +0 -423
- package/lib/components/loading.d.ts +0 -25
- package/lib/components/loading.d.ts.map +0 -1
- package/lib/components/loading.js +0 -76
- package/lib/components/loading.ts +0 -104
- package/lib/components/menu.d.ts +0 -38
- package/lib/components/menu.d.ts.map +0 -1
- package/lib/components/menu.js +0 -205
- package/lib/components/menu.ts +0 -279
- package/lib/components/modal.d.ts +0 -97
- package/lib/components/modal.d.ts.map +0 -1
- package/lib/components/modal.js +0 -463
- package/lib/components/modal.ts +0 -576
- package/lib/components/nav.d.ts +0 -46
- package/lib/components/nav.d.ts.map +0 -1
- package/lib/components/nav.js +0 -193
- package/lib/components/nav.ts +0 -261
- package/lib/components/paragraph.d.ts +0 -30
- package/lib/components/paragraph.d.ts.map +0 -1
- package/lib/components/paragraph.js +0 -93
- package/lib/components/paragraph.ts +0 -123
- package/lib/components/pen.d.ts +0 -125
- package/lib/components/pen.d.ts.map +0 -1
- package/lib/components/pen.js +0 -443
- package/lib/components/pen.ts +0 -567
- package/lib/components/progress.d.ts +0 -40
- package/lib/components/progress.d.ts.map +0 -1
- package/lib/components/progress.js +0 -116
- package/lib/components/progress.ts +0 -163
- package/lib/components/radio.d.ts +0 -43
- package/lib/components/radio.d.ts.map +0 -1
- package/lib/components/radio.js +0 -226
- package/lib/components/radio.ts +0 -303
- package/lib/components/registry.d.ts +0 -34
- package/lib/components/registry.d.ts.map +0 -1
- package/lib/components/registry.js +0 -163
- package/lib/components/registry.ts +0 -193
- package/lib/components/req.d.ts +0 -155
- package/lib/components/req.d.ts.map +0 -1
- package/lib/components/req.js +0 -253
- package/lib/components/req.ts +0 -303
- package/lib/components/script.d.ts +0 -14
- package/lib/components/script.d.ts.map +0 -1
- package/lib/components/script.js +0 -33
- package/lib/components/script.ts +0 -41
- package/lib/components/select.d.ts +0 -42
- package/lib/components/select.d.ts.map +0 -1
- package/lib/components/select.js +0 -209
- package/lib/components/select.ts +0 -281
- package/lib/components/sidebar.d.ts +0 -59
- package/lib/components/sidebar.d.ts.map +0 -1
- package/lib/components/sidebar.js +0 -298
- package/lib/components/sidebar.ts +0 -395
- package/lib/components/stack/BaseStack.d.ts +0 -65
- package/lib/components/stack/BaseStack.d.ts.map +0 -1
- package/lib/components/stack/BaseStack.js +0 -274
- package/lib/components/stack/BaseStack.ts +0 -328
- package/lib/components/stack/HStack.d.ts +0 -18
- package/lib/components/stack/HStack.d.ts.map +0 -1
- package/lib/components/stack/HStack.js +0 -22
- package/lib/components/stack/HStack.ts +0 -25
- package/lib/components/stack/VStack.d.ts +0 -19
- package/lib/components/stack/VStack.d.ts.map +0 -1
- package/lib/components/stack/VStack.js +0 -23
- package/lib/components/stack/VStack.ts +0 -26
- package/lib/components/stack/ZStack.d.ts +0 -18
- package/lib/components/stack/ZStack.d.ts.map +0 -1
- package/lib/components/stack/ZStack.js +0 -22
- package/lib/components/stack/ZStack.ts +0 -25
- package/lib/components/style.d.ts +0 -14
- package/lib/components/style.d.ts.map +0 -1
- package/lib/components/style.js +0 -33
- package/lib/components/style.ts +0 -41
- package/lib/components/switch.d.ts +0 -34
- package/lib/components/switch.d.ts.map +0 -1
- package/lib/components/switch.js +0 -209
- package/lib/components/switch.ts +0 -272
- package/lib/components/table.d.ts +0 -137
- package/lib/components/table.d.ts.map +0 -1
- package/lib/components/table.js +0 -1019
- package/lib/components/table.ts +0 -1225
- package/lib/components/tabs.d.ts +0 -53
- package/lib/components/tabs.d.ts.map +0 -1
- package/lib/components/tabs.js +0 -275
- package/lib/components/tabs.ts +0 -349
- package/lib/components/theme-toggle.d.ts +0 -45
- package/lib/components/theme-toggle.d.ts.map +0 -1
- package/lib/components/theme-toggle.js +0 -218
- package/lib/components/theme-toggle.ts +0 -297
- package/lib/components/tooltip.d.ts +0 -31
- package/lib/components/tooltip.d.ts.map +0 -1
- package/lib/components/tooltip.js +0 -112
- package/lib/components/tooltip.ts +0 -148
- package/lib/components/watcher.d.ts +0 -195
- package/lib/components/watcher.d.ts.map +0 -1
- package/lib/components/watcher.js +0 -241
- package/lib/components/watcher.ts +0 -261
- package/lib/components/write.d.ts +0 -107
- package/lib/components/write.d.ts.map +0 -1
- package/lib/components/write.js +0 -222
- package/lib/components/write.ts +0 -272
- package/lib/data/DataPipeline.d.ts +0 -113
- package/lib/data/DataPipeline.d.ts.map +0 -1
- package/lib/data/DataPipeline.js +0 -359
- package/lib/data/DataPipeline.ts +0 -452
- package/lib/facades/dataframe.jux +0 -0
- package/lib/globals.d.ts +0 -21
- package/lib/reactivity/state.d.ts +0 -36
- package/lib/reactivity/state.d.ts.map +0 -1
- package/lib/reactivity/state.js +0 -67
- package/lib/reactivity/state.ts +0 -78
- package/lib/storage/DataFrame.d.ts +0 -284
- package/lib/storage/DataFrame.d.ts.map +0 -1
- package/lib/storage/DataFrame.js +0 -1022
- package/lib/storage/DataFrame.ts +0 -1195
- package/lib/storage/DataFrameSource.d.ts +0 -158
- package/lib/storage/DataFrameSource.d.ts.map +0 -1
- package/lib/storage/DataFrameSource.js +0 -409
- package/lib/storage/DataFrameSource.ts +0 -556
- package/lib/storage/FileStorage.d.ts +0 -53
- package/lib/storage/FileStorage.d.ts.map +0 -1
- package/lib/storage/FileStorage.js +0 -80
- package/lib/storage/FileStorage.ts +0 -95
- package/lib/storage/IndexedDBDriver.d.ts +0 -75
- package/lib/storage/IndexedDBDriver.d.ts.map +0 -1
- package/lib/storage/IndexedDBDriver.js +0 -177
- package/lib/storage/IndexedDBDriver.ts +0 -226
- package/lib/storage/TabularDriver.d.ts +0 -118
- package/lib/storage/TabularDriver.d.ts.map +0 -1
- package/lib/storage/TabularDriver.js +0 -731
- package/lib/storage/TabularDriver.ts +0 -874
- package/lib/utils/codeparser.d.ts +0 -29
- package/lib/utils/codeparser.d.ts.map +0 -1
- package/lib/utils/codeparser.js +0 -409
- package/lib/utils/fetch.d.ts +0 -176
- package/lib/utils/fetch.d.ts.map +0 -1
- package/lib/utils/fetch.js +0 -427
- package/lib/utils/formatId.d.ts +0 -16
- package/lib/utils/formatId.d.ts.map +0 -1
- package/lib/utils/formatId.js +0 -27
- package/lib/utils/path-resolver.js +0 -23
package/lib/components/table.ts
DELETED
|
@@ -1,1225 +0,0 @@
|
|
|
1
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
2
|
-
* SECTION 1: DEFINITIONS
|
|
3
|
-
* Type definitions, constants, interfaces
|
|
4
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
5
|
-
|
|
6
|
-
import { BaseComponent } from './base/BaseComponent.js';
|
|
7
|
-
import { State } from '../reactivity/state.js';
|
|
8
|
-
|
|
9
|
-
// Event definitions
|
|
10
|
-
const TRIGGER_EVENTS = [
|
|
11
|
-
'rowClick', 'rowHover', 'cellClick',
|
|
12
|
-
'selected', 'deselected', 'selectionChange'
|
|
13
|
-
] as const;
|
|
14
|
-
|
|
15
|
-
const CALLBACK_EVENTS = [
|
|
16
|
-
'sortChange', 'filterChange', 'pageChange', 'dataChange'
|
|
17
|
-
] as const;
|
|
18
|
-
|
|
19
|
-
type TriggerEvent = typeof TRIGGER_EVENTS[number];
|
|
20
|
-
type CallbackEvent = typeof CALLBACK_EVENTS[number];
|
|
21
|
-
|
|
22
|
-
// Type definitions
|
|
23
|
-
export type SelectionBehaviorOnDataChange = 'clear' | 'preserve';
|
|
24
|
-
export type SelectionTrigger = 'row' | 'checkbox';
|
|
25
|
-
|
|
26
|
-
export interface ColumnDef {
|
|
27
|
-
key: string;
|
|
28
|
-
label: string;
|
|
29
|
-
width?: string;
|
|
30
|
-
render?: (value: any, row: any) => string | HTMLElement;
|
|
31
|
-
computed?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ✨ NEW: Computed column definition
|
|
35
|
-
export interface ComputedColumnDef {
|
|
36
|
-
key: string;
|
|
37
|
-
label: string;
|
|
38
|
-
compute: (row: any, rowIndex: number) => any;
|
|
39
|
-
render?: (value: any, row: any, rowIndex: number) => string | HTMLElement;
|
|
40
|
-
width?: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface TableOptions {
|
|
44
|
-
columns?: (string | ColumnDef)[];
|
|
45
|
-
rows?: any[][];
|
|
46
|
-
headers?: boolean;
|
|
47
|
-
striped?: boolean;
|
|
48
|
-
hoverable?: boolean;
|
|
49
|
-
bordered?: boolean;
|
|
50
|
-
compact?: boolean;
|
|
51
|
-
sortable?: boolean;
|
|
52
|
-
filterable?: boolean;
|
|
53
|
-
paginated?: boolean;
|
|
54
|
-
rowsPerPage?: number;
|
|
55
|
-
selectable?: boolean;
|
|
56
|
-
multiSelect?: boolean;
|
|
57
|
-
showCheckboxes?: boolean;
|
|
58
|
-
showBulkCheckbox?: boolean;
|
|
59
|
-
selectionTrigger?: SelectionTrigger;
|
|
60
|
-
style?: string;
|
|
61
|
-
class?: string;
|
|
62
|
-
rowIdField?: string;
|
|
63
|
-
selectionBehavior?: SelectionBehaviorOnDataChange;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
type TableState = {
|
|
67
|
-
columns: ColumnDef[];
|
|
68
|
-
rows: any[];
|
|
69
|
-
computedColumns: Map<string, ComputedColumnDef>;
|
|
70
|
-
headers: boolean;
|
|
71
|
-
striped: boolean;
|
|
72
|
-
hoverable: boolean;
|
|
73
|
-
bordered: boolean;
|
|
74
|
-
compact: boolean;
|
|
75
|
-
sortable: boolean;
|
|
76
|
-
filterable: boolean;
|
|
77
|
-
paginated: boolean;
|
|
78
|
-
rowsPerPage: number;
|
|
79
|
-
currentPage: number;
|
|
80
|
-
sortColumn: string | null;
|
|
81
|
-
sortDirection: 'asc' | 'desc';
|
|
82
|
-
filterText: string;
|
|
83
|
-
selectable: boolean;
|
|
84
|
-
multiSelect: boolean;
|
|
85
|
-
showCheckboxes: boolean;
|
|
86
|
-
showBulkCheckbox: boolean;
|
|
87
|
-
selectionTrigger: SelectionTrigger;
|
|
88
|
-
selectedIndexes: Set<number>;
|
|
89
|
-
style: string;
|
|
90
|
-
class: string;
|
|
91
|
-
rowIdField?: string;
|
|
92
|
-
selectionBehavior: SelectionBehaviorOnDataChange;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
96
|
-
* SECTION 2: CONSTRUCTOR & STORAGE
|
|
97
|
-
* Class declaration, instance variables, initialization
|
|
98
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
99
|
-
|
|
100
|
-
export class Table extends BaseComponent<TableState> {
|
|
101
|
-
private _tableElement: HTMLTableElement | null = null;
|
|
102
|
-
|
|
103
|
-
constructor(id: string, options: TableOptions = {}) {
|
|
104
|
-
const normalizedColumns = (options.columns ?? []).map(col =>
|
|
105
|
-
typeof col === 'string' ? { key: col, label: col } : col
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
// Initialize base with state
|
|
109
|
-
super(id, {
|
|
110
|
-
columns: normalizedColumns,
|
|
111
|
-
rows: options.rows ?? [],
|
|
112
|
-
computedColumns: new Map(), // ✨ NEW: Initialize empty Map
|
|
113
|
-
headers: options.headers ?? true,
|
|
114
|
-
striped: options.striped ?? false,
|
|
115
|
-
hoverable: options.hoverable ?? false,
|
|
116
|
-
bordered: options.bordered ?? false,
|
|
117
|
-
compact: options.compact ?? false,
|
|
118
|
-
sortable: options.sortable ?? false,
|
|
119
|
-
filterable: options.filterable ?? false,
|
|
120
|
-
paginated: options.paginated ?? false,
|
|
121
|
-
rowsPerPage: options.rowsPerPage ?? 10,
|
|
122
|
-
currentPage: 1,
|
|
123
|
-
sortColumn: null,
|
|
124
|
-
sortDirection: 'asc',
|
|
125
|
-
filterText: '',
|
|
126
|
-
selectable: options.selectable ?? false,
|
|
127
|
-
multiSelect: options.multiSelect ?? false,
|
|
128
|
-
showCheckboxes: options.showCheckboxes ?? false,
|
|
129
|
-
showBulkCheckbox: options.showBulkCheckbox ?? false,
|
|
130
|
-
selectionTrigger: options.selectionTrigger ?? 'row',
|
|
131
|
-
selectedIndexes: new Set<number>(),
|
|
132
|
-
style: options.style ?? '',
|
|
133
|
-
class: options.class ?? '',
|
|
134
|
-
rowIdField: options.rowIdField,
|
|
135
|
-
selectionBehavior: options.selectionBehavior ?? 'clear'
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// ✅ DEBUG: Log what was passed vs what was stored
|
|
139
|
-
console.log(`[Table ${id}] constructor options.filterable:`, options.filterable);
|
|
140
|
-
console.log(`[Table ${id}] this.state.filterable:`, this.state.filterable);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
144
|
-
* ABSTRACT METHOD IMPLEMENTATIONS
|
|
145
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
146
|
-
|
|
147
|
-
protected getTriggerEvents(): readonly string[] {
|
|
148
|
-
return TRIGGER_EVENTS;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
protected getCallbackEvents(): readonly string[] {
|
|
152
|
-
return CALLBACK_EVENTS;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
156
|
-
* SECTION 3: FLUENT API
|
|
157
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
158
|
-
|
|
159
|
-
// ✅ Inherited from BaseComponent: style(), class(), bind(), sync(), renderTo()
|
|
160
|
-
|
|
161
|
-
// Configuration methods
|
|
162
|
-
columns(value: (string | ColumnDef)[]): this {
|
|
163
|
-
this.state.columns = value.map(col =>
|
|
164
|
-
typeof col === 'string' ? { key: col, label: col } : col
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
// ✅ Rebuild header if table already rendered
|
|
168
|
-
if (this._tableElement) {
|
|
169
|
-
const oldThead = this._tableElement.querySelector('thead');
|
|
170
|
-
if (oldThead) oldThead.remove();
|
|
171
|
-
|
|
172
|
-
if (this.state.headers) {
|
|
173
|
-
const newThead = this._buildTableHeader();
|
|
174
|
-
this._tableElement.insertBefore(newThead, this._tableElement.firstChild);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return this;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
rows(value: any[]): this {
|
|
182
|
-
const previousRows = this.state.rows;
|
|
183
|
-
const hadSelections = this.state.selectedIndexes.size > 0;
|
|
184
|
-
|
|
185
|
-
// Handle selections based on behavior
|
|
186
|
-
if (this.state.selectionBehavior === 'preserve' && this.state.rowIdField) {
|
|
187
|
-
this._preserveSelections(previousRows, value);
|
|
188
|
-
} else {
|
|
189
|
-
this.state.selectedIndexes.clear();
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
this.state.rows = value;
|
|
193
|
-
|
|
194
|
-
// Auto-reset pagination if current page is now invalid
|
|
195
|
-
if (this.state.paginated) {
|
|
196
|
-
const totalPages = Math.ceil(value.length / this.state.rowsPerPage);
|
|
197
|
-
if (this.state.currentPage > totalPages && totalPages > 0) {
|
|
198
|
-
this.state.currentPage = totalPages;
|
|
199
|
-
}
|
|
200
|
-
if (totalPages === 0) {
|
|
201
|
-
this.state.currentPage = 1;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
this._updateTable();
|
|
206
|
-
|
|
207
|
-
// Fire callbacks
|
|
208
|
-
this._triggerCallback('dataChange', value, previousRows);
|
|
209
|
-
|
|
210
|
-
if (hadSelections && this._triggerHandlers.has('selectionChange')) {
|
|
211
|
-
this._triggerHandlers.get('selectionChange')!(
|
|
212
|
-
this.getSelectedRows(),
|
|
213
|
-
this.getSelectedIndexes(),
|
|
214
|
-
new CustomEvent('dataChange')
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return this;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Add a computed column that evaluates dynamically at render time
|
|
223
|
-
*
|
|
224
|
-
* @param key - Unique key for the column
|
|
225
|
-
* @param label - Display label in header
|
|
226
|
-
* @param compute - Function to compute value from row data
|
|
227
|
-
* @param render - Optional custom renderer for the computed value
|
|
228
|
-
* @param width - Optional column width
|
|
229
|
-
*/
|
|
230
|
-
computedColumn(
|
|
231
|
-
key: string,
|
|
232
|
-
label: string,
|
|
233
|
-
compute: (row: any, rowIndex: number) => any,
|
|
234
|
-
render?: (value: any, row: any, rowIndex: number) => string | HTMLElement,
|
|
235
|
-
width?: string
|
|
236
|
-
): this {
|
|
237
|
-
// Store computed column definition
|
|
238
|
-
this.state.computedColumns.set(key, {
|
|
239
|
-
key,
|
|
240
|
-
label,
|
|
241
|
-
compute,
|
|
242
|
-
render,
|
|
243
|
-
width
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
// Add to columns list if not already present
|
|
247
|
-
const existingColumn = this.state.columns.find(col => col.key === key);
|
|
248
|
-
if (!existingColumn) {
|
|
249
|
-
this.state.columns.push({
|
|
250
|
-
key,
|
|
251
|
-
label,
|
|
252
|
-
width,
|
|
253
|
-
computed: true,
|
|
254
|
-
render: render as any
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// If already rendered, update table
|
|
259
|
-
if (this._tableElement) {
|
|
260
|
-
const tbody = this._tableElement.querySelector('tbody');
|
|
261
|
-
const thead = this._tableElement.querySelector('thead');
|
|
262
|
-
|
|
263
|
-
if (tbody && thead) {
|
|
264
|
-
// Rebuild header with new column
|
|
265
|
-
const table = this._tableElement;
|
|
266
|
-
table.innerHTML = '';
|
|
267
|
-
const newThead = this._buildTableHeader();
|
|
268
|
-
table.appendChild(newThead);
|
|
269
|
-
|
|
270
|
-
const newTbody = document.createElement('tbody');
|
|
271
|
-
this._renderTableBody(newTbody);
|
|
272
|
-
table.appendChild(newTbody);
|
|
273
|
-
|
|
274
|
-
// Re-wire events
|
|
275
|
-
this._wireTriggerEvents(newTbody);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return this;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Remove a computed column
|
|
284
|
-
*/
|
|
285
|
-
removeComputedColumn(key: string): this {
|
|
286
|
-
this.state.computedColumns.delete(key);
|
|
287
|
-
this.state.columns = this.state.columns.filter(col => col.key !== key);
|
|
288
|
-
|
|
289
|
-
if (this._tableElement) {
|
|
290
|
-
const tbody = this._tableElement.querySelector('tbody');
|
|
291
|
-
const thead = this._tableElement.querySelector('thead');
|
|
292
|
-
|
|
293
|
-
if (tbody && thead) {
|
|
294
|
-
const table = this._tableElement;
|
|
295
|
-
table.innerHTML = '';
|
|
296
|
-
const newThead = this._buildTableHeader();
|
|
297
|
-
table.appendChild(newThead);
|
|
298
|
-
|
|
299
|
-
const newTbody = document.createElement('tbody');
|
|
300
|
-
this._renderTableBody(newTbody);
|
|
301
|
-
table.appendChild(newTbody);
|
|
302
|
-
|
|
303
|
-
this._wireTriggerEvents(newTbody);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return this;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Visual options
|
|
311
|
-
headers(value: boolean): this {
|
|
312
|
-
this.state.headers = value;
|
|
313
|
-
return this;
|
|
314
|
-
}
|
|
315
|
-
striped(value: boolean): this {
|
|
316
|
-
this.state.striped = value;
|
|
317
|
-
return this;
|
|
318
|
-
}
|
|
319
|
-
hoverable(value: boolean): this {
|
|
320
|
-
this.state.hoverable = value;
|
|
321
|
-
return this;
|
|
322
|
-
}
|
|
323
|
-
bordered(value: boolean): this {
|
|
324
|
-
this.state.bordered = value;
|
|
325
|
-
return this;
|
|
326
|
-
}
|
|
327
|
-
compact(value: boolean): this {
|
|
328
|
-
this.state.compact = value;
|
|
329
|
-
return this;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Feature toggles
|
|
333
|
-
sortable(value: boolean): this {
|
|
334
|
-
this.state.sortable = value;
|
|
335
|
-
return this;
|
|
336
|
-
}
|
|
337
|
-
filterable(value: boolean): this {
|
|
338
|
-
this.state.filterable = value;
|
|
339
|
-
return this;
|
|
340
|
-
}
|
|
341
|
-
paginated(value: boolean): this {
|
|
342
|
-
this.state.paginated = value;
|
|
343
|
-
return this;
|
|
344
|
-
}
|
|
345
|
-
rowsPerPage(value: number): this {
|
|
346
|
-
this.state.rowsPerPage = value;
|
|
347
|
-
return this;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Selection configuration
|
|
351
|
-
selectable(value: boolean): this {
|
|
352
|
-
this.state.selectable = value;
|
|
353
|
-
return this;
|
|
354
|
-
}
|
|
355
|
-
multiSelect(value: boolean): this {
|
|
356
|
-
this.state.multiSelect = value;
|
|
357
|
-
if (value) {
|
|
358
|
-
this.state.selectable = true; // multi-select implies selectable
|
|
359
|
-
}
|
|
360
|
-
return this;
|
|
361
|
-
}
|
|
362
|
-
showCheckboxes(value: boolean): this {
|
|
363
|
-
this.state.showCheckboxes = value;
|
|
364
|
-
|
|
365
|
-
// If already rendered, update immediately
|
|
366
|
-
if (this._tableElement) {
|
|
367
|
-
const tbody = this._tableElement.querySelector('tbody');
|
|
368
|
-
const thead = this._tableElement.querySelector('thead');
|
|
369
|
-
|
|
370
|
-
if (tbody && thead) {
|
|
371
|
-
// Update header
|
|
372
|
-
this._updateHeaderCheckbox(thead);
|
|
373
|
-
// Re-render body with/without checkboxes
|
|
374
|
-
this._renderTableBody(tbody);
|
|
375
|
-
// Update bulk checkbox state
|
|
376
|
-
this._updateBulkCheckboxState();
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return this;
|
|
381
|
-
}
|
|
382
|
-
showBulkCheckbox(value: boolean): this {
|
|
383
|
-
this.state.showBulkCheckbox = value;
|
|
384
|
-
if (value) {
|
|
385
|
-
this.state.multiSelect = true;
|
|
386
|
-
this.state.selectable = true;
|
|
387
|
-
this.state.showCheckboxes = true;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// If already rendered, update immediately
|
|
391
|
-
if (this._tableElement) {
|
|
392
|
-
const thead = this._tableElement.querySelector('thead');
|
|
393
|
-
const tbody = this._tableElement.querySelector('tbody');
|
|
394
|
-
|
|
395
|
-
if (thead && tbody) {
|
|
396
|
-
this._updateHeaderCheckbox(thead);
|
|
397
|
-
this._renderTableBody(tbody);
|
|
398
|
-
this._updateBulkCheckboxState();
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return this;
|
|
403
|
-
}
|
|
404
|
-
selectionTrigger(value: SelectionTrigger): this {
|
|
405
|
-
this.state.selectionTrigger = value;
|
|
406
|
-
|
|
407
|
-
// If already rendered, update cursor style
|
|
408
|
-
if (this._tableElement) {
|
|
409
|
-
const tbody = this._tableElement.querySelector('tbody');
|
|
410
|
-
if (tbody) {
|
|
411
|
-
if (this.state.selectionTrigger === 'row' && this.state.selectable) {
|
|
412
|
-
tbody.style.cursor = 'pointer';
|
|
413
|
-
} else {
|
|
414
|
-
tbody.style.cursor = '';
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
return this;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Selection actions
|
|
423
|
-
selectAll(): this {
|
|
424
|
-
this.state.rows.forEach((_, index) => {
|
|
425
|
-
this.state.selectedIndexes.add(index);
|
|
426
|
-
});
|
|
427
|
-
this._updateRowSelectionUI();
|
|
428
|
-
|
|
429
|
-
// Fire selectionChange
|
|
430
|
-
if (this._triggerHandlers.has('selectionChange')) {
|
|
431
|
-
this._triggerHandlers.get('selectionChange')!(
|
|
432
|
-
this.getSelectedRows(),
|
|
433
|
-
this.getSelectedIndexes(),
|
|
434
|
-
new CustomEvent('bulkSelect')
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
return this;
|
|
439
|
-
}
|
|
440
|
-
deselectAll(): this {
|
|
441
|
-
this.state.selectedIndexes.clear();
|
|
442
|
-
this._updateRowSelectionUI();
|
|
443
|
-
|
|
444
|
-
// Fire selectionChange
|
|
445
|
-
if (this._triggerHandlers.has('selectionChange')) {
|
|
446
|
-
this._triggerHandlers.get('selectionChange')!(
|
|
447
|
-
[],
|
|
448
|
-
[],
|
|
449
|
-
new CustomEvent('bulkDeselect')
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
return this;
|
|
454
|
-
}
|
|
455
|
-
clearSelection(): this {
|
|
456
|
-
this.state.selectedIndexes.clear();
|
|
457
|
-
this._updateRowSelectionUI();
|
|
458
|
-
return this;
|
|
459
|
-
}
|
|
460
|
-
selectRows(indexes: number[]): this {
|
|
461
|
-
if (!this.state.multiSelect) {
|
|
462
|
-
this.state.selectedIndexes.clear();
|
|
463
|
-
}
|
|
464
|
-
indexes.forEach(index => {
|
|
465
|
-
if (index >= 0 && index < this.state.rows.length) {
|
|
466
|
-
this.state.selectedIndexes.add(index);
|
|
467
|
-
}
|
|
468
|
-
});
|
|
469
|
-
this._updateRowSelectionUI();
|
|
470
|
-
return this;
|
|
471
|
-
}
|
|
472
|
-
deselectRows(indexes: number[]): this {
|
|
473
|
-
indexes.forEach(index => this.state.selectedIndexes.delete(index));
|
|
474
|
-
this._updateRowSelectionUI();
|
|
475
|
-
return this;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Public utilities
|
|
479
|
-
getSelectedIndexes(): number[] {
|
|
480
|
-
return Array.from(this.state.selectedIndexes);
|
|
481
|
-
}
|
|
482
|
-
getSelectedRows(): any[] {
|
|
483
|
-
return Array.from(this.state.selectedIndexes).map(index => this.state.rows[index]);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
488
|
-
* SECTION 5: RENDER LIFECYCLE
|
|
489
|
-
* Main render method and BUILD phase helpers
|
|
490
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
491
|
-
update(prop: string, value: any): void {
|
|
492
|
-
// No reactive updates needed
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
render(targetId?: string | HTMLElement | BaseComponent<any>): this {
|
|
496
|
-
const container = this._setupContainer(targetId);
|
|
497
|
-
const wrapper = this._buildWrapper();
|
|
498
|
-
const table = this._buildTable(wrapper);
|
|
499
|
-
const tbody = table.querySelector('tbody')!;
|
|
500
|
-
this._wireAllEvents(wrapper, tbody);
|
|
501
|
-
container.appendChild(wrapper);
|
|
502
|
-
this._tableElement = table;
|
|
503
|
-
return this;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Step 1: SETUP
|
|
507
|
-
protected _setupContainer(targetId?: string | HTMLElement | BaseComponent<any>): HTMLElement {
|
|
508
|
-
let container: HTMLElement;
|
|
509
|
-
|
|
510
|
-
if (targetId) {
|
|
511
|
-
if (typeof targetId === 'string') {
|
|
512
|
-
const target = document.querySelector(targetId);
|
|
513
|
-
if (!target || !(target instanceof HTMLElement)) {
|
|
514
|
-
throw new Error(`Table: Target "${targetId}" not found`);
|
|
515
|
-
}
|
|
516
|
-
container = target;
|
|
517
|
-
} else if (targetId instanceof HTMLElement) {
|
|
518
|
-
container = targetId;
|
|
519
|
-
} else if (targetId instanceof BaseComponent) {
|
|
520
|
-
// Find component's container by ID
|
|
521
|
-
const target = document.getElementById(targetId._id);
|
|
522
|
-
if (!target) {
|
|
523
|
-
throw new Error(`Table: Target component "${targetId._id}" not found`);
|
|
524
|
-
}
|
|
525
|
-
container = target;
|
|
526
|
-
} else {
|
|
527
|
-
throw new Error(`Table: Invalid target type`);
|
|
528
|
-
}
|
|
529
|
-
} else {
|
|
530
|
-
// Inline getOrCreateContainer functionality
|
|
531
|
-
let element = document.getElementById(this._id);
|
|
532
|
-
if (!element) {
|
|
533
|
-
element = document.createElement('div');
|
|
534
|
-
element.id = this._id;
|
|
535
|
-
document.body.appendChild(element);
|
|
536
|
-
}
|
|
537
|
-
container = element;
|
|
538
|
-
}
|
|
539
|
-
this.container = container;
|
|
540
|
-
return container;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// Step 2: BUILD wrapper
|
|
544
|
-
private _buildWrapper(): HTMLElement {
|
|
545
|
-
const { style, class: className } = this.state;
|
|
546
|
-
|
|
547
|
-
const wrapper = document.createElement('div');
|
|
548
|
-
wrapper.className = 'jux-table-wrapper';
|
|
549
|
-
wrapper.id = this._id;
|
|
550
|
-
if (className) wrapper.className += ` ${className}`;
|
|
551
|
-
if (style) wrapper.setAttribute('style', style);
|
|
552
|
-
|
|
553
|
-
if (this.state.selectable) {
|
|
554
|
-
const selectionStyles = document.createElement('style');
|
|
555
|
-
selectionStyles.textContent = `
|
|
556
|
-
.jux-table-row-selected { background-color: #e3f2fd !important; }
|
|
557
|
-
.jux-table-row-selected:hover { background-color: #bbdefb !important; }
|
|
558
|
-
`;
|
|
559
|
-
wrapper.appendChild(selectionStyles);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// ✅ Only show filter if filterable AND there are rows
|
|
563
|
-
if (this.state.filterable && this.state.rows.length > 0) {
|
|
564
|
-
wrapper.appendChild(this._buildFilterInput());
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
return wrapper;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
private _buildFilterInput(): HTMLInputElement {
|
|
571
|
-
const input = document.createElement('input');
|
|
572
|
-
input.type = 'text';
|
|
573
|
-
input.placeholder = 'Filter...';
|
|
574
|
-
input.className = 'jux-table-filter';
|
|
575
|
-
input.id = `${this._id}-filter`;
|
|
576
|
-
input.style.cssText = 'margin-bottom: 10px; padding: 5px; width: 100%; box-sizing: border-box;';
|
|
577
|
-
|
|
578
|
-
input.addEventListener('input', (e) => {
|
|
579
|
-
const target = e.target as HTMLInputElement;
|
|
580
|
-
this.state.filterText = target.value;
|
|
581
|
-
|
|
582
|
-
const tbody = this._tableElement?.querySelector('tbody');
|
|
583
|
-
if (tbody) {
|
|
584
|
-
this._renderTableBody(tbody);
|
|
585
|
-
|
|
586
|
-
const wrapper = this._tableElement?.closest('.jux-table-wrapper') as HTMLElement;
|
|
587
|
-
if (wrapper && this.state.paginated) {
|
|
588
|
-
this.state.currentPage = 1;
|
|
589
|
-
this._updatePagination(wrapper, tbody);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
this._triggerCallback('filterChange', target.value, e);
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
return input;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// Step 3: BUILD table
|
|
600
|
-
private _buildTable(wrapper: HTMLElement): HTMLTableElement {
|
|
601
|
-
const { striped, hoverable, bordered } = this.state;
|
|
602
|
-
const table = document.createElement('table');
|
|
603
|
-
table.className = 'jux-table';
|
|
604
|
-
if (striped) table.classList.add('jux-table-striped');
|
|
605
|
-
if (hoverable) table.classList.add('jux-table-hoverable');
|
|
606
|
-
if (bordered) table.classList.add('jux-table-bordered');
|
|
607
|
-
// Build and append header
|
|
608
|
-
if (this.state.headers) {
|
|
609
|
-
const thead = this._buildTableHeader();
|
|
610
|
-
table.appendChild(thead);
|
|
611
|
-
}
|
|
612
|
-
// Build and append body
|
|
613
|
-
const tbody = document.createElement('tbody');
|
|
614
|
-
this._renderTableBody(tbody);
|
|
615
|
-
table.appendChild(tbody);
|
|
616
|
-
wrapper.appendChild(table);
|
|
617
|
-
return table;
|
|
618
|
-
}
|
|
619
|
-
private _buildTableHeader(): HTMLTableSectionElement {
|
|
620
|
-
const thead = document.createElement('thead');
|
|
621
|
-
const headerRow = document.createElement('tr');
|
|
622
|
-
// Add bulk checkbox or empty checkbox column
|
|
623
|
-
if (this.state.showBulkCheckbox) {
|
|
624
|
-
const bulkTh = document.createElement('th');
|
|
625
|
-
bulkTh.style.width = '40px';
|
|
626
|
-
bulkTh.style.textAlign = 'center';
|
|
627
|
-
const bulkCheckbox = document.createElement('input');
|
|
628
|
-
bulkCheckbox.type = 'checkbox';
|
|
629
|
-
bulkCheckbox.className = 'jux-bulk-checkbox';
|
|
630
|
-
bulkCheckbox.style.cursor = 'pointer';
|
|
631
|
-
bulkCheckbox.title = 'Select all';
|
|
632
|
-
bulkCheckbox.addEventListener('change', (e) => {
|
|
633
|
-
if (bulkCheckbox.checked) {
|
|
634
|
-
this.selectAll();
|
|
635
|
-
} else {
|
|
636
|
-
this.deselectAll();
|
|
637
|
-
}
|
|
638
|
-
});
|
|
639
|
-
bulkTh.appendChild(bulkCheckbox);
|
|
640
|
-
headerRow.insertBefore(bulkTh, headerRow.firstChild);
|
|
641
|
-
} else if (this.state.showCheckboxes) {
|
|
642
|
-
const checkboxTh = document.createElement('th');
|
|
643
|
-
checkboxTh.style.width = '40px';
|
|
644
|
-
headerRow.insertBefore(checkboxTh, headerRow.firstChild);
|
|
645
|
-
}
|
|
646
|
-
// Add column headers with optional sorts
|
|
647
|
-
this.state.columns.forEach(col => {
|
|
648
|
-
const th = this._buildColumnHeader(col);
|
|
649
|
-
headerRow.appendChild(th);
|
|
650
|
-
});
|
|
651
|
-
thead.appendChild(headerRow);
|
|
652
|
-
thead.appendChild(headerRow);
|
|
653
|
-
return thead;
|
|
654
|
-
}
|
|
655
|
-
private _buildBulkCheckboxCell(): HTMLTableCellElement {
|
|
656
|
-
const bulkTh = document.createElement('th');
|
|
657
|
-
bulkTh.style.width = '40px';
|
|
658
|
-
bulkTh.style.textAlign = 'center';
|
|
659
|
-
const bulkCheckbox = document.createElement('input');
|
|
660
|
-
bulkCheckbox.type = 'checkbox';
|
|
661
|
-
bulkCheckbox.className = 'jux-bulk-checkbox';
|
|
662
|
-
bulkCheckbox.style.cursor = 'pointer';
|
|
663
|
-
bulkCheckbox.title = 'Select all';
|
|
664
|
-
bulkCheckbox.addEventListener('change', (e) => {
|
|
665
|
-
if (bulkCheckbox.checked) {
|
|
666
|
-
this.selectAll();
|
|
667
|
-
} else {
|
|
668
|
-
this.deselectAll();
|
|
669
|
-
}
|
|
670
|
-
});
|
|
671
|
-
bulkTh.appendChild(bulkCheckbox);
|
|
672
|
-
return bulkTh;
|
|
673
|
-
}
|
|
674
|
-
private _buildColumnHeader(col: ColumnDef): HTMLTableCellElement {
|
|
675
|
-
const th = document.createElement('th');
|
|
676
|
-
th.textContent = col.label;
|
|
677
|
-
th.setAttribute('data-column-key', col.key);
|
|
678
|
-
if (col.width) th.style.width = col.width;
|
|
679
|
-
if (this.state.sortable) {
|
|
680
|
-
th.style.cursor = 'pointer';
|
|
681
|
-
th.style.userSelect = 'none';
|
|
682
|
-
|
|
683
|
-
th.addEventListener('click', (e) => {
|
|
684
|
-
if (this.state.sortColumn === col.key) {
|
|
685
|
-
this.state.sortDirection = this.state.sortDirection === 'asc' ? 'desc' : 'asc';
|
|
686
|
-
} else {
|
|
687
|
-
this.state.sortColumn = col.key;
|
|
688
|
-
this.state.sortDirection = 'asc';
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
const tbody = this._tableElement!.querySelector('tbody')!;
|
|
692
|
-
this._renderTableBody(tbody);
|
|
693
|
-
|
|
694
|
-
const headerRow = th.parentElement!;
|
|
695
|
-
headerRow.querySelectorAll('th[data-column-key]').forEach(h => {
|
|
696
|
-
h.textContent = h.textContent?.replace(' ▲', '').replace(' ▼', '') || '';
|
|
697
|
-
});
|
|
698
|
-
th.textContent = col.label + (this.state.sortDirection === 'asc' ? ' ▲' : ' ▼');
|
|
699
|
-
|
|
700
|
-
this._triggerCallback('sortChange', col.key, this.state.sortDirection, e);
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
return th;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
private _wireAllEvents(wrapper: HTMLElement, tbody: HTMLTableSectionElement): void {
|
|
708
|
-
this._wireTriggerEvents(tbody);
|
|
709
|
-
if (this.state.paginated) {
|
|
710
|
-
this._updatePagination(wrapper, tbody);
|
|
711
|
-
}
|
|
712
|
-
this._wireStandardEvents(wrapper);
|
|
713
|
-
this._wireSyncBindings(wrapper, tbody);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
717
|
-
* SECTION 6: EVENT WIRING
|
|
718
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
719
|
-
|
|
720
|
-
private _wireTriggerEvents(tbody: HTMLTableSectionElement): void {
|
|
721
|
-
if (this._triggerHandlers.has('rowClick')) {
|
|
722
|
-
const handler = this._triggerHandlers.get('rowClick')!;
|
|
723
|
-
tbody.addEventListener('click', (e) => {
|
|
724
|
-
const tr = (e.target as HTMLElement).closest('tr');
|
|
725
|
-
if (tr && tbody.contains(tr)) {
|
|
726
|
-
const rowIndex = Array.from(tbody.children).indexOf(tr);
|
|
727
|
-
let rows = this._getFilteredRows();
|
|
728
|
-
rows = this._getSortedRows(rows);
|
|
729
|
-
rows = this._getPaginatedRows(rows);
|
|
730
|
-
const rowData = rows[rowIndex];
|
|
731
|
-
if (rowData) handler(rowData, rowIndex, e);
|
|
732
|
-
}
|
|
733
|
-
});
|
|
734
|
-
tbody.style.cursor = 'pointer';
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
if (this._triggerHandlers.has('cellClick')) {
|
|
738
|
-
const handler = this._triggerHandlers.get('cellClick')!;
|
|
739
|
-
tbody.addEventListener('click', (e) => {
|
|
740
|
-
const td = (e.target as HTMLElement).closest('td');
|
|
741
|
-
if (td) {
|
|
742
|
-
const tr = td.closest('tr');
|
|
743
|
-
if (tr && tbody.contains(tr)) {
|
|
744
|
-
const rowIndex = Array.from(tbody.children).indexOf(tr);
|
|
745
|
-
const cellIndex = Array.from(tr.children).indexOf(td);
|
|
746
|
-
let rows = this._getFilteredRows();
|
|
747
|
-
rows = this._getSortedRows(rows);
|
|
748
|
-
rows = this._getPaginatedRows(rows);
|
|
749
|
-
const rowData = rows[rowIndex];
|
|
750
|
-
const columnKey = this.state.columns[cellIndex]?.key;
|
|
751
|
-
const cellValue = rowData?.[columnKey as keyof typeof rowData];
|
|
752
|
-
if (rowData && columnKey) {
|
|
753
|
-
handler(cellValue, rowData, columnKey, rowIndex, cellIndex, e);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
if (this.state.selectable) {
|
|
761
|
-
tbody.addEventListener('click', (e) => {
|
|
762
|
-
const target = e.target as HTMLElement;
|
|
763
|
-
const isCheckboxClick = target.tagName === 'INPUT' && target.getAttribute('type') === 'checkbox';
|
|
764
|
-
|
|
765
|
-
if (this.state.selectionTrigger === 'checkbox' && !isCheckboxClick) return;
|
|
766
|
-
|
|
767
|
-
if (this.state.selectionTrigger === 'row' && isCheckboxClick) {
|
|
768
|
-
e.preventDefault();
|
|
769
|
-
const checkbox = target as HTMLInputElement;
|
|
770
|
-
checkbox.checked = !checkbox.checked;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
const tr = target.closest('tr');
|
|
774
|
-
if (tr && tbody.contains(tr)) {
|
|
775
|
-
const visualRowIndex = Array.from(tbody.children).indexOf(tr);
|
|
776
|
-
let rows = this._getFilteredRows();
|
|
777
|
-
rows = this._getSortedRows(rows);
|
|
778
|
-
const actualRowIndex = this.state.rows.indexOf(rows[
|
|
779
|
-
(this.state.currentPage - 1) * this.state.rowsPerPage + visualRowIndex
|
|
780
|
-
]);
|
|
781
|
-
|
|
782
|
-
if (actualRowIndex === -1) return;
|
|
783
|
-
const wasSelected = this.state.selectedIndexes.has(actualRowIndex);
|
|
784
|
-
|
|
785
|
-
if (this.state.multiSelect) {
|
|
786
|
-
if (wasSelected) {
|
|
787
|
-
this.state.selectedIndexes.delete(actualRowIndex);
|
|
788
|
-
tr.classList.remove('jux-table-row-selected');
|
|
789
|
-
const checkbox = tr.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
|
790
|
-
if (checkbox) checkbox.checked = false;
|
|
791
|
-
if (this._triggerHandlers.has('deselected')) {
|
|
792
|
-
this._triggerHandlers.get('deselected')!(this.state.rows[actualRowIndex], actualRowIndex, e);
|
|
793
|
-
}
|
|
794
|
-
} else {
|
|
795
|
-
this.state.selectedIndexes.add(actualRowIndex);
|
|
796
|
-
tr.classList.add('jux-table-row-selected');
|
|
797
|
-
const checkbox = tr.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
|
798
|
-
if (checkbox) checkbox.checked = true;
|
|
799
|
-
if (this._triggerHandlers.has('selected')) {
|
|
800
|
-
this._triggerHandlers.get('selected')!(this.state.rows[actualRowIndex], actualRowIndex, e);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
} else {
|
|
804
|
-
const previousSelection = Array.from(this.state.selectedIndexes);
|
|
805
|
-
this.state.selectedIndexes.clear();
|
|
806
|
-
tbody.querySelectorAll('tr').forEach(row => {
|
|
807
|
-
row.classList.remove('jux-table-row-selected');
|
|
808
|
-
const checkbox = row.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
|
809
|
-
if (checkbox) checkbox.checked = false;
|
|
810
|
-
});
|
|
811
|
-
|
|
812
|
-
if (!wasSelected) {
|
|
813
|
-
this.state.selectedIndexes.add(actualRowIndex);
|
|
814
|
-
tr.classList.add('jux-table-row-selected');
|
|
815
|
-
const checkbox = tr.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
|
816
|
-
if (checkbox) checkbox.checked = true;
|
|
817
|
-
if (this._triggerHandlers.has('selected')) {
|
|
818
|
-
this._triggerHandlers.get('selected')!(this.state.rows[actualRowIndex], actualRowIndex, e);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
if (this._triggerHandlers.has('deselected') && previousSelection.length > 0) {
|
|
823
|
-
previousSelection.forEach(idx => {
|
|
824
|
-
if (idx !== actualRowIndex) {
|
|
825
|
-
this._triggerHandlers.get('deselected')!(this.state.rows[idx], idx, e);
|
|
826
|
-
}
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
this._updateBulkCheckboxState();
|
|
832
|
-
|
|
833
|
-
if (this._triggerHandlers.has('selectionChange')) {
|
|
834
|
-
this._triggerHandlers.get('selectionChange')!(this.getSelectedRows(), this.getSelectedIndexes(), e);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
});
|
|
838
|
-
|
|
839
|
-
if (this.state.selectionTrigger === 'row') {
|
|
840
|
-
tbody.style.cursor = 'pointer';
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
private _wireSyncBindings(wrapper: HTMLElement, tbody: HTMLTableSectionElement): void {
|
|
846
|
-
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
847
|
-
if (property === 'rows' || property === 'data') {
|
|
848
|
-
this._wireSyncRows(stateObj, toComponent, tbody, wrapper);
|
|
849
|
-
} else if (property === 'columns') {
|
|
850
|
-
this._wireSyncColumns(stateObj, toComponent, wrapper, tbody);
|
|
851
|
-
}
|
|
852
|
-
});
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
private _wireSyncRows(
|
|
856
|
-
stateObj: State<any>,
|
|
857
|
-
toComponent: Function | undefined,
|
|
858
|
-
tbody: HTMLTableSectionElement,
|
|
859
|
-
wrapper: HTMLElement
|
|
860
|
-
): void {
|
|
861
|
-
const transform = toComponent || ((v: any) => v);
|
|
862
|
-
stateObj.subscribe((val: any) => {
|
|
863
|
-
const previousRows = this.state.rows;
|
|
864
|
-
const transformed = transform(val);
|
|
865
|
-
const hadSelections = this.state.selectedIndexes.size > 0;
|
|
866
|
-
|
|
867
|
-
if (this.state.selectionBehavior === 'preserve' && this.state.rowIdField) {
|
|
868
|
-
this._preserveSelections(previousRows, transformed);
|
|
869
|
-
} else {
|
|
870
|
-
const isAppend = transformed.length > previousRows.length;
|
|
871
|
-
if (isAppend) {
|
|
872
|
-
const maxValidIndex = Math.min(previousRows.length, transformed.length) - 1;
|
|
873
|
-
const validSelections = new Set<number>();
|
|
874
|
-
this.state.selectedIndexes.forEach(idx => {
|
|
875
|
-
if (idx <= maxValidIndex) validSelections.add(idx);
|
|
876
|
-
});
|
|
877
|
-
this.state.selectedIndexes = validSelections;
|
|
878
|
-
} else {
|
|
879
|
-
this.state.selectedIndexes.clear();
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
this.state.rows = transformed;
|
|
884
|
-
|
|
885
|
-
if (this.state.paginated) {
|
|
886
|
-
const totalPages = Math.ceil(transformed.length / this.state.rowsPerPage);
|
|
887
|
-
if (this.state.currentPage > totalPages && totalPages > 0) {
|
|
888
|
-
this.state.currentPage = totalPages;
|
|
889
|
-
}
|
|
890
|
-
if (totalPages === 0) {
|
|
891
|
-
this.state.currentPage = 1;
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
this._renderTableBody(tbody);
|
|
896
|
-
if (this.state.paginated) {
|
|
897
|
-
this._updatePagination(wrapper, tbody);
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
this._triggerCallback('dataChange', transformed, previousRows);
|
|
901
|
-
if (hadSelections || this.state.selectedIndexes.size > 0) {
|
|
902
|
-
this._triggerHandlers.get('selectionChange')?.(this.getSelectedRows(), this.getSelectedIndexes(), new CustomEvent('dataChange'));
|
|
903
|
-
}
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
private _wireSyncColumns(
|
|
908
|
-
stateObj: State<any>,
|
|
909
|
-
toComponent: Function | undefined,
|
|
910
|
-
wrapper: HTMLElement,
|
|
911
|
-
tbody: HTMLTableSectionElement
|
|
912
|
-
): void {
|
|
913
|
-
const transform = toComponent || ((v: any) => v);
|
|
914
|
-
stateObj.subscribe((val: any) => {
|
|
915
|
-
const transformed = transform(val);
|
|
916
|
-
this.state.columns = transformed;
|
|
917
|
-
|
|
918
|
-
const table = this._tableElement!;
|
|
919
|
-
table.innerHTML = '';
|
|
920
|
-
const thead = this._buildTableHeader();
|
|
921
|
-
table.appendChild(thead);
|
|
922
|
-
const newTbody = document.createElement('tbody');
|
|
923
|
-
this._renderTableBody(newTbody);
|
|
924
|
-
table.appendChild(newTbody);
|
|
925
|
-
|
|
926
|
-
this._wireTriggerEvents(newTbody);
|
|
927
|
-
if (this.state.paginated) {
|
|
928
|
-
this._updatePagination(wrapper, newTbody);
|
|
929
|
-
}
|
|
930
|
-
});
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
934
|
-
* SECTION 7: INTERNAL HELPERS
|
|
935
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
936
|
-
|
|
937
|
-
private _getFilteredRows(): any[] {
|
|
938
|
-
if (!this.state.filterText) return this.state.rows;
|
|
939
|
-
const searchText = this.state.filterText.toLowerCase();
|
|
940
|
-
return this.state.rows.filter(row => {
|
|
941
|
-
return this.state.columns.some(col => {
|
|
942
|
-
const value = row[col.key as keyof typeof row];
|
|
943
|
-
return String(value).toLowerCase().includes(searchText);
|
|
944
|
-
});
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
private _getSortedRows(rows: any[]): any[] {
|
|
949
|
-
if (!this.state.sortColumn) return rows;
|
|
950
|
-
|
|
951
|
-
const computedDef = this.state.computedColumns.get(this.state.sortColumn);
|
|
952
|
-
|
|
953
|
-
const sorted = [...rows].sort((a, b) => {
|
|
954
|
-
let aVal: any;
|
|
955
|
-
let bVal: any;
|
|
956
|
-
|
|
957
|
-
if (computedDef) {
|
|
958
|
-
const aIndex = this.state.rows.indexOf(a);
|
|
959
|
-
const bIndex = this.state.rows.indexOf(b);
|
|
960
|
-
aVal = computedDef.compute(a, aIndex);
|
|
961
|
-
bVal = computedDef.compute(b, bIndex);
|
|
962
|
-
} else {
|
|
963
|
-
aVal = a[this.state.sortColumn! as keyof typeof a];
|
|
964
|
-
bVal = b[this.state.sortColumn! as keyof typeof b];
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
if (aVal === bVal) return 0;
|
|
968
|
-
if (aVal == null) return 1;
|
|
969
|
-
if (bVal == null) return -1;
|
|
970
|
-
const comparison = aVal < bVal ? -1 : 1;
|
|
971
|
-
return this.state.sortDirection === 'asc' ? comparison : -comparison;
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
return sorted;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
private _getPaginatedRows(rows: any[]): any[] {
|
|
978
|
-
if (!this.state.paginated) return rows;
|
|
979
|
-
const start = (this.state.currentPage - 1) * this.state.rowsPerPage;
|
|
980
|
-
const end = start + this.state.rowsPerPage;
|
|
981
|
-
return rows.slice(start, end);
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
private _updateTable(): void {
|
|
985
|
-
if (!this._tableElement) return;
|
|
986
|
-
const tbody = this._tableElement.querySelector('tbody');
|
|
987
|
-
if (!tbody) return;
|
|
988
|
-
|
|
989
|
-
this._renderTableBody(tbody);
|
|
990
|
-
|
|
991
|
-
const wrapper = this._tableElement.closest('.jux-table-wrapper') as HTMLElement;
|
|
992
|
-
if (wrapper && this.state.paginated) {
|
|
993
|
-
this._updatePagination(wrapper, tbody);
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
// ✅ Show/hide filter based on whether there's data
|
|
997
|
-
if (wrapper && this.state.filterable) {
|
|
998
|
-
let filterInput = wrapper.querySelector('.jux-table-filter') as HTMLInputElement;
|
|
999
|
-
|
|
1000
|
-
if (this.state.rows.length > 0) {
|
|
1001
|
-
// Show filter - create if doesn't exist
|
|
1002
|
-
if (!filterInput) {
|
|
1003
|
-
filterInput = this._buildFilterInput();
|
|
1004
|
-
// Insert at the beginning of wrapper, before the table
|
|
1005
|
-
wrapper.insertBefore(filterInput, wrapper.firstChild);
|
|
1006
|
-
}
|
|
1007
|
-
} else {
|
|
1008
|
-
// Hide filter - remove if exists
|
|
1009
|
-
if (filterInput) {
|
|
1010
|
-
filterInput.remove();
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
private _renderTableBody(tbody: HTMLTableSectionElement): void {
|
|
1017
|
-
tbody.innerHTML = '';
|
|
1018
|
-
let rows = this._getFilteredRows();
|
|
1019
|
-
rows = this._getSortedRows(rows);
|
|
1020
|
-
rows = this._getPaginatedRows(rows);
|
|
1021
|
-
|
|
1022
|
-
rows.forEach((row) => {
|
|
1023
|
-
const tr = document.createElement('tr');
|
|
1024
|
-
const actualRowIndex = this.state.rows.indexOf(row);
|
|
1025
|
-
const isSelected = this.state.selectedIndexes.has(actualRowIndex);
|
|
1026
|
-
|
|
1027
|
-
if (isSelected) {
|
|
1028
|
-
tr.classList.add('jux-table-row-selected');
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
if (this.state.showCheckboxes) {
|
|
1032
|
-
const checkboxTd = document.createElement('td');
|
|
1033
|
-
checkboxTd.style.width = '40px';
|
|
1034
|
-
checkboxTd.style.textAlign = 'center';
|
|
1035
|
-
const checkbox = document.createElement('input');
|
|
1036
|
-
checkbox.type = 'checkbox';
|
|
1037
|
-
checkbox.checked = isSelected;
|
|
1038
|
-
checkbox.style.cursor = 'pointer';
|
|
1039
|
-
checkboxTd.appendChild(checkbox);
|
|
1040
|
-
tr.appendChild(checkboxTd);
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
this.state.columns.forEach(col => {
|
|
1044
|
-
const td = document.createElement('td');
|
|
1045
|
-
const computedDef = this.state.computedColumns.get(col.key);
|
|
1046
|
-
|
|
1047
|
-
let cellValue: any;
|
|
1048
|
-
let rendered: string | HTMLElement;
|
|
1049
|
-
|
|
1050
|
-
if (computedDef) {
|
|
1051
|
-
cellValue = computedDef.compute(row, actualRowIndex);
|
|
1052
|
-
if (computedDef.render) {
|
|
1053
|
-
rendered = computedDef.render(cellValue, row, actualRowIndex);
|
|
1054
|
-
} else {
|
|
1055
|
-
rendered = cellValue != null ? String(cellValue) : '';
|
|
1056
|
-
}
|
|
1057
|
-
} else {
|
|
1058
|
-
cellValue = row[col.key as keyof typeof row];
|
|
1059
|
-
if (col.render) {
|
|
1060
|
-
rendered = col.render(cellValue, row);
|
|
1061
|
-
} else {
|
|
1062
|
-
rendered = cellValue != null ? String(cellValue) : '';
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
if (typeof rendered === 'string') {
|
|
1067
|
-
td.innerHTML = rendered;
|
|
1068
|
-
} else {
|
|
1069
|
-
td.appendChild(rendered);
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
tr.appendChild(td);
|
|
1073
|
-
});
|
|
1074
|
-
|
|
1075
|
-
tbody.appendChild(tr);
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
private _updateHeaderCheckbox(thead: HTMLTableSectionElement): void {
|
|
1080
|
-
const headerRow = thead.querySelector('tr');
|
|
1081
|
-
if (!headerRow) return;
|
|
1082
|
-
|
|
1083
|
-
const existingCheckboxThs = headerRow.querySelectorAll('th:first-child');
|
|
1084
|
-
existingCheckboxThs.forEach(th => {
|
|
1085
|
-
if (th.querySelector('.jux-bulk-checkbox') || th.textContent === '') {
|
|
1086
|
-
th.remove();
|
|
1087
|
-
}
|
|
1088
|
-
});
|
|
1089
|
-
|
|
1090
|
-
if (this.state.showBulkCheckbox) {
|
|
1091
|
-
const bulkTh = this._buildBulkCheckboxCell();
|
|
1092
|
-
headerRow.insertBefore(bulkTh, headerRow.firstChild);
|
|
1093
|
-
} else if (this.state.showCheckboxes) {
|
|
1094
|
-
const checkboxTh = document.createElement('th');
|
|
1095
|
-
checkboxTh.style.width = '40px';
|
|
1096
|
-
headerRow.insertBefore(checkboxTh, headerRow.firstChild);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
private _updateBulkCheckboxState(): void {
|
|
1101
|
-
if (!this.state.showBulkCheckbox || !this._tableElement) return;
|
|
1102
|
-
const bulkCheckbox = this._tableElement.querySelector('.jux-bulk-checkbox') as HTMLInputElement;
|
|
1103
|
-
if (!bulkCheckbox) return;
|
|
1104
|
-
|
|
1105
|
-
const totalRows = this.state.rows.length;
|
|
1106
|
-
const selectedRows = this.state.selectedIndexes.size;
|
|
1107
|
-
|
|
1108
|
-
if (selectedRows === 0) {
|
|
1109
|
-
bulkCheckbox.checked = false;
|
|
1110
|
-
bulkCheckbox.indeterminate = false;
|
|
1111
|
-
} else if (selectedRows === totalRows) {
|
|
1112
|
-
bulkCheckbox.checked = true;
|
|
1113
|
-
bulkCheckbox.indeterminate = false;
|
|
1114
|
-
} else {
|
|
1115
|
-
bulkCheckbox.checked = false;
|
|
1116
|
-
bulkCheckbox.indeterminate = true;
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
private _updateRowSelectionUI(): void {
|
|
1121
|
-
if (!this._tableElement) return;
|
|
1122
|
-
const tbody = this._tableElement.querySelector('tbody');
|
|
1123
|
-
if (!tbody) return;
|
|
1124
|
-
|
|
1125
|
-
let rows = this._getFilteredRows();
|
|
1126
|
-
rows = this._getSortedRows(rows);
|
|
1127
|
-
const pageRows = this._getPaginatedRows(rows);
|
|
1128
|
-
|
|
1129
|
-
Array.from(tbody.children).forEach((tr, visualIndex) => {
|
|
1130
|
-
const pageRowData = pageRows[visualIndex];
|
|
1131
|
-
const actualRowIndex = this.state.rows.indexOf(pageRowData);
|
|
1132
|
-
const isSelected = this.state.selectedIndexes.has(actualRowIndex);
|
|
1133
|
-
|
|
1134
|
-
if (isSelected) {
|
|
1135
|
-
tr.classList.add('jux-table-row-selected');
|
|
1136
|
-
} else {
|
|
1137
|
-
tr.classList.remove('jux-table-row-selected');
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
const checkbox = tr.querySelector('input[type="checkbox"]') as HTMLInputElement;
|
|
1141
|
-
if (checkbox) {
|
|
1142
|
-
checkbox.checked = isSelected;
|
|
1143
|
-
}
|
|
1144
|
-
});
|
|
1145
|
-
|
|
1146
|
-
this._updateBulkCheckboxState();
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
private _preserveSelections(previousRows: any[], newRows: any[]): void {
|
|
1150
|
-
if (!this.state.rowIdField || this.state.selectionBehavior === 'clear') {
|
|
1151
|
-
this.state.selectedIndexes.clear();
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
const selectedIds = new Set<any>();
|
|
1156
|
-
Array.from(this.state.selectedIndexes).forEach(index => {
|
|
1157
|
-
const row = previousRows[index];
|
|
1158
|
-
if (row && row[this.state.rowIdField! as keyof typeof row] != null) {
|
|
1159
|
-
selectedIds.add(row[this.state.rowIdField! as keyof typeof row]);
|
|
1160
|
-
}
|
|
1161
|
-
});
|
|
1162
|
-
|
|
1163
|
-
this.state.selectedIndexes.clear();
|
|
1164
|
-
newRows.forEach((row, index) => {
|
|
1165
|
-
const rowId = row[this.state.rowIdField! as keyof typeof row];
|
|
1166
|
-
if (rowId != null && selectedIds.has(rowId)) {
|
|
1167
|
-
this.state.selectedIndexes.add(index);
|
|
1168
|
-
}
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
private _updatePagination(wrapper: HTMLElement, tbody: HTMLTableSectionElement): void {
|
|
1173
|
-
const existingPagination = wrapper.querySelector('.jux-table-pagination');
|
|
1174
|
-
if (existingPagination) existingPagination.remove();
|
|
1175
|
-
|
|
1176
|
-
let rows = this._getFilteredRows();
|
|
1177
|
-
rows = this._getSortedRows(rows);
|
|
1178
|
-
const totalPages = Math.ceil(rows.length / this.state.rowsPerPage);
|
|
1179
|
-
|
|
1180
|
-
if (totalPages <= 1) return;
|
|
1181
|
-
|
|
1182
|
-
const pagination = document.createElement('div');
|
|
1183
|
-
pagination.className = 'jux-table-pagination';
|
|
1184
|
-
pagination.style.cssText = 'margin-top: 10px; display: flex; gap: 5px; justify-content: center;';
|
|
1185
|
-
|
|
1186
|
-
const prevBtn = document.createElement('button');
|
|
1187
|
-
prevBtn.textContent = 'Previous';
|
|
1188
|
-
prevBtn.disabled = this.state.currentPage === 1;
|
|
1189
|
-
prevBtn.addEventListener('click', (e) => {
|
|
1190
|
-
if (this.state.currentPage > 1) {
|
|
1191
|
-
const previousPage = this.state.currentPage;
|
|
1192
|
-
this.state.currentPage--;
|
|
1193
|
-
this._renderTableBody(tbody);
|
|
1194
|
-
this._updatePagination(wrapper, tbody);
|
|
1195
|
-
this._triggerCallback('pageChange', this.state.currentPage, previousPage, e);
|
|
1196
|
-
}
|
|
1197
|
-
});
|
|
1198
|
-
pagination.appendChild(prevBtn);
|
|
1199
|
-
|
|
1200
|
-
const pageInfo = document.createElement('span');
|
|
1201
|
-
pageInfo.textContent = `Page ${this.state.currentPage} of ${totalPages}`;
|
|
1202
|
-
pageInfo.style.cssText = 'display: flex; align-items: center; padding: 0 10px;';
|
|
1203
|
-
pagination.appendChild(pageInfo);
|
|
1204
|
-
|
|
1205
|
-
const nextBtn = document.createElement('button');
|
|
1206
|
-
nextBtn.textContent = 'Next';
|
|
1207
|
-
nextBtn.disabled = this.state.currentPage === totalPages;
|
|
1208
|
-
nextBtn.addEventListener('click', (e) => {
|
|
1209
|
-
if (this.state.currentPage < totalPages) {
|
|
1210
|
-
const previousPage = this.state.currentPage;
|
|
1211
|
-
this.state.currentPage++;
|
|
1212
|
-
this._renderTableBody(tbody);
|
|
1213
|
-
this._updatePagination(wrapper, tbody);
|
|
1214
|
-
this._triggerCallback('pageChange', this.state.currentPage, previousPage, e);
|
|
1215
|
-
}
|
|
1216
|
-
});
|
|
1217
|
-
pagination.appendChild(nextBtn);
|
|
1218
|
-
|
|
1219
|
-
wrapper.appendChild(pagination);
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
export function table(id: string, options: TableOptions = {}): Table {
|
|
1224
|
-
return new Table(id, options);
|
|
1225
|
-
}
|