@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,235 @@
1
+ import { DomUtils } from '../utils/DomUtils.js';
2
+ import { Property } from '../utils/Property.js';
3
+ import { FluffBase } from './FluffBase.js';
4
+ import { FluffElement } from './FluffElement.js';
5
+ import { MarkerConfigGuards } from './MarkerConfigGuards.js';
6
+ import { registerScope } from './ScopeRegistry.js';
7
+ export class MarkerController {
8
+ id;
9
+ startMarker;
10
+ endMarker;
11
+ subscriptions = [];
12
+ host;
13
+ shadowRoot;
14
+ parentScope;
15
+ loopContext = {};
16
+ markerManager;
17
+ constructor(id, startMarker, endMarker, host, shadowRoot) {
18
+ this.id = id;
19
+ this.startMarker = startMarker;
20
+ this.endMarker = endMarker;
21
+ this.host = host;
22
+ this.shadowRoot = shadowRoot;
23
+ }
24
+ setParentScope(scope) {
25
+ this.parentScope = scope;
26
+ }
27
+ setLoopContext(context) {
28
+ this.loopContext = context;
29
+ }
30
+ setMarkerManager(markerManager) {
31
+ this.markerManager = markerManager;
32
+ }
33
+ cleanup() {
34
+ for (const sub of this.subscriptions) {
35
+ sub.unsubscribe();
36
+ }
37
+ this.subscriptions.length = 0;
38
+ }
39
+ updateRenderContext(renderContext) {
40
+ if (renderContext) {
41
+ return;
42
+ }
43
+ }
44
+ evaluateExpr(exprId) {
45
+ const scope = this.getScope();
46
+ const allLocals = this.collectLocalsFromScope(scope);
47
+ const fn = this.getCompiledExprFn(exprId);
48
+ try {
49
+ return fn(scope.host, allLocals);
50
+ }
51
+ catch (e) {
52
+ return undefined;
53
+ }
54
+ }
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
+ getScope() {
63
+ if (this.parentScope) {
64
+ return this.parentScope;
65
+ }
66
+ const fluffHost = this.__getFluffElementHost();
67
+ if (fluffHost) {
68
+ return fluffHost.__getScope();
69
+ }
70
+ return {
71
+ host: this.host, locals: this.loopContext, parent: undefined
72
+ };
73
+ }
74
+ collectLocalsFromScope(scope) {
75
+ const result = {};
76
+ if (scope.parent) {
77
+ Object.assign(result, this.collectLocalsFromScope(scope.parent));
78
+ }
79
+ Object.assign(result, scope.locals);
80
+ return result;
81
+ }
82
+ subscribeTo(deps, callback) {
83
+ const scope = this.getScope();
84
+ for (const dep of deps) {
85
+ const propName = Array.isArray(dep) ? dep[0] : dep;
86
+ if (propName.startsWith('['))
87
+ continue;
88
+ const reactiveProp = this.getReactivePropFromScope(propName, scope);
89
+ if (reactiveProp) {
90
+ const sub = reactiveProp.onChange.subscribe(callback);
91
+ this.subscriptions.push(sub);
92
+ }
93
+ }
94
+ }
95
+ getReactivePropFromScope(propName, scope) {
96
+ if (propName in scope.locals) {
97
+ return undefined;
98
+ }
99
+ if (scope.parent) {
100
+ return this.getReactivePropFromScope(propName, scope.parent);
101
+ }
102
+ if (MarkerConfigGuards.isFluffHostElement(scope.host) && scope.host.__getReactiveProp) {
103
+ const reactiveProp = scope.host.__getReactiveProp(propName);
104
+ if (reactiveProp instanceof Property) {
105
+ return reactiveProp;
106
+ }
107
+ }
108
+ return undefined;
109
+ }
110
+ createChildScope(locals) {
111
+ return {
112
+ host: this.host, locals, parent: this.parentScope
113
+ };
114
+ }
115
+ clearContentBetweenMarkersWithCleanup(bindingsSubscriptions) {
116
+ for (const sub of bindingsSubscriptions) {
117
+ sub.unsubscribe();
118
+ }
119
+ bindingsSubscriptions.length = 0;
120
+ if (!this.endMarker)
121
+ return;
122
+ const parent = this.startMarker.parentNode;
123
+ if (!parent)
124
+ return;
125
+ let current = this.startMarker.nextSibling;
126
+ while (current && current !== this.endMarker) {
127
+ const next = current.nextSibling;
128
+ if (current instanceof Comment) {
129
+ const markerMatch = /^fluff:(if|for|switch|text|break):(\d+)$/.exec(current.data);
130
+ if (markerMatch && this.markerManager?.cleanupController) {
131
+ const markerId = parseInt(markerMatch[2], 10);
132
+ this.markerManager.cleanupController(markerId, current);
133
+ }
134
+ }
135
+ if (!(current instanceof HTMLTemplateElement)) {
136
+ parent.removeChild(current);
137
+ }
138
+ current = next;
139
+ }
140
+ }
141
+ insertBeforeEndMarker(node) {
142
+ if (!this.endMarker)
143
+ return;
144
+ const parent = this.endMarker.parentNode;
145
+ if (!parent)
146
+ return;
147
+ parent.insertBefore(node, this.endMarker);
148
+ }
149
+ processBindingsOnElement(el, scope) {
150
+ const fluffHost = this.__getFluffElementHost();
151
+ if (!fluffHost)
152
+ return;
153
+ fluffHost.__processBindingsOnElementPublic(el, scope);
154
+ }
155
+ processBindingsOnElementWithSubscriptions(el, scope, subscriptions) {
156
+ const fluffHost = this.__getFluffElementHost();
157
+ if (!fluffHost)
158
+ return;
159
+ fluffHost.__processBindingsOnElementPublic(el, scope, subscriptions);
160
+ }
161
+ __getFluffElementHost() {
162
+ return this.host instanceof FluffElement ? this.host : null;
163
+ }
164
+ setScopeOnChildren(node, scope, renderContext, markerManager, bindingsSubscriptions) {
165
+ if (node instanceof Comment) {
166
+ const markerMatch = /^fluff:(if|for|switch|text|break):(\d+)$/.exec(node.data);
167
+ if (markerMatch && markerManager) {
168
+ const [, markerType, markerIdStr] = markerMatch;
169
+ const markerId = parseInt(markerIdStr, 10);
170
+ const endPattern = `/fluff:${markerType}:${markerId}`;
171
+ let controller = markerManager.getController(markerId, node);
172
+ const shouldInitialize = controller === undefined;
173
+ if (!controller && markerManager.ensureController) {
174
+ let endMarker = null;
175
+ let current = node.nextSibling;
176
+ while (current) {
177
+ if (current instanceof Comment && current.data === endPattern) {
178
+ endMarker = current;
179
+ break;
180
+ }
181
+ current = current.nextSibling;
182
+ }
183
+ controller = markerManager.ensureController(markerId, markerType, node, endMarker);
184
+ }
185
+ if (controller) {
186
+ controller.setParentScope(scope);
187
+ controller.setLoopContext(scope.locals);
188
+ controller.updateRenderContext(renderContext);
189
+ if (shouldInitialize) {
190
+ controller.initialize();
191
+ }
192
+ }
193
+ }
194
+ }
195
+ else if (node instanceof FluffElement) {
196
+ node.__loopContext = scope.locals;
197
+ node.__parentScope = scope;
198
+ this.processBindingsOnNode(node, scope, bindingsSubscriptions);
199
+ }
200
+ else if (node instanceof HTMLElement && DomUtils.isCustomElement(node)) {
201
+ const scopeId = registerScope(scope);
202
+ node.setAttribute('data-fluff-scope-id', scopeId);
203
+ this.processBindingsOnNode(node, scope, bindingsSubscriptions);
204
+ }
205
+ else if (node instanceof HTMLElement && node.hasAttribute('data-lid')) {
206
+ this.processBindingsOnNode(node, scope, bindingsSubscriptions);
207
+ }
208
+ for (const child of Array.from(node.childNodes)) {
209
+ this.setScopeOnChildren(child, scope, renderContext, markerManager, bindingsSubscriptions);
210
+ }
211
+ }
212
+ insertAndScopeTemplateContent(content, nodes, scope, renderContext, markerManager, bindingsSubscriptions) {
213
+ this.insertBeforeEndMarker(content);
214
+ for (const node of nodes) {
215
+ this.setScopeOnChildren(node, scope, renderContext, markerManager, bindingsSubscriptions);
216
+ }
217
+ }
218
+ cloneAndInsertTemplate(template, context, renderContext, bindingsSubscriptions) {
219
+ const content = template.content.cloneNode(true);
220
+ if (!(content instanceof DocumentFragment)) {
221
+ throw new Error('Expected DocumentFragment from template clone');
222
+ }
223
+ const nodes = Array.from(content.childNodes);
224
+ const scope = this.createChildScope(context);
225
+ this.insertAndScopeTemplateContent(content, nodes, scope, renderContext, this.markerManager, bindingsSubscriptions);
226
+ }
227
+ processBindingsOnNode(node, scope, bindingsSubscriptions) {
228
+ if (bindingsSubscriptions) {
229
+ this.processBindingsOnElementWithSubscriptions(node, scope, bindingsSubscriptions);
230
+ }
231
+ else {
232
+ this.processBindingsOnElement(node, scope);
233
+ }
234
+ }
235
+ }
@@ -0,0 +1,16 @@
1
+ import type { MarkerController } from './MarkerController.js';
2
+ export declare class MarkerManager {
3
+ private readonly controllers;
4
+ private readonly configs;
5
+ private readonly host;
6
+ private readonly shadowRoot;
7
+ constructor(host: HTMLElement, shadowRoot: ShadowRoot);
8
+ initializeFromConfig(configJson: string): void;
9
+ ensureController(id: number, type: string, startMarker: Comment, endMarker: Comment | null): MarkerController | undefined;
10
+ getController(id: number, startMarker: Comment): MarkerController | undefined;
11
+ cleanupController(id: number, startMarker?: Comment): void;
12
+ cleanup(): void;
13
+ private registerController;
14
+ private findMarkers;
15
+ private createController;
16
+ }
@@ -0,0 +1,136 @@
1
+ import { BreakController } from './BreakController.js';
2
+ import { ForController } from './ForController.js';
3
+ import { IfController } from './IfController.js';
4
+ import { MarkerConfigGuards } from './MarkerConfigGuards.js';
5
+ import { SwitchController } from './SwitchController.js';
6
+ import { TextController } from './TextController.js';
7
+ export class MarkerManager {
8
+ controllers = new Map();
9
+ configs = new Map();
10
+ host;
11
+ shadowRoot;
12
+ constructor(host, shadowRoot) {
13
+ this.host = host;
14
+ this.shadowRoot = shadowRoot;
15
+ }
16
+ initializeFromConfig(configJson) {
17
+ const entries = JSON.parse(configJson);
18
+ this.configs.clear();
19
+ for (const [id, config] of entries) {
20
+ this.configs.set(id, config);
21
+ }
22
+ for (const [id, config] of entries) {
23
+ const { startMarker, endMarker } = this.findMarkers(id, config.type);
24
+ if (!startMarker) {
25
+ continue;
26
+ }
27
+ const controller = this.createController(id, startMarker, endMarker, config);
28
+ if (controller) {
29
+ controller.setMarkerManager(this);
30
+ this.registerController(id, startMarker, controller);
31
+ }
32
+ }
33
+ const controllersToInit = [];
34
+ for (const controllersByMarker of this.controllers.values()) {
35
+ controllersToInit.push(...controllersByMarker.values());
36
+ }
37
+ for (const controller of controllersToInit) {
38
+ controller.initialize();
39
+ }
40
+ }
41
+ ensureController(id, type, startMarker, endMarker) {
42
+ const existing = this.getController(id, startMarker);
43
+ if (existing) {
44
+ return existing;
45
+ }
46
+ const config = this.configs.get(id);
47
+ if (config?.type !== type) {
48
+ return undefined;
49
+ }
50
+ const controller = this.createController(id, startMarker, endMarker, config);
51
+ if (!controller) {
52
+ return undefined;
53
+ }
54
+ controller.setMarkerManager(this);
55
+ this.registerController(id, startMarker, controller);
56
+ return controller;
57
+ }
58
+ getController(id, startMarker) {
59
+ return this.controllers.get(id)
60
+ ?.get(startMarker);
61
+ }
62
+ cleanupController(id, startMarker) {
63
+ const controllersForId = this.controllers.get(id);
64
+ if (!controllersForId)
65
+ return;
66
+ if (startMarker) {
67
+ const controller = controllersForId.get(startMarker);
68
+ if (!controller)
69
+ return;
70
+ controller.cleanup();
71
+ controllersForId.delete(startMarker);
72
+ if (controllersForId.size === 0) {
73
+ this.controllers.delete(id);
74
+ }
75
+ return;
76
+ }
77
+ for (const controller of controllersForId.values()) {
78
+ controller.cleanup();
79
+ }
80
+ this.controllers.delete(id);
81
+ }
82
+ cleanup() {
83
+ for (const controllersByMarker of this.controllers.values()) {
84
+ for (const controller of controllersByMarker.values()) {
85
+ controller.cleanup();
86
+ }
87
+ }
88
+ this.controllers.clear();
89
+ }
90
+ registerController(id, startMarker, controller) {
91
+ let controllersForId = this.controllers.get(id);
92
+ if (!controllersForId) {
93
+ controllersForId = new Map();
94
+ this.controllers.set(id, controllersForId);
95
+ }
96
+ controllersForId.set(startMarker, controller);
97
+ }
98
+ findMarkers(id, type) {
99
+ const startPattern = `fluff:${type}:${id}`;
100
+ const endPattern = `/fluff:${type}:${id}`;
101
+ let startMarker = null;
102
+ let endMarker = null;
103
+ const walker = document.createTreeWalker(this.shadowRoot, NodeFilter.SHOW_COMMENT, null);
104
+ let node = walker.nextNode();
105
+ while (node) {
106
+ if (node instanceof Comment) {
107
+ if (node.data === startPattern) {
108
+ startMarker = node;
109
+ }
110
+ else if (node.data === endPattern) {
111
+ endMarker = node;
112
+ }
113
+ }
114
+ node = walker.nextNode();
115
+ }
116
+ return { startMarker, endMarker };
117
+ }
118
+ createController(id, startMarker, endMarker, config) {
119
+ if (MarkerConfigGuards.isIfConfig(config)) {
120
+ return new IfController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
121
+ }
122
+ if (MarkerConfigGuards.isForConfig(config)) {
123
+ return new ForController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
124
+ }
125
+ if (MarkerConfigGuards.isSwitchConfig(config)) {
126
+ return new SwitchController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
127
+ }
128
+ if (MarkerConfigGuards.isTextConfig(config)) {
129
+ return new TextController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
130
+ }
131
+ if (MarkerConfigGuards.isBreakConfig(config)) {
132
+ return new BreakController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
133
+ }
134
+ return null;
135
+ }
136
+ }
@@ -0,0 +1,4 @@
1
+ export interface MarkerManagerInterface {
2
+ initializeFromConfig: (configJson: string) => void;
3
+ cleanup: () => void;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { FluffHostElement } from '../interfaces/FluffHostElement.js';
2
+ export interface Scope {
3
+ host: FluffHostElement;
4
+ locals: Record<string, unknown>;
5
+ parent?: Scope;
6
+ }
7
+ export declare function registerScope(scope: Scope): string;
8
+ export declare function getScope(id: string): Scope | undefined;
9
+ export declare function unregisterScope(id: string): void;
@@ -0,0 +1,13 @@
1
+ const scopeRegistry = new Map();
2
+ let scopeIdCounter = 0;
3
+ export function registerScope(scope) {
4
+ const id = `scope_${scopeIdCounter++}`;
5
+ scopeRegistry.set(id, scope);
6
+ return id;
7
+ }
8
+ export function getScope(id) {
9
+ return scopeRegistry.get(id);
10
+ }
11
+ export function unregisterScope(id) {
12
+ scopeRegistry.delete(id);
13
+ }
@@ -0,0 +1,9 @@
1
+ import type { SwitchMarkerConfig } from '../interfaces/SwitchMarkerConfig.js';
2
+ import { MarkerController } from './MarkerController.js';
3
+ export declare class SwitchController extends MarkerController {
4
+ private readonly config;
5
+ private templates;
6
+ private readonly bindingsSubscriptions;
7
+ constructor(id: number, startMarker: Comment, endMarker: Comment | null, host: HTMLElement, shadowRoot: ShadowRoot, config: SwitchMarkerConfig);
8
+ initialize(): void;
9
+ }
@@ -0,0 +1,42 @@
1
+ import { MarkerController } from './MarkerController.js';
2
+ export class SwitchController extends MarkerController {
3
+ config;
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
+ initialize() {
11
+ const hostTag = this.host.tagName.toLowerCase();
12
+ const templateIdPrefix = `${hostTag}-${this.id}-`;
13
+ this.templates = Array.from(this.shadowRoot.querySelectorAll(`template[data-fluff-case^="${templateIdPrefix}"]`));
14
+ const deps = this.config.deps ?? [];
15
+ const update = () => {
16
+ this.clearContentBetweenMarkersWithCleanup(this.bindingsSubscriptions);
17
+ const switchValue = this.evaluateExpr(this.config.expressionExprId);
18
+ let matched = false;
19
+ let shouldFallthrough = false;
20
+ const renderContext = {
21
+ shouldBreak: false
22
+ };
23
+ for (let i = 0; i < this.config.cases.length; i++) {
24
+ if (renderContext.shouldBreak)
25
+ break;
26
+ const caseInfo = this.config.cases[i];
27
+ const template = this.templates[i];
28
+ if (!template)
29
+ continue;
30
+ const caseMatches = caseInfo.isDefault
31
+ || (caseInfo.valueExprId !== undefined && this.evaluateExpr(caseInfo.valueExprId) === switchValue);
32
+ if (shouldFallthrough || (!matched && caseMatches)) {
33
+ matched = true;
34
+ this.cloneAndInsertTemplate(template, this.loopContext, renderContext, this.bindingsSubscriptions);
35
+ shouldFallthrough = caseInfo.fallthrough;
36
+ }
37
+ }
38
+ };
39
+ this.subscribeTo(deps, update);
40
+ update();
41
+ }
42
+ }
@@ -0,0 +1,10 @@
1
+ import type { TextMarkerConfig } from '../interfaces/TextMarkerConfig.js';
2
+ import { MarkerController } from './MarkerController.js';
3
+ export declare class TextController extends MarkerController {
4
+ private readonly config;
5
+ private textNode;
6
+ constructor(id: number, startMarker: Comment, endMarker: Comment | null, host: HTMLElement, shadowRoot: ShadowRoot, config: TextMarkerConfig);
7
+ initialize(): void;
8
+ private formatValue;
9
+ private applyPipe;
10
+ }
@@ -0,0 +1,53 @@
1
+ import { MarkerController } from './MarkerController.js';
2
+ export class TextController extends MarkerController {
3
+ config;
4
+ textNode = null;
5
+ constructor(id, startMarker, endMarker, host, shadowRoot, config) {
6
+ super(id, startMarker, endMarker, host, shadowRoot);
7
+ this.config = config;
8
+ }
9
+ initialize() {
10
+ this.textNode = document.createTextNode('');
11
+ this.insertBeforeEndMarker(this.textNode);
12
+ const deps = this.config.deps ?? [];
13
+ const pipes = this.config.pipes ?? [];
14
+ const update = () => {
15
+ let result = this.evaluateExpr(this.config.exprId);
16
+ for (const pipe of pipes) {
17
+ result = this.applyPipe(pipe.name, result, pipe.argExprIds);
18
+ }
19
+ if (this.textNode) {
20
+ this.textNode.textContent = this.formatValue(result);
21
+ }
22
+ };
23
+ this.subscribeTo(deps, update);
24
+ update();
25
+ }
26
+ formatValue(result) {
27
+ if (result === null || result === undefined) {
28
+ return '';
29
+ }
30
+ if (typeof result === 'object') {
31
+ return JSON.stringify(result);
32
+ }
33
+ if (typeof result === 'string') {
34
+ return result;
35
+ }
36
+ if (typeof result === 'number' || typeof result === 'boolean') {
37
+ return String(result);
38
+ }
39
+ return '';
40
+ }
41
+ applyPipe(name, value, args) {
42
+ const pipes = this.host.__pipes;
43
+ if (!pipes)
44
+ return value;
45
+ const pipeFn = pipes[name];
46
+ if (!pipeFn) {
47
+ console.warn(`Pipe "${name}" not found`);
48
+ return value;
49
+ }
50
+ const evaluatedArgs = args.map(arg => this.evaluateExpr(arg));
51
+ return pipeFn(value, ...evaluatedArgs);
52
+ }
53
+ }
@@ -0,0 +1,10 @@
1
+ import { Publisher } from '../../utils/Publisher.js';
2
+ import { FluffElement } from '../FluffElementImpl.js';
3
+ export declare class DirectOutputChild extends FluffElement {
4
+ readonly submit: Publisher<{
5
+ value: string;
6
+ }>;
7
+ protected __render(): void;
8
+ protected __setupBindings(): void;
9
+ emitSubmit(value: string): void;
10
+ }
@@ -0,0 +1,14 @@
1
+ import { Publisher } from '../../utils/Publisher.js';
2
+ import { FluffElement } from '../FluffElementImpl.js';
3
+ export class DirectOutputChild extends FluffElement {
4
+ submit = new Publisher();
5
+ __render() {
6
+ this.__getShadowRoot().innerHTML = '<button data-lid="l0">Submit</button>';
7
+ }
8
+ __setupBindings() {
9
+ super.__setupBindings();
10
+ }
11
+ emitSubmit(value) {
12
+ this.submit.emit({ value });
13
+ }
14
+ }
@@ -0,0 +1,9 @@
1
+ import { FluffElement } from '../FluffElementImpl.js';
2
+ export declare class DirectOutputParent extends FluffElement {
3
+ receivedValue: string | null;
4
+ onSubmit(event: {
5
+ value: string;
6
+ }): void;
7
+ protected __render(): void;
8
+ protected __setupBindings(): void;
9
+ }
@@ -0,0 +1,17 @@
1
+ import { FluffElement } from '../FluffElementImpl.js';
2
+ export class DirectOutputParent extends FluffElement {
3
+ receivedValue = null;
4
+ onSubmit(event) {
5
+ this.receivedValue = event.value;
6
+ }
7
+ __render() {
8
+ this.__getShadowRoot().innerHTML = '<direct-output-child x-fluff-component data-lid="l0"></direct-output-child>';
9
+ const bindings = {
10
+ l0: [{ n: 'submit', b: 'event', h: 0, d: ['onSubmit'] }]
11
+ };
12
+ Reflect.set(this.constructor, '__bindings', bindings);
13
+ }
14
+ __setupBindings() {
15
+ super.__setupBindings();
16
+ }
17
+ }
@@ -0,0 +1,9 @@
1
+ export type TaskStatus = 'todo' | 'in-progress' | 'done';
2
+ export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent';
3
+ export interface TaskStats {
4
+ total: number;
5
+ byStatus: Record<TaskStatus, number>;
6
+ byPriority: Record<TaskPriority, number>;
7
+ overdue: number;
8
+ dueToday: number;
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { FluffElement } from '../FluffElement.js';
2
+ export declare class TestChildTasksListComponent extends FluffElement {
3
+ private readonly __tasks;
4
+ get tasks(): number[];
5
+ set tasks(value: number[]);
6
+ protected __render(): void;
7
+ protected __setupBindings(): void;
8
+ }
@@ -0,0 +1,28 @@
1
+ import { Property } from '../../utils/Property.js';
2
+ import { FluffElement } from '../FluffElement.js';
3
+ import { MarkerManager } from '../MarkerManager.js';
4
+ export class TestChildTasksListComponent extends FluffElement {
5
+ __tasks = new Property([]);
6
+ get tasks() {
7
+ return this.__tasks.getValue() ?? [];
8
+ }
9
+ set tasks(value) {
10
+ this.__tasks.setValue(value);
11
+ }
12
+ __render() {
13
+ this.__getShadowRoot().innerHTML = `
14
+ <!--fluff:for:0-->
15
+ <!--/fluff:for:0-->
16
+ <template data-fluff-tpl="test-child-tasks-list-0">
17
+ <div class="item"></div>
18
+ </template>
19
+ `;
20
+ this.__setMarkerConfigs(JSON.stringify([
21
+ [0, { type: 'for', iterator: 'task', iterableExprId: 2, deps: ['tasks'], hasEmpty: false }]
22
+ ]));
23
+ }
24
+ __setupBindings() {
25
+ this.__initializeMarkers(MarkerManager);
26
+ super.__setupBindings();
27
+ }
28
+ }
@@ -0,0 +1,6 @@
1
+ import { FluffElement } from '../FluffElement.js';
2
+ export declare class TestForChildComponent extends FluffElement {
3
+ value: string;
4
+ protected __render(): void;
5
+ protected __setupBindings(): void;
6
+ }
@@ -0,0 +1,10 @@
1
+ import { FluffElement } from '../FluffElement.js';
2
+ export class TestForChildComponent extends FluffElement {
3
+ value = '';
4
+ __render() {
5
+ this.__getShadowRoot().innerHTML = '<span>child</span>';
6
+ }
7
+ __setupBindings() {
8
+ super.__setupBindings();
9
+ }
10
+ }
@@ -0,0 +1,6 @@
1
+ import { FluffElement } from '../FluffElement.js';
2
+ export declare class TestForComponent extends FluffElement {
3
+ items: string[];
4
+ protected __render(): void;
5
+ protected __setupBindings(): void;
6
+ }