juxscript 1.0.3 → 1.0.5
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/barchart.ts +1248 -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 +199 -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 +1969 -423
- 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 +508 -77
- 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 +280 -0
- package/lib/components/sidebar.ts +87 -37
- 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 +178 -68
- package/lib/presets/notion.css +521 -0
- package/lib/presets/notion.jux +27 -0
- package/lib/reactivity/state.ts +364 -0
- package/lib/themes/charts.js +126 -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/layout.ts
CHANGED
|
@@ -1,19 +1,45 @@
|
|
|
1
1
|
import { ErrorHandler } from './error-handler.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Layout
|
|
5
|
-
*
|
|
4
|
+
* Layout component interface
|
|
5
|
+
* Represents any Jux component that can be rendered
|
|
6
|
+
*/
|
|
7
|
+
interface LayoutComponent {
|
|
8
|
+
render: (target?: string) => any;
|
|
9
|
+
_componentId?: string;
|
|
10
|
+
_id?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Layout - Compose and render multiple components
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* // As a composition function
|
|
18
|
+
* jux.layout(
|
|
19
|
+
* jux.include('/lib/presets/notion/notion.css'),
|
|
20
|
+
* jux.header('appheader').render('#app'),
|
|
21
|
+
* jux.sidebar('appsidebar').render('#app'),
|
|
22
|
+
* jux.main('appmain').render('#app')
|
|
23
|
+
* );
|
|
24
|
+
*
|
|
25
|
+
* // Or load from a .jux file
|
|
26
|
+
* jux.layout('/lib/presets/notion/main.jux');
|
|
6
27
|
*/
|
|
7
28
|
export class Layout {
|
|
8
29
|
private _juxFile: string;
|
|
9
30
|
private _loaded: boolean = false;
|
|
31
|
+
private _components: any[] = [];
|
|
10
32
|
|
|
11
|
-
constructor(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (juxFile) {
|
|
33
|
+
constructor(...args: any[]) {
|
|
34
|
+
// If first arg is string, it's a file path
|
|
35
|
+
if (args.length === 1 && typeof args[0] === 'string' && args[0].endsWith('.jux')) {
|
|
36
|
+
this._juxFile = args[0];
|
|
16
37
|
this.load();
|
|
38
|
+
} else {
|
|
39
|
+
// Otherwise, it's a list of components to compose
|
|
40
|
+
this._juxFile = '';
|
|
41
|
+
this._components = args;
|
|
42
|
+
this._compose();
|
|
17
43
|
}
|
|
18
44
|
}
|
|
19
45
|
|
|
@@ -34,6 +60,41 @@ export class Layout {
|
|
|
34
60
|
return this._juxFile;
|
|
35
61
|
}
|
|
36
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Compose components immediately
|
|
65
|
+
* This handles the case where components are passed directly
|
|
66
|
+
*/
|
|
67
|
+
private _compose(): this {
|
|
68
|
+
try {
|
|
69
|
+
// Components are already rendered via chaining
|
|
70
|
+
// This just ensures they're tracked
|
|
71
|
+
this._components.forEach((component, index) => {
|
|
72
|
+
// Components that return themselves from .render() are already in DOM
|
|
73
|
+
// Just log for debugging
|
|
74
|
+
if (component && typeof component === 'object') {
|
|
75
|
+
const id = component._componentId || component._id || component.id || `component-${index}`;
|
|
76
|
+
console.log(`✓ Layout component composed: ${id}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this._loaded = true;
|
|
81
|
+
} catch (error: any) {
|
|
82
|
+
ErrorHandler.captureError({
|
|
83
|
+
component: 'Layout',
|
|
84
|
+
method: '_compose',
|
|
85
|
+
message: `Failed to compose layout: ${error.message}`,
|
|
86
|
+
stack: error.stack,
|
|
87
|
+
timestamp: new Date(),
|
|
88
|
+
context: {
|
|
89
|
+
componentCount: this._components.length,
|
|
90
|
+
error: 'composition_failed'
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
37
98
|
/**
|
|
38
99
|
* Normalize path to absolute URL from site root
|
|
39
100
|
*/
|
|
@@ -42,22 +103,22 @@ export class Layout {
|
|
|
42
103
|
if (path.startsWith('http://') || path.startsWith('https://')) {
|
|
43
104
|
return path;
|
|
44
105
|
}
|
|
45
|
-
|
|
106
|
+
|
|
46
107
|
// Convert relative path to absolute
|
|
47
108
|
// Remove leading './' if present
|
|
48
109
|
let cleanPath = path.replace(/^\.\//, '');
|
|
49
|
-
|
|
110
|
+
|
|
50
111
|
// Ensure it starts with /
|
|
51
112
|
if (!cleanPath.startsWith('/')) {
|
|
52
113
|
cleanPath = '/' + cleanPath;
|
|
53
114
|
}
|
|
54
|
-
|
|
115
|
+
|
|
55
116
|
// Return absolute URL with origin
|
|
56
117
|
return new URL(cleanPath, window.location.origin).href;
|
|
57
118
|
}
|
|
58
119
|
|
|
59
120
|
/**
|
|
60
|
-
* Load the layout
|
|
121
|
+
* Load the layout from a .jux file
|
|
61
122
|
* This will dynamically import the compiled JS file
|
|
62
123
|
*/
|
|
63
124
|
async load(): Promise<this> {
|
|
@@ -65,25 +126,33 @@ export class Layout {
|
|
|
65
126
|
return this;
|
|
66
127
|
}
|
|
67
128
|
|
|
129
|
+
if (!this._juxFile) {
|
|
130
|
+
console.warn('Layout: No file specified to load');
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
|
|
68
134
|
try {
|
|
69
135
|
// Convert .jux to .js for the compiled output
|
|
70
136
|
let jsFile = this._juxFile.replace(/\.jux$/, '.js');
|
|
71
|
-
|
|
137
|
+
|
|
72
138
|
// Normalize to absolute URL for browser import
|
|
73
139
|
jsFile = this.normalizePath(jsFile);
|
|
74
|
-
|
|
140
|
+
|
|
75
141
|
console.log(`Loading layout: ${jsFile}`);
|
|
76
|
-
|
|
142
|
+
|
|
77
143
|
// Dynamic import of the layout module
|
|
78
144
|
const layoutModule = await import(jsFile);
|
|
79
|
-
|
|
145
|
+
|
|
80
146
|
// If the module has an init or default export, call it
|
|
81
147
|
if (typeof layoutModule.default === 'function') {
|
|
82
148
|
await layoutModule.default();
|
|
83
149
|
} else if (typeof layoutModule.init === 'function') {
|
|
84
150
|
await layoutModule.init();
|
|
151
|
+
} else if (layoutModule.default && typeof layoutModule.default === 'object') {
|
|
152
|
+
// If default export is the layout object itself
|
|
153
|
+
console.log('✓ Layout module loaded (object export)');
|
|
85
154
|
}
|
|
86
|
-
|
|
155
|
+
|
|
87
156
|
this._loaded = true;
|
|
88
157
|
console.log(`✓ Layout loaded: ${this._juxFile}`);
|
|
89
158
|
} catch (error: any) {
|
|
@@ -93,7 +162,7 @@ export class Layout {
|
|
|
93
162
|
message: `Failed to load layout: ${error.message}`,
|
|
94
163
|
stack: error.stack,
|
|
95
164
|
timestamp: new Date(),
|
|
96
|
-
context: {
|
|
165
|
+
context: {
|
|
97
166
|
juxFile: this._juxFile,
|
|
98
167
|
jsFile: this._juxFile.replace(/\.jux$/, '.js'),
|
|
99
168
|
errorCode: error.code
|
|
@@ -110,4 +179,61 @@ export class Layout {
|
|
|
110
179
|
isLoaded(): boolean {
|
|
111
180
|
return this._loaded;
|
|
112
181
|
}
|
|
113
|
-
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get all composed components
|
|
185
|
+
*/
|
|
186
|
+
getComponents(): any[] {
|
|
187
|
+
return this._components;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Add a component to the layout
|
|
192
|
+
*/
|
|
193
|
+
add(component: any): this {
|
|
194
|
+
this._components.push(component);
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Render all components
|
|
200
|
+
* Useful if components weren't already rendered
|
|
201
|
+
*/
|
|
202
|
+
render(target?: string): this {
|
|
203
|
+
this._components.forEach((component) => {
|
|
204
|
+
if (component && typeof component.render === 'function') {
|
|
205
|
+
try {
|
|
206
|
+
component.render(target);
|
|
207
|
+
} catch (error: any) {
|
|
208
|
+
ErrorHandler.captureError({
|
|
209
|
+
component: 'Layout',
|
|
210
|
+
method: 'render',
|
|
211
|
+
message: `Failed to render component: ${error.message}`,
|
|
212
|
+
stack: error.stack,
|
|
213
|
+
timestamp: new Date(),
|
|
214
|
+
context: {
|
|
215
|
+
componentId: component._componentId || component._id || 'unknown',
|
|
216
|
+
target
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Factory helper
|
|
229
|
+
* Accepts either:
|
|
230
|
+
* - A file path: layout('/lib/presets/notion/main.jux')
|
|
231
|
+
* - A list of components: layout(header, sidebar, main)
|
|
232
|
+
*/
|
|
233
|
+
export function layout(...args: any[]): Layout {
|
|
234
|
+
return new Layout(...args);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* -------------------------
|
|
238
|
+
* Module Exports
|
|
239
|
+
* ------------------------- */
|
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
|
}
|