juxscript 1.1.239 → 1.1.243
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +7 -137
- package/lib/components/dataframe.ts +0 -569
- package/lib/components/tag.ts +68 -0
- package/lib/styles/shadcn.css +20 -7
- package/lib/utils/idgen.ts +6 -0
- package/package.json +5 -6
- package/index.d.ts +0 -239
- package/index.d.ts.map +0 -1
- package/lib/components/alert.d.ts +0 -36
- package/lib/components/alert.d.ts.map +0 -1
- package/lib/components/alert.js +0 -172
- package/lib/components/alert.ts +0 -219
- package/lib/components/app.d.ts +0 -89
- package/lib/components/app.d.ts.map +0 -1
- package/lib/components/app.js +0 -175
- package/lib/components/app.ts +0 -247
- package/lib/components/badge.d.ts +0 -26
- package/lib/components/badge.d.ts.map +0 -1
- package/lib/components/badge.js +0 -91
- package/lib/components/badge.ts +0 -118
- package/lib/components/base/Animations.d.ts +0 -36
- package/lib/components/base/Animations.d.ts.map +0 -1
- package/lib/components/base/Animations.js +0 -70
- package/lib/components/base/Animations.ts +0 -112
- package/lib/components/base/BaseComponent.d.ts +0 -294
- package/lib/components/base/BaseComponent.d.ts.map +0 -1
- package/lib/components/base/BaseComponent.js +0 -735
- package/lib/components/base/BaseComponent.ts +0 -884
- package/lib/components/base/FormInput.d.ts +0 -77
- package/lib/components/base/FormInput.d.ts.map +0 -1
- package/lib/components/base/FormInput.js +0 -171
- package/lib/components/base/FormInput.ts +0 -237
- package/lib/components/blueprint.d.ts +0 -40
- package/lib/components/blueprint.d.ts.map +0 -1
- package/lib/components/blueprint.js +0 -327
- package/lib/components/button.d.ts +0 -70
- package/lib/components/button.d.ts.map +0 -1
- package/lib/components/button.js +0 -177
- package/lib/components/button.ts +0 -237
- package/lib/components/card.d.ts +0 -35
- package/lib/components/card.d.ts.map +0 -1
- package/lib/components/card.js +0 -130
- package/lib/components/card.ts +0 -177
- package/lib/components/chart.d.ts +0 -49
- package/lib/components/chart.d.ts.map +0 -1
- package/lib/components/chart.js +0 -205
- package/lib/components/chart.ts +0 -254
- package/lib/components/checkbox.d.ts +0 -33
- package/lib/components/checkbox.d.ts.map +0 -1
- package/lib/components/checkbox.js +0 -202
- package/lib/components/checkbox.ts +0 -260
- package/lib/components/code.d.ts +0 -52
- package/lib/components/code.d.ts.map +0 -1
- package/lib/components/code.js +0 -201
- package/lib/components/code.ts +0 -260
- package/lib/components/container.d.ts +0 -60
- package/lib/components/container.d.ts.map +0 -1
- package/lib/components/container.js +0 -195
- package/lib/components/container.ts +0 -259
- package/lib/components/data.d.ts +0 -36
- package/lib/components/data.d.ts.map +0 -1
- package/lib/components/data.js +0 -110
- package/lib/components/data.ts +0 -135
- package/lib/components/dataframe/DataFrameSource.d.ts +0 -118
- package/lib/components/dataframe/DataFrameSource.d.ts.map +0 -1
- package/lib/components/dataframe/DataFrameSource.js +0 -421
- package/lib/components/dataframe/DataFrameSource.ts +0 -532
- package/lib/components/dataframe/ImportSettingsModal.d.ts +0 -60
- package/lib/components/dataframe/ImportSettingsModal.d.ts.map +0 -1
- package/lib/components/dataframe/ImportSettingsModal.js +0 -442
- package/lib/components/dataframe/ImportSettingsModal.ts +0 -531
- package/lib/components/dataframe.d.ts +0 -110
- package/lib/components/dataframe.d.ts.map +0 -1
- package/lib/components/dataframe.js +0 -470
- package/lib/components/datepicker.d.ts +0 -40
- package/lib/components/datepicker.d.ts.map +0 -1
- package/lib/components/datepicker.js +0 -193
- package/lib/components/datepicker.ts +0 -251
- package/lib/components/dialog.d.ts +0 -39
- package/lib/components/dialog.d.ts.map +0 -1
- package/lib/components/dialog.js +0 -131
- package/lib/components/dialog.ts +0 -178
- package/lib/components/divider.d.ts +0 -31
- package/lib/components/divider.d.ts.map +0 -1
- package/lib/components/divider.js +0 -72
- package/lib/components/divider.ts +0 -104
- package/lib/components/dropdown-menu.d.ts +0 -42
- package/lib/components/dropdown-menu.d.ts.map +0 -1
- package/lib/components/dropdown-menu.js +0 -177
- package/lib/components/dropdown-menu.ts +0 -214
- package/lib/components/dropdown.d.ts +0 -41
- package/lib/components/dropdown.d.ts.map +0 -1
- package/lib/components/dropdown.js +0 -136
- package/lib/components/dropdown.ts +0 -188
- package/lib/components/element.d.ts +0 -51
- package/lib/components/element.d.ts.map +0 -1
- package/lib/components/element.js +0 -209
- package/lib/components/element.ts +0 -271
- package/lib/components/event-chain.d.ts +0 -9
- package/lib/components/event-chain.d.ts.map +0 -1
- package/lib/components/event-chain.js +0 -33
- package/lib/components/fileupload.d.ts +0 -98
- package/lib/components/fileupload.d.ts.map +0 -1
- package/lib/components/fileupload.js +0 -351
- package/lib/components/fileupload.ts +0 -449
- package/lib/components/grid.d.ts +0 -88
- package/lib/components/grid.d.ts.map +0 -1
- package/lib/components/grid.js +0 -208
- package/lib/components/grid.ts +0 -295
- package/lib/components/heading.d.ts +0 -25
- package/lib/components/heading.d.ts.map +0 -1
- package/lib/components/heading.js +0 -83
- package/lib/components/heading.ts +0 -113
- package/lib/components/helpers.d.ts +0 -9
- package/lib/components/helpers.d.ts.map +0 -1
- package/lib/components/helpers.js +0 -30
- package/lib/components/helpers.ts +0 -41
- package/lib/components/hero.d.ts +0 -60
- package/lib/components/hero.d.ts.map +0 -1
- package/lib/components/hero.js +0 -239
- package/lib/components/hero.ts +0 -302
- package/lib/components/history/StateHistory.d.ts +0 -91
- package/lib/components/history/StateHistory.d.ts.map +0 -1
- package/lib/components/history/StateHistory.js +0 -154
- package/lib/components/history/StateHistory.ts +0 -200
- package/lib/components/icon.d.ts +0 -36
- package/lib/components/icon.d.ts.map +0 -1
- package/lib/components/icon.js +0 -135
- package/lib/components/icon.ts +0 -182
- package/lib/components/icons.d.ts +0 -25
- package/lib/components/icons.d.ts.map +0 -1
- package/lib/components/icons.js +0 -440
- package/lib/components/icons.ts +0 -464
- package/lib/components/image.d.ts +0 -42
- package/lib/components/image.d.ts.map +0 -1
- package/lib/components/image.js +0 -204
- package/lib/components/image.ts +0 -260
- package/lib/components/include.d.ts +0 -86
- package/lib/components/include.d.ts.map +0 -1
- package/lib/components/include.js +0 -238
- package/lib/components/include.ts +0 -281
- package/lib/components/input.d.ts +0 -85
- package/lib/components/input.d.ts.map +0 -1
- package/lib/components/input.js +0 -362
- package/lib/components/input.ts +0 -473
- package/lib/components/layer.d.ts +0 -72
- package/lib/components/layer.d.ts.map +0 -1
- package/lib/components/layer.js +0 -219
- package/lib/components/layer.ts +0 -304
- package/lib/components/link.d.ts +0 -41
- package/lib/components/link.d.ts.map +0 -1
- package/lib/components/link.js +0 -216
- package/lib/components/link.ts +0 -268
- package/lib/components/list.d.ts +0 -83
- package/lib/components/list.d.ts.map +0 -1
- package/lib/components/list.js +0 -314
- package/lib/components/list.ts +0 -423
- package/lib/components/loading.d.ts +0 -25
- package/lib/components/loading.d.ts.map +0 -1
- package/lib/components/loading.js +0 -76
- package/lib/components/loading.ts +0 -104
- package/lib/components/menu.d.ts +0 -38
- package/lib/components/menu.d.ts.map +0 -1
- package/lib/components/menu.js +0 -205
- package/lib/components/menu.ts +0 -279
- package/lib/components/modal.d.ts +0 -97
- package/lib/components/modal.d.ts.map +0 -1
- package/lib/components/modal.js +0 -463
- package/lib/components/modal.ts +0 -576
- package/lib/components/nav.d.ts +0 -46
- package/lib/components/nav.d.ts.map +0 -1
- package/lib/components/nav.js +0 -193
- package/lib/components/nav.ts +0 -261
- package/lib/components/paragraph.d.ts +0 -30
- package/lib/components/paragraph.d.ts.map +0 -1
- package/lib/components/paragraph.js +0 -93
- package/lib/components/paragraph.ts +0 -123
- package/lib/components/pen.d.ts +0 -125
- package/lib/components/pen.d.ts.map +0 -1
- package/lib/components/pen.js +0 -443
- package/lib/components/pen.ts +0 -567
- package/lib/components/progress.d.ts +0 -40
- package/lib/components/progress.d.ts.map +0 -1
- package/lib/components/progress.js +0 -116
- package/lib/components/progress.ts +0 -163
- package/lib/components/radio.d.ts +0 -43
- package/lib/components/radio.d.ts.map +0 -1
- package/lib/components/radio.js +0 -226
- package/lib/components/radio.ts +0 -303
- package/lib/components/registry.d.ts +0 -34
- package/lib/components/registry.d.ts.map +0 -1
- package/lib/components/registry.js +0 -163
- package/lib/components/registry.ts +0 -193
- package/lib/components/req.d.ts +0 -155
- package/lib/components/req.d.ts.map +0 -1
- package/lib/components/req.js +0 -253
- package/lib/components/req.ts +0 -303
- package/lib/components/script.d.ts +0 -14
- package/lib/components/script.d.ts.map +0 -1
- package/lib/components/script.js +0 -33
- package/lib/components/script.ts +0 -41
- package/lib/components/select.d.ts +0 -42
- package/lib/components/select.d.ts.map +0 -1
- package/lib/components/select.js +0 -209
- package/lib/components/select.ts +0 -281
- package/lib/components/sidebar.d.ts +0 -59
- package/lib/components/sidebar.d.ts.map +0 -1
- package/lib/components/sidebar.js +0 -298
- package/lib/components/sidebar.ts +0 -395
- package/lib/components/stack/BaseStack.d.ts +0 -65
- package/lib/components/stack/BaseStack.d.ts.map +0 -1
- package/lib/components/stack/BaseStack.js +0 -274
- package/lib/components/stack/BaseStack.ts +0 -328
- package/lib/components/stack/HStack.d.ts +0 -18
- package/lib/components/stack/HStack.d.ts.map +0 -1
- package/lib/components/stack/HStack.js +0 -22
- package/lib/components/stack/HStack.ts +0 -25
- package/lib/components/stack/VStack.d.ts +0 -19
- package/lib/components/stack/VStack.d.ts.map +0 -1
- package/lib/components/stack/VStack.js +0 -23
- package/lib/components/stack/VStack.ts +0 -26
- package/lib/components/stack/ZStack.d.ts +0 -18
- package/lib/components/stack/ZStack.d.ts.map +0 -1
- package/lib/components/stack/ZStack.js +0 -22
- package/lib/components/stack/ZStack.ts +0 -25
- package/lib/components/style.d.ts +0 -14
- package/lib/components/style.d.ts.map +0 -1
- package/lib/components/style.js +0 -33
- package/lib/components/style.ts +0 -41
- package/lib/components/switch.d.ts +0 -34
- package/lib/components/switch.d.ts.map +0 -1
- package/lib/components/switch.js +0 -209
- package/lib/components/switch.ts +0 -272
- package/lib/components/table.d.ts +0 -137
- package/lib/components/table.d.ts.map +0 -1
- package/lib/components/table.js +0 -1019
- package/lib/components/table.ts +0 -1225
- package/lib/components/tabs.d.ts +0 -53
- package/lib/components/tabs.d.ts.map +0 -1
- package/lib/components/tabs.js +0 -275
- package/lib/components/tabs.ts +0 -349
- package/lib/components/theme-toggle.d.ts +0 -45
- package/lib/components/theme-toggle.d.ts.map +0 -1
- package/lib/components/theme-toggle.js +0 -218
- package/lib/components/theme-toggle.ts +0 -297
- package/lib/components/tooltip.d.ts +0 -31
- package/lib/components/tooltip.d.ts.map +0 -1
- package/lib/components/tooltip.js +0 -112
- package/lib/components/tooltip.ts +0 -148
- package/lib/components/watcher.d.ts +0 -195
- package/lib/components/watcher.d.ts.map +0 -1
- package/lib/components/watcher.js +0 -241
- package/lib/components/watcher.ts +0 -261
- package/lib/components/write.d.ts +0 -107
- package/lib/components/write.d.ts.map +0 -1
- package/lib/components/write.js +0 -222
- package/lib/components/write.ts +0 -272
- package/lib/data/DataPipeline.d.ts +0 -113
- package/lib/data/DataPipeline.d.ts.map +0 -1
- package/lib/data/DataPipeline.js +0 -359
- package/lib/data/DataPipeline.ts +0 -452
- package/lib/facades/dataframe.jux +0 -0
- package/lib/globals.d.ts +0 -21
- package/lib/reactivity/state.d.ts +0 -36
- package/lib/reactivity/state.d.ts.map +0 -1
- package/lib/reactivity/state.js +0 -67
- package/lib/reactivity/state.ts +0 -78
- package/lib/storage/DataFrame.d.ts +0 -284
- package/lib/storage/DataFrame.d.ts.map +0 -1
- package/lib/storage/DataFrame.js +0 -1022
- package/lib/storage/DataFrame.ts +0 -1195
- package/lib/storage/DataFrameSource.d.ts +0 -158
- package/lib/storage/DataFrameSource.d.ts.map +0 -1
- package/lib/storage/DataFrameSource.js +0 -409
- package/lib/storage/DataFrameSource.ts +0 -556
- package/lib/storage/FileStorage.d.ts +0 -53
- package/lib/storage/FileStorage.d.ts.map +0 -1
- package/lib/storage/FileStorage.js +0 -80
- package/lib/storage/FileStorage.ts +0 -95
- package/lib/storage/IndexedDBDriver.d.ts +0 -75
- package/lib/storage/IndexedDBDriver.d.ts.map +0 -1
- package/lib/storage/IndexedDBDriver.js +0 -177
- package/lib/storage/IndexedDBDriver.ts +0 -226
- package/lib/storage/TabularDriver.d.ts +0 -118
- package/lib/storage/TabularDriver.d.ts.map +0 -1
- package/lib/storage/TabularDriver.js +0 -731
- package/lib/storage/TabularDriver.ts +0 -874
- package/lib/utils/codeparser.d.ts +0 -29
- package/lib/utils/codeparser.d.ts.map +0 -1
- package/lib/utils/codeparser.js +0 -409
- package/lib/utils/fetch.d.ts +0 -176
- package/lib/utils/fetch.d.ts.map +0 -1
- package/lib/utils/fetch.js +0 -427
- package/lib/utils/formatId.d.ts +0 -16
- package/lib/utils/formatId.d.ts.map +0 -1
- package/lib/utils/formatId.js +0 -27
- package/lib/utils/path-resolver.js +0 -23
|
@@ -1,735 +0,0 @@
|
|
|
1
|
-
import { getOrCreateContainer } from '../helpers.js';
|
|
2
|
-
import { registry } from '../registry.js';
|
|
3
|
-
import { stateHistory } from '../history/StateHistory.js';
|
|
4
|
-
import { formatIdAsLabel } from '../../utils/formatId.js';
|
|
5
|
-
import { applyAnimations } from './Animations.js'; // ✅ Import Animations
|
|
6
|
-
/**
|
|
7
|
-
* Abstract base class for all JUX components
|
|
8
|
-
* Provides common storage, event routing, and lifecycle methods
|
|
9
|
-
*
|
|
10
|
-
* Children must provide:
|
|
11
|
-
* - TRIGGER_EVENTS constant (readonly string[])
|
|
12
|
-
* - CALLBACK_EVENTS constant (readonly string[])
|
|
13
|
-
* - render() implementation
|
|
14
|
-
*/
|
|
15
|
-
export class BaseComponent {
|
|
16
|
-
constructor(id, initialState) {
|
|
17
|
-
this.container = null;
|
|
18
|
-
// Event & sync storage (populated by bind() and sync())
|
|
19
|
-
this._bindings = [];
|
|
20
|
-
this._syncBindings = [];
|
|
21
|
-
this._triggerHandlers = new Map();
|
|
22
|
-
this._callbackHandlers = new Map();
|
|
23
|
-
this._isUpdatingSync = false;
|
|
24
|
-
// Form-specific protected properties (only used by form components)
|
|
25
|
-
this._inputElement = null;
|
|
26
|
-
this._labelElement = null;
|
|
27
|
-
this._errorElement = null;
|
|
28
|
-
this._hasBeenValidated = false;
|
|
29
|
-
this._id = id;
|
|
30
|
-
this.id = id;
|
|
31
|
-
const stateWithDefaults = {
|
|
32
|
-
visible: true,
|
|
33
|
-
disabled: false,
|
|
34
|
-
loading: false,
|
|
35
|
-
class: '',
|
|
36
|
-
style: '',
|
|
37
|
-
attributes: {},
|
|
38
|
-
...initialState
|
|
39
|
-
};
|
|
40
|
-
this.state = new Proxy(stateWithDefaults, {
|
|
41
|
-
set: (target, prop, value) => {
|
|
42
|
-
const key = prop;
|
|
43
|
-
const oldValue = target[key];
|
|
44
|
-
if (oldValue !== value) {
|
|
45
|
-
stateHistory.recordStateChange(this._id, prop, oldValue, value);
|
|
46
|
-
target[key] = value;
|
|
47
|
-
this.update(prop, value);
|
|
48
|
-
if (!this._isUpdatingSync) {
|
|
49
|
-
this._notifySyncedState(prop, value);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
// ✅ Apply ONLY animation extensions
|
|
56
|
-
applyAnimations(this);
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* REACTIVE UPDATE HOOK (PUBLIC, CONCRETE)
|
|
60
|
-
* Called automatically when this.state[prop] changes via Proxy.
|
|
61
|
-
* Default implementation handles base properties.
|
|
62
|
-
* Children can override to add component-specific logic.
|
|
63
|
-
*/
|
|
64
|
-
update(prop, value) {
|
|
65
|
-
if (!this.container)
|
|
66
|
-
return;
|
|
67
|
-
const el = this.container.querySelector(`#${this._id}`);
|
|
68
|
-
if (!el)
|
|
69
|
-
return;
|
|
70
|
-
// Handle base properties
|
|
71
|
-
switch (prop) {
|
|
72
|
-
case 'visible':
|
|
73
|
-
el.style.display = value ? '' : 'none';
|
|
74
|
-
break;
|
|
75
|
-
case 'class':
|
|
76
|
-
const baseClasses = el.className.split(' ').filter(c => c.startsWith('jux-'));
|
|
77
|
-
const userClasses = value.split(' ').filter((c) => c);
|
|
78
|
-
el.className = [...baseClasses, ...userClasses].join(' ');
|
|
79
|
-
break;
|
|
80
|
-
case 'style':
|
|
81
|
-
el.setAttribute('style', value);
|
|
82
|
-
break;
|
|
83
|
-
case 'disabled':
|
|
84
|
-
el.setAttribute('aria-disabled', String(value));
|
|
85
|
-
const inputs = el.querySelectorAll('input, button, select, textarea');
|
|
86
|
-
inputs.forEach(input => {
|
|
87
|
-
input.disabled = value;
|
|
88
|
-
});
|
|
89
|
-
break;
|
|
90
|
-
case 'loading':
|
|
91
|
-
el.classList.toggle('jux-loading', value);
|
|
92
|
-
el.setAttribute('aria-busy', String(value));
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* ✨ Notify external State<T> objects when component state changes
|
|
98
|
-
* Prevents infinite loops with guard flag
|
|
99
|
-
*/
|
|
100
|
-
_notifySyncedState(prop, value) {
|
|
101
|
-
const syncBinding = this._syncBindings.find(b => b.property === prop);
|
|
102
|
-
if (syncBinding) {
|
|
103
|
-
const { stateObj, toState } = syncBinding;
|
|
104
|
-
// Set guard flag to prevent recursion
|
|
105
|
-
this._isUpdatingSync = true;
|
|
106
|
-
try {
|
|
107
|
-
const transformedValue = toState ? toState(value) : value;
|
|
108
|
-
stateObj.set(transformedValue);
|
|
109
|
-
}
|
|
110
|
-
finally {
|
|
111
|
-
// Always clear guard flag
|
|
112
|
-
this._isUpdatingSync = false;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
117
|
-
* COMMON FLUENT API (Inherited by all components)
|
|
118
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
119
|
-
/**
|
|
120
|
-
* Set component style
|
|
121
|
-
*/
|
|
122
|
-
style(value) {
|
|
123
|
-
this.state.style = value; // ✅ Triggers update()
|
|
124
|
-
return this;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Set component class
|
|
128
|
-
*/
|
|
129
|
-
class(value) {
|
|
130
|
-
this.state.class = value; // ✅ Triggers update()
|
|
131
|
-
return this;
|
|
132
|
-
}
|
|
133
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
134
|
-
* CSS CLASS MANAGEMENT
|
|
135
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
136
|
-
/**
|
|
137
|
-
* Add a CSS class to the component
|
|
138
|
-
*/
|
|
139
|
-
addClass(value) {
|
|
140
|
-
const current = this.state.class || '';
|
|
141
|
-
const classes = current.split(' ').filter((c) => c && c !== value);
|
|
142
|
-
classes.push(value);
|
|
143
|
-
this.state.class = classes.join(' '); // ✅ Triggers update()
|
|
144
|
-
return this;
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Remove a CSS class from the component
|
|
148
|
-
*/
|
|
149
|
-
removeClass(value) {
|
|
150
|
-
const current = this.state.class || '';
|
|
151
|
-
const classes = current.split(' ').filter((c) => c && c !== value);
|
|
152
|
-
this.state.class = classes.join(' '); // ✅ Triggers update()
|
|
153
|
-
return this;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Toggle a CSS class on the component
|
|
157
|
-
*/
|
|
158
|
-
toggleClass(value) {
|
|
159
|
-
const current = this.state.class || '';
|
|
160
|
-
const hasClass = current.split(' ').includes(value);
|
|
161
|
-
return hasClass ? this.removeClass(value) : this.addClass(value);
|
|
162
|
-
}
|
|
163
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
164
|
-
* VISIBILITY CONTROL
|
|
165
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
166
|
-
/**
|
|
167
|
-
* Set component visibility
|
|
168
|
-
*/
|
|
169
|
-
visible(value) {
|
|
170
|
-
this.state.visible = value; // ✅ Triggers update()
|
|
171
|
-
return this;
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Show the component
|
|
175
|
-
*/
|
|
176
|
-
show() {
|
|
177
|
-
return this.visible(true);
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Hide the component
|
|
181
|
-
*/
|
|
182
|
-
hide() {
|
|
183
|
-
return this.visible(false);
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* Toggle component visibility
|
|
187
|
-
*/
|
|
188
|
-
toggleVisibility() {
|
|
189
|
-
const isVisible = this.state.visible ?? true;
|
|
190
|
-
return this.visible(!isVisible);
|
|
191
|
-
}
|
|
192
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
193
|
-
* ATTRIBUTE MANAGEMENT
|
|
194
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
195
|
-
/**
|
|
196
|
-
* Set a single HTML attribute
|
|
197
|
-
*/
|
|
198
|
-
attr(name, value) {
|
|
199
|
-
const attrs = this.state.attributes || {};
|
|
200
|
-
this.state.attributes = { ...attrs, [name]: value };
|
|
201
|
-
if (this.container)
|
|
202
|
-
this.container.setAttribute(name, value);
|
|
203
|
-
return this;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Set multiple HTML attributes
|
|
207
|
-
*/
|
|
208
|
-
attrs(attributes) {
|
|
209
|
-
Object.entries(attributes).forEach(([name, value]) => {
|
|
210
|
-
this.attr(name, value);
|
|
211
|
-
});
|
|
212
|
-
return this;
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Remove an HTML attribute
|
|
216
|
-
*/
|
|
217
|
-
removeAttr(name) {
|
|
218
|
-
const attrs = this.state.attributes || {};
|
|
219
|
-
delete attrs[name];
|
|
220
|
-
if (this.container)
|
|
221
|
-
this.container.removeAttribute(name);
|
|
222
|
-
return this;
|
|
223
|
-
}
|
|
224
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
225
|
-
* DISABLED STATE
|
|
226
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
227
|
-
/**
|
|
228
|
-
* Set disabled state for interactive elements
|
|
229
|
-
*/
|
|
230
|
-
disabled(value) {
|
|
231
|
-
this.state.disabled = value; // ✅ Triggers update()
|
|
232
|
-
return this;
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* Enable the component
|
|
236
|
-
*/
|
|
237
|
-
enable() {
|
|
238
|
-
return this.disabled(false);
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Disable the component
|
|
242
|
-
*/
|
|
243
|
-
disable() {
|
|
244
|
-
return this.disabled(true);
|
|
245
|
-
}
|
|
246
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
247
|
-
* LOADING STATE
|
|
248
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
249
|
-
/**
|
|
250
|
-
* Set loading state
|
|
251
|
-
*/
|
|
252
|
-
loading(value) {
|
|
253
|
-
this.state.loading = value; // ✅ Triggers update()
|
|
254
|
-
return this;
|
|
255
|
-
}
|
|
256
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
257
|
-
* FOCUS MANAGEMENT
|
|
258
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
259
|
-
/**
|
|
260
|
-
* Focus the first focusable element in the component
|
|
261
|
-
*/
|
|
262
|
-
focus() {
|
|
263
|
-
if (this.container) {
|
|
264
|
-
const focusable = this.container.querySelector('input, button, select, textarea, [tabindex]');
|
|
265
|
-
if (focusable)
|
|
266
|
-
focusable.focus();
|
|
267
|
-
}
|
|
268
|
-
return this;
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Blur the currently focused element in the component
|
|
272
|
-
*/
|
|
273
|
-
blur() {
|
|
274
|
-
if (this.container) {
|
|
275
|
-
const focused = this.container.querySelector(':focus');
|
|
276
|
-
if (focused)
|
|
277
|
-
focused.blur();
|
|
278
|
-
}
|
|
279
|
-
return this;
|
|
280
|
-
}
|
|
281
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
282
|
-
* DOM MANIPULATION
|
|
283
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
284
|
-
/**
|
|
285
|
-
* Remove the component from the DOM
|
|
286
|
-
*/
|
|
287
|
-
remove() {
|
|
288
|
-
if (this.container) {
|
|
289
|
-
this.container.remove();
|
|
290
|
-
this.container = null;
|
|
291
|
-
// ✅ Unregister when removed
|
|
292
|
-
registry.unregister(this._id);
|
|
293
|
-
}
|
|
294
|
-
return this;
|
|
295
|
-
}
|
|
296
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
297
|
-
* EVENT BINDING (Shared logic)
|
|
298
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
299
|
-
bind(event, handler) {
|
|
300
|
-
// ✅ Record bind event
|
|
301
|
-
stateHistory.recordEvent(this._id, 'bind', event, { handlerName: handler.name });
|
|
302
|
-
if (this._isTriggerEvent(event)) {
|
|
303
|
-
this._triggerHandlers.set(event, handler);
|
|
304
|
-
}
|
|
305
|
-
else if (this._isCallbackEvent(event)) {
|
|
306
|
-
this._callbackHandlers.set(event, handler);
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
this._bindings.push({ event, handler });
|
|
310
|
-
}
|
|
311
|
-
return this;
|
|
312
|
-
}
|
|
313
|
-
sync(property, stateObj, toStateOrTransform, toComponent) {
|
|
314
|
-
if (!stateObj || typeof stateObj.subscribe !== 'function') {
|
|
315
|
-
throw new Error(`${this.constructor.name}.sync: Expected a State object for property "${property}"`);
|
|
316
|
-
}
|
|
317
|
-
const actualToState = (toComponent !== undefined) ? toStateOrTransform : undefined;
|
|
318
|
-
const actualToComponent = (toComponent !== undefined) ? toComponent : toStateOrTransform;
|
|
319
|
-
// ✅ Record sync event
|
|
320
|
-
stateHistory.recordEvent(this._id, 'sync', property, {
|
|
321
|
-
hasToState: !!actualToState,
|
|
322
|
-
hasToComponent: !!actualToComponent
|
|
323
|
-
});
|
|
324
|
-
this._syncBindings.push({
|
|
325
|
-
property,
|
|
326
|
-
stateObj,
|
|
327
|
-
toState: actualToState,
|
|
328
|
-
toComponent: actualToComponent
|
|
329
|
-
});
|
|
330
|
-
return this;
|
|
331
|
-
}
|
|
332
|
-
_isTriggerEvent(event) {
|
|
333
|
-
return this.getTriggerEvents().includes(event);
|
|
334
|
-
}
|
|
335
|
-
_isCallbackEvent(event) {
|
|
336
|
-
return this.getCallbackEvents().includes(event);
|
|
337
|
-
}
|
|
338
|
-
_triggerCallback(eventName, ...args) {
|
|
339
|
-
// ✅ Record callback event
|
|
340
|
-
stateHistory.recordEvent(this._id, 'callback', eventName, { argsCount: args.length });
|
|
341
|
-
if (this._callbackHandlers.has(eventName)) {
|
|
342
|
-
const handler = this._callbackHandlers.get(eventName);
|
|
343
|
-
handler(...args);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
347
|
-
* FLUENT EVENT SHORTHANDS
|
|
348
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
349
|
-
/**
|
|
350
|
-
* Register a click event handler
|
|
351
|
-
*/
|
|
352
|
-
click(handler) {
|
|
353
|
-
return this.bind('click', handler);
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Register a double-click event handler
|
|
357
|
-
*/
|
|
358
|
-
dblclick(handler) {
|
|
359
|
-
return this.bind('dblclick', handler);
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* Register a change event handler
|
|
363
|
-
*/
|
|
364
|
-
change(handler) {
|
|
365
|
-
return this.bind('change', handler);
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* Register an input event handler
|
|
369
|
-
*/
|
|
370
|
-
input(handler) {
|
|
371
|
-
return this.bind('input', handler);
|
|
372
|
-
}
|
|
373
|
-
/**
|
|
374
|
-
* Register a mouseover event handler
|
|
375
|
-
*/
|
|
376
|
-
hover(handler) {
|
|
377
|
-
return this.bind('mouseover', handler);
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Register a mouseout event handler
|
|
381
|
-
*/
|
|
382
|
-
mouseout(handler) {
|
|
383
|
-
return this.bind('mouseout', handler);
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Register a mousedown event handler
|
|
387
|
-
*/
|
|
388
|
-
mousedown(handler) {
|
|
389
|
-
return this.bind('mousedown', handler);
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Register a mouseup event handler
|
|
393
|
-
*/
|
|
394
|
-
mouseup(handler) {
|
|
395
|
-
return this.bind('mouseup', handler);
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* Register a focus event handler (renamed to onFocus to avoid conflict with focus() method)
|
|
399
|
-
*/
|
|
400
|
-
onFocus(handler) {
|
|
401
|
-
return this.bind('focus', handler);
|
|
402
|
-
}
|
|
403
|
-
/**
|
|
404
|
-
* Register a blur event handler (renamed to onBlur to avoid conflict with blur() method)
|
|
405
|
-
*/
|
|
406
|
-
onBlur(handler) {
|
|
407
|
-
return this.bind('blur', handler);
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* Register a keydown event handler
|
|
411
|
-
*/
|
|
412
|
-
keydown(handler) {
|
|
413
|
-
return this.bind('keydown', handler);
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Register a keyup event handler
|
|
417
|
-
*/
|
|
418
|
-
keyup(handler) {
|
|
419
|
-
return this.bind('keyup', handler);
|
|
420
|
-
}
|
|
421
|
-
/**
|
|
422
|
-
* Register a keypress event handler
|
|
423
|
-
*/
|
|
424
|
-
keypress(handler) {
|
|
425
|
-
return this.bind('keypress', handler);
|
|
426
|
-
}
|
|
427
|
-
/**
|
|
428
|
-
* Register a submit event handler
|
|
429
|
-
*/
|
|
430
|
-
submit(handler) {
|
|
431
|
-
return this.bind('submit', handler);
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* Register a scroll event handler
|
|
435
|
-
*/
|
|
436
|
-
scroll(handler) {
|
|
437
|
-
return this.bind('scroll', handler);
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* Register a contextmenu (right-click) event handler
|
|
441
|
-
*/
|
|
442
|
-
contextmenu(handler) {
|
|
443
|
-
return this.bind('contextmenu', handler);
|
|
444
|
-
}
|
|
445
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
446
|
-
* TIME-TRAVEL DEBUGGING (PUBLIC API)
|
|
447
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
448
|
-
/**
|
|
449
|
-
* Roll back to previous state
|
|
450
|
-
*/
|
|
451
|
-
rollback() {
|
|
452
|
-
const snapshot = stateHistory.moveBack();
|
|
453
|
-
if (snapshot && snapshot.componentId === this._id) {
|
|
454
|
-
stateHistory.startReplay();
|
|
455
|
-
this.state[snapshot.property] = snapshot.oldValue;
|
|
456
|
-
stateHistory.endReplay();
|
|
457
|
-
}
|
|
458
|
-
return this;
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* Roll forward to next state
|
|
462
|
-
*/
|
|
463
|
-
rollforward() {
|
|
464
|
-
const snapshot = stateHistory.moveForward();
|
|
465
|
-
if (snapshot && snapshot.componentId === this._id) {
|
|
466
|
-
stateHistory.startReplay();
|
|
467
|
-
this.state[snapshot.property] = snapshot.newValue;
|
|
468
|
-
stateHistory.endReplay();
|
|
469
|
-
}
|
|
470
|
-
return this;
|
|
471
|
-
}
|
|
472
|
-
/**
|
|
473
|
-
* Get complete timeline of this component's changes
|
|
474
|
-
*/
|
|
475
|
-
timeline() {
|
|
476
|
-
const stateChanges = stateHistory.getComponentHistory(this._id);
|
|
477
|
-
const events = stateHistory.getComponentEvents(this._id);
|
|
478
|
-
return [...stateChanges, ...events].sort((a, b) => a.timestamp - b.timestamp);
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* Get only state changes for this component
|
|
482
|
-
*/
|
|
483
|
-
stateHistory() {
|
|
484
|
-
return stateHistory.getComponentHistory(this._id);
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Get only events for this component
|
|
488
|
-
*/
|
|
489
|
-
eventHistory() {
|
|
490
|
-
return stateHistory.getComponentEvents(this._id);
|
|
491
|
-
}
|
|
492
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
493
|
-
* COMMON RENDER HELPERS
|
|
494
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
495
|
-
_setupContainer(targetId) {
|
|
496
|
-
// Determine Target Container Type
|
|
497
|
-
let container;
|
|
498
|
-
if (targetId) {
|
|
499
|
-
if (typeof targetId === 'string') {
|
|
500
|
-
const id = targetId.startsWith('#') ? targetId.slice(1) : targetId;
|
|
501
|
-
const target = document.getElementById(id);
|
|
502
|
-
if (target) {
|
|
503
|
-
container = target;
|
|
504
|
-
}
|
|
505
|
-
else {
|
|
506
|
-
console.warn(`[Jux] Target "${targetId}" not found, creating it with graceful fallback`);
|
|
507
|
-
container = getOrCreateContainer(id);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
else if (targetId instanceof HTMLElement) {
|
|
511
|
-
container = targetId;
|
|
512
|
-
}
|
|
513
|
-
else if (targetId instanceof BaseComponent) {
|
|
514
|
-
container = getOrCreateContainer(targetId._id);
|
|
515
|
-
}
|
|
516
|
-
else {
|
|
517
|
-
throw new Error(`[Jux] Invalid targetId type: ${typeof targetId}`);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
else {
|
|
521
|
-
container = getOrCreateContainer(this._id);
|
|
522
|
-
}
|
|
523
|
-
this.container = container;
|
|
524
|
-
// ✅ Auto-register component when container is set up
|
|
525
|
-
registry.register(this);
|
|
526
|
-
return container;
|
|
527
|
-
}
|
|
528
|
-
_wireStandardEvents(element) {
|
|
529
|
-
this._bindings.forEach(({ event, handler }) => {
|
|
530
|
-
element.addEventListener(event, handler);
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
/**
|
|
534
|
-
* Wire sync subscriptions with guard flag
|
|
535
|
-
*/
|
|
536
|
-
_wireAllSyncs() {
|
|
537
|
-
this._syncBindings.forEach(({ property, stateObj, toComponent }) => {
|
|
538
|
-
const transform = toComponent || ((v) => v);
|
|
539
|
-
const method = this[property];
|
|
540
|
-
if (typeof method === 'function') {
|
|
541
|
-
// Set initial value
|
|
542
|
-
const initialValue = transform(stateObj.value);
|
|
543
|
-
this._isUpdatingSync = true;
|
|
544
|
-
method.call(this, initialValue);
|
|
545
|
-
this._isUpdatingSync = false;
|
|
546
|
-
// Subscribe to changes with guard
|
|
547
|
-
stateObj.subscribe((val) => {
|
|
548
|
-
if (this._isUpdatingSync)
|
|
549
|
-
return;
|
|
550
|
-
const transformed = transform(val);
|
|
551
|
-
this._isUpdatingSync = true;
|
|
552
|
-
method.call(this, transformed);
|
|
553
|
-
this._isUpdatingSync = false;
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
console.warn(`[Jux] ${this.constructor.name}.sync('${property}'): ` +
|
|
558
|
-
`No method .${property}() found. Property will not be synced.`);
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
563
|
-
* PROPS ACCESSOR - Read-only access to component state
|
|
564
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
565
|
-
/**
|
|
566
|
-
* ✅ Read-only accessor for component state (getter, not a method)
|
|
567
|
-
*
|
|
568
|
-
* Usage:
|
|
569
|
-
* const myState = component.props; // ✅ Correct
|
|
570
|
-
* const myState = component.props(); // ❌ Error: props is not a function
|
|
571
|
-
*/
|
|
572
|
-
get props() {
|
|
573
|
-
return this.state;
|
|
574
|
-
}
|
|
575
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
576
|
-
* FORM INPUT API (Optional - only used by form components)
|
|
577
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
578
|
-
/**
|
|
579
|
-
* Set label for form inputs
|
|
580
|
-
*/
|
|
581
|
-
label(value) {
|
|
582
|
-
this.state.label = value;
|
|
583
|
-
if (this._labelElement) {
|
|
584
|
-
const requiredSpan = this._labelElement.querySelector('.jux-input-required');
|
|
585
|
-
this._labelElement.textContent = value;
|
|
586
|
-
if (requiredSpan)
|
|
587
|
-
this._labelElement.appendChild(requiredSpan);
|
|
588
|
-
}
|
|
589
|
-
return this;
|
|
590
|
-
}
|
|
591
|
-
/**
|
|
592
|
-
* Set required state for form inputs
|
|
593
|
-
*/
|
|
594
|
-
required(value) {
|
|
595
|
-
this.state.required = value;
|
|
596
|
-
return this;
|
|
597
|
-
}
|
|
598
|
-
/**
|
|
599
|
-
* Set name attribute for form inputs
|
|
600
|
-
*/
|
|
601
|
-
name(value) {
|
|
602
|
-
this.state.name = value;
|
|
603
|
-
return this;
|
|
604
|
-
}
|
|
605
|
-
/**
|
|
606
|
-
* Set custom validation handler
|
|
607
|
-
*/
|
|
608
|
-
onValidate(handler) {
|
|
609
|
-
this._onValidate = handler;
|
|
610
|
-
return this;
|
|
611
|
-
}
|
|
612
|
-
/**
|
|
613
|
-
* Validate form input (override in form components)
|
|
614
|
-
*/
|
|
615
|
-
validate() {
|
|
616
|
-
console.warn(`${this.constructor.name}.validate() not implemented`);
|
|
617
|
-
return true;
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Check if form input is valid (override in form components)
|
|
621
|
-
*/
|
|
622
|
-
isValid() {
|
|
623
|
-
console.warn(`${this.constructor.name}.isValid() not implemented`);
|
|
624
|
-
return true;
|
|
625
|
-
}
|
|
626
|
-
/**
|
|
627
|
-
* Get current value (override in form components)
|
|
628
|
-
*/
|
|
629
|
-
getValue() {
|
|
630
|
-
console.warn(`${this.constructor.name}.getValue() not implemented`);
|
|
631
|
-
return undefined;
|
|
632
|
-
}
|
|
633
|
-
/**
|
|
634
|
-
* Set current value (override in form components)
|
|
635
|
-
*/
|
|
636
|
-
setValue(value) {
|
|
637
|
-
console.warn(`${this.constructor.name}.setValue() not implemented`);
|
|
638
|
-
return this;
|
|
639
|
-
}
|
|
640
|
-
/* ═════════════════════════════════════════════════════════════════
|
|
641
|
-
* FORM VALIDATION HELPERS (Protected - for form components)
|
|
642
|
-
* ═════════════════════════════════════════════════════════════════ */
|
|
643
|
-
_showError(message) {
|
|
644
|
-
if (this._errorElement) {
|
|
645
|
-
this._errorElement.textContent = message;
|
|
646
|
-
this._errorElement.style.display = 'block';
|
|
647
|
-
}
|
|
648
|
-
if (this._inputElement) {
|
|
649
|
-
this._inputElement.classList.add('jux-input-invalid');
|
|
650
|
-
}
|
|
651
|
-
this.state.errorMessage = message;
|
|
652
|
-
}
|
|
653
|
-
_clearError() {
|
|
654
|
-
if (this._errorElement) {
|
|
655
|
-
this._errorElement.textContent = '';
|
|
656
|
-
this._errorElement.style.display = 'none';
|
|
657
|
-
}
|
|
658
|
-
if (this._inputElement) {
|
|
659
|
-
this._inputElement.classList.remove('jux-input-invalid');
|
|
660
|
-
}
|
|
661
|
-
this.state.errorMessage = undefined;
|
|
662
|
-
}
|
|
663
|
-
/**
|
|
664
|
-
* Build label element for form inputs
|
|
665
|
-
*/
|
|
666
|
-
_renderLabel() {
|
|
667
|
-
const label = this.state.label || formatIdAsLabel(this._id); // ✅ Auto-generate
|
|
668
|
-
const required = this.state.required || false;
|
|
669
|
-
const labelEl = document.createElement('label');
|
|
670
|
-
labelEl.className = 'jux-input-label';
|
|
671
|
-
labelEl.htmlFor = `${this._id}-input`;
|
|
672
|
-
labelEl.textContent = label;
|
|
673
|
-
if (required) {
|
|
674
|
-
const requiredSpan = document.createElement('span');
|
|
675
|
-
requiredSpan.className = 'jux-input-required';
|
|
676
|
-
requiredSpan.textContent = ' *';
|
|
677
|
-
labelEl.appendChild(requiredSpan);
|
|
678
|
-
}
|
|
679
|
-
this._labelElement = labelEl;
|
|
680
|
-
return labelEl;
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* Build error element for form inputs
|
|
684
|
-
*/
|
|
685
|
-
_renderError() {
|
|
686
|
-
const errorEl = document.createElement('div');
|
|
687
|
-
errorEl.className = 'jux-input-error';
|
|
688
|
-
errorEl.id = `${this._id}-error`;
|
|
689
|
-
errorEl.style.display = 'none';
|
|
690
|
-
this._errorElement = errorEl;
|
|
691
|
-
return errorEl;
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Wire up two-way sync for form inputs
|
|
695
|
-
*/
|
|
696
|
-
_wireFormSync(inputElement, eventName = 'input') {
|
|
697
|
-
const valueSync = this._syncBindings.find(b => b.property === 'value');
|
|
698
|
-
if (valueSync) {
|
|
699
|
-
const { stateObj, toState, toComponent } = valueSync;
|
|
700
|
-
const transformToState = toState || ((v) => v);
|
|
701
|
-
const transformToComponent = toComponent || ((v) => v);
|
|
702
|
-
let isUpdating = false;
|
|
703
|
-
// State → Component
|
|
704
|
-
stateObj.subscribe((val) => {
|
|
705
|
-
if (isUpdating)
|
|
706
|
-
return;
|
|
707
|
-
const transformed = transformToComponent(val);
|
|
708
|
-
this.setValue(transformed);
|
|
709
|
-
});
|
|
710
|
-
// Component → State
|
|
711
|
-
inputElement.addEventListener(eventName, () => {
|
|
712
|
-
if (isUpdating)
|
|
713
|
-
return;
|
|
714
|
-
isUpdating = true;
|
|
715
|
-
const value = this.getValue();
|
|
716
|
-
const transformed = transformToState(value);
|
|
717
|
-
this._clearError();
|
|
718
|
-
stateObj.set(transformed);
|
|
719
|
-
setTimeout(() => { isUpdating = false; }, 0);
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
else {
|
|
723
|
-
// Default behavior without sync
|
|
724
|
-
inputElement.addEventListener(eventName, () => {
|
|
725
|
-
this._clearError();
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
// Only validate on blur IF the field has been validated before
|
|
729
|
-
inputElement.addEventListener('blur', () => {
|
|
730
|
-
if (this._hasBeenValidated) {
|
|
731
|
-
this.validate();
|
|
732
|
-
}
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
}
|