creo 0.0.3-dev → 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 (48) hide show
  1. package/.env.development +1 -0
  2. package/.github/workflows/main.yml +24 -0
  3. package/README.md +1 -1
  4. package/TODOS.md +2 -0
  5. package/index.ts +1 -0
  6. package/package.json +7 -2
  7. package/src/DOM/Context.ts +36 -0
  8. package/src/DOM/DomEngine.ts +106 -0
  9. package/src/DOM/IRenderCycle.ts +9 -0
  10. package/src/DOM/Key.ts +1 -0
  11. package/src/DOM/Node.ts +472 -0
  12. package/src/DOM/Registry.ts +53 -0
  13. package/src/creo.ts +134 -0
  14. package/src/data-structures/assert/assert.ts +12 -0
  15. package/src/data-structures/indexed-map/IndexedMap.ts +281 -0
  16. package/src/data-structures/linked-map/LinkedMap.spec.ts +67 -0
  17. package/src/data-structures/linked-map/LinkedMap.ts +198 -0
  18. package/src/data-structures/list/List.spec.ts +181 -0
  19. package/src/data-structures/list/List.ts +195 -0
  20. package/src/data-structures/maybe/Maybe.ts +25 -0
  21. package/src/data-structures/null/null.ts +3 -0
  22. package/src/{tools/isRecordLike.spec.ts → data-structures/record/IsRecordLike.spec.ts} +1 -1
  23. package/src/{tools/isRecordLike.ts → data-structures/record/IsRecordLike.ts} +1 -1
  24. package/src/{record/record.spec.ts → data-structures/record/Record.spec.ts} +96 -2
  25. package/src/data-structures/record/Record.ts +145 -0
  26. package/src/data-structures/shalllowEqual/shallowEqual.ts +26 -0
  27. package/src/data-structures/simpleKey/simpleKey.ts +8 -0
  28. package/src/data-structures/wildcard/wildcard.ts +1 -0
  29. package/src/examples/SimpleTodoList/SimpleTodoList.ts +53 -0
  30. package/src/globals.d.ts +1 -0
  31. package/src/main.ts +22 -11
  32. package/src/style.css +24 -79
  33. package/src/ui/html/Block.ts +10 -0
  34. package/src/ui/html/Button.ts +12 -0
  35. package/src/ui/html/HStack.ts +10 -0
  36. package/src/ui/html/Inline.ts +12 -0
  37. package/src/ui/html/List.ts +10 -0
  38. package/src/ui/html/Text.ts +9 -0
  39. package/src/ui/html/VStack.ts +11 -0
  40. package/tsconfig.json +2 -2
  41. package/vite.config.js +10 -0
  42. package/bun.lockb +0 -0
  43. package/src/record/record.ts +0 -101
  44. package/src/tools/optional.ts +0 -25
  45. package/src/ui/component.ts +0 -1
  46. package/src/ui/prop.ts +0 -13
  47. package/src/ui/state.ts +0 -0
  48. /package/src/{ui/index.ts → examples/simple.ts} +0 -0
@@ -0,0 +1,198 @@
1
+ import { Maybe } from "../maybe/Maybe";
2
+
3
+ type LinkNode<T, K> = {
4
+ value: T;
5
+ cachedIndexValues: Map<keyof T, T[keyof T]>;
6
+ prev: Maybe<LinkNode<T, K>>;
7
+ next: Maybe<LinkNode<T, K>>;
8
+ };
9
+
10
+ export class LinkedMap<T extends object, K extends keyof T>
11
+ implements Iterable<T>
12
+ {
13
+ private head: Maybe<LinkNode<T, K>>;
14
+ private tail: Maybe<LinkNode<T, K>>;
15
+ private mapSize = 0;
16
+ private pk: K;
17
+ private map = new Map<T[K], LinkNode<T, K>>();
18
+
19
+ constructor(pk: K) {
20
+ this.pk = pk;
21
+ }
22
+
23
+ putFirst(item: T): void {
24
+ const key = item[this.pk];
25
+ // Delete previous, if any
26
+ this.delete(key);
27
+
28
+ const node: LinkNode<T, K> = {
29
+ value: item,
30
+ cachedIndexValues: new Map(),
31
+ prev: this.tail,
32
+ next: null,
33
+ };
34
+ if (!this.tail) {
35
+ this.tail = node;
36
+ }
37
+ if (this.head) {
38
+ this.head.prev = node;
39
+ }
40
+ this.head = node;
41
+ this.map.set(key, node);
42
+ this.mapSize++;
43
+ }
44
+ // Puts item to the end
45
+ // Removes previous item, if PK is matched
46
+ put(item: T): void {
47
+ const key = item[this.pk];
48
+ // Delete previous, if any
49
+ this.delete(key);
50
+
51
+ const node: LinkNode<T, K> = {
52
+ value: item,
53
+ cachedIndexValues: new Map(),
54
+ prev: this.tail,
55
+ next: null,
56
+ };
57
+ if (!this.head) {
58
+ this.head = node;
59
+ }
60
+ if (this.tail) {
61
+ this.tail.next = node;
62
+ }
63
+ this.tail = node;
64
+ this.map.set(key, node);
65
+ this.mapSize++;
66
+ }
67
+
68
+ putBefore(targetKey: T[K], item: T): void {
69
+ const target = this.map.get(targetKey);
70
+ if (target == null) {
71
+ throw new Error(`Key not found: ${String(targetKey)}`);
72
+ }
73
+ const key = item[this.pk];
74
+ if (targetKey === key) {
75
+ return;
76
+ }
77
+ this.delete(key);
78
+
79
+ const node: LinkNode<T, K> = {
80
+ value: item,
81
+ cachedIndexValues: new Map(),
82
+ prev: target.prev,
83
+ next: target,
84
+ };
85
+ if (target.prev) {
86
+ target.prev.next = node;
87
+ } else {
88
+ this.head = node;
89
+ }
90
+ target.prev = node;
91
+
92
+ this.map.set(key, node);
93
+ this.mapSize++;
94
+ }
95
+
96
+ putAfter(targetKey: T[K], item: T): void {
97
+ const target = this.map.get(targetKey);
98
+ if (target == null) {
99
+ throw new Error(`Key not found: ${String(targetKey)}`);
100
+ }
101
+ const key = item[this.pk];
102
+ if (targetKey === key) {
103
+ return;
104
+ }
105
+
106
+ const node: LinkNode<T, K> = {
107
+ value: item,
108
+ cachedIndexValues: new Map(),
109
+ prev: target,
110
+ next: target.next,
111
+ };
112
+ if (target.next) {
113
+ target.next.prev = node;
114
+ } else {
115
+ this.tail = node;
116
+ }
117
+ target.next = node;
118
+
119
+ this.map.set(key, node);
120
+ this.mapSize++;
121
+ }
122
+
123
+ delete(key: T[K]): boolean {
124
+ const node = this.map.get(key);
125
+ if (node == null) {
126
+ return false;
127
+ }
128
+
129
+ // unlink from list
130
+ if (node.prev) {
131
+ node.prev.next = node.next;
132
+ } else {
133
+ this.head = node.next;
134
+ }
135
+ if (node.next) {
136
+ node.next.prev = node.prev;
137
+ } else {
138
+ this.tail = node.prev;
139
+ }
140
+
141
+ // remove from indexes
142
+ this.map.delete(key);
143
+ this.mapSize--;
144
+ return true;
145
+ }
146
+
147
+ at(n: number): Maybe<T> {
148
+ let current: Maybe<LinkNode<T, K>>;
149
+ if (n >= 0) {
150
+ current = this.head;
151
+ for (let i = 0; i < n && current != null; i++) {
152
+ current = current.next;
153
+ }
154
+ } else {
155
+ current = this.tail;
156
+ for (let i = n; i < -1 && current != null; i++) {
157
+ current = current.prev;
158
+ }
159
+ }
160
+ return current?.value;
161
+ }
162
+
163
+ get(key: T[K]): Maybe<T> {
164
+ return this.map.get(key)?.value;
165
+ }
166
+
167
+ has(item: T): boolean {
168
+ return this.map.get(item[this.pk])?.value === item;
169
+ }
170
+
171
+ // Get next item in insertion order
172
+ getNext(item: T): Maybe<T> {
173
+ return this.map.get(item[this.pk])?.next?.value;
174
+ }
175
+
176
+ // Get previous item in insertion order
177
+ getPrev(item: T): Maybe<T> {
178
+ return this.map.get(item[this.pk])?.prev?.value;
179
+ }
180
+
181
+ size(): number {
182
+ return this.mapSize;
183
+ }
184
+
185
+ [Symbol.iterator](): Iterator<T> {
186
+ let curr = this.head;
187
+ return {
188
+ next(): IteratorResult<T> {
189
+ if (!curr) {
190
+ return { done: true, value: undefined! };
191
+ }
192
+ const value = curr.value;
193
+ curr = curr.next;
194
+ return { done: false, value };
195
+ },
196
+ };
197
+ }
198
+ }
@@ -0,0 +1,181 @@
1
+ import { expect, test } from "bun:test";
2
+ import { List, ListNode } from "./List";
3
+ import { assertJust } from "../assert/assert";
4
+
5
+ test("addToEnd adds items to the end", () => {
6
+ const listInstance = List();
7
+ listInstance.addToEnd('1');
8
+ listInstance.addToEnd('2');
9
+ listInstance.addToEnd('3');
10
+ listInstance.addToEnd('4');
11
+ listInstance.addToEnd('5');
12
+ listInstance.addToEnd('6');
13
+
14
+ expect(listInstance.at(0)?.value).toBe('1');
15
+ expect(listInstance.at(1)?.value).toBe('2');
16
+ expect(listInstance.at(3)?.value).toBe('4');
17
+ expect(listInstance.at(5)?.value).toBe('6');
18
+ });
19
+
20
+ test("at works with negative values", () => {
21
+ const l = List();
22
+ l.addToEnd('1');
23
+ l.addToEnd('2');
24
+ l.addToEnd('3');
25
+ l.addToEnd('4');
26
+ l.addToEnd('5');
27
+ l.addToEnd('6');
28
+
29
+ expect(l.at(-1)?.value).toBe('6');
30
+ expect(l.at(-5)?.value).toBe('2');
31
+ });
32
+
33
+ test("if at exceeds the length, null to be returned", () => {
34
+ const listInstance = List();
35
+ listInstance.addToEnd('1');
36
+ listInstance.addToEnd('2');
37
+ listInstance.addToEnd('3');
38
+ listInstance.addToEnd('4');
39
+ listInstance.addToEnd('5');
40
+ listInstance.addToEnd('6');
41
+
42
+ expect(listInstance.at(-10)).toBe(null);
43
+ expect(listInstance.at(10)).toBe(null);
44
+ });
45
+
46
+ test("Add to start", () => {
47
+ const listInstance = List();
48
+ listInstance.addToStart('1');
49
+ listInstance.addToStart('2');
50
+ listInstance.addToStart('3');
51
+ listInstance.addToStart('4');
52
+ listInstance.addToStart('5');
53
+ listInstance.addToStart('6');
54
+
55
+ expect(listInstance.at(0)?.value).toBe('6');
56
+ expect(listInstance.at(1)?.value).toBe('5');
57
+ expect(listInstance.at(2)?.value).toBe('4');
58
+ expect(listInstance.at(-3)?.value).toBe('3');
59
+ expect(listInstance.at(-2)?.value).toBe('2');
60
+ expect(listInstance.at(-1)?.value).toBe('1');
61
+ });
62
+
63
+ test("from is a static method which builds a list from an array", () => {
64
+ const listInstance = List.from(['1','2','3','4','5','6']);
65
+
66
+ expect(listInstance.at(-1)?.value).toBe('6');
67
+ expect(listInstance.at(-2)?.value).toBe('5');
68
+ expect(listInstance.at(-3)?.value).toBe('4');
69
+ expect(listInstance.at(2)?.value).toBe('3');
70
+ expect(listInstance.at(1)?.value).toBe('2');
71
+ expect(listInstance.at(0)?.value).toBe('1');
72
+ });
73
+
74
+ test("Iterable", () => {
75
+ const listInstance = List.from(['1','2','3','4','5','6']);
76
+
77
+ let resultString = ''
78
+ for (const item of listInstance) {
79
+ resultString += item;
80
+ }
81
+
82
+ expect(resultString).toBe('123456');
83
+ });
84
+
85
+ test("Mutating ListNode directly keeps List in correct state", () => {
86
+ const listInstance = List.from(['1','2','3','4','5','6']);
87
+
88
+ const listNode: ListNode<string> = listInstance.at(2)!;
89
+
90
+ expect(listNode.value).toBe('3');
91
+
92
+ listNode.prev = '10';
93
+
94
+ expect(listInstance.at(2)?.value).toBe('10');
95
+ expect(listInstance.at(3)?.value).toBe('3');
96
+ });
97
+
98
+ test("Mutating ListNode.prev directly keeps List in correct state", () => {
99
+ const listInstance = List.from(['1','2','3','4','5','6']);
100
+
101
+ const listNode: ListNode<string> = listInstance.at(2)!;
102
+
103
+ expect(listNode.value).toBe('3');
104
+
105
+ listNode.prev = '10';
106
+
107
+ expect(Array.from(listInstance).join('')).toBe('12103456');
108
+ });
109
+
110
+ test("Mutating ListNode.next directly keeps List in correct state", () => {
111
+ const listInstance = List.from(['1','2','3','4','5','6']);
112
+
113
+ const listNode: ListNode<string> = listInstance.at(2)!;
114
+
115
+ expect(listNode.value).toBe('3');
116
+
117
+ listNode.next = '10';
118
+
119
+ expect(Array.from(listInstance).join('')).toBe('12310456');
120
+ expect(listInstance.at(-3)?.value).toBe('4');
121
+ expect(listInstance.at(-4)?.value).toBe('10');
122
+ expect(listInstance.at(-5)?.value).toBe('3');
123
+ });
124
+
125
+ test("Mutating the first item using ListNode directly keeps List in correct state", () => {
126
+ const listInstance = List.from(['1','2','3','4','5','6']);
127
+
128
+ listInstance.at(0)!.prev = 'test'
129
+
130
+ expect(Array.from(listInstance).join('')).toBe('test123456');
131
+ });
132
+
133
+ test("Mutating the last item using ListNode directly keeps List in correct state", () => {
134
+ const listInstance: List<string> = List.from(['1','2','3','4','5','6']);
135
+
136
+ listInstance.at(-1)!.next = 'test'
137
+
138
+ expect(Array.from(listInstance).join('')).toBe('123456test');
139
+ expect(listInstance.at(-1)?.value).toBe('test');
140
+ expect(listInstance.at(-2)?.value).toBe('6');
141
+ });
142
+
143
+ test('Mutating item in a middle keeps the list correct', () => {
144
+ const listInstance = List.from(['1','2','3','4','5','6']);
145
+
146
+ listInstance.at(-4)!.next = 'test'
147
+
148
+ expect(Array.from(listInstance).join('')).toBe('123test456');
149
+ expect(listInstance.at(-2)?.value).toBe('5');
150
+ expect(listInstance.at(-4)?.value).toBe('test');
151
+ expect(listInstance.at(-5)?.value).toBe('3');
152
+ expect(listInstance.at(5)?.value).toBe('5');
153
+ })
154
+
155
+ test('First item deletion works correctly', () => {
156
+ const listInstance = List.from(['1','2','3','4','5','6']);
157
+
158
+ listInstance.at(0)!.delete();
159
+
160
+ expect(Array.from(listInstance).join('')).toBe('23456');
161
+ expect(listInstance.at(0)?.value).toBe('2');
162
+ expect(listInstance.at(1)?.value).toBe('3');
163
+
164
+ })
165
+
166
+ test('addTo returns node', () => {
167
+ const listInstance = List.from(['1','2','3','4','5','6']);
168
+
169
+ expect(listInstance.addToEnd('test')).toBe(listInstance.at(-1));
170
+ expect(listInstance.addToStart('test2')).toBe(listInstance.at(0));
171
+ })
172
+
173
+ test('Adding item in the middle of the list', () => {
174
+ const listInstance = List.from(['1','2','3','4','5','6']);
175
+ const node = listInstance.at(2);
176
+ assertJust(node);
177
+ expect(node.value).toBe('3');
178
+ node.next = '123';
179
+
180
+ expect([...listInstance]).toEqual(['1','2','3', '123','4','5','6'])
181
+ })
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Linked list impl
3
+ *
4
+ * ideas:
5
+ * [-] Shall we use Record for list as well?
6
+ * [x] Support iterator
7
+ * [-] Make prev and next to receive ListNode
8
+ * [ ] Size support
9
+ */
10
+
11
+ import { Maybe } from "../maybe/Maybe";
12
+
13
+ // #region List's Node
14
+ export class ListNode<T> {
15
+ #list: Maybe<WeakRef<ListClass<T>>>;
16
+ #next: Maybe<ListNode<T>>;
17
+ #prev: Maybe<ListNode<T>>;
18
+ public node: T;
19
+
20
+ constructor(node: T, prev: Maybe<ListNode<T>> = null, next: Maybe<ListNode<T>> = null, list: Maybe<WeakRef<ListClass<T>>>) {
21
+ this.#prev = prev;
22
+ this.#next = next;
23
+ this.node = node;
24
+ this.#list = list;
25
+ }
26
+
27
+ // #region Delete Node from LinkedList
28
+ delete() {
29
+ const maybeParent = this.#list?.deref();
30
+ if (this.#prev != null) {
31
+ this.#prev.#next = this.#next;
32
+ } else {
33
+ maybeParent?.updateHead_UNSAFE(this.#next);
34
+ }
35
+ if (this.#next != null) {
36
+ this.#next.#prev = this.#prev;
37
+ } else {
38
+ maybeParent?.updateTail_UNSAFE(this.#prev);
39
+ }
40
+ this.#next = null;
41
+ this.#prev = null;
42
+ }
43
+
44
+ // #region Getters/setters
45
+ set next(value: T) {
46
+ const oldNext = this.#next;
47
+ this.#next = new ListNode(value, this, this.#next, this.#list);
48
+
49
+ if (oldNext == null) {
50
+ const maybeParent = this.#list?.deref();
51
+ if (!maybeParent) {
52
+ return;
53
+ }
54
+ maybeParent.updateTail_UNSAFE(this.#next);
55
+ } else {
56
+ oldNext.#prev = this.#next;
57
+ }
58
+ }
59
+
60
+ set prev(value: T) {
61
+ const oldPrev = this.#prev;
62
+ this.#prev = new ListNode(value, this.#prev, this, this.#list);
63
+
64
+ if (oldPrev == null) {
65
+ const maybeParent = this.#list?.deref();
66
+ if (!maybeParent) {
67
+ return;
68
+ }
69
+ maybeParent.updateHead_UNSAFE(this.#prev);
70
+ } else {
71
+ oldPrev.#next = this.#prev;
72
+ }
73
+ }
74
+
75
+ get value(): T {
76
+ return this.node;
77
+ }
78
+
79
+ get next(): Maybe<ListNode<T>> {
80
+ return this.#next;
81
+ }
82
+
83
+ get prev(): Maybe<ListNode<T>> {
84
+ return this.#prev;
85
+ }
86
+
87
+ // #region Getter of List
88
+ get list() {
89
+ return this.#list?.deref();
90
+ }
91
+ }
92
+
93
+ // #region IList public interface
94
+ export interface List<T> extends Iterable<T> {
95
+ addToStart(value: T): ListNode<T>;
96
+ delete(n: number): boolean;
97
+ at(n: number): Maybe<ListNode<T>>;
98
+ addToEnd(value: T): ListNode<T>;
99
+ [Symbol.iterator](): IterableIterator<T>;
100
+ }
101
+
102
+
103
+ // #region List class
104
+ class ListClass<T> implements List<T>{
105
+ #head: Maybe<ListNode<T>>;
106
+ #tail: Maybe<ListNode<T>>;
107
+
108
+ // #region internal methods
109
+ updateHead_UNSAFE(maybeNewHead: Maybe<ListNode<T>>) {
110
+ this.#head = maybeNewHead;
111
+ }
112
+
113
+ updateTail_UNSAFE(maybeNewTail: Maybe<ListNode<T>>) {
114
+ this.#tail = maybeNewTail;
115
+ }
116
+
117
+ // #region Public methods
118
+ addToStart(value: T) {
119
+ if (this.#head != null) {
120
+ this.#head.prev = value;
121
+ } else {
122
+ const node = new ListNode(value, null, null, new WeakRef(this));
123
+ this.#head = node;
124
+ this.#tail = node;
125
+ }
126
+ return this.#head
127
+ }
128
+
129
+ delete(n: number): boolean {
130
+ const node = this.at(n);
131
+ if (node == null) {
132
+ // Cannot delete non-existed item
133
+ return false;
134
+ }
135
+
136
+ // Corner case: delete the first item
137
+ if (node === this.#head) {
138
+ this.#head = this.at(1);
139
+ }
140
+ if (node === this.#tail) {
141
+ this.#tail = this.at(-2);
142
+ }
143
+ node.delete();
144
+ return true;
145
+ }
146
+
147
+ at(n: number): Maybe<ListNode<T>> {
148
+ let current: Maybe<ListNode<T>>;
149
+ if (n >= 0) {
150
+ current = this.#head;
151
+ for (let i = 0; i < n && current != null; i++) {
152
+ current = current.next;
153
+ }
154
+ } else {
155
+ current = this.#tail;
156
+ for (let i = n; i < -1 && current != null; i++) {
157
+ current = current.prev;
158
+ }
159
+ }
160
+ return current;
161
+ }
162
+
163
+ addToEnd(value: T) {
164
+ if (this.#tail != null) {
165
+ this.#tail.next = value;
166
+ } else {
167
+ const node = new ListNode(value, null, null, new WeakRef(this));
168
+ this.#head = node;
169
+ this.#tail = node;
170
+ }
171
+ return this.#tail;
172
+ }
173
+
174
+ *[Symbol.iterator]() {
175
+ let current = this.#head;
176
+ while (current) {
177
+ yield current.value;
178
+ current = current.next;
179
+ }
180
+ }
181
+
182
+ static from<T>(arrayLike: Iterable<T>): List<T> {
183
+ const list = new ListClass<T>;
184
+ for (const item of arrayLike) {
185
+ list.addToEnd(item);
186
+ }
187
+ return list;
188
+ }
189
+ }
190
+ // #region Exports
191
+ export function List<T>(): List<T> {
192
+ return new ListClass<T>();
193
+ }
194
+
195
+ List.from = ListClass.from;
@@ -0,0 +1,25 @@
1
+ export type None = null | undefined;
2
+ export type Just<T> = T;
3
+ export type Maybe<T> = Just<T> | None;
4
+
5
+ export function isJust<T>(v: Maybe<T>): v is Just<T> {
6
+ return v != null;
7
+ }
8
+
9
+ export function isNone<T>(v: Maybe<T>): v is None {
10
+ return v == null;
11
+ }
12
+
13
+ export function unwrap<T>(v: Maybe<T>): Just<T> {
14
+ if (isJust(v)) {
15
+ return v;
16
+ }
17
+ throw new TypeError("Optional is none");
18
+ }
19
+
20
+ export function orDefault<T, K>(v: Maybe<T>, alternative: K) {
21
+ if (isJust(v)) {
22
+ return v;
23
+ }
24
+ return alternative;
25
+ }
@@ -0,0 +1,3 @@
1
+ export const _ = undefined;
2
+
3
+ export function emptyFn() {}
@@ -1,5 +1,5 @@
1
1
  import { expect, test } from "bun:test";
2
- import { isRecordLike } from "./isRecordLike";
2
+ import { isRecordLike } from "./IsRecordLike";
3
3
 
4
4
  test("Handles objects", () => {
5
5
  expect(isRecordLike({})).toBe(true);
@@ -1,3 +1,3 @@
1
1
  export function isRecordLike<T, K extends object>(value: T | K): value is K {
2
2
  return (typeof value === "object" || Array.isArray(value)) && value != null;
3
- }
3
+ }