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.
- package/bin/cli.js +121 -72
- package/lib/components/alert.ts +143 -92
- package/lib/components/badge.ts +93 -94
- package/lib/components/base/BaseComponent.ts +397 -0
- package/lib/components/base/FormInput.ts +322 -0
- package/lib/components/button.ts +40 -131
- package/lib/components/card.ts +57 -79
- 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/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
- package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
- package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
- package/lib/components/checkbox.ts +255 -204
- package/lib/components/code.ts +31 -78
- package/lib/components/container.ts +113 -130
- package/lib/components/data.ts +37 -5
- package/lib/components/datepicker.ts +180 -147
- package/lib/components/dialog.ts +218 -221
- package/lib/components/divider.ts +63 -87
- package/lib/components/docs-data.json +498 -2404
- package/lib/components/dropdown.ts +191 -236
- package/lib/components/element.ts +196 -145
- package/lib/components/fileupload.ts +253 -167
- package/lib/components/guard.ts +92 -0
- package/lib/components/heading.ts +31 -97
- package/lib/components/helpers.ts +13 -6
- package/lib/components/hero.ts +51 -114
- package/lib/components/icon.ts +33 -120
- package/lib/components/icons.ts +2 -1
- package/lib/components/include.ts +76 -3
- package/lib/components/input.ts +155 -407
- package/lib/components/kpicard.ts +16 -16
- package/lib/components/list.ts +358 -261
- package/lib/components/loading.ts +142 -211
- package/lib/components/menu.ts +63 -152
- package/lib/components/modal.ts +42 -129
- package/lib/components/nav.ts +79 -101
- package/lib/components/paragraph.ts +38 -102
- package/lib/components/progress.ts +108 -166
- package/lib/components/radio.ts +283 -234
- package/lib/components/script.ts +19 -87
- package/lib/components/select.ts +189 -199
- package/lib/components/sidebar.ts +110 -141
- package/lib/components/style.ts +19 -82
- package/lib/components/switch.ts +254 -183
- package/lib/components/table.ts +1078 -208
- package/lib/components/tabs.ts +42 -106
- package/lib/components/theme-toggle.ts +73 -165
- package/lib/components/tooltip.ts +85 -316
- package/lib/components/write.ts +108 -127
- package/lib/jux.ts +67 -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 -1
- 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 -1128
- package/lib/components/areachartsmooth.ts +0 -1380
- package/lib/components/barchart.ts +0 -1322
- package/lib/components/doughnutchart.ts +0 -1259
- 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,13 +1,21 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
this.state.
|
|
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
|
-
|
|
95
|
-
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();
|
|
96
124
|
return this;
|
|
97
125
|
}
|
|
98
126
|
|
|
99
|
-
|
|
100
|
-
this.state.
|
|
127
|
+
clearItems(): this {
|
|
128
|
+
this.state.items = [];
|
|
129
|
+
this._updateList();
|
|
101
130
|
return this;
|
|
102
131
|
}
|
|
103
132
|
|
|
104
|
-
|
|
105
|
-
this.
|
|
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
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
160
|
-
|
|
157
|
+
striped(value: boolean): this {
|
|
158
|
+
this.state.striped = value;
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
hoverable(value: boolean): this {
|
|
163
|
+
this.state.hoverable = value;
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
bordered(value: boolean): this {
|
|
168
|
+
this.state.bordered = value;
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
172
|
+
selectItem(index: number): this {
|
|
173
|
+
if (!this.state.selectable) return this;
|
|
175
174
|
|
|
176
|
-
|
|
177
|
-
|
|
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.
|
|
191
|
-
this.
|
|
184
|
+
this._updateSelection();
|
|
185
|
+
this._triggerSelectionChange();
|
|
192
186
|
return this;
|
|
193
187
|
}
|
|
194
188
|
|
|
195
|
-
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
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.
|
|
202
|
-
this.
|
|
199
|
+
this._updateSelection();
|
|
200
|
+
this._triggerSelectionChange();
|
|
203
201
|
return this;
|
|
204
202
|
}
|
|
205
203
|
|
|
206
|
-
|
|
207
|
-
if (this.state.selectedIndex === null) {
|
|
208
|
-
return this;
|
|
209
|
-
}
|
|
210
|
-
|
|
204
|
+
clearSelection(): this {
|
|
211
205
|
this.state.selectedIndex = null;
|
|
212
|
-
this.
|
|
206
|
+
this.state.selectedIndices = [];
|
|
207
|
+
this._updateSelection();
|
|
208
|
+
this._triggerSelectionChange();
|
|
213
209
|
return this;
|
|
214
210
|
}
|
|
215
211
|
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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
|
-
|
|
242
|
-
|
|
220
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
221
|
+
* PRIVATE HELPERS
|
|
222
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
243
223
|
|
|
244
|
-
|
|
224
|
+
private _updateList(): void {
|
|
225
|
+
if (!this._listElement) return;
|
|
226
|
+
this._listElement.innerHTML = '';
|
|
227
|
+
this._renderItems(this._listElement);
|
|
228
|
+
}
|
|
245
229
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
239
|
+
private _triggerSelectionChange(): void {
|
|
240
|
+
const selectedItems = this.getSelectedItems();
|
|
241
|
+
const selectedIndices = this.getSelectedIndices();
|
|
253
242
|
|
|
254
|
-
if (
|
|
255
|
-
|
|
243
|
+
if (this._onSelectionChange) {
|
|
244
|
+
this._onSelectionChange(selectedItems, selectedIndices);
|
|
256
245
|
}
|
|
257
246
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
267
|
-
const
|
|
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
|
|
274
|
-
|
|
255
|
+
const li = document.createElement('li');
|
|
256
|
+
li.className = 'jux-list-item';
|
|
275
257
|
|
|
276
|
-
if (
|
|
277
|
-
|
|
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.
|
|
285
|
-
iconEl
|
|
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
|
-
//
|
|
293
|
-
const
|
|
294
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
+
textContainer.appendChild(bodyEl);
|
|
308
294
|
}
|
|
309
295
|
|
|
310
|
-
|
|
296
|
+
content.appendChild(textContainer);
|
|
311
297
|
|
|
312
298
|
// Metadata
|
|
313
299
|
if (item.metadata) {
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
this.select(index);
|
|
326
|
-
}
|
|
326
|
+
// 🎯 Fire the itemClick callback event
|
|
327
|
+
this._triggerCallback('itemClick', { item, index, event: e });
|
|
328
|
+
});
|
|
327
329
|
|
|
328
|
-
|
|
329
|
-
this.
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
li.addEventListener('dblclick', (e) => {
|
|
331
|
+
if (this._onItemDoubleClick) {
|
|
332
|
+
this._onItemDoubleClick(item, index, e);
|
|
333
|
+
}
|
|
332
334
|
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
341
|
-
|
|
340
|
+
list.appendChild(li);
|
|
341
|
+
});
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
-
/*
|
|
345
|
-
|
|
346
|
-
|
|
344
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
345
|
+
* RENDER
|
|
346
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
347
347
|
|
|
348
348
|
render(targetId?: string): this {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
//
|
|
363
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
373
|
-
|
|
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
|
-
|
|
375
|
+
wrapper.appendChild(list);
|
|
390
376
|
|
|
391
|
-
|
|
392
|
-
this._bindings.forEach(({ event, handler }) => {
|
|
393
|
-
list.addEventListener(event, handler as EventListener);
|
|
394
|
-
});
|
|
377
|
+
this._wireStandardEvents(list);
|
|
395
378
|
|
|
396
|
-
// Wire sync bindings
|
|
379
|
+
// Wire sync bindings
|
|
397
380
|
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
398
381
|
if (property === 'items') {
|
|
399
|
-
const
|
|
382
|
+
const transform = toComponent || ((v: any) => v);
|
|
400
383
|
|
|
401
384
|
stateObj.subscribe((val: any) => {
|
|
402
|
-
const transformed =
|
|
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
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
|
|
428
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
|