@fluffjs/fluff 0.0.7 → 0.1.0
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/bundle.min.js +1 -0
- package/decorators/Component.d.ts +0 -1
- package/decorators/Component.js +0 -3
- package/decorators/HostBinding.d.ts +4 -0
- package/decorators/HostBinding.js +3 -10
- package/decorators/HostListener.d.ts +4 -0
- package/decorators/HostListener.js +3 -10
- package/decorators/Input.d.ts +1 -1
- package/decorators/Input.js +2 -16
- package/decorators/InputOutputHelper.d.ts +1 -0
- package/decorators/InputOutputHelper.js +18 -0
- package/decorators/MetadataArrayHelper.d.ts +4 -0
- package/decorators/MetadataArrayHelper.js +14 -0
- package/decorators/Output.d.ts +1 -1
- package/decorators/Output.js +2 -16
- package/decorators/ViewChild.d.ts +4 -0
- package/decorators/ViewChild.js +3 -10
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/interfaces/BreakMarkerConfig.d.ts +3 -0
- package/interfaces/BreakMarkerConfig.js +1 -0
- package/interfaces/FluffHostElement.d.ts +5 -0
- package/interfaces/FluffHostElement.js +1 -0
- package/interfaces/ForMarkerConfig.d.ts +8 -0
- package/interfaces/ForMarkerConfig.js +1 -0
- package/interfaces/IfMarkerConfig.d.ts +7 -0
- package/interfaces/IfMarkerConfig.js +1 -0
- package/interfaces/MarkerConfig.d.ts +6 -0
- package/interfaces/MarkerConfig.js +1 -0
- package/interfaces/PropertyChain.d.ts +1 -0
- package/interfaces/PropertyChain.js +1 -0
- package/interfaces/RenderContext.d.ts +3 -0
- package/interfaces/RenderContext.js +1 -0
- package/interfaces/SwitchMarkerConfig.d.ts +10 -0
- package/interfaces/SwitchMarkerConfig.js +1 -0
- package/interfaces/TextMarkerConfig.d.ts +9 -0
- package/interfaces/TextMarkerConfig.js +1 -0
- package/package.json +1 -1
- package/runtime/BreakController.d.ts +8 -0
- package/runtime/BreakController.js +13 -0
- package/runtime/FluffBase.d.ts +40 -0
- package/runtime/FluffBase.js +271 -0
- package/runtime/FluffElement.d.ts +5 -39
- package/runtime/FluffElement.js +3 -272
- package/runtime/FluffElementImpl.d.ts +49 -0
- package/runtime/FluffElementImpl.js +315 -0
- package/runtime/FluffMarkers.d.ts +8 -0
- package/runtime/FluffMarkers.js +8 -0
- package/runtime/ForController.d.ts +10 -0
- package/runtime/ForController.js +43 -0
- package/runtime/IfController.d.ts +10 -0
- package/runtime/IfController.js +46 -0
- package/runtime/MarkerConfigGuards.d.ts +15 -0
- package/runtime/MarkerConfigGuards.js +20 -0
- package/runtime/MarkerController.d.ts +46 -0
- package/runtime/MarkerController.js +235 -0
- package/runtime/MarkerManager.d.ts +16 -0
- package/runtime/MarkerManager.js +136 -0
- package/runtime/MarkerManagerInterface.d.ts +4 -0
- package/runtime/MarkerManagerInterface.js +1 -0
- package/runtime/ScopeRegistry.d.ts +9 -0
- package/runtime/ScopeRegistry.js +13 -0
- package/runtime/SwitchController.d.ts +9 -0
- package/runtime/SwitchController.js +42 -0
- package/runtime/TextController.d.ts +10 -0
- package/runtime/TextController.js +53 -0
- package/runtime/tests/DirectOutputChild.d.ts +10 -0
- package/runtime/tests/DirectOutputChild.js +14 -0
- package/runtime/tests/DirectOutputParent.d.ts +9 -0
- package/runtime/tests/DirectOutputParent.js +17 -0
- package/runtime/tests/TaskStats.d.ts +9 -0
- package/runtime/tests/TaskStats.js +1 -0
- package/runtime/tests/TestChildTasksListComponent.d.ts +8 -0
- package/runtime/tests/TestChildTasksListComponent.js +28 -0
- package/runtime/tests/TestForChildComponent.d.ts +6 -0
- package/runtime/tests/TestForChildComponent.js +10 -0
- package/runtime/tests/TestForComponent.d.ts +6 -0
- package/runtime/tests/TestForComponent.js +21 -0
- package/runtime/tests/TestForReinsertBindsInputParentComponent.d.ts +12 -0
- package/runtime/tests/TestForReinsertBindsInputParentComponent.js +57 -0
- package/runtime/tests/TestForTextMarkerCollisionNoTrackParentComponent.d.ts +8 -0
- package/runtime/tests/TestForTextMarkerCollisionNoTrackParentComponent.js +29 -0
- package/runtime/tests/TestForTextMarkerCollisionParentComponent.d.ts +9 -0
- package/runtime/tests/TestForTextMarkerCollisionParentComponent.js +46 -0
- package/runtime/tests/TestForUnsubscribeNestedParentComponent.d.ts +8 -0
- package/runtime/tests/TestForUnsubscribeNestedParentComponent.js +35 -0
- package/runtime/tests/TestGetterReactivityComponent.d.ts +9 -0
- package/runtime/tests/TestGetterReactivityComponent.js +27 -0
- package/runtime/tests/TestIfReinsertBindsInputChildComponent.d.ts +9 -0
- package/runtime/tests/TestIfReinsertBindsInputChildComponent.js +39 -0
- package/runtime/tests/TestIfReinsertBindsInputParentComponent.d.ts +12 -0
- package/runtime/tests/TestIfReinsertBindsInputParentComponent.js +54 -0
- package/runtime/tests/TestIfUnsubscribeNestedParentComponent.d.ts +8 -0
- package/runtime/tests/TestIfUnsubscribeNestedParentComponent.js +32 -0
- package/runtime/tests/TestLateDefineForChildComponent.d.ts +6 -0
- package/runtime/tests/TestLateDefineForChildComponent.js +10 -0
- package/runtime/tests/TestLateDefineForComponent.d.ts +9 -0
- package/runtime/tests/TestLateDefineForComponent.js +25 -0
- package/runtime/tests/TestNullInputTextComponent.d.ts +14 -0
- package/runtime/tests/TestNullInputTextComponent.js +37 -0
- package/runtime/tests/TestOutputBindingChildComponent.d.ts +10 -0
- package/runtime/tests/TestOutputBindingChildComponent.js +20 -0
- package/runtime/tests/TestOutputBindingParentComponent.d.ts +13 -0
- package/runtime/tests/TestOutputBindingParentComponent.js +38 -0
- package/runtime/tests/TestParentBindsTasksComponent.d.ts +7 -0
- package/runtime/tests/TestParentBindsTasksComponent.js +22 -0
- package/runtime/tests/TestSwitchReinsertBindsInputChildComponent.d.ts +9 -0
- package/runtime/tests/TestSwitchReinsertBindsInputChildComponent.js +39 -0
- package/runtime/tests/TestSwitchReinsertBindsInputParentComponent.d.ts +12 -0
- package/runtime/tests/TestSwitchReinsertBindsInputParentComponent.js +65 -0
- package/runtime/tests/TestSwitchUnsubscribeNestedParentComponent.d.ts +8 -0
- package/runtime/tests/TestSwitchUnsubscribeNestedParentComponent.js +42 -0
- package/runtime/tests/TestTemplateNestedMarkersComponent.d.ts +11 -0
- package/runtime/tests/TestTemplateNestedMarkersComponent.js +39 -0
- package/runtime/tests/TestUnsubscribeNestedChildComponent.d.ts +11 -0
- package/runtime/tests/TestUnsubscribeNestedChildComponent.js +43 -0
- package/runtime/tests/TestUnsubscribeNestedGrandchildComponent.d.ts +9 -0
- package/runtime/tests/TestUnsubscribeNestedGrandchildComponent.js +39 -0
- package/runtime/tests/TestUnsubscribeNestedParentBaseComponent.d.ts +8 -0
- package/runtime/tests/TestUnsubscribeNestedParentBaseComponent.js +33 -0
- package/utils/DomUtils.d.ts +3 -0
- package/utils/DomUtils.js +6 -0
- package/utils/Property.d.ts +8 -3
- package/utils/Property.js +26 -20
- package/utils/Publisher.d.ts +2 -1
- package/utils/Publisher.js +16 -1
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { DomUtils } from '../utils/DomUtils.js';
|
|
2
|
+
import { Property } from '../utils/Property.js';
|
|
3
|
+
import { Publisher } from '../utils/Publisher.js';
|
|
4
|
+
export class FluffBase extends HTMLElement {
|
|
5
|
+
static __e = [];
|
|
6
|
+
static __h = [];
|
|
7
|
+
static __bindings = {};
|
|
8
|
+
__parentScope;
|
|
9
|
+
__loopContext = {};
|
|
10
|
+
__baseSubscriptions = [];
|
|
11
|
+
__getScope() {
|
|
12
|
+
return {
|
|
13
|
+
host: this,
|
|
14
|
+
locals: this.__loopContext,
|
|
15
|
+
parent: this.__parentScope
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
__processBindingsOnElement(el, scope, subscriptions) {
|
|
19
|
+
const lid = el.getAttribute('data-lid');
|
|
20
|
+
if (!lid)
|
|
21
|
+
return;
|
|
22
|
+
const bindings = this.__getBindingsForLid(lid);
|
|
23
|
+
if (!bindings || bindings.length === 0)
|
|
24
|
+
return;
|
|
25
|
+
for (const binding of bindings) {
|
|
26
|
+
this.__applyBindingWithScope(el, binding, scope, subscriptions);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
__getBindingsForLid(lid) {
|
|
30
|
+
const ctor = this.constructor;
|
|
31
|
+
if (typeof ctor === 'function') {
|
|
32
|
+
const bindings = Reflect.get(ctor, '__bindings');
|
|
33
|
+
if (this.__isBindingsMap(bindings)) {
|
|
34
|
+
return bindings[lid];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
__isBindingsMap(value) {
|
|
40
|
+
return !(!value || typeof value !== 'object');
|
|
41
|
+
}
|
|
42
|
+
__applyBindingWithScope(el, binding, scope, subscriptions) {
|
|
43
|
+
switch (binding.b) {
|
|
44
|
+
case 'property':
|
|
45
|
+
this.__applyPropertyBindingWithScope(el, binding, scope, subscriptions);
|
|
46
|
+
break;
|
|
47
|
+
case 'event':
|
|
48
|
+
this.__applyEventBindingWithScope(el, binding, scope);
|
|
49
|
+
break;
|
|
50
|
+
case 'two-way':
|
|
51
|
+
this.__applyTwoWayBindingWithScope(el, binding, scope, subscriptions);
|
|
52
|
+
break;
|
|
53
|
+
case 'class':
|
|
54
|
+
this.__applyClassBindingWithScope(el, binding, scope, subscriptions);
|
|
55
|
+
break;
|
|
56
|
+
case 'style':
|
|
57
|
+
this.__applyStyleBindingWithScope(el, binding, scope, subscriptions);
|
|
58
|
+
break;
|
|
59
|
+
case 'ref':
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
__getCompiledExprFn(exprId) {
|
|
64
|
+
const fn = FluffBase.__e[exprId];
|
|
65
|
+
if (typeof fn !== 'function') {
|
|
66
|
+
throw new Error(`Missing compiled expression function for exprId ${exprId}`);
|
|
67
|
+
}
|
|
68
|
+
return fn;
|
|
69
|
+
}
|
|
70
|
+
__getCompiledHandlerFn(handlerId) {
|
|
71
|
+
const fn = FluffBase.__h[handlerId];
|
|
72
|
+
if (typeof fn !== 'function') {
|
|
73
|
+
throw new Error(`Missing compiled handler function for handlerId ${handlerId}`);
|
|
74
|
+
}
|
|
75
|
+
return fn;
|
|
76
|
+
}
|
|
77
|
+
__subscribeToExpressionInScope(deps, scope, callback, subscriptions) {
|
|
78
|
+
if (!deps)
|
|
79
|
+
return;
|
|
80
|
+
for (const dep of deps) {
|
|
81
|
+
const propName = Array.isArray(dep) ? dep[0] : dep;
|
|
82
|
+
const reactiveProp = this.__getReactivePropFromScope(propName, scope);
|
|
83
|
+
if (reactiveProp) {
|
|
84
|
+
const sub = reactiveProp.onChange.subscribe(callback);
|
|
85
|
+
if (subscriptions) {
|
|
86
|
+
subscriptions.push(sub);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
this.__baseSubscriptions.push(sub);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
__getReactivePropFromScope(propName, scope) {
|
|
95
|
+
if (propName in scope.locals) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const key = `__${propName}`;
|
|
99
|
+
if (key in scope.host) {
|
|
100
|
+
const candidate = Reflect.get(scope.host, key);
|
|
101
|
+
if (candidate instanceof Property) {
|
|
102
|
+
return candidate;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (scope.parent) {
|
|
106
|
+
return this.__getReactivePropFromScope(propName, scope.parent);
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
__setChildProperty(el, propName, value) {
|
|
111
|
+
const prop = Reflect.get(el, propName);
|
|
112
|
+
if (prop instanceof Property) {
|
|
113
|
+
prop.setValue(value, true);
|
|
114
|
+
}
|
|
115
|
+
else if (DomUtils.isCustomElement(el)) {
|
|
116
|
+
this.__whenDefined(el.tagName.toLowerCase(), () => {
|
|
117
|
+
if (el instanceof FluffBase) {
|
|
118
|
+
Reflect.set(el, propName, value);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
else if (propName in el) {
|
|
123
|
+
Reflect.set(el, propName, value);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
el.setAttribute(propName, String(value));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
__applyPropertyBindingWithScope(el, binding, scope, subscriptions) {
|
|
130
|
+
const tagName = el.tagName.toLowerCase();
|
|
131
|
+
const isCustomElement = customElements.get(tagName) !== undefined;
|
|
132
|
+
const update = () => {
|
|
133
|
+
try {
|
|
134
|
+
if (typeof binding.e !== 'number') {
|
|
135
|
+
throw new Error(`Binding for ${binding.n} is missing exprId`);
|
|
136
|
+
}
|
|
137
|
+
const fn = this.__getCompiledExprFn(binding.e);
|
|
138
|
+
const value = fn(this, scope.locals);
|
|
139
|
+
this.__setChildProperty(el, binding.n, value);
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
console.error('Property binding error:', e);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
this.__subscribeToExpressionInScope(binding.d, scope, update, subscriptions);
|
|
146
|
+
if (binding.s) {
|
|
147
|
+
this.__subscribeToExpressionInScope([binding.s], scope, update, subscriptions);
|
|
148
|
+
}
|
|
149
|
+
if (isCustomElement) {
|
|
150
|
+
if (el instanceof FluffBase) {
|
|
151
|
+
update();
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
this.__whenDefined(tagName, () => {
|
|
155
|
+
if (el instanceof FluffBase) {
|
|
156
|
+
update();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
update();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
__applyEventBindingWithScope(el, binding, scope) {
|
|
166
|
+
if (typeof binding.h !== 'number') {
|
|
167
|
+
throw new Error(`Event binding for ${binding.n} is missing handlerId`);
|
|
168
|
+
}
|
|
169
|
+
const handlerFn = this.__getCompiledHandlerFn(binding.h);
|
|
170
|
+
if (el.hasAttribute('x-fluff-component')) {
|
|
171
|
+
this.__applyOutputBinding(el, binding.n, handlerFn, scope);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
el.addEventListener(binding.n, (event) => {
|
|
175
|
+
try {
|
|
176
|
+
handlerFn(this, scope.locals, event);
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
console.error('Event binding error:', e);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
__applyOutputBinding(el, outputName, handlerFn, scope) {
|
|
185
|
+
const trySubscribe = () => {
|
|
186
|
+
const publisher = Reflect.get(el, outputName);
|
|
187
|
+
if (publisher instanceof Publisher) {
|
|
188
|
+
const sub = publisher.subscribe((value) => {
|
|
189
|
+
try {
|
|
190
|
+
handlerFn(this, scope.locals, value);
|
|
191
|
+
}
|
|
192
|
+
catch (e) {
|
|
193
|
+
console.error('Output binding error:', e);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
this.__baseSubscriptions.push(sub);
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
};
|
|
201
|
+
if (trySubscribe()) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
this.__whenDefined(el.tagName.toLowerCase(), () => {
|
|
205
|
+
trySubscribe();
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
__whenDefined(tagName, callback) {
|
|
209
|
+
customElements.whenDefined(tagName)
|
|
210
|
+
.then(callback)
|
|
211
|
+
.catch(console.error);
|
|
212
|
+
}
|
|
213
|
+
__applyTwoWayBindingWithScope(el, binding, scope, subscriptions) {
|
|
214
|
+
this.__applyPropertyBindingWithScope(el, binding, scope, subscriptions);
|
|
215
|
+
if (typeof binding.t !== 'string' || binding.t.length === 0) {
|
|
216
|
+
throw new Error(`Two-way binding for ${binding.n} is missing targetProp`);
|
|
217
|
+
}
|
|
218
|
+
const reactiveProp = this.__getReactivePropFromScope(binding.t, scope);
|
|
219
|
+
const childPropCandidate = Reflect.get(el, binding.n);
|
|
220
|
+
if (reactiveProp && childPropCandidate instanceof Property) {
|
|
221
|
+
const sub = childPropCandidate.onChange.subscribe((val) => {
|
|
222
|
+
reactiveProp.setValue(val, true);
|
|
223
|
+
});
|
|
224
|
+
if (subscriptions) {
|
|
225
|
+
subscriptions.push(sub);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
this.__baseSubscriptions.push(sub);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
__applyClassBindingWithScope(el, binding, scope, subscriptions) {
|
|
233
|
+
const update = () => {
|
|
234
|
+
try {
|
|
235
|
+
if (typeof binding.e !== 'number') {
|
|
236
|
+
throw new Error(`Class binding for ${binding.n} is missing exprId`);
|
|
237
|
+
}
|
|
238
|
+
const fn = this.__getCompiledExprFn(binding.e);
|
|
239
|
+
const value = fn(this, scope.locals);
|
|
240
|
+
if (value) {
|
|
241
|
+
el.classList.add(binding.n);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
el.classList.remove(binding.n);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch (e) {
|
|
248
|
+
console.error('Class binding error:', e);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
this.__subscribeToExpressionInScope(binding.d, scope, update, subscriptions);
|
|
252
|
+
update();
|
|
253
|
+
}
|
|
254
|
+
__applyStyleBindingWithScope(el, binding, scope, subscriptions) {
|
|
255
|
+
const update = () => {
|
|
256
|
+
try {
|
|
257
|
+
if (typeof binding.e !== 'number') {
|
|
258
|
+
throw new Error(`Style binding for ${binding.n} is missing exprId`);
|
|
259
|
+
}
|
|
260
|
+
const fn = this.__getCompiledExprFn(binding.e);
|
|
261
|
+
const value = fn(this, scope.locals);
|
|
262
|
+
el.style.setProperty(binding.n, String(value));
|
|
263
|
+
}
|
|
264
|
+
catch (e) {
|
|
265
|
+
console.error('Style binding error:', e);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
this.__subscribeToExpressionInScope(binding.d, scope, update, subscriptions);
|
|
269
|
+
update();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -1,39 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
private _subscriptions;
|
|
7
|
-
protected readonly _shadowRoot: ShadowRoot;
|
|
8
|
-
private _initialized;
|
|
9
|
-
constructor();
|
|
10
|
-
connectedCallback(): void;
|
|
11
|
-
disconnectedCallback(): void;
|
|
12
|
-
protected abstract __render(): void;
|
|
13
|
-
protected abstract __setupBindings(): void;
|
|
14
|
-
protected __addSubscription(sub: Subscription): void;
|
|
15
|
-
protected __pipe(name: string, value: unknown, ...args: unknown[]): unknown;
|
|
16
|
-
$watch: (_properties: string[], callback: () => void) => Subscription;
|
|
17
|
-
protected __getShadowRoot(): ShadowRoot;
|
|
18
|
-
protected __getElement(id: string): Element | null;
|
|
19
|
-
private isHTMLElement;
|
|
20
|
-
private isPublisher;
|
|
21
|
-
private isProperty;
|
|
22
|
-
protected __setText(id: string, text: string): void;
|
|
23
|
-
protected __bindText(id: string, getter: () => string): void;
|
|
24
|
-
protected __setProperty(id: string, prop: string, value: unknown): void;
|
|
25
|
-
protected __addClass(id: string, className: string): void;
|
|
26
|
-
protected __removeClass(id: string, className: string): void;
|
|
27
|
-
protected __bindEvent(id: string, event: string, handler: (e: Event) => void): void;
|
|
28
|
-
protected __bindPropertyChange<T>(prop: Property<T>, callback: (val: T) => void): void;
|
|
29
|
-
protected __connectProperties<T>(source: Property<T>, target: Property<T>): void;
|
|
30
|
-
protected __connectOutput<T>(source: Publisher<T>, handler: (val: T) => void): void;
|
|
31
|
-
protected __bindOutput(id: string, outputName: string, handler: (val: Event) => void): void;
|
|
32
|
-
protected __setChildProperty(el: Element, propName: string, value: unknown): void;
|
|
33
|
-
protected __bindToChild(id: string, propName: string, value: unknown): void;
|
|
34
|
-
protected __setChildPropertyDeferred(el: Element, propName: string, value: unknown): void;
|
|
35
|
-
protected __bindOutputOnElement(el: Element, outputName: string, handler: (val: Event) => void): void;
|
|
36
|
-
protected __wireEvents(container: Element, attrPrefix: string): void;
|
|
37
|
-
private __buildRefLookups;
|
|
38
|
-
private __getReactiveProp;
|
|
39
|
-
}
|
|
1
|
+
export { FluffBase } from './FluffBase.js';
|
|
2
|
+
export type { BindingInfo } from './FluffBase.js';
|
|
3
|
+
export { FluffElement } from './FluffElementImpl.js';
|
|
4
|
+
export { getScope, registerScope, unregisterScope } from './ScopeRegistry.js';
|
|
5
|
+
export type { Scope } from './ScopeRegistry.js';
|
package/runtime/FluffElement.js
CHANGED
|
@@ -1,272 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
_shadowRoot;
|
|
5
|
-
_initialized = false;
|
|
6
|
-
constructor() {
|
|
7
|
-
super();
|
|
8
|
-
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
|
9
|
-
}
|
|
10
|
-
connectedCallback() {
|
|
11
|
-
if (!this._initialized) {
|
|
12
|
-
this.__render();
|
|
13
|
-
this.__setupBindings();
|
|
14
|
-
this._initialized = true;
|
|
15
|
-
if ('onInit' in this && typeof this.onInit === 'function') {
|
|
16
|
-
this.onInit();
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
disconnectedCallback() {
|
|
21
|
-
if ('onDestroy' in this && typeof this.onDestroy === 'function') {
|
|
22
|
-
this.onDestroy();
|
|
23
|
-
}
|
|
24
|
-
for (const sub of this._subscriptions) {
|
|
25
|
-
sub.unsubscribe();
|
|
26
|
-
}
|
|
27
|
-
this._subscriptions = [];
|
|
28
|
-
}
|
|
29
|
-
__addSubscription(sub) {
|
|
30
|
-
this._subscriptions.push(sub);
|
|
31
|
-
}
|
|
32
|
-
__pipe(name, value, ...args) {
|
|
33
|
-
const pipe = this.__pipes[name];
|
|
34
|
-
if (!pipe) {
|
|
35
|
-
console.warn(`Pipe "${name}" not found`);
|
|
36
|
-
return value;
|
|
37
|
-
}
|
|
38
|
-
return pipe(value, ...args);
|
|
39
|
-
}
|
|
40
|
-
$watch = (_properties, callback) => {
|
|
41
|
-
callback();
|
|
42
|
-
return {
|
|
43
|
-
unsubscribe: () => {
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
};
|
|
47
|
-
__getShadowRoot() {
|
|
48
|
-
return this._shadowRoot;
|
|
49
|
-
}
|
|
50
|
-
__getElement(id) {
|
|
51
|
-
return this._shadowRoot.querySelector(`[data-lid="${id}"]`);
|
|
52
|
-
}
|
|
53
|
-
isHTMLElement(el) {
|
|
54
|
-
return el !== null;
|
|
55
|
-
}
|
|
56
|
-
isPublisher(value) {
|
|
57
|
-
return typeof value === 'object' && value !== null && 'subscribe' in value && typeof value.subscribe === 'function';
|
|
58
|
-
}
|
|
59
|
-
isProperty(value) {
|
|
60
|
-
return typeof value === 'object' && value !== null && 'onChange' in value && 'setValue' in value;
|
|
61
|
-
}
|
|
62
|
-
__setText(id, text) {
|
|
63
|
-
const el = this.__getElement(id);
|
|
64
|
-
if (el)
|
|
65
|
-
el.textContent = text;
|
|
66
|
-
}
|
|
67
|
-
__bindText(id, getter) {
|
|
68
|
-
const el = this.__getElement(id);
|
|
69
|
-
if (!el)
|
|
70
|
-
return;
|
|
71
|
-
const expr = el.getAttribute('data-text-bind') ?? '';
|
|
72
|
-
const propMatch = /this\.([a-zA-Z_][a-zA-Z0-9_]*)/.exec(expr);
|
|
73
|
-
const update = () => {
|
|
74
|
-
try {
|
|
75
|
-
el.textContent = getter();
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
el.textContent = '';
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
if (propMatch) {
|
|
82
|
-
const [, propName] = propMatch;
|
|
83
|
-
const reactiveProp = this.__getReactiveProp(propName);
|
|
84
|
-
if (reactiveProp) {
|
|
85
|
-
this.__bindPropertyChange(reactiveProp, update);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
update();
|
|
89
|
-
}
|
|
90
|
-
__setProperty(id, prop, value) {
|
|
91
|
-
const el = this.__getElement(id);
|
|
92
|
-
if (this.isHTMLElement(el)) {
|
|
93
|
-
if (prop in el) {
|
|
94
|
-
Reflect.set(el, prop, value);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
el.setAttribute(prop, String(value));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
__addClass(id, className) {
|
|
102
|
-
const el = this.__getElement(id);
|
|
103
|
-
if (this.isHTMLElement(el))
|
|
104
|
-
el.classList.add(className);
|
|
105
|
-
}
|
|
106
|
-
__removeClass(id, className) {
|
|
107
|
-
const el = this.__getElement(id);
|
|
108
|
-
if (this.isHTMLElement(el))
|
|
109
|
-
el.classList.remove(className);
|
|
110
|
-
}
|
|
111
|
-
__bindEvent(id, event, handler) {
|
|
112
|
-
const el = this.__getElement(id);
|
|
113
|
-
if (el) {
|
|
114
|
-
el.addEventListener(event, handler);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
__bindPropertyChange(prop, callback) {
|
|
118
|
-
const sub = prop.onChange.subscribe(callback);
|
|
119
|
-
this._subscriptions.push(sub);
|
|
120
|
-
const currentVal = prop.getValue();
|
|
121
|
-
if (currentVal !== null) {
|
|
122
|
-
callback(currentVal);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
__connectProperties(source, target) {
|
|
126
|
-
const sub = source.onChange.subscribe((val) => {
|
|
127
|
-
target.setValue(val, true);
|
|
128
|
-
});
|
|
129
|
-
this._subscriptions.push(sub);
|
|
130
|
-
const currentVal = source.getValue();
|
|
131
|
-
if (currentVal !== null) {
|
|
132
|
-
target.setValue(currentVal, true);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
__connectOutput(source, handler) {
|
|
136
|
-
const sub = source.subscribe(handler);
|
|
137
|
-
this._subscriptions.push(sub);
|
|
138
|
-
}
|
|
139
|
-
__bindOutput(id, outputName, handler) {
|
|
140
|
-
const el = this.__getElement(id);
|
|
141
|
-
if (el)
|
|
142
|
-
this.__bindOutputOnElement(el, outputName, handler);
|
|
143
|
-
}
|
|
144
|
-
__setChildProperty(el, propName, value) {
|
|
145
|
-
const prop = Reflect.get(el, propName);
|
|
146
|
-
if (this.isProperty(prop)) {
|
|
147
|
-
prop.setValue(value, true);
|
|
148
|
-
}
|
|
149
|
-
else if (propName in el) {
|
|
150
|
-
Reflect.set(el, propName, value);
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
el.setAttribute(propName, String(value));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
__bindToChild(id, propName, value) {
|
|
157
|
-
const el = this.__getElement(id);
|
|
158
|
-
if (!el)
|
|
159
|
-
return;
|
|
160
|
-
this.__setChildPropertyDeferred(el, propName, value);
|
|
161
|
-
}
|
|
162
|
-
__setChildPropertyDeferred(el, propName, value) {
|
|
163
|
-
if (Reflect.get(el, propName) !== undefined) {
|
|
164
|
-
this.__setChildProperty(el, propName, value);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
const tagName = el.tagName.toLowerCase();
|
|
168
|
-
if (tagName.includes('-')) {
|
|
169
|
-
customElements.whenDefined(tagName)
|
|
170
|
-
.then(() => {
|
|
171
|
-
this.__setChildProperty(el, propName, value);
|
|
172
|
-
})
|
|
173
|
-
.catch((e) => {
|
|
174
|
-
console.error(e);
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
this.__setChildProperty(el, propName, value);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
__bindOutputOnElement(el, outputName, handler) {
|
|
182
|
-
const maybeOutput = Reflect.get(el, outputName);
|
|
183
|
-
if (this.isPublisher(maybeOutput)) {
|
|
184
|
-
this.__connectOutput(maybeOutput, handler);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
const tagName = el.tagName.toLowerCase();
|
|
188
|
-
if (tagName.includes('-')) {
|
|
189
|
-
customElements.whenDefined(tagName)
|
|
190
|
-
.then(() => {
|
|
191
|
-
const innerOutput = Reflect.get(el, outputName);
|
|
192
|
-
if (this.isPublisher(innerOutput)) {
|
|
193
|
-
this.__connectOutput(innerOutput, handler);
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
el.addEventListener(outputName, handler);
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
.catch((e) => {
|
|
200
|
-
console.error(e);
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
el.addEventListener(outputName, handler);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
__wireEvents(container, attrPrefix) {
|
|
208
|
-
for (const el of Array.from(container.querySelectorAll(`[${attrPrefix}]`))) {
|
|
209
|
-
const attr = el.getAttribute(attrPrefix);
|
|
210
|
-
if (!attr)
|
|
211
|
-
continue;
|
|
212
|
-
const eventParts = attrPrefix.split('-event-');
|
|
213
|
-
const [, eventPart] = eventParts;
|
|
214
|
-
if (!eventPart)
|
|
215
|
-
continue;
|
|
216
|
-
const [baseEvent, ...modifiers] = eventPart.split('-');
|
|
217
|
-
let handler = attr;
|
|
218
|
-
if (attr.startsWith('[')) {
|
|
219
|
-
try {
|
|
220
|
-
const parsed = JSON.parse(attr);
|
|
221
|
-
if (Array.isArray(parsed) && typeof parsed[1] === 'string') {
|
|
222
|
-
[, handler] = parsed;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
catch {
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
el.addEventListener(baseEvent, (ev) => {
|
|
229
|
-
if (modifiers.length > 0 && ev instanceof KeyboardEvent) {
|
|
230
|
-
const keyEv = ev;
|
|
231
|
-
for (const mod of modifiers) {
|
|
232
|
-
if (mod === 'enter' && keyEv.key !== 'Enter')
|
|
233
|
-
return;
|
|
234
|
-
if (mod === 'escape' && keyEv.key !== 'Escape')
|
|
235
|
-
return;
|
|
236
|
-
if (mod === 'space' && keyEv.key !== ' ')
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
const refLookups = this.__buildRefLookups(handler);
|
|
241
|
-
// Dynamic event handlers are generated by the compiler from template attributes.
|
|
242
|
-
// The Function constructor is required to execute these at runtime.
|
|
243
|
-
// eslint-disable-next-line @typescript-eslint/no-implied-eval, @typescript-eslint/no-unsafe-type-assertion
|
|
244
|
-
const fn = Function('$event', refLookups + 'return this.' + handler)
|
|
245
|
-
.bind(this);
|
|
246
|
-
fn(ev);
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
__buildRefLookups(expr) {
|
|
251
|
-
const shadow = this._shadowRoot;
|
|
252
|
-
const refEls = shadow.querySelectorAll('[data-ref]');
|
|
253
|
-
let code = '';
|
|
254
|
-
for (const el of Array.from(refEls)) {
|
|
255
|
-
const refName = el.getAttribute('data-ref');
|
|
256
|
-
if (refName && new RegExp(`\\b${refName}\\b`).test(expr)) {
|
|
257
|
-
code += `const ${refName} = this._shadowRoot.querySelector('[data-ref="${refName}"]');`;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return code;
|
|
261
|
-
}
|
|
262
|
-
__getReactiveProp(propName) {
|
|
263
|
-
const key = `__${propName}`;
|
|
264
|
-
if (key in this) {
|
|
265
|
-
const candidate = Reflect.get(this, key);
|
|
266
|
-
if (this.isProperty(candidate)) {
|
|
267
|
-
return candidate;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return undefined;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
1
|
+
export { FluffBase } from './FluffBase.js';
|
|
2
|
+
export { FluffElement } from './FluffElementImpl.js';
|
|
3
|
+
export { getScope, registerScope, unregisterScope } from './ScopeRegistry.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Subscription } from '../interfaces/Subscription.js';
|
|
2
|
+
import { Property } from '../utils/Property.js';
|
|
3
|
+
import { Publisher } from '../utils/Publisher.js';
|
|
4
|
+
import { FluffBase } from './FluffBase.js';
|
|
5
|
+
import type { MarkerManagerInterface } from './MarkerManagerInterface.js';
|
|
6
|
+
import { type Scope } from './ScopeRegistry.js';
|
|
7
|
+
export declare abstract class FluffElement extends FluffBase {
|
|
8
|
+
protected __pipes: Record<string, (value: unknown, ...args: unknown[]) => unknown>;
|
|
9
|
+
protected readonly _shadowRoot: ShadowRoot;
|
|
10
|
+
private _subscriptions;
|
|
11
|
+
private _initialized;
|
|
12
|
+
private _markerManager;
|
|
13
|
+
private _markerConfigJson;
|
|
14
|
+
private _MarkerManagerClass;
|
|
15
|
+
constructor();
|
|
16
|
+
connectedCallback(): void;
|
|
17
|
+
disconnectedCallback(): void;
|
|
18
|
+
$watch: (_properties: string[], callback: () => void) => Subscription;
|
|
19
|
+
__processBindingsOnElementPublic(el: HTMLElement, scope: Scope, subscriptions?: Subscription[]): void;
|
|
20
|
+
protected abstract __render(): void;
|
|
21
|
+
protected __setupBindings(): void;
|
|
22
|
+
protected __addSubscription(sub: Subscription): void;
|
|
23
|
+
protected __pipe(name: string, value: unknown, ...args: unknown[]): unknown;
|
|
24
|
+
protected __getShadowRoot(): ShadowRoot;
|
|
25
|
+
protected __setMarkerConfigs(configJson: string): void;
|
|
26
|
+
protected __initializeMarkers(MarkerManagerClass: new (host: FluffElement, shadowRoot: ShadowRoot) => MarkerManagerInterface): void;
|
|
27
|
+
private __initializeMarkersInternal;
|
|
28
|
+
protected __getElement(id: string): Element | null;
|
|
29
|
+
protected __setText(id: string, text: string): void;
|
|
30
|
+
protected __bindText(id: string, getter: () => string): void;
|
|
31
|
+
protected __setProperty(id: string, prop: string, value: unknown): void;
|
|
32
|
+
protected __addClass(id: string, className: string): void;
|
|
33
|
+
protected __removeClass(id: string, className: string): void;
|
|
34
|
+
protected __bindEvent(id: string, event: string, handler: (e: Event) => void): void;
|
|
35
|
+
protected __bindPropertyChange<T>(prop: Property<T>, callback: (val: T) => void): void;
|
|
36
|
+
protected __connectProperties<T>(source: Property<T>, target: Property<T>): void;
|
|
37
|
+
protected __connectOutput<T>(source: Publisher<T>, handler: (val: T) => void): void;
|
|
38
|
+
protected __bindOutput(id: string, outputName: string, handler: (val: Event) => void): void;
|
|
39
|
+
protected __setChildProperty(el: Element, propName: string, value: unknown): void;
|
|
40
|
+
protected __bindToChild(id: string, propName: string, value: unknown): void;
|
|
41
|
+
protected __setChildPropertyDeferred(el: Element, propName: string, value: unknown): void;
|
|
42
|
+
protected __bindOutputOnElement(el: Element, outputName: string, handler: (val: Event) => void): void;
|
|
43
|
+
protected __getReactivePropFromScope(propName: string, scope: Scope): Property<unknown> | undefined;
|
|
44
|
+
protected __processBindings(): void;
|
|
45
|
+
private __applyPendingProps;
|
|
46
|
+
private isHTMLElement;
|
|
47
|
+
private isRecord;
|
|
48
|
+
private __getReactiveProp;
|
|
49
|
+
}
|