# BaseOverlayEditor

> _Since v0.13.0_

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

## Usage

```typescript
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();
  }
}
```

#### Example

```typescript
@Component({
  template: `
    <input #inlineInput readonly [value]="currentValue()"
      (click)="onInlineClick()" (keydown)="onInlineKeydown($event)" />
    <div #panel style="width: 280px">
      <!-- overlay content -->
    </div>
  `
})
export class MyEditor extends BaseOverlayEditor<MyRow, string> {
  panelRef = viewChild.required<ElementRef<HTMLElement>>('panel');
  inputRef = viewChild.required<ElementRef<HTMLInputElement>>('inlineInput');

  constructor() {
    super();
    effect(() => this.initOverlay(this.panelRef().nativeElement));
  }

  protected getInlineInput() { return this.inputRef()?.nativeElement ?? null; }
  protected onOverlayOutsideClick() { this.hideOverlay(); }
}
```

## Properties

| Property | Type | Description |
| -------- | ---- | ----------- |
| `overlayPosition` | <code><a href="/grid/angular/api/types/overlayposition/">OverlayPosition</a></code> | Position of the overlay panel relative to the anchor cell. Override in subclasses to change the default position. |
| `_isOpen` | <code>boolean</code> | Whether the overlay is currently visible. |
| `_focusObserver` | <code>MutationObserver &#124; unknown</code> | MutationObserver watching cell focus class changes. |

### Property Details

#### overlayPosition

**Default:** `'below'`

---

## Methods

### initOverlay()

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.

```ts
initOverlay(panel: HTMLElement): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `panel` | <code>HTMLElement</code> | The overlay panel DOM element |

***

### showOverlay()

Show the overlay panel.

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

```ts
showOverlay(): void
```

***

### hideOverlay()

Hide the overlay panel.

```ts
hideOverlay(suppressTabAdvance: boolean): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `suppressTabAdvance` | <code>boolean</code> | When `true`, skip synthetic Tab dispatch
  (useful when hiding is triggered by an external focus change). |

***

### reopenOverlay()

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

```ts
reopenOverlay(): void
```

***

### teardownOverlay()

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.

```ts
teardownOverlay(): void
```

***

### onEditClose()

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

```ts
onEditClose(): void
```

***

### onInlineKeydown()

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.

```ts
onInlineKeydown(event: KeyboardEvent): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `event` | <code>KeyboardEvent</code> |  |

***

### onInlineClick()

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

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

```ts
onInlineClick(): void
```

***

### handleEscape()

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.

```ts
handleEscape(event: Event): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `event` | <code>Event</code> |  |

***

### advanceGridFocus()

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.

```ts
advanceGridFocus(backward: boolean): void
```

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| `backward` | <code>boolean</code> | When `true`, dispatch Shift+Tab to move backwards. |

***

### getInlineInput()

Return the inline input element, if any.

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

```ts
getInlineInput(): HTMLInputElement | null
```

***

### onOverlayOutsideClick()

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

Typically, subclasses call `hideOverlay()` here.

```ts
onOverlayOutsideClick(): void
```

***

### onOverlayOpened()

Called after the overlay is shown.

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

```ts
onOverlayOpened(): void
```

***

### _setupFocusObserver()

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.

```ts
_setupFocusObserver(): void
```

***
