@yuneta/gobj-ui 2.2.0 → 2.2.4

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.
@@ -816,6 +816,17 @@ function update_stats() {
816
816
  });
817
817
  }
818
818
  /************************************************************
819
+ * Append a row and follow the tail — but only when the user is
820
+ * already at (or near) the bottom; a reader scrolled up into the
821
+ * backlog must not be yanked down. Container-local on purpose:
822
+ * scrollIntoView also scrolls the host page's ancestors.
823
+ ************************************************************/
824
+ function append_and_follow(logger, node) {
825
+ let at_bottom = logger.scrollHeight - logger.scrollTop - logger.clientHeight < 40;
826
+ logger.appendChild(node);
827
+ if (at_bottom) logger.scrollTop = logger.scrollHeight;
828
+ }
829
+ /************************************************************
819
830
  * Append one inter-event message. Kept in a bounded buffer so
820
831
  * view/filter switches repaint from memory. Shared by the legacy
821
832
  * C_YUI_WINDOW (setup_dev) and the modal (build_dev_panel).
@@ -876,8 +887,7 @@ function info_traffic(title, msg, direction, size) {
876
887
  entry.$node = node;
877
888
  let ph = logger.querySelector(".YDEV_EMPTY");
878
889
  if (ph) ph.remove();
879
- logger.appendChild(node);
880
- node.scrollIntoView({ block: "end" });
890
+ append_and_follow(logger, node);
881
891
  }
882
892
  update_stats();
883
893
  }
@@ -931,8 +941,7 @@ function info_log(level, msg, hora) {
931
941
  entry.$node = node;
932
942
  let ph = logger.querySelector(".YDEV_EMPTY");
933
943
  if (ph) ph.remove();
934
- logger.appendChild(node);
935
- node.scrollIntoView({ block: "end" });
944
+ append_and_follow(logger, node);
936
945
  }
937
946
  update_stats();
938
947
  } finally {
@@ -1061,6 +1070,11 @@ function traffic_to_text() {
1061
1070
  let out = [];
1062
1071
  for (let e of TRAFFIC_LOG) {
1063
1072
  if (entry_hidden(e, ctx)) continue;
1073
+ if (e.kind === "log") {
1074
+ out.push(`${e.ts} ${e.level}: ${e.text}`);
1075
+ out.push("");
1076
+ continue;
1077
+ }
1064
1078
  let head = `${e.ts} ${dir_arrow(e.dir)} ${e.title ? "[" + e.title + "] " : ""}${e.event}${e.command ? " " + e.command : ""}`;
1065
1079
  out.push(head);
1066
1080
  let payload = e.kw ? e.kw : e.jn;
@@ -2857,7 +2871,7 @@ function resolve_manager(gobj) {
2857
2871
  let m = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "manager");
2858
2872
  if (!m) return null;
2859
2873
  if (typeof m === "string") {
2860
- m = (0, _yuneta_gobj_js.gobj_find_service)(m, false);
2874
+ m = (0, _yuneta_gobj_js.gobj_find_service)(m, false) || null;
2861
2875
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "manager", m);
2862
2876
  }
2863
2877
  return m;
@@ -3154,13 +3168,14 @@ function close_window(gobj) {
3154
3168
  let kw_close = { abort_close: false };
3155
3169
  (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_WINDOW_TO_CLOSE", kw_close);
3156
3170
  let on_close = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "on_close");
3157
- if (on_close) on_close();
3158
3171
  if (!kw_close.abort_close) {
3172
+ if (on_close) on_close();
3159
3173
  if ((0, _yuneta_gobj_js.gobj_is_running)(gobj)) (0, _yuneta_gobj_js.gobj_stop)(gobj);
3160
3174
  (0, _yuneta_gobj_js.gobj_stop_children)(gobj);
3161
3175
  (0, _yuneta_gobj_js.gobj_destroy)(gobj);
3162
3176
  } else if (kw_close.warning) get_yesnocancel(kw_close.warning, function(resp) {
3163
3177
  if (resp === "yes") {
3178
+ if (on_close) on_close();
3164
3179
  (0, _yuneta_gobj_js.gobj_stop)(gobj);
3165
3180
  (0, _yuneta_gobj_js.gobj_stop_children)(gobj);
3166
3181
  (0, _yuneta_gobj_js.gobj_destroy)(gobj);
@@ -3242,6 +3257,9 @@ function mvStart(gobj, evt) {
3242
3257
  $container.style.transform = "translate3d(" + div_x + "px, " + div_y + "px, 0px)";
3243
3258
  }
3244
3259
  function mvStop(evt) {
3260
+ document.removeEventListener("pointermove", mvMove);
3261
+ document.removeEventListener("pointerup", mvStop);
3262
+ if ((0, _yuneta_gobj_js.gobj_is_destroying)(gobj)) return;
3245
3263
  div_x = evt.screenX - x;
3246
3264
  div_y = evt.screenY - y;
3247
3265
  let xx = pos_x + div_x;
@@ -3250,8 +3268,6 @@ function mvStart(gobj, evt) {
3250
3268
  $container.style.top = yy + "px";
3251
3269
  $container.style.transition = "none";
3252
3270
  $container.style.transform = "translate3d(0px, 0px, 0px)";
3253
- document.removeEventListener("pointermove", mvMove);
3254
- document.removeEventListener("pointerup", mvStop);
3255
3271
  let rect = $container.getBoundingClientRect();
3256
3272
  (0, _yuneta_gobj_js.gobj_write_integer_attr)(gobj, "x", rect.x);
3257
3273
  (0, _yuneta_gobj_js.gobj_write_integer_attr)(gobj, "y", rect.y);
@@ -3291,12 +3307,13 @@ function rsStart(gobj, evt) {
3291
3307
  $container.style.height = height + rel_h + "px";
3292
3308
  }
3293
3309
  function rsStop(evt) {
3310
+ document.removeEventListener("pointermove", rsMove);
3311
+ document.removeEventListener("pointerup", rsStop);
3312
+ if ((0, _yuneta_gobj_js.gobj_is_destroying)(gobj)) return;
3294
3313
  rel_w = evt.pageX - pageX;
3295
3314
  rel_h = evt.pageY - pageY;
3296
3315
  $container.style.width = width + rel_w + "px";
3297
3316
  $container.style.height = height + rel_h + "px";
3298
- document.removeEventListener("pointermove", rsMove);
3299
- document.removeEventListener("pointerup", rsStop);
3300
3317
  let rect = $container.getBoundingClientRect();
3301
3318
  if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "force_center")) {
3302
3319
  rect = do_center(gobj, rect.x, rect.y, rect.width, rect.height);
@@ -3675,7 +3692,7 @@ function build_dock(gobj) {
3675
3692
  ensure_dock_style();
3676
3693
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", (0, _yuneta_gobj_js.createElement2)([
3677
3694
  "div",
3678
- { class: "yui-dock is-empty" },
3695
+ { class: "C_YUI_WINDOW_MANAGER yui-dock is-empty" },
3679
3696
  []
3680
3697
  ]));
3681
3698
  apply_placement(gobj);
@@ -3861,10 +3878,20 @@ function ac_register_window(gobj, event, kw, src) {
3861
3878
  } }
3862
3879
  ]
3863
3880
  ],
3864
- { click: (evt) => {
3865
- evt.stopPropagation();
3866
- on_chip_click(gobj, entry);
3867
- } }
3881
+ {
3882
+ click: (evt) => {
3883
+ evt.stopPropagation();
3884
+ on_chip_click(gobj, entry);
3885
+ },
3886
+ keydown: (evt) => {
3887
+ if (evt.target !== evt.currentTarget) return;
3888
+ if (evt.key === "Enter" || evt.key === " ") {
3889
+ if (evt.key === " ") evt.preventDefault();
3890
+ evt.stopPropagation();
3891
+ on_chip_click(gobj, entry);
3892
+ }
3893
+ }
3894
+ }
3868
3895
  ]);
3869
3896
  entry.$chip = $chip;
3870
3897
  priv.windows.push(entry);
@@ -12340,9 +12367,10 @@ function navigate_to(gobj, route) {
12340
12367
  let prev_route = stage.active_route;
12341
12368
  if (prev_route && prev_route !== matched_route) {
12342
12369
  let prev_gobj = stage.items[prev_route];
12370
+ let $prev = null;
12343
12371
  if (prev_gobj) {
12344
- let $c = (0, _yuneta_gobj_js.gobj_read_attr)(prev_gobj, "$container");
12345
- if ($c) $c.classList.add("is-hidden");
12372
+ $prev = (0, _yuneta_gobj_js.gobj_read_attr)(prev_gobj, "$container");
12373
+ if ($prev) $prev.classList.add("is-hidden");
12346
12374
  }
12347
12375
  let prev_entry = priv.item_index[prev_route];
12348
12376
  if (prev_entry && prev_entry.target && prev_entry.target.lifecycle === "lazy_destroy") {
@@ -12352,6 +12380,7 @@ function navigate_to(gobj, route) {
12352
12380
  } catch (e) {
12353
12381
  (0, _yuneta_gobj_js.log_warning)(`C_YUI_SHELL: lazy_destroy of '${prev_route}' failed: ${e}`);
12354
12382
  }
12383
+ if ($prev && $prev.parentNode) $prev.parentNode.removeChild($prev);
12355
12384
  delete stage.items[prev_route];
12356
12385
  }
12357
12386
  }
@@ -1,4 +1,4 @@
1
- import { SDATA, SDATA_END, clean_name, createElement2, createOneHtml, create_template_record, data_type_t, debounce, duplicate_objects, empty_string, escapeHtml, event_flag_t, gclass_create, gclass_flag_t, getPositionRelativeToBody, gobj_command, gobj_create, gobj_create_service, gobj_current_state, gobj_default_service, gobj_destroy, gobj_find_child, gobj_find_service, gobj_full_name, gobj_gclass_name, gobj_has_attr, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_match_children, gobj_name, gobj_parent, gobj_publish_event, gobj_read_attr, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_save_persistent_attrs, gobj_send_event, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_stop, gobj_stop_children, gobj_subscribe_event, gobj_unsubscribe_event, gobj_write_attr, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, is_array, is_boolean, is_date, is_gobj, is_null, is_number, is_object, is_pure_number, is_string, json_deep_copy, json_object_size, json_object_update, json_object_update_missing, json_size, kw_clone_by_keys, kw_flag_t, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_local_storage_value, kw_get_str, kw_has_key, kw_set_dict_value, kw_set_local_storage_value, kwid_find_one_record, kwid_get_ids, kwid_new_dict, list2options, log_error, log_warning, msg_iev_get_stack, parseBoolean, parseSVG, refresh_language, safeSrc, sdata_flag_t, set_log_callback, sprintf, str_in_list, template_get_field_desc, trace_json, trace_msg, treedb_decoder_fkey, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size } from "@yuneta/gobj-js";
1
+ import { SDATA, SDATA_END, clean_name, createElement2, createOneHtml, create_template_record, data_type_t, debounce, duplicate_objects, empty_string, escapeHtml, event_flag_t, gclass_create, gclass_flag_t, getPositionRelativeToBody, gobj_command, gobj_create, gobj_create_service, gobj_current_state, gobj_default_service, gobj_destroy, gobj_find_child, gobj_find_service, gobj_full_name, gobj_gclass_name, gobj_has_attr, gobj_is_destroying, gobj_is_playing, gobj_is_pure_child, gobj_is_running, gobj_is_service, gobj_is_volatil, gobj_match_children, gobj_name, gobj_parent, gobj_publish_event, gobj_read_attr, gobj_read_bool_attr, gobj_read_integer_attr, gobj_read_pointer_attr, gobj_read_str_attr, gobj_save_persistent_attrs, gobj_send_event, gobj_set_trace_machine_format, gobj_short_name, gobj_start, gobj_stop, gobj_stop_children, gobj_subscribe_event, gobj_unsubscribe_event, gobj_write_attr, gobj_write_bool_attr, gobj_write_integer_attr, gobj_write_str_attr, gobj_yuno, is_array, is_boolean, is_date, is_gobj, is_null, is_number, is_object, is_pure_number, is_string, json_deep_copy, json_object_size, json_object_update, json_object_update_missing, json_size, kw_clone_by_keys, kw_flag_t, kw_get_dict, kw_get_dict_value, kw_get_int, kw_get_local_storage_value, kw_get_str, kw_has_key, kw_set_dict_value, kw_set_local_storage_value, kwid_find_one_record, kwid_get_ids, kwid_new_dict, list2options, log_error, log_warning, msg_iev_get_stack, parseBoolean, parseSVG, refresh_language, safeSrc, sdata_flag_t, set_log_callback, sprintf, str_in_list, template_get_field_desc, trace_json, trace_msg, treedb_decoder_fkey, treedb_encoder_fkey, treedb_get_field_desc, treedb_hook_data_size } from "@yuneta/gobj-js";
2
2
  import i18next, { t } from "i18next";
3
3
  import TomSelect from "tom-select";
4
4
  import { TabulatorFull } from "tabulator-tables";
@@ -811,6 +811,17 @@ function update_stats() {
811
811
  });
812
812
  }
813
813
  /************************************************************
814
+ * Append a row and follow the tail — but only when the user is
815
+ * already at (or near) the bottom; a reader scrolled up into the
816
+ * backlog must not be yanked down. Container-local on purpose:
817
+ * scrollIntoView also scrolls the host page's ancestors.
818
+ ************************************************************/
819
+ function append_and_follow(logger, node) {
820
+ let at_bottom = logger.scrollHeight - logger.scrollTop - logger.clientHeight < 40;
821
+ logger.appendChild(node);
822
+ if (at_bottom) logger.scrollTop = logger.scrollHeight;
823
+ }
824
+ /************************************************************
814
825
  * Append one inter-event message. Kept in a bounded buffer so
815
826
  * view/filter switches repaint from memory. Shared by the legacy
816
827
  * C_YUI_WINDOW (setup_dev) and the modal (build_dev_panel).
@@ -871,8 +882,7 @@ function info_traffic(title, msg, direction, size) {
871
882
  entry.$node = node;
872
883
  let ph = logger.querySelector(".YDEV_EMPTY");
873
884
  if (ph) ph.remove();
874
- logger.appendChild(node);
875
- node.scrollIntoView({ block: "end" });
885
+ append_and_follow(logger, node);
876
886
  }
877
887
  update_stats();
878
888
  }
@@ -926,8 +936,7 @@ function info_log(level, msg, hora) {
926
936
  entry.$node = node;
927
937
  let ph = logger.querySelector(".YDEV_EMPTY");
928
938
  if (ph) ph.remove();
929
- logger.appendChild(node);
930
- node.scrollIntoView({ block: "end" });
939
+ append_and_follow(logger, node);
931
940
  }
932
941
  update_stats();
933
942
  } finally {
@@ -1056,6 +1065,11 @@ function traffic_to_text() {
1056
1065
  let out = [];
1057
1066
  for (let e of TRAFFIC_LOG) {
1058
1067
  if (entry_hidden(e, ctx)) continue;
1068
+ if (e.kind === "log") {
1069
+ out.push(`${e.ts} ${e.level}: ${e.text}`);
1070
+ out.push("");
1071
+ continue;
1072
+ }
1059
1073
  let head = `${e.ts} ${dir_arrow(e.dir)} ${e.title ? "[" + e.title + "] " : ""}${e.event}${e.command ? " " + e.command : ""}`;
1060
1074
  out.push(head);
1061
1075
  let payload = e.kw ? e.kw : e.jn;
@@ -2852,7 +2866,7 @@ function resolve_manager(gobj) {
2852
2866
  let m = gobj_read_pointer_attr(gobj, "manager");
2853
2867
  if (!m) return null;
2854
2868
  if (typeof m === "string") {
2855
- m = gobj_find_service(m, false);
2869
+ m = gobj_find_service(m, false) || null;
2856
2870
  gobj_write_attr(gobj, "manager", m);
2857
2871
  }
2858
2872
  return m;
@@ -3149,13 +3163,14 @@ function close_window(gobj) {
3149
3163
  let kw_close = { abort_close: false };
3150
3164
  gobj_publish_event(gobj, "EV_WINDOW_TO_CLOSE", kw_close);
3151
3165
  let on_close = gobj_read_attr(gobj, "on_close");
3152
- if (on_close) on_close();
3153
3166
  if (!kw_close.abort_close) {
3167
+ if (on_close) on_close();
3154
3168
  if (gobj_is_running(gobj)) gobj_stop(gobj);
3155
3169
  gobj_stop_children(gobj);
3156
3170
  gobj_destroy(gobj);
3157
3171
  } else if (kw_close.warning) get_yesnocancel(kw_close.warning, function(resp) {
3158
3172
  if (resp === "yes") {
3173
+ if (on_close) on_close();
3159
3174
  gobj_stop(gobj);
3160
3175
  gobj_stop_children(gobj);
3161
3176
  gobj_destroy(gobj);
@@ -3237,6 +3252,9 @@ function mvStart(gobj, evt) {
3237
3252
  $container.style.transform = "translate3d(" + div_x + "px, " + div_y + "px, 0px)";
3238
3253
  }
3239
3254
  function mvStop(evt) {
3255
+ document.removeEventListener("pointermove", mvMove);
3256
+ document.removeEventListener("pointerup", mvStop);
3257
+ if (gobj_is_destroying(gobj)) return;
3240
3258
  div_x = evt.screenX - x;
3241
3259
  div_y = evt.screenY - y;
3242
3260
  let xx = pos_x + div_x;
@@ -3245,8 +3263,6 @@ function mvStart(gobj, evt) {
3245
3263
  $container.style.top = yy + "px";
3246
3264
  $container.style.transition = "none";
3247
3265
  $container.style.transform = "translate3d(0px, 0px, 0px)";
3248
- document.removeEventListener("pointermove", mvMove);
3249
- document.removeEventListener("pointerup", mvStop);
3250
3266
  let rect = $container.getBoundingClientRect();
3251
3267
  gobj_write_integer_attr(gobj, "x", rect.x);
3252
3268
  gobj_write_integer_attr(gobj, "y", rect.y);
@@ -3286,12 +3302,13 @@ function rsStart(gobj, evt) {
3286
3302
  $container.style.height = height + rel_h + "px";
3287
3303
  }
3288
3304
  function rsStop(evt) {
3305
+ document.removeEventListener("pointermove", rsMove);
3306
+ document.removeEventListener("pointerup", rsStop);
3307
+ if (gobj_is_destroying(gobj)) return;
3289
3308
  rel_w = evt.pageX - pageX;
3290
3309
  rel_h = evt.pageY - pageY;
3291
3310
  $container.style.width = width + rel_w + "px";
3292
3311
  $container.style.height = height + rel_h + "px";
3293
- document.removeEventListener("pointermove", rsMove);
3294
- document.removeEventListener("pointerup", rsStop);
3295
3312
  let rect = $container.getBoundingClientRect();
3296
3313
  if (gobj_read_bool_attr(gobj, "force_center")) {
3297
3314
  rect = do_center(gobj, rect.x, rect.y, rect.width, rect.height);
@@ -3670,7 +3687,7 @@ function build_dock(gobj) {
3670
3687
  ensure_dock_style();
3671
3688
  gobj_write_attr(gobj, "$container", createElement2([
3672
3689
  "div",
3673
- { class: "yui-dock is-empty" },
3690
+ { class: "C_YUI_WINDOW_MANAGER yui-dock is-empty" },
3674
3691
  []
3675
3692
  ]));
3676
3693
  apply_placement(gobj);
@@ -3856,10 +3873,20 @@ function ac_register_window(gobj, event, kw, src) {
3856
3873
  } }
3857
3874
  ]
3858
3875
  ],
3859
- { click: (evt) => {
3860
- evt.stopPropagation();
3861
- on_chip_click(gobj, entry);
3862
- } }
3876
+ {
3877
+ click: (evt) => {
3878
+ evt.stopPropagation();
3879
+ on_chip_click(gobj, entry);
3880
+ },
3881
+ keydown: (evt) => {
3882
+ if (evt.target !== evt.currentTarget) return;
3883
+ if (evt.key === "Enter" || evt.key === " ") {
3884
+ if (evt.key === " ") evt.preventDefault();
3885
+ evt.stopPropagation();
3886
+ on_chip_click(gobj, entry);
3887
+ }
3888
+ }
3889
+ }
3863
3890
  ]);
3864
3891
  entry.$chip = $chip;
3865
3892
  priv.windows.push(entry);
@@ -12335,9 +12362,10 @@ function navigate_to(gobj, route) {
12335
12362
  let prev_route = stage.active_route;
12336
12363
  if (prev_route && prev_route !== matched_route) {
12337
12364
  let prev_gobj = stage.items[prev_route];
12365
+ let $prev = null;
12338
12366
  if (prev_gobj) {
12339
- let $c = gobj_read_attr(prev_gobj, "$container");
12340
- if ($c) $c.classList.add("is-hidden");
12367
+ $prev = gobj_read_attr(prev_gobj, "$container");
12368
+ if ($prev) $prev.classList.add("is-hidden");
12341
12369
  }
12342
12370
  let prev_entry = priv.item_index[prev_route];
12343
12371
  if (prev_entry && prev_entry.target && prev_entry.target.lifecycle === "lazy_destroy") {
@@ -12347,6 +12375,7 @@ function navigate_to(gobj, route) {
12347
12375
  } catch (e) {
12348
12376
  log_warning(`C_YUI_SHELL: lazy_destroy of '${prev_route}' failed: ${e}`);
12349
12377
  }
12378
+ if ($prev && $prev.parentNode) $prev.parentNode.removeChild($prev);
12350
12379
  delete stage.items[prev_route];
12351
12380
  }
12352
12381
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "2.2.0",
3
+ "version": "2.2.4",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -29,13 +29,13 @@
29
29
  "devDependencies": {
30
30
  "@rollup/plugin-terser": "^1.0.0",
31
31
  "@vitest/coverage-v8": "^4.1.6",
32
- "@yuneta/gobj-js": "^7.3.4",
32
+ "@yuneta/gobj-js": "^7.7.0",
33
33
  "vite": "^8.0.10",
34
34
  "vitest": "^4.1.6"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@antv/g6": "^5.1.0",
38
- "@yuneta/gobj-js": "^7.3.4",
38
+ "@yuneta/gobj-js": "^7.7.0",
39
39
  "bulma": "^1.0.4",
40
40
  "i18next": "^26.0.7",
41
41
  "maplibre-gl": "^5.24.0",
@@ -1050,10 +1050,11 @@ function navigate_to(gobj, route)
1050
1050
  let prev_route = stage.active_route;
1051
1051
  if(prev_route && prev_route !== matched_route) {
1052
1052
  let prev_gobj = stage.items[prev_route];
1053
+ let $prev = null;
1053
1054
  if(prev_gobj) {
1054
- let $c = gobj_read_attr(prev_gobj, "$container");
1055
- if($c) {
1056
- $c.classList.add("is-hidden");
1055
+ $prev = gobj_read_attr(prev_gobj, "$container");
1056
+ if($prev) {
1057
+ $prev.classList.add("is-hidden");
1057
1058
  }
1058
1059
  }
1059
1060
  /* lazy_destroy: drop previous on exit */
@@ -1065,6 +1066,14 @@ function navigate_to(gobj, route)
1065
1066
  } catch(e) {
1066
1067
  log_warning(`C_YUI_SHELL: lazy_destroy of '${prev_route}' failed: ${e}`);
1067
1068
  }
1069
+ /* The shell appended $container on mount (build_view_gobj), so
1070
+ * remove it symmetrically here: a view that doesn't remove its
1071
+ * own container in mt_destroy would otherwise leak a hidden
1072
+ * copy in the stage on every revisit — and any fixed DOM id
1073
+ * inside it then shadows the fresh instance's. */
1074
+ if($prev && $prev.parentNode) {
1075
+ $prev.parentNode.removeChild($prev);
1076
+ }
1068
1077
  delete stage.items[prev_route];
1069
1078
  }
1070
1079
  }
@@ -38,6 +38,7 @@ import {
38
38
  gobj_stop,
39
39
  gobj_stop_children,
40
40
  gobj_is_running,
41
+ gobj_is_destroying,
41
42
  refresh_language,
42
43
  } from "@yuneta/gobj-js";
43
44
 
@@ -287,7 +288,7 @@ function resolve_manager(gobj)
287
288
  return null;
288
289
  }
289
290
  if(typeof m === "string") {
290
- m = gobj_find_service(m, false);
291
+ m = gobj_find_service(m, false) || null;
291
292
  gobj_write_attr(gobj, "manager", m);
292
293
  }
293
294
  return m;
@@ -656,12 +657,15 @@ function close_window(gobj)
656
657
  abort_close: false
657
658
  };
658
659
  gobj_publish_event(gobj, "EV_WINDOW_TO_CLOSE", kw_close);
660
+
661
+ /* on_close only fires when the close actually proceeds:
662
+ * a subscriber may abort it and keep the window open. */
659
663
  let on_close = gobj_read_attr(gobj, "on_close");
660
- if(on_close) {
661
- on_close();
662
- }
663
664
 
664
665
  if(!kw_close.abort_close) {
666
+ if(on_close) {
667
+ on_close();
668
+ }
665
669
  if(gobj_is_running(gobj)) {
666
670
  gobj_stop(gobj);
667
671
  }
@@ -670,6 +674,9 @@ function close_window(gobj)
670
674
  } else if(kw_close.warning) {
671
675
  get_yesnocancel(kw_close.warning, function(resp) {
672
676
  if(resp === "yes") {
677
+ if(on_close) {
678
+ on_close();
679
+ }
673
680
  gobj_stop(gobj);
674
681
  gobj_stop_children(gobj);
675
682
  gobj_destroy(gobj);
@@ -794,6 +801,14 @@ function mvStart(gobj, evt)
794
801
 
795
802
  function mvStop(evt)
796
803
  {
804
+ document.removeEventListener('pointermove', mvMove);
805
+ document.removeEventListener('pointerup', mvStop);
806
+
807
+ /* The window can be destroyed mid-drag (e.g. dock ✕). */
808
+ if(gobj_is_destroying(gobj)) {
809
+ return;
810
+ }
811
+
797
812
  div_x = (evt.screenX - x);
798
813
  div_y = (evt.screenY - y);
799
814
  let xx = pos_x + div_x;
@@ -804,9 +819,6 @@ function mvStart(gobj, evt)
804
819
  $container.style.transition = 'none';
805
820
  $container.style.transform = 'translate3d(0px, 0px, 0px)';
806
821
 
807
- document.removeEventListener('pointermove', mvMove);
808
- document.removeEventListener('pointerup', mvStop);
809
-
810
822
  // trigger event
811
823
  let rect = $container.getBoundingClientRect();
812
824
 
@@ -859,15 +871,20 @@ function rsStart(gobj, evt)
859
871
 
860
872
  function rsStop(evt)
861
873
  {
874
+ document.removeEventListener('pointermove', rsMove);
875
+ document.removeEventListener('pointerup', rsStop);
876
+
877
+ /* The window can be destroyed mid-resize (e.g. dock ✕). */
878
+ if(gobj_is_destroying(gobj)) {
879
+ return;
880
+ }
881
+
862
882
  rel_w = evt.pageX - pageX;
863
883
  rel_h = evt.pageY - pageY;
864
884
 
865
885
  $container.style.width = (width + rel_w) +'px';
866
886
  $container.style.height = (height + rel_h) +'px';
867
887
 
868
- document.removeEventListener('pointermove', rsMove);
869
- document.removeEventListener('pointerup', rsStop);
870
-
871
888
  // trigger event
872
889
  let rect = $container.getBoundingClientRect();
873
890
  if(gobj_read_bool_attr(gobj, "force_center")) {
@@ -228,7 +228,7 @@ function build_dock(gobj)
228
228
  {
229
229
  ensure_dock_style();
230
230
 
231
- let $dock = createElement2(['div', {class: 'yui-dock is-empty'}, []]);
231
+ let $dock = createElement2(['div', {class: 'C_YUI_WINDOW_MANAGER yui-dock is-empty'}, []]);
232
232
  gobj_write_attr(gobj, "$container", $dock);
233
233
 
234
234
  apply_placement(gobj);
@@ -462,6 +462,20 @@ function ac_register_window(gobj, event, kw, src)
462
462
  click: (evt) => {
463
463
  evt.stopPropagation();
464
464
  on_chip_click(gobj, entry);
465
+ },
466
+ keydown: (evt) => {
467
+ /* Only the chip itself — Enter on the inner ✕ button
468
+ * already fires its own click. */
469
+ if(evt.target !== evt.currentTarget) {
470
+ return;
471
+ }
472
+ if(evt.key === "Enter" || evt.key === " ") {
473
+ if(evt.key === " ") {
474
+ evt.preventDefault();
475
+ }
476
+ evt.stopPropagation();
477
+ on_chip_click(gobj, entry);
478
+ }
465
479
  }
466
480
  }]
467
481
  );
package/src/yui_dev.js CHANGED
@@ -808,6 +808,22 @@ function update_stats()
808
808
  });
809
809
  }
810
810
 
811
+ /************************************************************
812
+ * Append a row and follow the tail — but only when the user is
813
+ * already at (or near) the bottom; a reader scrolled up into the
814
+ * backlog must not be yanked down. Container-local on purpose:
815
+ * scrollIntoView also scrolls the host page's ancestors.
816
+ ************************************************************/
817
+ function append_and_follow(logger, node)
818
+ {
819
+ let at_bottom =
820
+ logger.scrollHeight - logger.scrollTop - logger.clientHeight < 40;
821
+ logger.appendChild(node);
822
+ if(at_bottom) {
823
+ logger.scrollTop = logger.scrollHeight;
824
+ }
825
+ }
826
+
811
827
  /************************************************************
812
828
  * Append one inter-event message. Kept in a bounded buffer so
813
829
  * view/filter switches repaint from memory. Shared by the legacy
@@ -886,8 +902,7 @@ function info_traffic(title, msg, direction, size)
886
902
  if(ph) {
887
903
  ph.remove();
888
904
  }
889
- logger.appendChild(node);
890
- node.scrollIntoView({block: "end"});
905
+ append_and_follow(logger, node);
891
906
  }
892
907
  update_stats();
893
908
  }
@@ -956,8 +971,7 @@ function info_log(level, msg, hora)
956
971
  if(ph) {
957
972
  ph.remove();
958
973
  }
959
- logger.appendChild(node);
960
- node.scrollIntoView({block: "end"});
974
+ append_and_follow(logger, node);
961
975
  }
962
976
  update_stats();
963
977
  } finally {
@@ -1130,6 +1144,12 @@ function traffic_to_text()
1130
1144
  if(entry_hidden(e, ctx)) {
1131
1145
  continue;
1132
1146
  }
1147
+ if(e.kind === "log") {
1148
+ /* Mirrored log/automata line: no event/kw — serialize as shown. */
1149
+ out.push(`${e.ts} ${e.level}: ${e.text}`);
1150
+ out.push("");
1151
+ continue;
1152
+ }
1133
1153
  let head = `${e.ts} ${dir_arrow(e.dir)} ` +
1134
1154
  `${e.title ? "[" + e.title + "] " : ""}${e.event}` +
1135
1155
  `${e.command ? " " + e.command : ""}`;