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,280 @@
|
|
|
1
|
+
import { getOrCreateContainer } from './helpers.js';
|
|
2
|
+
import { State } from '../reactivity/state.js';
|
|
3
|
+
|
|
4
|
+
export interface SelectOption {
|
|
5
|
+
label: string;
|
|
6
|
+
value: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SelectOptions {
|
|
11
|
+
options?: SelectOption[];
|
|
12
|
+
value?: string;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
name?: string;
|
|
17
|
+
onChange?: (value: string) => void;
|
|
18
|
+
style?: string;
|
|
19
|
+
class?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type SelectState = {
|
|
23
|
+
options: SelectOption[];
|
|
24
|
+
value: string;
|
|
25
|
+
placeholder: string;
|
|
26
|
+
label: string;
|
|
27
|
+
disabled: boolean;
|
|
28
|
+
name: string;
|
|
29
|
+
style: string;
|
|
30
|
+
class: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export class Select {
|
|
34
|
+
state: SelectState;
|
|
35
|
+
container: HTMLElement | null = null;
|
|
36
|
+
_id: string;
|
|
37
|
+
id: string;
|
|
38
|
+
private _onChange?: (value: string) => void;
|
|
39
|
+
private _boundState?: State<string>;
|
|
40
|
+
|
|
41
|
+
constructor(id: string, options: SelectOptions = {}) {
|
|
42
|
+
this._id = id;
|
|
43
|
+
this.id = id;
|
|
44
|
+
this._onChange = options.onChange;
|
|
45
|
+
|
|
46
|
+
this.state = {
|
|
47
|
+
options: options.options ?? [],
|
|
48
|
+
value: options.value ?? '',
|
|
49
|
+
placeholder: options.placeholder ?? 'Select...',
|
|
50
|
+
label: options.label ?? '',
|
|
51
|
+
disabled: options.disabled ?? false,
|
|
52
|
+
name: options.name ?? id,
|
|
53
|
+
style: options.style ?? '',
|
|
54
|
+
class: options.class ?? ''
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
options(value: SelectOption[]): this {
|
|
59
|
+
this.state.options = value;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
addOption(option: SelectOption): this {
|
|
64
|
+
this.state.options = [...this.state.options, option];
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
value(value: string): this {
|
|
69
|
+
this.state.value = value;
|
|
70
|
+
this._updateElement();
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
placeholder(value: string): this {
|
|
75
|
+
this.state.placeholder = value;
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
label(value: string): this {
|
|
80
|
+
this.state.label = value;
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
disabled(value: boolean): this {
|
|
85
|
+
this.state.disabled = value;
|
|
86
|
+
this._updateElement();
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
name(value: string): this {
|
|
91
|
+
this.state.name = value;
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
style(value: string): this {
|
|
96
|
+
this.state.style = value;
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class(value: string): this {
|
|
101
|
+
this.state.class = value;
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
onChange(handler: (value: string) => void): this {
|
|
106
|
+
this._onChange = handler;
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Two-way binding to state
|
|
112
|
+
*/
|
|
113
|
+
bind(stateObj: State<string>): this {
|
|
114
|
+
this._boundState = stateObj;
|
|
115
|
+
|
|
116
|
+
// Update select when state changes
|
|
117
|
+
stateObj.subscribe((val) => {
|
|
118
|
+
this.state.value = val;
|
|
119
|
+
this._updateElement();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Update state when select changes
|
|
123
|
+
const originalOnChange = this._onChange;
|
|
124
|
+
this._onChange = (value) => {
|
|
125
|
+
stateObj.set(value);
|
|
126
|
+
if (originalOnChange) {
|
|
127
|
+
originalOnChange(value);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private _updateElement(): void {
|
|
135
|
+
const select = document.getElementById(`${this._id}-select`) as HTMLSelectElement;
|
|
136
|
+
if (select) {
|
|
137
|
+
select.value = this.state.value;
|
|
138
|
+
select.disabled = this.state.disabled;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getValue(): string {
|
|
143
|
+
return this.state.value;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
render(targetId?: string): this {
|
|
147
|
+
let container: HTMLElement;
|
|
148
|
+
|
|
149
|
+
if (targetId) {
|
|
150
|
+
const target = document.querySelector(targetId);
|
|
151
|
+
if (!target || !(target instanceof HTMLElement)) {
|
|
152
|
+
throw new Error(`Select: Target element "${targetId}" not found`);
|
|
153
|
+
}
|
|
154
|
+
container = target;
|
|
155
|
+
} else {
|
|
156
|
+
container = getOrCreateContainer(this._id);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.container = container;
|
|
160
|
+
const { options, value, placeholder, label, disabled, name, style, class: className } = this.state;
|
|
161
|
+
|
|
162
|
+
const wrapper = document.createElement('div');
|
|
163
|
+
wrapper.className = 'jux-select';
|
|
164
|
+
wrapper.id = this._id;
|
|
165
|
+
|
|
166
|
+
if (className) {
|
|
167
|
+
wrapper.className += ` ${className}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (style) {
|
|
171
|
+
wrapper.setAttribute('style', style);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Label
|
|
175
|
+
if (label) {
|
|
176
|
+
const labelEl = document.createElement('label');
|
|
177
|
+
labelEl.className = 'jux-select-label';
|
|
178
|
+
labelEl.htmlFor = `${this._id}-select`;
|
|
179
|
+
labelEl.textContent = label;
|
|
180
|
+
wrapper.appendChild(labelEl);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const select = document.createElement('select');
|
|
184
|
+
select.className = 'jux-select-element';
|
|
185
|
+
select.id = `${this._id}-select`;
|
|
186
|
+
select.name = name;
|
|
187
|
+
select.disabled = disabled;
|
|
188
|
+
|
|
189
|
+
// Placeholder option
|
|
190
|
+
if (placeholder) {
|
|
191
|
+
const placeholderOpt = document.createElement('option');
|
|
192
|
+
placeholderOpt.value = '';
|
|
193
|
+
placeholderOpt.textContent = placeholder;
|
|
194
|
+
placeholderOpt.disabled = true;
|
|
195
|
+
placeholderOpt.selected = value === '';
|
|
196
|
+
select.appendChild(placeholderOpt);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Options
|
|
200
|
+
options.forEach(opt => {
|
|
201
|
+
const option = document.createElement('option');
|
|
202
|
+
option.value = opt.value;
|
|
203
|
+
option.textContent = opt.label;
|
|
204
|
+
option.disabled = opt.disabled ?? false;
|
|
205
|
+
option.selected = opt.value === value;
|
|
206
|
+
select.appendChild(option);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
select.addEventListener('change', (e) => {
|
|
210
|
+
const target = e.target as HTMLSelectElement;
|
|
211
|
+
this.state.value = target.value;
|
|
212
|
+
if (this._onChange) {
|
|
213
|
+
this._onChange(target.value);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
wrapper.appendChild(select);
|
|
218
|
+
container.appendChild(wrapper);
|
|
219
|
+
|
|
220
|
+
// Add default styles if not already present
|
|
221
|
+
this._injectDefaultStyles();
|
|
222
|
+
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private _injectDefaultStyles(): void {
|
|
227
|
+
const styleId = 'jux-select-styles';
|
|
228
|
+
if (document.getElementById(styleId)) return;
|
|
229
|
+
|
|
230
|
+
const style = document.createElement('style');
|
|
231
|
+
style.id = styleId;
|
|
232
|
+
style.textContent = `
|
|
233
|
+
.jux-select {
|
|
234
|
+
margin-bottom: 16px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.jux-select-label {
|
|
238
|
+
display: block;
|
|
239
|
+
margin-bottom: 6px;
|
|
240
|
+
font-weight: 500;
|
|
241
|
+
color: #374151;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.jux-select-element {
|
|
245
|
+
width: 100%;
|
|
246
|
+
padding: 8px 12px;
|
|
247
|
+
border: 1px solid #d1d5db;
|
|
248
|
+
border-radius: 6px;
|
|
249
|
+
font-size: 14px;
|
|
250
|
+
background: white;
|
|
251
|
+
cursor: pointer;
|
|
252
|
+
transition: border-color 0.2s;
|
|
253
|
+
box-sizing: border-box;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.jux-select-element:focus {
|
|
257
|
+
outline: none;
|
|
258
|
+
border-color: #3b82f6;
|
|
259
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.jux-select-element:disabled {
|
|
263
|
+
background-color: #f3f4f6;
|
|
264
|
+
cursor: not-allowed;
|
|
265
|
+
}
|
|
266
|
+
`;
|
|
267
|
+
document.head.appendChild(style);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
renderTo(juxComponent: any): this {
|
|
271
|
+
if (!juxComponent?._id) {
|
|
272
|
+
throw new Error('Select.renderTo: Invalid component');
|
|
273
|
+
}
|
|
274
|
+
return this.render(`#${juxComponent._id}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function select(id: string, options: SelectOptions = {}): Select {
|
|
279
|
+
return new Select(id, options);
|
|
280
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getOrCreateContainer } from './helpers.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Sidebar component options
|
|
@@ -9,6 +9,8 @@ export interface SidebarOptions {
|
|
|
9
9
|
position?: 'left' | 'right';
|
|
10
10
|
collapsible?: boolean;
|
|
11
11
|
collapsed?: boolean;
|
|
12
|
+
style?: string;
|
|
13
|
+
class?: string;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -20,6 +22,8 @@ type SidebarState = {
|
|
|
20
22
|
position: string;
|
|
21
23
|
collapsible: boolean;
|
|
22
24
|
collapsed: boolean;
|
|
25
|
+
style: string;
|
|
26
|
+
class: string;
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
/**
|
|
@@ -33,21 +37,25 @@ type SidebarState = {
|
|
|
33
37
|
* });
|
|
34
38
|
* sidebar.render('#appsidebar');
|
|
35
39
|
*/
|
|
36
|
-
export class Sidebar
|
|
37
|
-
state
|
|
40
|
+
export class Sidebar {
|
|
41
|
+
state: SidebarState;
|
|
38
42
|
container: HTMLElement | null = null;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.
|
|
43
|
+
_id: string;
|
|
44
|
+
id: string;
|
|
45
|
+
|
|
46
|
+
constructor(id: string, options: SidebarOptions = {}) {
|
|
47
|
+
this._id = id;
|
|
48
|
+
this.id = id;
|
|
49
|
+
|
|
50
|
+
this.state = {
|
|
45
51
|
title: options.title ?? '',
|
|
46
|
-
width: options.width ?? '
|
|
52
|
+
width: options.width ?? '300px',
|
|
47
53
|
position: options.position ?? 'left',
|
|
48
54
|
collapsible: options.collapsible ?? false,
|
|
49
|
-
collapsed: options.collapsed ?? false
|
|
50
|
-
|
|
55
|
+
collapsed: options.collapsed ?? false,
|
|
56
|
+
style: options.style ?? '',
|
|
57
|
+
class: options.class ?? ''
|
|
58
|
+
};
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
/* -------------------------
|
|
@@ -79,18 +87,55 @@ export class Sidebar extends Reactive {
|
|
|
79
87
|
return this;
|
|
80
88
|
}
|
|
81
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
|
+
|
|
82
100
|
toggle(): this {
|
|
83
101
|
this.state.collapsed = !this.state.collapsed;
|
|
102
|
+
this._updateDOM();
|
|
84
103
|
return this;
|
|
85
104
|
}
|
|
86
105
|
|
|
106
|
+
/* -------------------------
|
|
107
|
+
* Helpers
|
|
108
|
+
* ------------------------- */
|
|
109
|
+
|
|
110
|
+
private _updateDOM(): void {
|
|
111
|
+
if (!this.container) return;
|
|
112
|
+
|
|
113
|
+
const sidebar = this.container.querySelector(`#${this._id}`);
|
|
114
|
+
if (!sidebar || !(sidebar instanceof HTMLElement)) return;
|
|
115
|
+
|
|
116
|
+
const { width, collapsed } = this.state;
|
|
117
|
+
|
|
118
|
+
if (collapsed) {
|
|
119
|
+
sidebar.classList.add('jux-sidebar-collapsed');
|
|
120
|
+
sidebar.style.width = '0';
|
|
121
|
+
} else {
|
|
122
|
+
sidebar.classList.remove('jux-sidebar-collapsed');
|
|
123
|
+
sidebar.style.width = width;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const toggleBtn = sidebar.querySelector('.jux-sidebar-toggle');
|
|
127
|
+
if (toggleBtn) {
|
|
128
|
+
toggleBtn.textContent = collapsed ? '>' : '<';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
87
132
|
/* -------------------------
|
|
88
133
|
* Render
|
|
89
134
|
* ------------------------- */
|
|
90
135
|
|
|
91
136
|
render(targetId?: string): this {
|
|
92
137
|
let container: HTMLElement;
|
|
93
|
-
|
|
138
|
+
|
|
94
139
|
if (targetId) {
|
|
95
140
|
const target = document.querySelector(targetId);
|
|
96
141
|
if (!target || !(target instanceof HTMLElement)) {
|
|
@@ -98,49 +143,54 @@ export class Sidebar extends Reactive {
|
|
|
98
143
|
}
|
|
99
144
|
container = target;
|
|
100
145
|
} else {
|
|
101
|
-
container = getOrCreateContainer(this.
|
|
146
|
+
container = getOrCreateContainer(this._id);
|
|
102
147
|
}
|
|
103
|
-
|
|
148
|
+
|
|
104
149
|
this.container = container;
|
|
105
|
-
const { title, width, position, collapsible, collapsed } = this.state;
|
|
106
|
-
|
|
150
|
+
const { title, width, position, collapsible, collapsed, style, class: className } = this.state;
|
|
151
|
+
|
|
107
152
|
const sidebar = document.createElement('aside');
|
|
108
153
|
sidebar.className = `jux-sidebar jux-sidebar-${position}`;
|
|
109
|
-
sidebar.id = this.
|
|
154
|
+
sidebar.id = this._id;
|
|
110
155
|
sidebar.style.width = collapsed ? '0' : width;
|
|
111
|
-
|
|
156
|
+
|
|
157
|
+
if (className) {
|
|
158
|
+
sidebar.className += ` ${className}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (style) {
|
|
162
|
+
sidebar.setAttribute('style', sidebar.getAttribute('style') + '; ' + style);
|
|
163
|
+
}
|
|
164
|
+
|
|
112
165
|
if (collapsed) {
|
|
113
166
|
sidebar.classList.add('jux-sidebar-collapsed');
|
|
114
167
|
}
|
|
115
|
-
|
|
168
|
+
|
|
116
169
|
if (title) {
|
|
117
170
|
const titleEl = document.createElement('div');
|
|
118
171
|
titleEl.className = 'jux-sidebar-title';
|
|
119
172
|
titleEl.textContent = title;
|
|
120
173
|
sidebar.appendChild(titleEl);
|
|
121
174
|
}
|
|
122
|
-
|
|
175
|
+
|
|
123
176
|
const content = document.createElement('div');
|
|
124
177
|
content.className = 'jux-sidebar-content';
|
|
125
178
|
sidebar.appendChild(content);
|
|
126
|
-
|
|
127
|
-
container.appendChild(sidebar);
|
|
128
|
-
|
|
179
|
+
|
|
129
180
|
// Event binding - toggle button
|
|
130
181
|
if (collapsible) {
|
|
131
182
|
const toggleBtn = document.createElement('button');
|
|
132
183
|
toggleBtn.className = 'jux-sidebar-toggle';
|
|
133
184
|
toggleBtn.textContent = collapsed ? '>' : '<';
|
|
134
|
-
|
|
135
|
-
|
|
185
|
+
|
|
136
186
|
toggleBtn.addEventListener('click', () => {
|
|
137
187
|
this.toggle();
|
|
138
|
-
sidebar.classList.toggle('jux-sidebar-collapsed');
|
|
139
|
-
sidebar.style.width = this.state.collapsed ? '0' : width;
|
|
140
|
-
toggleBtn.textContent = this.state.collapsed ? '>' : '<';
|
|
141
188
|
});
|
|
189
|
+
|
|
190
|
+
sidebar.appendChild(toggleBtn);
|
|
142
191
|
}
|
|
143
|
-
|
|
192
|
+
|
|
193
|
+
container.appendChild(sidebar);
|
|
144
194
|
return this;
|
|
145
195
|
}
|
|
146
196
|
|
|
@@ -151,18 +201,18 @@ export class Sidebar extends Reactive {
|
|
|
151
201
|
if (!juxComponent || typeof juxComponent !== 'object') {
|
|
152
202
|
throw new Error('Sidebar.renderTo: Invalid component - not an object');
|
|
153
203
|
}
|
|
154
|
-
|
|
155
|
-
if (!juxComponent.
|
|
156
|
-
throw new Error('Sidebar.renderTo: Invalid component - missing
|
|
204
|
+
|
|
205
|
+
if (!juxComponent._id || typeof juxComponent._id !== 'string') {
|
|
206
|
+
throw new Error('Sidebar.renderTo: Invalid component - missing _id (not a Jux component)');
|
|
157
207
|
}
|
|
158
|
-
|
|
159
|
-
return this.render(`#${juxComponent.
|
|
208
|
+
|
|
209
|
+
return this.render(`#${juxComponent._id}`);
|
|
160
210
|
}
|
|
161
211
|
}
|
|
162
212
|
|
|
163
213
|
/**
|
|
164
214
|
* Factory helper
|
|
165
215
|
*/
|
|
166
|
-
export function sidebar(
|
|
167
|
-
return new Sidebar(
|
|
216
|
+
export function sidebar(id: string, options: SidebarOptions = {}): Sidebar {
|
|
217
|
+
return new Sidebar(id, options);
|
|
168
218
|
}
|
package/lib/components/style.ts
CHANGED
|
@@ -1,66 +1,53 @@
|
|
|
1
1
|
import { ErrorHandler } from './error-handler.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Style - Inject inline CSS into the document
|
|
5
|
+
* For external stylesheets, use Import component instead
|
|
6
|
+
* Auto-renders when created or modified
|
|
7
|
+
*/
|
|
3
8
|
export class Style {
|
|
4
9
|
private _content: string = '';
|
|
5
|
-
private
|
|
6
|
-
private _isUrl: boolean = false;
|
|
7
|
-
private _element: HTMLLinkElement | HTMLStyleElement | null = null;
|
|
10
|
+
private _element: HTMLStyleElement | null = null;
|
|
8
11
|
|
|
9
|
-
constructor(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
this._url = contentOrUrl;
|
|
15
|
-
this._isUrl = true;
|
|
16
|
-
} else {
|
|
17
|
-
this._content = contentOrUrl;
|
|
18
|
-
this._isUrl = false;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Auto-render if content/url provided
|
|
22
|
-
// render() has its own error handling, so no need for try-catch here
|
|
23
|
-
if (contentOrUrl) {
|
|
12
|
+
constructor(content: string = '') {
|
|
13
|
+
this._content = content;
|
|
14
|
+
|
|
15
|
+
// Auto-render if content provided
|
|
16
|
+
if (content) {
|
|
24
17
|
this.render();
|
|
25
18
|
}
|
|
26
19
|
}
|
|
27
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Set inline CSS content
|
|
23
|
+
*/
|
|
28
24
|
content(css: string): this {
|
|
29
25
|
this._content = css;
|
|
30
|
-
this._isUrl = false;
|
|
31
|
-
this._url = '';
|
|
32
|
-
this.render();
|
|
33
|
-
return this;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
url(href: string): this {
|
|
37
|
-
this._url = href;
|
|
38
|
-
this._isUrl = true;
|
|
39
|
-
this._content = '';
|
|
40
26
|
this.render();
|
|
41
27
|
return this;
|
|
42
28
|
}
|
|
43
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Append CSS to existing content
|
|
32
|
+
*/
|
|
44
33
|
append(css: string): this {
|
|
45
|
-
if (this._isUrl) {
|
|
46
|
-
console.warn('Cannot append to external stylesheet. Use content() to switch to inline styles.');
|
|
47
|
-
return this;
|
|
48
|
-
}
|
|
49
34
|
this._content += '\n' + css;
|
|
50
35
|
this.render();
|
|
51
36
|
return this;
|
|
52
37
|
}
|
|
53
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Prepend CSS to existing content
|
|
41
|
+
*/
|
|
54
42
|
prepend(css: string): this {
|
|
55
|
-
if (this._isUrl) {
|
|
56
|
-
console.warn('Cannot prepend to external stylesheet. Use content() to switch to inline styles.');
|
|
57
|
-
return this;
|
|
58
|
-
}
|
|
59
43
|
this._content = css + '\n' + this._content;
|
|
60
44
|
this.render();
|
|
61
45
|
return this;
|
|
62
46
|
}
|
|
63
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Render the inline style element
|
|
50
|
+
*/
|
|
64
51
|
render(): this {
|
|
65
52
|
if (typeof document === 'undefined') {
|
|
66
53
|
return this;
|
|
@@ -70,47 +57,22 @@ export class Style {
|
|
|
70
57
|
// Remove existing element if it exists
|
|
71
58
|
this.remove();
|
|
72
59
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
link.onerror = () => {
|
|
81
|
-
ErrorHandler.captureError({
|
|
82
|
-
component: 'Style',
|
|
83
|
-
method: 'render',
|
|
84
|
-
message: `Failed to load stylesheet: ${this._url}`,
|
|
85
|
-
timestamp: new Date(),
|
|
86
|
-
context: { url: this._url, type: 'external', error: 'load_failed' }
|
|
87
|
-
});
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
link.onload = () => {
|
|
91
|
-
console.log(`✓ Stylesheet loaded: ${this._url}`);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
document.head.appendChild(link);
|
|
95
|
-
this._element = link;
|
|
96
|
-
} else {
|
|
97
|
-
// Create <style> element for inline CSS
|
|
98
|
-
const style = document.createElement('style');
|
|
99
|
-
style.textContent = this._content;
|
|
100
|
-
document.head.appendChild(style);
|
|
101
|
-
this._element = style;
|
|
102
|
-
}
|
|
60
|
+
// Create <style> element for inline CSS
|
|
61
|
+
const style = document.createElement('style');
|
|
62
|
+
style.textContent = this._content;
|
|
63
|
+
document.head.appendChild(style);
|
|
64
|
+
this._element = style;
|
|
65
|
+
|
|
66
|
+
console.log('✓ Inline styles rendered');
|
|
103
67
|
} catch (error: any) {
|
|
104
|
-
// Catch DOM manipulation errors, permission errors, etc.
|
|
105
68
|
ErrorHandler.captureError({
|
|
106
69
|
component: 'Style',
|
|
107
70
|
method: 'render',
|
|
108
71
|
message: error.message,
|
|
109
72
|
stack: error.stack,
|
|
110
73
|
timestamp: new Date(),
|
|
111
|
-
context: {
|
|
112
|
-
|
|
113
|
-
url: this._url,
|
|
74
|
+
context: {
|
|
75
|
+
type: 'inline',
|
|
114
76
|
error: 'runtime_exception'
|
|
115
77
|
}
|
|
116
78
|
});
|
|
@@ -119,6 +81,9 @@ export class Style {
|
|
|
119
81
|
return this;
|
|
120
82
|
}
|
|
121
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Remove the style from the document
|
|
86
|
+
*/
|
|
122
87
|
remove(): this {
|
|
123
88
|
if (this._element && this._element.parentNode) {
|
|
124
89
|
this._element.parentNode.removeChild(this._element);
|
|
@@ -126,4 +91,16 @@ export class Style {
|
|
|
126
91
|
}
|
|
127
92
|
return this;
|
|
128
93
|
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Factory function
|
|
98
|
+
*
|
|
99
|
+
* Usage:
|
|
100
|
+
* jux.style('body { margin: 0; }');
|
|
101
|
+
* jux.style().content('.highlight { color: red; }');
|
|
102
|
+
* jux.style('h1 { font-size: 2rem; }').append('h2 { font-size: 1.5rem; }');
|
|
103
|
+
*/
|
|
104
|
+
export function style(content: string = ''): Style {
|
|
105
|
+
return new Style(content);
|
|
129
106
|
}
|