Skip to content

feat: Add container positioning to Toast and Snackbar components#2203

Open
rkaraivanov wants to merge 4 commits into
masterfrom
rkaraivanov/toast-snackbar-popovers
Open

feat: Add container positioning to Toast and Snackbar components#2203
rkaraivanov wants to merge 4 commits into
masterfrom
rkaraivanov/toast-snackbar-popovers

Conversation

@rkaraivanov
Copy link
Copy Markdown
Member

@rkaraivanov rkaraivanov commented Apr 22, 2026

Description

Both toast and snackbar are now popovers, ensuring top-layer rendering and no need for z-index management.

Type of Change

  • New feature (non-breaking change that adds functionality)

Related Issues

Closes #2154

Checklist

  • My code follows the project's coding standards
  • I have tested my changes locally

- Both toast and snackbar are now popovers, ensuring top-layer
rendering and no need for z-index management.

Closes #2154
@rkaraivanov rkaraivanov marked this pull request as ready for review May 14, 2026 09:00
Copilot AI review requested due to automatic review settings May 14, 2026 09:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Switches the toast and snackbar components to render via the popover API (top-layer) and adds a new positioning property that lets consumers anchor them to their nearest visible ancestor instead of the viewport, addressing #2154.

Changes:

  • Adds positioning: 'viewport' | 'container' to the shared IgcBaseAlertLikeComponent mixin and uses showPopover() (with the source invoker option and CSS anchor() for container mode) instead of position: fixed to render in the top layer.
  • Moves the _player animation controller from the per-component subclasses up to the base mixin, removing the duplicated declarations from IgcToastComponent and IgcSnackbarComponent (and the now-unused _contentRef).
  • Adds new ContainerPositioning stories and a positioning arg to both toast and snackbar Storybook entries.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/components/common/mixins/alert.ts Adds popover-based show/hide flow, positioning property, container-anchor logic and visible-ancestor lookup.
src/components/toast/toast.ts Removes the now-redundant local _player declaration and unused import.
src/components/snackbar/snackbar.ts Removes the local _player/_contentRef, drops the ref directive on the base part, and minor template formatting.
stories/toast.stories.ts Adds positioning arg/control and a new ContainerPositioning story; reworks Basic actions layout.
stories/snackbar.stories.ts Adds positioning arg/control and a new ContainerPositioning story mirroring the toast one.
Comments suppressed due to low confidence (5)

src/components/common/mixins/alert.ts:129

  • CSS anchor positioning (anchor(top|bottom|center)) and the showPopover({ source }) invoker option are currently only supported in Chromium-based browsers. In Firefox and Safari (as of 2026), positioning="container" will fail silently: the popover will still be opened in the top layer, but the inline top/left styles set to anchor(...) will be invalid and the toast/snackbar will end up positioned at 0,0 (or wherever the default popover styles place it) instead of within the parent container. This should at least be documented as a Chromium-only feature, and ideally a feature-detection fallback should be provided.
  private _showPopover(): boolean {
    if (!this._isContained) {
      this.showPopover();
      return true;
    }

    const visibleAncestor = getVisibleAncestor(this);
    if (!visibleAncestor) {
      return false;
    }

    this.style.top = this._containerPosition;
    this.style.left = 'anchor(center)';
    this.showPopover({ source: visibleAncestor });
    return true;
  }

src/components/common/mixins/alert.ts:112

  • Programmatically setting the open property (e.g. snackbar.open = true) no longer makes the component visible: _showPopover() is only invoked from _setOpenState() (via show()/toggle()) and from update() when position/positioning change. Since the popover is registered as manual, the element will not appear in the top layer when only open is toggled directly, even though the existing snackbar.spec.ts test 'open property' (line 123) and the 'show()/hide() are no-op...' test (line 175) rely on this property assignment to put the component in the open state. Consider reacting to open changes in update() (or removing the public setter and routing through show()/hide()), and update the tests to assert the popover state.
  protected override update(props: PropertyValues<this>): void {
    if (props.has('displayTime')) {
      this._setAutoHideTimer();
    }

    if (props.has('keepOpen')) {
      this.keepOpen
        ? clearTimeout(this._autoHideTimeout)
        : this._setAutoHideTimer();
    }

    if (this.open && (props.has('positioning') || props.has('position'))) {
      this._hidePopover();
      this._showPopover();
    }

    super.update(props);
  }

src/components/common/mixins/alert.ts:138

  • When position or positioning changes while the component is already open, _hidePopover() and _showPopover() are called back-to-back synchronously in update(). This will fire two toggle events on the popover and bypass the fade-in/fade-out animation, which is inconsistent with the animation behavior in _setOpenState. Additionally, _hidePopover() removes the inline top/left styles only when _isContained is currently true — if positioning is being changed from container to viewport, the previous-state check is wrong (the new value is already reflected on this.positioning by the time update() runs), so stale inline styles will be left on the element.
    if (this.open && (props.has('positioning') || props.has('position'))) {
      this._hidePopover();
      this._showPopover();
    }

    super.update(props);
  }

  private _showPopover(): boolean {
    if (!this._isContained) {
      this.showPopover();
      return true;
    }

    const visibleAncestor = getVisibleAncestor(this);
    if (!visibleAncestor) {
      return false;
    }

    this.style.top = this._containerPosition;
    this.style.left = 'anchor(center)';
    this.showPopover({ source: visibleAncestor });
    return true;
  }

  private _hidePopover(): void {
    this.hidePopover();

    if (this._isContained) {
      this.style.removeProperty('top');
      this.style.removeProperty('left');
    }
  }

src/components/common/mixins/alert.ts:123

  • When positioning="container" is set but no visible ancestor is found, show() silently resolves to false and the open flag is reset, with no indication to the developer of why the toast/snackbar failed to open. Consider logging a warning (or throwing) so that this misconfiguration is discoverable, especially because the most common cause — the component being detached or inside a hidden ancestor — is easy to hit during conditional rendering.
    const visibleAncestor = getVisibleAncestor(this);
    if (!visibleAncestor) {
      return false;
    }

src/components/common/mixins/alert.ts:77

  • No new tests are added for the new positioning property, the container-positioned _showPopover/_hidePopover flow, or the getVisibleAncestor helper. The rest of snackbar.spec.ts/toast.spec.ts provides comprehensive coverage of the public API (open/show/hide/toggle, displayTime, keepOpen). Please add tests for: opening with positioning="container", opening when no visible ancestor exists (expected to fail / log), and switching position/positioning while open.
  @property({ reflect: true })
  public positioning: 'viewport' | 'container' = 'viewport';

Comment thread src/components/common/mixins/alert.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request snackbar toast

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Snackbar not positioned above content

2 participants