juxscript 1.0.19 → 1.0.21

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 (77) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +212 -165
  3. package/lib/components/badge.ts +93 -103
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +63 -122
  7. package/lib/components/card.ts +109 -155
  8. package/lib/components/charts/areachart.ts +315 -0
  9. package/lib/components/charts/barchart.ts +421 -0
  10. package/lib/components/charts/doughnutchart.ts +263 -0
  11. package/lib/components/charts/lib/BaseChart.ts +402 -0
  12. package/lib/components/charts/lib/chart-types.ts +159 -0
  13. package/lib/components/charts/lib/chart-utils.ts +160 -0
  14. package/lib/components/charts/lib/chart.ts +707 -0
  15. package/lib/components/checkbox.ts +264 -127
  16. package/lib/components/code.ts +75 -108
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +195 -147
  20. package/lib/components/dialog.ts +187 -157
  21. package/lib/components/divider.ts +85 -191
  22. package/lib/components/docs-data.json +544 -2027
  23. package/lib/components/dropdown.ts +178 -136
  24. package/lib/components/element.ts +227 -171
  25. package/lib/components/fileupload.ts +285 -228
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +46 -69
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +107 -95
  30. package/lib/components/icon.ts +160 -0
  31. package/lib/components/icons.ts +175 -0
  32. package/lib/components/include.ts +153 -5
  33. package/lib/components/input.ts +174 -374
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +378 -240
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +103 -97
  38. package/lib/components/modal.ts +138 -144
  39. package/lib/components/nav.ts +169 -90
  40. package/lib/components/paragraph.ts +49 -150
  41. package/lib/components/progress.ts +118 -200
  42. package/lib/components/radio.ts +297 -149
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +184 -186
  45. package/lib/components/sidebar.ts +152 -140
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +258 -188
  48. package/lib/components/table.ts +1117 -170
  49. package/lib/components/tabs.ts +162 -145
  50. package/lib/components/theme-toggle.ts +108 -169
  51. package/lib/components/tooltip.ts +86 -157
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +86 -41
  54. package/machinery/build.js +466 -0
  55. package/machinery/compiler.js +354 -105
  56. package/machinery/server.js +23 -100
  57. package/machinery/watcher.js +153 -130
  58. package/package.json +1 -2
  59. package/presets/base.css +1166 -0
  60. package/presets/notion.css +2 -1975
  61. package/lib/adapters/base-adapter.js +0 -35
  62. package/lib/adapters/index.js +0 -33
  63. package/lib/adapters/mysql-adapter.js +0 -65
  64. package/lib/adapters/postgres-adapter.js +0 -70
  65. package/lib/adapters/sqlite-adapter.js +0 -56
  66. package/lib/components/areachart.ts +0 -1246
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1250
  69. package/lib/components/chart.ts +0 -127
  70. package/lib/components/doughnutchart.ts +0 -1191
  71. package/lib/components/footer.ts +0 -165
  72. package/lib/components/header.ts +0 -187
  73. package/lib/components/layout.ts +0 -239
  74. package/lib/components/main.ts +0 -137
  75. package/lib/layouts/default.jux +0 -8
  76. package/lib/layouts/figma.jux +0 -0
  77. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -1,401 +1,539 @@
1
- import { getOrCreateContainer } from './helpers.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+ import { renderIcon } from './icons.js';
3
+
4
+ // Event definitions
5
+ const TRIGGER_EVENTS = [] as const;
6
+ const CALLBACK_EVENTS = ['itemClick', 'itemDoubleClick', 'selectionChange'] as const;
2
7
 
3
- /**
4
- * List item interface
5
- */
6
8
  export interface ListItem {
9
+ id?: string;
7
10
  icon?: string;
8
11
  title?: string;
9
12
  body?: string;
10
13
  type?: 'success' | 'warning' | 'error' | 'info' | 'default' | string;
11
14
  metadata?: string;
15
+ itemClass?: string;
16
+ disabled?: boolean;
17
+ selected?: boolean;
18
+ data?: any; // Arbitrary data attached to item
12
19
  }
13
20
 
14
- /**
15
- * List component options
16
- */
17
21
  export interface ListOptions {
18
22
  items?: ListItem[];
19
23
  header?: string;
20
24
  gap?: string;
21
25
  direction?: 'vertical' | 'horizontal';
22
26
  selectable?: boolean;
27
+ multiSelect?: boolean;
23
28
  selectedIndex?: number | null;
29
+ selectedIndices?: number[];
24
30
  onItemClick?: (item: ListItem, index: number, e: Event) => void;
25
31
  onItemDoubleClick?: (item: ListItem, index: number, e: Event) => void;
32
+ onSelectionChange?: (selectedItems: ListItem[], selectedIndices: number[]) => void;
33
+ ordered?: boolean;
34
+ striped?: boolean;
35
+ hoverable?: boolean;
36
+ bordered?: boolean;
26
37
  style?: string;
27
38
  class?: string;
28
39
  }
29
40
 
30
- /**
31
- * List component state
32
- */
33
41
  type ListState = {
34
42
  items: ListItem[];
35
43
  header: string;
36
44
  gap: string;
37
45
  direction: string;
38
46
  selectable: boolean;
47
+ multiSelect: boolean;
39
48
  selectedIndex: number | null;
49
+ selectedIndices: number[];
50
+ ordered: boolean;
51
+ striped: boolean;
52
+ hoverable: boolean;
53
+ bordered: boolean;
40
54
  style: string;
41
55
  class: string;
42
56
  };
43
57
 
44
- /**
45
- * List component - renders a list of items with optional header
46
- *
47
- * Usage:
48
- * const myList = jux.list('myList', {
49
- * header: '✓ Accomplishments',
50
- * items: [
51
- * { icon: '✓', title: 'Task 1', body: 'Description', type: 'success' },
52
- * { icon: '⚠️', title: 'Task 2', body: 'Description', type: 'warning' }
53
- * ],
54
- * gap: '0.75rem',
55
- * selectable: true,
56
- * onItemClick: (item, index) => console.log('Clicked:', item, index)
57
- * });
58
- * myList.render();
59
- *
60
- * // Add item
61
- * myList.add({ icon: '🎉', title: 'New Task', body: 'Done!', type: 'success' });
62
- *
63
- * // Remove item by index
64
- * myList.remove(1);
65
- *
66
- * // Move item from index 0 to index 2
67
- * myList.move(0, 2);
68
- */
69
- export class List {
70
- state: ListState;
71
- container: HTMLElement | null = null;
72
- _id: string;
73
- id: string;
74
- private _onItemClick: ((item: ListItem, index: number, e: Event) => void) | null;
75
- private _onItemDoubleClick: ((item: ListItem, index: number, e: Event) => void) | null;
58
+ export class List extends BaseComponent<ListState> {
59
+ private _onItemClick: ((item: ListItem, index: number, e: Event) => void) | null = null;
60
+ private _onItemDoubleClick: ((item: ListItem, index: number, e: Event) => void) | null = null;
61
+ private _onSelectionChange: ((selectedItems: ListItem[], selectedIndices: number[]) => void) | null = null;
62
+ private _listElement: HTMLElement | null = null;
76
63
 
77
64
  constructor(id: string, options: ListOptions = {}) {
78
- this._id = id;
79
- this.id = id;
80
-
81
- this.state = {
65
+ super(id, {
82
66
  items: options.items ?? [],
83
67
  header: options.header ?? '',
84
68
  gap: options.gap ?? '0.5rem',
85
69
  direction: options.direction ?? 'vertical',
86
70
  selectable: options.selectable ?? false,
71
+ multiSelect: options.multiSelect ?? false,
87
72
  selectedIndex: options.selectedIndex ?? null,
73
+ selectedIndices: options.selectedIndices ?? [],
74
+ ordered: options.ordered ?? false,
75
+ striped: options.striped ?? false,
76
+ hoverable: options.hoverable ?? true,
77
+ bordered: options.bordered ?? false,
88
78
  style: options.style ?? '',
89
79
  class: options.class ?? ''
90
- };
80
+ });
91
81
 
92
82
  this._onItemClick = options.onItemClick ?? null;
93
83
  this._onItemDoubleClick = options.onItemDoubleClick ?? null;
84
+ this._onSelectionChange = options.onSelectionChange ?? null;
94
85
  }
95
86
 
96
- /* -------------------------
97
- * Fluent API
98
- * ------------------------- */
87
+ protected getTriggerEvents(): readonly string[] {
88
+ return TRIGGER_EVENTS;
89
+ }
90
+
91
+ protected getCallbackEvents(): readonly string[] {
92
+ return CALLBACK_EVENTS;
93
+ }
94
+
95
+ /* ═════════════════════════════════════════════════════════════════
96
+ * FLUENT API
97
+ * ═════════════════════════════════════════════════════════════════ */
98
+
99
+ // ✅ Inherited from BaseComponent
99
100
 
100
101
  items(value: ListItem[]): this {
101
102
  this.state.items = value;
103
+ this._updateList();
102
104
  return this;
103
105
  }
104
106
 
105
- header(value: string): this {
106
- this.state.header = value;
107
+ addItem(value: ListItem): this {
108
+ this.state.items = [...this.state.items, value];
109
+ this._updateList();
107
110
  return this;
108
111
  }
109
112
 
110
- gap(value: string): this {
111
- this.state.gap = value;
113
+ removeItem(index: number): this {
114
+ this.state.items = this.state.items.filter((_, i) => i !== index);
115
+ this._updateList();
112
116
  return this;
113
117
  }
114
118
 
115
- direction(value: 'vertical' | 'horizontal'): this {
116
- this.state.direction = value;
119
+ updateItem(index: number, updates: Partial<ListItem>): this {
120
+ this.state.items = this.state.items.map((item, i) =>
121
+ i === index ? { ...item, ...updates } : item
122
+ );
123
+ this._updateList();
117
124
  return this;
118
125
  }
119
126
 
120
- selectable(value: boolean): this {
121
- this.state.selectable = value;
127
+ clearItems(): this {
128
+ this.state.items = [];
129
+ this._updateList();
122
130
  return this;
123
131
  }
124
132
 
125
- style(value: string): this {
126
- this.state.style = value;
133
+ itemClass(className: string): this {
134
+ this.state.items = this.state.items.map(item => ({
135
+ ...item,
136
+ itemClass: className
137
+ }));
138
+ this._updateList();
127
139
  return this;
128
140
  }
129
141
 
130
- class(value: string): this {
131
- this.state.class = value;
142
+ ordered(value: boolean): this {
143
+ this.state.ordered = value;
132
144
  return this;
133
145
  }
134
146
 
135
- /* -------------------------
136
- * List operations
137
- * ------------------------- */
138
-
139
- add(item: ListItem, index?: number): this {
140
- const items = [...this.state.items];
141
-
142
- if (typeof index === 'number' && index >= 0 && index <= items.length) {
143
- items.splice(index, 0, item);
144
- } else {
145
- index = items.length;
146
- items.push(item);
147
- }
148
-
149
- this.state.items = items;
150
- this._updateDOM();
147
+ selectable(value: boolean): this {
148
+ this.state.selectable = value;
151
149
  return this;
152
150
  }
153
151
 
154
- remove(index: number): this {
155
- if (typeof index !== 'number' || index < 0 || index >= this.state.items.length) {
156
- console.error(`List: Invalid index ${index} for remove`);
157
- return this;
158
- }
159
-
160
- const items = [...this.state.items];
161
- items.splice(index, 1);
162
-
163
- // Adjust selected index
164
- if (this.state.selectedIndex !== null) {
165
- if (this.state.selectedIndex === index) {
166
- this.state.selectedIndex = null;
167
- } else if (this.state.selectedIndex > index) {
168
- this.state.selectedIndex--;
169
- }
170
- }
171
-
172
- this.state.items = items;
173
- this._updateDOM();
152
+ multiSelect(value: boolean): this {
153
+ this.state.multiSelect = value;
174
154
  return this;
175
155
  }
176
156
 
177
- move(fromIndex: number, toIndex: number): this {
178
- const items = [...this.state.items];
157
+ striped(value: boolean): this {
158
+ this.state.striped = value;
159
+ return this;
160
+ }
179
161
 
180
- if (fromIndex < 0 || fromIndex >= items.length) {
181
- console.error(`List: Invalid fromIndex ${fromIndex}`);
182
- return this;
183
- }
162
+ hoverable(value: boolean): this {
163
+ this.state.hoverable = value;
164
+ return this;
165
+ }
184
166
 
185
- if (toIndex < 0 || toIndex >= items.length) {
186
- console.error(`List: Invalid toIndex ${toIndex}`);
187
- return this;
188
- }
167
+ bordered(value: boolean): this {
168
+ this.state.bordered = value;
169
+ return this;
170
+ }
189
171
 
190
- if (fromIndex === toIndex) {
191
- return this;
192
- }
172
+ selectItem(index: number): this {
173
+ if (!this.state.selectable) return this;
193
174
 
194
- const [movedItem] = items.splice(fromIndex, 1);
195
- items.splice(toIndex, 0, movedItem);
196
-
197
- // Adjust selected index
198
- if (this.state.selectedIndex !== null) {
199
- if (this.state.selectedIndex === fromIndex) {
200
- this.state.selectedIndex = toIndex;
201
- } else if (fromIndex < this.state.selectedIndex && toIndex >= this.state.selectedIndex) {
202
- this.state.selectedIndex--;
203
- } else if (fromIndex > this.state.selectedIndex && toIndex <= this.state.selectedIndex) {
204
- this.state.selectedIndex++;
175
+ if (this.state.multiSelect) {
176
+ if (!this.state.selectedIndices.includes(index)) {
177
+ this.state.selectedIndices = [...this.state.selectedIndices, index];
205
178
  }
179
+ } else {
180
+ this.state.selectedIndex = index;
181
+ this.state.selectedIndices = [index];
206
182
  }
207
183
 
208
- this.state.items = items;
209
- this._updateDOM();
184
+ this._updateSelection();
185
+ this._triggerSelectionChange();
210
186
  return this;
211
187
  }
212
188
 
213
- select(index: number): this {
214
- if (index < 0 || index >= this.state.items.length) {
215
- console.error(`List: Invalid index ${index} for select`);
216
- return this;
189
+ deselectItem(index: number): this {
190
+ if (!this.state.selectable) return this;
191
+
192
+ if (this.state.multiSelect) {
193
+ this.state.selectedIndices = this.state.selectedIndices.filter(i => i !== index);
194
+ } else {
195
+ this.state.selectedIndex = null;
196
+ this.state.selectedIndices = [];
217
197
  }
218
198
 
219
- this.state.selectedIndex = index;
220
- this._updateDOM();
199
+ this._updateSelection();
200
+ this._triggerSelectionChange();
221
201
  return this;
222
202
  }
223
203
 
224
- deselect(): this {
225
- if (this.state.selectedIndex === null) {
226
- return this;
227
- }
228
-
204
+ clearSelection(): this {
229
205
  this.state.selectedIndex = null;
230
- this._updateDOM();
206
+ this.state.selectedIndices = [];
207
+ this._updateSelection();
208
+ this._triggerSelectionChange();
231
209
  return this;
232
210
  }
233
211
 
234
- getSelected(): { item: ListItem; index: number } | null {
235
- if (this.state.selectedIndex === null) {
236
- return null;
237
- }
238
- return {
239
- item: this.state.items[this.state.selectedIndex],
240
- index: this.state.selectedIndex
241
- };
212
+ getSelectedItems(): ListItem[] {
213
+ return this.state.selectedIndices.map(i => this.state.items[i]).filter(Boolean);
242
214
  }
243
215
 
244
- /* -------------------------
245
- * Helpers
246
- * ------------------------- */
247
-
248
- private _updateDOM(): void {
249
- if (!this.container) return;
250
-
251
- // Clear and re-render
252
- this.container.innerHTML = '';
253
- this._renderContent();
216
+ getSelectedIndices(): number[] {
217
+ return this.state.selectedIndices;
254
218
  }
255
219
 
256
- private _renderContent(): void {
257
- if (!this.container) return;
220
+ /* ═════════════════════════════════════════════════════════════════
221
+ * PRIVATE HELPERS
222
+ * ═════════════════════════════════════════════════════════════════ */
258
223
 
259
- const { items, header, gap, direction, selectable, selectedIndex, style, class: className } = this.state;
224
+ private _updateList(): void {
225
+ if (!this._listElement) return;
226
+ this._listElement.innerHTML = '';
227
+ this._renderItems(this._listElement);
228
+ }
260
229
 
261
- const wrapper = document.createElement('div');
262
- wrapper.className = 'jux-list-wrapper';
263
- wrapper.id = this._id;
230
+ private _updateSelection(): void {
231
+ if (!this._listElement) return;
232
+ const items = this._listElement.querySelectorAll('.jux-list-item');
233
+ items.forEach((el, index) => {
234
+ const isSelected = this.state.selectedIndices.includes(index);
235
+ el.classList.toggle('jux-list-item-selected', isSelected);
236
+ });
237
+ }
264
238
 
265
- if (className) {
266
- wrapper.className += ` ${className}`;
267
- }
239
+ private _triggerSelectionChange(): void {
240
+ const selectedItems = this.getSelectedItems();
241
+ const selectedIndices = this.getSelectedIndices();
268
242
 
269
- if (style) {
270
- wrapper.setAttribute('style', style);
243
+ if (this._onSelectionChange) {
244
+ this._onSelectionChange(selectedItems, selectedIndices);
271
245
  }
272
246
 
273
- // Header
274
- if (header) {
275
- const headerEl = document.createElement('div');
276
- headerEl.className = 'jux-list-header';
277
- headerEl.textContent = header;
278
- wrapper.appendChild(headerEl);
279
- }
247
+ // 🎯 Fire the selectionChange callback event
248
+ this._triggerCallback('selectionChange', { items: selectedItems, indices: selectedIndices });
249
+ }
280
250
 
281
- // List container
282
- const listContainer = document.createElement('div');
283
- listContainer.className = `jux-list jux-list-${direction}`;
284
- listContainer.style.gap = gap;
251
+ private _renderItems(list: HTMLElement): void {
252
+ const { items, selectable, hoverable, striped, bordered } = this.state;
285
253
 
286
- // Render items
287
254
  items.forEach((item, index) => {
288
- const itemEl = document.createElement('div');
289
- itemEl.className = `jux-list-item jux-list-item-${item.type || 'default'}`;
255
+ const li = document.createElement('li');
256
+ li.className = 'jux-list-item';
290
257
 
291
- if (selectable && selectedIndex === index) {
292
- itemEl.classList.add('jux-list-item-selected');
293
- }
258
+ if (item.itemClass) li.className += ` ${item.itemClass}`;
259
+ if (item.type && item.type !== 'default') li.classList.add(`jux-list-item-${item.type}`);
260
+ if (item.disabled) li.classList.add('jux-list-item-disabled');
261
+ if (this.state.selectedIndices.includes(index)) li.classList.add('jux-list-item-selected');
262
+ if (hoverable && !item.disabled) li.classList.add('jux-list-item-hoverable');
263
+ if (striped && index % 2 === 1) li.classList.add('jux-list-item-striped');
264
+ if (bordered) li.classList.add('jux-list-item-bordered');
265
+
266
+ // Content container
267
+ const content = document.createElement('div');
268
+ content.className = 'jux-list-item-content';
294
269
 
295
270
  // Icon
296
271
  if (item.icon) {
297
272
  const iconEl = document.createElement('span');
298
273
  iconEl.className = 'jux-list-item-icon';
299
- iconEl.textContent = item.icon;
300
- itemEl.appendChild(iconEl);
274
+ iconEl.appendChild(renderIcon(item.icon));
275
+ content.appendChild(iconEl);
301
276
  }
302
277
 
303
- // Content
304
- const contentEl = document.createElement('div');
305
- contentEl.className = 'jux-list-item-content';
278
+ // Text content
279
+ const textContainer = document.createElement('div');
280
+ textContainer.className = 'jux-list-item-text';
306
281
 
307
282
  if (item.title) {
308
283
  const titleEl = document.createElement('div');
309
284
  titleEl.className = 'jux-list-item-title';
310
285
  titleEl.textContent = item.title;
311
- contentEl.appendChild(titleEl);
286
+ textContainer.appendChild(titleEl);
312
287
  }
313
288
 
314
289
  if (item.body) {
315
290
  const bodyEl = document.createElement('div');
316
291
  bodyEl.className = 'jux-list-item-body';
317
292
  bodyEl.textContent = item.body;
318
- contentEl.appendChild(bodyEl);
293
+ textContainer.appendChild(bodyEl);
319
294
  }
320
295
 
321
- itemEl.appendChild(contentEl);
296
+ content.appendChild(textContainer);
322
297
 
323
298
  // Metadata
324
299
  if (item.metadata) {
325
- const metadataEl = document.createElement('span');
326
- metadataEl.className = 'jux-list-item-metadata';
327
- metadataEl.textContent = item.metadata;
328
- itemEl.appendChild(metadataEl);
300
+ const metaEl = document.createElement('div');
301
+ metaEl.className = 'jux-list-item-metadata';
302
+ metaEl.textContent = item.metadata;
303
+ content.appendChild(metaEl);
329
304
  }
330
305
 
331
- listContainer.appendChild(itemEl);
332
-
333
- // Event binding - click handlers
334
- itemEl.addEventListener('click', (e) => {
335
- if (selectable) {
336
- this.select(index);
337
- }
306
+ li.appendChild(content);
307
+
308
+ // Click handlers
309
+ if (!item.disabled) {
310
+ li.addEventListener('click', (e) => {
311
+ if (selectable) {
312
+ if (this.state.selectedIndices.includes(index)) {
313
+ this.deselectItem(index);
314
+ } else {
315
+ if (!this.state.multiSelect) {
316
+ this.clearSelection();
317
+ }
318
+ this.selectItem(index);
319
+ }
320
+ }
321
+
322
+ if (this._onItemClick) {
323
+ this._onItemClick(item, index, e);
324
+ }
325
+
326
+ // 🎯 Fire the itemClick callback event
327
+ this._triggerCallback('itemClick', { item, index, event: e });
328
+ });
338
329
 
339
- if (this._onItemClick) {
340
- this._onItemClick(item, index, e);
341
- }
342
- });
330
+ li.addEventListener('dblclick', (e) => {
331
+ if (this._onItemDoubleClick) {
332
+ this._onItemDoubleClick(item, index, e);
333
+ }
343
334
 
344
- if (this._onItemDoubleClick) {
345
- itemEl.addEventListener('dblclick', (e) => {
346
- this._onItemDoubleClick!(item, index, e);
335
+ // 🎯 Fire the itemDoubleClick callback event
336
+ this._triggerCallback('itemDoubleClick', { item, index, event: e });
347
337
  });
348
338
  }
349
- });
350
339
 
351
- wrapper.appendChild(listContainer);
352
- this.container.appendChild(wrapper);
340
+ list.appendChild(li);
341
+ });
353
342
  }
354
343
 
355
- /* -------------------------
356
- * Render
357
- * ------------------------- */
344
+ /* ═════════════════════════════════════════════════════════════════
345
+ * RENDER
346
+ * ═════════════════════════════════════════════════════════════════ */
358
347
 
359
348
  render(targetId?: string): this {
360
- let container: HTMLElement;
349
+ const container = this._setupContainer(targetId);
361
350
 
362
- if (targetId) {
363
- const target = document.querySelector(targetId);
364
- if (!target || !(target instanceof HTMLElement)) {
365
- throw new Error(`List: Target element "${targetId}" not found`);
366
- }
367
- container = target;
368
- } else {
369
- container = getOrCreateContainer(this._id);
351
+ const { header, ordered, style, class: className } = this.state;
352
+
353
+ const wrapper = document.createElement('div');
354
+ wrapper.className = 'jux-list-wrapper';
355
+ wrapper.id = `${this._id}-wrapper`;
356
+
357
+ // Header
358
+ if (header) {
359
+ const headerEl = document.createElement('div');
360
+ headerEl.className = 'jux-list-header';
361
+ headerEl.textContent = header;
362
+ wrapper.appendChild(headerEl);
370
363
  }
371
364
 
372
- this.container = container;
373
- this.container.innerHTML = '';
365
+ // List
366
+ const list = document.createElement(ordered ? 'ol' : 'ul') as HTMLOListElement | HTMLUListElement;
367
+ list.className = `jux-list ${ordered ? 'jux-list-ordered' : 'jux-list-unordered'}`;
368
+ list.id = this._id;
369
+ if (className) list.className += ` ${className}`;
370
+ if (style) list.setAttribute('style', style);
371
+
372
+ this._listElement = list;
373
+ this._renderItems(list);
374
+
375
+ wrapper.appendChild(list);
374
376
 
375
- this._renderContent();
377
+ this._wireStandardEvents(list);
378
+
379
+ // Wire sync bindings
380
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
381
+ if (property === 'items') {
382
+ const transform = toComponent || ((v: any) => v);
383
+
384
+ stateObj.subscribe((val: any) => {
385
+ const transformed = transform(val);
386
+ this.state.items = transformed;
387
+ this._updateList();
388
+ });
389
+ }
390
+ else if (property === 'selectedIndices') {
391
+ const transform = toComponent || ((v: any) => v);
392
+
393
+ stateObj.subscribe((val: any) => {
394
+ const transformed = transform(val);
395
+ this.state.selectedIndices = transformed;
396
+ this._updateSelection();
397
+ });
398
+ }
399
+ });
400
+
401
+ container.appendChild(wrapper);
402
+ this._injectListStyles();
403
+
404
+ requestAnimationFrame(() => {
405
+ if ((window as any).lucide) {
406
+ (window as any).lucide.createIcons();
407
+ }
408
+ });
376
409
 
377
410
  return this;
378
411
  }
379
412
 
380
- /**
381
- * Render to another Jux component's container
382
- */
383
- renderTo(juxComponent: any): this {
384
- if (!juxComponent || typeof juxComponent !== 'object') {
385
- throw new Error('List.renderTo: Invalid component - not an object');
386
- }
413
+ private _injectListStyles(): void {
414
+ const styleId = 'jux-list-styles';
415
+ if (document.getElementById(styleId)) return;
387
416
 
388
- if (!juxComponent._id || typeof juxComponent._id !== 'string') {
389
- throw new Error('List.renderTo: Invalid component - missing _id (not a Jux component)');
390
- }
417
+ const style = document.createElement('style');
418
+ style.id = styleId;
419
+ style.textContent = `
420
+ .jux-list-wrapper {
421
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
422
+ }
423
+
424
+ .jux-list-header {
425
+ font-size: 18px;
426
+ font-weight: 600;
427
+ margin-bottom: 12px;
428
+ color: #1f2937;
429
+ }
430
+
431
+ .jux-list {
432
+ list-style: none;
433
+ padding: 0;
434
+ margin: 0;
435
+ }
436
+
437
+ .jux-list-ordered {
438
+ list-style: decimal;
439
+ padding-left: 20px;
440
+ }
441
+
442
+ .jux-list-item {
443
+ position: relative;
444
+ padding: 12px 16px;
445
+ transition: all 0.2s;
446
+ cursor: default;
447
+ }
391
448
 
392
- return this.render(`#${juxComponent._id}`);
449
+ .jux-list-item-hoverable:not(.jux-list-item-disabled) {
450
+ cursor: pointer;
451
+ }
452
+
453
+ .jux-list-item-hoverable:not(.jux-list-item-disabled):hover {
454
+ background: #f3f4f6;
455
+ }
456
+
457
+ .jux-list-item-content {
458
+ display: flex;
459
+ align-items: center;
460
+ gap: 12px;
461
+ }
462
+
463
+ .jux-list-item-icon {
464
+ flex-shrink: 0;
465
+ display: flex;
466
+ align-items: center;
467
+ }
468
+
469
+ .jux-list-item-icon svg {
470
+ width: 20px;
471
+ height: 20px;
472
+ }
473
+
474
+ .jux-list-item-text {
475
+ flex: 1;
476
+ }
477
+
478
+ .jux-list-item-title {
479
+ font-size: 14px;
480
+ font-weight: 500;
481
+ color: #1f2937;
482
+ margin-bottom: 2px;
483
+ }
484
+
485
+ .jux-list-item-body {
486
+ font-size: 13px;
487
+ color: #6b7280;
488
+ line-height: 1.4;
489
+ }
490
+
491
+ .jux-list-item-metadata {
492
+ flex-shrink: 0;
493
+ font-size: 12px;
494
+ color: #9ca3af;
495
+ }
496
+
497
+ .jux-list-item-selected {
498
+ background: #dbeafe !important;
499
+ border-left: 3px solid #3b82f6;
500
+ }
501
+
502
+ .jux-list-item-striped {
503
+ background: #f9fafb;
504
+ }
505
+
506
+ .jux-list-item-bordered {
507
+ border: 1px solid #e5e7eb;
508
+ border-radius: 6px;
509
+ margin-bottom: 4px;
510
+ }
511
+
512
+ .jux-list-item-disabled {
513
+ opacity: 0.5;
514
+ cursor: not-allowed;
515
+ }
516
+
517
+ .jux-list-item-success {
518
+ border-left: 3px solid #10b981;
519
+ }
520
+
521
+ .jux-list-item-warning {
522
+ border-left: 3px solid #f59e0b;
523
+ }
524
+
525
+ .jux-list-item-error {
526
+ border-left: 3px solid #ef4444;
527
+ }
528
+
529
+ .jux-list-item-info {
530
+ border-left: 3px solid #3b82f6;
531
+ }
532
+ `;
533
+ document.head.appendChild(style);
393
534
  }
394
535
  }
395
536
 
396
- /**
397
- * Factory helper
398
- */
399
537
  export function list(id: string, options: ListOptions = {}): List {
400
538
  return new List(id, options);
401
539
  }