@yuneta/gobj-ui 7.23.52 → 7.23.54
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/dist/gobj-ui.cjs.js +316 -167
- package/dist/gobj-ui.es.js +316 -167
- package/package.json +2 -2
- package/src/c_yui_form.js +9 -1
- package/src/c_yui_treedb_topic_with_form.css +10 -0
- package/src/c_yui_treedb_topic_with_form.js +203 -17
- package/src/lib_icons.js +51 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuneta/gobj-ui",
|
|
3
|
-
"version": "7.23.
|
|
3
|
+
"version": "7.23.54",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/gobj-ui.cjs.js",
|
|
6
6
|
"module": "dist/gobj-ui.es.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@rollup/plugin-terser": "^1.0.0",
|
|
31
31
|
"@vitest/coverage-v8": "^4.1.10",
|
|
32
|
-
"@yuneta/gobj-js": "^7.16.
|
|
32
|
+
"@yuneta/gobj-js": "^7.16.3",
|
|
33
33
|
"vite": "^8.2.1",
|
|
34
34
|
"vitest": "^4.1.10"
|
|
35
35
|
},
|
package/src/c_yui_form.js
CHANGED
|
@@ -99,6 +99,7 @@ SDATA(data_type_t.DTP_STRING, "render_mode", 0, "exec", "'exec' interpre
|
|
|
99
99
|
SDATA(data_type_t.DTP_STRING, "pkey", 0, "id", "Primary key field name, used by form_mode"),
|
|
100
100
|
SDATA(data_type_t.DTP_STRING, "topic_name", 0, "", "Treedb topic name: labels use the i18n cascade '<topic>.<col>' -> '<col>' -> header (like table headers)"),
|
|
101
101
|
SDATA(data_type_t.DTP_BOOLEAN, "editable", 0, false, "Default is editable, set false to readonly"),
|
|
102
|
+
SDATA(data_type_t.DTP_BOOLEAN, "readonly", 0, false, "Show EVERY field read-only, whatever its schema says: a record being LOOKED at, not edited. Independent of `editable`, which drives the TABLE rendering; this one drives the FORM"),
|
|
102
103
|
SDATA(data_type_t.DTP_BOOLEAN, "selectable", 0, true, "Rows selectable with 'click' (BAD with editable=true)"),
|
|
103
104
|
SDATA(data_type_t.DTP_BOOLEAN, "with_checkbox", 0, false, "Show a first column with checkbox to select rows"),
|
|
104
105
|
SDATA(data_type_t.DTP_BOOLEAN, "with_rownum", 0, false, "Print first column with row number"),
|
|
@@ -586,7 +587,10 @@ function build_form_field_conf(gobj, field_desc)
|
|
|
586
587
|
|
|
587
588
|
options: [], // select,select2,radio options
|
|
588
589
|
extras: {}, // extra html input/textarea attributes
|
|
589
|
-
|
|
590
|
+
/* A form opened to LOOK at a record has no editable field, and
|
|
591
|
+
* that is a property of the opening, not of the schema: the same
|
|
592
|
+
* col is writable when the same form is opened to edit. */
|
|
593
|
+
readonly: gobj_read_bool_attr(gobj, "readonly") || !field_desc.is_writable,
|
|
590
594
|
required: field_desc.is_required,
|
|
591
595
|
hidden: field_desc.is_hidden,
|
|
592
596
|
default_value: field_desc.default_value,
|
|
@@ -661,6 +665,7 @@ function build_form_field_conf(gobj, field_desc)
|
|
|
661
665
|
break;
|
|
662
666
|
|
|
663
667
|
case "image":
|
|
668
|
+
case "icon":
|
|
664
669
|
case "string":
|
|
665
670
|
field_conf.tag = 'input';
|
|
666
671
|
field_conf.inputType = 'text';
|
|
@@ -1787,6 +1792,7 @@ function template2columns(gobj, columns, template, sub_elements)
|
|
|
1787
1792
|
break;
|
|
1788
1793
|
|
|
1789
1794
|
case "image":
|
|
1795
|
+
case "icon":
|
|
1790
1796
|
case "string":
|
|
1791
1797
|
// field_conf.tag = 'input';
|
|
1792
1798
|
// field_conf.inputType = 'text';
|
|
@@ -2516,6 +2522,7 @@ function treedb_value_2_form_value(gobj, field_desc, value)
|
|
|
2516
2522
|
case "date":
|
|
2517
2523
|
case "color":
|
|
2518
2524
|
case "image":
|
|
2525
|
+
case "icon":
|
|
2519
2526
|
case "tel":
|
|
2520
2527
|
case "template":
|
|
2521
2528
|
case "table":
|
|
@@ -2653,6 +2660,7 @@ function form_value_2_treedb_value(gobj, field_desc, value)
|
|
|
2653
2660
|
case "date":
|
|
2654
2661
|
case "color":
|
|
2655
2662
|
case "image":
|
|
2663
|
+
case "icon":
|
|
2656
2664
|
case "tel":
|
|
2657
2665
|
break;
|
|
2658
2666
|
case "template":
|
|
@@ -116,3 +116,13 @@
|
|
|
116
116
|
background: transparent !important;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
|
|
120
|
+
/* Cell of a col flagged `icon`: the glyph is a CSS mask sized in `em`,
|
|
121
|
+
* so it comes out at the row's font size -- smaller than the 18px the
|
|
122
|
+
* `image` cells next to it get. A touch larger, and centred on the row
|
|
123
|
+
* rather than on the text baseline. */
|
|
124
|
+
.ICON_CELL {
|
|
125
|
+
font-size: 1.25em;
|
|
126
|
+
line-height: 1;
|
|
127
|
+
vertical-align: middle;
|
|
128
|
+
}
|
|
@@ -86,11 +86,12 @@ import {
|
|
|
86
86
|
} from "./yui_table_select.js";
|
|
87
87
|
|
|
88
88
|
import {yui_table_filter_clear} from "./yui_table_filter_clear.js";
|
|
89
|
+
import {yui_icon_is_defined} from "./lib_icons.js";
|
|
89
90
|
import {row_matches} from "./yui_row_search.js";
|
|
90
91
|
|
|
91
92
|
import {t} from "i18next";
|
|
92
93
|
|
|
93
|
-
import {plan_treedb_writes} from "./treedb_write_plan.js";
|
|
94
|
+
import {plan_treedb_writes, READONLY_FORM_TOOLBAR} from "./treedb_write_plan.js";
|
|
94
95
|
import {delete_impact} from "./delete_impact.js";
|
|
95
96
|
|
|
96
97
|
import "./c_yui_treedb_topic_with_form.css";
|
|
@@ -1284,6 +1285,14 @@ function create_tabulator(gobj)
|
|
|
1284
1285
|
hozAlign = "center";
|
|
1285
1286
|
colFormatter = "color";
|
|
1286
1287
|
break;
|
|
1288
|
+
case "icon":
|
|
1289
|
+
/* The NAME of an icon of the app's set, not a file --
|
|
1290
|
+
* which is why `image` cannot stand in for it: it built
|
|
1291
|
+
* an `<img src="yi-bolt">` and drew a broken image. */
|
|
1292
|
+
hozAlign = "center";
|
|
1293
|
+
vertAlign = "middle";
|
|
1294
|
+
colFormatter = icon_formatter;
|
|
1295
|
+
break;
|
|
1287
1296
|
case "object":
|
|
1288
1297
|
case "dict":
|
|
1289
1298
|
case "template":
|
|
@@ -1671,6 +1680,31 @@ function create_tabulator(gobj)
|
|
|
1671
1680
|
tabulator.on("dataFiltered", function(filters, rows) {
|
|
1672
1681
|
update_rowcount(Array.isArray(rows)? rows.length : undefined);
|
|
1673
1682
|
});
|
|
1683
|
+
/* A click on a row, while the table is NOT in edition mode, OPENS the
|
|
1684
|
+
* record to be read. It used to do nothing at all: selection is driven
|
|
1685
|
+
* by the checkbox column and editing by the pencil, so the biggest
|
|
1686
|
+
* target on screen was the one thing that answered nothing.
|
|
1687
|
+
*
|
|
1688
|
+
* Only outside edition mode: in it a click starts editing a cell, and
|
|
1689
|
+
* the pencil already opens the same record to be written.
|
|
1690
|
+
*
|
|
1691
|
+
* Cells that carry an action of their own are left alone -- a json
|
|
1692
|
+
* preview opens its viewer, a hook opens its children, the Op buttons
|
|
1693
|
+
* edit and delete -- and the question is asked of the SCHEMA rather
|
|
1694
|
+
* than of the markup, so a new formatter does not silently start
|
|
1695
|
+
* opening two things at once. */
|
|
1696
|
+
tabulator.on("rowClick", function(e, row) {
|
|
1697
|
+
if(gobj_read_bool_attr(gobj, "editable")) {
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
let $cell = e.target? e.target.closest(".tabulator-cell") : null;
|
|
1701
|
+
let field = $cell? $cell.getAttribute("tabulator-field") : null;
|
|
1702
|
+
if(field && cell_has_its_own_action(gobj, field)) {
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
gobj_send_event(gobj, "EV_SHOW_RECORD", {index: row.getPosition()}, gobj);
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1674
1708
|
tabulator.on("rowSelected", function(row) {
|
|
1675
1709
|
gobj_send_event(gobj, "EV_SELECT_ROWS", {rows: [row.getData()]}, gobj);
|
|
1676
1710
|
});
|
|
@@ -1694,6 +1728,48 @@ function table__destroy(gobj)
|
|
|
1694
1728
|
}
|
|
1695
1729
|
}
|
|
1696
1730
|
|
|
1731
|
+
/************************************************************
|
|
1732
|
+
* Cell of a col flagged `icon`: the value is the NAME of an
|
|
1733
|
+
* icon of the app's set ("yi-bolt"), so it is DRAWN.
|
|
1734
|
+
*
|
|
1735
|
+
* Built as a DOM node and not as an HTML string: the value is
|
|
1736
|
+
* raw record data and must never be parsed as markup -- and
|
|
1737
|
+
* here it also becomes a CSS class, so it is filtered to the
|
|
1738
|
+
* shape of a class name before it gets near one.
|
|
1739
|
+
*
|
|
1740
|
+
* A name the set does not carry falls back to the TEXT of it.
|
|
1741
|
+
* The catch-all rule `[class^="yi-"]::before` paints a
|
|
1742
|
+
* currentColor box for any name, so drawing it anyway would
|
|
1743
|
+
* render a solid square -- a glyph nobody recognises rather
|
|
1744
|
+
* than a mistake somebody can fix.
|
|
1745
|
+
************************************************************/
|
|
1746
|
+
function icon_formatter(cell)
|
|
1747
|
+
{
|
|
1748
|
+
let name = cell.getValue();
|
|
1749
|
+
if(!is_string(name) || empty_string(name)) {
|
|
1750
|
+
return "";
|
|
1751
|
+
}
|
|
1752
|
+
name = name.trim();
|
|
1753
|
+
|
|
1754
|
+
if(!/^[a-z0-9-]+$/.test(name) || !yui_icon_is_defined(name)) {
|
|
1755
|
+
/* The NAME, in plain grey, and no invented label around it: it
|
|
1756
|
+
* is a developer's problem (a schema naming a glyph the set
|
|
1757
|
+
* does not carry) and the name IS the whole diagnosis -- while
|
|
1758
|
+
* a sentence here would be one more i18n key for every app
|
|
1759
|
+
* that mounts this table. */
|
|
1760
|
+
return createElement2([
|
|
1761
|
+
"span",
|
|
1762
|
+
{class: "ICON_CELL_UNKNOWN is-size-7 has-text-grey", title: name},
|
|
1763
|
+
name
|
|
1764
|
+
]);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
return createElement2([
|
|
1768
|
+
"span",
|
|
1769
|
+
{class: `ICON_CELL ${name}`, title: name, "aria-label": name}
|
|
1770
|
+
]);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1697
1773
|
/************************************************************
|
|
1698
1774
|
* Cell of a col holding a JSON document (dict/list/blob/
|
|
1699
1775
|
* template/coordinates/…): a one-line truncated preview,
|
|
@@ -1852,6 +1928,10 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field)
|
|
|
1852
1928
|
// Handled by Tabulator's built-in "color" formatter (see colDef above)
|
|
1853
1929
|
break;
|
|
1854
1930
|
|
|
1931
|
+
case "icon":
|
|
1932
|
+
// Handled by icon_formatter() (see colDef above)
|
|
1933
|
+
break;
|
|
1934
|
+
|
|
1855
1935
|
case "image":
|
|
1856
1936
|
// Handled by Tabulator's built-in "image" formatter (see colDef above)
|
|
1857
1937
|
break;
|
|
@@ -1875,10 +1955,25 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field)
|
|
|
1875
1955
|
}
|
|
1876
1956
|
|
|
1877
1957
|
/************************************************************
|
|
1878
|
-
* Build the form template from desc.cols:
|
|
1879
|
-
*
|
|
1880
|
-
*
|
|
1881
|
-
*
|
|
1958
|
+
* Build the form template from desc.cols: EVERY field of the
|
|
1959
|
+
* record, the ones that cannot be written included.
|
|
1960
|
+
*
|
|
1961
|
+
* They used to be dropped, and a record was then shown as the
|
|
1962
|
+
* handful of its fields somebody may type: an asset opened as
|
|
1963
|
+
* its id alone, with its type, its size, its date and where it
|
|
1964
|
+
* came from -- everything a person opens an asset to read --
|
|
1965
|
+
* nowhere on screen. What a form is for is READING a record;
|
|
1966
|
+
* writing it is one of the two things you can do next.
|
|
1967
|
+
*
|
|
1968
|
+
* C_YUI_FORM renders a col that is not `writable` read-only on
|
|
1969
|
+
* its own (`build_form_field_conf`), so this is the whole of
|
|
1970
|
+
* it. What must NOT follow is sending them back:
|
|
1971
|
+
* `transform__form_record_2_treedb_record()` drops them, and
|
|
1972
|
+
* the reason is written there.
|
|
1973
|
+
*
|
|
1974
|
+
* Still out: hooks (a hook is not a field of the record, it is
|
|
1975
|
+
* the children hanging off it), hidden cols, and the `_`
|
|
1976
|
+
* scaffolding the table adds.
|
|
1882
1977
|
************************************************************/
|
|
1883
1978
|
function build_form_template(gobj)
|
|
1884
1979
|
{
|
|
@@ -1895,10 +1990,6 @@ function build_form_template(gobj)
|
|
|
1895
1990
|
if(field_desc.type === "hook") {
|
|
1896
1991
|
continue; // hooks don't appear in forms
|
|
1897
1992
|
}
|
|
1898
|
-
if(!field_desc.is_writable &&
|
|
1899
|
-
field_desc.type !== "fkey" && col.id !== desc.pkey) {
|
|
1900
|
-
continue;
|
|
1901
|
-
}
|
|
1902
1993
|
template.push(col);
|
|
1903
1994
|
}
|
|
1904
1995
|
return template;
|
|
@@ -1968,6 +2059,14 @@ function open_form_dialog(gobj, mode, record)
|
|
|
1968
2059
|
title = t(topic_name) + (empty_string(rid) ? "" : " — " + rid);
|
|
1969
2060
|
}
|
|
1970
2061
|
|
|
2062
|
+
/* "view" is an "update" that writes nothing: same title, same pkey
|
|
2063
|
+
* handling (readonly), every field read-only and the write half of
|
|
2064
|
+
* the toolbar gone. It is what a row click opens when the table is
|
|
2065
|
+
* NOT in edition mode -- reading a record is the ordinary thing to
|
|
2066
|
+
* want, and until now a click on a row did nothing at all. */
|
|
2067
|
+
let view_only = (mode === "view");
|
|
2068
|
+
let form_mode = view_only? "update" : mode;
|
|
2069
|
+
|
|
1971
2070
|
/* A flex column so the hosted form takes the dialog height: fields
|
|
1972
2071
|
* scroll internally and the form's bottom toolbar stays visible. */
|
|
1973
2072
|
let $body = createElement2(
|
|
@@ -1983,19 +2082,22 @@ function open_form_dialog(gobj, mode, record)
|
|
|
1983
2082
|
template: build_form_template(gobj),
|
|
1984
2083
|
record: record,
|
|
1985
2084
|
fkey_options: build_fkey_options(gobj),
|
|
1986
|
-
form_mode:
|
|
2085
|
+
form_mode: form_mode,
|
|
2086
|
+
readonly: view_only,
|
|
1987
2087
|
/* A read-only topic still OPENS its form -- looking at a record
|
|
1988
2088
|
* is the point of a replica -- with the cells not editable and
|
|
1989
2089
|
* the write half of the toolbar gone. A null plan leaves the
|
|
1990
2090
|
* form's own default alone. */
|
|
1991
|
-
...(
|
|
2091
|
+
...((view_only? READONLY_FORM_TOOLBAR : form_plan.form_toolbar)?
|
|
2092
|
+
{toolbar: view_only? READONLY_FORM_TOOLBAR.slice()
|
|
2093
|
+
: form_plan.form_toolbar} : {}),
|
|
1992
2094
|
/* Editing the raw topic record: structured cols
|
|
1993
2095
|
* (template / table / coordinates) are raw JSON editors,
|
|
1994
2096
|
* not interpreted into sub-widgets (the pre-merge behaviour). */
|
|
1995
2097
|
render_mode: "edit",
|
|
1996
2098
|
pkey: desc.pkey || "id",
|
|
1997
2099
|
topic_name: topic_name,
|
|
1998
|
-
editable: !form_plan.readonly,
|
|
2100
|
+
editable: !form_plan.readonly && !view_only,
|
|
1999
2101
|
$parent: $body
|
|
2000
2102
|
},
|
|
2001
2103
|
gobj
|
|
@@ -2391,6 +2493,42 @@ function show_edit_form(gobj, row, index)
|
|
|
2391
2493
|
open_form_dialog(gobj, mode, row);
|
|
2392
2494
|
}
|
|
2393
2495
|
|
|
2496
|
+
/************************************************************
|
|
2497
|
+
* Does a click on this column already mean something else?
|
|
2498
|
+
*
|
|
2499
|
+
* The `_` columns are the table's own scaffolding (the selection
|
|
2500
|
+
* checkbox, the Op buttons); a hook opens its children and a col
|
|
2501
|
+
* holding a json document opens it in a viewer. Anything else is
|
|
2502
|
+
* plain data, and a click on it means "show me this record".
|
|
2503
|
+
************************************************************/
|
|
2504
|
+
const CELL_WITH_ACTION = [
|
|
2505
|
+
"hook", "object", "dict", "template", "array", "list",
|
|
2506
|
+
"coordinates", "blob", "gbuffer"
|
|
2507
|
+
];
|
|
2508
|
+
|
|
2509
|
+
function cell_has_its_own_action(gobj, field)
|
|
2510
|
+
{
|
|
2511
|
+
if(!field || field.charAt(0) === '_') {
|
|
2512
|
+
return true;
|
|
2513
|
+
}
|
|
2514
|
+
let col = get_schema_col(gobj, field);
|
|
2515
|
+
if(!col) {
|
|
2516
|
+
return false;
|
|
2517
|
+
}
|
|
2518
|
+
const field_desc = treedb_get_field_desc(col);
|
|
2519
|
+
return CELL_WITH_ACTION.includes(field_desc.type) ||
|
|
2520
|
+
CELL_WITH_ACTION.includes(field_desc.real_type);
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
/************************************************************
|
|
2524
|
+
* Show a record to be READ: every field, none of them editable.
|
|
2525
|
+
* What a row click opens while the table is not in edition mode.
|
|
2526
|
+
************************************************************/
|
|
2527
|
+
function show_view_form(gobj, row)
|
|
2528
|
+
{
|
|
2529
|
+
open_form_dialog(gobj, "view", row);
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2394
2532
|
/************************************************************
|
|
2395
2533
|
* Show the edit "create" form to a new record
|
|
2396
2534
|
* internally called from the top toolbar +New button
|
|
@@ -2421,6 +2559,8 @@ function get_schema_col(gobj, id)
|
|
|
2421
2559
|
************************************************************/
|
|
2422
2560
|
function transform__form_record_2_treedb_record(gobj, kw, operation)
|
|
2423
2561
|
{
|
|
2562
|
+
let desc = gobj_read_attr(gobj, "desc");
|
|
2563
|
+
let pkey = desc.pkey || "id";
|
|
2424
2564
|
let row = {};
|
|
2425
2565
|
|
|
2426
2566
|
for(let field_name of Object.keys(kw)) {
|
|
@@ -2428,12 +2568,33 @@ function transform__form_record_2_treedb_record(gobj, kw, operation)
|
|
|
2428
2568
|
continue;
|
|
2429
2569
|
}
|
|
2430
2570
|
let col = get_schema_col(gobj, field_name);
|
|
2431
|
-
if(col) {
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2571
|
+
if(!col) {
|
|
2572
|
+
continue;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
/* The form SHOWS every field and sends back only what the topic
|
|
2576
|
+
* accepts: the writable cols, the fkeys (a link is edited by
|
|
2577
|
+
* linking) and the pkey, which is not written but is what
|
|
2578
|
+
* ADDRESSES the record.
|
|
2579
|
+
*
|
|
2580
|
+
* It matters because `treedb_update_node()` does not check
|
|
2581
|
+
* `writable` -- it writes any col it is handed -- so a read-only
|
|
2582
|
+
* field travelling back is written with whatever the form made of
|
|
2583
|
+
* it, and the fields that describe a record are exactly the ones
|
|
2584
|
+
* that do not survive a round trip through a widget: `t` is an
|
|
2585
|
+
* integer rendered as a date. Nothing would look wrong until the
|
|
2586
|
+
* stored timestamp had moved.
|
|
2587
|
+
*/
|
|
2588
|
+
const field_desc = treedb_get_field_desc(col);
|
|
2589
|
+
if(!field_desc.is_writable &&
|
|
2590
|
+
field_desc.type !== "fkey" && col.id !== pkey) {
|
|
2591
|
+
continue;
|
|
2436
2592
|
}
|
|
2593
|
+
|
|
2594
|
+
let value = kw[field_name];
|
|
2595
|
+
row[field_name] = transform__form_value_2_treedb_value(
|
|
2596
|
+
gobj, col, value, operation
|
|
2597
|
+
);
|
|
2437
2598
|
}
|
|
2438
2599
|
return row;
|
|
2439
2600
|
}
|
|
@@ -3718,6 +3879,29 @@ function ac_open_columns(gobj, event, kw, src)
|
|
|
3718
3879
|
return 0;
|
|
3719
3880
|
}
|
|
3720
3881
|
|
|
3882
|
+
/************************************************************
|
|
3883
|
+
* A row was clicked outside edition mode: open the record to be read.
|
|
3884
|
+
************************************************************/
|
|
3885
|
+
function ac_show_record(gobj, event, kw, src)
|
|
3886
|
+
{
|
|
3887
|
+
let tabulator = gobj_read_attr(gobj, "tabulator");
|
|
3888
|
+
let index = kw? kw.index : undefined;
|
|
3889
|
+
if(!tabulator || typeof index !== "number") {
|
|
3890
|
+
log_error(`${gobj_short_name(gobj)}: no row ${index} to show`);
|
|
3891
|
+
return -1;
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3894
|
+
let row = tabulator.getRowFromPosition(index);
|
|
3895
|
+
if(!row) {
|
|
3896
|
+
log_error(`${gobj_short_name(gobj)}: row ${index} is gone`);
|
|
3897
|
+
return -1;
|
|
3898
|
+
}
|
|
3899
|
+
|
|
3900
|
+
show_view_form(gobj, row.getData());
|
|
3901
|
+
|
|
3902
|
+
return 0;
|
|
3903
|
+
}
|
|
3904
|
+
|
|
3721
3905
|
/************************************************************
|
|
3722
3906
|
* The reader picked another page size: remember it, so coming back to
|
|
3723
3907
|
* this topic opens where they left it instead of at 200.
|
|
@@ -3878,6 +4062,7 @@ function create_gclass(gclass_name)
|
|
|
3878
4062
|
["EV_SEARCH", ac_search, null],
|
|
3879
4063
|
["EV_OPEN_COLUMNS", ac_open_columns, null],
|
|
3880
4064
|
["EV_PAGE_SIZE_CHANGED", ac_page_size_changed, null],
|
|
4065
|
+
["EV_SHOW_RECORD", ac_show_record, null],
|
|
3881
4066
|
["EV_TOGGLE_COLUMN", ac_toggle_column, null],
|
|
3882
4067
|
["EV_EXPORT_TABLE", ac_export_table, null],
|
|
3883
4068
|
["EV_SHOW", ac_show, null],
|
|
@@ -3919,6 +4104,7 @@ function create_gclass(gclass_name)
|
|
|
3919
4104
|
["EV_SEARCH", 0],
|
|
3920
4105
|
["EV_OPEN_COLUMNS", 0],
|
|
3921
4106
|
["EV_PAGE_SIZE_CHANGED", 0],
|
|
4107
|
+
["EV_SHOW_RECORD", 0],
|
|
3922
4108
|
["EV_TOGGLE_COLUMN", 0],
|
|
3923
4109
|
["EV_EXPORT_TABLE", 0],
|
|
3924
4110
|
|
package/src/lib_icons.js
CHANGED
|
@@ -168,3 +168,54 @@ export function inject_svg_icons()
|
|
|
168
168
|
div.innerHTML = ICONS;
|
|
169
169
|
document.body.appendChild(div);
|
|
170
170
|
}
|
|
171
|
+
|
|
172
|
+
/*
|
|
173
|
+
* Is `yi-<name>` a real glyph of the loaded set?
|
|
174
|
+
*
|
|
175
|
+
* It has to be ASKED, not looked up in a list: the set is a CSS file
|
|
176
|
+
* (`yui_icons.css`) that an app may extend or replace, and a list here
|
|
177
|
+
* would be a second copy of it, wrong the first time somebody adds a
|
|
178
|
+
* mask rule. And the question matters because the catch-all rule
|
|
179
|
+
* `[class^="yi-"]::before` paints a `currentColor` box for ANY name --
|
|
180
|
+
* so an undefined icon does not fail, it renders as a solid black
|
|
181
|
+
* square, which reads as a glyph nobody recognises rather than as a
|
|
182
|
+
* mistake.
|
|
183
|
+
*
|
|
184
|
+
* The probe is the real stylesheet: a detached element gets no styles,
|
|
185
|
+
* so it is attached, hidden, measured and removed. Cached by name --
|
|
186
|
+
* a table draws one icon per row and the answer cannot change without
|
|
187
|
+
* a reload.
|
|
188
|
+
*/
|
|
189
|
+
const __icon_defined__ = {};
|
|
190
|
+
|
|
191
|
+
export function yui_icon_is_defined(name)
|
|
192
|
+
{
|
|
193
|
+
if(!name || typeof name !== "string") {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
if(__icon_defined__[name] !== undefined) {
|
|
197
|
+
return __icon_defined__[name];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let defined = false;
|
|
201
|
+
try {
|
|
202
|
+
const el = document.createElement("i");
|
|
203
|
+
el.className = name;
|
|
204
|
+
el.style.position = "absolute";
|
|
205
|
+
el.style.visibility = "hidden";
|
|
206
|
+
el.style.pointerEvents = "none";
|
|
207
|
+
document.body.appendChild(el);
|
|
208
|
+
const st = window.getComputedStyle(el, "::before");
|
|
209
|
+
const mask = st.maskImage || st.webkitMaskImage || "";
|
|
210
|
+
defined = !!mask && mask !== "none";
|
|
211
|
+
document.body.removeChild(el);
|
|
212
|
+
} catch(e) {
|
|
213
|
+
/* No DOM to ask (jsdom without the stylesheet, a test): answer
|
|
214
|
+
* "not defined" so the caller falls back to showing the name,
|
|
215
|
+
* which is the honest half of the pair. */
|
|
216
|
+
defined = false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
__icon_defined__[name] = defined;
|
|
220
|
+
return defined;
|
|
221
|
+
}
|