@momentum-design/components 0.134.0 → 0.134.1

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.
@@ -1,6 +1,7 @@
1
1
  import { type EventName } from '@lit/react';
2
2
  import Component from '../../components/input';
3
3
  import type { Events } from '../../components/input/input.types';
4
+ import type { Events as EventsInherited } from '../../utils/mixins/CharacterLimitMixin.ts';
4
5
  /**
5
6
  * mdc-input is a component that allows users to input text.
6
7
  * It contains:
@@ -20,6 +21,8 @@ import type { Events } from '../../components/input/input.types';
20
21
  * @event focus - (React: onFocus) This event is dispatched when the input receives focus.
21
22
  * @event blur - (React: onBlur) This event is dispatched when the input loses focus.
22
23
  * @event clear - (React: onClear) This event is dispatched when the input text is cleared.
24
+ * @event limitexceeded - (React: onLimitExceeded) This event is dispatched once when the character limit
25
+ * exceeds or restored.
23
26
  *
24
27
  * @dependency mdc-icon
25
28
  * @dependency mdc-text
@@ -66,6 +69,8 @@ import type { Events } from '../../components/input/input.types';
66
69
  * @csspart input-section - The container for the input field, leading icon, and prefix text elements.
67
70
  * @csspart input-text - The input field element.
68
71
  * @csspart trailing-button - The trailing button element that is displayed to clear the input field when the `trailingButton` property is set to true.
72
+ * @csspart input-footer - The footer element that contains the helper text and character counter.
73
+ * @csspart character-counter - The character counter element.
69
74
  */
70
75
  declare const reactWrapper: import("@lit/react").ReactWebComponent<Component, {
71
76
  onClear: EventName<Events["onClearEvent"]>;
@@ -73,5 +78,6 @@ declare const reactWrapper: import("@lit/react").ReactWebComponent<Component, {
73
78
  onChange: EventName<Events["onChangeEvent"]>;
74
79
  onFocus: EventName<Events["onFocusEvent"]>;
75
80
  onBlur: EventName<Events["onBlurEvent"]>;
81
+ onLimitExceeded: EventName<EventsInherited["onLimitExceededEvent"]>;
76
82
  }>;
77
83
  export default reactWrapper;
@@ -21,6 +21,8 @@ import { TAG_NAME } from '../../components/input/input.constants';
21
21
  * @event focus - (React: onFocus) This event is dispatched when the input receives focus.
22
22
  * @event blur - (React: onBlur) This event is dispatched when the input loses focus.
23
23
  * @event clear - (React: onClear) This event is dispatched when the input text is cleared.
24
+ * @event limitexceeded - (React: onLimitExceeded) This event is dispatched once when the character limit
25
+ * exceeds or restored.
24
26
  *
25
27
  * @dependency mdc-icon
26
28
  * @dependency mdc-text
@@ -67,6 +69,8 @@ import { TAG_NAME } from '../../components/input/input.constants';
67
69
  * @csspart input-section - The container for the input field, leading icon, and prefix text elements.
68
70
  * @csspart input-text - The input field element.
69
71
  * @csspart trailing-button - The trailing button element that is displayed to clear the input field when the `trailingButton` property is set to true.
72
+ * @csspart input-footer - The footer element that contains the helper text and character counter.
73
+ * @csspart character-counter - The character counter element.
70
74
  */
71
75
  const reactWrapper = createComponent({
72
76
  tagName: TAG_NAME,
@@ -78,6 +82,7 @@ const reactWrapper = createComponent({
78
82
  onChange: 'change',
79
83
  onFocus: 'focus',
80
84
  onBlur: 'blur',
85
+ onLimitExceeded: 'limitexceeded',
81
86
  },
82
87
  displayName: 'Input',
83
88
  });
@@ -1,6 +1,7 @@
1
1
  import { type EventName } from '@lit/react';
2
2
  import Component from '../../components/password';
3
- import type { Events as EventsInherited } from '../../components/input/input.types';
3
+ import type { Events as EventsInherited0 } from '../../components/input/input.types';
4
+ import type { Events as EventsInherited1 } from '../../utils/mixins/CharacterLimitMixin.ts';
4
5
  /**
5
6
  * `mdc-password` is a component that allows users to input their password.
6
7
  * It extends the `mdc-input` component and provides additional features specific to password fields.
@@ -61,10 +62,11 @@ import type { Events as EventsInherited } from '../../components/input/input.typ
61
62
  *
62
63
  */
63
64
  declare const reactWrapper: import("@lit/react").ReactWebComponent<Component, {
64
- onInput: EventName<EventsInherited["onInputEvent"]>;
65
- onChange: EventName<EventsInherited["onChangeEvent"]>;
66
- onFocus: EventName<EventsInherited["onFocusEvent"]>;
67
- onBlur: EventName<EventsInherited["onBlurEvent"]>;
68
- onClear: EventName<EventsInherited["onClearEvent"]>;
65
+ onInput: EventName<EventsInherited0["onInputEvent"]>;
66
+ onChange: EventName<EventsInherited0["onChangeEvent"]>;
67
+ onFocus: EventName<EventsInherited0["onFocusEvent"]>;
68
+ onBlur: EventName<EventsInherited0["onBlurEvent"]>;
69
+ onLimitExceeded: EventName<EventsInherited1["onLimitExceededEvent"]>;
70
+ onClear: EventName<EventsInherited0["onClearEvent"]>;
69
71
  }>;
70
72
  export default reactWrapper;
@@ -70,6 +70,7 @@ const reactWrapper = createComponent({
70
70
  onChange: 'change',
71
71
  onFocus: 'focus',
72
72
  onBlur: 'blur',
73
+ onLimitExceeded: 'limitexceeded',
73
74
  onClear: 'clear',
74
75
  },
75
76
  displayName: 'Password',
@@ -1,7 +1,8 @@
1
1
  import { type EventName } from '@lit/react';
2
2
  import Component from '../../components/searchfield';
3
3
  import type { Events } from '../../components/searchfield/searchfield.types';
4
- import type { Events as EventsInherited } from '../../components/input/input.types';
4
+ import type { Events as EventsInherited0 } from '../../components/input/input.types';
5
+ import type { Events as EventsInherited1 } from '../../utils/mixins/CharacterLimitMixin.ts';
5
6
  /**
6
7
  * `mdc-searchfield` component is used as an input field for search functionality.
7
8
  *
@@ -76,10 +77,11 @@ import type { Events as EventsInherited } from '../../components/input/input.typ
76
77
  */
77
78
  declare const reactWrapper: import("@lit/react").ReactWebComponent<Component, {
78
79
  onChipRemove: EventName<Events["onChipRemoveEvent"]>;
79
- onInput: EventName<EventsInherited["onInputEvent"]>;
80
- onChange: EventName<EventsInherited["onChangeEvent"]>;
81
- onFocus: EventName<EventsInherited["onFocusEvent"]>;
82
- onBlur: EventName<EventsInherited["onBlurEvent"]>;
83
- onClear: EventName<EventsInherited["onClearEvent"]>;
80
+ onInput: EventName<EventsInherited0["onInputEvent"]>;
81
+ onChange: EventName<EventsInherited0["onChangeEvent"]>;
82
+ onFocus: EventName<EventsInherited0["onFocusEvent"]>;
83
+ onBlur: EventName<EventsInherited0["onBlurEvent"]>;
84
+ onClear: EventName<EventsInherited0["onClearEvent"]>;
85
+ onLimitExceeded: EventName<EventsInherited1["onLimitExceededEvent"]>;
84
86
  }>;
85
87
  export default reactWrapper;
@@ -85,6 +85,7 @@ const reactWrapper = createComponent({
85
85
  onFocus: 'focus',
86
86
  onBlur: 'blur',
87
87
  onClear: 'clear',
88
+ onLimitExceeded: 'limitexceeded',
88
89
  },
89
90
  displayName: 'Searchfield',
90
91
  });
@@ -3,6 +3,7 @@ import Component from '../../components/searchpopover';
3
3
  import type { Events } from '../../components/searchpopover/searchpopover.types';
4
4
  import type { Events as EventsInherited0 } from '../../components/input/input.types';
5
5
  import type { Events as EventsInherited1 } from '../../components/searchfield/searchfield.types';
6
+ import type { Events as EventsInherited2 } from '../../utils/mixins/CharacterLimitMixin.ts';
6
7
  /**
7
8
  * `mdc-searchpopover` widget is a combination of the Searchfield and Popover components, connected to ensure
8
9
  * proper accessibility. This component should be used when search results or suggestions need to be displayed
@@ -88,5 +89,6 @@ declare const reactWrapper: import("@lit/react").ReactWebComponent<Component, {
88
89
  onBlur: EventName<EventsInherited0["onBlurEvent"]>;
89
90
  onClear: EventName<EventsInherited0["onClearEvent"]>;
90
91
  onChipRemove: EventName<EventsInherited1["onChipRemoveEvent"]>;
92
+ onLimitExceeded: EventName<EventsInherited2["onLimitExceededEvent"]>;
91
93
  }>;
92
94
  export default reactWrapper;
@@ -91,6 +91,7 @@ const reactWrapper = createComponent({
91
91
  onBlur: 'blur',
92
92
  onClear: 'clear',
93
93
  onChipRemove: 'chipRemove',
94
+ onLimitExceeded: 'limitexceeded',
94
95
  },
95
96
  displayName: 'Searchpopover',
96
97
  });
@@ -1,6 +1,7 @@
1
1
  import { type EventName } from '@lit/react';
2
2
  import Component from '../../components/textarea';
3
3
  import type { Events } from '../../components/textarea/textarea.types';
4
+ import type { Events as EventsInherited } from '../../utils/mixins/CharacterLimitMixin.ts';
4
5
  /**
5
6
  * mdc-textarea component, which is used to get the multi-line text input from the user.
6
7
  * It contains:
@@ -87,10 +88,10 @@ import type { Events } from '../../components/textarea/textarea.types';
87
88
  * @cssproperty --mdc-textarea-container-background-color - Background color for the textarea container
88
89
  */
89
90
  declare const reactWrapper: import("@lit/react").ReactWebComponent<Component, {
90
- onLimitExceeded: EventName<Events["onLimitExceededEvent"]>;
91
91
  onInput: EventName<Events["onInputEvent"]>;
92
92
  onChange: EventName<Events["onChangeEvent"]>;
93
93
  onFocus: EventName<Events["onFocusEvent"]>;
94
94
  onBlur: EventName<Events["onBlurEvent"]>;
95
+ onLimitExceeded: EventName<EventsInherited["onLimitExceededEvent"]>;
95
96
  }>;
96
97
  export default reactWrapper;
@@ -92,11 +92,11 @@ const reactWrapper = createComponent({
92
92
  elementClass: Component,
93
93
  react: React,
94
94
  events: {
95
- onLimitExceeded: 'limitexceeded',
96
95
  onInput: 'input',
97
96
  onChange: 'change',
98
97
  onFocus: 'focus',
99
98
  onBlur: 'blur',
99
+ onLimitExceeded: 'limitexceeded',
100
100
  },
101
101
  displayName: 'Textarea',
102
102
  });
@@ -0,0 +1,37 @@
1
+ import { LitElement, nothing, html } from 'lit';
2
+ import type { Constructor } from './index.types';
3
+ export interface CharacterLimitHost {
4
+ value: string;
5
+ helpText?: string;
6
+ }
7
+ interface Events {
8
+ onLimitExceededEvent: CustomEvent;
9
+ }
10
+ export type { Events };
11
+ export declare class CharacterLimitMixinInterface {
12
+ maxCharacterLimit?: number;
13
+ characterLimitAnnouncement?: string;
14
+ protected characterLimitAriaLiveAnnouncer?: string;
15
+ protected handleCharacterOverflowStateChange(): void;
16
+ protected announceCharacterLimitWarning(): void;
17
+ protected renderCharacterCounter(): typeof nothing | ReturnType<typeof html>;
18
+ }
19
+ /**
20
+ * Mixin that provides character limit / counter functionality.
21
+ *
22
+ * This mixin is shared between `mdc-input` and `mdc-textarea` components.
23
+ * It provides:
24
+ * - `maxCharacterLimit` and `characterLimitAnnouncement` properties
25
+ * - Character overflow state tracking and `limitexceeded` event dispatching
26
+ * - Screen reader announcement logic for character limit warnings
27
+ * - `renderCharacterCounter()` method for rendering the counter UI
28
+ *
29
+ * The consuming component must:
30
+ * - Have a `value` property (string)
31
+ * - Have a `helpText` property (string)
32
+ * - Call `handleCharacterOverflowStateChange()` when `value` changes
33
+ * - Call `announceCharacterLimitWarning()` when `helpText` changes or value is updated
34
+ *
35
+ * @param superClass - The base class to extend.
36
+ */
37
+ export declare const CharacterLimitMixin: <T extends Constructor<LitElement & CharacterLimitHost>>(superClass: T) => Constructor<CharacterLimitMixinInterface> & T;
@@ -0,0 +1,124 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { nothing, html } from 'lit';
11
+ import { property, state } from 'lit/decorators.js';
12
+ /**
13
+ * Mixin that provides character limit / counter functionality.
14
+ *
15
+ * This mixin is shared between `mdc-input` and `mdc-textarea` components.
16
+ * It provides:
17
+ * - `maxCharacterLimit` and `characterLimitAnnouncement` properties
18
+ * - Character overflow state tracking and `limitexceeded` event dispatching
19
+ * - Screen reader announcement logic for character limit warnings
20
+ * - `renderCharacterCounter()` method for rendering the counter UI
21
+ *
22
+ * The consuming component must:
23
+ * - Have a `value` property (string)
24
+ * - Have a `helpText` property (string)
25
+ * - Call `handleCharacterOverflowStateChange()` when `value` changes
26
+ * - Call `announceCharacterLimitWarning()` when `helpText` changes or value is updated
27
+ *
28
+ * @param superClass - The base class to extend.
29
+ */
30
+ export const CharacterLimitMixin = (superClass) => {
31
+ /**
32
+ * @event limitexceeded - (React: onLimitExceeded) This event is dispatched once when the character limit
33
+ * exceeds or restored.
34
+ */
35
+ class InnerMixinClass extends superClass {
36
+ constructor() {
37
+ super(...arguments);
38
+ /**
39
+ * @internal
40
+ */
41
+ this.characterLimitExceedingFired = false;
42
+ }
43
+ /**
44
+ * Dispatches the character overflow state change event.
45
+ * @internal
46
+ */
47
+ dispatchCharacterOverflowStateChangeEvent() {
48
+ this.dispatchEvent(new CustomEvent('limitexceeded', {
49
+ detail: {
50
+ currentCharacterCount: this.value.length,
51
+ maxCharacterLimit: this.maxCharacterLimit,
52
+ value: this.value,
53
+ },
54
+ bubbles: true,
55
+ composed: true,
56
+ }));
57
+ }
58
+ /**
59
+ * Handles the character overflow state change.
60
+ * Dispatches the character overflow state change event if the character limit is exceeded or restored.
61
+ */
62
+ handleCharacterOverflowStateChange() {
63
+ if (this.maxCharacterLimit) {
64
+ if (this.value.length > this.maxCharacterLimit && !this.characterLimitExceedingFired) {
65
+ this.dispatchCharacterOverflowStateChangeEvent();
66
+ this.characterLimitExceedingFired = true;
67
+ }
68
+ else if (this.value.length <= this.maxCharacterLimit && this.characterLimitExceedingFired) {
69
+ this.dispatchCharacterOverflowStateChangeEvent();
70
+ this.characterLimitExceedingFired = false;
71
+ }
72
+ }
73
+ }
74
+ /**
75
+ * Announces the character limit warning based on the current value length.
76
+ * If the value length exceeds the max character limit, the help text is announced (if help text is present).
77
+ * If the value length does not exceed the max character limit, then the character limit announcement is announced.
78
+ */
79
+ announceCharacterLimitWarning() {
80
+ this.characterLimitAriaLiveAnnouncer = '';
81
+ if (!this.maxCharacterLimit || this.value.length === 0) {
82
+ return;
83
+ }
84
+ if (this.helpText && this.value.length > this.maxCharacterLimit) {
85
+ this.updateComplete
86
+ .then(() => {
87
+ this.characterLimitAriaLiveAnnouncer = this.helpText;
88
+ })
89
+ .catch(() => { });
90
+ }
91
+ else if (this.characterLimitAnnouncement && this.value.length <= this.maxCharacterLimit) {
92
+ this.characterLimitAriaLiveAnnouncer = this.characterLimitAnnouncement
93
+ .replace('%{number-of-characters}', this.value.length.toString())
94
+ .replace('%{max-character-limit}', this.maxCharacterLimit.toString());
95
+ }
96
+ }
97
+ /**
98
+ * Renders the character counter element.
99
+ */
100
+ renderCharacterCounter() {
101
+ if (!this.maxCharacterLimit) {
102
+ return nothing;
103
+ }
104
+ return html `
105
+ <mdc-text part="character-counter" tagname="span" type="body-midsize-regular">
106
+ ${this.value.length}/${this.maxCharacterLimit}
107
+ </mdc-text>
108
+ `;
109
+ }
110
+ }
111
+ __decorate([
112
+ property({ type: Number, attribute: 'max-character-limit' }),
113
+ __metadata("design:type", Number)
114
+ ], InnerMixinClass.prototype, "maxCharacterLimit", void 0);
115
+ __decorate([
116
+ property({ type: String, attribute: 'character-limit-announcement' }),
117
+ __metadata("design:type", String)
118
+ ], InnerMixinClass.prototype, "characterLimitAnnouncement", void 0);
119
+ __decorate([
120
+ state(),
121
+ __metadata("design:type", String)
122
+ ], InnerMixinClass.prototype, "characterLimitAriaLiveAnnouncer", void 0);
123
+ return InnerMixinClass;
124
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@momentum-design/components",
3
3
  "packageManager": "yarn@3.2.4",
4
- "version": "0.134.0",
4
+ "version": "0.134.1",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",
7
7
  "npm": ">=8.0.0"