@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/src/c_yui_shell.js
CHANGED
|
@@ -1542,10 +1542,8 @@ function build_view_gobj(gobj, entry, route, stage)
|
|
|
1542
1542
|
* (synthesized "cards" C_YUI_NAV) is SHELL-owned DOM built after
|
|
1543
1543
|
* the host's one-shot refresh_language — apply the registered
|
|
1544
1544
|
* translator, same policy as lazily-built dropdown panels. */
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
target.gclass === "C_YUI_NAV") {
|
|
1548
|
-
refresh_language($view, priv.translator);
|
|
1545
|
+
if(target.gclass === "C_YUI_NAV") {
|
|
1546
|
+
yui_shell_translate(gobj, $view);
|
|
1549
1547
|
}
|
|
1550
1548
|
return view;
|
|
1551
1549
|
}
|
|
@@ -1643,6 +1641,51 @@ function build_toolbar(gobj, config)
|
|
|
1643
1641
|
$zone.appendChild($bar);
|
|
1644
1642
|
}
|
|
1645
1643
|
|
|
1644
|
+
/************************************************************
|
|
1645
|
+
* The badge node of a toolbar item — the little count pinned to
|
|
1646
|
+
* the corner of its icon (unread notifications, active alarms).
|
|
1647
|
+
*
|
|
1648
|
+
* ALWAYS rendered, hidden when empty, so the runtime setter has a
|
|
1649
|
+
* node to write into and does not have to rebuild the button.
|
|
1650
|
+
*
|
|
1651
|
+
* EMPTY IS 0, "" AND null ALIKE: a badge reading "0" is worse than
|
|
1652
|
+
* no badge — it draws the eye to say nothing. A count is only
|
|
1653
|
+
* worth a pixel when there IS something.
|
|
1654
|
+
*
|
|
1655
|
+
* Capped at 99+: the toolbar is a fixed-width row, and a four
|
|
1656
|
+
* digit pill pushes the items next to it off a phone screen.
|
|
1657
|
+
*
|
|
1658
|
+
* role="status" and NOT aria-hidden, deliberately. The button
|
|
1659
|
+
* carries an explicit aria-label, and an explicit label REPLACES
|
|
1660
|
+
* the element's content for a screen reader — so a badge inside
|
|
1661
|
+
* it would be silent. As its own live region it is both read and
|
|
1662
|
+
* announced when the count changes, which is the whole point of a
|
|
1663
|
+
* badge that means "something needs you".
|
|
1664
|
+
************************************************************/
|
|
1665
|
+
function badge_text(value)
|
|
1666
|
+
{
|
|
1667
|
+
if(value === null || value === undefined || value === false) {
|
|
1668
|
+
return "";
|
|
1669
|
+
}
|
|
1670
|
+
if(typeof value === "number") {
|
|
1671
|
+
if(!isFinite(value) || value <= 0) {
|
|
1672
|
+
return "";
|
|
1673
|
+
}
|
|
1674
|
+
return (value > 99) ? "99+" : String(Math.floor(value));
|
|
1675
|
+
}
|
|
1676
|
+
return String(value).trim();
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
function badge_node(value)
|
|
1680
|
+
{
|
|
1681
|
+
let text = badge_text(value);
|
|
1682
|
+
let attrs = {class: "yui-toolbar-badge", role: "status"};
|
|
1683
|
+
if(!text) {
|
|
1684
|
+
attrs.hidden = "hidden";
|
|
1685
|
+
}
|
|
1686
|
+
return ["span", attrs, text];
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1646
1689
|
/************************************************************
|
|
1647
1690
|
* Renderer for the default ("action") item kind.
|
|
1648
1691
|
************************************************************/
|
|
@@ -1650,8 +1693,15 @@ function build_toolbar_action_item(gobj, it)
|
|
|
1650
1693
|
{
|
|
1651
1694
|
let children = [];
|
|
1652
1695
|
if(!empty_string(it.icon)) {
|
|
1653
|
-
|
|
1654
|
-
|
|
1696
|
+
/* The badge is anchored to the ICON, not to the button: with a
|
|
1697
|
+
* label on (desktop) the button is wide, and a badge pinned to
|
|
1698
|
+
* its corner would float far from the glyph it counts. */
|
|
1699
|
+
children.push(["span", {class: "icon yui-toolbar-icon"},
|
|
1700
|
+
[
|
|
1701
|
+
["i", {class: it.icon, "aria-hidden": "true"}],
|
|
1702
|
+
badge_node(it.badge)
|
|
1703
|
+
]
|
|
1704
|
+
]);
|
|
1655
1705
|
}
|
|
1656
1706
|
if(!empty_string(it.name)) {
|
|
1657
1707
|
children.push(["span", {class: "yui-toolbar-item-label", i18n: it.name},
|
|
@@ -2050,9 +2100,7 @@ function open_toolbar_dropdown(gobj, item, action, $trigger)
|
|
|
2050
2100
|
priv.layers.popup.appendChild($panel);
|
|
2051
2101
|
|
|
2052
2102
|
/* Translate the lazily-built panel (see the note above). */
|
|
2053
|
-
|
|
2054
|
-
refresh_language($panel, priv.translator);
|
|
2055
|
-
}
|
|
2103
|
+
yui_shell_translate(gobj, $panel);
|
|
2056
2104
|
|
|
2057
2105
|
/* Click-outside (capture-phase mousedown) closes the dropdown.
|
|
2058
2106
|
* Capture phase so a click on a sibling toolbar trigger lands
|
|
@@ -2857,10 +2905,7 @@ function yui_shell_set_submenu(shell_gobj, parent_item_id, items)
|
|
|
2857
2905
|
gobj_send_event(view, "EV_SET_ITEMS", {items: items}, shell_gobj);
|
|
2858
2906
|
/* EV_SET_ITEMS rebuilt the DOM: re-apply the translator
|
|
2859
2907
|
* (shell-owned DOM, same policy as build_view_gobj). */
|
|
2860
|
-
|
|
2861
|
-
if($view && typeof priv.translator === "function") {
|
|
2862
|
-
refresh_language($view, priv.translator);
|
|
2863
|
-
}
|
|
2908
|
+
yui_shell_translate(shell_gobj, gobj_read_attr(view, "$container"));
|
|
2864
2909
|
}
|
|
2865
2910
|
}
|
|
2866
2911
|
}
|
|
@@ -3265,9 +3310,10 @@ function yui_shell_refresh_avatars(shell_gobj)
|
|
|
3265
3310
|
* Register the host's i18n translator (a t-function:
|
|
3266
3311
|
* key => translated string). The host still translates the
|
|
3267
3312
|
* static shell tree itself via refresh_language($container, t);
|
|
3268
|
-
* this is only so the shell
|
|
3269
|
-
*
|
|
3270
|
-
*
|
|
3313
|
+
* this is only so the shell — and the library components that
|
|
3314
|
+
* build DOM under it — can translate DOM built LAZILY, after
|
|
3315
|
+
* that one-shot pass and often outside $container.
|
|
3316
|
+
* Optional: with no translator such DOM renders raw keys
|
|
3271
3317
|
* (the previous behaviour).
|
|
3272
3318
|
************************************************************/
|
|
3273
3319
|
function yui_shell_set_translator(shell_gobj, t)
|
|
@@ -3279,6 +3325,39 @@ function yui_shell_set_translator(shell_gobj, t)
|
|
|
3279
3325
|
priv.translator = (typeof t === "function") ? t : null;
|
|
3280
3326
|
}
|
|
3281
3327
|
|
|
3328
|
+
/************************************************************
|
|
3329
|
+
* Apply the registered translator to a FRESHLY BUILT subtree.
|
|
3330
|
+
*
|
|
3331
|
+
* Carrying the `i18n` key on a node is not enough for it to
|
|
3332
|
+
* render translated: the node is born holding the raw English
|
|
3333
|
+
* key, and the host's refresh_language() passes walk what
|
|
3334
|
+
* ALREADY exists — the shell tree at start up, document.body on
|
|
3335
|
+
* a language switch. Anything built after that (a dropdown
|
|
3336
|
+
* panel, a nav a node renders when you walk into it) is reached
|
|
3337
|
+
* by neither, so it renders the key: lower-case English that
|
|
3338
|
+
* never changes language, i.e. exactly what a MISSING key looks
|
|
3339
|
+
* like.
|
|
3340
|
+
*
|
|
3341
|
+
* The division of labour, unchanged: LIBRARY-built DOM is
|
|
3342
|
+
* translated through here; APP view gclasses translate their own
|
|
3343
|
+
* DOM (they own a `t` — see mount_view).
|
|
3344
|
+
*
|
|
3345
|
+
* Silent no-op with no shell or no translator: an app that never
|
|
3346
|
+
* registered one keeps the previous behaviour instead of losing
|
|
3347
|
+
* its chrome.
|
|
3348
|
+
************************************************************/
|
|
3349
|
+
function yui_shell_translate(shell_gobj, $el)
|
|
3350
|
+
{
|
|
3351
|
+
if(!$el || !shell_gobj || !is_gobj(shell_gobj)) {
|
|
3352
|
+
return;
|
|
3353
|
+
}
|
|
3354
|
+
let priv = shell_gobj.priv;
|
|
3355
|
+
if(!priv || typeof priv.translator !== "function") {
|
|
3356
|
+
return;
|
|
3357
|
+
}
|
|
3358
|
+
refresh_language($el, priv.translator);
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3282
3361
|
/************************************************************
|
|
3283
3362
|
* The app switched the language: re-translate the whole document (every
|
|
3284
3363
|
* node carrying data-i18n / data-i18n-title / data-i18n-aria-label) and
|
|
@@ -3341,6 +3420,57 @@ function yui_shell_set_toolbar_item_icon(shell_gobj, item_id, icon_class)
|
|
|
3341
3420
|
}
|
|
3342
3421
|
}
|
|
3343
3422
|
|
|
3423
|
+
/************************************************************
|
|
3424
|
+
* Set (or clear) a toolbar item's badge — the count pinned to
|
|
3425
|
+
* its icon.
|
|
3426
|
+
*
|
|
3427
|
+
* yui_shell_set_toolbar_item_badge(shell, "alarms", 3);
|
|
3428
|
+
* yui_shell_set_toolbar_item_badge(shell, "alarms", 0); // hidden
|
|
3429
|
+
*
|
|
3430
|
+
* This is the API that matters: a count is a RUNTIME fact. The
|
|
3431
|
+
* `badge` field of a toolbar item only seeds the first paint, and
|
|
3432
|
+
* an app whose number never changes did not need a badge.
|
|
3433
|
+
*
|
|
3434
|
+
* 0 / "" / null / false all clear it, and a number over 99 renders
|
|
3435
|
+
* "99+" — see badge_node(). Accepts a string for the states that
|
|
3436
|
+
* are not counts ("!", "…").
|
|
3437
|
+
*
|
|
3438
|
+
* Silent no-op on an unknown item id: a toolbar that does not
|
|
3439
|
+
* declare the item (an app without alarms) must not be an error
|
|
3440
|
+
* at every tick of whatever feeds the number.
|
|
3441
|
+
************************************************************/
|
|
3442
|
+
function yui_shell_set_toolbar_item_badge(shell_gobj, item_id, value)
|
|
3443
|
+
{
|
|
3444
|
+
if(!shell_gobj || !is_gobj(shell_gobj) || empty_string(item_id)) {
|
|
3445
|
+
return;
|
|
3446
|
+
}
|
|
3447
|
+
let $container = gobj_read_attr(shell_gobj, "$container");
|
|
3448
|
+
if(!$container) {
|
|
3449
|
+
return;
|
|
3450
|
+
}
|
|
3451
|
+
let $badge = $container.querySelector(
|
|
3452
|
+
`[data-toolbar-item-id="${item_id}"] .yui-toolbar-badge`
|
|
3453
|
+
);
|
|
3454
|
+
if(!$badge) {
|
|
3455
|
+
return;
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
let text = badge_text(value);
|
|
3459
|
+
if(!text) {
|
|
3460
|
+
$badge.textContent = "";
|
|
3461
|
+
$badge.setAttribute("hidden", "hidden");
|
|
3462
|
+
return;
|
|
3463
|
+
}
|
|
3464
|
+
/* Do not touch the DOM when nothing changed: role="status" is a
|
|
3465
|
+
* live region, and rewriting the same number would have a screen
|
|
3466
|
+
* reader announce it again on every tick. */
|
|
3467
|
+
if($badge.textContent === text && !$badge.hasAttribute("hidden")) {
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
$badge.textContent = text;
|
|
3471
|
+
$badge.removeAttribute("hidden");
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3344
3474
|
/************************************************************
|
|
3345
3475
|
* Programmatic close of any open toolbar dropdown. Useful for
|
|
3346
3476
|
* external triggers (e.g. EV_LOGOUT firing from elsewhere) that
|
|
@@ -3383,9 +3513,11 @@ export {
|
|
|
3383
3513
|
yui_shell_set_avatar_provider,
|
|
3384
3514
|
yui_shell_refresh_avatars,
|
|
3385
3515
|
yui_shell_set_translator,
|
|
3516
|
+
yui_shell_translate,
|
|
3386
3517
|
yui_shell_language_changed,
|
|
3387
3518
|
yui_shell_set_connection_state,
|
|
3388
3519
|
yui_shell_set_toolbar_item_icon,
|
|
3520
|
+
yui_shell_set_toolbar_item_badge,
|
|
3389
3521
|
yui_shell_close_dropdown,
|
|
3390
3522
|
yui_shell_set_submenu
|
|
3391
3523
|
};
|