@yuneta/gobj-ui 5.16.0 → 6.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.
@@ -6467,6 +6467,7 @@ function build_form_field_conf(gobj, field_desc) {
6467
6467
  field_conf.extras.pattern = "^-?\\d+(\\.\\d+)?$";
6468
6468
  break;
6469
6469
  case "rowid":
6470
+ case "qualified":
6470
6471
  field_conf.tag = "input";
6471
6472
  field_conf.inputType = "text";
6472
6473
  break;
@@ -6608,7 +6609,8 @@ function build_fkey_ref$1(gobj, field_desc, value) {
6608
6609
  /************************************************************
6609
6610
  * Apply the form mode to the pkey field:
6610
6611
  * "update" -> pkey readonly, not required
6611
- * "create" -> pkey writable and required (readonly if rowid)
6612
+ * "create" -> pkey writable and required, unless the store
6613
+ * hands the key out itself (rowid, qualified)
6612
6614
  * Empty form_mode keeps the template-declared behaviour
6613
6615
  * (backward compatible).
6614
6616
  ************************************************************/
@@ -6621,8 +6623,8 @@ function apply_form_mode(gobj, $form) {
6621
6623
  log_error(sprintf("%s: form_mode '%s' but form has no pkey field '%s'", gobj_short_name(gobj), mode, pkey));
6622
6624
  return;
6623
6625
  }
6624
- let is_rowid = $input.field_desc && $input.field_desc.type === "rowid";
6625
- if (mode === "create" && !is_rowid) {
6626
+ let type = $input.field_desc ? $input.field_desc.type : "";
6627
+ if (mode === "create" && !(type === "rowid" || type === "qualified")) {
6626
6628
  $input.removeAttribute("readonly");
6627
6629
  $input.setAttribute("required", "");
6628
6630
  } else {
@@ -7400,6 +7402,7 @@ function template2columns(gobj, columns, template, sub_elements) {
7400
7402
  };
7401
7403
  break;
7402
7404
  case "rowid":
7405
+ case "qualified":
7403
7406
  column = null;
7404
7407
  break;
7405
7408
  case "boolean":
@@ -7765,6 +7768,7 @@ function treedb_value_2_form_value(gobj, field_desc, value) {
7765
7768
  case "enum":
7766
7769
  case "uuid":
7767
7770
  case "rowid":
7771
+ case "qualified":
7768
7772
  case "password":
7769
7773
  case "email":
7770
7774
  case "url": break;
@@ -7848,6 +7852,7 @@ function form_value_2_treedb_value(gobj, field_desc, value) {
7848
7852
  case "enum":
7849
7853
  case "uuid":
7850
7854
  case "rowid":
7855
+ case "qualified":
7851
7856
  case "password":
7852
7857
  case "email":
7853
7858
  case "url": break;
@@ -16925,7 +16930,8 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field) {
16925
16930
  case "email":
16926
16931
  case "tel":
16927
16932
  case "url":
16928
- case "rowid": break;
16933
+ case "rowid":
16934
+ case "qualified": break;
16929
16935
  case "integer": break;
16930
16936
  case "real": break;
16931
16937
  case "boolean": break;
@@ -17388,6 +17394,7 @@ function transform__form_value_2_treedb_value(gobj, col, value, operation) {
17388
17394
  const field_desc = treedb_get_field_desc(col);
17389
17395
  switch (field_desc.type) {
17390
17396
  case "rowid":
17397
+ case "qualified":
17391
17398
  if (operation === "create") value = "";
17392
17399
  break;
17393
17400
  case "string":
@@ -19754,11 +19761,26 @@ function register_c_yui_treedb_graph() {
19754
19761
  /***********************************************************************
19755
19762
  * c_yui_treedb_schema.js
19756
19763
  *
19757
- * Schema-graph landing (prototype): the treedb drawn as a GRAPH OF
19758
- * TOPICS one node per topic, one edge per hook/fkey relationship
19759
- * from the schema `descs` alone (no data, no backend calls). A
19760
- * node click opens that topic's table (a real hash navigation via
19761
- * the host-supplied `node_route`). An alternate landing to the
19764
+ * Schema-graph landing: the treedb drawn the way its `.c` literal
19765
+ * draws it in ASCII (treedb_schema_*.c, treedb_system_schema.c)
19766
+ * one CARD per topic listing its fields in schema order, one edge
19767
+ * per hook, from the row that declares the hook to the fkey row of
19768
+ * the child it names. Built from the schema `descs` alone: no data,
19769
+ * no backend calls.
19770
+ *
19771
+ * WHY THE CARD AND NOT A DOT. A topic is its fields; a schema is
19772
+ * read to find out what a topic holds and what links to what. A
19773
+ * graph of labelled dots answers neither, and the node graph next
19774
+ * door (C_G6_NODES_TREE) answers a different question entirely —
19775
+ * it draws the RECORDS, so on a treedb whose records are schemas
19776
+ * it draws one box per column, hundreds of them, each labelled by
19777
+ * a pkey that may be a rowid. This view draws the schema itself.
19778
+ *
19779
+ * The marks are the notation of the `.c` literals, so the drawing
19780
+ * and the source read the same — see schema_rows().
19781
+ *
19782
+ * A node click opens that topic's table (a real hash navigation
19783
+ * via the host-supplied `node_route`). An alternate landing to the
19762
19784
  * topic cards, in the spirit of "every treedb is a graph".
19763
19785
  *
19764
19786
  * Copyright (c) 2026, ArtGins.
@@ -19768,6 +19790,27 @@ function register_c_yui_treedb_graph() {
19768
19790
  * Constants
19769
19791
  ***************************************************************/
19770
19792
  var GCLASS_NAME$2 = "C_YUI_TREEDB_SCHEMA";
19793
+ /************************************************************
19794
+ * Geometry of a card, in one place: the row height is what
19795
+ * turns a field's index into the vertical position of its
19796
+ * port, so an edge leaves the very row that declares the
19797
+ * hook and lands on the row that declares the fkey.
19798
+ ************************************************************/
19799
+ var CARD_HEADER_H = 30;
19800
+ var CARD_ROW_H = 19;
19801
+ var CARD_PAD_V = 7;
19802
+ var CARD_MIN_W = 210;
19803
+ var CARD_MAX_W = 340;
19804
+ var topic_colors = [
19805
+ "rgb(237, 201, 73)",
19806
+ "rgb(118, 183, 178)",
19807
+ "rgb(255, 157, 167)",
19808
+ "rgb(175, 122, 161)",
19809
+ "rgb(89, 161, 79)",
19810
+ "rgb(186, 176, 171)",
19811
+ "rgb(66, 146, 198)"
19812
+ ];
19813
+ var SYSTEM_TOPIC_COLOR = "rgb(148, 163, 184)";
19771
19814
  /***************************************************************
19772
19815
  * Data
19773
19816
  ***************************************************************/
@@ -19829,48 +19872,254 @@ function build_ui$2(gobj) {
19829
19872
  gobj_write_attr(gobj, "$container", $container);
19830
19873
  }
19831
19874
  /************************************************************
19875
+ * The shape of a hook/fkey field, from the type it declares.
19876
+ * `dict` and `object` are the SAME thing to treedb (both build
19877
+ * a json object keyed by child id), and so are `list` and
19878
+ * `array` — see the hook/fkey switches in tr_treedb.c. A
19879
+ * `string` holds one reference, so it draws as one.
19880
+ ************************************************************/
19881
+ function hook_mark(type) {
19882
+ if (type === "dict" || type === "object") return "{}";
19883
+ if (type === "list" || type === "array") return "[]";
19884
+ return "()";
19885
+ }
19886
+ /************************************************************
19887
+ * The fields of one topic, as the schema declares them.
19888
+ *
19889
+ * The marks are the notation of the schema `.c` literals
19890
+ * (treedb_schema_*.c, treedb_system_schema.c), so the
19891
+ * drawing and the source read the same:
19892
+ * {} dict hook (N unique children)
19893
+ * [] list hook (n not-unique children)
19894
+ * () 1 child
19895
+ * (↖) 1 fkey (1 parent)
19896
+ * [↖] n fkeys (n parents)
19897
+ * {↖} N fkeys (N parents)
19898
+ * * required
19899
+ * # the primary key
19900
+ *
19901
+ * A column can be BOTH hook and fkey, so the flags are read
19902
+ * here rather than through treedb_get_field_desc's single
19903
+ * `type`, which keeps only the last flag it saw.
19904
+ ************************************************************/
19905
+ function schema_rows(desc) {
19906
+ let rows = [];
19907
+ if (!is_object(desc) || !Array.isArray(desc.cols)) return rows;
19908
+ for (let col of desc.cols) {
19909
+ if (!is_object(col) || !col.id) continue;
19910
+ let flags = Array.isArray(col.flag) ? col.flag : [];
19911
+ let is_hook = flags.indexOf("hook") >= 0;
19912
+ let is_fkey = flags.indexOf("fkey") >= 0;
19913
+ let mark = "";
19914
+ if (is_hook) mark = hook_mark(col.type);
19915
+ if (is_fkey) {
19916
+ let fmark = hook_mark(col.type).replace("}", "↖}").replace("]", "↖]").replace(")", "↖)");
19917
+ mark = mark ? mark + " " + fmark : fmark;
19918
+ }
19919
+ rows.push({
19920
+ name: col.id,
19921
+ type: col.type || "",
19922
+ mark,
19923
+ is_hook,
19924
+ is_fkey,
19925
+ is_pkey: col.id === desc.pkey,
19926
+ required: flags.indexOf("required") >= 0 || flags.indexOf("notnull") >= 0
19927
+ });
19928
+ }
19929
+ return rows;
19930
+ }
19931
+ /************************************************************
19932
+ * Size of the card that holds those rows. The width follows
19933
+ * the longest line so a long field name is not clipped into
19934
+ * an ellipsis — the point of the drawing is to read them.
19935
+ ************************************************************/
19936
+ function card_size(rows) {
19937
+ let longest = 0;
19938
+ for (let row of rows) {
19939
+ let len = row.name.length + (row.mark ? row.mark.length + 2 : 0) + 2;
19940
+ if (len > longest) longest = len;
19941
+ }
19942
+ let w = Math.round(26 + longest * 6.6);
19943
+ if (w < CARD_MIN_W) w = CARD_MIN_W;
19944
+ if (w > CARD_MAX_W) w = CARD_MAX_W;
19945
+ let h = 44 + rows.length * CARD_ROW_H;
19946
+ return [w, h];
19947
+ }
19948
+ /************************************************************
19949
+ * Vertical placement of a field's port, as a fraction of the
19950
+ * card height: the centre of its own row.
19951
+ ************************************************************/
19952
+ function row_placement(index, height) {
19953
+ return (37 + index * CARD_ROW_H + CARD_ROW_H / 2) / height;
19954
+ }
19955
+ /************************************************************
19956
+ * The card itself. Header = topic name on its topic colour;
19957
+ * body = one line per field, in schema order.
19958
+ ************************************************************/
19959
+ function build_card_innerHTML(topic, rows, color, dark) {
19960
+ let surface = dark ? "#1b2230" : "#ffffff";
19961
+ let bg = dark ? `color-mix(in srgb, ${color} 18%, #232b38)` : `color-mix(in srgb, ${color} 6%, ${surface})`;
19962
+ let border = dark ? `color-mix(in srgb, ${color} 85%, #ffffff)` : color;
19963
+ let header_bg = dark ? `color-mix(in srgb, ${color} 45%, #2c3542)` : `color-mix(in srgb, ${color} 28%, ${surface})`;
19964
+ let title_color = dark ? "#e8eaed" : "#0f172a";
19965
+ let text_color = dark ? "#d3d8de" : "#334155";
19966
+ let mute_color = dark ? "#98a2b0" : "#7c8694";
19967
+ let rule_color = dark ? "rgba(255,255,255,0.06)" : "rgba(15,23,42,0.05)";
19968
+ let shadow = dark ? "0 1px 3px rgba(0,0,0,0.45), 0 1px 2px rgba(0,0,0,0.30)" : "0 1px 3px rgba(15,23,42,0.12), 0 1px 2px rgba(15,23,42,0.06)";
19969
+ let rows_html = "";
19970
+ for (let row of rows) {
19971
+ let lead = row.is_pkey ? "#" : row.required ? "*" : "";
19972
+ let name_weight = row.is_pkey || row.required ? "600" : "400";
19973
+ let name_color = row.is_hook || row.is_fkey ? title_color : text_color;
19974
+ rows_html += ` <div class="TREEDB_SCHEMA_FIELD" style="
19975
+ display: flex; align-items: center; gap: 6px;
19976
+ height: ${CARD_ROW_H}px; line-height: ${CARD_ROW_H}px;
19977
+ padding: 0 10px; border-top: 1px solid ${rule_color};
19978
+ ">
19979
+ <span style="
19980
+ width: 8px; flex: 0 0 8px; text-align: center;
19981
+ color: ${mute_color}; font-size: 11px;
19982
+ ">${lead}</span>
19983
+ <span style="
19984
+ flex: 1 1 auto; min-width: 0; overflow: hidden;
19985
+ text-overflow: ellipsis; white-space: nowrap;
19986
+ font-size: 12px; font-weight: ${name_weight}; color: ${name_color};
19987
+ ">${escapeHtml(row.name)}</span>
19988
+ <span style="
19989
+ flex: 0 0 auto; font-size: 11px; color: ${mute_color};
19990
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
19991
+ ">${escapeHtml(row.mark)}</span>
19992
+ </div>
19993
+ `;
19994
+ }
19995
+ return `
19996
+ <div class="TREEDB_SCHEMA_CARD" title="${escapeHtml(topic)}" style="
19997
+ box-sizing: border-box;
19998
+ width: 100%;
19999
+ height: 100%;
20000
+ background: ${bg};
20001
+ border: 1.5px solid ${border};
20002
+ border-radius: 8px;
20003
+ box-shadow: ${shadow};
20004
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
20005
+ display: flex;
20006
+ flex-direction: column;
20007
+ overflow: hidden;
20008
+ cursor: pointer;
20009
+ ">
20010
+ <div class="TREEDB_SCHEMA_CARD_HEADER" style="
20011
+ height: ${CARD_HEADER_H}px; line-height: ${CARD_HEADER_H}px;
20012
+ flex: 0 0 ${CARD_HEADER_H}px;
20013
+ padding: 0 10px; background: ${header_bg}; color: ${title_color};
20014
+ font-size: 13px; font-weight: 700;
20015
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
20016
+ ">${escapeHtml(topic)}</div>
20017
+ <div class="TREEDB_SCHEMA_CARD_BODY" style="
20018
+ flex: 1 1 auto; padding: ${CARD_PAD_V}px 0; overflow: hidden;
20019
+ ">
20020
+ ${rows_html} </div>
20021
+ </div>
20022
+ `;
20023
+ }
20024
+ /************************************************************
19832
20025
  * Derive {nodes, edges} from the schema.
19833
- * Node = a topic. Edge = a hook (parent -> child) or an
19834
- * fkey (child -> parent, reversed to parent -> child), deduped.
20026
+ *
20027
+ * Node = a topic, drawn as the card the `.c` literal draws in
20028
+ * ASCII: its name and its fields. Edge = a hook, from the row
20029
+ * that declares it to the fkey row of the child it names —
20030
+ * `'hook': {'users': 'departments'}` says both ends, so the
20031
+ * arrow can land where the `.c` drawing lands it. An fkey
20032
+ * whose parent declares no hook still gets its edge, or a
20033
+ * half-declared schema would draw as disconnected.
20034
+ *
19835
20035
  * Left-to-right dagre follows the parent -> child data flow.
19836
20036
  ************************************************************/
19837
20037
  function schema_to_graph(gobj) {
19838
20038
  let descs = gobj_read_attr(gobj, "descs");
19839
20039
  let system = gobj_read_bool_attr(gobj, "system");
20040
+ let dark = yui_is_dark();
19840
20041
  let nodes = [];
19841
- let topic_set = {};
20042
+ let cards = {};
19842
20043
  if (!is_object(descs)) return {
19843
20044
  nodes,
19844
20045
  edges: []
19845
20046
  };
20047
+ let idx = 0;
19846
20048
  for (let topic of Object.keys(descs)) {
19847
- if (!system && topic.substring(0, 2) === "__") continue;
19848
- topic_set[topic] = true;
20049
+ let is_system = topic.substring(0, 2) === "__";
20050
+ if (!system && is_system) continue;
20051
+ let rows = schema_rows(descs[topic]);
20052
+ let color;
20053
+ if (is_system) color = SYSTEM_TOPIC_COLOR;
20054
+ else {
20055
+ color = topic_colors[idx % topic_colors.length];
20056
+ idx++;
20057
+ }
20058
+ cards[topic] = {
20059
+ rows,
20060
+ size: card_size(rows),
20061
+ color
20062
+ };
20063
+ }
20064
+ for (let topic of Object.keys(cards)) {
20065
+ let card = cards[topic];
20066
+ let [w, h] = card.size;
20067
+ let ports = [];
20068
+ for (let i = 0; i < card.rows.length; i++) {
20069
+ let row = card.rows[i];
20070
+ if (!row.is_hook && !row.is_fkey) continue;
20071
+ ports.push({
20072
+ key: row.name,
20073
+ placement: [row.is_hook ? 1 : 0, row_placement(i, h)],
20074
+ fill: card.color,
20075
+ stroke: getStrokeColor(card.color)
20076
+ });
20077
+ }
19849
20078
  nodes.push({
19850
20079
  id: topic,
19851
- data: { topic_name: topic }
20080
+ type: "html",
20081
+ style: {
20082
+ size: [w, h],
20083
+ dx: -w / 2,
20084
+ dy: -h / 2,
20085
+ innerHTML: build_card_innerHTML(topic, card.rows, card.color, dark),
20086
+ port: ports.length > 0,
20087
+ ports,
20088
+ portR: 3,
20089
+ portLineWidth: 1
20090
+ },
20091
+ data: {
20092
+ topic_name: topic,
20093
+ rows: card.rows,
20094
+ color: card.color
20095
+ }
19852
20096
  });
19853
20097
  }
19854
20098
  let edge_seen = {};
19855
20099
  let edges = [];
19856
- let add_edge = (source, target) => {
19857
- if (!topic_set[source] || !topic_set[target] || source === target) return;
19858
- let key = source + "" + target;
20100
+ let add_edge = (source, source_port, target, target_port) => {
20101
+ if (!cards[source] || !cards[target]) return;
20102
+ let key = `${source}|${source_port}|${target}|${target_port}`;
19859
20103
  if (edge_seen[key]) return;
19860
20104
  edge_seen[key] = true;
19861
20105
  edges.push({
19862
20106
  id: key,
20107
+ type: "cubic",
19863
20108
  source,
19864
- target
20109
+ target,
20110
+ style: {
20111
+ sourcePort: source_port,
20112
+ targetPort: target_port
20113
+ }
19865
20114
  });
19866
20115
  };
19867
- for (let topic of Object.keys(descs)) {
20116
+ for (let topic of Object.keys(cards)) {
19868
20117
  let desc = descs[topic];
19869
20118
  if (!is_object(desc) || !Array.isArray(desc.cols)) continue;
19870
20119
  for (let col of desc.cols) {
19871
- if (!col) continue;
19872
- if (is_object(col.hook)) for (let child of Object.keys(col.hook)) add_edge(topic, child);
19873
- else if (is_object(col.fkey)) for (let parent of Object.keys(col.fkey)) add_edge(parent, topic);
20120
+ if (!is_object(col) || !col.id) continue;
20121
+ if (is_object(col.hook)) for (let child of Object.keys(col.hook)) add_edge(topic, col.id, child, col.hook[child]);
20122
+ if (is_object(col.fkey)) for (let parent of Object.keys(col.fkey)) add_edge(parent, col.fkey[parent], topic, col.id);
19874
20123
  }
19875
20124
  }
19876
20125
  return {
@@ -19879,26 +20128,15 @@ function schema_to_graph(gobj) {
19879
20128
  };
19880
20129
  }
19881
20130
  /************************************************************
19882
- * The theme-dependent styles, in ONE place: they are applied
19883
- * as the graph is built and re-applied on a theme switch
20131
+ * The theme-dependent edge style, in ONE place: applied as
20132
+ * the graph is built and re-applied on a theme switch
19884
20133
  * (ac_theme), which restyles the LIVE graph — rebuilding it
19885
20134
  * would drop the user's zoom/pan and any dragged node.
19886
20135
  ************************************************************/
19887
- function node_style(dark) {
19888
- return {
19889
- size: 40,
19890
- fill: "#5B8FF9",
19891
- labelText: (d) => d.id,
19892
- labelPlacement: "bottom",
19893
- labelFill: dark ? "#e6e6e6" : "#333333",
19894
- labelBackground: true,
19895
- labelBackgroundFill: dark ? "rgba(0,0,0,0.5)" : "rgba(255,255,255,0.7)",
19896
- cursor: "pointer"
19897
- };
19898
- }
19899
20136
  function edge_style(dark) {
19900
20137
  return {
19901
- stroke: dark ? "#666666" : "#bbbbbb",
20138
+ stroke: dark ? "#7a8593" : "#9aa4b2",
20139
+ lineWidth: 1.2,
19902
20140
  endArrow: true
19903
20141
  };
19904
20142
  }
@@ -19930,13 +20168,12 @@ function build_graph$1(gobj) {
19930
20168
  container: $container,
19931
20169
  autoResize: true,
19932
20170
  data,
19933
- node: { style: node_style(dark) },
19934
20171
  edge: { style: edge_style(dark) },
19935
20172
  layout: {
19936
20173
  type: "antv-dagre",
19937
20174
  rankdir: "LR",
19938
- nodesep: 24,
19939
- ranksep: 60
20175
+ nodesep: 28,
20176
+ ranksep: 90
19940
20177
  },
19941
20178
  behaviors: [
19942
20179
  "zoom-canvas",
@@ -20021,8 +20258,17 @@ function ac_theme$1(gobj, event, kw, src) {
20021
20258
  let dark = kw && kw.theme ? kw.theme === "dark" : yui_is_dark();
20022
20259
  try {
20023
20260
  graph.setTheme(dark ? "dark" : "light");
20024
- graph.setNode({ style: node_style(dark) });
20025
20261
  graph.setEdge({ style: edge_style(dark) });
20262
+ let updates = [];
20263
+ for (let node of graph.getNodeData()) {
20264
+ let data = node.data;
20265
+ if (!is_object(data) || !Array.isArray(data.rows)) continue;
20266
+ updates.push({
20267
+ id: node.id,
20268
+ style: { innerHTML: build_card_innerHTML(data.topic_name, data.rows, data.color, dark) }
20269
+ });
20270
+ }
20271
+ if (updates.length > 0) graph.updateNodeData(updates);
20026
20272
  graph.draw().catch((e) => {
20027
20273
  log_error(`${gobj_short_name(gobj)}: schema graph redraw failed: ${e}`);
20028
20274
  });
@@ -20087,6 +20333,61 @@ function register_c_yui_treedb_schema() {
20087
20333
  return create_gclass$2(GCLASS_NAME$2);
20088
20334
  }
20089
20335
  //#endregion
20336
+ //#region src/treedb_node_label.js
20337
+ /***********************************************************************
20338
+ * treedb_node_label.js
20339
+ *
20340
+ * Pure, testable logic behind the label of a node in the treedb
20341
+ * graph: WHAT A RECORD IS CALLED, which is not always what it is
20342
+ * keyed by. Kept out of the gclass so it can be unit-tested with
20343
+ * no DOM and no G6.
20344
+ *
20345
+ * Copyright (c) 2026, ArtGins.
20346
+ * All Rights Reserved.
20347
+ ***********************************************************************/
20348
+ /************************************************************
20349
+ * What a node is CALLED, which is not always what it is
20350
+ * keyed by.
20351
+ *
20352
+ * A topic whose id column is flagged `rowid`, `uuid` or
20353
+ * `qualified` keys its records by something that is not the
20354
+ * name — a counter, a random string, or the name with every
20355
+ * ancestor in front of it — and the name a human knows the
20356
+ * record by lives in the secondary key the topic declares
20357
+ * (`pkey2s`, carried in the desc since SDK > 7.13.0). The
20358
+ * `topics` and `cols` topics of treedb_system_schema are the
20359
+ * case that forced this: named in `value`, so every card in
20360
+ * the graph read "181", "225", "193" while they were keyed by
20361
+ * rowid, and reads the whole path now that they are
20362
+ * qualified.
20363
+ *
20364
+ * The pkey stays reachable: it is the card's tooltip.
20365
+ *
20366
+ * Falls back to the id whenever the schema does not say
20367
+ * otherwise — an older node whose desc carries no `pkey2s`
20368
+ * included.
20369
+ ************************************************************/
20370
+ function node_label(desc, record) {
20371
+ let id = record.id;
20372
+ let id_col = null;
20373
+ if (Array.isArray(desc.cols)) {
20374
+ for (let col of desc.cols) if (col && col.id === (desc.pkey || "id")) {
20375
+ id_col = col;
20376
+ break;
20377
+ }
20378
+ }
20379
+ if (!id_col || !Array.isArray(id_col.flag)) return id;
20380
+ if (id_col.flag.indexOf("rowid") < 0 && id_col.flag.indexOf("uuid") < 0 && id_col.flag.indexOf("qualified") < 0) return id;
20381
+ let pkey2s = desc.pkey2s;
20382
+ if (typeof pkey2s === "string") pkey2s = pkey2s ? [pkey2s] : [];
20383
+ if (!Array.isArray(pkey2s)) return id;
20384
+ for (let name of pkey2s) {
20385
+ let value = record[name];
20386
+ if (typeof value === "string" && value.length > 0) return value;
20387
+ }
20388
+ return id;
20389
+ }
20390
+ //#endregion
20090
20391
  //#region node_modules/@babel/runtime/helpers/esm/typeof.js
20091
20392
  function _typeof(o) {
20092
20393
  "@babel/helpers - typeof";
@@ -47619,19 +47920,19 @@ function create_topic_node(gobj, desc, record) {
47619
47920
  style.size = [116, 40];
47620
47921
  style.dx = -58;
47621
47922
  style.dy = -20;
47622
- style.innerHTML = build_chip_innerHTML(desc.color, priv.theme, record.icon, record.id);
47923
+ style.innerHTML = build_chip_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), record.id);
47623
47924
  } else if (node_treedb_type === "extended") {
47624
47925
  node_graph_type = "html";
47625
47926
  style.size = [144, 66];
47626
47927
  style.dx = -72;
47627
47928
  style.dy = -33;
47628
- style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, record.id, desc.topic_name, true);
47929
+ style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, true, record.id);
47629
47930
  } else {
47630
47931
  node_graph_type = "html";
47631
47932
  style.size = [172, 96];
47632
47933
  style.dx = -86;
47633
47934
  style.dy = -48;
47634
- style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, record.id, desc.topic_name);
47935
+ style.innerHTML = build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, false, record.id);
47635
47936
  }
47636
47937
  let topic_props = priv._graph_properties[desc.topic_name];
47637
47938
  let topic_defaults = is_object(topic_props) && is_object(topic_props.defaults) ? topic_props.defaults : null;
@@ -47699,15 +48000,15 @@ function update_topic_node(gobj, desc, node_name, record) {
47699
48000
  nodedata.data.record = record;
47700
48001
  if (desc.node_treedb_type === "child") graph.updateNodeData([{
47701
48002
  id: node_name,
47702
- style: { innerHTML: build_chip_innerHTML(desc.color, priv.theme, record.icon, record.id) }
48003
+ style: { innerHTML: build_chip_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), record.id) }
47703
48004
  }]);
47704
48005
  else if (desc.node_treedb_type === "extended") graph.updateNodeData([{
47705
48006
  id: node_name,
47706
- style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, record.id, desc.topic_name, true) }
48007
+ style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, true, record.id) }
47707
48008
  }]);
47708
48009
  else if (desc.node_treedb_type === "hierarchical") graph.updateNodeData([{
47709
48010
  id: node_name,
47710
- style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, record.id, desc.topic_name) }
48011
+ style: { innerHTML: build_node_innerHTML(desc.color, priv.theme, record.icon, node_label(desc, record), desc.topic_name, false, record.id) }
47711
48012
  }]);
47712
48013
  } catch (e) {
47713
48014
  log_error(e.message);
@@ -49614,7 +49915,7 @@ function show_node_popover(gobj) {
49614
49915
  };
49615
49916
  if (node_graph_type === "hierarchical") {
49616
49917
  let record = nodeData.data.record || {};
49617
- updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, record.id, nodeData.data.desc.topic_name);
49918
+ updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, node_label(nodeData.data.desc, record), nodeData.data.desc.topic_name, false, record.id);
49618
49919
  }
49619
49920
  graph.updateNodeData([{
49620
49921
  id: node_id,
@@ -49654,7 +49955,7 @@ function show_node_popover(gobj) {
49654
49955
  };
49655
49956
  if (node_graph_type === "hierarchical") {
49656
49957
  let record = nodeData.data.record || {};
49657
- restoreStyle.innerHTML = build_node_innerHTML(origFill, priv.theme, record.icon, record.id, nodeData.data.desc.topic_name);
49958
+ restoreStyle.innerHTML = build_node_innerHTML(origFill, priv.theme, record.icon, node_label(nodeData.data.desc, record), nodeData.data.desc.topic_name, false, record.id);
49658
49959
  }
49659
49960
  graph.updateNodeData([{
49660
49961
  id: node_id,
@@ -49755,15 +50056,15 @@ function refresh_html_nodes_theme(gobj, theme) {
49755
50056
  let record = nd.data.record || {};
49756
50057
  if (tt === "hierarchical") updates.push({
49757
50058
  id: nodes[i].id,
49758
- style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, record.id, nd.data.desc.topic_name) }
50059
+ style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), nd.data.desc.topic_name, false, record.id) }
49759
50060
  });
49760
50061
  else if (tt === "child") updates.push({
49761
50062
  id: nodes[i].id,
49762
- style: { innerHTML: build_chip_innerHTML(nd.data.desc.color, theme, record.icon, record.id) }
50063
+ style: { innerHTML: build_chip_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), record.id) }
49763
50064
  });
49764
50065
  else if (tt === "extended") updates.push({
49765
50066
  id: nodes[i].id,
49766
- style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, record.id, nd.data.desc.topic_name, true) }
50067
+ style: { innerHTML: build_node_innerHTML(nd.data.desc.color, theme, record.icon, node_label(nd.data.desc, record), nd.data.desc.topic_name, true, record.id) }
49767
50068
  });
49768
50069
  }
49769
50070
  if (updates.length > 0) graph.updateNodeData(updates);
@@ -49795,7 +50096,8 @@ function refresh_default_edges_theme(gobj, theme) {
49795
50096
  * but lighter (1px border, no shadow, single line). The name is
49796
50097
  * always legible (ellipsis + native title tooltip on overflow).
49797
50098
  ************************************************************/
49798
- function build_chip_innerHTML(color, theme, icon, id) {
50099
+ function build_chip_innerHTML(color, theme, icon, label, key) {
50100
+ let title = key || label;
49799
50101
  let dark = theme === "dark";
49800
50102
  let bg = dark ? `color-mix(in srgb, ${color} 30%, #2c3542)` : `color-mix(in srgb, ${color} 10%, ${dark ? "#1b2230" : "#ffffff"})`;
49801
50103
  let border = dark ? `color-mix(in srgb, ${color} 85%, #ffffff)` : color;
@@ -49806,7 +50108,7 @@ function build_chip_innerHTML(color, theme, icon, id) {
49806
50108
  margin-right: 6px; flex: 0 0 auto;
49807
50109
  "/>`;
49808
50110
  return `
49809
- <div title="${escapeHtml(id)}" style="
50111
+ <div title="${escapeHtml(title)}" style="
49810
50112
  box-sizing: border-box;
49811
50113
  width: 100%;
49812
50114
  height: 100%;
@@ -49823,7 +50125,7 @@ function build_chip_innerHTML(color, theme, icon, id) {
49823
50125
  ">${icon_html}<span style="
49824
50126
  font-size: 12px; font-weight: 600; line-height: 1;
49825
50127
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
49826
- ">${escapeHtml(id)}</span>
50128
+ ">${escapeHtml(label)}</span>
49827
50129
  </div>
49828
50130
  `;
49829
50131
  }
@@ -49837,7 +50139,8 @@ function build_chip_innerHTML(color, theme, icon, id) {
49837
50139
  * colour is kept (per-topic differentiation) but softened via
49838
50140
  * color-mix instead of a harsh saturated fill. Theme-aware.
49839
50141
  ************************************************************/
49840
- function build_node_innerHTML(color, theme, icon, id, topic_name, structural) {
50142
+ function build_node_innerHTML(color, theme, icon, label, topic_name, structural, key) {
50143
+ let title = key || label;
49841
50144
  let dark = theme === "dark";
49842
50145
  let surface = dark ? "#1b2230" : "#ffffff";
49843
50146
  let bg, border, border_style;
@@ -49867,7 +50170,7 @@ function build_node_innerHTML(color, theme, icon, id, topic_name, structural) {
49867
50170
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
49868
50171
  ">${escapeHtml(topic_name)}</div>`;
49869
50172
  return `
49870
- <div title="${escapeHtml(id)}" style="
50173
+ <div title="${escapeHtml(title)}" style="
49871
50174
  box-sizing: border-box;
49872
50175
  width: 100%;
49873
50176
  height: 100%;
@@ -49890,7 +50193,7 @@ function build_node_innerHTML(color, theme, icon, id, topic_name, structural) {
49890
50193
  max-width: 100%; overflow: hidden; text-overflow: ellipsis;
49891
50194
  display: -webkit-box; -webkit-line-clamp: 2;
49892
50195
  -webkit-box-orient: vertical; word-break: break-word;
49893
- ">${escapeHtml(id)}</div>${sub_html}
50196
+ ">${escapeHtml(label)}</div>${sub_html}
49894
50197
  </div>
49895
50198
  `;
49896
50199
  }
@@ -50111,7 +50414,7 @@ function apply_node_properties(gobj, node_id, fill, stroke, lineWidth, scope) {
50111
50414
  };
50112
50415
  if (nd.data.desc.node_treedb_type === "hierarchical") {
50113
50416
  let record = nd.data.record || {};
50114
- updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, record.id, nd.data.desc.topic_name);
50417
+ updateStyle.innerHTML = build_node_innerHTML(fill, priv.theme, record.icon, node_label(nd.data.desc, record), nd.data.desc.topic_name, false, record.id);
50115
50418
  }
50116
50419
  updates.push({
50117
50420
  id: nodes[i].id,