@yuneta/gobj-ui 7.26.34 → 7.26.35
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 +223 -53
- package/dist/gobj-ui.cjs.js +1035 -141
- package/dist/gobj-ui.es.js +1035 -141
- package/package.json +2 -2
- package/scripts/validate-locales.mjs +672 -96
- package/src/c_yui_form.js +186 -9
- package/src/c_yui_gclass.js +2 -0
- package/src/c_yui_gobj_tree_js.js +2 -0
- package/src/c_yui_json.js +2 -0
- package/src/c_yui_json_graph.js +2 -0
- package/src/c_yui_nav.js +23 -5
- package/src/c_yui_shell.css +16 -0
- package/src/c_yui_shell.js +282 -26
- package/src/c_yui_treedb_graph.js +26 -8
- package/src/c_yui_treedb_topic_with_form.js +40 -18
- package/src/c_yui_treedb_topics.js +34 -11
- package/src/c_yui_window.js +132 -32
- package/src/c_yui_window_manager.js +156 -44
- package/src/form_json_editor_names.js +448 -0
- package/src/form_json_editor_names.wiring.test.js +595 -0
- package/src/form_label_i18n.wiring.test.js +169 -0
- package/src/i18n_key_functions.js +14 -6
- package/src/i18n_key_functions.test.js +124 -2
- package/src/lib_maplibre.js +26 -2
- package/src/lib_maplibre_relocalize.test.js +78 -0
- package/src/named_controls.wiring.test.js +468 -0
- package/src/nav_cards_helpers.js +9 -1
- package/src/nav_item_badge.wiring.test.js +182 -1
- package/src/shell_route_map.js +25 -8
- package/src/table_editor_name.wiring.test.js +201 -0
- package/src/treedb_column_title.wiring.test.js +151 -0
- package/src/treedb_json_drill.wiring.test.js +41 -1
- package/src/window_layer.wiring.test.js +308 -43
- package/src/window_show.wiring.test.js +6 -2
- package/src/window_stacking.wiring.test.js +334 -0
- package/src/yui_badge.js +58 -13
- package/src/yui_badge.test.js +40 -3
- package/src/yui_dev.js +2 -1
- package/src/yui_file_field.js +2 -0
- package/src/yui_frontend_view.js +2 -1
- package/src/yui_gclass_view.js +9 -6
- package/src/yui_tabulator_i18n.js +42 -0
- package/src/yui_tabulator_i18n.test.js +48 -1
|
@@ -50,6 +50,13 @@ import {
|
|
|
50
50
|
|
|
51
51
|
import {t} from "i18next";
|
|
52
52
|
import {text_node} from "./yui_text.js";
|
|
53
|
+
import {
|
|
54
|
+
yui_shell_of,
|
|
55
|
+
yui_shell_raise_window,
|
|
56
|
+
yui_shell_watch_changes,
|
|
57
|
+
yui_shell_unwatch_changes,
|
|
58
|
+
} from "./c_yui_shell.js";
|
|
59
|
+
import {yui_shell_popup_layer} from "./shell_modals.js";
|
|
53
60
|
|
|
54
61
|
|
|
55
62
|
/***************************************************************
|
|
@@ -57,13 +64,18 @@ import {text_node} from "./yui_text.js";
|
|
|
57
64
|
***************************************************************/
|
|
58
65
|
const GCLASS_NAME = "C_YUI_WINDOW_MANAGER";
|
|
59
66
|
|
|
60
|
-
/*
|
|
61
|
-
*
|
|
67
|
+
/* The floating dock sits at the top of the layer it lives in
|
|
68
|
+
* (dock_place()), the shell's popup layer: every window is in the
|
|
69
|
+
* window layer below it, whatever number it takes, and the shell's
|
|
70
|
+
* dialogs and toasts are layers above. */
|
|
62
71
|
const DOCK_Z = 1000000;
|
|
63
72
|
|
|
64
73
|
/* Chip close glyph (currentColor → theme-aware). */
|
|
65
74
|
const WC_X = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M4.5 4.5 L11.5 11.5 M11.5 4.5 L4.5 11.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>';
|
|
66
75
|
|
|
76
|
+
/* Unique ids for the label spans a chip is named by (aria-labelledby). */
|
|
77
|
+
let __chip_seq__ = 0;
|
|
78
|
+
|
|
67
79
|
|
|
68
80
|
/***************************************************************
|
|
69
81
|
* Data
|
|
@@ -74,14 +86,13 @@ SDATA(data_type_t.DTP_STRING, "dock_mode", 0, "floating", "floatin
|
|
|
74
86
|
SDATA(data_type_t.DTP_STRING, "dock_corner", 0, "bottom-left", "Floating placement: top-left | top-right | bottom-left | bottom-right"),
|
|
75
87
|
SDATA(data_type_t.DTP_STRING, "inline_selector", 0, "", "CSS selector of the container the dock mounts into when inline (e.g. a shell zone)"),
|
|
76
88
|
SDATA(data_type_t.DTP_STRING, "responsive_query", 0, "(max-width: 768px)", "Media query that selects inline placement when dock_mode is 'responsive'"),
|
|
77
|
-
SDATA(data_type_t.DTP_POINTER, "$parent", 0, null, "Where the floating dock mounts (default document.body)"),
|
|
89
|
+
SDATA(data_type_t.DTP_POINTER, "$parent", 0, null, "Where the floating dock mounts (default: the popup layer of the shell, document.body while there is none)"),
|
|
78
90
|
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Internal: the dock element"),
|
|
79
91
|
SDATA_END()
|
|
80
92
|
];
|
|
81
93
|
|
|
82
94
|
let PRIVATE_DATA = {
|
|
83
95
|
windows: null, /* [{gobj, $chip, minimized}] */
|
|
84
|
-
z: 0, /* z-order counter handed to focused windows */
|
|
85
96
|
mql: null, /* MediaQueryList when dock_mode is 'responsive' */
|
|
86
97
|
mql_handler: null, /* its change listener (kept for teardown) */
|
|
87
98
|
};
|
|
@@ -106,7 +117,6 @@ function mt_create(gobj)
|
|
|
106
117
|
let priv = gobj.priv;
|
|
107
118
|
|
|
108
119
|
priv.windows = [];
|
|
109
|
-
priv.z = 10;
|
|
110
120
|
|
|
111
121
|
/* In 'responsive' mode the placement follows a media query: watch it
|
|
112
122
|
* so the dock re-homes (floating <-> inline) when the viewport crosses
|
|
@@ -123,6 +133,13 @@ function mt_create(gobj)
|
|
|
123
133
|
|
|
124
134
|
build_dock(gobj);
|
|
125
135
|
|
|
136
|
+
/* The dock lives in a shell (its popup layer, or a zone when
|
|
137
|
+
* inline), and it outlives the shell: the app builds it once, a
|
|
138
|
+
* logout destroys the shell, the login builds another. Told when
|
|
139
|
+
* a shell is built or destroyed, it places itself again
|
|
140
|
+
* (ac_shell_changed). Unwatched in mt_destroy. */
|
|
141
|
+
yui_shell_watch_changes(gobj);
|
|
142
|
+
|
|
126
143
|
/*
|
|
127
144
|
* SERVICE subscription model
|
|
128
145
|
*/
|
|
@@ -139,6 +156,8 @@ function mt_destroy(gobj)
|
|
|
139
156
|
{
|
|
140
157
|
let priv = gobj.priv;
|
|
141
158
|
|
|
159
|
+
yui_shell_unwatch_changes(gobj);
|
|
160
|
+
|
|
142
161
|
if(priv.mql && priv.mql_handler) {
|
|
143
162
|
priv.mql.removeEventListener("change", priv.mql_handler);
|
|
144
163
|
priv.mql = null;
|
|
@@ -200,10 +219,18 @@ function ensure_dock_style()
|
|
|
200
219
|
}
|
|
201
220
|
.yui-dock-chip {
|
|
202
221
|
display: inline-flex; align-items: center; gap: 7px; font: inherit; font-size: 12px;
|
|
203
|
-
padding:
|
|
204
|
-
background: var(--bulma-scheme-main); color: var(--bulma-text);
|
|
222
|
+
padding: 0 5px 0 0; border: 1px solid var(--bulma-border); border-radius: 7px;
|
|
223
|
+
background: var(--bulma-scheme-main); color: var(--bulma-text); white-space: nowrap;
|
|
205
224
|
}
|
|
206
225
|
.yui-dock-chip:hover { border-color: var(--bulma-text-weak); }
|
|
226
|
+
/* The chip is the look; its toggle a button that adds none, and
|
|
227
|
+
* takes the pill's padding, so the whole label is what a press lands
|
|
228
|
+
* on and the chip reads as one pill with its ✕ at the end. */
|
|
229
|
+
.yui-dock-chip-toggle {
|
|
230
|
+
display: inline-flex; align-items: center; gap: 7px;
|
|
231
|
+
padding: 4px 0 4px 11px; margin: 0; border: 0; border-radius: 7px 0 0 7px;
|
|
232
|
+
background: transparent; font: inherit; color: inherit; cursor: pointer; white-space: nowrap;
|
|
233
|
+
}
|
|
207
234
|
/* The BORDER says active, not the text: blue ink on a blue tint is
|
|
208
235
|
* the same hue on itself -- 4.49:1 on the light chip, under the 4.5
|
|
209
236
|
* its 12px label needs. The label takes the text colour (11:1). */
|
|
@@ -307,12 +334,37 @@ function apply_placement(gobj)
|
|
|
307
334
|
$dock.classList.toggle(`yui-dock--${c}`, !inline && c === corner);
|
|
308
335
|
}
|
|
309
336
|
|
|
310
|
-
let target = inline ? $inline : (gobj_read_attr(gobj, "$parent") ||
|
|
337
|
+
let target = inline ? $inline : (gobj_read_attr(gobj, "$parent") || dock_place(gobj));
|
|
311
338
|
if($dock.parentNode !== target) {
|
|
312
339
|
target.appendChild($dock);
|
|
313
340
|
}
|
|
314
341
|
}
|
|
315
342
|
|
|
343
|
+
/************************************************************
|
|
344
|
+
* Where the FLOATING dock lives when the app names no
|
|
345
|
+
* `$parent`: the popup layer of the shell, at the top of it.
|
|
346
|
+
*
|
|
347
|
+
* It was document.body at z-index 1000000, and that put it
|
|
348
|
+
* above the WHOLE shell -- `.yui-shell` is a stacking context
|
|
349
|
+
* of its own, so its dialogs (modal layer, 99) and toasts (120)
|
|
350
|
+
* counted only inside it: the dock stayed clickable over a
|
|
351
|
+
* dialog's backdrop (a window could be closed from its chip
|
|
352
|
+
* while its own "all changes will be lost" question was open),
|
|
353
|
+
* and it covered a toast where they met.
|
|
354
|
+
*
|
|
355
|
+
* The popup layer, not the window layer: the dock has to stay
|
|
356
|
+
* above EVERY window, and windows live in two layers -- the
|
|
357
|
+
* window layer (no `$parent`) and the popup layer, where the
|
|
358
|
+
* site map and the JSON windows of the treedb views put theirs.
|
|
359
|
+
* Above both; below the dialogs, the toasts and the loading
|
|
360
|
+
* cover. No shell (before the first, between a logout and the
|
|
361
|
+
* next login): the body.
|
|
362
|
+
************************************************************/
|
|
363
|
+
function dock_place(gobj)
|
|
364
|
+
{
|
|
365
|
+
return yui_shell_popup_layer(yui_shell_of(gobj)) || document.body;
|
|
366
|
+
}
|
|
367
|
+
|
|
316
368
|
/************************************************************
|
|
317
369
|
* Destroy the dock element.
|
|
318
370
|
************************************************************/
|
|
@@ -416,7 +468,6 @@ function set_active(gobj, entry)
|
|
|
416
468
|
************************************************************/
|
|
417
469
|
function focus_entry(gobj, entry)
|
|
418
470
|
{
|
|
419
|
-
let priv = gobj.priv;
|
|
420
471
|
let $win = gobj_read_attr(entry.gobj, "$container");
|
|
421
472
|
if($win) {
|
|
422
473
|
if(entry.minimized) {
|
|
@@ -428,8 +479,10 @@ function focus_entry(gobj, entry)
|
|
|
428
479
|
entry.$chip.classList.remove('is-min');
|
|
429
480
|
}
|
|
430
481
|
}
|
|
431
|
-
|
|
432
|
-
|
|
482
|
+
/* The windows' ONE z-order, shared with the windows that have
|
|
483
|
+
* no dock (yui_shell_raise_window). A counter of its own put a
|
|
484
|
+
* window opened later without a dock UNDER every docked one. */
|
|
485
|
+
yui_shell_raise_window($win);
|
|
433
486
|
}
|
|
434
487
|
set_active(gobj, entry);
|
|
435
488
|
}
|
|
@@ -525,21 +578,52 @@ function ac_register_window(gobj, event, kw, src)
|
|
|
525
578
|
text_node(title)]); // data, not markup
|
|
526
579
|
}
|
|
527
580
|
|
|
581
|
+
/* The toggle's NAME is its label, read from the label's own spans
|
|
582
|
+
* (aria-labelledby): the data half as it is, the kind half from the
|
|
583
|
+
* node that carries its key -- so the name changes language with the
|
|
584
|
+
* label, which a composed aria-label could not. */
|
|
585
|
+
let chip_seq = ++__chip_seq__;
|
|
586
|
+
let label_ids = label_items.map(function(item, i) {
|
|
587
|
+
item[1].id = `yui-dock-chip-${chip_seq}-${i}`;
|
|
588
|
+
return item[1].id;
|
|
589
|
+
});
|
|
590
|
+
|
|
528
591
|
let entry = {gobj: win, $chip: null, minimized: false};
|
|
529
|
-
/*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
592
|
+
/* The chip is a CONTAINER of two sibling controls, and no control
|
|
593
|
+
* itself: the TOGGLE (minimise / bring back, the whole label) and
|
|
594
|
+
* the ✕ (EV_CLOSE_WINDOW → the window's own teardown →
|
|
595
|
+
* EV_UNREGISTER_WINDOW removes this chip). Up to 7.26.34 the chip
|
|
596
|
+
* was a role=button div HOLDING the ✕ button -- one control inside
|
|
597
|
+
* another, whose name read the label and then "close", and whose
|
|
598
|
+
* ✕ a reader could not reach as a control of its own.
|
|
599
|
+
*
|
|
600
|
+
* Both are real <button>s: a key presses them, with no handler of
|
|
601
|
+
* ours. Each says what it does with a title and an aria-label: the
|
|
602
|
+
* toggle the window's composed title (with its key as
|
|
603
|
+
* data-i18n-title / -aria-label when there is no DATA half; a
|
|
604
|
+
* reader takes the aria-labelledby, which changes language with the
|
|
605
|
+
* label), the ✕ "close". The toggle's click crosses the FSM
|
|
606
|
+
* (EV_TOGGLE_WINDOW), by the window's name: a kw is plain json. */
|
|
607
|
+
let window_name = gobj_full_name(win);
|
|
535
608
|
let $chip = createElement2(
|
|
536
|
-
['div',
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
609
|
+
['div', {class: 'DOCK_CHIP yui-dock-chip'}, [
|
|
610
|
+
['button', Object.assign(
|
|
611
|
+
{class: 'DOCK_CHIP_TOGGLE yui-dock-chip-toggle', type: 'button',
|
|
612
|
+
title: title, 'aria-label': title, 'aria-labelledby': label_ids.join(" ")},
|
|
613
|
+
(!prefix && title_key) ? {
|
|
614
|
+
'data-i18n-title': /* i18n: data */ title_key,
|
|
615
|
+
'data-i18n-aria-label': /* i18n: data */ title_key
|
|
616
|
+
} : {}
|
|
617
|
+
), [
|
|
618
|
+
chip_lead(kw.icon),
|
|
619
|
+
['span', {class: 'yui-dock-label'}, label_items]
|
|
620
|
+
], {
|
|
621
|
+
click: (evt) => {
|
|
622
|
+
evt.stopPropagation();
|
|
623
|
+
gobj_send_event(gobj, "EV_TOGGLE_WINDOW", {window: window_name}, gobj);
|
|
624
|
+
}
|
|
625
|
+
}],
|
|
626
|
+
['button', {class: 'DOCK_CHIP_CLOSE yui-dock-close', type: 'button',
|
|
543
627
|
title: t('close'), 'data-i18n-title': 'close',
|
|
544
628
|
'aria-label': t('close'), 'data-i18n-aria-label': 'close'}, html_markup(WC_X), {
|
|
545
629
|
click: (evt) => {
|
|
@@ -547,26 +631,7 @@ function ac_register_window(gobj, event, kw, src)
|
|
|
547
631
|
gobj_send_event(entry.gobj, "EV_CLOSE_WINDOW", {}, gobj);
|
|
548
632
|
}
|
|
549
633
|
}],
|
|
550
|
-
]
|
|
551
|
-
click: (evt) => {
|
|
552
|
-
evt.stopPropagation();
|
|
553
|
-
on_chip_click(gobj, entry);
|
|
554
|
-
},
|
|
555
|
-
keydown: (evt) => {
|
|
556
|
-
/* Only the chip itself — Enter on the inner ✕ button
|
|
557
|
-
* already fires its own click. */
|
|
558
|
-
if(evt.target !== evt.currentTarget) {
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
if(evt.key === "Enter" || evt.key === " ") {
|
|
562
|
-
if(evt.key === " ") {
|
|
563
|
-
evt.preventDefault();
|
|
564
|
-
}
|
|
565
|
-
evt.stopPropagation();
|
|
566
|
-
on_chip_click(gobj, entry);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}]
|
|
634
|
+
]]
|
|
570
635
|
);
|
|
571
636
|
entry.$chip = $chip;
|
|
572
637
|
priv.windows.push(entry);
|
|
@@ -635,6 +700,49 @@ function ac_focus_window(gobj, event, kw, src)
|
|
|
635
700
|
return 0;
|
|
636
701
|
}
|
|
637
702
|
|
|
703
|
+
/************************************************************
|
|
704
|
+
* EV_TOGGLE_WINDOW { window } -- the toggle of a chip of THIS
|
|
705
|
+
* dock was pressed: bring a minimized or covered window to the
|
|
706
|
+
* front, send the one in front to the dock. Only the dock's own
|
|
707
|
+
* chip sends it (src is the dock): the events a window sends
|
|
708
|
+
* about itself come from the window, and this one from the chip
|
|
709
|
+
* that names it.
|
|
710
|
+
************************************************************/
|
|
711
|
+
function ac_toggle_window(gobj, event, kw, src)
|
|
712
|
+
{
|
|
713
|
+
let priv = gobj.priv;
|
|
714
|
+
|
|
715
|
+
if(src !== gobj) {
|
|
716
|
+
log_error(
|
|
717
|
+
`${gobj_short_name(gobj)}: ${event} comes from the dock's own chip, ` +
|
|
718
|
+
`not from ${is_gobj(src) ? gobj_full_name(src) : String(src)}`
|
|
719
|
+
);
|
|
720
|
+
return -1;
|
|
721
|
+
}
|
|
722
|
+
let name = kw && kw.window;
|
|
723
|
+
let entry = priv.windows.find((e) => gobj_full_name(e.gobj) === name);
|
|
724
|
+
if(!entry) {
|
|
725
|
+
log_error(`${gobj_short_name(gobj)}: ${event}: no window '${name}' in the dock`);
|
|
726
|
+
return -1;
|
|
727
|
+
}
|
|
728
|
+
on_chip_click(gobj, entry);
|
|
729
|
+
return 0;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/************************************************************
|
|
733
|
+
* EV_SHELL_CHANGED -- a shell was built, or is being destroyed
|
|
734
|
+
* (yui_shell_watch_changes()). The dock's place -- the shell's
|
|
735
|
+
* popup layer, or its zone when inline -- may have gone with
|
|
736
|
+
* it, or be in the new one: place it again. Up to 7.26.34 an
|
|
737
|
+
* inline dock stayed in the destroyed shell's zone after a
|
|
738
|
+
* logout, detached, until a window registered.
|
|
739
|
+
************************************************************/
|
|
740
|
+
function ac_shell_changed(gobj, event, kw, src)
|
|
741
|
+
{
|
|
742
|
+
apply_placement(gobj);
|
|
743
|
+
return 0;
|
|
744
|
+
}
|
|
745
|
+
|
|
638
746
|
|
|
639
747
|
|
|
640
748
|
|
|
@@ -672,6 +780,8 @@ function create_gclass(gclass_name)
|
|
|
672
780
|
["EV_UNREGISTER_WINDOW", ac_unregister_window, null],
|
|
673
781
|
["EV_MINIMIZE_WINDOW", ac_minimize_window, null],
|
|
674
782
|
["EV_FOCUS_WINDOW", ac_focus_window, null],
|
|
783
|
+
["EV_TOGGLE_WINDOW", ac_toggle_window, null],
|
|
784
|
+
["EV_SHELL_CHANGED", ac_shell_changed, null],
|
|
675
785
|
]]
|
|
676
786
|
];
|
|
677
787
|
|
|
@@ -683,6 +793,8 @@ function create_gclass(gclass_name)
|
|
|
683
793
|
["EV_UNREGISTER_WINDOW", 0],
|
|
684
794
|
["EV_MINIMIZE_WINDOW", 0],
|
|
685
795
|
["EV_FOCUS_WINDOW", 0],
|
|
796
|
+
["EV_TOGGLE_WINDOW", 0],
|
|
797
|
+
["EV_SHELL_CHANGED", 0],
|
|
686
798
|
];
|
|
687
799
|
|
|
688
800
|
__gclass__ = gclass_create(
|