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,10 +1,13 @@
1
- import { getOrCreateContainer } from './helpers.js';
2
- import { State } from '../reactivity/state.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+
3
+ // Event definitions
4
+ const TRIGGER_EVENTS = [] as const;
5
+ const CALLBACK_EVENTS = [] as const;
3
6
 
4
7
  export interface BadgeOptions {
5
8
  text?: string;
6
- variant?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
7
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
9
+ variant?: 'default' | 'success' | 'warning' | 'error' | 'info';
10
+ pill?: boolean;
8
11
  style?: string;
9
12
  class?: string;
10
13
  }
@@ -12,141 +15,137 @@ export interface BadgeOptions {
12
15
  type BadgeState = {
13
16
  text: string;
14
17
  variant: string;
15
- size: string;
18
+ pill: boolean;
16
19
  style: string;
17
20
  class: string;
18
21
  };
19
22
 
20
- export class Badge {
21
- state: BadgeState;
22
- container: HTMLElement | null = null;
23
- _id: string;
24
- id: string;
25
-
26
- private _bindings: Array<{ event: string, handler: Function }> = [];
27
- private _syncBindings: Array<{
28
- property: string,
29
- stateObj: State<any>,
30
- toState?: Function,
31
- toComponent?: Function
32
- }> = [];
33
-
23
+ export class Badge extends BaseComponent<BadgeState> {
34
24
  constructor(id: string, options: BadgeOptions = {}) {
35
- this._id = id;
36
- this.id = id;
37
-
38
- this.state = {
25
+ super(id, {
39
26
  text: options.text ?? '',
40
- variant: options.variant ?? 'primary',
41
- size: options.size ?? 'md',
27
+ variant: options.variant ?? 'default',
28
+ pill: options.pill ?? false,
42
29
  style: options.style ?? '',
43
30
  class: options.class ?? ''
44
- };
31
+ });
45
32
  }
46
33
 
47
- text(value: string): this {
48
- this.state.text = value;
49
- return this;
34
+ protected getTriggerEvents(): readonly string[] {
35
+ return TRIGGER_EVENTS;
50
36
  }
51
37
 
52
- variant(value: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info'): this {
53
- this.state.variant = value;
54
- return this;
38
+ protected getCallbackEvents(): readonly string[] {
39
+ return CALLBACK_EVENTS;
55
40
  }
56
41
 
57
- size(value: 'xs' | 'sm' | 'md' | 'lg' | 'xl'): this {
58
- this.state.size = value;
59
- return this;
60
- }
42
+ /* ═════════════════════════════════════════════════════════════════
43
+ * FLUENT API
44
+ * ═════════════════════════════════════════════════════════════════ */
61
45
 
62
- style(value: string): this {
63
- this.state.style = value;
64
- return this;
65
- }
46
+ // Inherited from BaseComponent
66
47
 
67
- class(value: string): this {
68
- this.state.class = value;
48
+ text(value: string): this {
49
+ this.state.text = value;
69
50
  return this;
70
51
  }
71
52
 
72
- bind(event: string, handler: Function): this {
73
- this._bindings.push({ event, handler });
53
+ variant(value: 'default' | 'success' | 'warning' | 'error' | 'info'): this {
54
+ this.state.variant = value;
74
55
  return this;
75
56
  }
76
57
 
77
- sync(property: string, stateObj: State<any>, toState?: Function, toComponent?: Function): this {
78
- if (!stateObj || typeof stateObj.subscribe !== 'function') {
79
- throw new Error(`Badge.sync: Expected a State object for property "${property}"`);
80
- }
81
- this._syncBindings.push({ property, stateObj, toState, toComponent });
58
+ pill(value: boolean): this {
59
+ this.state.pill = value;
82
60
  return this;
83
61
  }
84
62
 
63
+ /* ═════════════════════════════════════════════════════════════════
64
+ * RENDER
65
+ * ═════════════════════════════════════════════════════════════════ */
66
+
85
67
  render(targetId?: string): this {
86
- // === 1. SETUP: Get or create container ===
87
- let container: HTMLElement;
88
- if (targetId) {
89
- const target = document.querySelector(targetId);
90
- if (!target || !(target instanceof HTMLElement)) {
91
- throw new Error(`Badge: Target "${targetId}" not found`);
92
- }
93
- container = target;
94
- } else {
95
- container = getOrCreateContainer(this._id);
96
- }
97
- this.container = container;
68
+ const container = this._setupContainer(targetId);
98
69
 
99
- // === 2. PREPARE: Destructure state ===
100
- const { text, variant, size, style, class: className } = this.state;
70
+ const { text, variant, pill, style, class: className } = this.state;
101
71
 
102
- // === 3. BUILD: Create DOM elements ===
103
72
  const badge = document.createElement('span');
104
- badge.className = `jux-badge jux-badge-${variant} jux-badge-${size}`;
73
+ badge.className = `jux-badge jux-badge-${variant}`;
74
+ if (pill) badge.classList.add('jux-badge-pill');
105
75
  badge.id = this._id;
106
- badge.textContent = text;
107
76
  if (className) badge.className += ` ${className}`;
108
77
  if (style) badge.setAttribute('style', style);
78
+ badge.textContent = text;
109
79
 
110
- // === 4. WIRE: Attach event listeners and sync bindings ===
111
-
112
- // Wire custom bindings from .bind() calls
113
- this._bindings.forEach(({ event, handler }) => {
114
- badge.addEventListener(event, handler as EventListener);
115
- });
80
+ this._wireStandardEvents(badge);
116
81
 
117
- // Wire sync bindings from .sync() calls
82
+ // Wire sync for text
118
83
  this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
119
84
  if (property === 'text') {
120
- const transformToComponent = toComponent || ((v: any) => String(v));
121
-
85
+ const transform = toComponent || ((v: any) => String(v));
122
86
  stateObj.subscribe((val: any) => {
123
- const transformed = transformToComponent(val);
124
- badge.textContent = transformed;
125
- this.state.text = transformed;
126
- });
127
- }
128
- else if (property === 'variant') {
129
- const transformToComponent = toComponent || ((v: any) => String(v));
130
-
131
- stateObj.subscribe((val: any) => {
132
- const transformed = transformToComponent(val);
133
- badge.classList.remove(`jux-badge-${this.state.variant}`);
134
- this.state.variant = transformed;
135
- badge.classList.add(`jux-badge-${transformed}`);
87
+ badge.textContent = transform(val);
88
+ this.state.text = transform(val);
136
89
  });
137
90
  }
138
91
  });
139
92
 
140
- // === 5. RENDER: Append to DOM and finalize ===
141
93
  container.appendChild(badge);
94
+ this._injectBadgeStyles();
95
+
142
96
  return this;
143
97
  }
144
98
 
145
- renderTo(juxComponent: any): this {
146
- if (!juxComponent?._id) {
147
- throw new Error('Badge.renderTo: Invalid component');
148
- }
149
- return this.render(`#${juxComponent._id}`);
99
+ private _injectBadgeStyles(): void {
100
+ const styleId = 'jux-badge-styles';
101
+ if (document.getElementById(styleId)) return;
102
+
103
+ const style = document.createElement('style');
104
+ style.id = styleId;
105
+ style.textContent = `
106
+ .jux-badge {
107
+ display: inline-block;
108
+ padding: 0.25rem 0.75rem;
109
+ font-size: 0.75rem;
110
+ font-weight: 600;
111
+ line-height: 1;
112
+ text-align: center;
113
+ white-space: nowrap;
114
+ vertical-align: baseline;
115
+ border-radius: 0.25rem;
116
+ transition: all 0.2s;
117
+ }
118
+
119
+ .jux-badge-pill {
120
+ border-radius: 10rem;
121
+ }
122
+
123
+ .jux-badge-default {
124
+ color: #374151;
125
+ background-color: #e5e7eb;
126
+ }
127
+
128
+ .jux-badge-success {
129
+ color: #065f46;
130
+ background-color: #d1fae5;
131
+ }
132
+
133
+ .jux-badge-warning {
134
+ color: #92400e;
135
+ background-color: #fef3c7;
136
+ }
137
+
138
+ .jux-badge-error {
139
+ color: #991b1b;
140
+ background-color: #fee2e2;
141
+ }
142
+
143
+ .jux-badge-info {
144
+ color: #1e40af;
145
+ background-color: #dbeafe;
146
+ }
147
+ `;
148
+ document.head.appendChild(style);
150
149
  }
151
150
  }
152
151
 
@@ -0,0 +1,397 @@
1
+ import { State } from '../../reactivity/state.js';
2
+ import { getOrCreateContainer } from '../helpers.js';
3
+
4
+ /**
5
+ * Abstract base class for all JUX components
6
+ * Provides common storage, event routing, and lifecycle methods
7
+ *
8
+ * Children must provide:
9
+ * - TRIGGER_EVENTS constant (readonly string[])
10
+ * - CALLBACK_EVENTS constant (readonly string[])
11
+ * - render() implementation
12
+ */
13
+ export abstract class BaseComponent<TState extends Record<string, any>> {
14
+ // Common properties (all components have these)
15
+ state: TState;
16
+ container: HTMLElement | null = null;
17
+ _id: string;
18
+ id: string;
19
+
20
+ // Event & sync storage (populated by bind() and sync())
21
+ protected _bindings: Array<{ event: string, handler: Function }> = [];
22
+ protected _syncBindings: Array<{
23
+ property: string,
24
+ stateObj: State<any>,
25
+ toState?: Function,
26
+ toComponent?: Function
27
+ }> = [];
28
+ protected _triggerHandlers: Map<string, Function> = new Map();
29
+ protected _callbackHandlers: Map<string, Function> = new Map();
30
+
31
+ constructor(id: string, initialState: TState) {
32
+ this._id = id;
33
+ this.id = id;
34
+ this.state = initialState;
35
+ }
36
+
37
+ /* ═════════════════════════════════════════════════════════════════
38
+ * ABSTRACT METHODS (Child must implement)
39
+ * ═════════════════════════════════════════════════════════════════ */
40
+
41
+ protected abstract getTriggerEvents(): readonly string[];
42
+ protected abstract getCallbackEvents(): readonly string[];
43
+ abstract render(targetId?: string): this;
44
+
45
+ /* ═════════════════════════════════════════════════════════════════
46
+ * COMMON FLUENT API (Inherited by all components)
47
+ * ═════════════════════════════════════════════════════════════════ */
48
+
49
+ /**
50
+ * Set component style
51
+ */
52
+ style(value: string): this {
53
+ (this.state as any).style = value;
54
+ return this;
55
+ }
56
+
57
+ /**
58
+ * Set component class
59
+ */
60
+ class(value: string): this {
61
+ (this.state as any).class = value;
62
+ return this;
63
+ }
64
+
65
+ /* ═════════════════════════════════════════════════════════════════
66
+ * CSS CLASS MANAGEMENT
67
+ * ═════════════════════════════════════════════════════════════════ */
68
+
69
+ /**
70
+ * Add a CSS class to the component
71
+ */
72
+ addClass(value: string): this {
73
+ const current = (this.state as any).class || '';
74
+ const classes = current.split(' ').filter((c: string) => c);
75
+ if (!classes.includes(value)) {
76
+ classes.push(value);
77
+ (this.state as any).class = classes.join(' ');
78
+ if (this.container) this.container.classList.add(value);
79
+ }
80
+ return this;
81
+ }
82
+
83
+ /**
84
+ * Remove a CSS class from the component
85
+ */
86
+ removeClass(value: string): this {
87
+ const current = (this.state as any).class || '';
88
+ const classes = current.split(' ').filter((c: string) => c && c !== value);
89
+ (this.state as any).class = classes.join(' ');
90
+ if (this.container) this.container.classList.remove(value);
91
+ return this;
92
+ }
93
+
94
+ /**
95
+ * Toggle a CSS class on the component
96
+ */
97
+ toggleClass(value: string): this {
98
+ const current = (this.state as any).class || '';
99
+ const hasClass = current.split(' ').includes(value);
100
+ return hasClass ? this.removeClass(value) : this.addClass(value);
101
+ }
102
+
103
+ /* ═════════════════════════════════════════════════════════════════
104
+ * VISIBILITY CONTROL
105
+ * ═════════════════════════════════════════════════════════════════ */
106
+
107
+ /**
108
+ * Set component visibility
109
+ */
110
+ visible(value: boolean): this {
111
+ (this.state as any).visible = value;
112
+ if (this.container) {
113
+ this.container.style.display = value ? '' : 'none';
114
+ }
115
+ return this;
116
+ }
117
+
118
+ /**
119
+ * Show the component
120
+ */
121
+ show(): this {
122
+ return this.visible(true);
123
+ }
124
+
125
+ /**
126
+ * Hide the component
127
+ */
128
+ hide(): this {
129
+ return this.visible(false);
130
+ }
131
+
132
+ /**
133
+ * Toggle component visibility
134
+ */
135
+ toggleVisibility(): this {
136
+ const isVisible = (this.state as any).visible ?? true;
137
+ return this.visible(!isVisible);
138
+ }
139
+
140
+ /* ═════════════════════════════════════════════════════════════════
141
+ * ATTRIBUTE MANAGEMENT
142
+ * ═════════════════════════════════════════════════════════════════ */
143
+
144
+ /**
145
+ * Set a single HTML attribute
146
+ */
147
+ attr(name: string, value: string): this {
148
+ const attrs = (this.state as any).attributes || {};
149
+ (this.state as any).attributes = { ...attrs, [name]: value };
150
+ if (this.container) this.container.setAttribute(name, value);
151
+ return this;
152
+ }
153
+
154
+ /**
155
+ * Set multiple HTML attributes
156
+ */
157
+ attrs(attributes: Record<string, string>): this {
158
+ Object.entries(attributes).forEach(([name, value]) => {
159
+ this.attr(name, value);
160
+ });
161
+ return this;
162
+ }
163
+
164
+ /**
165
+ * Remove an HTML attribute
166
+ */
167
+ removeAttr(name: string): this {
168
+ const attrs = (this.state as any).attributes || {};
169
+ delete attrs[name];
170
+ if (this.container) this.container.removeAttribute(name);
171
+ return this;
172
+ }
173
+
174
+ /* ═════════════════════════════════════════════════════════════════
175
+ * DISABLED STATE
176
+ * ═════════════════════════════════════════════════════════════════ */
177
+
178
+ /**
179
+ * Set disabled state for interactive elements
180
+ */
181
+ disabled(value: boolean): this {
182
+ (this.state as any).disabled = value;
183
+ if (this.container) {
184
+ const inputs = this.container.querySelectorAll('input, button, select, textarea');
185
+ inputs.forEach(el => {
186
+ (el as HTMLInputElement).disabled = value;
187
+ });
188
+ this.container.setAttribute('aria-disabled', String(value));
189
+ }
190
+ return this;
191
+ }
192
+
193
+ /**
194
+ * Enable the component
195
+ */
196
+ enable(): this {
197
+ return this.disabled(false);
198
+ }
199
+
200
+ /**
201
+ * Disable the component
202
+ */
203
+ disable(): this {
204
+ return this.disabled(true);
205
+ }
206
+
207
+ /* ═════════════════════════════════════════════════════════════════
208
+ * LOADING STATE
209
+ * ═════════════════════════════════════════════════════════════════ */
210
+
211
+ /**
212
+ * Set loading state
213
+ */
214
+ loading(value: boolean): this {
215
+ (this.state as any).loading = value;
216
+ if (this.container) {
217
+ if (value) {
218
+ this.container.classList.add('jux-loading');
219
+ this.container.setAttribute('aria-busy', 'true');
220
+ } else {
221
+ this.container.classList.remove('jux-loading');
222
+ this.container.removeAttribute('aria-busy');
223
+ }
224
+ }
225
+ return this;
226
+ }
227
+
228
+ /* ═════════════════════════════════════════════════════════════════
229
+ * FOCUS MANAGEMENT
230
+ * ═════════════════════════════════════════════════════════════════ */
231
+
232
+ /**
233
+ * Focus the first focusable element in the component
234
+ */
235
+ focus(): this {
236
+ if (this.container) {
237
+ const focusable = this.container.querySelector('input, button, select, textarea, [tabindex]');
238
+ if (focusable) (focusable as HTMLElement).focus();
239
+ }
240
+ return this;
241
+ }
242
+
243
+ /**
244
+ * Blur the currently focused element in the component
245
+ */
246
+ blur(): this {
247
+ if (this.container) {
248
+ const focused = this.container.querySelector(':focus');
249
+ if (focused) (focused as HTMLElement).blur();
250
+ }
251
+ return this;
252
+ }
253
+
254
+ /* ═════════════════════════════════════════════════════════════════
255
+ * DOM MANIPULATION
256
+ * ═════════════════════════════════════════════════════════════════ */
257
+
258
+ /**
259
+ * Remove the component from the DOM
260
+ */
261
+ remove(): this {
262
+ if (this.container) {
263
+ this.container.remove();
264
+ this.container = null;
265
+ }
266
+ return this;
267
+ }
268
+
269
+ /* ═════════════════════════════════════════════════════════════════
270
+ * EVENT BINDING (Shared logic)
271
+ * ═════════════════════════════════════════════════════════════════ */
272
+
273
+ bind(event: string, handler: Function): this {
274
+ if (this._isTriggerEvent(event)) {
275
+ this._triggerHandlers.set(event, handler);
276
+ } else if (this._isCallbackEvent(event)) {
277
+ this._callbackHandlers.set(event, handler);
278
+ } else {
279
+ this._bindings.push({ event, handler });
280
+ }
281
+ return this;
282
+ }
283
+
284
+ /**
285
+ * Sync a component property with a State object
286
+ * @param property - The property to sync
287
+ * @param stateObj - The State object to sync with
288
+ * @param toStateOrTransform - Either toState function OR a simple transform function
289
+ * @param toComponent - Optional toComponent function (if toState was provided)
290
+ */
291
+ sync(property: string, stateObj: State<any>, toStateOrTransform?: Function, toComponent?: Function): this {
292
+ if (!stateObj || typeof stateObj.subscribe !== 'function') {
293
+ throw new Error(`${this.constructor.name}.sync: Expected a State object for property "${property}"`);
294
+ }
295
+
296
+ // If only 3 args provided, treat the function as toComponent (the common case)
297
+ const actualToState = (toComponent !== undefined) ? toStateOrTransform : undefined;
298
+ const actualToComponent = (toComponent !== undefined) ? toComponent : toStateOrTransform;
299
+
300
+ this._syncBindings.push({
301
+ property,
302
+ stateObj,
303
+ toState: actualToState,
304
+ toComponent: actualToComponent
305
+ });
306
+ return this;
307
+ }
308
+
309
+ protected _isTriggerEvent(event: string): boolean {
310
+ return this.getTriggerEvents().includes(event);
311
+ }
312
+
313
+ protected _isCallbackEvent(event: string): boolean {
314
+ return this.getCallbackEvents().includes(event);
315
+ }
316
+
317
+ protected _triggerCallback(eventName: string, ...args: any[]): void {
318
+
319
+ if (this._callbackHandlers.has(eventName)) {
320
+ const handler = this._callbackHandlers.get(eventName)!;
321
+ handler(...args);
322
+ } else {
323
+ console.warn(`🔍 No handler found for "${eventName}"`);
324
+ }
325
+ }
326
+
327
+ /* ═════════════════════════════════════════════════════════════════
328
+ * COMMON RENDER HELPERS
329
+ * ═════════════════════════════════════════════════════════════════ */
330
+
331
+ protected _setupContainer(targetId?: string): HTMLElement {
332
+ let container: HTMLElement;
333
+ if (targetId) {
334
+ // Strip leading # if present
335
+ const id = targetId.startsWith('#') ? targetId.slice(1) : targetId;
336
+ const target = document.getElementById(id);
337
+ if (target) {
338
+ container = target;
339
+ } else {
340
+ // Gracefully create the container instead of throwing
341
+ console.warn(`[Jux] Target "${targetId}" not found, creating it with graceful fallback`);
342
+ container = getOrCreateContainer(id);
343
+ }
344
+ } else {
345
+ container = getOrCreateContainer(this._id);
346
+ }
347
+
348
+ // Add universal component class for DOM inspection
349
+ // container.classList.add('jux-component');
350
+
351
+ this.container = container;
352
+ return container;
353
+ }
354
+
355
+ protected _wireStandardEvents(element: HTMLElement): void {
356
+ this._bindings.forEach(({ event, handler }) => {
357
+ element.addEventListener(event, handler as EventListener);
358
+ });
359
+ }
360
+
361
+ /**
362
+ * Automatically wire ALL sync bindings by calling the corresponding method
363
+ * if it exists on the component
364
+ */
365
+ protected _wireAllSyncs(): void {
366
+ this._syncBindings.forEach(({ property, stateObj, toComponent }) => {
367
+ const transform = toComponent || ((v: any) => v);
368
+
369
+ // Check if component has a method matching the property name
370
+ const method = (this as any)[property];
371
+
372
+ if (typeof method === 'function') {
373
+ // Set initial value
374
+ const initialValue = transform(stateObj.value);
375
+ method.call(this, initialValue);
376
+
377
+ // Subscribe to changes
378
+ stateObj.subscribe((val: any) => {
379
+ const transformed = transform(val);
380
+ method.call(this, transformed);
381
+ });
382
+ } else {
383
+ console.warn(
384
+ `[Jux] ${this.constructor.name}.sync('${property}'): ` +
385
+ `No method .${property}() found. Property will not be synced.`
386
+ );
387
+ }
388
+ });
389
+ }
390
+
391
+ renderTo(juxComponent: any): this {
392
+ if (!juxComponent?._id) {
393
+ throw new Error(`${this.constructor.name}.renderTo: Invalid component`);
394
+ }
395
+ return this.render(`#${juxComponent._id}`);
396
+ }
397
+ }