@yuneta/gobj-ui 5.2.1 → 5.3.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 CHANGED
@@ -138,8 +138,160 @@ toggles it closed. The tree model is `yui_shell_nav_map()` /
138
138
  protocols (`yui_shell_set_sub_routes`, `yui_shell_register_event_handler`)
139
139
  live in [`ROUTING.md`](ROUTING.md).
140
140
 
141
+ ### C_YUI_NODE — navigation as a tree of gobjs (prototype)
142
+
143
+ `C_YUI_SHELL`'s menu tree is **two levels** (a primary item and its
144
+ `submenu.items`); a submenu item cannot declare a submenu of its own, so a
145
+ section with sub-sections has to flatten everything into one tab strip.
146
+
147
+ `C_YUI_NODE` is the prototype of the other model: **the gobj tree IS the
148
+ navigation tree.** A node is a gobj, the URL is the path of node ids under a
149
+ single declared `base_route`, and a parent holds *how it wants its children
150
+ seen*:
151
+
152
+ ```json
153
+ {
154
+ "gclass": "C_YUI_NODE",
155
+ "kw": {
156
+ "node_id": "cards", "base_route": "/cards",
157
+ "projection": {
158
+ "index": {"layout": "cards"},
159
+ "chrome": [{"layout": "tabs", "show_on": ">=tablet"},
160
+ {"layout": "backbar", "show_on": "<tablet"}]
161
+ },
162
+ "content": {"gclass": "C_MY_LANDING", "kw": {}},
163
+ "children": [
164
+ {"id": "energy", "label": "Energy", "icon": "yi-bolt",
165
+ "projection": {"index": {"layout": "cards"}},
166
+ "children": [ /* … any depth … */ ]}
167
+ ]
168
+ }
169
+ }
170
+ ```
171
+
172
+ - **`projection`** is a `C_YUI_NAV` render config (so cards/tabs/vertical/
173
+ icon-bar/backbar and `show_on` all work unchanged) in two modes: `index`
174
+ when the node is the tip of the path — the projection IS the page — and
175
+ `chrome` when a child is showing — the projection is the strip around it.
176
+ - **`content` and `children` are not exclusive**: a section with its own page
177
+ and sub-pages is one node.
178
+ - **The route table does not grow.** The host declares ONE route; everything
179
+ below arrives as the shell's `subpath` (ROUTING.md §4), and the tree
180
+ contributes its full shape to the site map via `yui_shell_set_sub_routes`.
181
+ - **Two ways to show depth.** Stacked chrome — one strip per ancestor —
182
+ reads well at three levels and eats the screen at five. The other way is
183
+ **`projection.path`**: the trail down to the user as ONE line
184
+ (`{"layout": "breadcrumb"}`), drawn from the tree root whichever node
185
+ declares it, each crumb a link to that level. Declared per branch, so a
186
+ deep corner can trade its strips for a breadcrumb (`chrome_depth: 0` +
187
+ `projection.path`) while the rest of the tree keeps its tabs. Note the
188
+ asymmetry that makes it a third mode and not a layout: `index` and `chrome`
189
+ project a node's CHILDREN; `path` projects the way in.
190
+ - **`chrome_depth`** caps the stacked chrome: with every ancestor painting its
191
+ own strip, depth N shows N-1 of them. A node declares how many its corner of
192
+ the tree deserves (`0` = none, omit = all), the **deepest declaration on the
193
+ path wins, and an intermediate node whose only job is to hold that number is
194
+ a legitimate node.
195
+ - **Declarative and dynamic are the same code.** The declared `children` attr
196
+ is fed to the same `EV_ADD_NODE` the runtime API uses:
197
+
198
+ ```js
199
+ yui_node_add(node, spec, index) yui_node_remove(node, node_id)
200
+ yui_node_set_projection(node, proj) yui_node_set_content(node, content)
201
+ yui_node_set_chrome_depth(node, n) yui_node_tree_version(node)
202
+ yui_node_find(node, "energy/north") yui_node_route(node)
203
+ ```
204
+
205
+ A node added at runtime is deep-linkable like one declared at boot. Removing
206
+ the branch the user is standing on moves them to the nearest living ancestor
207
+ (`replace`, logged) — with a live tree the ground can disappear under a
208
+ bookmark.
209
+
210
+ Every move goes through the URL: a projection click publishes
211
+ `EV_NAV_CLICKED`, the node turns it into a push navigation, and the shell's
212
+ `EV_ROUTE_CHANGED` walks back down the tree as `EV_ACTIVATE`. Back, Forward,
213
+ F5 and deep links are therefore correct by construction.
214
+
215
+ **The root can be a node too** — `config.shell.tree`. Declared there, the
216
+ shell stops owning the menu and keeps only the **space** (zones, layers,
217
+ stages, toolbar, overlays, theme, breakpoints): the root node's children are
218
+ the app's primary options, and it projects them into zones instead of into its
219
+ own body.
220
+
221
+ ```json
222
+ "shell": {
223
+ "zones": {"top": {"host": "toolbar"}, "left": {"show_on": ">=desktop"},
224
+ "bottom": {"show_on": "<desktop"}, "center": {"host": "stage.main"}},
225
+ "stages": {"main": {"zone": "center", "default_route": "/"}},
226
+ "tree": {
227
+ "base_route": "/", "stage": "main",
228
+ "projection": {
229
+ "index": [{"zone": "left", "layout": "vertical"},
230
+ {"zone": "bottom", "layout": "icon-bar"}],
231
+ "chrome": [{"zone": "left", "layout": "vertical"},
232
+ {"zone": "bottom", "layout": "icon-bar"}]
233
+ },
234
+ "children": [ /* the primary options, and everything under them */ ]
235
+ }
236
+ }
237
+ ```
238
+
239
+ Note what is NOT there: no zone declares `host: "menu.<id>"`, and there is no
240
+ `menu` block at all. A render config with a `zone` mounts through
241
+ `yui_shell_zone()` and **persists** — the rail is standing chrome, so it is
242
+ built once and told where the user is, not rebuilt per navigation.
243
+ `menu.primary.render` always was a per-zone projection; this just gives it an
244
+ owner that can hold it.
245
+
246
+ `shell.tree` synthesizes exactly ONE route entry, flagged `owns_subtree`, which
247
+ is the only case where root `/` may match as an ancestor (`route_resolver.js`).
248
+ The unknown-route diagnostic is not lost by that: it moves to the node that
249
+ actually knows the names of its children. Runnable reference:
250
+ `test-app/tree.html` (`_qa_root.mjs`), served beside `index.html` so the two
251
+ navigation models can be compared in one browser.
252
+
253
+ **Where the tree ends.** One gobj per structural node is right; one gobj per
254
+ meter reading is not. A node marks the boundary with `link` — a pointer into a
255
+ data space (a timeranger: millions of raw records, series/time, key/value) plus
256
+ the viewer suited to that shape:
257
+
258
+ ```json
259
+ {"id": "m1", "label": "Meter 1",
260
+ "link": {"kind": "tranger", "gclass": "C_MY_TRANGER_VIEW",
261
+ "kw": {"topic": "meters^north^m1"}}}
262
+ ```
263
+
264
+ A link node is always the **tip of the structure**: the url keeps going, but
265
+ its tail is handed to the viewer as `EV_ROUTE_CHANGED {base, subpath}` — the
266
+ same contract the shell gives a view (ROUTING.md §5), so a viewer cannot tell
267
+ whether the shell mounted it at a declared route or a node did, deep in a tree.
268
+ `base` is the node's canonical route, which is what the viewer builds its own
269
+ deep links from. An empty subpath means the viewer's home, which is what makes
270
+ Back out of a deep data position land on it. Below a link there are no nodes:
271
+ `link` + `children` (or `link` + `content`) is a config error, because a silent
272
+ winner in "who owns the subpath" would be the worst outcome.
273
+
274
+ **The tree is a contract, not runtime state.** Once published, a node's path
275
+ is a url a client may have bookmarked, scripted, or been sold as another door
276
+ into the system. So there is deliberately **no reparent/move API**: the shape
277
+ is versioned (`tree_version` on the root, `yui_node_tree_version()`), and a
278
+ rename migrates through `aliases` — the former id keeps resolving and the URL
279
+ is rewritten (replace) to the canonical spelling, the same shape as an HTTP
280
+ 301. Anything a version bump cannot cover is a new tree, declared as such.
281
+
282
+ Runnable reference: the **Cards** chapter of `test-app` (four levels plus a
283
+ panel that mutates the live tree), driven by `test-app/_qa_nodetree.mjs` and
284
+ `test-app/_qa_extra.mjs`.
285
+
141
286
  ### C_YUI_JSON — lazy JSON tree viewer
142
287
 
288
+ Indentation follows the house rule: four characters per level, plus a **guide
289
+ line per ancestor**. The rows are siblings with growing padding rather than
290
+ nested boxes, so the guides are painted as a repeating gradient bounded to
291
+ each row's own indentation (`background-size` set per row) — which is why the
292
+ hover state must set `background-color`, never the `background` shorthand, or
293
+ the guides vanish under the cursor.
294
+
143
295
  A container-agnostic viewer (like `C_YUI_PAGER`): it owns only a toolbar +
144
296
  scrollable tree body and exposes a `$container` the parent mounts wherever it
145
297
  wants (a `C_YUI_WINDOW` body, a `yui_shell_show_modal` card, or inline). It is
@@ -371,6 +523,19 @@ handlers re-run on their own (which is why a component rarely needs a dedicated
371
523
  fires **no** `input` event — a value loaded into the form, or `readonly` toggled
372
524
  by the form mode. No-op on an input that never got a clear.
373
525
 
526
+ ### Indentation is always FOUR spaces
527
+
528
+ Anywhere structure is shown as indentation — the site map's tree, `C_YUI_JSON`
529
+ and the raw dump behind it, any `JSON.stringify` a view puts on screen — one
530
+ level is **four** characters. Not two here and four there: the reader is using
531
+ the indentation to see the shape, and a shape that changes width between two
532
+ panels of the same app is one more thing to decode.
533
+
534
+ - `JSON.stringify(value, null, 4)` — never `2`.
535
+ - Rendered trees indent in **`ch`** (`padding-left: 4ch`), not `rem`: it
536
+ follows the row's own monospace font, so the guides stay lined up with the
537
+ text they belong to instead of drifting at some zoom level.
538
+
374
539
  ### Logical class names on important DOM blocks
375
540
 
376
541
  When a gclass builds DOM, tag its elements so the tree is self-describing in