@toolbox-web/grid-angular 0.14.2 → 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.
Files changed (26) hide show
  1. package/README.md +9 -20
  2. package/fesm2022/toolbox-web-grid-angular-features-export.mjs +1 -2
  3. package/fesm2022/toolbox-web-grid-angular-features-export.mjs.map +1 -1
  4. package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs +1 -2
  5. package/fesm2022/toolbox-web-grid-angular-features-filtering.mjs.map +1 -1
  6. package/fesm2022/toolbox-web-grid-angular-features-print.mjs +1 -2
  7. package/fesm2022/toolbox-web-grid-angular-features-print.mjs.map +1 -1
  8. package/fesm2022/toolbox-web-grid-angular-features-selection.mjs +1 -2
  9. package/fesm2022/toolbox-web-grid-angular-features-selection.mjs.map +1 -1
  10. package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs +1 -2
  11. package/fesm2022/toolbox-web-grid-angular-features-undo-redo.mjs.map +1 -1
  12. package/fesm2022/toolbox-web-grid-angular.mjs +65 -7
  13. package/fesm2022/toolbox-web-grid-angular.mjs.map +1 -1
  14. package/package.json +1 -1
  15. package/types/toolbox-web-grid-angular-features-export.d.ts +1 -2
  16. package/types/toolbox-web-grid-angular-features-export.d.ts.map +1 -1
  17. package/types/toolbox-web-grid-angular-features-filtering.d.ts +1 -2
  18. package/types/toolbox-web-grid-angular-features-filtering.d.ts.map +1 -1
  19. package/types/toolbox-web-grid-angular-features-print.d.ts +1 -2
  20. package/types/toolbox-web-grid-angular-features-print.d.ts.map +1 -1
  21. package/types/toolbox-web-grid-angular-features-selection.d.ts +1 -2
  22. package/types/toolbox-web-grid-angular-features-selection.d.ts.map +1 -1
  23. package/types/toolbox-web-grid-angular-features-undo-redo.d.ts +1 -2
  24. package/types/toolbox-web-grid-angular-features-undo-redo.d.ts.map +1 -1
  25. package/types/toolbox-web-grid-angular.d.ts +24 -7
  26. package/types/toolbox-web-grid-angular.d.ts.map +1 -1
@@ -2310,13 +2310,12 @@ function provideGridIcons(icons) {
2310
2310
  * ## Usage
2311
2311
  *
2312
2312
  * ```typescript
2313
- * import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2313
+ * import { Component } from '@angular/core';
2314
2314
  * import { Grid, injectGrid } from '@toolbox-web/grid-angular';
2315
2315
  *
2316
2316
  * @Component({
2317
2317
  * selector: 'app-my-grid',
2318
2318
  * imports: [Grid],
2319
- * schemas: [CUSTOM_ELEMENTS_SCHEMA],
2320
2319
  * template: `
2321
2320
  * <button (click)="handleResize()">Force Layout</button>
2322
2321
  * <button (click)="handleExport()" [disabled]="!grid.isReady()">Export</button>
@@ -2872,9 +2871,14 @@ class BaseGridEditor {
2872
2871
  const grid = this.elementRef.nativeElement.closest('tbw-grid');
2873
2872
  if (!grid)
2874
2873
  return;
2874
+ const beforeHandler = () => this.onBeforeEditClose();
2875
+ grid.addEventListener('before-edit-close', beforeHandler, { once: true });
2875
2876
  const handler = () => this.onEditClose();
2876
2877
  grid.addEventListener('edit-close', handler, { once: true });
2877
- this._editCloseCleanup = () => grid.removeEventListener('edit-close', handler);
2878
+ this._editCloseCleanup = () => {
2879
+ grid.removeEventListener('before-edit-close', beforeHandler);
2880
+ grid.removeEventListener('edit-close', handler);
2881
+ };
2878
2882
  }
2879
2883
  // ============================================================================
2880
2884
  // Methods
@@ -2891,6 +2895,18 @@ class BaseGridEditor {
2891
2895
  isCellFocused() {
2892
2896
  return this.elementRef.nativeElement.closest('[part="cell"]')?.classList.contains('cell-focus') ?? false;
2893
2897
  }
2898
+ /**
2899
+ * Called **before** the grid clears editing state and destroys editor DOM.
2900
+ *
2901
+ * At this point the commit callback is still active, so subclasses can
2902
+ * call {@link commitValue} to flush any pending/deferred values.
2903
+ *
2904
+ * This fires only on the **commit** path (not on revert/cancel).
2905
+ * Use {@link onEditClose} for cleanup that should happen on both paths.
2906
+ */
2907
+ onBeforeEditClose() {
2908
+ // Default: no-op. Subclasses override to flush pending values.
2909
+ }
2894
2910
  /**
2895
2911
  * Called when the grid ends the editing session for this cell.
2896
2912
  *
@@ -3191,6 +3207,7 @@ let anchorCounter = 0;
3191
3207
  * - **Escape handling** — closes the panel and returns focus to the inline input
3192
3208
  * - **Synthetic Tab dispatch** — advances grid focus after overlay close
3193
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
3194
3211
  *
3195
3212
  * ## Usage
3196
3213
  *
@@ -3313,6 +3330,9 @@ class BaseOverlayEditor extends BaseGridEditor {
3313
3330
  }
3314
3331
  // Move panel to body so it escapes grid overflow clipping
3315
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);
3316
3336
  // Set up click-outside detection
3317
3337
  this._abortCtrl = new AbortController();
3318
3338
  document.addEventListener('pointerdown', (e) => this._onDocumentPointerDown(e), {
@@ -3372,6 +3392,10 @@ class BaseOverlayEditor extends BaseGridEditor {
3372
3392
  this._abortCtrl = null;
3373
3393
  this._focusObserver?.disconnect();
3374
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
+ }
3375
3399
  if (this._panel?.parentNode) {
3376
3400
  this._panel.parentNode.removeChild(this._panel);
3377
3401
  }
@@ -3484,6 +3508,10 @@ class BaseOverlayEditor extends BaseGridEditor {
3484
3508
  _getCell() {
3485
3509
  return this._elementRef.nativeElement.closest('[part="cell"]') ?? null;
3486
3510
  }
3511
+ /** Find the parent `<tbw-grid>` element for this editor. */
3512
+ _getGridElement() {
3513
+ return this._elementRef.nativeElement.closest('tbw-grid') ?? null;
3514
+ }
3487
3515
  /**
3488
3516
  * JS fallback positioning for browsers without CSS Anchor Positioning.
3489
3517
  * Uses `getBoundingClientRect()` with viewport overflow detection.
@@ -3570,24 +3598,54 @@ class BaseOverlayEditor extends BaseGridEditor {
3570
3598
  * `cell-focus` class changes. This handles row-editing mode where
3571
3599
  * all editors exist simultaneously but only the focused cell's
3572
3600
  * editor should have its overlay visible.
3601
+ *
3602
+ * A `justOpened` flash guard suppresses the observer from
3603
+ * immediately closing the overlay when `beginBulkEdit()` moves
3604
+ * focus to the first editable column. Without this guard,
3605
+ * double-click triggers a "flash open then close" effect.
3573
3606
  */
3574
3607
  _setupFocusObserver() {
3575
3608
  const cell = this._getCell();
3576
3609
  if (!cell)
3577
3610
  return;
3611
+ let justOpened = false;
3612
+ let pendingHideRaf = 0;
3578
3613
  this._focusObserver = new MutationObserver((mutations) => {
3579
3614
  for (const mutation of mutations) {
3580
3615
  if (mutation.type !== 'attributes' || mutation.attributeName !== 'class')
3581
3616
  continue;
3582
3617
  const isFocused = cell.classList.contains('cell-focus');
3583
3618
  if (isFocused && !this._isOpen) {
3584
- // 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
+ }
3624
+ justOpened = true;
3585
3625
  this.showOverlay();
3586
3626
  this.onOverlayOpened();
3627
+ // Clear the guard after a macrotask so that an immediate
3628
+ // focus-away (e.g. beginBulkEdit focus adjustment) does
3629
+ // not close the overlay in the same event loop tick.
3630
+ setTimeout(() => {
3631
+ justOpened = false;
3632
+ }, 0);
3587
3633
  }
3588
- else if (!isFocused && this._isOpen) {
3589
- // Cell lost focus — hide overlay silently
3590
- this.hideOverlay(true);
3634
+ else if (!isFocused && this._isOpen && !justOpened) {
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
+ });
3591
3649
  }
3592
3650
  }
3593
3651
  });