tutuca 0.9.78 → 0.9.80

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.
Files changed (33) hide show
  1. package/README.md +1 -1
  2. package/dist/tutuca-cli.js +132 -58
  3. package/dist/tutuca-dev.ext.js +155 -83
  4. package/dist/tutuca-dev.js +155 -83
  5. package/dist/tutuca-dev.min.js +3 -3
  6. package/dist/tutuca-extra.ext.js +92 -72
  7. package/dist/tutuca-extra.js +92 -72
  8. package/dist/tutuca-extra.min.js +3 -3
  9. package/dist/tutuca.ext.js +93 -73
  10. package/dist/tutuca.js +92 -72
  11. package/dist/tutuca.min.js +3 -3
  12. package/package.json +1 -1
  13. package/skill/tutuca/SKILL.md +4 -3
  14. package/skill/tutuca/advanced.md +22 -18
  15. package/skill/tutuca/core.md +57 -2
  16. package/skill/tutuca/patterns/README.md +42 -0
  17. package/skill/tutuca/patterns/bind-text-and-attributes.md +30 -0
  18. package/skill/tutuca/patterns/conditional-attribute-value.md +29 -0
  19. package/skill/tutuca/patterns/coordinate-components.md +26 -0
  20. package/skill/tutuca/patterns/edit-through-a-dynamic-target.md +27 -0
  21. package/skill/tutuca/patterns/enrich-each-item.md +25 -0
  22. package/skill/tutuca/patterns/filter-a-list.md +23 -0
  23. package/skill/tutuca/patterns/handle-events.md +27 -0
  24. package/skill/tutuca/patterns/iterate-a-list.md +18 -0
  25. package/skill/tutuca/patterns/paginate-a-list.md +27 -0
  26. package/skill/tutuca/patterns/render-a-child-component.md +20 -0
  27. package/skill/tutuca/patterns/reuse-markup-with-macros.md +35 -0
  28. package/skill/tutuca/patterns/share-state-without-prop-drilling.md +30 -0
  29. package/skill/tutuca/patterns/show-or-hide-content.md +22 -0
  30. package/skill/tutuca/patterns/switch-between-views.md +26 -0
  31. package/skill/tutuca/patterns/tabbed-interface.md +38 -0
  32. package/skill/tutuca/semantics.md +2 -2
  33. package/skill/tutuca-source/tutuca.ext.js +93 -73
@@ -0,0 +1,38 @@
1
+ # Tabbed interface
2
+
3
+ **Problem:** build tabs — a single `currentView` field decides which panel
4
+ shows, and the active tab button is highlighted.
5
+
6
+ ```html
7
+ <div role="tablist" class="tabs">
8
+ <button
9
+ role="tab"
10
+ @if.class="equals? .currentView 'overview'"
11
+ @then="'tab tab-active'"
12
+ @else="'tab'"
13
+ @on.click="$setCurrentView 'overview'"
14
+ >Overview</button>
15
+ <button
16
+ role="tab"
17
+ @if.class="equals? .currentView 'pricing'"
18
+ @then="'tab tab-active'"
19
+ @else="'tab'"
20
+ @on.click="$setCurrentView 'pricing'"
21
+ >Pricing</button>
22
+ </div>
23
+
24
+ <div @show="equals? .currentView 'overview'">…overview…</div>
25
+ <div @show="equals? .currentView 'pricing'">…pricing…</div>
26
+ ```
27
+
28
+ ```js
29
+ fields: { currentView: "overview" }, // $setCurrentView is auto-generated
30
+ ```
31
+
32
+ One string field is the whole state machine. `equals? .currentView 'overview'`
33
+ drives both the panel's `@show` and the active-tab class via `@if.class` /
34
+ `@then` / `@else`. Tab clicks call the auto-generated setter with a
35
+ string-literal arg (`@on.click="$setCurrentView 'pricing'"`). This toggles
36
+ **sibling panels** by predicate; to swap a *component's own* rendered view
37
+ instead, see the switch-between-views recipe. The field name is yours to pick
38
+ (`tab`, `currentView`, …).
@@ -106,7 +106,7 @@ bubble handler can reply to the originator via `ctx.sendAtPath(ctx.targetPath,
106
106
  ## Dynamic-var teleporting
107
107
 
108
108
  A component rendered through `<x render="*sel">` *physically lives* at the
109
- producer that declared `dynamic: { sel: … }`, not under the consumer that
109
+ producer that declared `provide: { sel: … }`, not under the consumer that
110
110
  wrote the render. The reconstructed dispatch path keeps every intermediate
111
111
  component (so bubbling visits them), but `toTransactionPath()` teleports
112
112
  the `DynStep`: it pops the steps tagged with the marker's `interiorCids`
@@ -115,7 +115,7 @@ mutation therefore lands on the producer's data, and the consumer's view
115
115
  of it updates in lock-step. Authoring view: *Teleporting* in
116
116
  [advanced.md](./advanced.md).
117
117
 
118
- When the producer's `dynamic` value is a seq-access (`.sheets[.selId]`),
118
+ When the producer's `provide` value is a seq-access (`.sheets[.selId]`),
119
119
  the teleported steps include a `SeqAccessStep` — which is where async key
120
120
  races come from.
121
121
 
@@ -474,6 +474,7 @@ var G_BOOL = K_FIELD | K_METHOD | K_BIND | K_DYN | K_CONST;
474
474
  var G_TEXT = G_BOOL | K_STRTPL;
475
475
  var G_COMPONENT = K_FIELD | K_SEQ | K_DYN;
476
476
  var G_SEQUENCE = K_FIELD | K_DYN;
477
+ var G_PROVIDE = K_FIELD | K_SEQ;
477
478
  var G_FIELD = K_FIELD | K_METHOD | K_CONST | K_STR | K_SEQ;
478
479
  var G_VALUE = K_FIELD | K_METHOD | K_BIND | K_DYN | K_NAME | K_TYPE | K_REQUEST | K_CONST;
479
480
  var G_PRED_ARG = G_BOOL | K_STR;
@@ -592,6 +593,9 @@ class ValParser {
592
593
  parseField(s, px) {
593
594
  return this._parseSingle(s, px, G_FIELD);
594
595
  }
596
+ parseProvide(s, px) {
597
+ return this._parseSingle(s, px, G_PROVIDE);
598
+ }
595
599
  parseHandlerArg(s, px) {
596
600
  return this._parseSingle(s, px, G_HANDLER_ARG);
597
601
  }
@@ -1578,21 +1582,22 @@ function h(tagName, properties, children, namespace) {
1578
1582
  }
1579
1583
 
1580
1584
  // src/anode.js
1581
- function resolveDynProducer(comp, dynName) {
1582
- const dyn = comp?.dynamic?.[dynName];
1583
- if (dyn == null)
1584
- return null;
1585
- let producerComp, producerDyn;
1586
- if (dyn.compName != null) {
1587
- producerComp = comp.scope?.lookupComponent(dyn.compName);
1588
- producerDyn = producerComp?.dynamic?.[dyn.dynName];
1585
+ function resolveDynProducer(comp, name) {
1586
+ let producerComp, producerProvide;
1587
+ const lk = comp?.lookup?.[name];
1588
+ if (lk != null) {
1589
+ producerComp = comp.scope?.lookupComponent(lk.compName);
1590
+ producerProvide = producerComp?.provide?.[lk.provideName];
1589
1591
  } else {
1592
+ const p = comp?.provide?.[name];
1593
+ if (p == null)
1594
+ return null;
1590
1595
  producerComp = comp;
1591
- producerDyn = dyn;
1596
+ producerProvide = p;
1592
1597
  }
1593
- if (producerComp == null || producerDyn == null)
1598
+ if (producerComp == null || producerProvide == null)
1594
1599
  return null;
1595
- const pi = producerDyn.val?.toPathItem?.() ?? null;
1600
+ const pi = producerProvide.val?.toPathItem?.() ?? null;
1596
1601
  return { producerCompId: producerComp.id, producerSteps: pi ? [pi] : [] };
1597
1602
  }
1598
1603
 
@@ -2335,9 +2340,6 @@ class Components {
2335
2340
  getCompFor(v) {
2336
2341
  return v?.[this.getComponentSymbol]?.() ?? null;
2337
2342
  }
2338
- getOnEnterFor(v) {
2339
- return this.getCompFor(v)?.on.stackEnter ?? defaultOnStackEnter;
2340
- }
2341
2343
  getHandlerFor(v, name, key) {
2342
2344
  return this.getCompFor(v)?.[key][name] ?? null;
2343
2345
  }
@@ -2407,36 +2409,30 @@ class ComponentStack {
2407
2409
  }
2408
2410
  }
2409
2411
 
2410
- class Dynamic {
2412
+ class ProvideInfo {
2411
2413
  constructor(name, val, symbol) {
2412
2414
  this.name = name;
2413
2415
  this.val = val;
2414
2416
  this.symbol = symbol;
2415
2417
  }
2416
- getSymbol(_stack) {
2417
- return this.symbol;
2418
- }
2419
- evalAndBind(stack, binds) {
2420
- binds[this.getSymbol(stack)] = this.val.eval(stack);
2421
- }
2422
2418
  }
2423
2419
 
2424
- class DynamicAlias extends Dynamic {
2425
- constructor(name, val, compName, dynName) {
2426
- super(name, val, null);
2420
+ class LookupInfo {
2421
+ constructor(name, compName, provideName, val) {
2422
+ this.name = name;
2427
2423
  this.compName = compName;
2428
- this.dynName = dynName;
2429
- }
2430
- _resolveSymbol(stack) {
2431
- return stack.lookupType(this.compName)?.dynamic[this.dynName]?.symbol ?? null;
2424
+ this.provideName = provideName;
2425
+ this.val = val;
2426
+ this._sym = undefined;
2432
2427
  }
2433
- getSymbol(stack) {
2434
- this.symbol ??= this._resolveSymbol(stack);
2435
- return this.symbol;
2428
+ getProducerSymbol(stack) {
2429
+ if (this._sym === undefined)
2430
+ this._sym = stack.lookupType(this.compName)?.provide?.[this.provideName]?.symbol ?? null;
2431
+ return this._sym;
2436
2432
  }
2437
2433
  }
2438
2434
  var isString = (v) => typeof v === "string";
2439
- var _rawSpecKeys = "name view style commonStyle globalStyle input receive bubble response alter on views dynamic fields methods statics";
2435
+ var _rawSpecKeys = "name view style commonStyle globalStyle input receive bubble response alter views provide lookup fields methods statics";
2440
2436
  var KNOWN_SPEC_KEYS = new Set(_rawSpecKeys.split(" "));
2441
2437
  var _compId = 0;
2442
2438
 
@@ -2453,35 +2449,47 @@ class Component {
2453
2449
  this.bubble = o.bubble ?? {};
2454
2450
  this.response = o.response ?? {};
2455
2451
  this.alter = o.alter ?? {};
2456
- this.on = { stackEnter: o.on?.stackEnter ?? defaultOnStackEnter };
2457
2452
  for (const name in o.views ?? {}) {
2458
2453
  const v = o.views[name];
2459
2454
  const { view, style } = isString(v) ? { view: v } : v;
2460
2455
  this.views[name] = new View(name, view, style);
2461
2456
  }
2462
- this._rawDynamic = o.dynamic ?? {};
2463
- this.dynamic = {};
2457
+ this._rawProvide = o.provide ?? {};
2458
+ this._rawLookup = o.lookup ?? {};
2459
+ this.provide = {};
2460
+ this.lookup = {};
2464
2461
  this.scope = null;
2462
+ this.spec = o;
2465
2463
  this.extra = {};
2466
2464
  for (const key of Object.keys(o))
2467
2465
  if (!KNOWN_SPEC_KEYS.has(key))
2468
2466
  this.extra[key] = o[key];
2469
2467
  }
2468
+ clone() {
2469
+ return Component.fromSpec(this.spec);
2470
+ }
2470
2471
  compile(ParseContext2) {
2471
2472
  for (const name in this.views)
2472
2473
  this.views[name].compile(new ParseContext2, this.scope, this.id);
2473
- for (const key in this._rawDynamic) {
2474
- const dinfo = this._rawDynamic[key];
2475
- if (isString(dinfo)) {
2476
- const val = vp.parseField(dinfo, this.views.main.ctx);
2477
- this.dynamic[key] = new Dynamic(key, val, Symbol(key));
2478
- } else if (isString(dinfo?.default) && isString(dinfo?.for)) {
2479
- const val = vp.parseField(dinfo.default, this.views.main.ctx);
2480
- const [compName, dynName] = dinfo.for.split(".");
2481
- if (isString(compName) && isString(dynName))
2482
- this.dynamic[key] = new DynamicAlias(key, val, compName, dynName);
2483
- }
2484
- }
2474
+ const ctx = this.views.main.ctx;
2475
+ for (const key in this._rawProvide) {
2476
+ const val = vp.parseProvide(this._rawProvide[key], ctx);
2477
+ if (val)
2478
+ this.provide[key] = new ProvideInfo(key, val, Symbol(key));
2479
+ }
2480
+ for (const key in this._rawLookup) {
2481
+ const linfo = this._rawLookup[key];
2482
+ const forStr = isString(linfo) ? linfo : isString(linfo?.for) ? linfo.for : null;
2483
+ const [compName, provideName] = forStr === null ? [] : forStr.split(".");
2484
+ if (!isString(compName) || !isString(provideName))
2485
+ continue;
2486
+ const defStr = isString(linfo?.default) ? linfo.default : null;
2487
+ const val = defStr === null ? null : vp.parseField(defStr, ctx);
2488
+ this.lookup[key] = new LookupInfo(key, compName, provideName, val);
2489
+ }
2490
+ for (const key in this.lookup)
2491
+ if (this.provide[key] !== undefined)
2492
+ console.warn("name declared in both provide and lookup", this.name, key);
2485
2493
  }
2486
2494
  make(args, opts) {
2487
2495
  return this.Class.make(args, opts ?? { scope: this.scope });
@@ -2509,9 +2517,6 @@ class Component {
2509
2517
  `);
2510
2518
  }
2511
2519
  }
2512
- function defaultOnStackEnter() {
2513
- return null;
2514
- }
2515
2520
 
2516
2521
  // src/stack.js
2517
2522
  var STOP = Symbol("STOP");
@@ -2570,47 +2575,61 @@ class Stack {
2570
2575
  this.viewsId = viewsId;
2571
2576
  this.ctx = ctx;
2572
2577
  }
2573
- _enrichOnEnter() {
2574
- return this.withDynamicBinds(this.comps.getOnEnterFor(this.it).call(this.it));
2578
+ _pushProvides() {
2579
+ const provide = this.comps.getCompFor(this.it)?.provide;
2580
+ if (provide == null)
2581
+ return this;
2582
+ const dynObj = {};
2583
+ let has = false;
2584
+ for (const k in provide) {
2585
+ dynObj[provide[k].symbol] = provide[k].val.eval(this);
2586
+ has = true;
2587
+ }
2588
+ if (!has)
2589
+ return this;
2590
+ const newDynBinds = [new ObjectFrame(dynObj), this.dynBinds];
2591
+ const { comps, it, binds, views, viewsId, ctx } = this;
2592
+ return new Stack(comps, it, binds, newDynBinds, views, viewsId, ctx);
2575
2593
  }
2576
2594
  static root(comps, it, ctx) {
2577
2595
  const binds = [new BindFrame(it, { it }, true), null];
2578
2596
  const dynBinds = [new ObjectFrame({}), null];
2579
2597
  const views = ["main", null];
2580
- return new Stack(comps, it, binds, dynBinds, views, "", ctx)._enrichOnEnter();
2598
+ return new Stack(comps, it, binds, dynBinds, views, "", ctx)._pushProvides();
2581
2599
  }
2582
2600
  enter(it, bindings = {}, isFrame = true) {
2583
2601
  const { comps, binds, dynBinds, views, viewsId, ctx } = this;
2584
2602
  const newBinds = [new BindFrame(it, bindings, isFrame), binds];
2585
2603
  const stack = new Stack(comps, it, newBinds, dynBinds, views, viewsId, ctx);
2586
- return isFrame ? stack._enrichOnEnter() : stack;
2604
+ return isFrame ? stack._pushProvides() : stack;
2587
2605
  }
2588
2606
  pushViewName(name) {
2589
2607
  const { comps, it, binds, dynBinds, views, ctx } = this;
2590
2608
  const newViews = [name, views];
2591
2609
  return new Stack(comps, it, binds, dynBinds, newViews, computeViewsId(newViews), ctx);
2592
2610
  }
2593
- withDynamicBinds(dynamics) {
2594
- if (dynamics == null || dynamics.length === 0)
2595
- return this;
2596
- const dynObj = {};
2597
- const comp = this.comps.getCompFor(this.it);
2598
- for (const dynName of dynamics)
2599
- comp.dynamic[dynName].evalAndBind(this, dynObj);
2600
- const newDynBinds = [new ObjectFrame(dynObj), this.dynBinds];
2601
- const { comps, it, binds, views, viewsId, ctx } = this;
2602
- return new Stack(comps, it, binds, newDynBinds, views, viewsId, ctx);
2611
+ _pushDynBindValuesToArray(arr, comp) {
2612
+ for (const k in comp.provide)
2613
+ arr.push(this._lookupProvide(comp.provide[k]));
2614
+ for (const k in comp.lookup)
2615
+ arr.push(this._lookupAlias(comp.lookup[k]));
2603
2616
  }
2604
- _pushDynBindValuesToArray(arr, dyns) {
2605
- for (const k in dyns)
2606
- arr.push(this._lookupDynamicWithDynVal(dyns[k]));
2617
+ _lookupProvide(p) {
2618
+ return lookup(this.dynBinds, p.symbol) ?? p.val.eval(this) ?? null;
2607
2619
  }
2608
- _lookupDynamicWithDynVal(d) {
2609
- return lookup(this.dynBinds, d.getSymbol(this)) ?? d.val?.eval(this) ?? null;
2620
+ _lookupAlias(lk) {
2621
+ const sym = lk.getProducerSymbol(this);
2622
+ return (sym != null ? lookup(this.dynBinds, sym) : null) ?? lk.val?.eval(this) ?? null;
2610
2623
  }
2611
2624
  lookupDynamic(name) {
2612
- const d = this.comps.getCompFor(this.it)?.dynamic[name];
2613
- return d ? this._lookupDynamicWithDynVal(d) : null;
2625
+ const comp = this.comps.getCompFor(this.it);
2626
+ if (comp == null)
2627
+ return null;
2628
+ const lk = comp.lookup[name];
2629
+ if (lk !== undefined)
2630
+ return this._lookupAlias(lk);
2631
+ const p = comp.provide[name];
2632
+ return p !== undefined ? this._lookupProvide(p) : null;
2614
2633
  }
2615
2634
  lookupBind(name) {
2616
2635
  return lookup(this.binds, name);
@@ -3380,7 +3399,7 @@ class Renderer {
3380
3399
  _rValComp(stack, val, comp, node, key, viewName) {
3381
3400
  const cacheKey = `${viewName ?? stack.viewsId ?? ""}-${key}`;
3382
3401
  const cachePath = [node, val];
3383
- stack._pushDynBindValuesToArray(cachePath, comp.dynamic);
3402
+ stack._pushDynBindValuesToArray(cachePath, comp);
3384
3403
  const cachedNode = this.cache.get(cachePath, cacheKey);
3385
3404
  if (cachedNode)
3386
3405
  return cachedNode;
@@ -3920,7 +3939,8 @@ function classFromData(name, { fields = {}, methods, statics }) {
3920
3939
  b.statics(statics);
3921
3940
  return b.build();
3922
3941
  }
3923
- var component = (opts) => new Component(classFromData(opts.name, opts), opts);
3942
+ Component.fromSpec = (opts) => new Component(classFromData(opts.name, opts), opts);
3943
+ var component = (opts) => Component.fromSpec(opts);
3924
3944
 
3925
3945
  // index.js
3926
3946
  var css = String.raw;