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.
- package/bin/cli.js +121 -72
- package/lib/components/alert.ts +212 -165
- package/lib/components/badge.ts +93 -103
- package/lib/components/base/BaseComponent.ts +397 -0
- package/lib/components/base/FormInput.ts +322 -0
- package/lib/components/button.ts +63 -122
- package/lib/components/card.ts +109 -155
- package/lib/components/charts/areachart.ts +315 -0
- package/lib/components/charts/barchart.ts +421 -0
- package/lib/components/charts/doughnutchart.ts +263 -0
- package/lib/components/charts/lib/BaseChart.ts +402 -0
- package/lib/components/charts/lib/chart-types.ts +159 -0
- package/lib/components/charts/lib/chart-utils.ts +160 -0
- package/lib/components/charts/lib/chart.ts +707 -0
- package/lib/components/checkbox.ts +264 -127
- package/lib/components/code.ts +75 -108
- package/lib/components/container.ts +113 -130
- package/lib/components/data.ts +37 -5
- package/lib/components/datepicker.ts +195 -147
- package/lib/components/dialog.ts +187 -157
- package/lib/components/divider.ts +85 -191
- package/lib/components/docs-data.json +544 -2027
- package/lib/components/dropdown.ts +178 -136
- package/lib/components/element.ts +227 -171
- package/lib/components/fileupload.ts +285 -228
- package/lib/components/guard.ts +92 -0
- package/lib/components/heading.ts +46 -69
- package/lib/components/helpers.ts +13 -6
- package/lib/components/hero.ts +107 -95
- package/lib/components/icon.ts +160 -0
- package/lib/components/icons.ts +175 -0
- package/lib/components/include.ts +153 -5
- package/lib/components/input.ts +174 -374
- package/lib/components/kpicard.ts +16 -16
- package/lib/components/list.ts +378 -240
- package/lib/components/loading.ts +142 -211
- package/lib/components/menu.ts +103 -97
- package/lib/components/modal.ts +138 -144
- package/lib/components/nav.ts +169 -90
- package/lib/components/paragraph.ts +49 -150
- package/lib/components/progress.ts +118 -200
- package/lib/components/radio.ts +297 -149
- package/lib/components/script.ts +19 -87
- package/lib/components/select.ts +184 -186
- package/lib/components/sidebar.ts +152 -140
- package/lib/components/style.ts +19 -82
- package/lib/components/switch.ts +258 -188
- package/lib/components/table.ts +1117 -170
- package/lib/components/tabs.ts +162 -145
- package/lib/components/theme-toggle.ts +108 -169
- package/lib/components/tooltip.ts +86 -157
- package/lib/components/write.ts +108 -127
- package/lib/jux.ts +86 -41
- package/machinery/build.js +466 -0
- package/machinery/compiler.js +354 -105
- package/machinery/server.js +23 -100
- package/machinery/watcher.js +153 -130
- package/package.json +1 -2
- package/presets/base.css +1166 -0
- package/presets/notion.css +2 -1975
- package/lib/adapters/base-adapter.js +0 -35
- package/lib/adapters/index.js +0 -33
- package/lib/adapters/mysql-adapter.js +0 -65
- package/lib/adapters/postgres-adapter.js +0 -70
- package/lib/adapters/sqlite-adapter.js +0 -56
- package/lib/components/areachart.ts +0 -1246
- package/lib/components/areachartsmooth.ts +0 -1380
- package/lib/components/barchart.ts +0 -1250
- package/lib/components/chart.ts +0 -127
- package/lib/components/doughnutchart.ts +0 -1191
- package/lib/components/footer.ts +0 -165
- package/lib/components/header.ts +0 -187
- package/lib/components/layout.ts +0 -239
- package/lib/components/main.ts +0 -137
- package/lib/layouts/default.jux +0 -8
- package/lib/layouts/figma.jux +0 -0
- /package/lib/{themes → components/charts/lib}/charts.js +0 -0
package/lib/components/list.ts
CHANGED
|
@@ -1,401 +1,539 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
106
|
-
this.state.
|
|
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
|
-
|
|
111
|
-
this.state.
|
|
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
|
-
|
|
116
|
-
this.state.
|
|
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
|
-
|
|
121
|
-
this.state.
|
|
127
|
+
clearItems(): this {
|
|
128
|
+
this.state.items = [];
|
|
129
|
+
this._updateList();
|
|
122
130
|
return this;
|
|
123
131
|
}
|
|
124
132
|
|
|
125
|
-
|
|
126
|
-
this.state.
|
|
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
|
-
|
|
131
|
-
this.state.
|
|
142
|
+
ordered(value: boolean): this {
|
|
143
|
+
this.state.ordered = value;
|
|
132
144
|
return this;
|
|
133
145
|
}
|
|
134
146
|
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
178
|
-
|
|
157
|
+
striped(value: boolean): this {
|
|
158
|
+
this.state.striped = value;
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
179
161
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
162
|
+
hoverable(value: boolean): this {
|
|
163
|
+
this.state.hoverable = value;
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
184
166
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
167
|
+
bordered(value: boolean): this {
|
|
168
|
+
this.state.bordered = value;
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
189
171
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
172
|
+
selectItem(index: number): this {
|
|
173
|
+
if (!this.state.selectable) return this;
|
|
193
174
|
|
|
194
|
-
|
|
195
|
-
|
|
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.
|
|
209
|
-
this.
|
|
184
|
+
this._updateSelection();
|
|
185
|
+
this._triggerSelectionChange();
|
|
210
186
|
return this;
|
|
211
187
|
}
|
|
212
188
|
|
|
213
|
-
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
|
|
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.
|
|
220
|
-
this.
|
|
199
|
+
this._updateSelection();
|
|
200
|
+
this._triggerSelectionChange();
|
|
221
201
|
return this;
|
|
222
202
|
}
|
|
223
203
|
|
|
224
|
-
|
|
225
|
-
if (this.state.selectedIndex === null) {
|
|
226
|
-
return this;
|
|
227
|
-
}
|
|
228
|
-
|
|
204
|
+
clearSelection(): this {
|
|
229
205
|
this.state.selectedIndex = null;
|
|
230
|
-
this.
|
|
206
|
+
this.state.selectedIndices = [];
|
|
207
|
+
this._updateSelection();
|
|
208
|
+
this._triggerSelectionChange();
|
|
231
209
|
return this;
|
|
232
210
|
}
|
|
233
211
|
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
257
|
-
|
|
220
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
221
|
+
* PRIVATE HELPERS
|
|
222
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
258
223
|
|
|
259
|
-
|
|
224
|
+
private _updateList(): void {
|
|
225
|
+
if (!this._listElement) return;
|
|
226
|
+
this._listElement.innerHTML = '';
|
|
227
|
+
this._renderItems(this._listElement);
|
|
228
|
+
}
|
|
260
229
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
239
|
+
private _triggerSelectionChange(): void {
|
|
240
|
+
const selectedItems = this.getSelectedItems();
|
|
241
|
+
const selectedIndices = this.getSelectedIndices();
|
|
268
242
|
|
|
269
|
-
if (
|
|
270
|
-
|
|
243
|
+
if (this._onSelectionChange) {
|
|
244
|
+
this._onSelectionChange(selectedItems, selectedIndices);
|
|
271
245
|
}
|
|
272
246
|
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
282
|
-
const
|
|
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
|
|
289
|
-
|
|
255
|
+
const li = document.createElement('li');
|
|
256
|
+
li.className = 'jux-list-item';
|
|
290
257
|
|
|
291
|
-
if (
|
|
292
|
-
|
|
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.
|
|
300
|
-
|
|
274
|
+
iconEl.appendChild(renderIcon(item.icon));
|
|
275
|
+
content.appendChild(iconEl);
|
|
301
276
|
}
|
|
302
277
|
|
|
303
|
-
//
|
|
304
|
-
const
|
|
305
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
+
textContainer.appendChild(bodyEl);
|
|
319
294
|
}
|
|
320
295
|
|
|
321
|
-
|
|
296
|
+
content.appendChild(textContainer);
|
|
322
297
|
|
|
323
298
|
// Metadata
|
|
324
299
|
if (item.metadata) {
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
340
|
-
this.
|
|
341
|
-
|
|
342
|
-
|
|
330
|
+
li.addEventListener('dblclick', (e) => {
|
|
331
|
+
if (this._onItemDoubleClick) {
|
|
332
|
+
this._onItemDoubleClick(item, index, e);
|
|
333
|
+
}
|
|
343
334
|
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
352
|
-
|
|
340
|
+
list.appendChild(li);
|
|
341
|
+
});
|
|
353
342
|
}
|
|
354
343
|
|
|
355
|
-
/*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
344
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
345
|
+
* RENDER
|
|
346
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
358
347
|
|
|
359
348
|
render(targetId?: string): this {
|
|
360
|
-
|
|
349
|
+
const container = this._setupContainer(targetId);
|
|
361
350
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
373
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
389
|
-
|
|
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
|
-
|
|
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
|
}
|