juxscript 1.0.19 → 1.0.20
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/lib/components/alert.ts +124 -128
- package/lib/components/areachart.ts +169 -287
- package/lib/components/areachartsmooth.ts +2 -2
- package/lib/components/badge.ts +63 -72
- package/lib/components/barchart.ts +120 -48
- package/lib/components/button.ts +92 -60
- package/lib/components/card.ts +97 -121
- package/lib/components/chart-types.ts +159 -0
- package/lib/components/chart-utils.ts +160 -0
- package/lib/components/chart.ts +628 -48
- package/lib/components/checkbox.ts +137 -51
- package/lib/components/code.ts +89 -75
- package/lib/components/container.ts +1 -1
- package/lib/components/datepicker.ts +93 -78
- package/lib/components/dialog.ts +163 -130
- package/lib/components/divider.ts +111 -193
- package/lib/components/docs-data.json +697 -274
- package/lib/components/doughnutchart.ts +125 -57
- package/lib/components/dropdown.ts +172 -85
- package/lib/components/element.ts +66 -61
- package/lib/components/fileupload.ts +142 -171
- package/lib/components/heading.ts +64 -21
- package/lib/components/hero.ts +109 -34
- package/lib/components/icon.ts +247 -0
- package/lib/components/icons.ts +174 -0
- package/lib/components/include.ts +77 -2
- package/lib/components/input.ts +105 -53
- package/lib/components/list.ts +120 -79
- package/lib/components/menu.ts +97 -2
- package/lib/components/modal.ts +144 -63
- package/lib/components/nav.ts +153 -52
- package/lib/components/paragraph.ts +54 -91
- package/lib/components/progress.ts +83 -107
- package/lib/components/radio.ts +151 -52
- package/lib/components/select.ts +110 -102
- package/lib/components/sidebar.ts +148 -105
- package/lib/components/switch.ts +124 -125
- package/lib/components/table.ts +214 -137
- package/lib/components/tabs.ts +194 -113
- package/lib/components/theme-toggle.ts +38 -7
- package/lib/components/tooltip.ts +207 -47
- package/lib/jux.ts +24 -5
- package/package.json +1 -2
|
@@ -1,69 +1,51 @@
|
|
|
1
1
|
import { getOrCreateContainer } from './helpers.js';
|
|
2
2
|
import { State } from '../reactivity/state.js';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* DatePicker component options
|
|
6
|
-
*/
|
|
7
4
|
export interface DatePickerOptions {
|
|
8
5
|
value?: string;
|
|
9
|
-
|
|
10
|
-
max?: string;
|
|
6
|
+
label?: string;
|
|
11
7
|
placeholder?: string;
|
|
12
8
|
disabled?: boolean;
|
|
13
9
|
name?: string;
|
|
14
|
-
|
|
10
|
+
min?: string;
|
|
11
|
+
max?: string;
|
|
15
12
|
style?: string;
|
|
16
13
|
class?: string;
|
|
17
14
|
}
|
|
18
15
|
|
|
19
|
-
/**
|
|
20
|
-
* DatePicker component state
|
|
21
|
-
*/
|
|
22
16
|
type DatePickerState = {
|
|
23
17
|
value: string;
|
|
24
|
-
|
|
25
|
-
max: string;
|
|
18
|
+
label: string;
|
|
26
19
|
placeholder: string;
|
|
27
20
|
disabled: boolean;
|
|
28
|
-
name
|
|
21
|
+
name?: string;
|
|
22
|
+
min?: string;
|
|
23
|
+
max?: string;
|
|
29
24
|
style: string;
|
|
30
25
|
class: string;
|
|
31
26
|
};
|
|
32
27
|
|
|
33
|
-
/**
|
|
34
|
-
* DatePicker component - Calendar input
|
|
35
|
-
*
|
|
36
|
-
* Usage:
|
|
37
|
-
* jux.datepicker('start-date', {
|
|
38
|
-
* placeholder: 'Select date',
|
|
39
|
-
* value: '2024-01-15',
|
|
40
|
-
* onChange: (date) => console.log(date)
|
|
41
|
-
* }).render('#form');
|
|
42
|
-
*
|
|
43
|
-
* // Two-way binding
|
|
44
|
-
* const dateState = state('2024-01-15');
|
|
45
|
-
* jux.datepicker('date').bind(dateState).render('#form');
|
|
46
|
-
*/
|
|
47
28
|
export class DatePicker {
|
|
48
29
|
state: DatePickerState;
|
|
49
30
|
container: HTMLElement | null = null;
|
|
50
31
|
_id: string;
|
|
51
32
|
id: string;
|
|
52
|
-
|
|
53
|
-
private
|
|
33
|
+
|
|
34
|
+
private _bindings: Array<{ event: string, handler: Function }> = [];
|
|
35
|
+
private _syncBindings: Array<{ property: string, stateObj: State<any>, toState?: Function, toComponent?: Function }> = [];
|
|
54
36
|
|
|
55
37
|
constructor(id: string, options: DatePickerOptions = {}) {
|
|
56
38
|
this._id = id;
|
|
57
39
|
this.id = id;
|
|
58
|
-
this._onChange = options.onChange;
|
|
59
40
|
|
|
60
41
|
this.state = {
|
|
61
42
|
value: options.value ?? '',
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
placeholder: options.placeholder ?? 'Select date',
|
|
43
|
+
label: options.label ?? '',
|
|
44
|
+
placeholder: options.placeholder ?? '',
|
|
65
45
|
disabled: options.disabled ?? false,
|
|
66
|
-
name: options.name
|
|
46
|
+
name: options.name,
|
|
47
|
+
min: options.min,
|
|
48
|
+
max: options.max,
|
|
67
49
|
style: options.style ?? '',
|
|
68
50
|
class: options.class ?? ''
|
|
69
51
|
};
|
|
@@ -75,17 +57,11 @@ export class DatePicker {
|
|
|
75
57
|
|
|
76
58
|
value(value: string): this {
|
|
77
59
|
this.state.value = value;
|
|
78
|
-
this._updateElement();
|
|
79
60
|
return this;
|
|
80
61
|
}
|
|
81
62
|
|
|
82
|
-
|
|
83
|
-
this.state.
|
|
84
|
-
return this;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
max(value: string): this {
|
|
88
|
-
this.state.max = value;
|
|
63
|
+
label(value: string): this {
|
|
64
|
+
this.state.label = value;
|
|
89
65
|
return this;
|
|
90
66
|
}
|
|
91
67
|
|
|
@@ -96,12 +72,16 @@ export class DatePicker {
|
|
|
96
72
|
|
|
97
73
|
disabled(value: boolean): this {
|
|
98
74
|
this.state.disabled = value;
|
|
99
|
-
this._updateElement();
|
|
100
75
|
return this;
|
|
101
76
|
}
|
|
102
77
|
|
|
103
|
-
|
|
104
|
-
this.state.
|
|
78
|
+
min(value: string): this {
|
|
79
|
+
this.state.min = value;
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
max(value: string): this {
|
|
84
|
+
this.state.max = value;
|
|
105
85
|
return this;
|
|
106
86
|
}
|
|
107
87
|
|
|
@@ -115,24 +95,16 @@ export class DatePicker {
|
|
|
115
95
|
return this;
|
|
116
96
|
}
|
|
117
97
|
|
|
118
|
-
|
|
119
|
-
this.
|
|
98
|
+
bind(event: string, handler: Function): this {
|
|
99
|
+
this._bindings.push({ event, handler });
|
|
120
100
|
return this;
|
|
121
101
|
}
|
|
122
102
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
this.
|
|
128
|
-
|
|
129
|
-
stateObj.subscribe((val) => {
|
|
130
|
-
this.state.value = val;
|
|
131
|
-
this._updateElement();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
this.onChange((value) => stateObj.set(value));
|
|
135
|
-
|
|
103
|
+
sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
|
|
104
|
+
if (!stateObj || typeof stateObj.subscribe !== 'function') {
|
|
105
|
+
throw new Error(`DatePicker.sync: Expected a State object for property "${property}"`);
|
|
106
|
+
}
|
|
107
|
+
this._syncBindings.push({ property, stateObj, toState, toComponent });
|
|
136
108
|
return this;
|
|
137
109
|
}
|
|
138
110
|
|
|
@@ -161,31 +133,36 @@ export class DatePicker {
|
|
|
161
133
|
* ------------------------- */
|
|
162
134
|
|
|
163
135
|
render(targetId?: string): this {
|
|
136
|
+
// === 1. SETUP: Get or create container ===
|
|
164
137
|
let container: HTMLElement;
|
|
165
|
-
|
|
166
138
|
if (targetId) {
|
|
167
139
|
const target = document.querySelector(targetId);
|
|
168
140
|
if (!target || !(target instanceof HTMLElement)) {
|
|
169
|
-
throw new Error(`DatePicker: Target
|
|
141
|
+
throw new Error(`DatePicker: Target "${targetId}" not found`);
|
|
170
142
|
}
|
|
171
143
|
container = target;
|
|
172
144
|
} else {
|
|
173
145
|
container = getOrCreateContainer(this._id);
|
|
174
146
|
}
|
|
175
|
-
|
|
176
147
|
this.container = container;
|
|
177
|
-
const { value, min, max, placeholder, disabled, name, style, class: className } = this.state;
|
|
178
148
|
|
|
149
|
+
// === 2. PREPARE: Destructure state and check sync flags ===
|
|
150
|
+
const { value, label, disabled, name, min, max, style, class: className } = this.state;
|
|
151
|
+
const hasValueSync = this._syncBindings.some(b => b.property === 'value');
|
|
152
|
+
|
|
153
|
+
// === 3. BUILD: Create DOM elements ===
|
|
179
154
|
const wrapper = document.createElement('div');
|
|
180
155
|
wrapper.className = 'jux-datepicker';
|
|
181
156
|
wrapper.id = this._id;
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
157
|
+
if (className) wrapper.className += ` ${className}`;
|
|
158
|
+
if (style) wrapper.setAttribute('style', style);
|
|
159
|
+
|
|
160
|
+
if (label) {
|
|
161
|
+
const labelEl = document.createElement('label');
|
|
162
|
+
labelEl.className = 'jux-datepicker-label';
|
|
163
|
+
labelEl.htmlFor = `${this._id}-input`;
|
|
164
|
+
labelEl.textContent = label;
|
|
165
|
+
wrapper.appendChild(labelEl);
|
|
189
166
|
}
|
|
190
167
|
|
|
191
168
|
const input = document.createElement('input');
|
|
@@ -195,20 +172,58 @@ export class DatePicker {
|
|
|
195
172
|
input.name = name;
|
|
196
173
|
input.value = value;
|
|
197
174
|
input.disabled = disabled;
|
|
198
|
-
|
|
199
175
|
if (min) input.min = min;
|
|
200
176
|
if (max) input.max = max;
|
|
201
|
-
if (placeholder) input.setAttribute('placeholder', placeholder);
|
|
202
177
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
178
|
+
wrapper.appendChild(input);
|
|
179
|
+
|
|
180
|
+
// === 4. WIRE: Attach event listeners and sync bindings ===
|
|
181
|
+
|
|
182
|
+
// Default behavior (only if NOT using sync)
|
|
183
|
+
if (!hasValueSync) {
|
|
184
|
+
input.addEventListener('change', () => {
|
|
185
|
+
this.state.value = input.value;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Wire custom bindings from .bind() calls
|
|
190
|
+
this._bindings.forEach(({ event, handler }) => {
|
|
191
|
+
wrapper.addEventListener(event, handler as EventListener);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Wire sync bindings from .sync() calls
|
|
195
|
+
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
196
|
+
if (property === 'value') {
|
|
197
|
+
const transformToState = toState || ((v: any) => v);
|
|
198
|
+
const transformToComponent = toComponent || ((v: any) => String(v));
|
|
199
|
+
|
|
200
|
+
let isUpdating = false;
|
|
201
|
+
|
|
202
|
+
// State → Component
|
|
203
|
+
stateObj.subscribe((val: any) => {
|
|
204
|
+
if (isUpdating) return;
|
|
205
|
+
const transformed = transformToComponent(val);
|
|
206
|
+
if (input.value !== transformed) {
|
|
207
|
+
input.value = transformed;
|
|
208
|
+
this.state.value = transformed;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Component → State
|
|
213
|
+
input.addEventListener('change', () => {
|
|
214
|
+
if (isUpdating) return;
|
|
215
|
+
isUpdating = true;
|
|
216
|
+
|
|
217
|
+
const transformed = transformToState(input.value);
|
|
218
|
+
this.state.value = input.value;
|
|
219
|
+
stateObj.set(transformed);
|
|
220
|
+
|
|
221
|
+
setTimeout(() => { isUpdating = false; }, 0);
|
|
222
|
+
});
|
|
208
223
|
}
|
|
209
224
|
});
|
|
210
225
|
|
|
211
|
-
|
|
226
|
+
// === 5. RENDER: Append to DOM and finalize ===
|
|
212
227
|
container.appendChild(wrapper);
|
|
213
228
|
return this;
|
|
214
229
|
}
|
package/lib/components/dialog.ts
CHANGED
|
@@ -1,76 +1,67 @@
|
|
|
1
1
|
import { getOrCreateContainer } from './helpers.js';
|
|
2
|
+
import { State } from '../reactivity/state.js';
|
|
2
3
|
|
|
3
|
-
/**
|
|
4
|
-
* Dialog component options
|
|
5
|
-
*/
|
|
6
4
|
export interface DialogOptions {
|
|
7
5
|
title?: string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
onConfirm?: () => void;
|
|
13
|
-
onCancel?: () => void;
|
|
6
|
+
content?: string;
|
|
7
|
+
showClose?: boolean;
|
|
8
|
+
modal?: boolean;
|
|
9
|
+
width?: string;
|
|
14
10
|
style?: string;
|
|
15
11
|
class?: string;
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
/**
|
|
19
|
-
* Dialog component state
|
|
20
|
-
*/
|
|
21
14
|
type DialogState = {
|
|
22
15
|
title: string;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
content: string;
|
|
17
|
+
showClose: boolean;
|
|
18
|
+
modal: boolean;
|
|
19
|
+
width: string;
|
|
27
20
|
style: string;
|
|
28
21
|
class: string;
|
|
29
22
|
isOpen: boolean;
|
|
23
|
+
open: boolean;
|
|
24
|
+
message: string;
|
|
25
|
+
type: string;
|
|
26
|
+
confirmText: string;
|
|
27
|
+
cancelText: string;
|
|
28
|
+
onConfirm?: Function;
|
|
29
|
+
onCancel?: Function;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
* Dialog component - Confirmation dialogs
|
|
34
|
-
*
|
|
35
|
-
* Usage:
|
|
36
|
-
* jux.dialog('confirm-delete', {
|
|
37
|
-
* title: 'Delete Item?',
|
|
38
|
-
* message: 'This action cannot be undone.',
|
|
39
|
-
* confirmText: 'Delete',
|
|
40
|
-
* cancelText: 'Cancel',
|
|
41
|
-
* variant: 'danger',
|
|
42
|
-
* onConfirm: () => console.log('Deleted'),
|
|
43
|
-
* onCancel: () => console.log('Cancelled')
|
|
44
|
-
* }).render();
|
|
45
|
-
*
|
|
46
|
-
* // Open/close programmatically
|
|
47
|
-
* const dialog = jux.dialog('my-dialog').render();
|
|
48
|
-
* dialog.open();
|
|
49
|
-
* dialog.close();
|
|
50
|
-
*/
|
|
51
32
|
export class Dialog {
|
|
52
33
|
state: DialogState;
|
|
53
34
|
container: HTMLElement | null = null;
|
|
54
35
|
_id: string;
|
|
55
36
|
id: string;
|
|
56
|
-
|
|
57
|
-
|
|
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
|
+
}> = [];
|
|
58
46
|
|
|
59
47
|
constructor(id: string, options: DialogOptions = {}) {
|
|
60
48
|
this._id = id;
|
|
61
49
|
this.id = id;
|
|
62
|
-
this._onConfirm = options.onConfirm;
|
|
63
|
-
this._onCancel = options.onCancel;
|
|
64
50
|
|
|
65
51
|
this.state = {
|
|
66
|
-
title: options.title ?? '
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
52
|
+
title: options.title ?? '',
|
|
53
|
+
content: options.content ?? '',
|
|
54
|
+
showClose: options.showClose ?? true,
|
|
55
|
+
modal: options.modal ?? true,
|
|
56
|
+
width: options.width ?? '500px',
|
|
71
57
|
style: options.style ?? '',
|
|
72
58
|
class: options.class ?? '',
|
|
73
|
-
isOpen: false
|
|
59
|
+
isOpen: false,
|
|
60
|
+
open: false,
|
|
61
|
+
message: '',
|
|
62
|
+
type: 'info',
|
|
63
|
+
confirmText: 'OK',
|
|
64
|
+
cancelText: 'Cancel'
|
|
74
65
|
};
|
|
75
66
|
}
|
|
76
67
|
|
|
@@ -83,23 +74,23 @@ export class Dialog {
|
|
|
83
74
|
return this;
|
|
84
75
|
}
|
|
85
76
|
|
|
86
|
-
|
|
87
|
-
this.state.
|
|
77
|
+
content(value: string): this {
|
|
78
|
+
this.state.content = value;
|
|
88
79
|
return this;
|
|
89
80
|
}
|
|
90
81
|
|
|
91
|
-
|
|
92
|
-
this.state.
|
|
82
|
+
showClose(value: boolean): this {
|
|
83
|
+
this.state.showClose = value;
|
|
93
84
|
return this;
|
|
94
85
|
}
|
|
95
86
|
|
|
96
|
-
|
|
97
|
-
this.state.
|
|
87
|
+
modal(value: boolean): this {
|
|
88
|
+
this.state.modal = value;
|
|
98
89
|
return this;
|
|
99
90
|
}
|
|
100
91
|
|
|
101
|
-
|
|
102
|
-
this.state.
|
|
92
|
+
width(value: string): this {
|
|
93
|
+
this.state.width = value;
|
|
103
94
|
return this;
|
|
104
95
|
}
|
|
105
96
|
|
|
@@ -113,13 +104,16 @@ export class Dialog {
|
|
|
113
104
|
return this;
|
|
114
105
|
}
|
|
115
106
|
|
|
116
|
-
|
|
117
|
-
this.
|
|
107
|
+
bind(event: string, handler: Function): this {
|
|
108
|
+
this._bindings.push({ event, handler });
|
|
118
109
|
return this;
|
|
119
110
|
}
|
|
120
111
|
|
|
121
|
-
|
|
122
|
-
|
|
112
|
+
sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
|
|
113
|
+
if (!stateObj || typeof stateObj.subscribe !== 'function') {
|
|
114
|
+
throw new Error(`Dialog.sync: Expected a State object for property "${property}"`);
|
|
115
|
+
}
|
|
116
|
+
this._syncBindings.push({ property, stateObj, toState, toComponent });
|
|
123
117
|
return this;
|
|
124
118
|
}
|
|
125
119
|
|
|
@@ -129,19 +123,17 @@ export class Dialog {
|
|
|
129
123
|
|
|
130
124
|
open(): void {
|
|
131
125
|
this.state.isOpen = true;
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
setTimeout(() => element.classList.add('jux-dialog-open'), 10);
|
|
126
|
+
const dialog = document.getElementById(this._id);
|
|
127
|
+
if (dialog) {
|
|
128
|
+
dialog.style.display = 'flex';
|
|
136
129
|
}
|
|
137
130
|
}
|
|
138
131
|
|
|
139
132
|
close(): void {
|
|
140
133
|
this.state.isOpen = false;
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
setTimeout(() => element.style.display = 'none', 200);
|
|
134
|
+
const dialog = document.getElementById(this._id);
|
|
135
|
+
if (dialog) {
|
|
136
|
+
dialog.style.display = 'none';
|
|
145
137
|
}
|
|
146
138
|
}
|
|
147
139
|
|
|
@@ -150,98 +142,139 @@ export class Dialog {
|
|
|
150
142
|
* ------------------------- */
|
|
151
143
|
|
|
152
144
|
render(targetId?: string): this {
|
|
145
|
+
// === 1. SETUP: Get or create container ===
|
|
153
146
|
let container: HTMLElement;
|
|
154
|
-
|
|
155
147
|
if (targetId) {
|
|
156
148
|
const target = document.querySelector(targetId);
|
|
157
149
|
if (!target || !(target instanceof HTMLElement)) {
|
|
158
|
-
throw new Error(`Dialog: Target
|
|
150
|
+
throw new Error(`Dialog: Target "${targetId}" not found`);
|
|
159
151
|
}
|
|
160
152
|
container = target;
|
|
161
153
|
} else {
|
|
162
|
-
container =
|
|
154
|
+
container = getOrCreateContainer(this._id);
|
|
163
155
|
}
|
|
164
|
-
|
|
165
156
|
this.container = container;
|
|
166
|
-
const { title, message, confirmText, cancelText, variant, style, class: className } = this.state;
|
|
167
157
|
|
|
168
|
-
//
|
|
158
|
+
// === 2. PREPARE: Destructure state and check sync flags ===
|
|
159
|
+
const { open, title, message, type, confirmText, cancelText, onConfirm, onCancel, style, class: className } = this.state;
|
|
160
|
+
const hasOpenSync = this._syncBindings.some(b => b.property === 'open');
|
|
161
|
+
|
|
162
|
+
// === 3. BUILD: Create DOM elements ===
|
|
169
163
|
const overlay = document.createElement('div');
|
|
170
|
-
overlay.className = 'jux-dialog';
|
|
164
|
+
overlay.className = 'jux-dialog-overlay';
|
|
171
165
|
overlay.id = this._id;
|
|
172
|
-
overlay.style.display = 'none';
|
|
173
|
-
overlay.
|
|
174
|
-
overlay.setAttribute('
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
166
|
+
overlay.style.display = open ? 'flex' : 'none';
|
|
167
|
+
if (className) overlay.className += ` ${className}`;
|
|
168
|
+
if (style) overlay.setAttribute('style', style);
|
|
169
|
+
|
|
170
|
+
const dialog = document.createElement('div');
|
|
171
|
+
dialog.className = `jux-dialog jux-dialog-${type}`;
|
|
172
|
+
|
|
173
|
+
if (title) {
|
|
174
|
+
const header = document.createElement('div');
|
|
175
|
+
header.className = 'jux-dialog-header';
|
|
176
|
+
header.textContent = title;
|
|
177
|
+
dialog.appendChild(header);
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
if (style) {
|
|
181
|
-
overlay.setAttribute('style', style);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Click outside to close
|
|
185
|
-
overlay.addEventListener('click', (e) => {
|
|
186
|
-
if (e.target === overlay) {
|
|
187
|
-
this.close();
|
|
188
|
-
if (this._onCancel) {
|
|
189
|
-
this._onCancel();
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// Dialog container
|
|
195
|
-
const dialogBox = document.createElement('div');
|
|
196
|
-
dialogBox.className = `jux-dialog-box jux-dialog-${variant}`;
|
|
197
|
-
|
|
198
|
-
// Header
|
|
199
|
-
const header = document.createElement('div');
|
|
200
|
-
header.className = 'jux-dialog-header';
|
|
201
|
-
|
|
202
|
-
const titleEl = document.createElement('h3');
|
|
203
|
-
titleEl.className = 'jux-dialog-title';
|
|
204
|
-
titleEl.textContent = title;
|
|
205
|
-
header.appendChild(titleEl);
|
|
206
|
-
|
|
207
|
-
// Body
|
|
208
180
|
const body = document.createElement('div');
|
|
209
181
|
body.className = 'jux-dialog-body';
|
|
210
182
|
body.textContent = message;
|
|
183
|
+
dialog.appendChild(body);
|
|
211
184
|
|
|
212
|
-
// Footer
|
|
213
185
|
const footer = document.createElement('div');
|
|
214
186
|
footer.className = 'jux-dialog-footer';
|
|
215
187
|
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
188
|
+
const cancelButton = document.createElement('button');
|
|
189
|
+
cancelButton.className = 'jux-dialog-button jux-dialog-button-cancel';
|
|
190
|
+
cancelButton.textContent = cancelText;
|
|
191
|
+
|
|
192
|
+
const confirmButton = document.createElement('button');
|
|
193
|
+
confirmButton.className = 'jux-dialog-button jux-dialog-button-confirm';
|
|
194
|
+
confirmButton.textContent = confirmText;
|
|
195
|
+
|
|
196
|
+
footer.appendChild(cancelButton);
|
|
197
|
+
footer.appendChild(confirmButton);
|
|
198
|
+
dialog.appendChild(footer);
|
|
199
|
+
|
|
200
|
+
overlay.appendChild(dialog);
|
|
201
|
+
|
|
202
|
+
// === 4. WIRE: Attach event listeners and sync bindings ===
|
|
203
|
+
|
|
204
|
+
// Default button behavior (only if NOT using sync)
|
|
205
|
+
if (!hasOpenSync) {
|
|
206
|
+
cancelButton.addEventListener('click', () => {
|
|
207
|
+
this.state.open = false;
|
|
208
|
+
overlay.style.display = 'none';
|
|
209
|
+
if (onCancel) onCancel();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
confirmButton.addEventListener('click', () => {
|
|
213
|
+
this.state.open = false;
|
|
214
|
+
overlay.style.display = 'none';
|
|
215
|
+
if (onConfirm) onConfirm();
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Wire custom bindings from .bind() calls
|
|
220
|
+
this._bindings.forEach(({ event, handler }) => {
|
|
221
|
+
overlay.addEventListener(event, handler as EventListener);
|
|
224
222
|
});
|
|
225
223
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
224
|
+
// Wire sync bindings from .sync() calls
|
|
225
|
+
this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
|
|
226
|
+
if (property === 'open') {
|
|
227
|
+
const transformToState = toState || ((v: any) => Boolean(v));
|
|
228
|
+
const transformToComponent = toComponent || ((v: any) => Boolean(v));
|
|
229
|
+
|
|
230
|
+
let isUpdating = false;
|
|
231
|
+
|
|
232
|
+
// State → Component
|
|
233
|
+
stateObj.subscribe((val: any) => {
|
|
234
|
+
if (isUpdating) return;
|
|
235
|
+
const transformed = transformToComponent(val);
|
|
236
|
+
this.state.open = transformed;
|
|
237
|
+
overlay.style.display = transformed ? 'flex' : 'none';
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Component → State (button clicks)
|
|
241
|
+
cancelButton.addEventListener('click', () => {
|
|
242
|
+
if (isUpdating) return;
|
|
243
|
+
isUpdating = true;
|
|
244
|
+
|
|
245
|
+
this.state.open = false;
|
|
246
|
+
overlay.style.display = 'none';
|
|
247
|
+
if (onCancel) onCancel();
|
|
248
|
+
stateObj.set(transformToState(false));
|
|
249
|
+
|
|
250
|
+
setTimeout(() => { isUpdating = false; }, 0);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
confirmButton.addEventListener('click', () => {
|
|
254
|
+
if (isUpdating) return;
|
|
255
|
+
isUpdating = true;
|
|
256
|
+
|
|
257
|
+
this.state.open = false;
|
|
258
|
+
overlay.style.display = 'none';
|
|
259
|
+
if (onConfirm) onConfirm();
|
|
260
|
+
stateObj.set(transformToState(false));
|
|
261
|
+
|
|
262
|
+
setTimeout(() => { isUpdating = false; }, 0);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
else if (property === 'message') {
|
|
266
|
+
const transformToComponent = toComponent || ((v: any) => String(v));
|
|
267
|
+
|
|
268
|
+
stateObj.subscribe((val: any) => {
|
|
269
|
+
const transformed = transformToComponent(val);
|
|
270
|
+
body.textContent = transformed;
|
|
271
|
+
this.state.message = transformed;
|
|
272
|
+
});
|
|
233
273
|
}
|
|
234
274
|
});
|
|
235
275
|
|
|
236
|
-
|
|
237
|
-
footer.appendChild(confirmBtn);
|
|
238
|
-
|
|
239
|
-
dialogBox.appendChild(header);
|
|
240
|
-
dialogBox.appendChild(body);
|
|
241
|
-
dialogBox.appendChild(footer);
|
|
242
|
-
overlay.appendChild(dialogBox);
|
|
276
|
+
// === 5. RENDER: Append to DOM and finalize ===
|
|
243
277
|
container.appendChild(overlay);
|
|
244
|
-
|
|
245
278
|
return this;
|
|
246
279
|
}
|
|
247
280
|
|