creo 0.0.2 → 0.0.4-dev

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 (54) hide show
  1. package/.env.development +1 -0
  2. package/.github/workflows/main.yml +24 -0
  3. package/README.md +1 -52
  4. package/TODOS.md +2 -0
  5. package/index.html +13 -0
  6. package/index.ts +1 -0
  7. package/package.json +12 -44
  8. package/src/DOM/Context.ts +36 -0
  9. package/src/DOM/DomEngine.ts +106 -0
  10. package/src/DOM/IRenderCycle.ts +9 -0
  11. package/src/DOM/Key.ts +1 -0
  12. package/src/DOM/Node.ts +472 -0
  13. package/src/DOM/Registry.ts +53 -0
  14. package/src/creo.ts +134 -0
  15. package/src/data-structures/assert/assert.ts +12 -0
  16. package/src/data-structures/indexed-map/IndexedMap.ts +281 -0
  17. package/src/data-structures/linked-map/LinkedMap.spec.ts +67 -0
  18. package/src/data-structures/linked-map/LinkedMap.ts +198 -0
  19. package/src/data-structures/list/List.spec.ts +181 -0
  20. package/src/data-structures/list/List.ts +195 -0
  21. package/src/data-structures/maybe/Maybe.ts +25 -0
  22. package/src/data-structures/null/null.ts +3 -0
  23. package/src/data-structures/record/IsRecordLike.spec.ts +29 -0
  24. package/src/data-structures/record/IsRecordLike.ts +3 -0
  25. package/src/data-structures/record/Record.spec.ts +240 -0
  26. package/src/data-structures/record/Record.ts +145 -0
  27. package/src/data-structures/shalllowEqual/shallowEqual.ts +26 -0
  28. package/src/data-structures/simpleKey/simpleKey.ts +8 -0
  29. package/src/data-structures/wildcard/wildcard.ts +1 -0
  30. package/src/examples/SimpleTodoList/SimpleTodoList.ts +53 -0
  31. package/src/examples/simple.ts +0 -0
  32. package/src/globals.d.ts +1 -0
  33. package/src/main.ts +24 -0
  34. package/src/style.css +41 -0
  35. package/src/ui/html/Block.ts +10 -0
  36. package/src/ui/html/Button.ts +12 -0
  37. package/src/ui/html/HStack.ts +10 -0
  38. package/src/ui/html/Inline.ts +12 -0
  39. package/src/ui/html/List.ts +10 -0
  40. package/src/ui/html/Text.ts +9 -0
  41. package/src/ui/html/VStack.ts +11 -0
  42. package/src/vite-env.d.ts +1 -0
  43. package/tsconfig.json +23 -0
  44. package/vite.config.js +10 -0
  45. package/LICENSE +0 -21
  46. package/dist/index.cjs +0 -54
  47. package/dist/index.cjs.map +0 -1
  48. package/dist/index.d.ts +0 -19
  49. package/dist/index.js +0 -51
  50. package/dist/index.js.map +0 -1
  51. package/dist/index.mjs +0 -51
  52. package/dist/index.mjs.map +0 -1
  53. package/dist/index.umd.js +0 -61
  54. package/dist/index.umd.js.map +0 -1
@@ -0,0 +1,472 @@
1
+ /**
2
+ * Represents physical node for UI rendering engine
3
+ * In charge of:
4
+ * 1. UI changes
5
+ * 2. Animations
6
+ * 3. GC when gets destroyed
7
+ */
8
+
9
+ import { NodeBuilder, NodeMethods } from "../creo";
10
+ import { assertJust } from "../data-structures/assert/assert";
11
+ import { IndexedMap } from "../data-structures/indexed-map/IndexedMap";
12
+ import { Maybe, None } from "../data-structures/maybe/Maybe";
13
+ import { generateNextKey } from "../data-structures/simpleKey/simpleKey";
14
+ import { Wildcard } from "../data-structures/wildcard/wildcard";
15
+ import { Context } from "./Context";
16
+ import { DomEngine } from "./DomEngine";
17
+ import { IRenderCycle } from "./IRenderCycle";
18
+ import { Key } from "./Key";
19
+
20
+ export enum NodeStatus {
21
+ DIRTY,
22
+ UPDATING,
23
+ CLEAR,
24
+ }
25
+
26
+ export enum UpdateDirectiveEnum {
27
+ // Change element position
28
+ MOVE,
29
+ // No component, just create a new
30
+ NEW,
31
+ // Matched component, update existing item
32
+ REUSE,
33
+ // Matched key, but different component
34
+ REPLACE,
35
+ }
36
+
37
+ type UpdateDirective = {
38
+ updateDirective: UpdateDirectiveEnum;
39
+ node: Maybe<Node>;
40
+ };
41
+
42
+ export class Node implements IRenderCycle {
43
+ public publicApi: {
44
+ ext: Wildcard;
45
+ };
46
+ public c: Context<Wildcard>;
47
+
48
+ public children: IndexedMap<Node, "key"> = new IndexedMap("key");
49
+ public pendingChildrenState!: IndexedMap<Node, "key">;
50
+ public renderCursor: Maybe<Node>;
51
+
52
+ public lifecycle: NodeMethods<Wildcard, Wildcard>;
53
+
54
+ constructor(
55
+ public userKey: Maybe<Key>,
56
+ public key: Key,
57
+ p: Wildcard,
58
+ public slot: Maybe<() => void>,
59
+ public ctor: NodeBuilder<Wildcard, Wildcard>,
60
+ public parent: Node,
61
+ public parentUI: UINode,
62
+ public engine: DomEngine,
63
+ ) {
64
+ this.c = new Context(this, p, slot);
65
+ const { ext, ...lifecycle } = this.ctor(this.c);
66
+ this.lifecycle = lifecycle;
67
+ this.publicApi = {
68
+ ext,
69
+ }; //new Node(extension, this);
70
+ this.newNode(this);
71
+ }
72
+
73
+ newNode(_node: Node): void {
74
+ this.engine.newNode(this);
75
+ }
76
+
77
+ // sets status to dirty for the node
78
+ invalidate() {
79
+ this.willRender();
80
+ }
81
+
82
+ applyNewParams(newParams: Wildcard) {
83
+ if (this.shouldUpdate(newParams)) {
84
+ this.willRender();
85
+ }
86
+ this.c.p = newParams;
87
+ }
88
+
89
+ willRender() {
90
+ this.engine.willRender(this);
91
+ }
92
+
93
+ isRendering() {
94
+ this.engine.isRendering(this);
95
+ this.pendingChildrenState = new IndexedMap("key", ["userKey"]);
96
+ this.renderCursor = this.children.at(0);
97
+ }
98
+
99
+ didRender(): { justMounted: boolean } {
100
+ // When the rendering cycle ends, all items starting
101
+ // from this.renderingCursor were not used and hence, need to be deleted
102
+ while (this.renderCursor != null) {
103
+ this.renderCursor.dispose();
104
+ this.renderCursor = this.children.getNext(this.renderCursor);
105
+ }
106
+ // At the end of the cycle, we replace children with pending children (this.renderingChildren)
107
+ this.children = this.pendingChildrenState;
108
+ this.pendingChildrenState = new IndexedMap("key");
109
+ return this.engine.didRender(this);
110
+ }
111
+
112
+ render() {
113
+ __DEV__ && console.log("Render:", this.key);
114
+ this.isRendering();
115
+ this.lifecycle.render();
116
+ const { justMounted } = this.didRender();
117
+ if (justMounted) {
118
+ this.lifecycle.didMount?.();
119
+ }
120
+ {
121
+ this.lifecycle.didUpdate?.();
122
+ }
123
+ }
124
+
125
+ shouldUpdate(pendingParams: Wildcard): boolean {
126
+ if (this.lifecycle.shouldUpdate != null) {
127
+ return this.lifecycle.shouldUpdate(pendingParams);
128
+ }
129
+ return true;
130
+ }
131
+
132
+ generateUpdateDirective(
133
+ userKey: Maybe<Key>,
134
+ ctor: NodeBuilder<Wildcard, Wildcard>,
135
+ tag: Maybe<string>,
136
+ ): UpdateDirective {
137
+ const expectedChild: Maybe<Node> = this.renderCursor;
138
+ let expectedTag: Maybe<string> = null;
139
+ if (expectedChild instanceof UINode) {
140
+ expectedTag = expectedChild.tag;
141
+ }
142
+ if (
143
+ expectedChild != null &&
144
+ expectedChild.ctor === ctor &&
145
+ expectedTag == tag &&
146
+ // TODO: We should respect & identify artificially generated keys too
147
+ (userKey == null || userKey === expectedChild.userKey)
148
+ ) {
149
+ return {
150
+ updateDirective: UpdateDirectiveEnum.REUSE,
151
+ node: expectedChild,
152
+ };
153
+ }
154
+
155
+ // Unhappy path exploration.
156
+ const maybeKeyedMatchedChildren: Node[] =
157
+ userKey != null ? this.children.getByIndex("userKey", userKey) : [];
158
+
159
+ if (maybeKeyedMatchedChildren.length > 1) {
160
+ throw new Error("Spotted duplicate keys for the node");
161
+ }
162
+ const maybeKeyedMatchedChild: Maybe<Node> = maybeKeyedMatchedChildren[0];
163
+
164
+ // Case 1: Key, no next component, but matched key: something went wrong
165
+ if (
166
+ expectedChild == null &&
167
+ userKey != null &&
168
+ maybeKeyedMatchedChild != null
169
+ ) {
170
+ throw new Error(`Detected key duplication: ${userKey}`);
171
+ }
172
+
173
+ // Case 2: Key, matched key, not matched next element
174
+ if (
175
+ expectedChild != null &&
176
+ userKey != null &&
177
+ maybeKeyedMatchedChild != null
178
+ ) {
179
+ // We have matched key, but their constructors are different
180
+ // In theory that process is REPLACE, but for external usages, it works exactly the same way as NEW
181
+ // The difference is purely internal
182
+ if (maybeKeyedMatchedChild.ctor !== ctor || expectedTag != tag) {
183
+ return {
184
+ updateDirective: UpdateDirectiveEnum.REPLACE,
185
+ node: maybeKeyedMatchedChild,
186
+ };
187
+ }
188
+ // TODO detect potential key duplication?
189
+ // Everything is fine, we just need to move that item to different place
190
+ return {
191
+ updateDirective: UpdateDirectiveEnum.MOVE,
192
+ node: maybeKeyedMatchedChild,
193
+ };
194
+ }
195
+
196
+ // Default outcome: Component creation
197
+ // Cases:
198
+ // 1. No key, component mismatch or no component
199
+ // 2. Key, no next component, no matched key
200
+ return {
201
+ updateDirective: UpdateDirectiveEnum.NEW,
202
+ node: null,
203
+ };
204
+ }
205
+
206
+ renderChild<Tag extends Maybe<string>>(
207
+ userKey: Maybe<Key>,
208
+ ctor: NodeBuilder<Wildcard, Wildcard>,
209
+ params: Maybe<Wildcard>,
210
+ slot: Maybe<() => void>,
211
+ tag: Tag,
212
+ ): Tag extends None ? Node : UINode {
213
+ const directive = this.generateUpdateDirective(userKey, ctor, tag);
214
+
215
+ let newNode: Node;
216
+
217
+ switch (directive.updateDirective) {
218
+ case UpdateDirectiveEnum.REUSE: {
219
+ // TODO: MAYBE update SLOT
220
+ assertJust(directive.node, "Cannot re-use null node");
221
+ newNode = directive.node;
222
+ this.pendingChildrenState.put(newNode);
223
+ this.renderCursor =
224
+ this.renderCursor != null
225
+ ? this.children.getNext(this.renderCursor)
226
+ : null;
227
+ this.children.delete(newNode.key);
228
+ // The component can decide on its own if the component needs to get updated
229
+ directive.node.applyNewParams(params);
230
+ break;
231
+ }
232
+ case UpdateDirectiveEnum.MOVE: {
233
+ // TODO: move all DOM children which are connected to the top-level
234
+ throw new Error("Not implemented");
235
+ }
236
+ case UpdateDirectiveEnum.NEW: {
237
+ newNode = this.createNewNode(userKey, ctor, params, slot, tag);
238
+ break;
239
+ }
240
+ case UpdateDirectiveEnum.REPLACE: {
241
+ assertJust(directive.node, "Cannot replace null component");
242
+ const toReplace = directive.node;
243
+ if (this.renderCursor === toReplace) {
244
+ this.renderCursor = this.children.getNext(this.renderCursor);
245
+ }
246
+ // Delete conflicting element, as they should not exist anymore
247
+ this.children.delete(toReplace.key);
248
+ // Cleanup the component:
249
+ toReplace.dispose();
250
+ newNode = this.createNewNode(userKey, ctor, params, slot, tag);
251
+ break;
252
+ }
253
+ }
254
+ if (this.engine.shouldUpdateNode(newNode)) {
255
+ newNode.render();
256
+ }
257
+ return newNode;
258
+ }
259
+
260
+ protected createNewNode(
261
+ userKey: Maybe<Key>,
262
+ ctor: NodeBuilder<Wildcard, Wildcard>,
263
+ params: Maybe<Wildcard>,
264
+ slot: Maybe<() => void>,
265
+ tag: Maybe<string>,
266
+ ): Node {
267
+ let node: Node;
268
+ if (tag == null) {
269
+ node = new Node(
270
+ userKey,
271
+ generateNextKey(this.pendingChildrenState.size()),
272
+ params,
273
+ slot,
274
+ ctor,
275
+ this,
276
+ this.parentUI,
277
+ this.engine,
278
+ );
279
+ } else {
280
+ node = new UINode(
281
+ userKey,
282
+ generateNextKey(this.pendingChildrenState.size()),
283
+ params,
284
+ slot,
285
+ ctor,
286
+ this,
287
+ this.parentUI,
288
+ this.engine,
289
+ tag,
290
+ );
291
+ }
292
+
293
+ this.pendingChildrenState.put(node);
294
+ return node;
295
+ }
296
+
297
+ dispose(): void {
298
+ // delete any DOM / whatever nodes we have
299
+ // Propagate removal of data to all children
300
+
301
+ // Remove subscriptions
302
+ this.c.dispose();
303
+
304
+ // Parent component should alredy not to have children, but we can have sanity check there
305
+ for (const child of this.children) {
306
+ child.dispose();
307
+ }
308
+
309
+ // Mark component as not dirty, as there is no point in keeping that item anymore
310
+ this.engine.dispose(this);
311
+ }
312
+ }
313
+
314
+ export class UINode extends Node {
315
+ public uiChildren: IndexedMap<UINode, "key"> = new IndexedMap("key", [
316
+ "userKey",
317
+ ]);
318
+ public pendingUIChildrenState!: IndexedMap<UINode, "key">;
319
+ protected domNode: Maybe<HTMLElement>;
320
+ protected domText: Maybe<Text>;
321
+ public publicNode: () => Maybe<HTMLElement | Text>;
322
+ constructor(
323
+ userKey: Maybe<Key>,
324
+ internalKey: Key,
325
+ p: Wildcard,
326
+ slot: Maybe<() => void>,
327
+ ctor: NodeBuilder<Wildcard, Wildcard>,
328
+ parent: Node,
329
+ parentUI: UINode,
330
+ engine: DomEngine,
331
+ public tag: string,
332
+ ) {
333
+ super(userKey, internalKey, p, slot, ctor, parent, parentUI, engine);
334
+ this.publicNode = () => {
335
+ return (this.domNode ?? this.domText) as Maybe<HTMLElement | Text>;
336
+ };
337
+ // Root element does not have parentUI
338
+ this.parentUI?.appendUIChild(this);
339
+ }
340
+
341
+ appendUIChild(node: UINode) {
342
+ this.uiChildren.put(node);
343
+ }
344
+
345
+ protected createNewNode(
346
+ userKey: Maybe<Key>,
347
+ ctor: NodeBuilder<Wildcard, Wildcard>,
348
+ params: Maybe<Wildcard>,
349
+ slot: Maybe<() => void>,
350
+ tag: Maybe<string>,
351
+ ): Node {
352
+ let node: Node;
353
+ if (tag == null) {
354
+ node = new Node(
355
+ userKey,
356
+ generateNextKey(this.pendingChildrenState.size()),
357
+ params,
358
+ slot,
359
+ ctor,
360
+ this,
361
+ this,
362
+ this.engine,
363
+ );
364
+ } else {
365
+ node = new UINode(
366
+ userKey,
367
+ generateNextKey(this.pendingChildrenState.size()),
368
+ params,
369
+ slot,
370
+ ctor,
371
+ this,
372
+ this,
373
+ this.engine,
374
+ tag,
375
+ );
376
+ }
377
+
378
+ this.pendingChildrenState.put(node);
379
+ return node;
380
+ }
381
+
382
+ renderUI() {
383
+ // rerender
384
+ if (this.domText != null) {
385
+ const params = this.c.p;
386
+ if (typeof params === "string" && this.domText.textContent != params) {
387
+ const newItem = document.createTextNode(params);
388
+ this.parentUI.domNode?.replaceChild(newItem, this.domText);
389
+ this.domText = newItem;
390
+ }
391
+ return;
392
+ }
393
+ if (this.domNode != null) {
394
+ const params = this.c.p;
395
+ const element = this.domNode;
396
+ if (typeof params === "object") {
397
+ for (const key in params) {
398
+ if (element.getAttribute(key) !== params[key]) {
399
+ element.setAttribute(key, params[key]);
400
+ }
401
+ }
402
+ }
403
+ return;
404
+ }
405
+ // mount
406
+ if (this.tag === "text") {
407
+ this.domText = document.createTextNode(this.c.p);
408
+ this.parentUI.domNode?.appendChild(this.domText);
409
+ return;
410
+ } else {
411
+ this.domNode = document.createElement(this.tag);
412
+ const params = this.c.p;
413
+ const element = this.domNode;
414
+ if (typeof params === "object") {
415
+ for (const key in params) {
416
+ if (element.getAttribute(key) !== params[key]) {
417
+ element.setAttribute(key, params[key]);
418
+ }
419
+ }
420
+ }
421
+ this.parentUI.domNode?.appendChild(this.domNode);
422
+ }
423
+ }
424
+
425
+ render() {
426
+ this.isRendering();
427
+ __DEV__ && console.log("UI render:", this.tag, this.key);
428
+ this.renderUI();
429
+ //this.layoutNode = this.layout.renderNode(this);
430
+ this.lifecycle.render();
431
+ const { justMounted } = this.didRender();
432
+ if (justMounted) {
433
+ this.lifecycle.didMount?.();
434
+ } else {
435
+ this.lifecycle.didUpdate?.();
436
+ }
437
+ }
438
+
439
+ dispose(): void {
440
+ super.dispose();
441
+ const toDelete = this.domNode ?? this.domText;
442
+ if (toDelete != null && this.parentUI != null) {
443
+ this.parentUI.domNode?.removeChild(toDelete);
444
+ }
445
+ this.domNode = null;
446
+ this.domText = null;
447
+ }
448
+ }
449
+
450
+ export class RootNode extends UINode {
451
+ constructor(htmlElement: HTMLElement, slot: () => void, engine: DomEngine) {
452
+ super(
453
+ null,
454
+ "root",
455
+ null,
456
+ slot,
457
+ /* ctor */ (c) => ({
458
+ render() {
459
+ c.slot?.();
460
+ },
461
+ }),
462
+ // @ts-ignore
463
+ /* parent */ null,
464
+ /* parentUI */ null,
465
+ engine,
466
+ /* tag */ null,
467
+ );
468
+ this.domNode = htmlElement;
469
+ }
470
+
471
+ renderUI() {}
472
+ }
@@ -0,0 +1,53 @@
1
+ import { LinkedMap } from "../data-structures/linked-map/LinkedMap";
2
+ import { Maybe } from "../data-structures/maybe/Maybe";
3
+ import { IRenderCycle } from "./IRenderCycle";
4
+ import { Node } from "./Node";
5
+
6
+ export class Registry implements IRenderCycle {
7
+ protected needRender: LinkedMap<Node, "key"> = new LinkedMap("key");
8
+ protected rendering: LinkedMap<Node, "key"> = new LinkedMap("key");
9
+ protected register: LinkedMap<Node, "key"> = new LinkedMap("key");
10
+ protected mounting: LinkedMap<Node, "key"> = new LinkedMap("key");
11
+
12
+ public newNode(node: Node) {
13
+ this.register.put(node);
14
+ this.needRender.put(node);
15
+ this.mounting.put(node);
16
+ }
17
+
18
+ public willRender(node: Node) {
19
+ this.needRender.putFirst(node);
20
+ this.rendering.delete(node.key);
21
+ }
22
+
23
+ public isRendering(node: Node) {
24
+ this.needRender.delete(node.key);
25
+ this.rendering.put(node);
26
+ }
27
+
28
+ public didRender(node: Node): { justMounted: boolean } {
29
+ this.rendering.delete(node.key);
30
+ return {
31
+ justMounted: this.mounting.delete(node.key),
32
+ };
33
+ }
34
+
35
+ public shouldUpdateNode(node: Node): boolean {
36
+ return this.needRender.has(node);
37
+ }
38
+
39
+ public dispose(node: Node) {
40
+ this.register.delete(node.key);
41
+ this.rendering.delete(node.key);
42
+ this.mounting.delete(node.key);
43
+ this.needRender.delete(node.key);
44
+ }
45
+
46
+ public getNextToRender(): Maybe<Node> {
47
+ return this.needRender.at(0);
48
+ }
49
+
50
+ public getNextRendering(): Maybe<Node> {
51
+ return this.rendering.at(-1);
52
+ }
53
+ }
package/src/creo.ts ADDED
@@ -0,0 +1,134 @@
1
+ import { assertJust } from "./data-structures/assert/assert";
2
+ import { Maybe } from "./data-structures/maybe/Maybe";
3
+ import { Wildcard } from "./data-structures/wildcard/wildcard";
4
+ import { Context } from "./DOM/Context";
5
+ import { DomEngine, getActiveEngine } from "./DOM/DomEngine";
6
+ import { Key } from "./DOM/Key";
7
+ import { UINode } from "./DOM/Node";
8
+
9
+ export type NodeMethods<P = void, A = void> = {
10
+ render(): void;
11
+ didMount?(): void;
12
+ didUpdate?(): void;
13
+ dispose?(): void;
14
+ // Return true if the component should get updated with new params
15
+ shouldUpdate?(pendingParams: P): boolean;
16
+ ext?: A extends void ? undefined : A;
17
+ };
18
+
19
+ export type NodeBuilder<P = void, A = void> = (
20
+ c: Context<P>,
21
+ ) => NodeMethods<P, A>;
22
+
23
+ type NodeBuilderFn<P = void, A = void> = (
24
+ p?: P,
25
+ slot?: () => void,
26
+ ) => { ext: A };
27
+
28
+ export function creo<P = void, A = void>(
29
+ ctor: NodeBuilder<P, A>,
30
+ ): NodeBuilderFn<P, A> {
31
+ return (params?: P, slot?: () => void) => {
32
+ // Get Potential pre-existing instance of the component:
33
+ const maybeLayout = getActiveEngine();
34
+ assertJust(
35
+ maybeLayout,
36
+ "Component can be initialised only inside creo rendering cycle",
37
+ );
38
+ const engine: DomEngine = maybeLayout;
39
+
40
+ // 2. Check if there is an existing component
41
+ let userKey: Maybe<Key>;
42
+ // If key is provided separately, use provided key:
43
+ if (
44
+ params != null &&
45
+ typeof params === "object" &&
46
+ "key" in params &&
47
+ params.key != null &&
48
+ (typeof params.key === "string" || typeof params.key === "number")
49
+ ) {
50
+ userKey = params.key;
51
+ }
52
+
53
+ // 3. Get component's parent
54
+ const maybeParent = engine.getParent();
55
+ assertJust(
56
+ maybeParent,
57
+ "There is no rendering context for currently rendering component",
58
+ );
59
+ const parent = maybeParent;
60
+ const node = parent.renderChild(userKey, ctor, params, slot, null);
61
+
62
+ // 4. Public component contains all the required methods
63
+ return node.publicApi;
64
+ };
65
+ }
66
+
67
+ // UI renderers
68
+ type UINodeBuilderFn<P = void> = (
69
+ p?: P,
70
+ slot?: Maybe<() => void>,
71
+ ) => () => Maybe<HTMLElement>;
72
+
73
+ type UITextNodeBuilderFn<P = void> = (
74
+ p?: P,
75
+ slot?: Maybe<() => void>,
76
+ ) => () => Maybe<Text>;
77
+
78
+ function creoUI<P = void, Tag extends Maybe<string> = string>(
79
+ tag: Tag,
80
+ ): Tag extends "Text" ? UITextNodeBuilderFn<P> : UINodeBuilderFn<P> {
81
+ // @ts-ignore
82
+ return (params?: P, slot?: Maybe<() => void>) => {
83
+ // Get Potential pre-existing instance of the component:
84
+ const maybeLayout = getActiveEngine();
85
+ assertJust(
86
+ maybeLayout,
87
+ "Component can be initialised only inside creo rendering cycle",
88
+ );
89
+ const engine: DomEngine = maybeLayout;
90
+
91
+ // 2. Check if there is an existing component
92
+ let userKey: Maybe<Key>;
93
+ // If key is provided separately, use provided key:
94
+ if (
95
+ params != null &&
96
+ typeof params === "object" &&
97
+ "key" in params &&
98
+ params.key != null &&
99
+ (typeof params.key === "string" || typeof params.key === "number")
100
+ ) {
101
+ userKey = params.key;
102
+ }
103
+
104
+ // 3. Get component's parent
105
+ const maybeParent = engine.getParent();
106
+ assertJust(
107
+ maybeParent,
108
+ "There is no rendering context for currently rendering component",
109
+ );
110
+ const parent = maybeParent;
111
+ // TODO: Use separate methods to render Text and UI components
112
+ const node: UINode = parent.renderChild(
113
+ userKey,
114
+ uiCtor,
115
+ params,
116
+ slot,
117
+ tag,
118
+ ) as UINode;
119
+ // 4. Public component contains all the required methods
120
+ return node.publicNode;
121
+ };
122
+ }
123
+
124
+ const uiCtor = <P>(c: Context<P>) => ({
125
+ render() {
126
+ c.slot?.();
127
+ },
128
+ });
129
+
130
+ export const ui = creoUI;
131
+ export const Button = ui<Wildcard>("button");
132
+ export const Block = ui<Wildcard>("div");
133
+ export const Inline = ui<Wildcard>("span");
134
+ export const Text = ui<string>("text");
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Assert helpers for ts
3
+ *
4
+ */
5
+
6
+ import { isNone, Just, Maybe } from "../maybe/Maybe";
7
+
8
+ export function assertJust<T>(maybe: Maybe<T>, errorMessage?: string): asserts maybe is Just<T> {
9
+ if (isNone(maybe)) {
10
+ throw new Error(errorMessage ?? 'Expected Just, received None as Maybe');
11
+ }
12
+ }