juxscript 1.0.2 → 1.0.4
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/README.md +37 -92
- package/bin/cli.js +57 -56
- package/lib/components/alert.ts +240 -0
- package/lib/components/app.ts +216 -82
- package/lib/components/badge.ts +164 -0
- package/lib/components/button.ts +188 -53
- package/lib/components/card.ts +75 -61
- package/lib/components/chart.ts +17 -15
- package/lib/components/checkbox.ts +228 -0
- package/lib/components/code.ts +66 -152
- package/lib/components/container.ts +104 -208
- package/lib/components/data.ts +1 -3
- package/lib/components/datepicker.ts +226 -0
- package/lib/components/dialog.ts +258 -0
- package/lib/components/docs-data.json +1697 -388
- package/lib/components/dropdown.ts +244 -0
- package/lib/components/element.ts +271 -0
- package/lib/components/fileupload.ts +319 -0
- package/lib/components/footer.ts +37 -18
- package/lib/components/header.ts +53 -33
- package/lib/components/heading.ts +119 -0
- package/lib/components/helpers.ts +34 -0
- package/lib/components/hero.ts +57 -31
- package/lib/components/include.ts +292 -0
- package/lib/components/input.ts +166 -78
- package/lib/components/layout.ts +144 -18
- package/lib/components/list.ts +83 -74
- package/lib/components/loading.ts +263 -0
- package/lib/components/main.ts +43 -17
- package/lib/components/menu.ts +108 -24
- package/lib/components/modal.ts +50 -21
- package/lib/components/nav.ts +60 -18
- package/lib/components/paragraph.ts +111 -0
- package/lib/components/progress.ts +276 -0
- package/lib/components/radio.ts +236 -0
- package/lib/components/req.ts +300 -0
- package/lib/components/script.ts +33 -74
- package/lib/components/select.ts +247 -0
- package/lib/components/sidebar.ts +86 -36
- package/lib/components/style.ts +47 -70
- package/lib/components/switch.ts +261 -0
- package/lib/components/table.ts +47 -24
- package/lib/components/tabs.ts +105 -63
- package/lib/components/theme-toggle.ts +361 -0
- package/lib/components/token-calculator.ts +380 -0
- package/lib/components/tooltip.ts +244 -0
- package/lib/components/view.ts +36 -20
- package/lib/components/write.ts +284 -0
- package/lib/globals.d.ts +21 -0
- package/lib/jux.ts +172 -68
- package/lib/presets/notion.css +521 -0
- package/lib/presets/notion.jux +27 -0
- package/lib/reactivity/state.ts +364 -0
- package/machinery/compiler.js +126 -38
- package/machinery/generators/html.js +2 -3
- package/machinery/server.js +2 -2
- package/package.json +29 -3
- package/lib/components/import.ts +0 -430
- package/lib/components/node.ts +0 -200
- package/lib/components/reactivity.js +0 -104
- package/lib/components/theme.ts +0 -97
- package/lib/layouts/notion.css +0 -258
- package/lib/styles/base-theme.css +0 -186
- package/lib/styles/dark-theme.css +0 -144
- package/lib/styles/light-theme.css +0 -144
- package/lib/styles/tokens/dark.css +0 -86
- package/lib/styles/tokens/light.css +0 -86
- package/lib/templates/index.juxt +0 -33
- package/lib/themes/dark.css +0 -86
- package/lib/themes/light.css +0 -86
- /package/lib/{styles → presets}/global.css +0 -0
package/lib/components/list.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getOrCreateContainer } from './helpers.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* List item interface
|
|
@@ -23,6 +23,8 @@ export interface ListOptions {
|
|
|
23
23
|
selectedIndex?: number | null;
|
|
24
24
|
onItemClick?: (item: ListItem, index: number, e: Event) => void;
|
|
25
25
|
onItemDoubleClick?: (item: ListItem, index: number, e: Event) => void;
|
|
26
|
+
style?: string;
|
|
27
|
+
class?: string;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -35,6 +37,8 @@ type ListState = {
|
|
|
35
37
|
direction: string;
|
|
36
38
|
selectable: boolean;
|
|
37
39
|
selectedIndex: number | null;
|
|
40
|
+
style: string;
|
|
41
|
+
class: string;
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
/**
|
|
@@ -62,25 +66,29 @@ type ListState = {
|
|
|
62
66
|
* // Move item from index 0 to index 2
|
|
63
67
|
* myList.move(0, 2);
|
|
64
68
|
*/
|
|
65
|
-
export class List
|
|
66
|
-
state
|
|
69
|
+
export class List {
|
|
70
|
+
state: ListState;
|
|
67
71
|
container: HTMLElement | null = null;
|
|
72
|
+
_id: string;
|
|
73
|
+
id: string;
|
|
68
74
|
private _onItemClick: ((item: ListItem, index: number, e: Event) => void) | null;
|
|
69
75
|
private _onItemDoubleClick: ((item: ListItem, index: number, e: Event) => void) | null;
|
|
70
76
|
|
|
71
|
-
constructor(
|
|
72
|
-
|
|
73
|
-
this.
|
|
74
|
-
|
|
75
|
-
this.state =
|
|
77
|
+
constructor(id: string, options: ListOptions = {}) {
|
|
78
|
+
this._id = id;
|
|
79
|
+
this.id = id;
|
|
80
|
+
|
|
81
|
+
this.state = {
|
|
76
82
|
items: options.items ?? [],
|
|
77
83
|
header: options.header ?? '',
|
|
78
84
|
gap: options.gap ?? '0.5rem',
|
|
79
85
|
direction: options.direction ?? 'vertical',
|
|
80
86
|
selectable: options.selectable ?? false,
|
|
81
|
-
selectedIndex: options.selectedIndex ?? null
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
selectedIndex: options.selectedIndex ?? null,
|
|
88
|
+
style: options.style ?? '',
|
|
89
|
+
class: options.class ?? ''
|
|
90
|
+
};
|
|
91
|
+
|
|
84
92
|
this._onItemClick = options.onItemClick ?? null;
|
|
85
93
|
this._onItemDoubleClick = options.onItemDoubleClick ?? null;
|
|
86
94
|
}
|
|
@@ -114,22 +122,31 @@ export class List extends Reactive {
|
|
|
114
122
|
return this;
|
|
115
123
|
}
|
|
116
124
|
|
|
125
|
+
style(value: string): this {
|
|
126
|
+
this.state.style = value;
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
class(value: string): this {
|
|
131
|
+
this.state.class = value;
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
117
135
|
/* -------------------------
|
|
118
136
|
* List operations
|
|
119
137
|
* ------------------------- */
|
|
120
138
|
|
|
121
139
|
add(item: ListItem, index?: number): this {
|
|
122
140
|
const items = [...this.state.items];
|
|
123
|
-
|
|
141
|
+
|
|
124
142
|
if (typeof index === 'number' && index >= 0 && index <= items.length) {
|
|
125
143
|
items.splice(index, 0, item);
|
|
126
144
|
} else {
|
|
127
145
|
index = items.length;
|
|
128
146
|
items.push(item);
|
|
129
147
|
}
|
|
130
|
-
|
|
148
|
+
|
|
131
149
|
this.state.items = items;
|
|
132
|
-
this.emit('itemAdded', { item, index });
|
|
133
150
|
this._updateDOM();
|
|
134
151
|
return this;
|
|
135
152
|
}
|
|
@@ -139,10 +156,10 @@ export class List extends Reactive {
|
|
|
139
156
|
console.error(`List: Invalid index ${index} for remove`);
|
|
140
157
|
return this;
|
|
141
158
|
}
|
|
142
|
-
|
|
159
|
+
|
|
143
160
|
const items = [...this.state.items];
|
|
144
|
-
|
|
145
|
-
|
|
161
|
+
items.splice(index, 1);
|
|
162
|
+
|
|
146
163
|
// Adjust selected index
|
|
147
164
|
if (this.state.selectedIndex !== null) {
|
|
148
165
|
if (this.state.selectedIndex === index) {
|
|
@@ -151,33 +168,32 @@ export class List extends Reactive {
|
|
|
151
168
|
this.state.selectedIndex--;
|
|
152
169
|
}
|
|
153
170
|
}
|
|
154
|
-
|
|
171
|
+
|
|
155
172
|
this.state.items = items;
|
|
156
|
-
this.emit('itemRemoved', { item: removed, index });
|
|
157
173
|
this._updateDOM();
|
|
158
174
|
return this;
|
|
159
175
|
}
|
|
160
176
|
|
|
161
177
|
move(fromIndex: number, toIndex: number): this {
|
|
162
178
|
const items = [...this.state.items];
|
|
163
|
-
|
|
179
|
+
|
|
164
180
|
if (fromIndex < 0 || fromIndex >= items.length) {
|
|
165
181
|
console.error(`List: Invalid fromIndex ${fromIndex}`);
|
|
166
182
|
return this;
|
|
167
183
|
}
|
|
168
|
-
|
|
184
|
+
|
|
169
185
|
if (toIndex < 0 || toIndex >= items.length) {
|
|
170
186
|
console.error(`List: Invalid toIndex ${toIndex}`);
|
|
171
187
|
return this;
|
|
172
188
|
}
|
|
173
|
-
|
|
189
|
+
|
|
174
190
|
if (fromIndex === toIndex) {
|
|
175
191
|
return this;
|
|
176
192
|
}
|
|
177
|
-
|
|
193
|
+
|
|
178
194
|
const [movedItem] = items.splice(fromIndex, 1);
|
|
179
195
|
items.splice(toIndex, 0, movedItem);
|
|
180
|
-
|
|
196
|
+
|
|
181
197
|
// Adjust selected index
|
|
182
198
|
if (this.state.selectedIndex !== null) {
|
|
183
199
|
if (this.state.selectedIndex === fromIndex) {
|
|
@@ -188,9 +204,8 @@ export class List extends Reactive {
|
|
|
188
204
|
this.state.selectedIndex++;
|
|
189
205
|
}
|
|
190
206
|
}
|
|
191
|
-
|
|
207
|
+
|
|
192
208
|
this.state.items = items;
|
|
193
|
-
this.emit('itemMoved', { item: movedItem, fromIndex, toIndex });
|
|
194
209
|
this._updateDOM();
|
|
195
210
|
return this;
|
|
196
211
|
}
|
|
@@ -200,16 +215,8 @@ export class List extends Reactive {
|
|
|
200
215
|
console.error(`List: Invalid index ${index} for select`);
|
|
201
216
|
return this;
|
|
202
217
|
}
|
|
203
|
-
|
|
204
|
-
const previousIndex = this.state.selectedIndex;
|
|
218
|
+
|
|
205
219
|
this.state.selectedIndex = index;
|
|
206
|
-
|
|
207
|
-
this.emit('itemSelect', {
|
|
208
|
-
item: this.state.items[index],
|
|
209
|
-
index,
|
|
210
|
-
previousIndex
|
|
211
|
-
});
|
|
212
|
-
|
|
213
220
|
this._updateDOM();
|
|
214
221
|
return this;
|
|
215
222
|
}
|
|
@@ -218,11 +225,8 @@ export class List extends Reactive {
|
|
|
218
225
|
if (this.state.selectedIndex === null) {
|
|
219
226
|
return this;
|
|
220
227
|
}
|
|
221
|
-
|
|
222
|
-
const previousIndex = this.state.selectedIndex;
|
|
228
|
+
|
|
223
229
|
this.state.selectedIndex = null;
|
|
224
|
-
|
|
225
|
-
this.emit('itemDeselect', { previousIndex });
|
|
226
230
|
this._updateDOM();
|
|
227
231
|
return this;
|
|
228
232
|
}
|
|
@@ -243,7 +247,7 @@ export class List extends Reactive {
|
|
|
243
247
|
|
|
244
248
|
private _updateDOM(): void {
|
|
245
249
|
if (!this.container) return;
|
|
246
|
-
|
|
250
|
+
|
|
247
251
|
// Clear and re-render
|
|
248
252
|
this.container.innerHTML = '';
|
|
249
253
|
this._renderContent();
|
|
@@ -251,13 +255,21 @@ export class List extends Reactive {
|
|
|
251
255
|
|
|
252
256
|
private _renderContent(): void {
|
|
253
257
|
if (!this.container) return;
|
|
254
|
-
|
|
255
|
-
const { items, header, gap, direction, selectable, selectedIndex } = this.state;
|
|
256
|
-
|
|
258
|
+
|
|
259
|
+
const { items, header, gap, direction, selectable, selectedIndex, style, class: className } = this.state;
|
|
260
|
+
|
|
257
261
|
const wrapper = document.createElement('div');
|
|
258
262
|
wrapper.className = 'jux-list-wrapper';
|
|
259
|
-
wrapper.id = this.
|
|
260
|
-
|
|
263
|
+
wrapper.id = this._id;
|
|
264
|
+
|
|
265
|
+
if (className) {
|
|
266
|
+
wrapper.className += ` ${className}`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (style) {
|
|
270
|
+
wrapper.setAttribute('style', style);
|
|
271
|
+
}
|
|
272
|
+
|
|
261
273
|
// Header
|
|
262
274
|
if (header) {
|
|
263
275
|
const headerEl = document.createElement('div');
|
|
@@ -265,21 +277,21 @@ export class List extends Reactive {
|
|
|
265
277
|
headerEl.textContent = header;
|
|
266
278
|
wrapper.appendChild(headerEl);
|
|
267
279
|
}
|
|
268
|
-
|
|
280
|
+
|
|
269
281
|
// List container
|
|
270
282
|
const listContainer = document.createElement('div');
|
|
271
283
|
listContainer.className = `jux-list jux-list-${direction}`;
|
|
272
284
|
listContainer.style.gap = gap;
|
|
273
|
-
|
|
285
|
+
|
|
274
286
|
// Render items
|
|
275
287
|
items.forEach((item, index) => {
|
|
276
288
|
const itemEl = document.createElement('div');
|
|
277
289
|
itemEl.className = `jux-list-item jux-list-item-${item.type || 'default'}`;
|
|
278
|
-
|
|
290
|
+
|
|
279
291
|
if (selectable && selectedIndex === index) {
|
|
280
292
|
itemEl.classList.add('jux-list-item-selected');
|
|
281
293
|
}
|
|
282
|
-
|
|
294
|
+
|
|
283
295
|
// Icon
|
|
284
296
|
if (item.icon) {
|
|
285
297
|
const iconEl = document.createElement('span');
|
|
@@ -287,27 +299,27 @@ export class List extends Reactive {
|
|
|
287
299
|
iconEl.textContent = item.icon;
|
|
288
300
|
itemEl.appendChild(iconEl);
|
|
289
301
|
}
|
|
290
|
-
|
|
302
|
+
|
|
291
303
|
// Content
|
|
292
304
|
const contentEl = document.createElement('div');
|
|
293
305
|
contentEl.className = 'jux-list-item-content';
|
|
294
|
-
|
|
306
|
+
|
|
295
307
|
if (item.title) {
|
|
296
308
|
const titleEl = document.createElement('div');
|
|
297
309
|
titleEl.className = 'jux-list-item-title';
|
|
298
310
|
titleEl.textContent = item.title;
|
|
299
311
|
contentEl.appendChild(titleEl);
|
|
300
312
|
}
|
|
301
|
-
|
|
313
|
+
|
|
302
314
|
if (item.body) {
|
|
303
315
|
const bodyEl = document.createElement('div');
|
|
304
316
|
bodyEl.className = 'jux-list-item-body';
|
|
305
317
|
bodyEl.textContent = item.body;
|
|
306
318
|
contentEl.appendChild(bodyEl);
|
|
307
319
|
}
|
|
308
|
-
|
|
320
|
+
|
|
309
321
|
itemEl.appendChild(contentEl);
|
|
310
|
-
|
|
322
|
+
|
|
311
323
|
// Metadata
|
|
312
324
|
if (item.metadata) {
|
|
313
325
|
const metadataEl = document.createElement('span');
|
|
@@ -315,30 +327,27 @@ export class List extends Reactive {
|
|
|
315
327
|
metadataEl.textContent = item.metadata;
|
|
316
328
|
itemEl.appendChild(metadataEl);
|
|
317
329
|
}
|
|
318
|
-
|
|
330
|
+
|
|
319
331
|
listContainer.appendChild(itemEl);
|
|
320
|
-
|
|
332
|
+
|
|
321
333
|
// Event binding - click handlers
|
|
322
334
|
itemEl.addEventListener('click', (e) => {
|
|
323
335
|
if (selectable) {
|
|
324
336
|
this.select(index);
|
|
325
337
|
}
|
|
326
|
-
|
|
327
|
-
this.emit('itemClick', { item, index, event: e });
|
|
328
|
-
|
|
338
|
+
|
|
329
339
|
if (this._onItemClick) {
|
|
330
340
|
this._onItemClick(item, index, e);
|
|
331
341
|
}
|
|
332
342
|
});
|
|
333
|
-
|
|
343
|
+
|
|
334
344
|
if (this._onItemDoubleClick) {
|
|
335
345
|
itemEl.addEventListener('dblclick', (e) => {
|
|
336
|
-
this.emit('itemDoubleClick', { item, index, event: e });
|
|
337
346
|
this._onItemDoubleClick!(item, index, e);
|
|
338
347
|
});
|
|
339
348
|
}
|
|
340
349
|
});
|
|
341
|
-
|
|
350
|
+
|
|
342
351
|
wrapper.appendChild(listContainer);
|
|
343
352
|
this.container.appendChild(wrapper);
|
|
344
353
|
}
|
|
@@ -349,7 +358,7 @@ export class List extends Reactive {
|
|
|
349
358
|
|
|
350
359
|
render(targetId?: string): this {
|
|
351
360
|
let container: HTMLElement;
|
|
352
|
-
|
|
361
|
+
|
|
353
362
|
if (targetId) {
|
|
354
363
|
const target = document.querySelector(targetId);
|
|
355
364
|
if (!target || !(target instanceof HTMLElement)) {
|
|
@@ -357,14 +366,14 @@ export class List extends Reactive {
|
|
|
357
366
|
}
|
|
358
367
|
container = target;
|
|
359
368
|
} else {
|
|
360
|
-
container = getOrCreateContainer(this.
|
|
369
|
+
container = getOrCreateContainer(this._id);
|
|
361
370
|
}
|
|
362
|
-
|
|
371
|
+
|
|
363
372
|
this.container = container;
|
|
364
373
|
this.container.innerHTML = '';
|
|
365
|
-
|
|
374
|
+
|
|
366
375
|
this._renderContent();
|
|
367
|
-
|
|
376
|
+
|
|
368
377
|
return this;
|
|
369
378
|
}
|
|
370
379
|
|
|
@@ -375,18 +384,18 @@ export class List extends Reactive {
|
|
|
375
384
|
if (!juxComponent || typeof juxComponent !== 'object') {
|
|
376
385
|
throw new Error('List.renderTo: Invalid component - not an object');
|
|
377
386
|
}
|
|
378
|
-
|
|
379
|
-
if (!juxComponent.
|
|
380
|
-
throw new Error('List.renderTo: Invalid component - missing
|
|
387
|
+
|
|
388
|
+
if (!juxComponent._id || typeof juxComponent._id !== 'string') {
|
|
389
|
+
throw new Error('List.renderTo: Invalid component - missing _id (not a Jux component)');
|
|
381
390
|
}
|
|
382
|
-
|
|
383
|
-
return this.render(`#${juxComponent.
|
|
391
|
+
|
|
392
|
+
return this.render(`#${juxComponent._id}`);
|
|
384
393
|
}
|
|
385
394
|
}
|
|
386
395
|
|
|
387
396
|
/**
|
|
388
397
|
* Factory helper
|
|
389
398
|
*/
|
|
390
|
-
export function list(
|
|
391
|
-
return new List(
|
|
399
|
+
export function list(id: string, options: ListOptions = {}): List {
|
|
400
|
+
return new List(id, options);
|
|
392
401
|
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { getOrCreateContainer } from './helpers.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Loading component options
|
|
5
|
+
*/
|
|
6
|
+
export interface LoadingOptions {
|
|
7
|
+
variant?: 'spinner' | 'dots' | 'pulse' | 'skeleton';
|
|
8
|
+
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
text?: string;
|
|
10
|
+
fullscreen?: boolean;
|
|
11
|
+
style?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Loading component state
|
|
17
|
+
*/
|
|
18
|
+
type LoadingState = {
|
|
19
|
+
variant: string;
|
|
20
|
+
size: string;
|
|
21
|
+
text: string;
|
|
22
|
+
fullscreen: boolean;
|
|
23
|
+
style: string;
|
|
24
|
+
class: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Loading component - Spinners, skeletons, loading indicators
|
|
29
|
+
*
|
|
30
|
+
* Usage:
|
|
31
|
+
* jux.loading('my-spinner', {
|
|
32
|
+
* variant: 'spinner',
|
|
33
|
+
* size: 'lg',
|
|
34
|
+
* text: 'Loading...'
|
|
35
|
+
* }).render('#app');
|
|
36
|
+
*
|
|
37
|
+
* // Fullscreen overlay
|
|
38
|
+
* jux.loading('loading-overlay', {
|
|
39
|
+
* variant: 'spinner',
|
|
40
|
+
* fullscreen: true,
|
|
41
|
+
* text: 'Please wait...'
|
|
42
|
+
* }).render();
|
|
43
|
+
*
|
|
44
|
+
* Variants: spinner, dots, pulse, skeleton
|
|
45
|
+
*/
|
|
46
|
+
export class Loading {
|
|
47
|
+
state: LoadingState;
|
|
48
|
+
container: HTMLElement | null = null;
|
|
49
|
+
_id: string;
|
|
50
|
+
id: string;
|
|
51
|
+
|
|
52
|
+
constructor(id: string, options: LoadingOptions = {}) {
|
|
53
|
+
this._id = id;
|
|
54
|
+
this.id = id;
|
|
55
|
+
|
|
56
|
+
this.state = {
|
|
57
|
+
variant: options.variant ?? 'spinner',
|
|
58
|
+
size: options.size ?? 'md',
|
|
59
|
+
text: options.text ?? '',
|
|
60
|
+
fullscreen: options.fullscreen ?? false,
|
|
61
|
+
style: options.style ?? '',
|
|
62
|
+
class: options.class ?? ''
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* -------------------------
|
|
67
|
+
* Fluent API
|
|
68
|
+
* ------------------------- */
|
|
69
|
+
|
|
70
|
+
variant(value: 'spinner' | 'dots' | 'pulse' | 'skeleton'): this {
|
|
71
|
+
this.state.variant = value;
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
size(value: 'sm' | 'md' | 'lg'): this {
|
|
76
|
+
this.state.size = value;
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
text(value: string): this {
|
|
81
|
+
this.state.text = value;
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fullscreen(value: boolean): this {
|
|
86
|
+
this.state.fullscreen = value;
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
style(value: string): this {
|
|
91
|
+
this.state.style = value;
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
class(value: string): this {
|
|
96
|
+
this.state.class = value;
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* -------------------------
|
|
101
|
+
* Methods
|
|
102
|
+
* ------------------------- */
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Show the loading indicator
|
|
106
|
+
*/
|
|
107
|
+
show(): void {
|
|
108
|
+
const element = document.getElementById(this._id);
|
|
109
|
+
if (element) {
|
|
110
|
+
element.style.display = 'flex';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Hide the loading indicator
|
|
116
|
+
*/
|
|
117
|
+
hide(): void {
|
|
118
|
+
const element = document.getElementById(this._id);
|
|
119
|
+
if (element) {
|
|
120
|
+
element.style.display = 'none';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Remove the loading indicator
|
|
126
|
+
*/
|
|
127
|
+
remove(): void {
|
|
128
|
+
const element = document.getElementById(this._id);
|
|
129
|
+
if (element) {
|
|
130
|
+
element.remove();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* -------------------------
|
|
135
|
+
* Helpers
|
|
136
|
+
* ------------------------- */
|
|
137
|
+
|
|
138
|
+
private _renderSpinner(): HTMLElement {
|
|
139
|
+
const spinner = document.createElement('div');
|
|
140
|
+
spinner.className = 'jux-loading-spinner';
|
|
141
|
+
return spinner;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private _renderDots(): HTMLElement {
|
|
145
|
+
const dots = document.createElement('div');
|
|
146
|
+
dots.className = 'jux-loading-dots';
|
|
147
|
+
for (let i = 0; i < 3; i++) {
|
|
148
|
+
const dot = document.createElement('div');
|
|
149
|
+
dot.className = 'jux-loading-dot';
|
|
150
|
+
dots.appendChild(dot);
|
|
151
|
+
}
|
|
152
|
+
return dots;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private _renderPulse(): HTMLElement {
|
|
156
|
+
const pulse = document.createElement('div');
|
|
157
|
+
pulse.className = 'jux-loading-pulse';
|
|
158
|
+
return pulse;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private _renderSkeleton(): HTMLElement {
|
|
162
|
+
const skeleton = document.createElement('div');
|
|
163
|
+
skeleton.className = 'jux-loading-skeleton';
|
|
164
|
+
|
|
165
|
+
// Create 3 skeleton lines
|
|
166
|
+
for (let i = 0; i < 3; i++) {
|
|
167
|
+
const line = document.createElement('div');
|
|
168
|
+
line.className = 'jux-loading-skeleton-line';
|
|
169
|
+
if (i === 2) line.style.width = '60%'; // Last line shorter
|
|
170
|
+
skeleton.appendChild(line);
|
|
171
|
+
}
|
|
172
|
+
return skeleton;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* -------------------------
|
|
176
|
+
* Render
|
|
177
|
+
* ------------------------- */
|
|
178
|
+
|
|
179
|
+
render(targetId?: string): this {
|
|
180
|
+
let container: HTMLElement;
|
|
181
|
+
|
|
182
|
+
if (targetId) {
|
|
183
|
+
const target = document.querySelector(targetId);
|
|
184
|
+
if (!target || !(target instanceof HTMLElement)) {
|
|
185
|
+
throw new Error(`Loading: Target element "${targetId}" not found`);
|
|
186
|
+
}
|
|
187
|
+
container = target;
|
|
188
|
+
} else {
|
|
189
|
+
container = getOrCreateContainer(this._id);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.container = container;
|
|
193
|
+
const { variant, size, text, fullscreen, style, class: className } = this.state;
|
|
194
|
+
|
|
195
|
+
const loading = document.createElement('div');
|
|
196
|
+
loading.className = `jux-loading jux-loading-${size}`;
|
|
197
|
+
loading.id = this._id;
|
|
198
|
+
|
|
199
|
+
if (fullscreen) {
|
|
200
|
+
loading.className += ' jux-loading-fullscreen';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (className) {
|
|
204
|
+
loading.className += ` ${className}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (style) {
|
|
208
|
+
loading.setAttribute('style', style);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Render appropriate variant
|
|
212
|
+
let indicator: HTMLElement;
|
|
213
|
+
switch (variant) {
|
|
214
|
+
case 'dots':
|
|
215
|
+
indicator = this._renderDots();
|
|
216
|
+
break;
|
|
217
|
+
case 'pulse':
|
|
218
|
+
indicator = this._renderPulse();
|
|
219
|
+
break;
|
|
220
|
+
case 'skeleton':
|
|
221
|
+
indicator = this._renderSkeleton();
|
|
222
|
+
break;
|
|
223
|
+
case 'spinner':
|
|
224
|
+
default:
|
|
225
|
+
indicator = this._renderSpinner();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
loading.appendChild(indicator);
|
|
229
|
+
|
|
230
|
+
// Optional text
|
|
231
|
+
if (text) {
|
|
232
|
+
const textEl = document.createElement('div');
|
|
233
|
+
textEl.className = 'jux-loading-text';
|
|
234
|
+
textEl.textContent = text;
|
|
235
|
+
loading.appendChild(textEl);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
container.appendChild(loading);
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Render to another Jux component's container
|
|
244
|
+
*/
|
|
245
|
+
renderTo(juxComponent: any): this {
|
|
246
|
+
if (!juxComponent || typeof juxComponent !== 'object') {
|
|
247
|
+
throw new Error('Loading.renderTo: Invalid component - not an object');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!juxComponent._id || typeof juxComponent._id !== 'string') {
|
|
251
|
+
throw new Error('Loading.renderTo: Invalid component - missing _id (not a Jux component)');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return this.render(`#${juxComponent._id}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Factory helper
|
|
260
|
+
*/
|
|
261
|
+
export function loading(id: string, options: LoadingOptions = {}): Loading {
|
|
262
|
+
return new Loading(id, options);
|
|
263
|
+
}
|