@pyreon/runtime-dom 0.2.0 → 0.3.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.
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"955dce87-1","name":"hydration-debug.ts"},{"uid":"955dce87-3","name":"devtools.ts"},{"uid":"955dce87-5","name":"nodes.ts"},{"uid":"955dce87-7","name":"props.ts"},{"uid":"955dce87-9","name":"mount.ts"},{"uid":"955dce87-11","name":"hydrate.ts"},{"uid":"955dce87-13","name":"keep-alive.ts"},{"uid":"955dce87-15","name":"template.ts"},{"uid":"955dce87-17","name":"transition.ts"},{"uid":"955dce87-19","name":"transition-group.ts"},{"uid":"955dce87-21","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"955dce87-1":{"renderedLength":988,"gzipLength":509,"brotliLength":0,"metaUid":"955dce87-0"},"955dce87-3":{"renderedLength":6803,"gzipLength":2095,"brotliLength":0,"metaUid":"955dce87-2"},"955dce87-5":{"renderedLength":16149,"gzipLength":4315,"brotliLength":0,"metaUid":"955dce87-4"},"955dce87-7":{"renderedLength":5595,"gzipLength":2176,"brotliLength":0,"metaUid":"955dce87-6"},"955dce87-9":{"renderedLength":7932,"gzipLength":2325,"brotliLength":0,"metaUid":"955dce87-8"},"955dce87-11":{"renderedLength":7206,"gzipLength":2169,"brotliLength":0,"metaUid":"955dce87-10"},"955dce87-13":{"renderedLength":1447,"gzipLength":692,"brotliLength":0,"metaUid":"955dce87-12"},"955dce87-15":{"renderedLength":2691,"gzipLength":1210,"brotliLength":0,"metaUid":"955dce87-14"},"955dce87-17":{"renderedLength":4007,"gzipLength":1316,"brotliLength":0,"metaUid":"955dce87-16"},"955dce87-19":{"renderedLength":6362,"gzipLength":1937,"brotliLength":0,"metaUid":"955dce87-18"},"955dce87-21":{"renderedLength":782,"gzipLength":477,"brotliLength":0,"metaUid":"955dce87-20"}},"nodeMetas":{"955dce87-0":{"id":"/src/hydration-debug.ts","moduleParts":{"index.js":"955dce87-1"},"imported":[],"importedBy":[{"uid":"955dce87-20"},{"uid":"955dce87-10"}]},"955dce87-2":{"id":"/src/devtools.ts","moduleParts":{"index.js":"955dce87-3"},"imported":[],"importedBy":[{"uid":"955dce87-20"},{"uid":"955dce87-8"}]},"955dce87-4":{"id":"/src/nodes.ts","moduleParts":{"index.js":"955dce87-5"},"imported":[{"uid":"955dce87-23"}],"importedBy":[{"uid":"955dce87-10"},{"uid":"955dce87-8"}]},"955dce87-6":{"id":"/src/props.ts","moduleParts":{"index.js":"955dce87-7"},"imported":[{"uid":"955dce87-23"}],"importedBy":[{"uid":"955dce87-20"},{"uid":"955dce87-10"},{"uid":"955dce87-8"}]},"955dce87-8":{"id":"/src/mount.ts","moduleParts":{"index.js":"955dce87-9"},"imported":[{"uid":"955dce87-22"},{"uid":"955dce87-23"},{"uid":"955dce87-2"},{"uid":"955dce87-4"},{"uid":"955dce87-6"}],"importedBy":[{"uid":"955dce87-20"},{"uid":"955dce87-10"},{"uid":"955dce87-12"},{"uid":"955dce87-18"}]},"955dce87-10":{"id":"/src/hydrate.ts","moduleParts":{"index.js":"955dce87-11"},"imported":[{"uid":"955dce87-22"},{"uid":"955dce87-23"},{"uid":"955dce87-0"},{"uid":"955dce87-8"},{"uid":"955dce87-4"},{"uid":"955dce87-6"}],"importedBy":[{"uid":"955dce87-20"}]},"955dce87-12":{"id":"/src/keep-alive.ts","moduleParts":{"index.js":"955dce87-13"},"imported":[{"uid":"955dce87-22"},{"uid":"955dce87-23"},{"uid":"955dce87-8"}],"importedBy":[{"uid":"955dce87-20"}]},"955dce87-14":{"id":"/src/template.ts","moduleParts":{"index.js":"955dce87-15"},"imported":[],"importedBy":[{"uid":"955dce87-20"}]},"955dce87-16":{"id":"/src/transition.ts","moduleParts":{"index.js":"955dce87-17"},"imported":[{"uid":"955dce87-22"},{"uid":"955dce87-23"}],"importedBy":[{"uid":"955dce87-20"}]},"955dce87-18":{"id":"/src/transition-group.ts","moduleParts":{"index.js":"955dce87-19"},"imported":[{"uid":"955dce87-22"},{"uid":"955dce87-23"},{"uid":"955dce87-8"}],"importedBy":[{"uid":"955dce87-20"}]},"955dce87-20":{"id":"/src/index.ts","moduleParts":{"index.js":"955dce87-21"},"imported":[{"uid":"955dce87-10"},{"uid":"955dce87-0"},{"uid":"955dce87-12"},{"uid":"955dce87-8"},{"uid":"955dce87-6"},{"uid":"955dce87-14"},{"uid":"955dce87-16"},{"uid":"955dce87-18"},{"uid":"955dce87-2"}],"importedBy":[],"isEntry":true},"955dce87-22":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"955dce87-10"},{"uid":"955dce87-12"},{"uid":"955dce87-8"},{"uid":"955dce87-16"},{"uid":"955dce87-18"}]},"955dce87-23":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"955dce87-10"},{"uid":"955dce87-12"},{"uid":"955dce87-8"},{"uid":"955dce87-6"},{"uid":"955dce87-16"},{"uid":"955dce87-18"},{"uid":"955dce87-4"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"3dbea446-1","name":"delegate.ts"},{"uid":"3dbea446-3","name":"hydration-debug.ts"},{"uid":"3dbea446-5","name":"devtools.ts"},{"uid":"3dbea446-7","name":"nodes.ts"},{"uid":"3dbea446-9","name":"props.ts"},{"uid":"3dbea446-11","name":"mount.ts"},{"uid":"3dbea446-13","name":"hydrate.ts"},{"uid":"3dbea446-15","name":"keep-alive.ts"},{"uid":"3dbea446-17","name":"template.ts"},{"uid":"3dbea446-19","name":"transition.ts"},{"uid":"3dbea446-21","name":"transition-group.ts"},{"uid":"3dbea446-23","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"3dbea446-1":{"renderedLength":1968,"gzipLength":967,"brotliLength":0,"metaUid":"3dbea446-0"},"3dbea446-3":{"renderedLength":988,"gzipLength":509,"brotliLength":0,"metaUid":"3dbea446-2"},"3dbea446-5":{"renderedLength":6803,"gzipLength":2095,"brotliLength":0,"metaUid":"3dbea446-4"},"3dbea446-7":{"renderedLength":16149,"gzipLength":4315,"brotliLength":0,"metaUid":"3dbea446-6"},"3dbea446-9":{"renderedLength":5779,"gzipLength":2234,"brotliLength":0,"metaUid":"3dbea446-8"},"3dbea446-11":{"renderedLength":7932,"gzipLength":2325,"brotliLength":0,"metaUid":"3dbea446-10"},"3dbea446-13":{"renderedLength":7632,"gzipLength":2311,"brotliLength":0,"metaUid":"3dbea446-12"},"3dbea446-15":{"renderedLength":1447,"gzipLength":692,"brotliLength":0,"metaUid":"3dbea446-14"},"3dbea446-17":{"renderedLength":4250,"gzipLength":1760,"brotliLength":0,"metaUid":"3dbea446-16"},"3dbea446-19":{"renderedLength":4007,"gzipLength":1316,"brotliLength":0,"metaUid":"3dbea446-18"},"3dbea446-21":{"renderedLength":6362,"gzipLength":1937,"brotliLength":0,"metaUid":"3dbea446-20"},"3dbea446-23":{"renderedLength":811,"gzipLength":491,"brotliLength":0,"metaUid":"3dbea446-22"}},"nodeMetas":{"3dbea446-0":{"id":"/src/delegate.ts","moduleParts":{"index.js":"3dbea446-1"},"imported":[{"uid":"3dbea446-24"}],"importedBy":[{"uid":"3dbea446-22"},{"uid":"3dbea446-12"},{"uid":"3dbea446-8"}]},"3dbea446-2":{"id":"/src/hydration-debug.ts","moduleParts":{"index.js":"3dbea446-3"},"imported":[],"importedBy":[{"uid":"3dbea446-22"},{"uid":"3dbea446-12"}]},"3dbea446-4":{"id":"/src/devtools.ts","moduleParts":{"index.js":"3dbea446-5"},"imported":[],"importedBy":[{"uid":"3dbea446-22"},{"uid":"3dbea446-10"}]},"3dbea446-6":{"id":"/src/nodes.ts","moduleParts":{"index.js":"3dbea446-7"},"imported":[{"uid":"3dbea446-24"}],"importedBy":[{"uid":"3dbea446-12"},{"uid":"3dbea446-10"}]},"3dbea446-8":{"id":"/src/props.ts","moduleParts":{"index.js":"3dbea446-9"},"imported":[{"uid":"3dbea446-24"},{"uid":"3dbea446-0"}],"importedBy":[{"uid":"3dbea446-22"},{"uid":"3dbea446-12"},{"uid":"3dbea446-10"}]},"3dbea446-10":{"id":"/src/mount.ts","moduleParts":{"index.js":"3dbea446-11"},"imported":[{"uid":"3dbea446-25"},{"uid":"3dbea446-24"},{"uid":"3dbea446-4"},{"uid":"3dbea446-6"},{"uid":"3dbea446-8"}],"importedBy":[{"uid":"3dbea446-22"},{"uid":"3dbea446-12"},{"uid":"3dbea446-14"},{"uid":"3dbea446-20"}]},"3dbea446-12":{"id":"/src/hydrate.ts","moduleParts":{"index.js":"3dbea446-13"},"imported":[{"uid":"3dbea446-25"},{"uid":"3dbea446-24"},{"uid":"3dbea446-0"},{"uid":"3dbea446-2"},{"uid":"3dbea446-10"},{"uid":"3dbea446-6"},{"uid":"3dbea446-8"}],"importedBy":[{"uid":"3dbea446-22"}]},"3dbea446-14":{"id":"/src/keep-alive.ts","moduleParts":{"index.js":"3dbea446-15"},"imported":[{"uid":"3dbea446-25"},{"uid":"3dbea446-24"},{"uid":"3dbea446-10"}],"importedBy":[{"uid":"3dbea446-22"}]},"3dbea446-16":{"id":"/src/template.ts","moduleParts":{"index.js":"3dbea446-17"},"imported":[],"importedBy":[{"uid":"3dbea446-22"}]},"3dbea446-18":{"id":"/src/transition.ts","moduleParts":{"index.js":"3dbea446-19"},"imported":[{"uid":"3dbea446-25"},{"uid":"3dbea446-24"}],"importedBy":[{"uid":"3dbea446-22"}]},"3dbea446-20":{"id":"/src/transition-group.ts","moduleParts":{"index.js":"3dbea446-21"},"imported":[{"uid":"3dbea446-25"},{"uid":"3dbea446-24"},{"uid":"3dbea446-10"}],"importedBy":[{"uid":"3dbea446-22"}]},"3dbea446-22":{"id":"/src/index.ts","moduleParts":{"index.js":"3dbea446-23"},"imported":[{"uid":"3dbea446-0"},{"uid":"3dbea446-12"},{"uid":"3dbea446-2"},{"uid":"3dbea446-14"},{"uid":"3dbea446-10"},{"uid":"3dbea446-8"},{"uid":"3dbea446-16"},{"uid":"3dbea446-18"},{"uid":"3dbea446-20"},{"uid":"3dbea446-4"}],"importedBy":[],"isEntry":true},"3dbea446-24":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"3dbea446-0"},{"uid":"3dbea446-12"},{"uid":"3dbea446-14"},{"uid":"3dbea446-10"},{"uid":"3dbea446-8"},{"uid":"3dbea446-18"},{"uid":"3dbea446-20"},{"uid":"3dbea446-6"}]},"3dbea446-25":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"3dbea446-12"},{"uid":"3dbea446-14"},{"uid":"3dbea446-10"},{"uid":"3dbea446-18"},{"uid":"3dbea446-20"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/index.js CHANGED
@@ -1,6 +1,81 @@
1
- import { EMPTY_PROPS, ForSymbol, Fragment, PortalSymbol, createRef, dispatchToErrorBoundary, h, onMount, onUnmount, propagateError, reportError, runWithHooks } from "@pyreon/core";
2
1
  import { batch, effect, effectScope, renderEffect, runUntracked, setCurrentScope, signal } from "@pyreon/reactivity";
2
+ import { EMPTY_PROPS, ForSymbol, Fragment, PortalSymbol, createRef, dispatchToErrorBoundary, h, onMount, onUnmount, propagateError, reportError, runWithHooks } from "@pyreon/core";
3
3
 
4
+ //#region src/delegate.ts
5
+ /**
6
+ * Event delegation — single listener per event type on the mount container.
7
+ *
8
+ * Instead of calling addEventListener on every element, the compiler emits
9
+ * `el.__click = handler` (expando property). A single delegated listener on the
10
+ * container walks event.target up the DOM tree, checking for expandos.
11
+ *
12
+ * Benefits:
13
+ * - Saves ~2000 addEventListener calls for 1000 rows with 2 handlers each
14
+ * - Reduces memory per row (no per-element listener closure)
15
+ * - Faster initial mount (~0.4-0.8ms savings on 1000-row benchmarks)
16
+ */
17
+ /**
18
+ * Events that are delegated (common bubbling events).
19
+ * Non-bubbling events (focus, blur, mouseenter, mouseleave, load, error, scroll)
20
+ * are NOT delegated — they must use addEventListener.
21
+ */
22
+ const DELEGATED_EVENTS = new Set([
23
+ "click",
24
+ "dblclick",
25
+ "contextmenu",
26
+ "focusin",
27
+ "focusout",
28
+ "input",
29
+ "change",
30
+ "keydown",
31
+ "keyup",
32
+ "mousedown",
33
+ "mouseup",
34
+ "mousemove",
35
+ "mouseover",
36
+ "mouseout",
37
+ "pointerdown",
38
+ "pointerup",
39
+ "pointermove",
40
+ "pointerover",
41
+ "pointerout",
42
+ "touchstart",
43
+ "touchend",
44
+ "touchmove",
45
+ "submit"
46
+ ]);
47
+ /**
48
+ * Property name used on DOM elements to store delegated event handlers.
49
+ * Format: `__ev_{eventName}` e.g. `__ev_click`, `__ev_input`
50
+ */
51
+ function delegatedPropName(eventName) {
52
+ return `__ev_${eventName}`;
53
+ }
54
+ const _delegated = /* @__PURE__ */ new WeakSet();
55
+ /**
56
+ * Install delegation listeners on a container element.
57
+ * Called once from mount(). Idempotent — safe to call multiple times.
58
+ */
59
+ function setupDelegation(container) {
60
+ if (_delegated.has(container)) return;
61
+ _delegated.add(container);
62
+ for (const eventName of DELEGATED_EVENTS) {
63
+ const prop = delegatedPropName(eventName);
64
+ container.addEventListener(eventName, (e) => {
65
+ let el = e.target;
66
+ while (el && el !== container) {
67
+ const handler = el[prop];
68
+ if (handler) {
69
+ batch(() => handler(e));
70
+ if (e.cancelBubble) break;
71
+ }
72
+ el = el.parentElement;
73
+ }
74
+ });
75
+ }
76
+ }
77
+
78
+ //#endregion
4
79
  //#region src/hydration-debug.ts
5
80
  /**
6
81
  * Hydration mismatch warnings.
@@ -908,6 +983,13 @@ function applyProp(el, key, value) {
908
983
  if (EVENT_RE.test(key)) {
909
984
  const eventName = key[2]?.toLowerCase() + key.slice(3);
910
985
  const handler = value;
986
+ if (DELEGATED_EVENTS.has(eventName)) {
987
+ const prop = delegatedPropName(eventName);
988
+ el[prop] = (e) => batch(() => handler(e));
989
+ return () => {
990
+ el[prop] = void 0;
991
+ };
992
+ }
911
993
  const batched = (e) => batch(() => handler(e));
912
994
  el.addEventListener(eventName, batched);
913
995
  return () => el.removeEventListener(eventName, batched);
@@ -1239,7 +1321,7 @@ function firstReal(initialNode) {
1239
1321
  node = node.nextSibling;
1240
1322
  continue;
1241
1323
  }
1242
- if (node.nodeType === Node.TEXT_NODE && node.data.trim() === "") {
1324
+ if (node.nodeType === Node.TEXT_NODE && isWhitespaceOnly(node.data)) {
1243
1325
  node = node.nextSibling;
1244
1326
  continue;
1245
1327
  }
@@ -1247,6 +1329,14 @@ function firstReal(initialNode) {
1247
1329
  }
1248
1330
  return null;
1249
1331
  }
1332
+ /** Check if a string is whitespace-only without allocating a trimmed copy. */
1333
+ function isWhitespaceOnly(s) {
1334
+ for (let i = 0; i < s.length; i++) {
1335
+ const c = s.charCodeAt(i);
1336
+ if (c !== 32 && c !== 9 && c !== 10 && c !== 13 && c !== 12) return false;
1337
+ }
1338
+ return true;
1339
+ }
1250
1340
  /** Advance past a node, skipping whitespace-only text and comments */
1251
1341
  function nextReal(node) {
1252
1342
  return firstReal(node.nextSibling);
@@ -1273,11 +1363,10 @@ function hydrateReactiveChild(child, domNode, parent, anchor, path) {
1273
1363
  function hydrateReactiveText(child, domNode, parent, anchor, path) {
1274
1364
  if (domNode?.nodeType === Node.TEXT_NODE) {
1275
1365
  const textNode = domNode;
1276
- const e = effect(() => {
1366
+ return [renderEffect(() => {
1277
1367
  const v = child();
1278
1368
  textNode.data = v == null ? "" : String(v);
1279
- });
1280
- return [() => e.dispose(), nextReal(domNode)];
1369
+ }), nextReal(domNode)];
1281
1370
  }
1282
1371
  warnHydrationMismatch("text", "TextNode", domNode?.nodeType ?? "null", `${path} > reactive`);
1283
1372
  return [mountChild(child, parent, anchor), domNode];
@@ -1337,6 +1426,8 @@ function hydrateElement(vnode, domNode, parent, anchor, path = "root") {
1337
1426
  return [mountChild(vnode, parent, anchor), domNode];
1338
1427
  }
1339
1428
  function hydrateChildren(children, domNode, parent, anchor, path = "root") {
1429
+ if (children.length === 0) return [noop, domNode];
1430
+ if (children.length === 1) return hydrateChild(children[0], domNode, parent, anchor, path);
1340
1431
  const cleanups = [];
1341
1432
  let cursor = domNode;
1342
1433
  for (const child of children) {
@@ -1421,6 +1512,7 @@ function hydrateComponent(vnode, domNode, parent, anchor, path = "root") {
1421
1512
  * const unmount = hydrateRoot(document.getElementById("app")!, h(App, null))
1422
1513
  */
1423
1514
  function hydrateRoot(container, vnode) {
1515
+ setupDelegation(container);
1424
1516
  const [cleanup] = hydrateChild(vnode, firstReal(container.firstChild), container, null);
1425
1517
  return cleanup;
1426
1518
  }
@@ -1516,6 +1608,50 @@ function createTemplate(html, bind) {
1516
1608
  };
1517
1609
  };
1518
1610
  }
1611
+ /**
1612
+ * Compiler-emitted direct text binding for single-signal text nodes.
1613
+ *
1614
+ * When the compiler detects `{signal()}` as the only reactive expression
1615
+ * in a text binding, it emits `_bindText(signal, textNode)` instead of
1616
+ * `_bind(() => { textNode.data = signal() })`.
1617
+ *
1618
+ * This bypasses the effect system entirely:
1619
+ * - No deps array allocation
1620
+ * - No withTracking / setDepsCollector overhead
1621
+ * - No `run` closure
1622
+ * - Signal.subscribe is used directly (O(1) subscribe + unsubscribe)
1623
+ *
1624
+ * @param source - A signal (anything with `._v` and `.direct`)
1625
+ * @param node - The Text node to update
1626
+ */
1627
+ function _bindText(source, node) {
1628
+ const update = () => {
1629
+ const v = source._v;
1630
+ node.data = v == null || v === false ? "" : String(v);
1631
+ };
1632
+ update();
1633
+ return source.direct(update);
1634
+ }
1635
+ /**
1636
+ * Compiler-emitted direct binding for single-signal reactive expressions.
1637
+ *
1638
+ * Like _bindText but for arbitrary DOM updates (attributes, className, style).
1639
+ * When the compiler detects that a reactive expression depends on exactly one
1640
+ * signal call, it emits `_bindDirect(signal, updater)` instead of
1641
+ * `_bind(() => { updater() })`.
1642
+ *
1643
+ * Uses signal.direct() for zero-overhead registration:
1644
+ * - Flat array instead of Set (no hashing)
1645
+ * - Index-based disposal (no Set.delete)
1646
+ * - No deps array, no withTracking, no run closure
1647
+ *
1648
+ * @param source - A signal (anything with `._v` and `.direct`)
1649
+ * @param updater - Function that reads `source._v` and applies the DOM update
1650
+ */
1651
+ function _bindDirect(source, updater) {
1652
+ updater(source._v);
1653
+ return source.direct(() => updater(source._v));
1654
+ }
1519
1655
  const _tplCache = /* @__PURE__ */ new Map();
1520
1656
  /**
1521
1657
  * Compiler-emitted template instantiation.
@@ -1898,6 +2034,7 @@ const __DEV__ = typeof process !== "undefined" && process.env.NODE_ENV !== "prod
1898
2034
  function mount(root, container) {
1899
2035
  if (__DEV__ && container == null) throw new Error("[pyreon] mount() called with a null/undefined container. Make sure the element exists in the DOM, e.g. document.getElementById(\"app\")");
1900
2036
  installDevTools();
2037
+ setupDelegation(container);
1901
2038
  container.innerHTML = "";
1902
2039
  return mountChild(root, container, null);
1903
2040
  }
@@ -1905,5 +2042,5 @@ function mount(root, container) {
1905
2042
  const render = mount;
1906
2043
 
1907
2044
  //#endregion
1908
- export { KeepAlive, Transition, TransitionGroup, _tpl, applyProp, applyProps, createTemplate, disableHydrationWarnings, enableHydrationWarnings, hydrateRoot, mount, mountChild, render, sanitizeHtml, setSanitizer };
2045
+ export { DELEGATED_EVENTS, KeepAlive, Transition, TransitionGroup, _bindDirect, _bindText, _tpl, applyProp, applyProps, createTemplate, delegatedPropName, disableHydrationWarnings, enableHydrationWarnings, hydrateRoot, mount, mountChild, render, sanitizeHtml, setSanitizer, setupDelegation };
1909
2046
  //# sourceMappingURL=index.js.map