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.
Files changed (76) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +143 -92
  3. package/lib/components/badge.ts +93 -94
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +40 -131
  7. package/lib/components/card.ts +57 -79
  8. package/lib/components/charts/areachart.ts +315 -0
  9. package/lib/components/charts/barchart.ts +421 -0
  10. package/lib/components/charts/doughnutchart.ts +263 -0
  11. package/lib/components/charts/lib/BaseChart.ts +402 -0
  12. package/lib/components/{chart-types.ts → charts/lib/chart-types.ts} +1 -1
  13. package/lib/components/{chart-utils.ts → charts/lib/chart-utils.ts} +1 -1
  14. package/lib/components/{chart.ts → charts/lib/chart.ts} +3 -3
  15. package/lib/components/checkbox.ts +255 -204
  16. package/lib/components/code.ts +31 -78
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +180 -147
  20. package/lib/components/dialog.ts +218 -221
  21. package/lib/components/divider.ts +63 -87
  22. package/lib/components/docs-data.json +498 -2404
  23. package/lib/components/dropdown.ts +191 -236
  24. package/lib/components/element.ts +196 -145
  25. package/lib/components/fileupload.ts +253 -167
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +31 -97
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +51 -114
  30. package/lib/components/icon.ts +33 -120
  31. package/lib/components/icons.ts +2 -1
  32. package/lib/components/include.ts +76 -3
  33. package/lib/components/input.ts +155 -407
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +358 -261
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +63 -152
  38. package/lib/components/modal.ts +42 -129
  39. package/lib/components/nav.ts +79 -101
  40. package/lib/components/paragraph.ts +38 -102
  41. package/lib/components/progress.ts +108 -166
  42. package/lib/components/radio.ts +283 -234
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +189 -199
  45. package/lib/components/sidebar.ts +110 -141
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +254 -183
  48. package/lib/components/table.ts +1078 -208
  49. package/lib/components/tabs.ts +42 -106
  50. package/lib/components/theme-toggle.ts +73 -165
  51. package/lib/components/tooltip.ts +85 -316
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +67 -41
  54. package/machinery/build.js +466 -0
  55. package/machinery/compiler.js +354 -105
  56. package/machinery/server.js +23 -100
  57. package/machinery/watcher.js +153 -130
  58. package/package.json +1 -1
  59. package/presets/base.css +1166 -0
  60. package/presets/notion.css +2 -1975
  61. package/lib/adapters/base-adapter.js +0 -35
  62. package/lib/adapters/index.js +0 -33
  63. package/lib/adapters/mysql-adapter.js +0 -65
  64. package/lib/adapters/postgres-adapter.js +0 -70
  65. package/lib/adapters/sqlite-adapter.js +0 -56
  66. package/lib/components/areachart.ts +0 -1128
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1322
  69. package/lib/components/doughnutchart.ts +0 -1259
  70. package/lib/components/footer.ts +0 -165
  71. package/lib/components/header.ts +0 -187
  72. package/lib/components/layout.ts +0 -239
  73. package/lib/components/main.ts +0 -137
  74. package/lib/layouts/default.jux +0 -8
  75. package/lib/layouts/figma.jux +0 -0
  76. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -1,11 +1,11 @@
1
- import { getOrCreateContainer } from './helpers.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
2
  import { renderIcon } from './icons.js';
3
3
  import { req } from './req.js';
4
- import { State } from '../reactivity/state.js';
5
4
 
6
- /**
7
- * Menu item configuration
8
- */
5
+ // Event definitions
6
+ const TRIGGER_EVENTS = [] as const;
7
+ const CALLBACK_EVENTS = ['itemClick'] as const; // ✅ Fire when menu item is clicked
8
+
9
9
  export interface MenuItem {
10
10
  label: string;
11
11
  href?: string;
@@ -13,11 +13,9 @@ export interface MenuItem {
13
13
  icon?: string;
14
14
  items?: MenuItem[];
15
15
  active?: boolean;
16
+ itemClass?: string;
16
17
  }
17
18
 
18
- /**
19
- * Menu component options
20
- */
21
19
  export interface MenuOptions {
22
20
  items?: MenuItem[];
23
21
  orientation?: 'vertical' | 'horizontal';
@@ -25,9 +23,6 @@ export interface MenuOptions {
25
23
  class?: string;
26
24
  }
27
25
 
28
- /**
29
- * Menu component state
30
- */
31
26
  type MenuState = {
32
27
  items: MenuItem[];
33
28
  orientation: string;
@@ -35,56 +30,30 @@ type MenuState = {
35
30
  class: string;
36
31
  };
37
32
 
38
- /**
39
- * Menu component
40
- *
41
- * Usage:
42
- * const menu = jux.menu('myMenu', {
43
- * orientation: 'vertical',
44
- * items: [
45
- * { label: 'Home', href: '/' },
46
- * { label: 'About', href: '/about' }
47
- * ]
48
- * });
49
- * menu.render();
50
- *
51
- * Active states are automatically set based on current URL
52
- */
53
- export class Menu {
54
- state: MenuState;
55
- container: HTMLElement | null = null;
56
- _id: string;
57
- id: string;
58
-
59
- // Store bind() instructions (DOM events only)
60
- private _bindings: Array<{ event: string, handler: Function }> = [];
61
-
62
- // Store sync() instructions (state synchronization)
63
- private _syncBindings: Array<{ property: string, stateObj: State<any>, toState?: Function, toComponent?: Function }> = [];
64
-
33
+ export class Menu extends BaseComponent<MenuState> {
65
34
  constructor(id: string, options: MenuOptions = {}) {
66
- this._id = id;
67
- this.id = id;
68
-
69
- this.state = {
35
+ super(id, {
70
36
  items: options.items ?? [],
71
37
  orientation: options.orientation ?? 'vertical',
72
38
  style: options.style ?? '',
73
39
  class: options.class ?? ''
74
- };
40
+ });
75
41
 
76
- // Auto-set active state based on current path
77
42
  this._setActiveStates();
78
43
  }
79
44
 
80
- /**
81
- * Set active state on items based on current request path
82
- */
45
+ protected getTriggerEvents(): readonly string[] {
46
+ return TRIGGER_EVENTS;
47
+ }
48
+
49
+ protected getCallbackEvents(): readonly string[] {
50
+ return CALLBACK_EVENTS;
51
+ }
52
+
83
53
  private _setActiveStates(): void {
84
54
  this.state.items = this.state.items.map(item => ({
85
55
  ...item,
86
56
  active: item.href ? req.isActiveNavItem(item.href) : false,
87
- // Recursively set active for subitems
88
57
  items: item.items?.map(subItem => ({
89
58
  ...subItem,
90
59
  active: subItem.href ? req.isActiveNavItem(subItem.href) : false
@@ -92,34 +61,11 @@ export class Menu {
92
61
  }));
93
62
  }
94
63
 
95
- /**
96
- * Bind event handler (stores for wiring in render)
97
- * DOM events only: click, mouseenter, mouseleave, etc.
98
- */
99
- bind(event: string, handler: Function): this {
100
- this._bindings.push({ event, handler });
101
- return this;
102
- }
64
+ /* ═════════════════════════════════════════════════════════════════
65
+ * FLUENT API
66
+ * ═════════════════════════════════════════════════════════════════ */
103
67
 
104
- /**
105
- * Two-way sync with state (stores for wiring in render)
106
- *
107
- * @param property - Component property to sync ('items', 'orientation', etc)
108
- * @param stateObj - State object to sync with
109
- * @param toState - Optional transform function when going from component to state
110
- * @param toComponent - Optional transform function when going from state to component
111
- */
112
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
113
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
114
- throw new Error(`Menu.sync: Expected a State object for property "${property}"`);
115
- }
116
- this._syncBindings.push({ property, stateObj, toState, toComponent });
117
- return this;
118
- }
119
-
120
- /* -------------------------
121
- * Fluent API
122
- * ------------------------- */
68
+ // ✅ Inherited from BaseComponent
123
69
 
124
70
  items(value: MenuItem[]): this {
125
71
  this.state.items = value;
@@ -133,30 +79,35 @@ export class Menu {
133
79
  return this;
134
80
  }
135
81
 
136
- orientation(value: 'vertical' | 'horizontal'): this {
137
- this.state.orientation = value;
138
- return this;
139
- }
140
-
141
- style(value: string): this {
142
- this.state.style = value;
82
+ itemClass(className: string): this {
83
+ this.state.items = this.state.items.map(item => ({
84
+ ...item,
85
+ itemClass: className,
86
+ items: item.items?.map(subItem => ({
87
+ ...subItem,
88
+ itemClass: className
89
+ }))
90
+ }));
143
91
  return this;
144
92
  }
145
93
 
146
- class(value: string): this {
147
- this.state.class = value;
94
+ orientation(value: 'vertical' | 'horizontal'): this {
95
+ this.state.orientation = value;
148
96
  return this;
149
97
  }
150
98
 
151
- /* -------------------------
152
- * Helpers
153
- * ------------------------- */
99
+ /* ═════════════════════════════════════════════════════════════════
100
+ * HELPERS
101
+ * ═════════════════════════════════════════════════════════════════ */
154
102
 
155
103
  private _renderMenuItem(item: MenuItem): HTMLElement {
156
104
  const menuItem = document.createElement('div');
157
105
  menuItem.className = 'jux-menu-item';
158
106
 
159
- // Add active class if item is active
107
+ if (item.itemClass) {
108
+ menuItem.className += ` ${item.itemClass}`;
109
+ }
110
+
160
111
  if (item.active) {
161
112
  menuItem.classList.add('jux-menu-item-active');
162
113
  }
@@ -166,7 +117,6 @@ export class Menu {
166
117
  link.className = 'jux-menu-link';
167
118
  link.href = item.href;
168
119
 
169
- // Add active class to link if item is active
170
120
  if (item.active) {
171
121
  link.classList.add('jux-menu-link-active');
172
122
  }
@@ -174,8 +124,7 @@ export class Menu {
174
124
  if (item.icon) {
175
125
  const icon = document.createElement('span');
176
126
  icon.className = 'jux-menu-icon';
177
- const iconElement = renderIcon(item.icon);
178
- icon.appendChild(iconElement);
127
+ icon.appendChild(renderIcon(item.icon));
179
128
  link.appendChild(icon);
180
129
  }
181
130
 
@@ -184,6 +133,11 @@ export class Menu {
184
133
  label.textContent = item.label;
185
134
  link.appendChild(label);
186
135
 
136
+ // ✅ Fire itemClick callback when link is clicked
137
+ link.addEventListener('click', (e) => {
138
+ this._triggerCallback('itemClick', { item, event: e });
139
+ });
140
+
187
141
  menuItem.appendChild(link);
188
142
  } else {
189
143
  const button = document.createElement('button');
@@ -192,8 +146,7 @@ export class Menu {
192
146
  if (item.icon) {
193
147
  const icon = document.createElement('span');
194
148
  icon.className = 'jux-menu-icon';
195
- const iconElement = renderIcon(item.icon);
196
- icon.appendChild(iconElement);
149
+ icon.appendChild(renderIcon(item.icon));
197
150
  button.appendChild(icon);
198
151
  }
199
152
 
@@ -204,13 +157,15 @@ export class Menu {
204
157
 
205
158
  menuItem.appendChild(button);
206
159
 
207
- // Event binding - click
208
- if (item.click) {
209
- button.addEventListener('click', item.click);
210
- }
160
+ // Fire both the item's click handler AND the callback event
161
+ button.addEventListener('click', (e) => {
162
+ if (item.click) {
163
+ item.click();
164
+ }
165
+ this._triggerCallback('itemClick', { item, event: e });
166
+ });
211
167
  }
212
168
 
213
- // Nested items (submenu)
214
169
  if (item.items && item.items.length > 0) {
215
170
  const submenu = document.createElement('div');
216
171
  submenu.className = 'jux-menu-submenu';
@@ -225,30 +180,15 @@ export class Menu {
225
180
  return menuItem;
226
181
  }
227
182
 
228
- /* -------------------------
229
- * Render
230
- * ------------------------- */
183
+ /* ═════════════════════════════════════════════════════════════════
184
+ * RENDER
185
+ * ═════════════════════════════════════════════════════════════════ */
231
186
 
232
187
  render(targetId?: string): this {
233
- // === 1. SETUP: Get container ===
234
- let container: HTMLElement;
188
+ const container = this._setupContainer(targetId);
235
189
 
236
- if (targetId) {
237
- const target = document.querySelector(targetId);
238
- if (!target || !(target instanceof HTMLElement)) {
239
- throw new Error(`Menu: Target element "${targetId}" not found`);
240
- }
241
- container = target;
242
- } else {
243
- container = getOrCreateContainer(this._id);
244
- }
245
-
246
- this.container = container;
247
-
248
- // === 2. PREPARE: Destructure state ===
249
190
  const { items, orientation, style, class: className } = this.state;
250
191
 
251
- // === 3. BUILD: Create DOM elements ===
252
192
  const menu = document.createElement('nav');
253
193
  menu.className = `jux-menu jux-menu-${orientation}`;
254
194
  menu.id = this._id;
@@ -265,25 +205,18 @@ export class Menu {
265
205
  menu.appendChild(this._renderMenuItem(item));
266
206
  });
267
207
 
268
- // === 4. WIRE: Add event listeners ===
208
+ this._wireStandardEvents(menu);
269
209
 
270
- // Wire up custom event bindings (from .bind() calls)
271
- this._bindings.forEach(({ event, handler }) => {
272
- menu.addEventListener(event, handler as EventListener);
273
- });
274
-
275
- // Wire up sync bindings (from .sync() calls)
210
+ // Wire sync bindings
276
211
  this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
277
212
  if (property === 'items') {
278
- // Sync items array
279
- const transformToComponent = toComponent || ((v: any) => v);
213
+ const transform = toComponent || ((v: any) => v);
280
214
 
281
215
  stateObj.subscribe((val: any) => {
282
- const transformed = transformToComponent(val);
216
+ const transformed = transform(val);
283
217
  this.state.items = transformed;
284
218
  this._setActiveStates();
285
219
 
286
- // Re-render menu items
287
220
  const existingItems = menu.querySelectorAll('.jux-menu-item');
288
221
  existingItems.forEach(item => item.remove());
289
222
 
@@ -291,7 +224,6 @@ export class Menu {
291
224
  menu.appendChild(this._renderMenuItem(item));
292
225
  });
293
226
 
294
- // Re-trigger icon rendering
295
227
  requestAnimationFrame(() => {
296
228
  if ((window as any).lucide) {
297
229
  (window as any).lucide.createIcons();
@@ -300,11 +232,10 @@ export class Menu {
300
232
  });
301
233
  }
302
234
  else if (property === 'orientation') {
303
- // Sync orientation
304
- const transformToComponent = toComponent || ((v: any) => String(v));
235
+ const transform = toComponent || ((v: any) => String(v));
305
236
 
306
237
  stateObj.subscribe((val: any) => {
307
- const transformed = transformToComponent(val);
238
+ const transformed = transform(val);
308
239
  menu.classList.remove(`jux-menu-${this.state.orientation}`);
309
240
  this.state.orientation = transformed;
310
241
  menu.classList.add(`jux-menu-${transformed}`);
@@ -312,10 +243,8 @@ export class Menu {
312
243
  }
313
244
  });
314
245
 
315
- // === 5. RENDER: Append to DOM and finalize ===
316
246
  container.appendChild(menu);
317
247
 
318
- // Trigger Lucide icon rendering
319
248
  requestAnimationFrame(() => {
320
249
  if ((window as any).lucide) {
321
250
  (window as any).lucide.createIcons();
@@ -324,26 +253,8 @@ export class Menu {
324
253
 
325
254
  return this;
326
255
  }
327
-
328
- /**
329
- * Render to another Jux component's container
330
- */
331
- renderTo(juxComponent: any): this {
332
- if (!juxComponent || typeof juxComponent !== 'object') {
333
- throw new Error('Menu.renderTo: Invalid component - not an object');
334
- }
335
-
336
- if (!juxComponent._id || typeof juxComponent._id !== 'string') {
337
- throw new Error('Menu.renderTo: Invalid component - missing _id (not a Jux component)');
338
- }
339
-
340
- return this.render(`#${juxComponent._id}`);
341
- }
342
256
  }
343
257
 
344
- /**
345
- * Factory helper
346
- */
347
258
  export function menu(id: string, options: MenuOptions = {}): Menu {
348
259
  return new Menu(id, options);
349
260
  }
@@ -1,10 +1,9 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
3
- import { renderIcon } from './icons.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+
3
+ // Event definitions
4
+ const TRIGGER_EVENTS = [] as const;
5
+ const CALLBACK_EVENTS = ['open', 'close'] as const;
4
6
 
5
- /**
6
- * Modal component options
7
- */
8
7
  export interface ModalOptions {
9
8
  title?: string;
10
9
  content?: string;
@@ -15,68 +14,46 @@ export interface ModalOptions {
15
14
  class?: string;
16
15
  }
17
16
 
18
- /**
19
- * Modal component state
20
- */
21
17
  type ModalState = {
22
18
  title: string;
23
19
  content: string;
24
20
  showCloseButton: boolean;
25
21
  closeOnBackdropClick: boolean;
26
22
  size: string;
27
- isOpen: boolean;
28
23
  open: boolean;
29
24
  style: string;
30
25
  class: string;
31
26
  };
32
27
 
33
- /**
34
- * Modal component
35
- *
36
- * Usage:
37
- * const modal = jux.modal('myModal', {
38
- * title: 'Confirmation',
39
- * content: 'Are you sure?',
40
- * size: 'medium'
41
- * });
42
- * modal.render();
43
- * modal.open();
44
- */
45
- export class Modal {
46
- state: ModalState;
47
- container: HTMLElement | null = null;
48
- _id: string;
49
- id: string;
50
-
51
- // CRITICAL: Store bind/sync instructions for deferred wiring
52
- private _bindings: Array<{ event: string, handler: Function }> = [];
53
- private _syncBindings: Array<{
54
- property: string,
55
- stateObj: State<any>,
56
- toState?: Function,
57
- toComponent?: Function
58
- }> = [];
28
+ export class Modal extends BaseComponent<ModalState> {
29
+ private _overlay: HTMLElement | null = null;
59
30
 
60
31
  constructor(id: string, options: ModalOptions = {}) {
61
- this._id = id;
62
- this.id = id;
63
-
64
- this.state = {
32
+ super(id, {
65
33
  title: options.title ?? '',
66
34
  content: options.content ?? '',
67
35
  showCloseButton: options.showCloseButton ?? true,
68
36
  closeOnBackdropClick: options.closeOnBackdropClick ?? true,
69
37
  size: options.size ?? 'medium',
70
- isOpen: false,
71
38
  open: false,
72
39
  style: options.style ?? '',
73
40
  class: options.class ?? ''
74
- };
41
+ });
75
42
  }
76
43
 
77
- /* -------------------------
78
- * Fluent API
79
- * ------------------------- */
44
+ protected getTriggerEvents(): readonly string[] {
45
+ return TRIGGER_EVENTS;
46
+ }
47
+
48
+ protected getCallbackEvents(): readonly string[] {
49
+ return CALLBACK_EVENTS;
50
+ }
51
+
52
+ /* ═════════════════════════════════════════════════════════════════
53
+ * FLUENT API
54
+ * ═════════════════════════════════════════════════════════════════ */
55
+
56
+ // ✅ Inherited from BaseComponent
80
57
 
81
58
  title(value: string): this {
82
59
  this.state.title = value;
@@ -103,84 +80,43 @@ export class Modal {
103
80
  return this;
104
81
  }
105
82
 
106
- style(value: string): this {
107
- this.state.style = value;
108
- return this;
109
- }
110
-
111
- class(value: string): this {
112
- this.state.class = value;
113
- return this;
114
- }
115
-
116
- /* -------------------------
117
- * Modal controls
118
- * ------------------------- */
119
-
120
83
  open(): this {
121
- this.state.isOpen = true;
122
- if (this.container) {
123
- const modalEl = this.container.querySelector('.jux-modal');
124
- if (modalEl) {
125
- modalEl.classList.add('jux-modal-open');
126
- }
84
+ this.state.open = true;
85
+ if (this._overlay) {
86
+ this._overlay.style.display = 'flex';
127
87
  }
88
+ // 🎯 Fire the open callback event
89
+ this._triggerCallback('open');
128
90
  return this;
129
91
  }
130
92
 
131
93
  close(): this {
132
- this.state.isOpen = false;
133
- if (this.container) {
134
- const modalEl = this.container.querySelector('.jux-modal');
135
- if (modalEl) {
136
- modalEl.classList.remove('jux-modal-open');
137
- }
94
+ this.state.open = false;
95
+ if (this._overlay) {
96
+ this._overlay.style.display = 'none';
138
97
  }
98
+ // 🎯 Fire the close callback event
99
+ this._triggerCallback('close');
139
100
  return this;
140
101
  }
141
102
 
142
- /* -------------------------
143
- * Render
144
- * ------------------------- */
145
-
146
- bind(event: string, handler: Function): this {
147
- this._bindings.push({ event, handler });
148
- return this;
149
- }
150
-
151
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
152
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
153
- throw new Error(`Modal.sync: Expected a State object for property "${property}"`);
154
- }
155
- this._syncBindings.push({ property, stateObj, toState, toComponent });
156
- return this;
157
- }
103
+ /* ═════════════════════════════════════════════════════════════════
104
+ * RENDER
105
+ * ═════════════════════════════════════════════════════════════════ */
158
106
 
159
107
  render(targetId?: string): this {
160
- // === 1. SETUP: Get or create container ===
161
- let container: HTMLElement;
162
- if (targetId) {
163
- const target = document.querySelector(targetId);
164
- if (!target || !(target instanceof HTMLElement)) {
165
- throw new Error(`Modal: Target "${targetId}" not found`);
166
- }
167
- container = target;
168
- } else {
169
- container = getOrCreateContainer(this._id);
170
- }
171
- this.container = container;
108
+ const container = this._setupContainer(targetId);
172
109
 
173
- // === 2. PREPARE: Destructure state and check sync flags ===
174
110
  const { open, title, content, size, closeOnBackdropClick: closeOnBackdrop, showCloseButton: showClose, style, class: className } = this.state;
175
111
  const hasOpenSync = this._syncBindings.some(b => b.property === 'open');
176
112
 
177
- // === 3. BUILD: Create DOM elements ===
178
113
  const overlay = document.createElement('div');
179
114
  overlay.className = 'jux-modal-overlay';
180
115
  overlay.id = this._id;
181
116
  overlay.style.display = open ? 'flex' : 'none';
182
117
  if (className) overlay.className += ` ${className}`;
183
118
  if (style) overlay.setAttribute('style', style);
119
+ this._overlay = overlay;
184
120
 
185
121
  const modal = document.createElement('div');
186
122
  modal.className = `jux-modal jux-modal-${size}`;
@@ -206,9 +142,6 @@ export class Modal {
206
142
 
207
143
  overlay.appendChild(modal);
208
144
 
209
- // === 4. WIRE: Attach event listeners and sync bindings ===
210
-
211
- // Default close behavior (only if NOT using sync)
212
145
  if (!hasOpenSync) {
213
146
  if (showClose) {
214
147
  const closeButton = modal.querySelector('.jux-modal-close');
@@ -228,12 +161,8 @@ export class Modal {
228
161
  }
229
162
  }
230
163
 
231
- // Wire custom bindings from .bind() calls
232
- this._bindings.forEach(({ event, handler }) => {
233
- overlay.addEventListener(event, handler as EventListener);
234
- });
164
+ this._wireStandardEvents(overlay);
235
165
 
236
- // Wire sync bindings from .sync() calls
237
166
  this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
238
167
  if (property === 'open') {
239
168
  const transformToState = toState || ((v: any) => Boolean(v));
@@ -241,7 +170,6 @@ export class Modal {
241
170
 
242
171
  let isUpdating = false;
243
172
 
244
- // State → Component
245
173
  stateObj.subscribe((val: any) => {
246
174
  if (isUpdating) return;
247
175
  const transformed = transformToComponent(val);
@@ -249,7 +177,6 @@ export class Modal {
249
177
  overlay.style.display = transformed ? 'flex' : 'none';
250
178
  });
251
179
 
252
- // Component → State (close button, backdrop)
253
180
  if (showClose) {
254
181
  const closeButton = modal.querySelector('.jux-modal-close');
255
182
  closeButton?.addEventListener('click', () => {
@@ -280,19 +207,19 @@ export class Modal {
280
207
  }
281
208
  }
282
209
  else if (property === 'content') {
283
- const transformToComponent = toComponent || ((v: any) => String(v));
210
+ const transform = toComponent || ((v: any) => String(v));
284
211
 
285
212
  stateObj.subscribe((val: any) => {
286
- const transformed = transformToComponent(val);
213
+ const transformed = transform(val);
287
214
  body.innerHTML = transformed;
288
215
  this.state.content = transformed;
289
216
  });
290
217
  }
291
218
  else if (property === 'title') {
292
- const transformToComponent = toComponent || ((v: any) => String(v));
219
+ const transform = toComponent || ((v: any) => String(v));
293
220
 
294
221
  stateObj.subscribe((val: any) => {
295
- const transformed = transformToComponent(val);
222
+ const transformed = transform(val);
296
223
  const header = modal.querySelector('.jux-modal-header');
297
224
  if (header) {
298
225
  header.textContent = transformed;
@@ -302,25 +229,11 @@ export class Modal {
302
229
  }
303
230
  });
304
231
 
305
- // === 5. RENDER: Append to DOM and finalize ===
306
232
  container.appendChild(overlay);
307
233
  return this;
308
234
  }
309
-
310
- /**
311
- * Render to another Jux component's container
312
- */
313
- renderTo(juxComponent: any): this {
314
- if (!juxComponent?._id) {
315
- throw new Error('Modal.renderTo: Invalid component');
316
- }
317
- return this.render(`#${juxComponent._id}`);
318
- }
319
235
  }
320
236
 
321
- /**
322
- * Factory helper
323
- */
324
237
  export function modal(id: string, options: ModalOptions = {}): Modal {
325
238
  return new Modal(id, options);
326
239
  }