@yuneta/gobj-ui 1.0.1 → 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 +40 -363
- package/dist/gobj-ui.cjs.js +11154 -5506
- package/dist/gobj-ui.es.js +11131 -5508
- package/index.js +41 -14
- package/package.json +11 -9
- package/src/c_g6_nodes_tree.js +6 -1
- package/src/c_yui_form.js +1 -1
- package/src/c_yui_gobj_tree_js.js +1 -1
- package/src/c_yui_json_graph.js +1 -1
- package/src/c_yui_main.js +3 -3
- package/src/c_yui_map.js +6 -1
- package/src/c_yui_nav.js +881 -0
- package/src/c_yui_pager.js +545 -0
- package/src/c_yui_routing.css +1 -1
- package/src/c_yui_routing.js +1 -1
- package/src/c_yui_shell.css +571 -0
- package/src/c_yui_shell.js +2474 -0
- package/src/c_yui_tabs.js +1 -1
- package/src/c_yui_treedb_graph.js +35 -9
- package/src/c_yui_treedb_topic_with_form.js +1 -1
- package/src/c_yui_treedb_topics.js +36 -8
- package/src/c_yui_uplot.js +1 -1
- package/src/c_yui_window.js +242 -21
- package/src/c_yui_window_manager.js +602 -0
- package/src/c_yui_wizard.js +612 -0
- package/src/pager_helpers.js +138 -0
- package/src/pager_helpers.test.js +140 -0
- package/src/route_resolver.js +53 -0
- package/src/route_resolver.test.js +82 -0
- package/src/shell_focus_trap.js +123 -0
- package/src/shell_focus_trap.test.js +299 -0
- package/src/shell_modals.js +445 -0
- package/src/shell_show_on.js +91 -0
- package/src/shell_show_on.test.js +86 -0
- package/src/shell_toolbar_helpers.js +221 -0
- package/src/shell_toolbar_helpers.test.js +207 -0
- package/src/tabulator.css +53 -0
- package/src/wizard_helpers.js +117 -0
- package/src/wizard_helpers.test.js +122 -0
- package/src/yui_dev.js +1257 -362
- package/src/yui_icons.css +5 -0
- package/src/yui_inputs.css +32 -0
- package/src/yui_inputs.js +71 -0
- package/vite-plugin-yuneta-html.js +2 -2
- package/skeleton/config.json +0 -20
- package/skeleton/index.html +0 -37
package/src/c_yui_nav.js
ADDED
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* c_yui_nav.js
|
|
3
|
+
*
|
|
4
|
+
* C_YUI_NAV — Renders a menu (list of items) in one zone, using one
|
|
5
|
+
* of the supported layouts. Driven by C_YUI_SHELL; one instance per
|
|
6
|
+
* (menu, zone) pair.
|
|
7
|
+
*
|
|
8
|
+
* Layouts:
|
|
9
|
+
* "vertical" — Bulma .menu (left/right side nav)
|
|
10
|
+
* "icon-bar" — horizontal bar of icon+label (bottom/top on mobile)
|
|
11
|
+
* "tabs" — Bulma .tabs (secondary nav)
|
|
12
|
+
* "drawer" — off-canvas vertical (toggled by hamburger)
|
|
13
|
+
* "submenu" — Bulma .menu nested under a heading
|
|
14
|
+
* "accordion" — Bulma .menu list with collapsible group
|
|
15
|
+
*
|
|
16
|
+
* Each item supports:
|
|
17
|
+
* id, name, icon (CSS class or svg id), route, badge, disabled
|
|
18
|
+
* class — extra CSS class(es) on the item (tabs layout), e.g.
|
|
19
|
+
* a per-item state colour like "yui-nav-disconnected"
|
|
20
|
+
* closable — render a trailing ✕ that emits EV_NAV_ITEM_CLOSE
|
|
21
|
+
* instead of navigating (tabs layout)
|
|
22
|
+
*
|
|
23
|
+
* Items can be replaced at runtime by sending EV_SET_ITEMS
|
|
24
|
+
* {items:[...]} to the nav; the DOM is rebuilt in place and the
|
|
25
|
+
* active highlight restored. The app_config path is simply the
|
|
26
|
+
* first, startup caller of the same mechanism.
|
|
27
|
+
*
|
|
28
|
+
* In addition to navigable items, secondary navs accept two
|
|
29
|
+
* decorative item kinds for in-place visual grouping:
|
|
30
|
+
*
|
|
31
|
+
* { "type": "header", "name": "<group label>" }
|
|
32
|
+
* { "type": "divider" }
|
|
33
|
+
*
|
|
34
|
+
* Headers render as a small-caps section label; dividers as a thin
|
|
35
|
+
* separator line. Both are non-clickable and skipped by routing
|
|
36
|
+
* and click handling. Layouts that have no room for them
|
|
37
|
+
* (`tabs`, `icon-bar`) silently drop these entries.
|
|
38
|
+
*
|
|
39
|
+
* Copyright (c) 2026, ArtGins.
|
|
40
|
+
* All Rights Reserved.
|
|
41
|
+
***********************************************************************/
|
|
42
|
+
/* global document */
|
|
43
|
+
|
|
44
|
+
import {
|
|
45
|
+
SDATA, SDATA_END, data_type_t, event_flag_t,
|
|
46
|
+
gclass_create, log_error, log_warning,
|
|
47
|
+
gobj_subscribe_event,
|
|
48
|
+
gobj_parent,
|
|
49
|
+
gobj_read_attr, gobj_read_pointer_attr, gobj_write_attr,
|
|
50
|
+
gobj_publish_event,
|
|
51
|
+
createElement2, empty_string, is_array, is_object, is_string,
|
|
52
|
+
} from "@yuneta/gobj-js";
|
|
53
|
+
|
|
54
|
+
/***************************************************************
|
|
55
|
+
* Constants
|
|
56
|
+
***************************************************************/
|
|
57
|
+
const GCLASS_NAME = "C_YUI_NAV";
|
|
58
|
+
|
|
59
|
+
const SUPPORTED_LAYOUTS = [
|
|
60
|
+
"vertical", "icon-bar", "tabs", "drawer", "submenu", "accordion"
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/* Decorative items have no `route` and never participate in
|
|
64
|
+
* navigation; the shell's route indexer already skips items
|
|
65
|
+
* without a route, so this only affects the rendering side. */
|
|
66
|
+
function is_decorative(it)
|
|
67
|
+
{
|
|
68
|
+
return !!(it && (it.type === "header" || it.type === "divider"));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/***************************************************************
|
|
72
|
+
* Attrs
|
|
73
|
+
***************************************************************/
|
|
74
|
+
const attrs_table = [
|
|
75
|
+
SDATA(data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of output events"),
|
|
76
|
+
|
|
77
|
+
SDATA(data_type_t.DTP_STRING, "menu_id", 0, "", "Id of the menu this nav renders"),
|
|
78
|
+
SDATA(data_type_t.DTP_STRING, "nav_label", 0, "", "Human-readable label for aria/heading; falls back to menu_id"),
|
|
79
|
+
SDATA(data_type_t.DTP_JSON, "menu_items", 0, null, "Array of menu items to render"),
|
|
80
|
+
SDATA(data_type_t.DTP_STRING, "zone", 0, "", "Zone id where this nav lives"),
|
|
81
|
+
SDATA(data_type_t.DTP_STRING, "layout", 0, "vertical", "vertical|icon-bar|tabs|drawer|submenu|accordion"),
|
|
82
|
+
SDATA(data_type_t.DTP_STRING, "icon_pos", 0, "left", "left|right|top|bottom"),
|
|
83
|
+
SDATA(data_type_t.DTP_BOOLEAN, "show_label", 0, true, "Show item label next to/under the icon"),
|
|
84
|
+
SDATA(data_type_t.DTP_STRING, "level", 0, "primary", "primary|secondary"),
|
|
85
|
+
|
|
86
|
+
SDATA(data_type_t.DTP_POINTER, "shell", 0, null, "C_YUI_SHELL gobj (for navigation calls)"),
|
|
87
|
+
SDATA(data_type_t.DTP_POINTER, "$container", 0, null, "Root HTMLElement of this nav"),
|
|
88
|
+
SDATA(data_type_t.DTP_STRING, "active_route", 0, "", "Currently active route"),
|
|
89
|
+
SDATA(data_type_t.DTP_POINTER, "priv", 0, null, "Private runtime state (click listener, etc.)"),
|
|
90
|
+
SDATA_END()
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
/* Monotonic id generator for ARIA pairs (aria-controls etc.). */
|
|
94
|
+
let __nav_aria_seq__ = 0;
|
|
95
|
+
|
|
96
|
+
let PRIVATE_DATA = {};
|
|
97
|
+
let __gclass__ = null;
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
/******************************
|
|
103
|
+
* Framework Methods
|
|
104
|
+
******************************/
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
/***************************************************************
|
|
110
|
+
* Framework Method: Create
|
|
111
|
+
***************************************************************/
|
|
112
|
+
function mt_create(gobj)
|
|
113
|
+
{
|
|
114
|
+
/*
|
|
115
|
+
* CHILD subscription model
|
|
116
|
+
*/
|
|
117
|
+
let subscriber = gobj_read_pointer_attr(gobj, "subscriber");
|
|
118
|
+
if(!subscriber) {
|
|
119
|
+
subscriber = gobj_parent(gobj);
|
|
120
|
+
}
|
|
121
|
+
gobj_subscribe_event(gobj, null, {}, subscriber);
|
|
122
|
+
|
|
123
|
+
gobj_write_attr(gobj, "priv", {
|
|
124
|
+
click_handler: null
|
|
125
|
+
});
|
|
126
|
+
build_ui(gobj);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/***************************************************************
|
|
130
|
+
* Framework Method: Start
|
|
131
|
+
***************************************************************/
|
|
132
|
+
function mt_start(gobj)
|
|
133
|
+
{
|
|
134
|
+
let shell = gobj_read_attr(gobj, "shell");
|
|
135
|
+
if(shell) {
|
|
136
|
+
/* Listen to route changes from the shell so we can highlight
|
|
137
|
+
* the active item. */
|
|
138
|
+
try {
|
|
139
|
+
gobj_subscribe_event(shell, "EV_ROUTE_CHANGED", {}, gobj);
|
|
140
|
+
} catch(e) {
|
|
141
|
+
log_warning(`C_YUI_NAV: subscribe to EV_ROUTE_CHANGED failed: ${e}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/***************************************************************
|
|
147
|
+
* Framework Method: Stop
|
|
148
|
+
***************************************************************/
|
|
149
|
+
function mt_stop(gobj)
|
|
150
|
+
{
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/***************************************************************
|
|
154
|
+
* Framework Method: Destroy
|
|
155
|
+
***************************************************************/
|
|
156
|
+
function mt_destroy(gobj)
|
|
157
|
+
{
|
|
158
|
+
let $c = gobj_read_attr(gobj, "$container");
|
|
159
|
+
let priv = gobj_read_attr(gobj, "priv");
|
|
160
|
+
if($c && priv && priv.click_handler) {
|
|
161
|
+
$c.removeEventListener("click", priv.click_handler);
|
|
162
|
+
}
|
|
163
|
+
if($c && $c.parentNode) {
|
|
164
|
+
$c.parentNode.removeChild($c);
|
|
165
|
+
}
|
|
166
|
+
gobj_write_attr(gobj, "$container", null);
|
|
167
|
+
gobj_write_attr(gobj, "priv", null);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
/***************************
|
|
174
|
+
* Local Methods
|
|
175
|
+
***************************/
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
/************************************************************
|
|
181
|
+
* Build the UI: dispatch to one of the six layout renderers
|
|
182
|
+
************************************************************/
|
|
183
|
+
function build_ui(gobj)
|
|
184
|
+
{
|
|
185
|
+
let layout = gobj_read_attr(gobj, "layout") || "vertical";
|
|
186
|
+
if(SUPPORTED_LAYOUTS.indexOf(layout) < 0) {
|
|
187
|
+
log_error(`C_YUI_NAV: unsupported layout '${layout}', falling back to vertical`);
|
|
188
|
+
layout = "vertical";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let items = gobj_read_attr(gobj, "menu_items") || [];
|
|
192
|
+
let zone = gobj_read_attr(gobj, "zone") || "";
|
|
193
|
+
let menu_id = gobj_read_attr(gobj, "menu_id") || "nav";
|
|
194
|
+
let $container;
|
|
195
|
+
|
|
196
|
+
switch(layout) {
|
|
197
|
+
case "vertical": $container = render_vertical(gobj, items); break;
|
|
198
|
+
case "icon-bar": $container = render_icon_bar(gobj, items); break;
|
|
199
|
+
case "tabs": $container = render_tabs(gobj, items); break;
|
|
200
|
+
case "drawer": $container = render_drawer(gobj, items); break;
|
|
201
|
+
case "submenu": $container = render_submenu(gobj, items); break;
|
|
202
|
+
case "accordion": $container = render_accordion(gobj, items); break;
|
|
203
|
+
default: $container = render_vertical(gobj, items);
|
|
204
|
+
}
|
|
205
|
+
$container.setAttribute("data-nav-zone", zone);
|
|
206
|
+
$container.setAttribute("data-nav-layout", layout);
|
|
207
|
+
$container.classList.add("C_YUI_NAV", "yui-nav", `yui-nav-${layout}`);
|
|
208
|
+
|
|
209
|
+
/* Accessibility: every nav root is a landmark. For the drawer we
|
|
210
|
+
* tag the wrapper as role=dialog and the panel as role=navigation. */
|
|
211
|
+
if(layout !== "drawer") {
|
|
212
|
+
$container.setAttribute("role", "navigation");
|
|
213
|
+
if(!$container.hasAttribute("aria-label")) {
|
|
214
|
+
let label = gobj_read_attr(gobj, "nav_label") || menu_id;
|
|
215
|
+
$container.setAttribute("aria-label", label);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
wire_clicks(gobj, $container);
|
|
220
|
+
|
|
221
|
+
gobj_write_attr(gobj, "$container", $container);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/************************************************************
|
|
225
|
+
* Rebuild the nav DOM in place from the current menu_items.
|
|
226
|
+
* Used by EV_SET_ITEMS so a nav's tabs can be changed at runtime
|
|
227
|
+
* (the app_config path is just the first, startup caller).
|
|
228
|
+
************************************************************/
|
|
229
|
+
function rebuild(gobj)
|
|
230
|
+
{
|
|
231
|
+
let $old = gobj_read_attr(gobj, "$container");
|
|
232
|
+
let priv = gobj_read_attr(gobj, "priv") || {};
|
|
233
|
+
let parent = $old ? $old.parentNode : null;
|
|
234
|
+
let next = $old ? $old.nextSibling : null;
|
|
235
|
+
let was_hidden = !!($old && $old.classList.contains("is-hidden"));
|
|
236
|
+
|
|
237
|
+
/* Drop the old delegated click listener before discarding the node. */
|
|
238
|
+
if($old && priv.click_handler) {
|
|
239
|
+
$old.removeEventListener("click", priv.click_handler);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
build_ui(gobj); /* writes a fresh $container + wires new clicks */
|
|
243
|
+
|
|
244
|
+
let $new = gobj_read_attr(gobj, "$container");
|
|
245
|
+
if($new && was_hidden) {
|
|
246
|
+
$new.classList.add("is-hidden");
|
|
247
|
+
}
|
|
248
|
+
if(parent && $new) {
|
|
249
|
+
parent.insertBefore($new, next);
|
|
250
|
+
}
|
|
251
|
+
if($old && $old.parentNode) {
|
|
252
|
+
$old.parentNode.removeChild($old);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
apply_active_route(gobj);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/************************************************************
|
|
259
|
+
* Re-apply the active-route highlight after a rebuild (the
|
|
260
|
+
* shell only pushes EV_ROUTE_CHANGED on navigation, not on a
|
|
261
|
+
* self-triggered items change).
|
|
262
|
+
************************************************************/
|
|
263
|
+
function apply_active_route(gobj)
|
|
264
|
+
{
|
|
265
|
+
let $c = gobj_read_attr(gobj, "$container");
|
|
266
|
+
let route = gobj_read_attr(gobj, "active_route") || "";
|
|
267
|
+
if(!$c || empty_string(route)) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
let $a = $c.querySelector(`a[data-route="${css_escape(route)}"]`);
|
|
271
|
+
if($a) {
|
|
272
|
+
let $li = $a.closest("li");
|
|
273
|
+
if($li) {
|
|
274
|
+
$li.classList.add("is-active");
|
|
275
|
+
}
|
|
276
|
+
$a.classList.add("is-active");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/************************************************************
|
|
281
|
+
* Layouts
|
|
282
|
+
*
|
|
283
|
+
* IMPORTANT 1: createElement2 destructures node descriptors as
|
|
284
|
+
* [tag, attrs, content, events] positionally, so multiple
|
|
285
|
+
* children MUST be wrapped in a single array (otherwise the
|
|
286
|
+
* third sibling would be interpreted as an event-listener map
|
|
287
|
+
* and addEventListener would crash). In short:
|
|
288
|
+
*
|
|
289
|
+
* OK: ["ul", {}, [li1, li2, li3]]
|
|
290
|
+
* WRONG: ["ul", {}, li1, li2, li3]
|
|
291
|
+
*
|
|
292
|
+
* IMPORTANT 2: every translatable text node carries an `i18n`
|
|
293
|
+
* attribute with its canonical (English) key. createElement2
|
|
294
|
+
* maps it to `data-i18n` on the rendered element. The host app
|
|
295
|
+
* changes language by calling `refresh_language(element, t)`
|
|
296
|
+
* from `@yuneta/gobj-js`, which walks all `[data-i18n]` and
|
|
297
|
+
* re-translates the first text node with `t(key)`. See the
|
|
298
|
+
* legacy `change_language()` in `c_yui_main.js` for the
|
|
299
|
+
* canonical pattern.
|
|
300
|
+
************************************************************/
|
|
301
|
+
function render_vertical(gobj, items)
|
|
302
|
+
{
|
|
303
|
+
let show_label = gobj_read_attr(gobj, "show_label");
|
|
304
|
+
let icon_pos = gobj_read_attr(gobj, "icon_pos");
|
|
305
|
+
|
|
306
|
+
let lis = [];
|
|
307
|
+
for(let it of items) {
|
|
308
|
+
lis.push(item_li(gobj, it, { icon_pos, show_label, stacked: false }));
|
|
309
|
+
}
|
|
310
|
+
return createElement2(
|
|
311
|
+
["aside", {class: "menu p-3"},
|
|
312
|
+
["ul", {class: "menu-list"}, lis]
|
|
313
|
+
]
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function render_icon_bar(gobj, items)
|
|
318
|
+
{
|
|
319
|
+
let show_label = gobj_read_attr(gobj, "show_label");
|
|
320
|
+
let icon_pos = gobj_read_attr(gobj, "icon_pos"); /* typically "top" */
|
|
321
|
+
|
|
322
|
+
let bar_items = [];
|
|
323
|
+
for(let it of items) {
|
|
324
|
+
if(is_decorative(it)) {
|
|
325
|
+
continue; /* headers/dividers don't fit a horizontal icon bar */
|
|
326
|
+
}
|
|
327
|
+
bar_items.push(item_iconbar(gobj, it, { icon_pos, show_label }));
|
|
328
|
+
}
|
|
329
|
+
return createElement2(
|
|
330
|
+
["div", {class: "yui-nav-iconbar level is-mobile"}, bar_items]
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function render_tabs(gobj, items)
|
|
335
|
+
{
|
|
336
|
+
let show_label = gobj_read_attr(gobj, "show_label");
|
|
337
|
+
|
|
338
|
+
let lis = [];
|
|
339
|
+
for(let it of items) {
|
|
340
|
+
if(is_decorative(it)) {
|
|
341
|
+
continue; /* tab strips have no room for section labels */
|
|
342
|
+
}
|
|
343
|
+
let children = [];
|
|
344
|
+
if(!empty_string(it.icon)) {
|
|
345
|
+
children.push(["span", {class: "icon is-small"},
|
|
346
|
+
["i", {class: it.icon, "aria-hidden":"true"}]]);
|
|
347
|
+
}
|
|
348
|
+
if(show_label && !empty_string(it.name)) {
|
|
349
|
+
children.push(["span", {i18n: it.name}, it.name]);
|
|
350
|
+
}
|
|
351
|
+
/* Optional close affordance: a trailing ✕ that emits
|
|
352
|
+
* EV_NAV_ITEM_CLOSE instead of navigating (caught first in
|
|
353
|
+
* wire_clicks by its data-close-item marker). */
|
|
354
|
+
if(it.closable) {
|
|
355
|
+
children.push(["span", {class: "icon is-small yui-nav-close ml-2",
|
|
356
|
+
"data-close-item": it.id, role: "button",
|
|
357
|
+
"aria-label": "close", title: "close"},
|
|
358
|
+
["i", {class: "yi-xmark", "aria-hidden":"true"}]]);
|
|
359
|
+
}
|
|
360
|
+
let a_attrs = {
|
|
361
|
+
href: it.route ? "#" + it.route : "#",
|
|
362
|
+
"data-item-id": it.id,
|
|
363
|
+
"data-route": it.route || ""
|
|
364
|
+
};
|
|
365
|
+
let tip = it.tooltip || it.aria_label;
|
|
366
|
+
if(tip) {
|
|
367
|
+
a_attrs.title = tip;
|
|
368
|
+
a_attrs["data-i18n-title"] = tip;
|
|
369
|
+
}
|
|
370
|
+
/* Per-item state class (e.g. a "disconnected" colour) rides on
|
|
371
|
+
* the <li>, alongside Bulma's own is-active. */
|
|
372
|
+
let li_class = empty_string(it.class) ? "" : String(it.class);
|
|
373
|
+
lis.push(
|
|
374
|
+
["li", {class: li_class, "data-item-id": it.id, "data-route": it.route || ""},
|
|
375
|
+
["a", a_attrs, children]
|
|
376
|
+
]
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return createElement2(
|
|
380
|
+
["div", {class: "tabs is-boxed"},
|
|
381
|
+
["ul", {}, lis]
|
|
382
|
+
]
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function render_drawer(gobj, items)
|
|
387
|
+
{
|
|
388
|
+
/* Off-canvas drawer: initially hidden; toggled via .is-active on the
|
|
389
|
+
* outer wrapper. Open/close is driven from the toolbar or from the
|
|
390
|
+
* public yui_shell_{open,close,toggle}_drawer() helpers.
|
|
391
|
+
*/
|
|
392
|
+
let menu_id = gobj_read_attr(gobj, "menu_id") || "drawer";
|
|
393
|
+
let wrap = document.createElement("div");
|
|
394
|
+
wrap.className = "yui-drawer";
|
|
395
|
+
wrap.setAttribute("role", "dialog");
|
|
396
|
+
wrap.setAttribute("aria-modal", "true");
|
|
397
|
+
wrap.setAttribute("aria-label", menu_id);
|
|
398
|
+
|
|
399
|
+
let back = document.createElement("div");
|
|
400
|
+
back.className = "yui-drawer-backdrop";
|
|
401
|
+
back.setAttribute("data-close-drawer", "1");
|
|
402
|
+
back.setAttribute("aria-hidden", "true");
|
|
403
|
+
|
|
404
|
+
let panel = document.createElement("div");
|
|
405
|
+
panel.className = "yui-drawer-panel";
|
|
406
|
+
panel.setAttribute("role", "navigation");
|
|
407
|
+
panel.setAttribute("aria-label", menu_id);
|
|
408
|
+
panel.appendChild(render_vertical(gobj, items));
|
|
409
|
+
|
|
410
|
+
wrap.appendChild(back);
|
|
411
|
+
wrap.appendChild(panel);
|
|
412
|
+
return wrap;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function render_submenu(gobj, items)
|
|
416
|
+
{
|
|
417
|
+
let show_label = gobj_read_attr(gobj, "show_label");
|
|
418
|
+
let icon_pos = gobj_read_attr(gobj, "icon_pos");
|
|
419
|
+
let menu_id = gobj_read_attr(gobj, "menu_id") || "";
|
|
420
|
+
let nav_label = gobj_read_attr(gobj, "nav_label") || menu_id;
|
|
421
|
+
|
|
422
|
+
let lis = [];
|
|
423
|
+
for(let it of items) {
|
|
424
|
+
lis.push(item_li(gobj, it, { icon_pos, show_label, stacked: false, compact: true }));
|
|
425
|
+
}
|
|
426
|
+
let heading_text = nav_label || "—";
|
|
427
|
+
let heading_attrs = nav_label
|
|
428
|
+
? { class: "menu-label", i18n: nav_label }
|
|
429
|
+
: { class: "menu-label" };
|
|
430
|
+
return createElement2(
|
|
431
|
+
["aside", {class: "menu p-2"},
|
|
432
|
+
[
|
|
433
|
+
["p", heading_attrs, heading_text],
|
|
434
|
+
["ul", {class: "menu-list"}, lis]
|
|
435
|
+
]
|
|
436
|
+
]
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function render_accordion(gobj, items)
|
|
441
|
+
{
|
|
442
|
+
/* A flat accordion: each top item becomes a heading with a
|
|
443
|
+
* collapsible child list. Useful when reusing this renderer for
|
|
444
|
+
* deeply nested menus — first-level items here are accordion sections.
|
|
445
|
+
*/
|
|
446
|
+
let show_label = gobj_read_attr(gobj, "show_label");
|
|
447
|
+
let icon_pos = gobj_read_attr(gobj, "icon_pos");
|
|
448
|
+
|
|
449
|
+
let $container = createElement2(["aside", {class: "menu yui-nav-accordion p-2"}]);
|
|
450
|
+
for(let it of items) {
|
|
451
|
+
if(is_decorative(it)) {
|
|
452
|
+
continue; /* primary-level decorations don't apply to accordion sections */
|
|
453
|
+
}
|
|
454
|
+
let acc_id = ++__nav_aria_seq__;
|
|
455
|
+
let head_id = `yui-acc-head-${acc_id}`;
|
|
456
|
+
let body_id = `yui-acc-body-${acc_id}`;
|
|
457
|
+
|
|
458
|
+
let head_text = it.name || "";
|
|
459
|
+
let head_attrs = {
|
|
460
|
+
type: "button",
|
|
461
|
+
id: head_id,
|
|
462
|
+
class: "menu-label yui-accordion-head",
|
|
463
|
+
"data-item-id": it.id,
|
|
464
|
+
"data-route": it.route || "",
|
|
465
|
+
"aria-expanded": "false",
|
|
466
|
+
"aria-controls": body_id
|
|
467
|
+
};
|
|
468
|
+
if(!empty_string(head_text)) {
|
|
469
|
+
head_attrs.i18n = head_text;
|
|
470
|
+
}
|
|
471
|
+
let $hdr = createElement2(["button", head_attrs, head_text]);
|
|
472
|
+
$container.appendChild($hdr);
|
|
473
|
+
|
|
474
|
+
let $ul = createElement2(
|
|
475
|
+
["ul", {id: body_id,
|
|
476
|
+
class: "menu-list yui-accordion-body is-hidden",
|
|
477
|
+
role: "region",
|
|
478
|
+
"aria-labelledby": head_id}]
|
|
479
|
+
);
|
|
480
|
+
if(is_array(it.submenu && it.submenu.items)) {
|
|
481
|
+
for(let sub of it.submenu.items) {
|
|
482
|
+
$ul.appendChild(createElement2(
|
|
483
|
+
item_li(gobj, sub, { icon_pos, show_label, stacked: false })
|
|
484
|
+
));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
$container.appendChild($ul);
|
|
488
|
+
}
|
|
489
|
+
return $container;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/************************************************************
|
|
493
|
+
* Shared helpers
|
|
494
|
+
************************************************************/
|
|
495
|
+
function item_li(gobj, it, opts)
|
|
496
|
+
{
|
|
497
|
+
/* Decorative entries: a non-interactive section header or a
|
|
498
|
+
* thin divider rule. Used inside vertical/submenu/accordion
|
|
499
|
+
* navs to chunk the list visually without introducing a third
|
|
500
|
+
* navigation level. Both render as <li> with no <a>, so the
|
|
501
|
+
* click handler's `closest("[data-route]")` skips them. */
|
|
502
|
+
if(it && it.type === "divider") {
|
|
503
|
+
return ["li", {
|
|
504
|
+
class: "yui-nav-section-divider",
|
|
505
|
+
role: "separator",
|
|
506
|
+
"aria-hidden": "true"
|
|
507
|
+
}];
|
|
508
|
+
}
|
|
509
|
+
if(it && it.type === "header") {
|
|
510
|
+
let label = it.name || "";
|
|
511
|
+
let span_attrs = {class: "yui-nav-section-header-label"};
|
|
512
|
+
if(label) {
|
|
513
|
+
span_attrs.i18n = label;
|
|
514
|
+
}
|
|
515
|
+
return ["li", {class: "yui-nav-section-header", role: "presentation"},
|
|
516
|
+
["span", span_attrs, label]
|
|
517
|
+
];
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
let { icon_pos, show_label, stacked } = opts;
|
|
521
|
+
let children = [];
|
|
522
|
+
let label = it.name || "";
|
|
523
|
+
|
|
524
|
+
let icon_el = !empty_string(it.icon)
|
|
525
|
+
? ["span", {class: "icon"}, ["i", {class: it.icon, "aria-hidden":"true"}]]
|
|
526
|
+
: null;
|
|
527
|
+
let label_el = (show_label && !empty_string(label))
|
|
528
|
+
? ["span", {class: "yui-nav-label", i18n: label}, label]
|
|
529
|
+
: null;
|
|
530
|
+
|
|
531
|
+
let a_class = "yui-nav-item";
|
|
532
|
+
if(stacked || icon_pos === "top" || icon_pos === "bottom") {
|
|
533
|
+
a_class += " yui-nav-stacked";
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if(icon_pos === "right" || icon_pos === "bottom") {
|
|
537
|
+
if(label_el) {
|
|
538
|
+
children.push(label_el);
|
|
539
|
+
}
|
|
540
|
+
if(icon_el) {
|
|
541
|
+
children.push(icon_el);
|
|
542
|
+
}
|
|
543
|
+
} else {
|
|
544
|
+
if(icon_el) {
|
|
545
|
+
children.push(icon_el);
|
|
546
|
+
}
|
|
547
|
+
if(label_el) {
|
|
548
|
+
children.push(label_el);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
let aria_key = label || it.id;
|
|
553
|
+
let a_attrs = {
|
|
554
|
+
class: a_class,
|
|
555
|
+
href: it.route ? "#" + it.route : "#",
|
|
556
|
+
"data-item-id": it.id,
|
|
557
|
+
"data-route": it.route || "",
|
|
558
|
+
"data-disabled": it.disabled ? "1" : "0",
|
|
559
|
+
"aria-label": aria_key
|
|
560
|
+
};
|
|
561
|
+
/* Mirror the aria-label key in `data-i18n-aria-label` so
|
|
562
|
+
* refresh_language() can re-translate it on language switch.
|
|
563
|
+
* Only when sourced from a real label (skip the bare-id
|
|
564
|
+
* fallback — ids aren't translation keys). */
|
|
565
|
+
if(label) {
|
|
566
|
+
a_attrs["data-i18n-aria-label"] = label;
|
|
567
|
+
}
|
|
568
|
+
/* Hover tooltip: prefer explicit `tooltip`, fall back to
|
|
569
|
+
* `aria_label` (typically equivalent, e.g. "Search (Ctrl+F)").
|
|
570
|
+
* Skip when both empty so we don't emit `title=""` noise.
|
|
571
|
+
* Mirror the value in `data-i18n-title` so refresh_language()
|
|
572
|
+
* can re-translate the tooltip on language switch. */
|
|
573
|
+
let tip = it.tooltip || it.aria_label;
|
|
574
|
+
if(tip) {
|
|
575
|
+
a_attrs.title = tip;
|
|
576
|
+
a_attrs["data-i18n-title"] = tip;
|
|
577
|
+
}
|
|
578
|
+
if(it.disabled) {
|
|
579
|
+
a_attrs["aria-disabled"] = "true";
|
|
580
|
+
a_attrs["tabindex"] = "-1";
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return ["li", {},
|
|
584
|
+
["a", a_attrs, children]
|
|
585
|
+
];
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function item_iconbar(gobj, it, opts)
|
|
589
|
+
{
|
|
590
|
+
let { icon_pos, show_label } = opts;
|
|
591
|
+
let label = it.name || "";
|
|
592
|
+
let icon_el = !empty_string(it.icon)
|
|
593
|
+
? ["span", {class: "icon is-medium"},
|
|
594
|
+
["i", {class: it.icon, "aria-hidden":"true"}]]
|
|
595
|
+
: null;
|
|
596
|
+
let label_el = (show_label && !empty_string(label))
|
|
597
|
+
? ["span", {class: "yui-nav-label is-size-7", i18n: label}, label]
|
|
598
|
+
: null;
|
|
599
|
+
|
|
600
|
+
let children = [];
|
|
601
|
+
if(icon_pos === "bottom") {
|
|
602
|
+
if(label_el) {
|
|
603
|
+
children.push(label_el);
|
|
604
|
+
}
|
|
605
|
+
if(icon_el) {
|
|
606
|
+
children.push(icon_el);
|
|
607
|
+
}
|
|
608
|
+
} else {
|
|
609
|
+
if(icon_el) {
|
|
610
|
+
children.push(icon_el);
|
|
611
|
+
}
|
|
612
|
+
if(label_el) {
|
|
613
|
+
children.push(label_el);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
let a_attrs = {
|
|
618
|
+
class: "yui-nav-item yui-nav-stacked",
|
|
619
|
+
href: it.route ? "#" + it.route : "#",
|
|
620
|
+
"data-item-id": it.id,
|
|
621
|
+
"data-route": it.route || "",
|
|
622
|
+
"aria-label": label || it.id
|
|
623
|
+
};
|
|
624
|
+
if(label) {
|
|
625
|
+
a_attrs["data-i18n-aria-label"] = label;
|
|
626
|
+
}
|
|
627
|
+
let tip = it.tooltip || it.aria_label;
|
|
628
|
+
if(tip) {
|
|
629
|
+
a_attrs.title = tip;
|
|
630
|
+
a_attrs["data-i18n-title"] = tip;
|
|
631
|
+
}
|
|
632
|
+
return ["div", {class: "level-item"},
|
|
633
|
+
["a", a_attrs, children]
|
|
634
|
+
];
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function wire_clicks(gobj, $container)
|
|
638
|
+
{
|
|
639
|
+
let priv = gobj_read_attr(gobj, "priv") || {};
|
|
640
|
+
|
|
641
|
+
let handler = ev => {
|
|
642
|
+
let target = ev.target;
|
|
643
|
+
if(!target || !target.closest) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/* Close affordance (✕ on a closable tab): emit the intent and
|
|
648
|
+
* let the shell/app remove the item; never navigate. */
|
|
649
|
+
let $close = target.closest("[data-close-item]");
|
|
650
|
+
if($close) {
|
|
651
|
+
let $routed = $close.closest("[data-route]");
|
|
652
|
+
gobj_publish_event(gobj, "EV_NAV_ITEM_CLOSE", {
|
|
653
|
+
item_id: $close.getAttribute("data-close-item") || "",
|
|
654
|
+
route: ($routed && $routed.getAttribute("data-route")) || "",
|
|
655
|
+
menu_id: gobj_read_attr(gobj, "menu_id") || "",
|
|
656
|
+
zone: gobj_read_attr(gobj, "zone") || ""
|
|
657
|
+
});
|
|
658
|
+
ev.preventDefault();
|
|
659
|
+
ev.stopPropagation();
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/* Drawer backdrop close. Don't mutate the DOM directly —
|
|
664
|
+
* the shell owns the drawer state (focus-trap, escape
|
|
665
|
+
* stack) and must run its full close path. Publish the
|
|
666
|
+
* intent and let the shell handle it through
|
|
667
|
+
* ac_drawer_close_requested. */
|
|
668
|
+
let $bk = target.closest("[data-close-drawer]");
|
|
669
|
+
if($bk) {
|
|
670
|
+
gobj_publish_event(gobj, "EV_DRAWER_CLOSE_REQUESTED", {
|
|
671
|
+
menu_id: gobj_read_attr(gobj, "menu_id") || ""
|
|
672
|
+
});
|
|
673
|
+
ev.preventDefault();
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/* Accordion head toggle takes precedence over navigation: the
|
|
678
|
+
* head is a container for its sub-items, not a destination. */
|
|
679
|
+
let $head = target.closest(".yui-accordion-head");
|
|
680
|
+
if($head && $container.contains($head)) {
|
|
681
|
+
let $next = $head.nextElementSibling;
|
|
682
|
+
if($next && $next.classList.contains("yui-accordion-body")) {
|
|
683
|
+
let open = $next.classList.contains("is-hidden");
|
|
684
|
+
$next.classList.toggle("is-hidden", !open);
|
|
685
|
+
$head.classList.toggle("is-open", open);
|
|
686
|
+
$head.setAttribute("aria-expanded", open ? "true" : "false");
|
|
687
|
+
}
|
|
688
|
+
ev.preventDefault();
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/* Normal navigation intent.
|
|
693
|
+
* The nav never navigates on its own: it emits EV_NAV_CLICKED
|
|
694
|
+
* and lets the shell decide how to route (via hash or direct
|
|
695
|
+
* call). This breaks the circular import on yui_shell_navigate
|
|
696
|
+
* and keeps ownership of routing in one place. */
|
|
697
|
+
let $a = target.closest("[data-route]");
|
|
698
|
+
if(!$a) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
let route = $a.getAttribute("data-route");
|
|
702
|
+
if(empty_string(route)) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
if($a.getAttribute("data-disabled") === "1") {
|
|
706
|
+
ev.preventDefault();
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
ev.preventDefault();
|
|
710
|
+
gobj_publish_event(gobj, "EV_NAV_CLICKED", {
|
|
711
|
+
route: route,
|
|
712
|
+
item_id: $a.getAttribute("data-item-id") || "",
|
|
713
|
+
zone: gobj_read_attr(gobj, "zone") || "",
|
|
714
|
+
level: gobj_read_attr(gobj, "level") || "primary"
|
|
715
|
+
});
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
$container.addEventListener("click", handler);
|
|
719
|
+
priv.click_handler = handler;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
/***************************
|
|
726
|
+
* Actions
|
|
727
|
+
***************************/
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
/************************************************************
|
|
733
|
+
* EV_SET_ITEMS — replace this nav's items and re-render in place.
|
|
734
|
+
* This is the runtime counterpart of the startup config path:
|
|
735
|
+
* the shell (or app, via the shell) drives dynamic navs through it.
|
|
736
|
+
************************************************************/
|
|
737
|
+
function ac_set_items(gobj, event, kw, src)
|
|
738
|
+
{
|
|
739
|
+
gobj_write_attr(gobj, "menu_items", is_array(kw.items) ? kw.items : []);
|
|
740
|
+
rebuild(gobj);
|
|
741
|
+
return 0;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/************************************************************
|
|
745
|
+
* EV_ROUTE_CHANGED from the shell — refresh active highlight
|
|
746
|
+
************************************************************/
|
|
747
|
+
function ac_route_changed(gobj, event, kw, src)
|
|
748
|
+
{
|
|
749
|
+
let route = kw.route || "";
|
|
750
|
+
let parent_item = kw.parent_item;
|
|
751
|
+
let item = kw.item;
|
|
752
|
+
let level = gobj_read_attr(gobj, "level");
|
|
753
|
+
|
|
754
|
+
gobj_write_attr(gobj, "active_route", route);
|
|
755
|
+
let $c = gobj_read_attr(gobj, "$container");
|
|
756
|
+
if(!$c) {
|
|
757
|
+
return 0;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/* Remove prior active class on any children */
|
|
761
|
+
let previouslyActive = $c.querySelectorAll(".is-active");
|
|
762
|
+
previouslyActive.forEach(n => n.classList.remove("is-active"));
|
|
763
|
+
|
|
764
|
+
/* Decide which id we highlight:
|
|
765
|
+
* primary nav highlights parent_item (or item if top-level)
|
|
766
|
+
* secondary nav highlights the leaf item
|
|
767
|
+
*
|
|
768
|
+
* Multi-menu scoping: primary navs short-circuit when the
|
|
769
|
+
* route's owning menu differs from this nav's menu — two
|
|
770
|
+
* primary-style menus that share an item id (legitimate per
|
|
771
|
+
* TODO #5) must NOT cross-highlight. Treat empty kw.menu_id
|
|
772
|
+
* as a permissive match for backwards compatibility with
|
|
773
|
+
* pre-TODO #5 callers. */
|
|
774
|
+
let id_to_mark = null;
|
|
775
|
+
if(level === "primary") {
|
|
776
|
+
let nav_menu_id = gobj_read_attr(gobj, "menu_id") || "";
|
|
777
|
+
if(kw.menu_id && nav_menu_id && nav_menu_id !== kw.menu_id) {
|
|
778
|
+
return 0;
|
|
779
|
+
}
|
|
780
|
+
id_to_mark = parent_item ? parent_item.id : (item && item.id);
|
|
781
|
+
} else {
|
|
782
|
+
id_to_mark = item && item.id;
|
|
783
|
+
}
|
|
784
|
+
if(!id_to_mark) {
|
|
785
|
+
return 0;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
let $a = $c.querySelector(`[data-item-id="${css_escape(id_to_mark)}"]`);
|
|
789
|
+
if($a) {
|
|
790
|
+
/* For Bulma .tabs and .menu, 'is-active' goes on the <li> parent. */
|
|
791
|
+
let $li = $a.closest("li");
|
|
792
|
+
if($li) {
|
|
793
|
+
$li.classList.add("is-active");
|
|
794
|
+
}
|
|
795
|
+
$a.classList.add("is-active");
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/* Accordion: ensure only the section containing the active leaf
|
|
799
|
+
* is expanded. Any other open section collapses. */
|
|
800
|
+
if(gobj_read_attr(gobj, "layout") === "accordion") {
|
|
801
|
+
let active_leaf_id = (item && item.id) || id_to_mark;
|
|
802
|
+
let heads = $c.querySelectorAll(".yui-accordion-head");
|
|
803
|
+
heads.forEach($hd => {
|
|
804
|
+
let $body = $hd.nextElementSibling;
|
|
805
|
+
if(!$body || !$body.classList.contains("yui-accordion-body")) {
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
let contains_active = !!$body.querySelector(
|
|
809
|
+
`[data-item-id="${css_escape(active_leaf_id)}"]`
|
|
810
|
+
);
|
|
811
|
+
$body.classList.toggle("is-hidden", !contains_active);
|
|
812
|
+
$hd.classList.toggle("is-open", contains_active);
|
|
813
|
+
$hd.setAttribute("aria-expanded", contains_active ? "true" : "false");
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
return 0;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function css_escape(s)
|
|
820
|
+
{
|
|
821
|
+
/* Minimal escape for attribute selector */
|
|
822
|
+
return String(s).replace(/"/g, '\\"');
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/***************************************************************
|
|
826
|
+
* FSM
|
|
827
|
+
***************************************************************/
|
|
828
|
+
/*---------------------------------------------*
|
|
829
|
+
* Global methods table
|
|
830
|
+
*---------------------------------------------*/
|
|
831
|
+
const gmt = {
|
|
832
|
+
mt_create: mt_create,
|
|
833
|
+
mt_start: mt_start,
|
|
834
|
+
mt_stop: mt_stop,
|
|
835
|
+
mt_destroy: mt_destroy
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
function create_gclass(gclass_name)
|
|
839
|
+
{
|
|
840
|
+
if(__gclass__) {
|
|
841
|
+
log_error(`GClass ALREADY created: ${gclass_name}`);
|
|
842
|
+
return -1;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const states = [
|
|
846
|
+
["ST_IDLE", [
|
|
847
|
+
["EV_ROUTE_CHANGED", ac_route_changed, null],
|
|
848
|
+
["EV_SET_ITEMS", ac_set_items, null]
|
|
849
|
+
]]
|
|
850
|
+
];
|
|
851
|
+
|
|
852
|
+
const event_types = [
|
|
853
|
+
["EV_ROUTE_CHANGED", 0],
|
|
854
|
+
["EV_SET_ITEMS", 0],
|
|
855
|
+
["EV_NAV_CLICKED", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_PUBLIC_EVENT],
|
|
856
|
+
["EV_NAV_ITEM_CLOSE", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_PUBLIC_EVENT],
|
|
857
|
+
["EV_DRAWER_CLOSE_REQUESTED", event_flag_t.EVF_OUTPUT_EVENT|event_flag_t.EVF_PUBLIC_EVENT]
|
|
858
|
+
];
|
|
859
|
+
|
|
860
|
+
__gclass__ = gclass_create(
|
|
861
|
+
gclass_name,
|
|
862
|
+
event_types,
|
|
863
|
+
states,
|
|
864
|
+
gmt,
|
|
865
|
+
0,
|
|
866
|
+
attrs_table,
|
|
867
|
+
PRIVATE_DATA,
|
|
868
|
+
0, 0, 0, 0
|
|
869
|
+
);
|
|
870
|
+
if(!__gclass__) {
|
|
871
|
+
return -1;
|
|
872
|
+
}
|
|
873
|
+
return 0;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function register_c_yui_nav()
|
|
877
|
+
{
|
|
878
|
+
return create_gclass(GCLASS_NAME);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
export { register_c_yui_nav };
|