@yuneta/gobj-ui 3.0.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 +324 -6
- package/dist/gobj-ui.cjs.js +10891 -5006
- package/dist/gobj-ui.es.js +10915 -5064
- package/index.js +45 -1
- package/package.json +4 -4
- package/src/c_g6_nodes_tree.js +128 -40
- package/src/c_yui_form.js +112 -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 +33 -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 +7 -42
- package/src/c_yui_treedb_topic_with_form.js +152 -103
- package/src/c_yui_treedb_topics.css +73 -0
- package/src/c_yui_treedb_topics.js +1070 -29
- package/src/c_yui_window.js +180 -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 +117 -39
- package/src/shell_route_map.css +225 -0
- package/src/shell_route_map.js +363 -0
- package/src/tabulator.css +67 -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.js +86 -43
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,22 @@ 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";
|
|
49
54
|
|
|
50
55
|
import "./c_yui_window.css";
|
|
51
56
|
|
|
@@ -78,7 +83,7 @@ SDATA(data_type_t.DTP_INTEGER, "y", 0, "100", "Y position"),
|
|
|
78
83
|
SDATA(data_type_t.DTP_INTEGER, "width", 0, "700", "Width of the window"),
|
|
79
84
|
SDATA(data_type_t.DTP_INTEGER, "height", 0, "500", "Height of the window"),
|
|
80
85
|
SDATA(data_type_t.DTP_BOOLEAN, "auto_save_size_and_position", 0, false, "Automatically save size and position"),
|
|
81
|
-
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)"),
|
|
82
87
|
SDATA(data_type_t.DTP_POINTER, "body", 0, null, "Can be a gobj with $container or any createElement2() 'content' parameter"),
|
|
83
88
|
SDATA(data_type_t.DTP_POINTER, "footer", 0, null, "Can be a gobj with $container or any createElement2() 'content' parameter"),
|
|
84
89
|
SDATA(data_type_t.DTP_BOOLEAN, "center", 0, true, "Center the window"),
|
|
@@ -88,17 +93,21 @@ SDATA(data_type_t.DTP_BOOLEAN, "resizable", 0, true, "Allow resizing"),
|
|
|
88
93
|
SDATA(data_type_t.DTP_BOOLEAN, "showFooter", 0, true, "Show footer"),
|
|
89
94
|
SDATA(data_type_t.DTP_BOOLEAN, "openMaximized",0, false, "Open the window maximized"),
|
|
90
95
|
SDATA(data_type_t.DTP_BOOLEAN, "showMax", 0, true, "Show maximize button"),
|
|
91
|
-
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`"),
|
|
92
97
|
SDATA(data_type_t.DTP_BOOLEAN, "maximized", 0, false, "Flag to indicate if maximized"),
|
|
93
98
|
SDATA(data_type_t.DTP_JSON, "window_style", 0, "{}", "Override window style"),
|
|
94
99
|
SDATA(data_type_t.DTP_POINTER, "on_close", 0, null, "Callback on destroy"),
|
|
95
100
|
SDATA(data_type_t.DTP_POINTER, "manager", 0, null, "Optional C_YUI_WINDOW_MANAGER (gobj or service name) for dock/taskbar"),
|
|
96
|
-
SDATA(data_type_t.DTP_STRING, "
|
|
97
|
-
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"),
|
|
98
105
|
// TODO pendiente focus modal keyboard
|
|
99
106
|
SDATA(data_type_t.DTP_POINTER, "focus", 0, null, "Brings focus to the element, can be a number or selector"),
|
|
100
107
|
SDATA(data_type_t.DTP_BOOLEAN, "modal", 0, false, "Enable modal mode"),
|
|
101
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)"),
|
|
102
111
|
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Internal: Window container element"),
|
|
103
112
|
SDATA(data_type_t.DTP_STRING, "window_id", 0, "", "Internal: Window ID"),
|
|
104
113
|
SDATA(data_type_t.DTP_POINTER, "win_resize_handler", 0, null, "Internal: native window 'resize' listener"),
|
|
@@ -107,7 +116,6 @@ SDATA_END()
|
|
|
107
116
|
|
|
108
117
|
let PRIVATE_DATA = {
|
|
109
118
|
prevSize: 0,
|
|
110
|
-
preShadeHeight: 0,
|
|
111
119
|
};
|
|
112
120
|
|
|
113
121
|
let __gclass__ = null;
|
|
@@ -137,12 +145,15 @@ function mt_create(gobj)
|
|
|
137
145
|
|
|
138
146
|
let window_id = "window-" + clean_name(gobj_short_name(gobj));
|
|
139
147
|
gobj_write_attr(gobj, "window_id", window_id);
|
|
140
|
-
build_ui(gobj);
|
|
141
148
|
|
|
142
149
|
/* Optional window manager (dock/taskbar): register, and raise/
|
|
143
|
-
* highlight on any pointer press.
|
|
144
|
-
*
|
|
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. */
|
|
145
153
|
let manager = resolve_manager(gobj);
|
|
154
|
+
|
|
155
|
+
build_ui(gobj);
|
|
156
|
+
|
|
146
157
|
if(manager) {
|
|
147
158
|
let $c = gobj_read_attr(gobj, "$container");
|
|
148
159
|
if($c) {
|
|
@@ -153,21 +164,44 @@ function mt_create(gobj)
|
|
|
153
164
|
}
|
|
154
165
|
}, true);
|
|
155
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);
|
|
156
174
|
gobj_send_event(manager, "EV_REGISTER_WINDOW", {
|
|
157
175
|
window: gobj,
|
|
158
|
-
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") || "",
|
|
159
181
|
icon: gobj_read_attr(gobj, "icon") || "",
|
|
160
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
|
+
}
|
|
161
195
|
}
|
|
162
196
|
|
|
163
197
|
/* Keep the window inside the viewport on a breakpoint change.
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
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. */
|
|
171
205
|
let on_win_resize = function() {
|
|
172
206
|
handleResize(gobj);
|
|
173
207
|
};
|
|
@@ -175,29 +209,6 @@ function mt_create(gobj)
|
|
|
175
209
|
window.addEventListener("resize", on_win_resize);
|
|
176
210
|
}
|
|
177
211
|
|
|
178
|
-
/***************************************************************
|
|
179
|
-
* Framework Method: Start
|
|
180
|
-
***************************************************************/
|
|
181
|
-
function mt_start(gobj)
|
|
182
|
-
{
|
|
183
|
-
let __yui_main__ = gobj_find_service("__yui_main__", false);
|
|
184
|
-
if(__yui_main__) {
|
|
185
|
-
gobj_subscribe_event(__yui_main__, "EV_RESIZE", {}, gobj);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/***************************************************************
|
|
190
|
-
* Framework Method: Stop
|
|
191
|
-
***************************************************************/
|
|
192
|
-
function mt_stop(gobj)
|
|
193
|
-
{
|
|
194
|
-
// TODO quita esto para chequear el fallo dl_subscribings not implemented
|
|
195
|
-
let __yui_main__ = gobj_find_service("__yui_main__", false);
|
|
196
|
-
if(__yui_main__) {
|
|
197
|
-
gobj_unsubscribe_event(__yui_main__, "EV_RESIZE", {}, gobj);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
212
|
/***************************************************************
|
|
202
213
|
* Framework Method: Destroy
|
|
203
214
|
***************************************************************/
|
|
@@ -208,6 +219,17 @@ function mt_destroy(gobj)
|
|
|
208
219
|
gobj_send_event(manager, "EV_UNREGISTER_WINDOW", {window: gobj}, gobj);
|
|
209
220
|
}
|
|
210
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
|
+
|
|
211
233
|
let on_win_resize = gobj_read_attr(gobj, "win_resize_handler");
|
|
212
234
|
if(on_win_resize) {
|
|
213
235
|
window.removeEventListener("resize", on_win_resize);
|
|
@@ -243,6 +265,16 @@ function ensure_window_style()
|
|
|
243
265
|
-webkit-user-select: none; user-select: none;
|
|
244
266
|
}
|
|
245
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
|
+
}
|
|
246
278
|
.yui-wc {
|
|
247
279
|
width: 30px; height: 26px; display: inline-flex; align-items: center; justify-content: center;
|
|
248
280
|
padding: 0; border: 0; background: transparent; color: var(--bulma-text); cursor: pointer;
|
|
@@ -251,9 +283,6 @@ function ensure_window_style()
|
|
|
251
283
|
.yui-wc:hover { background: var(--bulma-scheme-main-ter); color: var(--bulma-text-strong); }
|
|
252
284
|
.yui-wc.wc-close:hover { background: #e0364a; color: #fff; }
|
|
253
285
|
.yui-wc svg { width: 15px; height: 15px; display: block; }
|
|
254
|
-
.C_YUI_WINDOW.is-shaded .yui-window-body,
|
|
255
|
-
.C_YUI_WINDOW.is-shaded .yui-window-footer { display: none !important; }
|
|
256
|
-
.C_YUI_WINDOW.is-shaded { height: auto !important; }
|
|
257
286
|
.yui-window-resize { color: var(--bulma-text-weak); opacity: 0.55; }
|
|
258
287
|
.yui-window-resize:hover { opacity: 1; color: var(--bulma-text); }
|
|
259
288
|
@media (max-width: ${MOBILE_MAX}px) {
|
|
@@ -270,6 +299,81 @@ function ensure_window_style()
|
|
|
270
299
|
document.head.appendChild($style);
|
|
271
300
|
}
|
|
272
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
|
+
|
|
273
377
|
/************************************************************
|
|
274
378
|
* Below the mobile breakpoint the window is a full-screen
|
|
275
379
|
* sheet (no float / drag / resize).
|
|
@@ -298,40 +402,17 @@ function resolve_manager(gobj)
|
|
|
298
402
|
}
|
|
299
403
|
|
|
300
404
|
/************************************************************
|
|
301
|
-
* Minimize:
|
|
302
|
-
*
|
|
405
|
+
* Minimize: send the window to the dock. Only reachable when
|
|
406
|
+
* a manager exists — the button is not painted otherwise.
|
|
303
407
|
************************************************************/
|
|
304
408
|
function do_minimize(gobj)
|
|
305
409
|
{
|
|
306
410
|
let manager = gobj_read_pointer_attr(gobj, "manager");
|
|
307
|
-
if(manager) {
|
|
308
|
-
|
|
309
|
-
} else {
|
|
310
|
-
toggle_shade(gobj);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/************************************************************
|
|
315
|
-
* Minimize as "shade": roll the window up to its title bar
|
|
316
|
-
* in place (self-contained; no window manager needed). Toggles.
|
|
317
|
-
************************************************************/
|
|
318
|
-
function toggle_shade(gobj)
|
|
319
|
-
{
|
|
320
|
-
let priv = gobj.priv;
|
|
321
|
-
let $container = gobj_read_attr(gobj, "$container");
|
|
322
|
-
if(!$container) {
|
|
411
|
+
if(!manager) {
|
|
412
|
+
log_error(`${gobj_short_name(gobj)}: minimize without a window manager`);
|
|
323
413
|
return;
|
|
324
414
|
}
|
|
325
|
-
|
|
326
|
-
$container.classList.remove("is-shaded");
|
|
327
|
-
if(priv.preShadeHeight) {
|
|
328
|
-
$container.style.height = parseInt(priv.preShadeHeight) + 'px';
|
|
329
|
-
}
|
|
330
|
-
} else {
|
|
331
|
-
priv.preShadeHeight = $container.getBoundingClientRect().height;
|
|
332
|
-
$container.classList.add("is-shaded");
|
|
333
|
-
$container.style.height = "auto";
|
|
334
|
-
}
|
|
415
|
+
gobj_send_event(manager, "EV_MINIMIZE_WINDOW", {window: gobj}, gobj);
|
|
335
416
|
}
|
|
336
417
|
|
|
337
418
|
/************************************************************
|
|
@@ -346,6 +427,9 @@ function build_ui(gobj)
|
|
|
346
427
|
if(is_gobj(header)) {
|
|
347
428
|
header = gobj_read_attr(header, "$container");
|
|
348
429
|
}
|
|
430
|
+
if(!header) {
|
|
431
|
+
header = build_default_header(gobj);
|
|
432
|
+
}
|
|
349
433
|
let body = gobj_read_attr(gobj, "body");
|
|
350
434
|
if(is_gobj(body)) {
|
|
351
435
|
body = gobj_read_attr(body, "$container");
|
|
@@ -413,16 +497,24 @@ function build_ui(gobj)
|
|
|
413
497
|
};
|
|
414
498
|
Object.assign(window_style, gobj_read_attr(gobj, "window_style"));
|
|
415
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
|
+
|
|
416
507
|
let $container = createElement2(
|
|
417
508
|
['div', {
|
|
418
|
-
class: 'C_YUI_WINDOW
|
|
509
|
+
class: 'C_YUI_WINDOW' + (logical ? ' ' + logical : '') +
|
|
510
|
+
' strong-shadow is-flex is-flex-direction-column' + (mobile ? ' is-mobile-sheet' : ''),
|
|
419
511
|
style: window_style}, [
|
|
420
512
|
|
|
421
513
|
/*----------------------------*
|
|
422
514
|
* Header
|
|
423
515
|
*----------------------------*/
|
|
424
516
|
['div', {
|
|
425
|
-
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;'
|
|
426
518
|
}, [
|
|
427
519
|
/* Custom header content: a single-row, horizontally
|
|
428
520
|
* scrollable strip. It takes the remaining width and
|
|
@@ -444,14 +536,18 @@ function build_ui(gobj)
|
|
|
444
536
|
]],
|
|
445
537
|
/* Window controls: pinned top-right, never shrink,
|
|
446
538
|
* never wrap, always on top so the click always lands.
|
|
447
|
-
* Minimize (
|
|
448
|
-
|
|
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;'}, [
|
|
449
544
|
/*----------------------------*
|
|
450
|
-
* Minimize (
|
|
545
|
+
* Minimize (to dock)
|
|
451
546
|
*----------------------------*/
|
|
452
547
|
['button', {
|
|
453
|
-
class: 'yui-wc wc-min', type: 'button', 'aria-label': 'minimize',
|
|
454
|
-
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;',
|
|
455
551
|
}, WC_MIN, {
|
|
456
552
|
click: (evt) => {
|
|
457
553
|
evt.stopPropagation();
|
|
@@ -462,7 +558,7 @@ function build_ui(gobj)
|
|
|
462
558
|
* Maximize / restore
|
|
463
559
|
*----------------------------*/
|
|
464
560
|
['button', {
|
|
465
|
-
class: 'yui-wc wc-max', type: 'button', 'aria-label': 'maximize',
|
|
561
|
+
class: 'WINDOW_MAX yui-wc wc-max', type: 'button', 'aria-label': 'maximize',
|
|
466
562
|
style: gobj_read_bool_attr(gobj, "showMax") ? '' : 'display:none;',
|
|
467
563
|
}, WC_MAX, {
|
|
468
564
|
click: (evt) => {
|
|
@@ -474,7 +570,7 @@ function build_ui(gobj)
|
|
|
474
570
|
* Close
|
|
475
571
|
*----------------------------*/
|
|
476
572
|
['button', {
|
|
477
|
-
class: 'yui-wc wc-close', type: 'button', 'aria-label': 'close',
|
|
573
|
+
class: 'WINDOW_CLOSE yui-wc wc-close', type: 'button', 'aria-label': 'close',
|
|
478
574
|
}, WC_CLOSE, {
|
|
479
575
|
click: (evt) => {
|
|
480
576
|
evt.stopPropagation();
|
|
@@ -523,7 +619,7 @@ function build_ui(gobj)
|
|
|
523
619
|
* Body
|
|
524
620
|
*----------------------------*/
|
|
525
621
|
['div', {
|
|
526
|
-
class: 'yui-window-body is-flex-grow-1 p-1',
|
|
622
|
+
class: 'WINDOW_BODY yui-window-body is-flex-grow-1 p-1',
|
|
527
623
|
style: {
|
|
528
624
|
"position": "relative", // to resize button (absolute position) when no footer bar
|
|
529
625
|
"overflow": "auto",
|
|
@@ -535,7 +631,7 @@ function build_ui(gobj)
|
|
|
535
631
|
/*----------------------------*
|
|
536
632
|
* Footer
|
|
537
633
|
*----------------------------*/
|
|
538
|
-
['div', {class: 'yui-window-footer is-flex-shrink-0'}, [
|
|
634
|
+
['div', {class: 'WINDOW_FOOTER yui-window-footer is-flex-shrink-0'}, [
|
|
539
635
|
['div', {
|
|
540
636
|
class: 'is-justify-content-space-between is-align-items-center p-1',
|
|
541
637
|
style: {
|
|
@@ -586,7 +682,7 @@ function build_ui(gobj)
|
|
|
586
682
|
*----------------------------*/
|
|
587
683
|
if(gobj_read_bool_attr(gobj, "resizable") && !gobj_read_bool_attr(gobj, "showFooter")) {
|
|
588
684
|
let $resizable_btn = createElement2(['div', {
|
|
589
|
-
class: 'without-border yui-window-resize',
|
|
685
|
+
class: 'WINDOW_RESIZE without-border yui-window-resize',
|
|
590
686
|
style: {
|
|
591
687
|
cursor: "nwse-resize",
|
|
592
688
|
display: "flex",
|
|
@@ -1051,15 +1147,6 @@ function handleResize(gobj)
|
|
|
1051
1147
|
|
|
1052
1148
|
|
|
1053
1149
|
|
|
1054
|
-
/************************************************************
|
|
1055
|
-
*
|
|
1056
|
-
************************************************************/
|
|
1057
|
-
function ac_resize(gobj, event, kw, src)
|
|
1058
|
-
{
|
|
1059
|
-
handleResize(gobj);
|
|
1060
|
-
return 0;
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
1150
|
/************************************************************
|
|
1064
1151
|
*
|
|
1065
1152
|
************************************************************/
|
|
@@ -1110,8 +1197,6 @@ function ac_close(gobj, event, kw, src)
|
|
|
1110
1197
|
*---------------------------------------------*/
|
|
1111
1198
|
const gmt = {
|
|
1112
1199
|
mt_create: mt_create,
|
|
1113
|
-
mt_start: mt_start,
|
|
1114
|
-
mt_stop: mt_stop,
|
|
1115
1200
|
mt_destroy: mt_destroy
|
|
1116
1201
|
};
|
|
1117
1202
|
|
|
@@ -1130,7 +1215,6 @@ function create_gclass(gclass_name)
|
|
|
1130
1215
|
*---------------------------------------------*/
|
|
1131
1216
|
const states = [
|
|
1132
1217
|
["ST_IDLE", [
|
|
1133
|
-
["EV_RESIZE", ac_resize, null],
|
|
1134
1218
|
["EV_REFRESH", ac_refresh, null],
|
|
1135
1219
|
["EV_SHOW", ac_show, null],
|
|
1136
1220
|
["EV_HIDE", ac_hide, null],
|
|
@@ -1145,7 +1229,6 @@ function create_gclass(gclass_name)
|
|
|
1145
1229
|
["EV_WINDOW_TO_CLOSE", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_NO_WARN_SUBS],
|
|
1146
1230
|
["EV_WINDOW_MOVED", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_NO_WARN_SUBS],
|
|
1147
1231
|
["EV_WINDOW_RESIZED", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_NO_WARN_SUBS],
|
|
1148
|
-
["EV_RESIZE", 0],
|
|
1149
1232
|
["EV_REFRESH", 0],
|
|
1150
1233
|
["EV_SHOW", 0],
|
|
1151
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();
|