@yuneta/gobj-ui 5.4.0 → 5.6.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 +114 -0
- package/dist/gobj-ui.cjs.js +562 -216
- package/dist/gobj-ui.es.js +559 -217
- package/index.js +6 -0
- package/package.json +44 -44
- package/src/c_yui_nav.js +6 -1
- package/src/c_yui_node.js +34 -2
- package/src/c_yui_service_view.js +362 -0
- package/src/c_yui_shell.css +47 -0
- package/src/c_yui_shell.js +148 -16
package/README.md
CHANGED
|
@@ -322,6 +322,61 @@ Runnable reference: the **Cards** chapter of `test-app` (four levels plus a
|
|
|
322
322
|
panel that mutates the live tree), driven by `test-app/_qa_nodetree.mjs` and
|
|
323
323
|
`test-app/_qa_extra.mjs`.
|
|
324
324
|
|
|
325
|
+
### C_YUI_SERVICE_VIEW — mounting a view that talks to a backend
|
|
326
|
+
|
|
327
|
+
A view asks the backend for data with `gobj_command(remote, …, src = itself)`,
|
|
328
|
+
and `C_IEVENT_CLI` routes the answer back with
|
|
329
|
+
`gobj_find_service(gobj_name(src))` — **which only finds registered services**.
|
|
330
|
+
Neither host creates one, so a backend-talking view mounted directly at a route
|
|
331
|
+
never receives a single answer: it sits empty while the ievent logs *"service
|
|
332
|
+
not found"* once per answer. And a route's `target.kw` is static JSON, so it
|
|
333
|
+
cannot carry the live transport pointer either.
|
|
334
|
+
|
|
335
|
+
Two shapes, because the callers are not alike:
|
|
336
|
+
|
|
337
|
+
```js
|
|
338
|
+
/* A route with NO extras: declare the host, name the view it hosts. */
|
|
339
|
+
{ gclass: "C_YUI_SERVICE_VIEW", kw: {
|
|
340
|
+
view_gclass: "C_MY_VIEW",
|
|
341
|
+
service_name: "#my-view", // UNIQUE per mount — see below
|
|
342
|
+
view_kw: { title: "…" }
|
|
343
|
+
}}
|
|
344
|
+
|
|
345
|
+
/* A wrapper that keeps its own extras (url segments into the hosted view,
|
|
346
|
+
* rebinding it when a connection drops): drop only the boilerplate. */
|
|
347
|
+
let view = yui_mount_service_view(gobj, {
|
|
348
|
+
gclass: "C_YUI_TREEDB_TOPICS",
|
|
349
|
+
name: service_name(gobj),
|
|
350
|
+
kw: {...},
|
|
351
|
+
transport: remote // already resolved (e.g. per connection);
|
|
352
|
+
}); // omit it for "__remote_service__"
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
The hosted gclass must declare the attr the transport is injected under
|
|
356
|
+
(`gobj_remote_yuno` by default), build its `$container` in `mt_create`, and flag
|
|
357
|
+
`EVF_PUBLIC_EVENT` on whatever arrives from the backend — the ievent drops
|
|
358
|
+
events that are not public.
|
|
359
|
+
|
|
360
|
+
**The service name must be unique per mount**, and a duplicate is dangerous
|
|
361
|
+
precisely because it is not fatal: gobj-js logs *"service ALREADY REGISTERED.
|
|
362
|
+
Will be UPDATED"* and **rebinds the name**, so two mounts of one route would
|
|
363
|
+
cross their answers. Derive it from the route (or from whatever else makes the
|
|
364
|
+
mount unique — a connection id, a workspace), never from the gclass alone.
|
|
365
|
+
|
|
366
|
+
> **Why the hosts do not just create services.** It would make every routed view
|
|
367
|
+
> an inter-yuno endpoint by default, against the framework's rule that only
|
|
368
|
+
> named services are; most views never talk to a backend; and the collision
|
|
369
|
+
> above would become the default failure mode. Opt-in per route instead.
|
|
370
|
+
>
|
|
371
|
+
> **Known asymmetry, deliberately left alone:** `C_YUI_SHELL` mounts a view with
|
|
372
|
+
> `gobj_create()` (a plain child) and `C_YUI_NODE` with
|
|
373
|
+
> `gobj_create_pure_child()`. The flag decides whether a gclass that consults
|
|
374
|
+
> `gobj_is_pure_child()` sends its output event straight to the parent or
|
|
375
|
+
> publishes it. Today nothing consults it *on a view* (only `c_ievent_cli` and
|
|
376
|
+
> `c_timer` do, and always about themselves), so the difference has no observed
|
|
377
|
+
> consequence — but the same view gclass does get a different flag depending on
|
|
378
|
+
> who mounted it. Align it the day it bites, with the case that bit.
|
|
379
|
+
|
|
325
380
|
### C_YUI_JSON — lazy JSON tree viewer
|
|
326
381
|
|
|
327
382
|
Indentation follows the house rule: four characters per level, plus a **guide
|
|
@@ -409,6 +464,46 @@ host can use it to toggle. The tree is a **pure child of the window**, so every
|
|
|
409
464
|
teardown path (the ✕, or the host destroying the window to toggle the entry
|
|
410
465
|
off) takes it down too.
|
|
411
466
|
|
|
467
|
+
### Toolbar badge — a count pinned to an item's icon
|
|
468
|
+
|
|
469
|
+
```js
|
|
470
|
+
/* app_config.json — seeds the FIRST paint only */
|
|
471
|
+
{ "id": "alarms", "icon": "yi-triangle-exclamation", "align": "end",
|
|
472
|
+
"aria_label": "alarms", "badge": 0,
|
|
473
|
+
"action": {"type": "navigate", "route": "/alarms"} }
|
|
474
|
+
|
|
475
|
+
/* the interface that matters: a count is a RUNTIME fact */
|
|
476
|
+
yui_shell_set_toolbar_item_badge(shell, "alarms", 3);
|
|
477
|
+
yui_shell_set_toolbar_item_badge(shell, "alarms", 0); // clears it
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
An icon-only toolbar button is a link; the badge is what makes it a
|
|
481
|
+
**signal**. Without a number, an alarm bell cannot say whether anything needs
|
|
482
|
+
you — which is the reason to look at it at all.
|
|
483
|
+
|
|
484
|
+
Rules baked in, all for the same reason (a badge that lies costs more than no
|
|
485
|
+
badge):
|
|
486
|
+
|
|
487
|
+
- **`0`, `""`, `null` and `false` all clear it.** A badge reading "0" is worse
|
|
488
|
+
than none: it draws the eye to say nothing.
|
|
489
|
+
- **Over 99 renders `99+`.** The toolbar is a fixed-width row and a four-digit
|
|
490
|
+
pill pushes its neighbours off a phone screen.
|
|
491
|
+
- **A string passes through** for the states that are not counts (`"!"`, `"…"`).
|
|
492
|
+
- **Unknown item id is a silent no-op**, so an app whose toolbar has no such
|
|
493
|
+
item does not log an error on every tick of whatever feeds the number.
|
|
494
|
+
- Writing the **same** value again touches no DOM: the badge is a
|
|
495
|
+
`role="status"` live region, and rewriting it would have a screen reader
|
|
496
|
+
announce the same number on every tick.
|
|
497
|
+
|
|
498
|
+
`role="status"` and not `aria-hidden`, deliberately: the button carries an
|
|
499
|
+
explicit `aria-label`, and an explicit label **replaces** an element's content
|
|
500
|
+
for a screen reader — a badge inside it would otherwise be silent. As its own
|
|
501
|
+
live region it is both read and announced when it changes.
|
|
502
|
+
|
|
503
|
+
> `C_YUI_NAV` items do **not** have this. Its item contract listed `badge` for a
|
|
504
|
+
> long time and nothing ever rendered it; the claim is gone. Implement it there
|
|
505
|
+
> the day a menu entry needs one.
|
|
506
|
+
|
|
412
507
|
### Modals — `yui_shell_show_modal` and the `before_close` veto
|
|
413
508
|
|
|
414
509
|
`yui_shell_show_modal(shell, $box, opts)` is the standard popup: pass
|
|
@@ -440,6 +535,25 @@ shapes, and the fix for each:
|
|
|
440
535
|
| A composed string (`` `${key} · ${t(mode)}` ``) | carries no key at all | split it: the translatable halves get their own key. (Note `createElement2` **trims** text nodes — space a `·` separator with CSS, not with spaces.) |
|
|
441
536
|
| `title` / `aria-label` set with `t()` | tooltip stuck in the old language | `data-i18n-title` / `data-i18n-aria-label` |
|
|
442
537
|
| Anything a WIDGET renders (a Tabulator header, its paginator, a formatter) | drawn once; no attribute reaches it | subscribe to the shell and re-render (below) |
|
|
538
|
+
| DOM built AFTER start up (a node's strips, a dropdown panel) | renders the raw key — indistinguishable from a MISSING key | `yui_shell_translate(shell, $el)` right after building it (below) |
|
|
539
|
+
|
|
540
|
+
**Carrying the key is not enough for the FIRST render.** A node is born holding
|
|
541
|
+
the raw English key, and the app's `refresh_language()` passes only walk what
|
|
542
|
+
already exists: the shell tree at start up, `document.body` on a language
|
|
543
|
+
switch. Anything built later — a `C_YUI_NODE` strip rendered when you walk into
|
|
544
|
+
it, a lazily-built toolbar panel — is reached by neither, so it renders the key:
|
|
545
|
+
lower-case English that never changes language, which is exactly what a missing
|
|
546
|
+
key looks like. Division of labour:
|
|
547
|
+
|
|
548
|
+
```js
|
|
549
|
+
yui_shell_translate(shell, $el); // LIBRARY-built DOM, right after building it
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
and **app view gclasses translate their own DOM** — they own a `t`, so they call
|
|
553
|
+
`refresh_language($container, t)` at the end of their build (this is why the
|
|
554
|
+
shell does not translate a mounted view: see `mount_view` in `c_yui_shell.js`).
|
|
555
|
+
`yui_shell_translate` is a no-op when the app registered no translator, so
|
|
556
|
+
behaviour is unchanged for apps that never call `yui_shell_set_translator`.
|
|
443
557
|
|
|
444
558
|
**The contract.** The app owns the locales: it switches its i18next and calls
|
|
445
559
|
|