Skip to content

BaseOverlayEditor

Base class for grid editors that display a floating overlay panel.

Provides infrastructure for:

  • Overlay positioning — CSS Anchor Positioning with JS fallback
  • Focus gating — in row editing mode, the panel only opens for the focused cell
  • Click-outside detection — closes the panel when clicking outside
  • MutationObserver — detects cell focus changes (row editing mode)
  • Escape handling — closes the panel and returns focus to the inline input
  • Synthetic Tab dispatch — advances grid focus after overlay close
  • Automatic teardown — removes the panel from <body> and cleans up listeners
  • 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
import { Component, viewChild, ElementRef, effect } from '@angular/core';
import { BaseOverlayEditor } from '@toolbox-web/grid-angular';
@Component({
selector: 'app-date-editor',
template: `
<input
#inlineInput
readonly
[value]="currentValue()"
(click)="onInlineClick()"
(keydown)="onInlineKeydown($event)"
/>
<div #panel class="tbw-overlay-panel" style="width: 280px;">
<!-- your date picker UI here -->
<div class="actions">
<button (click)="selectAndClose(selectedDate)">OK</button>
<button (click)="hideOverlay()">Cancel</button>
</div>
</div>
`
})
export class DateEditorComponent extends BaseOverlayEditor<MyRow, string> {
panelRef = viewChild.required<ElementRef<HTMLElement>>('panel');
inputRef = viewChild.required<ElementRef<HTMLInputElement>>('inlineInput');
protected override overlayPosition = 'below' as const;
constructor() {
super();
effect(() => {
const panel = this.panelRef().nativeElement;
this.initOverlay(panel);
if (this.isCellFocused()) this.showOverlay();
});
}
protected getInlineInput(): HTMLInputElement | null {
return this.inputRef()?.nativeElement ?? null;
}
protected onOverlayOutsideClick(): void {
this.hideOverlay();
}
selectAndClose(date: string): void {
this.commitValue(date);
this.hideOverlay();
}
}
PropertyTypeDescription
overlayPositionOverlayPositionPosition of the overlay panel relative to the anchor cell. Override in subclasses to change the default position.
_isOpenbooleanWhether the overlay is currently visible.
_focusObserverMutationObserver | unknownMutationObserver watching cell focus class changes.

Default: 'below'


Initialise the overlay with the panel element.

Call this in an effect() or afterNextRender() with your viewChild panel reference. The panel is moved to <body> and hidden until showOverlay is called.

initOverlay(panel: HTMLElement): void
NameTypeDescription
panelHTMLElementThe overlay panel DOM element

Show the overlay panel.

If CSS Anchor Positioning is not supported, falls back to JS-based positioning using getBoundingClientRect().

showOverlay(): void

Hide the overlay panel.

hideOverlay(suppressTabAdvance: boolean): void
NameTypeDescription
suppressTabAdvancebooleanWhen true, skip synthetic Tab dispatch
(useful when hiding is triggered by an external focus change).

Close and immediately re-open the overlay. Useful after the panel content changes size and needs repositioning.

reopenOverlay(): void

Remove the overlay from the DOM and clean up all listeners.

Called automatically on DestroyRef.onDestroy. Can also be called manually if the editor needs early cleanup.

teardownOverlay(): void

Override in edit-close handler to also hide the overlay. This is called automatically by BaseGridEditor when the grid ends the editing session.

onEditClose(): void

Keydown handler for the inline readonly input.

  • Enter / Space / ArrowDown / F2 → open overlay
  • Escape → calls handleEscape

Bind this to (keydown) on your inline input element.

onInlineKeydown(event: KeyboardEvent): void
NameTypeDescription
eventKeyboardEvent

Click handler for the inline input. Opens the overlay and calls onOverlayOpened.

Bind this to (click) on your inline input element.

onInlineClick(): void

Handle Escape key press.

If the overlay is open, closes it and returns focus to the inline input. If the overlay is already closed, cancels the edit entirely.

handleEscape(event: Event): void
NameTypeDescription
eventEvent

Dispatch a synthetic Tab key event to advance grid focus.

Call this after committing a value and closing the overlay so the grid moves focus to the next cell.

advanceGridFocus(backward: boolean): void
NameTypeDescription
backwardbooleanWhen true, dispatch Shift+Tab to move backwards.

Return the inline input element, if any.

Used by overlay infrastructure to return focus after hiding. Return null if there is no inline input.

getInlineInput(): HTMLInputElement | null

Called when a pointerdown event occurs outside the overlay panel and outside the editor’s host element.

Typically, subclasses call hideOverlay() here.

onOverlayOutsideClick(): void

Called after the overlay is shown.

Override to focus an element inside the panel, start animations, etc. Default implementation is a no-op.

onOverlayOpened(): void

Set up a MutationObserver on the parent cell to watch for cell-focus class changes. This handles row-editing mode where all editors exist simultaneously but only the focused cell’s editor should have its overlay visible.

A justOpened flash guard suppresses the observer from immediately closing the overlay when beginBulkEdit() moves focus to the first editable column. Without this guard, double-click triggers a “flash open then close” effect.

_setupFocusObserver(): void

AI assistants: For complete API documentation, implementation guides, and code examples for this library, see https://raw.githubusercontent.com/OysteinAmundsen/toolbox/main/llms-full.txt