@yuneta/gobj-ui 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +324 -6
  2. package/dist/gobj-ui.cjs.js +10891 -5006
  3. package/dist/gobj-ui.es.js +10915 -5064
  4. package/index.js +45 -1
  5. package/package.json +4 -4
  6. package/src/c_g6_nodes_tree.js +128 -40
  7. package/src/c_yui_form.js +112 -53
  8. package/src/c_yui_gobj_tree_js.js +58 -36
  9. package/src/c_yui_json.css +139 -0
  10. package/src/c_yui_json.js +928 -0
  11. package/src/c_yui_json_graph.js +110 -20
  12. package/src/c_yui_map.js +18 -36
  13. package/src/c_yui_nav.js +6 -9
  14. package/src/c_yui_period.css +184 -0
  15. package/src/c_yui_period.js +1441 -0
  16. package/src/c_yui_shell.css +33 -0
  17. package/src/c_yui_shell.js +672 -111
  18. package/src/c_yui_treedb_graph.js +639 -37
  19. package/src/c_yui_treedb_schema.js +478 -0
  20. package/src/c_yui_treedb_topic_with_form.css +7 -42
  21. package/src/c_yui_treedb_topic_with_form.js +152 -103
  22. package/src/c_yui_treedb_topics.css +73 -0
  23. package/src/c_yui_treedb_topics.js +1070 -29
  24. package/src/c_yui_window.js +180 -97
  25. package/src/c_yui_window_manager.js +38 -4
  26. package/src/json_view_helpers.js +214 -0
  27. package/src/json_view_helpers.test.js +135 -0
  28. package/src/route_map_model.js +317 -0
  29. package/src/route_map_model.test.js +209 -0
  30. package/src/route_resolver.js +24 -1
  31. package/src/route_resolver.test.js +40 -1
  32. package/src/shell_modals.js +117 -39
  33. package/src/shell_route_map.css +225 -0
  34. package/src/shell_route_map.js +363 -0
  35. package/src/tabulator.css +67 -0
  36. package/src/yui_dev.js +130 -8
  37. package/src/yui_frontend_view.js +106 -0
  38. package/src/yui_icons.css +62 -0
  39. package/src/yui_inputs.css +8 -3
  40. package/src/yui_inputs.js +51 -8
  41. package/src/yui_tabulator_i18n.js +128 -0
  42. package/src/yui_theme.js +147 -0
  43. package/src/yui_time.js +666 -0
  44. package/src/yui_time.test.js +330 -0
  45. package/src/yui_toolbar.js +86 -43
@@ -0,0 +1,128 @@
1
+ /***********************************************************************
2
+ * yui_tabulator_i18n.js
3
+ *
4
+ * The strings TABULATOR renders on its own — the paginator ("Page
5
+ * Size", "First", "Prev", "Next", "Last"), the placeholder ("No Data
6
+ * Available"), the loading/error notices — through the app's i18n.
7
+ *
8
+ * They are not built by any gclass, so nothing here or in an app ever
9
+ * passed them through t(): a table sat in English inside an otherwise
10
+ * Spanish view, and a language switch did not touch it.
11
+ *
12
+ * Two calls:
13
+ *
14
+ * new Tabulator($el, {..., ...yui_tabulator_lang(t)});
15
+ * yui_tabulator_relocalize(table, t); // on a language change
16
+ *
17
+ * Every key carries an English `defaultValue`, so an app that does not
18
+ * define it renders exactly what Tabulator rendered before — the
19
+ * keys are an OPPORTUNITY to translate, never a requirement.
20
+ *
21
+ * Copyright (c) 2026, ArtGins.
22
+ * All Rights Reserved.
23
+ ***********************************************************************/
24
+ import {log_warning} from "@yuneta/gobj-js";
25
+
26
+
27
+ /* Tabulator re-renders the parts it owns only when the locale NAME changes:
28
+ * setLocale() with the name already in force is a no-op, so re-registering
29
+ * fresh strings under "default" left the paginator in the old language. Each
30
+ * application of a language therefore gets its own name. */
31
+ let __lang_seq__ = 0;
32
+
33
+ function next_lang_name()
34
+ {
35
+ return `yui-${++__lang_seq__}`;
36
+ }
37
+
38
+ /***************************************************************
39
+ * Tabulator's own strings, in the CURRENT language.
40
+ *
41
+ * ONE lang dict (not one per locale): the app owns the locales and hands us
42
+ * only its live `t`, so a language change rebuilds the dict and re-applies
43
+ * it under a fresh name (yui_tabulator_relocalize).
44
+ ***************************************************************/
45
+ function yui_tabulator_lang(t)
46
+ {
47
+ let name = next_lang_name();
48
+ let langs = {};
49
+ langs[name] = tabulator_strings(t);
50
+ return {
51
+ locale: name,
52
+ langs: langs
53
+ };
54
+ }
55
+
56
+ function tabulator_strings(t)
57
+ {
58
+ let tr = (key, def) => t(key, {defaultValue: def});
59
+
60
+ return {
61
+ data: {
62
+ loading: tr("loading", "Loading"),
63
+ error: tr("error", "Error")
64
+ },
65
+ pagination: {
66
+ page_size: tr("page size", "Page Size"),
67
+ page_title: tr("show page", "Show Page"),
68
+ first: tr("first", "First"),
69
+ first_title: tr("first page", "First Page"),
70
+ last: tr("last", "Last"),
71
+ last_title: tr("last page", "Last Page"),
72
+ prev: tr("prev", "Prev"),
73
+ prev_title: tr("prev page", "Prev Page"),
74
+ next: tr("next", "Next"),
75
+ next_title: tr("next page", "Next Page"),
76
+ all: tr("all", "All")
77
+ },
78
+ headerFilters: {
79
+ default: tr("filter column", "filter column...")
80
+ }
81
+ };
82
+ }
83
+
84
+ /***************************************************************
85
+ * Put an EXISTING table in the current language: rebuild the strings and
86
+ * re-apply the locale, which is what makes Tabulator re-render the parts
87
+ * it owns (the paginator above all — it is drawn once, at build).
88
+ *
89
+ * Silent no-op on a table that is gone: a language switch races nothing,
90
+ * but a view torn down mid-switch must not log a failure it cannot act on.
91
+ ***************************************************************/
92
+ function yui_tabulator_relocalize(table, t)
93
+ {
94
+ if(!table) {
95
+ return;
96
+ }
97
+ try {
98
+ let name = next_lang_name();
99
+ let strings = tabulator_strings(t);
100
+
101
+ /* Tabulator DEEP-CLONES options.langs into its localize module when
102
+ * the table is built, and never looks at the option again: writing a
103
+ * new language there and calling setLocale() only earned a
104
+ * "Matching locale not found, using default: yui-5" — and the
105
+ * paginator it was meant to translate stayed in the old language.
106
+ * Install it where the module actually reads it. The option is still
107
+ * written, so a table rebuilt from its options keeps the language. */
108
+ table.options.langs = table.options.langs || {};
109
+ table.options.langs[name] = strings;
110
+
111
+ let localize = table.modules && table.modules.localize;
112
+ if(localize && typeof localize.installLang === "function") {
113
+ localize.installLang(name, strings);
114
+ } else {
115
+ log_warning(`yui_tabulator_relocalize: no localize module: ` +
116
+ `Tabulator's own chrome stays in the old language`);
117
+ }
118
+
119
+ table.setLocale(name); /* a NEW name: this is what re-renders */
120
+ } catch(e) {
121
+ log_warning(`yui_tabulator_relocalize: table gone: ${e}`);
122
+ }
123
+ }
124
+
125
+ export {
126
+ yui_tabulator_lang,
127
+ yui_tabulator_relocalize,
128
+ };
@@ -0,0 +1,147 @@
1
+ /***********************************************************************
2
+ * yui_theme.js
3
+ *
4
+ * The app's light/dark theme, and how a gclass follows it.
5
+ *
6
+ * The theme lives in ONE place: the `data-theme` attribute of
7
+ * <html> (the Bulma convention, set by the shell's theme toggle).
8
+ * Reading it is the only way to know the current theme; there is
9
+ * no service to ask.
10
+ *
11
+ * Components used to ask a legacy C_YUI_MAIN "__yui_main__"
12
+ * service instead — read its `theme` attr, subscribe to its
13
+ * EV_THEME. That path was retired: nothing ever WROTE that attr,
14
+ * so it answered "light" for the life of the app, and no shell
15
+ * published EV_THEME. C_YUI_SHELL has no such service at all.
16
+ *
17
+ * A theme change is a DOM mutation — an OS notification — so it
18
+ * enters the machine as an event: yui_watch_theme() translates
19
+ * the mutation into EV_THEME and the gclass's action does the
20
+ * restyling. Nothing happens in the observer callback itself.
21
+ *
22
+ * Copyright (c) 2026, ArtGins.
23
+ * All Rights Reserved.
24
+ ***********************************************************************/
25
+ import {
26
+ gobj_send_event,
27
+ gobj_is_destroying,
28
+ } from "@yuneta/gobj-js";
29
+
30
+
31
+ /************************************************************
32
+ * The active theme: "dark" | "light".
33
+ *
34
+ * <html data-theme> when the app sets it, else the OS
35
+ * preference, else light.
36
+ ************************************************************/
37
+ function yui_theme_now()
38
+ {
39
+ if(typeof document === "undefined") {
40
+ return "light";
41
+ }
42
+ let attr = document.documentElement.getAttribute("data-theme");
43
+ if(attr === "dark" || attr === "light") {
44
+ return attr;
45
+ }
46
+ if(typeof window !== "undefined" && window.matchMedia) {
47
+ return window.matchMedia("(prefers-color-scheme: dark)").matches
48
+ ? "dark" : "light";
49
+ }
50
+ return "light";
51
+ }
52
+
53
+ /************************************************************
54
+ * True when the app renders dark.
55
+ ************************************************************/
56
+ function yui_is_dark()
57
+ {
58
+ return yui_theme_now() === "dark";
59
+ }
60
+
61
+ /************************************************************
62
+ * Follow the theme: watch <html data-theme> and send `event`
63
+ * (default EV_THEME, kw {theme}) to `gobj` on every change,
64
+ * so the gclass restyles in its ACTION — the observer only
65
+ * translates the notification.
66
+ *
67
+ * The gclass must declare `event` in its FSM, and must
68
+ * disconnect the returned observer in mt_destroy:
69
+ *
70
+ * priv.theme_observer = yui_watch_theme(gobj);
71
+ * ...
72
+ * if(priv.theme_observer) {
73
+ * priv.theme_observer.disconnect();
74
+ * priv.theme_observer = null;
75
+ * }
76
+ *
77
+ * Watches BOTH sources: <html data-theme> (a MutationObserver)
78
+ * and the OS preference (matchMedia prefers-color-scheme, which
79
+ * is the live source while the attribute is absent — the
80
+ * "system" theme).
81
+ *
82
+ * Returns a handle with disconnect(), or null where there is
83
+ * no DOM.
84
+ ************************************************************/
85
+ function yui_watch_theme(gobj, event = "EV_THEME")
86
+ {
87
+ if(typeof MutationObserver === "undefined" || typeof document === "undefined") {
88
+ return null;
89
+ }
90
+
91
+ let $html = document.documentElement;
92
+ let last = yui_theme_now();
93
+
94
+ let notify = function() {
95
+ let theme = yui_theme_now();
96
+ /* data-theme can be rewritten with the same value (a re-render,
97
+ * another observer); only a real change is an event. */
98
+ if(theme === last) {
99
+ return;
100
+ }
101
+ last = theme;
102
+ if(gobj_is_destroying(gobj)) {
103
+ return;
104
+ }
105
+ gobj_send_event(gobj, event, {theme: theme}, gobj);
106
+ };
107
+
108
+ let mo = new MutationObserver(notify);
109
+ mo.observe($html, {attributes: true, attributeFilter: ["data-theme"]});
110
+
111
+ /* The "system" theme: with data-theme ABSENT, yui_theme_now()
112
+ * answers from the OS preference (and Bulma follows it via
113
+ * prefers-color-scheme), so an OS auto-switch (sunset) IS a theme
114
+ * change — without this listener the CSS would flip while every
115
+ * canvas kept its old palette, the exact bug class the attribute
116
+ * observer kills. notify() dedupes, so when the app pins
117
+ * data-theme this listener never fires an event (the attribute
118
+ * wins inside yui_theme_now). */
119
+ let mql = null;
120
+ if(typeof window !== "undefined" && window.matchMedia) {
121
+ mql = window.matchMedia("(prefers-color-scheme: dark)");
122
+ if(typeof mql.addEventListener === "function") {
123
+ mql.addEventListener("change", notify);
124
+ } else {
125
+ mql = null;
126
+ }
127
+ }
128
+
129
+ /* One handle, one disconnect() — same contract as a bare
130
+ * MutationObserver, covering both sources. */
131
+ return {
132
+ disconnect: function() {
133
+ mo.disconnect();
134
+ if(mql) {
135
+ mql.removeEventListener("change", notify);
136
+ mql = null;
137
+ }
138
+ }
139
+ };
140
+ }
141
+
142
+
143
+ export {
144
+ yui_theme_now,
145
+ yui_is_dark,
146
+ yui_watch_theme,
147
+ };