@momentum-design/components 0.120.24 → 0.120.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,7 +16,7 @@ const styles = css `
16
16
  background-position: right;
17
17
  }
18
18
  :host::part(body) {
19
- margin-top: 2rem;
19
+ margin-top: 2rem !important;
20
20
  display: grid;
21
21
  grid-template-columns: repeat(auto-fit, minmax(0px, 1fr));
22
22
  }
@@ -170,8 +170,8 @@ class Combobox extends CaptureDestroyEventForChildElement(AutoFocusOnMountMixin(
170
170
  * @internal
171
171
  */
172
172
  this.handleDestroyEvent = (event) => {
173
- const destroyedElement = event.target;
174
- if (!this.isValidItem(destroyedElement) || destroyedElement.tabIndex !== 0) {
173
+ const destroyedElement = event.detail.originalTarget;
174
+ if (destroyedElement && (!this.isValidItem(destroyedElement) || destroyedElement.tabIndex !== 0)) {
175
175
  return;
176
176
  }
177
177
  const destroyedItemIndex = this.navItems.findIndex(node => node === destroyedElement);
@@ -69,7 +69,7 @@ class List extends ListNavigationMixin(CaptureDestroyEventForChildElement(Compon
69
69
  * @internal
70
70
  */
71
71
  this.handleDestroyEvent = (event) => {
72
- const destroyedElement = event.target;
72
+ const destroyedElement = event.detail.originalTarget;
73
73
  if (!this.isValidItem(destroyedElement) || destroyedElement.tabIndex !== 0) {
74
74
  return;
75
75
  }
@@ -66,6 +66,12 @@ declare class ScreenreaderAnnouncer extends Component {
66
66
  * @default 20_000
67
67
  */
68
68
  timeout: number;
69
+ /**
70
+ * The debounce time delay in milliseconds for announcements.
71
+ *
72
+ * @default 500
73
+ */
74
+ debounceTime: number;
69
75
  /**
70
76
  * Array to store timeOutIds for clearing timeouts later.
71
77
  * @internal
@@ -76,6 +82,8 @@ declare class ScreenreaderAnnouncer extends Component {
76
82
  * @internal
77
83
  */
78
84
  private ariaLiveAnnouncementIds;
85
+ /** @internal */
86
+ private debouncedAnnounce?;
79
87
  /**
80
88
  * Announces the given announcement to the screen reader.
81
89
  *
@@ -102,6 +110,13 @@ declare class ScreenreaderAnnouncer extends Component {
102
110
  * `mdc-screenreaderannouncer-identity`.
103
111
  */
104
112
  private createAnnouncementAriaLiveRegion;
113
+ /**
114
+ * Creates a single debounced function that will read the latest this.announcement when executed.
115
+ *
116
+ * This function is used to debounce the announcement so that it will only be made after
117
+ * this.debounceTime milliseconds have passed since the last time this.announcement was updated.
118
+ */
119
+ private setupDebouncedAnnounce;
105
120
  connectedCallback(): void;
106
121
  disconnectedCallback(): void;
107
122
  protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
@@ -10,6 +10,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  import { property } from 'lit/decorators.js';
11
11
  import { v4 as uuidv4 } from 'uuid';
12
12
  import { Component } from '../../models';
13
+ import { debounce } from '../../utils/debounce';
13
14
  import { DEFAULTS } from './screenreaderannouncer.constants';
14
15
  import styles from './screenreaderannouncer.styles';
15
16
  /**
@@ -79,6 +80,12 @@ class ScreenreaderAnnouncer extends Component {
79
80
  * @default 20_000
80
81
  */
81
82
  this.timeout = DEFAULTS.TIMEOUT;
83
+ /**
84
+ * The debounce time delay in milliseconds for announcements.
85
+ *
86
+ * @default 500
87
+ */
88
+ this.debounceTime = DEFAULTS.DEBOUNCE;
82
89
  /**
83
90
  * Array to store timeOutIds for clearing timeouts later.
84
91
  * @internal
@@ -109,8 +116,8 @@ class ScreenreaderAnnouncer extends Component {
109
116
  if (announcement.length > 0) {
110
117
  const announcementId = `mdc-screenreaderannouncer-announcement-${uuidv4()}`;
111
118
  const announcementContainer = document.createElement('div');
112
- announcementContainer.id = announcementId;
113
- announcementContainer.ariaLive = ariaLive;
119
+ announcementContainer.setAttribute('id', announcementId);
120
+ announcementContainer.setAttribute('aria-live', ariaLive);
114
121
  (_a = document.getElementById(this.identity)) === null || _a === void 0 ? void 0 : _a.appendChild(announcementContainer);
115
122
  const timeOutId = window.setTimeout(() => {
116
123
  const announcementElement = document.createElement('p');
@@ -166,25 +173,48 @@ class ScreenreaderAnnouncer extends Component {
166
173
  document.body.appendChild(liveRegionLightDom);
167
174
  }
168
175
  }
176
+ /**
177
+ * Creates a single debounced function that will read the latest this.announcement when executed.
178
+ *
179
+ * This function is used to debounce the announcement so that it will only be made after
180
+ * this.debounceTime milliseconds have passed since the last time this.announcement was updated.
181
+ */
182
+ setupDebouncedAnnounce() {
183
+ // create single debounced function that will read latest this.announcement when executed
184
+ this.debouncedAnnounce = debounce(() => {
185
+ if (this.announcement.length > 0) {
186
+ this.announce(this.announcement, this.delay, this.timeout, this.dataAriaLive);
187
+ this.announcement = '';
188
+ }
189
+ }, this.debounceTime);
190
+ }
169
191
  connectedCallback() {
170
192
  super.connectedCallback();
171
193
  if (this.identity.length === 0) {
172
194
  this.identity = DEFAULTS.IDENTITY;
173
195
  }
174
196
  this.createAnnouncementAriaLiveRegion();
197
+ this.setupDebouncedAnnounce();
175
198
  }
176
199
  disconnectedCallback() {
200
+ var _a;
177
201
  super.disconnectedCallback();
178
202
  this.clearTimeOutsAndAnnouncements();
203
+ // cancel any pending debounced action and clear DOM timeouts
204
+ (_a = this.debouncedAnnounce) === null || _a === void 0 ? void 0 : _a.cancel();
179
205
  }
180
206
  updated(changedProperties) {
207
+ var _a;
181
208
  if (changedProperties.has('identity') && this.identity.length === 0) {
182
209
  this.identity = DEFAULTS.IDENTITY;
183
210
  this.createAnnouncementAriaLiveRegion();
184
211
  }
212
+ if (changedProperties.has('debounceTime')) {
213
+ // Reinitiate debounced function if debounceTime changed
214
+ this.setupDebouncedAnnounce();
215
+ }
185
216
  if (changedProperties.has('announcement') && this.announcement.length > 0) {
186
- this.announce(this.announcement, this.delay, this.timeout, this.dataAriaLive);
187
- this.announcement = '';
217
+ (_a = this.debouncedAnnounce) === null || _a === void 0 ? void 0 : _a.call(this);
188
218
  }
189
219
  }
190
220
  }
@@ -209,4 +239,8 @@ __decorate([
209
239
  property({ type: Number, reflect: true }),
210
240
  __metadata("design:type", Number)
211
241
  ], ScreenreaderAnnouncer.prototype, "timeout", void 0);
242
+ __decorate([
243
+ property({ type: Number, reflect: true, attribute: 'debounce-time' }),
244
+ __metadata("design:type", Number)
245
+ ], ScreenreaderAnnouncer.prototype, "debounceTime", void 0);
212
246
  export default ScreenreaderAnnouncer;
@@ -9,5 +9,6 @@ declare const DEFAULTS: {
9
9
  readonly DELAY: 150;
10
10
  readonly IDENTITY: "mdc-screenreaderannouncer-identity";
11
11
  readonly TIMEOUT: 20000;
12
+ readonly DEBOUNCE: 500;
12
13
  };
13
14
  export { ARIA_LIVE_VALUES, DEFAULTS, TAG_NAME };
@@ -10,5 +10,6 @@ const DEFAULTS = {
10
10
  DELAY: 150,
11
11
  IDENTITY: 'mdc-screenreaderannouncer-identity',
12
12
  TIMEOUT: 20000,
13
+ DEBOUNCE: 500,
13
14
  };
14
15
  export { ARIA_LIVE_VALUES, DEFAULTS, TAG_NAME };
@@ -1,6 +1,7 @@
1
1
  import Textarea from './textarea.component';
2
2
  import '../button';
3
3
  import '../icon';
4
+ import '../screenreaderannouncer';
4
5
  import '../text';
5
6
  import '../toggletip';
6
7
  declare global {
@@ -2,6 +2,7 @@ import Textarea from './textarea.component';
2
2
  import { TAG_NAME } from './textarea.constants';
3
3
  import '../button';
4
4
  import '../icon';
5
+ import '../screenreaderannouncer';
5
6
  import '../text';
6
7
  import '../toggletip';
7
8
  Textarea.register(TAG_NAME);
@@ -110,14 +110,25 @@ declare class Textarea extends Textarea_base {
110
110
  */
111
111
  minlength?: number;
112
112
  /**
113
- * maximum character limit for the textarea field for character counter.
113
+ * The maximum character limit for the textarea field for character counter.
114
114
  */
115
115
  maxCharacterLimit?: number;
116
+ /**
117
+ * Template string for the announcement that will be read by screen readers when the max character limit is set.
118
+ * Consumers must use the placeholders `%{number-of-characters}` and `%{max-character-limit}` in the string,
119
+ * which will be dynamically replaced with the actual values at runtime.
120
+ * For example: `%{number-of-characters} out of %{max-character-limit} characters are typed.`
121
+ * Example output: "93 out of 140 characters are typed."
122
+ */
123
+ characterLimitAnnouncement?: string;
116
124
  /**
117
125
  * @internal
118
126
  * The textarea element
119
127
  */
120
128
  inputElement: HTMLTextAreaElement;
129
+ /** @internal */
130
+ private ariaLiveAnnouncer?;
131
+ /** @internal */
121
132
  private characterLimitExceedingFired;
122
133
  protected get textarea(): HTMLTextAreaElement;
123
134
  connectedCallback(): void;
@@ -160,6 +171,12 @@ declare class Textarea extends Textarea_base {
160
171
  * @returns void
161
172
  */
162
173
  private updateValue;
174
+ /**
175
+ * Announces the character limit warning based on the current value length.
176
+ * If the value length exceeds the max character limit, the help text is announced (if help text is present).
177
+ * If the value length does not exceed the max character limit, then the character limit announcement is announced.
178
+ */
179
+ private announceMaxLengthWarning;
163
180
  /**
164
181
  * Handles the change event of the textarea field.
165
182
  * Updates the value and sets the validity of the textarea field.
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  };
10
10
  import { html, nothing } from 'lit';
11
11
  import { ifDefined } from 'lit/directives/if-defined.js';
12
- import { property, query } from 'lit/decorators.js';
12
+ import { property, query, state } from 'lit/decorators.js';
13
13
  import FormfieldWrapper from '../formfieldwrapper';
14
14
  import { DEFAULTS as FORMFIELD_DEFAULTS, VALIDATION } from '../formfieldwrapper/formfieldwrapper.constants';
15
15
  import { AUTO_CAPITALIZE } from '../input/input.constants';
@@ -110,6 +110,7 @@ class Textarea extends AutoFocusOnMountMixin(FormInternalsMixin(DataAriaLabelMix
110
110
  * @default 'off'
111
111
  */
112
112
  this.autocomplete = AUTO_COMPLETE.OFF;
113
+ /** @internal */
113
114
  this.characterLimitExceedingFired = false;
114
115
  }
115
116
  get textarea() {
@@ -255,6 +256,33 @@ class Textarea extends AutoFocusOnMountMixin(FormInternalsMixin(DataAriaLabelMix
255
256
  updateValue() {
256
257
  this.value = this.textarea.value;
257
258
  this.internals.setFormValue(this.textarea.value);
259
+ this.announceMaxLengthWarning();
260
+ }
261
+ /**
262
+ * Announces the character limit warning based on the current value length.
263
+ * If the value length exceeds the max character limit, the help text is announced (if help text is present).
264
+ * If the value length does not exceed the max character limit, then the character limit announcement is announced.
265
+ */
266
+ announceMaxLengthWarning() {
267
+ this.ariaLiveAnnouncer = '';
268
+ if (!this.maxCharacterLimit || this.value.length === 0) {
269
+ return;
270
+ }
271
+ if (this.helpText && this.value.length > this.maxCharacterLimit) {
272
+ // We need to assign the same value multiple times, when the input reaches the max limit,
273
+ // Lit does a `===` strict comparison and doesn't update the value
274
+ // Hence we need to manually wait for the update to complete and then assign the value.
275
+ this.updateComplete
276
+ .then(() => {
277
+ this.ariaLiveAnnouncer = this.helpText;
278
+ })
279
+ .catch(() => { });
280
+ }
281
+ else if (this.characterLimitAnnouncement && this.value.length <= this.maxCharacterLimit) {
282
+ this.ariaLiveAnnouncer = this.characterLimitAnnouncement
283
+ .replace('%{number-of-characters}', this.value.length.toString())
284
+ .replace('%{max-character-limit}', this.maxCharacterLimit.toString());
285
+ }
258
286
  }
259
287
  /**
260
288
  * Handles the change event of the textarea field.
@@ -315,6 +343,11 @@ class Textarea extends AutoFocusOnMountMixin(FormInternalsMixin(DataAriaLabelMix
315
343
  aria-describedby="${ifDefined(this.helpText ? FORMFIELD_DEFAULTS.HELPER_TEXT_ID : '')}"
316
344
  aria-invalid="${this.helpTextType === 'error' ? 'true' : 'false'}"
317
345
  ></textarea>
346
+ <mdc-screenreaderannouncer
347
+ identity="${this.inputId}"
348
+ announcement="${ifDefined(this.ariaLiveAnnouncer)}"
349
+ data-aria-live="polite"
350
+ ></mdc-screenreaderannouncer>
318
351
  </div>
319
352
  ${this.renderTextareaFooter()}
320
353
  `;
@@ -361,8 +394,16 @@ __decorate([
361
394
  property({ type: Number, attribute: 'max-character-limit' }),
362
395
  __metadata("design:type", Number)
363
396
  ], Textarea.prototype, "maxCharacterLimit", void 0);
397
+ __decorate([
398
+ property({ type: String, attribute: 'character-limit-announcement' }),
399
+ __metadata("design:type", String)
400
+ ], Textarea.prototype, "characterLimitAnnouncement", void 0);
364
401
  __decorate([
365
402
  query('textarea'),
366
403
  __metadata("design:type", HTMLTextAreaElement)
367
404
  ], Textarea.prototype, "inputElement", void 0);
405
+ __decorate([
406
+ state(),
407
+ __metadata("design:type", String)
408
+ ], Textarea.prototype, "ariaLiveAnnouncer", void 0);
368
409
  export default Textarea;