@yuneta/gobj-ui 7.22.0 → 7.23.1
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 -6
- package/dist/gobj-ui.cjs.js +116 -8
- package/dist/gobj-ui.es.js +116 -8
- package/package.json +1 -1
- package/src/c_yui_json.js +13 -4
- package/src/c_yui_json_graph.js +151 -10
package/README.md
CHANGED
|
@@ -444,7 +444,24 @@ same two facilities the tree has: a **find box** (rate-limited, `EV_FIND_NODES`)
|
|
|
444
444
|
that highlights matching rows and outlines the cards they are in and says how
|
|
445
445
|
many matched, and **expand-all / collapse-all** (`EV_EXPAND_ALL` /
|
|
446
446
|
`EV_COLLAPSE_ALL`) that fold every card but the root, marking each cut with
|
|
447
|
-
`▸ N` so the shape stays legible and you can see where the rest went.
|
|
447
|
+
`▸ N` so the shape stays legible and you can see where the rest went. Since
|
|
448
|
+
`7.23.0` each card that HAS a branch also carries its own handle in its header
|
|
449
|
+
(`EV_TOGGLE_FOLD {path}`); a leaf gets a spacer of the same width instead,
|
|
450
|
+
because a handle that does nothing is worse than none and the labels should
|
|
451
|
+
stay on one axis.
|
|
452
|
+
|
|
453
|
+
That handle is the **same filled chip the gobj tree draws** (`+N` folded, `−`
|
|
454
|
+
open — see `render_toggle_html` in `c_yui_gobj_tree_js.js`), and it is a chip
|
|
455
|
+
for a reason: `7.23.0` shipped it as a bare `▾` and on a phone it was
|
|
456
|
+
invisible. At the zoom that fits a document on screen a glyph is a couple of
|
|
457
|
+
pixels of ink; a filled chip is still a visible blob and reads as something you
|
|
458
|
+
press. `+N` also carries the count of what is hidden.
|
|
459
|
+
|
|
460
|
+
The handle is delegated from the canvas mount in the **capture** phase, over
|
|
461
|
+
`pointerdown`/`pointerup`/`mousedown`/`click`: the card is an `innerHTML`
|
|
462
|
+
string so nothing can be bound to it directly, G6 binds on the node element
|
|
463
|
+
below, and G6 builds its click from the POINTER sequence — swallowing `click`
|
|
464
|
+
alone leaves the fold working and the card also reporting an item click. A find
|
|
448
465
|
does not move the camera — a viewport that jumps on every keystroke is
|
|
449
466
|
unusable — which is why the count is there.
|
|
450
467
|
|
|
@@ -491,11 +508,13 @@ Two layout facts the browser taught this component, both worth keeping:
|
|
|
491
508
|
`"text"` / `"graph"`; **no mode advances** to the next view, which is what
|
|
492
509
|
the two-view toggle did when the list was two long), plus `EV_REFRESH` /
|
|
493
510
|
`EV_SHOW` / `EV_HIDE` / `EV_LANGUAGE_CHANGED`.
|
|
494
|
-
- Output
|
|
495
|
-
must declare it in its own FSM (CHILD subscription model)
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
511
|
+
- Output event: `EV_EXPAND_PATH {path, size}` (`EVF_OUTPUT_EVENT`) — the parent
|
|
512
|
+
must declare it in its own FSM (CHILD subscription model). That is the ONLY
|
|
513
|
+
one, on purpose: this viewer is a child of its host and subscribes it to
|
|
514
|
+
everything it publishes, so every output event is a mandatory declaration in
|
|
515
|
+
every host's FSM. The graph child's `EV_JSON_ITEM_CLICKED` stops here
|
|
516
|
+
(`7.21.0` forwarded it and broke exactly that way); a host that wants node
|
|
517
|
+
clicks mounts `C_YUI_JSON_GRAPH` itself.
|
|
499
518
|
- Internal (DOM → FSM): `EV_TOGGLE_NODE`, `EV_EXPAND_COLLAPSED`, `EV_SEARCH`,
|
|
500
519
|
`EV_EXPAND_ALL`, `EV_COLLAPSE_ALL`, `EV_COPY_ALL`. Every kw carries only a
|
|
501
520
|
`path` string — never a DOM node or gobj.
|
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -9473,6 +9473,12 @@ function ensure_drag_canvas_patch() {
|
|
|
9473
9473
|
* Constants
|
|
9474
9474
|
***************************************************************/
|
|
9475
9475
|
var GCLASS_NAME$11 = "C_YUI_JSON_GRAPH";
|
|
9476
|
+
var FOLD_SWALLOWED_EVENTS = [
|
|
9477
|
+
"pointerdown",
|
|
9478
|
+
"pointerup",
|
|
9479
|
+
"mousedown",
|
|
9480
|
+
"click"
|
|
9481
|
+
];
|
|
9476
9482
|
/***************************************************************
|
|
9477
9483
|
* Data
|
|
9478
9484
|
***************************************************************/
|
|
@@ -9497,6 +9503,7 @@ var PRIVATE_DATA$11 = {
|
|
|
9497
9503
|
collapsed: null,
|
|
9498
9504
|
match_count: 0,
|
|
9499
9505
|
find_timer: null,
|
|
9506
|
+
fold_listener: null,
|
|
9500
9507
|
$find_input: null,
|
|
9501
9508
|
$find_result: null,
|
|
9502
9509
|
$find_count: null
|
|
@@ -9659,6 +9666,18 @@ function build_ui$10(gobj) {
|
|
|
9659
9666
|
]]
|
|
9660
9667
|
]);
|
|
9661
9668
|
(0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", $container);
|
|
9669
|
+
let $mount = $container.querySelector("#" + priv.canvas_id);
|
|
9670
|
+
if ($mount) {
|
|
9671
|
+
priv.fold_listener = function(evt) {
|
|
9672
|
+
let $handle = evt.target && evt.target.closest ? evt.target.closest("[data-fold-path]") : null;
|
|
9673
|
+
if (!$handle) return;
|
|
9674
|
+
evt.stopPropagation();
|
|
9675
|
+
evt.preventDefault();
|
|
9676
|
+
if (evt.type !== "click") return;
|
|
9677
|
+
(0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_TOGGLE_FOLD", { path: $handle.getAttribute("data-fold-path") }, gobj);
|
|
9678
|
+
};
|
|
9679
|
+
for (let type of FOLD_SWALLOWED_EVENTS) $mount.addEventListener(type, priv.fold_listener, true);
|
|
9680
|
+
} else (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: no canvas mount, fold handles are dead`);
|
|
9662
9681
|
priv.$find_input = $container.querySelector(".JSON_GRAPH_FIND_INPUT");
|
|
9663
9682
|
priv.$find_result = $container.querySelector(".JSON_GRAPH_FIND_RESULT");
|
|
9664
9683
|
priv.$find_count = $container.querySelector(".JSON_GRAPH_FIND_COUNT");
|
|
@@ -9668,7 +9687,13 @@ function build_ui$10(gobj) {
|
|
|
9668
9687
|
* Destroy UI
|
|
9669
9688
|
************************************************************/
|
|
9670
9689
|
function destroy_ui$6(gobj) {
|
|
9690
|
+
let priv = gobj.priv;
|
|
9671
9691
|
let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
|
|
9692
|
+
if (priv.fold_listener) {
|
|
9693
|
+
let $mount = $container ? $container.querySelector("#" + priv.canvas_id) : null;
|
|
9694
|
+
if ($mount) for (let type of FOLD_SWALLOWED_EVENTS) $mount.removeEventListener(type, priv.fold_listener, true);
|
|
9695
|
+
priv.fold_listener = null;
|
|
9696
|
+
}
|
|
9672
9697
|
if ($container) {
|
|
9673
9698
|
if ($container.parentNode) $container.parentNode.removeChild($container);
|
|
9674
9699
|
(0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", null);
|
|
@@ -9906,6 +9931,51 @@ function build_cell_html(key, value, type, dark, matched) {
|
|
|
9906
9931
|
return row;
|
|
9907
9932
|
}
|
|
9908
9933
|
/************************************************************
|
|
9934
|
+
* The per-node fold handle, drawn in the card's header.
|
|
9935
|
+
*
|
|
9936
|
+
* Only a card with a BRANCH gets one — a leaf has nothing to
|
|
9937
|
+
* fold, and a handle that does nothing is worse than none.
|
|
9938
|
+
* It carries the path in `data-fold-path`, which is what the
|
|
9939
|
+
* delegated listener in build_ui reads; the card is an
|
|
9940
|
+
* innerHTML string, so there is no other way to hang a
|
|
9941
|
+
* handler on it.
|
|
9942
|
+
*
|
|
9943
|
+
* A filled CHIP, the same one the gobj tree draws
|
|
9944
|
+
* (`c_yui_gobj_tree_js.js`, render_toggle_html): background,
|
|
9945
|
+
* border, radius, and `+N` / `−` rather than a triangle. It
|
|
9946
|
+
* shipped once as a bare `▾` and on a phone it was invisible —
|
|
9947
|
+
* a glyph is a couple of pixels of ink at the zoom that fits a
|
|
9948
|
+
* document on screen, while a filled chip stays a visible blob
|
|
9949
|
+
* and reads as something you press. `+N` also carries the
|
|
9950
|
+
* count of what is hidden, which the open state does not need.
|
|
9951
|
+
*
|
|
9952
|
+
* No `title`: it would be a word inside an innerHTML string,
|
|
9953
|
+
* where it could carry no i18n key and would sit in English
|
|
9954
|
+
* forever. `+3` and `−` need no language.
|
|
9955
|
+
************************************************************/
|
|
9956
|
+
function fold_toggle_html(path, foldable, folded, branches, colors) {
|
|
9957
|
+
if (!foldable) return `<span style="width:24px; flex:0 0 auto;"></span>`;
|
|
9958
|
+
let label = folded ? "+" + branches : "−";
|
|
9959
|
+
return `<span data-fold-path="${(0, _yuneta_gobj_js.escapeHtml)(path)}" style="
|
|
9960
|
+
flex: 0 0 auto;
|
|
9961
|
+
display: inline-flex;
|
|
9962
|
+
align-items: center;
|
|
9963
|
+
justify-content: center;
|
|
9964
|
+
min-width: 22px;
|
|
9965
|
+
height: 16px;
|
|
9966
|
+
padding: 0 5px;
|
|
9967
|
+
background: ${colors.bg};
|
|
9968
|
+
color: ${colors.border};
|
|
9969
|
+
border: 1px solid ${colors.bg};
|
|
9970
|
+
border-radius: 6px;
|
|
9971
|
+
font-size: 11px;
|
|
9972
|
+
font-weight: 700;
|
|
9973
|
+
line-height: 1;
|
|
9974
|
+
cursor: pointer;
|
|
9975
|
+
user-select: none;
|
|
9976
|
+
">${label}</span>`;
|
|
9977
|
+
}
|
|
9978
|
+
/************************************************************
|
|
9909
9979
|
* Does this key/value pair match the find term?
|
|
9910
9980
|
*
|
|
9911
9981
|
* Scalars are matched on what the card SHOWS. A complex value
|
|
@@ -9990,8 +10060,8 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
|
|
|
9990
10060
|
});
|
|
9991
10061
|
}
|
|
9992
10062
|
let label = get_group_label(path) || (is_dict ? "{}" : "[]");
|
|
9993
|
-
let
|
|
9994
|
-
|
|
10063
|
+
let foldable = pending_complex.length > 0;
|
|
10064
|
+
let folded = foldable && priv.collapsed.has(path);
|
|
9995
10065
|
let colors = json_card_style(is_dict ? GROUP_COLORS.dict : GROUP_COLORS.list, dark);
|
|
9996
10066
|
let border_color = card_matched ? dark ? "#ffb300" : "#ff8f00" : colors.border;
|
|
9997
10067
|
let border_width = card_matched ? 2 : 1;
|
|
@@ -10012,8 +10082,10 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
|
|
|
10012
10082
|
font-weight: bold;
|
|
10013
10083
|
font-size: 12px;
|
|
10014
10084
|
padding: 3px 8px;
|
|
10015
|
-
|
|
10016
|
-
|
|
10085
|
+
display: flex;
|
|
10086
|
+
align-items: center;
|
|
10087
|
+
gap: 6px;
|
|
10088
|
+
">${fold_toggle_html(path, foldable, folded, pending_complex.length, colors)}<span style="flex:1 1 auto; text-align:center;">${(0, _yuneta_gobj_js.escapeHtml)(label)}</span></div>
|
|
10017
10089
|
${content_html}
|
|
10018
10090
|
</div>`;
|
|
10019
10091
|
let node = {
|
|
@@ -10230,6 +10302,27 @@ function ac_refresh$5(gobj, event, kw, src) {
|
|
|
10230
10302
|
}
|
|
10231
10303
|
/************************************************************
|
|
10232
10304
|
* {theme: "dark"|"light"} — the app switched theme.
|
|
10305
|
+
* EV_TOGGLE_FOLD { path } — fold or unfold ONE card.
|
|
10306
|
+
*
|
|
10307
|
+
* The camera does not move: folding one branch is a local
|
|
10308
|
+
* edit of the picture, and refitting the whole graph for it
|
|
10309
|
+
* would throw away where the reader was looking. The toolbar
|
|
10310
|
+
* pair, which changes everything at once, does refit.
|
|
10311
|
+
************************************************************/
|
|
10312
|
+
function ac_toggle_fold(gobj, event, kw, src) {
|
|
10313
|
+
let priv = gobj.priv;
|
|
10314
|
+
let path = kw && kw.path || "";
|
|
10315
|
+
if ((0, _yuneta_gobj_js.empty_string)(path)) {
|
|
10316
|
+
(0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: EV_TOGGLE_FOLD with no path`);
|
|
10317
|
+
return -1;
|
|
10318
|
+
}
|
|
10319
|
+
if (priv.collapsed.has(path)) priv.collapsed.delete(path);
|
|
10320
|
+
else priv.collapsed.add(path);
|
|
10321
|
+
refresh_json(gobj, { preserve_view: true });
|
|
10322
|
+
update_find_result(gobj);
|
|
10323
|
+
return 0;
|
|
10324
|
+
}
|
|
10325
|
+
/************************************************************
|
|
10233
10326
|
* EV_FIND_NODES { text }
|
|
10234
10327
|
*
|
|
10235
10328
|
* The camera does NOT move: a find is a way of reading what is
|
|
@@ -10418,6 +10511,11 @@ function create_gclass$11(gclass_name) {
|
|
|
10418
10511
|
ac_find_nodes$2,
|
|
10419
10512
|
null
|
|
10420
10513
|
],
|
|
10514
|
+
[
|
|
10515
|
+
"EV_TOGGLE_FOLD",
|
|
10516
|
+
ac_toggle_fold,
|
|
10517
|
+
null
|
|
10518
|
+
],
|
|
10421
10519
|
[
|
|
10422
10520
|
"EV_EXPAND_ALL",
|
|
10423
10521
|
ac_expand_all$2,
|
|
@@ -10478,6 +10576,7 @@ function create_gclass$11(gclass_name) {
|
|
|
10478
10576
|
["EV_LOAD_DATA", 0],
|
|
10479
10577
|
["EV_REFRESH", 0],
|
|
10480
10578
|
["EV_FIND_NODES", 0],
|
|
10579
|
+
["EV_TOGGLE_FOLD", 0],
|
|
10481
10580
|
["EV_EXPAND_ALL", 0],
|
|
10482
10581
|
["EV_COLLAPSE_ALL", 0],
|
|
10483
10582
|
["EV_THEME", 0],
|
|
@@ -11681,11 +11780,20 @@ function ac_set_view_mode(gobj, event, kw, src) {
|
|
|
11681
11780
|
/************************************************************
|
|
11682
11781
|
* EV_JSON_ITEM_CLICKED — from the hosted graph child.
|
|
11683
11782
|
*
|
|
11684
|
-
*
|
|
11685
|
-
*
|
|
11783
|
+
* It STOPS here, and that is deliberate. 7.21.0 republished
|
|
11784
|
+
* it "so the host has one contract", which got the framework
|
|
11785
|
+
* backwards: this viewer is a CHILD of its host and subscribes
|
|
11786
|
+
* it to everything it publishes, so a new output event is a
|
|
11787
|
+
* new MANDATORY declaration in every host's FSM. None of the
|
|
11788
|
+
* six that mount this viewer asked for node clicks, and every
|
|
11789
|
+
* one of them answered a click on a graph card with
|
|
11790
|
+
* "Event NOT DEFINED in state".
|
|
11791
|
+
*
|
|
11792
|
+
* A host that wants node clicks mounts C_YUI_JSON_GRAPH
|
|
11793
|
+
* itself — that gclass publishes them, and a host that mounts
|
|
11794
|
+
* it has declared them.
|
|
11686
11795
|
************************************************************/
|
|
11687
11796
|
function ac_json_item_clicked(gobj, event, kw, src) {
|
|
11688
|
-
(0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_JSON_ITEM_CLICKED", kw);
|
|
11689
11797
|
return 0;
|
|
11690
11798
|
}
|
|
11691
11799
|
/************************************************************
|
|
@@ -11858,7 +11966,7 @@ function create_gclass$10(gclass_name) {
|
|
|
11858
11966
|
["EV_COLLAPSE_ALL", 0],
|
|
11859
11967
|
["EV_COPY_ALL", 0],
|
|
11860
11968
|
["EV_SET_VIEW_MODE", 0],
|
|
11861
|
-
["EV_JSON_ITEM_CLICKED",
|
|
11969
|
+
["EV_JSON_ITEM_CLICKED", 0],
|
|
11862
11970
|
["EV_LANGUAGE_CHANGED", 0],
|
|
11863
11971
|
["EV_REFRESH", 0],
|
|
11864
11972
|
["EV_SHOW", 0],
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -9468,6 +9468,12 @@ function ensure_drag_canvas_patch() {
|
|
|
9468
9468
|
* Constants
|
|
9469
9469
|
***************************************************************/
|
|
9470
9470
|
var GCLASS_NAME$11 = "C_YUI_JSON_GRAPH";
|
|
9471
|
+
var FOLD_SWALLOWED_EVENTS = [
|
|
9472
|
+
"pointerdown",
|
|
9473
|
+
"pointerup",
|
|
9474
|
+
"mousedown",
|
|
9475
|
+
"click"
|
|
9476
|
+
];
|
|
9471
9477
|
/***************************************************************
|
|
9472
9478
|
* Data
|
|
9473
9479
|
***************************************************************/
|
|
@@ -9492,6 +9498,7 @@ var PRIVATE_DATA$11 = {
|
|
|
9492
9498
|
collapsed: null,
|
|
9493
9499
|
match_count: 0,
|
|
9494
9500
|
find_timer: null,
|
|
9501
|
+
fold_listener: null,
|
|
9495
9502
|
$find_input: null,
|
|
9496
9503
|
$find_result: null,
|
|
9497
9504
|
$find_count: null
|
|
@@ -9654,6 +9661,18 @@ function build_ui$10(gobj) {
|
|
|
9654
9661
|
]]
|
|
9655
9662
|
]);
|
|
9656
9663
|
gobj_write_attr(gobj, "$container", $container);
|
|
9664
|
+
let $mount = $container.querySelector("#" + priv.canvas_id);
|
|
9665
|
+
if ($mount) {
|
|
9666
|
+
priv.fold_listener = function(evt) {
|
|
9667
|
+
let $handle = evt.target && evt.target.closest ? evt.target.closest("[data-fold-path]") : null;
|
|
9668
|
+
if (!$handle) return;
|
|
9669
|
+
evt.stopPropagation();
|
|
9670
|
+
evt.preventDefault();
|
|
9671
|
+
if (evt.type !== "click") return;
|
|
9672
|
+
gobj_send_event(gobj, "EV_TOGGLE_FOLD", { path: $handle.getAttribute("data-fold-path") }, gobj);
|
|
9673
|
+
};
|
|
9674
|
+
for (let type of FOLD_SWALLOWED_EVENTS) $mount.addEventListener(type, priv.fold_listener, true);
|
|
9675
|
+
} else log_error(`${gobj_short_name(gobj)}: no canvas mount, fold handles are dead`);
|
|
9657
9676
|
priv.$find_input = $container.querySelector(".JSON_GRAPH_FIND_INPUT");
|
|
9658
9677
|
priv.$find_result = $container.querySelector(".JSON_GRAPH_FIND_RESULT");
|
|
9659
9678
|
priv.$find_count = $container.querySelector(".JSON_GRAPH_FIND_COUNT");
|
|
@@ -9663,7 +9682,13 @@ function build_ui$10(gobj) {
|
|
|
9663
9682
|
* Destroy UI
|
|
9664
9683
|
************************************************************/
|
|
9665
9684
|
function destroy_ui$6(gobj) {
|
|
9685
|
+
let priv = gobj.priv;
|
|
9666
9686
|
let $container = gobj_read_attr(gobj, "$container");
|
|
9687
|
+
if (priv.fold_listener) {
|
|
9688
|
+
let $mount = $container ? $container.querySelector("#" + priv.canvas_id) : null;
|
|
9689
|
+
if ($mount) for (let type of FOLD_SWALLOWED_EVENTS) $mount.removeEventListener(type, priv.fold_listener, true);
|
|
9690
|
+
priv.fold_listener = null;
|
|
9691
|
+
}
|
|
9667
9692
|
if ($container) {
|
|
9668
9693
|
if ($container.parentNode) $container.parentNode.removeChild($container);
|
|
9669
9694
|
gobj_write_attr(gobj, "$container", null);
|
|
@@ -9901,6 +9926,51 @@ function build_cell_html(key, value, type, dark, matched) {
|
|
|
9901
9926
|
return row;
|
|
9902
9927
|
}
|
|
9903
9928
|
/************************************************************
|
|
9929
|
+
* The per-node fold handle, drawn in the card's header.
|
|
9930
|
+
*
|
|
9931
|
+
* Only a card with a BRANCH gets one — a leaf has nothing to
|
|
9932
|
+
* fold, and a handle that does nothing is worse than none.
|
|
9933
|
+
* It carries the path in `data-fold-path`, which is what the
|
|
9934
|
+
* delegated listener in build_ui reads; the card is an
|
|
9935
|
+
* innerHTML string, so there is no other way to hang a
|
|
9936
|
+
* handler on it.
|
|
9937
|
+
*
|
|
9938
|
+
* A filled CHIP, the same one the gobj tree draws
|
|
9939
|
+
* (`c_yui_gobj_tree_js.js`, render_toggle_html): background,
|
|
9940
|
+
* border, radius, and `+N` / `−` rather than a triangle. It
|
|
9941
|
+
* shipped once as a bare `▾` and on a phone it was invisible —
|
|
9942
|
+
* a glyph is a couple of pixels of ink at the zoom that fits a
|
|
9943
|
+
* document on screen, while a filled chip stays a visible blob
|
|
9944
|
+
* and reads as something you press. `+N` also carries the
|
|
9945
|
+
* count of what is hidden, which the open state does not need.
|
|
9946
|
+
*
|
|
9947
|
+
* No `title`: it would be a word inside an innerHTML string,
|
|
9948
|
+
* where it could carry no i18n key and would sit in English
|
|
9949
|
+
* forever. `+3` and `−` need no language.
|
|
9950
|
+
************************************************************/
|
|
9951
|
+
function fold_toggle_html(path, foldable, folded, branches, colors) {
|
|
9952
|
+
if (!foldable) return `<span style="width:24px; flex:0 0 auto;"></span>`;
|
|
9953
|
+
let label = folded ? "+" + branches : "−";
|
|
9954
|
+
return `<span data-fold-path="${escapeHtml(path)}" style="
|
|
9955
|
+
flex: 0 0 auto;
|
|
9956
|
+
display: inline-flex;
|
|
9957
|
+
align-items: center;
|
|
9958
|
+
justify-content: center;
|
|
9959
|
+
min-width: 22px;
|
|
9960
|
+
height: 16px;
|
|
9961
|
+
padding: 0 5px;
|
|
9962
|
+
background: ${colors.bg};
|
|
9963
|
+
color: ${colors.border};
|
|
9964
|
+
border: 1px solid ${colors.bg};
|
|
9965
|
+
border-radius: 6px;
|
|
9966
|
+
font-size: 11px;
|
|
9967
|
+
font-weight: 700;
|
|
9968
|
+
line-height: 1;
|
|
9969
|
+
cursor: pointer;
|
|
9970
|
+
user-select: none;
|
|
9971
|
+
">${label}</span>`;
|
|
9972
|
+
}
|
|
9973
|
+
/************************************************************
|
|
9904
9974
|
* Does this key/value pair match the find term?
|
|
9905
9975
|
*
|
|
9906
9976
|
* Scalars are matched on what the card SHOWS. A complex value
|
|
@@ -9985,8 +10055,8 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
|
|
|
9985
10055
|
});
|
|
9986
10056
|
}
|
|
9987
10057
|
let label = get_group_label(path) || (is_dict ? "{}" : "[]");
|
|
9988
|
-
let
|
|
9989
|
-
|
|
10058
|
+
let foldable = pending_complex.length > 0;
|
|
10059
|
+
let folded = foldable && priv.collapsed.has(path);
|
|
9990
10060
|
let colors = json_card_style(is_dict ? GROUP_COLORS.dict : GROUP_COLORS.list, dark);
|
|
9991
10061
|
let border_color = card_matched ? dark ? "#ffb300" : "#ff8f00" : colors.border;
|
|
9992
10062
|
let border_width = card_matched ? 2 : 1;
|
|
@@ -10007,8 +10077,10 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id) {
|
|
|
10007
10077
|
font-weight: bold;
|
|
10008
10078
|
font-size: 12px;
|
|
10009
10079
|
padding: 3px 8px;
|
|
10010
|
-
|
|
10011
|
-
|
|
10080
|
+
display: flex;
|
|
10081
|
+
align-items: center;
|
|
10082
|
+
gap: 6px;
|
|
10083
|
+
">${fold_toggle_html(path, foldable, folded, pending_complex.length, colors)}<span style="flex:1 1 auto; text-align:center;">${escapeHtml(label)}</span></div>
|
|
10012
10084
|
${content_html}
|
|
10013
10085
|
</div>`;
|
|
10014
10086
|
let node = {
|
|
@@ -10225,6 +10297,27 @@ function ac_refresh$5(gobj, event, kw, src) {
|
|
|
10225
10297
|
}
|
|
10226
10298
|
/************************************************************
|
|
10227
10299
|
* {theme: "dark"|"light"} — the app switched theme.
|
|
10300
|
+
* EV_TOGGLE_FOLD { path } — fold or unfold ONE card.
|
|
10301
|
+
*
|
|
10302
|
+
* The camera does not move: folding one branch is a local
|
|
10303
|
+
* edit of the picture, and refitting the whole graph for it
|
|
10304
|
+
* would throw away where the reader was looking. The toolbar
|
|
10305
|
+
* pair, which changes everything at once, does refit.
|
|
10306
|
+
************************************************************/
|
|
10307
|
+
function ac_toggle_fold(gobj, event, kw, src) {
|
|
10308
|
+
let priv = gobj.priv;
|
|
10309
|
+
let path = kw && kw.path || "";
|
|
10310
|
+
if (empty_string(path)) {
|
|
10311
|
+
log_error(`${gobj_short_name(gobj)}: EV_TOGGLE_FOLD with no path`);
|
|
10312
|
+
return -1;
|
|
10313
|
+
}
|
|
10314
|
+
if (priv.collapsed.has(path)) priv.collapsed.delete(path);
|
|
10315
|
+
else priv.collapsed.add(path);
|
|
10316
|
+
refresh_json(gobj, { preserve_view: true });
|
|
10317
|
+
update_find_result(gobj);
|
|
10318
|
+
return 0;
|
|
10319
|
+
}
|
|
10320
|
+
/************************************************************
|
|
10228
10321
|
* EV_FIND_NODES { text }
|
|
10229
10322
|
*
|
|
10230
10323
|
* The camera does NOT move: a find is a way of reading what is
|
|
@@ -10413,6 +10506,11 @@ function create_gclass$11(gclass_name) {
|
|
|
10413
10506
|
ac_find_nodes$2,
|
|
10414
10507
|
null
|
|
10415
10508
|
],
|
|
10509
|
+
[
|
|
10510
|
+
"EV_TOGGLE_FOLD",
|
|
10511
|
+
ac_toggle_fold,
|
|
10512
|
+
null
|
|
10513
|
+
],
|
|
10416
10514
|
[
|
|
10417
10515
|
"EV_EXPAND_ALL",
|
|
10418
10516
|
ac_expand_all$2,
|
|
@@ -10473,6 +10571,7 @@ function create_gclass$11(gclass_name) {
|
|
|
10473
10571
|
["EV_LOAD_DATA", 0],
|
|
10474
10572
|
["EV_REFRESH", 0],
|
|
10475
10573
|
["EV_FIND_NODES", 0],
|
|
10574
|
+
["EV_TOGGLE_FOLD", 0],
|
|
10476
10575
|
["EV_EXPAND_ALL", 0],
|
|
10477
10576
|
["EV_COLLAPSE_ALL", 0],
|
|
10478
10577
|
["EV_THEME", 0],
|
|
@@ -11676,11 +11775,20 @@ function ac_set_view_mode(gobj, event, kw, src) {
|
|
|
11676
11775
|
/************************************************************
|
|
11677
11776
|
* EV_JSON_ITEM_CLICKED — from the hosted graph child.
|
|
11678
11777
|
*
|
|
11679
|
-
*
|
|
11680
|
-
*
|
|
11778
|
+
* It STOPS here, and that is deliberate. 7.21.0 republished
|
|
11779
|
+
* it "so the host has one contract", which got the framework
|
|
11780
|
+
* backwards: this viewer is a CHILD of its host and subscribes
|
|
11781
|
+
* it to everything it publishes, so a new output event is a
|
|
11782
|
+
* new MANDATORY declaration in every host's FSM. None of the
|
|
11783
|
+
* six that mount this viewer asked for node clicks, and every
|
|
11784
|
+
* one of them answered a click on a graph card with
|
|
11785
|
+
* "Event NOT DEFINED in state".
|
|
11786
|
+
*
|
|
11787
|
+
* A host that wants node clicks mounts C_YUI_JSON_GRAPH
|
|
11788
|
+
* itself — that gclass publishes them, and a host that mounts
|
|
11789
|
+
* it has declared them.
|
|
11681
11790
|
************************************************************/
|
|
11682
11791
|
function ac_json_item_clicked(gobj, event, kw, src) {
|
|
11683
|
-
gobj_publish_event(gobj, "EV_JSON_ITEM_CLICKED", kw);
|
|
11684
11792
|
return 0;
|
|
11685
11793
|
}
|
|
11686
11794
|
/************************************************************
|
|
@@ -11853,7 +11961,7 @@ function create_gclass$10(gclass_name) {
|
|
|
11853
11961
|
["EV_COLLAPSE_ALL", 0],
|
|
11854
11962
|
["EV_COPY_ALL", 0],
|
|
11855
11963
|
["EV_SET_VIEW_MODE", 0],
|
|
11856
|
-
["EV_JSON_ITEM_CLICKED",
|
|
11964
|
+
["EV_JSON_ITEM_CLICKED", 0],
|
|
11857
11965
|
["EV_LANGUAGE_CHANGED", 0],
|
|
11858
11966
|
["EV_REFRESH", 0],
|
|
11859
11967
|
["EV_SHOW", 0],
|
package/package.json
CHANGED
package/src/c_yui_json.js
CHANGED
|
@@ -1175,12 +1175,21 @@ function ac_set_view_mode(gobj, event, kw, src)
|
|
|
1175
1175
|
/************************************************************
|
|
1176
1176
|
* EV_JSON_ITEM_CLICKED — from the hosted graph child.
|
|
1177
1177
|
*
|
|
1178
|
-
*
|
|
1179
|
-
*
|
|
1178
|
+
* It STOPS here, and that is deliberate. 7.21.0 republished
|
|
1179
|
+
* it "so the host has one contract", which got the framework
|
|
1180
|
+
* backwards: this viewer is a CHILD of its host and subscribes
|
|
1181
|
+
* it to everything it publishes, so a new output event is a
|
|
1182
|
+
* new MANDATORY declaration in every host's FSM. None of the
|
|
1183
|
+
* six that mount this viewer asked for node clicks, and every
|
|
1184
|
+
* one of them answered a click on a graph card with
|
|
1185
|
+
* "Event NOT DEFINED in state".
|
|
1186
|
+
*
|
|
1187
|
+
* A host that wants node clicks mounts C_YUI_JSON_GRAPH
|
|
1188
|
+
* itself — that gclass publishes them, and a host that mounts
|
|
1189
|
+
* it has declared them.
|
|
1180
1190
|
************************************************************/
|
|
1181
1191
|
function ac_json_item_clicked(gobj, event, kw, src)
|
|
1182
1192
|
{
|
|
1183
|
-
gobj_publish_event(gobj, "EV_JSON_ITEM_CLICKED", kw);
|
|
1184
1193
|
return 0;
|
|
1185
1194
|
}
|
|
1186
1195
|
|
|
@@ -1307,7 +1316,7 @@ function create_gclass(gclass_name)
|
|
|
1307
1316
|
["EV_COLLAPSE_ALL", 0],
|
|
1308
1317
|
["EV_COPY_ALL", 0],
|
|
1309
1318
|
["EV_SET_VIEW_MODE", 0],
|
|
1310
|
-
["EV_JSON_ITEM_CLICKED",
|
|
1319
|
+
["EV_JSON_ITEM_CLICKED", 0],
|
|
1311
1320
|
["EV_LANGUAGE_CHANGED", 0],
|
|
1312
1321
|
["EV_REFRESH", 0],
|
|
1313
1322
|
["EV_SHOW", 0],
|
package/src/c_yui_json_graph.js
CHANGED
|
@@ -67,6 +67,13 @@ import { yui_is_dark, yui_theme_now, yui_watch_theme } from "./yui_theme.js";
|
|
|
67
67
|
***************************************************************/
|
|
68
68
|
const GCLASS_NAME = "C_YUI_JSON_GRAPH";
|
|
69
69
|
|
|
70
|
+
/*
|
|
71
|
+
* What a click on a fold handle has to swallow so G6 never turns it
|
|
72
|
+
* into a node click. `click` is the one that ACTS; the rest are cut
|
|
73
|
+
* because G6 assembles its own click from the pointer sequence.
|
|
74
|
+
*/
|
|
75
|
+
const FOLD_SWALLOWED_EVENTS = ["pointerdown", "pointerup", "mousedown", "click"];
|
|
76
|
+
|
|
70
77
|
/***************************************************************
|
|
71
78
|
* Data
|
|
72
79
|
***************************************************************/
|
|
@@ -102,6 +109,7 @@ let PRIVATE_DATA = {
|
|
|
102
109
|
collapsed: null, // Set<string> of paths whose children are hidden
|
|
103
110
|
match_count: 0, // matches of the last find, for the count chip
|
|
104
111
|
find_timer: null, // rate-limits the find box
|
|
112
|
+
fold_listener: null, // delegated click on the card fold handles
|
|
105
113
|
$find_input: null,
|
|
106
114
|
$find_result: null,
|
|
107
115
|
$find_count: null,
|
|
@@ -303,6 +311,46 @@ function build_ui(gobj)
|
|
|
303
311
|
|
|
304
312
|
gobj_write_attr(gobj, "$container", $container);
|
|
305
313
|
|
|
314
|
+
/*
|
|
315
|
+
* The fold handles live inside an `innerHTML` string, so no handler
|
|
316
|
+
* can be attached to them: the click is delegated from the canvas
|
|
317
|
+
* mount, in the CAPTURE phase, over FOUR event types.
|
|
318
|
+
*
|
|
319
|
+
* Capture because G6 binds on the html node ELEMENT, a descendant
|
|
320
|
+
* of this one: in the bubble phase G6 has already run by the time
|
|
321
|
+
* this listener sees the event, so stopping it there stops nothing.
|
|
322
|
+
*
|
|
323
|
+
* Four types because G6 does not use the DOM `click` at all — it
|
|
324
|
+
* builds its own from the POINTER sequence. Swallowing `click`
|
|
325
|
+
* alone left the fold working and the card ALSO reporting an item
|
|
326
|
+
* click; `pointerdown`/`pointerup` are the pair that has to be cut,
|
|
327
|
+
* and `mousedown` goes with them for a browser that still sends it.
|
|
328
|
+
* Measured by instrumenting the mount, not guessed.
|
|
329
|
+
*/
|
|
330
|
+
let $mount = $container.querySelector('#' + priv.canvas_id);
|
|
331
|
+
if($mount) {
|
|
332
|
+
priv.fold_listener = function(evt) {
|
|
333
|
+
let $handle = evt.target && evt.target.closest
|
|
334
|
+
? evt.target.closest('[data-fold-path]')
|
|
335
|
+
: null;
|
|
336
|
+
if(!$handle) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
evt.stopPropagation();
|
|
340
|
+
evt.preventDefault();
|
|
341
|
+
if(evt.type !== "click") {
|
|
342
|
+
return; /* swallowed so G6 never sees a node click */
|
|
343
|
+
}
|
|
344
|
+
gobj_send_event(gobj, "EV_TOGGLE_FOLD",
|
|
345
|
+
{path: $handle.getAttribute('data-fold-path')}, gobj);
|
|
346
|
+
};
|
|
347
|
+
for(let type of FOLD_SWALLOWED_EVENTS) {
|
|
348
|
+
$mount.addEventListener(type, priv.fold_listener, true);
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
log_error(`${gobj_short_name(gobj)}: no canvas mount, fold handles are dead`);
|
|
352
|
+
}
|
|
353
|
+
|
|
306
354
|
priv.$find_input = $container.querySelector('.JSON_GRAPH_FIND_INPUT');
|
|
307
355
|
priv.$find_result = $container.querySelector('.JSON_GRAPH_FIND_RESULT');
|
|
308
356
|
priv.$find_count = $container.querySelector('.JSON_GRAPH_FIND_COUNT');
|
|
@@ -315,7 +363,19 @@ function build_ui(gobj)
|
|
|
315
363
|
************************************************************/
|
|
316
364
|
function destroy_ui(gobj)
|
|
317
365
|
{
|
|
366
|
+
let priv = gobj.priv;
|
|
318
367
|
let $container = gobj_read_attr(gobj, "$container");
|
|
368
|
+
|
|
369
|
+
if(priv.fold_listener) {
|
|
370
|
+
let $mount = $container? $container.querySelector('#' + priv.canvas_id): null;
|
|
371
|
+
if($mount) {
|
|
372
|
+
for(let type of FOLD_SWALLOWED_EVENTS) {
|
|
373
|
+
$mount.removeEventListener(type, priv.fold_listener, true);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
priv.fold_listener = null;
|
|
377
|
+
}
|
|
378
|
+
|
|
319
379
|
if($container) {
|
|
320
380
|
if($container.parentNode) {
|
|
321
381
|
$container.parentNode.removeChild($container);
|
|
@@ -598,6 +658,59 @@ function build_cell_html(key, value, type, dark, matched)
|
|
|
598
658
|
return row;
|
|
599
659
|
}
|
|
600
660
|
|
|
661
|
+
/************************************************************
|
|
662
|
+
* The per-node fold handle, drawn in the card's header.
|
|
663
|
+
*
|
|
664
|
+
* Only a card with a BRANCH gets one — a leaf has nothing to
|
|
665
|
+
* fold, and a handle that does nothing is worse than none.
|
|
666
|
+
* It carries the path in `data-fold-path`, which is what the
|
|
667
|
+
* delegated listener in build_ui reads; the card is an
|
|
668
|
+
* innerHTML string, so there is no other way to hang a
|
|
669
|
+
* handler on it.
|
|
670
|
+
*
|
|
671
|
+
* A filled CHIP, the same one the gobj tree draws
|
|
672
|
+
* (`c_yui_gobj_tree_js.js`, render_toggle_html): background,
|
|
673
|
+
* border, radius, and `+N` / `−` rather than a triangle. It
|
|
674
|
+
* shipped once as a bare `▾` and on a phone it was invisible —
|
|
675
|
+
* a glyph is a couple of pixels of ink at the zoom that fits a
|
|
676
|
+
* document on screen, while a filled chip stays a visible blob
|
|
677
|
+
* and reads as something you press. `+N` also carries the
|
|
678
|
+
* count of what is hidden, which the open state does not need.
|
|
679
|
+
*
|
|
680
|
+
* No `title`: it would be a word inside an innerHTML string,
|
|
681
|
+
* where it could carry no i18n key and would sit in English
|
|
682
|
+
* forever. `+3` and `−` need no language.
|
|
683
|
+
************************************************************/
|
|
684
|
+
function fold_toggle_html(path, foldable, folded, branches, colors)
|
|
685
|
+
{
|
|
686
|
+
if(!foldable) {
|
|
687
|
+
/* A spacer the width of the chip, so the label of a leaf card
|
|
688
|
+
* stays on the same axis as the label of a foldable one. */
|
|
689
|
+
return `<span style="width:24px; flex:0 0 auto;"></span>`;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
let label = folded? ("+" + branches): "−"; /* +N / − */
|
|
693
|
+
|
|
694
|
+
return `<span data-fold-path="${escapeHtml(path)}" style="
|
|
695
|
+
flex: 0 0 auto;
|
|
696
|
+
display: inline-flex;
|
|
697
|
+
align-items: center;
|
|
698
|
+
justify-content: center;
|
|
699
|
+
min-width: 22px;
|
|
700
|
+
height: 16px;
|
|
701
|
+
padding: 0 5px;
|
|
702
|
+
background: ${colors.bg};
|
|
703
|
+
color: ${colors.border};
|
|
704
|
+
border: 1px solid ${colors.bg};
|
|
705
|
+
border-radius: 6px;
|
|
706
|
+
font-size: 11px;
|
|
707
|
+
font-weight: 700;
|
|
708
|
+
line-height: 1;
|
|
709
|
+
cursor: pointer;
|
|
710
|
+
user-select: none;
|
|
711
|
+
">${label}</span>`;
|
|
712
|
+
}
|
|
713
|
+
|
|
601
714
|
/************************************************************
|
|
602
715
|
* Does this key/value pair match the find term?
|
|
603
716
|
*
|
|
@@ -725,17 +838,12 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
|
|
|
725
838
|
* Folded: keep the card, drop the branch, and SAY how much went
|
|
726
839
|
* with it. A cut that is not marked reads as a document that ends
|
|
727
840
|
* there — the same rule the lazy tree follows for a `__collapsed__`
|
|
728
|
-
* sentinel. The
|
|
841
|
+
* sentinel. The mark is a glyph and a number on purpose: the card
|
|
729
842
|
* is an innerHTML string, where a word could carry no i18n key and
|
|
730
843
|
* would sit there in English forever.
|
|
731
844
|
*/
|
|
732
|
-
let
|
|
733
|
-
|
|
734
|
-
lines.push(
|
|
735
|
-
`<span style="color:${type_color("number", dark)}; font-weight:bold;">` +
|
|
736
|
-
`▸ ${pending_complex.length}</span>`
|
|
737
|
-
);
|
|
738
|
-
}
|
|
845
|
+
let foldable = pending_complex.length > 0;
|
|
846
|
+
let folded = foldable && priv.collapsed.has(path);
|
|
739
847
|
let colors = json_card_style(
|
|
740
848
|
is_dict ? GROUP_COLORS.dict : GROUP_COLORS.list, dark
|
|
741
849
|
);
|
|
@@ -766,8 +874,10 @@ function build_json_nodes(gobj, path, kw, nodes, edges, parent_id)
|
|
|
766
874
|
font-weight: bold;
|
|
767
875
|
font-size: 12px;
|
|
768
876
|
padding: 3px 8px;
|
|
769
|
-
|
|
770
|
-
|
|
877
|
+
display: flex;
|
|
878
|
+
align-items: center;
|
|
879
|
+
gap: 6px;
|
|
880
|
+
">${fold_toggle_html(path, foldable, folded, pending_complex.length, colors)}<span style="flex:1 1 auto; text-align:center;">${escapeHtml(label)}</span></div>
|
|
771
881
|
${content_html}
|
|
772
882
|
</div>`;
|
|
773
883
|
|
|
@@ -1097,6 +1207,35 @@ function ac_refresh(gobj, event, kw, src)
|
|
|
1097
1207
|
|
|
1098
1208
|
/************************************************************
|
|
1099
1209
|
* {theme: "dark"|"light"} — the app switched theme.
|
|
1210
|
+
* EV_TOGGLE_FOLD { path } — fold or unfold ONE card.
|
|
1211
|
+
*
|
|
1212
|
+
* The camera does not move: folding one branch is a local
|
|
1213
|
+
* edit of the picture, and refitting the whole graph for it
|
|
1214
|
+
* would throw away where the reader was looking. The toolbar
|
|
1215
|
+
* pair, which changes everything at once, does refit.
|
|
1216
|
+
************************************************************/
|
|
1217
|
+
function ac_toggle_fold(gobj, event, kw, src)
|
|
1218
|
+
{
|
|
1219
|
+
let priv = gobj.priv;
|
|
1220
|
+
let path = (kw && kw.path) || "";
|
|
1221
|
+
|
|
1222
|
+
if(empty_string(path)) {
|
|
1223
|
+
log_error(`${gobj_short_name(gobj)}: EV_TOGGLE_FOLD with no path`);
|
|
1224
|
+
return -1;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if(priv.collapsed.has(path)) {
|
|
1228
|
+
priv.collapsed.delete(path);
|
|
1229
|
+
} else {
|
|
1230
|
+
priv.collapsed.add(path);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
refresh_json(gobj, {preserve_view: true});
|
|
1234
|
+
update_find_result(gobj);
|
|
1235
|
+
return 0;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/************************************************************
|
|
1100
1239
|
* EV_FIND_NODES { text }
|
|
1101
1240
|
*
|
|
1102
1241
|
* The camera does NOT move: a find is a way of reading what is
|
|
@@ -1349,6 +1488,7 @@ function create_gclass(gclass_name)
|
|
|
1349
1488
|
["EV_LOAD_DATA", ac_load_data, null],
|
|
1350
1489
|
["EV_REFRESH", ac_refresh, null],
|
|
1351
1490
|
["EV_FIND_NODES", ac_find_nodes, null],
|
|
1491
|
+
["EV_TOGGLE_FOLD", ac_toggle_fold, null],
|
|
1352
1492
|
["EV_EXPAND_ALL", ac_expand_all, null],
|
|
1353
1493
|
["EV_COLLAPSE_ALL", ac_collapse_all, null],
|
|
1354
1494
|
["EV_THEME", ac_theme, null],
|
|
@@ -1370,6 +1510,7 @@ function create_gclass(gclass_name)
|
|
|
1370
1510
|
["EV_LOAD_DATA", 0],
|
|
1371
1511
|
["EV_REFRESH", 0],
|
|
1372
1512
|
["EV_FIND_NODES", 0],
|
|
1513
|
+
["EV_TOGGLE_FOLD", 0],
|
|
1373
1514
|
["EV_EXPAND_ALL", 0],
|
|
1374
1515
|
["EV_COLLAPSE_ALL", 0],
|
|
1375
1516
|
["EV_THEME", 0],
|