juxscript 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +121 -72
- package/lib/components/alert.ts +212 -165
- package/lib/components/badge.ts +93 -103
- package/lib/components/base/BaseComponent.ts +397 -0
- package/lib/components/base/FormInput.ts +322 -0
- package/lib/components/button.ts +63 -122
- package/lib/components/card.ts +109 -155
- package/lib/components/charts/areachart.ts +315 -0
- package/lib/components/charts/barchart.ts +421 -0
- package/lib/components/charts/doughnutchart.ts +263 -0
- package/lib/components/charts/lib/BaseChart.ts +402 -0
- package/lib/components/charts/lib/chart-types.ts +159 -0
- package/lib/components/charts/lib/chart-utils.ts +160 -0
- package/lib/components/charts/lib/chart.ts +707 -0
- package/lib/components/checkbox.ts +264 -127
- package/lib/components/code.ts +75 -108
- package/lib/components/container.ts +113 -130
- package/lib/components/data.ts +37 -5
- package/lib/components/datepicker.ts +195 -147
- package/lib/components/dialog.ts +187 -157
- package/lib/components/divider.ts +85 -191
- package/lib/components/docs-data.json +544 -2027
- package/lib/components/dropdown.ts +178 -136
- package/lib/components/element.ts +227 -171
- package/lib/components/fileupload.ts +285 -228
- package/lib/components/guard.ts +92 -0
- package/lib/components/heading.ts +46 -69
- package/lib/components/helpers.ts +13 -6
- package/lib/components/hero.ts +107 -95
- package/lib/components/icon.ts +160 -0
- package/lib/components/icons.ts +175 -0
- package/lib/components/include.ts +153 -5
- package/lib/components/input.ts +174 -374
- package/lib/components/kpicard.ts +16 -16
- package/lib/components/list.ts +378 -240
- package/lib/components/loading.ts +142 -211
- package/lib/components/menu.ts +103 -97
- package/lib/components/modal.ts +138 -144
- package/lib/components/nav.ts +169 -90
- package/lib/components/paragraph.ts +49 -150
- package/lib/components/progress.ts +118 -200
- package/lib/components/radio.ts +297 -149
- package/lib/components/script.ts +19 -87
- package/lib/components/select.ts +184 -186
- package/lib/components/sidebar.ts +152 -140
- package/lib/components/style.ts +19 -82
- package/lib/components/switch.ts +258 -188
- package/lib/components/table.ts +1117 -170
- package/lib/components/tabs.ts +162 -145
- package/lib/components/theme-toggle.ts +108 -169
- package/lib/components/tooltip.ts +86 -157
- package/lib/components/write.ts +108 -127
- package/lib/jux.ts +86 -41
- package/machinery/build.js +466 -0
- package/machinery/compiler.js +354 -105
- package/machinery/server.js +23 -100
- package/machinery/watcher.js +153 -130
- package/package.json +1 -2
- package/presets/base.css +1166 -0
- package/presets/notion.css +2 -1975
- package/lib/adapters/base-adapter.js +0 -35
- package/lib/adapters/index.js +0 -33
- package/lib/adapters/mysql-adapter.js +0 -65
- package/lib/adapters/postgres-adapter.js +0 -70
- package/lib/adapters/sqlite-adapter.js +0 -56
- package/lib/components/areachart.ts +0 -1246
- package/lib/components/areachartsmooth.ts +0 -1380
- package/lib/components/barchart.ts +0 -1250
- package/lib/components/chart.ts +0 -127
- package/lib/components/doughnutchart.ts +0 -1191
- package/lib/components/footer.ts +0 -165
- package/lib/components/header.ts +0 -187
- package/lib/components/layout.ts +0 -239
- package/lib/components/main.ts +0 -137
- package/lib/layouts/default.jux +0 -8
- package/lib/layouts/figma.jux +0 -0
- /package/lib/{themes → components/charts/lib}/charts.js +0 -0
package/lib/components/radio.ts
CHANGED
|
@@ -1,103 +1,86 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { FormInput, FormInputState } from './base/FormInput.js';
|
|
2
|
+
|
|
3
|
+
// Event definitions
|
|
4
|
+
const TRIGGER_EVENTS = [] as const;
|
|
5
|
+
const CALLBACK_EVENTS = ['change'] as const;
|
|
3
6
|
|
|
4
|
-
/**
|
|
5
|
-
* Radio option
|
|
6
|
-
*/
|
|
7
7
|
export interface RadioOption {
|
|
8
8
|
label: string;
|
|
9
9
|
value: string;
|
|
10
10
|
disabled?: boolean;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* Radio component options
|
|
15
|
-
*/
|
|
16
13
|
export interface RadioOptions {
|
|
17
14
|
options?: RadioOption[];
|
|
18
15
|
value?: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
required?: boolean;
|
|
18
|
+
disabled?: boolean;
|
|
19
19
|
name?: string;
|
|
20
|
-
|
|
21
|
-
orientation?: 'vertical' | 'horizontal';
|
|
20
|
+
orientation?: 'vertical' | 'horizontal'; // ✅ Add orientation
|
|
22
21
|
style?: string;
|
|
23
22
|
class?: string;
|
|
23
|
+
onValidate?: (value: string) => boolean | string;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
* Radio component state
|
|
28
|
-
*/
|
|
29
|
-
type RadioState = {
|
|
26
|
+
interface RadioState extends FormInputState {
|
|
30
27
|
options: RadioOption[];
|
|
31
28
|
value: string;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
style: string;
|
|
35
|
-
class: string;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Radio component - Radio button group
|
|
40
|
-
*
|
|
41
|
-
* Usage:
|
|
42
|
-
* jux.radio('size', {
|
|
43
|
-
* options: [
|
|
44
|
-
* { label: 'Small', value: 's' },
|
|
45
|
-
* { label: 'Medium', value: 'm' },
|
|
46
|
-
* { label: 'Large', value: 'l' }
|
|
47
|
-
* ],
|
|
48
|
-
* value: 'm',
|
|
49
|
-
* onChange: (val) => console.log(val)
|
|
50
|
-
* }).render('#form');
|
|
51
|
-
*
|
|
52
|
-
* // Two-way binding
|
|
53
|
-
* const sizeState = state('m');
|
|
54
|
-
* jux.radio('size').bind(sizeState).render('#form');
|
|
55
|
-
*/
|
|
56
|
-
export class Radio {
|
|
57
|
-
state: RadioState;
|
|
58
|
-
container: HTMLElement | null = null;
|
|
59
|
-
_id: string;
|
|
60
|
-
id: string;
|
|
61
|
-
private _onChange?: (value: string) => void;
|
|
62
|
-
private _boundState?: State<string>;
|
|
29
|
+
orientation: 'vertical' | 'horizontal'; // ✅ Add to state
|
|
30
|
+
}
|
|
63
31
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.id = id;
|
|
67
|
-
this._onChange = options.onChange;
|
|
32
|
+
export class Radio extends FormInput<RadioState> {
|
|
33
|
+
private _radioInputs: HTMLInputElement[] = [];
|
|
68
34
|
|
|
69
|
-
|
|
35
|
+
constructor(id: string, options: RadioOptions = {}) {
|
|
36
|
+
super(id, {
|
|
70
37
|
options: options.options ?? [],
|
|
71
38
|
value: options.value ?? '',
|
|
39
|
+
label: options.label ?? '',
|
|
40
|
+
required: options.required ?? false,
|
|
41
|
+
disabled: options.disabled ?? false,
|
|
72
42
|
name: options.name ?? id,
|
|
73
|
-
orientation: options.orientation ?? 'vertical',
|
|
43
|
+
orientation: options.orientation ?? 'vertical', // ✅ Default to vertical
|
|
74
44
|
style: options.style ?? '',
|
|
75
|
-
class: options.class ?? ''
|
|
76
|
-
|
|
45
|
+
class: options.class ?? '',
|
|
46
|
+
errorMessage: undefined
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (options.onValidate) {
|
|
50
|
+
this._onValidate = options.onValidate;
|
|
51
|
+
}
|
|
77
52
|
}
|
|
78
53
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
54
|
+
protected getTriggerEvents(): readonly string[] {
|
|
55
|
+
return TRIGGER_EVENTS;
|
|
56
|
+
}
|
|
82
57
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return this;
|
|
58
|
+
protected getCallbackEvents(): readonly string[] {
|
|
59
|
+
return CALLBACK_EVENTS;
|
|
86
60
|
}
|
|
87
61
|
|
|
88
|
-
|
|
89
|
-
|
|
62
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
63
|
+
* FLUENT API
|
|
64
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
65
|
+
|
|
66
|
+
// ✅ Inherited from FormInput/BaseComponent:
|
|
67
|
+
// - label(), required(), name(), onValidate()
|
|
68
|
+
// - validate(), isValid()
|
|
69
|
+
// - style(), class()
|
|
70
|
+
// - bind(), sync(), renderTo()
|
|
71
|
+
// - disabled(), enable(), disable()
|
|
72
|
+
|
|
73
|
+
options(value: RadioOption[]): this {
|
|
74
|
+
this.state.options = value;
|
|
90
75
|
return this;
|
|
91
76
|
}
|
|
92
77
|
|
|
93
78
|
value(value: string): this {
|
|
94
|
-
this.
|
|
95
|
-
this._updateElement();
|
|
96
|
-
return this;
|
|
79
|
+
return this.setValue(value);
|
|
97
80
|
}
|
|
98
81
|
|
|
99
|
-
|
|
100
|
-
this.state.
|
|
82
|
+
addOption(option: RadioOption): this {
|
|
83
|
+
this.state.options = [...this.state.options, option];
|
|
101
84
|
return this;
|
|
102
85
|
}
|
|
103
86
|
|
|
@@ -106,128 +89,293 @@ export class Radio {
|
|
|
106
89
|
return this;
|
|
107
90
|
}
|
|
108
91
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
class(value: string): this {
|
|
115
|
-
this.state.class = value;
|
|
116
|
-
return this;
|
|
117
|
-
}
|
|
92
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
93
|
+
* FORM INPUT IMPLEMENTATION
|
|
94
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
118
95
|
|
|
119
|
-
|
|
120
|
-
this.
|
|
121
|
-
return this;
|
|
96
|
+
getValue(): string {
|
|
97
|
+
return this.state.value;
|
|
122
98
|
}
|
|
123
99
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
*/
|
|
127
|
-
bind(stateObj: State<string>): this {
|
|
128
|
-
this._boundState = stateObj;
|
|
100
|
+
setValue(value: string): this {
|
|
101
|
+
this.state.value = value;
|
|
129
102
|
|
|
130
|
-
// Update radio
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
this._updateElement();
|
|
103
|
+
// Update all radio inputs
|
|
104
|
+
this._radioInputs.forEach(input => {
|
|
105
|
+
input.checked = input.value === value;
|
|
134
106
|
});
|
|
135
107
|
|
|
136
|
-
// Update state when radio changes
|
|
137
|
-
this.onChange((value) => stateObj.set(value));
|
|
138
|
-
|
|
139
108
|
return this;
|
|
140
109
|
}
|
|
141
110
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
* ------------------------- */
|
|
111
|
+
protected _validateValue(value: string): boolean | string {
|
|
112
|
+
const { required, options } = this.state;
|
|
145
113
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
114
|
+
if (required && !value) {
|
|
115
|
+
return 'Please select an option';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Validate that value is one of the options
|
|
119
|
+
if (value && !options.some(opt => opt.value === value)) {
|
|
120
|
+
return 'Invalid option selected';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (this._onValidate) {
|
|
124
|
+
const result = this._onValidate(value);
|
|
125
|
+
if (result !== true) {
|
|
126
|
+
return result || 'Invalid value';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return true;
|
|
152
131
|
}
|
|
153
132
|
|
|
154
|
-
|
|
155
|
-
|
|
133
|
+
protected _buildInputElement(): HTMLElement {
|
|
134
|
+
// Radio uses a container, not a single input
|
|
135
|
+
const container = document.createElement('div');
|
|
136
|
+
container.className = 'jux-radio-options';
|
|
137
|
+
return container;
|
|
156
138
|
}
|
|
157
139
|
|
|
158
|
-
/*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
140
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
141
|
+
* RENDER
|
|
142
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
161
143
|
|
|
162
144
|
render(targetId?: string): this {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (targetId) {
|
|
166
|
-
const target = document.querySelector(targetId);
|
|
167
|
-
if (!target || !(target instanceof HTMLElement)) {
|
|
168
|
-
throw new Error(`Radio: Target element "${targetId}" not found`);
|
|
169
|
-
}
|
|
170
|
-
container = target;
|
|
171
|
-
} else {
|
|
172
|
-
container = getOrCreateContainer(this._id);
|
|
173
|
-
}
|
|
145
|
+
const container = this._setupContainer(targetId);
|
|
174
146
|
|
|
175
|
-
this.
|
|
176
|
-
const { options, value, name, orientation, style, class: className } = this.state;
|
|
147
|
+
const { options, value, name, disabled, orientation, style, class: className } = this.state; // ✅ Destructure orientation
|
|
177
148
|
|
|
149
|
+
// Build wrapper
|
|
178
150
|
const wrapper = document.createElement('div');
|
|
179
|
-
wrapper.className =
|
|
151
|
+
wrapper.className = 'jux-radio';
|
|
180
152
|
wrapper.id = this._id;
|
|
181
|
-
wrapper.
|
|
153
|
+
if (className) wrapper.className += ` ${className}`;
|
|
154
|
+
if (style) wrapper.setAttribute('style', style);
|
|
182
155
|
|
|
183
|
-
|
|
184
|
-
|
|
156
|
+
// Label
|
|
157
|
+
if (this.state.label) {
|
|
158
|
+
wrapper.appendChild(this._renderLabel());
|
|
185
159
|
}
|
|
186
160
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
161
|
+
// Radio options container
|
|
162
|
+
const optionsContainer = document.createElement('div');
|
|
163
|
+
optionsContainer.className = `jux-radio-options jux-radio-${orientation}`; // ✅ Add orientation class
|
|
164
|
+
this._inputElement = optionsContainer;
|
|
165
|
+
|
|
166
|
+
this._radioInputs = [];
|
|
190
167
|
|
|
191
|
-
options.forEach((
|
|
192
|
-
const
|
|
193
|
-
|
|
168
|
+
options.forEach((option, index) => {
|
|
169
|
+
const radioWrapper = document.createElement('label');
|
|
170
|
+
radioWrapper.className = 'jux-radio-option';
|
|
194
171
|
|
|
195
172
|
const input = document.createElement('input');
|
|
196
173
|
input.type = 'radio';
|
|
197
174
|
input.className = 'jux-radio-input';
|
|
198
|
-
input.id = `${this._id}-${index}`;
|
|
175
|
+
input.id = `${this._id}-option-${index}`;
|
|
199
176
|
input.name = name;
|
|
200
|
-
input.value =
|
|
201
|
-
input.checked =
|
|
202
|
-
input.disabled =
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
177
|
+
input.value = option.value;
|
|
178
|
+
input.checked = option.value === value;
|
|
179
|
+
input.disabled = disabled || option.disabled || false;
|
|
180
|
+
|
|
181
|
+
this._radioInputs.push(input);
|
|
182
|
+
|
|
183
|
+
const radioMark = document.createElement('span');
|
|
184
|
+
radioMark.className = 'jux-radio-mark';
|
|
185
|
+
|
|
186
|
+
const labelText = document.createElement('span');
|
|
187
|
+
labelText.className = 'jux-radio-label-text';
|
|
188
|
+
labelText.textContent = option.label;
|
|
189
|
+
|
|
190
|
+
radioWrapper.appendChild(input);
|
|
191
|
+
radioWrapper.appendChild(radioMark);
|
|
192
|
+
radioWrapper.appendChild(labelText);
|
|
193
|
+
|
|
194
|
+
optionsContainer.appendChild(radioWrapper);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
wrapper.appendChild(optionsContainer);
|
|
198
|
+
|
|
199
|
+
// Error element
|
|
200
|
+
wrapper.appendChild(this._renderError());
|
|
201
|
+
|
|
202
|
+
// Wire events
|
|
203
|
+
this._wireStandardEvents(wrapper);
|
|
204
|
+
|
|
205
|
+
// Wire radio-specific sync
|
|
206
|
+
const valueSync = this._syncBindings.find(b => b.property === 'value');
|
|
207
|
+
|
|
208
|
+
if (valueSync) {
|
|
209
|
+
const { stateObj, toState, toComponent } = valueSync;
|
|
210
|
+
|
|
211
|
+
const transformToState = toState || ((v: string) => v);
|
|
212
|
+
const transformToComponent = toComponent || ((v: any) => String(v));
|
|
213
|
+
|
|
214
|
+
let isUpdating = false;
|
|
215
|
+
|
|
216
|
+
// State → Component
|
|
217
|
+
stateObj.subscribe((val: any) => {
|
|
218
|
+
if (isUpdating) return;
|
|
219
|
+
const transformed = transformToComponent(val);
|
|
220
|
+
this.setValue(transformed);
|
|
210
221
|
});
|
|
211
222
|
|
|
212
|
-
|
|
223
|
+
// Component → State
|
|
224
|
+
this._radioInputs.forEach(input => {
|
|
225
|
+
input.addEventListener('change', () => {
|
|
226
|
+
if (isUpdating) return;
|
|
227
|
+
isUpdating = true;
|
|
213
228
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
229
|
+
const selectedValue = input.value;
|
|
230
|
+
this.state.value = selectedValue;
|
|
231
|
+
this._clearError();
|
|
232
|
+
|
|
233
|
+
const transformed = transformToState(selectedValue);
|
|
234
|
+
stateObj.set(transformed);
|
|
235
|
+
|
|
236
|
+
// 🎯 Fire the change callback event
|
|
237
|
+
this._triggerCallback('change', selectedValue);
|
|
238
|
+
|
|
239
|
+
setTimeout(() => { isUpdating = false; }, 0);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
} else {
|
|
243
|
+
// Default behavior without sync
|
|
244
|
+
this._radioInputs.forEach(input => {
|
|
245
|
+
input.addEventListener('change', () => {
|
|
246
|
+
this.state.value = input.value;
|
|
247
|
+
this._clearError();
|
|
248
|
+
|
|
249
|
+
// 🎯 Fire the change callback event
|
|
250
|
+
this._triggerCallback('change', input.value);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
}
|
|
218
254
|
|
|
219
|
-
|
|
255
|
+
// Always add blur validation
|
|
256
|
+
this._radioInputs.forEach(input => {
|
|
257
|
+
input.addEventListener('blur', () => {
|
|
258
|
+
this.validate();
|
|
259
|
+
});
|
|
220
260
|
});
|
|
221
261
|
|
|
262
|
+
// Sync label changes
|
|
263
|
+
const labelSync = this._syncBindings.find(b => b.property === 'label');
|
|
264
|
+
if (labelSync) {
|
|
265
|
+
const transform = labelSync.toComponent || ((v: any) => String(v));
|
|
266
|
+
labelSync.stateObj.subscribe((val: any) => {
|
|
267
|
+
this.label(transform(val));
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
222
271
|
container.appendChild(wrapper);
|
|
272
|
+
this._injectRadioStyles();
|
|
273
|
+
|
|
223
274
|
return this;
|
|
224
275
|
}
|
|
225
276
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
277
|
+
private _injectRadioStyles(): void {
|
|
278
|
+
const styleId = 'jux-radio-styles';
|
|
279
|
+
if (document.getElementById(styleId)) return;
|
|
280
|
+
|
|
281
|
+
const style = document.createElement('style');
|
|
282
|
+
style.id = styleId;
|
|
283
|
+
style.textContent = `
|
|
284
|
+
.jux-radio {
|
|
285
|
+
margin-bottom: 12px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.jux-radio-options {
|
|
289
|
+
display: flex;
|
|
290
|
+
gap: 8px;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* ✅ Vertical orientation (default) */
|
|
294
|
+
.jux-radio-vertical {
|
|
295
|
+
flex-direction: column;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* ✅ Horizontal orientation */
|
|
299
|
+
.jux-radio-horizontal {
|
|
300
|
+
flex-direction: row;
|
|
301
|
+
flex-wrap: wrap;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.jux-radio-option {
|
|
305
|
+
display: inline-flex;
|
|
306
|
+
align-items: center;
|
|
307
|
+
cursor: pointer;
|
|
308
|
+
user-select: none;
|
|
309
|
+
position: relative;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.jux-radio-input {
|
|
313
|
+
position: absolute;
|
|
314
|
+
opacity: 0;
|
|
315
|
+
cursor: pointer;
|
|
316
|
+
height: 0;
|
|
317
|
+
width: 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.jux-radio-mark {
|
|
321
|
+
position: relative;
|
|
322
|
+
height: 20px;
|
|
323
|
+
width: 20px;
|
|
324
|
+
border: 2px solid #d1d5db;
|
|
325
|
+
border-radius: 50%;
|
|
326
|
+
background-color: white;
|
|
327
|
+
transition: all 0.2s;
|
|
328
|
+
display: flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
justify-content: center;
|
|
331
|
+
margin-right: 8px;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.jux-radio-mark::after {
|
|
335
|
+
content: '';
|
|
336
|
+
position: absolute;
|
|
337
|
+
width: 10px;
|
|
338
|
+
height: 10px;
|
|
339
|
+
border-radius: 50%;
|
|
340
|
+
background-color: white;
|
|
341
|
+
opacity: 0;
|
|
342
|
+
transition: opacity 0.2s;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.jux-radio-input:checked ~ .jux-radio-mark {
|
|
346
|
+
background-color: #3b82f6;
|
|
347
|
+
border-color: #3b82f6;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.jux-radio-input:checked ~ .jux-radio-mark::after {
|
|
351
|
+
opacity: 1;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.jux-radio-input:disabled ~ .jux-radio-mark {
|
|
355
|
+
background-color: #f3f4f6;
|
|
356
|
+
border-color: #d1d5db;
|
|
357
|
+
cursor: not-allowed;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.jux-radio-input:disabled ~ .jux-radio-label-text {
|
|
361
|
+
color: #9ca3af;
|
|
362
|
+
cursor: not-allowed;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.jux-radio-input:focus ~ .jux-radio-mark {
|
|
366
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.jux-radio-input.jux-input-invalid ~ .jux-radio-mark {
|
|
370
|
+
border-color: #ef4444;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.jux-radio-label-text {
|
|
374
|
+
font-size: 14px;
|
|
375
|
+
color: #374151;
|
|
376
|
+
}
|
|
377
|
+
`;
|
|
378
|
+
document.head.appendChild(style);
|
|
231
379
|
}
|
|
232
380
|
}
|
|
233
381
|
|
package/lib/components/script.ts
CHANGED
|
@@ -6,106 +6,38 @@ import { ErrorHandler } from './error-handler.js';
|
|
|
6
6
|
* Auto-renders when created or modified
|
|
7
7
|
*/
|
|
8
8
|
export class Script {
|
|
9
|
-
private
|
|
10
|
-
private
|
|
11
|
-
private _element: HTMLScriptElement | null = null;
|
|
9
|
+
private _id: string;
|
|
10
|
+
private code: string;
|
|
12
11
|
|
|
13
|
-
constructor(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (content) {
|
|
18
|
-
this.render();
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Set inline JavaScript content
|
|
24
|
-
*/
|
|
25
|
-
content(js: string): this {
|
|
26
|
-
this._content = js;
|
|
27
|
-
this.render();
|
|
28
|
-
return this;
|
|
12
|
+
constructor(code: string, id?: string) {
|
|
13
|
+
// ID only for deduplication, auto-generate if not provided
|
|
14
|
+
this._id = id || `jux-script-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
15
|
+
this.code = code;
|
|
29
16
|
}
|
|
30
17
|
|
|
31
|
-
/**
|
|
32
|
-
* Set script type (e.g., 'module', 'text/javascript')
|
|
33
|
-
*/
|
|
34
|
-
type(value: string): this {
|
|
35
|
-
this._type = value;
|
|
36
|
-
this.render();
|
|
37
|
-
return this;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Set as module script
|
|
42
|
-
*/
|
|
43
|
-
module(): this {
|
|
44
|
-
this._type = 'module';
|
|
45
|
-
this.render();
|
|
46
|
-
return this;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Render the inline script element
|
|
51
|
-
*/
|
|
52
18
|
render(): this {
|
|
53
|
-
if
|
|
19
|
+
// Check if script with this ID already exists
|
|
20
|
+
if (document.getElementById(this._id)) {
|
|
54
21
|
return this;
|
|
55
22
|
}
|
|
56
23
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const script = document.createElement('script');
|
|
62
|
-
script.textContent = this._content;
|
|
63
|
-
|
|
64
|
-
if (this._type) {
|
|
65
|
-
script.type = this._type;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
document.head.appendChild(script);
|
|
69
|
-
this._element = script;
|
|
70
|
-
|
|
71
|
-
console.log('✓ Inline script rendered');
|
|
72
|
-
} catch (error: any) {
|
|
73
|
-
ErrorHandler.captureError({
|
|
74
|
-
component: 'Script',
|
|
75
|
-
method: 'render',
|
|
76
|
-
message: error.message,
|
|
77
|
-
stack: error.stack,
|
|
78
|
-
timestamp: new Date(),
|
|
79
|
-
context: {
|
|
80
|
-
type: this._type || 'inline',
|
|
81
|
-
error: 'runtime_exception'
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
}
|
|
24
|
+
const script = document.createElement('script');
|
|
25
|
+
script.id = this._id;
|
|
26
|
+
script.textContent = this.code;
|
|
27
|
+
document.head.appendChild(script);
|
|
85
28
|
|
|
86
29
|
return this;
|
|
87
30
|
}
|
|
88
31
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (this._element && this._element.parentNode) {
|
|
94
|
-
this._element.parentNode.removeChild(this._element);
|
|
95
|
-
this._element = null;
|
|
32
|
+
remove(): void {
|
|
33
|
+
const existing = document.getElementById(this._id);
|
|
34
|
+
if (existing) {
|
|
35
|
+
existing.remove();
|
|
96
36
|
}
|
|
97
|
-
return this;
|
|
98
37
|
}
|
|
99
38
|
}
|
|
100
39
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
* Usage:
|
|
105
|
-
* jux.script('console.log("Hello")');
|
|
106
|
-
* jux.script().content('alert("Hi")');
|
|
107
|
-
* jux.script('export const x = 1;').module();
|
|
108
|
-
*/
|
|
109
|
-
export function script(content: string = ''): Script {
|
|
110
|
-
return new Script(content);
|
|
40
|
+
// ✅ ID is optional
|
|
41
|
+
export function script(code: string, id?: string): Script {
|
|
42
|
+
return new Script(code, id);
|
|
111
43
|
}
|