@yuneta/gobj-ui 2.2.3 → 2.2.5

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 {
@@ -2862,7 +2871,7 @@ function resolve_manager(gobj) {
2862
2871
  let m = (0, _yuneta_gobj_js.gobj_read_pointer_attr)(gobj, "manager");
2863
2872
  if (!m) return null;
2864
2873
  if (typeof m === "string") {
2865
- m = (0, _yuneta_gobj_js.gobj_find_service)(m, false);
2874
+ m = (0, _yuneta_gobj_js.gobj_find_service)(m, false) || null;
2866
2875
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "manager", m);
2867
2876
  }
2868
2877
  return m;
@@ -3159,13 +3168,14 @@ function close_window(gobj) {
3159
3168
  let kw_close = { abort_close: false };
3160
3169
  (0, _yuneta_gobj_js.gobj_publish_event)(gobj, "EV_WINDOW_TO_CLOSE", kw_close);
3161
3170
  let on_close = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "on_close");
3162
- if (on_close) on_close();
3163
3171
  if (!kw_close.abort_close) {
3172
+ if (on_close) on_close();
3164
3173
  if ((0, _yuneta_gobj_js.gobj_is_running)(gobj)) (0, _yuneta_gobj_js.gobj_stop)(gobj);
3165
3174
  (0, _yuneta_gobj_js.gobj_stop_children)(gobj);
3166
3175
  (0, _yuneta_gobj_js.gobj_destroy)(gobj);
3167
3176
  } else if (kw_close.warning) get_yesnocancel(kw_close.warning, function(resp) {
3168
3177
  if (resp === "yes") {
3178
+ if (on_close) on_close();
3169
3179
  (0, _yuneta_gobj_js.gobj_stop)(gobj);
3170
3180
  (0, _yuneta_gobj_js.gobj_stop_children)(gobj);
3171
3181
  (0, _yuneta_gobj_js.gobj_destroy)(gobj);
@@ -3247,6 +3257,9 @@ function mvStart(gobj, evt) {
3247
3257
  $container.style.transform = "translate3d(" + div_x + "px, " + div_y + "px, 0px)";
3248
3258
  }
3249
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;
3250
3263
  div_x = evt.screenX - x;
3251
3264
  div_y = evt.screenY - y;
3252
3265
  let xx = pos_x + div_x;
@@ -3255,8 +3268,6 @@ function mvStart(gobj, evt) {
3255
3268
  $container.style.top = yy + "px";
3256
3269
  $container.style.transition = "none";
3257
3270
  $container.style.transform = "translate3d(0px, 0px, 0px)";
3258
- document.removeEventListener("pointermove", mvMove);
3259
- document.removeEventListener("pointerup", mvStop);
3260
3271
  let rect = $container.getBoundingClientRect();
3261
3272
  (0, _yuneta_gobj_js.gobj_write_integer_attr)(gobj, "x", rect.x);
3262
3273
  (0, _yuneta_gobj_js.gobj_write_integer_attr)(gobj, "y", rect.y);
@@ -3296,12 +3307,13 @@ function rsStart(gobj, evt) {
3296
3307
  $container.style.height = height + rel_h + "px";
3297
3308
  }
3298
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;
3299
3313
  rel_w = evt.pageX - pageX;
3300
3314
  rel_h = evt.pageY - pageY;
3301
3315
  $container.style.width = width + rel_w + "px";
3302
3316
  $container.style.height = height + rel_h + "px";
3303
- document.removeEventListener("pointermove", rsMove);
3304
- document.removeEventListener("pointerup", rsStop);
3305
3317
  let rect = $container.getBoundingClientRect();
3306
3318
  if ((0, _yuneta_gobj_js.gobj_read_bool_attr)(gobj, "force_center")) {
3307
3319
  rect = do_center(gobj, rect.x, rect.y, rect.width, rect.height);
@@ -3680,7 +3692,7 @@ function build_dock(gobj) {
3680
3692
  ensure_dock_style();
3681
3693
  (0, _yuneta_gobj_js.gobj_write_attr)(gobj, "$container", (0, _yuneta_gobj_js.createElement2)([
3682
3694
  "div",
3683
- { class: "yui-dock is-empty" },
3695
+ { class: "C_YUI_WINDOW_MANAGER yui-dock is-empty" },
3684
3696
  []
3685
3697
  ]));
3686
3698
  apply_placement(gobj);
@@ -3866,10 +3878,20 @@ function ac_register_window(gobj, event, kw, src) {
3866
3878
  } }
3867
3879
  ]
3868
3880
  ],
3869
- { click: (evt) => {
3870
- evt.stopPropagation();
3871
- on_chip_click(gobj, entry);
3872
- } }
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
+ }
3873
3895
  ]);
3874
3896
  entry.$chip = $chip;
3875
3897
  priv.windows.push(entry);
@@ -15526,7 +15548,13 @@ function create_tabulator(gobj) {
15526
15548
  selectableRows: selectable,
15527
15549
  footerElement: "<span class='yui-tabulator-rowcount' style='display:block;text-align:right;font-size:0.8rem;color:#6b7280;padding:0 0.6rem;'></span>"
15528
15550
  });
15529
- let tabulator = new tabulator_tables.TabulatorFull(`#${table_id}`, tabulator_settings);
15551
+ let $view_container = (0, _yuneta_gobj_js.gobj_read_attr)(gobj, "$container");
15552
+ let $table_el = $view_container ? $view_container.querySelector(`#${table_id}`) : null;
15553
+ if (!$table_el) {
15554
+ (0, _yuneta_gobj_js.log_error)(`${(0, _yuneta_gobj_js.gobj_short_name)(gobj)}: table element '#${table_id}' not found in $container`);
15555
+ return;
15556
+ }
15557
+ let tabulator = new tabulator_tables.TabulatorFull($table_el, tabulator_settings);
15530
15558
  function update_rowcount() {
15531
15559
  let $rc = tabulator.element && tabulator.element.querySelector(".yui-tabulator-rowcount");
15532
15560
  if (!$rc) return;
@@ -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 {
@@ -2857,7 +2866,7 @@ function resolve_manager(gobj) {
2857
2866
  let m = gobj_read_pointer_attr(gobj, "manager");
2858
2867
  if (!m) return null;
2859
2868
  if (typeof m === "string") {
2860
- m = gobj_find_service(m, false);
2869
+ m = gobj_find_service(m, false) || null;
2861
2870
  gobj_write_attr(gobj, "manager", m);
2862
2871
  }
2863
2872
  return m;
@@ -3154,13 +3163,14 @@ function close_window(gobj) {
3154
3163
  let kw_close = { abort_close: false };
3155
3164
  gobj_publish_event(gobj, "EV_WINDOW_TO_CLOSE", kw_close);
3156
3165
  let on_close = gobj_read_attr(gobj, "on_close");
3157
- if (on_close) on_close();
3158
3166
  if (!kw_close.abort_close) {
3167
+ if (on_close) on_close();
3159
3168
  if (gobj_is_running(gobj)) gobj_stop(gobj);
3160
3169
  gobj_stop_children(gobj);
3161
3170
  gobj_destroy(gobj);
3162
3171
  } else if (kw_close.warning) get_yesnocancel(kw_close.warning, function(resp) {
3163
3172
  if (resp === "yes") {
3173
+ if (on_close) on_close();
3164
3174
  gobj_stop(gobj);
3165
3175
  gobj_stop_children(gobj);
3166
3176
  gobj_destroy(gobj);
@@ -3242,6 +3252,9 @@ function mvStart(gobj, evt) {
3242
3252
  $container.style.transform = "translate3d(" + div_x + "px, " + div_y + "px, 0px)";
3243
3253
  }
3244
3254
  function mvStop(evt) {
3255
+ document.removeEventListener("pointermove", mvMove);
3256
+ document.removeEventListener("pointerup", mvStop);
3257
+ if (gobj_is_destroying(gobj)) return;
3245
3258
  div_x = evt.screenX - x;
3246
3259
  div_y = evt.screenY - y;
3247
3260
  let xx = pos_x + div_x;
@@ -3250,8 +3263,6 @@ function mvStart(gobj, evt) {
3250
3263
  $container.style.top = yy + "px";
3251
3264
  $container.style.transition = "none";
3252
3265
  $container.style.transform = "translate3d(0px, 0px, 0px)";
3253
- document.removeEventListener("pointermove", mvMove);
3254
- document.removeEventListener("pointerup", mvStop);
3255
3266
  let rect = $container.getBoundingClientRect();
3256
3267
  gobj_write_integer_attr(gobj, "x", rect.x);
3257
3268
  gobj_write_integer_attr(gobj, "y", rect.y);
@@ -3291,12 +3302,13 @@ function rsStart(gobj, evt) {
3291
3302
  $container.style.height = height + rel_h + "px";
3292
3303
  }
3293
3304
  function rsStop(evt) {
3305
+ document.removeEventListener("pointermove", rsMove);
3306
+ document.removeEventListener("pointerup", rsStop);
3307
+ if (gobj_is_destroying(gobj)) return;
3294
3308
  rel_w = evt.pageX - pageX;
3295
3309
  rel_h = evt.pageY - pageY;
3296
3310
  $container.style.width = width + rel_w + "px";
3297
3311
  $container.style.height = height + rel_h + "px";
3298
- document.removeEventListener("pointermove", rsMove);
3299
- document.removeEventListener("pointerup", rsStop);
3300
3312
  let rect = $container.getBoundingClientRect();
3301
3313
  if (gobj_read_bool_attr(gobj, "force_center")) {
3302
3314
  rect = do_center(gobj, rect.x, rect.y, rect.width, rect.height);
@@ -3675,7 +3687,7 @@ function build_dock(gobj) {
3675
3687
  ensure_dock_style();
3676
3688
  gobj_write_attr(gobj, "$container", createElement2([
3677
3689
  "div",
3678
- { class: "yui-dock is-empty" },
3690
+ { class: "C_YUI_WINDOW_MANAGER yui-dock is-empty" },
3679
3691
  []
3680
3692
  ]));
3681
3693
  apply_placement(gobj);
@@ -3861,10 +3873,20 @@ function ac_register_window(gobj, event, kw, src) {
3861
3873
  } }
3862
3874
  ]
3863
3875
  ],
3864
- { click: (evt) => {
3865
- evt.stopPropagation();
3866
- on_chip_click(gobj, entry);
3867
- } }
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
+ }
3868
3890
  ]);
3869
3891
  entry.$chip = $chip;
3870
3892
  priv.windows.push(entry);
@@ -15521,7 +15543,13 @@ function create_tabulator(gobj) {
15521
15543
  selectableRows: selectable,
15522
15544
  footerElement: "<span class='yui-tabulator-rowcount' style='display:block;text-align:right;font-size:0.8rem;color:#6b7280;padding:0 0.6rem;'></span>"
15523
15545
  });
15524
- let tabulator = new TabulatorFull(`#${table_id}`, tabulator_settings);
15546
+ let $view_container = gobj_read_attr(gobj, "$container");
15547
+ let $table_el = $view_container ? $view_container.querySelector(`#${table_id}`) : null;
15548
+ if (!$table_el) {
15549
+ log_error(`${gobj_short_name(gobj)}: table element '#${table_id}' not found in $container`);
15550
+ return;
15551
+ }
15552
+ let tabulator = new TabulatorFull($table_el, tabulator_settings);
15525
15553
  function update_rowcount() {
15526
15554
  let $rc = tabulator.element && tabulator.element.querySelector(".yui-tabulator-rowcount");
15527
15555
  if (!$rc) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "2.2.3",
3
+ "version": "2.2.5",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -787,7 +787,21 @@ function create_tabulator(gobj)
787
787
  "color:#6b7280;padding:0 0.6rem;'></span>",
788
788
  });
789
789
 
790
- let tabulator = new Tabulator(`#${table_id}`, tabulator_settings);
790
+ /*
791
+ * Attach by ELEMENT, resolved inside OUR $container — a bare `#id`
792
+ * selector needs the element to be in the document already (a view
793
+ * built before being mounted crashes Tabulator: "no element found",
794
+ * then .on() dies on externalEvents null) and is shadowed by any
795
+ * stale duplicate id elsewhere in the page.
796
+ */
797
+ let $view_container = gobj_read_attr(gobj, "$container");
798
+ let $table_el = $view_container ?
799
+ $view_container.querySelector(`#${table_id}`) : null;
800
+ if(!$table_el) {
801
+ log_error(`${gobj_short_name(gobj)}: table element '#${table_id}' not found in $container`);
802
+ return;
803
+ }
804
+ let tabulator = new Tabulator($table_el, tabulator_settings);
791
805
 
792
806
  /* Keep the footer in sync with the visible (active) row count. */
793
807
  function update_rowcount() {
@@ -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 {