creo 0.1.0 → 0.2.1

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