@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
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* c_yui_window.css
|
|
3
|
+
*
|
|
4
|
+
* Styles C_YUI_WINDOW tags on its chrome. Moved from
|
|
5
|
+
* c_yui_main.css (2.6.1) so windows are self-contained and no
|
|
6
|
+
* longer depend on the legacy stack's stylesheet.
|
|
7
|
+
*
|
|
8
|
+
* Copyright (c) 2026, ArtGins.
|
|
9
|
+
* All Rights Reserved.
|
|
10
|
+
***********************************************************************/
|
|
11
|
+
|
|
12
|
+
.strong-shadow {
|
|
13
|
+
box-shadow:
|
|
14
|
+
0 1em 2em -0.125em hsla(var(--bulma-shadow-h), var(--bulma-shadow-s), var(--bulma-shadow-l), 0.3),
|
|
15
|
+
0 0 0 2px hsla(var(--bulma-shadow-h), var(--bulma-shadow-s), var(--bulma-shadow-l), 0.1) !important;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.without-border {
|
|
19
|
+
border: none !important;
|
|
20
|
+
box-shadow: none !important;
|
|
21
|
+
background: transparent !important;
|
|
22
|
+
}
|
package/src/c_yui_window.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
kw_set_local_storage_value,
|
|
28
28
|
gobj_read_attr,
|
|
29
29
|
gobj_read_bool_attr,
|
|
30
|
+
gobj_read_str_attr,
|
|
30
31
|
gobj_is_service,
|
|
31
32
|
gobj_name,
|
|
32
33
|
gobj_read_integer_attr,
|
|
@@ -34,18 +35,24 @@ import {
|
|
|
34
35
|
gobj_destroy,
|
|
35
36
|
gobj_write_bool_attr,
|
|
36
37
|
gobj_write_integer_attr,
|
|
37
|
-
gobj_unsubscribe_event,
|
|
38
38
|
gobj_stop,
|
|
39
39
|
gobj_stop_children,
|
|
40
40
|
gobj_is_running,
|
|
41
41
|
gobj_is_destroying,
|
|
42
42
|
refresh_language,
|
|
43
|
+
empty_string,
|
|
43
44
|
} from "@yuneta/gobj-js";
|
|
44
45
|
|
|
45
46
|
import {t} from "i18next";
|
|
46
47
|
|
|
47
48
|
import {yui_shell_confirm_yesnocancel} from "./shell_modals.js";
|
|
48
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
yui_shell_of,
|
|
51
|
+
yui_shell_register_overlay,
|
|
52
|
+
yui_shell_overlay_dismissed,
|
|
53
|
+
} from "./c_yui_shell.js";
|
|
54
|
+
|
|
55
|
+
import "./c_yui_window.css";
|
|
49
56
|
|
|
50
57
|
/***************************************************************
|
|
51
58
|
* Constants
|
|
@@ -76,7 +83,7 @@ SDATA(data_type_t.DTP_INTEGER, "y", 0, "100", "Y position"),
|
|
|
76
83
|
SDATA(data_type_t.DTP_INTEGER, "width", 0, "700", "Width of the window"),
|
|
77
84
|
SDATA(data_type_t.DTP_INTEGER, "height", 0, "500", "Height of the window"),
|
|
78
85
|
SDATA(data_type_t.DTP_BOOLEAN, "auto_save_size_and_position", 0, false, "Automatically save size and position"),
|
|
79
|
-
SDATA(data_type_t.DTP_POINTER, "header", 0, null, "
|
|
86
|
+
SDATA(data_type_t.DTP_POINTER, "header", 0, null, "Title-bar content, overriding the default `icon`+`title` strip: a gobj with $container or any createElement2() 'content' parameter. Only for a bar that is more than a title (e.g. a toolbar)"),
|
|
80
87
|
SDATA(data_type_t.DTP_POINTER, "body", 0, null, "Can be a gobj with $container or any createElement2() 'content' parameter"),
|
|
81
88
|
SDATA(data_type_t.DTP_POINTER, "footer", 0, null, "Can be a gobj with $container or any createElement2() 'content' parameter"),
|
|
82
89
|
SDATA(data_type_t.DTP_BOOLEAN, "center", 0, true, "Center the window"),
|
|
@@ -86,17 +93,21 @@ SDATA(data_type_t.DTP_BOOLEAN, "resizable", 0, true, "Allow resizing"),
|
|
|
86
93
|
SDATA(data_type_t.DTP_BOOLEAN, "showFooter", 0, true, "Show footer"),
|
|
87
94
|
SDATA(data_type_t.DTP_BOOLEAN, "openMaximized",0, false, "Open the window maximized"),
|
|
88
95
|
SDATA(data_type_t.DTP_BOOLEAN, "showMax", 0, true, "Show maximize button"),
|
|
89
|
-
SDATA(data_type_t.DTP_BOOLEAN, "showMin", 0, true, "Show minimize (
|
|
96
|
+
SDATA(data_type_t.DTP_BOOLEAN, "showMin", 0, true, "Show minimize (to dock) button; ignored without a `manager`"),
|
|
90
97
|
SDATA(data_type_t.DTP_BOOLEAN, "maximized", 0, false, "Flag to indicate if maximized"),
|
|
91
98
|
SDATA(data_type_t.DTP_JSON, "window_style", 0, "{}", "Override window style"),
|
|
92
99
|
SDATA(data_type_t.DTP_POINTER, "on_close", 0, null, "Callback on destroy"),
|
|
93
100
|
SDATA(data_type_t.DTP_POINTER, "manager", 0, null, "Optional C_YUI_WINDOW_MANAGER (gobj or service name) for dock/taskbar"),
|
|
94
|
-
SDATA(data_type_t.DTP_STRING, "
|
|
95
|
-
SDATA(data_type_t.DTP_STRING, "
|
|
101
|
+
SDATA(data_type_t.DTP_STRING, "logical_class",0, "", "Logical UPPER_SNAKE class(es) added to the window root, so the app can target THIS window exactly (e.g. 'TRANGER_KEYS_WINDOW')"),
|
|
102
|
+
SDATA(data_type_t.DTP_STRING, "title_prefix", 0, "", "Optional DATA half of the title (a topic/service/marker name), shown before `title` and never translated. Use it instead of composing `${name} · ${t(key)}` into `title`, which cannot re-translate"),
|
|
103
|
+
SDATA(data_type_t.DTP_STRING, "title", 0, "", "Window title, an i18n KEY: painted in the title bar (unless `header` overrides it) and on the dock chip. Pass the key, not t(key), or it cannot re-translate"),
|
|
104
|
+
SDATA(data_type_t.DTP_STRING, "icon", 0, "", "Window icon (by window type), leading the title bar and the dock chip: a yi-* class name or inline SVG"),
|
|
96
105
|
// TODO pendiente focus modal keyboard
|
|
97
106
|
SDATA(data_type_t.DTP_POINTER, "focus", 0, null, "Brings focus to the element, can be a number or selector"),
|
|
98
107
|
SDATA(data_type_t.DTP_BOOLEAN, "modal", 0, false, "Enable modal mode"),
|
|
99
108
|
SDATA(data_type_t.DTP_BOOLEAN, "keyboard", 0, true, "Close window on ESC if not modal"),
|
|
109
|
+
SDATA(data_type_t.DTP_BOOLEAN, "back_dismissable", 0, true, "Browser Back closes this window (floating overlays only; ignored when it has a `manager`)"),
|
|
110
|
+
SDATA(data_type_t.DTP_POINTER, "back_overlay", 0, null, "Internal: overlay-history entry (Back-button integration)"),
|
|
100
111
|
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Internal: Window container element"),
|
|
101
112
|
SDATA(data_type_t.DTP_STRING, "window_id", 0, "", "Internal: Window ID"),
|
|
102
113
|
SDATA(data_type_t.DTP_POINTER, "win_resize_handler", 0, null, "Internal: native window 'resize' listener"),
|
|
@@ -105,7 +116,6 @@ SDATA_END()
|
|
|
105
116
|
|
|
106
117
|
let PRIVATE_DATA = {
|
|
107
118
|
prevSize: 0,
|
|
108
|
-
preShadeHeight: 0,
|
|
109
119
|
};
|
|
110
120
|
|
|
111
121
|
let __gclass__ = null;
|
|
@@ -135,12 +145,15 @@ function mt_create(gobj)
|
|
|
135
145
|
|
|
136
146
|
let window_id = "window-" + clean_name(gobj_short_name(gobj));
|
|
137
147
|
gobj_write_attr(gobj, "window_id", window_id);
|
|
138
|
-
build_ui(gobj);
|
|
139
148
|
|
|
140
149
|
/* Optional window manager (dock/taskbar): register, and raise/
|
|
141
|
-
* highlight on any pointer press.
|
|
142
|
-
*
|
|
150
|
+
* highlight on any pointer press. Resolved BEFORE build_ui: minimize
|
|
151
|
+
* means "send to the dock", so without a manager there is nowhere to
|
|
152
|
+
* minimize TO and build_ui must not paint the button at all. */
|
|
143
153
|
let manager = resolve_manager(gobj);
|
|
154
|
+
|
|
155
|
+
build_ui(gobj);
|
|
156
|
+
|
|
144
157
|
if(manager) {
|
|
145
158
|
let $c = gobj_read_attr(gobj, "$container");
|
|
146
159
|
if($c) {
|
|
@@ -151,21 +164,44 @@ function mt_create(gobj)
|
|
|
151
164
|
}
|
|
152
165
|
}, true);
|
|
153
166
|
}
|
|
167
|
+
/* The chip gets the SPLIT halves so its label re-translates
|
|
168
|
+
* like the title bar: `title_key` is the i18n KEY (the chip
|
|
169
|
+
* renders it with data-i18n, so refresh_language reaches it),
|
|
170
|
+
* `title` the translated fallback text so the chip is born in
|
|
171
|
+
* the current language (the manager has no translator of its
|
|
172
|
+
* own), `title_prefix` the DATA half, never translated. */
|
|
173
|
+
let chip_title = composed_title(gobj);
|
|
154
174
|
gobj_send_event(manager, "EV_REGISTER_WINDOW", {
|
|
155
175
|
window: gobj,
|
|
156
|
-
title:
|
|
176
|
+
title: chip_title ? chip_title : gobj_short_name(gobj),
|
|
177
|
+
title_key: gobj_read_str_attr(gobj, "title") || "",
|
|
178
|
+
title_kind_text: empty_string(gobj_read_str_attr(gobj, "title"))
|
|
179
|
+
? "" : t(gobj_read_str_attr(gobj, "title")),
|
|
180
|
+
title_prefix: gobj_read_str_attr(gobj, "title_prefix") || "",
|
|
157
181
|
icon: gobj_read_attr(gobj, "icon") || "",
|
|
158
182
|
}, gobj);
|
|
183
|
+
} else if(gobj_read_bool_attr(gobj, "back_dismissable")) {
|
|
184
|
+
/* Floating overlay window (no dock manager): the browser Back
|
|
185
|
+
* button closes it, like a modal/popup. Dock-managed windows
|
|
186
|
+
* are persistent workspace surfaces and are left out. Retired
|
|
187
|
+
* in mt_destroy (covers every teardown path). */
|
|
188
|
+
let shell = yui_shell_of(gobj);
|
|
189
|
+
if(shell) {
|
|
190
|
+
let overlay = yui_shell_register_overlay(
|
|
191
|
+
shell, function() { close_window(gobj); }
|
|
192
|
+
);
|
|
193
|
+
gobj_write_attr(gobj, "back_overlay", overlay);
|
|
194
|
+
}
|
|
159
195
|
}
|
|
160
196
|
|
|
161
197
|
/* Keep the window inside the viewport on a breakpoint change.
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
198
|
+
* The browser's own 'resize' is the source: this IS the OS
|
|
199
|
+
* notification, and handleResize() clamps to screen (+ optional
|
|
200
|
+
* re-center). The legacy path — subscribing to EV_RESIZE from a
|
|
201
|
+
* C_YUI_MAIN "__yui_main__" service — was retired: C_YUI_SHELL has
|
|
202
|
+
* no such service, so it never fired under v2, and it duplicated
|
|
203
|
+
* this listener wherever an app did provide one. Detached in
|
|
204
|
+
* mt_destroy. */
|
|
169
205
|
let on_win_resize = function() {
|
|
170
206
|
handleResize(gobj);
|
|
171
207
|
};
|
|
@@ -173,29 +209,6 @@ function mt_create(gobj)
|
|
|
173
209
|
window.addEventListener("resize", on_win_resize);
|
|
174
210
|
}
|
|
175
211
|
|
|
176
|
-
/***************************************************************
|
|
177
|
-
* Framework Method: Start
|
|
178
|
-
***************************************************************/
|
|
179
|
-
function mt_start(gobj)
|
|
180
|
-
{
|
|
181
|
-
let __yui_main__ = gobj_find_service("__yui_main__", false);
|
|
182
|
-
if(__yui_main__) {
|
|
183
|
-
gobj_subscribe_event(__yui_main__, "EV_RESIZE", {}, gobj);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/***************************************************************
|
|
188
|
-
* Framework Method: Stop
|
|
189
|
-
***************************************************************/
|
|
190
|
-
function mt_stop(gobj)
|
|
191
|
-
{
|
|
192
|
-
// TODO quita esto para chequear el fallo dl_subscribings not implemented
|
|
193
|
-
let __yui_main__ = gobj_find_service("__yui_main__", false);
|
|
194
|
-
if(__yui_main__) {
|
|
195
|
-
gobj_unsubscribe_event(__yui_main__, "EV_RESIZE", {}, gobj);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
212
|
/***************************************************************
|
|
200
213
|
* Framework Method: Destroy
|
|
201
214
|
***************************************************************/
|
|
@@ -206,6 +219,17 @@ function mt_destroy(gobj)
|
|
|
206
219
|
gobj_send_event(manager, "EV_UNREGISTER_WINDOW", {window: gobj}, gobj);
|
|
207
220
|
}
|
|
208
221
|
|
|
222
|
+
/* Retire the overlay-history entry, if this was a Back-dismissable
|
|
223
|
+
* floating window (no-op when Back itself triggered the teardown). */
|
|
224
|
+
let back_overlay = gobj_read_attr(gobj, "back_overlay");
|
|
225
|
+
if(back_overlay) {
|
|
226
|
+
let shell = yui_shell_of(gobj);
|
|
227
|
+
if(shell) {
|
|
228
|
+
yui_shell_overlay_dismissed(shell, back_overlay);
|
|
229
|
+
}
|
|
230
|
+
gobj_write_attr(gobj, "back_overlay", null);
|
|
231
|
+
}
|
|
232
|
+
|
|
209
233
|
let on_win_resize = gobj_read_attr(gobj, "win_resize_handler");
|
|
210
234
|
if(on_win_resize) {
|
|
211
235
|
window.removeEventListener("resize", on_win_resize);
|
|
@@ -241,6 +265,16 @@ function ensure_window_style()
|
|
|
241
265
|
-webkit-user-select: none; user-select: none;
|
|
242
266
|
}
|
|
243
267
|
.yui-window-titlebar-controls { display: flex; align-items: center; gap: 2px; padding-left: 6px; }
|
|
268
|
+
.yui-window-title { white-space: nowrap; }
|
|
269
|
+
.yui-window-title .icon { flex: 0 0 auto; }
|
|
270
|
+
.yui-window-title svg { width: 14px; height: 14px; display: block; }
|
|
271
|
+
/* Separator between the data half and the translated kind half. CSS,
|
|
272
|
+
* not a text node: createElement2 trims text nodes and eats the spaces. */
|
|
273
|
+
.WINDOW_TITLE_PREFIX + .WINDOW_TITLE_KIND::before {
|
|
274
|
+
content: "·";
|
|
275
|
+
padding: 0 0.4em;
|
|
276
|
+
color: var(--bulma-text-weak);
|
|
277
|
+
}
|
|
244
278
|
.yui-wc {
|
|
245
279
|
width: 30px; height: 26px; display: inline-flex; align-items: center; justify-content: center;
|
|
246
280
|
padding: 0; border: 0; background: transparent; color: var(--bulma-text); cursor: pointer;
|
|
@@ -249,9 +283,6 @@ function ensure_window_style()
|
|
|
249
283
|
.yui-wc:hover { background: var(--bulma-scheme-main-ter); color: var(--bulma-text-strong); }
|
|
250
284
|
.yui-wc.wc-close:hover { background: #e0364a; color: #fff; }
|
|
251
285
|
.yui-wc svg { width: 15px; height: 15px; display: block; }
|
|
252
|
-
.C_YUI_WINDOW.is-shaded .yui-window-body,
|
|
253
|
-
.C_YUI_WINDOW.is-shaded .yui-window-footer { display: none !important; }
|
|
254
|
-
.C_YUI_WINDOW.is-shaded { height: auto !important; }
|
|
255
286
|
.yui-window-resize { color: var(--bulma-text-weak); opacity: 0.55; }
|
|
256
287
|
.yui-window-resize:hover { opacity: 1; color: var(--bulma-text); }
|
|
257
288
|
@media (max-width: ${MOBILE_MAX}px) {
|
|
@@ -268,6 +299,81 @@ function ensure_window_style()
|
|
|
268
299
|
document.head.appendChild($style);
|
|
269
300
|
}
|
|
270
301
|
|
|
302
|
+
/************************************************************
|
|
303
|
+
* The title bar's default content: `icon` + `title_prefix` +
|
|
304
|
+
* `title`, used when the caller supplies no `header`. Until this
|
|
305
|
+
* existed, `title` only reached the dock chip, so a window without
|
|
306
|
+
* a hand-rolled header painted an EMPTY bar — every caller that
|
|
307
|
+
* wanted a title built the same icon+text strip itself, and the
|
|
308
|
+
* ones that didn't (Keys, Raw JSON) ended up anonymous or titled
|
|
309
|
+
* inside their body.
|
|
310
|
+
*
|
|
311
|
+
* `icon` follows the dock chip's convention: inline SVG when it
|
|
312
|
+
* starts with '<', otherwise a yi-* class name.
|
|
313
|
+
*
|
|
314
|
+
* The two halves are separate text nodes on purpose. Titles here are
|
|
315
|
+
* nearly always "<what> · <kind>" — `raw_tracks · keys` — and
|
|
316
|
+
* composing that into one string is what makes a title untranslatable:
|
|
317
|
+
* `${topic} · ${t("keys")}` is not an i18n key, so i18next answers it
|
|
318
|
+
* with itself and it stays in the language it was built in for the
|
|
319
|
+
* life of the window. Split, the DATA half (`title_prefix`) is left
|
|
320
|
+
* alone and the KIND half carries its key, so a host
|
|
321
|
+
* refresh_language() re-translates just that one. The separator is
|
|
322
|
+
* CSS (::before), never a text node: createElement2 TRIMS text nodes
|
|
323
|
+
* and would eat the spaces around it.
|
|
324
|
+
************************************************************/
|
|
325
|
+
function build_default_header(gobj)
|
|
326
|
+
{
|
|
327
|
+
let title = gobj_read_str_attr(gobj, "title");
|
|
328
|
+
let prefix = gobj_read_str_attr(gobj, "title_prefix");
|
|
329
|
+
if(empty_string(title) && empty_string(prefix)) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
let icon = gobj_read_str_attr(gobj, "icon");
|
|
334
|
+
let items = [];
|
|
335
|
+
if(!empty_string(icon)) {
|
|
336
|
+
if(icon.charAt(0) === '<') {
|
|
337
|
+
items.push(['span', {class: 'icon'}, icon]);
|
|
338
|
+
} else {
|
|
339
|
+
items.push(['span', {class: 'icon'}, [['i', {class: icon}]]]);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if(!empty_string(prefix)) {
|
|
343
|
+
items.push(
|
|
344
|
+
['span', {class: 'WINDOW_TITLE_PREFIX has-text-weight-semibold'},
|
|
345
|
+
prefix]
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
if(!empty_string(title)) {
|
|
349
|
+
items.push(
|
|
350
|
+
['span', {class: 'WINDOW_TITLE_KIND has-text-weight-semibold',
|
|
351
|
+
i18n: title}, title]
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return createElement2(
|
|
356
|
+
['span', {class: 'WINDOW_TITLE yui-window-title icon-text ml-1'}, items]
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/************************************************************
|
|
361
|
+
* The window's title as ONE plain string, for consumers that
|
|
362
|
+
* cannot hold markup: the dock chip's label and its `title`
|
|
363
|
+
* tooltip. Same two halves as the bar, joined here.
|
|
364
|
+
************************************************************/
|
|
365
|
+
function composed_title(gobj)
|
|
366
|
+
{
|
|
367
|
+
let title = gobj_read_str_attr(gobj, "title");
|
|
368
|
+
let prefix = gobj_read_str_attr(gobj, "title_prefix");
|
|
369
|
+
let kind = empty_string(title) ? "" : t(title);
|
|
370
|
+
|
|
371
|
+
if(!empty_string(prefix) && !empty_string(kind)) {
|
|
372
|
+
return `${prefix} · ${kind}`;
|
|
373
|
+
}
|
|
374
|
+
return empty_string(prefix) ? kind : prefix;
|
|
375
|
+
}
|
|
376
|
+
|
|
271
377
|
/************************************************************
|
|
272
378
|
* Below the mobile breakpoint the window is a full-screen
|
|
273
379
|
* sheet (no float / drag / resize).
|
|
@@ -296,40 +402,17 @@ function resolve_manager(gobj)
|
|
|
296
402
|
}
|
|
297
403
|
|
|
298
404
|
/************************************************************
|
|
299
|
-
* Minimize:
|
|
300
|
-
*
|
|
405
|
+
* Minimize: send the window to the dock. Only reachable when
|
|
406
|
+
* a manager exists — the button is not painted otherwise.
|
|
301
407
|
************************************************************/
|
|
302
408
|
function do_minimize(gobj)
|
|
303
409
|
{
|
|
304
410
|
let manager = gobj_read_pointer_attr(gobj, "manager");
|
|
305
|
-
if(manager) {
|
|
306
|
-
|
|
307
|
-
} else {
|
|
308
|
-
toggle_shade(gobj);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/************************************************************
|
|
313
|
-
* Minimize as "shade": roll the window up to its title bar
|
|
314
|
-
* in place (self-contained; no window manager needed). Toggles.
|
|
315
|
-
************************************************************/
|
|
316
|
-
function toggle_shade(gobj)
|
|
317
|
-
{
|
|
318
|
-
let priv = gobj.priv;
|
|
319
|
-
let $container = gobj_read_attr(gobj, "$container");
|
|
320
|
-
if(!$container) {
|
|
411
|
+
if(!manager) {
|
|
412
|
+
log_error(`${gobj_short_name(gobj)}: minimize without a window manager`);
|
|
321
413
|
return;
|
|
322
414
|
}
|
|
323
|
-
|
|
324
|
-
$container.classList.remove("is-shaded");
|
|
325
|
-
if(priv.preShadeHeight) {
|
|
326
|
-
$container.style.height = parseInt(priv.preShadeHeight) + 'px';
|
|
327
|
-
}
|
|
328
|
-
} else {
|
|
329
|
-
priv.preShadeHeight = $container.getBoundingClientRect().height;
|
|
330
|
-
$container.classList.add("is-shaded");
|
|
331
|
-
$container.style.height = "auto";
|
|
332
|
-
}
|
|
415
|
+
gobj_send_event(manager, "EV_MINIMIZE_WINDOW", {window: gobj}, gobj);
|
|
333
416
|
}
|
|
334
417
|
|
|
335
418
|
/************************************************************
|
|
@@ -344,6 +427,9 @@ function build_ui(gobj)
|
|
|
344
427
|
if(is_gobj(header)) {
|
|
345
428
|
header = gobj_read_attr(header, "$container");
|
|
346
429
|
}
|
|
430
|
+
if(!header) {
|
|
431
|
+
header = build_default_header(gobj);
|
|
432
|
+
}
|
|
347
433
|
let body = gobj_read_attr(gobj, "body");
|
|
348
434
|
if(is_gobj(body)) {
|
|
349
435
|
body = gobj_read_attr(body, "$container");
|
|
@@ -411,16 +497,24 @@ function build_ui(gobj)
|
|
|
411
497
|
};
|
|
412
498
|
Object.assign(window_style, gobj_read_attr(gobj, "window_style"));
|
|
413
499
|
|
|
500
|
+
/* Logical (UPPER_SNAKE) block names, per the repo's DOM convention:
|
|
501
|
+
* uppercase identifies the block, lowercase styles it. `logical_class`
|
|
502
|
+
* is the CALLER's name for this particular window (e.g.
|
|
503
|
+
* TRANGER_KEYS_WINDOW) — several windows share the C_YUI_WINDOW class,
|
|
504
|
+
* only this tells them apart in the Inspector or in a selector. */
|
|
505
|
+
let logical = gobj_read_str_attr(gobj, "logical_class");
|
|
506
|
+
|
|
414
507
|
let $container = createElement2(
|
|
415
508
|
['div', {
|
|
416
|
-
class: 'C_YUI_WINDOW
|
|
509
|
+
class: 'C_YUI_WINDOW' + (logical ? ' ' + logical : '') +
|
|
510
|
+
' strong-shadow is-flex is-flex-direction-column' + (mobile ? ' is-mobile-sheet' : ''),
|
|
417
511
|
style: window_style}, [
|
|
418
512
|
|
|
419
513
|
/*----------------------------*
|
|
420
514
|
* Header
|
|
421
515
|
*----------------------------*/
|
|
422
516
|
['div', {
|
|
423
|
-
class: 'yui-window-header p-1 is-flex-shrink-0 is-flex is-flex-nowrap is-justify-content-space-between is-align-items-center', style: 'border-bottom:1px solid var(--bulma-border); cursor:move; box-sizing: border-box;'
|
|
517
|
+
class: 'WINDOW_HEADER yui-window-header p-1 is-flex-shrink-0 is-flex is-flex-nowrap is-justify-content-space-between is-align-items-center', style: 'border-bottom:1px solid var(--bulma-border); cursor:move; box-sizing: border-box;'
|
|
424
518
|
}, [
|
|
425
519
|
/* Custom header content: a single-row, horizontally
|
|
426
520
|
* scrollable strip. It takes the remaining width and
|
|
@@ -442,14 +536,18 @@ function build_ui(gobj)
|
|
|
442
536
|
]],
|
|
443
537
|
/* Window controls: pinned top-right, never shrink,
|
|
444
538
|
* never wrap, always on top so the click always lands.
|
|
445
|
-
* Minimize (
|
|
446
|
-
|
|
539
|
+
* Minimize (to dock) · maximize/restore · close.
|
|
540
|
+
* Minimize exists ONLY with a window manager: it means
|
|
541
|
+
* "send to the dock", and there is no such place without
|
|
542
|
+
* one. `manager` is already resolved (mt_create). */
|
|
543
|
+
['div', {class: 'WINDOW_CONTROLS yui-window-titlebar-controls is-flex-shrink-0', style: 'position:relative; z-index:1; cursor:default;'}, [
|
|
447
544
|
/*----------------------------*
|
|
448
|
-
* Minimize (
|
|
545
|
+
* Minimize (to dock)
|
|
449
546
|
*----------------------------*/
|
|
450
547
|
['button', {
|
|
451
|
-
class: 'yui-wc wc-min', type: 'button', 'aria-label': 'minimize',
|
|
452
|
-
style: gobj_read_bool_attr(gobj, "showMin")
|
|
548
|
+
class: 'WINDOW_MIN yui-wc wc-min', type: 'button', 'aria-label': 'minimize',
|
|
549
|
+
style: (gobj_read_bool_attr(gobj, "showMin") &&
|
|
550
|
+
gobj_read_pointer_attr(gobj, "manager")) ? '' : 'display:none;',
|
|
453
551
|
}, WC_MIN, {
|
|
454
552
|
click: (evt) => {
|
|
455
553
|
evt.stopPropagation();
|
|
@@ -460,7 +558,7 @@ function build_ui(gobj)
|
|
|
460
558
|
* Maximize / restore
|
|
461
559
|
*----------------------------*/
|
|
462
560
|
['button', {
|
|
463
|
-
class: 'yui-wc wc-max', type: 'button', 'aria-label': 'maximize',
|
|
561
|
+
class: 'WINDOW_MAX yui-wc wc-max', type: 'button', 'aria-label': 'maximize',
|
|
464
562
|
style: gobj_read_bool_attr(gobj, "showMax") ? '' : 'display:none;',
|
|
465
563
|
}, WC_MAX, {
|
|
466
564
|
click: (evt) => {
|
|
@@ -472,7 +570,7 @@ function build_ui(gobj)
|
|
|
472
570
|
* Close
|
|
473
571
|
*----------------------------*/
|
|
474
572
|
['button', {
|
|
475
|
-
class: 'yui-wc wc-close', type: 'button', 'aria-label': 'close',
|
|
573
|
+
class: 'WINDOW_CLOSE yui-wc wc-close', type: 'button', 'aria-label': 'close',
|
|
476
574
|
}, WC_CLOSE, {
|
|
477
575
|
click: (evt) => {
|
|
478
576
|
evt.stopPropagation();
|
|
@@ -521,7 +619,7 @@ function build_ui(gobj)
|
|
|
521
619
|
* Body
|
|
522
620
|
*----------------------------*/
|
|
523
621
|
['div', {
|
|
524
|
-
class: 'yui-window-body is-flex-grow-1 p-1',
|
|
622
|
+
class: 'WINDOW_BODY yui-window-body is-flex-grow-1 p-1',
|
|
525
623
|
style: {
|
|
526
624
|
"position": "relative", // to resize button (absolute position) when no footer bar
|
|
527
625
|
"overflow": "auto",
|
|
@@ -533,7 +631,7 @@ function build_ui(gobj)
|
|
|
533
631
|
/*----------------------------*
|
|
534
632
|
* Footer
|
|
535
633
|
*----------------------------*/
|
|
536
|
-
['div', {class: 'yui-window-footer is-flex-shrink-0'}, [
|
|
634
|
+
['div', {class: 'WINDOW_FOOTER yui-window-footer is-flex-shrink-0'}, [
|
|
537
635
|
['div', {
|
|
538
636
|
class: 'is-justify-content-space-between is-align-items-center p-1',
|
|
539
637
|
style: {
|
|
@@ -584,7 +682,7 @@ function build_ui(gobj)
|
|
|
584
682
|
*----------------------------*/
|
|
585
683
|
if(gobj_read_bool_attr(gobj, "resizable") && !gobj_read_bool_attr(gobj, "showFooter")) {
|
|
586
684
|
let $resizable_btn = createElement2(['div', {
|
|
587
|
-
class: 'without-border yui-window-resize',
|
|
685
|
+
class: 'WINDOW_RESIZE without-border yui-window-resize',
|
|
588
686
|
style: {
|
|
589
687
|
cursor: "nwse-resize",
|
|
590
688
|
display: "flex",
|
|
@@ -1049,15 +1147,6 @@ function handleResize(gobj)
|
|
|
1049
1147
|
|
|
1050
1148
|
|
|
1051
1149
|
|
|
1052
|
-
/************************************************************
|
|
1053
|
-
*
|
|
1054
|
-
************************************************************/
|
|
1055
|
-
function ac_resize(gobj, event, kw, src)
|
|
1056
|
-
{
|
|
1057
|
-
handleResize(gobj);
|
|
1058
|
-
return 0;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
1150
|
/************************************************************
|
|
1062
1151
|
*
|
|
1063
1152
|
************************************************************/
|
|
@@ -1108,8 +1197,6 @@ function ac_close(gobj, event, kw, src)
|
|
|
1108
1197
|
*---------------------------------------------*/
|
|
1109
1198
|
const gmt = {
|
|
1110
1199
|
mt_create: mt_create,
|
|
1111
|
-
mt_start: mt_start,
|
|
1112
|
-
mt_stop: mt_stop,
|
|
1113
1200
|
mt_destroy: mt_destroy
|
|
1114
1201
|
};
|
|
1115
1202
|
|
|
@@ -1128,7 +1215,6 @@ function create_gclass(gclass_name)
|
|
|
1128
1215
|
*---------------------------------------------*/
|
|
1129
1216
|
const states = [
|
|
1130
1217
|
["ST_IDLE", [
|
|
1131
|
-
["EV_RESIZE", ac_resize, null],
|
|
1132
1218
|
["EV_REFRESH", ac_refresh, null],
|
|
1133
1219
|
["EV_SHOW", ac_show, null],
|
|
1134
1220
|
["EV_HIDE", ac_hide, null],
|
|
@@ -1143,7 +1229,6 @@ function create_gclass(gclass_name)
|
|
|
1143
1229
|
["EV_WINDOW_TO_CLOSE", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_NO_WARN_SUBS],
|
|
1144
1230
|
["EV_WINDOW_MOVED", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_NO_WARN_SUBS],
|
|
1145
1231
|
["EV_WINDOW_RESIZED", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_NO_WARN_SUBS],
|
|
1146
|
-
["EV_RESIZE", 0],
|
|
1147
1232
|
["EV_REFRESH", 0],
|
|
1148
1233
|
["EV_SHOW", 0],
|
|
1149
1234
|
["EV_HIDE", 0],
|
|
@@ -205,6 +205,10 @@ function ensure_dock_style()
|
|
|
205
205
|
.yui-dock-icon svg { width: 14px; height: 14px; display: block; }
|
|
206
206
|
.yui-dock-icon i { line-height: 1; }
|
|
207
207
|
.yui-dock-label { flex: 0 0 auto; }
|
|
208
|
+
/* Split label halves (prefix = DATA, kind = translatable). The
|
|
209
|
+
* separator is CSS, never a text node — same rule as the window
|
|
210
|
+
* title bar (createElement2 trims text nodes). */
|
|
211
|
+
.yui-dock-label .yui-dock-label-prefix + .yui-dock-label-kind::before { content: " \\00b7 "; }
|
|
208
212
|
.yui-dock-close {
|
|
209
213
|
width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center;
|
|
210
214
|
padding: 0; margin-left: 1px; border: 0; border-radius: 4px;
|
|
@@ -428,7 +432,15 @@ function on_chip_click(gobj, entry)
|
|
|
428
432
|
|
|
429
433
|
|
|
430
434
|
/************************************************************
|
|
431
|
-
* EV_REGISTER_WINDOW { window, title
|
|
435
|
+
* EV_REGISTER_WINDOW { window, title, title_key,
|
|
436
|
+
* title_kind_text, title_prefix, icon }
|
|
437
|
+
*
|
|
438
|
+
* `title` is the composed, already-translated fallback (also the
|
|
439
|
+
* tooltip). The label itself renders the SPLIT halves so it can
|
|
440
|
+
* change language: `title_prefix` (DATA, plain text) + the KIND
|
|
441
|
+
* half carrying `title_key` as data-i18n, born as
|
|
442
|
+
* `title_kind_text` (the manager has no translator of its own —
|
|
443
|
+
* the app's refresh_language() re-translates the key later).
|
|
432
444
|
************************************************************/
|
|
433
445
|
function ac_register_window(gobj, event, kw, src)
|
|
434
446
|
{
|
|
@@ -442,16 +454,38 @@ function ac_register_window(gobj, event, kw, src)
|
|
|
442
454
|
apply_placement(gobj);
|
|
443
455
|
|
|
444
456
|
let title = kw.title || gobj_name(win) || "window";
|
|
457
|
+
let title_key = kw.title_key || "";
|
|
458
|
+
let kind_text = kw.title_kind_text || "";
|
|
459
|
+
let prefix = kw.title_prefix || "";
|
|
460
|
+
let label_items = [];
|
|
461
|
+
if(prefix) {
|
|
462
|
+
label_items.push(['span', {class: 'yui-dock-label-prefix'}, prefix]);
|
|
463
|
+
}
|
|
464
|
+
if(title_key) {
|
|
465
|
+
label_items.push(
|
|
466
|
+
['span', {class: 'yui-dock-label-kind', i18n: title_key},
|
|
467
|
+
kind_text || title_key]
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
if(label_items.length === 0) {
|
|
471
|
+
/* No split halves supplied (legacy caller): plain text. */
|
|
472
|
+
label_items.push(['span', {class: 'yui-dock-label-kind'}, title]);
|
|
473
|
+
}
|
|
445
474
|
|
|
446
475
|
let entry = {gobj: win, $chip: null, minimized: false};
|
|
447
476
|
/* A div (not a button) so it can hold the close button — a
|
|
448
477
|
* button inside a button is invalid. The chip toggles; the ✕
|
|
449
478
|
* closes the window (EV_CLOSE_WINDOW → the window's own teardown
|
|
450
|
-
* → EV_UNREGISTER_WINDOW removes this chip).
|
|
479
|
+
* → EV_UNREGISTER_WINDOW removes this chip).
|
|
480
|
+
* Tooltip: the composed string; when there is no DATA half it
|
|
481
|
+
* carries the key as data-i18n-title so it re-translates too. */
|
|
451
482
|
let $chip = createElement2(
|
|
452
|
-
['div',
|
|
483
|
+
['div', Object.assign(
|
|
484
|
+
{class: 'yui-dock-chip', role: 'button', tabindex: '0', title: title},
|
|
485
|
+
(!prefix && title_key) ? {'data-i18n-title': title_key} : {}
|
|
486
|
+
), [
|
|
453
487
|
chip_lead(kw.icon),
|
|
454
|
-
['span', {class: 'yui-dock-label'},
|
|
488
|
+
['span', {class: 'yui-dock-label'}, label_items],
|
|
455
489
|
['button', {class: 'yui-dock-close', type: 'button', 'aria-label': 'close'}, WC_X, {
|
|
456
490
|
click: (evt) => {
|
|
457
491
|
evt.stopPropagation();
|