@yuneta/gobj-ui 2.0.0 → 2.2.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 +25 -20
- package/dist/gobj-ui.cjs.js +2331 -526
- package/dist/gobj-ui.es.js +2331 -528
- package/index.js +32 -31
- package/package.json +5 -6
- package/{c_g6_nodes_tree.js → src/c_g6_nodes_tree.js} +6 -1
- package/{c_yui_form.js → src/c_yui_form.js} +1 -1
- package/{c_yui_gobj_tree_js.js → src/c_yui_gobj_tree_js.js} +1 -1
- package/{c_yui_json_graph.js → src/c_yui_json_graph.js} +1 -1
- package/{c_yui_main.js → src/c_yui_main.js} +3 -3
- package/{c_yui_map.js → src/c_yui_map.js} +6 -1
- package/{c_yui_nav.js → src/c_yui_nav.js} +134 -26
- package/{c_yui_pager.js → src/c_yui_pager.js} +1 -1
- package/{c_yui_routing.css → src/c_yui_routing.css} +1 -1
- package/{c_yui_routing.js → src/c_yui_routing.js} +1 -1
- package/{c_yui_shell.css → src/c_yui_shell.css} +110 -0
- package/{c_yui_shell.js → src/c_yui_shell.js} +163 -10
- package/{c_yui_tabs.js → src/c_yui_tabs.js} +1 -1
- package/{c_yui_treedb_graph.js → src/c_yui_treedb_graph.js} +35 -9
- package/{c_yui_treedb_topic_with_form.js → src/c_yui_treedb_topic_with_form.js} +1 -1
- package/{c_yui_treedb_topics.js → src/c_yui_treedb_topics.js} +36 -8
- package/{c_yui_uplot.js → src/c_yui_uplot.js} +1 -1
- package/{c_yui_window.js → src/c_yui_window.js} +242 -21
- package/src/c_yui_window_manager.js +602 -0
- package/{c_yui_wizard.js → src/c_yui_wizard.js} +1 -1
- package/{shell_modals.js → src/shell_modals.js} +53 -13
- package/src/tabulator.css +53 -0
- package/src/yui_dev.js +1478 -0
- package/{yui_icons.css → src/yui_icons.css} +5 -0
- package/src/yui_inputs.css +32 -0
- package/src/yui_inputs.js +71 -0
- package/vite.config.js +0 -131
- package/yui_dev.js +0 -583
- /package/{c_yui_main.css → src/c_yui_main.css} +0 -0
- /package/{c_yui_map.css → src/c_yui_map.css} +0 -0
- /package/{c_yui_treedb_topic_with_form.css → src/c_yui_treedb_topic_with_form.css} +0 -0
- /package/{g6_drag_canvas_touch.js → src/g6_drag_canvas_touch.js} +0 -0
- /package/{lib_graph.css → src/lib_graph.css} +0 -0
- /package/{lib_graph.js → src/lib_graph.js} +0 -0
- /package/{lib_icons.js → src/lib_icons.js} +0 -0
- /package/{lib_maplibre.js → src/lib_maplibre.js} +0 -0
- /package/{pager_helpers.js → src/pager_helpers.js} +0 -0
- /package/{pager_helpers.test.js → src/pager_helpers.test.js} +0 -0
- /package/{route_resolver.js → src/route_resolver.js} +0 -0
- /package/{route_resolver.test.js → src/route_resolver.test.js} +0 -0
- /package/{shell_focus_trap.js → src/shell_focus_trap.js} +0 -0
- /package/{shell_focus_trap.test.js → src/shell_focus_trap.test.js} +0 -0
- /package/{shell_show_on.js → src/shell_show_on.js} +0 -0
- /package/{shell_show_on.test.js → src/shell_show_on.test.js} +0 -0
- /package/{shell_toolbar_helpers.js → src/shell_toolbar_helpers.js} +0 -0
- /package/{shell_toolbar_helpers.test.js → src/shell_toolbar_helpers.test.js} +0 -0
- /package/{themes.js → src/themes.js} +0 -0
- /package/{wizard_helpers.js → src/wizard_helpers.js} +0 -0
- /package/{wizard_helpers.test.js → src/wizard_helpers.test.js} +0 -0
- /package/{ytable.css → src/ytable.css} +0 -0
- /package/{ytable.js → src/ytable.js} +0 -0
- /package/{yui_toolbar.css → src/yui_toolbar.css} +0 -0
- /package/{yui_toolbar.js → src/yui_toolbar.js} +0 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* c_yui_window_manager.js
|
|
3
|
+
*
|
|
4
|
+
* Window manager / dock (taskbar).
|
|
5
|
+
*
|
|
6
|
+
* A light registry + dock strip for C_YUI_WINDOW instances. Windows
|
|
7
|
+
* opt in via their `manager` attr: on create they REGISTER, on
|
|
8
|
+
* destroy they UNREGISTER, their minimize button MINIMIZEs (roll to
|
|
9
|
+
* the dock instead of shading in place), and any pointer press
|
|
10
|
+
* FOCUSes (raise z-order + highlight the dock chip).
|
|
11
|
+
*
|
|
12
|
+
* The manager never owns window lifecycle: it only toggles a
|
|
13
|
+
* window's `$container` display / z-index and reflects state in the
|
|
14
|
+
* dock. Closing stays the window's own affordance (its ✕ →
|
|
15
|
+
* gobj_destroy → EV_UNREGISTER_WINDOW removes the chip).
|
|
16
|
+
*
|
|
17
|
+
* Orthogonal to C_YUI_PAGER (page-stack): a window may host a pager
|
|
18
|
+
* in its body; the manager only arranges the windows themselves.
|
|
19
|
+
*
|
|
20
|
+
* The dock placement is configurable via `dock_mode`:
|
|
21
|
+
* - "floating" (default) a detached bar pinned to a corner
|
|
22
|
+
* (`dock_corner`), hovering over the content.
|
|
23
|
+
* - "inline" a full-width taskbar row mounted inside a layout
|
|
24
|
+
* container (`inline_selector`, e.g. a shell zone).
|
|
25
|
+
* - "responsive" floating on wide viewports, inline on narrow ones
|
|
26
|
+
* (`responsive_query`) — so on mobile it lives above
|
|
27
|
+
* the bottom menu instead of covering it.
|
|
28
|
+
*
|
|
29
|
+
* Copyright (c) 2026, ArtGins.
|
|
30
|
+
* All Rights Reserved.
|
|
31
|
+
***********************************************************************/
|
|
32
|
+
import {
|
|
33
|
+
SDATA,
|
|
34
|
+
SDATA_END,
|
|
35
|
+
data_type_t,
|
|
36
|
+
gclass_create,
|
|
37
|
+
log_error,
|
|
38
|
+
gobj_read_pointer_attr,
|
|
39
|
+
gobj_subscribe_event,
|
|
40
|
+
gobj_send_event,
|
|
41
|
+
createElement2,
|
|
42
|
+
gobj_write_attr,
|
|
43
|
+
gobj_read_attr,
|
|
44
|
+
gobj_name,
|
|
45
|
+
} from "@yuneta/gobj-js";
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
/***************************************************************
|
|
49
|
+
* Constants
|
|
50
|
+
***************************************************************/
|
|
51
|
+
const GCLASS_NAME = "C_YUI_WINDOW_MANAGER";
|
|
52
|
+
|
|
53
|
+
/* Dock sits above every window: windows raise to an incrementing
|
|
54
|
+
* counter that starts well below this. */
|
|
55
|
+
const DOCK_Z = 1000000;
|
|
56
|
+
|
|
57
|
+
/* Chip close glyph (currentColor → theme-aware). */
|
|
58
|
+
const WC_X = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M4.5 4.5 L11.5 11.5 M11.5 4.5 L4.5 11.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>';
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
/***************************************************************
|
|
62
|
+
* Data
|
|
63
|
+
***************************************************************/
|
|
64
|
+
const attrs_table = [
|
|
65
|
+
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
|
|
66
|
+
SDATA(data_type_t.DTP_STRING, "dock_mode", 0, "floating", "floating | inline | responsive (floating on wide screens, inline on narrow)"),
|
|
67
|
+
SDATA(data_type_t.DTP_STRING, "dock_corner", 0, "bottom-left", "Floating placement: top-left | top-right | bottom-left | bottom-right"),
|
|
68
|
+
SDATA(data_type_t.DTP_STRING, "inline_selector", 0, "", "CSS selector of the container the dock mounts into when inline (e.g. a shell zone)"),
|
|
69
|
+
SDATA(data_type_t.DTP_STRING, "responsive_query", 0, "(max-width: 768px)", "Media query that selects inline placement when dock_mode is 'responsive'"),
|
|
70
|
+
SDATA(data_type_t.DTP_POINTER, "$parent", 0, null, "Where the floating dock mounts (default document.body)"),
|
|
71
|
+
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Internal: the dock element"),
|
|
72
|
+
SDATA_END()
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
let PRIVATE_DATA = {
|
|
76
|
+
windows: null, /* [{gobj, $chip, minimized}] */
|
|
77
|
+
z: 0, /* z-order counter handed to focused windows */
|
|
78
|
+
mql: null, /* MediaQueryList when dock_mode is 'responsive' */
|
|
79
|
+
mql_handler: null, /* its change listener (kept for teardown) */
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let __gclass__ = null;
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
/******************************
|
|
88
|
+
* Framework Methods
|
|
89
|
+
******************************/
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
/***************************************************************
|
|
95
|
+
* Framework Method: Create
|
|
96
|
+
***************************************************************/
|
|
97
|
+
function mt_create(gobj)
|
|
98
|
+
{
|
|
99
|
+
let priv = gobj.priv;
|
|
100
|
+
|
|
101
|
+
priv.windows = [];
|
|
102
|
+
priv.z = 10;
|
|
103
|
+
|
|
104
|
+
/* In 'responsive' mode the placement follows a media query: watch it
|
|
105
|
+
* so the dock re-homes (floating <-> inline) when the viewport crosses
|
|
106
|
+
* the breakpoint. Set up BEFORE build_dock so the first placement is
|
|
107
|
+
* already correct. */
|
|
108
|
+
if((gobj_read_attr(gobj, "dock_mode") || "floating") === "responsive") {
|
|
109
|
+
let q = gobj_read_attr(gobj, "responsive_query") || "(max-width: 768px)";
|
|
110
|
+
priv.mql = window.matchMedia(q);
|
|
111
|
+
priv.mql_handler = () => {
|
|
112
|
+
apply_placement(gobj);
|
|
113
|
+
};
|
|
114
|
+
priv.mql.addEventListener("change", priv.mql_handler);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
build_dock(gobj);
|
|
118
|
+
|
|
119
|
+
/*
|
|
120
|
+
* SERVICE subscription model
|
|
121
|
+
*/
|
|
122
|
+
const subscriber = gobj_read_pointer_attr(gobj, "subscriber");
|
|
123
|
+
if(subscriber) {
|
|
124
|
+
gobj_subscribe_event(gobj, null, {}, subscriber);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/***************************************************************
|
|
129
|
+
* Framework Method: Destroy
|
|
130
|
+
***************************************************************/
|
|
131
|
+
function mt_destroy(gobj)
|
|
132
|
+
{
|
|
133
|
+
let priv = gobj.priv;
|
|
134
|
+
|
|
135
|
+
if(priv.mql && priv.mql_handler) {
|
|
136
|
+
priv.mql.removeEventListener("change", priv.mql_handler);
|
|
137
|
+
priv.mql = null;
|
|
138
|
+
priv.mql_handler = null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
destroy_dock(gobj);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
/***************************
|
|
148
|
+
* Local Methods
|
|
149
|
+
***************************/
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
/************************************************************
|
|
155
|
+
* Inject the dock stylesheet once (theme-aware).
|
|
156
|
+
************************************************************/
|
|
157
|
+
function ensure_dock_style()
|
|
158
|
+
{
|
|
159
|
+
if(document.getElementById('yui-dock-style')) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
let css = `
|
|
163
|
+
.yui-dock {
|
|
164
|
+
z-index: ${DOCK_Z};
|
|
165
|
+
display: flex; gap: 6px; align-items: center; padding: 5px 8px;
|
|
166
|
+
overflow-x: auto;
|
|
167
|
+
background: var(--bulma-scheme-main-bis); color: var(--bulma-text);
|
|
168
|
+
border: 1px solid var(--bulma-border);
|
|
169
|
+
font-family: var(--bulma-family-primary, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
|
|
170
|
+
}
|
|
171
|
+
.yui-dock.is-empty { display: none; }
|
|
172
|
+
|
|
173
|
+
/* Floating: a detached bar hovering over the content, pinned to a corner. */
|
|
174
|
+
.yui-dock--floating {
|
|
175
|
+
position: fixed;
|
|
176
|
+
max-width: calc(100vw - 24px);
|
|
177
|
+
border-radius: 10px;
|
|
178
|
+
box-shadow: 0 6px 22px rgba(0,0,0,0.25);
|
|
179
|
+
}
|
|
180
|
+
.yui-dock--bottom-left { bottom: 12px; left: 12px; }
|
|
181
|
+
.yui-dock--bottom-right { bottom: 12px; right: 12px; }
|
|
182
|
+
.yui-dock--top-left { top: 12px; left: 12px; }
|
|
183
|
+
.yui-dock--top-right { top: 12px; right: 12px; }
|
|
184
|
+
|
|
185
|
+
/* Inline: flows inside a layout zone as a full-width taskbar row (the zone
|
|
186
|
+
* already provides the separating border, so the bar itself stays flat). */
|
|
187
|
+
.yui-dock--inline {
|
|
188
|
+
position: relative;
|
|
189
|
+
width: 100%; max-width: 100%;
|
|
190
|
+
border: 0;
|
|
191
|
+
border-radius: 0;
|
|
192
|
+
box-shadow: none;
|
|
193
|
+
}
|
|
194
|
+
.yui-dock-chip {
|
|
195
|
+
display: inline-flex; align-items: center; gap: 7px; font: inherit; font-size: 12px;
|
|
196
|
+
padding: 4px 5px 4px 11px; border: 1px solid var(--bulma-border); border-radius: 7px;
|
|
197
|
+
background: var(--bulma-scheme-main); color: var(--bulma-text); cursor: pointer; white-space: nowrap;
|
|
198
|
+
}
|
|
199
|
+
.yui-dock-chip:hover { border-color: var(--bulma-text-weak); }
|
|
200
|
+
.yui-dock-chip.is-active { border-color: #2563eb; color: #2563eb; background: rgba(37,99,235,0.10); font-weight: 600; }
|
|
201
|
+
.yui-dock-chip.is-min { opacity: 0.65; }
|
|
202
|
+
.yui-dock-dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; flex: 0 0 auto; }
|
|
203
|
+
.yui-dock-chip.is-min .yui-dock-dot { background: var(--bulma-text-weak); }
|
|
204
|
+
.yui-dock-icon { display: inline-flex; align-items: center; justify-content: center; width: 15px; height: 15px; font-size: 14px; flex: 0 0 auto; }
|
|
205
|
+
.yui-dock-icon svg { width: 14px; height: 14px; display: block; }
|
|
206
|
+
.yui-dock-icon i { line-height: 1; }
|
|
207
|
+
.yui-dock-label { flex: 0 0 auto; }
|
|
208
|
+
.yui-dock-close {
|
|
209
|
+
width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center;
|
|
210
|
+
padding: 0; margin-left: 1px; border: 0; border-radius: 4px;
|
|
211
|
+
background: transparent; color: var(--bulma-text-weak); cursor: pointer; flex: 0 0 auto;
|
|
212
|
+
}
|
|
213
|
+
.yui-dock-close:hover { background: #e0364a; color: #fff; }
|
|
214
|
+
.yui-dock-close svg { width: 11px; height: 11px; display: block; }
|
|
215
|
+
:root[data-theme="dark"] .yui-dock-chip.is-active { border-color: #60a5fa; color: #93c5fd; background: rgba(96,165,250,0.16); }
|
|
216
|
+
`;
|
|
217
|
+
let $style = document.createElement('style');
|
|
218
|
+
$style.id = 'yui-dock-style';
|
|
219
|
+
$style.textContent = css;
|
|
220
|
+
document.head.appendChild($style);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/************************************************************
|
|
224
|
+
* Build the dock element (empty, hidden until a window
|
|
225
|
+
* registers).
|
|
226
|
+
************************************************************/
|
|
227
|
+
function build_dock(gobj)
|
|
228
|
+
{
|
|
229
|
+
ensure_dock_style();
|
|
230
|
+
|
|
231
|
+
let $dock = createElement2(['div', {class: 'yui-dock is-empty'}, []]);
|
|
232
|
+
gobj_write_attr(gobj, "$container", $dock);
|
|
233
|
+
|
|
234
|
+
apply_placement(gobj);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/************************************************************
|
|
238
|
+
* Whether the dock should render inline (in a layout zone)
|
|
239
|
+
* rather than floating, given the mode and — for
|
|
240
|
+
* 'responsive' — the current viewport.
|
|
241
|
+
************************************************************/
|
|
242
|
+
function want_inline_placement(gobj)
|
|
243
|
+
{
|
|
244
|
+
let mode = gobj_read_attr(gobj, "dock_mode") || "floating";
|
|
245
|
+
if(mode === "inline") {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
if(mode === "responsive") {
|
|
249
|
+
let priv = gobj.priv;
|
|
250
|
+
return !!(priv.mql && priv.mql.matches);
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/************************************************************
|
|
256
|
+
* Place (or re-place) the dock: pick floating vs inline,
|
|
257
|
+
* set the modifier classes, and mount it in the right
|
|
258
|
+
* container. Also re-attaches the dock if it got detached
|
|
259
|
+
* (e.g. a shell that replaced its parent's children after
|
|
260
|
+
* the dock was first mounted, or the responsive breakpoint
|
|
261
|
+
* flipped). Idempotent — safe to call on every register and
|
|
262
|
+
* on every media-query change.
|
|
263
|
+
************************************************************/
|
|
264
|
+
function apply_placement(gobj)
|
|
265
|
+
{
|
|
266
|
+
let $dock = gobj_read_attr(gobj, "$container");
|
|
267
|
+
if(!$dock) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* Inline needs a host in the DOM. The shell is often built lazily, so
|
|
272
|
+
* its zone may not exist yet — fall back to floating until it does, so
|
|
273
|
+
* the dock never vanishes. */
|
|
274
|
+
let $inline = null;
|
|
275
|
+
if(want_inline_placement(gobj)) {
|
|
276
|
+
let sel = gobj_read_attr(gobj, "inline_selector");
|
|
277
|
+
if(sel) {
|
|
278
|
+
$inline = document.querySelector(sel);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
let inline = !!$inline;
|
|
282
|
+
|
|
283
|
+
$dock.classList.toggle("yui-dock--inline", inline);
|
|
284
|
+
$dock.classList.toggle("yui-dock--floating", !inline);
|
|
285
|
+
|
|
286
|
+
let corner = gobj_read_attr(gobj, "dock_corner") || "bottom-left";
|
|
287
|
+
for(let c of ["top-left", "top-right", "bottom-left", "bottom-right"]) {
|
|
288
|
+
$dock.classList.toggle(`yui-dock--${c}`, !inline && c === corner);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
let target = inline ? $inline : (gobj_read_attr(gobj, "$parent") || document.body);
|
|
292
|
+
if($dock.parentNode !== target) {
|
|
293
|
+
target.appendChild($dock);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/************************************************************
|
|
298
|
+
* Destroy the dock element.
|
|
299
|
+
************************************************************/
|
|
300
|
+
function destroy_dock(gobj)
|
|
301
|
+
{
|
|
302
|
+
let $dock = gobj_read_attr(gobj, "$container");
|
|
303
|
+
if($dock) {
|
|
304
|
+
if($dock.parentNode) {
|
|
305
|
+
$dock.parentNode.removeChild($dock);
|
|
306
|
+
}
|
|
307
|
+
gobj_write_attr(gobj, "$container", null);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/************************************************************
|
|
312
|
+
* The chip's leading mark: a per-type icon if the window
|
|
313
|
+
* provides one (a `yi-*` class name, or inline SVG/HTML), else
|
|
314
|
+
* the status dot. Returns a createElement2() spec.
|
|
315
|
+
************************************************************/
|
|
316
|
+
function chip_lead(icon)
|
|
317
|
+
{
|
|
318
|
+
if(!icon) {
|
|
319
|
+
return ['span', {class: 'yui-dock-dot'}, ''];
|
|
320
|
+
}
|
|
321
|
+
if(icon.charAt(0) === '<') {
|
|
322
|
+
return ['span', {class: 'yui-dock-icon'}, icon];
|
|
323
|
+
}
|
|
324
|
+
return ['span', {class: 'yui-dock-icon'}, [['i', {class: icon}]]];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/************************************************************
|
|
328
|
+
* Find a registry entry by its window gobj.
|
|
329
|
+
************************************************************/
|
|
330
|
+
function find_entry(priv, win)
|
|
331
|
+
{
|
|
332
|
+
for(let e of priv.windows) {
|
|
333
|
+
if(e.gobj === win) {
|
|
334
|
+
return e;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/************************************************************
|
|
341
|
+
* Show / hide the empty state.
|
|
342
|
+
************************************************************/
|
|
343
|
+
function sync_dock_visibility(gobj)
|
|
344
|
+
{
|
|
345
|
+
let priv = gobj.priv;
|
|
346
|
+
let $dock = gobj_read_attr(gobj, "$container");
|
|
347
|
+
if($dock) {
|
|
348
|
+
$dock.classList.toggle('is-empty', priv.windows.length === 0);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/************************************************************
|
|
353
|
+
* Mark one entry active (raised), the rest inactive.
|
|
354
|
+
************************************************************/
|
|
355
|
+
function set_active(gobj, entry)
|
|
356
|
+
{
|
|
357
|
+
let priv = gobj.priv;
|
|
358
|
+
for(let e of priv.windows) {
|
|
359
|
+
if(e.$chip) {
|
|
360
|
+
e.$chip.classList.toggle('is-active', e === entry);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/************************************************************
|
|
366
|
+
* Bring a window to front and mark it active/visible.
|
|
367
|
+
************************************************************/
|
|
368
|
+
function focus_entry(gobj, entry)
|
|
369
|
+
{
|
|
370
|
+
let priv = gobj.priv;
|
|
371
|
+
let $win = gobj_read_attr(entry.gobj, "$container");
|
|
372
|
+
if($win) {
|
|
373
|
+
if(entry.minimized) {
|
|
374
|
+
/* Clear the inline display so the window falls back to its
|
|
375
|
+
* Bulma `is-flex` layout (see minimize_entry). */
|
|
376
|
+
$win.style.removeProperty('display');
|
|
377
|
+
entry.minimized = false;
|
|
378
|
+
if(entry.$chip) {
|
|
379
|
+
entry.$chip.classList.remove('is-min');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
priv.z += 1;
|
|
383
|
+
$win.style.zIndex = priv.z;
|
|
384
|
+
}
|
|
385
|
+
set_active(gobj, entry);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/************************************************************
|
|
389
|
+
* Send a window to the dock (hidden, chip dimmed).
|
|
390
|
+
************************************************************/
|
|
391
|
+
function minimize_entry(gobj, entry)
|
|
392
|
+
{
|
|
393
|
+
let $win = gobj_read_attr(entry.gobj, "$container");
|
|
394
|
+
if($win) {
|
|
395
|
+
/* The window container carries Bulma's `is-flex`
|
|
396
|
+
* (display:flex !important), so a plain inline display:none
|
|
397
|
+
* is overridden — hide with `!important` to win. */
|
|
398
|
+
$win.style.setProperty('display', 'none', 'important');
|
|
399
|
+
}
|
|
400
|
+
entry.minimized = true;
|
|
401
|
+
if(entry.$chip) {
|
|
402
|
+
entry.$chip.classList.add('is-min');
|
|
403
|
+
entry.$chip.classList.remove('is-active');
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/************************************************************
|
|
408
|
+
* Dock chip click: taskbar toggle — restore+focus a
|
|
409
|
+
* minimized/inactive window, minimize the active one.
|
|
410
|
+
************************************************************/
|
|
411
|
+
function on_chip_click(gobj, entry)
|
|
412
|
+
{
|
|
413
|
+
if(entry.minimized || !entry.$chip.classList.contains('is-active')) {
|
|
414
|
+
focus_entry(gobj, entry);
|
|
415
|
+
} else {
|
|
416
|
+
minimize_entry(gobj, entry);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
/***************************
|
|
424
|
+
* Actions
|
|
425
|
+
***************************/
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
/************************************************************
|
|
431
|
+
* EV_REGISTER_WINDOW { window, title }
|
|
432
|
+
************************************************************/
|
|
433
|
+
function ac_register_window(gobj, event, kw, src)
|
|
434
|
+
{
|
|
435
|
+
let priv = gobj.priv;
|
|
436
|
+
|
|
437
|
+
let win = kw.window;
|
|
438
|
+
if(!win || find_entry(priv, win)) {
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
apply_placement(gobj);
|
|
443
|
+
|
|
444
|
+
let title = kw.title || gobj_name(win) || "window";
|
|
445
|
+
|
|
446
|
+
let entry = {gobj: win, $chip: null, minimized: false};
|
|
447
|
+
/* A div (not a button) so it can hold the close button — a
|
|
448
|
+
* button inside a button is invalid. The chip toggles; the ✕
|
|
449
|
+
* closes the window (EV_CLOSE_WINDOW → the window's own teardown
|
|
450
|
+
* → EV_UNREGISTER_WINDOW removes this chip). */
|
|
451
|
+
let $chip = createElement2(
|
|
452
|
+
['div', {class: 'yui-dock-chip', role: 'button', tabindex: '0', title: title}, [
|
|
453
|
+
chip_lead(kw.icon),
|
|
454
|
+
['span', {class: 'yui-dock-label'}, title],
|
|
455
|
+
['button', {class: 'yui-dock-close', type: 'button', 'aria-label': 'close'}, WC_X, {
|
|
456
|
+
click: (evt) => {
|
|
457
|
+
evt.stopPropagation();
|
|
458
|
+
gobj_send_event(entry.gobj, "EV_CLOSE_WINDOW", {}, gobj);
|
|
459
|
+
}
|
|
460
|
+
}],
|
|
461
|
+
], {
|
|
462
|
+
click: (evt) => {
|
|
463
|
+
evt.stopPropagation();
|
|
464
|
+
on_chip_click(gobj, entry);
|
|
465
|
+
}
|
|
466
|
+
}]
|
|
467
|
+
);
|
|
468
|
+
entry.$chip = $chip;
|
|
469
|
+
priv.windows.push(entry);
|
|
470
|
+
|
|
471
|
+
let $dock = gobj_read_attr(gobj, "$container");
|
|
472
|
+
if($dock) {
|
|
473
|
+
$dock.appendChild($chip);
|
|
474
|
+
}
|
|
475
|
+
sync_dock_visibility(gobj);
|
|
476
|
+
focus_entry(gobj, entry);
|
|
477
|
+
return 0;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/************************************************************
|
|
481
|
+
* EV_UNREGISTER_WINDOW { window }
|
|
482
|
+
************************************************************/
|
|
483
|
+
function ac_unregister_window(gobj, event, kw, src)
|
|
484
|
+
{
|
|
485
|
+
let priv = gobj.priv;
|
|
486
|
+
|
|
487
|
+
let entry = find_entry(priv, kw.window);
|
|
488
|
+
if(!entry) {
|
|
489
|
+
return 0;
|
|
490
|
+
}
|
|
491
|
+
if(entry.$chip && entry.$chip.parentNode) {
|
|
492
|
+
entry.$chip.parentNode.removeChild(entry.$chip);
|
|
493
|
+
}
|
|
494
|
+
priv.windows = priv.windows.filter((e) => e !== entry);
|
|
495
|
+
sync_dock_visibility(gobj);
|
|
496
|
+
return 0;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/************************************************************
|
|
500
|
+
* EV_MINIMIZE_WINDOW { window }
|
|
501
|
+
************************************************************/
|
|
502
|
+
function ac_minimize_window(gobj, event, kw, src)
|
|
503
|
+
{
|
|
504
|
+
let entry = find_entry(gobj.priv, kw.window);
|
|
505
|
+
if(entry) {
|
|
506
|
+
minimize_entry(gobj, entry);
|
|
507
|
+
}
|
|
508
|
+
return 0;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/************************************************************
|
|
512
|
+
* EV_FOCUS_WINDOW { window }
|
|
513
|
+
************************************************************/
|
|
514
|
+
function ac_focus_window(gobj, event, kw, src)
|
|
515
|
+
{
|
|
516
|
+
let entry = find_entry(gobj.priv, kw.window);
|
|
517
|
+
if(entry) {
|
|
518
|
+
focus_entry(gobj, entry);
|
|
519
|
+
}
|
|
520
|
+
return 0;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
/***************************
|
|
527
|
+
* FSM
|
|
528
|
+
***************************/
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
/*---------------------------------------------*
|
|
534
|
+
* Global methods table
|
|
535
|
+
*---------------------------------------------*/
|
|
536
|
+
const gmt = {
|
|
537
|
+
mt_create: mt_create,
|
|
538
|
+
mt_destroy: mt_destroy,
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
/***************************************************************
|
|
542
|
+
* Create the GClass
|
|
543
|
+
***************************************************************/
|
|
544
|
+
function create_gclass(gclass_name)
|
|
545
|
+
{
|
|
546
|
+
if(__gclass__) {
|
|
547
|
+
log_error(`GClass ALREADY created: ${gclass_name}`);
|
|
548
|
+
return -1;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/*---------------------------------------------*
|
|
552
|
+
* States
|
|
553
|
+
*---------------------------------------------*/
|
|
554
|
+
const states = [
|
|
555
|
+
["ST_IDLE", [
|
|
556
|
+
["EV_REGISTER_WINDOW", ac_register_window, null],
|
|
557
|
+
["EV_UNREGISTER_WINDOW", ac_unregister_window, null],
|
|
558
|
+
["EV_MINIMIZE_WINDOW", ac_minimize_window, null],
|
|
559
|
+
["EV_FOCUS_WINDOW", ac_focus_window, null],
|
|
560
|
+
]]
|
|
561
|
+
];
|
|
562
|
+
|
|
563
|
+
/*---------------------------------------------*
|
|
564
|
+
* Events
|
|
565
|
+
*---------------------------------------------*/
|
|
566
|
+
const event_types = [
|
|
567
|
+
["EV_REGISTER_WINDOW", 0],
|
|
568
|
+
["EV_UNREGISTER_WINDOW", 0],
|
|
569
|
+
["EV_MINIMIZE_WINDOW", 0],
|
|
570
|
+
["EV_FOCUS_WINDOW", 0],
|
|
571
|
+
];
|
|
572
|
+
|
|
573
|
+
__gclass__ = gclass_create(
|
|
574
|
+
gclass_name,
|
|
575
|
+
event_types,
|
|
576
|
+
states,
|
|
577
|
+
gmt,
|
|
578
|
+
0, // lmt,
|
|
579
|
+
attrs_table,
|
|
580
|
+
PRIVATE_DATA,
|
|
581
|
+
0, // authz_table,
|
|
582
|
+
0, // command_table,
|
|
583
|
+
0, // s_user_trace_level
|
|
584
|
+
0 // gclass_flag
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
if(!__gclass__) {
|
|
588
|
+
return -1;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return 0;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/***************************************************************
|
|
595
|
+
* Register GClass
|
|
596
|
+
***************************************************************/
|
|
597
|
+
function register_c_yui_window_manager()
|
|
598
|
+
{
|
|
599
|
+
return create_gclass(GCLASS_NAME);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export { register_c_yui_window_manager };
|
|
@@ -155,7 +155,7 @@ function mt_destroy(gobj)
|
|
|
155
155
|
function build_ui(gobj)
|
|
156
156
|
{
|
|
157
157
|
let $container = createElement2(
|
|
158
|
-
['div', {class: '
|
|
158
|
+
['div', {class: 'C_YUI_WIZARD', style: 'height:100%; display:flex; flex-direction:column;'}, [
|
|
159
159
|
['div', {class: 'yui-wizard-header is-flex is-align-items-center is-flex-grow-0',
|
|
160
160
|
style: 'gap:.5rem; padding:.25rem .5rem;'}, [
|
|
161
161
|
['span', {class: 'yui-wizard-title is-flex-grow-1', style: 'font-weight:600;'}, ''],
|