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/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
|
}
|
package/lib/components/select.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
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
7
|
export interface SelectOption {
|
|
5
8
|
label: string;
|
|
@@ -10,74 +13,68 @@ export interface SelectOption {
|
|
|
10
13
|
export interface SelectOptions {
|
|
11
14
|
options?: SelectOption[];
|
|
12
15
|
value?: string;
|
|
13
|
-
placeholder?: string;
|
|
14
16
|
label?: string;
|
|
17
|
+
placeholder?: string;
|
|
18
|
+
required?: boolean;
|
|
15
19
|
disabled?: boolean;
|
|
16
20
|
name?: string;
|
|
17
21
|
style?: string;
|
|
18
22
|
class?: string;
|
|
23
|
+
onValidate?: (value: string) => boolean | string;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
interface SelectState extends FormInputState {
|
|
22
27
|
options: SelectOption[];
|
|
23
28
|
value: string;
|
|
24
29
|
placeholder: string;
|
|
25
|
-
|
|
26
|
-
disabled: boolean;
|
|
27
|
-
name: string;
|
|
28
|
-
style: string;
|
|
29
|
-
class: string;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export class Select {
|
|
33
|
-
state: SelectState;
|
|
34
|
-
container: HTMLElement | null = null;
|
|
35
|
-
_id: string;
|
|
36
|
-
id: string;
|
|
37
|
-
|
|
38
|
-
// CRITICAL: Store bind/sync instructions for deferred wiring
|
|
39
|
-
private _bindings: Array<{ event: string, handler: Function }> = [];
|
|
40
|
-
private _syncBindings: Array<{
|
|
41
|
-
property: string,
|
|
42
|
-
stateObj: State<any>,
|
|
43
|
-
toState?: Function,
|
|
44
|
-
toComponent?: Function
|
|
45
|
-
}> = [];
|
|
30
|
+
}
|
|
46
31
|
|
|
32
|
+
export class Select extends FormInput<SelectState> {
|
|
47
33
|
constructor(id: string, options: SelectOptions = {}) {
|
|
48
|
-
|
|
49
|
-
this.id = id;
|
|
50
|
-
|
|
51
|
-
this.state = {
|
|
34
|
+
super(id, {
|
|
52
35
|
options: options.options ?? [],
|
|
53
36
|
value: options.value ?? '',
|
|
54
|
-
placeholder: options.placeholder ?? 'Select
|
|
37
|
+
placeholder: options.placeholder ?? 'Select an option',
|
|
55
38
|
label: options.label ?? '',
|
|
39
|
+
required: options.required ?? false,
|
|
56
40
|
disabled: options.disabled ?? false,
|
|
57
41
|
name: options.name ?? id,
|
|
58
42
|
style: options.style ?? '',
|
|
59
|
-
class: options.class ?? ''
|
|
60
|
-
|
|
43
|
+
class: options.class ?? '',
|
|
44
|
+
errorMessage: undefined
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (options.onValidate) {
|
|
48
|
+
this._onValidate = options.onValidate;
|
|
49
|
+
}
|
|
61
50
|
}
|
|
62
51
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
protected getTriggerEvents(): readonly string[] {
|
|
53
|
+
return TRIGGER_EVENTS;
|
|
54
|
+
}
|
|
66
55
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return this;
|
|
56
|
+
protected getCallbackEvents(): readonly string[] {
|
|
57
|
+
return CALLBACK_EVENTS;
|
|
70
58
|
}
|
|
71
59
|
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
61
|
+
* FLUENT API
|
|
62
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
63
|
+
|
|
64
|
+
// ✅ Inherited from FormInput/BaseComponent:
|
|
65
|
+
// - label(), required(), name(), onValidate()
|
|
66
|
+
// - validate(), isValid()
|
|
67
|
+
// - style(), class()
|
|
68
|
+
// - bind(), sync(), renderTo()
|
|
69
|
+
// - disabled(), enable(), disable()
|
|
70
|
+
|
|
71
|
+
options(value: SelectOption[]): this {
|
|
72
|
+
this.state.options = value;
|
|
74
73
|
return this;
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
value(value: string): this {
|
|
78
|
-
this.
|
|
79
|
-
this._updateElement();
|
|
80
|
-
return this;
|
|
77
|
+
return this.setValue(value);
|
|
81
78
|
}
|
|
82
79
|
|
|
83
80
|
placeholder(value: string): this {
|
|
@@ -85,201 +82,194 @@ export class Select {
|
|
|
85
82
|
return this;
|
|
86
83
|
}
|
|
87
84
|
|
|
88
|
-
|
|
89
|
-
this.state.
|
|
90
|
-
return this;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
disabled(value: boolean): this {
|
|
94
|
-
this.state.disabled = value;
|
|
95
|
-
this._updateElement();
|
|
85
|
+
addOption(option: SelectOption): this {
|
|
86
|
+
this.state.options = [...this.state.options, option];
|
|
96
87
|
return this;
|
|
97
88
|
}
|
|
98
89
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
90
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
91
|
+
* FORM INPUT IMPLEMENTATION
|
|
92
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
103
93
|
|
|
104
|
-
|
|
105
|
-
this.state.
|
|
106
|
-
return this;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
class(value: string): this {
|
|
110
|
-
this.state.class = value;
|
|
111
|
-
return this;
|
|
94
|
+
getValue(): string {
|
|
95
|
+
return this.state.value;
|
|
112
96
|
}
|
|
113
97
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
* DOM events only: change, focus, blur, etc.
|
|
117
|
-
*/
|
|
118
|
-
bind(event: string, handler: Function): this {
|
|
119
|
-
this._bindings.push({ event, handler });
|
|
120
|
-
return this;
|
|
121
|
-
}
|
|
98
|
+
setValue(value: string): this {
|
|
99
|
+
this.state.value = value;
|
|
122
100
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
*
|
|
126
|
-
* @param property - Component property to sync ('value', 'label', 'disabled', 'options')
|
|
127
|
-
* @param stateObj - State object to sync with
|
|
128
|
-
* @param toState - Optional transform function when going from component to state
|
|
129
|
-
* @param toComponent - Optional transform function when going from state to component
|
|
130
|
-
*/
|
|
131
|
-
sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
|
|
132
|
-
if (!stateObj || typeof stateObj.subscribe !== 'function') {
|
|
133
|
-
throw new Error(`Select.sync: Expected a State object for property "${property}"`);
|
|
101
|
+
if (this._inputElement) {
|
|
102
|
+
(this._inputElement as HTMLSelectElement).value = value;
|
|
134
103
|
}
|
|
135
|
-
|
|
104
|
+
|
|
136
105
|
return this;
|
|
137
106
|
}
|
|
138
107
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
* ------------------------- */
|
|
108
|
+
protected _validateValue(value: string): boolean | string {
|
|
109
|
+
const { required, options } = this.state;
|
|
142
110
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (select) {
|
|
146
|
-
select.value = this.state.value;
|
|
147
|
-
select.disabled = this.state.disabled;
|
|
111
|
+
if (required && !value) {
|
|
112
|
+
return 'Please select an option';
|
|
148
113
|
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
getValue(): string {
|
|
152
|
-
return this.state.value;
|
|
153
|
-
}
|
|
154
114
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
115
|
+
// Validate that value is one of the options
|
|
116
|
+
if (value && !options.some(opt => opt.value === value)) {
|
|
117
|
+
return 'Invalid option selected';
|
|
118
|
+
}
|
|
158
119
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const target = document.querySelector(targetId);
|
|
164
|
-
if (!target || !(target instanceof HTMLElement)) {
|
|
165
|
-
throw new Error(`Select: Target "${targetId}" not found`);
|
|
120
|
+
if (this._onValidate) {
|
|
121
|
+
const result = this._onValidate(value);
|
|
122
|
+
if (result !== true) {
|
|
123
|
+
return result || 'Invalid value';
|
|
166
124
|
}
|
|
167
|
-
container = target;
|
|
168
|
-
} else {
|
|
169
|
-
container = getOrCreateContainer(this._id);
|
|
170
125
|
}
|
|
171
|
-
this.container = container;
|
|
172
|
-
|
|
173
|
-
// === 2. PREPARE: Destructure state and check sync flags ===
|
|
174
|
-
const { value, options, label, disabled, name, style, class: className } = this.state;
|
|
175
|
-
const hasValueSync = this._syncBindings.some(b => b.property === 'value');
|
|
176
126
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
wrapper.className = 'jux-select';
|
|
180
|
-
wrapper.id = this._id;
|
|
181
|
-
if (className) wrapper.className += ` ${className}`;
|
|
182
|
-
if (style) wrapper.setAttribute('style', style);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
183
129
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
labelEl.className = 'jux-select-label';
|
|
187
|
-
labelEl.htmlFor = `${this._id}-select`;
|
|
188
|
-
labelEl.textContent = label;
|
|
189
|
-
wrapper.appendChild(labelEl);
|
|
190
|
-
}
|
|
130
|
+
protected _buildInputElement(): HTMLElement {
|
|
131
|
+
const { options, value, placeholder, required, disabled, name } = this.state;
|
|
191
132
|
|
|
192
133
|
const select = document.createElement('select');
|
|
193
|
-
select.className = 'jux-select-element';
|
|
194
|
-
select.id = `${this._id}-
|
|
134
|
+
select.className = 'jux-input-element jux-select-element';
|
|
135
|
+
select.id = `${this._id}-input`;
|
|
195
136
|
select.name = name;
|
|
196
|
-
select.
|
|
137
|
+
select.required = required;
|
|
197
138
|
select.disabled = disabled;
|
|
198
139
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
140
|
+
// Placeholder option
|
|
141
|
+
if (placeholder) {
|
|
142
|
+
const placeholderOption = document.createElement('option');
|
|
143
|
+
placeholderOption.value = '';
|
|
144
|
+
placeholderOption.textContent = placeholder;
|
|
145
|
+
placeholderOption.disabled = true;
|
|
146
|
+
placeholderOption.selected = !value;
|
|
147
|
+
select.appendChild(placeholderOption);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Options
|
|
151
|
+
options.forEach(option => {
|
|
152
|
+
const optionEl = document.createElement('option');
|
|
153
|
+
optionEl.value = option.value;
|
|
154
|
+
optionEl.textContent = option.label;
|
|
155
|
+
optionEl.disabled = option.disabled || false;
|
|
156
|
+
optionEl.selected = option.value === value;
|
|
157
|
+
select.appendChild(optionEl);
|
|
205
158
|
});
|
|
206
159
|
|
|
207
|
-
|
|
160
|
+
return select;
|
|
161
|
+
}
|
|
208
162
|
|
|
209
|
-
|
|
163
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
164
|
+
* RENDER
|
|
165
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
210
166
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
select.addEventListener('change', () => {
|
|
214
|
-
this.state.value = select.value;
|
|
215
|
-
});
|
|
216
|
-
}
|
|
167
|
+
render(targetId?: string): this {
|
|
168
|
+
const container = this._setupContainer(targetId);
|
|
217
169
|
|
|
218
|
-
|
|
219
|
-
this._bindings.forEach(({ event, handler }) => {
|
|
220
|
-
wrapper.addEventListener(event, handler as EventListener);
|
|
221
|
-
});
|
|
170
|
+
const { style, class: className } = this.state;
|
|
222
171
|
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
let isUpdating = false;
|
|
230
|
-
|
|
231
|
-
// State → Component
|
|
232
|
-
stateObj.subscribe((val: any) => {
|
|
233
|
-
if (isUpdating) return;
|
|
234
|
-
const transformed = transformToComponent(val);
|
|
235
|
-
if (select.value !== transformed) {
|
|
236
|
-
select.value = transformed;
|
|
237
|
-
this.state.value = transformed;
|
|
238
|
-
}
|
|
239
|
-
});
|
|
172
|
+
// Build wrapper
|
|
173
|
+
const wrapper = document.createElement('div');
|
|
174
|
+
wrapper.className = 'jux-input jux-select';
|
|
175
|
+
wrapper.id = this._id;
|
|
176
|
+
if (className) wrapper.className += ` ${className}`;
|
|
177
|
+
if (style) wrapper.setAttribute('style', style);
|
|
240
178
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
179
|
+
// Label
|
|
180
|
+
if (this.state.label) {
|
|
181
|
+
wrapper.appendChild(this._renderLabel());
|
|
182
|
+
}
|
|
245
183
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
184
|
+
// Select container
|
|
185
|
+
const selectContainer = document.createElement('div');
|
|
186
|
+
selectContainer.className = 'jux-input-container jux-select-container';
|
|
187
|
+
|
|
188
|
+
// Select element
|
|
189
|
+
const selectEl = this._buildInputElement() as HTMLSelectElement;
|
|
190
|
+
this._inputElement = selectEl;
|
|
191
|
+
selectContainer.appendChild(selectEl);
|
|
192
|
+
wrapper.appendChild(selectContainer);
|
|
193
|
+
|
|
194
|
+
// Error element
|
|
195
|
+
wrapper.appendChild(this._renderError());
|
|
196
|
+
|
|
197
|
+
// Wire events
|
|
198
|
+
this._wireStandardEvents(wrapper);
|
|
199
|
+
this._wireFormSync(selectEl, 'change');
|
|
200
|
+
|
|
201
|
+
// Sync label changes
|
|
202
|
+
const labelSync = this._syncBindings.find(b => b.property === 'label');
|
|
203
|
+
if (labelSync) {
|
|
204
|
+
const transform = labelSync.toComponent || ((v: any) => String(v));
|
|
205
|
+
labelSync.stateObj.subscribe((val: any) => {
|
|
206
|
+
this.label(transform(val));
|
|
207
|
+
});
|
|
208
|
+
}
|
|
249
209
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
210
|
+
// Sync options changes
|
|
211
|
+
const optionsSync = this._syncBindings.find(b => b.property === 'options');
|
|
212
|
+
if (optionsSync) {
|
|
213
|
+
const transform = optionsSync.toComponent || ((v: any) => v);
|
|
214
|
+
optionsSync.stateObj.subscribe((val: any) => {
|
|
215
|
+
const transformed = transform(val);
|
|
216
|
+
this.state.options = transformed;
|
|
217
|
+
|
|
218
|
+
// Rebuild select options
|
|
219
|
+
selectEl.innerHTML = '';
|
|
220
|
+
|
|
221
|
+
if (this.state.placeholder) {
|
|
222
|
+
const placeholderOption = document.createElement('option');
|
|
223
|
+
placeholderOption.value = '';
|
|
224
|
+
placeholderOption.textContent = this.state.placeholder;
|
|
225
|
+
placeholderOption.disabled = true;
|
|
226
|
+
placeholderOption.selected = !this.state.value;
|
|
227
|
+
selectEl.appendChild(placeholderOption);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
transformed.forEach((option: SelectOption) => {
|
|
231
|
+
const optionEl = document.createElement('option');
|
|
232
|
+
optionEl.value = option.value;
|
|
233
|
+
optionEl.textContent = option.label;
|
|
234
|
+
optionEl.disabled = option.disabled || false;
|
|
235
|
+
optionEl.selected = option.value === this.state.value;
|
|
236
|
+
selectEl.appendChild(optionEl);
|
|
269
237
|
});
|
|
270
|
-
}
|
|
271
|
-
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
272
240
|
|
|
273
|
-
// === 5. RENDER: Append to DOM and finalize ===
|
|
274
241
|
container.appendChild(wrapper);
|
|
242
|
+
this._injectSelectStyles();
|
|
243
|
+
this._injectFormStyles();
|
|
244
|
+
|
|
275
245
|
return this;
|
|
276
246
|
}
|
|
277
247
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
248
|
+
private _injectSelectStyles(): void {
|
|
249
|
+
const styleId = 'jux-select-styles';
|
|
250
|
+
if (document.getElementById(styleId)) return;
|
|
251
|
+
|
|
252
|
+
const style = document.createElement('style');
|
|
253
|
+
style.id = styleId;
|
|
254
|
+
style.textContent = `
|
|
255
|
+
.jux-select-container {
|
|
256
|
+
position: relative;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.jux-select-element {
|
|
260
|
+
appearance: none;
|
|
261
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236b7280' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
|
|
262
|
+
background-repeat: no-repeat;
|
|
263
|
+
background-position: right 12px center;
|
|
264
|
+
padding-right: 36px;
|
|
265
|
+
cursor: pointer;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.jux-select-element:disabled {
|
|
269
|
+
cursor: not-allowed;
|
|
270
|
+
}
|
|
271
|
+
`;
|
|
272
|
+
document.head.appendChild(style);
|
|
283
273
|
}
|
|
284
274
|
}
|
|
285
275
|
|