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
|
@@ -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
|
+
}
|
package/lib/components/main.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getOrCreateContainer } from './helpers.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Main component options
|
|
@@ -6,6 +6,8 @@ import { Reactive, getOrCreateContainer } from './reactivity.js';
|
|
|
6
6
|
export interface MainOptions {
|
|
7
7
|
content?: string;
|
|
8
8
|
padding?: string;
|
|
9
|
+
style?: string;
|
|
10
|
+
class?: string;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -14,6 +16,8 @@ export interface MainOptions {
|
|
|
14
16
|
type MainState = {
|
|
15
17
|
content: string;
|
|
16
18
|
padding: string;
|
|
19
|
+
style: string;
|
|
20
|
+
class: string;
|
|
17
21
|
};
|
|
18
22
|
|
|
19
23
|
/**
|
|
@@ -26,18 +30,22 @@ type MainState = {
|
|
|
26
30
|
* });
|
|
27
31
|
* main.render('#appmain');
|
|
28
32
|
*/
|
|
29
|
-
export class Main
|
|
30
|
-
state
|
|
33
|
+
export class Main {
|
|
34
|
+
state: MainState;
|
|
31
35
|
container: HTMLElement | null = null;
|
|
36
|
+
_id: string;
|
|
37
|
+
id: string;
|
|
32
38
|
|
|
33
|
-
constructor(
|
|
34
|
-
|
|
35
|
-
this.
|
|
39
|
+
constructor(id: string, options: MainOptions = {}) {
|
|
40
|
+
this._id = id;
|
|
41
|
+
this.id = id;
|
|
36
42
|
|
|
37
|
-
this.state =
|
|
43
|
+
this.state = {
|
|
38
44
|
content: options.content ?? '',
|
|
39
|
-
padding: options.padding ?? 'var(--space-xl)'
|
|
40
|
-
|
|
45
|
+
padding: options.padding ?? 'var(--space-xl)',
|
|
46
|
+
style: options.style ?? '',
|
|
47
|
+
class: options.class ?? ''
|
|
48
|
+
};
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
/* -------------------------
|
|
@@ -54,6 +62,16 @@ export class Main extends Reactive {
|
|
|
54
62
|
return this;
|
|
55
63
|
}
|
|
56
64
|
|
|
65
|
+
style(value: string): this {
|
|
66
|
+
this.state.style = value;
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class(value: string): this {
|
|
71
|
+
this.state.class = value;
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
/* -------------------------
|
|
58
76
|
* Render
|
|
59
77
|
* ------------------------- */
|
|
@@ -68,17 +86,25 @@ export class Main extends Reactive {
|
|
|
68
86
|
}
|
|
69
87
|
container = target;
|
|
70
88
|
} else {
|
|
71
|
-
container = getOrCreateContainer(this.
|
|
89
|
+
container = getOrCreateContainer(this._id);
|
|
72
90
|
}
|
|
73
91
|
|
|
74
92
|
this.container = container;
|
|
75
|
-
const { content, padding } = this.state;
|
|
93
|
+
const { content, padding, style, class: className } = this.state;
|
|
76
94
|
|
|
77
95
|
const main = document.createElement('main');
|
|
78
96
|
main.className = 'jux-main';
|
|
79
|
-
main.id = this.
|
|
97
|
+
main.id = this._id;
|
|
80
98
|
main.style.padding = padding;
|
|
81
99
|
|
|
100
|
+
if (style) {
|
|
101
|
+
main.setAttribute('style', style);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (className) {
|
|
105
|
+
main.className += ` ${className}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
82
108
|
if (content) {
|
|
83
109
|
main.innerHTML = content;
|
|
84
110
|
}
|
|
@@ -95,17 +121,17 @@ export class Main extends Reactive {
|
|
|
95
121
|
throw new Error('Main.renderTo: Invalid component - not an object');
|
|
96
122
|
}
|
|
97
123
|
|
|
98
|
-
if (!juxComponent.
|
|
99
|
-
throw new Error('Main.renderTo: Invalid component - missing
|
|
124
|
+
if (!juxComponent._id || typeof juxComponent._id !== 'string') {
|
|
125
|
+
throw new Error('Main.renderTo: Invalid component - missing _id (not a Jux component)');
|
|
100
126
|
}
|
|
101
127
|
|
|
102
|
-
return this.render(`#${juxComponent.
|
|
128
|
+
return this.render(`#${juxComponent._id}`);
|
|
103
129
|
}
|
|
104
130
|
}
|
|
105
131
|
|
|
106
132
|
/**
|
|
107
133
|
* Factory helper
|
|
108
134
|
*/
|
|
109
|
-
export function main(
|
|
110
|
-
return new Main(
|
|
135
|
+
export function main(id: string, options: MainOptions = {}): Main {
|
|
136
|
+
return new Main(id, options);
|
|
111
137
|
}
|
package/lib/components/menu.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getOrCreateContainer } from './helpers.js';
|
|
2
|
+
import { req } from './req.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Menu item configuration
|
|
@@ -6,9 +7,10 @@ import { Reactive, getOrCreateContainer } from './reactivity.js';
|
|
|
6
7
|
export interface MenuItem {
|
|
7
8
|
label: string;
|
|
8
9
|
href?: string;
|
|
9
|
-
|
|
10
|
+
click?: () => void;
|
|
10
11
|
icon?: string;
|
|
11
12
|
items?: MenuItem[];
|
|
13
|
+
active?: boolean;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -17,6 +19,8 @@ export interface MenuItem {
|
|
|
17
19
|
export interface MenuOptions {
|
|
18
20
|
items?: MenuItem[];
|
|
19
21
|
orientation?: 'vertical' | 'horizontal';
|
|
22
|
+
style?: string;
|
|
23
|
+
class?: string;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
/**
|
|
@@ -25,6 +29,8 @@ export interface MenuOptions {
|
|
|
25
29
|
type MenuState = {
|
|
26
30
|
items: MenuItem[];
|
|
27
31
|
orientation: string;
|
|
32
|
+
style: string;
|
|
33
|
+
class: string;
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
/**
|
|
@@ -39,19 +45,43 @@ type MenuState = {
|
|
|
39
45
|
* ]
|
|
40
46
|
* });
|
|
41
47
|
* menu.render();
|
|
48
|
+
*
|
|
49
|
+
* Active states are automatically set based on current URL
|
|
42
50
|
*/
|
|
43
|
-
export class Menu
|
|
44
|
-
state
|
|
51
|
+
export class Menu {
|
|
52
|
+
state: MenuState;
|
|
45
53
|
container: HTMLElement | null = null;
|
|
54
|
+
_id: string;
|
|
55
|
+
id: string;
|
|
46
56
|
|
|
47
|
-
constructor(
|
|
48
|
-
|
|
49
|
-
this.
|
|
57
|
+
constructor(id: string, options: MenuOptions = {}) {
|
|
58
|
+
this._id = id;
|
|
59
|
+
this.id = id;
|
|
50
60
|
|
|
51
|
-
this.state =
|
|
61
|
+
this.state = {
|
|
52
62
|
items: options.items ?? [],
|
|
53
|
-
orientation: options.orientation ?? 'vertical'
|
|
54
|
-
|
|
63
|
+
orientation: options.orientation ?? 'vertical',
|
|
64
|
+
style: options.style ?? '',
|
|
65
|
+
class: options.class ?? ''
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Auto-set active state based on current path
|
|
69
|
+
this._setActiveStates();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Set active state on items based on current request path
|
|
74
|
+
*/
|
|
75
|
+
private _setActiveStates(): void {
|
|
76
|
+
this.state.items = this.state.items.map(item => ({
|
|
77
|
+
...item,
|
|
78
|
+
active: item.href ? req.isActiveNavItem(item.href) : false,
|
|
79
|
+
// Recursively set active for subitems
|
|
80
|
+
items: item.items?.map(subItem => ({
|
|
81
|
+
...subItem,
|
|
82
|
+
active: subItem.href ? req.isActiveNavItem(subItem.href) : false
|
|
83
|
+
}))
|
|
84
|
+
}));
|
|
55
85
|
}
|
|
56
86
|
|
|
57
87
|
/* -------------------------
|
|
@@ -60,11 +90,13 @@ export class Menu extends Reactive {
|
|
|
60
90
|
|
|
61
91
|
items(value: MenuItem[]): this {
|
|
62
92
|
this.state.items = value;
|
|
93
|
+
this._setActiveStates();
|
|
63
94
|
return this;
|
|
64
95
|
}
|
|
65
96
|
|
|
66
97
|
addItem(item: MenuItem): this {
|
|
67
|
-
this.state.items.
|
|
98
|
+
this.state.items = [...this.state.items, item];
|
|
99
|
+
this._setActiveStates();
|
|
68
100
|
return this;
|
|
69
101
|
}
|
|
70
102
|
|
|
@@ -73,6 +105,16 @@ export class Menu extends Reactive {
|
|
|
73
105
|
return this;
|
|
74
106
|
}
|
|
75
107
|
|
|
108
|
+
style(value: string): this {
|
|
109
|
+
this.state.style = value;
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class(value: string): this {
|
|
114
|
+
this.state.class = value;
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
|
|
76
118
|
/* -------------------------
|
|
77
119
|
* Helpers
|
|
78
120
|
* ------------------------- */
|
|
@@ -81,21 +123,55 @@ export class Menu extends Reactive {
|
|
|
81
123
|
const menuItem = document.createElement('div');
|
|
82
124
|
menuItem.className = 'jux-menu-item';
|
|
83
125
|
|
|
126
|
+
// Add active class if item is active
|
|
127
|
+
if (item.active) {
|
|
128
|
+
menuItem.classList.add('jux-menu-item-active');
|
|
129
|
+
}
|
|
130
|
+
|
|
84
131
|
if (item.href) {
|
|
85
132
|
const link = document.createElement('a');
|
|
86
133
|
link.className = 'jux-menu-link';
|
|
87
134
|
link.href = item.href;
|
|
88
|
-
|
|
135
|
+
|
|
136
|
+
// Add active class to link if item is active
|
|
137
|
+
if (item.active) {
|
|
138
|
+
link.classList.add('jux-menu-link-active');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (item.icon) {
|
|
142
|
+
const icon = document.createElement('span');
|
|
143
|
+
icon.className = 'jux-menu-icon';
|
|
144
|
+
icon.textContent = item.icon;
|
|
145
|
+
link.appendChild(icon);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const label = document.createElement('span');
|
|
149
|
+
label.className = 'jux-menu-label';
|
|
150
|
+
label.textContent = item.label;
|
|
151
|
+
link.appendChild(label);
|
|
152
|
+
|
|
89
153
|
menuItem.appendChild(link);
|
|
90
154
|
} else {
|
|
91
155
|
const button = document.createElement('button');
|
|
92
156
|
button.className = 'jux-menu-button';
|
|
93
|
-
|
|
157
|
+
|
|
158
|
+
if (item.icon) {
|
|
159
|
+
const icon = document.createElement('span');
|
|
160
|
+
icon.className = 'jux-menu-icon';
|
|
161
|
+
icon.textContent = item.icon;
|
|
162
|
+
button.appendChild(icon);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const label = document.createElement('span');
|
|
166
|
+
label.className = 'jux-menu-label';
|
|
167
|
+
label.textContent = item.label;
|
|
168
|
+
button.appendChild(label);
|
|
169
|
+
|
|
94
170
|
menuItem.appendChild(button);
|
|
95
171
|
|
|
96
|
-
// Event binding -
|
|
97
|
-
if (item.
|
|
98
|
-
button.addEventListener('click', item.
|
|
172
|
+
// Event binding - click
|
|
173
|
+
if (item.click) {
|
|
174
|
+
button.addEventListener('click', item.click);
|
|
99
175
|
}
|
|
100
176
|
}
|
|
101
177
|
|
|
@@ -128,15 +204,23 @@ export class Menu extends Reactive {
|
|
|
128
204
|
}
|
|
129
205
|
container = target;
|
|
130
206
|
} else {
|
|
131
|
-
container = getOrCreateContainer(this.
|
|
207
|
+
container = getOrCreateContainer(this._id);
|
|
132
208
|
}
|
|
133
209
|
|
|
134
210
|
this.container = container;
|
|
135
|
-
const { items, orientation } = this.state;
|
|
211
|
+
const { items, orientation, style, class: className } = this.state;
|
|
136
212
|
|
|
137
213
|
const menu = document.createElement('nav');
|
|
138
214
|
menu.className = `jux-menu jux-menu-${orientation}`;
|
|
139
|
-
menu.id = this.
|
|
215
|
+
menu.id = this._id;
|
|
216
|
+
|
|
217
|
+
if (className) {
|
|
218
|
+
menu.className += ` ${className}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (style) {
|
|
222
|
+
menu.setAttribute('style', style);
|
|
223
|
+
}
|
|
140
224
|
|
|
141
225
|
items.forEach(item => {
|
|
142
226
|
menu.appendChild(this._renderMenuItem(item));
|
|
@@ -154,17 +238,17 @@ export class Menu extends Reactive {
|
|
|
154
238
|
throw new Error('Menu.renderTo: Invalid component - not an object');
|
|
155
239
|
}
|
|
156
240
|
|
|
157
|
-
if (!juxComponent.
|
|
158
|
-
throw new Error('Menu.renderTo: Invalid component - missing
|
|
241
|
+
if (!juxComponent._id || typeof juxComponent._id !== 'string') {
|
|
242
|
+
throw new Error('Menu.renderTo: Invalid component - missing _id (not a Jux component)');
|
|
159
243
|
}
|
|
160
244
|
|
|
161
|
-
return this.render(`#${juxComponent.
|
|
245
|
+
return this.render(`#${juxComponent._id}`);
|
|
162
246
|
}
|
|
163
247
|
}
|
|
164
248
|
|
|
165
249
|
/**
|
|
166
250
|
* Factory helper
|
|
167
251
|
*/
|
|
168
|
-
export function menu(
|
|
169
|
-
return new Menu(
|
|
252
|
+
export function menu(id: string, options: MenuOptions = {}): Menu {
|
|
253
|
+
return new Menu(id, options);
|
|
170
254
|
}
|