Core

Overlay

Every floating component in NGWR (dialog, drawer, dropdown, select, tooltip, popover, popconfirm, toast) is built on Angular CDK's overlay primitives. provideWrOverlay() gives them an overlay container and an Overlay instance of their own, so they never share a DOM root — or a z-index stack — with another CDK consumer.

Why this exists

CDK creates a single overlay container at the document root, and every CDK consumer (NGWR, Angular Material, NG-ZORRO) attaches its overlays to that one element. Styling collisions are the usual symptom — z-index wars, shared .cdk-overlay-pane rules. provideWrOverlay() replaces two tokens NGWR components inject instead of CDK's own: WR_OVERLAY_CONTAINER with a WrOverlayContainer subclass that tags its element .wr-overlay-container, and WR_OVERLAY with a fresh Overlay built in a child injector bound to that container. It also installs WrVisualViewport, which publishes --wr-keyboard-inset so a bottom-sheet can lift clear of the on-screen keyboard.

import { bootstrapApplication } from '@angular/platform-browser';
import { provideWrOverlay } from 'ngwr/overlay';

bootstrapApplication(AppComponent, {
  providers: [
    provideWrOverlay(),
  ],
});

Scope styles to NGWR overlays

After provideWrOverlay() is registered, the overlay root element has both the standard .cdk-overlay-container class and .wr-overlay-container. Use the second to scope your overrides:

// Scope custom overlay styles to NGWR overlays so other libs aren't affected.
.wr-overlay-container .cdk-overlay-pane {
  z-index: 1100;
}

Caveats

CDK's own root Overlay and OverlayContainer are left untouched, so other libraries keep the container they already had — the isolation is NGWR moving out, not NGWR taking over. What the call does not do is isolate two OTHER libraries from each other. And it is not load-bearing: both tokens fall back to CDK's root instances, so overlays still open without it — they just land in the shared container, and the keyboard inset is never published.

Focus, on the way in

A modal overlay — WrDialog, <wr-drawer> and WrDrawerManager — records document.activeElement before it attaches, installs a CDK focus trap once the content is in the DOM, and moves focus inside. What it moves focus TO is decided in this order.

  1. The element carrying cdkFocusInitial, if the content has one. It is a plain attribute — no import, no directive — because the trap resolves it with querySelector. This is the answer to "focus has to land on Cancel, not on Delete"; nothing needs to call focus() after open().
  2. Otherwise the first tabbable element inside the overlay, in DOM order.
  3. Otherwise nothing moves, and the trap still holds.

Which means the two drawer forms differ, and the difference is DOM order rather than policy: <wr-drawer> renders its ✕ in its own template, above the projected content, so the ✕ is the first tabbable and takes focus; WrDialog and WrDrawerManager append the ✕ AFTER the content portal — so it paints above the panel — and focus lands on the content's first control. Pin it with cdkFocusInitial rather than relying on either.

<!-- Inside the dialog's own component template. `cdkFocusInitial` is a plain
     attribute the CDK focus trap looks up — nothing to import. -->
<h2 wrDialogTitle>Delete project</h2>
<p>This cannot be undone.</p>

<footer>
  <button wr-btn cdkFocusInitial [wrDialogClose]="false">Cancel</button>
  <button wr-btn color="danger" [wrDialogClose]="true">Delete</button>
</footer>

Focus, on the way out

Every dismissal path funnels through one close(), and that one method destroys the trap, disposes the overlay and then restores focus to the element recorded on open. There is no path that skips it, and no option that turns it off.

The four ways a dialog closes, and where focus is afterwards.
Closed byFocus goes toNote
EscapeThe opener Off with closeOnEscape: false. Focus need not be inside the overlay — the CDK keeps one document listener and routes the key to the topmost overlay.
The built-in ✕The openerOff with closable: false; renamed with closeLabel.
[wrDialogClose] / ref.close(result)The openerThe same method the ✕ calls, so the result is the only difference.
Backdrop clickThe openerOff with closeOnBackdropClick: false.

A fifth path is not a dismissal: with closeOnNavigation left on, a route change disposes the overlay so a modal cannot outlive the page it was opened over. And a dismissal is not distinguishable fromclose(undefined) — both resolve awaitClose() with undefined, so a dialog that needs to tell "cancelled" from "saved nothing" has to close with a value that says which.

Components that use the overlay

// Overlay-based building blocks shipped by the lib:
import { WrDialog }      from 'ngwr/dialog';
import { WrToast }       from 'ngwr/toast';
import { WrDrawer }    from 'ngwr/drawer';
import { WrPopover }   from 'ngwr/popover';  // also covers tooltip via mode="tooltip"
import { WrPopconfirm } from 'ngwr/popconfirm';
import { WrDropdown }  from 'ngwr/dropdown';
import { WrSelect }    from 'ngwr/select';