@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
package/src/yui_dev.js CHANGED
@@ -15,9 +15,10 @@ import {
15
15
  gobj_write_attr,
16
16
  gobj_create_service,
17
17
  gobj_find_service,
18
+ gobj_start,
18
19
  set_log_callback,
20
+ set_console_log_enabled,
19
21
  gobj_set_trace_machine_format,
20
- trace_json,
21
22
  } from "@yuneta/gobj-js";
22
23
 
23
24
  import i18next from 'i18next';
@@ -37,6 +38,13 @@ let TRAFFIC_LOG = []; // [{title,event,command,sig,dir,size,ts,kw,
37
38
  let TRAFFIC_COUNTS = new Map(); // signature -> occurrences (for periodic detection)
38
39
  let SEARCH_TEXT = ""; // session-only free-text filter (not persisted)
39
40
 
41
+ /* Running totals of framework errors/warnings seen since page load (or the
42
+ * last Clear). Kept apart from TRAFFIC_LOG so the 600-entry cap can't rotate
43
+ * an error out of the count — the whole point of the status-line tally is to
44
+ * not lose that signal under a flood of automata/debug lines. */
45
+ let LOG_ERR_COUNT = 0;
46
+ let LOG_WARN_COUNT = 0;
47
+
40
48
  /* Field names whose numeric value is a Unix timestamp (seconds). */
41
49
  const TRAFFIC_TS_FIELDS = {
42
50
  "__t__": 1, "__tm__": 1, "tm": 1, "t": 1,
@@ -161,6 +169,41 @@ function dev_hide_periodic()
161
169
  return dev_num("dev_hide_periodic", 0) ? true : false;
162
170
  }
163
171
 
172
+ /* Where dev output (traffic + all logs + automata) is routed:
173
+ * "window" → dev window only (browser console silenced framework-wide)
174
+ * "console" → browser console only (nothing appended to the window)
175
+ * "both" → console + window (default; unchanged from prior behaviour)
176
+ * The console side of logs is gated in gobj-js (set_console_log_enabled);
177
+ * the window side is gated here, in info_traffic / info_log. */
178
+ function dev_route()
179
+ {
180
+ let v = kw_get_local_storage_value("dev_output_route", "both", false);
181
+ return (v === "window" || v === "console") ? v : "both";
182
+ }
183
+
184
+ /* Push the current route's console decision down to gobj-js. Called from
185
+ * apply_dev_traces (startup) and whenever the Output selector changes. */
186
+ function apply_console_route()
187
+ {
188
+ set_console_log_enabled(dev_route() !== "window");
189
+ }
190
+
191
+ /* Emit one inter-event traffic line to the browser console (routes
192
+ * "console" and "both"). gobj-js only knows about framework logs, not this
193
+ * traffic feed, so the console mirror is produced here. */
194
+ function console_traffic(title, jn, direction, size)
195
+ {
196
+ let event = (jn && jn.event) ? String(jn.event) : "(no event)";
197
+ let kw = (jn && jn.kw && typeof jn.kw === "object") ? jn.kw : jn;
198
+ let color = (direction === 2) ? "#16a34a" : (direction === 3) ? "#dc2626" : "#2563eb";
199
+ window.console.log(
200
+ "%c" + dir_arrow(direction) + " " + (title ? "[" + title + "] " : "") + event +
201
+ (size ? " (" + size + "b)" : ""),
202
+ "color:" + color,
203
+ kw
204
+ );
205
+ }
206
+
164
207
  function dev_muted()
165
208
  {
166
209
  let a = kw_get_local_storage_value("dev_muted_events", [], false);
@@ -182,6 +225,17 @@ function set_view(v)
182
225
  refresh_dev_chrome();
183
226
  }
184
227
 
228
+ function set_output_route(v)
229
+ {
230
+ if(v !== "window" && v !== "console" && v !== "both") {
231
+ v = "both";
232
+ }
233
+ kw_set_local_storage_value("dev_output_route", v);
234
+ apply_console_route();
235
+ rerender_all();
236
+ refresh_dev_chrome();
237
+ }
238
+
185
239
  function toggle_pref(key, def)
186
240
  {
187
241
  let v = dev_num(key, def) ? 0 : 1;
@@ -353,6 +407,8 @@ function ensure_dev_style()
353
407
  background: rgba(217,119,6,0.12); color: #b45309; cursor: pointer;
354
408
  }
355
409
  .YDEV_STAT.s-out { color: #2563eb; } .YDEV_STAT.s-in { color: #059669; } .YDEV_STAT.s-err { color: #dc2626; }
410
+ .YDEV_STAT.s-warn { color: #d97706; }
411
+ .YDEV_STAT.is-strong { font-weight: 700; }
356
412
  .YDEV_TITLE { display: flex; align-items: baseline; gap: 8px; }
357
413
  .YDEV_TITLE_MAIN { font-weight: 700; }
358
414
  .YDEV_TITLE_SUB { opacity: 0.7; font-size: 12px; }
@@ -419,6 +475,7 @@ details.TRAFFIC_NEST > summary::-webkit-details-marker { display: none; }
419
475
  :root[data-theme="dark"] .dir-in .TRAFFIC_ARROW, :root[data-theme="dark"] .dir-in .TRAFFIC_EVENT { color: #34d399; }
420
476
  :root[data-theme="dark"] .dir-err .TRAFFIC_ARROW, :root[data-theme="dark"] .dir-err .TRAFFIC_EVENT { color: #f87171; }
421
477
  :root[data-theme="dark"] .YDEV_STAT.s-out { color: #60a5fa; } :root[data-theme="dark"] .YDEV_STAT.s-in { color: #34d399; } :root[data-theme="dark"] .YDEV_STAT.s-err { color: #f87171; }
478
+ :root[data-theme="dark"] .YDEV_STAT.s-warn { color: #fbbf24; }
422
479
  :root[data-theme="dark"] .TRAFFIC_VAL.t-num { color: #22d3ee; }
423
480
  :root[data-theme="dark"] .TRAFFIC_VAL.t-bool, :root[data-theme="dark"] .TRAFFIC_VAL.t-null { color: #c084fc; }
424
481
  :root[data-theme="dark"] .YDEV_CHIP.is-active { background: rgba(96,165,250,0.2); border-color: #60a5fa; color: #93c5fd; }
@@ -726,6 +783,8 @@ function clear_traffic()
726
783
  {
727
784
  TRAFFIC_LOG.length = 0;
728
785
  TRAFFIC_COUNTS.clear();
786
+ LOG_ERR_COUNT = 0;
787
+ LOG_WARN_COUNT = 0;
729
788
  let logger = document.getElementById('developer-traffic-logger');
730
789
  if(logger) {
731
790
  logger.replaceChildren();
@@ -792,7 +851,11 @@ function update_stats()
792
851
  }
793
852
  }
794
853
  $s.replaceChildren();
854
+ /* Errors/warnings first — the signal you don't want to miss. Session
855
+ * totals (see LOG_ERR_COUNT), bold when non-zero so they stand out. */
795
856
  let cells = [
857
+ [`✖ ${LOG_ERR_COUNT} err`, 's-err' + (LOG_ERR_COUNT ? ' is-strong' : '')],
858
+ [`▲ ${LOG_WARN_COUNT} warn`, 's-warn' + (LOG_WARN_COUNT ? ' is-strong' : '')],
796
859
  [`${shown}/${total} shown`, ''],
797
860
  [`⇢ ${out}`, 's-out'],
798
861
  [`⇠ ${inc}`, 's-in'],
@@ -834,12 +897,6 @@ function append_and_follow(logger, node)
834
897
  ************************************************************/
835
898
  function info_traffic(title, msg, direction, size)
836
899
  {
837
- let logger = document.getElementById('developer-traffic-logger');
838
- if(!logger) {
839
- trace_json(msg);
840
- return;
841
- }
842
-
843
900
  if(!size) {
844
901
  size = 0;
845
902
  }
@@ -851,6 +908,21 @@ function info_traffic(title, msg, direction, size)
851
908
  return;
852
909
  }
853
910
 
911
+ let route = dev_route();
912
+
913
+ /* Console side (routes "console" / "both"). */
914
+ if(route !== "window") {
915
+ console_traffic(title, jn, direction, size);
916
+ }
917
+
918
+ /* Window side: skip when routed to the console only, or when the window
919
+ * is not mounted (closed). No fallback console dump — console_traffic
920
+ * above already covered every route that wants console output. */
921
+ let logger = document.getElementById('developer-traffic-logger');
922
+ if(!logger || route === "console") {
923
+ return;
924
+ }
925
+
854
926
  ensure_dev_style();
855
927
 
856
928
  let event = (jn && jn.event) ? String(jn.event) : "(no event)";
@@ -923,6 +995,21 @@ function info_log(level, msg, hora)
923
995
  if(__in_info_log__) {
924
996
  return;
925
997
  }
998
+ /* Tally errors/warnings for the status line, always — before any routing
999
+ * or window-open guard, so the count reflects the whole session even
1000
+ * while the window is closed or output is routed to the console. */
1001
+ if(level === "error") {
1002
+ LOG_ERR_COUNT++;
1003
+ } else if(level === "warning") {
1004
+ LOG_WARN_COUNT++;
1005
+ }
1006
+ /* Routed to the console only: the line already reached the browser
1007
+ * console (gobj-js gate left on), so don't mirror it into the window —
1008
+ * but keep the status-line tally live if the window is open. */
1009
+ if(dev_route() === "console") {
1010
+ update_stats();
1011
+ return;
1012
+ }
926
1013
  let logger = document.getElementById('developer-traffic-logger');
927
1014
  if(!logger) {
928
1015
  return;
@@ -1084,6 +1171,11 @@ function refresh_dev_chrome()
1084
1171
  $b.classList.toggle('is-active', $b.getAttribute('data-view') === view);
1085
1172
  });
1086
1173
 
1174
+ let route = dev_route();
1175
+ document.querySelectorAll('.YDEV_SEG_BTN[data-output]').forEach(($b) => {
1176
+ $b.classList.toggle('is-active', $b.getAttribute('data-output') === route);
1177
+ });
1178
+
1087
1179
  /* The Expand section toggles only apply to the Expanded view — show
1088
1180
  * the group only there, and reflect each toggle's persisted state. */
1089
1181
  let $eg = document.getElementById('ydev-expand-grp');
@@ -1236,6 +1328,27 @@ function build_control_bar()
1236
1328
  mk_view('name', 'Name only'),
1237
1329
  ]];
1238
1330
 
1331
+ /* Output routing: send traffic + all logs + automata to the dev window,
1332
+ * the browser console, or both. */
1333
+ let OUT_TITLES = {
1334
+ window: 'Dev window only (browser console stays clean)',
1335
+ console: 'Browser console only (nothing shown in this window)',
1336
+ both: 'Dev window and browser console',
1337
+ };
1338
+ let mk_out = (v, label) => ['button', {
1339
+ class: 'YDEV_SEG_BTN', 'data-output': v, type: 'button', title: OUT_TITLES[v],
1340
+ }, label, {
1341
+ click: (ev) => {
1342
+ ev.stopPropagation();
1343
+ set_output_route(v);
1344
+ }
1345
+ }];
1346
+ let output_seg = ['div', {class: 'YDEV_SEG', id: 'ydev-output'}, [
1347
+ mk_out('window', 'Window'),
1348
+ mk_out('console', 'Console'),
1349
+ mk_out('both', 'Both'),
1350
+ ]];
1351
+
1239
1352
  /* Expanded-view section toggles (only meaningful in the 'full' view;
1240
1353
  * the group is shown/hidden by refresh_dev_chrome). */
1241
1354
  let mk_expand = (key, label) => ['button', {
@@ -1312,6 +1425,7 @@ function build_control_bar()
1312
1425
 
1313
1426
  return createElement2(['div', {class: 'YDEV_BAR'}, [
1314
1427
  grp('Traces', [...trace_chips, simple_mach]), sep(),
1428
+ grp('Output', [output_seg]), sep(),
1315
1429
  grp('View', [view_seg]), expand_grp, sep(),
1316
1430
  grp('Show', [...dir_chips, periodic_chip]), sep(),
1317
1431
  grp('Find', [search]), sep(),
@@ -1393,6 +1507,10 @@ function apply_dev_traces()
1393
1507
  * automata FSM trace arrives as debug) into the monitor. info_log no-ops
1394
1508
  * while the window is closed, so this is safe to leave armed. */
1395
1509
  set_log_callback(info_log);
1510
+
1511
+ /* Honour the persisted Window / Console / Both routing at startup so a
1512
+ * refresh keeps the console silenced when "Window only" was chosen. */
1513
+ apply_console_route();
1396
1514
  }
1397
1515
 
1398
1516
  /************************************************************
@@ -1409,7 +1527,7 @@ function setup_dev(self, show)
1409
1527
  if(show) {
1410
1528
  ensure_dev_style();
1411
1529
 
1412
- gobj_create_service(
1530
+ let win = gobj_create_service(
1413
1531
  "Developer-Window",
1414
1532
  "C_YUI_WINDOW",
1415
1533
  {
@@ -1435,6 +1553,10 @@ function setup_dev(self, show)
1435
1553
  },
1436
1554
  self
1437
1555
  );
1556
+ /* c_yuno's mt_play only starts the DEFAULT service, so a service
1557
+ * created here is ours to start; unstarted, it shows up in every
1558
+ * trace line as `!!C_YUI_WINDOW^Developer-Window`. */
1559
+ gobj_start(win);
1438
1560
 
1439
1561
  kw_set_local_storage_value("open_developer_window", 1);
1440
1562
 
@@ -0,0 +1,106 @@
1
+ /***********************************************************************
2
+ * yui_frontend_view.js
3
+ *
4
+ * Frontend view — the gobj tree of the own yuno in a
5
+ * floating window, peer of the developer window (yui_dev.js).
6
+ *
7
+ * Copyright (c) 2026, ArtGins.
8
+ * All Rights Reserved.
9
+ ***********************************************************************/
10
+ import {
11
+ createElement2,
12
+ gobj_create_service,
13
+ gobj_create_pure_child,
14
+ gobj_find_service,
15
+ gobj_read_attr,
16
+ gobj_start_tree,
17
+ log_error,
18
+ } from "@yuneta/gobj-js";
19
+
20
+ /* Service name of the window. The host toggles the entry by looking
21
+ * it up, exactly as it does with "Developer-Window". */
22
+ const WIN_NAME = "Frontend-View-Window";
23
+
24
+
25
+ /******************************
26
+ * Public API
27
+ ******************************/
28
+
29
+
30
+ /************************************************************
31
+ * Open the gobj tree (C_YUI_GOBJ_TREE_JS) inside a non-modal
32
+ * C_YUI_WINDOW (title bar + maximize + close + resize), the
33
+ * same shape as the developer window.
34
+ *
35
+ * Shell-agnostic: the legacy C_YUI_MAIN shell has a
36
+ * '#top-layer' stacking element; the new C_YUI_SHELL does not.
37
+ * We pass that element when present, otherwise null — C_YUI_WINDOW
38
+ * falls back to document.body by contract.
39
+ *
40
+ * Returns the window gobj, or null.
41
+ ************************************************************/
42
+ function setup_frontend_view(self)
43
+ {
44
+ if(gobj_find_service(WIN_NAME, false)) {
45
+ return null;
46
+ }
47
+
48
+ /* The tree is created AFTER the window, as a pure child of it, so
49
+ * every teardown path (the ✕, or the host destroying the window to
50
+ * toggle the entry off) takes the tree down with it. That is why
51
+ * the body is this placeholder: C_YUI_WINDOW builds its UI in
52
+ * mt_create, so it cannot be handed a gobj that does not exist yet. */
53
+ let $body = createElement2(
54
+ ["div", {class: "YFRONT_BODY",
55
+ style: "height:100%; display:flex; flex-direction:column;"}, []]
56
+ );
57
+
58
+ let win = gobj_create_service(
59
+ WIN_NAME,
60
+ "C_YUI_WINDOW",
61
+ {
62
+ $parent: (typeof document !== "undefined" &&
63
+ document.getElementById("top-layer")) || null,
64
+ subscriber: null,
65
+ modal: false,
66
+ showMax: true,
67
+ showFooter: false,
68
+ resizable: true,
69
+ center: false,
70
+ auto_save_size_and_position: true,
71
+ width: 900,
72
+ height: 640,
73
+ logical_class: "FRONTEND_VIEW_WINDOW",
74
+ title: "frontend view",
75
+ icon: "yi-hexagon-nodes",
76
+ body: $body,
77
+ /* Opt into the dock/taskbar if the app provides one. `|| null`
78
+ * because gobj_find_service returns undefined when absent, and
79
+ * an undefined attr value logs "attr undefined: manager" (apps
80
+ * without a window manager, e.g. wattyzer). null = no dock. */
81
+ manager: gobj_find_service("__window_manager__", false) || null,
82
+ },
83
+ self
84
+ );
85
+ if(!win) {
86
+ log_error("yui_frontend_view: cannot create the frontend-view window");
87
+ return null;
88
+ }
89
+
90
+ /* The window is mounted by its mt_create, so $body is already live
91
+ * DOM here: the tree's own mt_start (build_graph + load_tree) and its
92
+ * ResizeObserver need the canvas attached to measure it. */
93
+ let tree = gobj_create_pure_child("frontend_view_tree", "C_YUI_GOBJ_TREE_JS", {}, win);
94
+ let $tree = gobj_read_attr(tree, "$container");
95
+ if(!$tree) {
96
+ log_error("yui_frontend_view: C_YUI_GOBJ_TREE_JS without $container");
97
+ return win;
98
+ }
99
+ $body.appendChild($tree);
100
+
101
+ gobj_start_tree(win);
102
+
103
+ return win;
104
+ }
105
+
106
+ export {setup_frontend_view};
package/src/yui_icons.css CHANGED
@@ -82,6 +82,17 @@
82
82
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M256 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 160-160 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l160 0 0 160c0 17.7 14.3 32 32 32s32-14.3 32-32l0-160 160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-160 0 0-160z'/%3E%3C/svg%3E");
83
83
  }
84
84
 
85
+ /* yi-plug / yi-plug-slash: hand-drawn masks (connect / disconnect). */
86
+ .yi-plug::before {
87
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Crect x='64' y='0' width='64' height='168' rx='32'/%3E%3Crect x='256' y='0' width='64' height='168' rx='32'/%3E%3Cpath d='M32 128h320c17.7 0 32 14.3 32 32v48c0 88.4-71.6 160-160 160v112c0 17.7-14.3 32-32 32s-32-14.3-32-32V368C71.6 368 0 296.4 0 208v-48c0-17.7 14.3-32 32-32z'/%3E%3C/svg%3E");
88
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Crect x='64' y='0' width='64' height='168' rx='32'/%3E%3Crect x='256' y='0' width='64' height='168' rx='32'/%3E%3Cpath d='M32 128h320c17.7 0 32 14.3 32 32v48c0 88.4-71.6 160-160 160v112c0 17.7-14.3 32-32 32s-32-14.3-32-32V368C71.6 368 0 296.4 0 208v-48c0-17.7 14.3-32 32-32z'/%3E%3C/svg%3E");
89
+ }
90
+
91
+ .yi-plug-slash::before {
92
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Crect x='64' y='0' width='64' height='168' rx='32'/%3E%3Crect x='256' y='0' width='64' height='168' rx='32'/%3E%3Cpath d='M32 128h320c17.7 0 32 14.3 32 32v48c0 88.4-71.6 160-160 160v112c0 17.7-14.3 32-32 32s-32-14.3-32-32V368C71.6 368 0 296.4 0 208v-48c0-17.7 14.3-32 32-32z'/%3E%3Cpath d='M10.5 81.9 53.5 46.1 373.5 430.1 330.5 465.9z'/%3E%3C/svg%3E");
93
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Crect x='64' y='0' width='64' height='168' rx='32'/%3E%3Crect x='256' y='0' width='64' height='168' rx='32'/%3E%3Cpath d='M32 128h320c17.7 0 32 14.3 32 32v48c0 88.4-71.6 160-160 160v112c0 17.7-14.3 32-32 32s-32-14.3-32-32V368C71.6 368 0 296.4 0 208v-48c0-17.7 14.3-32 32-32z'/%3E%3Cpath d='M10.5 81.9 53.5 46.1 373.5 430.1 330.5 465.9z'/%3E%3C/svg%3E");
94
+ }
95
+
85
96
  .yi-question::before {
86
97
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M64 160c0-53 43-96 96-96s96 43 96 96c0 42.7-27.9 78.9-66.5 91.4-28.4 9.2-61.5 35.3-61.5 76.6l0 24c0 17.7 14.3 32 32 32s32-14.3 32-32l0-24c0-1.7 .6-4.1 3.5-7.3 3-3.3 7.9-6.5 13.7-8.4 64.3-20.7 110.8-81 110.8-152.3 0-88.4-71.6-160-160-160S0 71.6 0 160c0 17.7 14.3 32 32 32s32-14.3 32-32zm96 352c22.1 0 40-17.9 40-40s-17.9-40-40-40-40 17.9-40 40 17.9 40 40 40z'/%3E%3C/svg%3E");
87
98
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M64 160c0-53 43-96 96-96s96 43 96 96c0 42.7-27.9 78.9-66.5 91.4-28.4 9.2-61.5 35.3-61.5 76.6l0 24c0 17.7 14.3 32 32 32s32-14.3 32-32l0-24c0-1.7 .6-4.1 3.5-7.3 3-3.3 7.9-6.5 13.7-8.4 64.3-20.7 110.8-81 110.8-152.3 0-88.4-71.6-160-160-160S0 71.6 0 160c0 17.7 14.3 32 32 32s32-14.3 32-32zm96 352c22.1 0 40-17.9 40-40s-17.9-40-40-40-40 17.9-40 40 17.9 40 40 40z'/%3E%3C/svg%3E");
@@ -254,3 +265,54 @@
254
265
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zm32 224a32 32 0 1 1 -64 0 32 32 0 1 1 64 0z'/%3E%3C/svg%3E");
255
266
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zm32 224a32 32 0 1 1 -64 0 32 32 0 1 1 64 0z'/%3E%3C/svg%3E");
256
267
  }
268
+
269
+ .yi-pause::before {
270
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M48 64C21.5 64 0 85.5 0 112L0 400c0 26.5 21.5 48 48 48l32 0c26.5 0 48-21.5 48-48l0-288c0-26.5-21.5-48-48-48L48 64zm192 0c-26.5 0-48 21.5-48 48l0 288c0 26.5 21.5 48 48 48l32 0c26.5 0 48-21.5 48-48l0-288c0-26.5-21.5-48-48-48l-32 0z'/%3E%3C/svg%3E");
271
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M48 64C21.5 64 0 85.5 0 112L0 400c0 26.5 21.5 48 48 48l32 0c26.5 0 48-21.5 48-48l0-288c0-26.5-21.5-48-48-48L48 64zm192 0c-26.5 0-48 21.5-48 48l0 288c0 26.5 21.5 48 48 48l32 0c26.5 0 48-21.5 48-48l0-288c0-26.5-21.5-48-48-48l-32 0z'/%3E%3C/svg%3E");
272
+ }
273
+
274
+ .yi-play::before {
275
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80L0 432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z'/%3E%3C/svg%3E");
276
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 384 512'%3E%3Cpath d='M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80L0 432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z'/%3E%3C/svg%3E");
277
+ }
278
+
279
+ .yi-download::before {
280
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 242.7-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7 288 32zM64 352c-35.3 0-64 28.7-64 64l0 32c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-32c0-35.3-28.7-64-64-64l-101.5 0-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352 64 352zm368 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z'/%3E%3C/svg%3E");
281
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M288 32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 242.7-73.4-73.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l128 128c12.5 12.5 32.8 12.5 45.3 0l128-128c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L288 274.7 288 32zM64 352c-35.3 0-64 28.7-64 64l0 32c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-32c0-35.3-28.7-64-64-64l-101.5 0-45.3 45.3c-25 25-65.5 25-90.5 0L165.5 352 64 352zm368 56a24 24 0 1 1 0 48 24 24 0 1 1 0-48z'/%3E%3C/svg%3E");
282
+ }
283
+
284
+ .yi-link::before {
285
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 512'%3E%3Cpath d='M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z'/%3E%3C/svg%3E");
286
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 512'%3E%3Cpath d='M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z'/%3E%3C/svg%3E");
287
+ }
288
+
289
+ /* --- Date navigator (C_YUI_PERIOD) --- */
290
+ .yi-chevron-left::before {
291
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z'/%3E%3C/svg%3E");
292
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z'/%3E%3C/svg%3E");
293
+ }
294
+
295
+ .yi-chevron-right::before {
296
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z'/%3E%3C/svg%3E");
297
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z'/%3E%3C/svg%3E");
298
+ }
299
+
300
+ .yi-forward-step::before {
301
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M52.5 440.6c-9.5 7.9-22.8 9.7-34.1 4.4S0 428.4 0 416L0 96C0 83.6 7.2 72.3 18.4 67s24.5-3.6 34.1 4.4l192 160L256 241l0-145c0-17.7 14.3-32 32-32s32 14.3 32 32l0 320c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-145-11.5 9.6-192 160z'/%3E%3C/svg%3E");
302
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M52.5 440.6c-9.5 7.9-22.8 9.7-34.1 4.4S0 428.4 0 416L0 96C0 83.6 7.2 72.3 18.4 67s24.5-3.6 34.1 4.4l192 160L256 241l0-145c0-17.7 14.3-32 32-32s32 14.3 32 32l0 320c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-145-11.5 9.6-192 160z'/%3E%3C/svg%3E");
303
+ }
304
+
305
+ .yi-calendar-days::before {
306
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M128 0c17.7 0 32 14.3 32 32l0 32 128 0 0-32c0-17.7 14.3-32 32-32s32 14.3 32 32l0 32 48 0c26.5 0 48 21.5 48 48l0 48L0 160l0-48C0 85.5 21.5 64 48 64l48 0 0-32c0-17.7 14.3-32 32-32zM0 192l448 0 0 272c0 26.5-21.5 48-48 48L48 512c-26.5 0-48-21.5-48-48L0 192zm64 80l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16zm128 0l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16zm144-16c-8.8 0-16 7.2-16 16l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0zM64 400l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16zm144-16c-8.8 0-16 7.2-16 16l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0zm112 16l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16z'/%3E%3C/svg%3E");
307
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M128 0c17.7 0 32 14.3 32 32l0 32 128 0 0-32c0-17.7 14.3-32 32-32s32 14.3 32 32l0 32 48 0c26.5 0 48 21.5 48 48l0 48L0 160l0-48C0 85.5 21.5 64 48 64l48 0 0-32c0-17.7 14.3-32 32-32zM0 192l448 0 0 272c0 26.5-21.5 48-48 48L48 512c-26.5 0-48-21.5-48-48L0 192zm64 80l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16zm128 0l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16zm144-16c-8.8 0-16 7.2-16 16l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0zM64 400l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16zm144-16c-8.8 0-16 7.2-16 16l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0zm112 16l0 32c0 8.8 7.2 16 16 16l32 0c8.8 0 16-7.2 16-16l0-32c0-8.8-7.2-16-16-16l-32 0c-8.8 0-16 7.2-16 16z'/%3E%3C/svg%3E");
308
+ }
309
+
310
+ .yi-ellipsis::before {
311
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M8 256a56 56 0 1 1 112 0A56 56 0 1 1 8 256zm160 0a56 56 0 1 1 112 0 56 56 0 1 1 -112 0zm216-56a56 56 0 1 1 0 112 56 56 0 1 1 0-112z'/%3E%3C/svg%3E");
312
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E%3Cpath d='M8 256a56 56 0 1 1 112 0A56 56 0 1 1 8 256zm160 0a56 56 0 1 1 112 0 56 56 0 1 1 -112 0zm216-56a56 56 0 1 1 0 112 56 56 0 1 1 0-112z'/%3E%3C/svg%3E");
313
+ }
314
+
315
+ .yi-backward-step::before {
316
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M267.5 440.6c9.5 7.9 22.8 9.7 34.1 4.4s18.4-16.6 18.4-29l0-320c0-12.4-7.2-23.7-18.4-29s-24.5-3.6-34.1 4.4l-192 160L64 241l0-145c0-17.7-14.3-32-32-32S0 78.3 0 96L0 416c0 17.7 14.3 32 32 32s32-14.3 32-32l0-145 11.5 9.6 192 160z'/%3E%3C/svg%3E");
317
+ mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath d='M267.5 440.6c9.5 7.9 22.8 9.7 34.1 4.4s18.4-16.6 18.4-29l0-320c0-12.4-7.2-23.7-18.4-29s-24.5-3.6-34.1 4.4l-192 160L64 241l0-145c0-17.7-14.3-32-32-32S0 78.3 0 96L0 416c0 17.7 14.3 32 32 32s32-14.3 32-32l0-145 11.5 9.6 192 160z'/%3E%3C/svg%3E");
318
+ }
@@ -4,8 +4,10 @@
4
4
  * Styling for the editable-input helpers in yui_inputs.js.
5
5
  *
6
6
  * The clear button itself is Bulma's `.delete`; here we only
7
- * position it over the input's right padding and show it only
8
- * while the field has content.
7
+ * position it over the input's right padding and reveal it only
8
+ * while the field has content AND is focused for editing — so a
9
+ * form full of pre-filled fields shows the ✕ on just the field the
10
+ * user is on, not on every populated one at once.
9
11
  *
10
12
  * Copyright (c) 2026, ArtGins.
11
13
  * All Rights Reserved.
@@ -27,6 +29,9 @@
27
29
  display: none;
28
30
  z-index: 4;
29
31
  }
30
- .control.has-clear .yui-input-clear.is-visible {
32
+ /* `.is-visible` (JS-toggled) means "has content"; the ✕ only actually
33
+ * appears while the control also holds focus, so an edit form doesn't
34
+ * light up an ✕ on every filled field — only on the one being edited. */
35
+ .control.has-clear:focus-within .yui-input-clear.is-visible {
31
36
  display: inline-block;
32
37
  }
package/src/yui_inputs.js CHANGED
@@ -4,7 +4,10 @@
4
4
  * Reusable editable-input helpers.
5
5
  *
6
6
  * NORM: every editable text/search input gets a clear (✕) button
7
- * that appears only while the field has content. `attach_clear()`
7
+ * that appears only while the field has content AND is focused for
8
+ * editing (`:focus-within`, see yui_inputs.css) — so a form full of
9
+ * pre-filled fields shows the ✕ on the field the user is on, not on
10
+ * every populated field at once. `attach_clear()`
8
11
  * wires it onto any Bulma `.control` that wraps an `<input>`:
9
12
  *
10
13
  * let $input = createElement2(["input", {class:"input"}, ...]);
@@ -28,6 +31,16 @@ import {createElement2} from "@yuneta/gobj-js";
28
31
  import i18next from "i18next";
29
32
 
30
33
 
34
+ /***************************************************************
35
+ * The ✕ shows only when there is something to clear AND the field
36
+ * can actually be edited — a readonly/disabled input (e.g. the pkey
37
+ * in "update" mode) must not offer it.
38
+ ***************************************************************/
39
+ function clear_visible($input)
40
+ {
41
+ return !!$input.value && !$input.readOnly && !$input.disabled;
42
+ }
43
+
31
44
  /***************************************************************
32
45
  * Add a clear (✕) button to a Bulma control wrapping an input.
33
46
  * Returns the button element (already appended to $control).
@@ -39,21 +52,29 @@ function attach_clear($control, $input, on_clear)
39
52
  }
40
53
  $control.classList.add("has-clear");
41
54
 
55
+ /* The key travels with the button: a `title` set from t() at build time
56
+ * is invisible to refresh_language(), so the tooltip stayed in the old
57
+ * language for the life of the input. */
42
58
  let $btn = createElement2(["button", {
43
- type: "button",
44
- class: "delete is-medium yui-input-clear",
45
- tabindex: "-1",
46
- title: i18next.t("clear"),
47
- "aria-label": i18next.t("clear")
59
+ type: "button",
60
+ class: "delete is-medium yui-input-clear",
61
+ tabindex: "-1",
62
+ title: i18next.t("clear"),
63
+ "aria-label": i18next.t("clear"),
64
+ "data-i18n-title": "clear",
65
+ "data-i18n-aria-label": "clear"
48
66
  }]);
49
67
 
50
68
  function sync()
51
69
  {
52
- $btn.classList.toggle("is-visible", !!$input.value);
70
+ $btn.classList.toggle("is-visible", clear_visible($input));
53
71
  }
54
72
 
55
73
  $input.addEventListener("input", sync);
56
74
  $btn.addEventListener("click", () => {
75
+ if($input.readOnly || $input.disabled) {
76
+ return;
77
+ }
57
78
  $input.value = "";
58
79
  $input.dispatchEvent(new Event("input", {bubbles: true}));
59
80
  if(typeof on_clear === "function") {
@@ -68,4 +89,26 @@ function attach_clear($control, $input, on_clear)
68
89
  return $btn;
69
90
  }
70
91
 
71
- export {attach_clear};
92
+ /***************************************************************
93
+ * Re-evaluate a clear (✕) after a PROGRAMMATIC change to its input
94
+ * — a value loaded into the form, or `readonly` toggled by the form
95
+ * mode — neither of which fires an `input` event. No-op on inputs
96
+ * that never got a clear.
97
+ ***************************************************************/
98
+ function refresh_clear($input)
99
+ {
100
+ if(!$input) {
101
+ return;
102
+ }
103
+ let $control = $input.closest(".control.has-clear");
104
+ if(!$control) {
105
+ return;
106
+ }
107
+ let $btn = $control.querySelector(".yui-input-clear");
108
+ if(!$btn) {
109
+ return;
110
+ }
111
+ $btn.classList.toggle("is-visible", clear_visible($input));
112
+ }
113
+
114
+ export {attach_clear, refresh_clear};