juxscript 1.1.2 → 1.1.4

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