tutuca 0.9.26 → 0.9.28

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/README.md CHANGED
@@ -7,7 +7,7 @@ Zero-dependency batteries included SPA framework.
7
7
  - **Fits in your head** (and the context window)
8
8
  - **View source friendly** — step through the whole stack
9
9
  - **As much HTML as possible, as little JS as needed**
10
- - ~107KB minified, ~29KB brotli compressed
10
+ - ~169KB minified, ~37KB brotli compressed
11
11
 
12
12
  ## Quick Start
13
13
 
@@ -64,17 +64,17 @@ Zero-dependency batteries included SPA framework.
64
64
  Tutuca ships a single-file CLI (`dist/tutuca-cli.js`) for inspecting, linting,
65
65
  documenting, and rendering components defined in an ES module. The module just
66
66
  needs to export `getComponents()` and, for render-time commands, `getExamples()`
67
- in the storybook shape `{ title, description?, groups?, items: [{ title, description?, value, view? }] }`.
67
+ in the storybook shape `{ title, description?, items: [{ title, description?, value, view? }] }` (a single section, or an array of sections).
68
68
 
69
69
  ### Setup
70
70
 
71
71
  ```sh
72
- npm install --save-dev tutuca jsdom
72
+ npm install --save-dev tutuca
73
73
  # prettier is optional, only needed for --pretty
74
74
  npm install --save-dev prettier
75
75
  ```
76
76
 
77
- The package exposes `tutuca` via `bin`, so `npx tutuca` (or a global `npm i -g tutuca jsdom`) just works. `jsdom` is a peer dep because it's only needed for `render`, `lint`, and `doctor`.
77
+ The package exposes `tutuca` via `bin`, so `npx tutuca` (or a global `npm i -g tutuca`) just works. `jsdom` ships as a regular dependency (it's needed by `render`, `lint`, and `doctor`) and is installed automatically.
78
78
 
79
79
  ### Commands
80
80
 
@@ -221,15 +221,11 @@ function normalizeModule(mod, { path = null } = {}) {
221
221
  "getMacros",
222
222
  "getRequestHandlers",
223
223
  "getExamples",
224
- "getStoryBookSection",
225
224
  "getRoot"
226
225
  ]) {
227
226
  if (typeof mod[key] === "function")
228
227
  present.add(key);
229
228
  }
230
- if (present.has("getStoryBookSection") && !present.has("getExamples")) {
231
- throw shapeError("module exports getStoryBookSection; rename it to getExamples.", "module");
232
- }
233
229
  const components = present.has("getComponents") ? mod.getComponents() : [];
234
230
  const macros = present.has("getMacros") ? mod.getMacros() : null;
235
231
  const requestHandlers = present.has("getRequestHandlers") ? mod.getRequestHandlers() : null;
@@ -739,13 +735,10 @@ class ValParser {
739
735
  switch (charCode) {
740
736
  case 94: {
741
737
  const newS = px.frame.macroVars?.[s.slice(1)];
742
- if (newS !== undefined) {
738
+ if (newS !== undefined)
743
739
  return this.parse(newS, px);
744
- }
745
740
  return null;
746
741
  }
747
- case 126:
748
- return this.okStrTpl ? parseConst(s.slice(1), px) : null;
749
742
  case 39:
750
743
  return this.okStrTpl ? parseConst(s.slice(1, -1), px) : null;
751
744
  case 64:
@@ -858,7 +851,7 @@ function getValSubType(s) {
858
851
  return open === 1 && close === 1 ? VAL_SUB_TYPE_SEQ_ACCESS : VAL_SUB_TYPE_INVALID;
859
852
  return -1;
860
853
  }
861
- var VALID_VAL_ID_RE, isValidValId = (name) => VALID_VAL_ID_RE.test(name), VALID_FLOAT_RE, parseStrTemplate = (v, px) => StrTplVal.parse(v, px), parseConst = (v, _) => new ConstVal(v), parseName = (v, _) => isValidValId(v) ? new NameVal(v) : null, parseType = (v, _) => isValidValId(v) ? new TypeVal(v) : null, parseBind = (v, _) => isValidValId(v) ? new BindVal(v) : null, parseDyn = (v, _) => isValidValId(v) ? new DynVal(v) : null, parseField = (v, _) => isValidValId(v) ? new FieldVal(v) : null, parseComp = (v, _) => isValidValId(v) ? new ComputedVal(v) : null, parseReq = (v, _) => isValidValId(v) ? new RequestVal(v) : null, ConstVal, VarVal, StrTplVal, NameVal, InputHandlerNameVal, AlterHandlerNameVal, mk404Handler = (type, name) => function(...args) {
854
+ var VALID_VAL_ID_RE, isValidValId = (name) => VALID_VAL_ID_RE.test(name), VALID_FLOAT_RE, STR_TPL_SPLIT_RE, parseStrTemplate = (v, px) => StrTplVal.parse(v, px), parseConst = (v, _) => new ConstVal(v), parseName = (v, _) => isValidValId(v) ? new NameVal(v) : null, parseType = (v, _) => isValidValId(v) ? new TypeVal(v) : null, parseBind = (v, _) => isValidValId(v) ? new BindVal(v) : null, parseDyn = (v, _) => isValidValId(v) ? new DynVal(v) : null, parseField = (v, _) => isValidValId(v) ? new FieldVal(v) : null, parseComp = (v, _) => isValidValId(v) ? new ComputedVal(v) : null, parseReq = (v, _) => isValidValId(v) ? new RequestVal(v) : null, ConstVal, VarVal, StrTplVal, NameVal, InputHandlerNameVal, AlterHandlerNameVal, mk404Handler = (type, name) => function(...args) {
862
855
  console.warn("handler not found", { type, name, args }, this);
863
856
  return this;
864
857
  }, TypeVal, RequestVal, RawFieldVal, RenderVal, RenderNameVal, BindVal, DynVal, FieldVal, ComputedVal, SeqAccessVal, VAL_SUB_TYPE_STRING_TEMPLATE = 0, VAL_SUB_TYPE_SEQ_ACCESS = 1, VAL_SUB_TYPE_INVALID = 2, VAL_SUB_TYPE_CONST_STRING = 3, vp;
@@ -866,6 +859,7 @@ var init_value = __esm(() => {
866
859
  init_path();
867
860
  VALID_VAL_ID_RE = /^[a-zA-Z][a-zA-Z0-9_]*$/;
868
861
  VALID_FLOAT_RE = /^-?[0-9]+(\.[0-9]+)?$/;
862
+ STR_TPL_SPLIT_RE = /(\{[^}]+\})/g;
869
863
  ConstVal = class ConstVal extends BaseVal {
870
864
  constructor(val) {
871
865
  super();
@@ -899,7 +893,7 @@ var init_value = __esm(() => {
899
893
  return strs.join("");
900
894
  }
901
895
  static parse(s, px) {
902
- const parts = s.split(/(\{[^}]+\})/g);
896
+ const parts = s.split(STR_TPL_SPLIT_RE);
903
897
  const vals = new Array(parts.length);
904
898
  let allConsts = true;
905
899
  for (let i = 0;i < parts.length; i++) {
@@ -1333,9 +1327,8 @@ function optimizeChilds(childs) {
1333
1327
  }
1334
1328
  }
1335
1329
  function optimizeNode(node) {
1336
- if (node.isConstant()) {
1330
+ if (node.isConstant())
1337
1331
  return new RenderOnceNode(node);
1338
- }
1339
1332
  node.optimize();
1340
1333
  return node;
1341
1334
  }
@@ -1401,12 +1394,6 @@ class ParseContext {
1401
1394
  newDOMParser() {
1402
1395
  return new this.DOMParser;
1403
1396
  }
1404
- isTextNode(v) {
1405
- return v instanceof this.Text;
1406
- }
1407
- isCommentNode(v) {
1408
- return v instanceof this.Comment;
1409
- }
1410
1397
  addNodeIf(Class, val, extra) {
1411
1398
  if (val !== null) {
1412
1399
  const nodeId = this.nodes.length;
@@ -1425,21 +1412,18 @@ class ParseContext {
1425
1412
  newMacroNode(macroName, mAttrs, childs) {
1426
1413
  const anySlot = [];
1427
1414
  const slots = { _: new FragmentNode(anySlot) };
1428
- for (const child of childs) {
1429
- if (child instanceof SlotNode) {
1415
+ for (const child of childs)
1416
+ if (child instanceof SlotNode)
1430
1417
  slots[child.val.val] = child.node;
1431
- } else if (!(child instanceof TextNode) || !child.isWhiteSpace()) {
1418
+ else if (!(child instanceof TextNode) || !child.isWhiteSpace())
1432
1419
  anySlot.push(child);
1433
- }
1434
- }
1435
1420
  const node = new MacroNode(macroName, mAttrs, slots, this);
1436
1421
  this.macroNodes.push(node);
1437
1422
  return node;
1438
1423
  }
1439
1424
  compile(scope) {
1440
- for (let i = 0;i < this.macroNodes.length; i++) {
1425
+ for (let i = 0;i < this.macroNodes.length; i++)
1441
1426
  this.macroNodes[i].compile(scope);
1442
- }
1443
1427
  }
1444
1428
  *genEventNames() {
1445
1429
  for (const event of this.events)
@@ -1454,26 +1438,28 @@ class ParseContext {
1454
1438
  onAttributes(_attrs, _wrapperAttrs, _textChild, _isMacroCall) {}
1455
1439
  }
1456
1440
  function condenseChildsWhites(childs) {
1457
- let end = childs.length;
1458
- if (end === 0)
1441
+ if (childs.length === 0)
1459
1442
  return childs;
1460
- let start = 0;
1461
1443
  let changed = false;
1462
- if (isTextNodeAllBlanks(childs[0])) {
1463
- start = 1;
1444
+ if (childs[0].isWhiteSpace?.()) {
1445
+ childs[0].condenseWhiteSpace();
1464
1446
  changed = true;
1465
1447
  }
1466
- if (end > 1 && isTextNodeAllBlanks(childs[end - 1])) {
1467
- end -= 1;
1448
+ const last = childs.length - 1;
1449
+ if (last > 0 && childs[last].isWhiteSpace?.()) {
1450
+ childs[last].condenseWhiteSpace();
1468
1451
  changed = true;
1469
1452
  }
1470
- for (let i = 1;i < end - 1; i++) {
1453
+ for (let i = 1;i < last; i++) {
1471
1454
  const cur = childs[i];
1472
- if (isTextNodeAllBlanks(cur) && isFirstDomNode(childs[i - 1]) && isFirstDomNode(childs[i + 1]) && cur.hasNewLine()) {
1473
- cur.condenseWhiteSpace();
1455
+ if (cur.isWhiteSpace?.() && cur.hasNewLine()) {
1456
+ const bothBlock = isBlockDomNode(childs[i - 1]) && isBlockDomNode(childs[i + 1]);
1457
+ cur.condenseWhiteSpace(bothBlock ? "" : " ");
1458
+ if (bothBlock)
1459
+ changed = true;
1474
1460
  }
1475
1461
  }
1476
- return changed ? childs.slice(start, end) : childs;
1462
+ return changed ? childs.filter((c) => !(c instanceof TextNode && c.val === "")) : childs;
1477
1463
  }
1478
1464
 
1479
1465
  class NodeEvents {
@@ -1490,12 +1476,11 @@ class NodeEvents {
1490
1476
  }
1491
1477
  getHandlersFor(eventName) {
1492
1478
  let r = null;
1493
- for (const handler of this.handlers) {
1479
+ for (const handler of this.handlers)
1494
1480
  if (handler.handlesEventName(eventName)) {
1495
1481
  r ??= [];
1496
1482
  r.push(handler);
1497
1483
  }
1498
- }
1499
1484
  return r;
1500
1485
  }
1501
1486
  }
@@ -1530,7 +1515,10 @@ function compileModifiers(eventName, names) {
1530
1515
  return w(this, f, args, ctx);
1531
1516
  };
1532
1517
  }
1533
- var TextNode, CommentNode, ChildsNode, DomNode, FragmentNode, maybeFragment = (xs) => xs.length === 1 ? xs[0] : new FragmentNode(xs), VALID_NODE_RE, _parser = null, ANode, MacroNode, RenderViewId, RenderNode, RenderItNode, RenderEachNode, RenderTextNode, RenderOnceNode, WrapperNode, ShowNode, HideNode, PushViewNameNode, SlotNode, ScopeNode, EachNode, filterAlwaysTrue = (_v, _k, _seq) => true, nullLoopWith = (seq) => ({ seq }), WRAPPER_NODES, isTextNodeAllBlanks = (n) => n instanceof TextNode && n.isWhiteSpace(), isFirstDomNode = (n) => n instanceof DomNode || n instanceof FragmentNode && n.childs[0] instanceof DomNode, isMac, fwdIfCtxPred = (pred) => (w) => (that, f, args, ctx) => pred(ctx) ? w(that, f, args, ctx) : that, fwdIfKey = (keyName) => fwdIfCtxPred((ctx) => ctx.e.key === keyName), fwdCtrl, fwdMeta, fwdAlt, metaWraps, MOD_WRAPPERS_BY_EVENT, identityModifierWrapper = (f, _ctx) => f;
1518
+ var TextNode, CommentNode, ChildsNode, DomNode, FragmentNode, maybeFragment = (xs) => xs.length === 1 ? xs[0] : new FragmentNode(xs), VALID_NODE_RE, _parser = null, ANode, MacroNode, RenderViewId, RenderNode, RenderItNode, RenderEachNode, RenderTextNode, RenderOnceNode, WrapperNode, ShowNode, HideNode, PushViewNameNode, SlotNode, ScopeNode, EachNode, filterAlwaysTrue = (_v, _k, _seq) => true, nullLoopWith = (seq) => ({ seq }), WRAPPER_NODES, _htmlBlockTags = "ADDRESS,ARTICLE,ASIDE,BLOCKQUOTE,CAPTION,COL,COLGROUP,DETAILS,DIALOG,DIV,DD,DL,DT,FIELDSET,FIGCAPTION,FIGURE,FOOTER,FORM,H1,H2,H3,H4,H5,H6,HEADER,HGROUP,HR,LEGEND,LI,MAIN,MENU,NAV,OL,P,PRE,SECTION,SUMMARY,TABLE,TBODY,TD,TFOOT,TH,THEAD,TR,UL", HTML_BLOCK_TAGS, isBlockDomNode = (n) => {
1519
+ const node = n instanceof FragmentNode ? n.childs[0] : n;
1520
+ return node instanceof DomNode && HTML_BLOCK_TAGS.has(node.tagName);
1521
+ }, isMac, fwdIfCtxPred = (pred) => (w) => (that, f, args, ctx) => pred(ctx) ? w(that, f, args, ctx) : that, fwdIfKey = (keyName) => fwdIfCtxPred((ctx) => ctx.e.key === keyName), fwdCtrl, fwdMeta, fwdAlt, metaWraps, MOD_WRAPPERS_BY_EVENT, identityModifierWrapper = (f, _ctx) => f;
1534
1522
  var init_anode = __esm(() => {
1535
1523
  init_attribute();
1536
1524
  init_path();
@@ -1559,8 +1547,8 @@ var init_anode = __esm(() => {
1559
1547
  }
1560
1548
  return false;
1561
1549
  }
1562
- condenseWhiteSpace() {
1563
- this.val = "";
1550
+ condenseWhiteSpace(replacement = "") {
1551
+ this.val = replacement;
1564
1552
  }
1565
1553
  isConstant() {
1566
1554
  return true;
@@ -1629,9 +1617,9 @@ var init_anode = __esm(() => {
1629
1617
  return ANode.fromDOM(nodes[0] ?? new px.Text(""), px);
1630
1618
  }
1631
1619
  static fromDOM(node, px) {
1632
- if (px.isTextNode(node))
1620
+ if (node instanceof px.Text)
1633
1621
  return new TextNode(node.textContent);
1634
- else if (px.isCommentNode(node))
1622
+ else if (node instanceof px.Comment)
1635
1623
  return new CommentNode(node.textContent);
1636
1624
  const { childNodes, attributes: attrs, tagName: tag } = node;
1637
1625
  const childs = new Array(childNodes.length);
@@ -1677,7 +1665,7 @@ var init_anode = __esm(() => {
1677
1665
  if (textChild)
1678
1666
  childs.unshift(new RenderTextNode(null, textChild));
1679
1667
  const domChilds = tag !== "PRE" ? condenseChildsWhites(childs) : childs;
1680
- return wrap(new DomNode(tag.toLowerCase(), nAttrs, domChilds), px, wrappers);
1668
+ return wrap(new DomNode(tag, nAttrs, domChilds), px, wrappers);
1681
1669
  }
1682
1670
  return new CommentNode(`Error: InvalidTagName ${tag}`);
1683
1671
  }
@@ -1697,9 +1685,9 @@ var init_anode = __esm(() => {
1697
1685
  if (this.px.isInsideMacro(name))
1698
1686
  throw new Error(`Recursive macro expansion: ${name}`);
1699
1687
  const macro = scope.lookupMacro(name);
1700
- if (macro === null) {
1688
+ if (macro === null)
1701
1689
  this.node = new CommentNode(`bad macro: ${name}`);
1702
- } else {
1690
+ else {
1703
1691
  const vars = { ...macro.defaults, ...attrs };
1704
1692
  this.node = macro.expand(this.px.enterMacro(name, vars, slots));
1705
1693
  for (const key in this.dataAttrs)
@@ -1853,6 +1841,7 @@ var init_anode = __esm(() => {
1853
1841
  scope: ScopeNode,
1854
1842
  "push-view": PushViewNameNode
1855
1843
  };
1844
+ HTML_BLOCK_TAGS = new Set(_htmlBlockTags.split(","));
1856
1845
  isMac = (globalThis.navigator?.userAgent ?? "").toLowerCase().includes("mac");
1857
1846
  fwdCtrl = fwdIfCtxPred(({ e }) => isMac && e.metaKey || e.ctrlKey);
1858
1847
  fwdMeta = fwdIfCtxPred(({ e }) => e.metaKey);
@@ -1870,10 +1859,8 @@ var init_anode = __esm(() => {
1870
1859
 
1871
1860
  // src/cache.js
1872
1861
  class NullDomCache {
1873
- get(_k, _cacheKey) {}
1874
- set(_k, _cacheKey, _v) {}
1875
- get2(_k1, _k2, _cacheKey) {}
1876
- set2(_k1, _k2, _cacheKey, _v) {}
1862
+ get(_keys, _cacheKey) {}
1863
+ set(_keys, _cacheKey, _v) {}
1877
1864
  evict() {
1878
1865
  return { hit: 0, miss: 0, badKey: 0 };
1879
1866
  }
@@ -1882,7 +1869,7 @@ class NullDomCache {
1882
1869
  class WeakMapDomCache {
1883
1870
  constructor() {
1884
1871
  this.hit = this.miss = this.badKey = 0;
1885
- this.map = new WeakMap;
1872
+ this.keysByLen = new Map;
1886
1873
  }
1887
1874
  _returnValue(r) {
1888
1875
  if (r === undefined)
@@ -1891,41 +1878,51 @@ class WeakMapDomCache {
1891
1878
  this.hit += 1;
1892
1879
  return r;
1893
1880
  }
1894
- get(k, cacheKey) {
1895
- return this._returnValue(this.map.get(k)?.[cacheKey]);
1896
- }
1897
- set(k, cacheKey, v) {
1898
- const cur = this.map.get(k);
1899
- if (cur)
1900
- cur[cacheKey] = v;
1901
- else if (typeof k === "object")
1902
- this.map.set(k, { [cacheKey]: v });
1881
+ get(keys, cacheKey) {
1882
+ const len = keys.length;
1883
+ let cur = this.keysByLen.get(len);
1884
+ if (!cur)
1885
+ return this._returnValue(undefined);
1886
+ for (let i = 0;i < len - 1; i++) {
1887
+ cur = cur.get(keys[i]);
1888
+ if (!cur)
1889
+ return this._returnValue(undefined);
1890
+ }
1891
+ return this._returnValue(cur.get(keys[len - 1])?.[cacheKey]);
1892
+ }
1893
+ set(keys, cacheKey, v) {
1894
+ const len = keys.length;
1895
+ let cur = this.keysByLen.get(len);
1896
+ if (!cur) {
1897
+ cur = new WeakMap;
1898
+ this.keysByLen.set(len, cur);
1899
+ }
1900
+ for (let i = 0;i < len - 1; i++) {
1901
+ const key = keys[i];
1902
+ let next = cur.get(key);
1903
+ if (!next) {
1904
+ if (typeof key !== "object") {
1905
+ this.badKey += 1;
1906
+ return;
1907
+ }
1908
+ next = new WeakMap;
1909
+ cur.set(key, next);
1910
+ }
1911
+ cur = next;
1912
+ }
1913
+ const lastKey = keys[len - 1];
1914
+ const leaf = cur.get(lastKey);
1915
+ if (leaf)
1916
+ leaf[cacheKey] = v;
1917
+ else if (typeof lastKey === "object")
1918
+ cur.set(lastKey, { [cacheKey]: v });
1903
1919
  else
1904
1920
  this.badKey += 1;
1905
1921
  }
1906
- get2(k1, k2, cacheKey) {
1907
- return this._returnValue(this.map.get(k1)?.get?.(k2)?.[cacheKey]);
1908
- }
1909
- set2(k1, k2, cacheKey, v) {
1910
- const cur1 = this.map.get(k1);
1911
- if (cur1) {
1912
- const cur = cur1.get(k2);
1913
- if (cur)
1914
- cur[cacheKey] = v;
1915
- else
1916
- cur1.set(k2, { [cacheKey]: v });
1917
- } else if (typeof k1 === "object" && typeof k2 === "object") {
1918
- const cur = new WeakMap;
1919
- cur.set(k2, { [cacheKey]: v });
1920
- this.map.set(k1, cur);
1921
- } else {
1922
- this.badKey += 1;
1923
- }
1924
- }
1925
1922
  evict() {
1926
1923
  const { hit, miss, badKey } = this;
1927
1924
  this.hit = this.miss = this.badKey = 0;
1928
- this.map = new WeakMap;
1925
+ this.keysByLen = new Map;
1929
1926
  return { hit, miss, badKey };
1930
1927
  }
1931
1928
  }
@@ -2047,8 +2044,8 @@ class ComponentStack {
2047
2044
  return this.macros[name] ?? this.parent?.lookupMacro(name) ?? null;
2048
2045
  }
2049
2046
  }
2050
- function defaultOnStackEnter(stack) {
2051
- return stack;
2047
+ function defaultOnStackEnter() {
2048
+ return null;
2052
2049
  }
2053
2050
  var init_components = __esm(() => {
2054
2051
  init_attribute();
@@ -2466,7 +2463,7 @@ class Stack {
2466
2463
  this.ctx = ctx;
2467
2464
  }
2468
2465
  _enrichOnEnter() {
2469
- return this.comps.getOnEnterFor(this.it).call(this.it, this) ?? this;
2466
+ return this.withDynamicBindings(this.comps.getOnEnterFor(this.it).call(this.it));
2470
2467
  }
2471
2468
  upToFrameBinds() {
2472
2469
  const { comps, binds, dynBinds, views, viewsId, ctx } = this;
@@ -2481,7 +2478,8 @@ class Stack {
2481
2478
  enter(it, bindings = {}, isFrame = true) {
2482
2479
  const { comps, binds, dynBinds, views, viewsId, ctx } = this;
2483
2480
  const newBinds = [new BindFrame(it, bindings, isFrame), binds];
2484
- return new Stack(comps, it, newBinds, dynBinds, views, viewsId, ctx)._enrichOnEnter();
2481
+ const stack = new Stack(comps, it, newBinds, dynBinds, views, viewsId, ctx);
2482
+ return isFrame ? stack._enrichOnEnter() : stack;
2485
2483
  }
2486
2484
  pushViewName(name) {
2487
2485
  const { comps, it, binds, dynBinds, views, ctx } = this;
@@ -2489,17 +2487,26 @@ class Stack {
2489
2487
  return new Stack(comps, it, binds, dynBinds, newViews, computeViewsId(newViews), ctx);
2490
2488
  }
2491
2489
  withDynamicBindings(dynamics) {
2490
+ if (dynamics == null || dynamics.length === 0)
2491
+ return this;
2492
2492
  const dynObj = {};
2493
2493
  const comp = this.comps.getCompFor(this.it);
2494
2494
  for (const dynName of dynamics)
2495
2495
  comp.dynamic[dynName].evalAndBind(this, dynObj);
2496
- const { comps, it, binds, views, viewsId, ctx } = this;
2497
2496
  const newDynBinds = [new ObjectFrame(dynObj), this.dynBinds];
2497
+ const { comps, it, binds, views, viewsId, ctx } = this;
2498
2498
  return new Stack(comps, it, binds, newDynBinds, views, viewsId, ctx);
2499
2499
  }
2500
+ _pushDynBindValuesToArray(arr, dyns) {
2501
+ for (const k in dyns)
2502
+ arr.push(this._lookupDynamicWithDynVal(dyns[k]));
2503
+ }
2504
+ _lookupDynamicWithDynVal(d) {
2505
+ return lookup(this.dynBinds, d.getSymbol(this)) ?? d.val.eval(this);
2506
+ }
2500
2507
  lookupDynamic(name) {
2501
2508
  const d = this.comps.getCompFor(this.it)?.dynamic[name];
2502
- return d ? lookup(this.dynBinds, d.getSymbol(this)) ?? d.val.eval(this) : null;
2509
+ return d ? this._lookupDynamicWithDynVal(d) : null;
2503
2510
  }
2504
2511
  lookupBind(name) {
2505
2512
  return lookup(this.binds, name);
@@ -2982,23 +2989,20 @@ function morphNode(domNode, source, target, opts) {
2982
2989
  domNode.data = target.text;
2983
2990
  return domNode;
2984
2991
  }
2985
- if (type === 1 && source.tag === target.tag && source.namespace === target.namespace && source.key === target.key) {
2992
+ if (type === 1 && source.isSameKind(target)) {
2986
2993
  const propsDiff = diffProps(source.attrs, target.attrs);
2987
2994
  const isSelect = source.tag === "SELECT";
2988
2995
  if (propsDiff) {
2989
2996
  if (isSelect && "value" in propsDiff) {
2990
2997
  const { value: _v, ...rest } = propsDiff;
2991
2998
  applyProperties(domNode, rest, source.attrs);
2992
- } else {
2999
+ } else
2993
3000
  applyProperties(domNode, propsDiff, source.attrs);
2994
- }
2995
3001
  }
2996
- if (!target.attrs.dangerouslySetInnerHTML) {
3002
+ if (!target.attrs.dangerouslySetInnerHTML)
2997
3003
  morphChildren(domNode, source.childs, target.childs, opts);
2998
- }
2999
- if (isSelect && target.attrs.value !== undefined) {
3004
+ if (isSelect && target.attrs.value !== undefined)
3000
3005
  applyProperties(domNode, { value: target.attrs.value }, source.attrs);
3001
- }
3002
3006
  return domNode;
3003
3007
  }
3004
3008
  if (type === 11) {
@@ -3077,23 +3081,20 @@ function morphChildren(parentDom, oldChilds, newChilds, opts) {
3077
3081
  if (!used[i] && domNodes[i].parentNode === parentDom)
3078
3082
  parentDom.removeChild(domNodes[i]);
3079
3083
  }
3080
- function render(vnode, container, options) {
3081
- const cached = renderCache.get(container);
3084
+ function render(vnode, container, options, prev) {
3082
3085
  const isFragment = vnode instanceof VFragment;
3083
- if (cached && cached.vnode instanceof VFragment === isFragment) {
3084
- const oldDom = isFragment ? container : cached.dom;
3085
- const newDom = morphNode(oldDom, cached.vnode, vnode, options);
3086
- renderCache.set(container, { vnode, dom: isFragment ? container : newDom });
3087
- return newDom;
3086
+ if (prev && prev.vnode instanceof VFragment === isFragment) {
3087
+ const oldDom = isFragment ? container : prev.dom;
3088
+ const newDom = morphNode(oldDom, prev.vnode, vnode, options);
3089
+ return { vnode, dom: isFragment ? container : newDom };
3088
3090
  }
3089
- renderCache.delete(container);
3090
3091
  const domNode = vnode.toDom(options);
3091
3092
  container.replaceChildren(domNode);
3092
- renderCache.set(container, { vnode, dom: isFragment ? container : domNode });
3093
- return domNode;
3093
+ return { vnode, dom: isFragment ? container : domNode };
3094
3094
  }
3095
3095
  function h(tagName, properties, children) {
3096
- const tag = tagName.toUpperCase();
3096
+ const c = tagName.charCodeAt(0);
3097
+ const tag = c >= 97 && c <= 122 ? tagName.toUpperCase() : tagName;
3097
3098
  const props = {};
3098
3099
  let key, namespace;
3099
3100
  if (properties) {
@@ -3121,7 +3122,7 @@ function h(tagName, properties, children) {
3121
3122
  addChild(normalizedChildren, children);
3122
3123
  return new VNode(tag, props, normalizedChildren, key, namespace);
3123
3124
  }
3124
- var isHtmlAttribute = (propName) => propName[4] === "-" && (propName[0] === "d" || propName[0] === "a"), isObject = (v) => v !== null && typeof v === "object", prototypesDiffer = (a, b) => Object.getPrototypeOf(a) !== Object.getPrototypeOf(b), getKey = (child) => child instanceof VNode ? child.key : undefined, isIterable = (obj) => obj != null && typeof obj !== "string" && typeof obj[Symbol.iterator] === "function", VText, VComment, VFragment, VNode, renderCache;
3125
+ var isHtmlAttribute = (propName) => propName[4] === "-" && (propName[0] === "d" || propName[0] === "a"), isObject = (v) => v !== null && typeof v === "object", prototypesDiffer = (a, b) => Object.getPrototypeOf(a) !== Object.getPrototypeOf(b), getKey = (child) => child instanceof VNode ? child.key : undefined, isIterable = (obj) => obj != null && typeof obj !== "string" && typeof obj[Symbol.iterator] === "function", VText, VComment, VFragment, VNode;
3125
3126
  var init_vdom = __esm(() => {
3126
3127
  VText = class VText extends VBase {
3127
3128
  constructor(text) {
@@ -3185,10 +3186,13 @@ var init_vdom = __esm(() => {
3185
3186
  get nodeType() {
3186
3187
  return 1;
3187
3188
  }
3189
+ isSameKind(other) {
3190
+ return this.tag === other.tag && this.namespace === other.namespace && this.key === other.key;
3191
+ }
3188
3192
  isEqualTo(other) {
3189
3193
  if (this === other)
3190
3194
  return true;
3191
- if (!(other instanceof VNode) || this.tag !== other.tag || this.key !== other.key || this.namespace !== other.namespace || this.childs.length !== other.childs.length) {
3195
+ if (!(other instanceof VNode) || !this.isSameKind(other) || this.childs.length !== other.childs.length) {
3192
3196
  return false;
3193
3197
  }
3194
3198
  if (this.attrs !== other.attrs) {
@@ -3216,7 +3220,6 @@ var init_vdom = __esm(() => {
3216
3220
  return node;
3217
3221
  }
3218
3222
  };
3219
- renderCache = new WeakMap;
3220
3223
  });
3221
3224
 
3222
3225
  // src/app.js
@@ -3239,6 +3242,7 @@ class App {
3239
3242
  };
3240
3243
  this._compiled = false;
3241
3244
  this._renderOpts = { document: rootNode.ownerDocument };
3245
+ this._renderState = null;
3242
3246
  }
3243
3247
  get state() {
3244
3248
  return this.transactor.state;
@@ -3345,7 +3349,10 @@ class App {
3345
3349
  render() {
3346
3350
  const root = this.state.val;
3347
3351
  const stack = this.makeStack(root);
3348
- return render(this.renderer.renderRoot(stack, root), this.rootNode, this._renderOpts);
3352
+ const { renderer, rootNode, _renderOpts, _renderState } = this;
3353
+ const newState = render(renderer.renderRoot(stack, root), rootNode, _renderOpts, _renderState);
3354
+ this._renderState = newState;
3355
+ return newState.dom;
3349
3356
  }
3350
3357
  onChange(callback) {
3351
3358
  this.transactor.state.onChange(callback);
@@ -7716,7 +7723,7 @@ class Renderer {
7716
7723
  renderToDOM(stack, val) {
7717
7724
  const rootNode = document.createElement("div");
7718
7725
  const rOpts = { document };
7719
- render(h("div", null, [this.renderRoot(stack, val)]), rootNode, rOpts);
7726
+ render(h("DIV", null, [this.renderRoot(stack, val)]), rootNode, rOpts);
7720
7727
  return rootNode.childNodes[0];
7721
7728
  }
7722
7729
  renderToString(stack, val, cleanAttrs = true) {
@@ -7740,13 +7747,15 @@ class Renderer {
7740
7747
  }
7741
7748
  _rValComp(stack, val, comp, nid, key, viewName) {
7742
7749
  const cacheKey = `${viewName ?? stack.viewsId ?? ""}${nid}-${key}`;
7743
- const cachedNode = this.cache.get(val, cacheKey);
7750
+ const cachePath = [val];
7751
+ stack._pushDynBindValuesToArray(cachePath, comp.dynamic);
7752
+ const cachedNode = this.cache.get(cachePath, cacheKey);
7744
7753
  if (cachedNode)
7745
7754
  return cachedNode;
7746
7755
  const view = viewName ? comp.getView(viewName) : stack.lookupBestView(comp.views, "main");
7747
7756
  const meta = this._renderMetadata({ $: "Comp", nid });
7748
7757
  const dom = new VFragment([meta, this.renderView(view, stack)]);
7749
- this.cache.set(val, cacheKey, dom);
7758
+ this.cache.set(cachePath, cacheKey, dom);
7750
7759
  return dom;
7751
7760
  }
7752
7761
  pushEachEntry(r, nid, attrName, key, dom) {
@@ -7758,8 +7767,7 @@ class Renderer {
7758
7767
  const iterData = loopWith.call(stack.it, seq);
7759
7768
  this.getSeqInfo(seq)(seq, (key, value, attrName) => {
7760
7769
  if (filter.call(stack.it, key, value, iterData)) {
7761
- const newStack = stack.enter(value, { key }, true);
7762
- const dom = this.renderIt(newStack, nodeId, key, viewName);
7770
+ const dom = this.renderIt(stack.enter(value, { key }, true), nodeId, key, viewName);
7763
7771
  this.pushEachEntry(r, nodeId, attrName, key, dom);
7764
7772
  }
7765
7773
  });
@@ -7772,25 +7780,19 @@ class Renderer {
7772
7780
  const it = stack.it;
7773
7781
  this.getSeqInfo(seq)(seq, (key, value, attrName) => {
7774
7782
  if (filter.call(it, key, value, iterData)) {
7783
+ const cachePath = enricher ? [it, value] : [value];
7775
7784
  const bindings = { key, value };
7776
7785
  const cacheKey = `${nid}-${key}`;
7777
- let cachedNode;
7778
- if (enricher) {
7786
+ if (enricher)
7779
7787
  enricher.call(it, bindings, key, value, iterData);
7780
- cachedNode = this.cache.get2(it, value, cacheKey);
7781
- } else
7782
- cachedNode = this.cache.get(value, cacheKey);
7783
- if (cachedNode) {
7788
+ const cachedNode = this.cache.get(cachePath, cacheKey);
7789
+ if (cachedNode)
7784
7790
  this.pushEachEntry(r, nid, attrName, key, cachedNode);
7785
- return;
7791
+ else {
7792
+ const dom = this.renderView(view, stack.enter(value, bindings, false));
7793
+ this.pushEachEntry(r, nid, attrName, key, dom);
7794
+ this.cache.set(cachePath, cacheKey, dom);
7786
7795
  }
7787
- const newStack = stack.enter(value, bindings, false);
7788
- const dom = this.renderView(view, newStack);
7789
- this.pushEachEntry(r, nid, attrName, key, dom);
7790
- if (enricher)
7791
- this.cache.set2(it, value, cacheKey, dom);
7792
- else
7793
- this.cache.set(value, cacheKey, dom);
7794
7796
  }
7795
7797
  });
7796
7798
  return r;
@@ -8069,9 +8071,6 @@ MODULE CONVENTION
8069
8071
 
8070
8072
  export function getRoot() // optional; returned by info
8071
8073
 
8072
- The legacy \`getStoryBookSection()\` name fails fast with
8073
- EXAMPLES_SHAPE_MISMATCH — rename it to \`getExamples\`.
8074
-
8075
8074
  COMMANDS (require <module-path>)
8076
8075
  info
8077
8076
  Summarize which getX() exports are present and count components,