@yuneta/gobj-ui 7.23.40 → 7.23.42
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 +77 -7
- package/dist/gobj-ui.es.js +77 -7
- package/package.json +1 -1
- package/src/bulma_is_light.test.js +160 -0
- package/src/bulma_tint.js +71 -0
- package/src/c_yui_schema_editor.js +27 -3
- package/src/c_yui_treedb_topic_with_form.js +25 -3
- package/src/shell_modals.js +2 -1
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -44,6 +44,75 @@ maplibre_gl = __toESM(maplibre_gl, 1);
|
|
|
44
44
|
let uplot = require("uplot");
|
|
45
45
|
uplot = __toESM(uplot, 1);
|
|
46
46
|
let _antv_g6 = require("@antv/g6");
|
|
47
|
+
//#region src/bulma_tint.js
|
|
48
|
+
/***********************************************************************
|
|
49
|
+
* bulma_tint.js
|
|
50
|
+
*
|
|
51
|
+
* Bulma's "soften this colour" modifier, applied so that it cannot
|
|
52
|
+
* be applied alone.
|
|
53
|
+
*
|
|
54
|
+
* WHY THIS IS A FUNCTION AND NOT A STRING. 'light' is one of
|
|
55
|
+
* Bulma's colours, so `is-light` is BOTH a colour and the modifier
|
|
56
|
+
* that softens another one. A compound selector may match the same
|
|
57
|
+
* class twice, so `.tag.is-light.is-light` matches an element that
|
|
58
|
+
* carries the class ONCE -- and that rule sets the text to
|
|
59
|
+
* var(--bulma-light-light-invert-l), WHICH BULMA NEVER DEFINES:
|
|
60
|
+
* referenced four times in bulma.css, declared zero times. The
|
|
61
|
+
* colour declaration is then invalid while the background still
|
|
62
|
+
* resolves, so what is left on screen is a pill with no readable
|
|
63
|
+
* label.
|
|
64
|
+
*
|
|
65
|
+
* Two selectors of Bulma 1 reach that undefined variable:
|
|
66
|
+
*
|
|
67
|
+
* .tag.is-light.is-light
|
|
68
|
+
* .notification.is-light.is-light
|
|
69
|
+
*
|
|
70
|
+
* It cost three unreadable chips in the schema editor -- the topic
|
|
71
|
+
* count, the schema version, and every column flag with no colour of
|
|
72
|
+
* its own ('writable', 'image', 'wild'...). They read as empty
|
|
73
|
+
* pills, which looks like a bug in the DATA and is a missing CSS
|
|
74
|
+
* variable.
|
|
75
|
+
*
|
|
76
|
+
* The neutral chip is a bare element with no modifier at all: it
|
|
77
|
+
* takes --bulma-background-l and --bulma-text-l, follows the theme
|
|
78
|
+
* and stays legible in both.
|
|
79
|
+
*
|
|
80
|
+
* Copyright (c) 2026, ArtGins.
|
|
81
|
+
* All Rights Reserved.
|
|
82
|
+
***********************************************************************/
|
|
83
|
+
var TINTABLE = [
|
|
84
|
+
"primary",
|
|
85
|
+
"link",
|
|
86
|
+
"info",
|
|
87
|
+
"success",
|
|
88
|
+
"warning",
|
|
89
|
+
"danger",
|
|
90
|
+
"black",
|
|
91
|
+
"dark",
|
|
92
|
+
"white",
|
|
93
|
+
"text",
|
|
94
|
+
"ghost"
|
|
95
|
+
];
|
|
96
|
+
/***************************************************************
|
|
97
|
+
* A softened colour, or nothing.
|
|
98
|
+
*
|
|
99
|
+
* yui_tint("warning") -> "is-warning is-light"
|
|
100
|
+
* yui_tint("is-warning") -> "is-warning is-light"
|
|
101
|
+
* yui_tint("light") -> "" (there is nothing to soften)
|
|
102
|
+
* yui_tint("") -> ""
|
|
103
|
+
* yui_tint(undefined) -> ""
|
|
104
|
+
*
|
|
105
|
+
* Anything it does not recognise comes back empty rather than being
|
|
106
|
+
* passed through: a class this function cannot vouch for is exactly the
|
|
107
|
+
* case it exists to stop.
|
|
108
|
+
***************************************************************/
|
|
109
|
+
function yui_tint(colour) {
|
|
110
|
+
if (typeof colour !== "string") return "";
|
|
111
|
+
const name = colour.trim().replace(/^is-/, "");
|
|
112
|
+
if (TINTABLE.indexOf(name) < 0) return "";
|
|
113
|
+
return `is-${name} is-light`;
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
47
116
|
//#region src/shell_focus_trap.js
|
|
48
117
|
/***********************************************************************
|
|
49
118
|
* shell_focus_trap.js
|
|
@@ -2858,7 +2927,7 @@ function show_notification(shell, kind, message, opts) {
|
|
|
2858
2927
|
let $note = (0, _yuneta_gobj_js.createElement2)([
|
|
2859
2928
|
"div",
|
|
2860
2929
|
{
|
|
2861
|
-
class: `TOAST notification yui-notification
|
|
2930
|
+
class: `TOAST notification yui-notification ${yui_tint(kind)}`,
|
|
2862
2931
|
role: kind === "danger" ? "alert" : "status"
|
|
2863
2932
|
},
|
|
2864
2933
|
[["button", {
|
|
@@ -23149,14 +23218,14 @@ function build_ui$5(gobj) {
|
|
|
23149
23218
|
"div",
|
|
23150
23219
|
{
|
|
23151
23220
|
class: "C_YUI_TREEDB_TOPIC_WITH_FORM",
|
|
23152
|
-
style: "height:100%;"
|
|
23221
|
+
style: "height:100%; min-height:0; display:flex; flex-direction:column;"
|
|
23153
23222
|
},
|
|
23154
23223
|
[["div", {
|
|
23155
23224
|
class: "TREEDB_TABLE_TOOLBAR toolbar_tabulator_table m-1",
|
|
23156
|
-
style: "display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap;"
|
|
23225
|
+
style: "flex:none; display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap;"
|
|
23157
23226
|
}], ["div", {
|
|
23158
23227
|
id: `${table_id}`,
|
|
23159
|
-
style: "margin-top:0px !important;"
|
|
23228
|
+
style: "margin-top:0px !important; flex:1 1 auto; min-height:0;"
|
|
23160
23229
|
}]]
|
|
23161
23230
|
]);
|
|
23162
23231
|
if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_checkbox") && (0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "with_selection_bar")) {
|
|
@@ -23166,6 +23235,7 @@ function build_ui$5(gobj) {
|
|
|
23166
23235
|
on_clear: () => (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_CLEAR_SELECTION", {}, gobj)
|
|
23167
23236
|
});
|
|
23168
23237
|
(0, _yuneta_gobj_js.gobj_write_attr)(gobj, "selection_bar", bar);
|
|
23238
|
+
bar.$el.style.flex = "none";
|
|
23169
23239
|
$container.insertBefore(bar.$el, $container.querySelector(`#${table_id}`));
|
|
23170
23240
|
}
|
|
23171
23241
|
let $toolbar_slot = $container.querySelector(".toolbar_tabulator_table");
|
|
@@ -62413,7 +62483,7 @@ function open_card(gobj, logical, title, icon, tags, event, kw) {
|
|
|
62413
62483
|
let $tags = tags.map((tag) => {
|
|
62414
62484
|
return [
|
|
62415
62485
|
"span",
|
|
62416
|
-
{ class: `${logical}_TAG tag ${tag.style || "
|
|
62486
|
+
{ class: `${logical}_TAG tag ${tag.style || ""}`.trimEnd() },
|
|
62417
62487
|
tag.i18n ? [[
|
|
62418
62488
|
"span",
|
|
62419
62489
|
{ i18n: tag.i18n },
|
|
@@ -62560,7 +62630,7 @@ function render_topics(gobj, $body) {
|
|
|
62560
62630
|
topic.system_topic ? [
|
|
62561
62631
|
"span",
|
|
62562
62632
|
{
|
|
62563
|
-
class: "SCHEMA_TOPIC_SYSTEM tag
|
|
62633
|
+
class: "SCHEMA_TOPIC_SYSTEM tag ml-2",
|
|
62564
62634
|
i18n: "system"
|
|
62565
62635
|
},
|
|
62566
62636
|
(0, i18next.t)("system")
|
|
@@ -62909,7 +62979,7 @@ function column_row(gobj, topic, col, index, readonly, twice) {
|
|
|
62909
62979
|
let $flags = flags.map((flag) => {
|
|
62910
62980
|
return [
|
|
62911
62981
|
"span",
|
|
62912
|
-
{ class: `SCHEMA_COL_FLAG tag
|
|
62982
|
+
{ class: `SCHEMA_COL_FLAG tag ${yui_tint(flag_style(flag))}`.trimEnd() },
|
|
62913
62983
|
`${flag}`
|
|
62914
62984
|
];
|
|
62915
62985
|
});
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -39,6 +39,75 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
39
39
|
enumerable: true
|
|
40
40
|
}) : target, mod));
|
|
41
41
|
//#endregion
|
|
42
|
+
//#region src/bulma_tint.js
|
|
43
|
+
/***********************************************************************
|
|
44
|
+
* bulma_tint.js
|
|
45
|
+
*
|
|
46
|
+
* Bulma's "soften this colour" modifier, applied so that it cannot
|
|
47
|
+
* be applied alone.
|
|
48
|
+
*
|
|
49
|
+
* WHY THIS IS A FUNCTION AND NOT A STRING. 'light' is one of
|
|
50
|
+
* Bulma's colours, so `is-light` is BOTH a colour and the modifier
|
|
51
|
+
* that softens another one. A compound selector may match the same
|
|
52
|
+
* class twice, so `.tag.is-light.is-light` matches an element that
|
|
53
|
+
* carries the class ONCE -- and that rule sets the text to
|
|
54
|
+
* var(--bulma-light-light-invert-l), WHICH BULMA NEVER DEFINES:
|
|
55
|
+
* referenced four times in bulma.css, declared zero times. The
|
|
56
|
+
* colour declaration is then invalid while the background still
|
|
57
|
+
* resolves, so what is left on screen is a pill with no readable
|
|
58
|
+
* label.
|
|
59
|
+
*
|
|
60
|
+
* Two selectors of Bulma 1 reach that undefined variable:
|
|
61
|
+
*
|
|
62
|
+
* .tag.is-light.is-light
|
|
63
|
+
* .notification.is-light.is-light
|
|
64
|
+
*
|
|
65
|
+
* It cost three unreadable chips in the schema editor -- the topic
|
|
66
|
+
* count, the schema version, and every column flag with no colour of
|
|
67
|
+
* its own ('writable', 'image', 'wild'...). They read as empty
|
|
68
|
+
* pills, which looks like a bug in the DATA and is a missing CSS
|
|
69
|
+
* variable.
|
|
70
|
+
*
|
|
71
|
+
* The neutral chip is a bare element with no modifier at all: it
|
|
72
|
+
* takes --bulma-background-l and --bulma-text-l, follows the theme
|
|
73
|
+
* and stays legible in both.
|
|
74
|
+
*
|
|
75
|
+
* Copyright (c) 2026, ArtGins.
|
|
76
|
+
* All Rights Reserved.
|
|
77
|
+
***********************************************************************/
|
|
78
|
+
var TINTABLE = [
|
|
79
|
+
"primary",
|
|
80
|
+
"link",
|
|
81
|
+
"info",
|
|
82
|
+
"success",
|
|
83
|
+
"warning",
|
|
84
|
+
"danger",
|
|
85
|
+
"black",
|
|
86
|
+
"dark",
|
|
87
|
+
"white",
|
|
88
|
+
"text",
|
|
89
|
+
"ghost"
|
|
90
|
+
];
|
|
91
|
+
/***************************************************************
|
|
92
|
+
* A softened colour, or nothing.
|
|
93
|
+
*
|
|
94
|
+
* yui_tint("warning") -> "is-warning is-light"
|
|
95
|
+
* yui_tint("is-warning") -> "is-warning is-light"
|
|
96
|
+
* yui_tint("light") -> "" (there is nothing to soften)
|
|
97
|
+
* yui_tint("") -> ""
|
|
98
|
+
* yui_tint(undefined) -> ""
|
|
99
|
+
*
|
|
100
|
+
* Anything it does not recognise comes back empty rather than being
|
|
101
|
+
* passed through: a class this function cannot vouch for is exactly the
|
|
102
|
+
* case it exists to stop.
|
|
103
|
+
***************************************************************/
|
|
104
|
+
function yui_tint(colour) {
|
|
105
|
+
if (typeof colour !== "string") return "";
|
|
106
|
+
const name = colour.trim().replace(/^is-/, "");
|
|
107
|
+
if (TINTABLE.indexOf(name) < 0) return "";
|
|
108
|
+
return `is-${name} is-light`;
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
42
111
|
//#region src/shell_focus_trap.js
|
|
43
112
|
/***********************************************************************
|
|
44
113
|
* shell_focus_trap.js
|
|
@@ -2853,7 +2922,7 @@ function show_notification(shell, kind, message, opts) {
|
|
|
2853
2922
|
let $note = createElement2([
|
|
2854
2923
|
"div",
|
|
2855
2924
|
{
|
|
2856
|
-
class: `TOAST notification yui-notification
|
|
2925
|
+
class: `TOAST notification yui-notification ${yui_tint(kind)}`,
|
|
2857
2926
|
role: kind === "danger" ? "alert" : "status"
|
|
2858
2927
|
},
|
|
2859
2928
|
[["button", {
|
|
@@ -23144,14 +23213,14 @@ function build_ui$5(gobj) {
|
|
|
23144
23213
|
"div",
|
|
23145
23214
|
{
|
|
23146
23215
|
class: "C_YUI_TREEDB_TOPIC_WITH_FORM",
|
|
23147
|
-
style: "height:100%;"
|
|
23216
|
+
style: "height:100%; min-height:0; display:flex; flex-direction:column;"
|
|
23148
23217
|
},
|
|
23149
23218
|
[["div", {
|
|
23150
23219
|
class: "TREEDB_TABLE_TOOLBAR toolbar_tabulator_table m-1",
|
|
23151
|
-
style: "display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap;"
|
|
23220
|
+
style: "flex:none; display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap;"
|
|
23152
23221
|
}], ["div", {
|
|
23153
23222
|
id: `${table_id}`,
|
|
23154
|
-
style: "margin-top:0px !important;"
|
|
23223
|
+
style: "margin-top:0px !important; flex:1 1 auto; min-height:0;"
|
|
23155
23224
|
}]]
|
|
23156
23225
|
]);
|
|
23157
23226
|
if (gobj_read_bool_attr(gobj, "with_checkbox") && gobj_read_bool_attr(gobj, "with_selection_bar")) {
|
|
@@ -23161,6 +23230,7 @@ function build_ui$5(gobj) {
|
|
|
23161
23230
|
on_clear: () => gobj_send_event(gobj, "EV_CLEAR_SELECTION", {}, gobj)
|
|
23162
23231
|
});
|
|
23163
23232
|
gobj_write_attr(gobj, "selection_bar", bar);
|
|
23233
|
+
bar.$el.style.flex = "none";
|
|
23164
23234
|
$container.insertBefore(bar.$el, $container.querySelector(`#${table_id}`));
|
|
23165
23235
|
}
|
|
23166
23236
|
let $toolbar_slot = $container.querySelector(".toolbar_tabulator_table");
|
|
@@ -62408,7 +62478,7 @@ function open_card(gobj, logical, title, icon, tags, event, kw) {
|
|
|
62408
62478
|
let $tags = tags.map((tag) => {
|
|
62409
62479
|
return [
|
|
62410
62480
|
"span",
|
|
62411
|
-
{ class: `${logical}_TAG tag ${tag.style || "
|
|
62481
|
+
{ class: `${logical}_TAG tag ${tag.style || ""}`.trimEnd() },
|
|
62412
62482
|
tag.i18n ? [[
|
|
62413
62483
|
"span",
|
|
62414
62484
|
{ i18n: tag.i18n },
|
|
@@ -62555,7 +62625,7 @@ function render_topics(gobj, $body) {
|
|
|
62555
62625
|
topic.system_topic ? [
|
|
62556
62626
|
"span",
|
|
62557
62627
|
{
|
|
62558
|
-
class: "SCHEMA_TOPIC_SYSTEM tag
|
|
62628
|
+
class: "SCHEMA_TOPIC_SYSTEM tag ml-2",
|
|
62559
62629
|
i18n: "system"
|
|
62560
62630
|
},
|
|
62561
62631
|
t("system")
|
|
@@ -62904,7 +62974,7 @@ function column_row(gobj, topic, col, index, readonly, twice) {
|
|
|
62904
62974
|
let $flags = flags.map((flag) => {
|
|
62905
62975
|
return [
|
|
62906
62976
|
"span",
|
|
62907
|
-
{ class: `SCHEMA_COL_FLAG tag
|
|
62977
|
+
{ class: `SCHEMA_COL_FLAG tag ${yui_tint(flag_style(flag))}`.trimEnd() },
|
|
62908
62978
|
`${flag}`
|
|
62909
62979
|
];
|
|
62910
62980
|
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* bulma_is_light.test.js
|
|
3
|
+
*
|
|
4
|
+
* `is-light` MUST NEVER BE THE ONLY BULMA COLOUR ON AN ELEMENT.
|
|
5
|
+
*
|
|
6
|
+
* 'light' is one of Bulma's colours, so `is-light` is both a colour
|
|
7
|
+
* and the "soften it" modifier. A compound selector may match the
|
|
8
|
+
* same class twice, so `.tag.is-light.is-light` matches an element
|
|
9
|
+
* that carries the class ONCE -- and that rule sets the text to
|
|
10
|
+
* var(--bulma-light-light-invert-l), WHICH BULMA NEVER DEFINES:
|
|
11
|
+
* referenced four times in bulma.css, declared zero times. The
|
|
12
|
+
* colour declaration is then invalid while the background still
|
|
13
|
+
* resolves, and what is left on screen is a pill with no readable
|
|
14
|
+
* label.
|
|
15
|
+
*
|
|
16
|
+
* Two selectors in Bulma 1 reach that undefined variable:
|
|
17
|
+
*
|
|
18
|
+
* .tag.is-light.is-light
|
|
19
|
+
* .notification.is-light.is-light
|
|
20
|
+
*
|
|
21
|
+
* It cost three unreadable chips in the schema editor -- the topic
|
|
22
|
+
* count, the schema version, and every column flag without a colour
|
|
23
|
+
* of its own ('writable', 'image', 'wild'...). They looked like
|
|
24
|
+
* empty pills, which reads as a rendering bug in the data and is a
|
|
25
|
+
* missing CSS variable.
|
|
26
|
+
*
|
|
27
|
+
* THIS IS A SOURCE SCAN AND NOT A RENDER TEST ON PURPOSE: jsdom does
|
|
28
|
+
* not load Bulma, so no mounted test can see the contrast. What can
|
|
29
|
+
* be checked is the rule that produces it.
|
|
30
|
+
*
|
|
31
|
+
* Copyright (c) 2026, ArtGins.
|
|
32
|
+
* All Rights Reserved.
|
|
33
|
+
***********************************************************************/
|
|
34
|
+
import {describe, it, expect} from "vitest";
|
|
35
|
+
import {readdirSync, readFileSync} from "node:fs";
|
|
36
|
+
import {dirname, join} from "node:path";
|
|
37
|
+
import {fileURLToPath} from "node:url";
|
|
38
|
+
|
|
39
|
+
import {yui_tint, TINTABLE} from "./bulma_tint.js";
|
|
40
|
+
|
|
41
|
+
const SRC = dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
* The ONE file allowed to write the literal: yui_tint() emits it only
|
|
45
|
+
* next to a colour it has already checked against TINTABLE, and the
|
|
46
|
+
* colour is interpolated, so no source scan can see it. What guards
|
|
47
|
+
* that file is the behaviour suite at the bottom, not the scan.
|
|
48
|
+
*/
|
|
49
|
+
const EMITTER = "bulma_tint.js";
|
|
50
|
+
|
|
51
|
+
/* Every Bulma colour except 'light' itself. */
|
|
52
|
+
const COLOURS = [
|
|
53
|
+
"is-primary", "is-link", "is-info", "is-success", "is-warning",
|
|
54
|
+
"is-danger", "is-black", "is-dark", "is-white", "is-text", "is-ghost"
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/*
|
|
58
|
+
* How far around an occurrence to look for its colour. The colour does
|
|
59
|
+
* not have to sit on the same line: it may come from a ternary, from a
|
|
60
|
+
* string concatenation split across two lines, or from a variable
|
|
61
|
+
* interpolated just before. A window is what covers all three without
|
|
62
|
+
* parsing javascript.
|
|
63
|
+
*/
|
|
64
|
+
const WINDOW = 260;
|
|
65
|
+
|
|
66
|
+
function offenders(source)
|
|
67
|
+
{
|
|
68
|
+
let out = [];
|
|
69
|
+
let at = source.indexOf("is-light");
|
|
70
|
+
while(at >= 0) {
|
|
71
|
+
const from = Math.max(0, at - WINDOW);
|
|
72
|
+
const to = Math.min(source.length, at + WINDOW);
|
|
73
|
+
const around = source.slice(from, to);
|
|
74
|
+
|
|
75
|
+
/* A comment ABOUT the rule is not a use of it. */
|
|
76
|
+
const line_start = source.lastIndexOf("\n", at) + 1;
|
|
77
|
+
const line = source.slice(line_start, source.indexOf("\n", at));
|
|
78
|
+
const is_comment = /^\s*(\*|\/\/|\/\*)/.test(line);
|
|
79
|
+
|
|
80
|
+
if(!is_comment && !COLOURS.some((c) => around.includes(c))) {
|
|
81
|
+
out.push(line.trim());
|
|
82
|
+
}
|
|
83
|
+
at = source.indexOf("is-light", at + 1);
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
describe("bulma: is-light never travels alone", () => {
|
|
89
|
+
const files = readdirSync(SRC).filter(
|
|
90
|
+
(f) => f.endsWith(".js") && !f.endsWith(".test.js") && f !== EMITTER
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
it("scans every source file of the library", () => {
|
|
94
|
+
expect(files.length).toBeGreaterThan(0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
for(let file of files) {
|
|
98
|
+
it(`${file} carries no colourless is-light`, () => {
|
|
99
|
+
const source = readFileSync(join(SRC, file), "utf-8");
|
|
100
|
+
expect(offenders(source)).toEqual([]);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("the scan itself", () => {
|
|
106
|
+
it("catches a colourless is-light", () => {
|
|
107
|
+
const bad = 'return ["span", {class: `SOMETHING tag is-light`}, "x"];';
|
|
108
|
+
expect(offenders(bad)).toHaveLength(1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("accepts one that travels with a colour", () => {
|
|
112
|
+
const good = 'return ["span", {class: `SOMETHING tag is-warning is-light`}, "x"];';
|
|
113
|
+
expect(offenders(good)).toEqual([]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("accepts a colour that arrives split across lines", () => {
|
|
117
|
+
const good = 'const c = `tag ${x ? "is-danger" : "is-warning"}`\n'
|
|
118
|
+
+ ' + " is-light";';
|
|
119
|
+
expect(offenders(good)).toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("yui_tint", () => {
|
|
124
|
+
it("softens every colour that CAN be softened", () => {
|
|
125
|
+
for(let name of TINTABLE) {
|
|
126
|
+
expect(yui_tint(name)).toBe(`is-${name} is-light`);
|
|
127
|
+
expect(yui_tint(`is-${name}`)).toBe(`is-${name} is-light`);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("refuses to soften 'light' with itself", () => {
|
|
132
|
+
expect(yui_tint("light")).toBe("");
|
|
133
|
+
expect(yui_tint("is-light")).toBe("");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("answers nothing for nothing", () => {
|
|
137
|
+
expect(yui_tint("")).toBe("");
|
|
138
|
+
expect(yui_tint(undefined)).toBe("");
|
|
139
|
+
expect(yui_tint(null)).toBe("");
|
|
140
|
+
expect(yui_tint(7)).toBe("");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("does not pass through what it cannot vouch for", () => {
|
|
144
|
+
/* A class this function cannot recognise is exactly the case it
|
|
145
|
+
* exists to stop, so it comes back empty instead of forwarded. */
|
|
146
|
+
expect(yui_tint("is-whatever")).toBe("");
|
|
147
|
+
expect(yui_tint("has-text-danger")).toBe("");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("never emits is-light on its own", () => {
|
|
151
|
+
for(let input of TINTABLE.concat(
|
|
152
|
+
["light", "is-light", "", "nope", "is-nope"]
|
|
153
|
+
)) {
|
|
154
|
+
const out = yui_tint(input);
|
|
155
|
+
if(out.includes("is-light")) {
|
|
156
|
+
expect(out).toMatch(/is-(primary|link|info|success|warning|danger|black|dark|white|text|ghost) is-light/);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/***********************************************************************
|
|
2
|
+
* bulma_tint.js
|
|
3
|
+
*
|
|
4
|
+
* Bulma's "soften this colour" modifier, applied so that it cannot
|
|
5
|
+
* be applied alone.
|
|
6
|
+
*
|
|
7
|
+
* WHY THIS IS A FUNCTION AND NOT A STRING. 'light' is one of
|
|
8
|
+
* Bulma's colours, so `is-light` is BOTH a colour and the modifier
|
|
9
|
+
* that softens another one. A compound selector may match the same
|
|
10
|
+
* class twice, so `.tag.is-light.is-light` matches an element that
|
|
11
|
+
* carries the class ONCE -- and that rule sets the text to
|
|
12
|
+
* var(--bulma-light-light-invert-l), WHICH BULMA NEVER DEFINES:
|
|
13
|
+
* referenced four times in bulma.css, declared zero times. The
|
|
14
|
+
* colour declaration is then invalid while the background still
|
|
15
|
+
* resolves, so what is left on screen is a pill with no readable
|
|
16
|
+
* label.
|
|
17
|
+
*
|
|
18
|
+
* Two selectors of Bulma 1 reach that undefined variable:
|
|
19
|
+
*
|
|
20
|
+
* .tag.is-light.is-light
|
|
21
|
+
* .notification.is-light.is-light
|
|
22
|
+
*
|
|
23
|
+
* It cost three unreadable chips in the schema editor -- the topic
|
|
24
|
+
* count, the schema version, and every column flag with no colour of
|
|
25
|
+
* its own ('writable', 'image', 'wild'...). They read as empty
|
|
26
|
+
* pills, which looks like a bug in the DATA and is a missing CSS
|
|
27
|
+
* variable.
|
|
28
|
+
*
|
|
29
|
+
* The neutral chip is a bare element with no modifier at all: it
|
|
30
|
+
* takes --bulma-background-l and --bulma-text-l, follows the theme
|
|
31
|
+
* and stays legible in both.
|
|
32
|
+
*
|
|
33
|
+
* Copyright (c) 2026, ArtGins.
|
|
34
|
+
* All Rights Reserved.
|
|
35
|
+
***********************************************************************/
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* Bulma's colours, minus 'light' -- which is exactly the one that must
|
|
39
|
+
* never be softened with itself.
|
|
40
|
+
*/
|
|
41
|
+
const TINTABLE = [
|
|
42
|
+
"primary", "link", "info", "success", "warning",
|
|
43
|
+
"danger", "black", "dark", "white", "text", "ghost"
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/***************************************************************
|
|
47
|
+
* A softened colour, or nothing.
|
|
48
|
+
*
|
|
49
|
+
* yui_tint("warning") -> "is-warning is-light"
|
|
50
|
+
* yui_tint("is-warning") -> "is-warning is-light"
|
|
51
|
+
* yui_tint("light") -> "" (there is nothing to soften)
|
|
52
|
+
* yui_tint("") -> ""
|
|
53
|
+
* yui_tint(undefined) -> ""
|
|
54
|
+
*
|
|
55
|
+
* Anything it does not recognise comes back empty rather than being
|
|
56
|
+
* passed through: a class this function cannot vouch for is exactly the
|
|
57
|
+
* case it exists to stop.
|
|
58
|
+
***************************************************************/
|
|
59
|
+
function yui_tint(colour)
|
|
60
|
+
{
|
|
61
|
+
if(typeof colour !== "string") {
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
const name = colour.trim().replace(/^is-/, "");
|
|
65
|
+
if(TINTABLE.indexOf(name) < 0) {
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
return `is-${name} is-light`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export {yui_tint, TINTABLE};
|
|
@@ -109,6 +109,7 @@ import {
|
|
|
109
109
|
is_object,
|
|
110
110
|
empty_string,
|
|
111
111
|
} from "@yuneta/gobj-js";
|
|
112
|
+
import {yui_tint} from "./bulma_tint.js";
|
|
112
113
|
|
|
113
114
|
import "./c_yui_schema_editor.css";
|
|
114
115
|
|
|
@@ -871,7 +872,24 @@ function render(gobj)
|
|
|
871
872
|
function open_card(gobj, logical, title, icon, tags, event, kw)
|
|
872
873
|
{
|
|
873
874
|
let $tags = tags.map((tag) => {
|
|
874
|
-
|
|
875
|
+
/*
|
|
876
|
+
* `is-light` ALONE IS NOT A NEUTRAL CHIP, IT IS AN UNREADABLE ONE.
|
|
877
|
+
*
|
|
878
|
+
* 'light' is one of Bulma's colours, so `is-light` is both the
|
|
879
|
+
* colour and the modifier -- and `.tag.is-light.is-light` matches
|
|
880
|
+
* an element carrying the class ONCE (a compound selector may
|
|
881
|
+
* match the same class twice). That rule sets the text to
|
|
882
|
+
* var(--bulma-light-light-invert-l), WHICH BULMA NEVER DEFINES:
|
|
883
|
+
* used four times in its stylesheet, declared zero. The colour
|
|
884
|
+
* declaration is then invalid, the background still resolves, and
|
|
885
|
+
* what is left is a pill with no readable label.
|
|
886
|
+
*
|
|
887
|
+
* A bare `.tag` is the neutral chip: it takes --bulma-background-l
|
|
888
|
+
* and --bulma-text-l, so it follows the theme and stays legible in
|
|
889
|
+
* both. `is-light` goes on only NEXT TO a real colour, where the
|
|
890
|
+
* variable it needs does exist.
|
|
891
|
+
*/
|
|
892
|
+
return ["span", {class: `${logical}_TAG tag ${tag.style || ""}`.trimEnd()},
|
|
875
893
|
tag.i18n
|
|
876
894
|
? [["span", {i18n: tag.i18n}, t(tag.i18n)],
|
|
877
895
|
["span", {class: "ml-1"}, `${tag.text}`]]
|
|
@@ -989,7 +1007,7 @@ function render_topics(gobj, $body)
|
|
|
989
1007
|
["td", {class: "SCHEMA_TOPIC_NAME"}, [
|
|
990
1008
|
["span", {class: "has-text-weight-semibold"}, `${topic.name}`],
|
|
991
1009
|
topic.system_topic
|
|
992
|
-
? ["span", {class: "SCHEMA_TOPIC_SYSTEM tag
|
|
1010
|
+
? ["span", {class: "SCHEMA_TOPIC_SYSTEM tag ml-2",
|
|
993
1011
|
i18n: "system"}, t("system")]
|
|
994
1012
|
: ["span", {}, ""],
|
|
995
1013
|
ambiguous
|
|
@@ -1222,7 +1240,13 @@ function column_row(gobj, topic, col, index, readonly, twice)
|
|
|
1222
1240
|
let is_pkey = (col.name === (topic.pkey || "id"));
|
|
1223
1241
|
|
|
1224
1242
|
let $flags = flags.map((flag) => {
|
|
1225
|
-
|
|
1243
|
+
/* The colour carries the meaning, and `is-light` softens it. With
|
|
1244
|
+
* no colour there is nothing to soften: a bare tag is the neutral
|
|
1245
|
+
* one, and `is-light` on its own would erase the label -- see
|
|
1246
|
+
* open_card(). It is why 'writable', 'image', 'wild' and every
|
|
1247
|
+
* other unstyled flag rendered as an empty pill. */
|
|
1248
|
+
return ["span", {class: `SCHEMA_COL_FLAG tag ${yui_tint(flag_style(flag))}`.trimEnd()},
|
|
1249
|
+
`${flag}`];
|
|
1226
1250
|
});
|
|
1227
1251
|
|
|
1228
1252
|
/* An unset `blob` column comes back as `{}`, which is an object and
|
|
@@ -759,13 +759,33 @@ function build_ui(gobj)
|
|
|
759
759
|
* Layout Schema
|
|
760
760
|
*----------------------------------------------*/
|
|
761
761
|
let table_id = gobj_read_str_attr(gobj, "table_id");
|
|
762
|
+
/*
|
|
763
|
+
* A FLEX COLUMN, and that is the whole point of this block.
|
|
764
|
+
*
|
|
765
|
+
* It used to be a plain `height:100%` box with the toolbar and the
|
|
766
|
+
* table stacked inside it. The toolbar took its natural height and the
|
|
767
|
+
* table asked for `maxHeight: 100%` -- 100% of the CONTAINER, not of
|
|
768
|
+
* what the toolbar left -- so the box overflowed by exactly the height
|
|
769
|
+
* of the toolbar. The parent scrolls (`overflow:auto`), so the page
|
|
770
|
+
* ended up with TWO scrolls, and what fell off the bottom was the
|
|
771
|
+
* table's own footer: the row count and the paginator. Scrolling the
|
|
772
|
+
* outer one to reach them pushed the toolbar out of sight instead.
|
|
773
|
+
*
|
|
774
|
+
* As a column with the chrome at `flex:none` and the table at
|
|
775
|
+
* `flex:1 1 auto; min-height:0`, the table is sized to the space that
|
|
776
|
+
* is actually left, its footer stays put, and the only thing that
|
|
777
|
+
* scrolls is the rows -- which is the one scroll a table should have.
|
|
778
|
+
*/
|
|
762
779
|
let $container = createElement2(
|
|
763
|
-
['div', {class: 'C_YUI_TREEDB_TOPIC_WITH_FORM',
|
|
764
|
-
|
|
780
|
+
['div', {class: 'C_YUI_TREEDB_TOPIC_WITH_FORM',
|
|
781
|
+
style: 'height:100%; min-height:0; display:flex; flex-direction:column;'}, [
|
|
782
|
+
['div', {class: 'TREEDB_TABLE_TOOLBAR toolbar_tabulator_table m-1',
|
|
783
|
+
style: 'flex:none; display:flex; justify-content:space-between; ' +
|
|
784
|
+
'align-items:center; flex-wrap:wrap;'}],
|
|
765
785
|
['div',
|
|
766
786
|
{
|
|
767
787
|
id: `${table_id}`,
|
|
768
|
-
style: 'margin-top:0px !important;',
|
|
788
|
+
style: 'margin-top:0px !important; flex:1 1 auto; min-height:0;',
|
|
769
789
|
}
|
|
770
790
|
]
|
|
771
791
|
]]
|
|
@@ -782,6 +802,8 @@ function build_ui(gobj)
|
|
|
782
802
|
on_clear: () => gobj_send_event(gobj, "EV_CLEAR_SELECTION", {}, gobj)
|
|
783
803
|
});
|
|
784
804
|
gobj_write_attr(gobj, "selection_bar", bar);
|
|
805
|
+
/* Chrome too: it must not take height from the rows. */
|
|
806
|
+
bar.$el.style.flex = "none";
|
|
785
807
|
$container.insertBefore(bar.$el, $container.querySelector(`#${table_id}`));
|
|
786
808
|
}
|
|
787
809
|
|
package/src/shell_modals.js
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
empty_string,
|
|
34
34
|
log_warning,
|
|
35
35
|
} from "@yuneta/gobj-js";
|
|
36
|
+
import {yui_tint} from "./bulma_tint.js";
|
|
36
37
|
|
|
37
38
|
import {
|
|
38
39
|
activate_focus_trap_on,
|
|
@@ -121,7 +122,7 @@ function show_notification(shell, kind, message, opts)
|
|
|
121
122
|
? {i18n: message}
|
|
122
123
|
: {};
|
|
123
124
|
let $note = createElement2(
|
|
124
|
-
["div", {class: `TOAST notification yui-notification
|
|
125
|
+
["div", {class: `TOAST notification yui-notification ${yui_tint(kind)}`,
|
|
125
126
|
role: kind === "danger" ? "alert" : "status"},
|
|
126
127
|
[
|
|
127
128
|
["button", {class: "TOAST_CLOSE delete", "aria-label": "close"}],
|