tutuca 0.9.52 → 0.9.54

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
@@ -3,7 +3,7 @@
3
3
  Zero-dependency batteries included SPA framework.
4
4
 
5
5
  - **Single file, no build, no dependencies, no setup** — a script tag is all you need
6
- - **Batteries included** — state management, side effects, automatic memoization, drag and drop and more
6
+ - **Batteries included** — state management, side effects, automatic memoization, drag and drop, testing, CLI tooling, LLM skills and more
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**
@@ -3615,16 +3615,25 @@ function getFieldMethods(field) {
3615
3615
  name: `reset${uname}`,
3616
3616
  sig: `reset${uname}()`,
3617
3617
  desc: "Reset to default value"
3618
- },
3618
+ }
3619
+ ];
3620
+ const truthy = [
3619
3621
  {
3620
- name: `is${uname}NotSet`,
3621
- sig: `is${uname}NotSet()`,
3622
- desc: "Check if null/undefined"
3622
+ name: `is${uname}Truthy`,
3623
+ sig: `is${uname}Truthy()`,
3624
+ desc: "Check if value is truthy"
3623
3625
  },
3624
3626
  {
3625
- name: `is${uname}Set`,
3626
- sig: `is${uname}Set()`,
3627
- desc: "Check if not null/undefined"
3627
+ name: `is${uname}Falsy`,
3628
+ sig: `is${uname}Falsy()`,
3629
+ desc: "Check if value is falsy"
3630
+ }
3631
+ ];
3632
+ const nullable = [
3633
+ {
3634
+ name: `is${uname}Null`,
3635
+ sig: `is${uname}Null()`,
3636
+ desc: "Check if value is null/undefined"
3628
3637
  }
3629
3638
  ];
3630
3639
  switch (type3) {
@@ -3634,21 +3643,28 @@ function getFieldMethods(field) {
3634
3643
  name: `toggle${uname}`,
3635
3644
  sig: `toggle${uname}()`,
3636
3645
  desc: "Toggle boolean value"
3637
- });
3646
+ }, ...truthy);
3647
+ break;
3648
+ case "int":
3649
+ case "float":
3650
+ methods.push(...truthy);
3651
+ break;
3652
+ case "any":
3653
+ methods.push(...nullable, ...truthy);
3638
3654
  break;
3639
3655
  case "text":
3640
3656
  methods.push({
3641
3657
  name: `is${uname}Empty`,
3642
3658
  sig: `is${uname}Empty()`,
3643
3659
  desc: "Check if string is empty"
3644
- }, { name: `${name}Len`, sig: `${name}Len()`, desc: "Get string length" });
3660
+ }, { name: `${name}Len`, sig: `${name}Len()`, desc: "Get string length" }, ...truthy);
3645
3661
  break;
3646
3662
  case "list":
3647
3663
  methods.push({
3648
3664
  name: `is${uname}Empty`,
3649
3665
  sig: `is${uname}Empty()`,
3650
3666
  desc: "Check if list is empty"
3651
- }, { name: `${name}Len`, sig: `${name}Len()`, desc: "Get list size" }, {
3667
+ }, { name: `${name}Len`, sig: `${name}Len()`, desc: "Get list size" }, ...truthy, {
3652
3668
  name: `setIn${uname}At`,
3653
3669
  sig: `setIn${uname}At(i, v)`,
3654
3670
  desc: "Set item at index"
@@ -3689,7 +3705,7 @@ function getFieldMethods(field) {
3689
3705
  name: `${name}Len`,
3690
3706
  sig: `${name}Len()`,
3691
3707
  desc: `Get ${label} size`
3692
- }, {
3708
+ }, ...truthy, {
3693
3709
  name: `setIn${uname}At`,
3694
3710
  sig: `setIn${uname}At(key, v)`,
3695
3711
  desc: "Set value at key"
@@ -3717,7 +3733,7 @@ function getFieldMethods(field) {
3717
3733
  name: `is${uname}Empty`,
3718
3734
  sig: `is${uname}Empty()`,
3719
3735
  desc: "Check if set is empty"
3720
- }, { name: `${name}Len`, sig: `${name}Len()`, desc: "Get set size" }, {
3736
+ }, { name: `${name}Len`, sig: `${name}Len()`, desc: "Get set size" }, ...truthy, {
3721
3737
  name: `addIn${uname}`,
3722
3738
  sig: `addIn${uname}(v)`,
3723
3739
  desc: "Add value to set"
@@ -3739,6 +3755,9 @@ function getFieldMethods(field) {
3739
3755
  desc: "Toggle value in set"
3740
3756
  });
3741
3757
  break;
3758
+ default:
3759
+ methods.push(...nullable, ...truthy);
3760
+ break;
3742
3761
  }
3743
3762
  return methods;
3744
3763
  }
@@ -5406,15 +5425,13 @@ class Components {
5406
5425
  return v?.[this.getComponentSymbol]?.() ?? null;
5407
5426
  }
5408
5427
  getOnEnterFor(v) {
5409
- const comp = this.getCompFor(v);
5410
- return comp ? comp.on.stackEnter : defaultOnStackEnter;
5428
+ return this.getCompFor(v)?.on.stackEnter ?? defaultOnStackEnter;
5411
5429
  }
5412
5430
  getHandlerFor(v, name, key) {
5413
5431
  return this.getCompFor(v)?.[key][name] ?? null;
5414
5432
  }
5415
5433
  getRequestFor(v, name) {
5416
- const comp = this.getCompFor(v);
5417
- return comp ? comp.scope.lookupRequest(name) : null;
5434
+ return this.getCompFor(v)?.scope.lookupRequest(name) ?? null;
5418
5435
  }
5419
5436
  compileStyles() {
5420
5437
  const styles2 = [];
@@ -13630,9 +13647,6 @@ class Renderer {
13630
13647
  this.comps = comps;
13631
13648
  this.cache = new WeakMapDomCache;
13632
13649
  }
13633
- getSeqInfo(seq) {
13634
- return isIndexed(seq) ? imIndexedIter : isKeyed(seq) ? imKeyedIter : seq?.[SEQ_INFO] ?? unkIter;
13635
- }
13636
13650
  renderTag(tag, attrs, childs) {
13637
13651
  return h(tag, attrs, childs);
13638
13652
  }
@@ -13690,7 +13704,7 @@ class Renderer {
13690
13704
  const { seq, filter, loopWith } = iterInfo.eval(stack);
13691
13705
  const r = [];
13692
13706
  const iterData = loopWith.call(stack.it, seq);
13693
- this.getSeqInfo(seq)(seq, (key, value, attrName) => {
13707
+ getSeqInfo(seq)(seq, (key, value, attrName) => {
13694
13708
  if (filter.call(stack.it, key, value, iterData)) {
13695
13709
  const dom = this.renderIt(stack.enter(value, { key }, true), nodeId, key, viewName);
13696
13710
  this.pushEachEntry(r, nodeId, attrName, key, dom);
@@ -13703,7 +13717,7 @@ class Renderer {
13703
13717
  const r = [];
13704
13718
  const it = stack.it;
13705
13719
  const iterData = loopWith.call(it, seq);
13706
- this.getSeqInfo(seq)(seq, (key, value, attrName) => {
13720
+ getSeqInfo(seq)(seq, (key, value, attrName) => {
13707
13721
  if (filter.call(it, key, value, iterData)) {
13708
13722
  const cachePath = enricher ? [it, value] : [value];
13709
13723
  const binds = { key, value };
@@ -13740,7 +13754,7 @@ class Renderer {
13740
13754
  return new VComment(`§${JSON.stringify(info)}§`);
13741
13755
  }
13742
13756
  }
13743
- var DATASET_ATTRS, imIndexedIter = (seq, visit) => {
13757
+ var DATASET_ATTRS, getSeqInfo = (seq) => isIndexed(seq) ? imIndexedIter : isKeyed(seq) ? imKeyedIter : seq?.[SEQ_INFO] ?? unkIter, imIndexedIter = (seq, visit) => {
13744
13758
  let i = 0;
13745
13759
  for (const v of seq)
13746
13760
  visit(i++, v, "si");
@@ -8510,15 +8510,13 @@ class Components {
8510
8510
  return v?.[this.getComponentSymbol]?.() ?? null;
8511
8511
  }
8512
8512
  getOnEnterFor(v) {
8513
- const comp = this.getCompFor(v);
8514
- return comp ? comp.on.stackEnter : defaultOnStackEnter;
8513
+ return this.getCompFor(v)?.on.stackEnter ?? defaultOnStackEnter;
8515
8514
  }
8516
8515
  getHandlerFor(v, name, key) {
8517
8516
  return this.getCompFor(v)?.[key][name] ?? null;
8518
8517
  }
8519
8518
  getRequestFor(v, name) {
8520
- const comp = this.getCompFor(v);
8521
- return comp ? comp.scope.lookupRequest(name) : null;
8519
+ return this.getCompFor(v)?.scope.lookupRequest(name) ?? null;
8522
8520
  }
8523
8521
  compileStyles() {
8524
8522
  const styles2 = [];
@@ -8603,8 +8601,7 @@ class DynamicAlias extends Dynamic {
8603
8601
  this.dynName = dynName;
8604
8602
  }
8605
8603
  _resolveSymbol(stack) {
8606
- const t = stack.lookupType(this.compName);
8607
- return t?.dynamic[this.dynName]?.symbol ?? null;
8604
+ return stack.lookupType(this.compName)?.dynamic[this.dynName]?.symbol ?? null;
8608
8605
  }
8609
8606
  getSymbol(stack) {
8610
8607
  this.symbol ??= this._resolveSymbol(stack);
@@ -8652,9 +8649,8 @@ class Component {
8652
8649
  } else if (isString(dinfo?.default) && isString(dinfo?.for)) {
8653
8650
  const val = vp.parseDynamic(dinfo.default, this.views.main.ctx);
8654
8651
  const [compName, dynName] = dinfo.for.split(".");
8655
- if (isString(compName) && isString(dynName)) {
8652
+ if (isString(compName) && isString(dynName))
8656
8653
  this.dynamic[key] = new DynamicAlias(key, val, compName, dynName);
8657
- }
8658
8654
  }
8659
8655
  }
8660
8656
  for (const name in this.views)
@@ -14601,9 +14597,6 @@ class Renderer {
14601
14597
  this.comps = comps;
14602
14598
  this.cache = new WeakMapDomCache;
14603
14599
  }
14604
- getSeqInfo(seq) {
14605
- return isIndexed(seq) ? imIndexedIter : isKeyed(seq) ? imKeyedIter : seq?.[SEQ_INFO] ?? unkIter;
14606
- }
14607
14600
  renderTag(tag, attrs, childs) {
14608
14601
  return h(tag, attrs, childs);
14609
14602
  }
@@ -14661,7 +14654,7 @@ class Renderer {
14661
14654
  const { seq, filter, loopWith } = iterInfo.eval(stack);
14662
14655
  const r = [];
14663
14656
  const iterData = loopWith.call(stack.it, seq);
14664
- this.getSeqInfo(seq)(seq, (key, value, attrName) => {
14657
+ getSeqInfo(seq)(seq, (key, value, attrName) => {
14665
14658
  if (filter.call(stack.it, key, value, iterData)) {
14666
14659
  const dom = this.renderIt(stack.enter(value, { key }, true), nodeId, key, viewName);
14667
14660
  this.pushEachEntry(r, nodeId, attrName, key, dom);
@@ -14674,7 +14667,7 @@ class Renderer {
14674
14667
  const r = [];
14675
14668
  const it = stack.it;
14676
14669
  const iterData = loopWith.call(it, seq);
14677
- this.getSeqInfo(seq)(seq, (key, value, attrName) => {
14670
+ getSeqInfo(seq)(seq, (key, value, attrName) => {
14678
14671
  if (filter.call(it, key, value, iterData)) {
14679
14672
  const cachePath = enricher ? [it, value] : [value];
14680
14673
  const binds = { key, value };
@@ -14711,6 +14704,7 @@ class Renderer {
14711
14704
  return new VComment(`§${JSON.stringify(info)}§`);
14712
14705
  }
14713
14706
  }
14707
+ var getSeqInfo = (seq) => isIndexed(seq) ? imIndexedIter : isKeyed(seq) ? imKeyedIter : seq?.[SEQ_INFO] ?? unkIter;
14714
14708
  var imIndexedIter = (seq, visit) => {
14715
14709
  let i = 0;
14716
14710
  for (const v of seq)
@@ -14871,16 +14865,25 @@ function getFieldMethods(field) {
14871
14865
  name: `reset${uname}`,
14872
14866
  sig: `reset${uname}()`,
14873
14867
  desc: "Reset to default value"
14874
- },
14868
+ }
14869
+ ];
14870
+ const truthy = [
14875
14871
  {
14876
- name: `is${uname}NotSet`,
14877
- sig: `is${uname}NotSet()`,
14878
- desc: "Check if null/undefined"
14872
+ name: `is${uname}Truthy`,
14873
+ sig: `is${uname}Truthy()`,
14874
+ desc: "Check if value is truthy"
14879
14875
  },
14880
14876
  {
14881
- name: `is${uname}Set`,
14882
- sig: `is${uname}Set()`,
14883
- desc: "Check if not null/undefined"
14877
+ name: `is${uname}Falsy`,
14878
+ sig: `is${uname}Falsy()`,
14879
+ desc: "Check if value is falsy"
14880
+ }
14881
+ ];
14882
+ const nullable = [
14883
+ {
14884
+ name: `is${uname}Null`,
14885
+ sig: `is${uname}Null()`,
14886
+ desc: "Check if value is null/undefined"
14884
14887
  }
14885
14888
  ];
14886
14889
  switch (type3) {
@@ -14890,21 +14893,28 @@ function getFieldMethods(field) {
14890
14893
  name: `toggle${uname}`,
14891
14894
  sig: `toggle${uname}()`,
14892
14895
  desc: "Toggle boolean value"
14893
- });
14896
+ }, ...truthy);
14897
+ break;
14898
+ case "int":
14899
+ case "float":
14900
+ methods.push(...truthy);
14901
+ break;
14902
+ case "any":
14903
+ methods.push(...nullable, ...truthy);
14894
14904
  break;
14895
14905
  case "text":
14896
14906
  methods.push({
14897
14907
  name: `is${uname}Empty`,
14898
14908
  sig: `is${uname}Empty()`,
14899
14909
  desc: "Check if string is empty"
14900
- }, { name: `${name}Len`, sig: `${name}Len()`, desc: "Get string length" });
14910
+ }, { name: `${name}Len`, sig: `${name}Len()`, desc: "Get string length" }, ...truthy);
14901
14911
  break;
14902
14912
  case "list":
14903
14913
  methods.push({
14904
14914
  name: `is${uname}Empty`,
14905
14915
  sig: `is${uname}Empty()`,
14906
14916
  desc: "Check if list is empty"
14907
- }, { name: `${name}Len`, sig: `${name}Len()`, desc: "Get list size" }, {
14917
+ }, { name: `${name}Len`, sig: `${name}Len()`, desc: "Get list size" }, ...truthy, {
14908
14918
  name: `setIn${uname}At`,
14909
14919
  sig: `setIn${uname}At(i, v)`,
14910
14920
  desc: "Set item at index"
@@ -14945,7 +14955,7 @@ function getFieldMethods(field) {
14945
14955
  name: `${name}Len`,
14946
14956
  sig: `${name}Len()`,
14947
14957
  desc: `Get ${label} size`
14948
- }, {
14958
+ }, ...truthy, {
14949
14959
  name: `setIn${uname}At`,
14950
14960
  sig: `setIn${uname}At(key, v)`,
14951
14961
  desc: "Set value at key"
@@ -14973,7 +14983,7 @@ function getFieldMethods(field) {
14973
14983
  name: `is${uname}Empty`,
14974
14984
  sig: `is${uname}Empty()`,
14975
14985
  desc: "Check if set is empty"
14976
- }, { name: `${name}Len`, sig: `${name}Len()`, desc: "Get set size" }, {
14986
+ }, { name: `${name}Len`, sig: `${name}Len()`, desc: "Get set size" }, ...truthy, {
14977
14987
  name: `addIn${uname}`,
14978
14988
  sig: `addIn${uname}(v)`,
14979
14989
  desc: "Add value to set"
@@ -14995,6 +15005,9 @@ function getFieldMethods(field) {
14995
15005
  desc: "Toggle value in set"
14996
15006
  });
14997
15007
  break;
15008
+ default:
15009
+ methods.push(...nullable, ...truthy);
15010
+ break;
14998
15011
  }
14999
15012
  return methods;
15000
15013
  }
@@ -15036,6 +15049,51 @@ function docComponents(normalized, { name = null } = {}) {
15036
15049
  const picked = name === null ? comps : comps.filter((c) => c.name === name);
15037
15050
  return new ComponentDocs({ items: getComponentsDocs(picked) });
15038
15051
  }
15052
+ // src/util/testing.js
15053
+ var filterAlwaysTrue2 = () => true;
15054
+ var nullLoopWith2 = (seq) => ({ seq });
15055
+ var plainArrayIter = (seq, visit) => {
15056
+ for (let i = 0;i < seq.length; i++)
15057
+ visit(i, seq[i]);
15058
+ };
15059
+ var plainMapIter = (seq, visit) => {
15060
+ for (const [k, v] of seq.entries())
15061
+ visit(k, v);
15062
+ };
15063
+ function pickIter(seq) {
15064
+ if (Array.isArray(seq))
15065
+ return plainArrayIter;
15066
+ if (seq instanceof Map)
15067
+ return plainMapIter;
15068
+ return getSeqInfo(seq);
15069
+ }
15070
+ function resolveAlter(Comp, name) {
15071
+ if (name == null)
15072
+ return null;
15073
+ const fn = Comp.alter?.[name];
15074
+ if (typeof fn !== "function") {
15075
+ throw new Error(`alter handler '${name}' not found on component '${Comp.name}'`);
15076
+ }
15077
+ return fn;
15078
+ }
15079
+ function collectIterBindings(Comp, compInstance, seq, opts = {}) {
15080
+ const whenFn = resolveAlter(Comp, opts.when) ?? filterAlwaysTrue2;
15081
+ const loopWithFn = resolveAlter(Comp, opts.loopWith) ?? nullLoopWith2;
15082
+ const enrichFn = resolveAlter(Comp, opts.enrichWith);
15083
+ const it = compInstance;
15084
+ const iterData = loopWithFn.call(it, seq);
15085
+ const out = [];
15086
+ const iter = pickIter(seq);
15087
+ iter(seq, (key, value) => {
15088
+ if (!whenFn.call(it, key, value, iterData))
15089
+ return;
15090
+ const binds = { key, value };
15091
+ if (enrichFn)
15092
+ enrichFn.call(it, binds, key, value, iterData);
15093
+ out.push(binds);
15094
+ });
15095
+ return out;
15096
+ }
15039
15097
 
15040
15098
  // dev.js
15041
15099
  async function test3(opts = {}) {
@@ -15156,6 +15214,7 @@ export {
15156
15214
  component,
15157
15215
  compileClassesToStyleText,
15158
15216
  compileClassesToStyle,
15217
+ collectIterBindings,
15159
15218
  checkComponent,
15160
15219
  check,
15161
15220
  UNSUPPORTED_EXPR_SYNTAX,