juxscript 1.0.19 → 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 (77) hide show
  1. package/bin/cli.js +121 -72
  2. package/lib/components/alert.ts +212 -165
  3. package/lib/components/badge.ts +93 -103
  4. package/lib/components/base/BaseComponent.ts +397 -0
  5. package/lib/components/base/FormInput.ts +322 -0
  6. package/lib/components/button.ts +63 -122
  7. package/lib/components/card.ts +109 -155
  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/charts/lib/chart-types.ts +159 -0
  13. package/lib/components/charts/lib/chart-utils.ts +160 -0
  14. package/lib/components/charts/lib/chart.ts +707 -0
  15. package/lib/components/checkbox.ts +264 -127
  16. package/lib/components/code.ts +75 -108
  17. package/lib/components/container.ts +113 -130
  18. package/lib/components/data.ts +37 -5
  19. package/lib/components/datepicker.ts +195 -147
  20. package/lib/components/dialog.ts +187 -157
  21. package/lib/components/divider.ts +85 -191
  22. package/lib/components/docs-data.json +544 -2027
  23. package/lib/components/dropdown.ts +178 -136
  24. package/lib/components/element.ts +227 -171
  25. package/lib/components/fileupload.ts +285 -228
  26. package/lib/components/guard.ts +92 -0
  27. package/lib/components/heading.ts +46 -69
  28. package/lib/components/helpers.ts +13 -6
  29. package/lib/components/hero.ts +107 -95
  30. package/lib/components/icon.ts +160 -0
  31. package/lib/components/icons.ts +175 -0
  32. package/lib/components/include.ts +153 -5
  33. package/lib/components/input.ts +174 -374
  34. package/lib/components/kpicard.ts +16 -16
  35. package/lib/components/list.ts +378 -240
  36. package/lib/components/loading.ts +142 -211
  37. package/lib/components/menu.ts +103 -97
  38. package/lib/components/modal.ts +138 -144
  39. package/lib/components/nav.ts +169 -90
  40. package/lib/components/paragraph.ts +49 -150
  41. package/lib/components/progress.ts +118 -200
  42. package/lib/components/radio.ts +297 -149
  43. package/lib/components/script.ts +19 -87
  44. package/lib/components/select.ts +184 -186
  45. package/lib/components/sidebar.ts +152 -140
  46. package/lib/components/style.ts +19 -82
  47. package/lib/components/switch.ts +258 -188
  48. package/lib/components/table.ts +1117 -170
  49. package/lib/components/tabs.ts +162 -145
  50. package/lib/components/theme-toggle.ts +108 -169
  51. package/lib/components/tooltip.ts +86 -157
  52. package/lib/components/write.ts +108 -127
  53. package/lib/jux.ts +86 -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 -2
  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 -1246
  67. package/lib/components/areachartsmooth.ts +0 -1380
  68. package/lib/components/barchart.ts +0 -1250
  69. package/lib/components/chart.ts +0 -127
  70. package/lib/components/doughnutchart.ts +0 -1191
  71. package/lib/components/footer.ts +0 -165
  72. package/lib/components/header.ts +0 -187
  73. package/lib/components/layout.ts +0 -239
  74. package/lib/components/main.ts +0 -137
  75. package/lib/layouts/default.jux +0 -8
  76. package/lib/layouts/figma.jux +0 -0
  77. /package/lib/{themes → components/charts/lib}/charts.js +0 -0
@@ -1,79 +1,57 @@
1
- import { getOrCreateContainer } from './helpers.js';
1
+ import { BaseComponent } from './base/BaseComponent.js';
2
+
3
+ // Event definitions
4
+ const TRIGGER_EVENTS = [] as const;
5
+ const CALLBACK_EVENTS = [] as const;
2
6
 
3
- /**
4
- * Badge component options
5
- */
6
7
  export interface BadgeOptions {
7
8
  text?: string;
8
9
  variant?: 'default' | 'success' | 'warning' | 'error' | 'info';
9
- size?: 'sm' | 'md' | 'lg';
10
10
  pill?: boolean;
11
11
  style?: string;
12
12
  class?: string;
13
13
  }
14
14
 
15
- /**
16
- * Badge component state
17
- */
18
15
  type BadgeState = {
19
16
  text: string;
20
17
  variant: string;
21
- size: string;
22
18
  pill: boolean;
23
19
  style: string;
24
20
  class: string;
25
21
  };
26
22
 
27
- /**
28
- * Badge component - Status indicators, counts, labels
29
- *
30
- * Usage:
31
- * jux.badge('status', {
32
- * text: 'Active',
33
- * variant: 'success',
34
- * pill: true
35
- * }).render('#card');
36
- *
37
- * jux.badge('count', { text: '5' }).render('#notifications');
38
- */
39
- export class Badge {
40
- state: BadgeState;
41
- container: HTMLElement | null = null;
42
- _id: string;
43
- id: string;
44
-
23
+ export class Badge extends BaseComponent<BadgeState> {
45
24
  constructor(id: string, options: BadgeOptions = {}) {
46
- this._id = id;
47
- this.id = id;
48
-
49
- this.state = {
25
+ super(id, {
50
26
  text: options.text ?? '',
51
27
  variant: options.variant ?? 'default',
52
- size: options.size ?? 'md',
53
28
  pill: options.pill ?? false,
54
29
  style: options.style ?? '',
55
30
  class: options.class ?? ''
56
- };
31
+ });
32
+ }
33
+
34
+ protected getTriggerEvents(): readonly string[] {
35
+ return TRIGGER_EVENTS;
36
+ }
37
+
38
+ protected getCallbackEvents(): readonly string[] {
39
+ return CALLBACK_EVENTS;
57
40
  }
58
41
 
59
- /* -------------------------
60
- * Fluent API
61
- * ------------------------- */
42
+ /* ═════════════════════════════════════════════════════════════════
43
+ * FLUENT API
44
+ * ═════════════════════════════════════════════════════════════════ */
45
+
46
+ // ✅ Inherited from BaseComponent
62
47
 
63
48
  text(value: string): this {
64
49
  this.state.text = value;
65
- this._updateElement();
66
50
  return this;
67
51
  }
68
52
 
69
53
  variant(value: 'default' | 'success' | 'warning' | 'error' | 'info'): this {
70
54
  this.state.variant = value;
71
- this._updateElement();
72
- return this;
73
- }
74
-
75
- size(value: 'sm' | 'md' | 'lg'): this {
76
- this.state.size = value;
77
55
  return this;
78
56
  }
79
57
 
@@ -82,80 +60,92 @@ export class Badge {
82
60
  return this;
83
61
  }
84
62
 
85
- style(value: string): this {
86
- this.state.style = value;
87
- return this;
88
- }
89
-
90
- class(value: string): this {
91
- this.state.class = value;
92
- return this;
93
- }
94
-
95
- /* -------------------------
96
- * Helpers
97
- * ------------------------- */
98
-
99
- private _updateElement(): void {
100
- const element = document.getElementById(this._id);
101
- if (element) {
102
- element.textContent = this.state.text;
103
- element.className = `jux-badge jux-badge-${this.state.variant} jux-badge-${this.state.size}`;
104
- if (this.state.pill) {
105
- element.classList.add('jux-badge-pill');
106
- }
107
- if (this.state.class) {
108
- element.className += ` ${this.state.class}`;
109
- }
110
- }
111
- }
112
-
113
- /* -------------------------
114
- * Render
115
- * ------------------------- */
63
+ /* ═════════════════════════════════════════════════════════════════
64
+ * RENDER
65
+ * ═════════════════════════════════════════════════════════════════ */
116
66
 
117
67
  render(targetId?: string): this {
118
- let container: HTMLElement;
119
-
120
- if (targetId) {
121
- const target = document.querySelector(targetId);
122
- if (!target || !(target instanceof HTMLElement)) {
123
- throw new Error(`Badge: Target element "${targetId}" not found`);
124
- }
125
- container = target;
126
- } else {
127
- container = getOrCreateContainer(this._id);
128
- }
68
+ const container = this._setupContainer(targetId);
129
69
 
130
- this.container = container;
131
- const { text, variant, size, pill, style, class: className } = this.state;
70
+ const { text, variant, pill, style, class: className } = this.state;
132
71
 
133
72
  const badge = document.createElement('span');
134
- 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');
135
75
  badge.id = this._id;
76
+ if (className) badge.className += ` ${className}`;
77
+ if (style) badge.setAttribute('style', style);
136
78
  badge.textContent = text;
137
79
 
138
- if (pill) {
139
- badge.classList.add('jux-badge-pill');
140
- }
80
+ this._wireStandardEvents(badge);
141
81
 
142
- if (className) {
143
- badge.className += ` ${className}`;
144
- }
145
-
146
- if (style) {
147
- badge.setAttribute('style', style);
148
- }
82
+ // Wire sync for text
83
+ this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
84
+ if (property === 'text') {
85
+ const transform = toComponent || ((v: any) => String(v));
86
+ stateObj.subscribe((val: any) => {
87
+ badge.textContent = transform(val);
88
+ this.state.text = transform(val);
89
+ });
90
+ }
91
+ });
149
92
 
150
93
  container.appendChild(badge);
94
+ this._injectBadgeStyles();
95
+
151
96
  return this;
152
97
  }
153
98
 
154
- renderTo(juxComponent: any): this {
155
- if (!juxComponent?._id) {
156
- throw new Error('Badge.renderTo: Invalid component');
157
- }
158
- 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);
159
149
  }
160
150
  }
161
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
+ }