arrmatura 4.2.2 → 5.0.2

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 (93) hide show
  1. package/README.md +30 -25
  2. package/commons/auth/UserService.ts +107 -0
  3. package/commons/auth/index.ts +4 -0
  4. package/commons/auth/user.xml +66 -0
  5. package/commons/components/buttons.xml +23 -0
  6. package/commons/components/index.ts +13 -0
  7. package/commons/components/inputs.xml +33 -0
  8. package/commons/components/layouts.xml +77 -0
  9. package/commons/components/media.xml +21 -0
  10. package/commons/components/modal.xml +37 -0
  11. package/commons/components/navigation.xml +59 -0
  12. package/commons/components/popups.xml +12 -0
  13. package/commons/components/searchbar.xml +40 -0
  14. package/commons/components/table.xml +120 -0
  15. package/commons/components/toasts.xml +13 -0
  16. package/commons/components/viewport.xml +48 -0
  17. package/commons/forms/FieldItem.xml +20 -0
  18. package/commons/forms/FormController.ts +73 -0
  19. package/commons/forms/Item.ts +68 -0
  20. package/commons/forms/ItemCollection.ts +152 -0
  21. package/commons/forms/ItemController.ts +62 -0
  22. package/commons/forms/MetadataService.ts +45 -0
  23. package/commons/forms/MultiEnumField.xml +44 -0
  24. package/commons/forms/MultivalueController.ts +49 -0
  25. package/commons/forms/SmartareaField.xml +51 -0
  26. package/commons/forms/SuggestionController.ts +80 -0
  27. package/commons/forms/SuggestionField.xml +28 -0
  28. package/commons/forms/fields.xml +138 -0
  29. package/commons/forms/form.xml +37 -0
  30. package/commons/forms/index.ts +29 -0
  31. package/commons/index.ts +6 -0
  32. package/commons/pipes/index.ts +21 -0
  33. package/commons/pipes/showCounts.ts +16 -0
  34. package/commons/services/ErrorHandlingService.ts +11 -0
  35. package/commons/services/FirebaseService.ts +185 -0
  36. package/commons/services/GSheetsService.ts +92 -0
  37. package/commons/services/HeartbeatService.ts +35 -0
  38. package/commons/services/Invoke.ts +13 -0
  39. package/commons/services/JsonpService.ts +18 -0
  40. package/commons/services/Loader.ts +17 -0
  41. package/commons/services/LocalStorage.ts +46 -0
  42. package/commons/services/NavigationService.ts +55 -0
  43. package/commons/services/Service.ts +68 -0
  44. package/commons/services/ServiceWorker.ts +111 -0
  45. package/commons/services/ToastService.ts +49 -0
  46. package/commons/services/index.ts +14 -0
  47. package/core/Arrmatura.ts +79 -0
  48. package/core/CNode.ts +112 -0
  49. package/core/Component.ts +431 -0
  50. package/core/Platform.ts +3 -0
  51. package/core/cnodes/composition.ts +100 -0
  52. package/core/cnodes/conditionals.ts +51 -0
  53. package/core/cnodes/elementary.ts +45 -0
  54. package/core/cnodes/iterations.ts +94 -0
  55. package/core/cnodes/routing.ts +25 -0
  56. package/core/compileNode.ts +39 -0
  57. package/core/expression.ts +73 -0
  58. package/core/index.ts +2 -0
  59. package/core/register.ts +43 -0
  60. package/core/utils.ts +25 -0
  61. package/core-web/WebPlatform.ts +25 -0
  62. package/core-web/attributes.ts +197 -0
  63. package/core-web/elements.ts +64 -0
  64. package/core-web/index.ts +19 -0
  65. package/core-web/reflow.ts +30 -0
  66. package/core-web/type.ts +19 -0
  67. package/docs/index.js +191 -4077
  68. package/lib/arrayGroupBy.ts +19 -0
  69. package/lib/arraySortBy.ts +49 -0
  70. package/lib/arrayToObject.ts +18 -0
  71. package/lib/codus.ts +25 -0
  72. package/lib/consts.ts +3 -0
  73. package/lib/createListeners.ts +12 -0
  74. package/lib/curry.ts +8 -0
  75. package/lib/dateFormat.ts +69 -0
  76. package/lib/dateOf.ts +31 -0
  77. package/lib/dateParseISO8601.ts +27 -0
  78. package/lib/fx.ts +62 -0
  79. package/lib/guid.ts +7 -0
  80. package/lib/index.ts +19 -0
  81. package/lib/l10n.ts +40 -0
  82. package/lib/nextId.ts +9 -0
  83. package/lib/noop.ts +8 -0
  84. package/lib/obj.ts +62 -0
  85. package/lib/predicat.ts +26 -0
  86. package/lib/resources.ru.ts +20 -0
  87. package/lib/resources.ts +20 -0
  88. package/lib/scalar.ts +25 -0
  89. package/lib/str.ts +76 -0
  90. package/lib/urls.ts +91 -0
  91. package/lib/xml.ts +117 -0
  92. package/package.json +17 -8
  93. package/docs/index.js.map +0 -1
package/core/CNode.ts ADDED
@@ -0,0 +1,112 @@
1
+ import { nextId } from 'nextId';
2
+ import { identity } from 'noop';
3
+ import { Component } from './Component';
4
+ import { compileExpression, compilePipeExpression } from './expression';
5
+
6
+ /**
7
+ * Design-time Component class.
8
+ */
9
+ export abstract class CNode<T extends Component = Component> implements INode<T> {
10
+ content?: ComponentContent;
11
+ uid: Uid = nextId('N');
12
+ refId?: string;
13
+ private propertyResolvers: Array<PropertyResolver> = [];
14
+ private initialState?: Hash;
15
+ initializers: Array<(c: IComponent) => undefined | ComponentSubsription> = [];
16
+ tag: any;
17
+
18
+ abstract get ComponentConstructor(): new (node: INode<T>, parent?: IComponent, scope?: IScope) => T;
19
+
20
+ createComponent(parent?: IComponent, scope?: Component): T {
21
+ return new this.ComponentConstructor(this as INode<T>, parent, scope);
22
+ }
23
+
24
+ addInitialState(values: Hash) {
25
+ this.initialState = Object.entries(values).reduce((acc, [key, val]) => {
26
+ acc[key] = val;
27
+ return acc;
28
+ }, this.initialState || {});
29
+ return this;
30
+ }
31
+
32
+ addPropertyResolver(getter: PropertyGetter, propKey: string) {
33
+ this.propertyResolvers.push((c: IComponent, delta: Delta) => {
34
+ delta[propKey] = getter(c);
35
+ return delta;
36
+ });
37
+ return this;
38
+ }
39
+
40
+ addPropertiesResolver(getter: PropertyGetter) {
41
+ this.propertyResolvers.push((c: IComponent, delta: Delta) => {
42
+ const value = getter(c);
43
+ if (value && typeof value === 'object') {
44
+ Object.entries(value).forEach(([key, val]) => {
45
+ delta[key] = val;
46
+ });
47
+ }
48
+ return delta;
49
+ });
50
+ return this;
51
+ }
52
+
53
+ addPropertyConnector(expr: Expression, getter: (x: unknown) => unknown) {
54
+ const { key, pipec } = compilePipeExpression(expr);
55
+ this.initializers.push((c: IComponent) => c.connect(key, (rr: unknown) => getter(pipec(c, rr))));
56
+ }
57
+
58
+ addEmitter(expr: Expression, k: PropertyKey) {
59
+ const { key, pipec } = compilePipeExpression(expr);
60
+ this.initializers.push((c) => ({
61
+ payload: { [k]: (data: unknown) => c.scope?.emit(key, pipec(c, data) as Delta) },
62
+ }));
63
+ }
64
+
65
+ resolveInitialProps(c: IComponent) {
66
+ return this.resolveProps(c, { ...this.initialState });
67
+ }
68
+
69
+ resolveProps(c: IComponent, props: Delta = {}) {
70
+ return this.propertyResolvers.reduce((delta, resolver) => resolver(c, delta), props);
71
+ }
72
+
73
+ compileAttribute(k: string, v: unknown) {
74
+ if (k === 'id') {
75
+ this.addInitialState({ [k]: v });
76
+ } else if (k.startsWith('ui:')) {
77
+ const sv = String(v);
78
+ if (k === 'ui:ref') {
79
+ this.refId = sv;
80
+ } else if (k === 'ui:props') {
81
+ if (sv[0] === '<' && sv[1] === '-') {
82
+ this.addPropertyConnector(sv.slice(2), identity);
83
+ } else {
84
+ this.addPropertiesResolver(compileExpression(v));
85
+ }
86
+ }
87
+ } else {
88
+ if (typeof v !== 'string') {
89
+ this.addInitialState({ [k]: v });
90
+ } else {
91
+ if (v[0] === '<' && v[1] === '-') {
92
+ this.addPropertyConnector(v.slice(2), (rr) => ({ [k]: rr }));
93
+ } else if (v[0] === '-' && v[1] === '>') {
94
+ this.addEmitter(v.slice(2), k);
95
+ } else {
96
+ if (!v.includes('{') && v[0] !== ':') {
97
+ this.addInitialState({ [k]: v });
98
+ } else {
99
+ this.addPropertyResolver(compileExpression(v), k);
100
+ }
101
+ }
102
+ }
103
+ }
104
+ return this;
105
+ }
106
+
107
+ compileAttributes(attrs?: Hash) {
108
+ if (attrs) {
109
+ Object.entries(attrs).forEach((keyValue) => this.compileAttribute(...keyValue));
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,431 @@
1
+ /* eslint-disable no-console */
2
+ import { applyValue, methodName, stringifyState } from './utils';
3
+ import { scalarParse } from 'scalar';
4
+ import { createListeners } from 'createListeners';
5
+
6
+ /**
7
+ * Runtime Component class.
8
+ */
9
+ export class Component<T extends INode = INode> implements IComponent, IScope {
10
+ impl: IComponentImplementation;
11
+ element?: any;
12
+
13
+ readonly scope: IScope;
14
+ children?: Map<Uid, IComponent>;
15
+
16
+ protected state: any = {};
17
+
18
+ private _parent?: IComponent;
19
+ #isInited: boolean = false;
20
+ #isDone: boolean = false;
21
+ // #notifying: boolean = false;
22
+ #listeners?: Listeners<void>;
23
+ #defered?: Array<ComponentProcedure> = [];
24
+ #weak?: Map<any, any>;
25
+ #refs?: Hash<Component>;
26
+ #propFnMap?: Hash<Procedure>;
27
+
28
+ constructor(readonly _node: INode, _parent?: IComponent, scope?: IScope) {
29
+ this.scope = scope || this;
30
+ this._parent = _parent;
31
+ this.impl = this.createImplementation(this.getInitialState());
32
+
33
+ if (this.refId) {
34
+ this.scope.addComponentReference(this.refId, this);
35
+ }
36
+ }
37
+
38
+ get parent(): IComponent {
39
+ return this._parent || this;
40
+ }
41
+
42
+ get node(): T {
43
+ return this._node as T;
44
+ }
45
+
46
+ get displayName(): string {
47
+ return '🔹';
48
+ }
49
+
50
+ get arrmatura(): IArrmatura {
51
+ return this.parent.arrmatura;
52
+ }
53
+
54
+ get uid() {
55
+ return this.node.uid;
56
+ }
57
+
58
+ createImplementation(initials: Hash): IComponentImplementation {
59
+ return { ...initials };
60
+ }
61
+
62
+ // --- State
63
+
64
+ getInitialState(): Hash {
65
+ return this.node.resolveInitialProps(this);
66
+ }
67
+
68
+ up(Δ: Delta | Promise<any> = {}, force: boolean = false): void {
69
+ if (this.#isDone) {
70
+ return;
71
+ }
72
+ if (Δ.then && Δ.catch) {
73
+ this.upAsync(Δ as Promise<any>);
74
+ return;
75
+ }
76
+
77
+ const changes = new Map<string, unknown>();
78
+ Object.entries(Δ).forEach(([k, v]) => {
79
+ const pv = v as Promise<any>;
80
+ if (v && pv.then && pv.catch) {
81
+ this.upAsync(pv, k);
82
+ } else if (k && typeof v !== 'undefined' && v !== this.state[k]) {
83
+ changes.set(k, v);
84
+ this.state[k] = v;
85
+ }
86
+ });
87
+
88
+ if (changes.size || force) {
89
+ this.stateChanged(changes);
90
+ this.recontent();
91
+ if (this.refId) {
92
+ this.notify();
93
+ }
94
+ }
95
+ }
96
+
97
+ upAsync(promise: Promise<any>, key?: string) {
98
+ const racer = this.raceCondition('set:' + key);
99
+ const up = (r: Delta) => racer(() => this.up(r));
100
+ if (key && key !== '*') {
101
+ const akey = key.replace('Promise', '');
102
+ promise.then((val) => up({ [akey]: val }));
103
+ } else {
104
+ promise.then(up);
105
+ }
106
+ return promise;
107
+ }
108
+
109
+ stateChanged(changes: Map<string, unknown>) {
110
+ if (this.impl.stateChanged) {
111
+ this.impl.stateChanged(changes);
112
+ } else {
113
+ changes.forEach((v, k) => {
114
+ const setter = this.impl[methodName(k, 'set')];
115
+ if (typeof setter === 'function') {
116
+ setter.call(this.impl, v);
117
+ } else {
118
+ this.impl[k] = v;
119
+ }
120
+ });
121
+ }
122
+ }
123
+
124
+ get(propId: string): unknown {
125
+ const map = this.#propFnMap || (this.#propFnMap = {});
126
+ let fn = map[propId];
127
+ if (fn) {
128
+ return fn();
129
+ }
130
+
131
+ const impl = this.impl;
132
+ const instant = impl[propId];
133
+ if (instant && typeof instant === 'function') {
134
+ const bound = instant.bind(impl);
135
+ fn = () => bound;
136
+ } else {
137
+ const [pk, ...path] = propId.split('.');
138
+ const gettr = impl[methodName(pk, 'get')];
139
+
140
+ const fn0 = typeof gettr === 'function' ? () => gettr.call(impl) : () => impl[pk];
141
+
142
+ fn = !path.length ? fn0 : () => path.reduce((r, p) => (r && p in r ? r[p] : undefined), fn0());
143
+ }
144
+
145
+ map[propId] = fn;
146
+
147
+ return fn();
148
+ }
149
+
150
+ getFromScope(propId: string): unknown {
151
+ return this.scope.get(propId);
152
+ }
153
+
154
+ // --- Left Arrow.
155
+
156
+ notify() {
157
+ this.#listeners?.notify();
158
+ }
159
+
160
+ subscribe(target: Component, fn: (src: Component) => Delta): ComponentSubsription {
161
+ const listeners = this.#listeners || (this.#listeners = createListeners());
162
+ return {
163
+ payload: fn(this),
164
+ cancel: listeners.add(() => {
165
+ try {
166
+ const r = fn(this);
167
+ // this.log('subscribe', r);
168
+ target.up(r);
169
+ } catch (ex) {
170
+ this.logError('Notify ', ex);
171
+ }
172
+ }),
173
+ };
174
+ }
175
+
176
+ connect(key: string, applicator: (x: unknown) => unknown): undefined | ComponentSubsription {
177
+ const [refId, propId = '*'] = key.split('.');
178
+ const ref = this.scope.getByRef(refId) as Component;
179
+ if (!ref) {
180
+ this.logError('Connect: No such ref: ' + refId);
181
+ return;
182
+ }
183
+ return ref.subscribe(this, (c: Component) => applyValue(propId === '*' ? c.impl : c.get(propId), applicator));
184
+ }
185
+
186
+ // --- Right Arrow.
187
+
188
+ emit(key: string, data: Delta): void {
189
+ if (!key || !key.includes('.')) {
190
+ this.up(key ? { [key]: data } : data);
191
+ this.log('update:' + key, data, this.impl);
192
+ return;
193
+ }
194
+
195
+ const [refId, target] = key.split('.');
196
+ try {
197
+ const ref = this.getByRef(refId);
198
+ if (!ref) {
199
+ throw new Error('No such reference: ' + refId);
200
+ }
201
+
202
+ const propId = methodName(target, 'on');
203
+ const impl = ref.impl;
204
+
205
+ const method = impl[propId];
206
+ if (!method || !(typeof method === 'function')) {
207
+ throw new Error('No such method ' + refId + '.' + propId);
208
+ }
209
+
210
+ const result = method.call(impl, data, impl, ref);
211
+
212
+ this.log(refId + ':' + propId, result, data, impl);
213
+ if (result) {
214
+ ref.up(result);
215
+ }
216
+ } catch (ex) {
217
+ this.logError('emit ' + key + ':', ex);
218
+ }
219
+ }
220
+
221
+ // --- Life-cycle hooks.
222
+
223
+ init(initials: Delta) {
224
+ if (this.#isDone || this.#isInited) {
225
+ return;
226
+ }
227
+ this.#isInited = true;
228
+ const all: Array<Promise<any>> = [];
229
+ const initializers = this.node.initializers;
230
+ if (initializers?.length) {
231
+ initializers
232
+ .map((f) => f(this))
233
+ .forEach((r) => {
234
+ if (!r) return;
235
+ const { payload, cancel } = r;
236
+ if (cancel) {
237
+ this.defer(cancel);
238
+ }
239
+ if (payload && payload.then) {
240
+ all.push(payload as unknown as Promise<any>);
241
+ } else {
242
+ Object.assign(initials, payload);
243
+ }
244
+ });
245
+ }
246
+
247
+ if (all.length) {
248
+ Promise.all(all).then((args) => {
249
+ this.up(
250
+ args.reduce((r, e) => Object.assign(r, e), initials),
251
+ true
252
+ );
253
+ if (this.impl.init) {
254
+ const d = this.impl.init(this);
255
+ if (d) {
256
+ this.up(d);
257
+ }
258
+ }
259
+ });
260
+ } else {
261
+ this.up(initials, true);
262
+ if (this.impl.init) {
263
+ const d = this.impl.init(this);
264
+ if (d) {
265
+ this.up(d);
266
+ }
267
+ }
268
+ }
269
+ return this;
270
+ }
271
+
272
+ done() {
273
+ if (this.#isDone) {
274
+ return;
275
+ }
276
+ this.#isDone = true;
277
+
278
+ this.impl.done?.(this);
279
+
280
+ const parent = this._parent;
281
+ this._parent = undefined;
282
+
283
+ this.children?.forEach((c) => c.done());
284
+
285
+ parent?.children?.delete(this.uid);
286
+
287
+ this.#defered?.forEach((f) => f(this));
288
+ this.#defered = undefined;
289
+
290
+ this.impl.$ = null;
291
+ }
292
+
293
+ // -- Contents --------------------------------------------------------
294
+
295
+ get content(): ComponentContent | undefined {
296
+ return this.node.content;
297
+ }
298
+
299
+ get recontentScope(): IScope {
300
+ return this.scope;
301
+ }
302
+
303
+ setupChildren(children: ComponentChildren) {
304
+ this.children = children;
305
+
306
+ children.forEach((e) => e.settleAsChild());
307
+ }
308
+
309
+ settleAsChild() {
310
+ if (!this.#isInited) {
311
+ this.init(this.node.resolveInitialProps(this));
312
+ } else {
313
+ this.up(this.node.resolveProps(this), true);
314
+ }
315
+ }
316
+
317
+ recontent() {
318
+ const scope = this.recontentScope;
319
+ const nodes = this.content;
320
+
321
+ this.arrmatura.requestReflow();
322
+
323
+ this.children?.forEach((e, uid) => {
324
+ if (!nodes?.get(uid)) {
325
+ e.done();
326
+ }
327
+ });
328
+
329
+ const children = new Map<Uid, IComponent>();
330
+
331
+ nodes?.forEach((node, uid) => {
332
+ const e = this.children?.get(uid) || node.createComponent(this, scope);
333
+ children.set(e.uid, e);
334
+ });
335
+
336
+ this.setupChildren(children);
337
+ }
338
+
339
+ // --- Referencing.
340
+
341
+ get refId() {
342
+ return this._node.refId;
343
+ }
344
+
345
+ get refs(): Hash<Component> {
346
+ return this.#refs || (this.#refs = {});
347
+ }
348
+
349
+ getByRef(refId: string): Component | null {
350
+ if (refId === 'this') {
351
+ return this;
352
+ }
353
+ const local = this.refs[refId] || null;
354
+ if (local) {
355
+ return local as Component;
356
+ }
357
+ const ref = this === this.scope ? null : this.scope.getByRef(refId);
358
+ return ref as Component;
359
+ }
360
+
361
+ addComponentReference(refId: string, c: Component) {
362
+ this.refs[refId] = c;
363
+ }
364
+
365
+ // --- Pipes ----------------------------------------------------------------
366
+
367
+ pipe(key: string) {
368
+ return this.arrmatura.pipe(key);
369
+ }
370
+
371
+ applyPipe(key: string, value: unknown) {
372
+ try {
373
+ const [id, ...args] = key.split(':');
374
+ const fn = this.pipe(id);
375
+ return fn.apply(this.impl, [
376
+ value,
377
+ ...args.map((a) => (a[0] === '@' ? this.getFromScope(a.slice(1)) : scalarParse(a))),
378
+ ]);
379
+ } catch (ex) {
380
+ this.logError('ERROR: pipe ' + key, ex);
381
+ return value;
382
+ }
383
+ }
384
+
385
+ // --- Routines -------------------------
386
+
387
+ raceCondition(key: string) {
388
+ const COUNTERS = this.#weak || (this.#weak = new Map());
389
+ let counter = 1 + (COUNTERS.get(key) || 0);
390
+ COUNTERS.set(key, counter);
391
+ return (fn: Procedure) => {
392
+ if (counter === COUNTERS.get(key)) {
393
+ counter = 0;
394
+ fn();
395
+ }
396
+ };
397
+ }
398
+
399
+ defer(fn: ComponentProcedure) {
400
+ if (fn && typeof fn === 'function') {
401
+ (this.#defered || (this.#defered = [])).push(fn);
402
+ }
403
+ }
404
+
405
+ log(...args: unknown[]) {
406
+ console.log('' + this.displayName + ':' + this.uid, ...args);
407
+ }
408
+
409
+ logError(...args: unknown[]) {
410
+ console.error('⛔ ERROR: ' + this.displayName + ':' + this.uid, ...args);
411
+ }
412
+
413
+ res(key: string) {
414
+ return this.arrmatura.res(key);
415
+ }
416
+
417
+ stringify(tab = '') {
418
+ const { uid, displayName: tag = uid, state, children } = this;
419
+ if (tag === '#text') {
420
+ return `${tab}` + state.text;
421
+ }
422
+ const ssubs: string = children?.size
423
+ ? [...children.values()].map((c) => (c as Component).stringify(` ${tab}`)).join('\n')
424
+ : '';
425
+ return `${tab}<${tag}${stringifyState(state)}` + (!ssubs ? '/>' : `>\n${ssubs}\n${tab}</${tag}>`);
426
+ }
427
+
428
+ toString() {
429
+ return this.stringify();
430
+ }
431
+ }
@@ -0,0 +1,3 @@
1
+ export class Platform {
2
+ static instance: IPlatform;
3
+ }
@@ -0,0 +1,100 @@
1
+ import { CNode } from '../CNode';
2
+ import { compileNodes } from '../compileNode';
3
+ import { Component } from '../Component';
4
+ import { ComponentDefinition, getByTag } from '../register';
5
+
6
+ const compileSlots = (tag: string, nodes?: XmlNode[]) => {
7
+ const slots =
8
+ nodes?.reduce((groups, e) => {
9
+ const isSlot = e.tag.startsWith(tag + ':');
10
+ const gId = isSlot ? e.tag.split(':')[1] : 'default';
11
+ groups[gId] = (groups[gId] || []).concat(isSlot ? e.nodes || [] : e);
12
+ return groups;
13
+ }, {} as Hash<XmlNode[]>) || {};
14
+
15
+ return Object.entries(slots).reduce((groups, [gId, nodes]) => {
16
+ groups[gId] = compileNodes(nodes);
17
+ return groups;
18
+ }, {} as Hash<ComponentContent>);
19
+ };
20
+
21
+ export class SlotComponent extends Component {
22
+ get displayName(): string {
23
+ return '🔸';
24
+ }
25
+
26
+ get compositeScope(): CompositeComponent {
27
+ return this.scope as CompositeComponent;
28
+ }
29
+
30
+ get content(): ComponentContent {
31
+ return this.compositeScope.slotContent((this.node as CSlotNode).id);
32
+ }
33
+
34
+ get recontentScope(): IScope {
35
+ return this.compositeScope.scope;
36
+ }
37
+ }
38
+
39
+ export class CSlotNode extends CNode<SlotComponent> {
40
+ id?: string;
41
+
42
+ constructor(x: XmlNode) {
43
+ super();
44
+ this.id = x.attrs?.id as string;
45
+ }
46
+
47
+ get ComponentConstructor() {
48
+ return SlotComponent;
49
+ }
50
+ }
51
+
52
+ export class CompositeComponent extends Component<CCompositeNode> {
53
+ get displayName(): string {
54
+ return '💠' + this.node.definition.tag;
55
+ }
56
+
57
+ get content() {
58
+ return this.node.definition.content;
59
+ }
60
+
61
+ get recontentScope(): Component {
62
+ return this;
63
+ }
64
+
65
+ slotContent(slotId?: string): ComponentContent {
66
+ return this.node.slotContent[slotId || 'default'];
67
+ }
68
+
69
+ createImplementation(initials: Hash) {
70
+ return new this.node.definition.Ctor(initials, this);
71
+ }
72
+ }
73
+
74
+ export class CCompositeNode extends CNode<CompositeComponent> {
75
+ Ctor: any;
76
+ slotContent: Hash<ComponentContent>;
77
+ definition: ComponentDefinition;
78
+
79
+ constructor({ tag, attrs, nodes }: XmlNode) {
80
+ super();
81
+ this.definition = getByTag(tag);
82
+ this.compileAttributes(attrs);
83
+ this.slotContent = compileSlots(tag, nodes);
84
+ }
85
+
86
+ get ComponentConstructor() {
87
+ return CompositeComponent;
88
+ }
89
+ }
90
+
91
+ export class CFragmentNode extends CNode<Component> {
92
+ constructor({ nodes }: XmlNode) {
93
+ super();
94
+ this.content = compileNodes(nodes);
95
+ }
96
+
97
+ get ComponentConstructor() {
98
+ return Component;
99
+ }
100
+ }
@@ -0,0 +1,51 @@
1
+ import { CNode } from '../CNode';
2
+ import { compileExpression } from '../expression';
3
+ import { compileNodes } from '../compileNode';
4
+ import { CompositeComponent } from './composition';
5
+ import { Component } from '../Component';
6
+
7
+ class IfComponent extends Component<CIfNode> {
8
+ get content() {
9
+ return this.state.condition ? this.node.then : this.node.else;
10
+ }
11
+ }
12
+
13
+ export class CIfNode extends CNode<IfComponent> {
14
+ else?: ComponentContent;
15
+ then?: ComponentContent;
16
+ constructor(x: XmlNode, expr: string) {
17
+ super();
18
+ this.compileCondition(expr);
19
+ this.compileBranches(x);
20
+ }
21
+ get ComponentConstructor() {
22
+ return IfComponent;
23
+ }
24
+ compileBranches(x: XmlNode) {
25
+ const { nodes } = x;
26
+
27
+ let $then: XmlNode[] | undefined = [x];
28
+ if (nodes?.length) {
29
+ const ifElse = nodes.find((e) => e.tag === 'ui:else');
30
+ const ifThen = nodes.find((e) => e.tag === 'ui:then');
31
+ if (ifElse) {
32
+ this.else = compileNodes(ifElse.nodes);
33
+ $then = ifThen ? ifThen.nodes : [Object.assign(x, { nodes: nodes.filter((e) => e !== ifElse) })];
34
+ } else if (ifThen) {
35
+ $then = ifThen.nodes;
36
+ }
37
+ }
38
+ this.then = compileNodes($then);
39
+ }
40
+ compileCondition(expr: string): void {
41
+ if (expr[0] === '<' && expr[1] === '-') {
42
+ this.addPropertyConnector(expr.slice(2), (condition: unknown) => ({ condition }));
43
+ } else if (expr.slice(0, 5) === 'slot(') {
44
+ const slotId = expr.slice(5, -1);
45
+ // DO NOT place inside iterations.
46
+ this.addPropertyResolver((c: IComponent) => (c.scope as CompositeComponent).slotContent?.(slotId), 'condition');
47
+ } else {
48
+ this.addPropertyResolver(compileExpression(expr[0] === '{' ? expr : `{${expr}}`), 'condition');
49
+ }
50
+ }
51
+ }