@toolbox-web/grid-angular 0.14.3 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3207,6 +3207,7 @@ let anchorCounter = 0;
3207
3207
  * - **Escape handling** — closes the panel and returns focus to the inline input
3208
3208
  * - **Synthetic Tab dispatch** — advances grid focus after overlay close
3209
3209
  * - **Automatic teardown** — removes the panel from `<body>` and cleans up listeners
3210
+ * - **External focus registration** — auto-registers the panel via `grid.registerExternalFocusContainer()` so the grid keeps `data-has-focus` and editors stay open while the overlay has focus
3210
3211
  *
3211
3212
  * ## Usage
3212
3213
  *
@@ -3329,6 +3330,9 @@ class BaseOverlayEditor extends BaseGridEditor {
3329
3330
  }
3330
3331
  // Move panel to body so it escapes grid overflow clipping
3331
3332
  document.body.appendChild(panel);
3333
+ // Register the panel as an external focus container on the grid
3334
+ // so focus moving into the overlay is treated as "still in the grid"
3335
+ this._getGridElement()?.registerExternalFocusContainer?.(panel);
3332
3336
  // Set up click-outside detection
3333
3337
  this._abortCtrl = new AbortController();
3334
3338
  document.addEventListener('pointerdown', (e) => this._onDocumentPointerDown(e), {
@@ -3388,6 +3392,10 @@ class BaseOverlayEditor extends BaseGridEditor {
3388
3392
  this._abortCtrl = null;
3389
3393
  this._focusObserver?.disconnect();
3390
3394
  this._focusObserver = null;
3395
+ // Unregister the panel from the grid's external focus container registry
3396
+ if (this._panel) {
3397
+ this._getGridElement()?.unregisterExternalFocusContainer?.(this._panel);
3398
+ }
3391
3399
  if (this._panel?.parentNode) {
3392
3400
  this._panel.parentNode.removeChild(this._panel);
3393
3401
  }
@@ -3500,6 +3508,10 @@ class BaseOverlayEditor extends BaseGridEditor {
3500
3508
  _getCell() {
3501
3509
  return this._elementRef.nativeElement.closest('[part="cell"]') ?? null;
3502
3510
  }
3511
+ /** Find the parent `<tbw-grid>` element for this editor. */
3512
+ _getGridElement() {
3513
+ return this._elementRef.nativeElement.closest('tbw-grid') ?? null;
3514
+ }
3503
3515
  /**
3504
3516
  * JS fallback positioning for browsers without CSS Anchor Positioning.
3505
3517
  * Uses `getBoundingClientRect()` with viewport overflow detection.
@@ -3597,13 +3609,18 @@ class BaseOverlayEditor extends BaseGridEditor {
3597
3609
  if (!cell)
3598
3610
  return;
3599
3611
  let justOpened = false;
3612
+ let pendingHideRaf = 0;
3600
3613
  this._focusObserver = new MutationObserver((mutations) => {
3601
3614
  for (const mutation of mutations) {
3602
3615
  if (mutation.type !== 'attributes' || mutation.attributeName !== 'class')
3603
3616
  continue;
3604
3617
  const isFocused = cell.classList.contains('cell-focus');
3605
3618
  if (isFocused && !this._isOpen) {
3606
- // Cell just gained focus — open overlay if appropriate
3619
+ // Cell just gained focus — cancel any pending hide and open overlay.
3620
+ if (pendingHideRaf) {
3621
+ cancelAnimationFrame(pendingHideRaf);
3622
+ pendingHideRaf = 0;
3623
+ }
3607
3624
  justOpened = true;
3608
3625
  this.showOverlay();
3609
3626
  this.onOverlayOpened();
@@ -3615,8 +3632,20 @@ class BaseOverlayEditor extends BaseGridEditor {
3615
3632
  }, 0);
3616
3633
  }
3617
3634
  else if (!isFocused && this._isOpen && !justOpened) {
3618
- // Cell lost focus — hide overlay silently
3619
- this.hideOverlay(true);
3635
+ // Cell lost focus — defer hide to allow render cycles to settle.
3636
+ // Re-renders (e.g., from ResizeObserver after a footer appears)
3637
+ // may transiently toggle cell-focus within the same frame.
3638
+ // Deferring to the next animation frame lets the render pipeline
3639
+ // finish before we decide whether the overlay should actually close.
3640
+ if (pendingHideRaf)
3641
+ cancelAnimationFrame(pendingHideRaf);
3642
+ pendingHideRaf = requestAnimationFrame(() => {
3643
+ pendingHideRaf = 0;
3644
+ // Re-check settled state — cell-focus may have been re-applied
3645
+ if (!cell.classList.contains('cell-focus') && this._isOpen) {
3646
+ this.hideOverlay(true);
3647
+ }
3648
+ });
3620
3649
  }
3621
3650
  }
3622
3651
  });