@yuneta/gobj-ui 5.3.2 → 5.4.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.
- package/README.md +40 -1
- package/package.json +1 -1
- package/src/c_yui_node.css +19 -0
- package/src/c_yui_node.js +113 -5
- package/src/node_tree_model.js +67 -0
- package/src/node_tree_model.test.js +70 -0
package/README.md
CHANGED
|
@@ -162,7 +162,11 @@ seen*:
|
|
|
162
162
|
"content": {"gclass": "C_MY_LANDING", "kw": {}},
|
|
163
163
|
"children": [
|
|
164
164
|
{"id": "energy", "label": "Energy", "icon": "yi-bolt",
|
|
165
|
-
"projection": {
|
|
165
|
+
"projection": {
|
|
166
|
+
"index": {"layout": "cards"},
|
|
167
|
+
"chrome": [{"layout": "tabs", "show_on": ">=tablet"},
|
|
168
|
+
{"layout": "backbar", "show_on": "<tablet"}]
|
|
169
|
+
},
|
|
166
170
|
"children": [ /* … any depth … */ ]}
|
|
167
171
|
]
|
|
168
172
|
}
|
|
@@ -173,6 +177,16 @@ seen*:
|
|
|
173
177
|
icon-bar/backbar and `show_on` all work unchanged) in two modes: `index`
|
|
174
178
|
when the node is the tip of the path — the projection IS the page — and
|
|
175
179
|
`chrome` when a child is showing — the projection is the strip around it.
|
|
180
|
+
- **Chrome belongs to the node that declares it — so every branch declares its
|
|
181
|
+
own.** A node's `chrome` strip lists *that* node's children, and its backbar
|
|
182
|
+
goes back to *that* node's route (`back_route = my_route`). A branch that
|
|
183
|
+
declares no `chrome` therefore contributes no strip, and the only ← the user
|
|
184
|
+
can reach is the nearest ancestor that did declare one. Declaring the pair
|
|
185
|
+
**only at the root** is the mistake this rule exists to name: the whole
|
|
186
|
+
subtree then shows one "← root" that says the same thing at every depth,
|
|
187
|
+
instead of one ← per level going up exactly one level. Repeat the same
|
|
188
|
+
`chrome` on every branch that can have a tip below it — that is what
|
|
189
|
+
`test-app`'s `/cards` does, and what makes its ← hierarchical.
|
|
176
190
|
- **`content` and `children` are not exclusive**: a section with its own page
|
|
177
191
|
and sub-pages is one node.
|
|
178
192
|
- **The route table does not grow.** The host declares ONE route; everything
|
|
@@ -187,6 +201,30 @@ seen*:
|
|
|
187
201
|
`projection.path`) while the rest of the tree keeps its tabs. Note the
|
|
188
202
|
asymmetry that makes it a third mode and not a layout: `index` and `chrome`
|
|
189
203
|
project a node's CHILDREN; `path` projects the way in.
|
|
204
|
+
- **`nav_mode` — the three shapes as one runtime knob.** The two bullets above
|
|
205
|
+
describe what a tree *declares*; `nav_mode` is how a user *chooses* between
|
|
206
|
+
the shapes without the app rewriting anything:
|
|
207
|
+
|
|
208
|
+
| mode | what it shows | equivalent declaration |
|
|
209
|
+
|---|---|---|
|
|
210
|
+
| `"stack"` (default) | one strip per ancestor | whatever each branch declares |
|
|
211
|
+
| `"back"` | only the tip's parent, as a `← parent` | `chrome: {"layout":"backbar"}` everywhere + `chrome_depth: 1` |
|
|
212
|
+
| `"path"` | the trail as ONE line | `path: {"layout":"breadcrumb"}` on the root + `chrome_depth: 0` |
|
|
213
|
+
|
|
214
|
+
It belongs to the **root** (`yui_node_set_nav_mode(root, "path")`, or
|
|
215
|
+
`"nav_mode"` in its declaration) and the whole tree reads it from there —
|
|
216
|
+
ancestors stacking strips while a descendant drew a breadcrumb would be
|
|
217
|
+
saying the same thing twice in two languages. Set on a middle node it is
|
|
218
|
+
refused, loudly, rather than accepted and ignored.
|
|
219
|
+
|
|
220
|
+
A mode **filters the renders as they are asked for; it never rewrites what
|
|
221
|
+
the app declared.** That is what makes `"stack"` an exact restore: a branch
|
|
222
|
+
that declared `vertical` chrome comes back as `vertical`, not as the tabs a
|
|
223
|
+
canonical "stacked" shape would have imposed. The `index` projection is
|
|
224
|
+
never touched by a mode — how a node shows its own children when it IS the
|
|
225
|
+
page is not a statement about depth. Modes are per tree, so an app can run
|
|
226
|
+
`/admin` as a breadcrumb and `/alarms` as a backbar; `test-app`'s node lab
|
|
227
|
+
cycles all three on the live tree.
|
|
190
228
|
- **`chrome_depth`** caps the stacked chrome: with every ancestor painting its
|
|
191
229
|
own strip, depth N shows N-1 of them. A node declares how many its corner of
|
|
192
230
|
the tree deserves (`0` = none, omit = all), the **deepest declaration on the
|
|
@@ -199,6 +237,7 @@ seen*:
|
|
|
199
237
|
yui_node_add(node, spec, index) yui_node_remove(node, node_id)
|
|
200
238
|
yui_node_set_projection(node, proj) yui_node_set_content(node, content)
|
|
201
239
|
yui_node_set_chrome_depth(node, n) yui_node_tree_version(node)
|
|
240
|
+
yui_node_set_nav_mode(root, mode) yui_node_nav_mode(node)
|
|
202
241
|
yui_node_find(node, "energy/north") yui_node_route(node)
|
|
203
242
|
```
|
|
204
243
|
|
package/package.json
CHANGED
package/src/c_yui_node.css
CHANGED
|
@@ -41,6 +41,25 @@
|
|
|
41
41
|
flex: 0 0 auto;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/* A LINK node is the other case: its viewer IS the page, so it takes
|
|
45
|
+
* the remaining height and is BOUNDED by it — the box the stage gives
|
|
46
|
+
* a view the shell mounts, which is the whole promise of a link ("a
|
|
47
|
+
* viewer cannot tell whether the shell mounted it or a node did").
|
|
48
|
+
*
|
|
49
|
+
* Sized by content, as above, it overflows its host instead of
|
|
50
|
+
* shrinking, and a viewer that MEASURES its own box to size a canvas
|
|
51
|
+
* (C_G6_NODES_TREE and its ResizeObserver) then grows the box it just
|
|
52
|
+
* measured: observe → setSize → bigger box → observe. That gclass
|
|
53
|
+
* documents "setSize does not change the observed box, so there is no
|
|
54
|
+
* feedback loop", and it is right — as long as somebody bounds the
|
|
55
|
+
* height. Under a link, that somebody is this rule. */
|
|
56
|
+
.NODE_CONTENT_LINK {
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
flex: 1 1 auto;
|
|
60
|
+
min-height: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
44
63
|
/* The index projection (a card grid, a list…) is the page when this
|
|
45
64
|
* node is the tip: it takes the remaining height and scrolls. */
|
|
46
65
|
.NODE_INDEX {
|
package/src/c_yui_node.js
CHANGED
|
@@ -126,6 +126,10 @@ import {
|
|
|
126
126
|
child_nav_items,
|
|
127
127
|
chrome_visible,
|
|
128
128
|
normalize_spec,
|
|
129
|
+
is_nav_mode,
|
|
130
|
+
nav_mode_renders,
|
|
131
|
+
nav_mode_depth,
|
|
132
|
+
NAV_MODES,
|
|
129
133
|
} from "./node_tree_model.js";
|
|
130
134
|
|
|
131
135
|
import {
|
|
@@ -169,6 +173,7 @@ SDATA(data_type_t.DTP_JSON, "children", 0, null, "Declared child spec
|
|
|
169
173
|
|
|
170
174
|
SDATA(data_type_t.DTP_STRING, "base_route", 0, "", "ROOT only: the declared route the whole tree hangs from"),
|
|
171
175
|
SDATA(data_type_t.DTP_STRING, "tree_version", 0, "", "ROOT only: version of the tree CONTRACT (its paths are public urls)"),
|
|
176
|
+
SDATA(data_type_t.DTP_STRING, "nav_mode", 0, "stack","ROOT only: how the way in is shown — stack|back|path"),
|
|
172
177
|
|
|
173
178
|
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Root HTMLElement (shell view contract)"),
|
|
174
179
|
SDATA_END()
|
|
@@ -389,6 +394,29 @@ function tree_root_of(gobj)
|
|
|
389
394
|
}
|
|
390
395
|
}
|
|
391
396
|
|
|
397
|
+
/************************************************************
|
|
398
|
+
* The navigation mode in force for this node.
|
|
399
|
+
*
|
|
400
|
+
* It is ONE knob per tree, held by the root: a tree whose ancestors
|
|
401
|
+
* stacked strips while a descendant drew a breadcrumb would be
|
|
402
|
+
* showing the way in twice, in two languages. Descendants read it
|
|
403
|
+
* from the root rather than keeping a copy, so a node added at
|
|
404
|
+
* runtime is born in the mode the tree is already in.
|
|
405
|
+
************************************************************/
|
|
406
|
+
function nav_mode_of(gobj)
|
|
407
|
+
{
|
|
408
|
+
let mode = gobj_read_attr(tree_root_of(gobj), "nav_mode") || "stack";
|
|
409
|
+
|
|
410
|
+
if(!is_nav_mode(mode)) {
|
|
411
|
+
log_error(
|
|
412
|
+
`${GCLASS_NAME} '${node_path_str(gobj)}': unknown nav_mode ` +
|
|
413
|
+
`'${mode}' — expected one of ${NAV_MODES.join("|")}`
|
|
414
|
+
);
|
|
415
|
+
return "stack";
|
|
416
|
+
}
|
|
417
|
+
return mode;
|
|
418
|
+
}
|
|
419
|
+
|
|
392
420
|
/************************************************************
|
|
393
421
|
* Node ids from the root DOWN TO this node, root excluded
|
|
394
422
|
* (the root's own segment is already inside base_route).
|
|
@@ -494,12 +522,14 @@ function resolve_path_info(gobj, subpath, inherited)
|
|
|
494
522
|
{
|
|
495
523
|
let effective = (typeof inherited === "number") ? inherited : null;
|
|
496
524
|
|
|
525
|
+
let mode = nav_mode_of(gobj);
|
|
526
|
+
|
|
497
527
|
let own = gobj_read_attr(gobj, "chrome_depth");
|
|
498
528
|
if(typeof own === "number" && own >= 0) {
|
|
499
529
|
effective = own;
|
|
500
530
|
}
|
|
501
531
|
if(has_link(gobj)) {
|
|
502
|
-
return {distance: 0, chrome_depth: effective, chain: []};
|
|
532
|
+
return {distance: 0, chrome_depth: nav_mode_depth(effective, mode), chain: []};
|
|
503
533
|
}
|
|
504
534
|
|
|
505
535
|
/* Distance is counted in STRUCTURAL segments: the tail a viewer
|
|
@@ -524,7 +554,11 @@ function resolve_path_info(gobj, subpath, inherited)
|
|
|
524
554
|
break;
|
|
525
555
|
}
|
|
526
556
|
}
|
|
527
|
-
return {
|
|
557
|
+
return {
|
|
558
|
+
distance: distance,
|
|
559
|
+
chrome_depth: nav_mode_depth(effective, mode),
|
|
560
|
+
chain: chain
|
|
561
|
+
};
|
|
528
562
|
}
|
|
529
563
|
|
|
530
564
|
/************************************************************
|
|
@@ -575,7 +609,8 @@ function render_projection(gobj, mode, $where, active_route, active_id, zones_on
|
|
|
575
609
|
if(!priv.children.length) {
|
|
576
610
|
return;
|
|
577
611
|
}
|
|
578
|
-
let renders =
|
|
612
|
+
let renders = nav_mode_renders(gobj_read_attr(gobj, "projection"),
|
|
613
|
+
nav_mode_of(gobj), mode, priv.is_root);
|
|
579
614
|
if(!renders.length) {
|
|
580
615
|
return;
|
|
581
616
|
}
|
|
@@ -760,7 +795,11 @@ function render_self(gobj)
|
|
|
760
795
|
if(view) {
|
|
761
796
|
let $v = gobj_read_attr(view, "$container");
|
|
762
797
|
if($v) {
|
|
763
|
-
|
|
798
|
+
/* A LINK's viewer IS the page and must be BOUNDED by the
|
|
799
|
+
* body; a plain content is a header above the index and
|
|
800
|
+
* keeps its natural height. Two boxes, so two classes. */
|
|
801
|
+
let $slot = createElement2(["div", {class: has_link(gobj)
|
|
802
|
+
? "NODE_CONTENT NODE_CONTENT_LINK" : "NODE_CONTENT"}]);
|
|
764
803
|
$slot.appendChild($v);
|
|
765
804
|
priv.$body.appendChild($slot);
|
|
766
805
|
}
|
|
@@ -829,7 +868,8 @@ function render_child(gobj, child)
|
|
|
829
868
|
function render_path(gobj, $where)
|
|
830
869
|
{
|
|
831
870
|
let priv = gobj.priv;
|
|
832
|
-
let renders =
|
|
871
|
+
let renders = nav_mode_renders(gobj_read_attr(gobj, "projection"),
|
|
872
|
+
nav_mode_of(gobj), "path", priv.is_root);
|
|
833
873
|
|
|
834
874
|
if(!renders.length) {
|
|
835
875
|
return;
|
|
@@ -1324,6 +1364,54 @@ function ac_set_chrome_depth(gobj, event, kw, src)
|
|
|
1324
1364
|
return 0;
|
|
1325
1365
|
}
|
|
1326
1366
|
|
|
1367
|
+
/************************************************************
|
|
1368
|
+
* Runtime API: change how this TREE shows the way in.
|
|
1369
|
+
*
|
|
1370
|
+
* One knob for the whole tree, and deliberately root-only: the
|
|
1371
|
+
* mode answers "how deep am I and how do I get back", a question
|
|
1372
|
+
* that has one answer per tree. Set on a middle node it would
|
|
1373
|
+
* silently do nothing (descendants read the root's), and a knob
|
|
1374
|
+
* that accepts a call and ignores it is worse than one that
|
|
1375
|
+
* refuses it.
|
|
1376
|
+
*
|
|
1377
|
+
* Nothing declared is rewritten — the mode filters the renders as
|
|
1378
|
+
* they are asked for — so going back to "stack" restores exactly
|
|
1379
|
+
* what the app declared, per branch, including layouts this mode
|
|
1380
|
+
* never mentions.
|
|
1381
|
+
************************************************************/
|
|
1382
|
+
function ac_set_nav_mode(gobj, event, kw, src)
|
|
1383
|
+
{
|
|
1384
|
+
let priv = gobj.priv;
|
|
1385
|
+
let mode = kw.nav_mode || "";
|
|
1386
|
+
|
|
1387
|
+
if(!is_nav_mode(mode)) {
|
|
1388
|
+
log_error(
|
|
1389
|
+
`${GCLASS_NAME} '${node_path_str(gobj)}': unknown nav_mode ` +
|
|
1390
|
+
`'${mode}' — expected one of ${NAV_MODES.join("|")}`
|
|
1391
|
+
);
|
|
1392
|
+
return -1;
|
|
1393
|
+
}
|
|
1394
|
+
if(!priv.is_root) {
|
|
1395
|
+
log_error(
|
|
1396
|
+
`${GCLASS_NAME} '${node_path_str(gobj)}': nav_mode belongs to the ` +
|
|
1397
|
+
`tree ROOT — set it there, the whole tree reads it`
|
|
1398
|
+
);
|
|
1399
|
+
return -1;
|
|
1400
|
+
}
|
|
1401
|
+
gobj_write_attr(gobj, "nav_mode", mode);
|
|
1402
|
+
|
|
1403
|
+
/* Same reason as EV_SET_CHROME_DEPTH: what changes is the shape
|
|
1404
|
+
* of the ACTIVE PATH, whose strips are painted by the ancestors,
|
|
1405
|
+
* so the tree is asked to re-apply the current route instead of
|
|
1406
|
+
* repainting one node. */
|
|
1407
|
+
let shell = yui_shell_of(gobj);
|
|
1408
|
+
if(shell) {
|
|
1409
|
+
yui_shell_navigate(shell, gobj_read_attr(shell, "current_route") ||
|
|
1410
|
+
route_of(gobj), {replace: true});
|
|
1411
|
+
}
|
|
1412
|
+
return 0;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1327
1415
|
/************************************************************
|
|
1328
1416
|
* Runtime API: change (or set) my content.
|
|
1329
1417
|
************************************************************/
|
|
@@ -1398,6 +1486,20 @@ function yui_node_set_chrome_depth(node, depth)
|
|
|
1398
1486
|
{chrome_depth: depth}, node);
|
|
1399
1487
|
}
|
|
1400
1488
|
|
|
1489
|
+
/************************************************************
|
|
1490
|
+
* The tree's navigation mode: "stack" | "back" | "path".
|
|
1491
|
+
* Send it to the ROOT — the whole tree reads it from there.
|
|
1492
|
+
************************************************************/
|
|
1493
|
+
function yui_node_set_nav_mode(node, mode)
|
|
1494
|
+
{
|
|
1495
|
+
return gobj_send_event(node, "EV_SET_NAV_MODE", {nav_mode: mode}, node);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
function yui_node_nav_mode(node)
|
|
1499
|
+
{
|
|
1500
|
+
return gobj_read_attr(tree_root_of(node), "nav_mode") || "stack";
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1401
1503
|
/************************************************************
|
|
1402
1504
|
* Resolve a path of ids from a node ("" = the node itself).
|
|
1403
1505
|
* Returns null when any segment is missing — callers that mean
|
|
@@ -1480,6 +1582,7 @@ function create_gclass(gclass_name)
|
|
|
1480
1582
|
["EV_REMOVE_NODE", ac_remove_node, null],
|
|
1481
1583
|
["EV_SET_PROJECTION", ac_set_projection, null],
|
|
1482
1584
|
["EV_SET_CHROME_DEPTH", ac_set_chrome_depth, null],
|
|
1585
|
+
["EV_SET_NAV_MODE", ac_set_nav_mode, null],
|
|
1483
1586
|
["EV_SET_CONTENT", ac_set_content, null],
|
|
1484
1587
|
["EV_NODE_TREE_CHANGED", ac_tree_changed, null]
|
|
1485
1588
|
]],
|
|
@@ -1492,6 +1595,7 @@ function create_gclass(gclass_name)
|
|
|
1492
1595
|
["EV_REMOVE_NODE", ac_remove_node, null],
|
|
1493
1596
|
["EV_SET_PROJECTION", ac_set_projection, null],
|
|
1494
1597
|
["EV_SET_CHROME_DEPTH", ac_set_chrome_depth, null],
|
|
1598
|
+
["EV_SET_NAV_MODE", ac_set_nav_mode, null],
|
|
1495
1599
|
["EV_SET_CONTENT", ac_set_content, null],
|
|
1496
1600
|
["EV_NODE_TREE_CHANGED", ac_tree_changed, null]
|
|
1497
1601
|
]],
|
|
@@ -1504,6 +1608,7 @@ function create_gclass(gclass_name)
|
|
|
1504
1608
|
["EV_REMOVE_NODE", ac_remove_node, null],
|
|
1505
1609
|
["EV_SET_PROJECTION", ac_set_projection, null],
|
|
1506
1610
|
["EV_SET_CHROME_DEPTH", ac_set_chrome_depth, null],
|
|
1611
|
+
["EV_SET_NAV_MODE", ac_set_nav_mode, null],
|
|
1507
1612
|
["EV_SET_CONTENT", ac_set_content, null],
|
|
1508
1613
|
["EV_NODE_TREE_CHANGED", ac_tree_changed, null]
|
|
1509
1614
|
]]
|
|
@@ -1521,6 +1626,7 @@ function create_gclass(gclass_name)
|
|
|
1521
1626
|
["EV_REMOVE_NODE", 0],
|
|
1522
1627
|
["EV_SET_PROJECTION", 0],
|
|
1523
1628
|
["EV_SET_CHROME_DEPTH", 0],
|
|
1629
|
+
["EV_SET_NAV_MODE", 0],
|
|
1524
1630
|
["EV_SET_CONTENT", 0],
|
|
1525
1631
|
["EV_NODE_TREE_CHANGED", 0]
|
|
1526
1632
|
];
|
|
@@ -1561,6 +1667,8 @@ export {
|
|
|
1561
1667
|
yui_node_set_projection,
|
|
1562
1668
|
yui_node_set_content,
|
|
1563
1669
|
yui_node_set_chrome_depth,
|
|
1670
|
+
yui_node_set_nav_mode,
|
|
1671
|
+
yui_node_nav_mode,
|
|
1564
1672
|
yui_node_find,
|
|
1565
1673
|
yui_node_route,
|
|
1566
1674
|
yui_node_tree_version
|
package/src/node_tree_model.js
CHANGED
|
@@ -211,6 +211,73 @@ export function chrome_visible(distance, depth)
|
|
|
211
211
|
return distance <= depth;
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/************************************************************
|
|
215
|
+
* NAVIGATION MODES — the three ways a tree can show the way in.
|
|
216
|
+
*
|
|
217
|
+
* "stack" — one chrome strip per ancestor. Whatever each
|
|
218
|
+
* node declared, painted at every level.
|
|
219
|
+
* "back" — only the tip's parent, and only as a "← parent".
|
|
220
|
+
* "path" — no strips at all: the trail as ONE breadcrumb
|
|
221
|
+
* line, drawn by the root.
|
|
222
|
+
*
|
|
223
|
+
* A mode is a FILTER over what the app declared, applied when the
|
|
224
|
+
* renders are asked for — never a rewrite of the declared
|
|
225
|
+
* projections. That is what makes going back to "stack" free and
|
|
226
|
+
* exact: an app that declares `vertical` chrome gets `vertical`
|
|
227
|
+
* back, not the tabs a canonical "stack" shape would have imposed.
|
|
228
|
+
*
|
|
229
|
+
* The index projection is NEVER touched: how a node shows its own
|
|
230
|
+
* children when it IS the page is not a statement about depth.
|
|
231
|
+
************************************************************/
|
|
232
|
+
export const NAV_MODES = ["stack", "back", "path"];
|
|
233
|
+
|
|
234
|
+
export function is_nav_mode(mode)
|
|
235
|
+
{
|
|
236
|
+
return NAV_MODES.indexOf(mode) >= 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/************************************************************
|
|
240
|
+
* The renders of one projection slot under a nav mode.
|
|
241
|
+
*
|
|
242
|
+
* `is_root` only matters for "path": the trail is drawn from the
|
|
243
|
+
* tree root down to the tip whoever declares it, so exactly one
|
|
244
|
+
* node must draw it or the same line appears once per ancestor.
|
|
245
|
+
************************************************************/
|
|
246
|
+
export function nav_mode_renders(projection, mode, slot, is_root)
|
|
247
|
+
{
|
|
248
|
+
if(slot === "index" || !is_nav_mode(mode) || mode === "stack") {
|
|
249
|
+
return projection_renders(projection, slot);
|
|
250
|
+
}
|
|
251
|
+
if(mode === "back") {
|
|
252
|
+
return (slot === "chrome") ? [{layout: "backbar"}] : [];
|
|
253
|
+
}
|
|
254
|
+
/* "path" */
|
|
255
|
+
if(slot === "path") {
|
|
256
|
+
return is_root ? [{layout: "breadcrumb"}] : [];
|
|
257
|
+
}
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/************************************************************
|
|
262
|
+
* The effective chrome depth under a nav mode.
|
|
263
|
+
*
|
|
264
|
+
* "back" is exactly `chrome_depth: 1` — one strip, the parent's.
|
|
265
|
+
* "path" is `0`: the trail already says everything the strips
|
|
266
|
+
* would, and a strip under it would be the same information twice.
|
|
267
|
+
* Both override what the tree declared, because the user asked for
|
|
268
|
+
* a shape and the declaration is what they asked to change.
|
|
269
|
+
************************************************************/
|
|
270
|
+
export function nav_mode_depth(declared_depth, mode)
|
|
271
|
+
{
|
|
272
|
+
if(mode === "back") {
|
|
273
|
+
return 1;
|
|
274
|
+
}
|
|
275
|
+
if(mode === "path") {
|
|
276
|
+
return 0;
|
|
277
|
+
}
|
|
278
|
+
return declared_depth;
|
|
279
|
+
}
|
|
280
|
+
|
|
214
281
|
/************************************************************
|
|
215
282
|
* Validate + fill one node spec. Returns the normalized spec,
|
|
216
283
|
* or null when it is unusable; every rejection pushes a
|
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
projection_renders,
|
|
13
13
|
child_nav_items,
|
|
14
14
|
normalize_spec,
|
|
15
|
+
is_nav_mode,
|
|
16
|
+
nav_mode_renders,
|
|
17
|
+
nav_mode_depth,
|
|
15
18
|
} from "./node_tree_model.js";
|
|
16
19
|
|
|
17
20
|
|
|
@@ -280,3 +283,70 @@ test("a link IS the node's content — declaring both is rejected", () => {
|
|
|
280
283
|
}, errors, "/")).toBe(null);
|
|
281
284
|
expect(errors.some((e) => /both 'link' and 'content'/.test(e))).toBe(true);
|
|
282
285
|
});
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
/***************************************************************
|
|
289
|
+
* navigation modes
|
|
290
|
+
***************************************************************/
|
|
291
|
+
const DECLARED = {
|
|
292
|
+
index: {layout: "cards"},
|
|
293
|
+
chrome: [
|
|
294
|
+
{layout: "tabs", show_on: ">=tablet"},
|
|
295
|
+
{layout: "backbar", show_on: "<tablet"}
|
|
296
|
+
]
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
test("only the three modes are modes", () => {
|
|
300
|
+
expect(is_nav_mode("stack")).toBe(true);
|
|
301
|
+
expect(is_nav_mode("back")).toBe(true);
|
|
302
|
+
expect(is_nav_mode("path")).toBe(true);
|
|
303
|
+
expect(is_nav_mode("breadcrumb")).toBe(false); /* that is a LAYOUT */
|
|
304
|
+
expect(is_nav_mode("")).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("stack gives back exactly what the node declared", () => {
|
|
308
|
+
expect(nav_mode_renders(DECLARED, "stack", "chrome", true))
|
|
309
|
+
.toEqual(DECLARED.chrome);
|
|
310
|
+
expect(nav_mode_renders("vertical", "stack", "index", false))
|
|
311
|
+
.toEqual([{layout: "vertical"}]);
|
|
312
|
+
/* An unknown mode must not silently reshape a tree */
|
|
313
|
+
expect(nav_mode_renders(DECLARED, "nonsense", "chrome", true))
|
|
314
|
+
.toEqual(DECLARED.chrome);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("back replaces every chrome with one backbar, and drops the trail", () => {
|
|
318
|
+
expect(nav_mode_renders(DECLARED, "back", "chrome", true))
|
|
319
|
+
.toEqual([{layout: "backbar"}]);
|
|
320
|
+
expect(nav_mode_renders(DECLARED, "back", "chrome", false))
|
|
321
|
+
.toEqual([{layout: "backbar"}]);
|
|
322
|
+
expect(nav_mode_renders({path: {layout: "breadcrumb"}}, "back", "path", true))
|
|
323
|
+
.toEqual([]);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("path draws the trail ONCE, at the root, and no strips anywhere", () => {
|
|
327
|
+
expect(nav_mode_renders(DECLARED, "path", "path", true))
|
|
328
|
+
.toEqual([{layout: "breadcrumb"}]);
|
|
329
|
+
expect(nav_mode_renders(DECLARED, "path", "path", false)).toEqual([]);
|
|
330
|
+
expect(nav_mode_renders(DECLARED, "path", "chrome", true)).toEqual([]);
|
|
331
|
+
expect(nav_mode_renders(DECLARED, "path", "chrome", false)).toEqual([]);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test("the index projection survives every mode", () => {
|
|
335
|
+
for(const mode of ["stack", "back", "path"]) {
|
|
336
|
+
expect(nav_mode_renders(DECLARED, mode, "index", true))
|
|
337
|
+
.toEqual([{layout: "cards"}]);
|
|
338
|
+
}
|
|
339
|
+
/* and an undeclared projection keeps defaulting to cards */
|
|
340
|
+
expect(nav_mode_renders(null, "path", "index", true))
|
|
341
|
+
.toEqual([{layout: "cards"}]);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("back is depth 1 and path is depth 0, whatever the tree declared", () => {
|
|
345
|
+
expect(nav_mode_depth(null, "back")).toBe(1);
|
|
346
|
+
expect(nav_mode_depth(3, "back")).toBe(1);
|
|
347
|
+
expect(nav_mode_depth(null, "path")).toBe(0);
|
|
348
|
+
expect(nav_mode_depth(3, "path")).toBe(0);
|
|
349
|
+
/* stack keeps the declaration, unlimited included */
|
|
350
|
+
expect(nav_mode_depth(null, "stack")).toBe(null);
|
|
351
|
+
expect(nav_mode_depth(2, "stack")).toBe(2);
|
|
352
|
+
});
|