@fluffjs/fluff 0.4.4 → 0.5.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 -1
- package/decorators/Directive.d.ts +16 -0
- package/decorators/Directive.js +23 -0
- package/decorators/HostElement.d.ts +1 -0
- package/decorators/HostElement.js +4 -0
- package/index.d.ts +4 -0
- package/index.js +3 -0
- package/interfaces/ElementWithDirectives.d.ts +4 -0
- package/interfaces/FluffHostElement.d.ts +3 -10
- package/package.json +1 -1
- package/runtime/BreakController.d.ts +2 -3
- package/runtime/BreakController.js +0 -3
- package/runtime/FluffBase.d.ts +50 -42
- package/runtime/FluffBase.js +185 -242
- package/runtime/FluffDirective.d.ts +9 -0
- package/runtime/FluffDirective.js +36 -0
- package/runtime/FluffElement.d.ts +0 -1
- package/runtime/FluffElementImpl.d.ts +5 -10
- package/runtime/FluffElementImpl.js +59 -77
- package/runtime/FluffMarkers.d.ts +0 -1
- package/runtime/FluffMarkers.js +0 -1
- package/runtime/ForController.d.ts +2 -5
- package/runtime/ForController.js +7 -9
- package/runtime/IfController.d.ts +2 -5
- package/runtime/IfController.js +12 -13
- package/runtime/MarkerController.d.ts +10 -12
- package/runtime/MarkerController.js +7 -4
- package/runtime/MarkerManager.d.ts +2 -5
- package/runtime/MarkerManager.js +11 -50
- package/runtime/MarkerManagerInterface.d.ts +5 -2
- package/runtime/SwitchController.d.ts +2 -5
- package/runtime/SwitchController.js +10 -13
- package/runtime/TextController.d.ts +2 -4
- package/runtime/TextController.js +8 -16
- package/runtime/tests/DirectOutputParent.js +4 -1
- package/runtime/tests/TestChildTasksListComponent.js +4 -1
- package/runtime/tests/TestForComponent.js +4 -1
- package/runtime/tests/TestForReinsertBindsInputParentComponent.js +7 -2
- package/runtime/tests/TestForTextMarkerCollisionNoTrackParentComponent.js +5 -2
- package/runtime/tests/TestForTextMarkerCollisionParentComponent.js +5 -10
- package/runtime/tests/TestForUnsubscribeNestedParentComponent.js +7 -2
- package/runtime/tests/TestGetterReactivityComponent.js +4 -1
- package/runtime/tests/TestHarness.d.ts +1 -1
- package/runtime/tests/TestHarness.js +3 -3
- package/runtime/tests/TestIfReinsertBindsInputChildComponent.js +4 -1
- package/runtime/tests/TestIfReinsertBindsInputParentComponent.js +7 -2
- package/runtime/tests/TestIfUnsubscribeNestedParentComponent.js +7 -2
- package/runtime/tests/TestInterpolationNestedPropertyComponentBase.js +4 -1
- package/runtime/tests/TestLateDefineForComponent.js +4 -1
- package/runtime/tests/TestNullInputTextComponent.js +5 -2
- package/runtime/tests/TestOutputBindingChildComponent.js +4 -1
- package/runtime/tests/TestOutputBindingParentComponent.js +7 -2
- package/runtime/tests/TestParentBindsTasksComponent.js +4 -1
- package/runtime/tests/TestSwitchReinsertBindsInputChildComponent.js +4 -1
- package/runtime/tests/TestSwitchReinsertBindsInputParentComponent.js +8 -10
- package/runtime/tests/TestSwitchUnsubscribeNestedParentComponent.js +7 -9
- package/runtime/tests/TestTemplateNestedMarkersComponent.js +5 -2
- package/runtime/tests/TestUnsubscribeNestedChildComponent.js +4 -1
- package/runtime/tests/TestUnsubscribeNestedGrandchildComponent.js +4 -1
- package/runtime/tests/createPipeUnwrapTestComponent.js +4 -1
- package/runtime/tests/createPropBindParentComponent.js +4 -1
- package/runtime/tests/createTestInterpolationPipeComponent.js +4 -1
- package/runtime/tests/createTestInterpolationPipeWithArgsComponent.js +4 -1
- package/interfaces/BreakMarkerConfig.d.ts +0 -3
- package/interfaces/ForMarkerConfig.d.ts +0 -8
- package/interfaces/ForMarkerConfig.js +0 -1
- package/interfaces/IfMarkerConfig.d.ts +0 -7
- package/interfaces/IfMarkerConfig.js +0 -1
- package/interfaces/MarkerConfig.d.ts +0 -6
- package/interfaces/MarkerConfig.js +0 -1
- package/interfaces/SwitchMarkerConfig.d.ts +0 -10
- package/interfaces/SwitchMarkerConfig.js +0 -1
- package/interfaces/TextMarkerConfig.d.ts +0 -9
- package/interfaces/TextMarkerConfig.js +0 -1
- package/runtime/MarkerConfigGuards.d.ts +0 -13
- package/runtime/MarkerConfigGuards.js +0 -17
- /package/interfaces/{BreakMarkerConfig.js → ElementWithDirectives.js} +0 -0
package/runtime/FluffBase.js
CHANGED
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
import { Property } from '../utils/Property.js';
|
|
2
2
|
import { Publisher } from '../utils/Publisher.js';
|
|
3
|
-
const BINDING_TYPES = ['property', 'event', 'two-way', 'class', 'style', 'ref'];
|
|
4
|
-
/**
|
|
5
|
-
* Compact Marker Config Format (Decoder)
|
|
6
|
-
*
|
|
7
|
-
* Marker configs use the same string table as bindings. Type is numeric:
|
|
8
|
-
* 0=if, 1=for, 2=text, 3=switch, 4=break
|
|
9
|
-
*
|
|
10
|
-
* Format varies by type:
|
|
11
|
-
* - if: [0, branches[]] where branch = [exprId?, deps?]
|
|
12
|
-
* - for: [1, iteratorIdx, iterableExprId, hasEmpty, deps?, trackByIdx?]
|
|
13
|
-
* - text: [2, exprId, deps?, pipes?]
|
|
14
|
-
* - switch: [3, expressionExprId, deps?, cases[]]
|
|
15
|
-
* - break: [4]
|
|
16
|
-
*
|
|
17
|
-
* deps are interned as CompactDep[] (same as bindings)
|
|
18
|
-
* pipes are [pipeNameIdx, argExprIds[]][]
|
|
19
|
-
*/
|
|
20
|
-
const MARKER_TYPES = ['if', 'for', 'text', 'switch', 'break'];
|
|
21
3
|
export class FluffBase extends HTMLElement {
|
|
22
4
|
static __e = [];
|
|
23
5
|
static __h = [];
|
|
@@ -37,9 +19,6 @@ export class FluffBase extends HTMLElement {
|
|
|
37
19
|
callback();
|
|
38
20
|
}
|
|
39
21
|
}
|
|
40
|
-
static __decodeString(idx) {
|
|
41
|
-
return FluffBase.__s[idx];
|
|
42
|
-
}
|
|
43
22
|
static __decodeDep(dep) {
|
|
44
23
|
if (Array.isArray(dep)) {
|
|
45
24
|
return dep.map(idx => FluffBase.__s[idx]);
|
|
@@ -51,107 +30,6 @@ export class FluffBase extends HTMLElement {
|
|
|
51
30
|
return undefined;
|
|
52
31
|
return deps.map(d => FluffBase.__decodeDep(d));
|
|
53
32
|
}
|
|
54
|
-
static __decodeMarkerConfig(compact) {
|
|
55
|
-
const [typeNum] = compact;
|
|
56
|
-
const type = MARKER_TYPES[typeNum];
|
|
57
|
-
switch (typeNum) {
|
|
58
|
-
case 0: // if
|
|
59
|
-
{
|
|
60
|
-
const [, rawBranches] = compact;
|
|
61
|
-
const branches = rawBranches.map(b => {
|
|
62
|
-
if (b.length === 0)
|
|
63
|
-
return {};
|
|
64
|
-
const [branchExprId, branchDeps] = b;
|
|
65
|
-
const result = {};
|
|
66
|
-
if (branchExprId !== null)
|
|
67
|
-
result.exprId = branchExprId;
|
|
68
|
-
if (branchDeps)
|
|
69
|
-
result.deps = FluffBase.__decodeDeps(branchDeps);
|
|
70
|
-
return result;
|
|
71
|
-
});
|
|
72
|
-
return { type, branches };
|
|
73
|
-
}
|
|
74
|
-
case 1: // for
|
|
75
|
-
{
|
|
76
|
-
const [, iteratorIdx, iterableExprId, hasEmpty, deps, trackByIdx] = compact;
|
|
77
|
-
const result = {
|
|
78
|
-
type,
|
|
79
|
-
iterator: FluffBase.__s[iteratorIdx],
|
|
80
|
-
iterableExprId,
|
|
81
|
-
hasEmpty
|
|
82
|
-
};
|
|
83
|
-
if (deps)
|
|
84
|
-
result.deps = FluffBase.__decodeDeps(deps);
|
|
85
|
-
if (trackByIdx !== null)
|
|
86
|
-
result.trackBy = FluffBase.__s[trackByIdx];
|
|
87
|
-
return result;
|
|
88
|
-
}
|
|
89
|
-
case 2: // text
|
|
90
|
-
{
|
|
91
|
-
const [, exprId, deps, pipes] = compact;
|
|
92
|
-
const result = { type, exprId };
|
|
93
|
-
if (deps)
|
|
94
|
-
result.deps = FluffBase.__decodeDeps(deps);
|
|
95
|
-
if (pipes) {
|
|
96
|
-
result.pipes = pipes.map(([nameIdx, argExprIds]) => ({
|
|
97
|
-
name: FluffBase.__s[nameIdx],
|
|
98
|
-
argExprIds
|
|
99
|
-
}));
|
|
100
|
-
}
|
|
101
|
-
return result;
|
|
102
|
-
}
|
|
103
|
-
case 3: // switch
|
|
104
|
-
{
|
|
105
|
-
const [, expressionExprId, deps, cases] = compact;
|
|
106
|
-
const result = { type, expressionExprId };
|
|
107
|
-
if (deps)
|
|
108
|
-
result.deps = FluffBase.__decodeDeps(deps);
|
|
109
|
-
result.cases = cases.map(([isDefault, fallthrough, valueExprId]) => {
|
|
110
|
-
const c = { isDefault, fallthrough };
|
|
111
|
-
if (valueExprId !== null)
|
|
112
|
-
c.valueExprId = valueExprId;
|
|
113
|
-
return c;
|
|
114
|
-
});
|
|
115
|
-
return result;
|
|
116
|
-
}
|
|
117
|
-
case 4: // break
|
|
118
|
-
return { type };
|
|
119
|
-
default:
|
|
120
|
-
return { type: 'unknown' };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
static __decodeBinding(compact) {
|
|
124
|
-
const [nameIdx, bType, deps, id, extras] = compact;
|
|
125
|
-
const n = FluffBase.__s[nameIdx];
|
|
126
|
-
const b = BINDING_TYPES[bType];
|
|
127
|
-
const result = { n, b };
|
|
128
|
-
if (deps) {
|
|
129
|
-
result.d = deps.map(d => FluffBase.__decodeDep(d));
|
|
130
|
-
}
|
|
131
|
-
if (b === 'event') {
|
|
132
|
-
if (id !== null) {
|
|
133
|
-
result.h = id;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
else if (id !== null) {
|
|
137
|
-
result.e = id;
|
|
138
|
-
}
|
|
139
|
-
if (extras) {
|
|
140
|
-
if (extras.t) {
|
|
141
|
-
result.t = extras.t;
|
|
142
|
-
}
|
|
143
|
-
if (extras.s) {
|
|
144
|
-
result.s = extras.s;
|
|
145
|
-
}
|
|
146
|
-
if (extras.p) {
|
|
147
|
-
result.p = extras.p.map(([pipeNameIdx, args]) => ({
|
|
148
|
-
n: FluffBase.__s[pipeNameIdx],
|
|
149
|
-
a: args
|
|
150
|
-
}));
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return result;
|
|
154
|
-
}
|
|
155
33
|
static __areExpressionsReady() {
|
|
156
34
|
return FluffBase.__expressionsReady;
|
|
157
35
|
}
|
|
@@ -185,13 +63,14 @@ export class FluffBase extends HTMLElement {
|
|
|
185
63
|
if (result instanceof Property) {
|
|
186
64
|
result = result.getValue();
|
|
187
65
|
}
|
|
188
|
-
for (const
|
|
189
|
-
const
|
|
66
|
+
for (const [nameIdx, argExprIds] of pipes) {
|
|
67
|
+
const name = FluffBase.__s[nameIdx];
|
|
68
|
+
const pipeFn = this.__getPipeFn(name);
|
|
190
69
|
if (!pipeFn) {
|
|
191
|
-
console.warn(`Pipe "${
|
|
70
|
+
console.warn(`Pipe "${name}" not found`);
|
|
192
71
|
continue;
|
|
193
72
|
}
|
|
194
|
-
const args =
|
|
73
|
+
const args = argExprIds.map(id => this.__getCompiledExprFn(id)(this, locals));
|
|
195
74
|
result = pipeFn(result, ...args);
|
|
196
75
|
}
|
|
197
76
|
return result;
|
|
@@ -211,42 +90,37 @@ export class FluffBase extends HTMLElement {
|
|
|
211
90
|
const ctor = this.constructor;
|
|
212
91
|
if (typeof ctor === 'function') {
|
|
213
92
|
const bindings = Reflect.get(ctor, '__bindings');
|
|
214
|
-
if (this.
|
|
93
|
+
if (this.__isRecord(bindings) && lid in bindings) {
|
|
215
94
|
const lidBindings = bindings[lid];
|
|
216
|
-
if (lidBindings) {
|
|
217
|
-
return lidBindings
|
|
95
|
+
if (this.__isCompactBindingArray(lidBindings)) {
|
|
96
|
+
return lidBindings;
|
|
218
97
|
}
|
|
219
98
|
}
|
|
220
99
|
}
|
|
221
100
|
return undefined;
|
|
222
101
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return FluffBase.__decodeBinding(binding);
|
|
226
|
-
}
|
|
227
|
-
return binding;
|
|
228
|
-
}
|
|
229
|
-
__isBindingsMap(value) {
|
|
230
|
-
return !(!value || typeof value !== 'object');
|
|
102
|
+
__isCompactBindingArray(value) {
|
|
103
|
+
return Array.isArray(value);
|
|
231
104
|
}
|
|
105
|
+
// CompactBinding[1] type dispatch: 0=property, 1=event, 2=two-way, 3=class, 4=style, 5=ref
|
|
232
106
|
__applyBindingWithScope(el, binding, scope, subscriptions) {
|
|
233
|
-
switch (binding
|
|
234
|
-
case
|
|
107
|
+
switch (binding[1]) {
|
|
108
|
+
case 0:
|
|
235
109
|
this.__applyPropertyBindingWithScope(el, binding, scope, subscriptions);
|
|
236
110
|
break;
|
|
237
|
-
case
|
|
111
|
+
case 1:
|
|
238
112
|
this.__applyEventBindingWithScope(el, binding, scope);
|
|
239
113
|
break;
|
|
240
|
-
case
|
|
114
|
+
case 2:
|
|
241
115
|
this.__applyTwoWayBindingWithScope(el, binding, scope, subscriptions);
|
|
242
116
|
break;
|
|
243
|
-
case
|
|
117
|
+
case 3:
|
|
244
118
|
this.__applyClassBindingWithScope(el, binding, scope, subscriptions);
|
|
245
119
|
break;
|
|
246
|
-
case
|
|
120
|
+
case 4:
|
|
247
121
|
this.__applyStyleBindingWithScope(el, binding, scope, subscriptions);
|
|
248
122
|
break;
|
|
249
|
-
case
|
|
123
|
+
case 5:
|
|
250
124
|
break;
|
|
251
125
|
}
|
|
252
126
|
}
|
|
@@ -264,9 +138,6 @@ export class FluffBase extends HTMLElement {
|
|
|
264
138
|
}
|
|
265
139
|
return fn;
|
|
266
140
|
}
|
|
267
|
-
__applyPipes(value, pipes, locals) {
|
|
268
|
-
return this.__applyPipesForController(value, pipes.map(p => ({ name: p.n, argExprIds: p.a })), locals);
|
|
269
|
-
}
|
|
270
141
|
__getPipeFn(_name) {
|
|
271
142
|
return undefined;
|
|
272
143
|
}
|
|
@@ -302,34 +173,7 @@ export class FluffBase extends HTMLElement {
|
|
|
302
173
|
const [first, ...rest] = chain;
|
|
303
174
|
const reactiveProp = this.__getReactivePropFromScope(first, scope);
|
|
304
175
|
if (reactiveProp) {
|
|
305
|
-
|
|
306
|
-
addSub(reactiveProp.onChange.subscribe(callback));
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
let nestedSubs = [];
|
|
310
|
-
const resubscribeNested = () => {
|
|
311
|
-
for (const sub of nestedSubs) {
|
|
312
|
-
sub.unsubscribe();
|
|
313
|
-
}
|
|
314
|
-
nestedSubs = [];
|
|
315
|
-
const currentValue = reactiveProp.getValue();
|
|
316
|
-
if (currentValue !== null && currentValue !== undefined) {
|
|
317
|
-
this.__subscribeToNestedChain(currentValue, rest, callback, (sub) => {
|
|
318
|
-
nestedSubs.push(sub);
|
|
319
|
-
addSub(sub);
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
callback();
|
|
323
|
-
};
|
|
324
|
-
addSub(reactiveProp.onChange.subscribe(resubscribeNested));
|
|
325
|
-
const currentValue = reactiveProp.getValue();
|
|
326
|
-
if (currentValue !== null && currentValue !== undefined) {
|
|
327
|
-
this.__subscribeToNestedChain(currentValue, rest, callback, (sub) => {
|
|
328
|
-
nestedSubs.push(sub);
|
|
329
|
-
addSub(sub);
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
}
|
|
176
|
+
this.__subscribeToProp(reactiveProp, rest, callback, addSub);
|
|
333
177
|
}
|
|
334
178
|
else if (first in scope.locals) {
|
|
335
179
|
const localValue = scope.locals[first];
|
|
@@ -341,40 +185,42 @@ export class FluffBase extends HTMLElement {
|
|
|
341
185
|
console.warn(`Binding dependency "${first}" not found on component ${scope.host.constructor.name}`);
|
|
342
186
|
}
|
|
343
187
|
}
|
|
188
|
+
__subscribeToProp(prop, rest, callback, addSub) {
|
|
189
|
+
if (rest.length === 0) {
|
|
190
|
+
addSub(prop.onChange.subscribe(callback));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
let nestedSubs = [];
|
|
194
|
+
const resubscribeNested = () => {
|
|
195
|
+
for (const sub of nestedSubs) {
|
|
196
|
+
sub.unsubscribe();
|
|
197
|
+
}
|
|
198
|
+
nestedSubs = [];
|
|
199
|
+
const currentValue = prop.getValue();
|
|
200
|
+
if (currentValue !== null && currentValue !== undefined) {
|
|
201
|
+
this.__subscribeToNestedChain(currentValue, rest, callback, (sub) => {
|
|
202
|
+
nestedSubs.push(sub);
|
|
203
|
+
addSub(sub);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
callback();
|
|
207
|
+
};
|
|
208
|
+
addSub(prop.onChange.subscribe(resubscribeNested));
|
|
209
|
+
const currentValue = prop.getValue();
|
|
210
|
+
if (currentValue !== null && currentValue !== undefined) {
|
|
211
|
+
this.__subscribeToNestedChain(currentValue, rest, callback, (sub) => {
|
|
212
|
+
nestedSubs.push(sub);
|
|
213
|
+
addSub(sub);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
344
217
|
__subscribeToNestedChain(obj, chain, callback, addSub) {
|
|
345
218
|
if (chain.length === 0 || obj === null || obj === undefined)
|
|
346
219
|
return;
|
|
347
220
|
const [first, ...rest] = chain;
|
|
348
221
|
const prop = Reflect.get(obj, first);
|
|
349
222
|
if (prop instanceof Property) {
|
|
350
|
-
|
|
351
|
-
addSub(prop.onChange.subscribe(callback));
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
354
|
-
let nestedSubs = [];
|
|
355
|
-
const resubscribeNested = () => {
|
|
356
|
-
for (const sub of nestedSubs) {
|
|
357
|
-
sub.unsubscribe();
|
|
358
|
-
}
|
|
359
|
-
nestedSubs = [];
|
|
360
|
-
const currentValue = prop.getValue();
|
|
361
|
-
if (currentValue !== null && currentValue !== undefined) {
|
|
362
|
-
this.__subscribeToNestedChain(currentValue, rest, callback, (sub) => {
|
|
363
|
-
nestedSubs.push(sub);
|
|
364
|
-
addSub(sub);
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
callback();
|
|
368
|
-
};
|
|
369
|
-
addSub(prop.onChange.subscribe(resubscribeNested));
|
|
370
|
-
const currentValue = prop.getValue();
|
|
371
|
-
if (currentValue !== null && currentValue !== undefined) {
|
|
372
|
-
this.__subscribeToNestedChain(currentValue, rest, callback, (sub) => {
|
|
373
|
-
nestedSubs.push(sub);
|
|
374
|
-
addSub(sub);
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
}
|
|
223
|
+
this.__subscribeToProp(prop, rest, callback, addSub);
|
|
378
224
|
}
|
|
379
225
|
else if (rest.length > 0 && prop !== null && prop !== undefined) {
|
|
380
226
|
this.__subscribeToNestedChain(prop, rest, callback, addSub);
|
|
@@ -417,28 +263,32 @@ export class FluffBase extends HTMLElement {
|
|
|
417
263
|
}
|
|
418
264
|
return value;
|
|
419
265
|
}
|
|
266
|
+
// CompactBinding: [nameIdx, type, deps, exprId, extras?]
|
|
420
267
|
__applyPropertyBindingWithScope(el, binding, scope, subscriptions) {
|
|
268
|
+
const [nameIdx, , compactDeps, exprId, extras] = binding;
|
|
269
|
+
const name = FluffBase.__s[nameIdx];
|
|
270
|
+
const deps = FluffBase.__decodeDeps(compactDeps);
|
|
421
271
|
const tagName = el.tagName.toLowerCase();
|
|
422
272
|
const isCustomElement = customElements.get(tagName) !== undefined;
|
|
423
273
|
const update = () => {
|
|
424
274
|
try {
|
|
425
|
-
if (typeof
|
|
426
|
-
throw new Error(`Binding for ${
|
|
275
|
+
if (typeof exprId !== 'number') {
|
|
276
|
+
throw new Error(`Binding for ${name} is missing exprId`);
|
|
427
277
|
}
|
|
428
|
-
const fn = this.__getCompiledExprFn(
|
|
278
|
+
const fn = this.__getCompiledExprFn(exprId);
|
|
429
279
|
let value = fn(this, scope.locals);
|
|
430
|
-
if (
|
|
431
|
-
value = this.
|
|
280
|
+
if (extras?.p && extras.p.length > 0) {
|
|
281
|
+
value = this.__applyPipesForController(value, extras.p, scope.locals);
|
|
432
282
|
}
|
|
433
|
-
this.__setChildProperty(el,
|
|
283
|
+
this.__setChildProperty(el, name, value);
|
|
434
284
|
}
|
|
435
285
|
catch (e) {
|
|
436
286
|
console.error('Property binding error:', e);
|
|
437
287
|
}
|
|
438
288
|
};
|
|
439
|
-
this.__subscribeToExpressionInScope(
|
|
440
|
-
if (
|
|
441
|
-
this.__subscribeToExpressionInScope([
|
|
289
|
+
this.__subscribeToExpressionInScope(deps, scope, update, subscriptions);
|
|
290
|
+
if (extras?.s) {
|
|
291
|
+
this.__subscribeToExpressionInScope([extras.s], scope, update, subscriptions);
|
|
442
292
|
}
|
|
443
293
|
if (isCustomElement) {
|
|
444
294
|
if (el instanceof FluffBase) {
|
|
@@ -450,7 +300,7 @@ export class FluffBase extends HTMLElement {
|
|
|
450
300
|
update();
|
|
451
301
|
}
|
|
452
302
|
else {
|
|
453
|
-
console.warn(`Element <${tagName}> is not a FluffBase instance after whenDefined - binding for "${
|
|
303
|
+
console.warn(`Element <${tagName}> is not a FluffBase instance after whenDefined - binding for "${name}" skipped`);
|
|
454
304
|
}
|
|
455
305
|
});
|
|
456
306
|
}
|
|
@@ -459,20 +309,24 @@ export class FluffBase extends HTMLElement {
|
|
|
459
309
|
update();
|
|
460
310
|
}
|
|
461
311
|
}
|
|
312
|
+
// CompactBinding: [nameIdx, type, deps, handlerId, extras?]
|
|
462
313
|
__applyEventBindingWithScope(el, binding, scope) {
|
|
463
|
-
const
|
|
314
|
+
const [nameIdx, , , handlerId] = binding;
|
|
315
|
+
const name = FluffBase.__s[nameIdx];
|
|
316
|
+
const boundKey = `__fluff_event_${name}`;
|
|
464
317
|
if (Reflect.has(el, boundKey))
|
|
465
318
|
return;
|
|
466
319
|
Reflect.set(el, boundKey, true);
|
|
467
|
-
if (typeof
|
|
468
|
-
throw new Error(`Event binding for ${
|
|
320
|
+
if (typeof handlerId !== 'number') {
|
|
321
|
+
throw new Error(`Event binding for ${name} is missing handlerId`);
|
|
469
322
|
}
|
|
470
|
-
const handlerFn = this.__getCompiledHandlerFn(
|
|
471
|
-
|
|
472
|
-
|
|
323
|
+
const handlerFn = this.__getCompiledHandlerFn(handlerId);
|
|
324
|
+
const hasDirectives = el.hasAttribute('data-fluff-directives');
|
|
325
|
+
if (el.hasAttribute('x-fluff-component') || hasDirectives) {
|
|
326
|
+
this.__applyOutputBinding(el, name, handlerFn, scope);
|
|
473
327
|
}
|
|
474
328
|
else {
|
|
475
|
-
el.addEventListener(
|
|
329
|
+
el.addEventListener(name, (event) => {
|
|
476
330
|
try {
|
|
477
331
|
handlerFn(this, scope.locals, event);
|
|
478
332
|
}
|
|
@@ -483,8 +337,7 @@ export class FluffBase extends HTMLElement {
|
|
|
483
337
|
}
|
|
484
338
|
}
|
|
485
339
|
__applyOutputBinding(el, outputName, handlerFn, scope) {
|
|
486
|
-
const
|
|
487
|
-
const publisher = Reflect.get(el, outputName);
|
|
340
|
+
const subscribeToPublisher = (publisher) => {
|
|
488
341
|
if (publisher instanceof Publisher) {
|
|
489
342
|
const sub = publisher.subscribe((value) => {
|
|
490
343
|
try {
|
|
@@ -499,25 +352,48 @@ export class FluffBase extends HTMLElement {
|
|
|
499
352
|
}
|
|
500
353
|
return false;
|
|
501
354
|
};
|
|
502
|
-
|
|
503
|
-
|
|
355
|
+
const subscribeToElement = () => {
|
|
356
|
+
const publisher = Reflect.get(el, outputName);
|
|
357
|
+
return subscribeToPublisher(publisher);
|
|
358
|
+
};
|
|
359
|
+
const subscribeToDirectives = () => {
|
|
360
|
+
let subscribed = false;
|
|
361
|
+
const directives = el.__fluffDirectives;
|
|
362
|
+
if (directives) {
|
|
363
|
+
for (const directive of directives) {
|
|
364
|
+
const directivePublisher = Reflect.get(directive, outputName);
|
|
365
|
+
if (subscribeToPublisher(directivePublisher)) {
|
|
366
|
+
subscribed = true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return subscribed;
|
|
371
|
+
};
|
|
372
|
+
subscribeToDirectives();
|
|
373
|
+
if (!subscribeToElement()) {
|
|
374
|
+
const tagName = el.tagName.toLowerCase();
|
|
375
|
+
if (tagName.includes('-')) {
|
|
376
|
+
this.__whenDefined(tagName, () => {
|
|
377
|
+
subscribeToElement();
|
|
378
|
+
});
|
|
379
|
+
}
|
|
504
380
|
}
|
|
505
|
-
this.__whenDefined(el.tagName.toLowerCase(), () => {
|
|
506
|
-
trySubscribe();
|
|
507
|
-
});
|
|
508
381
|
}
|
|
509
382
|
__whenDefined(tagName, callback) {
|
|
510
383
|
customElements.whenDefined(tagName)
|
|
511
384
|
.then(callback)
|
|
512
385
|
.catch(console.error);
|
|
513
386
|
}
|
|
387
|
+
// CompactBinding: [nameIdx, type, deps, exprId, extras?] — extras.t = targetProp for two-way
|
|
514
388
|
__applyTwoWayBindingWithScope(el, binding, scope, subscriptions) {
|
|
515
389
|
this.__applyPropertyBindingWithScope(el, binding, scope, subscriptions);
|
|
516
|
-
|
|
517
|
-
|
|
390
|
+
const name = FluffBase.__s[binding[0]];
|
|
391
|
+
const targetProp = binding[4]?.t;
|
|
392
|
+
if (typeof targetProp !== 'string' || targetProp.length === 0) {
|
|
393
|
+
throw new Error(`Two-way binding for ${name} is missing targetProp`);
|
|
518
394
|
}
|
|
519
|
-
const reactiveProp = this.__getReactivePropFromScope(
|
|
520
|
-
const childPropCandidate = Reflect.get(el,
|
|
395
|
+
const reactiveProp = this.__getReactivePropFromScope(targetProp, scope);
|
|
396
|
+
const childPropCandidate = Reflect.get(el, name);
|
|
521
397
|
if (reactiveProp && childPropCandidate instanceof Property) {
|
|
522
398
|
const sub = childPropCandidate.onChange.subscribe((val) => {
|
|
523
399
|
reactiveProp.setValue(val, true);
|
|
@@ -530,48 +406,115 @@ export class FluffBase extends HTMLElement {
|
|
|
530
406
|
}
|
|
531
407
|
}
|
|
532
408
|
}
|
|
409
|
+
// CompactBinding: [nameIdx, type, deps, exprId]
|
|
533
410
|
__applyClassBindingWithScope(el, binding, scope, subscriptions) {
|
|
411
|
+
const [nameIdx, , compactDeps, exprId] = binding;
|
|
412
|
+
const name = FluffBase.__s[nameIdx];
|
|
413
|
+
const deps = FluffBase.__decodeDeps(compactDeps);
|
|
534
414
|
const update = () => {
|
|
535
415
|
try {
|
|
536
|
-
if (typeof
|
|
537
|
-
throw new Error(`Class binding for ${
|
|
416
|
+
if (typeof exprId !== 'number') {
|
|
417
|
+
throw new Error(`Class binding for ${name} is missing exprId`);
|
|
538
418
|
}
|
|
539
|
-
const fn = this.__getCompiledExprFn(
|
|
419
|
+
const fn = this.__getCompiledExprFn(exprId);
|
|
540
420
|
const value = this.__unwrap(fn(this, scope.locals));
|
|
541
421
|
if (value) {
|
|
542
|
-
el.classList.add(
|
|
422
|
+
el.classList.add(name);
|
|
543
423
|
}
|
|
544
424
|
else {
|
|
545
|
-
el.classList.remove(
|
|
425
|
+
el.classList.remove(name);
|
|
546
426
|
}
|
|
547
427
|
}
|
|
548
428
|
catch (e) {
|
|
549
429
|
console.error('Class binding error:', e);
|
|
550
430
|
}
|
|
551
431
|
};
|
|
552
|
-
this.__subscribeToExpressionInScope(
|
|
432
|
+
this.__subscribeToExpressionInScope(deps, scope, update, subscriptions);
|
|
553
433
|
update();
|
|
554
434
|
}
|
|
435
|
+
// CompactBinding: [nameIdx, type, deps, exprId]
|
|
555
436
|
__applyStyleBindingWithScope(el, binding, scope, subscriptions) {
|
|
437
|
+
const [nameIdx, , compactDeps, exprId] = binding;
|
|
438
|
+
const name = FluffBase.__s[nameIdx];
|
|
439
|
+
const deps = FluffBase.__decodeDeps(compactDeps);
|
|
556
440
|
const update = () => {
|
|
557
441
|
try {
|
|
558
|
-
if (typeof
|
|
559
|
-
throw new Error(`Style binding for ${
|
|
442
|
+
if (typeof exprId !== 'number') {
|
|
443
|
+
throw new Error(`Style binding for ${name} is missing exprId`);
|
|
560
444
|
}
|
|
561
|
-
const fn = this.__getCompiledExprFn(
|
|
445
|
+
const fn = this.__getCompiledExprFn(exprId);
|
|
562
446
|
const value = this.__unwrap(fn(this, scope.locals));
|
|
563
447
|
if (this.__hasStyle(el)) {
|
|
564
|
-
el.style.setProperty(
|
|
448
|
+
el.style.setProperty(name, String(value));
|
|
565
449
|
}
|
|
566
450
|
}
|
|
567
451
|
catch (e) {
|
|
568
452
|
console.error('Style binding error:', e);
|
|
569
453
|
}
|
|
570
454
|
};
|
|
571
|
-
this.__subscribeToExpressionInScope(
|
|
455
|
+
this.__subscribeToExpressionInScope(deps, scope, update, subscriptions);
|
|
572
456
|
update();
|
|
573
457
|
}
|
|
574
458
|
__hasStyle(el) {
|
|
575
459
|
return 'style' in el;
|
|
576
460
|
}
|
|
461
|
+
__createProp(nameOrIdx, options) {
|
|
462
|
+
const name = typeof nameOrIdx === 'number' ? FluffBase.__s[nameOrIdx] : nameOrIdx;
|
|
463
|
+
const prop = new Property(options);
|
|
464
|
+
Object.defineProperty(this, name, {
|
|
465
|
+
get() {
|
|
466
|
+
return prop.getValue();
|
|
467
|
+
},
|
|
468
|
+
set(v) {
|
|
469
|
+
prop.setValue(v);
|
|
470
|
+
},
|
|
471
|
+
enumerable: true,
|
|
472
|
+
configurable: true
|
|
473
|
+
});
|
|
474
|
+
return prop;
|
|
475
|
+
}
|
|
476
|
+
__defineClassHostBinding(name, className, privateName) {
|
|
477
|
+
const host = this.__getHostElement();
|
|
478
|
+
Object.defineProperty(this, name, {
|
|
479
|
+
get: () => Boolean(Reflect.get(this, privateName)),
|
|
480
|
+
set: (v) => {
|
|
481
|
+
Reflect.set(this, privateName, v);
|
|
482
|
+
if (v) {
|
|
483
|
+
host.classList.add(className);
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
host.classList.remove(className);
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
enumerable: true,
|
|
490
|
+
configurable: true
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
__applyPendingProps() {
|
|
494
|
+
const existing = Reflect.get(this, '__pendingProps');
|
|
495
|
+
if (!this.__isRecord(existing)) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
for (const [propName, value] of Object.entries(existing)) {
|
|
499
|
+
const key = `__${propName}`;
|
|
500
|
+
if (key in this) {
|
|
501
|
+
const prop = Reflect.get(this, key);
|
|
502
|
+
if (prop instanceof Property) {
|
|
503
|
+
prop.setValue(value, true);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
else if (propName in this) {
|
|
507
|
+
Reflect.set(this, propName, value);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
Reflect.deleteProperty(this, '__pendingProps');
|
|
511
|
+
}
|
|
512
|
+
__isRecord(value) {
|
|
513
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
514
|
+
}
|
|
515
|
+
__getHostElement() {
|
|
516
|
+
return this;
|
|
517
|
+
}
|
|
518
|
+
__initHostBindings() {
|
|
519
|
+
}
|
|
577
520
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FluffBase } from './FluffBase.js';
|
|
2
|
+
export declare class FluffDirective extends FluffBase {
|
|
3
|
+
private _hostElement;
|
|
4
|
+
private _initialized;
|
|
5
|
+
__setHostElement(hostElement: HTMLElement): void;
|
|
6
|
+
initialize(): void;
|
|
7
|
+
destroy(): void;
|
|
8
|
+
protected __getHostElement(): HTMLElement;
|
|
9
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { FluffBase } from './FluffBase.js';
|
|
2
|
+
export class FluffDirective extends FluffBase {
|
|
3
|
+
_hostElement = null;
|
|
4
|
+
_initialized = false;
|
|
5
|
+
__setHostElement(hostElement) {
|
|
6
|
+
this._hostElement = hostElement;
|
|
7
|
+
if ('__assignHostElementProps' in this && typeof this.__assignHostElementProps === 'function') {
|
|
8
|
+
this.__assignHostElementProps(hostElement);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
initialize() {
|
|
12
|
+
if (this._initialized)
|
|
13
|
+
return;
|
|
14
|
+
this._initialized = true;
|
|
15
|
+
this.__applyPendingProps();
|
|
16
|
+
this.__initHostBindings();
|
|
17
|
+
if ('onInit' in this && typeof this.onInit === 'function') {
|
|
18
|
+
this.onInit();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
destroy() {
|
|
22
|
+
if ('onDestroy' in this && typeof this.onDestroy === 'function') {
|
|
23
|
+
this.onDestroy();
|
|
24
|
+
}
|
|
25
|
+
for (const sub of this.__baseSubscriptions) {
|
|
26
|
+
sub.unsubscribe();
|
|
27
|
+
}
|
|
28
|
+
this.__baseSubscriptions = [];
|
|
29
|
+
}
|
|
30
|
+
__getHostElement() {
|
|
31
|
+
if (!this._hostElement) {
|
|
32
|
+
throw new Error('Directive host element not set');
|
|
33
|
+
}
|
|
34
|
+
return this._hostElement;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { FluffBase } from './FluffBase.js';
|
|
2
|
-
export type { BindingInfo } from './FluffBase.js';
|
|
3
2
|
export { FluffElement } from './FluffElementImpl.js';
|
|
4
3
|
export { getScope, registerScope, unregisterScope } from './ScopeRegistry.js';
|
|
5
4
|
export type { Scope } from './ScopeRegistry.js';
|