juxscript 1.0.20 → 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 +143 -92
- package/lib/components/badge.ts +93 -94
- package/lib/components/base/BaseComponent.ts +397 -0
- package/lib/components/base/FormInput.ts +322 -0
- package/lib/components/button.ts +40 -131
- package/lib/components/card.ts +57 -79
- 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/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
- package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
- package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
- package/lib/components/checkbox.ts +255 -204
- package/lib/components/code.ts +31 -78
- package/lib/components/container.ts +113 -130
- package/lib/components/data.ts +37 -5
- package/lib/components/datepicker.ts +180 -147
- package/lib/components/dialog.ts +218 -221
- package/lib/components/divider.ts +63 -87
- package/lib/components/docs-data.json +498 -2404
- package/lib/components/dropdown.ts +191 -236
- package/lib/components/element.ts +196 -145
- package/lib/components/fileupload.ts +253 -167
- package/lib/components/guard.ts +92 -0
- package/lib/components/heading.ts +31 -97
- package/lib/components/helpers.ts +13 -6
- package/lib/components/hero.ts +51 -114
- package/lib/components/icon.ts +33 -120
- package/lib/components/icons.ts +2 -1
- package/lib/components/include.ts +76 -3
- package/lib/components/input.ts +155 -407
- package/lib/components/kpicard.ts +16 -16
- package/lib/components/list.ts +358 -261
- package/lib/components/loading.ts +142 -211
- package/lib/components/menu.ts +63 -152
- package/lib/components/modal.ts +42 -129
- package/lib/components/nav.ts +79 -101
- package/lib/components/paragraph.ts +38 -102
- package/lib/components/progress.ts +108 -166
- package/lib/components/radio.ts +283 -234
- package/lib/components/script.ts +19 -87
- package/lib/components/select.ts +189 -199
- package/lib/components/sidebar.ts +110 -141
- package/lib/components/style.ts +19 -82
- package/lib/components/switch.ts +254 -183
- package/lib/components/table.ts +1078 -208
- package/lib/components/tabs.ts +42 -106
- package/lib/components/theme-toggle.ts +73 -165
- package/lib/components/tooltip.ts +85 -316
- package/lib/components/write.ts +108 -127
- package/lib/jux.ts +67 -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 -1
- 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 -1128
- package/lib/components/areachartsmooth.ts +0 -1380
- package/lib/components/barchart.ts +0 -1322
- package/lib/components/doughnutchart.ts +0 -1259
- 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,111 +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
|
-
orientation?: 'vertical' | 'horizontal';
|
|
20
|
+
orientation?: 'vertical' | 'horizontal'; // ✅ Add orientation
|
|
21
21
|
style?: string;
|
|
22
22
|
class?: string;
|
|
23
|
+
onValidate?: (value: string) => boolean | string;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
* Radio component state
|
|
27
|
-
*/
|
|
28
|
-
type RadioState = {
|
|
26
|
+
interface RadioState extends FormInputState {
|
|
29
27
|
options: RadioOption[];
|
|
30
28
|
value: string;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
style: string;
|
|
34
|
-
class: string;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Radio component - Radio button group
|
|
39
|
-
*
|
|
40
|
-
* Usage:
|
|
41
|
-
* jux.radio('size', {
|
|
42
|
-
* options: [
|
|
43
|
-
* { label: 'Small', value: 's' },
|
|
44
|
-
* { label: 'Medium', value: 'm' },
|
|
45
|
-
* { label: 'Large', value: 'l' }
|
|
46
|
-
* ],
|
|
47
|
-
* value: 'm'
|
|
48
|
-
* })
|
|
49
|
-
* .bind('change', (e) => console.log(e.target.value))
|
|
50
|
-
* .render('#form');
|
|
51
|
-
*
|
|
52
|
-
* // Two-way binding with state
|
|
53
|
-
* const sizeState = state('m');
|
|
54
|
-
* jux.radio('size')
|
|
55
|
-
* .sync('value', sizeState)
|
|
56
|
-
* .render('#form');
|
|
57
|
-
*/
|
|
58
|
-
export class Radio {
|
|
59
|
-
state: RadioState;
|
|
60
|
-
container: HTMLElement | null = null;
|
|
61
|
-
_id: string;
|
|
62
|
-
id: string;
|
|
63
|
-
|
|
64
|
-
// CRITICAL: Store bind/sync instructions for deferred wiring
|
|
65
|
-
private _bindings: Array<{ event: string, handler: Function }> = [];
|
|
66
|
-
private _syncBindings: Array<{
|
|
67
|
-
property: string,
|
|
68
|
-
stateObj: State<any>,
|
|
69
|
-
toState?: Function,
|
|
70
|
-
toComponent?: Function
|
|
71
|
-
}> = [];
|
|
29
|
+
orientation: 'vertical' | 'horizontal'; // ✅ Add to state
|
|
30
|
+
}
|
|
72
31
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
this.id = id;
|
|
32
|
+
export class Radio extends FormInput<RadioState> {
|
|
33
|
+
private _radioInputs: HTMLInputElement[] = [];
|
|
76
34
|
|
|
77
|
-
|
|
35
|
+
constructor(id: string, options: RadioOptions = {}) {
|
|
36
|
+
super(id, {
|
|
78
37
|
options: options.options ?? [],
|
|
79
38
|
value: options.value ?? '',
|
|
39
|
+
label: options.label ?? '',
|
|
40
|
+
required: options.required ?? false,
|
|
41
|
+
disabled: options.disabled ?? false,
|
|
80
42
|
name: options.name ?? id,
|
|
81
|
-
orientation: options.orientation ?? 'vertical',
|
|
43
|
+
orientation: options.orientation ?? 'vertical', // ✅ Default to vertical
|
|
82
44
|
style: options.style ?? '',
|
|
83
|
-
class: options.class ?? ''
|
|
84
|
-
|
|
45
|
+
class: options.class ?? '',
|
|
46
|
+
errorMessage: undefined
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (options.onValidate) {
|
|
50
|
+
this._onValidate = options.onValidate;
|
|
51
|
+
}
|
|
85
52
|
}
|
|
86
53
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
54
|
+
protected getTriggerEvents(): readonly string[] {
|
|
55
|
+
return TRIGGER_EVENTS;
|
|
56
|
+
}
|
|
90
57
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return this;
|
|
58
|
+
protected getCallbackEvents(): readonly string[] {
|
|
59
|
+
return CALLBACK_EVENTS;
|
|
94
60
|
}
|
|
95
61
|
|
|
96
|
-
|
|
97
|
-
|
|
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;
|
|
98
75
|
return this;
|
|
99
76
|
}
|
|
100
77
|
|
|
101
78
|
value(value: string): this {
|
|
102
|
-
this.
|
|
103
|
-
this._updateElement();
|
|
104
|
-
return this;
|
|
79
|
+
return this.setValue(value);
|
|
105
80
|
}
|
|
106
81
|
|
|
107
|
-
|
|
108
|
-
this.state.
|
|
82
|
+
addOption(option: RadioOption): this {
|
|
83
|
+
this.state.options = [...this.state.options, option];
|
|
109
84
|
return this;
|
|
110
85
|
}
|
|
111
86
|
|
|
@@ -114,219 +89,293 @@ export class Radio {
|
|
|
114
89
|
return this;
|
|
115
90
|
}
|
|
116
91
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
92
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
93
|
+
* FORM INPUT IMPLEMENTATION
|
|
94
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
121
95
|
|
|
122
|
-
|
|
123
|
-
this.state.
|
|
124
|
-
return this;
|
|
96
|
+
getValue(): string {
|
|
97
|
+
return this.state.value;
|
|
125
98
|
}
|
|
126
99
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
100
|
+
setValue(value: string): this {
|
|
101
|
+
this.state.value = value;
|
|
102
|
+
|
|
103
|
+
// Update all radio inputs
|
|
104
|
+
this._radioInputs.forEach(input => {
|
|
105
|
+
input.checked = input.value === value;
|
|
106
|
+
});
|
|
107
|
+
|
|
133
108
|
return this;
|
|
134
109
|
}
|
|
135
110
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
* @param toState - Optional transform function when going from component to state
|
|
142
|
-
* @param toComponent - Optional transform function when going from state to component
|
|
143
|
-
*/
|
|
144
|
-
sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
|
|
145
|
-
if (!stateObj || typeof stateObj.subscribe !== 'function') {
|
|
146
|
-
throw new Error(`Radio.sync: Expected a State object for property "${property}"`);
|
|
111
|
+
protected _validateValue(value: string): boolean | string {
|
|
112
|
+
const { required, options } = this.state;
|
|
113
|
+
|
|
114
|
+
if (required && !value) {
|
|
115
|
+
return 'Please select an option';
|
|
147
116
|
}
|
|
148
|
-
this._syncBindings.push({ property, stateObj, toState, toComponent });
|
|
149
|
-
return this;
|
|
150
|
-
}
|
|
151
117
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
118
|
+
// Validate that value is one of the options
|
|
119
|
+
if (value && !options.some(opt => opt.value === value)) {
|
|
120
|
+
return 'Invalid option selected';
|
|
121
|
+
}
|
|
155
122
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
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;
|
|
162
131
|
}
|
|
163
132
|
|
|
164
|
-
|
|
165
|
-
|
|
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;
|
|
166
138
|
}
|
|
167
139
|
|
|
168
|
-
/*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
140
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
141
|
+
* RENDER
|
|
142
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
171
143
|
|
|
172
144
|
render(targetId?: string): this {
|
|
173
|
-
|
|
174
|
-
let container: HTMLElement;
|
|
175
|
-
if (targetId) {
|
|
176
|
-
const target = document.querySelector(targetId);
|
|
177
|
-
if (!target || !(target instanceof HTMLElement)) {
|
|
178
|
-
throw new Error(`Radio: Target "${targetId}" not found`);
|
|
179
|
-
}
|
|
180
|
-
container = target;
|
|
181
|
-
} else {
|
|
182
|
-
container = getOrCreateContainer(this._id);
|
|
183
|
-
}
|
|
184
|
-
this.container = container;
|
|
145
|
+
const container = this._setupContainer(targetId);
|
|
185
146
|
|
|
186
|
-
|
|
187
|
-
const { options, value, name, orientation, style, class: className } = this.state;
|
|
188
|
-
const hasValueSync = this._syncBindings.some(b => b.property === 'value');
|
|
147
|
+
const { options, value, name, disabled, orientation, style, class: className } = this.state; // ✅ Destructure orientation
|
|
189
148
|
|
|
190
|
-
//
|
|
149
|
+
// Build wrapper
|
|
191
150
|
const wrapper = document.createElement('div');
|
|
192
|
-
wrapper.className =
|
|
151
|
+
wrapper.className = 'jux-radio';
|
|
193
152
|
wrapper.id = this._id;
|
|
194
153
|
if (className) wrapper.className += ` ${className}`;
|
|
195
154
|
if (style) wrapper.setAttribute('style', style);
|
|
196
155
|
|
|
197
|
-
|
|
156
|
+
// Label
|
|
157
|
+
if (this.state.label) {
|
|
158
|
+
wrapper.appendChild(this._renderLabel());
|
|
159
|
+
}
|
|
160
|
+
|
|
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;
|
|
198
165
|
|
|
199
|
-
|
|
200
|
-
const radioItem = document.createElement('div');
|
|
201
|
-
radioItem.className = 'jux-radio-item';
|
|
166
|
+
this._radioInputs = [];
|
|
202
167
|
|
|
203
|
-
|
|
204
|
-
|
|
168
|
+
options.forEach((option, index) => {
|
|
169
|
+
const radioWrapper = document.createElement('label');
|
|
170
|
+
radioWrapper.className = 'jux-radio-option';
|
|
205
171
|
|
|
206
172
|
const input = document.createElement('input');
|
|
207
173
|
input.type = 'radio';
|
|
208
174
|
input.className = 'jux-radio-input';
|
|
175
|
+
input.id = `${this._id}-option-${index}`;
|
|
209
176
|
input.name = name;
|
|
210
177
|
input.value = option.value;
|
|
211
178
|
input.checked = option.value === value;
|
|
212
|
-
input.disabled = option.disabled || false;
|
|
213
|
-
|
|
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';
|
|
214
185
|
|
|
215
|
-
const
|
|
216
|
-
|
|
186
|
+
const labelText = document.createElement('span');
|
|
187
|
+
labelText.className = 'jux-radio-label-text';
|
|
188
|
+
labelText.textContent = option.label;
|
|
217
189
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
190
|
+
radioWrapper.appendChild(input);
|
|
191
|
+
radioWrapper.appendChild(radioMark);
|
|
192
|
+
radioWrapper.appendChild(labelText);
|
|
221
193
|
|
|
222
|
-
|
|
223
|
-
label.appendChild(checkmark);
|
|
224
|
-
label.appendChild(text);
|
|
225
|
-
radioItem.appendChild(label);
|
|
226
|
-
wrapper.appendChild(radioItem);
|
|
194
|
+
optionsContainer.appendChild(radioWrapper);
|
|
227
195
|
});
|
|
228
196
|
|
|
229
|
-
|
|
197
|
+
wrapper.appendChild(optionsContainer);
|
|
230
198
|
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
inputs.forEach(input => {
|
|
234
|
-
input.addEventListener('change', () => {
|
|
235
|
-
if (input.checked) {
|
|
236
|
-
this.state.value = input.value;
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
}
|
|
199
|
+
// Error element
|
|
200
|
+
wrapper.appendChild(this._renderError());
|
|
241
201
|
|
|
242
|
-
// Wire
|
|
243
|
-
this.
|
|
244
|
-
wrapper.addEventListener(event, handler as EventListener);
|
|
245
|
-
});
|
|
202
|
+
// Wire events
|
|
203
|
+
this._wireStandardEvents(wrapper);
|
|
246
204
|
|
|
247
|
-
// Wire
|
|
248
|
-
this._syncBindings.
|
|
249
|
-
if (property === 'value') {
|
|
250
|
-
const transformToState = toState || ((v: any) => v);
|
|
251
|
-
const transformToComponent = toComponent || ((v: any) => String(v));
|
|
205
|
+
// Wire radio-specific sync
|
|
206
|
+
const valueSync = this._syncBindings.find(b => b.property === 'value');
|
|
252
207
|
|
|
253
|
-
|
|
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);
|
|
221
|
+
});
|
|
254
222
|
|
|
255
|
-
|
|
256
|
-
|
|
223
|
+
// Component → State
|
|
224
|
+
this._radioInputs.forEach(input => {
|
|
225
|
+
input.addEventListener('change', () => {
|
|
257
226
|
if (isUpdating) return;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
this.
|
|
263
|
-
});
|
|
227
|
+
isUpdating = true;
|
|
228
|
+
|
|
229
|
+
const selectedValue = input.value;
|
|
230
|
+
this.state.value = selectedValue;
|
|
231
|
+
this._clearError();
|
|
264
232
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
input.addEventListener('change', () => {
|
|
268
|
-
if (isUpdating || !input.checked) return;
|
|
269
|
-
isUpdating = true;
|
|
233
|
+
const transformed = transformToState(selectedValue);
|
|
234
|
+
stateObj.set(transformed);
|
|
270
235
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
stateObj.set(transformed);
|
|
236
|
+
// 🎯 Fire the change callback event
|
|
237
|
+
this._triggerCallback('change', selectedValue);
|
|
274
238
|
|
|
275
|
-
|
|
276
|
-
});
|
|
239
|
+
setTimeout(() => { isUpdating = false; }, 0);
|
|
277
240
|
});
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
this.
|
|
285
|
-
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
transformed.forEach((option: any) => {
|
|
289
|
-
const radioItem = document.createElement('div');
|
|
290
|
-
radioItem.className = 'jux-radio-item';
|
|
291
|
-
|
|
292
|
-
const label = document.createElement('label');
|
|
293
|
-
label.className = 'jux-radio-label';
|
|
294
|
-
|
|
295
|
-
const input = document.createElement('input');
|
|
296
|
-
input.type = 'radio';
|
|
297
|
-
input.className = 'jux-radio-input';
|
|
298
|
-
input.name = this.state.name;
|
|
299
|
-
input.value = option.value;
|
|
300
|
-
input.checked = option.value === this.state.value;
|
|
301
|
-
input.disabled = option.disabled || false;
|
|
302
|
-
|
|
303
|
-
const checkmark = document.createElement('span');
|
|
304
|
-
checkmark.className = 'jux-radio-checkmark';
|
|
305
|
-
|
|
306
|
-
const text = document.createElement('span');
|
|
307
|
-
text.className = 'jux-radio-text';
|
|
308
|
-
text.textContent = option.label;
|
|
309
|
-
|
|
310
|
-
label.appendChild(input);
|
|
311
|
-
label.appendChild(checkmark);
|
|
312
|
-
label.appendChild(text);
|
|
313
|
-
radioItem.appendChild(label);
|
|
314
|
-
wrapper.appendChild(radioItem);
|
|
315
|
-
});
|
|
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);
|
|
316
251
|
});
|
|
317
|
-
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Always add blur validation
|
|
256
|
+
this._radioInputs.forEach(input => {
|
|
257
|
+
input.addEventListener('blur', () => {
|
|
258
|
+
this.validate();
|
|
259
|
+
});
|
|
318
260
|
});
|
|
319
261
|
|
|
320
|
-
//
|
|
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
|
+
|
|
321
271
|
container.appendChild(wrapper);
|
|
272
|
+
this._injectRadioStyles();
|
|
273
|
+
|
|
322
274
|
return this;
|
|
323
275
|
}
|
|
324
276
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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);
|
|
330
379
|
}
|
|
331
380
|
}
|
|
332
381
|
|