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.
Files changed (297) hide show
  1. package/index.js +7 -137
  2. package/lib/components/dataframe.ts +0 -569
  3. package/lib/components/tag.ts +68 -0
  4. package/lib/styles/shadcn.css +20 -7
  5. package/lib/utils/idgen.ts +6 -0
  6. package/package.json +5 -6
  7. package/index.d.ts +0 -239
  8. package/index.d.ts.map +0 -1
  9. package/lib/components/alert.d.ts +0 -36
  10. package/lib/components/alert.d.ts.map +0 -1
  11. package/lib/components/alert.js +0 -172
  12. package/lib/components/alert.ts +0 -219
  13. package/lib/components/app.d.ts +0 -89
  14. package/lib/components/app.d.ts.map +0 -1
  15. package/lib/components/app.js +0 -175
  16. package/lib/components/app.ts +0 -247
  17. package/lib/components/badge.d.ts +0 -26
  18. package/lib/components/badge.d.ts.map +0 -1
  19. package/lib/components/badge.js +0 -91
  20. package/lib/components/badge.ts +0 -118
  21. package/lib/components/base/Animations.d.ts +0 -36
  22. package/lib/components/base/Animations.d.ts.map +0 -1
  23. package/lib/components/base/Animations.js +0 -70
  24. package/lib/components/base/Animations.ts +0 -112
  25. package/lib/components/base/BaseComponent.d.ts +0 -294
  26. package/lib/components/base/BaseComponent.d.ts.map +0 -1
  27. package/lib/components/base/BaseComponent.js +0 -735
  28. package/lib/components/base/BaseComponent.ts +0 -884
  29. package/lib/components/base/FormInput.d.ts +0 -77
  30. package/lib/components/base/FormInput.d.ts.map +0 -1
  31. package/lib/components/base/FormInput.js +0 -171
  32. package/lib/components/base/FormInput.ts +0 -237
  33. package/lib/components/blueprint.d.ts +0 -40
  34. package/lib/components/blueprint.d.ts.map +0 -1
  35. package/lib/components/blueprint.js +0 -327
  36. package/lib/components/button.d.ts +0 -70
  37. package/lib/components/button.d.ts.map +0 -1
  38. package/lib/components/button.js +0 -177
  39. package/lib/components/button.ts +0 -237
  40. package/lib/components/card.d.ts +0 -35
  41. package/lib/components/card.d.ts.map +0 -1
  42. package/lib/components/card.js +0 -130
  43. package/lib/components/card.ts +0 -177
  44. package/lib/components/chart.d.ts +0 -49
  45. package/lib/components/chart.d.ts.map +0 -1
  46. package/lib/components/chart.js +0 -205
  47. package/lib/components/chart.ts +0 -254
  48. package/lib/components/checkbox.d.ts +0 -33
  49. package/lib/components/checkbox.d.ts.map +0 -1
  50. package/lib/components/checkbox.js +0 -202
  51. package/lib/components/checkbox.ts +0 -260
  52. package/lib/components/code.d.ts +0 -52
  53. package/lib/components/code.d.ts.map +0 -1
  54. package/lib/components/code.js +0 -201
  55. package/lib/components/code.ts +0 -260
  56. package/lib/components/container.d.ts +0 -60
  57. package/lib/components/container.d.ts.map +0 -1
  58. package/lib/components/container.js +0 -195
  59. package/lib/components/container.ts +0 -259
  60. package/lib/components/data.d.ts +0 -36
  61. package/lib/components/data.d.ts.map +0 -1
  62. package/lib/components/data.js +0 -110
  63. package/lib/components/data.ts +0 -135
  64. package/lib/components/dataframe/DataFrameSource.d.ts +0 -118
  65. package/lib/components/dataframe/DataFrameSource.d.ts.map +0 -1
  66. package/lib/components/dataframe/DataFrameSource.js +0 -421
  67. package/lib/components/dataframe/DataFrameSource.ts +0 -532
  68. package/lib/components/dataframe/ImportSettingsModal.d.ts +0 -60
  69. package/lib/components/dataframe/ImportSettingsModal.d.ts.map +0 -1
  70. package/lib/components/dataframe/ImportSettingsModal.js +0 -442
  71. package/lib/components/dataframe/ImportSettingsModal.ts +0 -531
  72. package/lib/components/dataframe.d.ts +0 -110
  73. package/lib/components/dataframe.d.ts.map +0 -1
  74. package/lib/components/dataframe.js +0 -470
  75. package/lib/components/datepicker.d.ts +0 -40
  76. package/lib/components/datepicker.d.ts.map +0 -1
  77. package/lib/components/datepicker.js +0 -193
  78. package/lib/components/datepicker.ts +0 -251
  79. package/lib/components/dialog.d.ts +0 -39
  80. package/lib/components/dialog.d.ts.map +0 -1
  81. package/lib/components/dialog.js +0 -131
  82. package/lib/components/dialog.ts +0 -178
  83. package/lib/components/divider.d.ts +0 -31
  84. package/lib/components/divider.d.ts.map +0 -1
  85. package/lib/components/divider.js +0 -72
  86. package/lib/components/divider.ts +0 -104
  87. package/lib/components/dropdown-menu.d.ts +0 -42
  88. package/lib/components/dropdown-menu.d.ts.map +0 -1
  89. package/lib/components/dropdown-menu.js +0 -177
  90. package/lib/components/dropdown-menu.ts +0 -214
  91. package/lib/components/dropdown.d.ts +0 -41
  92. package/lib/components/dropdown.d.ts.map +0 -1
  93. package/lib/components/dropdown.js +0 -136
  94. package/lib/components/dropdown.ts +0 -188
  95. package/lib/components/element.d.ts +0 -51
  96. package/lib/components/element.d.ts.map +0 -1
  97. package/lib/components/element.js +0 -209
  98. package/lib/components/element.ts +0 -271
  99. package/lib/components/event-chain.d.ts +0 -9
  100. package/lib/components/event-chain.d.ts.map +0 -1
  101. package/lib/components/event-chain.js +0 -33
  102. package/lib/components/fileupload.d.ts +0 -98
  103. package/lib/components/fileupload.d.ts.map +0 -1
  104. package/lib/components/fileupload.js +0 -351
  105. package/lib/components/fileupload.ts +0 -449
  106. package/lib/components/grid.d.ts +0 -88
  107. package/lib/components/grid.d.ts.map +0 -1
  108. package/lib/components/grid.js +0 -208
  109. package/lib/components/grid.ts +0 -295
  110. package/lib/components/heading.d.ts +0 -25
  111. package/lib/components/heading.d.ts.map +0 -1
  112. package/lib/components/heading.js +0 -83
  113. package/lib/components/heading.ts +0 -113
  114. package/lib/components/helpers.d.ts +0 -9
  115. package/lib/components/helpers.d.ts.map +0 -1
  116. package/lib/components/helpers.js +0 -30
  117. package/lib/components/helpers.ts +0 -41
  118. package/lib/components/hero.d.ts +0 -60
  119. package/lib/components/hero.d.ts.map +0 -1
  120. package/lib/components/hero.js +0 -239
  121. package/lib/components/hero.ts +0 -302
  122. package/lib/components/history/StateHistory.d.ts +0 -91
  123. package/lib/components/history/StateHistory.d.ts.map +0 -1
  124. package/lib/components/history/StateHistory.js +0 -154
  125. package/lib/components/history/StateHistory.ts +0 -200
  126. package/lib/components/icon.d.ts +0 -36
  127. package/lib/components/icon.d.ts.map +0 -1
  128. package/lib/components/icon.js +0 -135
  129. package/lib/components/icon.ts +0 -182
  130. package/lib/components/icons.d.ts +0 -25
  131. package/lib/components/icons.d.ts.map +0 -1
  132. package/lib/components/icons.js +0 -440
  133. package/lib/components/icons.ts +0 -464
  134. package/lib/components/image.d.ts +0 -42
  135. package/lib/components/image.d.ts.map +0 -1
  136. package/lib/components/image.js +0 -204
  137. package/lib/components/image.ts +0 -260
  138. package/lib/components/include.d.ts +0 -86
  139. package/lib/components/include.d.ts.map +0 -1
  140. package/lib/components/include.js +0 -238
  141. package/lib/components/include.ts +0 -281
  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
- }