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,281 @@
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
+ // Notifies when item gets added to a particular index
11
+ type IndexAddedSubscriber<T> = (val: T[keyof T]) => void;
12
+
13
+ export class IndexedMap<T extends object, K extends keyof T>
14
+ implements Iterable<T>
15
+ {
16
+ private head: Maybe<LinkNode<T, K>>;
17
+ private tail: Maybe<LinkNode<T, K>>;
18
+ private mapSize = 0;
19
+ private pk: K;
20
+ private map = new Map<T[K], LinkNode<T, K>>();
21
+ private indexes: Map<keyof T, Map<T[keyof T], Set<LinkNode<T, K>>>> =
22
+ new Map();
23
+ private indexAddedSubscribers: Map<keyof T, Set<IndexAddedSubscriber<T>>> =
24
+ new Map();
25
+
26
+ constructor(pk: K, indexFields: Array<keyof T> = []) {
27
+ this.pk = pk;
28
+ for (const field of indexFields) {
29
+ if (field === pk) continue;
30
+ this.indexes.set(field, new Map());
31
+ }
32
+ }
33
+
34
+ // Puts item to the end
35
+ // Removes previous item, if PK is matched
36
+ put(item: T): void {
37
+ const key = item[this.pk];
38
+ // Delete previous, if any
39
+ this.delete(key);
40
+
41
+ const node: LinkNode<T, K> = {
42
+ value: item,
43
+ cachedIndexValues: new Map(),
44
+ prev: this.tail,
45
+ next: null,
46
+ };
47
+ if (!this.head) {
48
+ this.head = node;
49
+ }
50
+ if (this.tail) {
51
+ this.tail.next = node;
52
+ }
53
+ this.tail = node;
54
+ this.map.set(key, node);
55
+ this.mapSize++;
56
+
57
+ // update secondary indexes
58
+ this.indexNewNode(node);
59
+ }
60
+
61
+ putBefore(targetKey: T[K], item: T): void {
62
+ const target = this.map.get(targetKey);
63
+ if (target == null) {
64
+ throw new Error(`Key not found: ${String(targetKey)}`);
65
+ }
66
+ const key = item[this.pk];
67
+ if (targetKey === key) {
68
+ return;
69
+ }
70
+ this.delete(key);
71
+
72
+ const node: LinkNode<T, K> = {
73
+ value: item,
74
+ cachedIndexValues: new Map(),
75
+ prev: target.prev,
76
+ next: target,
77
+ };
78
+ if (target.prev) {
79
+ target.prev.next = node;
80
+ } else {
81
+ this.head = node;
82
+ }
83
+ target.prev = node;
84
+
85
+ this.map.set(key, node);
86
+ this.mapSize++;
87
+ this.indexNewNode(node);
88
+ }
89
+
90
+ // TODO: need to make it in background, instead of making eng to call it manually
91
+ updateIndex(item: T) {
92
+ const node = this.map.get(item[this.pk]);
93
+ if (node == null) {
94
+ return;
95
+ }
96
+ for (const [field, map] of this.indexes) {
97
+ const newVal = node.value[field];
98
+ const oldVal = node.cachedIndexValues.get(field);
99
+ if (oldVal !== newVal) {
100
+ const oldSet = this.indexes
101
+ .get(field)
102
+ // We know for sure, it's okay to index on that value
103
+ ?.get(oldVal as T[keyof T]);
104
+
105
+ oldSet?.delete(node);
106
+ let set = this.indexes.get(field)?.get(newVal);
107
+ if (!set) {
108
+ set = new Set();
109
+ map.set(newVal, set);
110
+ }
111
+ set.add(node);
112
+ node.cachedIndexValues.set(field, newVal);
113
+ this.notifySubscribers(field, newVal);
114
+ }
115
+ }
116
+ }
117
+
118
+ private indexNewNode(node: LinkNode<T, K>) {
119
+ for (const [field, map] of this.indexes) {
120
+ const val = node.value[field];
121
+ node.cachedIndexValues.set(field, val);
122
+ let set = map.get(val);
123
+ if (!set) {
124
+ set = new Set();
125
+ map.set(val, set);
126
+ }
127
+ set.add(node);
128
+ this.notifySubscribers(field, val);
129
+ }
130
+ }
131
+
132
+ private notifySubscribers(field: keyof T, val: T[keyof T] | undefined) {
133
+ if (val === undefined) {
134
+ return;
135
+ }
136
+ const listeners = this.indexAddedSubscribers.get(field);
137
+ if (!listeners) {
138
+ return;
139
+ }
140
+ for (const listener of listeners) {
141
+ listener(val);
142
+ }
143
+ }
144
+
145
+ subscribeToIndexChange(
146
+ field: keyof T,
147
+ listener: IndexAddedSubscriber<T>,
148
+ ): () => void {
149
+ if (!this.indexes.has(field)) {
150
+ throw new Error(
151
+ `Cannot subscribe to non-indexed field "${String(field)}"`,
152
+ );
153
+ }
154
+ let listeners = this.indexAddedSubscribers.get(field);
155
+ if (!listeners) {
156
+ listeners = new Set();
157
+ this.indexAddedSubscribers.set(field, listeners);
158
+ }
159
+ listeners.add(listener);
160
+ return () => {
161
+ listeners.delete(listener);
162
+ };
163
+ }
164
+
165
+ putAfter(targetKey: T[K], item: T): void {
166
+ const target = this.map.get(targetKey);
167
+ if (target == null) {
168
+ throw new Error(`Key not found: ${String(targetKey)}`);
169
+ }
170
+ const key = item[this.pk];
171
+ if (targetKey === key) {
172
+ return;
173
+ }
174
+
175
+ const node: LinkNode<T, K> = {
176
+ value: item,
177
+ cachedIndexValues: new Map(),
178
+ prev: target,
179
+ next: target.next,
180
+ };
181
+ if (target.next) {
182
+ target.next.prev = node;
183
+ } else {
184
+ this.tail = node;
185
+ }
186
+ target.next = node;
187
+
188
+ this.map.set(key, node);
189
+ this.mapSize++;
190
+ this.indexNewNode(node);
191
+ }
192
+
193
+ delete(key: T[K]): boolean {
194
+ const node = this.map.get(key);
195
+ if (node == null) {
196
+ return false;
197
+ }
198
+
199
+ // unlink from list
200
+ if (node.prev) {
201
+ node.prev.next = node.next;
202
+ } else {
203
+ this.head = node.next;
204
+ }
205
+ if (node.next) {
206
+ node.next.prev = node.prev;
207
+ } else {
208
+ this.tail = node.prev;
209
+ }
210
+
211
+ // remove from indexes
212
+ this.map.delete(key);
213
+ for (const [field, map] of this.indexes) {
214
+ const indexValue = node.value[field];
215
+ const set = map.get(indexValue);
216
+ if (set != null) {
217
+ set.delete(node);
218
+ }
219
+ }
220
+
221
+ this.mapSize--;
222
+ return true;
223
+ }
224
+
225
+ at(n: number): Maybe<T> {
226
+ let current: Maybe<LinkNode<T, K>>;
227
+ if (n >= 0) {
228
+ current = this.head;
229
+ for (let i = 0; i < n && current != null; i++) {
230
+ current = current.next;
231
+ }
232
+ } else {
233
+ current = this.tail;
234
+ for (let i = n; i < -1 && current != null; i++) {
235
+ current = current.prev;
236
+ }
237
+ }
238
+ return current?.value;
239
+ }
240
+
241
+ get(key: T[K]): Maybe<T> {
242
+ return this.map.get(key)?.value;
243
+ }
244
+
245
+ getByIndex<K extends keyof T>(field: K, val: T[keyof T]): Array<T> {
246
+ const map = this.indexes.get(field);
247
+ if (!map) {
248
+ throw new Error(`No index defined for field "${String(field)}"`);
249
+ }
250
+ const set = map.get(val);
251
+ return set ? Array.from(set.values(), (node) => node.value) : [];
252
+ }
253
+
254
+ // Get next item in insertion order
255
+ getNext(item: T): Maybe<T> {
256
+ return this.map.get(item[this.pk])?.next?.value;
257
+ }
258
+
259
+ // Get previous item in insertion order
260
+ getPrev(item: T): Maybe<T> {
261
+ return this.map.get(item[this.pk])?.prev?.value;
262
+ }
263
+
264
+ size(): number {
265
+ return this.mapSize;
266
+ }
267
+
268
+ [Symbol.iterator](): Iterator<T> {
269
+ let curr = this.head;
270
+ return {
271
+ next(): IteratorResult<T> {
272
+ if (!curr) {
273
+ return { done: true, value: undefined! };
274
+ }
275
+ const value = curr.value;
276
+ curr = curr.next;
277
+ return { done: false, value };
278
+ },
279
+ };
280
+ }
281
+ }
@@ -0,0 +1,67 @@
1
+ import { expect, test } from "bun:test";
2
+ import { LinkedMap } from "./LinkedMap";
3
+
4
+ type Entry = { key: string; value: number };
5
+
6
+ test("put adds items to the end", () => {
7
+ const map = new LinkedMap<Entry, "key">("key");
8
+ map.put({ key: "foo", value: 1 });
9
+ map.put({ key: "bar", value: 2 });
10
+ map.put({ key: "baz", value: 94 });
11
+
12
+ expect([...map].map((e) => [e.key, e.value])).toEqual([
13
+ ["foo", 1],
14
+ ["bar", 2],
15
+ ["baz", 94],
16
+ ]);
17
+ });
18
+
19
+ test("putBefore and putAfter work correctly", () => {
20
+ const map = new LinkedMap<Entry, "key">("key");
21
+ map.put({ key: "foo", value: 1 });
22
+ map.put({ key: "bar", value: 2 });
23
+ map.put({ key: "baz", value: 94 });
24
+
25
+ map.putBefore("bar", { key: "hey", value: 13 }); // insert before "bar"
26
+
27
+ expect([...map].map((e) => [e.key, e.value])).toEqual([
28
+ ["foo", 1],
29
+ ["hey", 13],
30
+ ["bar", 2],
31
+ ["baz", 94],
32
+ ]);
33
+
34
+ map.putAfter("baz", { key: "zap", value: 42 }); // insert after "baz"
35
+
36
+ expect([...map].map((e) => [e.key, e.value])).toEqual([
37
+ ["foo", 1],
38
+ ["hey", 13],
39
+ ["bar", 2],
40
+ ["baz", 94],
41
+ ["zap", 42],
42
+ ]);
43
+ });
44
+
45
+ test("map has access by key", () => {
46
+ const map = new LinkedMap<Entry, "key">("key");
47
+ map.put({ key: "foo", value: 1 });
48
+ map.put({ key: "bar", value: 2 });
49
+ map.put({ key: "hey", value: 13 });
50
+ map.put({ key: "baz", value: 94 });
51
+
52
+ expect(map.get("baz")?.value).toEqual(94);
53
+ expect(map.get("bar")?.value).toEqual(2);
54
+ expect(map.get("hey")?.value).toEqual(13);
55
+ });
56
+
57
+ test("map has access by index", () => {
58
+ const map = new LinkedMap<Entry, "key">("key");
59
+ map.put({ key: "foo", value: 1 });
60
+ map.put({ key: "bar", value: 2 });
61
+ map.put({ key: "hey", value: 13 });
62
+ map.put({ key: "baz", value: 94 });
63
+
64
+ expect(map.at(-1)?.value).toEqual(94);
65
+ expect(map.at(2)?.value).toEqual(13);
66
+ expect(map.at(0)?.value).toEqual(1);
67
+ });
@@ -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
+ }