Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
eb61c68
feat: Invoker commands integration for components
rkaraivanov May 13, 2026
8c51213
feat: Integrate Invoker Commands API into igc-dialog
rkaraivanov May 13, 2026
0997d89
feat: Integrate InvokerCommandController into Banner component
rkaraivanov May 13, 2026
b19ca98
Merge remote-tracking branch 'origin/master' into rkaraivanov/invoker…
rkaraivanov May 19, 2026
3fc6994
feat: Integrate invoker commands into nav drawer component
rkaraivanov May 19, 2026
08b8ca2
Merge remote-tracking branch 'origin/master' into rkaraivanov/invoker…
rkaraivanov May 20, 2026
380ec9a
feat: Integrate invoker commands with snackbar and toast components
rkaraivanov May 20, 2026
8f3056b
docs: Updated changelog
rkaraivanov May 20, 2026
4f09dfa
feat: Resolve id references in invoker commands
rkaraivanov May 21, 2026
99bd114
Merge remote-tracking branch 'origin/master' into rkaraivanov/invoker…
rkaraivanov May 26, 2026
6d45d58
Merge remote-tracking branch 'origin/master' into rkaraivanov/invoker…
rkaraivanov May 26, 2026
c4ea19c
refactor: Cleaned up JSDoc class comments
rkaraivanov May 26, 2026
7be7212
fix: Addressed ID resolver comments
rkaraivanov May 26, 2026
8875039
fix: Addressed banner PR feedback
rkaraivanov May 26, 2026
893d394
fix: Addressed button base and icon-button PR feedback
rkaraivanov May 26, 2026
792eab3
fix: Addressed dialog PR feedback
rkaraivanov May 26, 2026
60865d4
refactor: Removed focus() and blur() methods from button-base.ts
rkaraivanov May 26, 2026
adb7a1a
test(button): Minor refactor
rkaraivanov May 26, 2026
af39a63
chore: analyzer tags only cs-suppress
damyanpetev May 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .vscode/cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"aria-valuenow",
"aria-valuetext",
"combobox",
"commandfor",
"listbox",
"listitem",
"progressbar",
Expand All @@ -32,9 +33,9 @@
"igniteui",
"slotchange",
"stylelint",
"webcomponents"
],
"ignoreRegExpList": [
"θ"
"webcomponents",
"noopener",
"noreferrer"
],
"ignoreRegExpList": ["θ"]
}
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `keepOpenOnEscape` property — prevents the drawer from closing when the user presses the **Escape** key (non-relative positions only).
- Added `igcClosing` event — emitted just before the drawer is closed by user interaction. Cancelable.
- Added `igcClosed` event — emitted just after the drawer is closed by user interaction.
- #### Invoker Commands API
- `igc-button` and `igc-icon-button` now support `command` and `commandfor` properties, enabling declarative control of target components without JavaScript.
- `igc-banner`, `igc-dialog`, `igc-nav-drawer`, `igc-snackbar`, and `igc-toast` now respond to `--show`, `--hide`, and `--toggle` commands dispatched by an invoker button.

### Changed
- #### Nav Drawer
Expand Down
89 changes: 88 additions & 1 deletion src/components/banner/banner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import {
fixture,
html,
nextFrame,
waitUntil,
} from '@open-wc/testing';
import { spy } from 'sinon';

import IgcButtonComponent from '../button/button.js';
import { defineComponents } from '../common/definitions/defineComponents.js';
import { finishAnimationsFor, simulateClick } from '../common/utils.spec.js';
import IgcIconComponent from '../icon/icon.js';
import IgcBannerComponent from './banner.js';

describe('Banner', () => {
before(() => {
defineComponents(IgcBannerComponent, IgcIconComponent);
defineComponents(IgcBannerComponent, IgcButtonComponent, IgcIconComponent);
});

const createDefaultBanner = () => html`
Expand Down Expand Up @@ -287,4 +289,89 @@ describe('Banner', () => {
expect(banner.open).to.be.true;
});
});

describe('Invoker Commands API', () => {
afterEach(async () => {
if (banner.open) {
await banner.hide();
}
});

describe('with igc-button', () => {
let invoker: IgcButtonComponent;

beforeEach(async () => {
const container = await fixture<HTMLElement>(html`
<div>
<igc-button command="--show" commandfor="invoker-banner"
>Show</igc-button
>
<igc-banner id="invoker-banner"
>You are currently offline.</igc-banner
>
</div>
`);

invoker = container.querySelector<IgcButtonComponent>('igc-button')!;
banner = container.querySelector<IgcBannerComponent>('igc-banner')!;
});

it('`--show` opens the banner', async () => {
expect(banner.open).to.be.false;

invoker.click();
await waitUntil(() => banner.open);

expect(banner.open).to.be.true;
});

it('`--hide` closes an open banner', async () => {
await banner.show();
expect(banner.open).to.be.true;

invoker.command = '--hide';
await elementUpdated(invoker);

invoker.click();
await waitUntil(() => !banner.open);

expect(banner.open).to.be.false;
});

it('`--toggle` opens a closed banner', async () => {
expect(banner.open).to.be.false;

invoker.command = '--toggle';
await elementUpdated(invoker);

invoker.click();
await waitUntil(() => banner.open);

expect(banner.open).to.be.true;
});

it('`--toggle` closes an open banner', async () => {
await banner.show();
expect(banner.open).to.be.true;

invoker.command = '--toggle';
await elementUpdated(invoker);

invoker.click();
await waitUntil(() => !banner.open);

expect(banner.open).to.be.false;
});

it('a disabled igc-button does not invoke commands', async () => {
invoker.disabled = true;
await elementUpdated(invoker);

invoker.click();
await elementUpdated(banner);

expect(banner.open).to.be.false;
});
});
});
});
76 changes: 58 additions & 18 deletions src/components/banner/banner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { addAnimationController } from '../../animations/player.js';
import { growVerIn, growVerOut } from '../../animations/presets/grow/index.js';
import { addThemingController } from '../../theming/theming-controller.js';
import IgcButtonComponent from '../button/button.js';
import { addCommandController } from '../common/controllers/command.js';
import { addInternalsController } from '../common/controllers/internals.js';
import { addSlotController, setSlots } from '../common/controllers/slot.js';
import { registerComponent } from '../common/definitions/register.js';
Expand All @@ -19,25 +20,39 @@ export interface IgcBannerComponentEventMap {
}

/**
* The `igc-banner` component displays important and concise message(s) for a user to address, that is specific to a page or feature.
* A non-modal notification banner that displays important, concise messages
* requiring user acknowledgement.
*
* The banner slides into view with an animated grow transition and renders
* inline, pushing the surrounding page content rather than overlaying it.
*
* The component integrates with the
* [Invoker Commands API](https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API):
* an Ignite button or a native `<button>` with `command="--show"` / `"--hide"` /
* `"--toggle"` and `commandfor` pointing to this element will call the
* corresponding method declaratively without any JavaScript.
*
* @element igc-banner
*
* @slot - Renders the text content of the banner message.
* @slot prefix - Renders additional content at the start of the message block.
* @slot actions - Renders any action elements.
* @fires igcClosing - Emitted just before the banner closes in response to the
* default action button being clicked. Cancelable — call
* `event.preventDefault()` to abort the closing sequence.
* @fires igcClosed - Emitted after the banner has fully closed and its exit
* animation has completed.
*
* @fires igcClosing - Emitted before closing the banner - when a user interacts (click) with the default action of the banner.
* @fires igcClosed - Emitted after the banner is closed - when a user interacts (click) with the default action of the banner.
* @slot - The banner message text content.
* @slot prefix - An icon or illustration rendered to the left of the message.
* Useful for reinforcing the message type (info, warning, success, etc.).
* @slot actions - Custom action elements rendered in the banner's action area.
* When provided, replaces the default "OK" dismiss button.
*
* @csspart base - The base wrapper of the banner component.
* @csspart spacer - The inner wrapper that sets the space around the banner.
* @csspart message - The part that holds the text and the illustration.
* @csspart illustration - The part that holds the banner icon/illustration.
* @csspart content - The part that holds the banner text content.
* @csspart actions - The part that holds the banner action buttons.
* @csspart base - The root wrapper element of the banner.
* @csspart spacer - The inner wrapper that controls the spacing around the banner content.
* @csspart message - The container that holds the illustration and text content.
* @csspart illustration - The container for the prefix slot (icon/illustration).
* @csspart content - The container for the default message slot.
* @csspart actions - The container for the action buttons slot.
*/

export default class IgcBannerComponent extends EventEmitterMixin<
IgcBannerComponentEventMap,
Constructor<LitElement>
Expand All @@ -54,8 +69,14 @@ export default class IgcBannerComponent extends EventEmitterMixin<
private readonly _player = addAnimationController(this, this._bannerRef);

/**
* Determines whether the banner is being shown/hidden.
* @attr
* Whether the banner is open.
*
* Setting this property programmatically will immediately show or hide the
* banner without animation and without emitting close events.
* Prefer the `show()`, `hide()`, and `toggle()` methods for animated
* transitions.
* @attr open
* @default false
*/
@property({ type: Boolean, reflect: true })
public open = false;
Expand All @@ -71,6 +92,10 @@ export default class IgcBannerComponent extends EventEmitterMixin<
ariaLive: 'polite',
},
});
addCommandController(this)
.set('--show', this.show)
.set('--hide', this.hide)
.set('--toggle', this.toggle);
}

private async _handleClick(): Promise<void> {
Expand All @@ -80,7 +105,12 @@ export default class IgcBannerComponent extends EventEmitterMixin<
}
}

/** Shows the banner if not already shown. Returns `true` when the animation has completed. */
/**
* Opens the banner with an animated grow-in transition.
*
* Returns `true` when the banner was successfully opened, or `false` if
* it was already open.
*/
public async show(): Promise<boolean> {
if (this.open) {
return false;
Expand All @@ -90,7 +120,12 @@ export default class IgcBannerComponent extends EventEmitterMixin<
return this._player.playExclusive(growVerIn());
}

/** Hides the banner if not already hidden. Returns `true` when the animation has completed. */
/**
* Closes the banner with an animated grow-out transition.
*
* Returns `true` when the banner was successfully closed, or `false` if
* it was already closed.
*/
public async hide(): Promise<boolean> {
if (!this.open) {
return false;
Expand All @@ -101,7 +136,12 @@ export default class IgcBannerComponent extends EventEmitterMixin<
return true;
}

/** Toggles between shown/hidden state. Returns `true` when the animation has completed. */
/**
* Toggles the banner open or closed depending on its current state.
*
* Equivalent to calling `show()` when closed and `hide()` when open.
* Returns `true` when the transition completed successfully.
*/
public async toggle(): Promise<boolean> {
return this.open ? this.hide() : this.show();
}
Expand Down
Loading
Loading