juxscript 1.1.240 → 1.1.244

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.
Files changed (297) hide show
  1. package/index.js +7 -137
  2. package/lib/components/dataframe.ts +0 -569
  3. package/lib/components/include.ts +229 -229
  4. package/lib/components/tag.ts +68 -0
  5. package/lib/styles/shadcn.css +17 -5
  6. package/lib/utils/idgen.ts +6 -0
  7. package/package.json +5 -6
  8. package/index.d.ts +0 -239
  9. package/index.d.ts.map +0 -1
  10. package/lib/components/alert.d.ts +0 -36
  11. package/lib/components/alert.d.ts.map +0 -1
  12. package/lib/components/alert.js +0 -172
  13. package/lib/components/alert.ts +0 -219
  14. package/lib/components/app.d.ts +0 -89
  15. package/lib/components/app.d.ts.map +0 -1
  16. package/lib/components/app.js +0 -175
  17. package/lib/components/app.ts +0 -247
  18. package/lib/components/badge.d.ts +0 -26
  19. package/lib/components/badge.d.ts.map +0 -1
  20. package/lib/components/badge.js +0 -91
  21. package/lib/components/badge.ts +0 -118
  22. package/lib/components/base/Animations.d.ts +0 -36
  23. package/lib/components/base/Animations.d.ts.map +0 -1
  24. package/lib/components/base/Animations.js +0 -70
  25. package/lib/components/base/Animations.ts +0 -112
  26. package/lib/components/base/BaseComponent.d.ts +0 -294
  27. package/lib/components/base/BaseComponent.d.ts.map +0 -1
  28. package/lib/components/base/BaseComponent.js +0 -735
  29. package/lib/components/base/BaseComponent.ts +0 -884
  30. package/lib/components/base/FormInput.d.ts +0 -77
  31. package/lib/components/base/FormInput.d.ts.map +0 -1
  32. package/lib/components/base/FormInput.js +0 -171
  33. package/lib/components/base/FormInput.ts +0 -237
  34. package/lib/components/blueprint.d.ts +0 -40
  35. package/lib/components/blueprint.d.ts.map +0 -1
  36. package/lib/components/blueprint.js +0 -327
  37. package/lib/components/button.d.ts +0 -70
  38. package/lib/components/button.d.ts.map +0 -1
  39. package/lib/components/button.js +0 -177
  40. package/lib/components/button.ts +0 -237
  41. package/lib/components/card.d.ts +0 -35
  42. package/lib/components/card.d.ts.map +0 -1
  43. package/lib/components/card.js +0 -130
  44. package/lib/components/card.ts +0 -177
  45. package/lib/components/chart.d.ts +0 -49
  46. package/lib/components/chart.d.ts.map +0 -1
  47. package/lib/components/chart.js +0 -205
  48. package/lib/components/chart.ts +0 -254
  49. package/lib/components/checkbox.d.ts +0 -33
  50. package/lib/components/checkbox.d.ts.map +0 -1
  51. package/lib/components/checkbox.js +0 -202
  52. package/lib/components/checkbox.ts +0 -260
  53. package/lib/components/code.d.ts +0 -52
  54. package/lib/components/code.d.ts.map +0 -1
  55. package/lib/components/code.js +0 -201
  56. package/lib/components/code.ts +0 -260
  57. package/lib/components/container.d.ts +0 -60
  58. package/lib/components/container.d.ts.map +0 -1
  59. package/lib/components/container.js +0 -195
  60. package/lib/components/container.ts +0 -259
  61. package/lib/components/data.d.ts +0 -36
  62. package/lib/components/data.d.ts.map +0 -1
  63. package/lib/components/data.js +0 -110
  64. package/lib/components/data.ts +0 -135
  65. package/lib/components/dataframe/DataFrameSource.d.ts +0 -118
  66. package/lib/components/dataframe/DataFrameSource.d.ts.map +0 -1
  67. package/lib/components/dataframe/DataFrameSource.js +0 -421
  68. package/lib/components/dataframe/DataFrameSource.ts +0 -532
  69. package/lib/components/dataframe/ImportSettingsModal.d.ts +0 -60
  70. package/lib/components/dataframe/ImportSettingsModal.d.ts.map +0 -1
  71. package/lib/components/dataframe/ImportSettingsModal.js +0 -442
  72. package/lib/components/dataframe/ImportSettingsModal.ts +0 -531
  73. package/lib/components/dataframe.d.ts +0 -110
  74. package/lib/components/dataframe.d.ts.map +0 -1
  75. package/lib/components/dataframe.js +0 -470
  76. package/lib/components/datepicker.d.ts +0 -40
  77. package/lib/components/datepicker.d.ts.map +0 -1
  78. package/lib/components/datepicker.js +0 -193
  79. package/lib/components/datepicker.ts +0 -251
  80. package/lib/components/dialog.d.ts +0 -39
  81. package/lib/components/dialog.d.ts.map +0 -1
  82. package/lib/components/dialog.js +0 -131
  83. package/lib/components/dialog.ts +0 -178
  84. package/lib/components/divider.d.ts +0 -31
  85. package/lib/components/divider.d.ts.map +0 -1
  86. package/lib/components/divider.js +0 -72
  87. package/lib/components/divider.ts +0 -104
  88. package/lib/components/dropdown-menu.d.ts +0 -42
  89. package/lib/components/dropdown-menu.d.ts.map +0 -1
  90. package/lib/components/dropdown-menu.js +0 -177
  91. package/lib/components/dropdown-menu.ts +0 -214
  92. package/lib/components/dropdown.d.ts +0 -41
  93. package/lib/components/dropdown.d.ts.map +0 -1
  94. package/lib/components/dropdown.js +0 -136
  95. package/lib/components/dropdown.ts +0 -188
  96. package/lib/components/element.d.ts +0 -51
  97. package/lib/components/element.d.ts.map +0 -1
  98. package/lib/components/element.js +0 -209
  99. package/lib/components/element.ts +0 -271
  100. package/lib/components/event-chain.d.ts +0 -9
  101. package/lib/components/event-chain.d.ts.map +0 -1
  102. package/lib/components/event-chain.js +0 -33
  103. package/lib/components/fileupload.d.ts +0 -98
  104. package/lib/components/fileupload.d.ts.map +0 -1
  105. package/lib/components/fileupload.js +0 -351
  106. package/lib/components/fileupload.ts +0 -449
  107. package/lib/components/grid.d.ts +0 -88
  108. package/lib/components/grid.d.ts.map +0 -1
  109. package/lib/components/grid.js +0 -208
  110. package/lib/components/grid.ts +0 -295
  111. package/lib/components/heading.d.ts +0 -25
  112. package/lib/components/heading.d.ts.map +0 -1
  113. package/lib/components/heading.js +0 -83
  114. package/lib/components/heading.ts +0 -113
  115. package/lib/components/helpers.d.ts +0 -9
  116. package/lib/components/helpers.d.ts.map +0 -1
  117. package/lib/components/helpers.js +0 -30
  118. package/lib/components/helpers.ts +0 -41
  119. package/lib/components/hero.d.ts +0 -60
  120. package/lib/components/hero.d.ts.map +0 -1
  121. package/lib/components/hero.js +0 -239
  122. package/lib/components/hero.ts +0 -302
  123. package/lib/components/history/StateHistory.d.ts +0 -91
  124. package/lib/components/history/StateHistory.d.ts.map +0 -1
  125. package/lib/components/history/StateHistory.js +0 -154
  126. package/lib/components/history/StateHistory.ts +0 -200
  127. package/lib/components/icon.d.ts +0 -36
  128. package/lib/components/icon.d.ts.map +0 -1
  129. package/lib/components/icon.js +0 -135
  130. package/lib/components/icon.ts +0 -182
  131. package/lib/components/icons.d.ts +0 -25
  132. package/lib/components/icons.d.ts.map +0 -1
  133. package/lib/components/icons.js +0 -440
  134. package/lib/components/icons.ts +0 -464
  135. package/lib/components/image.d.ts +0 -42
  136. package/lib/components/image.d.ts.map +0 -1
  137. package/lib/components/image.js +0 -204
  138. package/lib/components/image.ts +0 -260
  139. package/lib/components/include.d.ts +0 -86
  140. package/lib/components/include.d.ts.map +0 -1
  141. package/lib/components/include.js +0 -238
  142. package/lib/components/input.d.ts +0 -85
  143. package/lib/components/input.d.ts.map +0 -1
  144. package/lib/components/input.js +0 -362
  145. package/lib/components/input.ts +0 -473
  146. package/lib/components/layer.d.ts +0 -72
  147. package/lib/components/layer.d.ts.map +0 -1
  148. package/lib/components/layer.js +0 -219
  149. package/lib/components/layer.ts +0 -304
  150. package/lib/components/link.d.ts +0 -41
  151. package/lib/components/link.d.ts.map +0 -1
  152. package/lib/components/link.js +0 -216
  153. package/lib/components/link.ts +0 -268
  154. package/lib/components/list.d.ts +0 -83
  155. package/lib/components/list.d.ts.map +0 -1
  156. package/lib/components/list.js +0 -314
  157. package/lib/components/list.ts +0 -423
  158. package/lib/components/loading.d.ts +0 -25
  159. package/lib/components/loading.d.ts.map +0 -1
  160. package/lib/components/loading.js +0 -76
  161. package/lib/components/loading.ts +0 -104
  162. package/lib/components/menu.d.ts +0 -38
  163. package/lib/components/menu.d.ts.map +0 -1
  164. package/lib/components/menu.js +0 -205
  165. package/lib/components/menu.ts +0 -279
  166. package/lib/components/modal.d.ts +0 -97
  167. package/lib/components/modal.d.ts.map +0 -1
  168. package/lib/components/modal.js +0 -463
  169. package/lib/components/modal.ts +0 -576
  170. package/lib/components/nav.d.ts +0 -46
  171. package/lib/components/nav.d.ts.map +0 -1
  172. package/lib/components/nav.js +0 -193
  173. package/lib/components/nav.ts +0 -261
  174. package/lib/components/paragraph.d.ts +0 -30
  175. package/lib/components/paragraph.d.ts.map +0 -1
  176. package/lib/components/paragraph.js +0 -93
  177. package/lib/components/paragraph.ts +0 -123
  178. package/lib/components/pen.d.ts +0 -125
  179. package/lib/components/pen.d.ts.map +0 -1
  180. package/lib/components/pen.js +0 -443
  181. package/lib/components/pen.ts +0 -567
  182. package/lib/components/progress.d.ts +0 -40
  183. package/lib/components/progress.d.ts.map +0 -1
  184. package/lib/components/progress.js +0 -116
  185. package/lib/components/progress.ts +0 -163
  186. package/lib/components/radio.d.ts +0 -43
  187. package/lib/components/radio.d.ts.map +0 -1
  188. package/lib/components/radio.js +0 -226
  189. package/lib/components/radio.ts +0 -303
  190. package/lib/components/registry.d.ts +0 -34
  191. package/lib/components/registry.d.ts.map +0 -1
  192. package/lib/components/registry.js +0 -163
  193. package/lib/components/registry.ts +0 -193
  194. package/lib/components/req.d.ts +0 -155
  195. package/lib/components/req.d.ts.map +0 -1
  196. package/lib/components/req.js +0 -253
  197. package/lib/components/req.ts +0 -303
  198. package/lib/components/script.d.ts +0 -14
  199. package/lib/components/script.d.ts.map +0 -1
  200. package/lib/components/script.js +0 -33
  201. package/lib/components/script.ts +0 -41
  202. package/lib/components/select.d.ts +0 -42
  203. package/lib/components/select.d.ts.map +0 -1
  204. package/lib/components/select.js +0 -209
  205. package/lib/components/select.ts +0 -281
  206. package/lib/components/sidebar.d.ts +0 -59
  207. package/lib/components/sidebar.d.ts.map +0 -1
  208. package/lib/components/sidebar.js +0 -298
  209. package/lib/components/sidebar.ts +0 -395
  210. package/lib/components/stack/BaseStack.d.ts +0 -65
  211. package/lib/components/stack/BaseStack.d.ts.map +0 -1
  212. package/lib/components/stack/BaseStack.js +0 -274
  213. package/lib/components/stack/BaseStack.ts +0 -328
  214. package/lib/components/stack/HStack.d.ts +0 -18
  215. package/lib/components/stack/HStack.d.ts.map +0 -1
  216. package/lib/components/stack/HStack.js +0 -22
  217. package/lib/components/stack/HStack.ts +0 -25
  218. package/lib/components/stack/VStack.d.ts +0 -19
  219. package/lib/components/stack/VStack.d.ts.map +0 -1
  220. package/lib/components/stack/VStack.js +0 -23
  221. package/lib/components/stack/VStack.ts +0 -26
  222. package/lib/components/stack/ZStack.d.ts +0 -18
  223. package/lib/components/stack/ZStack.d.ts.map +0 -1
  224. package/lib/components/stack/ZStack.js +0 -22
  225. package/lib/components/stack/ZStack.ts +0 -25
  226. package/lib/components/style.d.ts +0 -14
  227. package/lib/components/style.d.ts.map +0 -1
  228. package/lib/components/style.js +0 -33
  229. package/lib/components/style.ts +0 -41
  230. package/lib/components/switch.d.ts +0 -34
  231. package/lib/components/switch.d.ts.map +0 -1
  232. package/lib/components/switch.js +0 -209
  233. package/lib/components/switch.ts +0 -272
  234. package/lib/components/table.d.ts +0 -137
  235. package/lib/components/table.d.ts.map +0 -1
  236. package/lib/components/table.js +0 -1019
  237. package/lib/components/table.ts +0 -1225
  238. package/lib/components/tabs.d.ts +0 -53
  239. package/lib/components/tabs.d.ts.map +0 -1
  240. package/lib/components/tabs.js +0 -275
  241. package/lib/components/tabs.ts +0 -349
  242. package/lib/components/theme-toggle.d.ts +0 -45
  243. package/lib/components/theme-toggle.d.ts.map +0 -1
  244. package/lib/components/theme-toggle.js +0 -218
  245. package/lib/components/theme-toggle.ts +0 -297
  246. package/lib/components/tooltip.d.ts +0 -31
  247. package/lib/components/tooltip.d.ts.map +0 -1
  248. package/lib/components/tooltip.js +0 -112
  249. package/lib/components/tooltip.ts +0 -148
  250. package/lib/components/watcher.d.ts +0 -195
  251. package/lib/components/watcher.d.ts.map +0 -1
  252. package/lib/components/watcher.js +0 -241
  253. package/lib/components/watcher.ts +0 -261
  254. package/lib/components/write.d.ts +0 -107
  255. package/lib/components/write.d.ts.map +0 -1
  256. package/lib/components/write.js +0 -222
  257. package/lib/components/write.ts +0 -272
  258. package/lib/data/DataPipeline.d.ts +0 -113
  259. package/lib/data/DataPipeline.d.ts.map +0 -1
  260. package/lib/data/DataPipeline.js +0 -359
  261. package/lib/data/DataPipeline.ts +0 -452
  262. package/lib/facades/dataframe.jux +0 -0
  263. package/lib/globals.d.ts +0 -21
  264. package/lib/reactivity/state.d.ts +0 -36
  265. package/lib/reactivity/state.d.ts.map +0 -1
  266. package/lib/reactivity/state.js +0 -67
  267. package/lib/reactivity/state.ts +0 -78
  268. package/lib/storage/DataFrame.d.ts +0 -284
  269. package/lib/storage/DataFrame.d.ts.map +0 -1
  270. package/lib/storage/DataFrame.js +0 -1022
  271. package/lib/storage/DataFrame.ts +0 -1195
  272. package/lib/storage/DataFrameSource.d.ts +0 -158
  273. package/lib/storage/DataFrameSource.d.ts.map +0 -1
  274. package/lib/storage/DataFrameSource.js +0 -409
  275. package/lib/storage/DataFrameSource.ts +0 -556
  276. package/lib/storage/FileStorage.d.ts +0 -53
  277. package/lib/storage/FileStorage.d.ts.map +0 -1
  278. package/lib/storage/FileStorage.js +0 -80
  279. package/lib/storage/FileStorage.ts +0 -95
  280. package/lib/storage/IndexedDBDriver.d.ts +0 -75
  281. package/lib/storage/IndexedDBDriver.d.ts.map +0 -1
  282. package/lib/storage/IndexedDBDriver.js +0 -177
  283. package/lib/storage/IndexedDBDriver.ts +0 -226
  284. package/lib/storage/TabularDriver.d.ts +0 -118
  285. package/lib/storage/TabularDriver.d.ts.map +0 -1
  286. package/lib/storage/TabularDriver.js +0 -731
  287. package/lib/storage/TabularDriver.ts +0 -874
  288. package/lib/utils/codeparser.d.ts +0 -29
  289. package/lib/utils/codeparser.d.ts.map +0 -1
  290. package/lib/utils/codeparser.js +0 -409
  291. package/lib/utils/fetch.d.ts +0 -176
  292. package/lib/utils/fetch.d.ts.map +0 -1
  293. package/lib/utils/fetch.js +0 -427
  294. package/lib/utils/formatId.d.ts +0 -16
  295. package/lib/utils/formatId.d.ts.map +0 -1
  296. package/lib/utils/formatId.js +0 -27
  297. package/lib/utils/path-resolver.js +0 -23
@@ -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
- }