arrmatura 6.2.1 → 6.3.1

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.
@@ -1,58 +1,50 @@
1
1
  import { areEqual, capitalize, identity } from "ultimus";
2
2
  import { stringify } from "../utils/stringify";
3
3
  import type { Delta, Fn, Hash, Proc, Uid } from "ultimus/types";
4
- import type {
5
- EProcedure,
6
- ICtx,
7
- ICtxNode,
8
- IComponent,
9
- IPlatform,
10
- } from "arrmatura-api/types";
4
+ import type { IEntitron, IManifestNode, IComponent, IPlatform } from "arrmatura/types";
5
+
6
+ type EProcedure = (c: IEntitron) => void;
11
7
 
12
8
  /**
13
- * The Component Context class.
14
- * It creates, refers and manages a component instance.
15
- *
16
- * A component instance gets reference to its context in 2nd constructor parameter.
9
+ * The Entitron is the essential the framework concept.
10
+ *
11
+ * It creates a own component instance,
12
+ * manages its state and behavior,
13
+ * and ties it up with others.
17
14
  */
18
- export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
15
+ export class Entitron<T extends IManifestNode = IManifestNode> implements IEntitron {
19
16
  readonly component: IComponent;
20
- $children?: Map<Uid, ICtx>;
21
- readonly root: ICtx;
22
- readonly scope: ICtx;
17
+ $children?: Map<Uid, IEntitron>;
18
+ readonly root: IEntitron;
19
+ readonly scope: IEntitron;
23
20
  isDone = false;
24
21
 
25
22
  #isInited = false;
26
23
  #state: Hash = {};
27
24
  #initialState: Hash = {};
28
- #listeners?: Set<(c: Ctx) => void>;
25
+ #listeners?: Set<(c: Entitron) => void>;
29
26
  #defered?: EProcedure[] = [];
30
27
  #weak?: Map<string, number>;
31
- #refs?: Hash<ICtx>;
28
+ #refs?: Hash<IEntitron>;
32
29
  #propFnMap?: Map<string, Proc>;
33
30
 
34
- constructor(
35
- readonly platform: IPlatform,
36
- protected readonly node: T,
37
- private _parent?: ICtx,
38
- scope?: ICtx
39
- ) {
31
+ constructor(readonly platform: IPlatform, protected readonly manifest: T, private _parent?: IEntitron, scope?: IEntitron) {
40
32
  this.scope = scope ?? this;
41
33
  this.root = scope?.root ?? this;
42
- this.#initialState = this.node.resolveInitialProps(this);
34
+ this.#initialState = this.manifest.resolveInitialProps(this);
43
35
  this.component = this.createComponent(this.#initialState);
44
36
 
45
37
  if (this.refId) {
46
- this.scope.addEntityReference(this.refId, this);
38
+ this.scope.addReference(this.refId, this);
47
39
  }
48
40
  }
49
41
 
50
- get parent(): ICtx | undefined {
42
+ get parent(): IEntitron | undefined {
51
43
  return this._parent;
52
44
  }
53
45
 
54
46
  get uid(): string {
55
- return this.node.uid;
47
+ return this.manifest.uid;
56
48
  }
57
49
 
58
50
  get displayName(): string {
@@ -60,6 +52,7 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
60
52
  }
61
53
 
62
54
  createComponent(initials: Hash): IComponent {
55
+ // by default, a component is just a plain object.
63
56
  return { ...initials };
64
57
  }
65
58
 
@@ -80,11 +73,7 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
80
73
  Object.entries(Δ).forEach(([k, v]) => {
81
74
  if (v instanceof Promise) {
82
75
  this.upAsync(v, k);
83
- } else if (
84
- k &&
85
- typeof v !== "undefined" &&
86
- !areEqual(v, this.#state[k])
87
- ) {
76
+ } else if (k && typeof v !== "undefined" && !areEqual(v, this.#state[k])) {
88
77
  this.#state[k] = v;
89
78
  changes.set(k, v);
90
79
  }
@@ -148,14 +137,9 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
148
137
  fn = () => val;
149
138
  } else {
150
139
  const gettr = impl[`get${capitalize(pk)}`];
151
- const fn0 =
152
- typeof gettr === "function"
153
- ? () => gettr.call(impl) ?? null
154
- : () => impl[pk] ?? null;
155
-
156
- fn = !path.length
157
- ? fn0
158
- : () => path.reduce((r, p) => r?.[p] ?? null, fn0());
140
+ const fn0 = typeof gettr === "function" ? () => gettr.call(impl) ?? null : () => impl[pk] ?? null;
141
+
142
+ fn = !path.length ? fn0 : () => path.reduce((r, p) => r?.[p] ?? null, fn0());
159
143
  }
160
144
  }
161
145
 
@@ -175,7 +159,7 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
175
159
  }
176
160
 
177
161
  // add listener to componet state changes
178
- subscribe(handler: (src: Ctx) => any): Proc {
162
+ subscribe(handler: (src: Entitron) => any): Proc {
179
163
  const listeners = this.#listeners ?? (this.#listeners = new Set());
180
164
  listeners.add(handler);
181
165
  return () => {
@@ -187,38 +171,46 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
187
171
 
188
172
  // emits action event to this component
189
173
  emit(key: string, data: Delta): void {
190
- if (!key || !key.includes(".")) {
191
- this.up(key ? { [key]: data } : data);
192
- return;
193
- }
194
-
195
- const [refId, target] = key.split(".");
196
174
  try {
175
+ // when key.endsWith("...") it will produce empty target, so don't need to hande it peculiarly
176
+ let [refId, target] = key.split(".");
177
+ if (!target) {
178
+ target = refId;
179
+ refId = 'this'
180
+ }
181
+
197
182
  const ref = this.getByRef(refId);
198
183
  if (!ref) {
199
184
  throw new Error(`No such reference: ${refId}`);
200
185
  }
201
- if (!target) {
186
+
187
+ if (!target) { // spread '...'
202
188
  ref.up(data);
203
189
  return;
204
190
  }
205
191
 
206
- const propId = `on${capitalize(target)}`;
192
+ if (target[0] === "@") {
193
+ ref.up({ [target.slice(1)]: data });
194
+ return;
195
+ }
196
+
207
197
  const impl = ref.component;
208
- const method = impl[propId];
198
+ const method = impl[target] ?? impl[`on${capitalize(target)}`];
209
199
 
200
+ // deprecated. Use @-prefix instead.
210
201
  if (!method) {
211
- ref.up({ [target]: data });
202
+ const propKey = target[0] === "@" ? target.slice(1) : target;
203
+ ref.up({ [propKey]: data });
212
204
  return;
213
205
  }
214
206
 
215
- if (!method || !(typeof method === "function")) {
216
- throw new Error(`No such method ${refId}.${propId}`);
207
+ if (!(typeof method === "function")) {
208
+ throw new Error(`Not a method: ${refId}.${target}`);
217
209
  }
218
210
 
219
211
  const result = method.call(impl, data, impl, ref);
220
212
 
221
- this.log(`-> ${refId}.${propId}`, result, data, impl);
213
+ this.log(`-> ${refId}.${target}`, result, data, impl);
222
214
 
223
215
  ref.up(result);
224
216
  } catch (ex) {
@@ -246,13 +238,13 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
246
238
  }
247
239
 
248
240
  // map of children contexts
249
- get children(): Map<Uid, ICtx> | undefined {
241
+ get children(): Map<Uid, IEntitron> | undefined {
250
242
  return this.$children;
251
243
  }
252
244
 
253
245
  // map of chidren of its node
254
- get content() {
255
- return this.node.getContent(this.platform);
246
+ get contentManifests() {
247
+ return this.manifest.getSubNodes(this.platform);
256
248
  }
257
249
 
258
250
  // actual scope for re-content
@@ -270,13 +262,13 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
270
262
 
271
263
  this.up(this.component.init?.(this));
272
264
  } else {
273
- void this.up(this.node.resolveProps(this), true);
265
+ void this.up(this.manifest.resolveProps(this), true);
274
266
  }
275
267
  }
276
268
 
277
269
  // perform children instantiation and updates
278
270
  private recontent() {
279
- const nodes = this.content;
271
+ const nodes = this.contentManifests;
280
272
 
281
273
  this.platform.redraw(this, this.root);
282
274
 
@@ -286,12 +278,10 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
286
278
  }
287
279
  });
288
280
 
289
- const children = new Map<Uid, ICtx>();
281
+ const children = new Map<Uid, IEntitron>();
290
282
 
291
- nodes?.forEach((node: ICtxNode, uid: Uid) => {
292
- const e =
293
- this.children?.get(uid) ??
294
- node.createContext(this.platform, this, this.recontentScope);
283
+ nodes?.forEach((node: IManifestNode, uid: Uid) => {
284
+ const e = this.children?.get(uid) ?? node.createEntitron(this.platform, this, this.recontentScope);
295
285
  children.set(e.uid, e);
296
286
  });
297
287
 
@@ -305,11 +295,11 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
305
295
  // --- Referencing.
306
296
 
307
297
  get refId() {
308
- return this.node.refId;
298
+ return this.manifest.refId;
309
299
  }
310
300
 
311
301
  // gets any context instance in current or upper scope by key
312
- getByRef(refId: string): ICtx | undefined {
302
+ getByRef(refId: string): IEntitron | undefined {
313
303
  if (!refId) return undefined;
314
304
 
315
305
  if (refId === "this") {
@@ -320,41 +310,39 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
320
310
  return local;
321
311
  }
322
312
  const ref = this === this.scope ? null : this.scope.getByRef(refId);
323
- return ref as Ctx;
313
+ return ref as Entitron;
324
314
  }
325
315
 
326
- addEntityReference(refId: string, c: ICtx) {
316
+ addReference(refId: string, c: IEntitron) {
327
317
  (this.#refs ?? (this.#refs = {}))[refId] = c;
328
318
  }
329
319
 
330
320
  // --- Routines -------------------------
331
321
 
332
322
  private initConnectors() {
333
- if (this.node.connectors) {
334
- for (const [key, pipes] of this.node.connectors.entries()) {
323
+ if (this.manifest.connectors) {
324
+ for (const [key, pipes] of this.manifest.connectors.entries()) {
335
325
  const [srcIds, targetPropName] = key.split("|");
336
- const [refId, ...propIds] = srcIds.split(".");
326
+ const [refId, sourcePropName] = srcIds.split(".");
337
327
 
338
- const ref = this.scope.getByRef(refId) as Ctx;
328
+ const ref = this.scope.getByRef(refId) as Entitron;
339
329
  if (!ref) {
340
330
  this.logError(`Connect: No such ref: ${refId}`);
341
331
  continue;
342
332
  }
343
333
 
344
- if (!propIds.length) {
334
+ if (!sourcePropName) {
345
335
  this.logError(`Connect: No prop specified: ${key}`);
346
336
  continue;
347
337
  }
348
338
 
349
- const propId = propIds.join(".");
350
- const applicator = targetPropName
351
- ? (rr: any) => ({ [targetPropName]: rr })
352
- : identity;
339
+ const propName = sourcePropName[0] === '@' ? sourcePropName.slice(1) : sourcePropName;
340
+ const applicator = targetPropName ? (rr: any) => ({ [targetPropName]: rr }) : identity;
353
341
 
354
342
  this.defer(
355
- ref.subscribe((source: Ctx) => {
343
+ ref.subscribe((source: Entitron) => {
356
344
  try {
357
- const r = applicator(pipes(this, source.get(propId)));
345
+ const r = applicator(pipes(this, source.get(propName)));
358
346
  this.up(r as Delta);
359
347
  } catch (ex) {
360
348
  source.logError("Notify ", ex);
@@ -363,10 +351,7 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
363
351
  );
364
352
 
365
353
  // hot subscription
366
- Object.assign(
367
- this.#initialState,
368
- applicator(pipes(ref, ref.get(propId)))
369
- );
354
+ Object.assign(this.#initialState, applicator(pipes(ref, ref.get(propName))));
370
355
  }
371
356
  }
372
357
  }
@@ -405,10 +390,7 @@ export class Ctx<T extends ICtxNode = ICtxNode> implements ICtx {
405
390
  level: "error",
406
391
  source: `${this.displayName}:${this.uid}`,
407
392
  error,
408
- message: `${String(error.message || error)}\n${this.toString().slice(
409
- 0,
410
- 120
411
- )}`,
393
+ message: `${String(error.message || error)}\n${this.toString().slice(0, 120)}`,
412
394
  params: args,
413
395
  });
414
396
  }
@@ -0,0 +1,139 @@
1
+ import { nextId } from "ultimus";
2
+ import { compilePipeExpression } from "../utils/compileExpression";
3
+ import type { Delta, ExpressionText, Hash, Uid } from "ultimus/types";
4
+ import type { IPlatform, IEntitron, IManifestNode } from "arrmatura/types";
5
+ import { resolveExpression } from "./resolveExpression";
6
+
7
+ type PropertyResolver = (c: IEntitron, acc: Delta) => Delta;
8
+
9
+ type PropertyGetter = (c: IEntitron) => unknown;
10
+
11
+ /**
12
+ * ManifestNode is a abstract factory for Entitron.
13
+ *
14
+ * Each specific kind of Entitron has its own descending implementation of ManifestNode.
15
+ */
16
+ export abstract class ManifestNode implements IManifestNode {
17
+ protected $content?: Map<Uid, IManifestNode> | undefined;
18
+ uid: Uid = nextId("N");
19
+ refId?: string;
20
+ private readonly propertyResolvers: PropertyResolver[] = [];
21
+ private initialState: Hash<(c: IEntitron) => any> = {};
22
+ connectors?: Map<string, (c: IEntitron, x: unknown) => unknown>;
23
+ tag: any;
24
+
25
+ abstract get EntitronConstructor(): new (platform: IPlatform, node: typeof this, parent?: IEntitron, scope?: IEntitron) => IEntitron;
26
+
27
+ /**
28
+ * Create a context object.
29
+ *
30
+ * @param {IPlatform} platform - The platform object.
31
+ * @param {IEntitron} parent - The parent context object (optional).
32
+ * @param {IEntitron} scope - The scope context object (optional).
33
+ * @return {IEntitron} The created context object.
34
+ */
35
+ createEntitron(platform: IPlatform, parent?: IEntitron, scope?: IEntitron): IEntitron {
36
+ return new this.EntitronConstructor(platform, this, parent, scope);
37
+ }
38
+
39
+ getSubNodes(_platform: IPlatform): Map<Uid, IManifestNode> | undefined {
40
+ return this.$content;
41
+ }
42
+
43
+ addPropertyResolver(getter: PropertyGetter, propKey: string) {
44
+ this.propertyResolvers.push((c: IEntitron, delta: Delta) => {
45
+ delta[propKey] = getter(c);
46
+ return delta;
47
+ });
48
+ return this;
49
+ }
50
+
51
+ private addDataPropertyResolver(getter: PropertyGetter, propKey: string) {
52
+ this.propertyResolvers.push((c: IEntitron, delta: Delta) => {
53
+ const val = getter(c);
54
+ delta.data = Object.assign((delta.data as object) || {}, {
55
+ [propKey]: val,
56
+ });
57
+ return delta;
58
+ });
59
+ return this;
60
+ }
61
+
62
+ private addPropertiesResolver(getter: PropertyGetter) {
63
+ this.propertyResolvers.push((c: IEntitron, delta: Delta) => {
64
+ const value = getter(c);
65
+ if (value && typeof value === "object") {
66
+ Object.entries(value).forEach(([key, val]) => {
67
+ delta[key] = val;
68
+ });
69
+ }
70
+ return delta;
71
+ });
72
+ return this;
73
+ }
74
+
75
+ addConnector(expr: ExpressionText, propName: string) {
76
+ const { key, pipec } = compilePipeExpression(expr);
77
+ (this.connectors ?? (this.connectors = new Map())).set(`${key}|${propName}`, pipec);
78
+ }
79
+
80
+ private addEmitter(expr: ExpressionText, k: string) {
81
+ const { key, pipec } = compilePipeExpression(expr);
82
+ this.initialState[k] = (c) => (data: unknown) => c.scope?.emit(key, pipec(c, data) as Delta);
83
+ }
84
+
85
+ resolveInitialProps(c: IEntitron) {
86
+ return this.propertyResolvers.reduce(
87
+ (delta, resolver) => resolver(c, delta),
88
+ Object.entries(this.initialState).reduce((acc, [key, fn]) => {
89
+ acc[key] = fn(c);
90
+ return acc;
91
+ }, {} as Hash)
92
+ );
93
+ }
94
+
95
+ resolveProps(c: IEntitron) {
96
+ return this.propertyResolvers.reduce((delta, resolver) => resolver(c, delta), {});
97
+ }
98
+
99
+ compileAttribute(k: string, v: unknown) {
100
+ if (k.startsWith("data-")) {
101
+ this.addDataPropertyResolver(resolveExpression(v), k.slice(5));
102
+ } else if (k === "ref") {
103
+ this.refId = String(v);
104
+ } else if (k === "props" || k.startsWith('{...')) {
105
+ const sv = k.startsWith('{...') ? k.slice(4, -1) : String(v);
106
+ if (sv[0] === "<" && sv[1] === "-") {
107
+ this.addConnector(sv.slice(2), "");
108
+ } else {
109
+ this.addPropertiesResolver(resolveExpression(sv));
110
+ }
111
+ } else {
112
+ if (typeof v !== "string") {
113
+ this.initialState[k] = () => v;
114
+ } else {
115
+ if (v[0] === "<" && v[1] === "-") {
116
+ this.addConnector(v.slice(2), k);
117
+ } else if (v[0] === "-" && v[1] === ">") {
118
+ this.addEmitter(v.slice(2), k);
119
+ } else {
120
+ if (!v.includes("{") && v[0] !== "@") {
121
+ this.initialState[k] = () => v;
122
+ } else {
123
+ if (v[0] === "@" && v[1] === "@" && v.match(/^@@[a-z0-9\-_.]+$/i) != null) {
124
+ this.initialState[k] = (c) => c.platform.getResource(v.slice(2));
125
+ } else {
126
+ this.addPropertyResolver(resolveExpression(v), k);
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ compileAttributes(attrs?: Hash) {
135
+ if (attrs) {
136
+ Object.entries(attrs).forEach((keyValue) => this.compileAttribute(...keyValue));
137
+ }
138
+ }
139
+ }
@@ -0,0 +1,20 @@
1
+ import { createStringInterpolator } from "ultimus/src/parsing/createStringInterpolator";
2
+ import { compileConstant, compileJsExpression, compilePlaceholder } from "../utils/compileExpression";
3
+
4
+ const CACHE = new Map();
5
+
6
+ function compileExpression(v: unknown) {
7
+ if (typeof v !== "string") return () => v;
8
+
9
+ if (v[0] === "@") return compilePlaceholder(v);
10
+
11
+ if (v.startsWith("js:")) return compileJsExpression(v.slice(3));
12
+
13
+ if (v.includes("{")) return createStringInterpolator(v.replace(/\s+/g, " "), compilePlaceholder);
14
+
15
+ return compileConstant(v);
16
+ }
17
+
18
+ export const resolveExpression = (x: unknown) => {
19
+ return CACHE.has(x) ? CACHE.get(x) : CACHE.set(x, compileExpression(x)).get(x);
20
+ }
@@ -1,31 +1,31 @@
1
- import { CtxNode } from "../ctx/CtxNode";
2
- import { Ctx } from "../ctx/Ctx";
1
+ import { ManifestNode } from "../core/ManifestNode";
2
+ import { Entitron } from "../core/Entitron";
3
3
  import type { Hash, XmlNode } from "ultimus/types";
4
- import type { EContent, ICtxNode, IPlatform } from "arrmatura-api/types";
4
+ import type { EContent, IManifestNode, IPlatform } from "arrmatura/types";
5
5
 
6
- export class Composite extends Ctx<CCompositeNode> {
6
+ export class Composite extends Entitron<CCompositeNode> {
7
7
  get displayName(): string {
8
- return `💠${this.node.tag}`;
8
+ return `💠${this.manifest.tag}`;
9
9
  }
10
10
 
11
- get recontentScope(): Ctx {
11
+ get recontentScope(): Entitron {
12
12
  return this;
13
13
  }
14
14
 
15
15
  slotContent(slotId?: string): EContent | undefined {
16
- return this.node.getSlotContent(this.platform, slotId);
16
+ return this.manifest.getSlotContent(this.platform, slotId);
17
17
  }
18
18
 
19
19
  createComponent(initials: Hash) {
20
- return this.platform.createComponent(this.node.tag, initials, this);
20
+ return this.platform.createComponent(this.manifest.tag, initials, this);
21
21
  }
22
22
  }
23
23
 
24
- export class CCompositeNode extends CtxNode {
24
+ export class CCompositeNode extends ManifestNode {
25
25
  slotContent: Hash<EContent> | null = null;
26
26
  tag: string;
27
27
  nodes?: XmlNode["nodes"];
28
- #content: Map<string, ICtxNode>;
28
+ #content?: Map<string, IManifestNode>;
29
29
 
30
30
  constructor({ tag, attrs, nodes }: XmlNode) {
31
31
  super();
@@ -43,29 +43,24 @@ export class CCompositeNode extends CtxNode {
43
43
  gId = e.tag.split(":")[1];
44
44
  isContainer = true;
45
45
  }
46
- groups[gId] = (groups[gId] || []).concat(
47
- isContainer ? e.nodes ?? [] : e
48
- );
46
+ groups[gId] = (groups[gId] || []).concat(isContainer ? e.nodes ?? [] : e);
49
47
  return groups;
50
48
  }, {});
51
49
 
52
- this.slotContent = Object.entries(slots ?? {}).reduce<Hash<EContent>>(
53
- (groups, [gId, nodes]) => {
54
- groups[gId] = platform.getCompiledNodes(nodes);
55
- return groups;
56
- },
57
- {}
58
- );
50
+ this.slotContent = Object.entries(slots ?? {}).reduce<Hash<EContent>>((groups, [gId, nodes]) => {
51
+ groups[gId] = platform.getCompiledNodes(nodes);
52
+ return groups;
53
+ }, {});
59
54
  }
60
55
 
61
56
  return this.slotContent[slotId ?? "default"] ?? undefined;
62
57
  }
63
58
 
64
- getContent(platform: IPlatform) {
59
+ getSubNodes(platform: IPlatform) {
65
60
  return this.#content ?? (this.#content = platform.getCompiledNodes(this.tag));
66
61
  }
67
62
 
68
- get EntityConstructor() {
63
+ get EntitronConstructor() {
69
64
  return Composite;
70
65
  }
71
66
  }
@@ -1,13 +1,13 @@
1
- import { CtxNode } from "../ctx/CtxNode";
1
+ import { ManifestNode } from "../core/ManifestNode";
2
2
  import { Composite } from "./composition";
3
- import { Ctx } from "../ctx/Ctx";
4
- import type { EContent, ICtx, IPlatform } from "arrmatura-api/types";
3
+ import { Entitron } from "../core/Entitron";
4
+ import type { EContent, IEntitron, IPlatform } from "arrmatura/types";
5
5
  import type { XmlNode } from "ultimus/types";
6
6
  import { compilePlaceholder } from "../utils/compileExpression";
7
7
 
8
- class Conditional extends Ctx<CIfNode> {
9
- get content() {
10
- const branches = this.node.getBranches(this.platform);
8
+ class Conditional extends Entitron<CIfNode> {
9
+ get contentManifests() {
10
+ const branches = this.manifest.getBranches(this.platform);
11
11
  return this.component.condition ? branches.then : branches.else;
12
12
  }
13
13
 
@@ -16,7 +16,7 @@ class Conditional extends Ctx<CIfNode> {
16
16
  }
17
17
  }
18
18
 
19
- export class CIfNode extends CtxNode {
19
+ export class CIfNode extends ManifestNode {
20
20
  #xml: XmlNode;
21
21
  #branches?: { then?: EContent; else?: EContent };
22
22
  constructor(xml: XmlNode, expr: string) {
@@ -25,7 +25,7 @@ export class CIfNode extends CtxNode {
25
25
  this.compileCondition(expr);
26
26
  }
27
27
 
28
- get EntityConstructor() {
28
+ get EntitronConstructor() {
29
29
  return Conditional;
30
30
  }
31
31
 
@@ -43,16 +43,14 @@ export class CIfNode extends CtxNode {
43
43
  const ifElse = nodes.find((e) => e.tag === "Else");
44
44
  const ifThen = nodes.find((e) => e.tag === "Then");
45
45
  if (ifElse) {
46
- this.#branches.else = ifElse.nodes
47
- ? platform.getCompiledNodes(ifElse.nodes)
48
- : undefined;
46
+ this.#branches.else = ifElse.nodes ? platform.getCompiledNodes(ifElse.nodes) : undefined;
49
47
  $then = ifThen
50
48
  ? ifThen.nodes
51
49
  : [
52
- Object.assign(this.#xml, {
53
- nodes: nodes.filter((e) => e !== ifElse),
54
- }),
55
- ];
50
+ Object.assign(this.#xml, {
51
+ nodes: nodes.filter((e) => e !== ifElse),
52
+ }),
53
+ ];
56
54
  } else if (ifThen) {
57
55
  $then = ifThen.nodes;
58
56
  }
@@ -68,10 +66,7 @@ export class CIfNode extends CtxNode {
68
66
  } else if (expr.slice(0, 5) === "slot(") {
69
67
  const slotId = expr.slice(5, -1);
70
68
  // DO NOT place inside iterations.
71
- this.addPropertyResolver(
72
- (c: ICtx) => !!(c.scope as Composite).slotContent?.(slotId)?.size,
73
- "condition"
74
- );
69
+ this.addPropertyResolver((c: IEntitron) => !!(c.scope as Composite).slotContent?.(slotId)?.size, "condition");
75
70
  } else {
76
71
  const resolver = compilePlaceholder(expr);
77
72
  this.addPropertyResolver((c) => !!resolver(c), "condition");
@@ -0,0 +1,36 @@
1
+ import { ManifestNode } from "../core/ManifestNode";
2
+ import { Entitron } from "../core/Entitron";
3
+ import type { Hash, XmlNode } from "ultimus/types";
4
+ import { IPlatform } from "arrmatura/types";
5
+
6
+ class NativeElementEntitron extends Entitron<CElementNode> {
7
+ get displayName(): string {
8
+ return this.manifest.tag;
9
+ }
10
+
11
+ createComponent(initials: Hash) {
12
+ return this.platform.createComponent({ tag: this.manifest.tag, native: true }, initials, this);
13
+ }
14
+ }
15
+
16
+ export class CElementNode extends ManifestNode {
17
+ #nodes: XmlNode[] | undefined;
18
+
19
+ constructor({ tag, attrs, nodes, text }: XmlNode) {
20
+ super();
21
+ this.tag = tag;
22
+ this.compileAttributes(attrs);
23
+ if (text) {
24
+ this.compileAttribute("#text", text);
25
+ }
26
+ this.#nodes = nodes;
27
+ }
28
+
29
+ getSubNodes(platform: IPlatform) {
30
+ return this.#nodes ? platform.getCompiledNodes(this.#nodes) : undefined;
31
+ }
32
+
33
+ get EntitronConstructor() {
34
+ return NativeElementEntitron;
35
+ }
36
+ }