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.
Files changed (43) hide show
  1. package/lib/components/alert.ts +124 -128
  2. package/lib/components/areachart.ts +169 -287
  3. package/lib/components/areachartsmooth.ts +2 -2
  4. package/lib/components/badge.ts +63 -72
  5. package/lib/components/barchart.ts +120 -48
  6. package/lib/components/button.ts +92 -60
  7. package/lib/components/card.ts +97 -121
  8. package/lib/components/chart-types.ts +159 -0
  9. package/lib/components/chart-utils.ts +160 -0
  10. package/lib/components/chart.ts +628 -48
  11. package/lib/components/checkbox.ts +137 -51
  12. package/lib/components/code.ts +89 -75
  13. package/lib/components/container.ts +1 -1
  14. package/lib/components/datepicker.ts +93 -78
  15. package/lib/components/dialog.ts +163 -130
  16. package/lib/components/divider.ts +111 -193
  17. package/lib/components/docs-data.json +697 -274
  18. package/lib/components/doughnutchart.ts +125 -57
  19. package/lib/components/dropdown.ts +172 -85
  20. package/lib/components/element.ts +66 -61
  21. package/lib/components/fileupload.ts +142 -171
  22. package/lib/components/heading.ts +64 -21
  23. package/lib/components/hero.ts +109 -34
  24. package/lib/components/icon.ts +247 -0
  25. package/lib/components/icons.ts +174 -0
  26. package/lib/components/include.ts +77 -2
  27. package/lib/components/input.ts +105 -53
  28. package/lib/components/list.ts +120 -79
  29. package/lib/components/menu.ts +97 -2
  30. package/lib/components/modal.ts +144 -63
  31. package/lib/components/nav.ts +153 -52
  32. package/lib/components/paragraph.ts +54 -91
  33. package/lib/components/progress.ts +83 -107
  34. package/lib/components/radio.ts +151 -52
  35. package/lib/components/select.ts +110 -102
  36. package/lib/components/sidebar.ts +148 -105
  37. package/lib/components/switch.ts +124 -125
  38. package/lib/components/table.ts +214 -137
  39. package/lib/components/tabs.ts +194 -113
  40. package/lib/components/theme-toggle.ts +38 -7
  41. package/lib/components/tooltip.ts +207 -47
  42. package/lib/jux.ts +24 -5
  43. 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
- min?: string;
10
- max?: string;
6
+ label?: string;
11
7
  placeholder?: string;
12
8
  disabled?: boolean;
13
9
  name?: string;
14
- onChange?: (value: string) => void;
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
- min: string;
25
- max: string;
18
+ label: string;
26
19
  placeholder: string;
27
20
  disabled: boolean;
28
- name: string;
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
- private _onChange?: (value: string) => void;
53
- private _boundState?: State<string>;
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
- min: options.min ?? '',
63
- max: options.max ?? '',
64
- placeholder: options.placeholder ?? 'Select date',
43
+ label: options.label ?? '',
44
+ placeholder: options.placeholder ?? '',
65
45
  disabled: options.disabled ?? false,
66
- name: options.name ?? id,
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
- min(value: string): this {
83
- this.state.min = value;
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
- name(value: string): this {
104
- this.state.name = value;
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
- onChange(handler: (value: string) => void): this {
119
- this._onChange = handler;
98
+ bind(event: string, handler: Function): this {
99
+ this._bindings.push({ event, handler });
120
100
  return this;
121
101
  }
122
102
 
123
- /**
124
- * Two-way binding to state
125
- */
126
- bind(stateObj: State<string>): this {
127
- this._boundState = stateObj;
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 element "${targetId}" not found`);
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 (className) {
184
- wrapper.className += ` ${className}`;
185
- }
186
-
187
- if (style) {
188
- wrapper.setAttribute('style', style);
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
- input.addEventListener('change', (e) => {
204
- const target = e.target as HTMLInputElement;
205
- this.state.value = target.value;
206
- if (this._onChange) {
207
- this._onChange(target.value);
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
- wrapper.appendChild(input);
226
+ // === 5. RENDER: Append to DOM and finalize ===
212
227
  container.appendChild(wrapper);
213
228
  return this;
214
229
  }
@@ -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
- message?: string;
9
- confirmText?: string;
10
- cancelText?: string;
11
- variant?: 'default' | 'danger' | 'warning';
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
- message: string;
24
- confirmText: string;
25
- cancelText: string;
26
- variant: string;
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
- private _onConfirm?: () => void;
57
- private _onCancel?: () => void;
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 ?? 'Confirm',
67
- message: options.message ?? '',
68
- confirmText: options.confirmText ?? 'Confirm',
69
- cancelText: options.cancelText ?? 'Cancel',
70
- variant: options.variant ?? 'default',
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
- message(value: string): this {
87
- this.state.message = value;
77
+ content(value: string): this {
78
+ this.state.content = value;
88
79
  return this;
89
80
  }
90
81
 
91
- confirmText(value: string): this {
92
- this.state.confirmText = value;
82
+ showClose(value: boolean): this {
83
+ this.state.showClose = value;
93
84
  return this;
94
85
  }
95
86
 
96
- cancelText(value: string): this {
97
- this.state.cancelText = value;
87
+ modal(value: boolean): this {
88
+ this.state.modal = value;
98
89
  return this;
99
90
  }
100
91
 
101
- variant(value: 'default' | 'danger' | 'warning'): this {
102
- this.state.variant = value;
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
- onConfirm(handler: () => void): this {
117
- this._onConfirm = handler;
107
+ bind(event: string, handler: Function): this {
108
+ this._bindings.push({ event, handler });
118
109
  return this;
119
110
  }
120
111
 
121
- onCancel(handler: () => void): this {
122
- this._onCancel = handler;
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 element = document.getElementById(this._id);
133
- if (element) {
134
- element.style.display = 'flex';
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 element = document.getElementById(this._id);
142
- if (element) {
143
- element.classList.remove('jux-dialog-open');
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 element "${targetId}" not found`);
150
+ throw new Error(`Dialog: Target "${targetId}" not found`);
159
151
  }
160
152
  container = target;
161
153
  } else {
162
- container = document.body;
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
- // Overlay
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.setAttribute('role', 'dialog');
174
- overlay.setAttribute('aria-modal', 'true');
175
-
176
- if (className) {
177
- overlay.className += ` ${className}`;
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 cancelBtn = document.createElement('button');
217
- cancelBtn.className = 'jux-dialog-button jux-dialog-button-cancel';
218
- cancelBtn.textContent = cancelText;
219
- cancelBtn.addEventListener('click', () => {
220
- this.close();
221
- if (this._onCancel) {
222
- this._onCancel();
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
- const confirmBtn = document.createElement('button');
227
- confirmBtn.className = `jux-dialog-button jux-dialog-button-confirm jux-dialog-button-${variant}`;
228
- confirmBtn.textContent = confirmText;
229
- confirmBtn.addEventListener('click', () => {
230
- this.close();
231
- if (this._onConfirm) {
232
- this._onConfirm();
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
- footer.appendChild(cancelBtn);
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