@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.
Files changed (77) hide show
  1. package/bundle.min.js +1 -1
  2. package/decorators/Directive.d.ts +16 -0
  3. package/decorators/Directive.js +23 -0
  4. package/decorators/HostElement.d.ts +1 -0
  5. package/decorators/HostElement.js +4 -0
  6. package/index.d.ts +4 -0
  7. package/index.js +3 -0
  8. package/interfaces/ElementWithDirectives.d.ts +4 -0
  9. package/interfaces/FluffHostElement.d.ts +3 -10
  10. package/package.json +1 -1
  11. package/runtime/BreakController.d.ts +2 -3
  12. package/runtime/BreakController.js +0 -3
  13. package/runtime/FluffBase.d.ts +50 -42
  14. package/runtime/FluffBase.js +185 -242
  15. package/runtime/FluffDirective.d.ts +9 -0
  16. package/runtime/FluffDirective.js +36 -0
  17. package/runtime/FluffElement.d.ts +0 -1
  18. package/runtime/FluffElementImpl.d.ts +5 -10
  19. package/runtime/FluffElementImpl.js +59 -77
  20. package/runtime/FluffMarkers.d.ts +0 -1
  21. package/runtime/FluffMarkers.js +0 -1
  22. package/runtime/ForController.d.ts +2 -5
  23. package/runtime/ForController.js +7 -9
  24. package/runtime/IfController.d.ts +2 -5
  25. package/runtime/IfController.js +12 -13
  26. package/runtime/MarkerController.d.ts +9 -11
  27. package/runtime/MarkerController.js +6 -3
  28. package/runtime/MarkerManager.d.ts +2 -5
  29. package/runtime/MarkerManager.js +11 -50
  30. package/runtime/MarkerManagerInterface.d.ts +5 -2
  31. package/runtime/SwitchController.d.ts +2 -5
  32. package/runtime/SwitchController.js +10 -13
  33. package/runtime/TextController.d.ts +2 -4
  34. package/runtime/TextController.js +8 -16
  35. package/runtime/tests/DirectOutputParent.js +4 -1
  36. package/runtime/tests/TestChildTasksListComponent.js +4 -1
  37. package/runtime/tests/TestForComponent.js +4 -1
  38. package/runtime/tests/TestForReinsertBindsInputParentComponent.js +7 -2
  39. package/runtime/tests/TestForTextMarkerCollisionNoTrackParentComponent.js +5 -2
  40. package/runtime/tests/TestForTextMarkerCollisionParentComponent.js +5 -10
  41. package/runtime/tests/TestForUnsubscribeNestedParentComponent.js +7 -2
  42. package/runtime/tests/TestGetterReactivityComponent.js +4 -1
  43. package/runtime/tests/TestHarness.d.ts +1 -1
  44. package/runtime/tests/TestHarness.js +3 -3
  45. package/runtime/tests/TestIfReinsertBindsInputChildComponent.js +4 -1
  46. package/runtime/tests/TestIfReinsertBindsInputParentComponent.js +7 -2
  47. package/runtime/tests/TestIfUnsubscribeNestedParentComponent.js +7 -2
  48. package/runtime/tests/TestInterpolationNestedPropertyComponentBase.js +4 -1
  49. package/runtime/tests/TestLateDefineForComponent.js +4 -1
  50. package/runtime/tests/TestNullInputTextComponent.js +5 -2
  51. package/runtime/tests/TestOutputBindingChildComponent.js +4 -1
  52. package/runtime/tests/TestOutputBindingParentComponent.js +7 -2
  53. package/runtime/tests/TestParentBindsTasksComponent.js +4 -1
  54. package/runtime/tests/TestSwitchReinsertBindsInputChildComponent.js +4 -1
  55. package/runtime/tests/TestSwitchReinsertBindsInputParentComponent.js +8 -10
  56. package/runtime/tests/TestSwitchUnsubscribeNestedParentComponent.js +7 -9
  57. package/runtime/tests/TestTemplateNestedMarkersComponent.js +5 -2
  58. package/runtime/tests/TestUnsubscribeNestedChildComponent.js +4 -1
  59. package/runtime/tests/TestUnsubscribeNestedGrandchildComponent.js +4 -1
  60. package/runtime/tests/createPipeUnwrapTestComponent.js +4 -1
  61. package/runtime/tests/createPropBindParentComponent.js +4 -1
  62. package/runtime/tests/createTestInterpolationPipeComponent.js +4 -1
  63. package/runtime/tests/createTestInterpolationPipeWithArgsComponent.js +4 -1
  64. package/interfaces/BreakMarkerConfig.d.ts +0 -3
  65. package/interfaces/ForMarkerConfig.d.ts +0 -8
  66. package/interfaces/ForMarkerConfig.js +0 -1
  67. package/interfaces/IfMarkerConfig.d.ts +0 -7
  68. package/interfaces/IfMarkerConfig.js +0 -1
  69. package/interfaces/MarkerConfig.d.ts +0 -6
  70. package/interfaces/MarkerConfig.js +0 -1
  71. package/interfaces/SwitchMarkerConfig.d.ts +0 -10
  72. package/interfaces/SwitchMarkerConfig.js +0 -1
  73. package/interfaces/TextMarkerConfig.d.ts +0 -9
  74. package/interfaces/TextMarkerConfig.js +0 -1
  75. package/runtime/MarkerConfigGuards.d.ts +0 -13
  76. package/runtime/MarkerConfigGuards.js +0 -17
  77. /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
- protected __pipe(name: string, value: unknown, ...args: unknown[]): unknown;
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
- const scopeId = this.getAttribute('data-fluff-scope-id');
32
- if (scopeId && !this.__parentScope) {
33
- this.__parentScope = getScope(scopeId);
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
- if (this.getAttribute('data-fluff-scope-id')) {
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
- __pipe(name, value, ...args) {
91
- const pipe = this.__pipes[name];
92
- if (!pipe) {
93
- console.warn(`Pipe "${name}" not found`);
94
- return value;
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
- __getReactivePropFromScope(propName, scope) {
160
- const key = `__${propName}`;
161
- if (key in scope.host) {
162
- const candidate = Reflect.get(scope.host, key);
163
- if (candidate instanceof Property) {
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';
@@ -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 { ForMarkerConfig } from '../interfaces/ForMarkerConfig.js';
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
  }
@@ -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
- const deps = this.config.deps ?? [];
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(this.config.iterableExprId);
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, [this.config.iterator]: items[i], $index: i
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 { IfMarkerConfig } from '../interfaces/IfMarkerConfig.js';
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
  }
@@ -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 this.config.branches) {
17
- if (branch.deps) {
18
- allDeps.push(...branch.deps);
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 < this.config.branches.length; i++) {
24
- const branch = this.config.branches[i];
25
- if (branch.exprId === undefined) {
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.exprId);
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
- interface MarkerManagerLike {
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: MarkerManagerLike | undefined;
21
- protected constructor(id: number, startMarker: Comment, endMarker: Comment | null, host: FluffHostElement, shadowRoot: ShadowRoot);
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: MarkerManagerLike): void;
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?: MarkerManagerLike, bindingsSubscriptions?: Subscription[]): void;
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?.cleanupController) {
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 && markerManager.ensureController) {
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;
@@ -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, rawConfig] of entries) {
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.type);
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?.type !== type) {
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
- if (MarkerConfigGuards.isIfConfig(config)) {
153
- return new IfController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
154
- }
155
- if (MarkerConfigGuards.isForConfig(config)) {
156
- return new ForController(id, startMarker, endMarker, this.host, this.shadowRoot, config);
157
- }
158
- if (MarkerConfigGuards.isSwitchConfig(config)) {
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
- export type MarkerConfigEntries = [number, MarkerConfig | CompactMarkerConfig][];
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 { SwitchMarkerConfig } from '../interfaces/SwitchMarkerConfig.js';
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
- const deps = this.config.deps ?? [];
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(this.config.expressionExprId);
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 < this.config.cases.length; i++) {
20
+ for (let i = 0; i < cases.length; i++) {
24
21
  if (renderContext.shouldBreak)
25
22
  break;
26
- const caseInfo = this.config.cases[i];
23
+ const [isDefault, fallthrough, valueExprId] = cases[i];
27
24
  const template = this.templates[i];
28
25
  if (!template)
29
26
  continue;
30
- const caseMatches = caseInfo.isDefault
31
- || (caseInfo.valueExprId !== undefined && this.evaluateExpr(caseInfo.valueExprId) === switchValue);
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 = caseInfo.fallthrough;
32
+ shouldFallthrough = fallthrough;
36
33
  }
37
34
  }
38
35
  this.refreshParentBindings();