juxscript 1.0.18 → 1.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/alert.ts +124 -128
- package/lib/components/areachart.ts +169 -287
- package/lib/components/areachartsmooth.ts +2 -2
- package/lib/components/badge.ts +63 -72
- package/lib/components/barchart.ts +120 -48
- package/lib/components/button.ts +99 -101
- package/lib/components/card.ts +97 -121
- package/lib/components/chart-types.ts +159 -0
- package/lib/components/chart-utils.ts +160 -0
- package/lib/components/chart.ts +628 -48
- package/lib/components/checkbox.ts +137 -51
- package/lib/components/code.ts +89 -75
- package/lib/components/container.ts +1 -1
- package/lib/components/datepicker.ts +93 -78
- package/lib/components/dialog.ts +163 -130
- package/lib/components/divider.ts +111 -193
- package/lib/components/docs-data.json +711 -264
- package/lib/components/doughnutchart.ts +125 -57
- package/lib/components/dropdown.ts +172 -85
- package/lib/components/element.ts +66 -61
- package/lib/components/fileupload.ts +142 -171
- package/lib/components/heading.ts +64 -21
- package/lib/components/hero.ts +109 -34
- package/lib/components/icon.ts +247 -0
- package/lib/components/icons.ts +174 -0
- package/lib/components/include.ts +77 -2
- package/lib/components/input.ts +174 -125
- package/lib/components/list.ts +120 -79
- package/lib/components/menu.ts +97 -2
- package/lib/components/modal.ts +144 -63
- package/lib/components/nav.ts +153 -52
- package/lib/components/paragraph.ts +78 -28
- package/lib/components/progress.ts +83 -107
- package/lib/components/radio.ts +151 -52
- package/lib/components/select.ts +110 -102
- package/lib/components/sidebar.ts +148 -105
- package/lib/components/switch.ts +124 -125
- package/lib/components/table.ts +214 -137
- package/lib/components/tabs.ts +194 -113
- package/lib/components/theme-toggle.ts +38 -7
- package/lib/components/tooltip.ts +207 -47
- package/lib/jux.ts +24 -5
- package/lib/reactivity/state.ts +13 -299
- package/package.json +1 -2
package/lib/components/table.ts
CHANGED
|
@@ -1,71 +1,63 @@
|
|
|
1
1
|
import { getOrCreateContainer } from './helpers.js';
|
|
2
|
+
import { State } from '../reactivity/state.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
+
* Column definition
|
|
5
6
|
*/
|
|
6
|
-
export interface
|
|
7
|
+
export interface ColumnDef {
|
|
7
8
|
key: string;
|
|
8
9
|
label: string;
|
|
9
10
|
width?: string;
|
|
10
|
-
|
|
11
|
-
renderCell?: (value: any, row: any) => string | HTMLElement;
|
|
11
|
+
render?: (value: any, row: any) => string | HTMLElement;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Table
|
|
15
|
+
* Table options
|
|
16
16
|
*/
|
|
17
17
|
export interface TableOptions {
|
|
18
|
-
columns?:
|
|
19
|
-
|
|
18
|
+
columns?: (string | ColumnDef)[];
|
|
19
|
+
rows?: any[][];
|
|
20
|
+
headers?: boolean;
|
|
20
21
|
striped?: boolean;
|
|
21
22
|
hoverable?: boolean;
|
|
22
23
|
bordered?: boolean;
|
|
23
|
-
|
|
24
|
+
compact?: boolean;
|
|
24
25
|
style?: string;
|
|
25
26
|
class?: string;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
|
-
* Table
|
|
30
|
+
* Table state
|
|
30
31
|
*/
|
|
31
32
|
type TableState = {
|
|
32
|
-
columns:
|
|
33
|
-
|
|
33
|
+
columns: ColumnDef[];
|
|
34
|
+
rows: any[][];
|
|
35
|
+
headers: boolean;
|
|
34
36
|
striped: boolean;
|
|
35
37
|
hoverable: boolean;
|
|
36
38
|
bordered: boolean;
|
|
37
|
-
|
|
39
|
+
compact: boolean;
|
|
38
40
|
style: string;
|
|
39
41
|
class: string;
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
/**
|
|
43
45
|
* Table component
|
|
44
|
-
*
|
|
46
|
+
*
|
|
45
47
|
* Usage:
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* columns: [
|
|
60
|
-
* { key: 'name', label: 'Name' },
|
|
61
|
-
* { key: 'age', label: 'Age', align: 'center' }
|
|
62
|
-
* ],
|
|
63
|
-
* data: [
|
|
64
|
-
* { name: 'Alice', age: 30 },
|
|
65
|
-
* { name: 'Bob', age: 25 }
|
|
66
|
-
* ]
|
|
67
|
-
* });
|
|
68
|
-
* table.render();
|
|
48
|
+
* jux.table('users', {
|
|
49
|
+
* columns: ['Name', 'Email', 'Role'],
|
|
50
|
+
* rows: [['John', 'john@example.com', 'Admin']]
|
|
51
|
+
* }).render('#app');
|
|
52
|
+
*
|
|
53
|
+
* // With state binding
|
|
54
|
+
* const todos = state([]);
|
|
55
|
+
* jux.table('todos-table')
|
|
56
|
+
* .columns(['ID', 'Text', 'Done'])
|
|
57
|
+
* .sync('rows', todos, (items) =>
|
|
58
|
+
* items.map(item => [item.id, item.text, item.done])
|
|
59
|
+
* )
|
|
60
|
+
* .render('#app');
|
|
69
61
|
*/
|
|
70
62
|
export class Table {
|
|
71
63
|
state: TableState;
|
|
@@ -73,17 +65,35 @@ export class Table {
|
|
|
73
65
|
_id: string;
|
|
74
66
|
id: string;
|
|
75
67
|
|
|
68
|
+
// CRITICAL: Store bind/sync instructions for deferred wiring
|
|
69
|
+
private _bindings: Array<{ event: string, handler: Function }> = [];
|
|
70
|
+
private _syncBindings: Array<{
|
|
71
|
+
property: string,
|
|
72
|
+
stateObj: State<any>,
|
|
73
|
+
toState?: Function,
|
|
74
|
+
toComponent?: Function
|
|
75
|
+
}> = [];
|
|
76
|
+
|
|
77
|
+
// Store table element reference
|
|
78
|
+
private _tableElement: HTMLTableElement | null = null;
|
|
79
|
+
|
|
76
80
|
constructor(id: string, options: TableOptions = {}) {
|
|
77
81
|
this._id = id;
|
|
78
82
|
this.id = id;
|
|
79
83
|
|
|
84
|
+
// Normalize columns to ColumnDef format
|
|
85
|
+
const normalizedColumns = (options.columns ?? []).map(col =>
|
|
86
|
+
typeof col === 'string' ? { key: col, label: col } : col
|
|
87
|
+
);
|
|
88
|
+
|
|
80
89
|
this.state = {
|
|
81
|
-
columns:
|
|
82
|
-
|
|
90
|
+
columns: normalizedColumns,
|
|
91
|
+
rows: options.rows ?? [],
|
|
92
|
+
headers: options.headers ?? true,
|
|
83
93
|
striped: options.striped ?? false,
|
|
84
|
-
hoverable: options.hoverable ??
|
|
94
|
+
hoverable: options.hoverable ?? false,
|
|
85
95
|
bordered: options.bordered ?? false,
|
|
86
|
-
|
|
96
|
+
compact: options.compact ?? false,
|
|
87
97
|
style: options.style ?? '',
|
|
88
98
|
class: options.class ?? ''
|
|
89
99
|
};
|
|
@@ -93,13 +103,21 @@ export class Table {
|
|
|
93
103
|
* Fluent API
|
|
94
104
|
* ------------------------- */
|
|
95
105
|
|
|
96
|
-
columns(value:
|
|
97
|
-
this.state.columns = value
|
|
106
|
+
columns(value: (string | ColumnDef)[]): this {
|
|
107
|
+
this.state.columns = value.map(col =>
|
|
108
|
+
typeof col === 'string' ? { key: col, label: col } : col
|
|
109
|
+
);
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
rows(value: any[][]): this {
|
|
114
|
+
this.state.rows = value;
|
|
115
|
+
this._updateTable();
|
|
98
116
|
return this;
|
|
99
117
|
}
|
|
100
118
|
|
|
101
|
-
|
|
102
|
-
this.state.
|
|
119
|
+
headers(value: boolean): this {
|
|
120
|
+
this.state.headers = value;
|
|
103
121
|
return this;
|
|
104
122
|
}
|
|
105
123
|
|
|
@@ -118,8 +136,8 @@ export class Table {
|
|
|
118
136
|
return this;
|
|
119
137
|
}
|
|
120
138
|
|
|
121
|
-
|
|
122
|
-
this.state.
|
|
139
|
+
compact(value: boolean): this {
|
|
140
|
+
this.state.compact = value;
|
|
123
141
|
return this;
|
|
124
142
|
}
|
|
125
143
|
|
|
@@ -133,44 +151,53 @@ export class Table {
|
|
|
133
151
|
return this;
|
|
134
152
|
}
|
|
135
153
|
|
|
136
|
-
|
|
137
|
-
*
|
|
138
|
-
*
|
|
154
|
+
/**
|
|
155
|
+
* Bind event handler (stores for wiring in render)
|
|
156
|
+
* DOM events only: click, mouseenter, etc.
|
|
157
|
+
*/
|
|
158
|
+
bind(event: string, handler: Function): this {
|
|
159
|
+
this._bindings.push({ event, handler });
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
139
162
|
|
|
140
163
|
/**
|
|
141
|
-
*
|
|
164
|
+
* Sync with state (one-way: State → Component)
|
|
165
|
+
*
|
|
166
|
+
* @param property - Component property to sync ('rows', 'columns')
|
|
167
|
+
* @param stateObj - State object to sync with
|
|
168
|
+
* @param transform - Optional transform function from state to component
|
|
142
169
|
*/
|
|
143
|
-
|
|
144
|
-
if (!
|
|
145
|
-
|
|
170
|
+
sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
|
|
171
|
+
if (!stateObj || typeof stateObj.subscribe !== 'function') {
|
|
172
|
+
throw new Error(`Table.sync: Expected a State object for property "${property}"`);
|
|
146
173
|
}
|
|
174
|
+
this._syncBindings.push({ property, stateObj, toState, toComponent });
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* -------------------------
|
|
179
|
+
* Helpers
|
|
180
|
+
* ------------------------- */
|
|
147
181
|
|
|
148
|
-
|
|
149
|
-
|
|
182
|
+
private _updateTable(): void {
|
|
183
|
+
if (!this._tableElement) return;
|
|
150
184
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
label: this._formatLabel(key)
|
|
154
|
-
}));
|
|
155
|
-
}
|
|
185
|
+
const tbody = this._tableElement.querySelector('tbody');
|
|
186
|
+
if (!tbody) return;
|
|
156
187
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return words
|
|
172
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
173
|
-
.join(' ');
|
|
188
|
+
// Clear existing rows
|
|
189
|
+
tbody.innerHTML = '';
|
|
190
|
+
|
|
191
|
+
// Add new rows
|
|
192
|
+
this.state.rows.forEach(rowData => {
|
|
193
|
+
const tr = document.createElement('tr');
|
|
194
|
+
rowData.forEach(cellData => {
|
|
195
|
+
const td = document.createElement('td');
|
|
196
|
+
td.textContent = String(cellData);
|
|
197
|
+
tr.appendChild(td);
|
|
198
|
+
});
|
|
199
|
+
tbody.appendChild(tr);
|
|
200
|
+
});
|
|
174
201
|
}
|
|
175
202
|
|
|
176
203
|
/* -------------------------
|
|
@@ -178,125 +205,175 @@ export class Table {
|
|
|
178
205
|
* ------------------------- */
|
|
179
206
|
|
|
180
207
|
render(targetId?: string): this {
|
|
208
|
+
// === 1. SETUP: Get or create container ===
|
|
181
209
|
let container: HTMLElement;
|
|
182
|
-
|
|
183
210
|
if (targetId) {
|
|
184
211
|
const target = document.querySelector(targetId);
|
|
185
212
|
if (!target || !(target instanceof HTMLElement)) {
|
|
186
|
-
throw new Error(`Table: Target
|
|
213
|
+
throw new Error(`Table: Target "${targetId}" not found`);
|
|
187
214
|
}
|
|
188
215
|
container = target;
|
|
189
216
|
} else {
|
|
190
217
|
container = getOrCreateContainer(this._id);
|
|
191
218
|
}
|
|
192
|
-
|
|
193
219
|
this.container = container;
|
|
194
|
-
let { columns, data, striped, hoverable, bordered, allowHtml, style, class: className } = this.state;
|
|
195
220
|
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
columns = this._autoGenerateColumns(data);
|
|
199
|
-
}
|
|
221
|
+
// === 2. PREPARE: Destructure state and check sync flags ===
|
|
222
|
+
const { columns, rows, striped, hoverable, bordered, style, class: className } = this.state;
|
|
200
223
|
|
|
224
|
+
// === 3. BUILD: Create DOM elements ===
|
|
201
225
|
const wrapper = document.createElement('div');
|
|
202
226
|
wrapper.className = 'jux-table-wrapper';
|
|
203
227
|
wrapper.id = this._id;
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
wrapper.className += ` ${className}`;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (style) {
|
|
210
|
-
wrapper.setAttribute('style', style);
|
|
211
|
-
}
|
|
228
|
+
if (className) wrapper.className += ` ${className}`;
|
|
229
|
+
if (style) wrapper.setAttribute('style', style);
|
|
212
230
|
|
|
213
231
|
const table = document.createElement('table');
|
|
214
232
|
table.className = 'jux-table';
|
|
215
|
-
|
|
216
233
|
if (striped) table.classList.add('jux-table-striped');
|
|
217
234
|
if (hoverable) table.classList.add('jux-table-hoverable');
|
|
218
235
|
if (bordered) table.classList.add('jux-table-bordered');
|
|
219
236
|
|
|
220
|
-
//
|
|
237
|
+
// Header
|
|
221
238
|
const thead = document.createElement('thead');
|
|
222
239
|
const headerRow = document.createElement('tr');
|
|
223
|
-
|
|
224
240
|
columns.forEach(col => {
|
|
225
241
|
const th = document.createElement('th');
|
|
226
242
|
th.textContent = col.label;
|
|
227
243
|
if (col.width) th.style.width = col.width;
|
|
228
|
-
if (col.align) th.style.textAlign = col.align;
|
|
229
244
|
headerRow.appendChild(th);
|
|
230
245
|
});
|
|
231
|
-
|
|
232
246
|
thead.appendChild(headerRow);
|
|
233
247
|
table.appendChild(thead);
|
|
234
248
|
|
|
235
|
-
//
|
|
249
|
+
// Body
|
|
236
250
|
const tbody = document.createElement('tbody');
|
|
237
|
-
|
|
238
|
-
data.forEach(row => {
|
|
251
|
+
rows.forEach(row => {
|
|
239
252
|
const tr = document.createElement('tr');
|
|
240
|
-
|
|
241
253
|
columns.forEach(col => {
|
|
242
254
|
const td = document.createElement('td');
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const cellValue = row[col.key] ?? '';
|
|
255
|
+
const cellValue = row[col.key];
|
|
246
256
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
257
|
+
if (col.render) {
|
|
258
|
+
const rendered = col.render(cellValue, row);
|
|
259
|
+
if (typeof rendered === 'string') {
|
|
260
|
+
td.innerHTML = rendered;
|
|
261
|
+
} else {
|
|
251
262
|
td.appendChild(rendered);
|
|
252
|
-
} else if (typeof rendered === 'string') {
|
|
253
|
-
if (allowHtml) {
|
|
254
|
-
td.innerHTML = rendered;
|
|
255
|
-
} else {
|
|
256
|
-
td.textContent = rendered;
|
|
257
|
-
}
|
|
258
263
|
}
|
|
259
264
|
} else {
|
|
260
|
-
|
|
261
|
-
if (allowHtml && typeof cellValue === 'string' && cellValue.includes('<')) {
|
|
262
|
-
td.innerHTML = cellValue;
|
|
263
|
-
} else {
|
|
264
|
-
td.textContent = String(cellValue);
|
|
265
|
-
}
|
|
265
|
+
td.textContent = cellValue != null ? String(cellValue) : '';
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
tr.appendChild(td);
|
|
269
269
|
});
|
|
270
|
-
|
|
271
270
|
tbody.appendChild(tr);
|
|
272
271
|
});
|
|
273
|
-
|
|
274
272
|
table.appendChild(tbody);
|
|
275
273
|
wrapper.appendChild(table);
|
|
276
|
-
container.appendChild(wrapper);
|
|
277
274
|
|
|
275
|
+
// === 4. WIRE: Attach event listeners and sync bindings ===
|
|
276
|
+
|
|
277
|
+
// Wire custom bindings from .bind() calls
|
|
278
|
+
this._bindings.forEach(({ event, handler }) => {
|
|
279
|
+
wrapper.addEventListener(event, handler as EventListener);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Wire sync bindings from .sync() calls
|
|
283
|
+
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
284
|
+
if (property === 'rows' || property === 'data') {
|
|
285
|
+
const transformToComponent = toComponent || ((v: any) => v);
|
|
286
|
+
|
|
287
|
+
stateObj.subscribe((val: any) => {
|
|
288
|
+
const transformed = transformToComponent(val);
|
|
289
|
+
this.state.rows = transformed;
|
|
290
|
+
|
|
291
|
+
// Re-render tbody
|
|
292
|
+
tbody.innerHTML = '';
|
|
293
|
+
transformed.forEach((row: any) => {
|
|
294
|
+
const tr = document.createElement('tr');
|
|
295
|
+
this.state.columns.forEach(col => {
|
|
296
|
+
const td = document.createElement('td');
|
|
297
|
+
const cellValue = row[col.key];
|
|
298
|
+
|
|
299
|
+
if (col.render) {
|
|
300
|
+
const rendered = col.render(cellValue, row);
|
|
301
|
+
if (typeof rendered === 'string') {
|
|
302
|
+
td.innerHTML = rendered;
|
|
303
|
+
} else {
|
|
304
|
+
td.appendChild(rendered);
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
td.textContent = cellValue != null ? String(cellValue) : '';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
tr.appendChild(td);
|
|
311
|
+
});
|
|
312
|
+
tbody.appendChild(tr);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
else if (property === 'columns') {
|
|
317
|
+
const transformToComponent = toComponent || ((v: any) => v);
|
|
318
|
+
|
|
319
|
+
stateObj.subscribe((val: any) => {
|
|
320
|
+
const transformed = transformToComponent(val);
|
|
321
|
+
this.state.columns = transformed;
|
|
322
|
+
|
|
323
|
+
// Re-render entire table
|
|
324
|
+
table.innerHTML = '';
|
|
325
|
+
|
|
326
|
+
const thead = document.createElement('thead');
|
|
327
|
+
const headerRow = document.createElement('tr');
|
|
328
|
+
transformed.forEach((col: any) => {
|
|
329
|
+
const th = document.createElement('th');
|
|
330
|
+
th.textContent = col.label;
|
|
331
|
+
if (col.width) th.style.width = col.width;
|
|
332
|
+
headerRow.appendChild(th);
|
|
333
|
+
});
|
|
334
|
+
thead.appendChild(headerRow);
|
|
335
|
+
table.appendChild(thead);
|
|
336
|
+
|
|
337
|
+
const tbody = document.createElement('tbody');
|
|
338
|
+
this.state.rows.forEach(row => {
|
|
339
|
+
const tr = document.createElement('tr');
|
|
340
|
+
transformed.forEach((col: any) => {
|
|
341
|
+
const td = document.createElement('td');
|
|
342
|
+
const cellValue = row[col.key];
|
|
343
|
+
|
|
344
|
+
if (col.render) {
|
|
345
|
+
const rendered = col.render(cellValue, row);
|
|
346
|
+
if (typeof rendered === 'string') {
|
|
347
|
+
td.innerHTML = rendered;
|
|
348
|
+
} else {
|
|
349
|
+
td.appendChild(rendered);
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
td.textContent = cellValue != null ? String(cellValue) : '';
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
tr.appendChild(td);
|
|
356
|
+
});
|
|
357
|
+
tbody.appendChild(tr);
|
|
358
|
+
});
|
|
359
|
+
table.appendChild(tbody);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// === 5. RENDER: Append to DOM and finalize ===
|
|
365
|
+
container.appendChild(wrapper);
|
|
278
366
|
return this;
|
|
279
367
|
}
|
|
280
368
|
|
|
281
|
-
/**
|
|
282
|
-
* Render to another Jux component's container
|
|
283
|
-
*/
|
|
284
369
|
renderTo(juxComponent: any): this {
|
|
285
|
-
if (!juxComponent
|
|
286
|
-
throw new Error('Table.renderTo: Invalid component
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (!juxComponent._id || typeof juxComponent._id !== 'string') {
|
|
290
|
-
throw new Error('Table.renderTo: Invalid component - missing _id (not a Jux component)');
|
|
370
|
+
if (!juxComponent?._id) {
|
|
371
|
+
throw new Error('Table.renderTo: Invalid component');
|
|
291
372
|
}
|
|
292
|
-
|
|
293
373
|
return this.render(`#${juxComponent._id}`);
|
|
294
374
|
}
|
|
295
375
|
}
|
|
296
376
|
|
|
297
|
-
/**
|
|
298
|
-
* Factory helper
|
|
299
|
-
*/
|
|
300
377
|
export function table(id: string, options: TableOptions = {}): Table {
|
|
301
378
|
return new Table(id, options);
|
|
302
379
|
}
|