@yuneta/gobj-ui 7.23.43 → 7.23.44

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.
@@ -6040,173 +6040,460 @@ function yui_watch_theme(gobj, event = "EV_THEME") {
6040
6040
  }
6041
6041
  } };
6042
6042
  }
6043
- //#endregion
6044
- //#region src/c_yui_form.js
6045
- /***********************************************************************
6046
- * c_yui_form.js
6047
- *
6048
- * Form (in Window or not)
6049
- *
6050
- * Copyright (c) 2024-2026, ArtGins.
6051
- * All Rights Reserved.
6052
- ***********************************************************************/
6053
- /***************************************************************
6054
- * Constants
6055
- ***************************************************************/
6056
- var GCLASS_NAME$16 = "C_YUI_FORM";
6057
- /***************************************************************
6058
- * Data
6059
- ***************************************************************/
6060
- var attrs_table$16 = [
6061
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of publishing messages"),
6062
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "$parent", 0, null, "$container will be appended to $parent if not null, else do it manually"),
6063
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "template", 0, "<div>You must supply a template for form</div>", "Template for form, object -> TEMPLATE, array -> DESC"),
6064
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "record", 0, "{}", "Data to form"),
6065
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "placeholders", 0, "{}", "Placeholder values for form fields"),
6066
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "fkey_options", 0, "{}", "Options for fkey fields: {topic_name: [ids or {id} records]}, read at build time"),
6067
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "form_mode", 0, "", "'create' (pkey editable+required) or 'update' (pkey readonly); empty = no pkey handling"),
6068
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "render_mode", 0, "exec", "'exec' interprets structured cols (template->sub-form, table->grid, coordinates->picker); 'edit' shows them as raw JSON editors (record-editing, e.g. the treedb topic form)"),
6069
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "pkey", 0, "id", "Primary key field name, used by form_mode"),
6070
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "topic_name", 0, "", "Treedb topic name: labels use the i18n cascade '<topic>.<col>' -> '<col>' -> header (like table headers)"),
6071
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "editable", 0, false, "Default is editable, set false to readonly"),
6072
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "selectable", 0, true, "Rows selectable with 'click' (BAD with editable=true)"),
6073
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_checkbox", 0, false, "Show a first column with checkbox to select rows"),
6074
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_rownum", 0, false, "Print first column with row number"),
6075
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_delete_row_btn", 0, false, "Print last column with delete row button"),
6076
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "toolbar", 0, [
6077
- "save",
6078
- "undo",
6079
- "clear",
6080
- "copy",
6081
- "paste"
6082
- ], "Toolbar buttons to show, in order: save, undo, clear, copy, paste. [] hides the toolbar"),
6083
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "tabulator_settings", 0, {
6084
- layout: "fitDataFill",
6085
- validationMode: "highlight",
6086
- columnDefaults: { resizable: true },
6087
- maxHeight: "100%"
6088
- }, "Default settings for Tabulator"),
6089
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "$container", 0, null, "Container element"),
6090
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "form_id", 0, "", "Form identifier"),
6091
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "initialized", 0, false, "Indicates if it is initialized"),
6092
- (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "changes", 0, false, "Indicates if changes have been made"),
6093
- (0, _yuneta_gobj_js.SDATA_END)()
6094
- ];
6095
- var PRIVATE_DATA$16 = {};
6096
- var __gclass__$16 = null;
6097
- /***************************
6098
- * Framework Methods
6099
- ***************************/
6100
6043
  /************************************************************
6101
- * Framework Method create
6044
+ * JSON type discriminator (viewer vocabulary)
6102
6045
  ************************************************************/
6103
- function mt_create$16(gobj) {
6104
- let subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
6105
- if (!subscriber) subscriber = (0, _yuneta_gobj_js.gobj_parent)(gobj);
6106
- (0, _yuneta_gobj_js.gobj_subscribe_event)(gobj, null, {}, subscriber);
6107
- let name = (0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(gobj));
6108
- (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "form_id", "form" + name);
6109
- build_ui$14(gobj);
6046
+ function json_type(value) {
6047
+ if (value === null) return "null";
6048
+ if (Array.isArray(value)) return "array";
6049
+ switch (typeof value) {
6050
+ case "string": return "string";
6051
+ case "number": return "number";
6052
+ case "boolean": return "boolean";
6053
+ case "object": return "object";
6054
+ default: return "unknown";
6055
+ }
6110
6056
  }
6111
6057
  /************************************************************
6112
- * Framework Method destroy
6113
- * In this point, all children
6114
- * and subscriptions are already deleted.
6058
+ * Detect a kw_collapse() sentinel.
6059
+ *
6060
+ * The kernel replaces an over-limit dict with:
6061
+ * { "__collapsed__": { "path": ..., "size": N } }
6062
+ * and an over-limit array with:
6063
+ * [ { "__collapsed__": { "path": ..., "size": N } } ]
6064
+ *
6065
+ * Returns { size, path, is_array } or null.
6115
6066
  ************************************************************/
6116
- function mt_destroy$16(gobj) {
6117
- destroy_ui$10(gobj);
6067
+ function is_collapsed(value) {
6068
+ if (value && typeof value === "object" && !Array.isArray(value)) {
6069
+ let keys = Object.keys(value);
6070
+ if (keys.length === 1 && keys[0] === "__collapsed__") {
6071
+ let c = value.__collapsed__ || {};
6072
+ return {
6073
+ size: c.size,
6074
+ path: c.path,
6075
+ is_array: false
6076
+ };
6077
+ }
6078
+ return null;
6079
+ }
6080
+ if (Array.isArray(value) && value.length === 1) {
6081
+ let e = value[0];
6082
+ if (e && typeof e === "object" && !Array.isArray(e) && Object.keys(e).length === 1 && e.__collapsed__) {
6083
+ let c = e.__collapsed__ || {};
6084
+ return {
6085
+ size: c.size,
6086
+ path: c.path,
6087
+ is_array: true
6088
+ };
6089
+ }
6090
+ }
6091
+ return null;
6118
6092
  }
6119
6093
  /************************************************************
6120
- * Framework Method start
6094
+ * Join / split absolute path segments (kernel convention)
6121
6095
  ************************************************************/
6122
- function mt_start$15(gobj) {
6123
- let record = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "record");
6124
- if (record) (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_LOAD_RECORD", record, gobj);
6096
+ function seg_join(segments) {
6097
+ return segments.map(function(s) {
6098
+ return String(s);
6099
+ }).join("`");
6100
+ }
6101
+ function seg_split(path) {
6102
+ if (path === "" || path === null || path === void 0) return [];
6103
+ return path.split("`");
6125
6104
  }
6126
6105
  /************************************************************
6127
- * Framework Method stop
6106
+ * Walk a JSON tree by absolute segments; undefined if the
6107
+ * path does not resolve.
6128
6108
  ************************************************************/
6129
- function mt_stop$15(gobj) {}
6130
- /***************************
6131
- * Local Methods
6132
- ***************************/
6109
+ function get_by_segments(root, segments) {
6110
+ let v = root;
6111
+ for (let seg of segments) {
6112
+ if (v === null || v === void 0) return;
6113
+ if (Array.isArray(v)) v = v[Number(seg)];
6114
+ else if (typeof v === "object") v = v[seg];
6115
+ else return;
6116
+ }
6117
+ return v;
6118
+ }
6133
6119
  /************************************************************
6120
+ * Set a value at absolute segments (mutates root).
6134
6121
  *
6122
+ * segments == [] means "replace the whole root" — the caller
6123
+ * must use the RETURNED value in that case (a primitive/array
6124
+ * root cannot be mutated in place).
6135
6125
  ************************************************************/
6136
- function isEventSet(table, event) {
6137
- let eventListeners = table.externalEvents.events;
6138
- return eventListeners && eventListeners[event] && eventListeners[event].length > 0;
6126
+ function set_by_segments(root, segments, new_value) {
6127
+ if (segments.length === 0) return new_value;
6128
+ let parent = get_by_segments(root, segments.slice(0, -1));
6129
+ if (parent === null || parent === void 0 || typeof parent !== "object") return root;
6130
+ let last = segments[segments.length - 1];
6131
+ if (Array.isArray(parent)) parent[Number(last)] = new_value;
6132
+ else parent[last] = new_value;
6133
+ return root;
6139
6134
  }
6140
6135
  /************************************************************
6136
+ * Does `value` (or any loaded descendant / its own key)
6137
+ * contain the lower-cased search term?
6141
6138
  *
6139
+ * Collapsed (not-yet-loaded) subtrees can't be searched, so
6140
+ * they never match on content — only their key can match,
6141
+ * which the renderer checks separately.
6142
6142
  ************************************************************/
6143
- function set_changed_stated(gobj, changed) {
6144
- let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
6145
- let $form = $container.querySelector("form");
6146
- const enable = ($btn, on, color) => {
6147
- if (!$btn) return;
6148
- if (on) $btn.removeAttribute("disabled");
6149
- else $btn.setAttribute("disabled", true);
6150
- const $i = $btn.querySelector("span i");
6151
- if ($i) $i.style.color = color;
6152
- };
6153
- const OFF_COLOR = "hsl(var(--bulma-button-h), var(--bulma-button-s), var(--bulma-button-color-l))";
6154
- let $button_save = $container.querySelector(".button-save");
6155
- let $button_undo = $container.querySelector(".button-undo");
6156
- if (changed) {
6157
- (0, _yuneta_gobj_js.gobj_write_bool_attr)(gobj, "changes", true);
6158
- enable($button_save, true, "MediumSeaGreen");
6159
- enable($button_undo, true, "Magenta");
6160
- $form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
6161
- if (isEventSet($table.tabulator, "dataChanged")) $table.tabulator.off("dataChanged");
6162
- $table.querySelectorAll(".yui-tabulator-table-nested").forEach(($table_nested) => {
6163
- if (isEventSet($table_nested.tabulator, "dataChanged")) $table_nested.tabulator.off("dataChanged");
6164
- });
6165
- });
6166
- } else {
6167
- (0, _yuneta_gobj_js.gobj_write_bool_attr)(gobj, "changes", false);
6168
- enable($button_save, false, OFF_COLOR);
6169
- enable($button_undo, false, OFF_COLOR);
6170
- setTimeout(function() {
6171
- $form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
6172
- if (isEventSet($table.tabulator, "dataChanged")) $table.tabulator.off("dataChanged");
6173
- $table.tabulator.on("dataChanged", function(data) {
6174
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_RECORD_CHANGED", {}, gobj);
6175
- });
6176
- $table.querySelectorAll(".yui-tabulator-table-nested").forEach(($table_nested) => {
6177
- if (isEventSet($table_nested.tabulator, "dataChanged")) $table_nested.tabulator.off("dataChanged");
6178
- $table_nested.tabulator.on("dataChanged", function(data) {
6179
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_RECORD_CHANGED", {}, gobj);
6180
- });
6181
- });
6182
- });
6183
- }, 200);
6143
+ function subtree_matches(value, term) {
6144
+ if (!term) return true;
6145
+ if (is_collapsed(value)) return false;
6146
+ let type = json_type(value);
6147
+ if (type === "object") {
6148
+ for (let [k, v] of Object.entries(value)) {
6149
+ if (String(k).toLowerCase().includes(term)) return true;
6150
+ if (subtree_matches(v, term)) return true;
6151
+ }
6152
+ return false;
6153
+ }
6154
+ if (type === "array") {
6155
+ for (let v of value) if (subtree_matches(v, term)) return true;
6156
+ return false;
6184
6157
  }
6158
+ return String(value).toLowerCase().includes(term);
6185
6159
  }
6186
6160
  /************************************************************
6161
+ * Fields whose numeric value is an epoch timestamp.
6187
6162
  *
6163
+ * THE ONE LIST. C_YUI_FORM's jsoneditor takes its
6164
+ * `timestampTag` from here: it used to carry its own copy,
6165
+ * and a copy is how the two stopped agreeing.
6166
+ *
6167
+ * TWO SPELLINGS, AND LAS DOS SON DEL BACKEND. timeranger2
6168
+ * writes `from_t`/`from_tm` in the match_cond of a query and
6169
+ * `fr_t`/`fr_tm` in the metadata of a topic and of a file --
6170
+ * which is what a Raw JSON of a treedb actually shows. With
6171
+ * only the long pair listed, `to_t` got its date and the
6172
+ * `fr_t` right above it did not, which reads as a broken
6173
+ * annotator rather than a missing name.
6174
+ ************************************************************/
6175
+ var TIME_FIELDS = [
6176
+ "__t__",
6177
+ "__tm__",
6178
+ "t",
6179
+ "tm",
6180
+ "time",
6181
+ "from_t",
6182
+ "to_t",
6183
+ "from_tm",
6184
+ "to_tm",
6185
+ "fr_t",
6186
+ "fr_tm",
6187
+ "t_input",
6188
+ "t_output"
6189
+ ];
6190
+ function is_time_field(field) {
6191
+ return TIME_FIELDS.indexOf(field) >= 0;
6192
+ }
6193
+ /************************************************************
6194
+ * Format an epoch value as a local wall-clock string.
6195
+ * Accepts seconds or milliseconds; returns null when the
6196
+ * value is not a usable positive timestamp (0 == "unset").
6188
6197
  ************************************************************/
6189
- async function readClipboard$1(gobj) {
6190
- try {
6191
- const text = await navigator.clipboard.readText();
6192
- let kw = JSON.parse(text);
6193
- (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_PASTE_RECORD", kw, gobj);
6194
- } catch (error) {
6195
- (0, _yuneta_gobj_js.log_error)(`Failed to read clipboard contents: ${error}`);
6196
- }
6198
+ function format_epoch(value) {
6199
+ if (typeof value !== "number" || !isFinite(value) || value <= 0) return null;
6200
+ let ms = Math.abs(value) < 0xe8d4a51000 ? value * 1e3 : value;
6201
+ let d = new Date(ms);
6202
+ if (isNaN(d.getTime())) return null;
6203
+ return d.toLocaleString();
6197
6204
  }
6198
6205
  /************************************************************
6199
- * Bottom toolbar, from the `toolbar` attr.
6206
+ * The text view's payload: the document as text, indented
6207
+ * four characters, cut at `max_chars`.
6200
6208
  *
6201
- * The five buttons it can show, and their order, are the
6202
- * caller's: a dialog with a single action asks for ["save"],
6203
- * and [] leaves no toolbar at all. Default is the five it
6204
- * always had, so nothing moves for a caller that says nothing.
6209
+ * Returns {text, capped}. `capped` is what the caller
6210
+ * announces under the dump — a cut that is not announced
6211
+ * reads as a document that ends there.
6205
6212
  ************************************************************/
6206
- function toolbar_button$1(gobj, name) {
6207
- switch (name) {
6208
- case "save": return [
6209
- "button",
6213
+ function json_text_dump(root, max_chars) {
6214
+ let text;
6215
+ try {
6216
+ text = JSON.stringify(root, null, 4);
6217
+ } catch (e) {
6218
+ return {
6219
+ text: "",
6220
+ capped: false,
6221
+ error: String(e)
6222
+ };
6223
+ }
6224
+ if (text === void 0) return {
6225
+ text: "",
6226
+ capped: false,
6227
+ error: "not representable as JSON"
6228
+ };
6229
+ if (typeof max_chars === "number" && max_chars > 0 && text.length > max_chars) return {
6230
+ text: text.slice(0, max_chars),
6231
+ capped: true
6232
+ };
6233
+ return {
6234
+ text,
6235
+ capped: false
6236
+ };
6237
+ }
6238
+ /************************************************************
6239
+ * Which view a freshly mounted viewer opens on.
6240
+ *
6241
+ * Three answers, in order of authority: what the HOST asked
6242
+ * for, what the READER chose last time, and `fallback`.
6243
+ *
6244
+ * The host wins because a viewer mounted to show a graph has
6245
+ * to show one. The memory comes next, because reopening a
6246
+ * document in the view you just left is the whole point of
6247
+ * remembering it. `fallback` is the floor.
6248
+ *
6249
+ * Anything not in `modes` is treated as unsaid, which covers
6250
+ * an absent value, an empty one, and a stored string from an
6251
+ * older release that named a view no longer offered.
6252
+ ************************************************************/
6253
+ function pick_view_mode(asked, remembered, modes, fallback) {
6254
+ let known = (m) => Array.isArray(modes) && modes.indexOf(m) >= 0;
6255
+ if (known(asked)) return asked;
6256
+ if (known(remembered)) return remembered;
6257
+ return fallback;
6258
+ }
6259
+ /************************************************************
6260
+ * The short label a container row carries beside its size.
6261
+ *
6262
+ * An array of dicts that carry an `id` is a list of RECORDS,
6263
+ * and a row that says only `2: {15}` makes you open all
6264
+ * fifteen fields of every one of them to find out WHICH
6265
+ * record it is. The id is the one field that answers that,
6266
+ * so the row says it without being opened.
6267
+ *
6268
+ * Only a SCALAR id, and only when it is really there: an `id`
6269
+ * that is itself a dict or a list is not a name, it is more
6270
+ * document, and printing `[object Object]` beside the size
6271
+ * would be worse than printing nothing.
6272
+ *
6273
+ * Cut at `max_chars`, because this rides on a row whose job
6274
+ * is to stay one line.
6275
+ ************************************************************/
6276
+ function container_label(value, max_chars) {
6277
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
6278
+ if (!Object.prototype.hasOwnProperty.call(value, "id")) return "";
6279
+ let id = value.id;
6280
+ let t = typeof id;
6281
+ if (t !== "string" && t !== "number" && t !== "boolean") return "";
6282
+ let text = String(id).trim();
6283
+ if (text === "") return "";
6284
+ let cap = typeof max_chars === "number" && max_chars > 0 ? max_chars : 0;
6285
+ if (cap && text.length > cap) return text.slice(0, cap) + "…";
6286
+ return text;
6287
+ }
6288
+ /************************************************************
6289
+ * A dict, in the JSON sense: not an array and not null.
6290
+ ************************************************************/
6291
+ function is_plain_object(v) {
6292
+ return v !== null && typeof v === "object" && !Array.isArray(v);
6293
+ }
6294
+ /************************************************************
6295
+ * Does this container hold ONLY other containers?
6296
+ *
6297
+ * Such a value is not a thing, it is a LIST of things -- and a
6298
+ * list is fully described by the row that names it in its
6299
+ * parent, plus the edges to what it holds. Drawing it as a
6300
+ * card of its own put a node in the middle of the graph with no
6301
+ * data in it, saying nothing the parent's row did not already
6302
+ * say, and pushed everything below it one level down.
6303
+ *
6304
+ * An EMPTY container inside counts as content: `{0}` is
6305
+ * something the card has to show, and there is no child card
6306
+ * to show it instead.
6307
+ ************************************************************/
6308
+ function is_pure_collection(value) {
6309
+ let entries = is_plain_object(value) ? Object.entries(value) : Array.isArray(value) ? value.map((v, i) => [i, v]) : [];
6310
+ if (entries.length === 0) return false;
6311
+ for (let [, v] of entries) if (!has_branch(v)) return false;
6312
+ return true;
6313
+ }
6314
+ /************************************************************
6315
+ * A value that gets a card, or children, of its own.
6316
+ ************************************************************/
6317
+ function has_branch(value) {
6318
+ return is_plain_object(value) && Object.keys(value).length > 0 || Array.isArray(value) && value.length > 0;
6319
+ }
6320
+ /************************************************************
6321
+ * How many branches a container has, which is what the fold
6322
+ * chip counts.
6323
+ ************************************************************/
6324
+ function count_branches(value) {
6325
+ let entries = is_plain_object(value) ? Object.values(value) : Array.isArray(value) ? value : [];
6326
+ let n = 0;
6327
+ for (let v of entries) if (has_branch(v)) n++;
6328
+ return n;
6329
+ }
6330
+ //#endregion
6331
+ //#region src/c_yui_form.js
6332
+ /***********************************************************************
6333
+ * c_yui_form.js
6334
+ *
6335
+ * Form (in Window or not)
6336
+ *
6337
+ * Copyright (c) 2024-2026, ArtGins.
6338
+ * All Rights Reserved.
6339
+ ***********************************************************************/
6340
+ /***************************************************************
6341
+ * Constants
6342
+ ***************************************************************/
6343
+ var GCLASS_NAME$16 = "C_YUI_FORM";
6344
+ /***************************************************************
6345
+ * Data
6346
+ ***************************************************************/
6347
+ var attrs_table$16 = [
6348
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "subscriber", 0, null, "Subscriber of publishing messages"),
6349
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "$parent", 0, null, "$container will be appended to $parent if not null, else do it manually"),
6350
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "template", 0, "<div>You must supply a template for form</div>", "Template for form, object -> TEMPLATE, array -> DESC"),
6351
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "record", 0, "{}", "Data to form"),
6352
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "placeholders", 0, "{}", "Placeholder values for form fields"),
6353
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "fkey_options", 0, "{}", "Options for fkey fields: {topic_name: [ids or {id} records]}, read at build time"),
6354
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "form_mode", 0, "", "'create' (pkey editable+required) or 'update' (pkey readonly); empty = no pkey handling"),
6355
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "render_mode", 0, "exec", "'exec' interprets structured cols (template->sub-form, table->grid, coordinates->picker); 'edit' shows them as raw JSON editors (record-editing, e.g. the treedb topic form)"),
6356
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "pkey", 0, "id", "Primary key field name, used by form_mode"),
6357
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "topic_name", 0, "", "Treedb topic name: labels use the i18n cascade '<topic>.<col>' -> '<col>' -> header (like table headers)"),
6358
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "editable", 0, false, "Default is editable, set false to readonly"),
6359
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "selectable", 0, true, "Rows selectable with 'click' (BAD with editable=true)"),
6360
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_checkbox", 0, false, "Show a first column with checkbox to select rows"),
6361
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_rownum", 0, false, "Print first column with row number"),
6362
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "with_delete_row_btn", 0, false, "Print last column with delete row button"),
6363
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "toolbar", 0, [
6364
+ "save",
6365
+ "undo",
6366
+ "clear",
6367
+ "copy",
6368
+ "paste"
6369
+ ], "Toolbar buttons to show, in order: save, undo, clear, copy, paste. [] hides the toolbar"),
6370
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_JSON, "tabulator_settings", 0, {
6371
+ layout: "fitDataFill",
6372
+ validationMode: "highlight",
6373
+ columnDefaults: { resizable: true },
6374
+ maxHeight: "100%"
6375
+ }, "Default settings for Tabulator"),
6376
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_POINTER, "$container", 0, null, "Container element"),
6377
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_STRING, "form_id", 0, "", "Form identifier"),
6378
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "initialized", 0, false, "Indicates if it is initialized"),
6379
+ (0, _yuneta_gobj_js.SDATA)(_yuneta_gobj_js.data_type_t.DTP_BOOLEAN, "changes", 0, false, "Indicates if changes have been made"),
6380
+ (0, _yuneta_gobj_js.SDATA_END)()
6381
+ ];
6382
+ var PRIVATE_DATA$16 = {};
6383
+ var __gclass__$16 = null;
6384
+ /***************************
6385
+ * Framework Methods
6386
+ ***************************/
6387
+ /************************************************************
6388
+ * Framework Method create
6389
+ ************************************************************/
6390
+ function mt_create$16(gobj) {
6391
+ let subscriber = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "subscriber");
6392
+ if (!subscriber) subscriber = (0, _yuneta_gobj_js.gobj_parent)(gobj);
6393
+ (0, _yuneta_gobj_js.gobj_subscribe_event)(gobj, null, {}, subscriber);
6394
+ let name = (0, _yuneta_gobj_js.clean_name)((0, _yuneta_gobj_js.gobj_name)(gobj));
6395
+ (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "form_id", "form" + name);
6396
+ build_ui$14(gobj);
6397
+ }
6398
+ /************************************************************
6399
+ * Framework Method destroy
6400
+ * In this point, all children
6401
+ * and subscriptions are already deleted.
6402
+ ************************************************************/
6403
+ function mt_destroy$16(gobj) {
6404
+ destroy_ui$10(gobj);
6405
+ }
6406
+ /************************************************************
6407
+ * Framework Method start
6408
+ ************************************************************/
6409
+ function mt_start$15(gobj) {
6410
+ let record = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "record");
6411
+ if (record) (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_LOAD_RECORD", record, gobj);
6412
+ }
6413
+ /************************************************************
6414
+ * Framework Method stop
6415
+ ************************************************************/
6416
+ function mt_stop$15(gobj) {}
6417
+ /***************************
6418
+ * Local Methods
6419
+ ***************************/
6420
+ /************************************************************
6421
+ *
6422
+ ************************************************************/
6423
+ function isEventSet(table, event) {
6424
+ let eventListeners = table.externalEvents.events;
6425
+ return eventListeners && eventListeners[event] && eventListeners[event].length > 0;
6426
+ }
6427
+ /************************************************************
6428
+ *
6429
+ ************************************************************/
6430
+ function set_changed_stated(gobj, changed) {
6431
+ let $container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
6432
+ let $form = $container.querySelector("form");
6433
+ const enable = ($btn, on, color) => {
6434
+ if (!$btn) return;
6435
+ if (on) $btn.removeAttribute("disabled");
6436
+ else $btn.setAttribute("disabled", true);
6437
+ const $i = $btn.querySelector("span i");
6438
+ if ($i) $i.style.color = color;
6439
+ };
6440
+ const OFF_COLOR = "hsl(var(--bulma-button-h), var(--bulma-button-s), var(--bulma-button-color-l))";
6441
+ let $button_save = $container.querySelector(".button-save");
6442
+ let $button_undo = $container.querySelector(".button-undo");
6443
+ if (changed) {
6444
+ (0, _yuneta_gobj_js.gobj_write_bool_attr)(gobj, "changes", true);
6445
+ enable($button_save, true, "MediumSeaGreen");
6446
+ enable($button_undo, true, "Magenta");
6447
+ $form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
6448
+ if (isEventSet($table.tabulator, "dataChanged")) $table.tabulator.off("dataChanged");
6449
+ $table.querySelectorAll(".yui-tabulator-table-nested").forEach(($table_nested) => {
6450
+ if (isEventSet($table_nested.tabulator, "dataChanged")) $table_nested.tabulator.off("dataChanged");
6451
+ });
6452
+ });
6453
+ } else {
6454
+ (0, _yuneta_gobj_js.gobj_write_bool_attr)(gobj, "changes", false);
6455
+ enable($button_save, false, OFF_COLOR);
6456
+ enable($button_undo, false, OFF_COLOR);
6457
+ setTimeout(function() {
6458
+ $form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
6459
+ if (isEventSet($table.tabulator, "dataChanged")) $table.tabulator.off("dataChanged");
6460
+ $table.tabulator.on("dataChanged", function(data) {
6461
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_RECORD_CHANGED", {}, gobj);
6462
+ });
6463
+ $table.querySelectorAll(".yui-tabulator-table-nested").forEach(($table_nested) => {
6464
+ if (isEventSet($table_nested.tabulator, "dataChanged")) $table_nested.tabulator.off("dataChanged");
6465
+ $table_nested.tabulator.on("dataChanged", function(data) {
6466
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_RECORD_CHANGED", {}, gobj);
6467
+ });
6468
+ });
6469
+ });
6470
+ }, 200);
6471
+ }
6472
+ }
6473
+ /************************************************************
6474
+ *
6475
+ ************************************************************/
6476
+ async function readClipboard$1(gobj) {
6477
+ try {
6478
+ const text = await navigator.clipboard.readText();
6479
+ let kw = JSON.parse(text);
6480
+ (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_PASTE_RECORD", kw, gobj);
6481
+ } catch (error) {
6482
+ (0, _yuneta_gobj_js.log_error)(`Failed to read clipboard contents: ${error}`);
6483
+ }
6484
+ }
6485
+ /************************************************************
6486
+ * Bottom toolbar, from the `toolbar` attr.
6487
+ *
6488
+ * The five buttons it can show, and their order, are the
6489
+ * caller's: a dialog with a single action asks for ["save"],
6490
+ * and [] leaves no toolbar at all. Default is the five it
6491
+ * always had, so nothing moves for a caller that says nothing.
6492
+ ************************************************************/
6493
+ function toolbar_button$1(gobj, name) {
6494
+ switch (name) {
6495
+ case "save": return [
6496
+ "button",
6210
6497
  {
6211
6498
  class: "button p-1 button-save",
6212
6499
  title: "save",
@@ -7168,7 +7455,7 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
7168
7455
  (0, _yuneta_gobj_js.gobj_send_event)(gobj, "EV_RECORD_CHANGED", {}, gobj);
7169
7456
  },
7170
7457
  timestampTag: function({ field, value, path }) {
7171
- return field === "__t__" || field === "__tm__" || field === "tm" || field === "t" || field === "time" || field === "from_t" || field === "to_t" || field === "t_input" || field === "t_output" || field === "from_tm" || field === "to_tm";
7458
+ return is_time_field(field);
7172
7459
  }
7173
7460
  }
7174
7461
  });
@@ -10038,559 +10325,297 @@ var ZoomCanvasPinch = class extends _antv_g6.ZoomCanvas {
10038
10325
  }
10039
10326
  let dx = touches[0].clientX - touches[1].clientX;
10040
10327
  let dy = touches[0].clientY - touches[1].clientY;
10041
- let dist = Math.sqrt(dx * dx + dy * dy);
10042
- if (dist <= 0) return;
10043
- if (this._pinch_dist0 === 0) {
10044
- this._pinch_dist0 = dist;
10045
- this._pinch_zoom0 = graph.getZoom();
10046
- return;
10047
- }
10048
- this._pinch_to(dist / this._pinch_dist0, touches, event);
10049
- };
10050
- state.container.addEventListener("touchmove", this._on_touch_move, { passive: true });
10051
- }
10052
- /************************************************************
10053
- * Zoom to `scale` times the zoom the pinch started at,
10054
- * about the midpoint between the two fingers.
10055
- *
10056
- * ABSOLUTE against the start of the gesture, where G6's
10057
- * own pinch feeds `zoom()` an increment per frame: an
10058
- * increment drifts away from the fingers over a long
10059
- * gesture, and cannot come back when they close again.
10060
- ************************************************************/
10061
- _pinch_to(scale, touches, event) {
10062
- if (!this.validate(event)) return;
10063
- let graph = this.context.graph;
10064
- let state = touch_state_of(graph);
10065
- let origin = null;
10066
- let rect = state.container?.getBoundingClientRect();
10067
- if (rect) origin = [(touches[0].clientX + touches[1].clientX) / 2 - rect.left, (touches[0].clientY + touches[1].clientY) / 2 - rect.top];
10068
- graph.zoomTo(this._pinch_zoom0 * scale, false, origin);
10069
- this.options.onFinish?.();
10070
- }
10071
- destroy() {
10072
- if (this._on_touch_move) {
10073
- __touch_state__.get(this.context.graph)?.container?.removeEventListener("touchmove", this._on_touch_move);
10074
- this._on_touch_move = null;
10075
- }
10076
- super.destroy();
10077
- }
10078
- };
10079
- /************************************************************
10080
- * Register once, over the built-in `zoom-canvas`, so every
10081
- * graph (tree, json, treedb, editor) pinches without
10082
- * touching its behaviors list.
10083
- ************************************************************/
10084
- var __zoom_canvas_patched__ = false;
10085
- function ensure_pinch_zoom_patch() {
10086
- if (__zoom_canvas_patched__) return;
10087
- (0, _antv_g6.register)(_antv_g6.ExtensionCategory.BEHAVIOR, "zoom-canvas", ZoomCanvasPinch);
10088
- __zoom_canvas_patched__ = true;
10089
- }
10090
- /***************************
10091
- * Long press
10092
- ***************************/
10093
- /************************************************************
10094
- * Is the click being dispatched right now the tail of a long
10095
- * press? Answers true ONCE, to the first asker.
10096
- *
10097
- * @antv/g does not take `click` from the DOM either: it makes
10098
- * one out of the pointerdown/pointerup pair itself, inside
10099
- * the same `onPointerUp` that just fed us the release. So
10100
- * the press that opened the menu goes on to click whatever it
10101
- * opened the menu ON -- selecting the node, or worse, dropping
10102
- * a selection of several -- unless the handler asks.
10103
- ************************************************************/
10104
- function consume_long_press_click(graph) {
10105
- if (!graph) return false;
10106
- let state = __touch_state__.get(graph);
10107
- if (!state || !state.long_press_click) return false;
10108
- state.long_press_click = false;
10109
- return true;
10110
- }
10111
- /************************************************************
10112
- * Make a long press open the context menu.
10113
- *
10114
- * Returns the function that uninstalls it.
10115
- *
10116
- * The menu the press opens is whatever the `contextmenu`
10117
- * plugin builds: we re-emit G6's own forwarded event under
10118
- * the name the plugin listens for, so `getItems(e)` and
10119
- * `enable(e)` see the same object a right-click would give
10120
- * them -- same `target`, same `targetType`, same `client`.
10121
- *
10122
- * `options.enable(event)` decides, at the DOWN, whether this
10123
- * press is even a candidate. Pass the plugin's own predicate:
10124
- * a press on the empty canvas would otherwise arm a menu the
10125
- * plugin then refuses to show, and swallow the click that
10126
- * would have cleared the selection.
10127
- ************************************************************/
10128
- function install_long_press_contextmenu(graph, options) {
10129
- if (!graph) return () => {};
10130
- let enable = options?.enable;
10131
- let state = touch_state_of(graph);
10132
- let pending = null;
10133
- let disarm = () => {
10134
- if (pending) {
10135
- clearTimeout(pending.tick);
10136
- pending = null;
10137
- }
10138
- };
10139
- let announce = () => {
10140
- if (!pending) return;
10141
- if (state.multi) return;
10142
- navigator.vibrate?.(15);
10143
- };
10144
- let fire = (event) => {
10145
- state.long_press_click = true;
10146
- setTimeout(() => {
10147
- state.long_press_click = false;
10148
- }, 0);
10149
- let swallow = (evt) => {
10150
- evt.stopPropagation();
10151
- document.removeEventListener("click", swallow, true);
10152
- };
10153
- document.addEventListener("click", swallow, true);
10154
- setTimeout(() => {
10155
- document.removeEventListener("click", swallow, true);
10156
- }, 1e3);
10157
- graph.emit(`${event.targetType}:${_antv_g6.CommonEvent.CONTEXT_MENU}`, event);
10158
- graph.emit(_antv_g6.CommonEvent.CONTEXT_MENU, event);
10159
- };
10160
- let on_down = (event) => {
10161
- disarm();
10162
- if (event.pointerType !== "touch") return;
10163
- if (state.count > 1 || state.multi) return;
10164
- if (enable && !enable(event)) return;
10165
- let x = event.client?.x;
10166
- let y = event.client?.y;
10167
- if (x === void 0 || y === void 0) return;
10168
- pending = {
10169
- x,
10170
- y,
10171
- t0: Date.now(),
10172
- event: Object.assign({}, event),
10173
- tick: setTimeout(announce, 500)
10174
- };
10175
- };
10176
- let on_move = (event) => {
10177
- if (!pending) return;
10178
- if (state.count > 1 || state.multi) {
10179
- disarm();
10180
- return;
10181
- }
10182
- let x = event.client?.x;
10183
- let y = event.client?.y;
10184
- if (x === void 0 || y === void 0) return;
10185
- if (press_moved(pending, x, y)) disarm();
10186
- };
10187
- let on_up = (event) => {
10188
- let press = pending;
10189
- if (press) clearTimeout(press.tick);
10190
- pending = null;
10191
- if (!press) return;
10192
- if (state.multi) return;
10193
- let x = event.client?.x;
10194
- let y = event.client?.y;
10195
- if (x === void 0) x = press.x;
10196
- if (y === void 0) y = press.y;
10197
- if (classify_press(press, x, y, Date.now()) !== "long") return;
10198
- fire(press.event);
10199
- };
10200
- let on_touch_cancel = () => {
10201
- disarm();
10202
- };
10203
- state.container?.addEventListener("touchcancel", on_touch_cancel);
10204
- graph.on(_antv_g6.CommonEvent.POINTER_DOWN, on_down);
10205
- graph.on(_antv_g6.CommonEvent.POINTER_MOVE, on_move);
10206
- graph.on(_antv_g6.CommonEvent.POINTER_UP, on_up);
10207
- return () => {
10208
- disarm();
10209
- state.container?.removeEventListener("touchcancel", on_touch_cancel);
10210
- graph.off(_antv_g6.CommonEvent.POINTER_DOWN, on_down);
10211
- graph.off(_antv_g6.CommonEvent.POINTER_MOVE, on_move);
10212
- graph.off(_antv_g6.CommonEvent.POINTER_UP, on_up);
10213
- };
10214
- }
10215
- //#endregion
10216
- //#region src/g6_drag_canvas_touch.js
10217
- /***********************************************************************
10218
- * g6_drag_canvas_touch.js
10219
- *
10220
- * Drop-in replacement for G6 v5 built-in 'drag-canvas'.
10221
- *
10222
- * Why this exists
10223
- * ----------------
10224
- * G6 v5.1.0 drag-canvas computes the pan delta as:
10225
- *
10226
- * const x = event.movement?.x ?? event.dx;
10227
- *
10228
- * but @antv/g always attaches a `movement` Point to every
10229
- * federated pointer event (g-lite: `this.movement = new
10230
- * Point()`), and fills it from the native
10231
- * `PointerEvent.movementX/Y` (g-lite: `event.movement.x =
10232
- * nativeEvent.movementX`). So `event.movement?.x` is never
10233
- * nullish and the `?? event.dx` fallback never runs.
10234
- *
10235
- * Native `movementX/Y`:
10236
- * - on touch pointers most mobile browsers leave it at 0
10237
- * (it is a mouse / pointer-lock feature), so the canvas
10238
- * barely pans on mobile;
10239
- * - on desktop it is skewed by OS pointer acceleration and
10240
- * devicePixelRatio, so the graph lags the cursor.
10241
- *
10242
- * The fix tracks `event.viewport` (the federated pointer
10243
- * position already mapped to canvas/viewport space by
10244
- * @antv/g, the exact units `translateBy` expects) and pans
10245
- * by its frame-to-frame delta. This is:
10246
- * - reliable on mouse AND touch (derived from clientX/Y,
10247
- * never from native movementX);
10248
- * - correct for ANY canvas scale (it is post-`getScale()`,
10249
- * so a configured-vs-displayed size mismatch can no
10250
- * longer make panning over- or under-shoot the cursor);
10251
- * - the same source G6's own `zoom-canvas` uses for its
10252
- * origin (`event.viewport`).
10253
- * The subclass reuses all of the built-in clamp/cursor logic
10254
- * and only swaps the delta source.
10255
- *
10256
- * Registered globally over the built-in 'drag-canvas' id so
10257
- * every graph (tree, json, treedb, editor) is fixed without
10258
- * touching each consumer's behaviors list.
10259
- *
10260
- * It also has to keep the ONE guard the base class has and
10261
- * this replacement dropped: G6's `onDrag` refuses to pan
10262
- * while a pinch is in progress. See `is_pinching` in
10263
- * `g6_touch_gestures.js`, which is where the pinch that
10264
- * matters is now recognised.
10265
- *
10266
- * Copyright (c) 2026, ArtGins.
10267
- * All Rights Reserved.
10268
- ***********************************************************************/
10269
- /************************************************************
10270
- * Custom drag-canvas: same behavior, reliable delta source
10271
- ************************************************************/
10272
- var DragCanvasTouch = class extends _antv_g6.DragCanvas {
10273
- constructor(context, options) {
10274
- super(context, options);
10275
- this._lastVP = null;
10276
- this._onDragTouch = (event) => {
10277
- if (!this.isDragging) {
10278
- this._lastVP = null;
10279
- return;
10280
- }
10281
- if (is_pinching(this.context.graph)) {
10282
- this._lastVP = null;
10283
- return;
10284
- }
10285
- let vp = event.viewport;
10286
- if (!vp || typeof vp.x !== "number" || typeof vp.y !== "number") return;
10287
- if (this._lastVP === null) {
10288
- this._lastVP = {
10289
- x: vp.x,
10290
- y: vp.y
10291
- };
10328
+ let dist = Math.sqrt(dx * dx + dy * dy);
10329
+ if (dist <= 0) return;
10330
+ if (this._pinch_dist0 === 0) {
10331
+ this._pinch_dist0 = dist;
10332
+ this._pinch_zoom0 = graph.getZoom();
10292
10333
  return;
10293
10334
  }
10294
- let x = vp.x - this._lastVP.x;
10295
- let y = vp.y - this._lastVP.y;
10296
- this._lastVP.x = vp.x;
10297
- this._lastVP.y = vp.y;
10298
- if (x !== 0 || y !== 0) this.translate([x, y], false);
10299
- };
10300
- this._onDragEndReset = () => {
10301
- this._lastVP = null;
10335
+ this._pinch_to(dist / this._pinch_dist0, touches, event);
10302
10336
  };
10303
- let graph = this.context.graph;
10304
- graph.off(_antv_g6.CommonEvent.DRAG, this.onDrag);
10305
- graph.on(_antv_g6.CommonEvent.DRAG, this._onDragTouch);
10306
- graph.on(_antv_g6.CommonEvent.DRAG_END, this._onDragEndReset);
10337
+ state.container.addEventListener("touchmove", this._on_touch_move, { passive: true });
10307
10338
  }
10308
- update(options) {
10309
- super.update(options);
10339
+ /************************************************************
10340
+ * Zoom to `scale` times the zoom the pinch started at,
10341
+ * about the midpoint between the two fingers.
10342
+ *
10343
+ * ABSOLUTE against the start of the gesture, where G6's
10344
+ * own pinch feeds `zoom()` an increment per frame: an
10345
+ * increment drifts away from the fingers over a long
10346
+ * gesture, and cannot come back when they close again.
10347
+ ************************************************************/
10348
+ _pinch_to(scale, touches, event) {
10349
+ if (!this.validate(event)) return;
10310
10350
  let graph = this.context.graph;
10311
- graph.off(_antv_g6.CommonEvent.DRAG, this.onDrag);
10312
- graph.off(_antv_g6.CommonEvent.DRAG, this._onDragTouch);
10313
- graph.off(_antv_g6.CommonEvent.DRAG_END, this._onDragEndReset);
10314
- graph.on(_antv_g6.CommonEvent.DRAG, this._onDragTouch);
10315
- graph.on(_antv_g6.CommonEvent.DRAG_END, this._onDragEndReset);
10351
+ let state = touch_state_of(graph);
10352
+ let origin = null;
10353
+ let rect = state.container?.getBoundingClientRect();
10354
+ if (rect) origin = [(touches[0].clientX + touches[1].clientX) / 2 - rect.left, (touches[0].clientY + touches[1].clientY) / 2 - rect.top];
10355
+ graph.zoomTo(this._pinch_zoom0 * scale, false, origin);
10356
+ this.options.onFinish?.();
10316
10357
  }
10317
10358
  destroy() {
10318
- let graph = this.context.graph;
10319
- graph.off(_antv_g6.CommonEvent.DRAG, this._onDragTouch);
10320
- graph.off(_antv_g6.CommonEvent.DRAG_END, this._onDragEndReset);
10359
+ if (this._on_touch_move) {
10360
+ __touch_state__.get(this.context.graph)?.container?.removeEventListener("touchmove", this._on_touch_move);
10361
+ this._on_touch_move = null;
10362
+ }
10321
10363
  super.destroy();
10322
10364
  }
10323
10365
  };
10324
10366
  /************************************************************
10325
- * Register once, replacing the built-in 'drag-canvas'
10326
- ************************************************************/
10327
- var __drag_canvas_patched__ = false;
10328
- function ensure_drag_canvas_patch() {
10329
- if (__drag_canvas_patched__) return;
10330
- (0, _antv_g6.register)(_antv_g6.ExtensionCategory.BEHAVIOR, "drag-canvas", DragCanvasTouch);
10331
- __drag_canvas_patched__ = true;
10332
- }
10333
- /************************************************************
10334
- * JSON type discriminator (viewer vocabulary)
10367
+ * Register once, over the built-in `zoom-canvas`, so every
10368
+ * graph (tree, json, treedb, editor) pinches without
10369
+ * touching its behaviors list.
10335
10370
  ************************************************************/
10336
- function json_type(value) {
10337
- if (value === null) return "null";
10338
- if (Array.isArray(value)) return "array";
10339
- switch (typeof value) {
10340
- case "string": return "string";
10341
- case "number": return "number";
10342
- case "boolean": return "boolean";
10343
- case "object": return "object";
10344
- default: return "unknown";
10345
- }
10371
+ var __zoom_canvas_patched__ = false;
10372
+ function ensure_pinch_zoom_patch() {
10373
+ if (__zoom_canvas_patched__) return;
10374
+ (0, _antv_g6.register)(_antv_g6.ExtensionCategory.BEHAVIOR, "zoom-canvas", ZoomCanvasPinch);
10375
+ __zoom_canvas_patched__ = true;
10346
10376
  }
10377
+ /***************************
10378
+ * Long press
10379
+ ***************************/
10347
10380
  /************************************************************
10348
- * Detect a kw_collapse() sentinel.
10349
- *
10350
- * The kernel replaces an over-limit dict with:
10351
- * { "__collapsed__": { "path": ..., "size": N } }
10352
- * and an over-limit array with:
10353
- * [ { "__collapsed__": { "path": ..., "size": N } } ]
10381
+ * Is the click being dispatched right now the tail of a long
10382
+ * press? Answers true ONCE, to the first asker.
10354
10383
  *
10355
- * Returns { size, path, is_array } or null.
10356
- ************************************************************/
10357
- function is_collapsed(value) {
10358
- if (value && typeof value === "object" && !Array.isArray(value)) {
10359
- let keys = Object.keys(value);
10360
- if (keys.length === 1 && keys[0] === "__collapsed__") {
10361
- let c = value.__collapsed__ || {};
10362
- return {
10363
- size: c.size,
10364
- path: c.path,
10365
- is_array: false
10366
- };
10367
- }
10368
- return null;
10369
- }
10370
- if (Array.isArray(value) && value.length === 1) {
10371
- let e = value[0];
10372
- if (e && typeof e === "object" && !Array.isArray(e) && Object.keys(e).length === 1 && e.__collapsed__) {
10373
- let c = e.__collapsed__ || {};
10374
- return {
10375
- size: c.size,
10376
- path: c.path,
10377
- is_array: true
10378
- };
10379
- }
10380
- }
10381
- return null;
10382
- }
10383
- /************************************************************
10384
- * Join / split absolute path segments (kernel convention)
10385
- ************************************************************/
10386
- function seg_join(segments) {
10387
- return segments.map(function(s) {
10388
- return String(s);
10389
- }).join("`");
10390
- }
10391
- function seg_split(path) {
10392
- if (path === "" || path === null || path === void 0) return [];
10393
- return path.split("`");
10394
- }
10395
- /************************************************************
10396
- * Walk a JSON tree by absolute segments; undefined if the
10397
- * path does not resolve.
10384
+ * @antv/g does not take `click` from the DOM either: it makes
10385
+ * one out of the pointerdown/pointerup pair itself, inside
10386
+ * the same `onPointerUp` that just fed us the release. So
10387
+ * the press that opened the menu goes on to click whatever it
10388
+ * opened the menu ON -- selecting the node, or worse, dropping
10389
+ * a selection of several -- unless the handler asks.
10398
10390
  ************************************************************/
10399
- function get_by_segments(root, segments) {
10400
- let v = root;
10401
- for (let seg of segments) {
10402
- if (v === null || v === void 0) return;
10403
- if (Array.isArray(v)) v = v[Number(seg)];
10404
- else if (typeof v === "object") v = v[seg];
10405
- else return;
10406
- }
10407
- return v;
10391
+ function consume_long_press_click(graph) {
10392
+ if (!graph) return false;
10393
+ let state = __touch_state__.get(graph);
10394
+ if (!state || !state.long_press_click) return false;
10395
+ state.long_press_click = false;
10396
+ return true;
10408
10397
  }
10409
10398
  /************************************************************
10410
- * Set a value at absolute segments (mutates root).
10399
+ * Make a long press open the context menu.
10411
10400
  *
10412
- * segments == [] means "replace the whole root" — the caller
10413
- * must use the RETURNED value in that case (a primitive/array
10414
- * root cannot be mutated in place).
10415
- ************************************************************/
10416
- function set_by_segments(root, segments, new_value) {
10417
- if (segments.length === 0) return new_value;
10418
- let parent = get_by_segments(root, segments.slice(0, -1));
10419
- if (parent === null || parent === void 0 || typeof parent !== "object") return root;
10420
- let last = segments[segments.length - 1];
10421
- if (Array.isArray(parent)) parent[Number(last)] = new_value;
10422
- else parent[last] = new_value;
10423
- return root;
10424
- }
10425
- /************************************************************
10426
- * Does `value` (or any loaded descendant / its own key)
10427
- * contain the lower-cased search term?
10401
+ * Returns the function that uninstalls it.
10428
10402
  *
10429
- * Collapsed (not-yet-loaded) subtrees can't be searched, so
10430
- * they never match on content — only their key can match,
10431
- * which the renderer checks separately.
10432
- ************************************************************/
10433
- function subtree_matches(value, term) {
10434
- if (!term) return true;
10435
- if (is_collapsed(value)) return false;
10436
- let type = json_type(value);
10437
- if (type === "object") {
10438
- for (let [k, v] of Object.entries(value)) {
10439
- if (String(k).toLowerCase().includes(term)) return true;
10440
- if (subtree_matches(v, term)) return true;
10441
- }
10442
- return false;
10443
- }
10444
- if (type === "array") {
10445
- for (let v of value) if (subtree_matches(v, term)) return true;
10446
- return false;
10447
- }
10448
- return String(value).toLowerCase().includes(term);
10449
- }
10450
- /************************************************************
10451
- * Fields whose numeric value is an epoch timestamp.
10452
- * Mirrors the timestampTag list wired into C_YUI_FORM's
10453
- * jsoneditor control, so the two viewers agree.
10454
- ************************************************************/
10455
- function is_time_field(field) {
10456
- return field === "__t__" || field === "__tm__" || field === "tm" || field === "t" || field === "time" || field === "from_t" || field === "to_t" || field === "t_input" || field === "t_output" || field === "from_tm" || field === "to_tm";
10457
- }
10458
- /************************************************************
10459
- * Format an epoch value as a local wall-clock string.
10460
- * Accepts seconds or milliseconds; returns null when the
10461
- * value is not a usable positive timestamp (0 == "unset").
10462
- ************************************************************/
10463
- function format_epoch(value) {
10464
- if (typeof value !== "number" || !isFinite(value) || value <= 0) return null;
10465
- let ms = Math.abs(value) < 0xe8d4a51000 ? value * 1e3 : value;
10466
- let d = new Date(ms);
10467
- if (isNaN(d.getTime())) return null;
10468
- return d.toLocaleString();
10469
- }
10470
- /************************************************************
10471
- * The text view's payload: the document as text, indented
10472
- * four characters, cut at `max_chars`.
10403
+ * The menu the press opens is whatever the `contextmenu`
10404
+ * plugin builds: we re-emit G6's own forwarded event under
10405
+ * the name the plugin listens for, so `getItems(e)` and
10406
+ * `enable(e)` see the same object a right-click would give
10407
+ * them -- same `target`, same `targetType`, same `client`.
10473
10408
  *
10474
- * Returns {text, capped}. `capped` is what the caller
10475
- * announces under the dump — a cut that is not announced
10476
- * reads as a document that ends there.
10409
+ * `options.enable(event)` decides, at the DOWN, whether this
10410
+ * press is even a candidate. Pass the plugin's own predicate:
10411
+ * a press on the empty canvas would otherwise arm a menu the
10412
+ * plugin then refuses to show, and swallow the click that
10413
+ * would have cleared the selection.
10477
10414
  ************************************************************/
10478
- function json_text_dump(root, max_chars) {
10479
- let text;
10480
- try {
10481
- text = JSON.stringify(root, null, 4);
10482
- } catch (e) {
10483
- return {
10484
- text: "",
10485
- capped: false,
10486
- error: String(e)
10415
+ function install_long_press_contextmenu(graph, options) {
10416
+ if (!graph) return () => {};
10417
+ let enable = options?.enable;
10418
+ let state = touch_state_of(graph);
10419
+ let pending = null;
10420
+ let disarm = () => {
10421
+ if (pending) {
10422
+ clearTimeout(pending.tick);
10423
+ pending = null;
10424
+ }
10425
+ };
10426
+ let announce = () => {
10427
+ if (!pending) return;
10428
+ if (state.multi) return;
10429
+ navigator.vibrate?.(15);
10430
+ };
10431
+ let fire = (event) => {
10432
+ state.long_press_click = true;
10433
+ setTimeout(() => {
10434
+ state.long_press_click = false;
10435
+ }, 0);
10436
+ let swallow = (evt) => {
10437
+ evt.stopPropagation();
10438
+ document.removeEventListener("click", swallow, true);
10487
10439
  };
10488
- }
10489
- if (text === void 0) return {
10490
- text: "",
10491
- capped: false,
10492
- error: "not representable as JSON"
10440
+ document.addEventListener("click", swallow, true);
10441
+ setTimeout(() => {
10442
+ document.removeEventListener("click", swallow, true);
10443
+ }, 1e3);
10444
+ graph.emit(`${event.targetType}:${_antv_g6.CommonEvent.CONTEXT_MENU}`, event);
10445
+ graph.emit(_antv_g6.CommonEvent.CONTEXT_MENU, event);
10493
10446
  };
10494
- if (typeof max_chars === "number" && max_chars > 0 && text.length > max_chars) return {
10495
- text: text.slice(0, max_chars),
10496
- capped: true
10447
+ let on_down = (event) => {
10448
+ disarm();
10449
+ if (event.pointerType !== "touch") return;
10450
+ if (state.count > 1 || state.multi) return;
10451
+ if (enable && !enable(event)) return;
10452
+ let x = event.client?.x;
10453
+ let y = event.client?.y;
10454
+ if (x === void 0 || y === void 0) return;
10455
+ pending = {
10456
+ x,
10457
+ y,
10458
+ t0: Date.now(),
10459
+ event: Object.assign({}, event),
10460
+ tick: setTimeout(announce, 500)
10461
+ };
10497
10462
  };
10498
- return {
10499
- text,
10500
- capped: false
10463
+ let on_move = (event) => {
10464
+ if (!pending) return;
10465
+ if (state.count > 1 || state.multi) {
10466
+ disarm();
10467
+ return;
10468
+ }
10469
+ let x = event.client?.x;
10470
+ let y = event.client?.y;
10471
+ if (x === void 0 || y === void 0) return;
10472
+ if (press_moved(pending, x, y)) disarm();
10473
+ };
10474
+ let on_up = (event) => {
10475
+ let press = pending;
10476
+ if (press) clearTimeout(press.tick);
10477
+ pending = null;
10478
+ if (!press) return;
10479
+ if (state.multi) return;
10480
+ let x = event.client?.x;
10481
+ let y = event.client?.y;
10482
+ if (x === void 0) x = press.x;
10483
+ if (y === void 0) y = press.y;
10484
+ if (classify_press(press, x, y, Date.now()) !== "long") return;
10485
+ fire(press.event);
10486
+ };
10487
+ let on_touch_cancel = () => {
10488
+ disarm();
10489
+ };
10490
+ state.container?.addEventListener("touchcancel", on_touch_cancel);
10491
+ graph.on(_antv_g6.CommonEvent.POINTER_DOWN, on_down);
10492
+ graph.on(_antv_g6.CommonEvent.POINTER_MOVE, on_move);
10493
+ graph.on(_antv_g6.CommonEvent.POINTER_UP, on_up);
10494
+ return () => {
10495
+ disarm();
10496
+ state.container?.removeEventListener("touchcancel", on_touch_cancel);
10497
+ graph.off(_antv_g6.CommonEvent.POINTER_DOWN, on_down);
10498
+ graph.off(_antv_g6.CommonEvent.POINTER_MOVE, on_move);
10499
+ graph.off(_antv_g6.CommonEvent.POINTER_UP, on_up);
10501
10500
  };
10502
10501
  }
10503
- /************************************************************
10504
- * Which view a freshly mounted viewer opens on.
10502
+ //#endregion
10503
+ //#region src/g6_drag_canvas_touch.js
10504
+ /***********************************************************************
10505
+ * g6_drag_canvas_touch.js
10505
10506
  *
10506
- * Three answers, in order of authority: what the HOST asked
10507
- * for, what the READER chose last time, and `fallback`.
10507
+ * Drop-in replacement for G6 v5 built-in 'drag-canvas'.
10508
10508
  *
10509
- * The host wins because a viewer mounted to show a graph has
10510
- * to show one. The memory comes next, because reopening a
10511
- * document in the view you just left is the whole point of
10512
- * remembering it. `fallback` is the floor.
10509
+ * Why this exists
10510
+ * ----------------
10511
+ * G6 v5.1.0 drag-canvas computes the pan delta as:
10513
10512
  *
10514
- * Anything not in `modes` is treated as unsaid, which covers
10515
- * an absent value, an empty one, and a stored string from an
10516
- * older release that named a view no longer offered.
10517
- ************************************************************/
10518
- function pick_view_mode(asked, remembered, modes, fallback) {
10519
- let known = (m) => Array.isArray(modes) && modes.indexOf(m) >= 0;
10520
- if (known(asked)) return asked;
10521
- if (known(remembered)) return remembered;
10522
- return fallback;
10523
- }
10524
- /************************************************************
10525
- * The short label a container row carries beside its size.
10513
+ * const x = event.movement?.x ?? event.dx;
10526
10514
  *
10527
- * An array of dicts that carry an `id` is a list of RECORDS,
10528
- * and a row that says only `2: {15}` makes you open all
10529
- * fifteen fields of every one of them to find out WHICH
10530
- * record it is. The id is the one field that answers that,
10531
- * so the row says it without being opened.
10515
+ * but @antv/g always attaches a `movement` Point to every
10516
+ * federated pointer event (g-lite: `this.movement = new
10517
+ * Point()`), and fills it from the native
10518
+ * `PointerEvent.movementX/Y` (g-lite: `event.movement.x =
10519
+ * nativeEvent.movementX`). So `event.movement?.x` is never
10520
+ * nullish and the `?? event.dx` fallback never runs.
10532
10521
  *
10533
- * Only a SCALAR id, and only when it is really there: an `id`
10534
- * that is itself a dict or a list is not a name, it is more
10535
- * document, and printing `[object Object]` beside the size
10536
- * would be worse than printing nothing.
10522
+ * Native `movementX/Y`:
10523
+ * - on touch pointers most mobile browsers leave it at 0
10524
+ * (it is a mouse / pointer-lock feature), so the canvas
10525
+ * barely pans on mobile;
10526
+ * - on desktop it is skewed by OS pointer acceleration and
10527
+ * devicePixelRatio, so the graph lags the cursor.
10537
10528
  *
10538
- * Cut at `max_chars`, because this rides on a row whose job
10539
- * is to stay one line.
10540
- ************************************************************/
10541
- function container_label(value, max_chars) {
10542
- if (value === null || typeof value !== "object" || Array.isArray(value)) return "";
10543
- if (!Object.prototype.hasOwnProperty.call(value, "id")) return "";
10544
- let id = value.id;
10545
- let t = typeof id;
10546
- if (t !== "string" && t !== "number" && t !== "boolean") return "";
10547
- let text = String(id).trim();
10548
- if (text === "") return "";
10549
- let cap = typeof max_chars === "number" && max_chars > 0 ? max_chars : 0;
10550
- if (cap && text.length > cap) return text.slice(0, cap) + "…";
10551
- return text;
10552
- }
10553
- /************************************************************
10554
- * A dict, in the JSON sense: not an array and not null.
10555
- ************************************************************/
10556
- function is_plain_object(v) {
10557
- return v !== null && typeof v === "object" && !Array.isArray(v);
10558
- }
10559
- /************************************************************
10560
- * Does this container hold ONLY other containers?
10529
+ * The fix tracks `event.viewport` (the federated pointer
10530
+ * position already mapped to canvas/viewport space by
10531
+ * @antv/g, the exact units `translateBy` expects) and pans
10532
+ * by its frame-to-frame delta. This is:
10533
+ * - reliable on mouse AND touch (derived from clientX/Y,
10534
+ * never from native movementX);
10535
+ * - correct for ANY canvas scale (it is post-`getScale()`,
10536
+ * so a configured-vs-displayed size mismatch can no
10537
+ * longer make panning over- or under-shoot the cursor);
10538
+ * - the same source G6's own `zoom-canvas` uses for its
10539
+ * origin (`event.viewport`).
10540
+ * The subclass reuses all of the built-in clamp/cursor logic
10541
+ * and only swaps the delta source.
10561
10542
  *
10562
- * Such a value is not a thing, it is a LIST of things -- and a
10563
- * list is fully described by the row that names it in its
10564
- * parent, plus the edges to what it holds. Drawing it as a
10565
- * card of its own put a node in the middle of the graph with no
10566
- * data in it, saying nothing the parent's row did not already
10567
- * say, and pushed everything below it one level down.
10543
+ * Registered globally over the built-in 'drag-canvas' id so
10544
+ * every graph (tree, json, treedb, editor) is fixed without
10545
+ * touching each consumer's behaviors list.
10568
10546
  *
10569
- * An EMPTY container inside counts as content: `{0}` is
10570
- * something the card has to show, and there is no child card
10571
- * to show it instead.
10572
- ************************************************************/
10573
- function is_pure_collection(value) {
10574
- let entries = is_plain_object(value) ? Object.entries(value) : Array.isArray(value) ? value.map((v, i) => [i, v]) : [];
10575
- if (entries.length === 0) return false;
10576
- for (let [, v] of entries) if (!has_branch(v)) return false;
10577
- return true;
10578
- }
10547
+ * It also has to keep the ONE guard the base class has and
10548
+ * this replacement dropped: G6's `onDrag` refuses to pan
10549
+ * while a pinch is in progress. See `is_pinching` in
10550
+ * `g6_touch_gestures.js`, which is where the pinch that
10551
+ * matters is now recognised.
10552
+ *
10553
+ * Copyright (c) 2026, ArtGins.
10554
+ * All Rights Reserved.
10555
+ ***********************************************************************/
10579
10556
  /************************************************************
10580
- * A value that gets a card, or children, of its own.
10557
+ * Custom drag-canvas: same behavior, reliable delta source
10581
10558
  ************************************************************/
10582
- function has_branch(value) {
10583
- return is_plain_object(value) && Object.keys(value).length > 0 || Array.isArray(value) && value.length > 0;
10584
- }
10559
+ var DragCanvasTouch = class extends _antv_g6.DragCanvas {
10560
+ constructor(context, options) {
10561
+ super(context, options);
10562
+ this._lastVP = null;
10563
+ this._onDragTouch = (event) => {
10564
+ if (!this.isDragging) {
10565
+ this._lastVP = null;
10566
+ return;
10567
+ }
10568
+ if (is_pinching(this.context.graph)) {
10569
+ this._lastVP = null;
10570
+ return;
10571
+ }
10572
+ let vp = event.viewport;
10573
+ if (!vp || typeof vp.x !== "number" || typeof vp.y !== "number") return;
10574
+ if (this._lastVP === null) {
10575
+ this._lastVP = {
10576
+ x: vp.x,
10577
+ y: vp.y
10578
+ };
10579
+ return;
10580
+ }
10581
+ let x = vp.x - this._lastVP.x;
10582
+ let y = vp.y - this._lastVP.y;
10583
+ this._lastVP.x = vp.x;
10584
+ this._lastVP.y = vp.y;
10585
+ if (x !== 0 || y !== 0) this.translate([x, y], false);
10586
+ };
10587
+ this._onDragEndReset = () => {
10588
+ this._lastVP = null;
10589
+ };
10590
+ let graph = this.context.graph;
10591
+ graph.off(_antv_g6.CommonEvent.DRAG, this.onDrag);
10592
+ graph.on(_antv_g6.CommonEvent.DRAG, this._onDragTouch);
10593
+ graph.on(_antv_g6.CommonEvent.DRAG_END, this._onDragEndReset);
10594
+ }
10595
+ update(options) {
10596
+ super.update(options);
10597
+ let graph = this.context.graph;
10598
+ graph.off(_antv_g6.CommonEvent.DRAG, this.onDrag);
10599
+ graph.off(_antv_g6.CommonEvent.DRAG, this._onDragTouch);
10600
+ graph.off(_antv_g6.CommonEvent.DRAG_END, this._onDragEndReset);
10601
+ graph.on(_antv_g6.CommonEvent.DRAG, this._onDragTouch);
10602
+ graph.on(_antv_g6.CommonEvent.DRAG_END, this._onDragEndReset);
10603
+ }
10604
+ destroy() {
10605
+ let graph = this.context.graph;
10606
+ graph.off(_antv_g6.CommonEvent.DRAG, this._onDragTouch);
10607
+ graph.off(_antv_g6.CommonEvent.DRAG_END, this._onDragEndReset);
10608
+ super.destroy();
10609
+ }
10610
+ };
10585
10611
  /************************************************************
10586
- * How many branches a container has, which is what the fold
10587
- * chip counts.
10612
+ * Register once, replacing the built-in 'drag-canvas'
10588
10613
  ************************************************************/
10589
- function count_branches(value) {
10590
- let entries = is_plain_object(value) ? Object.values(value) : Array.isArray(value) ? value : [];
10591
- let n = 0;
10592
- for (let v of entries) if (has_branch(v)) n++;
10593
- return n;
10614
+ var __drag_canvas_patched__ = false;
10615
+ function ensure_drag_canvas_patch() {
10616
+ if (__drag_canvas_patched__) return;
10617
+ (0, _antv_g6.register)(_antv_g6.ExtensionCategory.BEHAVIOR, "drag-canvas", DragCanvasTouch);
10618
+ __drag_canvas_patched__ = true;
10594
10619
  }
10595
10620
  //#endregion
10596
10621
  //#region src/c_yui_json_graph.js