creo 0.1.0 → 0.2.0

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.
package/dist/index.js CHANGED
@@ -1,354 +1,30 @@
1
- // src/structures/list.ts
2
- var $next = Symbol("next");
3
- var $prev = Symbol("prev");
4
- var $owner = Symbol("owner");
5
-
6
- class ListNode {
7
- [$owner];
8
- [$next];
9
- [$prev];
10
- v;
11
- constructor(node, prev = null, next = null, list) {
12
- this[$prev] = prev;
13
- this[$next] = next;
14
- this[$owner] = list;
15
- this.v = node;
16
- }
17
- isFirst() {
18
- return this[$prev] == null;
19
- }
20
- isLast() {
21
- return this[$next] == null;
22
- }
23
- delete() {
24
- this[$owner]?.delete(this);
25
- }
26
- clearFields() {
27
- this[$next] = null;
28
- this[$prev] = null;
29
- this[$owner] = null;
30
- }
31
- insertNext(value) {
32
- const owner = this[$owner];
33
- if (!owner) {
34
- throw new Error("The item is detached from DataContainer");
35
- }
36
- return owner.insertNext(this, value);
37
- }
38
- insertPrev(value) {
39
- const owner = this[$owner];
40
- if (!owner) {
41
- throw new Error("The item is detached from DataContainer");
42
- }
43
- return owner.insertPrev(this, value);
44
- }
45
- getNext() {
46
- return this[$next];
47
- }
48
- getPrev() {
49
- return this[$prev];
50
- }
51
- getList() {
52
- return this[$owner];
53
- }
54
- }
55
-
56
- class InternalList {
57
- #head;
58
- #tail;
59
- #size = 0;
60
- #cursorNode;
61
- #cursorIndex = -1;
62
- #invalidateCursor() {
63
- this.#cursorNode = null;
64
- this.#cursorIndex = -1;
65
- }
66
- insertStart(value) {
67
- const node = new ListNode(value, null, this.#head, this);
68
- if (this.#head != null) {
69
- this.#head[$prev] = node;
70
- } else {
71
- this.#tail = node;
72
- }
73
- this.#head = node;
74
- this.#size++;
75
- this.#invalidateCursor();
76
- return this.#head;
77
- }
78
- delete(node) {
79
- const n = node;
80
- const prev = n[$prev];
81
- const next = n[$next];
82
- if (next) {
83
- next[$prev] = prev;
84
- }
85
- if (prev) {
86
- prev[$next] = next;
87
- }
88
- if (node === this.#head) {
89
- this.#head = next;
90
- }
91
- if (node === this.#tail) {
92
- this.#tail = prev;
93
- }
94
- n.clearFields();
95
- this.#size--;
96
- this.#invalidateCursor();
97
- }
98
- at(n) {
99
- if (n < 0)
100
- n = this.#size + n;
101
- if (n < 0 || n >= this.#size)
102
- return;
103
- let current;
104
- let pos;
105
- const distFromHead = n;
106
- const distFromTail = this.#size - 1 - n;
107
- const distFromCursor = this.#cursorNode != null ? Math.abs(n - this.#cursorIndex) : Infinity;
108
- if (distFromCursor <= distFromHead && distFromCursor <= distFromTail) {
109
- current = this.#cursorNode;
110
- pos = this.#cursorIndex;
111
- } else if (distFromHead <= distFromTail) {
112
- current = this.#head;
113
- pos = 0;
114
- } else {
115
- current = this.#tail;
116
- pos = this.#size - 1;
117
- }
118
- while (pos < n && current != null) {
119
- current = current[$next];
120
- pos++;
121
- }
122
- while (pos > n && current != null) {
123
- current = current[$prev];
124
- pos--;
125
- }
126
- if (current != null) {
127
- this.#cursorNode = current;
128
- this.#cursorIndex = pos;
129
- }
130
- return current;
131
- }
132
- get size() {
133
- return this.#size;
134
- }
135
- clear() {
136
- this.#head = null;
137
- this.#tail = null;
138
- this.#size = 0;
139
- this.#invalidateCursor();
140
- }
141
- first() {
142
- return this.#head;
143
- }
144
- last() {
145
- return this.#tail;
146
- }
147
- insertEnd(value) {
148
- const node = new ListNode(value, this.#tail, null, this);
149
- if (this.#tail != null) {
150
- this.#tail[$next] = node;
151
- } else {
152
- this.#head = node;
153
- }
154
- this.#tail = node;
155
- this.#size++;
156
- return this.#tail;
157
- }
158
- insertNext(ref, value) {
159
- const r = ref;
160
- if (r[$owner] != this) {
161
- throw new TypeError("The reference node does not belong to the current list");
162
- }
163
- const node = new ListNode(value, r, r[$next], this);
164
- if (r[$next]) {
165
- r[$next][$prev] = node;
166
- } else {
167
- this.#tail = node;
168
- }
169
- r[$next] = node;
170
- this.#size++;
171
- this.#invalidateCursor();
172
- return node;
173
- }
174
- insertPrev(ref, value) {
175
- const r = ref;
176
- if (r[$owner] != this) {
177
- throw new TypeError("The reference node does not belong to the current list");
178
- }
179
- const node = new ListNode(value, r[$prev], r, this);
180
- if (r[$prev]) {
181
- r[$prev][$next] = node;
182
- } else {
183
- this.#head = node;
184
- }
185
- r[$prev] = node;
186
- this.#size++;
187
- this.#invalidateCursor();
188
- return node;
189
- }
190
- *[Symbol.iterator]() {
191
- let current = this.#head;
192
- while (current) {
193
- const next = current[$next];
194
- yield current;
195
- current = next;
196
- }
197
- }
198
- }
199
-
200
- // src/structures/indexed_list.ts
201
- class IndexedList {
202
- #list = new InternalList;
203
- #map = new Map;
204
- push(item) {
205
- const existing = this.#map.get(item);
206
- if (existing)
207
- return existing;
208
- const node = this.#list.insertEnd(item);
209
- this.#map.set(item, node);
210
- return node;
211
- }
212
- unshift(item) {
213
- if (this.#map.has(item))
214
- return;
215
- const node = this.#list.insertStart(item);
216
- this.#map.set(item, node);
217
- }
218
- insertAfter(ref, item) {
219
- if (this.#map.has(item))
220
- return;
221
- const refNode = this.#map.get(ref);
222
- if (!refNode) {
223
- this.push(item);
224
- return;
225
- }
226
- const node = refNode.insertNext(item);
227
- this.#map.set(item, node);
228
- }
229
- delete(item) {
230
- const node = this.#map.get(item);
231
- if (!node)
232
- return;
233
- this.#map.delete(item);
234
- node.delete();
235
- }
236
- has(item) {
237
- return this.#map.has(item);
238
- }
239
- get length() {
240
- return this.#map.size;
241
- }
242
- first() {
243
- return this.#list.first()?.v;
244
- }
245
- last() {
246
- return this.#list.last()?.v;
247
- }
248
- getNode(item) {
249
- return this.#map.get(item);
250
- }
251
- at(index) {
252
- return this.#list.at(index)?.v;
253
- }
254
- upsert(pos, item) {
255
- const existing = this.#list.at(pos);
256
- if (!existing) {
257
- return this.push(item);
258
- }
259
- this.#map.delete(existing.v);
260
- existing.v = item;
261
- this.#map.set(item, existing);
262
- return existing;
263
- }
264
- swap(a, b) {
265
- const nodeA = this.#map.get(a);
266
- const nodeB = this.#map.get(b);
267
- if (!nodeA || !nodeB)
268
- return;
269
- nodeA.v = b;
270
- nodeB.v = a;
271
- this.#map.set(a, nodeB);
272
- this.#map.set(b, nodeA);
273
- }
274
- clear() {
275
- this.#list.clear();
276
- this.#map.clear();
277
- }
278
- *[Symbol.iterator]() {
279
- for (const node of this.#list) {
280
- yield node.v;
281
- }
1
+ // src/internal/internal_view.ts
2
+ var F_PENDING = 1;
3
+ var F_DIRTY = 1 << 1;
4
+ var F_MOVED = 1 << 2;
5
+ var F_QUICK_RERENDER = 1 << 3;
6
+ var F_PRIMITIVE = 1 << 4;
7
+ var F_TEXT_CONTENT = 1 << 5;
8
+ function hasScStructuralChange(prev, next) {
9
+ const prevLen = prev?.length ?? 0;
10
+ const nextLen = next?.length ?? 0;
11
+ if (prevLen === 0 && nextLen === 0)
12
+ return false;
13
+ if (!prev || !next)
14
+ return true;
15
+ if (prev.length !== next.length)
16
+ return true;
17
+ for (let i = 0;i < next.length; i++) {
18
+ if (next[i].viewFn !== prev[i].viewFn)
19
+ return true;
20
+ if (next[i].userKey !== prev[i].userKey)
21
+ return true;
282
22
  }
23
+ return false;
283
24
  }
284
25
 
285
- // src/internal/engine.ts
286
- class Engine {
287
- renderer;
288
- dirty = new IndexedList;
289
- #scheduler;
290
- #renderScheduled = false;
291
- #collector;
292
- constructor(renderer, scheduler) {
293
- this.renderer = renderer;
294
- this.#scheduler = scheduler ?? "scheduler" in globalThis ? (cb) => window.scheduler.postTask(cb) : (cb) => queueMicrotask(cb);
295
- }
296
- disposeView(view) {
297
- this.dirty.delete(view);
298
- }
299
- schedule() {
300
- if (this.#renderScheduled) {
301
- return;
302
- }
303
- this.#renderScheduled = true;
304
- this.#scheduler(() => {
305
- this.#renderScheduled = false;
306
- this.render();
307
- });
308
- }
309
- markDirty(view) {
310
- this.dirty.push(view);
311
- this.schedule();
312
- }
313
- pendingView(view) {
314
- this.#collector?.(view);
315
- }
316
- collect(slot) {
317
- const list = [];
318
- const prev = this.#collector;
319
- this.#collector = list.push.bind(list);
320
- slot();
321
- this.#collector = prev;
322
- return list;
323
- }
324
- #rendering = false;
325
- render() {
326
- if (this.#rendering)
327
- return;
328
- this.#rendering = true;
329
- try {
330
- const cbs = [];
331
- while (this.dirty.length > 0) {
332
- const view = this.dirty.first();
333
- const isNew = view.renderRef == null;
334
- if (!isNew)
335
- view.onUpdateBefore();
336
- this.renderer.render(view);
337
- if (view.dirty) {
338
- view.reconsile();
339
- cbs.push(isNew ? view.onMount : view.onUpdateAfter);
340
- view.dirty = false;
341
- }
342
- this.dirty.delete(view);
343
- }
344
- for (const cb of cbs) {
345
- cb();
346
- }
347
- } finally {
348
- this.#rendering = false;
349
- }
350
- }
351
- }
26
+ // src/public/primitive.ts
27
+ var $primitive = Symbol("primitive");
352
28
 
353
29
  // src/public/state.ts
354
30
  class State {
@@ -436,219 +112,484 @@ function shallowEqual(a, b) {
436
112
  if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) {
437
113
  return false;
438
114
  }
439
- const keysA = Object.keys(a);
440
- if (keysA.length !== Object.keys(b).length)
441
- return false;
442
- for (let i = 0;i < keysA.length; i++) {
443
- const key = keysA[i];
444
- if (!Object.is(a[key], b[key])) {
115
+ let countA = 0;
116
+ for (const key in a) {
117
+ countA++;
118
+ if (!Object.is(a[key], b[key]))
445
119
  return false;
120
+ }
121
+ let countB = 0;
122
+ for (const _ in b)
123
+ countB++;
124
+ return countA === countB;
125
+ }
126
+
127
+ // src/functional/lis.ts
128
+ function lis(arr) {
129
+ const len = arr.length;
130
+ if (len === 0)
131
+ return new Set;
132
+ const tails = [];
133
+ const prev = new Array(len).fill(-1);
134
+ for (let i = 0;i < len; i++) {
135
+ if (arr[i] < 0)
136
+ continue;
137
+ const val = arr[i];
138
+ let lo = 0;
139
+ let hi = tails.length;
140
+ while (lo < hi) {
141
+ const mid = lo + hi >> 1;
142
+ if (arr[tails[mid]] < val)
143
+ lo = mid + 1;
144
+ else
145
+ hi = mid;
446
146
  }
147
+ if (lo > 0)
148
+ prev[i] = tails[lo - 1];
149
+ tails[lo] = i;
150
+ }
151
+ const result = new Set;
152
+ if (tails.length === 0)
153
+ return result;
154
+ let idx = tails[tails.length - 1];
155
+ for (let k = tails.length - 1;k >= 0; k--) {
156
+ result.add(idx);
157
+ idx = prev[idx];
447
158
  }
448
- return true;
159
+ return result;
449
160
  }
450
161
 
451
- // src/internal/internal_view.ts
452
- class View {
453
- viewFn;
454
- engine;
455
- parent;
456
- userKey;
457
- renderRef;
458
- props;
459
- slotChildren;
460
- body;
461
- virtualDom;
462
- keyToIndex;
463
- dirty = true;
464
- moved = false;
465
- quickRerender = true;
466
- #unsubscribe = [];
467
- constructor(viewFn, initialProps, slot, engine, parent, userKey) {
468
- this.viewFn = viewFn;
469
- this.engine = engine;
470
- this.parent = parent;
471
- this.userKey = userKey;
472
- this.props = initialProps;
473
- this.body = viewFn({
474
- props: () => this.props,
475
- use: (storeOrInitial) => {
476
- if (!isStore(storeOrInitial)) {
477
- return new State(storeOrInitial, this.markDirty);
162
+ // src/internal/orchestrator.ts
163
+ class Orchestrator {
164
+ #currentEngine;
165
+ setCurrentEngine(engine) {
166
+ this.#currentEngine = engine;
167
+ }
168
+ currentEngine() {
169
+ return this.#currentEngine;
170
+ }
171
+ }
172
+ var orchestrator = new Orchestrator;
173
+
174
+ // src/internal/engine.ts
175
+ class Engine {
176
+ renderer;
177
+ #dirtyQueue = new Set;
178
+ #collector;
179
+ #collectFor;
180
+ #scheduler;
181
+ #renderScheduled = false;
182
+ #rendering = false;
183
+ constructor(renderer, scheduler) {
184
+ this.renderer = renderer;
185
+ this.#scheduler = scheduler ?? ((cb) => queueMicrotask(cb));
186
+ renderer.engine = this;
187
+ }
188
+ newView(viewFn, parent, props, slot, userKey) {
189
+ const res = {
190
+ viewFn,
191
+ userKey,
192
+ props,
193
+ slot,
194
+ body: null,
195
+ sc: null,
196
+ renderRef: null,
197
+ flags: F_PENDING | (viewFn[$primitive] != null ? F_PRIMITIVE : 0),
198
+ children: null,
199
+ keyToView: null,
200
+ unsubscribe: null,
201
+ parent,
202
+ scHost: null
203
+ };
204
+ if (slot) {
205
+ res.sc = this.#collect(slot, [], res);
206
+ }
207
+ return res;
208
+ }
209
+ view(viewFn, props, slot, userKey) {
210
+ const view = this.newView(viewFn, this.#collectFor, props, slot, userKey);
211
+ this.#collector?.push(view);
212
+ return view;
213
+ }
214
+ views(views) {
215
+ if (views)
216
+ this.#collector?.push(...views);
217
+ }
218
+ initViewBody(view) {
219
+ if (!(view.flags & F_PENDING))
220
+ return;
221
+ view.flags &= ~F_PENDING;
222
+ if (view.flags & F_PRIMITIVE) {
223
+ const engine = this;
224
+ view.body = {
225
+ render() {
226
+ engine.views(view.sc);
227
+ }
228
+ };
229
+ return;
230
+ }
231
+ view.body = view.viewFn({
232
+ props: () => view.props,
233
+ use: (storeOrState) => {
234
+ if (!isStore(storeOrState)) {
235
+ return new State(storeOrState, () => this.markDirty(view));
478
236
  }
479
- const s = storeOrInitial;
480
- this.#unsubscribe.push(this.markDirty);
237
+ const s = storeOrState;
238
+ const unsub = s.subscribe(() => this.markDirty(view));
239
+ if (!view.unsubscribe)
240
+ view.unsubscribe = [];
241
+ view.unsubscribe.push(unsub);
481
242
  return s;
482
243
  },
483
244
  slot: () => {
484
- if (!this.slotChildren) {
245
+ if (!view.sc)
485
246
  return;
486
- }
487
- for (const child of this.slotChildren) {
488
- this.engine.pendingView(child);
247
+ view.scHost = this.#collectFor ?? view;
248
+ for (const child of view.sc) {
249
+ child.parent = this.#collectFor ?? view;
250
+ this.#collector?.push(child);
489
251
  }
490
252
  }
491
253
  });
492
- this.slotChildren = slot && this.engine.collect(slot);
493
- this.markDirty();
494
254
  }
495
- markDirty = () => {
496
- this.dirty = true;
497
- this.engine.markDirty(this);
498
- };
499
- markMove() {
500
- this.moved = true;
501
- this.engine.markDirty(this);
255
+ createRoot(children, props) {
256
+ orchestrator.setCurrentEngine(this);
257
+ const view = this.newView(({ slot }) => ({
258
+ render() {
259
+ slot();
260
+ }
261
+ }), null, props, children, null);
262
+ this.markDirty(view);
263
+ return view;
502
264
  }
503
- shouldUpdate(nextProps) {
504
- if (this.body.shouldUpdate) {
505
- return this.body.shouldUpdate(nextProps);
265
+ markDirty(view) {
266
+ if (view.flags & F_DIRTY) {
267
+ return;
506
268
  }
507
- return !shallowEqual(this.props, nextProps);
269
+ view.flags |= F_DIRTY;
270
+ this.#dirtyQueue.add(view);
271
+ this.schedule();
508
272
  }
509
- onMount = () => {
510
- this.quickRerender = false;
511
- this.body.onMount?.();
512
- };
513
- onUpdateBefore() {
514
- this.body.onUpdateBefore?.();
273
+ markMoved(view) {
274
+ if (view.flags & F_MOVED) {
275
+ return;
276
+ }
277
+ view.flags |= F_MOVED;
278
+ this.#dirtyQueue.add(view);
279
+ this.schedule();
515
280
  }
516
- onUpdateAfter = () => {
517
- this.quickRerender = false;
518
- this.body.onUpdateBefore?.();
519
- };
520
- nextProps(nextProps, nextSlot) {
521
- const prevChildren = this.slotChildren;
522
- this.slotChildren = nextSlot && this.engine.collect(nextSlot);
523
- if (this.shouldUpdate(nextProps) || hasNewChildren(prevChildren, this.slotChildren)) {
524
- this.markDirty();
525
- this.props = nextProps;
281
+ schedule() {
282
+ if (this.#renderScheduled)
283
+ return;
284
+ this.#renderScheduled = true;
285
+ this.#scheduler(() => {
286
+ this.#renderScheduled = false;
287
+ this.render();
288
+ });
289
+ }
290
+ #collect(render, collector, parent) {
291
+ const before = this.#collector;
292
+ const beforeParent = this.#collectFor;
293
+ this.#collector = collector;
294
+ this.#collectFor = parent;
295
+ render();
296
+ this.#collector = before;
297
+ this.#collectFor = beforeParent;
298
+ return collector;
299
+ }
300
+ nextProps(view, nextProps, nextSlot, preCollectedSc) {
301
+ const prevSc = view.sc;
302
+ view.slot = nextSlot;
303
+ if (preCollectedSc) {
304
+ for (const child of preCollectedSc)
305
+ child.parent = view;
306
+ view.sc = preCollectedSc;
307
+ } else {
308
+ view.sc = nextSlot ? this.#collect(nextSlot, [], view) : null;
309
+ }
310
+ const structChanged = hasScStructuralChange(prevSc, view.sc);
311
+ const shouldUpdate = view.body?.shouldUpdate ? view.body.shouldUpdate(nextProps) : !shallowEqual(view.props, nextProps);
312
+ if (shouldUpdate || structChanged) {
313
+ view.props = nextProps;
314
+ this.markDirty(view);
315
+ } else if (view.sc && view.scHost?.children) {
316
+ this.#propagateScProps(view);
317
+ } else if (view.sc && prevSc) {
318
+ for (let i = 0;i < view.sc.length; i++) {
319
+ if (view.sc[i].props !== prevSc[i].props) {
320
+ this.markDirty(view);
321
+ break;
322
+ }
323
+ }
526
324
  }
527
325
  }
528
- reconsile() {
529
- const children = this.engine.collect(this.body.render);
530
- if (!this.virtualDom && children.length === 0)
326
+ #propagateScProps(owner) {
327
+ const host = owner.scHost;
328
+ const sc = owner.sc;
329
+ if (!host?.children || !sc)
531
330
  return;
532
- if (!this.virtualDom)
533
- this.virtualDom = new IndexedList;
534
- this.quickRerender = true;
535
- const vdom = this.virtualDom;
536
- for (let i = 0;i < children.length; i++) {
537
- const pending = children[i];
538
- const expectedNext = vdom.at(i);
539
- if (pending.userKey == null) {
540
- if (expectedNext?.viewFn === pending.viewFn) {
541
- this.quickRerender = false;
542
- expectedNext.nextProps(pending.props, pending.slot);
543
- } else {
544
- if (expectedNext) {
545
- if (expectedNext.userKey)
546
- this.keyToIndex?.delete(expectedNext.userKey);
547
- expectedNext[Symbol.dispose]();
331
+ if (host.keyToView) {
332
+ for (const item of sc) {
333
+ if (item.userKey != null) {
334
+ const live = host.keyToView.get(item.userKey);
335
+ if (live && live.viewFn === item.viewFn) {
336
+ this.nextProps(live, item.props, item.slot);
548
337
  }
549
- vdom.upsert(i, new View(pending.viewFn, pending.props, pending.slot, this.engine, this, null));
550
338
  }
551
- } else {
552
- const matchedIndex = this.keyToIndex?.get(pending.userKey);
553
- const matched = matchedIndex != null ? vdom.at(matchedIndex) : null;
554
- if (matched && matched.viewFn === pending.viewFn) {
555
- this.quickRerender = false;
556
- matched.nextProps(pending.props, pending.slot);
557
- if (expectedNext != null) {
558
- if (matched !== expectedNext) {
559
- matched.markMove();
560
- expectedNext.markMove();
561
- vdom.swap(matched, expectedNext);
562
- if (this.keyToIndex) {
563
- if (matched.userKey != null)
564
- this.keyToIndex.set(matched.userKey, i);
565
- if (expectedNext.userKey != null)
566
- this.keyToIndex.set(expectedNext.userKey, matchedIndex);
567
- }
568
- }
569
- } else {
570
- matched.markMove();
571
- vdom.upsert(i, matched);
572
- if (this.keyToIndex && matched.userKey != null) {
573
- this.keyToIndex.set(matched.userKey, i);
574
- }
575
- }
576
- } else {
577
- if (expectedNext) {
578
- if (expectedNext.userKey)
579
- this.keyToIndex?.delete(expectedNext.userKey);
580
- expectedNext[Symbol.dispose]();
581
- }
582
- vdom.upsert(i, new View(pending.viewFn, pending.props, pending.slot, this.engine, this, pending.userKey));
583
- if (!this.keyToIndex)
584
- this.keyToIndex = new Map;
585
- this.keyToIndex.set(pending.userKey, i);
339
+ }
340
+ } else {
341
+ const len = Math.min(sc.length, host.children.length);
342
+ for (let i = 0;i < len; i++) {
343
+ const live = host.children[i];
344
+ if (live.viewFn === sc[i].viewFn) {
345
+ this.nextProps(live, sc[i].props, sc[i].slot);
586
346
  }
587
347
  }
588
348
  }
589
- if (children.length < vdom.length) {
590
- for (let i = vdom.length;i >= children.length; i--) {
591
- const removed = vdom.at(i);
592
- if (removed) {
593
- if (removed.userKey)
594
- this.keyToIndex?.delete(removed.userKey);
595
- removed[Symbol.dispose]();
596
- vdom.delete(removed);
349
+ }
350
+ reconcile(view) {
351
+ let pendingChildren = null;
352
+ if (view.body?.render) {
353
+ pendingChildren = this.#collect(view.body.render, [], view);
354
+ }
355
+ if (pendingChildren == null || pendingChildren.length === 0) {
356
+ if (view.children)
357
+ for (const child of view.children)
358
+ this.dispose(child);
359
+ return;
360
+ }
361
+ if (view.children == null) {
362
+ view.children = pendingChildren;
363
+ for (const child of pendingChildren) {
364
+ this.initViewBody(child);
365
+ this.markDirty(child);
366
+ if (child.userKey != null) {
367
+ if (!view.keyToView)
368
+ view.keyToView = new Map;
369
+ view.keyToView.set(child.userKey, child);
597
370
  }
598
371
  }
372
+ return;
373
+ }
374
+ const hasKeys = pendingChildren.some((c) => c.userKey != null);
375
+ if (hasKeys) {
376
+ this.#reconcileKeyed(view, view.children, pendingChildren);
377
+ } else {
378
+ this.#reconcileNonKeyed(view, view.children, pendingChildren);
599
379
  }
600
380
  }
601
- [Symbol.dispose]() {
602
- if (this.virtualDom) {
603
- for (const child of this.virtualDom) {
604
- child[Symbol.dispose]();
381
+ #reconcileNonKeyed(view, oldChildren, pending) {
382
+ const oldLen = oldChildren.length;
383
+ const newLen = pending.length;
384
+ const minLen = Math.min(oldLen, newLen);
385
+ for (let i = 0;i < minLen; i++) {
386
+ const old = oldChildren[i];
387
+ const pend = pending[i];
388
+ this.#patchOrReplace(view, oldChildren, i, old, pend);
389
+ }
390
+ for (let i = oldLen;i < newLen; i++) {
391
+ oldChildren[i] = pending[i];
392
+ this.initViewBody(pending[i]);
393
+ this.markDirty(pending[i]);
394
+ }
395
+ for (let i = newLen;i < oldLen; i++) {
396
+ this.dispose(oldChildren[i]);
397
+ }
398
+ oldChildren.length = newLen;
399
+ }
400
+ #reconcileKeyed(view, oldChildren, pending) {
401
+ let i = 0;
402
+ let oldEnd = oldChildren.length - 1;
403
+ let newEnd = pending.length - 1;
404
+ while (i <= oldEnd && i <= newEnd) {
405
+ const oldView = oldChildren[i];
406
+ const pendView = pending[i];
407
+ if (oldView.userKey !== pendView.userKey)
408
+ break;
409
+ this.#patchOrReplace(view, oldChildren, i, oldView, pendView);
410
+ i++;
411
+ }
412
+ while (oldEnd >= i && newEnd >= i) {
413
+ const oldView = oldChildren[oldEnd];
414
+ const pendView = pending[newEnd];
415
+ if (oldView.userKey !== pendView.userKey)
416
+ break;
417
+ this.#patchOrReplace(view, oldChildren, oldEnd, oldView, pendView);
418
+ oldEnd--;
419
+ newEnd--;
420
+ }
421
+ if (i > oldEnd) {
422
+ for (let j = i;j <= newEnd; j++) {
423
+ this.initViewBody(pending[j]);
424
+ this.markDirty(pending[j]);
425
+ if (pending[j].userKey != null) {
426
+ if (!view.keyToView)
427
+ view.keyToView = new Map;
428
+ view.keyToView.set(pending[j].userKey, pending[j]);
429
+ }
430
+ }
431
+ view.children = [
432
+ ...oldChildren.slice(0, i),
433
+ ...pending.slice(i, newEnd + 1),
434
+ ...oldChildren.slice(i, oldEnd + 1)
435
+ ];
436
+ return;
437
+ }
438
+ if (i > newEnd) {
439
+ for (let j = i;j <= oldEnd; j++) {
440
+ this.dispose(oldChildren[j]);
605
441
  }
442
+ view.children = [
443
+ ...oldChildren.slice(0, i),
444
+ ...oldChildren.slice(oldEnd + 1)
445
+ ];
446
+ return;
447
+ }
448
+ const newKeyToIndex = new Map;
449
+ for (let j = i;j <= newEnd; j++) {
450
+ const key = pending[j].userKey;
451
+ if (key != null)
452
+ newKeyToIndex.set(key, j);
606
453
  }
607
- this.engine.renderer.unmount(this);
608
- this.engine.disposeView(this);
454
+ const middleLen = newEnd - i + 1;
455
+ const newIdxToOldIdx = new Array(middleLen).fill(-1);
456
+ const matched = new Set;
457
+ for (let j = i;j <= oldEnd; j++) {
458
+ const oldView = oldChildren[j];
459
+ const key = oldView.userKey;
460
+ if (key != null && newKeyToIndex.has(key)) {
461
+ const newIdx = newKeyToIndex.get(key);
462
+ newIdxToOldIdx[newIdx - i] = j;
463
+ matched.add(j);
464
+ }
465
+ }
466
+ for (let j = i;j <= oldEnd; j++) {
467
+ if (!matched.has(j)) {
468
+ this.dispose(oldChildren[j]);
469
+ }
470
+ }
471
+ const stable = lis(newIdxToOldIdx);
472
+ const newChildren = new Array(middleLen);
473
+ for (let j = middleLen - 1;j >= 0; j--) {
474
+ const newIdx = i + j;
475
+ const pendView = pending[newIdx];
476
+ if (newIdxToOldIdx[j] === -1) {
477
+ this.initViewBody(pendView);
478
+ this.markDirty(pendView);
479
+ if (pendView.userKey != null) {
480
+ if (!view.keyToView)
481
+ view.keyToView = new Map;
482
+ view.keyToView.set(pendView.userKey, pendView);
483
+ }
484
+ newChildren[j] = pendView;
485
+ } else {
486
+ const oldView = oldChildren[newIdxToOldIdx[j]];
487
+ this.#patchOrReplace(view, oldChildren, newIdxToOldIdx[j], oldView, pendView);
488
+ if (!stable.has(j)) {
489
+ this.markMoved(oldView);
490
+ }
491
+ newChildren[j] = oldView;
492
+ }
493
+ }
494
+ const head = oldChildren.slice(0, i);
495
+ const tail = oldChildren.slice(oldEnd + 1);
496
+ view.children = [...head, ...newChildren, ...tail];
609
497
  }
610
- }
611
- function hasNewChildren(prev, next) {
612
- const prevLen = prev?.length ?? 0;
613
- const nextLen = next?.length ?? 0;
614
- if (prevLen === 0 && nextLen === 0)
615
- return false;
616
- if (!prev || !next)
617
- return true;
618
- if (prev.length !== next.length)
619
- return true;
620
- for (let i = 0;i < next.length; i++) {
621
- if (next[i].viewFn !== prev[i].viewFn)
622
- return true;
623
- if (next[i].userKey !== prev[i].userKey)
624
- return true;
625
- if (next[i].props !== prev[i].props)
626
- return true;
498
+ #patchOrReplace(parent, oldChildren, idx, oldView, pendView) {
499
+ if (oldView.viewFn === pendView.viewFn) {
500
+ this.nextProps(oldView, pendView.props, pendView.slot, pendView.sc);
501
+ } else {
502
+ this.dispose(oldView);
503
+ oldChildren[idx] = pendView;
504
+ this.initViewBody(pendView);
505
+ this.markDirty(pendView);
506
+ if (pendView.userKey != null) {
507
+ if (!parent.keyToView)
508
+ parent.keyToView = new Map;
509
+ parent.keyToView.set(pendView.userKey, pendView);
510
+ }
511
+ }
627
512
  }
628
- return false;
629
- }
630
-
631
- // src/internal/orchestrator.ts
632
- class Orchestrator {
633
- #currentEngine;
634
- setCurrentEngine(engine) {
635
- this.#currentEngine = engine;
513
+ dispose(view) {
514
+ const isPrimitiveWithDom = view.flags & F_PRIMITIVE && view.renderRef;
515
+ if (view.children) {
516
+ for (const child of view.children) {
517
+ if (isPrimitiveWithDom) {
518
+ this.#disposeVirtual(child);
519
+ } else {
520
+ this.dispose(child);
521
+ }
522
+ }
523
+ }
524
+ if (view.unsubscribe)
525
+ for (const unsub of view.unsubscribe)
526
+ unsub();
527
+ if (view.userKey != null)
528
+ view.parent?.keyToView?.delete(view.userKey);
529
+ this.renderer.unmount(view);
530
+ this.#dirtyQueue.delete(view);
531
+ }
532
+ #disposeVirtual(view) {
533
+ if (view.children) {
534
+ for (const child of view.children)
535
+ this.#disposeVirtual(child);
536
+ }
537
+ if (view.unsubscribe)
538
+ for (const unsub of view.unsubscribe)
539
+ unsub();
540
+ if (view.userKey != null)
541
+ view.parent?.keyToView?.delete(view.userKey);
542
+ view.renderRef = undefined;
543
+ this.#dirtyQueue.delete(view);
636
544
  }
637
- currentEngine() {
638
- return this.#currentEngine;
545
+ render() {
546
+ if (this.#rendering)
547
+ return;
548
+ orchestrator.setCurrentEngine(this);
549
+ this.#rendering = true;
550
+ try {
551
+ const cbs = [];
552
+ for (const view of this.#dirtyQueue) {
553
+ this.initViewBody(view);
554
+ const isNew = !view.renderRef;
555
+ if (!isNew)
556
+ view.body?.onUpdateBefore?.();
557
+ if (view.flags & F_DIRTY) {
558
+ this.reconcile(view);
559
+ this.renderer.render(view);
560
+ view.flags &= ~F_DIRTY;
561
+ view.flags &= ~F_MOVED;
562
+ if (isNew && view.body?.onMount) {
563
+ const b = view.body;
564
+ cbs.push(() => b.onMount());
565
+ } else if (!isNew && view.body?.onUpdateAfter) {
566
+ const b = view.body;
567
+ cbs.push(() => b.onUpdateAfter());
568
+ }
569
+ } else {
570
+ this.renderer.render(view);
571
+ view.flags &= ~F_MOVED;
572
+ }
573
+ }
574
+ this.#dirtyQueue.clear();
575
+ orchestrator.setCurrentEngine(null);
576
+ this.#rendering = false;
577
+ for (const cb of cbs)
578
+ cb();
579
+ } catch (e) {
580
+ orchestrator.setCurrentEngine(null);
581
+ this.#rendering = false;
582
+ throw e;
583
+ }
639
584
  }
640
585
  }
641
- var orchestrator = new Orchestrator;
642
586
 
643
587
  // src/public/app.ts
644
588
  function createApp(slot, renderer, options) {
645
589
  return {
646
590
  mount(props) {
647
591
  const engine = new Engine(renderer, options?.scheduler);
648
- orchestrator.setCurrentEngine(engine);
649
- new View(() => ({ render() {
650
- slot();
651
- } }), props ?? {}, null, engine, null, null);
592
+ engine.createRoot(slot, props ?? {});
652
593
  engine.render();
653
594
  return { engine };
654
595
  }
@@ -657,12 +598,7 @@ function createApp(slot, renderer, options) {
657
598
  // src/public/view.ts
658
599
  function view(body) {
659
600
  return (props, slot) => {
660
- orchestrator.currentEngine().pendingView({
661
- viewFn: body,
662
- props,
663
- slot,
664
- userKey: maybeGetUserKey(props)
665
- });
601
+ orchestrator.currentEngine().view(body, props, slot, maybeGetUserKey(props));
666
602
  };
667
603
  }
668
604
  function maybeGetUserKey(params) {
@@ -670,8 +606,6 @@ function maybeGetUserKey(params) {
670
606
  return params.key;
671
607
  }
672
608
  }
673
- // src/public/primitive.ts
674
- var $primitive = Symbol("primitive");
675
609
  // src/public/primitives/primitives.ts
676
610
  function html(tag) {
677
611
  const fn = ({
@@ -684,7 +618,10 @@ function html(tag) {
684
618
  fn[$primitive] = tag;
685
619
  return view(fn);
686
620
  }
687
- var text = html("text");
621
+ var textViewFn = Object.assign(() => ({ render() {} }), { [$primitive]: "text" });
622
+ function text(content) {
623
+ orchestrator.currentEngine().view(textViewFn, content, null, null);
624
+ }
688
625
  var div = html("div");
689
626
  var span = html("span");
690
627
  var section = html("section");
@@ -792,20 +729,85 @@ var track = html("track");
792
729
  var map = html("map");
793
730
  var area = html("area");
794
731
  // src/render/html_render.ts
732
+ function isEventProp(key) {
733
+ return key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.charCodeAt(2) >= 65 && key.charCodeAt(2) <= 90;
734
+ }
795
735
  var DOM_EVENT = {
796
- click: "click",
797
- dblclick: "dblclick",
798
- pointerDown: "pointerdown",
799
- pointerUp: "pointerup",
800
- pointerMove: "pointermove",
801
- input: "input",
802
- change: "change",
803
- keyDown: "keydown",
804
- keyUp: "keyup",
805
- focus: "focus",
806
- blur: "blur"
736
+ Click: "click",
737
+ Dblclick: "dblclick",
738
+ PointerDown: "pointerdown",
739
+ PointerUp: "pointerup",
740
+ PointerMove: "pointermove",
741
+ Input: "input",
742
+ Change: "change",
743
+ KeyDown: "keydown",
744
+ KeyUp: "keyup",
745
+ Focus: "focus",
746
+ Blur: "blur"
807
747
  };
808
- var SKIP_PROPS = new Set(["key"]);
748
+ var $EV = Symbol.for("creo.ev");
749
+ var containerState = new WeakMap;
750
+ function getState(container) {
751
+ let state = containerState.get(container);
752
+ if (!state) {
753
+ state = {
754
+ counts: new Map,
755
+ handler(e) {
756
+ const domEvent = e.type;
757
+ let dom = e.target;
758
+ while (dom && dom !== container) {
759
+ const evObj = dom[$EV];
760
+ if (evObj) {
761
+ const handler = evObj[domEvent];
762
+ if (handler) {
763
+ handler(mapEventData(domEvent, e));
764
+ if (e.cancelBubble)
765
+ return;
766
+ }
767
+ }
768
+ dom = dom.parentElement;
769
+ }
770
+ }
771
+ };
772
+ containerState.set(container, state);
773
+ }
774
+ return state;
775
+ }
776
+ function ensureDelegated(container, domEvent) {
777
+ const state = getState(container);
778
+ const count = state.counts.get(domEvent) ?? 0;
779
+ if (count === 0) {
780
+ container.addEventListener(domEvent, state.handler);
781
+ }
782
+ state.counts.set(domEvent, count + 1);
783
+ }
784
+ function removeDelegated(container, domEvent) {
785
+ const state = getState(container);
786
+ const count = state.counts.get(domEvent) ?? 0;
787
+ if (count <= 1) {
788
+ state.counts.delete(domEvent);
789
+ container.removeEventListener(domEvent, state.handler);
790
+ } else {
791
+ state.counts.set(domEvent, count - 1);
792
+ }
793
+ }
794
+ function mapEventData(domEvent, e) {
795
+ let data2;
796
+ if (domEvent === "click" || domEvent === "dblclick" || domEvent === "pointerdown" || domEvent === "pointerup" || domEvent === "pointermove") {
797
+ const pe = e;
798
+ data2 = { x: pe.clientX, y: pe.clientY };
799
+ } else if (domEvent === "input" || domEvent === "change") {
800
+ data2 = { value: e.target.value };
801
+ } else if (domEvent === "keydown" || domEvent === "keyup") {
802
+ const ke = e;
803
+ data2 = { key: ke.key, code: ke.code };
804
+ } else {
805
+ data2 = {};
806
+ }
807
+ data2.stopPropagation = () => e.stopPropagation();
808
+ data2.preventDefault = () => e.preventDefault();
809
+ return data2;
810
+ }
809
811
  var DOM_PROPERTIES = new Set([
810
812
  "value",
811
813
  "checked",
@@ -815,52 +817,62 @@ var DOM_PROPERTIES = new Set([
815
817
 
816
818
  class HtmlRender {
817
819
  container;
820
+ engine;
818
821
  constructor(container) {
819
822
  this.container = container;
820
823
  }
821
824
  render(view2) {
822
- const ref = view2.renderRef;
823
- if (!ref) {
824
- const node = this.buildDom(view2);
825
- const parent = view2.parent;
826
- if (!parent) {
827
- this.container.appendChild(node);
828
- } else {
829
- const parentNode = this.getParentDomNode(parent);
830
- if (parentNode) {
831
- parentNode.insertBefore(node, this.fastInsertionPoint(parent, view2));
825
+ if (!view2.renderRef) {
826
+ if (view2.flags & F_PRIMITIVE) {
827
+ const parentNode = this.findParentDom(view2);
828
+ const refNode = this.findInsertionPoint(view2);
829
+ const tag = view2.viewFn[$primitive];
830
+ if (tag === "text") {
831
+ const textNode = document.createTextNode(String(view2.props));
832
+ view2.renderRef = { element: textNode, prevProps: null };
833
+ parentNode.insertBefore(textNode, refNode);
834
+ } else {
835
+ const element = document.createElement(tag);
836
+ const props = view2.props;
837
+ const domRef = { element, prevProps: null };
838
+ view2.renderRef = domRef;
839
+ this.setAttributes(element, props);
840
+ if (view2.children?.length === 1) {
841
+ const child = view2.children[0];
842
+ if (child.flags & F_PRIMITIVE && child.viewFn[$primitive] === "text") {
843
+ element.textContent = String(child.props);
844
+ child.renderRef = { element, prevProps: null };
845
+ child.flags |= F_TEXT_CONTENT;
846
+ }
847
+ }
848
+ parentNode.insertBefore(element, refNode);
832
849
  }
833
- }
834
- const newRef = view2.renderRef;
835
- if (newRef?.kind === "primitive" && newRef.element instanceof HTMLElement && view2.props?.autofocus) {
836
- newRef.element.focus();
850
+ } else {
851
+ view2.renderRef = true;
837
852
  }
838
853
  return;
839
854
  }
840
- if (view2.moved) {
841
- view2.moved = false;
842
- if (view2.parent) {
843
- const expectedStart = this.fastInsertionPoint(view2.parent, view2);
844
- const firstDom = this.getFirstDomNode(view2);
845
- if (firstDom && firstDom !== expectedStart) {
846
- const parentNode = this.getParentDomNode(view2.parent);
847
- if (parentNode) {
848
- if (ref.kind === "primitive") {
849
- parentNode.insertBefore(ref.element, expectedStart);
850
- } else {
851
- if (view2.virtualDom) {
852
- for (const child of view2.virtualDom) {
853
- this.moveDomNodes(child, parentNode, expectedStart);
854
- }
855
- }
856
- parentNode.insertBefore(ref.endComment, expectedStart);
857
- }
858
- }
859
- }
855
+ if (view2.flags & F_MOVED) {
856
+ const parentNode = this.findParentDom(view2);
857
+ const refNode = this.findInsertionPoint(view2);
858
+ if (view2.flags & F_PRIMITIVE) {
859
+ const ref2 = view2.renderRef;
860
+ parentNode.insertBefore(ref2.element, refNode);
861
+ } else {
862
+ this.moveDomNodes(view2, parentNode, refNode);
860
863
  }
861
864
  }
862
- if (ref.kind !== "primitive")
865
+ if (!(view2.flags & F_PRIMITIVE))
866
+ return;
867
+ const ref = view2.renderRef;
868
+ if (view2.flags & F_TEXT_CONTENT) {
869
+ const parentEl = ref.element;
870
+ const nextText = String(view2.props);
871
+ if (parentEl.textContent !== nextText) {
872
+ parentEl.textContent = nextText;
873
+ }
863
874
  return;
875
+ }
864
876
  if (ref.element instanceof Text) {
865
877
  const nextText = String(view2.props);
866
878
  if (ref.element.textContent !== nextText) {
@@ -870,74 +882,70 @@ class HtmlRender {
870
882
  }
871
883
  const nextProps = view2.props;
872
884
  if (!ref.prevProps) {
873
- this.setAttributes(ref, ref.element, nextProps);
874
- } else {
875
- this.diffAttributes(ref, ref.element, ref.prevProps, nextProps);
885
+ this.setAttributes(ref.element, nextProps);
886
+ } else if (ref.prevProps !== nextProps) {
887
+ this.diffAttributes(ref.element, ref.prevProps, nextProps);
876
888
  }
877
- ref.prevProps = { ...nextProps };
889
+ ref.prevProps = nextProps;
878
890
  }
879
891
  unmount(view2) {
880
- this.removeDomNodes(view2);
892
+ if (view2.flags & F_PRIMITIVE) {
893
+ this.removeDomNodes(view2);
894
+ }
881
895
  view2.renderRef = undefined;
882
896
  }
883
- buildDom(view2) {
884
- const tag = view2.viewFn[$primitive];
885
- if (tag != null) {
886
- if (tag === "text") {
887
- const textNode = document.createTextNode(String(view2.props));
888
- const ref2 = {
889
- kind: "primitive",
890
- element: textNode,
891
- prevProps: null,
892
- listeners: null
893
- };
894
- view2.renderRef = ref2;
895
- return textNode;
897
+ findParentDom(view2) {
898
+ let parent = view2.parent;
899
+ while (parent) {
900
+ if (parent.flags & F_PRIMITIVE) {
901
+ const ref = parent.renderRef;
902
+ if (ref && ref.element instanceof HTMLElement)
903
+ return ref.element;
896
904
  }
897
- const element = document.createElement(tag);
898
- const props = view2.props;
899
- const ref = {
900
- kind: "primitive",
901
- element,
902
- prevProps: null,
903
- listeners: null
904
- };
905
- view2.renderRef = ref;
906
- this.setAttributes(ref, element, props);
907
- return element;
905
+ parent = parent.parent;
908
906
  }
909
- const endComment = document.createComment("");
910
- view2.renderRef = {
911
- kind: "composite",
912
- endComment
913
- };
914
- return endComment;
907
+ return this.container;
915
908
  }
916
- isEventProp(key, value) {
917
- return key.length > 2 && key[0] === "o" && key[1] === "n" && key[2] >= "A" && key[2] <= "Z" && typeof value === "function";
909
+ findInsertionPoint(view2) {
910
+ const parent = view2.parent;
911
+ if (!parent?.children)
912
+ return null;
913
+ const children = parent.children;
914
+ if (children[children.length - 1] === view2) {
915
+ return this.#parentEndAnchor(parent);
916
+ }
917
+ const idx = children.indexOf(view2);
918
+ for (let i2 = idx + 1;i2 < children.length; i2++) {
919
+ const dom = this.getFirstDomNode(children[i2]);
920
+ if (dom)
921
+ return dom;
922
+ }
923
+ return this.#parentEndAnchor(parent);
918
924
  }
919
- eventPropToCreoName(prop) {
920
- return prop[2].toLowerCase() + prop.slice(3);
925
+ #parentEndAnchor(parent) {
926
+ if (parent.flags & F_PRIMITIVE)
927
+ return null;
928
+ return this.findInsertionPoint(parent);
921
929
  }
922
- setAttributes(ref, element, props) {
930
+ setAttributes(element, props) {
923
931
  for (const key in props) {
924
932
  const value = props[key];
925
- if (SKIP_PROPS.has(key) || value == null)
933
+ if (key === "key" || value == null)
926
934
  continue;
927
- if (this.isEventProp(key, value)) {
928
- this.bindEvent(ref, element, key, value);
935
+ if (isEventProp(key)) {
936
+ this.bindEvent(element, key, value);
929
937
  continue;
930
938
  }
931
939
  this.setAttribute(element, key, value);
932
940
  }
933
941
  }
934
- diffAttributes(ref, element, prev, next) {
935
- for (const key of Object.keys(prev)) {
936
- if (SKIP_PROPS.has(key))
942
+ diffAttributes(element, prev, next) {
943
+ for (const key in prev) {
944
+ if (key === "key")
937
945
  continue;
938
946
  if (!(key in next) || next[key] == null) {
939
- if (this.isEventProp(key, prev[key])) {
940
- this.unbindEvent(ref, element, key);
947
+ if (isEventProp(key)) {
948
+ this.unbindEvent(element, key);
941
949
  } else {
942
950
  this.removeAttribute(element, key);
943
951
  }
@@ -945,54 +953,39 @@ class HtmlRender {
945
953
  }
946
954
  for (const key in next) {
947
955
  const value = next[key];
948
- if (SKIP_PROPS.has(key) || value == null)
956
+ if (key === "key" || value == null)
949
957
  continue;
950
958
  if (prev[key] === value)
951
959
  continue;
952
- if (this.isEventProp(key, value)) {
953
- this.unbindEvent(ref, element, key);
954
- this.bindEvent(ref, element, key, value);
960
+ if (isEventProp(key)) {
961
+ const creoName = key.slice(2);
962
+ const domEvent = DOM_EVENT[creoName] ?? creoName.toLowerCase();
963
+ const evObj = element[$EV];
964
+ if (evObj) {
965
+ evObj[domEvent] = value;
966
+ } else {
967
+ this.bindEvent(element, key, value);
968
+ }
955
969
  } else {
956
970
  this.setAttribute(element, key, value);
957
971
  }
958
972
  }
959
973
  }
960
- bindEvent(ref, element, prop, handler) {
961
- const creoEvent = this.eventPropToCreoName(prop);
962
- const domEvent = DOM_EVENT[creoEvent] ?? creoEvent.toLowerCase();
963
- const wrapped = (e) => {
964
- const data2 = this.mapEventData(creoEvent, e);
965
- data2.stopPropagation = () => e.stopPropagation();
966
- data2.preventDefault = () => e.preventDefault();
967
- handler(data2);
968
- };
969
- if (!ref.listeners)
970
- ref.listeners = new Map;
971
- ref.listeners.set(prop, wrapped);
972
- element.addEventListener(domEvent, wrapped);
973
- }
974
- unbindEvent(ref, element, prop) {
975
- const wrapped = ref.listeners?.get(prop);
976
- if (!wrapped)
977
- return;
978
- const creoEvent = this.eventPropToCreoName(prop);
979
- const domEvent = DOM_EVENT[creoEvent] ?? creoEvent.toLowerCase();
980
- element.removeEventListener(domEvent, wrapped);
981
- ref.listeners.delete(prop);
982
- }
983
- mapEventData(creoEvent, e) {
984
- if (creoEvent === "click" || creoEvent === "dblclick" || creoEvent.startsWith("pointer")) {
985
- const pe = e;
986
- return { x: pe.clientX, y: pe.clientY };
987
- }
988
- if (creoEvent === "input" || creoEvent === "change") {
989
- return { value: e.target.value };
990
- }
991
- if (creoEvent.startsWith("key")) {
992
- const ke = e;
993
- return { key: ke.key, code: ke.code };
974
+ bindEvent(element, prop, handler) {
975
+ const creoName = prop.slice(2);
976
+ const domEvent = DOM_EVENT[creoName] ?? creoName.toLowerCase();
977
+ const evObj = element[$EV] ?? (element[$EV] = {});
978
+ evObj[domEvent] = handler;
979
+ ensureDelegated(this.container, domEvent);
980
+ }
981
+ unbindEvent(element, prop) {
982
+ const creoName = prop.slice(2);
983
+ const domEvent = DOM_EVENT[creoName] ?? creoName.toLowerCase();
984
+ const evObj = element[$EV];
985
+ if (evObj) {
986
+ delete evObj[domEvent];
994
987
  }
995
- return {};
988
+ removeDelegated(this.container, domEvent);
996
989
  }
997
990
  setAttribute(element, key, value) {
998
991
  if (key === "class") {
@@ -1002,11 +995,10 @@ class HtmlRender {
1002
995
  } else if (DOM_PROPERTIES.has(key)) {
1003
996
  element[key] = value;
1004
997
  } else if (typeof value === "boolean") {
1005
- if (value) {
998
+ if (value)
1006
999
  element.setAttribute(key, "");
1007
- } else {
1000
+ else
1008
1001
  element.removeAttribute(key);
1009
- }
1010
1002
  } else {
1011
1003
  element.setAttribute(key, String(value));
1012
1004
  }
@@ -1022,94 +1014,52 @@ class HtmlRender {
1022
1014
  element.removeAttribute(key);
1023
1015
  }
1024
1016
  }
1025
- fastInsertionPoint(parent, view2) {
1026
- const vdom = parent.virtualDom;
1027
- if (vdom && vdom.length > 0) {
1028
- const node = vdom.getNode(view2);
1029
- if (node) {
1030
- if (node.isLast() || parent.quickRerender) {
1031
- const ref2 = parent.renderRef;
1032
- if (ref2?.kind === "composite") {
1033
- return ref2.endComment;
1034
- } else {
1035
- return null;
1036
- }
1037
- }
1038
- const prev = node.getPrev();
1039
- if (prev) {
1040
- const prevRef = prev.v.renderRef;
1041
- if (prevRef) {
1042
- return (prevRef.kind === "composite" ? prevRef.endComment : prevRef.element).nextSibling;
1043
- }
1044
- let cur = prev.getPrev();
1045
- while (cur) {
1046
- const curRef = cur.v.renderRef;
1047
- if (curRef) {
1048
- return (curRef.kind === "composite" ? curRef.endComment : curRef.element).nextSibling;
1049
- }
1050
- cur = cur.getPrev();
1051
- }
1052
- }
1053
- }
1054
- }
1055
- const ref = parent.renderRef;
1056
- if (!ref)
1057
- return null;
1058
- return ref.kind === "composite" ? ref.endComment : ref.element.firstChild;
1059
- }
1060
- getParentDomNode(parent) {
1061
- const ref = parent.renderRef;
1062
- if (!ref)
1017
+ getFirstDomNode(view2) {
1018
+ if (!view2.renderRef)
1063
1019
  return null;
1064
- if (ref.kind === "primitive") {
1065
- return ref.element;
1020
+ if (view2.flags & F_PRIMITIVE)
1021
+ return view2.renderRef.element;
1022
+ if (view2.children) {
1023
+ for (const child of view2.children) {
1024
+ const dom = this.getFirstDomNode(child);
1025
+ if (dom)
1026
+ return dom;
1027
+ }
1066
1028
  }
1067
- return ref.endComment.parentNode;
1029
+ return null;
1068
1030
  }
1069
1031
  moveDomNodes(view2, parentNode, insertBefore) {
1070
- const ref = view2.renderRef;
1071
- if (!ref)
1032
+ if (!view2.renderRef)
1072
1033
  return;
1073
- if (ref.kind === "primitive") {
1074
- parentNode.insertBefore(ref.element, insertBefore);
1075
- } else {
1076
- if (view2.virtualDom) {
1077
- for (const child of view2.virtualDom) {
1078
- this.moveDomNodes(child, parentNode, insertBefore);
1079
- }
1034
+ if (view2.flags & F_PRIMITIVE) {
1035
+ parentNode.insertBefore(view2.renderRef.element, insertBefore);
1036
+ } else if (view2.children) {
1037
+ for (const child of view2.children) {
1038
+ this.moveDomNodes(child, parentNode, insertBefore);
1080
1039
  }
1081
- parentNode.insertBefore(ref.endComment, insertBefore);
1082
1040
  }
1083
1041
  }
1084
- getFirstDomNode(view2) {
1085
- const ref = view2.renderRef;
1086
- if (!ref)
1087
- return null;
1088
- if (ref.kind === "primitive")
1089
- return ref.element;
1090
- if (view2.virtualDom) {
1091
- for (const child of view2.virtualDom) {
1092
- const node = this.getFirstDomNode(child);
1093
- if (node)
1094
- return node;
1095
- }
1096
- }
1097
- return ref.endComment;
1098
- }
1099
1042
  removeDomNodes(view2) {
1100
1043
  const ref = view2.renderRef;
1101
- if (!ref)
1102
- return;
1103
- if (ref.kind === "primitive") {
1104
- ref.element.parentNode?.removeChild(ref.element);
1044
+ if (!ref || !(view2.flags & F_PRIMITIVE))
1105
1045
  return;
1046
+ const evObj = ref.element[$EV];
1047
+ if (evObj) {
1048
+ for (const domEvent in evObj) {
1049
+ removeDelegated(this.container, domEvent);
1050
+ }
1051
+ delete ref.element[$EV];
1106
1052
  }
1107
- ref.endComment.parentNode?.removeChild(ref.endComment);
1053
+ ref.element.parentNode?.removeChild(ref.element);
1108
1054
  }
1109
1055
  }
1110
1056
  // src/render/json_render.ts
1111
1057
  class JsonRender {
1112
1058
  root;
1059
+ engine;
1060
+ constructor() {
1061
+ this.root = null;
1062
+ }
1113
1063
  render(view2) {
1114
1064
  const existing = view2.renderRef;
1115
1065
  if (!existing) {
@@ -1118,23 +1068,25 @@ class JsonRender {
1118
1068
  this.root = node;
1119
1069
  return;
1120
1070
  }
1121
- const parentNode2 = view2.parent.renderRef;
1122
- if (parentNode2) {
1123
- parentNode2.children.push(node);
1071
+ const parentNode = view2.parent.renderRef;
1072
+ if (parentNode) {
1073
+ const idx = view2.parent.children ? view2.parent.children.indexOf(view2) : -1;
1074
+ if (idx >= 0 && idx < parentNode.children.length) {
1075
+ parentNode.children.splice(idx, 0, node);
1076
+ } else {
1077
+ parentNode.children.push(node);
1078
+ }
1124
1079
  }
1125
1080
  return;
1126
1081
  }
1127
- const parentNode = view2.parent?.renderRef;
1128
- if (parentNode) {
1129
- const oldIdx = parentNode.children.indexOf(existing);
1130
- if (oldIdx !== -1) {
1131
- const nextSibling = this.getNextSibling(view2);
1132
- const nextNode = nextSibling?.renderRef;
1133
- const expectedIdx = nextNode ? parentNode.children.indexOf(nextNode) : parentNode.children.length;
1134
- if (oldIdx !== expectedIdx && oldIdx !== expectedIdx - 1) {
1082
+ if (view2.parent) {
1083
+ const parentNode = view2.parent.renderRef;
1084
+ if (parentNode) {
1085
+ const oldIdx = parentNode.children.indexOf(existing);
1086
+ const targetIdx = view2.parent.children ? view2.parent.children.indexOf(view2) : -1;
1087
+ if (oldIdx !== -1 && targetIdx !== -1 && oldIdx !== targetIdx) {
1135
1088
  parentNode.children.splice(oldIdx, 1);
1136
- const insertIdx = nextNode ? parentNode.children.indexOf(nextNode) : parentNode.children.length;
1137
- parentNode.children.splice(insertIdx, 0, existing);
1089
+ parentNode.children.splice(Math.min(targetIdx, parentNode.children.length), 0, existing);
1138
1090
  }
1139
1091
  }
1140
1092
  }
@@ -1143,16 +1095,15 @@ class JsonRender {
1143
1095
  }
1144
1096
  unmount(view2) {
1145
1097
  const childNode = view2.renderRef;
1146
- const parentNode = view2.parent?.renderRef;
1147
- if (parentNode && childNode) {
1098
+ if (!childNode || !view2.parent)
1099
+ return;
1100
+ const parentNode = view2.parent.renderRef;
1101
+ if (parentNode) {
1148
1102
  const idx = parentNode.children.indexOf(childNode);
1149
1103
  if (idx !== -1)
1150
1104
  parentNode.children.splice(idx, 1);
1151
1105
  }
1152
1106
  }
1153
- getNextSibling(view2) {
1154
- return view2.parent?.virtualDom?.getNode(view2)?.getNext()?.v;
1155
- }
1156
1107
  buildNode(view2) {
1157
1108
  const tag = view2.viewFn[$primitive];
1158
1109
  const props = tag === "text" ? { content: view2.props } : { ...view2.props };
@@ -1180,52 +1131,84 @@ var VOID_TAGS = new Set([
1180
1131
  "col",
1181
1132
  "wbr"
1182
1133
  ]);
1134
+ var DOM_PROPERTIES2 = new Set(["value", "checked", "selected", "indeterminate"]);
1135
+ function isEventProp2(key) {
1136
+ return key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && key.charCodeAt(2) >= 65 && key.charCodeAt(2) <= 90;
1137
+ }
1138
+ function escapeHtml(str) {
1139
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1140
+ }
1141
+ function escapeAttr(str) {
1142
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1143
+ }
1183
1144
 
1184
- class StringRender {
1185
- rootView;
1145
+ class HtmlStringRender {
1146
+ root = null;
1147
+ engine;
1186
1148
  render(view2) {
1187
1149
  if (!view2.parent) {
1188
- this.rootView = view2;
1150
+ this.root = view2;
1189
1151
  }
1190
1152
  }
1191
1153
  unmount(_view) {}
1192
1154
  renderToString() {
1193
- if (!this.rootView)
1155
+ if (!this.root)
1194
1156
  return "";
1195
- return this.buildString(this.rootView);
1157
+ return this.buildString(this.root);
1196
1158
  }
1197
- buildString(view2) {
1198
- const tag = view2.viewFn[$primitive];
1159
+ buildString(rec) {
1160
+ const tag = rec.viewFn[$primitive];
1199
1161
  if (tag != null) {
1200
1162
  if (tag === "text") {
1201
- return `${view2.props}`;
1163
+ return escapeHtml(String(rec.props));
1202
1164
  }
1165
+ const attrs = this.buildAttrs(rec.props);
1203
1166
  if (VOID_TAGS.has(tag)) {
1204
- return this.buildVoidTag(tag, view2);
1167
+ return `<${tag}${attrs}>`;
1205
1168
  }
1206
- return `<${tag}>${this.buildChildren(view2)}</${tag}>`;
1169
+ return `<${tag}${attrs}>${this.buildChildren(rec)}</${tag}>`;
1207
1170
  }
1208
- return this.buildChildren(view2);
1209
- }
1210
- buildVoidTag(tag, view2) {
1211
- const props = view2.props;
1212
- let attrs = "";
1213
- if (props.src)
1214
- attrs += ` src="${props.src}"`;
1215
- if (props.alt)
1216
- attrs += ` alt="${props.alt}"`;
1217
- return `<${tag}${attrs} />`;
1218
- }
1219
- buildChildren(view2) {
1171
+ return this.buildChildren(rec);
1172
+ }
1173
+ buildAttrs(props) {
1220
1174
  let result = "";
1221
- if (view2.virtualDom) {
1222
- for (const child of view2.virtualDom) {
1223
- result += this.buildString(child);
1175
+ for (const key in props) {
1176
+ const value = props[key];
1177
+ if (key === "key" || value == null)
1178
+ continue;
1179
+ if (isEventProp2(key))
1180
+ continue;
1181
+ if (DOM_PROPERTIES2.has(key))
1182
+ continue;
1183
+ if (key === "style") {
1184
+ let css = String(value);
1185
+ css = css.trim();
1186
+ if (css && !css.endsWith(";"))
1187
+ css += ";";
1188
+ result += ` style="${escapeAttr(css)}"`;
1189
+ continue;
1190
+ }
1191
+ if (typeof value === "boolean") {
1192
+ if (value)
1193
+ result += ` ${key}=""`;
1194
+ } else {
1195
+ const attrName = key === "class" ? "class" : key;
1196
+ result += ` ${attrName}="${escapeAttr(String(value))}"`;
1224
1197
  }
1225
1198
  }
1226
1199
  return result;
1227
1200
  }
1201
+ buildChildren(rec) {
1202
+ if (!rec.children)
1203
+ return "";
1204
+ let result = "";
1205
+ for (const child of rec.children) {
1206
+ result += this.buildString(child);
1207
+ }
1208
+ return result;
1209
+ }
1228
1210
  }
1211
+ var StringRender = HtmlStringRender;
1229
1212
  // src/functional/maybe.ts
1230
1213
  function just(maybe, errorMessage) {
1231
1214
  if (maybe == null) {
@@ -1365,9 +1348,10 @@ export {
1365
1348
  Store,
1366
1349
  State,
1367
1350
  JsonRender,
1351
+ HtmlStringRender,
1368
1352
  HtmlRender,
1369
1353
  Engine,
1370
1354
  $primitive
1371
1355
  };
1372
1356
 
1373
- //# debugId=3A0F56854FC011F464756E2164756E21
1357
+ //# debugId=3138F663141CF77564756E2164756E21