juxscript 1.0.20 → 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 (76) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +143 -92
  3. package/lib/components/badge.ts +93 -94
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +40 -131
  7. package/lib/components/card.ts +57 -79
  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/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
  13. package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
  14. package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
  15. package/lib/components/checkbox.ts +255 -204
  16. package/lib/components/code.ts +31 -78
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +180 -147
  20. package/lib/components/dialog.ts +218 -221
  21. package/lib/components/divider.ts +63 -87
  22. package/lib/components/docs-data.json +498 -2404
  23. package/lib/components/dropdown.ts +191 -236
  24. package/lib/components/element.ts +196 -145
  25. package/lib/components/fileupload.ts +253 -167
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +31 -97
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +51 -114
  30. package/lib/components/icon.ts +33 -120
  31. package/lib/components/icons.ts +2 -1
  32. package/lib/components/include.ts +76 -3
  33. package/lib/components/input.ts +155 -407
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +358 -261
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +63 -152
  38. package/lib/components/modal.ts +42 -129
  39. package/lib/components/nav.ts +79 -101
  40. package/lib/components/paragraph.ts +38 -102
  41. package/lib/components/progress.ts +108 -166
  42. package/lib/components/radio.ts +283 -234
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +189 -199
  45. package/lib/components/sidebar.ts +110 -141
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +254 -183
  48. package/lib/components/table.ts +1078 -208
  49. package/lib/components/tabs.ts +42 -106
  50. package/lib/components/theme-toggle.ts +73 -165
  51. package/lib/components/tooltip.ts +85 -316
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +67 -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 -1
  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 -1128
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1322
  69. package/lib/components/doughnutchart.ts +0 -1259
  70. package/lib/components/footer.ts +0 -165
  71. package/lib/components/header.ts +0 -187
  72. package/lib/components/layout.ts +0 -239
  73. package/lib/components/main.ts +0 -137
  74. package/lib/layouts/default.jux +0 -8
  75. package/lib/layouts/figma.jux +0 -0
  76. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -1,13 +1,21 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
3
2
  import { renderIcon } from './icons.js';
4
3
 
4
+ // Event definitions
5
+ const TRIGGER_EVENTS = [] as const;
6
+ const CALLBACK_EVENTS = ['itemClick', 'itemDoubleClick', 'selectionChange'] as const;
7
+
5
8
  export interface ListItem {
9
+ id?: string;
6
10
  icon?: string;
7
11
  title?: string;
8
12
  body?: string;
9
13
  type?: 'success' | 'warning' | 'error' | 'info' | 'default' | string;
10
14
  metadata?: string;
15
+ itemClass?: string;
16
+ disabled?: boolean;
17
+ selected?: boolean;
18
+ data?: any; // Arbitrary data attached to item
11
19
  }
12
20
 
13
21
  export interface ListOptions {
@@ -16,9 +24,16 @@ export interface ListOptions {
16
24
  gap?: string;
17
25
  direction?: 'vertical' | 'horizontal';
18
26
  selectable?: boolean;
27
+ multiSelect?: boolean;
19
28
  selectedIndex?: number | null;
29
+ selectedIndices?: number[];
20
30
  onItemClick?: (item: ListItem, index: number, e: Event) => void;
21
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;
22
37
  style?: string;
23
38
  class?: string;
24
39
  }
@@ -29,411 +44,493 @@ type ListState = {
29
44
  gap: string;
30
45
  direction: string;
31
46
  selectable: boolean;
47
+ multiSelect: boolean;
32
48
  selectedIndex: number | null;
33
- ordered?: boolean;
49
+ selectedIndices: number[];
50
+ ordered: boolean;
51
+ striped: boolean;
52
+ hoverable: boolean;
53
+ bordered: boolean;
34
54
  style: string;
35
55
  class: string;
36
56
  };
37
57
 
38
- export class List {
39
- state: ListState;
40
- container: HTMLElement | null = null;
41
- _id: string;
42
- id: string;
43
-
44
- // CRITICAL: Store bind/sync instructions for deferred wiring
45
- private _bindings: Array<{ event: string, handler: Function }> = [];
46
- private _syncBindings: Array<{
47
- property: string,
48
- stateObj: State<any>,
49
- toState?: Function,
50
- toComponent?: Function
51
- }> = [];
52
-
58
+ export class List extends BaseComponent<ListState> {
53
59
  private _onItemClick: ((item: ListItem, index: number, e: Event) => void) | null = null;
54
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;
55
63
 
56
64
  constructor(id: string, options: ListOptions = {}) {
57
- this._id = id;
58
- this.id = id;
59
-
60
- this.state = {
65
+ super(id, {
61
66
  items: options.items ?? [],
62
67
  header: options.header ?? '',
63
68
  gap: options.gap ?? '0.5rem',
64
69
  direction: options.direction ?? 'vertical',
65
70
  selectable: options.selectable ?? false,
71
+ multiSelect: options.multiSelect ?? false,
66
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,
67
78
  style: options.style ?? '',
68
79
  class: options.class ?? ''
69
- };
80
+ });
70
81
 
71
82
  this._onItemClick = options.onItemClick ?? null;
72
83
  this._onItemDoubleClick = options.onItemDoubleClick ?? null;
84
+ this._onSelectionChange = options.onSelectionChange ?? null;
73
85
  }
74
86
 
75
- /* -------------------------
76
- * Fluent API
77
- * ------------------------- */
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
78
100
 
79
101
  items(value: ListItem[]): this {
80
102
  this.state.items = value;
103
+ this._updateList();
81
104
  return this;
82
105
  }
83
106
 
84
107
  addItem(value: ListItem): this {
85
108
  this.state.items = [...this.state.items, value];
109
+ this._updateList();
86
110
  return this;
87
111
  }
88
112
 
89
- ordered(value: boolean): this {
90
- this.state.ordered = value;
113
+ removeItem(index: number): this {
114
+ this.state.items = this.state.items.filter((_, i) => i !== index);
115
+ this._updateList();
91
116
  return this;
92
117
  }
93
118
 
94
- style(value: string): this {
95
- this.state.style = 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();
96
124
  return this;
97
125
  }
98
126
 
99
- class(value: string): this {
100
- this.state.class = value;
127
+ clearItems(): this {
128
+ this.state.items = [];
129
+ this._updateList();
101
130
  return this;
102
131
  }
103
132
 
104
- bind(event: string, handler: Function): this {
105
- this._bindings.push({ event, handler });
133
+ itemClass(className: string): this {
134
+ this.state.items = this.state.items.map(item => ({
135
+ ...item,
136
+ itemClass: className
137
+ }));
138
+ this._updateList();
106
139
  return this;
107
140
  }
108
141
 
109
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
110
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
111
- throw new Error(`List.sync: Expected a State object for property "${property}"`);
112
- }
113
- this._syncBindings.push({ property, stateObj, toState, toComponent });
142
+ ordered(value: boolean): this {
143
+ this.state.ordered = value;
114
144
  return this;
115
145
  }
116
146
 
117
- /* -------------------------
118
- * List operations
119
- * ------------------------- */
120
-
121
- add(item: ListItem, index?: number): this {
122
- const items = [...this.state.items];
123
-
124
- if (typeof index === 'number' && index >= 0 && index <= items.length) {
125
- items.splice(index, 0, item);
126
- } else {
127
- index = items.length;
128
- items.push(item);
129
- }
130
-
131
- this.state.items = items;
132
- this._updateDOM();
147
+ selectable(value: boolean): this {
148
+ this.state.selectable = value;
133
149
  return this;
134
150
  }
135
151
 
136
- remove(index: number): this {
137
- if (typeof index !== 'number' || index < 0 || index >= this.state.items.length) {
138
- console.error(`List: Invalid index ${index} for remove`);
139
- return this;
140
- }
141
-
142
- const items = [...this.state.items];
143
- items.splice(index, 1);
144
-
145
- // Adjust selected index
146
- if (this.state.selectedIndex !== null) {
147
- if (this.state.selectedIndex === index) {
148
- this.state.selectedIndex = null;
149
- } else if (this.state.selectedIndex > index) {
150
- this.state.selectedIndex--;
151
- }
152
- }
153
-
154
- this.state.items = items;
155
- this._updateDOM();
152
+ multiSelect(value: boolean): this {
153
+ this.state.multiSelect = value;
156
154
  return this;
157
155
  }
158
156
 
159
- move(fromIndex: number, toIndex: number): this {
160
- const items = [...this.state.items];
157
+ striped(value: boolean): this {
158
+ this.state.striped = value;
159
+ return this;
160
+ }
161
161
 
162
- if (fromIndex < 0 || fromIndex >= items.length) {
163
- console.error(`List: Invalid fromIndex ${fromIndex}`);
164
- return this;
165
- }
162
+ hoverable(value: boolean): this {
163
+ this.state.hoverable = value;
164
+ return this;
165
+ }
166
166
 
167
- if (toIndex < 0 || toIndex >= items.length) {
168
- console.error(`List: Invalid toIndex ${toIndex}`);
169
- return this;
170
- }
167
+ bordered(value: boolean): this {
168
+ this.state.bordered = value;
169
+ return this;
170
+ }
171
171
 
172
- if (fromIndex === toIndex) {
173
- return this;
174
- }
172
+ selectItem(index: number): this {
173
+ if (!this.state.selectable) return this;
175
174
 
176
- const [movedItem] = items.splice(fromIndex, 1);
177
- items.splice(toIndex, 0, movedItem);
178
-
179
- // Adjust selected index
180
- if (this.state.selectedIndex !== null) {
181
- if (this.state.selectedIndex === fromIndex) {
182
- this.state.selectedIndex = toIndex;
183
- } else if (fromIndex < this.state.selectedIndex && toIndex >= this.state.selectedIndex) {
184
- this.state.selectedIndex--;
185
- } else if (fromIndex > this.state.selectedIndex && toIndex <= this.state.selectedIndex) {
186
- this.state.selectedIndex++;
175
+ if (this.state.multiSelect) {
176
+ if (!this.state.selectedIndices.includes(index)) {
177
+ this.state.selectedIndices = [...this.state.selectedIndices, index];
187
178
  }
179
+ } else {
180
+ this.state.selectedIndex = index;
181
+ this.state.selectedIndices = [index];
188
182
  }
189
183
 
190
- this.state.items = items;
191
- this._updateDOM();
184
+ this._updateSelection();
185
+ this._triggerSelectionChange();
192
186
  return this;
193
187
  }
194
188
 
195
- select(index: number): this {
196
- if (index < 0 || index >= this.state.items.length) {
197
- console.error(`List: Invalid index ${index} for select`);
198
- 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 = [];
199
197
  }
200
198
 
201
- this.state.selectedIndex = index;
202
- this._updateDOM();
199
+ this._updateSelection();
200
+ this._triggerSelectionChange();
203
201
  return this;
204
202
  }
205
203
 
206
- deselect(): this {
207
- if (this.state.selectedIndex === null) {
208
- return this;
209
- }
210
-
204
+ clearSelection(): this {
211
205
  this.state.selectedIndex = null;
212
- this._updateDOM();
206
+ this.state.selectedIndices = [];
207
+ this._updateSelection();
208
+ this._triggerSelectionChange();
213
209
  return this;
214
210
  }
215
211
 
216
- getSelected(): { item: ListItem; index: number } | null {
217
- if (this.state.selectedIndex === null) {
218
- return null;
219
- }
220
- return {
221
- item: this.state.items[this.state.selectedIndex],
222
- index: this.state.selectedIndex
223
- };
212
+ getSelectedItems(): ListItem[] {
213
+ return this.state.selectedIndices.map(i => this.state.items[i]).filter(Boolean);
224
214
  }
225
215
 
226
- /* -------------------------
227
- * Helpers
228
- * ------------------------- */
229
-
230
- private _updateDOM(): void {
231
- if (!this.container) return;
232
-
233
- const existingWrapper = this.container.querySelector(`#${this._id}`);
234
- if (existingWrapper) {
235
- existingWrapper.remove();
236
- }
237
-
238
- this._buildAndAppendList();
216
+ getSelectedIndices(): number[] {
217
+ return this.state.selectedIndices;
239
218
  }
240
219
 
241
- private _buildAndAppendList(): void {
242
- if (!this.container) return;
220
+ /* ═════════════════════════════════════════════════════════════════
221
+ * PRIVATE HELPERS
222
+ * ═════════════════════════════════════════════════════════════════ */
243
223
 
244
- 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
+ }
245
229
 
246
- const wrapper = document.createElement('div');
247
- wrapper.className = 'jux-list-wrapper';
248
- 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
+ }
249
238
 
250
- if (className) {
251
- wrapper.className += ` ${className}`;
252
- }
239
+ private _triggerSelectionChange(): void {
240
+ const selectedItems = this.getSelectedItems();
241
+ const selectedIndices = this.getSelectedIndices();
253
242
 
254
- if (style) {
255
- wrapper.setAttribute('style', style);
243
+ if (this._onSelectionChange) {
244
+ this._onSelectionChange(selectedItems, selectedIndices);
256
245
  }
257
246
 
258
- // Header
259
- if (header) {
260
- const headerEl = document.createElement('div');
261
- headerEl.className = 'jux-list-header';
262
- headerEl.textContent = header;
263
- wrapper.appendChild(headerEl);
264
- }
247
+ // 🎯 Fire the selectionChange callback event
248
+ this._triggerCallback('selectionChange', { items: selectedItems, indices: selectedIndices });
249
+ }
265
250
 
266
- // List container
267
- const listContainer = document.createElement('div');
268
- listContainer.className = `jux-list jux-list-${direction}`;
269
- listContainer.style.gap = gap;
251
+ private _renderItems(list: HTMLElement): void {
252
+ const { items, selectable, hoverable, striped, bordered } = this.state;
270
253
 
271
- // Render items
272
254
  items.forEach((item, index) => {
273
- const itemEl = document.createElement('div');
274
- itemEl.className = `jux-list-item jux-list-item-${item.type || 'default'}`;
255
+ const li = document.createElement('li');
256
+ li.className = 'jux-list-item';
275
257
 
276
- if (selectable && selectedIndex === index) {
277
- itemEl.classList.add('jux-list-item-selected');
278
- }
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';
279
269
 
280
270
  // Icon
281
271
  if (item.icon) {
282
272
  const iconEl = document.createElement('span');
283
273
  iconEl.className = 'jux-list-item-icon';
284
- iconEl.style.display = 'flex';
285
- iconEl.style.alignItems = 'center';
286
- iconEl.style.justifyContent = 'center';
287
- const iconElement = renderIcon(item.icon);
288
- iconEl.appendChild(iconElement);
289
- itemEl.appendChild(iconEl);
274
+ iconEl.appendChild(renderIcon(item.icon));
275
+ content.appendChild(iconEl);
290
276
  }
291
277
 
292
- // Content
293
- const contentEl = document.createElement('div');
294
- contentEl.className = 'jux-list-item-content';
278
+ // Text content
279
+ const textContainer = document.createElement('div');
280
+ textContainer.className = 'jux-list-item-text';
295
281
 
296
282
  if (item.title) {
297
283
  const titleEl = document.createElement('div');
298
284
  titleEl.className = 'jux-list-item-title';
299
285
  titleEl.textContent = item.title;
300
- contentEl.appendChild(titleEl);
286
+ textContainer.appendChild(titleEl);
301
287
  }
302
288
 
303
289
  if (item.body) {
304
290
  const bodyEl = document.createElement('div');
305
291
  bodyEl.className = 'jux-list-item-body';
306
292
  bodyEl.textContent = item.body;
307
- contentEl.appendChild(bodyEl);
293
+ textContainer.appendChild(bodyEl);
308
294
  }
309
295
 
310
- itemEl.appendChild(contentEl);
296
+ content.appendChild(textContainer);
311
297
 
312
298
  // Metadata
313
299
  if (item.metadata) {
314
- const metadataEl = document.createElement('span');
315
- metadataEl.className = 'jux-list-item-metadata';
316
- metadataEl.textContent = item.metadata;
317
- 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);
318
304
  }
319
305
 
320
- listContainer.appendChild(itemEl);
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
+ }
321
325
 
322
- // Event binding - click handlers
323
- itemEl.addEventListener('click', (e) => {
324
- if (selectable) {
325
- this.select(index);
326
- }
326
+ // 🎯 Fire the itemClick callback event
327
+ this._triggerCallback('itemClick', { item, index, event: e });
328
+ });
327
329
 
328
- if (this._onItemClick) {
329
- this._onItemClick(item, index, e);
330
- }
331
- });
330
+ li.addEventListener('dblclick', (e) => {
331
+ if (this._onItemDoubleClick) {
332
+ this._onItemDoubleClick(item, index, e);
333
+ }
332
334
 
333
- if (this._onItemDoubleClick) {
334
- itemEl.addEventListener('dblclick', (e) => {
335
- this._onItemDoubleClick!(item, index, e);
335
+ // 🎯 Fire the itemDoubleClick callback event
336
+ this._triggerCallback('itemDoubleClick', { item, index, event: e });
336
337
  });
337
338
  }
338
- });
339
339
 
340
- wrapper.appendChild(listContainer);
341
- this.container.appendChild(wrapper);
340
+ list.appendChild(li);
341
+ });
342
342
  }
343
343
 
344
- /* -------------------------
345
- * Render
346
- * ------------------------- */
344
+ /* ═════════════════════════════════════════════════════════════════
345
+ * RENDER
346
+ * ═════════════════════════════════════════════════════════════════ */
347
347
 
348
348
  render(targetId?: string): this {
349
- // === 1. SETUP: Get or create container ===
350
- let container: HTMLElement;
351
- if (targetId) {
352
- const target = document.querySelector(targetId);
353
- if (!target || !(target instanceof HTMLElement)) {
354
- throw new Error(`List: Target "${targetId}" not found`);
355
- }
356
- container = target;
357
- } else {
358
- container = getOrCreateContainer(this._id);
359
- }
360
- this.container = container;
349
+ const container = this._setupContainer(targetId);
350
+
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`;
361
356
 
362
- // === 2. PREPARE: Destructure state ===
363
- const { items, ordered, style, class: className } = this.state;
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);
363
+ }
364
364
 
365
- // === 3. BUILD: Create DOM elements ===
365
+ // List
366
366
  const list = document.createElement(ordered ? 'ol' : 'ul') as HTMLOListElement | HTMLUListElement;
367
367
  list.className = `jux-list ${ordered ? 'jux-list-ordered' : 'jux-list-unordered'}`;
368
368
  list.id = this._id;
369
369
  if (className) list.className += ` ${className}`;
370
370
  if (style) list.setAttribute('style', style);
371
371
 
372
- items.forEach(item => {
373
- const li = document.createElement('li');
374
- li.className = 'jux-list-item';
375
-
376
- // Handle ListItem object
377
- if (item.title) {
378
- li.textContent = item.title;
379
- }
380
- if (item.body) {
381
- const bodyEl = document.createElement('div');
382
- bodyEl.textContent = item.body;
383
- li.appendChild(bodyEl);
384
- }
385
-
386
- list.appendChild(li);
387
- });
372
+ this._listElement = list;
373
+ this._renderItems(list);
388
374
 
389
- // === 4. WIRE: Attach event listeners and sync bindings ===
375
+ wrapper.appendChild(list);
390
376
 
391
- // Wire custom bindings from .bind() calls
392
- this._bindings.forEach(({ event, handler }) => {
393
- list.addEventListener(event, handler as EventListener);
394
- });
377
+ this._wireStandardEvents(list);
395
378
 
396
- // Wire sync bindings from .sync() calls
379
+ // Wire sync bindings
397
380
  this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
398
381
  if (property === 'items') {
399
- const transformToComponent = toComponent || ((v: any) => v);
382
+ const transform = toComponent || ((v: any) => v);
400
383
 
401
384
  stateObj.subscribe((val: any) => {
402
- const transformed = transformToComponent(val);
385
+ const transformed = transform(val);
403
386
  this.state.items = transformed;
387
+ this._updateList();
388
+ });
389
+ }
390
+ else if (property === 'selectedIndices') {
391
+ const transform = toComponent || ((v: any) => v);
404
392
 
405
- // Re-render items
406
- list.innerHTML = '';
407
- transformed.forEach((item: ListItem) => {
408
- const li = document.createElement('li');
409
- li.className = 'jux-list-item';
410
-
411
- // Handle ListItem object
412
- if (item.title) {
413
- li.textContent = item.title;
414
- }
415
- if (item.body) {
416
- const bodyEl = document.createElement('div');
417
- bodyEl.textContent = item.body;
418
- li.appendChild(bodyEl);
419
- }
420
-
421
- list.appendChild(li);
422
- });
393
+ stateObj.subscribe((val: any) => {
394
+ const transformed = transform(val);
395
+ this.state.selectedIndices = transformed;
396
+ this._updateSelection();
423
397
  });
424
398
  }
425
399
  });
426
400
 
427
- // === 5. RENDER: Append to DOM and finalize ===
428
- container.appendChild(list);
401
+ container.appendChild(wrapper);
402
+ this._injectListStyles();
403
+
404
+ requestAnimationFrame(() => {
405
+ if ((window as any).lucide) {
406
+ (window as any).lucide.createIcons();
407
+ }
408
+ });
409
+
429
410
  return this;
430
411
  }
431
412
 
432
- renderTo(juxComponent: any): this {
433
- if (!juxComponent?._id) {
434
- throw new Error('List.renderTo: Invalid component');
435
- }
436
- return this.render(`#${juxComponent._id}`);
413
+ private _injectListStyles(): void {
414
+ const styleId = 'jux-list-styles';
415
+ if (document.getElementById(styleId)) return;
416
+
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
+ }
448
+
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);
437
534
  }
438
535
  }
439
536