@fluffjs/fluff 0.4.5 → 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 +9 -11
- package/runtime/MarkerController.js +6 -3
- 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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { Subscription } from '../interfaces/Subscription.js';
|
|
2
|
-
import { Property } from '../utils/Property.js';
|
|
3
2
|
import { FluffBase } from './FluffBase.js';
|
|
4
3
|
import type { MarkerConfigEntries, MarkerManagerInterface } from './MarkerManagerInterface.js';
|
|
5
4
|
import { type Scope } from './ScopeRegistry.js';
|
|
6
5
|
export declare abstract class FluffElement extends FluffBase {
|
|
6
|
+
private static readonly __globalStyleSheets;
|
|
7
|
+
static __addGlobalStyleSheet(sheet: CSSStyleSheet): void;
|
|
7
8
|
protected __pipes: Record<string, (value: unknown, ...args: unknown[]) => unknown>;
|
|
8
9
|
protected readonly _shadowRoot: ShadowRoot;
|
|
9
10
|
private _subscriptions;
|
|
@@ -12,27 +13,21 @@ export declare abstract class FluffElement extends FluffBase {
|
|
|
12
13
|
private _markerManager;
|
|
13
14
|
private _markerConfigEntries;
|
|
14
15
|
private _MarkerManagerClass;
|
|
16
|
+
private _scopeId;
|
|
15
17
|
constructor();
|
|
16
18
|
connectedCallback(): void;
|
|
17
19
|
disconnectedCallback(): void;
|
|
20
|
+
private __destroyDirectives;
|
|
18
21
|
$watch: (_properties: string[], callback: (changed: string) => void) => Subscription;
|
|
19
22
|
__processBindingsOnElementPublic(el: Element, scope: Scope, subscriptions?: Subscription[]): void;
|
|
20
23
|
protected abstract __render(): void;
|
|
21
24
|
protected __setupBindings(): void;
|
|
22
|
-
|
|
25
|
+
private __instantiateDirectives;
|
|
23
26
|
protected __getPipeFn(name: string): ((value: unknown, ...args: unknown[]) => unknown) | undefined;
|
|
24
27
|
protected __getShadowRoot(): ShadowRoot;
|
|
25
|
-
protected __createProp<T>(nameOrIdx: string | number, options: T | {
|
|
26
|
-
initialValue: T;
|
|
27
|
-
[key: string]: unknown;
|
|
28
|
-
}): Property<T>;
|
|
29
|
-
protected __defineClassHostBinding(name: string, className: string, privateName: string): void;
|
|
30
28
|
protected __setMarkerConfigs(entries: MarkerConfigEntries): void;
|
|
31
29
|
protected __initializeMarkers(MarkerManagerClass: new (host: FluffElement, shadowRoot: ShadowRoot) => MarkerManagerInterface): void;
|
|
32
30
|
private __initializeMarkersInternal;
|
|
33
31
|
protected __setChildProperty(el: Element, propName: string, value: unknown): void;
|
|
34
|
-
protected __getReactivePropFromScope(propName: string, scope: Scope): Property<unknown> | undefined;
|
|
35
32
|
protected __processBindings(): void;
|
|
36
|
-
private __applyPendingProps;
|
|
37
|
-
private isRecord;
|
|
38
33
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { getDirectiveClass } from '../decorators/Directive.js';
|
|
1
2
|
import { getPipeTransform } from '../decorators/Pipe.js';
|
|
2
|
-
import { Property } from '../utils/Property.js';
|
|
3
3
|
import { FluffBase } from './FluffBase.js';
|
|
4
4
|
import { MarkerManager } from './MarkerManager.js';
|
|
5
|
-
import { getScope } from './ScopeRegistry.js';
|
|
5
|
+
import { getScope, unregisterScope } from './ScopeRegistry.js';
|
|
6
6
|
export class FluffElement extends FluffBase {
|
|
7
|
+
static __globalStyleSheets = [];
|
|
8
|
+
static __addGlobalStyleSheet(sheet) {
|
|
9
|
+
FluffElement.__globalStyleSheets.push(sheet);
|
|
10
|
+
}
|
|
7
11
|
__pipes = {};
|
|
8
12
|
_shadowRoot;
|
|
9
13
|
_subscriptions = [];
|
|
@@ -12,9 +16,13 @@ export class FluffElement extends FluffBase {
|
|
|
12
16
|
_markerManager = null;
|
|
13
17
|
_markerConfigEntries = null;
|
|
14
18
|
_MarkerManagerClass = null;
|
|
19
|
+
_scopeId = null;
|
|
15
20
|
constructor() {
|
|
16
21
|
super();
|
|
17
22
|
this._shadowRoot = this.attachShadow({ mode: 'open' });
|
|
23
|
+
if (FluffElement.__globalStyleSheets.length > 0) {
|
|
24
|
+
this._shadowRoot.adoptedStyleSheets = [...FluffElement.__globalStyleSheets];
|
|
25
|
+
}
|
|
18
26
|
}
|
|
19
27
|
connectedCallback() {
|
|
20
28
|
if (!this._initialized) {
|
|
@@ -28,9 +36,9 @@ export class FluffElement extends FluffBase {
|
|
|
28
36
|
}
|
|
29
37
|
return;
|
|
30
38
|
}
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
this.__parentScope = getScope(
|
|
39
|
+
this._scopeId = this.getAttribute('data-fluff-scope-id');
|
|
40
|
+
if (this._scopeId && !this.__parentScope) {
|
|
41
|
+
this.__parentScope = getScope(this._scopeId);
|
|
34
42
|
if (this.__parentScope) {
|
|
35
43
|
this.__loopContext = this.__parentScope.locals;
|
|
36
44
|
}
|
|
@@ -46,7 +54,8 @@ export class FluffElement extends FluffBase {
|
|
|
46
54
|
this.__applyPendingProps();
|
|
47
55
|
this.__render();
|
|
48
56
|
this.__setupBindings();
|
|
49
|
-
|
|
57
|
+
this.__initHostBindings();
|
|
58
|
+
if (this._scopeId) {
|
|
50
59
|
this.__processBindings();
|
|
51
60
|
}
|
|
52
61
|
this._initialized = true;
|
|
@@ -59,6 +68,7 @@ export class FluffElement extends FluffBase {
|
|
|
59
68
|
if ('onDestroy' in this && typeof this.onDestroy === 'function') {
|
|
60
69
|
this.onDestroy();
|
|
61
70
|
}
|
|
71
|
+
this.__destroyDirectives();
|
|
62
72
|
if (this._markerManager) {
|
|
63
73
|
this._markerManager.cleanup();
|
|
64
74
|
this._markerManager = null;
|
|
@@ -71,6 +81,22 @@ export class FluffElement extends FluffBase {
|
|
|
71
81
|
sub.unsubscribe();
|
|
72
82
|
}
|
|
73
83
|
this.__baseSubscriptions = [];
|
|
84
|
+
if (this._scopeId) {
|
|
85
|
+
unregisterScope(this._scopeId);
|
|
86
|
+
this._scopeId = null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
__destroyDirectives() {
|
|
90
|
+
const elements = this._shadowRoot.querySelectorAll('[data-fluff-directives]');
|
|
91
|
+
for (const el of Array.from(elements)) {
|
|
92
|
+
const directives = el.__fluffDirectives;
|
|
93
|
+
if (directives) {
|
|
94
|
+
for (const directive of directives) {
|
|
95
|
+
directive.destroy();
|
|
96
|
+
}
|
|
97
|
+
el.__fluffDirectives = undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
74
100
|
}
|
|
75
101
|
$watch = (_properties, callback) => {
|
|
76
102
|
callback('');
|
|
@@ -84,16 +110,31 @@ export class FluffElement extends FluffBase {
|
|
|
84
110
|
}
|
|
85
111
|
__setupBindings() {
|
|
86
112
|
this.__initializeMarkers(MarkerManager);
|
|
113
|
+
this.__instantiateDirectives();
|
|
87
114
|
this.__processBindings();
|
|
88
115
|
this.__initializeMarkersInternal();
|
|
89
116
|
}
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
117
|
+
__instantiateDirectives() {
|
|
118
|
+
const elements = this._shadowRoot.querySelectorAll('[data-fluff-directives]');
|
|
119
|
+
for (const el of Array.from(elements)) {
|
|
120
|
+
const directiveAttr = el.getAttribute('data-fluff-directives');
|
|
121
|
+
if (!directiveAttr)
|
|
122
|
+
continue;
|
|
123
|
+
const selectors = directiveAttr.split(',');
|
|
124
|
+
const directives = [];
|
|
125
|
+
for (const selector of selectors) {
|
|
126
|
+
const DirectiveClass = getDirectiveClass(selector.trim());
|
|
127
|
+
if (DirectiveClass && el instanceof HTMLElement) {
|
|
128
|
+
const instance = new DirectiveClass();
|
|
129
|
+
instance.__setHostElement(el);
|
|
130
|
+
directives.push(instance);
|
|
131
|
+
instance.initialize();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (directives.length > 0) {
|
|
135
|
+
el.__fluffDirectives = directives;
|
|
136
|
+
}
|
|
95
137
|
}
|
|
96
|
-
return pipe(value, ...args);
|
|
97
138
|
}
|
|
98
139
|
__getPipeFn(name) {
|
|
99
140
|
return this.__pipes[name] ?? getPipeTransform(name);
|
|
@@ -101,37 +142,6 @@ export class FluffElement extends FluffBase {
|
|
|
101
142
|
__getShadowRoot() {
|
|
102
143
|
return this._shadowRoot;
|
|
103
144
|
}
|
|
104
|
-
__createProp(nameOrIdx, options) {
|
|
105
|
-
const name = typeof nameOrIdx === 'number' ? FluffBase.__decodeString(nameOrIdx) : nameOrIdx;
|
|
106
|
-
const prop = new Property(options);
|
|
107
|
-
Object.defineProperty(this, name, {
|
|
108
|
-
get() {
|
|
109
|
-
return prop.getValue();
|
|
110
|
-
},
|
|
111
|
-
set(v) {
|
|
112
|
-
prop.setValue(v);
|
|
113
|
-
},
|
|
114
|
-
enumerable: true,
|
|
115
|
-
configurable: true
|
|
116
|
-
});
|
|
117
|
-
return prop;
|
|
118
|
-
}
|
|
119
|
-
__defineClassHostBinding(name, className, privateName) {
|
|
120
|
-
Object.defineProperty(this, name, {
|
|
121
|
-
get: () => Boolean(Reflect.get(this, privateName)),
|
|
122
|
-
set: (v) => {
|
|
123
|
-
Reflect.set(this, privateName, v);
|
|
124
|
-
if (v) {
|
|
125
|
-
this.classList.add(className);
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
this.classList.remove(className);
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
enumerable: true,
|
|
132
|
-
configurable: true
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
145
|
__setMarkerConfigs(entries) {
|
|
136
146
|
this._markerConfigEntries = entries;
|
|
137
147
|
}
|
|
@@ -155,19 +165,14 @@ export class FluffElement extends FluffBase {
|
|
|
155
165
|
}
|
|
156
166
|
}
|
|
157
167
|
super.__setChildProperty(el, propName, value);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return candidate;
|
|
168
|
+
const directives = el.__fluffDirectives;
|
|
169
|
+
if (directives) {
|
|
170
|
+
for (const directive of directives) {
|
|
171
|
+
if (propName in directive) {
|
|
172
|
+
Reflect.set(directive, propName, value);
|
|
173
|
+
}
|
|
165
174
|
}
|
|
166
175
|
}
|
|
167
|
-
if (scope.parent) {
|
|
168
|
-
return this.__getReactivePropFromScope(propName, scope.parent);
|
|
169
|
-
}
|
|
170
|
-
return undefined;
|
|
171
176
|
}
|
|
172
177
|
__processBindings() {
|
|
173
178
|
const elements = this._shadowRoot.querySelectorAll('[data-lid]');
|
|
@@ -179,27 +184,4 @@ export class FluffElement extends FluffBase {
|
|
|
179
184
|
this.__processBindingsOnElement(el, scope);
|
|
180
185
|
}
|
|
181
186
|
}
|
|
182
|
-
__applyPendingProps() {
|
|
183
|
-
const existing = Reflect.get(this, '__pendingProps');
|
|
184
|
-
if (!this.isRecord(existing)) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
for (const [propName, value] of Object.entries(existing)) {
|
|
188
|
-
console.log('apply-pending-prop', { propName, value, el: this.tagName });
|
|
189
|
-
const key = `__${propName}`;
|
|
190
|
-
if (key in this) {
|
|
191
|
-
const prop = Reflect.get(this, key);
|
|
192
|
-
if (prop instanceof Property) {
|
|
193
|
-
prop.setValue(value, true);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
else if (propName in this) {
|
|
197
|
-
Reflect.set(this, propName, value);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
Reflect.deleteProperty(this, '__pendingProps');
|
|
201
|
-
}
|
|
202
|
-
isRecord(value) {
|
|
203
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
204
|
-
}
|
|
205
187
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { MarkerManager } from './MarkerManager.js';
|
|
2
|
-
export { MarkerConfigGuards } from './MarkerConfigGuards.js';
|
|
3
2
|
export { MarkerController } from './MarkerController.js';
|
|
4
3
|
export { IfController } from './IfController.js';
|
|
5
4
|
export { ForController } from './ForController.js';
|
package/runtime/FluffMarkers.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { MarkerManager } from './MarkerManager.js';
|
|
2
|
-
export { MarkerConfigGuards } from './MarkerConfigGuards.js';
|
|
3
2
|
export { MarkerController } from './MarkerController.js';
|
|
4
3
|
export { IfController } from './IfController.js';
|
|
5
4
|
export { ForController } from './ForController.js';
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CompactForConfig } from './FluffBase.js';
|
|
2
2
|
import { MarkerController } from './MarkerController.js';
|
|
3
|
-
export declare class ForController extends MarkerController {
|
|
4
|
-
private readonly config;
|
|
3
|
+
export declare class ForController extends MarkerController<CompactForConfig> {
|
|
5
4
|
private itemTemplate;
|
|
6
5
|
private emptyTemplate;
|
|
7
|
-
private readonly bindingsSubscriptions;
|
|
8
|
-
constructor(id: number, startMarker: Comment, endMarker: Comment | null, host: HTMLElement, shadowRoot: ShadowRoot, config: ForMarkerConfig);
|
|
9
6
|
initialize(): void;
|
|
10
7
|
}
|
package/runtime/ForController.js
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
+
import { FluffBase } from './FluffBase.js';
|
|
1
2
|
import { MarkerController } from './MarkerController.js';
|
|
2
3
|
export class ForController extends MarkerController {
|
|
3
|
-
config;
|
|
4
4
|
itemTemplate = null;
|
|
5
5
|
emptyTemplate = null;
|
|
6
|
-
bindingsSubscriptions = [];
|
|
7
|
-
constructor(id, startMarker, endMarker, host, shadowRoot, config) {
|
|
8
|
-
super(id, startMarker, endMarker, host, shadowRoot);
|
|
9
|
-
this.config = config;
|
|
10
|
-
}
|
|
11
6
|
initialize() {
|
|
12
7
|
const hostTag = this.host.tagName.toLowerCase();
|
|
13
8
|
const templateId = `${hostTag}-${this.id}`;
|
|
14
9
|
this.itemTemplate = this.shadowRoot.querySelector(`template[data-fluff-tpl="${templateId}"]`);
|
|
15
10
|
this.emptyTemplate = this.shadowRoot.querySelector(`template[data-fluff-empty="${templateId}"]`);
|
|
16
|
-
|
|
11
|
+
// CompactForConfig: [1, iteratorNameIdx, iterableExprId, hasEmpty, deps, trackByNameIdx]
|
|
12
|
+
const [, iteratorIdx, iterableExprId, , compactDeps] = this.config;
|
|
13
|
+
const iterator = FluffBase.__s[iteratorIdx];
|
|
14
|
+
const deps = FluffBase.__decodeDeps(compactDeps) ?? [];
|
|
17
15
|
const update = () => {
|
|
18
16
|
this.clearContentBetweenMarkersWithCleanup(this.bindingsSubscriptions);
|
|
19
|
-
const items = this.evaluateExpr(
|
|
17
|
+
const items = this.evaluateExpr(iterableExprId);
|
|
20
18
|
if (!Array.isArray(items) || items.length === 0) {
|
|
21
19
|
if (this.emptyTemplate) {
|
|
22
20
|
this.cloneAndInsertTemplate(this.emptyTemplate, this.loopContext, undefined, this.bindingsSubscriptions);
|
|
@@ -32,7 +30,7 @@ export class ForController extends MarkerController {
|
|
|
32
30
|
if (renderContext.shouldBreak)
|
|
33
31
|
break;
|
|
34
32
|
const itemContext = {
|
|
35
|
-
...this.loopContext, [
|
|
33
|
+
...this.loopContext, [iterator]: items[i], $index: i
|
|
36
34
|
};
|
|
37
35
|
this.cloneAndInsertTemplate(this.itemTemplate, itemContext, renderContext, this.bindingsSubscriptions);
|
|
38
36
|
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CompactIfConfig } from './FluffBase.js';
|
|
2
2
|
import { MarkerController } from './MarkerController.js';
|
|
3
|
-
export declare class IfController extends MarkerController {
|
|
4
|
-
private readonly config;
|
|
3
|
+
export declare class IfController extends MarkerController<CompactIfConfig> {
|
|
5
4
|
private templates;
|
|
6
5
|
private currentBranchIndex;
|
|
7
|
-
private readonly bindingsSubscriptions;
|
|
8
|
-
constructor(id: number, startMarker: Comment, endMarker: Comment | null, host: HTMLElement, shadowRoot: ShadowRoot, config: IfMarkerConfig);
|
|
9
6
|
initialize(): void;
|
|
10
7
|
}
|
package/runtime/IfController.js
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
|
+
import { FluffBase } from './FluffBase.js';
|
|
1
2
|
import { MarkerController } from './MarkerController.js';
|
|
2
3
|
export class IfController extends MarkerController {
|
|
3
|
-
config;
|
|
4
4
|
templates = [];
|
|
5
5
|
currentBranchIndex = -1;
|
|
6
|
-
bindingsSubscriptions = [];
|
|
7
|
-
constructor(id, startMarker, endMarker, host, shadowRoot, config) {
|
|
8
|
-
super(id, startMarker, endMarker, host, shadowRoot);
|
|
9
|
-
this.config = config;
|
|
10
|
-
}
|
|
11
6
|
initialize() {
|
|
12
7
|
const hostTag = this.host.tagName.toLowerCase();
|
|
13
8
|
const templateIdPrefix = `${hostTag}-${this.id}-`;
|
|
14
9
|
this.templates = Array.from(this.shadowRoot.querySelectorAll(`template[data-fluff-branch^="${templateIdPrefix}"]`));
|
|
10
|
+
// CompactIfConfig: [0, branches[]] — branch = [exprId?, deps?] or [] for else
|
|
11
|
+
const [, branches] = this.config;
|
|
15
12
|
const allDeps = [];
|
|
16
|
-
for (const branch of
|
|
17
|
-
if (branch.
|
|
18
|
-
|
|
13
|
+
for (const branch of branches) {
|
|
14
|
+
if (branch.length > 1 && branch[1]) {
|
|
15
|
+
const decoded = FluffBase.__decodeDeps(branch[1]);
|
|
16
|
+
if (decoded)
|
|
17
|
+
allDeps.push(...decoded);
|
|
19
18
|
}
|
|
20
19
|
}
|
|
21
20
|
const update = () => {
|
|
22
21
|
let matchedIndex = -1;
|
|
23
|
-
for (let i = 0; i <
|
|
24
|
-
const branch =
|
|
25
|
-
if (branch.
|
|
22
|
+
for (let i = 0; i < branches.length; i++) {
|
|
23
|
+
const branch = branches[i];
|
|
24
|
+
if (branch.length === 0 || branch[0] === null) {
|
|
26
25
|
matchedIndex = i;
|
|
27
26
|
break;
|
|
28
27
|
}
|
|
29
|
-
const result = this.evaluateExpr(branch
|
|
28
|
+
const result = this.evaluateExpr(branch[0]);
|
|
30
29
|
if (result) {
|
|
31
30
|
matchedIndex = i;
|
|
32
31
|
break;
|
|
@@ -2,26 +2,25 @@ 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 { type CompactMarkerConfig } from './FluffBase.js';
|
|
6
|
+
import type { MarkerManagerInterface } from './MarkerManagerInterface.js';
|
|
5
7
|
import { type Scope } from './ScopeRegistry.js';
|
|
6
|
-
|
|
7
|
-
getController: (id: number, startMarker: Comment) => MarkerController | undefined;
|
|
8
|
-
ensureController?: (id: number, type: string, startMarker: Comment, endMarker: Comment | null) => MarkerController | undefined;
|
|
9
|
-
cleanupController?: (id: number, startMarker?: Comment) => void;
|
|
10
|
-
}
|
|
11
|
-
export declare abstract class MarkerController {
|
|
8
|
+
export declare abstract class MarkerController<TConfig extends CompactMarkerConfig = CompactMarkerConfig> {
|
|
12
9
|
protected readonly id: number;
|
|
13
10
|
protected readonly startMarker: Comment;
|
|
14
11
|
protected readonly endMarker: Comment | null;
|
|
12
|
+
protected readonly config: TConfig;
|
|
15
13
|
protected readonly subscriptions: Subscription[];
|
|
14
|
+
protected readonly bindingsSubscriptions: Subscription[];
|
|
16
15
|
protected readonly host: FluffHostElement;
|
|
17
16
|
protected readonly shadowRoot: ShadowRoot;
|
|
18
17
|
protected parentScope: Scope | undefined;
|
|
19
18
|
protected loopContext: Record<string, unknown>;
|
|
20
|
-
protected markerManager:
|
|
21
|
-
|
|
19
|
+
protected markerManager: MarkerManagerInterface | undefined;
|
|
20
|
+
constructor(id: number, startMarker: Comment, endMarker: Comment | null, host: FluffHostElement, shadowRoot: ShadowRoot, config: TConfig);
|
|
22
21
|
setParentScope(scope: Scope | undefined): void;
|
|
23
22
|
setLoopContext(context: Record<string, unknown>): void;
|
|
24
|
-
setMarkerManager(markerManager:
|
|
23
|
+
setMarkerManager(markerManager: MarkerManagerInterface): void;
|
|
25
24
|
abstract initialize(): void;
|
|
26
25
|
cleanup(): void;
|
|
27
26
|
updateRenderContext(renderContext?: RenderContext): void;
|
|
@@ -34,9 +33,8 @@ export declare abstract class MarkerController {
|
|
|
34
33
|
protected insertBeforeEndMarker(node: Node): void;
|
|
35
34
|
protected refreshParentBindings(): void;
|
|
36
35
|
private __getFluffElementHost;
|
|
37
|
-
protected setScopeOnChildren(node: Node, scope: Scope, renderContext?: RenderContext, markerManager?:
|
|
36
|
+
protected setScopeOnChildren(node: Node, scope: Scope, renderContext?: RenderContext, markerManager?: MarkerManagerInterface, bindingsSubscriptions?: Subscription[]): void;
|
|
38
37
|
protected cloneAndInsertTemplate(template: HTMLTemplateElement, context: Record<string, unknown>, renderContext: RenderContext | undefined, bindingsSubscriptions: Subscription[]): void;
|
|
39
38
|
private unwrapNamespaceWrapper;
|
|
40
39
|
protected processBindingsOnNode(node: Element, scope: Scope, bindingsSubscriptions?: Subscription[]): void;
|
|
41
40
|
}
|
|
42
|
-
export {};
|
|
@@ -6,16 +6,19 @@ export class MarkerController {
|
|
|
6
6
|
id;
|
|
7
7
|
startMarker;
|
|
8
8
|
endMarker;
|
|
9
|
+
config;
|
|
9
10
|
subscriptions = [];
|
|
11
|
+
bindingsSubscriptions = [];
|
|
10
12
|
host;
|
|
11
13
|
shadowRoot;
|
|
12
14
|
parentScope;
|
|
13
15
|
loopContext = {};
|
|
14
16
|
markerManager;
|
|
15
|
-
constructor(id, startMarker, endMarker, host, shadowRoot) {
|
|
17
|
+
constructor(id, startMarker, endMarker, host, shadowRoot, config) {
|
|
16
18
|
this.id = id;
|
|
17
19
|
this.startMarker = startMarker;
|
|
18
20
|
this.endMarker = endMarker;
|
|
21
|
+
this.config = config;
|
|
19
22
|
this.host = host;
|
|
20
23
|
this.shadowRoot = shadowRoot;
|
|
21
24
|
}
|
|
@@ -109,7 +112,7 @@ export class MarkerController {
|
|
|
109
112
|
const next = current.nextSibling;
|
|
110
113
|
if (current instanceof Comment) {
|
|
111
114
|
const markerMatch = MARKER_REGEX.exec(current.data);
|
|
112
|
-
if (markerMatch && this.markerManager
|
|
115
|
+
if (markerMatch && this.markerManager) {
|
|
113
116
|
const markerId = parseInt(markerMatch[2], 10);
|
|
114
117
|
this.markerManager.cleanupController(markerId, current);
|
|
115
118
|
}
|
|
@@ -152,7 +155,7 @@ export class MarkerController {
|
|
|
152
155
|
const endPattern = `/fluff:${markerType}:${markerId}`;
|
|
153
156
|
let controller = markerManager.getController(markerId, node);
|
|
154
157
|
const shouldInitialize = controller === undefined;
|
|
155
|
-
if (!controller
|
|
158
|
+
if (!controller) {
|
|
156
159
|
let endMarker = null;
|
|
157
160
|
let current = node.nextSibling;
|
|
158
161
|
while (current) {
|
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import type { MarkerConfigEntries } from './MarkerManagerInterface.js';
|
|
1
|
+
import type { MarkerConfigEntries, MarkerManagerInterface } from './MarkerManagerInterface.js';
|
|
2
2
|
import type { MarkerController } from './MarkerController.js';
|
|
3
|
-
export declare class MarkerManager {
|
|
3
|
+
export declare class MarkerManager implements MarkerManagerInterface {
|
|
4
4
|
private readonly controllers;
|
|
5
5
|
private readonly configs;
|
|
6
6
|
private readonly host;
|
|
7
7
|
private readonly shadowRoot;
|
|
8
8
|
constructor(host: HTMLElement, shadowRoot: ShadowRoot);
|
|
9
|
-
private decodeConfig;
|
|
10
|
-
private isMarkerConfig;
|
|
11
|
-
private isCompactMarkerConfig;
|
|
12
9
|
initializeFromConfig(entries: MarkerConfigEntries): void;
|
|
13
10
|
ensureController(id: number, type: string, startMarker: Comment, endMarker: Comment | null): MarkerController | undefined;
|
|
14
11
|
getController(id: number, startMarker: Comment): MarkerController | undefined;
|
package/runtime/MarkerManager.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { BreakController } from './BreakController.js';
|
|
2
|
-
import { FluffBase } from './FluffBase.js';
|
|
3
2
|
import { ForController } from './ForController.js';
|
|
4
3
|
import { IfController } from './IfController.js';
|
|
5
|
-
import { MarkerConfigGuards } from './MarkerConfigGuards.js';
|
|
6
4
|
import { SwitchController } from './SwitchController.js';
|
|
7
5
|
import { TextController } from './TextController.js';
|
|
6
|
+
const MARKER_TYPES = ['if', 'for', 'text', 'switch', 'break'];
|
|
8
7
|
export class MarkerManager {
|
|
9
8
|
controllers = new Map();
|
|
10
9
|
configs = new Map();
|
|
@@ -14,46 +13,16 @@ export class MarkerManager {
|
|
|
14
13
|
this.host = host;
|
|
15
14
|
this.shadowRoot = shadowRoot;
|
|
16
15
|
}
|
|
17
|
-
decodeConfig(config) {
|
|
18
|
-
if (this.isCompactMarkerConfig(config)) {
|
|
19
|
-
const decoded = FluffBase.__decodeMarkerConfig(config);
|
|
20
|
-
if (this.isMarkerConfig(decoded)) {
|
|
21
|
-
return decoded;
|
|
22
|
-
}
|
|
23
|
-
throw new Error('Decoded marker config is invalid');
|
|
24
|
-
}
|
|
25
|
-
if (!Array.isArray(config)) {
|
|
26
|
-
return config;
|
|
27
|
-
}
|
|
28
|
-
throw new Error('Invalid marker config format');
|
|
29
|
-
}
|
|
30
|
-
isMarkerConfig(value) {
|
|
31
|
-
if (typeof value !== 'object' || value === null)
|
|
32
|
-
return false;
|
|
33
|
-
if (!('type' in value))
|
|
34
|
-
return false;
|
|
35
|
-
const typeVal = value.type;
|
|
36
|
-
return typeof typeVal === 'string' && ['if', 'for', 'text', 'switch', 'break'].includes(typeVal);
|
|
37
|
-
}
|
|
38
|
-
isCompactMarkerConfig(config) {
|
|
39
|
-
if (!Array.isArray(config))
|
|
40
|
-
return false;
|
|
41
|
-
if (config.length === 0)
|
|
42
|
-
return false;
|
|
43
|
-
const firstElement = config[0];
|
|
44
|
-
return typeof firstElement === 'number' && firstElement >= 0 && firstElement <= 4;
|
|
45
|
-
}
|
|
46
16
|
initializeFromConfig(entries) {
|
|
47
17
|
this.configs.clear();
|
|
48
|
-
for (const [id,
|
|
49
|
-
const config = this.decodeConfig(rawConfig);
|
|
18
|
+
for (const [id, config] of entries) {
|
|
50
19
|
this.configs.set(id, config);
|
|
51
20
|
}
|
|
52
21
|
for (const [id] of entries) {
|
|
53
22
|
const config = this.configs.get(id);
|
|
54
23
|
if (!config)
|
|
55
24
|
continue;
|
|
56
|
-
const { startMarker, endMarker } = this.findMarkers(id, config
|
|
25
|
+
const { startMarker, endMarker } = this.findMarkers(id, MARKER_TYPES[config[0]]);
|
|
57
26
|
if (!startMarker) {
|
|
58
27
|
continue;
|
|
59
28
|
}
|
|
@@ -77,7 +46,7 @@ export class MarkerManager {
|
|
|
77
46
|
return existing;
|
|
78
47
|
}
|
|
79
48
|
const config = this.configs.get(id);
|
|
80
|
-
if (config
|
|
49
|
+
if (!config || MARKER_TYPES[config[0]] !== type) {
|
|
81
50
|
return undefined;
|
|
82
51
|
}
|
|
83
52
|
const controller = this.createController(id, startMarker, endMarker, config);
|
|
@@ -149,21 +118,13 @@ export class MarkerManager {
|
|
|
149
118
|
return { startMarker, endMarker };
|
|
150
119
|
}
|
|
151
120
|
createController(id, startMarker, endMarker, config) {
|
|
152
|
-
|
|
153
|
-
return new IfController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return new
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return new SwitchController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
|
|
160
|
-
}
|
|
161
|
-
if (MarkerConfigGuards.isTextConfig(config)) {
|
|
162
|
-
return new TextController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
|
|
163
|
-
}
|
|
164
|
-
if (MarkerConfigGuards.isBreakConfig(config)) {
|
|
165
|
-
return new BreakController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
|
|
121
|
+
switch (config[0]) {
|
|
122
|
+
case 0: return new IfController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
|
|
123
|
+
case 1: return new ForController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
|
|
124
|
+
case 2: return new TextController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
|
|
125
|
+
case 3: return new SwitchController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
|
|
126
|
+
case 4: return new BreakController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
|
|
127
|
+
default: return null;
|
|
166
128
|
}
|
|
167
|
-
return null;
|
|
168
129
|
}
|
|
169
130
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import type { MarkerConfig } from '../interfaces/MarkerConfig.js';
|
|
2
1
|
import type { CompactMarkerConfig } from './FluffBase.js';
|
|
3
|
-
|
|
2
|
+
import type { MarkerController } from './MarkerController.js';
|
|
3
|
+
export type MarkerConfigEntries = [number, CompactMarkerConfig][];
|
|
4
4
|
export interface MarkerManagerInterface {
|
|
5
5
|
initializeFromConfig: (entries: MarkerConfigEntries) => void;
|
|
6
|
+
getController: (id: number, startMarker: Comment) => MarkerController | undefined;
|
|
7
|
+
ensureController: (id: number, type: string, startMarker: Comment, endMarker: Comment | null) => MarkerController | undefined;
|
|
8
|
+
cleanupController: (id: number, startMarker?: Comment) => void;
|
|
6
9
|
cleanup: () => void;
|
|
7
10
|
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CompactSwitchConfig } from './FluffBase.js';
|
|
2
2
|
import { MarkerController } from './MarkerController.js';
|
|
3
|
-
export declare class SwitchController extends MarkerController {
|
|
4
|
-
private readonly config;
|
|
3
|
+
export declare class SwitchController extends MarkerController<CompactSwitchConfig> {
|
|
5
4
|
private templates;
|
|
6
|
-
private readonly bindingsSubscriptions;
|
|
7
|
-
constructor(id: number, startMarker: Comment, endMarker: Comment | null, host: HTMLElement, shadowRoot: ShadowRoot, config: SwitchMarkerConfig);
|
|
8
5
|
initialize(): void;
|
|
9
6
|
}
|
|
@@ -1,38 +1,35 @@
|
|
|
1
|
+
import { FluffBase } from './FluffBase.js';
|
|
1
2
|
import { MarkerController } from './MarkerController.js';
|
|
2
3
|
export class SwitchController extends MarkerController {
|
|
3
|
-
config;
|
|
4
4
|
templates = [];
|
|
5
|
-
bindingsSubscriptions = [];
|
|
6
|
-
constructor(id, startMarker, endMarker, host, shadowRoot, config) {
|
|
7
|
-
super(id, startMarker, endMarker, host, shadowRoot);
|
|
8
|
-
this.config = config;
|
|
9
|
-
}
|
|
10
5
|
initialize() {
|
|
11
6
|
const hostTag = this.host.tagName.toLowerCase();
|
|
12
7
|
const templateIdPrefix = `${hostTag}-${this.id}-`;
|
|
13
8
|
this.templates = Array.from(this.shadowRoot.querySelectorAll(`template[data-fluff-case^="${templateIdPrefix}"]`));
|
|
14
|
-
|
|
9
|
+
// CompactSwitchConfig: [3, exprId, deps, cases[]] — case = [isDefault, fallthrough, valueExprId]
|
|
10
|
+
const [, expressionExprId, compactDeps, cases] = this.config;
|
|
11
|
+
const deps = FluffBase.__decodeDeps(compactDeps) ?? [];
|
|
15
12
|
const update = () => {
|
|
16
13
|
this.clearContentBetweenMarkersWithCleanup(this.bindingsSubscriptions);
|
|
17
|
-
const switchValue = this.evaluateExpr(
|
|
14
|
+
const switchValue = this.evaluateExpr(expressionExprId);
|
|
18
15
|
let matched = false;
|
|
19
16
|
let shouldFallthrough = false;
|
|
20
17
|
const renderContext = {
|
|
21
18
|
shouldBreak: false
|
|
22
19
|
};
|
|
23
|
-
for (let i = 0; i <
|
|
20
|
+
for (let i = 0; i < cases.length; i++) {
|
|
24
21
|
if (renderContext.shouldBreak)
|
|
25
22
|
break;
|
|
26
|
-
const
|
|
23
|
+
const [isDefault, fallthrough, valueExprId] = cases[i];
|
|
27
24
|
const template = this.templates[i];
|
|
28
25
|
if (!template)
|
|
29
26
|
continue;
|
|
30
|
-
const caseMatches =
|
|
31
|
-
|| (
|
|
27
|
+
const caseMatches = isDefault
|
|
28
|
+
|| (valueExprId !== null && this.evaluateExpr(valueExprId) === switchValue);
|
|
32
29
|
if (shouldFallthrough || (!matched && caseMatches)) {
|
|
33
30
|
matched = true;
|
|
34
31
|
this.cloneAndInsertTemplate(template, this.loopContext, renderContext, this.bindingsSubscriptions);
|
|
35
|
-
shouldFallthrough =
|
|
32
|
+
shouldFallthrough = fallthrough;
|
|
36
33
|
}
|
|
37
34
|
}
|
|
38
35
|
this.refreshParentBindings();
|