• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-webcomponents / 26496577965

27 May 2026 07:12AM UTC coverage: 98.338%. Remained the same
26496577965

push

github

web-flow
feat: Invoker commands integration for components (#2225)

This commit integrates invoker commands into our components,
allowing for more flexible and dynamic interactions.

The changes include updates to button components,
the addition of a new command controller,
and modifications to related stories and configurations.

---------

Co-authored-by: damyanpetev <damyanpetev@users.noreply.github.com>

5858 of 6156 branches covered (95.16%)

Branch coverage included in aggregate %.

683 of 688 new or added lines in 12 files covered. (99.27%)

41466 of 41968 relevant lines covered (98.8%)

1577.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.64
/src/components/button/button-base.ts
1
import { html, LitElement, type TemplateResult } from 'lit';
30✔
2
import { property, query } from 'lit/decorators.js';
30✔
3
import { ifDefined } from 'lit/directives/if-defined.js';
30✔
4
import { addKeyboardFocusRing } from '../common/controllers/focus-ring.js';
30✔
5
import { addIdRefResolver } from '../common/controllers/id-resolver.js';
30✔
6
import { addInternalsController } from '../common/controllers/internals.js';
30✔
7
import { blazorDeepImport } from '../common/decorators/blazorDeepImport.js';
30✔
8
import { shadowOptions } from '../common/decorators/shadow-options.js';
30✔
9
import type { Constructor } from '../common/mixins/constructor.js';
30✔
10
import { EventEmitterMixin } from '../common//mixins/event-emitter.js';
30✔
11
import { partMap } from '../common/part-map.js';
30✔
12
import { bindIf, getElementByIdFromRoot } from '../common/util.js';
30✔
13

30✔
14
export interface IgcButtonEventMap {
30✔
15
  // For analyzer meta only:
30✔
16
  /* skipWCPrefix */
30✔
17
  focus: FocusEvent;
30✔
18
  /* skipWCPrefix */
30✔
19
  blur: FocusEvent;
30✔
20
}
30✔
21

30✔
22
/**
30✔
23
 * Abstract base class shared by `igc-button` and `igc-icon-button`.
30✔
24
 *
30✔
25
 * Provides common form-association behavior, link-button rendering
30✔
26
 * (renders as `<a>` when `href` is set), keyboard focus-ring management,
30✔
27
 * and the Invoker Commands API (`command` / `commandfor`).
30✔
28
 *
30✔
29
 * Concrete subclasses must implement `_renderContent()` to supply the
30✔
30
 * visual content projected inside the native `<button>` or `<a>` element.
30✔
31
 */
30✔
32
@blazorDeepImport
30✔
33
@shadowOptions({ delegatesFocus: true })
30✔
34
export abstract class IgcButtonBaseComponent extends EventEmitterMixin<
30✔
35
  IgcButtonEventMap,
30✔
36
  Constructor<LitElement>
30✔
37
>(LitElement) {
30✔
38
  public static readonly formAssociated = true;
1,584✔
39

1,584✔
40
  //#region Internal state
1,584✔
41

1,584✔
42
  private readonly _focusRingManager = addKeyboardFocusRing(this);
1,584✔
43
  private readonly _internals = addInternalsController(this);
1,584✔
44
  private readonly _resolver = addIdRefResolver(
1,584✔
45
    this,
1,584✔
46
    this._resolveCommandForElement
1,584✔
47
  );
1,584✔
48

1,584✔
49
  private _disabled = false;
1,584✔
50
  private _commandfor: string | null = null;
1,584✔
51
  private _commandForElement: Element | null = null;
1,584✔
52

1,584✔
53
  @query('[part="base"]', true)
1,584✔
54
  private readonly _nativeButton?: HTMLButtonElement | HTMLAnchorElement;
1,584✔
55

1,584✔
56
  //#endregion
1,584✔
57

1,584✔
58
  //#region Public properties
1,584✔
59

1,584✔
60
  /* alternateName: displayType */
1,584✔
61
  /**
1,584✔
62
   * The type of the button, which determines its behavior and semantics.
1,584✔
63
   * - `'button'` – no default action; useful for custom JavaScript handlers.
1,584✔
64
   * - `'submit'` – submits the associated form when clicked.
1,584✔
65
   * - `'reset'` – resets the associated form fields to their initial values.
1,584✔
66
   *
1,584✔
67
   * Ignored when the button is rendered as a link (i.e. `href` is set).
1,584✔
68
   * @attr type
1,584✔
69
   * @default 'button'
1,584✔
70
   */
1,584✔
71
  @property({ reflect: true })
1,584✔
72
  public type: 'button' | 'reset' | 'submit' = 'button';
1,584✔
73

30✔
74
  /**
30✔
75
   * The URL the button points to. When set, the component renders as an
30✔
76
   * `<a>` element instead of a `<button>`, enabling navigation on click.
30✔
77
   * Use together with `target`, `download`, and `rel` for full anchor semantics.
30✔
78
   * @attr href
30✔
79
   */
30✔
80
  @property()
30✔
81
  public href?: string;
30✔
82

30✔
83
  /**
30✔
84
   * Prompts the browser to download the linked resource rather than navigating
30✔
85
   * to it. The optional value is used as the suggested file name.
30✔
86
   * Only effective when `href` is set.
30✔
87
   * @attr download
30✔
88
   */
30✔
89
  @property()
30✔
90
  public download?: string;
30✔
91

30✔
92
  /**
30✔
93
   * Where to open the linked document. Only effective when `href` is set.
30✔
94
   * - `'_self'` – current browsing context (default browser behavior).
30✔
95
   * - `'_blank'` – new tab or window.
30✔
96
   * - `'_parent'` – parent browsing context; falls back to `_self` if none.
30✔
97
   * - `'_top'` – top-level browsing context; falls back to `_self` if none.
30✔
98
   * @attr target
30✔
99
   */
30✔
100
  @property()
30✔
101
  public target?: '_blank' | '_parent' | '_self' | '_top';
30✔
102

30✔
103
  /**
30✔
104
   * The relationship between the current document and the linked URL.
30✔
105
   * Accepts a space-separated list of link types (e.g. `'noopener noreferrer'`).
30✔
106
   * Only effective when `href` is set. When `target="_blank"` is used,
30✔
107
   * setting `rel="noopener noreferrer"` is strongly recommended for security.
30✔
108
   * @attr rel
30✔
109
   */
30✔
110
  @property()
30✔
111
  public rel?: string;
30✔
112

30✔
113
  /**
30✔
114
   * When set, the button will be disabled and non-interactive.
30✔
115
   *
30✔
116
   * @attr disabled
30✔
117
   * @default false
30✔
118
   */
30✔
119
  @property({ type: Boolean, reflect: true })
30✔
120
  public set disabled(value: boolean) {
30✔
121
    this._disabled = value;
131✔
122
    this.toggleAttribute('disabled', Boolean(this._disabled));
131✔
123
  }
131✔
124

30✔
125
  public get disabled(): boolean {
30✔
126
    return this._disabled;
5,098✔
127
  }
5,098✔
128

30✔
129
  /**
30✔
130
   * The command to invoke on the target element specified by `commandfor`.
30✔
131
   * Part of the [Invoker Commands](https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API) API.
30✔
132
   * Custom commands must start with two dashes (e.g. `'--my-command'`).
30✔
133
   * @attr command
30✔
134
   */
30✔
135
  @property({ reflect: true })
30✔
136
  public command?: string;
30✔
137

30✔
138
  /**
30✔
139
   * The ID of the target element for the invoker command.
30✔
140
   * Part of the [Invoker Commands API](https://developer.mozilla.org/en-US/docs/Web/API/Invoker_Commands_API).
30✔
141
   * @attr commandfor
30✔
142
   */
30✔
143
  @property({ attribute: 'commandfor' })
30✔
144
  public set commandfor(value: string | null) {
30✔
145
    this._commandfor = value;
32✔
146
    if (value) {
32✔
147
      this._resolver.observe();
32✔
148
      this._commandForElement = getElementByIdFromRoot(this, value);
32✔
149
    } else {
32!
NEW
150
      this._commandForElement = null;
×
NEW
151
      this._resolver.unobserve();
×
NEW
152
    }
×
153
  }
32✔
154

30✔
155
  public get commandfor(): string | null {
30✔
156
    return this._commandfor;
1,622✔
157
  }
1,622✔
158

30✔
159
  /* blazorCSSuppress */
30✔
160
  /**
30✔
161
   * The target element for the invoker command. Resolved from the `commandfor` ID.
30✔
162
   */
30✔
163
  public get commandForElement(): Element | null {
30✔
164
    return this._commandForElement;
4✔
165
  }
4✔
166

30✔
167
  /* blazorCSSuppress */
30✔
168
  public set commandForElement(value: Element | null) {
30✔
169
    this._commandForElement = value;
1✔
170
    this.requestUpdate();
1✔
171
  }
1✔
172

30✔
173
  /* blazorCSSuppress */
30✔
174
  /* alternateType: object */
30✔
175
  /**
30✔
176
   * The `<form>` element the button is associated with.
30✔
177
   * Resolved through the standard form-association mechanism — either the
30✔
178
   * closest ancestor `<form>` or the form referenced by the button's `form`
30✔
179
   * attribute. Returns `null` when no form is associated.
30✔
180
   * Relevant only when `type` is `'submit'` or `'reset'`.
30✔
181
   */
30✔
182
  public get form(): HTMLFormElement | null {
30✔
183
    return this._internals.form;
3✔
184
  }
3✔
185

30✔
186
  //#endregion
30✔
187

30✔
188
  //#region Lifecycle
30✔
189

30✔
190
  protected override firstUpdated(): void {
30✔
191
    if (this._commandfor) {
1,558✔
192
      this._commandForElement = getElementByIdFromRoot(this, this._commandfor);
32✔
193
      this.requestUpdate();
32✔
194
    }
32✔
195
  }
1,558✔
196

30✔
197
  //#endregion
30✔
198

30✔
199
  //#region Event handlers
30✔
200

30✔
201
  protected _handleClick(): void {
30✔
202
    switch (this.type) {
47✔
203
      case 'submit':
47✔
204
        this.form?.requestSubmit();
1✔
205
        break;
1✔
206
      case 'reset':
47✔
207
        this.form?.reset();
1✔
208
        break;
1✔
209
    }
47✔
210
  }
47✔
211

30✔
212
  protected formDisabledCallback(state: boolean): void {
30✔
213
    this._disabled = state;
125✔
214
    this.requestUpdate();
125✔
215
  }
125✔
216

30✔
217
  //#endregion
30✔
218

30✔
219
  private _resolveCommandForElement(ids: Set<string>): void {
30✔
220
    if (this._commandfor && ids.has(this._commandfor)) {
2✔
221
      this._commandForElement = getElementByIdFromRoot(this, this._commandfor);
2✔
222
      this.requestUpdate();
2✔
223
    }
2✔
224
  }
2✔
225

30✔
226
  //#region Public API
30✔
227

30✔
228
  /** Simulates a mouse click on the button, triggering its click handler and any associated form action. */
30✔
229
  public override click(): void {
30✔
230
    this._nativeButton?.click();
52✔
231
  }
52✔
232

30✔
233
  //#endregion
30✔
234

30✔
235
  private _renderButton() {
30✔
236
    return html`
1,728✔
237
      <button
1,728✔
238
        command=${ifDefined(this.command)}
1,728✔
239
        .commandForElement=${this._commandForElement}
1,728✔
240
        part=${partMap({ base: true, focused: this._focusRingManager.focused })}
1,728✔
241
        aria-label=${bindIf(this.ariaLabel, this.ariaLabel)}
1,728✔
242
        ?disabled=${this.disabled}
1,728✔
243
        type=${ifDefined(this.type)}
1,728✔
244
        @click=${this._handleClick}
1,728✔
245
      >
1,728✔
246
        ${this._renderContent()}
1,728✔
247
      </button>
1,728✔
248
    `;
1,728✔
249
  }
1,728✔
250

30✔
251
  private _renderLinkButton() {
30✔
252
    return html`
17✔
253
      <a
17✔
254
        part=${partMap({ base: true, focused: this._focusRingManager.focused })}
17✔
255
        role="button"
17✔
256
        aria-label=${bindIf(this.ariaLabel, this.ariaLabel)}
17✔
257
        aria-disabled=${this.disabled}
17✔
258
        href=${ifDefined(this.href)}
17✔
259
        target=${ifDefined(this.target)}
17✔
260
        download=${ifDefined(this.download)}
17✔
261
        rel=${ifDefined(this.rel)}
17✔
262
      >
17✔
263
        ${this._renderContent()}
17✔
264
      </a>
17✔
265
    `;
17✔
266
  }
17✔
267

30✔
268
  protected abstract _renderContent(): TemplateResult;
30✔
269

30✔
270
  protected override render() {
30✔
271
    return this.href != null ? this._renderLinkButton() : this._renderButton();
1,745✔
272
  }
1,745✔
273
}
30✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc