@fluffjs/fluff 0.0.7 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/bundle.min.js +1 -0
  2. package/decorators/Component.d.ts +0 -1
  3. package/decorators/Component.js +0 -3
  4. package/decorators/HostBinding.d.ts +4 -0
  5. package/decorators/HostBinding.js +3 -10
  6. package/decorators/HostListener.d.ts +4 -0
  7. package/decorators/HostListener.js +3 -10
  8. package/decorators/Input.d.ts +1 -1
  9. package/decorators/Input.js +2 -16
  10. package/decorators/InputOutputHelper.d.ts +1 -0
  11. package/decorators/InputOutputHelper.js +18 -0
  12. package/decorators/MetadataArrayHelper.d.ts +4 -0
  13. package/decorators/MetadataArrayHelper.js +14 -0
  14. package/decorators/Output.d.ts +1 -1
  15. package/decorators/Output.js +2 -16
  16. package/decorators/ViewChild.d.ts +4 -0
  17. package/decorators/ViewChild.js +3 -10
  18. package/index.d.ts +2 -0
  19. package/index.js +2 -0
  20. package/interfaces/BreakMarkerConfig.d.ts +3 -0
  21. package/interfaces/BreakMarkerConfig.js +1 -0
  22. package/interfaces/FluffHostElement.d.ts +5 -0
  23. package/interfaces/FluffHostElement.js +1 -0
  24. package/interfaces/ForMarkerConfig.d.ts +8 -0
  25. package/interfaces/ForMarkerConfig.js +1 -0
  26. package/interfaces/IfMarkerConfig.d.ts +7 -0
  27. package/interfaces/IfMarkerConfig.js +1 -0
  28. package/interfaces/MarkerConfig.d.ts +6 -0
  29. package/interfaces/MarkerConfig.js +1 -0
  30. package/interfaces/PropertyChain.d.ts +1 -0
  31. package/interfaces/PropertyChain.js +1 -0
  32. package/interfaces/RenderContext.d.ts +3 -0
  33. package/interfaces/RenderContext.js +1 -0
  34. package/interfaces/SwitchMarkerConfig.d.ts +10 -0
  35. package/interfaces/SwitchMarkerConfig.js +1 -0
  36. package/interfaces/TextMarkerConfig.d.ts +9 -0
  37. package/interfaces/TextMarkerConfig.js +1 -0
  38. package/package.json +1 -1
  39. package/runtime/BreakController.d.ts +8 -0
  40. package/runtime/BreakController.js +13 -0
  41. package/runtime/FluffBase.d.ts +40 -0
  42. package/runtime/FluffBase.js +271 -0
  43. package/runtime/FluffElement.d.ts +5 -39
  44. package/runtime/FluffElement.js +3 -272
  45. package/runtime/FluffElementImpl.d.ts +49 -0
  46. package/runtime/FluffElementImpl.js +315 -0
  47. package/runtime/FluffMarkers.d.ts +8 -0
  48. package/runtime/FluffMarkers.js +8 -0
  49. package/runtime/ForController.d.ts +10 -0
  50. package/runtime/ForController.js +43 -0
  51. package/runtime/IfController.d.ts +10 -0
  52. package/runtime/IfController.js +46 -0
  53. package/runtime/MarkerConfigGuards.d.ts +15 -0
  54. package/runtime/MarkerConfigGuards.js +20 -0
  55. package/runtime/MarkerController.d.ts +46 -0
  56. package/runtime/MarkerController.js +235 -0
  57. package/runtime/MarkerManager.d.ts +16 -0
  58. package/runtime/MarkerManager.js +136 -0
  59. package/runtime/MarkerManagerInterface.d.ts +4 -0
  60. package/runtime/MarkerManagerInterface.js +1 -0
  61. package/runtime/ScopeRegistry.d.ts +9 -0
  62. package/runtime/ScopeRegistry.js +13 -0
  63. package/runtime/SwitchController.d.ts +9 -0
  64. package/runtime/SwitchController.js +42 -0
  65. package/runtime/TextController.d.ts +10 -0
  66. package/runtime/TextController.js +53 -0
  67. package/runtime/tests/DirectOutputChild.d.ts +10 -0
  68. package/runtime/tests/DirectOutputChild.js +14 -0
  69. package/runtime/tests/DirectOutputParent.d.ts +9 -0
  70. package/runtime/tests/DirectOutputParent.js +17 -0
  71. package/runtime/tests/TaskStats.d.ts +9 -0
  72. package/runtime/tests/TaskStats.js +1 -0
  73. package/runtime/tests/TestChildTasksListComponent.d.ts +8 -0
  74. package/runtime/tests/TestChildTasksListComponent.js +28 -0
  75. package/runtime/tests/TestForChildComponent.d.ts +6 -0
  76. package/runtime/tests/TestForChildComponent.js +10 -0
  77. package/runtime/tests/TestForComponent.d.ts +6 -0
  78. package/runtime/tests/TestForComponent.js +21 -0
  79. package/runtime/tests/TestForReinsertBindsInputParentComponent.d.ts +12 -0
  80. package/runtime/tests/TestForReinsertBindsInputParentComponent.js +57 -0
  81. package/runtime/tests/TestForTextMarkerCollisionNoTrackParentComponent.d.ts +8 -0
  82. package/runtime/tests/TestForTextMarkerCollisionNoTrackParentComponent.js +29 -0
  83. package/runtime/tests/TestForTextMarkerCollisionParentComponent.d.ts +9 -0
  84. package/runtime/tests/TestForTextMarkerCollisionParentComponent.js +46 -0
  85. package/runtime/tests/TestForUnsubscribeNestedParentComponent.d.ts +8 -0
  86. package/runtime/tests/TestForUnsubscribeNestedParentComponent.js +35 -0
  87. package/runtime/tests/TestGetterReactivityComponent.d.ts +9 -0
  88. package/runtime/tests/TestGetterReactivityComponent.js +27 -0
  89. package/runtime/tests/TestIfReinsertBindsInputChildComponent.d.ts +9 -0
  90. package/runtime/tests/TestIfReinsertBindsInputChildComponent.js +39 -0
  91. package/runtime/tests/TestIfReinsertBindsInputParentComponent.d.ts +12 -0
  92. package/runtime/tests/TestIfReinsertBindsInputParentComponent.js +54 -0
  93. package/runtime/tests/TestIfUnsubscribeNestedParentComponent.d.ts +8 -0
  94. package/runtime/tests/TestIfUnsubscribeNestedParentComponent.js +32 -0
  95. package/runtime/tests/TestLateDefineForChildComponent.d.ts +6 -0
  96. package/runtime/tests/TestLateDefineForChildComponent.js +10 -0
  97. package/runtime/tests/TestLateDefineForComponent.d.ts +9 -0
  98. package/runtime/tests/TestLateDefineForComponent.js +25 -0
  99. package/runtime/tests/TestNullInputTextComponent.d.ts +14 -0
  100. package/runtime/tests/TestNullInputTextComponent.js +37 -0
  101. package/runtime/tests/TestOutputBindingChildComponent.d.ts +10 -0
  102. package/runtime/tests/TestOutputBindingChildComponent.js +20 -0
  103. package/runtime/tests/TestOutputBindingParentComponent.d.ts +13 -0
  104. package/runtime/tests/TestOutputBindingParentComponent.js +38 -0
  105. package/runtime/tests/TestParentBindsTasksComponent.d.ts +7 -0
  106. package/runtime/tests/TestParentBindsTasksComponent.js +22 -0
  107. package/runtime/tests/TestSwitchReinsertBindsInputChildComponent.d.ts +9 -0
  108. package/runtime/tests/TestSwitchReinsertBindsInputChildComponent.js +39 -0
  109. package/runtime/tests/TestSwitchReinsertBindsInputParentComponent.d.ts +12 -0
  110. package/runtime/tests/TestSwitchReinsertBindsInputParentComponent.js +65 -0
  111. package/runtime/tests/TestSwitchUnsubscribeNestedParentComponent.d.ts +8 -0
  112. package/runtime/tests/TestSwitchUnsubscribeNestedParentComponent.js +42 -0
  113. package/runtime/tests/TestTemplateNestedMarkersComponent.d.ts +11 -0
  114. package/runtime/tests/TestTemplateNestedMarkersComponent.js +39 -0
  115. package/runtime/tests/TestUnsubscribeNestedChildComponent.d.ts +11 -0
  116. package/runtime/tests/TestUnsubscribeNestedChildComponent.js +43 -0
  117. package/runtime/tests/TestUnsubscribeNestedGrandchildComponent.d.ts +9 -0
  118. package/runtime/tests/TestUnsubscribeNestedGrandchildComponent.js +39 -0
  119. package/runtime/tests/TestUnsubscribeNestedParentBaseComponent.d.ts +8 -0
  120. package/runtime/tests/TestUnsubscribeNestedParentBaseComponent.js +33 -0
  121. package/utils/DomUtils.d.ts +3 -0
  122. package/utils/DomUtils.js +6 -0
  123. package/utils/Property.d.ts +8 -3
  124. package/utils/Property.js +26 -20
  125. package/utils/Publisher.d.ts +2 -1
  126. package/utils/Publisher.js +16 -1
@@ -0,0 +1,315 @@
1
+ import { DomUtils } from '../utils/DomUtils.js';
2
+ import { Property } from '../utils/Property.js';
3
+ import { Publisher } from '../utils/Publisher.js';
4
+ import { FluffBase } from './FluffBase.js';
5
+ import { getScope } from './ScopeRegistry.js';
6
+ export class FluffElement extends FluffBase {
7
+ __pipes = {};
8
+ _shadowRoot;
9
+ _subscriptions = [];
10
+ _initialized = false;
11
+ _markerManager = null;
12
+ _markerConfigJson = null;
13
+ _MarkerManagerClass = null;
14
+ constructor() {
15
+ super();
16
+ this._shadowRoot = this.attachShadow({ mode: 'open' });
17
+ }
18
+ connectedCallback() {
19
+ if (!this._initialized) {
20
+ const scopeId = this.getAttribute('data-fluff-scope-id');
21
+ if (scopeId && !this.__parentScope) {
22
+ this.__parentScope = getScope(scopeId);
23
+ if (this.__parentScope) {
24
+ this.__loopContext = this.__parentScope.locals;
25
+ }
26
+ }
27
+ const contextAttr = this.getAttribute('data-fluff-loop-context');
28
+ if (contextAttr && Object.keys(this.__loopContext).length === 0) {
29
+ try {
30
+ this.__loopContext = JSON.parse(contextAttr);
31
+ }
32
+ catch {
33
+ }
34
+ }
35
+ this.__applyPendingProps();
36
+ this.__render();
37
+ this.__setupBindings();
38
+ if (this.getAttribute('data-fluff-scope-id')) {
39
+ this.__processBindings();
40
+ }
41
+ this._initialized = true;
42
+ if ('onInit' in this && typeof this.onInit === 'function') {
43
+ this.onInit();
44
+ }
45
+ }
46
+ }
47
+ disconnectedCallback() {
48
+ if ('onDestroy' in this && typeof this.onDestroy === 'function') {
49
+ this.onDestroy();
50
+ }
51
+ if (this._markerManager) {
52
+ this._markerManager.cleanup();
53
+ this._markerManager = null;
54
+ }
55
+ for (const sub of this._subscriptions) {
56
+ sub.unsubscribe();
57
+ }
58
+ this._subscriptions = [];
59
+ for (const sub of this.__baseSubscriptions) {
60
+ sub.unsubscribe();
61
+ }
62
+ this.__baseSubscriptions = [];
63
+ }
64
+ $watch = (_properties, callback) => {
65
+ callback();
66
+ return {
67
+ unsubscribe: () => {
68
+ }
69
+ };
70
+ };
71
+ __processBindingsOnElementPublic(el, scope, subscriptions) {
72
+ this.__processBindingsOnElement(el, scope, subscriptions);
73
+ }
74
+ __setupBindings() {
75
+ this.__processBindings();
76
+ this.__initializeMarkersInternal();
77
+ }
78
+ __addSubscription(sub) {
79
+ this._subscriptions.push(sub);
80
+ }
81
+ __pipe(name, value, ...args) {
82
+ const pipe = this.__pipes[name];
83
+ if (!pipe) {
84
+ console.warn(`Pipe "${name}" not found`);
85
+ return value;
86
+ }
87
+ return pipe(value, ...args);
88
+ }
89
+ __getShadowRoot() {
90
+ return this._shadowRoot;
91
+ }
92
+ __setMarkerConfigs(configJson) {
93
+ this._markerConfigJson = configJson;
94
+ }
95
+ __initializeMarkers(MarkerManagerClass) {
96
+ this._MarkerManagerClass = MarkerManagerClass;
97
+ }
98
+ __initializeMarkersInternal() {
99
+ if (!this._markerConfigJson || !this._MarkerManagerClass)
100
+ return;
101
+ this._markerManager = new this._MarkerManagerClass(this, this._shadowRoot);
102
+ this._markerManager.initializeFromConfig(this._markerConfigJson);
103
+ }
104
+ __getElement(id) {
105
+ return this._shadowRoot.querySelector(`[data-lid="${id}"]`);
106
+ }
107
+ __setText(id, text) {
108
+ const el = this.__getElement(id);
109
+ if (el)
110
+ el.textContent = text;
111
+ }
112
+ __bindText(id, getter) {
113
+ const el = this.__getElement(id);
114
+ if (!el)
115
+ return;
116
+ const expr = el.getAttribute('data-text-bind') ?? '';
117
+ const propMatch = /this\.([a-zA-Z_][a-zA-Z0-9_]*)/.exec(expr);
118
+ const update = () => {
119
+ try {
120
+ el.textContent = getter();
121
+ }
122
+ catch {
123
+ el.textContent = '';
124
+ }
125
+ };
126
+ if (propMatch) {
127
+ const [, propName] = propMatch;
128
+ const reactiveProp = this.__getReactiveProp(propName);
129
+ if (reactiveProp) {
130
+ this.__bindPropertyChange(reactiveProp, update);
131
+ }
132
+ }
133
+ update();
134
+ }
135
+ __setProperty(id, prop, value) {
136
+ const el = this.__getElement(id);
137
+ if (this.isHTMLElement(el)) {
138
+ if (prop in el) {
139
+ Reflect.set(el, prop, value);
140
+ }
141
+ else {
142
+ el.setAttribute(prop, String(value));
143
+ }
144
+ }
145
+ }
146
+ __addClass(id, className) {
147
+ const el = this.__getElement(id);
148
+ if (this.isHTMLElement(el))
149
+ el.classList.add(className);
150
+ }
151
+ __removeClass(id, className) {
152
+ const el = this.__getElement(id);
153
+ if (this.isHTMLElement(el))
154
+ el.classList.remove(className);
155
+ }
156
+ __bindEvent(id, event, handler) {
157
+ const el = this.__getElement(id);
158
+ if (el) {
159
+ el.addEventListener(event, handler);
160
+ }
161
+ }
162
+ __bindPropertyChange(prop, callback) {
163
+ const sub = prop.onChange.subscribe(callback);
164
+ this._subscriptions.push(sub);
165
+ const currentVal = prop.getValue();
166
+ if (currentVal !== null) {
167
+ callback(currentVal);
168
+ }
169
+ }
170
+ __connectProperties(source, target) {
171
+ const sub = source.onChange.subscribe((val) => {
172
+ target.setValue(val, true);
173
+ });
174
+ this._subscriptions.push(sub);
175
+ const currentVal = source.getValue();
176
+ if (currentVal !== null) {
177
+ target.setValue(currentVal, true);
178
+ }
179
+ }
180
+ __connectOutput(source, handler) {
181
+ const sub = source.subscribe(handler);
182
+ this._subscriptions.push(sub);
183
+ }
184
+ __bindOutput(id, outputName, handler) {
185
+ const el = this.__getElement(id);
186
+ if (el)
187
+ this.__bindOutputOnElement(el, outputName, handler);
188
+ }
189
+ __setChildProperty(el, propName, value) {
190
+ if (el instanceof HTMLElement && el.hasAttribute('x-fluff-component')) {
191
+ const tagName = el.tagName.toLowerCase();
192
+ if (customElements.get(tagName) === undefined) {
193
+ this.__whenDefined(tagName, () => {
194
+ this.__setChildProperty(el, propName, value);
195
+ });
196
+ return;
197
+ }
198
+ }
199
+ const prop = Reflect.get(el, propName);
200
+ if (prop instanceof Property) {
201
+ prop.setValue(value, true);
202
+ }
203
+ else if (propName in el) {
204
+ Reflect.set(el, propName, value);
205
+ }
206
+ else {
207
+ el.setAttribute(propName, String(value));
208
+ }
209
+ }
210
+ __bindToChild(id, propName, value) {
211
+ const el = this.__getElement(id);
212
+ if (!el)
213
+ return;
214
+ this.__setChildPropertyDeferred(el, propName, value);
215
+ }
216
+ __setChildPropertyDeferred(el, propName, value) {
217
+ if (Reflect.get(el, propName) !== undefined) {
218
+ this.__setChildProperty(el, propName, value);
219
+ return;
220
+ }
221
+ if (DomUtils.isCustomElement(el)) {
222
+ this.__whenDefined(el.tagName.toLowerCase(), () => {
223
+ this.__setChildProperty(el, propName, value);
224
+ });
225
+ }
226
+ else {
227
+ this.__setChildProperty(el, propName, value);
228
+ }
229
+ }
230
+ __bindOutputOnElement(el, outputName, handler) {
231
+ const maybeOutput = Reflect.get(el, outputName);
232
+ if (maybeOutput instanceof Publisher) {
233
+ this.__connectOutput(maybeOutput, handler);
234
+ return;
235
+ }
236
+ if (DomUtils.isCustomElement(el)) {
237
+ this.__whenDefined(el.tagName.toLowerCase(), () => {
238
+ const innerOutput = Reflect.get(el, outputName);
239
+ if (innerOutput instanceof Publisher) {
240
+ this.__connectOutput(innerOutput, handler);
241
+ }
242
+ else {
243
+ el.addEventListener(outputName, handler);
244
+ }
245
+ });
246
+ }
247
+ else {
248
+ el.addEventListener(outputName, handler);
249
+ }
250
+ }
251
+ __getReactivePropFromScope(propName, scope) {
252
+ const key = `__${propName}`;
253
+ if (key in scope.host) {
254
+ const candidate = Reflect.get(scope.host, key);
255
+ if (candidate instanceof Property) {
256
+ return candidate;
257
+ }
258
+ }
259
+ if (scope.parent) {
260
+ return this.__getReactivePropFromScope(propName, scope.parent);
261
+ }
262
+ return undefined;
263
+ }
264
+ __processBindings() {
265
+ const elements = this._shadowRoot.querySelectorAll('[data-lid]');
266
+ const scope = this.__getScope();
267
+ for (const el of Array.from(elements)) {
268
+ const closestComponent = el.closest('[x-fluff-component]');
269
+ if (closestComponent && closestComponent !== el)
270
+ continue;
271
+ if (el instanceof HTMLElement) {
272
+ const tagName = el.tagName.toLowerCase();
273
+ if (customElements.get(tagName))
274
+ continue;
275
+ this.__processBindingsOnElement(el, scope);
276
+ }
277
+ }
278
+ }
279
+ __applyPendingProps() {
280
+ const existing = Reflect.get(this, '__pendingProps');
281
+ if (!this.isRecord(existing)) {
282
+ return;
283
+ }
284
+ for (const [propName, value] of Object.entries(existing)) {
285
+ console.log('apply-pending-prop', { propName, value, el: this.tagName });
286
+ const key = `__${propName}`;
287
+ if (key in this) {
288
+ const prop = Reflect.get(this, key);
289
+ if (prop instanceof Property) {
290
+ prop.setValue(value, true);
291
+ }
292
+ }
293
+ else if (propName in this) {
294
+ Reflect.set(this, propName, value);
295
+ }
296
+ }
297
+ Reflect.deleteProperty(this, '__pendingProps');
298
+ }
299
+ isHTMLElement(el) {
300
+ return el !== null;
301
+ }
302
+ isRecord(value) {
303
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
304
+ }
305
+ __getReactiveProp(propName) {
306
+ const key = `__${propName}`;
307
+ if (key in this) {
308
+ const candidate = Reflect.get(this, key);
309
+ if (candidate instanceof Property) {
310
+ return candidate;
311
+ }
312
+ }
313
+ return undefined;
314
+ }
315
+ }
@@ -0,0 +1,8 @@
1
+ export { MarkerManager } from './MarkerManager.js';
2
+ export { MarkerConfigGuards } from './MarkerConfigGuards.js';
3
+ export { MarkerController } from './MarkerController.js';
4
+ export { IfController } from './IfController.js';
5
+ export { ForController } from './ForController.js';
6
+ export { SwitchController } from './SwitchController.js';
7
+ export { TextController } from './TextController.js';
8
+ export { BreakController } from './BreakController.js';
@@ -0,0 +1,8 @@
1
+ export { MarkerManager } from './MarkerManager.js';
2
+ export { MarkerConfigGuards } from './MarkerConfigGuards.js';
3
+ export { MarkerController } from './MarkerController.js';
4
+ export { IfController } from './IfController.js';
5
+ export { ForController } from './ForController.js';
6
+ export { SwitchController } from './SwitchController.js';
7
+ export { TextController } from './TextController.js';
8
+ export { BreakController } from './BreakController.js';
@@ -0,0 +1,10 @@
1
+ import type { ForMarkerConfig } from '../interfaces/ForMarkerConfig.js';
2
+ import { MarkerController } from './MarkerController.js';
3
+ export declare class ForController extends MarkerController {
4
+ private readonly config;
5
+ private itemTemplate;
6
+ private emptyTemplate;
7
+ private readonly bindingsSubscriptions;
8
+ constructor(id: number, startMarker: Comment, endMarker: Comment | null, host: HTMLElement, shadowRoot: ShadowRoot, config: ForMarkerConfig);
9
+ initialize(): void;
10
+ }
@@ -0,0 +1,43 @@
1
+ import { MarkerController } from './MarkerController.js';
2
+ export class ForController extends MarkerController {
3
+ config;
4
+ itemTemplate = null;
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
+ initialize() {
12
+ const hostTag = this.host.tagName.toLowerCase();
13
+ const templateId = `${hostTag}-${this.id}`;
14
+ this.itemTemplate = this.shadowRoot.querySelector(`template[data-fluff-tpl="${templateId}"]`);
15
+ this.emptyTemplate = this.shadowRoot.querySelector(`template[data-fluff-empty="${templateId}"]`);
16
+ const deps = this.config.deps ?? [];
17
+ const update = () => {
18
+ this.clearContentBetweenMarkersWithCleanup(this.bindingsSubscriptions);
19
+ const items = this.evaluateExpr(this.config.iterableExprId);
20
+ if (!Array.isArray(items) || items.length === 0) {
21
+ if (this.emptyTemplate) {
22
+ this.cloneAndInsertTemplate(this.emptyTemplate, this.loopContext, undefined, this.bindingsSubscriptions);
23
+ }
24
+ return;
25
+ }
26
+ if (!this.itemTemplate)
27
+ return;
28
+ const renderContext = {
29
+ shouldBreak: false
30
+ };
31
+ for (let i = 0; i < items.length; i++) {
32
+ if (renderContext.shouldBreak)
33
+ break;
34
+ const itemContext = {
35
+ ...this.loopContext, [this.config.iterator]: items[i], $index: i
36
+ };
37
+ this.cloneAndInsertTemplate(this.itemTemplate, itemContext, renderContext, this.bindingsSubscriptions);
38
+ }
39
+ };
40
+ this.subscribeTo(deps, update);
41
+ update();
42
+ }
43
+ }
@@ -0,0 +1,10 @@
1
+ import type { IfMarkerConfig } from '../interfaces/IfMarkerConfig.js';
2
+ import { MarkerController } from './MarkerController.js';
3
+ export declare class IfController extends MarkerController {
4
+ private readonly config;
5
+ private templates;
6
+ private currentBranchIndex;
7
+ private readonly bindingsSubscriptions;
8
+ constructor(id: number, startMarker: Comment, endMarker: Comment | null, host: HTMLElement, shadowRoot: ShadowRoot, config: IfMarkerConfig);
9
+ initialize(): void;
10
+ }
@@ -0,0 +1,46 @@
1
+ import { MarkerController } from './MarkerController.js';
2
+ export class IfController extends MarkerController {
3
+ config;
4
+ templates = [];
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
+ initialize() {
12
+ const hostTag = this.host.tagName.toLowerCase();
13
+ const templateIdPrefix = `${hostTag}-${this.id}-`;
14
+ this.templates = Array.from(this.shadowRoot.querySelectorAll(`template[data-fluff-branch^="${templateIdPrefix}"]`));
15
+ const allDeps = [];
16
+ for (const branch of this.config.branches) {
17
+ if (branch.deps) {
18
+ allDeps.push(...branch.deps);
19
+ }
20
+ }
21
+ const update = () => {
22
+ let matchedIndex = -1;
23
+ for (let i = 0; i < this.config.branches.length; i++) {
24
+ const branch = this.config.branches[i];
25
+ if (branch.exprId === undefined) {
26
+ matchedIndex = i;
27
+ break;
28
+ }
29
+ const result = this.evaluateExpr(branch.exprId);
30
+ if (result) {
31
+ matchedIndex = i;
32
+ break;
33
+ }
34
+ }
35
+ if (matchedIndex !== this.currentBranchIndex) {
36
+ this.clearContentBetweenMarkersWithCleanup(this.bindingsSubscriptions);
37
+ this.currentBranchIndex = matchedIndex;
38
+ if (matchedIndex >= 0 && matchedIndex < this.templates.length) {
39
+ this.cloneAndInsertTemplate(this.templates[matchedIndex], this.loopContext, undefined, this.bindingsSubscriptions);
40
+ }
41
+ }
42
+ };
43
+ this.subscribeTo(allDeps, update);
44
+ update();
45
+ }
46
+ }
@@ -0,0 +1,15 @@
1
+ import type { BreakMarkerConfig } from '../interfaces/BreakMarkerConfig.js';
2
+ import type { FluffHostElement } from '../interfaces/FluffHostElement.js';
3
+ import type { ForMarkerConfig } from '../interfaces/ForMarkerConfig.js';
4
+ import type { IfMarkerConfig } from '../interfaces/IfMarkerConfig.js';
5
+ import type { MarkerConfig } from '../interfaces/MarkerConfig.js';
6
+ import type { SwitchMarkerConfig } from '../interfaces/SwitchMarkerConfig.js';
7
+ import type { TextMarkerConfig } from '../interfaces/TextMarkerConfig.js';
8
+ export declare class MarkerConfigGuards {
9
+ static isFluffHostElement(el: Element): el is FluffHostElement;
10
+ static isIfConfig(config: MarkerConfig): config is IfMarkerConfig;
11
+ static isForConfig(config: MarkerConfig): config is ForMarkerConfig;
12
+ static isSwitchConfig(config: MarkerConfig): config is SwitchMarkerConfig;
13
+ static isTextConfig(config: MarkerConfig): config is TextMarkerConfig;
14
+ static isBreakConfig(config: MarkerConfig): config is BreakMarkerConfig;
15
+ }
@@ -0,0 +1,20 @@
1
+ export class MarkerConfigGuards {
2
+ static isFluffHostElement(el) {
3
+ return '__getReactiveProp' in el;
4
+ }
5
+ static isIfConfig(config) {
6
+ return config.type === 'if';
7
+ }
8
+ static isForConfig(config) {
9
+ return config.type === 'for';
10
+ }
11
+ static isSwitchConfig(config) {
12
+ return config.type === 'switch';
13
+ }
14
+ static isTextConfig(config) {
15
+ return config.type === 'text';
16
+ }
17
+ static isBreakConfig(config) {
18
+ return config.type === 'break';
19
+ }
20
+ }
@@ -0,0 +1,46 @@
1
+ import type { FluffHostElement } from '../interfaces/FluffHostElement.js';
2
+ import type { PropertyChain } from '../interfaces/PropertyChain.js';
3
+ import type { RenderContext } from '../interfaces/RenderContext.js';
4
+ import type { Subscription } from '../interfaces/Subscription.js';
5
+ import { Property } from '../utils/Property.js';
6
+ import { type Scope } from './ScopeRegistry.js';
7
+ interface MarkerManagerLike {
8
+ getController: (id: number, startMarker: Comment) => MarkerController | undefined;
9
+ ensureController?: (id: number, type: string, startMarker: Comment, endMarker: Comment | null) => MarkerController | undefined;
10
+ cleanupController?: (id: number, startMarker?: Comment) => void;
11
+ }
12
+ export declare abstract class MarkerController {
13
+ protected readonly id: number;
14
+ protected readonly startMarker: Comment;
15
+ protected readonly endMarker: Comment | null;
16
+ protected readonly subscriptions: Subscription[];
17
+ protected readonly host: FluffHostElement;
18
+ protected readonly shadowRoot: ShadowRoot;
19
+ protected parentScope: Scope | undefined;
20
+ protected loopContext: Record<string, unknown>;
21
+ protected markerManager: MarkerManagerLike | undefined;
22
+ protected constructor(id: number, startMarker: Comment, endMarker: Comment | null, host: FluffHostElement, shadowRoot: ShadowRoot);
23
+ setParentScope(scope: Scope | undefined): void;
24
+ setLoopContext(context: Record<string, unknown>): void;
25
+ setMarkerManager(markerManager: MarkerManagerLike): void;
26
+ abstract initialize(): void;
27
+ cleanup(): void;
28
+ updateRenderContext(renderContext?: RenderContext): void;
29
+ protected evaluateExpr(exprId: number): unknown;
30
+ private getCompiledExprFn;
31
+ protected getScope(): Scope;
32
+ protected collectLocalsFromScope(scope: Scope): Record<string, unknown>;
33
+ protected subscribeTo(deps: PropertyChain[], callback: () => void): void;
34
+ protected getReactivePropFromScope(propName: string, scope: Scope): Property<unknown> | undefined;
35
+ protected createChildScope(locals: Record<string, unknown>): Scope;
36
+ protected clearContentBetweenMarkersWithCleanup(bindingsSubscriptions: Subscription[]): void;
37
+ protected insertBeforeEndMarker(node: Node): void;
38
+ protected processBindingsOnElement(el: HTMLElement, scope: Scope): void;
39
+ protected processBindingsOnElementWithSubscriptions(el: HTMLElement, scope: Scope, subscriptions: Subscription[]): void;
40
+ private __getFluffElementHost;
41
+ protected setScopeOnChildren(node: Node, scope: Scope, renderContext?: RenderContext, markerManager?: MarkerManagerLike, bindingsSubscriptions?: Subscription[]): void;
42
+ protected insertAndScopeTemplateContent(content: Node, nodes: Node[], scope: Scope, renderContext: RenderContext | undefined, markerManager: MarkerManagerLike | undefined, bindingsSubscriptions: Subscription[]): void;
43
+ protected cloneAndInsertTemplate(template: HTMLTemplateElement, context: Record<string, unknown>, renderContext: RenderContext | undefined, bindingsSubscriptions: Subscription[]): void;
44
+ protected processBindingsOnNode(node: HTMLElement, scope: Scope, bindingsSubscriptions?: Subscription[]): void;
45
+ }
46
+ export {};