@yuneta/gobj-ui 2.6.0 → 4.0.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 +331 -11
- package/dist/gobj-ui.cjs.js +21386 -17919
- package/dist/gobj-ui.es.js +26059 -22613
- package/index.js +45 -9
- package/package.json +4 -4
- package/src/c_g6_nodes_tree.js +128 -40
- package/src/c_yui_form.css +9 -0
- package/src/c_yui_form.js +113 -53
- package/src/c_yui_gobj_tree_js.js +58 -36
- package/src/c_yui_json.css +139 -0
- package/src/c_yui_json.js +928 -0
- package/src/c_yui_json_graph.js +110 -20
- package/src/c_yui_map.js +18 -36
- package/src/c_yui_nav.js +6 -9
- package/src/c_yui_period.css +184 -0
- package/src/c_yui_period.js +1441 -0
- package/src/c_yui_shell.css +120 -0
- package/src/c_yui_shell.js +672 -111
- package/src/c_yui_treedb_graph.js +639 -37
- package/src/c_yui_treedb_schema.js +478 -0
- package/src/c_yui_treedb_topic_with_form.css +18 -0
- package/src/c_yui_treedb_topic_with_form.js +153 -103
- package/src/c_yui_treedb_topics.css +73 -0
- package/src/c_yui_treedb_topics.js +1070 -29
- package/src/c_yui_window.css +22 -0
- package/src/c_yui_window.js +182 -97
- package/src/c_yui_window_manager.js +38 -4
- package/src/json_view_helpers.js +214 -0
- package/src/json_view_helpers.test.js +135 -0
- package/src/route_map_model.js +317 -0
- package/src/route_map_model.test.js +209 -0
- package/src/route_resolver.js +24 -1
- package/src/route_resolver.test.js +40 -1
- package/src/shell_modals.js +162 -52
- package/src/shell_route_map.css +225 -0
- package/src/shell_route_map.js +363 -0
- package/src/tabulator.css +245 -0
- package/src/yui_dev.js +130 -8
- package/src/yui_frontend_view.js +106 -0
- package/src/yui_icons.css +62 -0
- package/src/yui_inputs.css +8 -3
- package/src/yui_inputs.js +51 -8
- package/src/yui_tabulator_i18n.js +128 -0
- package/src/yui_theme.js +147 -0
- package/src/yui_time.js +666 -0
- package/src/yui_time.test.js +330 -0
- package/src/yui_toolbar.css +28 -0
- package/src/yui_toolbar.js +86 -43
- package/src/c_yui_main.css +0 -501
- package/src/c_yui_main.js +0 -1623
- package/src/c_yui_routing.css +0 -18
- package/src/c_yui_routing.js +0 -837
- package/src/c_yui_tabs.js +0 -487
- package/src/themes.js +0 -215
- package/src/ytable.css +0 -63
- package/src/ytable.js +0 -505
package/src/shell_modals.js
CHANGED
|
@@ -41,6 +41,8 @@ import {
|
|
|
41
41
|
import {
|
|
42
42
|
yui_shell_push_escape,
|
|
43
43
|
yui_shell_pop_escape,
|
|
44
|
+
yui_shell_register_overlay,
|
|
45
|
+
yui_shell_overlay_dismissed,
|
|
44
46
|
} from "./c_yui_shell.js";
|
|
45
47
|
|
|
46
48
|
|
|
@@ -52,7 +54,7 @@ function notification_layer(shell)
|
|
|
52
54
|
if(!shell) {
|
|
53
55
|
return null;
|
|
54
56
|
}
|
|
55
|
-
let priv =
|
|
57
|
+
let priv = shell.priv;
|
|
56
58
|
return priv && priv.layers && priv.layers.notification;
|
|
57
59
|
}
|
|
58
60
|
|
|
@@ -61,7 +63,7 @@ function modal_layer(shell)
|
|
|
61
63
|
if(!shell) {
|
|
62
64
|
return null;
|
|
63
65
|
}
|
|
64
|
-
let priv =
|
|
66
|
+
let priv = shell.priv;
|
|
65
67
|
return priv && priv.layers && priv.layers.modal;
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -75,7 +77,7 @@ export function yui_shell_popup_layer(shell)
|
|
|
75
77
|
if(!shell) {
|
|
76
78
|
return null;
|
|
77
79
|
}
|
|
78
|
-
let priv =
|
|
80
|
+
let priv = shell.priv;
|
|
79
81
|
return (priv && priv.layers && priv.layers.popup) || null;
|
|
80
82
|
}
|
|
81
83
|
|
|
@@ -119,11 +121,11 @@ function show_notification(shell, kind, message, opts)
|
|
|
119
121
|
? {i18n: message}
|
|
120
122
|
: {};
|
|
121
123
|
let $note = createElement2(
|
|
122
|
-
["div", {class: `notification yui-notification is-${kind} is-light`,
|
|
124
|
+
["div", {class: `TOAST notification yui-notification is-${kind} is-light`,
|
|
123
125
|
role: kind === "danger" ? "alert" : "status"},
|
|
124
126
|
[
|
|
125
|
-
["button", {class: "delete", "aria-label": "close"}],
|
|
126
|
-
["p", p_attrs, message]
|
|
127
|
+
["button", {class: "TOAST_CLOSE delete", "aria-label": "close"}],
|
|
128
|
+
["p", {...p_attrs, class: "TOAST_MSG"}, message]
|
|
127
129
|
]
|
|
128
130
|
]
|
|
129
131
|
);
|
|
@@ -205,6 +207,14 @@ export function yui_shell_show_modal(shell, content, opts)
|
|
|
205
207
|
* history.back()), so gobj-ui stays routing-agnostic. */
|
|
206
208
|
let dialog = !!(opts && opts.dialog);
|
|
207
209
|
let title = (opts && opts.title) || "";
|
|
210
|
+
/* Optional DATA half of the title (a topic/service name), never
|
|
211
|
+
* translated, shown before `title`. Same contract as C_YUI_WINDOW's
|
|
212
|
+
* `title_prefix`, and for the same reason: composing
|
|
213
|
+
* `${topic} · ${t("keys")}` into `title` yields a string that is not
|
|
214
|
+
* an i18n key, so it never re-translates. Split, only the kind half
|
|
215
|
+
* carries a key. The separator is CSS — createElement2 trims text
|
|
216
|
+
* nodes and would eat the spaces around it. */
|
|
217
|
+
let title_prefix = (opts && opts.title_prefix) || "";
|
|
208
218
|
|
|
209
219
|
/* The external Bulma `.modal-close is-large` sits at the
|
|
210
220
|
* top-right of the viewport, outside the content box. Callers
|
|
@@ -214,37 +224,47 @@ export function yui_shell_show_modal(shell, content, opts)
|
|
|
214
224
|
let with_close = !(opts && opts.with_close_button === false);
|
|
215
225
|
let modal_children;
|
|
216
226
|
if(dialog) {
|
|
217
|
-
let header = ["div", {class: "yui-dialog-header"}, [
|
|
218
|
-
["button", {class: "yui-dialog-back", type: "button", "aria-label": "back"},
|
|
227
|
+
let header = ["div", {class: "MODAL_HEADER yui-dialog-header"}, [
|
|
228
|
+
["button", {class: "MODAL_BACK yui-dialog-back", type: "button", "aria-label": "back"},
|
|
219
229
|
[["i", {class: "yi-arrow-left"}]]],
|
|
220
|
-
["span", {class: "yui-dialog-title"
|
|
221
|
-
|
|
230
|
+
["span", {class: "MODAL_TITLE yui-dialog-title"},
|
|
231
|
+
(title_prefix ? [["span", {class: "MODAL_TITLE_PREFIX"}, title_prefix]] : [])
|
|
232
|
+
.concat(title
|
|
233
|
+
? [["span", {class: "MODAL_TITLE_KIND", i18n: title}, title]]
|
|
234
|
+
: [])],
|
|
235
|
+
["button", {class: "MODAL_CLOSE yui-dialog-x", type: "button", "aria-label": "close"},
|
|
222
236
|
[["i", {class: "yi-xmark"}]]],
|
|
223
237
|
]];
|
|
224
|
-
let body = ["div", {class: "yui-dialog-body"}, inner ? [inner] : []];
|
|
238
|
+
let body = ["div", {class: "MODAL_BODY yui-dialog-body"}, inner ? [inner] : []];
|
|
225
239
|
modal_children = [
|
|
226
|
-
["div", {class: "modal-background"}],
|
|
227
|
-
["div", {class: "modal-content yui-dialog-content"}, [header, body]]
|
|
240
|
+
["div", {class: "MODAL_BACKDROP modal-background"}],
|
|
241
|
+
["div", {class: "MODAL_CONTENT modal-content yui-dialog-content"}, [header, body]]
|
|
228
242
|
];
|
|
229
243
|
/* The header X replaces the external Bulma close button. */
|
|
230
244
|
with_close = false;
|
|
231
245
|
} else {
|
|
232
246
|
modal_children = [
|
|
233
|
-
["div", {class: "modal-background"}],
|
|
234
|
-
["div", {class: "modal-content"},
|
|
247
|
+
["div", {class: "MODAL_BACKDROP modal-background"}],
|
|
248
|
+
["div", {class: "MODAL_CONTENT modal-content"},
|
|
235
249
|
inner ? [inner] : []
|
|
236
250
|
]
|
|
237
251
|
];
|
|
238
252
|
if(with_close) {
|
|
239
253
|
modal_children.push(
|
|
240
|
-
["button", {class: "modal-close is-large",
|
|
254
|
+
["button", {class: "MODAL_CLOSE modal-close is-large",
|
|
241
255
|
"aria-label": "close"}]
|
|
242
256
|
);
|
|
243
257
|
}
|
|
244
258
|
}
|
|
245
259
|
|
|
260
|
+
/* `opts.logical_class`: the CALLER's UPPER_SNAKE name for THIS modal
|
|
261
|
+
* (e.g. TRANGER_KEYS_SHEET). Every modal shares the MODAL block names,
|
|
262
|
+
* only this tells one popup from another in the Inspector or a selector. */
|
|
263
|
+
let logical = (opts && opts.logical_class) || "";
|
|
264
|
+
|
|
246
265
|
let $modal = createElement2(
|
|
247
|
-
["div", {class: "
|
|
266
|
+
["div", {class: "MODAL" + (logical ? " " + logical : "") +
|
|
267
|
+
" modal yui-modal" + (dialog ? " yui-dialog" : "") + " is-active",
|
|
248
268
|
role: "dialog", "aria-modal": "true"},
|
|
249
269
|
modal_children
|
|
250
270
|
]
|
|
@@ -261,10 +281,13 @@ export function yui_shell_show_modal(shell, content, opts)
|
|
|
261
281
|
maybe_apply_translator($modal, opts);
|
|
262
282
|
|
|
263
283
|
let closed = false;
|
|
264
|
-
let close_fn = null;
|
|
265
284
|
let release_focus = null;
|
|
285
|
+
let overlay = null;
|
|
286
|
+
let escape_handler = null;
|
|
266
287
|
|
|
267
|
-
|
|
288
|
+
/* Unconditional teardown. The returned close() maps here: a
|
|
289
|
+
* programmatic close always closes, bypassing before_close. */
|
|
290
|
+
let do_close = function() {
|
|
268
291
|
if(closed) {
|
|
269
292
|
return;
|
|
270
293
|
}
|
|
@@ -273,13 +296,16 @@ export function yui_shell_show_modal(shell, content, opts)
|
|
|
273
296
|
release_focus();
|
|
274
297
|
release_focus = null;
|
|
275
298
|
}
|
|
276
|
-
if(
|
|
277
|
-
yui_shell_pop_escape(shell,
|
|
278
|
-
|
|
299
|
+
if(escape_handler) {
|
|
300
|
+
yui_shell_pop_escape(shell, escape_handler);
|
|
301
|
+
escape_handler = null;
|
|
279
302
|
}
|
|
280
303
|
if($modal.parentNode) {
|
|
281
304
|
$modal.parentNode.removeChild($modal);
|
|
282
305
|
}
|
|
306
|
+
/* Retire the browser-history entry (no-op when Back triggered
|
|
307
|
+
* this close — the entry is already gone). */
|
|
308
|
+
yui_shell_overlay_dismissed(shell, overlay);
|
|
283
309
|
if(opts && typeof opts.on_close === "function") {
|
|
284
310
|
try {
|
|
285
311
|
opts.on_close();
|
|
@@ -288,9 +314,50 @@ export function yui_shell_show_modal(shell, content, opts)
|
|
|
288
314
|
}
|
|
289
315
|
}
|
|
290
316
|
};
|
|
291
|
-
close_fn = close;
|
|
292
317
|
|
|
293
|
-
|
|
318
|
+
/* `opts.before_close`: a guard consulted on EVERY user-driven dismiss
|
|
319
|
+
* (Escape, backdrop, the X / back-arrow, browser Back). Return false to
|
|
320
|
+
* VETO — the modal stays up and the caller takes over (e.g. an
|
|
321
|
+
* unsaved-changes prompt that calls close() itself on confirm). Return
|
|
322
|
+
* true/undefined to let it close. Absent guard ⇒ always closes, so
|
|
323
|
+
* existing callers are unaffected. */
|
|
324
|
+
let guarded_close = function(on_veto) {
|
|
325
|
+
if(closed) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
let allow = true;
|
|
329
|
+
if(opts && typeof opts.before_close === "function") {
|
|
330
|
+
try {
|
|
331
|
+
allow = opts.before_close();
|
|
332
|
+
} catch(e) {
|
|
333
|
+
log_warning(`yui_shell_show_modal: before_close threw: ${e}`);
|
|
334
|
+
allow = true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if(allow === false) {
|
|
338
|
+
if(typeof on_veto === "function") {
|
|
339
|
+
on_veto();
|
|
340
|
+
}
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
do_close();
|
|
344
|
+
};
|
|
345
|
+
let request_close = function() {
|
|
346
|
+
guarded_close(null);
|
|
347
|
+
};
|
|
348
|
+
/* Browser Back already consumed the history entry; if the guard vetoes,
|
|
349
|
+
* re-arm a fresh entry so a later Back still targets this modal. */
|
|
350
|
+
let on_back = function() {
|
|
351
|
+
guarded_close(function() {
|
|
352
|
+
overlay = yui_shell_register_overlay(shell, on_back);
|
|
353
|
+
});
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
escape_handler = request_close;
|
|
357
|
+
yui_shell_push_escape(shell, "modal", request_close);
|
|
358
|
+
/* Browser Back closes the top-most modal (see overlay history in
|
|
359
|
+
* c_yui_shell.js). Null when history integration is off. */
|
|
360
|
+
overlay = yui_shell_register_overlay(shell, on_back);
|
|
294
361
|
/* Trap on $modal (not on .modal-content) so the .modal-close
|
|
295
362
|
* button — rendered as a SIBLING of .modal-content — is
|
|
296
363
|
* reachable via Tab. Without this, Tab/Shift+Tab can only
|
|
@@ -299,35 +366,50 @@ export function yui_shell_show_modal(shell, content, opts)
|
|
|
299
366
|
release_focus = activate_focus_trap_on($modal);
|
|
300
367
|
|
|
301
368
|
if((opts == null || opts.dismiss_on_background !== false)) {
|
|
302
|
-
$modal.querySelector(".modal-background").addEventListener("click",
|
|
369
|
+
$modal.querySelector(".modal-background").addEventListener("click", request_close);
|
|
303
370
|
}
|
|
304
371
|
let $close_btn = $modal.querySelector(".modal-close");
|
|
305
372
|
if($close_btn) {
|
|
306
|
-
$close_btn.addEventListener("click",
|
|
373
|
+
$close_btn.addEventListener("click", request_close);
|
|
307
374
|
}
|
|
308
375
|
if(dialog) {
|
|
309
376
|
let $back = $modal.querySelector(".yui-dialog-back");
|
|
310
377
|
if($back) {
|
|
311
|
-
$back.addEventListener("click",
|
|
378
|
+
$back.addEventListener("click", request_close);
|
|
312
379
|
}
|
|
313
380
|
let $x = $modal.querySelector(".yui-dialog-x");
|
|
314
381
|
if($x) {
|
|
315
|
-
$x.addEventListener("click",
|
|
382
|
+
$x.addEventListener("click", request_close);
|
|
316
383
|
}
|
|
317
384
|
}
|
|
318
385
|
|
|
319
|
-
return { close };
|
|
386
|
+
return { close: do_close };
|
|
320
387
|
}
|
|
321
388
|
|
|
322
389
|
|
|
323
390
|
/***************************************************************
|
|
324
|
-
* Blocking dialogs (
|
|
391
|
+
* Blocking dialogs (icon-centric .modal-card)
|
|
325
392
|
*
|
|
326
393
|
* build_dialog returns a Promise that resolves with the
|
|
327
394
|
* clicked button's `value`. Escape, the close button and
|
|
328
395
|
* the dismiss action all resolve with the LAST button's value
|
|
329
396
|
* (cancel/no/ok by convention — the safe-default action).
|
|
397
|
+
*
|
|
398
|
+
* Layout ported from the legacy volatil modals (2.5.0
|
|
399
|
+
* redesign, removed in 3.0.0): a narrow rounded card with a
|
|
400
|
+
* tinted round icon of the `opts.type`
|
|
401
|
+
* (question/success/info/warning/error, `danger` aliases
|
|
402
|
+
* error), optional capitalized title, centered message and
|
|
403
|
+
* buttons. Everything maps to Bulma vars (light and dark).
|
|
330
404
|
***************************************************************/
|
|
405
|
+
const CONFIRM_TYPE_ICONS = {
|
|
406
|
+
"info": "yi-circle-info",
|
|
407
|
+
"question": "yi-question",
|
|
408
|
+
"success": "yi-square-check",
|
|
409
|
+
"warning": "yi-triangle-exclamation",
|
|
410
|
+
"error": "yi-circle-exclamation",
|
|
411
|
+
};
|
|
412
|
+
|
|
331
413
|
function build_dialog(shell, message, buttons, opts)
|
|
332
414
|
{
|
|
333
415
|
let $layer = modal_layer(shell);
|
|
@@ -339,16 +421,18 @@ function build_dialog(shell, message, buttons, opts)
|
|
|
339
421
|
let title = (opts && opts.title) || "";
|
|
340
422
|
let dismiss_value = buttons[buttons.length - 1].value;
|
|
341
423
|
|
|
424
|
+
let type = ((opts && opts.type) || "question").toLowerCase();
|
|
425
|
+
if(type === "danger") {
|
|
426
|
+
type = "error";
|
|
427
|
+
}
|
|
428
|
+
let icon = CONFIRM_TYPE_ICONS[type] || CONFIRM_TYPE_ICONS["question"];
|
|
429
|
+
|
|
342
430
|
let $body_children = (typeof message === "string")
|
|
343
|
-
? [["p", {i18n: message}, message]]
|
|
431
|
+
? [["p", {class: "CONFIRM_MSG yui-confirm-msg", i18n: message}, message]]
|
|
344
432
|
: [message];
|
|
345
433
|
|
|
346
|
-
let title_attrs = !empty_string(title)
|
|
347
|
-
? {class: "modal-card-title", i18n: title}
|
|
348
|
-
: {class: "modal-card-title"};
|
|
349
|
-
|
|
350
434
|
let $footer_children = buttons.map(b => {
|
|
351
|
-
let cls = "button";
|
|
435
|
+
let cls = "CONFIRM_BTN button px-5";
|
|
352
436
|
if(b.kind === "primary") {
|
|
353
437
|
cls += " is-link";
|
|
354
438
|
} else if(b.kind === "danger") {
|
|
@@ -362,24 +446,34 @@ function build_dialog(shell, message, buttons, opts)
|
|
|
362
446
|
return ["button", btn_attrs, b.label];
|
|
363
447
|
});
|
|
364
448
|
|
|
449
|
+
let $card_children = [
|
|
450
|
+
["div", {class: "CONFIRM_ICON yui-confirm-icon"},
|
|
451
|
+
[["i", {class: icon, "aria-hidden": "true"}]]
|
|
452
|
+
]
|
|
453
|
+
];
|
|
454
|
+
if(!empty_string(title)) {
|
|
455
|
+
$card_children.push(
|
|
456
|
+
["p", {class: "CONFIRM_TITLE yui-confirm-title has-text-centered",
|
|
457
|
+
i18n: title}, title]
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
$card_children.push(
|
|
461
|
+
["button", {class: "CONFIRM_CLOSE delete yui-confirm-x", "aria-label": "close"}],
|
|
462
|
+
["section", {class: "CONFIRM_BODY modal-card-body has-text-centered"},
|
|
463
|
+
$body_children],
|
|
464
|
+
["footer", {class: "CONFIRM_FOOT modal-card-foot"}, $footer_children]
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
/* Caller's own UPPER_SNAKE name for THIS confirm, same contract as
|
|
468
|
+
* yui_shell_show_modal's `logical_class`. */
|
|
469
|
+
let logical = (opts && opts.logical_class) ? " " + opts.logical_class : "";
|
|
470
|
+
|
|
365
471
|
let $modal = createElement2(
|
|
366
|
-
["div", {class:
|
|
472
|
+
["div", {class: `CONFIRM${logical} modal yui-modal yui-confirm is-active is-${type}`,
|
|
367
473
|
role: "dialog", "aria-modal": "true"},
|
|
368
474
|
[
|
|
369
|
-
["div", {class: "modal-background"}],
|
|
370
|
-
["div", {class: "modal-card"},
|
|
371
|
-
[
|
|
372
|
-
["header", {class: "modal-card-head"},
|
|
373
|
-
[
|
|
374
|
-
["p", title_attrs, title],
|
|
375
|
-
["button", {class: "delete",
|
|
376
|
-
"aria-label": "close"}]
|
|
377
|
-
]
|
|
378
|
-
],
|
|
379
|
-
["section", {class: "modal-card-body"}, $body_children],
|
|
380
|
-
["footer", {class: "modal-card-foot"}, $footer_children]
|
|
381
|
-
]
|
|
382
|
-
]
|
|
475
|
+
["div", {class: "CONFIRM_BACKDROP modal-background"}],
|
|
476
|
+
["div", {class: "CONFIRM_CARD modal-card"}, $card_children]
|
|
383
477
|
]
|
|
384
478
|
]
|
|
385
479
|
);
|
|
@@ -390,6 +484,7 @@ function build_dialog(shell, message, buttons, opts)
|
|
|
390
484
|
let resolved = false;
|
|
391
485
|
let close_fn = null;
|
|
392
486
|
let release_focus = null;
|
|
487
|
+
let overlay = null;
|
|
393
488
|
|
|
394
489
|
let close = function(value) {
|
|
395
490
|
if(resolved) {
|
|
@@ -407,11 +502,17 @@ function build_dialog(shell, message, buttons, opts)
|
|
|
407
502
|
if($modal.parentNode) {
|
|
408
503
|
$modal.parentNode.removeChild($modal);
|
|
409
504
|
}
|
|
505
|
+
/* Retire the browser-history entry (no-op when Back
|
|
506
|
+
* triggered this close). */
|
|
507
|
+
yui_shell_overlay_dismissed(shell, overlay);
|
|
410
508
|
resolve(value);
|
|
411
509
|
};
|
|
412
510
|
close_fn = () => close(dismiss_value);
|
|
413
511
|
|
|
414
512
|
yui_shell_push_escape(shell, "modal", close_fn);
|
|
513
|
+
/* Browser Back dismisses the dialog with the safe-default value,
|
|
514
|
+
* exactly like Escape. */
|
|
515
|
+
overlay = yui_shell_register_overlay(shell, close_fn);
|
|
415
516
|
release_focus = activate_focus_trap_on(
|
|
416
517
|
$modal.querySelector(".modal-card")
|
|
417
518
|
);
|
|
@@ -419,7 +520,7 @@ function build_dialog(shell, message, buttons, opts)
|
|
|
419
520
|
$modal.querySelector(".modal-background").addEventListener(
|
|
420
521
|
"click", () => close(dismiss_value)
|
|
421
522
|
);
|
|
422
|
-
$modal.querySelector(".modal-card
|
|
523
|
+
$modal.querySelector(".modal-card .delete").addEventListener(
|
|
423
524
|
"click", () => close(dismiss_value)
|
|
424
525
|
);
|
|
425
526
|
let footer_buttons = $modal.querySelectorAll(
|
|
@@ -430,6 +531,12 @@ function build_dialog(shell, message, buttons, opts)
|
|
|
430
531
|
close($btn.getAttribute("data-modal-button-value"));
|
|
431
532
|
});
|
|
432
533
|
});
|
|
534
|
+
|
|
535
|
+
/* Enter answers the primary action, like the volatil modals. */
|
|
536
|
+
let $primary = $modal.querySelector(".modal-card-foot button");
|
|
537
|
+
if($primary) {
|
|
538
|
+
$primary.focus();
|
|
539
|
+
}
|
|
433
540
|
});
|
|
434
541
|
}
|
|
435
542
|
|
|
@@ -437,6 +544,9 @@ function build_dialog(shell, message, buttons, opts)
|
|
|
437
544
|
export function yui_shell_confirm_ok(shell, message, opts)
|
|
438
545
|
{
|
|
439
546
|
let label = (opts && opts.ok_label) || "OK";
|
|
547
|
+
if(!opts || !opts.type) {
|
|
548
|
+
opts = Object.assign({}, opts, {type: "success"});
|
|
549
|
+
}
|
|
440
550
|
return build_dialog(shell, message, [
|
|
441
551
|
{label: label, value: "ok", kind: "primary"}
|
|
442
552
|
], opts).then(() => undefined);
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* shell_route_map.css
|
|
3
|
+
*
|
|
4
|
+
* Styling for the site-map (route-tree) viewer in shell_route_map.js.
|
|
5
|
+
* A monospaced, indented tree; theme-aware via Bulma variables;
|
|
6
|
+
* and @media print rules (scoped by body.routemap-printing) so the
|
|
7
|
+
* Print button prints only the tree.
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2026, ArtGins.
|
|
10
|
+
* All Rights Reserved.
|
|
11
|
+
***********************************************************************/
|
|
12
|
+
.ROUTEMAP_BODY {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
width: 100%;
|
|
16
|
+
min-height: 0;
|
|
17
|
+
}
|
|
18
|
+
/* In the resizable window, fill the window body so the tree grows/shrinks
|
|
19
|
+
* with it (the WINDOW_BODY is is-flex-grow-1 with a definite height). */
|
|
20
|
+
.yui-window-body .ROUTEMAP_BODY {
|
|
21
|
+
height: 100%;
|
|
22
|
+
}
|
|
23
|
+
.ROUTEMAP_SEARCH_ROW {
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
gap: 0.6rem;
|
|
27
|
+
}
|
|
28
|
+
.ROUTEMAP_SEARCH_CTRL {
|
|
29
|
+
max-width: 22rem;
|
|
30
|
+
flex: 1 1 auto;
|
|
31
|
+
}
|
|
32
|
+
.ROUTEMAP_COUNT {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
gap: 0.25rem;
|
|
35
|
+
white-space: nowrap;
|
|
36
|
+
flex: 0 0 auto;
|
|
37
|
+
}
|
|
38
|
+
.ROUTEMAP_COUNT_N {
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
}
|
|
41
|
+
/* Highlight a row that itself matches the filter. */
|
|
42
|
+
.ROUTEMAP_MATCH {
|
|
43
|
+
background: var(--bulma-warning-light, #fff6d9) !important;
|
|
44
|
+
}
|
|
45
|
+
[data-theme="dark"] .ROUTEMAP_MATCH {
|
|
46
|
+
background: rgba(255, 221, 87, 0.20) !important;
|
|
47
|
+
}
|
|
48
|
+
.ROUTEMAP_MATCH .ROUTEMAP_NAME,
|
|
49
|
+
.ROUTEMAP_MATCH .ROUTEMAP_ROUTE {
|
|
50
|
+
color: var(--bulma-text-strong, #363636);
|
|
51
|
+
}
|
|
52
|
+
[data-theme="dark"] .ROUTEMAP_MATCH .ROUTEMAP_NAME,
|
|
53
|
+
[data-theme="dark"] .ROUTEMAP_MATCH .ROUTEMAP_ROUTE {
|
|
54
|
+
color: #1a1a1a;
|
|
55
|
+
}
|
|
56
|
+
.ROUTEMAP_TREE {
|
|
57
|
+
font-family: var(--bulma-family-monospace, monospace);
|
|
58
|
+
font-size: 0.85rem;
|
|
59
|
+
line-height: 1.5;
|
|
60
|
+
overflow: auto;
|
|
61
|
+
min-height: 6rem;
|
|
62
|
+
/* Modal fallback: cap and scroll. In the window the rule below
|
|
63
|
+
* makes it flex-fill instead. */
|
|
64
|
+
flex: 0 1 auto;
|
|
65
|
+
max-height: 68vh;
|
|
66
|
+
border: 1px solid var(--bulma-border-weak, #dbdbdb);
|
|
67
|
+
border-radius: 0.375rem;
|
|
68
|
+
padding: 0.75rem 1rem;
|
|
69
|
+
background: var(--bulma-scheme-main, #fff);
|
|
70
|
+
}
|
|
71
|
+
/* In the window: grow to fill the remaining height (after the filter
|
|
72
|
+
* and print row) and scroll internally. */
|
|
73
|
+
.yui-window-body .ROUTEMAP_TREE {
|
|
74
|
+
flex: 1 1 auto;
|
|
75
|
+
max-height: none;
|
|
76
|
+
}
|
|
77
|
+
.ROUTEMAP_UL {
|
|
78
|
+
list-style: none;
|
|
79
|
+
margin: 0;
|
|
80
|
+
padding-left: 1.1rem;
|
|
81
|
+
border-left: 1px dotted var(--bulma-border, #b5b5b5);
|
|
82
|
+
}
|
|
83
|
+
.ROUTEMAP_ROOT {
|
|
84
|
+
padding-left: 0;
|
|
85
|
+
border-left: none;
|
|
86
|
+
}
|
|
87
|
+
.ROUTEMAP_LI {
|
|
88
|
+
margin: 0.05rem 0;
|
|
89
|
+
}
|
|
90
|
+
.ROUTEMAP_ROW {
|
|
91
|
+
display: inline-flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 0.4rem;
|
|
94
|
+
padding: 0.05rem 0.25rem;
|
|
95
|
+
border-radius: 0.25rem;
|
|
96
|
+
}
|
|
97
|
+
.ROUTEMAP_ICON {
|
|
98
|
+
font-size: 0.95rem;
|
|
99
|
+
color: var(--bulma-text-weak, #7a7a7a);
|
|
100
|
+
}
|
|
101
|
+
a.ROUTEMAP_LINK {
|
|
102
|
+
text-decoration: none;
|
|
103
|
+
color: inherit;
|
|
104
|
+
}
|
|
105
|
+
a.ROUTEMAP_LINK:hover {
|
|
106
|
+
background: var(--bulma-background, #f5f5f5);
|
|
107
|
+
}
|
|
108
|
+
a.ROUTEMAP_LINK .ROUTEMAP_NAME {
|
|
109
|
+
font-weight: 600;
|
|
110
|
+
color: var(--bulma-text, inherit);
|
|
111
|
+
}
|
|
112
|
+
.ROUTEMAP_NAME {
|
|
113
|
+
color: var(--bulma-text, inherit);
|
|
114
|
+
}
|
|
115
|
+
.ROUTEMAP_ROUTE {
|
|
116
|
+
background: transparent;
|
|
117
|
+
color: var(--bulma-link, #485fc7);
|
|
118
|
+
padding: 0;
|
|
119
|
+
font-size: 0.8rem;
|
|
120
|
+
}
|
|
121
|
+
/* Implementing GClass pill (teal) — distinct from route (blue) and
|
|
122
|
+
* event (grey). */
|
|
123
|
+
.ROUTEMAP_GCLASS {
|
|
124
|
+
font-family: var(--bulma-family-monospace, monospace);
|
|
125
|
+
font-size: 0.7rem;
|
|
126
|
+
padding: 0.02rem 0.35rem;
|
|
127
|
+
border-radius: 0.25rem;
|
|
128
|
+
background: var(--bulma-link-light, #eff1fa);
|
|
129
|
+
color: var(--bulma-link-dark, #3850b7);
|
|
130
|
+
white-space: nowrap;
|
|
131
|
+
}
|
|
132
|
+
[data-theme="dark"] .ROUTEMAP_GCLASS {
|
|
133
|
+
background: rgba(120, 150, 255, 0.16);
|
|
134
|
+
color: #aab8ff;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Action event badge — explicit colours (not Bulma's is-light tag) so it
|
|
138
|
+
* stays legible in dark mode. */
|
|
139
|
+
.ROUTEMAP_EVENT {
|
|
140
|
+
font-family: var(--bulma-family-monospace, monospace);
|
|
141
|
+
font-size: 0.72rem;
|
|
142
|
+
padding: 0.02rem 0.4rem;
|
|
143
|
+
border-radius: 0.25rem;
|
|
144
|
+
background: var(--bulma-background, #f0f0f0);
|
|
145
|
+
color: var(--bulma-text-strong, #363636);
|
|
146
|
+
border: 1px solid var(--bulma-border-weak, #dbdbdb);
|
|
147
|
+
white-space: nowrap;
|
|
148
|
+
}
|
|
149
|
+
[data-theme="dark"] .ROUTEMAP_EVENT {
|
|
150
|
+
background: rgba(255, 255, 255, 0.10);
|
|
151
|
+
color: #e6e6e6;
|
|
152
|
+
border-color: rgba(255, 255, 255, 0.20);
|
|
153
|
+
}
|
|
154
|
+
@media (prefers-color-scheme: dark) {
|
|
155
|
+
:root:not([data-theme="light"]) .ROUTEMAP_EVENT {
|
|
156
|
+
background: rgba(255, 255, 255, 0.10);
|
|
157
|
+
color: #e6e6e6;
|
|
158
|
+
border-color: rgba(255, 255, 255, 0.20);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/* "You are here" — the row of the route the user is currently on. */
|
|
162
|
+
.ROUTEMAP_CURRENT {
|
|
163
|
+
background: var(--bulma-link-light, #eff1fa) !important;
|
|
164
|
+
outline: 1px solid var(--bulma-link, #485fc7);
|
|
165
|
+
}
|
|
166
|
+
[data-theme="dark"] .ROUTEMAP_CURRENT {
|
|
167
|
+
background: rgba(120, 150, 255, 0.16) !important;
|
|
168
|
+
outline-color: rgba(120, 150, 255, 0.55);
|
|
169
|
+
}
|
|
170
|
+
.ROUTEMAP_HERE {
|
|
171
|
+
font-size: 0.68rem;
|
|
172
|
+
font-weight: 700;
|
|
173
|
+
text-transform: uppercase;
|
|
174
|
+
letter-spacing: 0.03em;
|
|
175
|
+
padding: 0.02rem 0.4rem;
|
|
176
|
+
border-radius: 0.25rem;
|
|
177
|
+
background: var(--bulma-link, #485fc7);
|
|
178
|
+
color: var(--bulma-link-invert, #fff);
|
|
179
|
+
white-space: nowrap;
|
|
180
|
+
}
|
|
181
|
+
.ROUTEMAP_STRUCT .ROUTEMAP_NAME {
|
|
182
|
+
color: var(--bulma-text-weak, #7a7a7a);
|
|
183
|
+
text-transform: uppercase;
|
|
184
|
+
letter-spacing: 0.03em;
|
|
185
|
+
font-size: 0.78rem;
|
|
186
|
+
font-weight: 700;
|
|
187
|
+
}
|
|
188
|
+
.ROUTEMAP_HINT {
|
|
189
|
+
color: var(--bulma-text-weak, #7a7a7a);
|
|
190
|
+
}
|
|
191
|
+
.ROUTEMAP_ACTIONS {
|
|
192
|
+
display: flex;
|
|
193
|
+
justify-content: flex-end;
|
|
194
|
+
margin-top: 0.75rem;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* Print only the tree. On print the button clones the tree into
|
|
198
|
+
* `.routemap-print-area` (off-screen) and sets body.routemap-printing;
|
|
199
|
+
* everything else is hidden regardless of where the window/modal lives. */
|
|
200
|
+
.routemap-print-area {
|
|
201
|
+
display: none;
|
|
202
|
+
}
|
|
203
|
+
@media print {
|
|
204
|
+
body.routemap-printing > *:not(.routemap-print-area) {
|
|
205
|
+
display: none !important;
|
|
206
|
+
}
|
|
207
|
+
body.routemap-printing .routemap-print-area {
|
|
208
|
+
display: block !important;
|
|
209
|
+
}
|
|
210
|
+
.routemap-print-area .ROUTEMAP_TREE {
|
|
211
|
+
max-height: none;
|
|
212
|
+
border: none;
|
|
213
|
+
overflow: visible;
|
|
214
|
+
font-size: 10pt;
|
|
215
|
+
}
|
|
216
|
+
.routemap-print-area .ROUTEMAP_ROUTE,
|
|
217
|
+
.routemap-print-area .ROUTEMAP_NAME {
|
|
218
|
+
color: #000 !important;
|
|
219
|
+
}
|
|
220
|
+
.routemap-print-area .ROUTEMAP_EVENT {
|
|
221
|
+
background: transparent !important;
|
|
222
|
+
color: #000 !important;
|
|
223
|
+
border-color: #999 !important;
|
|
224
|
+
}
|
|
225
|
+
}
|