@yuneta/gobj-ui 5.2.1 → 5.3.1
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 +165 -0
- package/dist/gobj-ui.cjs.js +386 -41
- package/dist/gobj-ui.es.js +386 -41
- package/package.json +1 -1
- package/src/c_yui_json.css +23 -3
- package/src/c_yui_json.js +17 -3
- package/src/c_yui_nav.js +63 -3
- package/src/c_yui_node.css +77 -0
- package/src/c_yui_node.js +1567 -0
- package/src/c_yui_shell.css +18 -0
- package/src/c_yui_shell.js +206 -4
- package/src/c_yui_window.js +8 -3
- package/src/node_tree_model.js +463 -0
- package/src/node_tree_model.test.js +282 -0
- package/src/route_map_model.js +48 -0
- package/src/route_map_model.test.js +70 -0
- package/src/route_resolver.js +11 -0
- package/src/shell_route_map.css +36 -2
- package/src/shell_route_map.js +205 -45
- package/src/yui_dev.js +8 -3
- package/src/yui_icons.css +13 -0
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
|