@fluffjs/fluff 0.2.3 → 0.3.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 -8
- package/decorators/Component.js +0 -11
- package/decorators/Input.d.ts +1 -1
- package/decorators/InputOutputHelper.d.ts +2 -1
- package/decorators/InputOutputHelper.js +3 -1
- package/decorators/Output.d.ts +1 -1
- package/decorators/Pipe.d.ts +1 -0
- package/decorators/Pipe.js +1 -1
- package/decorators/Reactive.d.ts +2 -1
- package/decorators/Reactive.js +1 -1
- package/index.d.ts +2 -1
- package/index.js +1 -1
- package/interfaces/FluffHostElement.d.ts +13 -3
- package/interfaces/ReactiveOptions.d.ts +6 -0
- package/interfaces/ReactiveOptions.js +1 -0
- package/package.json +1 -1
- package/runtime/FluffBase.d.ts +12 -1
- package/runtime/FluffBase.js +70 -22
- package/runtime/FluffElementImpl.d.ts +9 -21
- package/runtime/FluffElementImpl.js +48 -149
- package/runtime/ForController.js +1 -2
- package/runtime/IfController.js +1 -2
- package/runtime/MarkerConfigGuards.d.ts +0 -2
- package/runtime/MarkerConfigGuards.js +0 -3
- package/runtime/MarkerController.d.ts +1 -9
- package/runtime/MarkerController.js +28 -144
- package/runtime/MarkerManager.d.ts +2 -1
- package/runtime/MarkerManager.js +1 -2
- package/runtime/MarkerManagerInterface.d.ts +3 -1
- package/runtime/SwitchController.js +1 -2
- package/runtime/TextController.d.ts +0 -1
- package/runtime/TextController.js +5 -19
- package/runtime/tests/TestChildTasksListComponent.js +2 -2
- package/runtime/tests/TestForComponent.js +2 -2
- package/runtime/tests/TestForReinsertBindsInputParentComponent.js +2 -2
- package/runtime/tests/TestForTextMarkerCollisionNoTrackParentComponent.js +2 -2
- package/runtime/tests/TestForTextMarkerCollisionParentComponent.js +2 -2
- package/runtime/tests/TestForUnsubscribeNestedParentComponent.js +2 -2
- package/runtime/tests/TestGetterReactivityComponent.js +2 -2
- package/runtime/tests/TestHarness.d.ts +9 -0
- package/runtime/tests/TestHarness.js +29 -0
- package/runtime/tests/TestIfReinsertBindsInputChildComponent.js +2 -2
- package/runtime/tests/TestIfReinsertBindsInputParentComponent.js +2 -2
- package/runtime/tests/TestIfUnsubscribeNestedParentComponent.js +2 -2
- package/runtime/tests/TestInterpolationNestedPropertyComponentBase.js +2 -2
- package/runtime/tests/TestLateDefineForComponent.js +2 -2
- package/runtime/tests/TestNullInputTextComponent.js +2 -2
- package/runtime/tests/TestOutputBindingParentComponent.js +2 -2
- package/runtime/tests/TestParentBindsTasksComponent.js +2 -2
- package/runtime/tests/TestSwitchReinsertBindsInputChildComponent.js +2 -2
- package/runtime/tests/TestSwitchReinsertBindsInputParentComponent.js +2 -2
- package/runtime/tests/TestSwitchUnsubscribeNestedParentComponent.js +2 -2
- package/runtime/tests/TestTemplateNestedMarkersComponent.js +2 -2
- package/runtime/tests/TestUnsubscribeNestedGrandchildComponent.js +2 -2
- package/runtime/tests/createPipeUnwrapTestComponent.js +2 -2
- package/runtime/tests/createPropertyUnwrapNativeTestComponents.d.ts +4 -0
- package/runtime/tests/createPropertyUnwrapNativeTestComponents.js +23 -0
- package/runtime/tests/createTestInterpolationPipeComponent.js +2 -2
- package/runtime/tests/createTestInterpolationPipeWithArgsComponent.js +2 -2
- package/runtime/tests/typeguards.d.ts +21 -0
- package/runtime/tests/typeguards.js +48 -0
- package/utils/Property.d.ts +8 -2
- package/utils/Property.js +48 -8
- package/utils/DomUtils.d.ts +0 -3
- package/utils/DomUtils.js +0 -6
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { getPipeTransform } from '../decorators/Pipe.js';
|
|
2
|
-
import { DomUtils } from '../utils/DomUtils.js';
|
|
3
2
|
import { Property } from '../utils/Property.js';
|
|
4
|
-
import { Publisher } from '../utils/Publisher.js';
|
|
5
3
|
import { FluffBase } from './FluffBase.js';
|
|
4
|
+
import { MarkerManager } from './MarkerManager.js';
|
|
6
5
|
import { getScope } from './ScopeRegistry.js';
|
|
7
6
|
export class FluffElement extends FluffBase {
|
|
8
7
|
__pipes = {};
|
|
9
8
|
_shadowRoot;
|
|
10
9
|
_subscriptions = [];
|
|
11
10
|
_initialized = false;
|
|
11
|
+
_pendingInit = false;
|
|
12
12
|
_markerManager = null;
|
|
13
|
-
|
|
13
|
+
_markerConfigEntries = null;
|
|
14
14
|
_MarkerManagerClass = null;
|
|
15
15
|
constructor() {
|
|
16
16
|
super();
|
|
@@ -18,6 +18,16 @@ export class FluffElement extends FluffBase {
|
|
|
18
18
|
}
|
|
19
19
|
connectedCallback() {
|
|
20
20
|
if (!this._initialized) {
|
|
21
|
+
if (!FluffBase.__areExpressionsReady()) {
|
|
22
|
+
if (!this._pendingInit) {
|
|
23
|
+
this._pendingInit = true;
|
|
24
|
+
FluffBase.__addPendingInit(() => {
|
|
25
|
+
this._pendingInit = false;
|
|
26
|
+
this.connectedCallback();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
21
31
|
const scopeId = this.getAttribute('data-fluff-scope-id');
|
|
22
32
|
if (scopeId && !this.__parentScope) {
|
|
23
33
|
this.__parentScope = getScope(scopeId);
|
|
@@ -73,12 +83,10 @@ export class FluffElement extends FluffBase {
|
|
|
73
83
|
this.__processBindingsOnElement(el, scope, subscriptions);
|
|
74
84
|
}
|
|
75
85
|
__setupBindings() {
|
|
86
|
+
this.__initializeMarkers(MarkerManager);
|
|
76
87
|
this.__processBindings();
|
|
77
88
|
this.__initializeMarkersInternal();
|
|
78
89
|
}
|
|
79
|
-
__addSubscription(sub) {
|
|
80
|
-
this._subscriptions.push(sub);
|
|
81
|
-
}
|
|
82
90
|
__pipe(name, value, ...args) {
|
|
83
91
|
const pipe = this.__pipes[name];
|
|
84
92
|
if (!pipe) {
|
|
@@ -93,102 +101,47 @@ export class FluffElement extends FluffBase {
|
|
|
93
101
|
__getShadowRoot() {
|
|
94
102
|
return this._shadowRoot;
|
|
95
103
|
}
|
|
96
|
-
|
|
97
|
-
|
|
104
|
+
__createProp(name, options) {
|
|
105
|
+
const prop = new Property(options);
|
|
106
|
+
Object.defineProperty(this, name, {
|
|
107
|
+
get() {
|
|
108
|
+
return prop.getValue();
|
|
109
|
+
},
|
|
110
|
+
set(v) {
|
|
111
|
+
prop.setValue(v);
|
|
112
|
+
},
|
|
113
|
+
enumerable: true,
|
|
114
|
+
configurable: true
|
|
115
|
+
});
|
|
116
|
+
return prop;
|
|
117
|
+
}
|
|
118
|
+
__defineClassHostBinding(name, className, privateName) {
|
|
119
|
+
Object.defineProperty(this, name, {
|
|
120
|
+
get: () => Boolean(Reflect.get(this, privateName)),
|
|
121
|
+
set: (v) => {
|
|
122
|
+
Reflect.set(this, privateName, v);
|
|
123
|
+
if (v) {
|
|
124
|
+
this.classList.add(className);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
this.classList.remove(className);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
enumerable: true,
|
|
131
|
+
configurable: true
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
__setMarkerConfigs(entries) {
|
|
135
|
+
this._markerConfigEntries = entries;
|
|
98
136
|
}
|
|
99
137
|
__initializeMarkers(MarkerManagerClass) {
|
|
100
138
|
this._MarkerManagerClass = MarkerManagerClass;
|
|
101
139
|
}
|
|
102
140
|
__initializeMarkersInternal() {
|
|
103
|
-
if (!this.
|
|
141
|
+
if (!this._markerConfigEntries || !this._MarkerManagerClass)
|
|
104
142
|
return;
|
|
105
143
|
this._markerManager = new this._MarkerManagerClass(this, this._shadowRoot);
|
|
106
|
-
this._markerManager.initializeFromConfig(this.
|
|
107
|
-
}
|
|
108
|
-
__getElement(id) {
|
|
109
|
-
return this._shadowRoot.querySelector(`[data-lid="${id}"]`);
|
|
110
|
-
}
|
|
111
|
-
__setText(id, text) {
|
|
112
|
-
const el = this.__getElement(id);
|
|
113
|
-
if (el)
|
|
114
|
-
el.textContent = text;
|
|
115
|
-
}
|
|
116
|
-
__bindText(id, getter) {
|
|
117
|
-
const el = this.__getElement(id);
|
|
118
|
-
if (!el)
|
|
119
|
-
return;
|
|
120
|
-
const expr = el.getAttribute('data-text-bind') ?? '';
|
|
121
|
-
const propMatch = /this\.([a-zA-Z_][a-zA-Z0-9_]*)/.exec(expr);
|
|
122
|
-
const update = () => {
|
|
123
|
-
try {
|
|
124
|
-
el.textContent = getter();
|
|
125
|
-
}
|
|
126
|
-
catch {
|
|
127
|
-
el.textContent = '';
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
if (propMatch) {
|
|
131
|
-
const [, propName] = propMatch;
|
|
132
|
-
const reactiveProp = this.__getReactiveProp(propName);
|
|
133
|
-
if (reactiveProp) {
|
|
134
|
-
this.__bindPropertyChange(reactiveProp, update);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
update();
|
|
138
|
-
}
|
|
139
|
-
__setProperty(id, prop, value) {
|
|
140
|
-
const el = this.__getElement(id);
|
|
141
|
-
if (this.isHTMLElement(el)) {
|
|
142
|
-
if (prop in el) {
|
|
143
|
-
Reflect.set(el, prop, value);
|
|
144
|
-
}
|
|
145
|
-
else {
|
|
146
|
-
el.setAttribute(prop, String(value));
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
__addClass(id, className) {
|
|
151
|
-
const el = this.__getElement(id);
|
|
152
|
-
if (this.isHTMLElement(el))
|
|
153
|
-
el.classList.add(className);
|
|
154
|
-
}
|
|
155
|
-
__removeClass(id, className) {
|
|
156
|
-
const el = this.__getElement(id);
|
|
157
|
-
if (this.isHTMLElement(el))
|
|
158
|
-
el.classList.remove(className);
|
|
159
|
-
}
|
|
160
|
-
__bindEvent(id, event, handler) {
|
|
161
|
-
const el = this.__getElement(id);
|
|
162
|
-
if (el) {
|
|
163
|
-
el.addEventListener(event, handler);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
__bindPropertyChange(prop, callback) {
|
|
167
|
-
const sub = prop.onChange.subscribe(callback);
|
|
168
|
-
this._subscriptions.push(sub);
|
|
169
|
-
const currentVal = prop.getValue();
|
|
170
|
-
if (currentVal !== null) {
|
|
171
|
-
callback(currentVal);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
__connectProperties(source, target) {
|
|
175
|
-
const sub = source.onChange.subscribe((val) => {
|
|
176
|
-
target.setValue(val, true);
|
|
177
|
-
});
|
|
178
|
-
this._subscriptions.push(sub);
|
|
179
|
-
const currentVal = source.getValue();
|
|
180
|
-
if (currentVal !== null) {
|
|
181
|
-
target.setValue(currentVal, true);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
__connectOutput(source, handler) {
|
|
185
|
-
const sub = source.subscribe(handler);
|
|
186
|
-
this._subscriptions.push(sub);
|
|
187
|
-
}
|
|
188
|
-
__bindOutput(id, outputName, handler) {
|
|
189
|
-
const el = this.__getElement(id);
|
|
190
|
-
if (el)
|
|
191
|
-
this.__bindOutputOnElement(el, outputName, handler);
|
|
144
|
+
this._markerManager.initializeFromConfig(this._markerConfigEntries);
|
|
192
145
|
}
|
|
193
146
|
__setChildProperty(el, propName, value) {
|
|
194
147
|
if (el instanceof HTMLElement && el.hasAttribute('x-fluff-component')) {
|
|
@@ -202,47 +155,6 @@ export class FluffElement extends FluffBase {
|
|
|
202
155
|
}
|
|
203
156
|
super.__setChildProperty(el, propName, value);
|
|
204
157
|
}
|
|
205
|
-
__bindToChild(id, propName, value) {
|
|
206
|
-
const el = this.__getElement(id);
|
|
207
|
-
if (!el)
|
|
208
|
-
return;
|
|
209
|
-
this.__setChildPropertyDeferred(el, propName, value);
|
|
210
|
-
}
|
|
211
|
-
__setChildPropertyDeferred(el, propName, value) {
|
|
212
|
-
if (Reflect.get(el, propName) !== undefined) {
|
|
213
|
-
this.__setChildProperty(el, propName, value);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (DomUtils.isCustomElement(el)) {
|
|
217
|
-
this.__whenDefined(el.tagName.toLowerCase(), () => {
|
|
218
|
-
this.__setChildProperty(el, propName, value);
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
this.__setChildProperty(el, propName, value);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
__bindOutputOnElement(el, outputName, handler) {
|
|
226
|
-
const maybeOutput = Reflect.get(el, outputName);
|
|
227
|
-
if (maybeOutput instanceof Publisher) {
|
|
228
|
-
this.__connectOutput(maybeOutput, handler);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
if (DomUtils.isCustomElement(el)) {
|
|
232
|
-
this.__whenDefined(el.tagName.toLowerCase(), () => {
|
|
233
|
-
const innerOutput = Reflect.get(el, outputName);
|
|
234
|
-
if (innerOutput instanceof Publisher) {
|
|
235
|
-
this.__connectOutput(innerOutput, handler);
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
el.addEventListener(outputName, handler);
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
el.addEventListener(outputName, handler);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
158
|
__getReactivePropFromScope(propName, scope) {
|
|
247
159
|
const key = `__${propName}`;
|
|
248
160
|
if (key in scope.host) {
|
|
@@ -286,20 +198,7 @@ export class FluffElement extends FluffBase {
|
|
|
286
198
|
}
|
|
287
199
|
Reflect.deleteProperty(this, '__pendingProps');
|
|
288
200
|
}
|
|
289
|
-
isHTMLElement(el) {
|
|
290
|
-
return el !== null;
|
|
291
|
-
}
|
|
292
201
|
isRecord(value) {
|
|
293
202
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
294
203
|
}
|
|
295
|
-
__getReactiveProp(propName) {
|
|
296
|
-
const key = `__${propName}`;
|
|
297
|
-
if (key in this) {
|
|
298
|
-
const candidate = Reflect.get(this, key);
|
|
299
|
-
if (candidate instanceof Property) {
|
|
300
|
-
return candidate;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return undefined;
|
|
304
|
-
}
|
|
305
204
|
}
|
package/runtime/ForController.js
CHANGED
package/runtime/IfController.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { BreakMarkerConfig } from '../interfaces/BreakMarkerConfig.js';
|
|
2
|
-
import type { FluffHostElement } from '../interfaces/FluffHostElement.js';
|
|
3
2
|
import type { ForMarkerConfig } from '../interfaces/ForMarkerConfig.js';
|
|
4
3
|
import type { IfMarkerConfig } from '../interfaces/IfMarkerConfig.js';
|
|
5
4
|
import type { MarkerConfig } from '../interfaces/MarkerConfig.js';
|
|
6
5
|
import type { SwitchMarkerConfig } from '../interfaces/SwitchMarkerConfig.js';
|
|
7
6
|
import type { TextMarkerConfig } from '../interfaces/TextMarkerConfig.js';
|
|
8
7
|
export declare class MarkerConfigGuards {
|
|
9
|
-
static isFluffHostElement(el: Element): el is FluffHostElement;
|
|
10
8
|
static isIfConfig(config: MarkerConfig): config is IfMarkerConfig;
|
|
11
9
|
static isForConfig(config: MarkerConfig): config is ForMarkerConfig;
|
|
12
10
|
static isSwitchConfig(config: MarkerConfig): config is SwitchMarkerConfig;
|
|
@@ -2,7 +2,6 @@ import type { FluffHostElement } from '../interfaces/FluffHostElement.js';
|
|
|
2
2
|
import type { PropertyChain } from '../interfaces/PropertyChain.js';
|
|
3
3
|
import type { RenderContext } from '../interfaces/RenderContext.js';
|
|
4
4
|
import type { Subscription } from '../interfaces/Subscription.js';
|
|
5
|
-
import { Property } from '../utils/Property.js';
|
|
6
5
|
import { type Scope } from './ScopeRegistry.js';
|
|
7
6
|
interface MarkerManagerLike {
|
|
8
7
|
getController: (id: number, startMarker: Comment) => MarkerController | undefined;
|
|
@@ -27,22 +26,15 @@ export declare abstract class MarkerController {
|
|
|
27
26
|
cleanup(): void;
|
|
28
27
|
updateRenderContext(renderContext?: RenderContext): void;
|
|
29
28
|
protected evaluateExpr(exprId: number): unknown;
|
|
30
|
-
private getCompiledExprFn;
|
|
31
29
|
protected getScope(): Scope;
|
|
32
30
|
protected collectLocalsFromScope(scope: Scope): Record<string, unknown>;
|
|
33
|
-
protected
|
|
34
|
-
private subscribeToPropertyChain;
|
|
35
|
-
private subscribeToNestedChain;
|
|
36
|
-
protected getReactivePropFromScope(propName: string, scope: Scope): Property<unknown> | undefined;
|
|
31
|
+
protected subscribeAndRun(deps: PropertyChain[], callback: () => void): void;
|
|
37
32
|
protected createChildScope(locals: Record<string, unknown>): Scope;
|
|
38
33
|
protected clearContentBetweenMarkersWithCleanup(bindingsSubscriptions: Subscription[]): void;
|
|
39
34
|
protected insertBeforeEndMarker(node: Node): void;
|
|
40
35
|
protected refreshParentBindings(): void;
|
|
41
|
-
protected processBindingsOnElement(el: HTMLElement, scope: Scope): void;
|
|
42
|
-
protected processBindingsOnElementWithSubscriptions(el: HTMLElement, scope: Scope, subscriptions: Subscription[]): void;
|
|
43
36
|
private __getFluffElementHost;
|
|
44
37
|
protected setScopeOnChildren(node: Node, scope: Scope, renderContext?: RenderContext, markerManager?: MarkerManagerLike, bindingsSubscriptions?: Subscription[]): void;
|
|
45
|
-
protected insertAndScopeTemplateContent(content: Node, nodes: Node[], scope: Scope, renderContext: RenderContext | undefined, markerManager: MarkerManagerLike | undefined, bindingsSubscriptions: Subscription[]): void;
|
|
46
38
|
protected cloneAndInsertTemplate(template: HTMLTemplateElement, context: Record<string, unknown>, renderContext: RenderContext | undefined, bindingsSubscriptions: Subscription[]): void;
|
|
47
39
|
protected processBindingsOnNode(node: HTMLElement, scope: Scope, bindingsSubscriptions?: Subscription[]): void;
|
|
48
40
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { DomUtils } from '../utils/DomUtils.js';
|
|
2
|
-
import { Property } from '../utils/Property.js';
|
|
3
1
|
import { FluffBase } from './FluffBase.js';
|
|
4
2
|
import { FluffElement } from './FluffElement.js';
|
|
5
|
-
import { MarkerConfigGuards } from './MarkerConfigGuards.js';
|
|
6
3
|
import { registerScope } from './ScopeRegistry.js';
|
|
4
|
+
const MARKER_REGEX = /^fluff:(if|for|switch|text|break):(\d+)$/;
|
|
7
5
|
export class MarkerController {
|
|
8
6
|
id;
|
|
9
7
|
startMarker;
|
|
@@ -44,21 +42,20 @@ export class MarkerController {
|
|
|
44
42
|
evaluateExpr(exprId) {
|
|
45
43
|
const scope = this.getScope();
|
|
46
44
|
const allLocals = this.collectLocalsFromScope(scope);
|
|
47
|
-
|
|
45
|
+
if (scope.host.__evaluateExpr) {
|
|
46
|
+
return scope.host.__evaluateExpr(exprId, allLocals);
|
|
47
|
+
}
|
|
48
|
+
const fn = FluffBase.__e[exprId];
|
|
49
|
+
if (typeof fn !== 'function') {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
48
52
|
try {
|
|
49
53
|
return fn(scope.host, allLocals);
|
|
50
54
|
}
|
|
51
|
-
catch
|
|
55
|
+
catch {
|
|
52
56
|
return undefined;
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
|
-
getCompiledExprFn(exprId) {
|
|
56
|
-
const fn = FluffBase.__e[exprId];
|
|
57
|
-
if (typeof fn !== 'function') {
|
|
58
|
-
throw new Error(`Missing compiled expression function for exprId ${exprId}`);
|
|
59
|
-
}
|
|
60
|
-
return fn;
|
|
61
|
-
}
|
|
62
59
|
getScope() {
|
|
63
60
|
if (this.parentScope) {
|
|
64
61
|
return this.parentScope;
|
|
@@ -79,114 +76,18 @@ export class MarkerController {
|
|
|
79
76
|
Object.assign(result, scope.locals);
|
|
80
77
|
return result;
|
|
81
78
|
}
|
|
82
|
-
|
|
79
|
+
subscribeAndRun(deps, callback) {
|
|
83
80
|
const scope = this.getScope();
|
|
84
|
-
|
|
81
|
+
const filteredDeps = deps.filter(dep => {
|
|
85
82
|
if (Array.isArray(dep)) {
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
if (dep.startsWith('['))
|
|
90
|
-
continue;
|
|
91
|
-
const reactiveProp = this.getReactivePropFromScope(dep, scope);
|
|
92
|
-
if (reactiveProp) {
|
|
93
|
-
const sub = reactiveProp.onChange.subscribe(callback);
|
|
94
|
-
this.subscriptions.push(sub);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
subscribeToPropertyChain(chain, scope, callback) {
|
|
100
|
-
if (chain.length === 0)
|
|
101
|
-
return;
|
|
102
|
-
const [first, ...rest] = chain;
|
|
103
|
-
if (first.startsWith('['))
|
|
104
|
-
return;
|
|
105
|
-
const reactiveProp = this.getReactivePropFromScope(first, scope);
|
|
106
|
-
if (reactiveProp) {
|
|
107
|
-
if (rest.length === 0) {
|
|
108
|
-
this.subscriptions.push(reactiveProp.onChange.subscribe(callback));
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
let nestedSubs = [];
|
|
112
|
-
const resubscribeNested = () => {
|
|
113
|
-
for (const sub of nestedSubs) {
|
|
114
|
-
sub.unsubscribe();
|
|
115
|
-
}
|
|
116
|
-
nestedSubs = [];
|
|
117
|
-
const currentValue = reactiveProp.getValue();
|
|
118
|
-
if (currentValue !== null && currentValue !== undefined) {
|
|
119
|
-
this.subscribeToNestedChain(currentValue, rest, callback, (sub) => {
|
|
120
|
-
nestedSubs.push(sub);
|
|
121
|
-
this.subscriptions.push(sub);
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
callback();
|
|
125
|
-
};
|
|
126
|
-
this.subscriptions.push(reactiveProp.onChange.subscribe(resubscribeNested));
|
|
127
|
-
const currentValue = reactiveProp.getValue();
|
|
128
|
-
if (currentValue !== null && currentValue !== undefined) {
|
|
129
|
-
this.subscribeToNestedChain(currentValue, rest, callback, (sub) => {
|
|
130
|
-
nestedSubs.push(sub);
|
|
131
|
-
this.subscriptions.push(sub);
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
subscribeToNestedChain(obj, chain, callback, addSub) {
|
|
138
|
-
if (chain.length === 0 || obj === null || obj === undefined)
|
|
139
|
-
return;
|
|
140
|
-
const [first, ...rest] = chain;
|
|
141
|
-
const prop = Reflect.get(obj, first);
|
|
142
|
-
if (prop instanceof Property) {
|
|
143
|
-
if (rest.length === 0) {
|
|
144
|
-
addSub(prop.onChange.subscribe(callback));
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
let nestedSubs = [];
|
|
148
|
-
const resubscribeNested = () => {
|
|
149
|
-
for (const sub of nestedSubs) {
|
|
150
|
-
sub.unsubscribe();
|
|
151
|
-
}
|
|
152
|
-
nestedSubs = [];
|
|
153
|
-
const currentValue = prop.getValue();
|
|
154
|
-
if (currentValue !== null && currentValue !== undefined) {
|
|
155
|
-
this.subscribeToNestedChain(currentValue, rest, callback, (sub) => {
|
|
156
|
-
nestedSubs.push(sub);
|
|
157
|
-
addSub(sub);
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
callback();
|
|
161
|
-
};
|
|
162
|
-
addSub(prop.onChange.subscribe(resubscribeNested));
|
|
163
|
-
const currentValue = prop.getValue();
|
|
164
|
-
if (currentValue !== null && currentValue !== undefined) {
|
|
165
|
-
this.subscribeToNestedChain(currentValue, rest, callback, (sub) => {
|
|
166
|
-
nestedSubs.push(sub);
|
|
167
|
-
addSub(sub);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
83
|
+
return dep.length > 0 && !dep[0].startsWith('[');
|
|
170
84
|
}
|
|
85
|
+
return !dep.startsWith('[');
|
|
86
|
+
});
|
|
87
|
+
if (scope.host.__subscribeToExpression) {
|
|
88
|
+
scope.host.__subscribeToExpression(filteredDeps, scope, callback, this.subscriptions);
|
|
171
89
|
}
|
|
172
|
-
|
|
173
|
-
this.subscribeToNestedChain(prop, rest, callback, addSub);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
getReactivePropFromScope(propName, scope) {
|
|
177
|
-
if (propName in scope.locals) {
|
|
178
|
-
return undefined;
|
|
179
|
-
}
|
|
180
|
-
if (scope.parent) {
|
|
181
|
-
return this.getReactivePropFromScope(propName, scope.parent);
|
|
182
|
-
}
|
|
183
|
-
if (MarkerConfigGuards.isFluffHostElement(scope.host) && scope.host.__getReactiveProp) {
|
|
184
|
-
const reactiveProp = scope.host.__getReactiveProp(propName);
|
|
185
|
-
if (reactiveProp instanceof Property) {
|
|
186
|
-
return reactiveProp;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return undefined;
|
|
90
|
+
callback();
|
|
190
91
|
}
|
|
191
92
|
createChildScope(locals) {
|
|
192
93
|
return {
|
|
@@ -207,7 +108,7 @@ export class MarkerController {
|
|
|
207
108
|
while (current && current !== this.endMarker) {
|
|
208
109
|
const next = current.nextSibling;
|
|
209
110
|
if (current instanceof Comment) {
|
|
210
|
-
const markerMatch =
|
|
111
|
+
const markerMatch = MARKER_REGEX.exec(current.data);
|
|
211
112
|
if (markerMatch && this.markerManager?.cleanupController) {
|
|
212
113
|
const markerId = parseInt(markerMatch[2], 10);
|
|
213
114
|
this.markerManager.cleanupController(markerId, current);
|
|
@@ -239,24 +140,12 @@ export class MarkerController {
|
|
|
239
140
|
const scope = this.getScope();
|
|
240
141
|
fluffHost.__processBindingsOnElementPublic(parent, scope);
|
|
241
142
|
}
|
|
242
|
-
processBindingsOnElement(el, scope) {
|
|
243
|
-
const fluffHost = this.__getFluffElementHost();
|
|
244
|
-
if (!fluffHost)
|
|
245
|
-
return;
|
|
246
|
-
fluffHost.__processBindingsOnElementPublic(el, scope);
|
|
247
|
-
}
|
|
248
|
-
processBindingsOnElementWithSubscriptions(el, scope, subscriptions) {
|
|
249
|
-
const fluffHost = this.__getFluffElementHost();
|
|
250
|
-
if (!fluffHost)
|
|
251
|
-
return;
|
|
252
|
-
fluffHost.__processBindingsOnElementPublic(el, scope, subscriptions);
|
|
253
|
-
}
|
|
254
143
|
__getFluffElementHost() {
|
|
255
144
|
return this.host instanceof FluffElement ? this.host : null;
|
|
256
145
|
}
|
|
257
146
|
setScopeOnChildren(node, scope, renderContext, markerManager, bindingsSubscriptions) {
|
|
258
147
|
if (node instanceof Comment) {
|
|
259
|
-
const markerMatch =
|
|
148
|
+
const markerMatch = MARKER_REGEX.exec(node.data);
|
|
260
149
|
if (markerMatch && markerManager) {
|
|
261
150
|
const [, markerType, markerIdStr] = markerMatch;
|
|
262
151
|
const markerId = parseInt(markerIdStr, 10);
|
|
@@ -290,7 +179,7 @@ export class MarkerController {
|
|
|
290
179
|
node.__parentScope = scope;
|
|
291
180
|
this.processBindingsOnNode(node, scope, bindingsSubscriptions);
|
|
292
181
|
}
|
|
293
|
-
else if (node instanceof HTMLElement &&
|
|
182
|
+
else if (node instanceof HTMLElement && customElements.get(node.tagName.toLowerCase()) !== undefined) {
|
|
294
183
|
const scopeId = registerScope(scope);
|
|
295
184
|
node.setAttribute('data-fluff-scope-id', scopeId);
|
|
296
185
|
this.processBindingsOnNode(node, scope, bindingsSubscriptions);
|
|
@@ -302,12 +191,6 @@ export class MarkerController {
|
|
|
302
191
|
this.setScopeOnChildren(child, scope, renderContext, markerManager, bindingsSubscriptions);
|
|
303
192
|
}
|
|
304
193
|
}
|
|
305
|
-
insertAndScopeTemplateContent(content, nodes, scope, renderContext, markerManager, bindingsSubscriptions) {
|
|
306
|
-
this.insertBeforeEndMarker(content);
|
|
307
|
-
for (const node of nodes) {
|
|
308
|
-
this.setScopeOnChildren(node, scope, renderContext, markerManager, bindingsSubscriptions);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
194
|
cloneAndInsertTemplate(template, context, renderContext, bindingsSubscriptions) {
|
|
312
195
|
const content = template.content.cloneNode(true);
|
|
313
196
|
if (!(content instanceof DocumentFragment)) {
|
|
@@ -315,14 +198,15 @@ export class MarkerController {
|
|
|
315
198
|
}
|
|
316
199
|
const nodes = Array.from(content.childNodes);
|
|
317
200
|
const scope = this.createChildScope(context);
|
|
318
|
-
this.
|
|
201
|
+
this.insertBeforeEndMarker(content);
|
|
202
|
+
for (const node of nodes) {
|
|
203
|
+
this.setScopeOnChildren(node, scope, renderContext, this.markerManager, bindingsSubscriptions);
|
|
204
|
+
}
|
|
319
205
|
}
|
|
320
206
|
processBindingsOnNode(node, scope, bindingsSubscriptions) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
this.processBindingsOnElement(node, scope);
|
|
326
|
-
}
|
|
207
|
+
const fluffHost = this.__getFluffElementHost();
|
|
208
|
+
if (!fluffHost)
|
|
209
|
+
return;
|
|
210
|
+
fluffHost.__processBindingsOnElementPublic(node, scope, bindingsSubscriptions);
|
|
327
211
|
}
|
|
328
212
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MarkerConfigEntries } from './MarkerManagerInterface.js';
|
|
1
2
|
import type { MarkerController } from './MarkerController.js';
|
|
2
3
|
export declare class MarkerManager {
|
|
3
4
|
private readonly controllers;
|
|
@@ -5,7 +6,7 @@ export declare class MarkerManager {
|
|
|
5
6
|
private readonly host;
|
|
6
7
|
private readonly shadowRoot;
|
|
7
8
|
constructor(host: HTMLElement, shadowRoot: ShadowRoot);
|
|
8
|
-
initializeFromConfig(
|
|
9
|
+
initializeFromConfig(entries: MarkerConfigEntries): void;
|
|
9
10
|
ensureController(id: number, type: string, startMarker: Comment, endMarker: Comment | null): MarkerController | undefined;
|
|
10
11
|
getController(id: number, startMarker: Comment): MarkerController | undefined;
|
|
11
12
|
cleanupController(id: number, startMarker?: Comment): void;
|
package/runtime/MarkerManager.js
CHANGED
|
@@ -13,8 +13,7 @@ export class MarkerManager {
|
|
|
13
13
|
this.host = host;
|
|
14
14
|
this.shadowRoot = shadowRoot;
|
|
15
15
|
}
|
|
16
|
-
initializeFromConfig(
|
|
17
|
-
const entries = JSON.parse(configJson);
|
|
16
|
+
initializeFromConfig(entries) {
|
|
18
17
|
this.configs.clear();
|
|
19
18
|
for (const [id, config] of entries) {
|
|
20
19
|
this.configs.set(id, config);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { MarkerConfig } from '../interfaces/MarkerConfig.js';
|
|
2
|
+
export type MarkerConfigEntries = [number, MarkerConfig][];
|
|
1
3
|
export interface MarkerManagerInterface {
|
|
2
|
-
initializeFromConfig: (
|
|
4
|
+
initializeFromConfig: (entries: MarkerConfigEntries) => void;
|
|
3
5
|
cleanup: () => void;
|
|
4
6
|
}
|
|
@@ -6,5 +6,4 @@ export declare class TextController extends MarkerController {
|
|
|
6
6
|
constructor(id: number, startMarker: Comment, endMarker: Comment | null, host: HTMLElement, shadowRoot: ShadowRoot, config: TextMarkerConfig);
|
|
7
7
|
initialize(): void;
|
|
8
8
|
private formatValue;
|
|
9
|
-
private applyPipe;
|
|
10
9
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { getPipeTransform } from '../decorators/Pipe.js';
|
|
2
|
-
import { Property } from '../utils/Property.js';
|
|
3
1
|
import { MarkerController } from './MarkerController.js';
|
|
4
2
|
export class TextController extends MarkerController {
|
|
5
3
|
config;
|
|
@@ -15,18 +13,16 @@ export class TextController extends MarkerController {
|
|
|
15
13
|
const pipes = this.config.pipes ?? [];
|
|
16
14
|
const update = () => {
|
|
17
15
|
let result = this.evaluateExpr(this.config.exprId);
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
result = this.applyPipe(pipe.name, result, pipe.argExprIds);
|
|
16
|
+
if (this.host.__applyPipesForController) {
|
|
17
|
+
const scope = this.getScope();
|
|
18
|
+
const allLocals = this.collectLocalsFromScope(scope);
|
|
19
|
+
result = this.host.__applyPipesForController(result, pipes, allLocals);
|
|
23
20
|
}
|
|
24
21
|
if (this.textNode) {
|
|
25
22
|
this.textNode.textContent = this.formatValue(result);
|
|
26
23
|
}
|
|
27
24
|
};
|
|
28
|
-
this.
|
|
29
|
-
update();
|
|
25
|
+
this.subscribeAndRun(deps, update);
|
|
30
26
|
}
|
|
31
27
|
formatValue(result) {
|
|
32
28
|
if (result === null || result === undefined) {
|
|
@@ -43,14 +39,4 @@ export class TextController extends MarkerController {
|
|
|
43
39
|
}
|
|
44
40
|
return '';
|
|
45
41
|
}
|
|
46
|
-
applyPipe(name, value, args) {
|
|
47
|
-
const pipes = this.host.__pipes;
|
|
48
|
-
const pipeFn = pipes?.[name] ?? getPipeTransform(name);
|
|
49
|
-
if (!pipeFn) {
|
|
50
|
-
console.warn(`Pipe "${name}" not found`);
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
const evaluatedArgs = args.map(arg => this.evaluateExpr(arg));
|
|
54
|
-
return pipeFn(value, ...evaluatedArgs);
|
|
55
|
-
}
|
|
56
42
|
}
|