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