juxscript 1.1.4 → 1.1.6

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