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.
- package/README.md +1 -1
- package/dist/tutuca-cli.js +132 -58
- package/dist/tutuca-dev.ext.js +155 -83
- package/dist/tutuca-dev.js +155 -83
- package/dist/tutuca-dev.min.js +3 -3
- package/dist/tutuca-extra.ext.js +92 -72
- package/dist/tutuca-extra.js +92 -72
- package/dist/tutuca-extra.min.js +3 -3
- package/dist/tutuca.ext.js +93 -73
- package/dist/tutuca.js +92 -72
- package/dist/tutuca.min.js +3 -3
- package/package.json +1 -1
- package/skill/tutuca/SKILL.md +4 -3
- package/skill/tutuca/advanced.md +22 -18
- package/skill/tutuca/core.md +57 -2
- package/skill/tutuca/patterns/README.md +42 -0
- package/skill/tutuca/patterns/bind-text-and-attributes.md +30 -0
- package/skill/tutuca/patterns/conditional-attribute-value.md +29 -0
- package/skill/tutuca/patterns/coordinate-components.md +26 -0
- package/skill/tutuca/patterns/edit-through-a-dynamic-target.md +27 -0
- package/skill/tutuca/patterns/enrich-each-item.md +25 -0
- package/skill/tutuca/patterns/filter-a-list.md +23 -0
- package/skill/tutuca/patterns/handle-events.md +27 -0
- package/skill/tutuca/patterns/iterate-a-list.md +18 -0
- package/skill/tutuca/patterns/paginate-a-list.md +27 -0
- package/skill/tutuca/patterns/render-a-child-component.md +20 -0
- package/skill/tutuca/patterns/reuse-markup-with-macros.md +35 -0
- package/skill/tutuca/patterns/share-state-without-prop-drilling.md +30 -0
- package/skill/tutuca/patterns/show-or-hide-content.md +22 -0
- package/skill/tutuca/patterns/switch-between-views.md +26 -0
- package/skill/tutuca/patterns/tabbed-interface.md +38 -0
- package/skill/tutuca/semantics.md +2 -2
- 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 `
|
|
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 `
|
|
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,
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
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
|
-
|
|
1596
|
+
producerProvide = p;
|
|
1592
1597
|
}
|
|
1593
|
-
if (producerComp == null ||
|
|
1598
|
+
if (producerComp == null || producerProvide == null)
|
|
1594
1599
|
return null;
|
|
1595
|
-
const pi =
|
|
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
|
|
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
|
|
2425
|
-
constructor(name,
|
|
2426
|
-
|
|
2420
|
+
class LookupInfo {
|
|
2421
|
+
constructor(name, compName, provideName, val) {
|
|
2422
|
+
this.name = name;
|
|
2427
2423
|
this.compName = compName;
|
|
2428
|
-
this.
|
|
2429
|
-
|
|
2430
|
-
|
|
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
|
-
|
|
2434
|
-
this.
|
|
2435
|
-
|
|
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
|
|
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.
|
|
2463
|
-
this.
|
|
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
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
this.
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
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
|
-
|
|
2574
|
-
|
|
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).
|
|
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.
|
|
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
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
const
|
|
2597
|
-
|
|
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
|
-
|
|
2605
|
-
|
|
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
|
-
|
|
2609
|
-
|
|
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
|
|
2613
|
-
|
|
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
|
|
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
|
-
|
|
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;
|