Skip to content

Commit eae2558

Browse files
authored
refactor: extract message-input logic into reusable mixin (#8249)
1 parent 86e36af commit eae2558

File tree

5 files changed

+234
-205
lines changed

5 files changed

+234
-205
lines changed

packages/message-input/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"polymer"
3636
],
3737
"dependencies": {
38+
"@open-wc/dedupe-mixin": "^1.3.0",
3839
"@polymer/polymer": "^3.0.0",
3940
"@vaadin/button": "24.6.0-beta1",
4041
"@vaadin/component-base": "24.6.0-beta1",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2021 - 2024 Vaadin Ltd.
4+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5+
*/
6+
import type { Constructor } from '@open-wc/dedupe-mixin';
7+
import type { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js';
8+
9+
export interface MessageInputI18n {
10+
send: string;
11+
message: string;
12+
}
13+
14+
export declare function MessageInputMixin<T extends Constructor<HTMLElement>>(
15+
base: T,
16+
): Constructor<ControllerMixinClass> & Constructor<MessageInputMixinClass> & T;
17+
18+
export declare class MessageInputMixinClass {
19+
/**
20+
* Current content of the text input field
21+
*/
22+
value: string | null | undefined;
23+
24+
/**
25+
* The object used to localize this component.
26+
* For changing the default localization, change the entire
27+
* `i18n` object.
28+
*
29+
* The object has the following JSON structure and default values:
30+
*
31+
* ```
32+
* {
33+
* // Used as the button label
34+
* send: 'Send',
35+
*
36+
* // Used as the input field's placeholder and aria-label
37+
* message: 'Message'
38+
* }
39+
* ```
40+
*/
41+
i18n: MessageInputI18n;
42+
43+
/**
44+
* Set to true to disable this element.
45+
*/
46+
disabled: boolean;
47+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2021 - 2024 Vaadin Ltd.
4+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5+
*/
6+
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
7+
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
8+
import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
9+
10+
/**
11+
* @polymerMixin
12+
* @mixes ControllerMixin
13+
*/
14+
export const MessageInputMixin = (superClass) =>
15+
class MessageInputMixinClass extends ControllerMixin(superClass) {
16+
static get properties() {
17+
return {
18+
/**
19+
* Current content of the text input field
20+
*/
21+
value: {
22+
type: String,
23+
value: '',
24+
},
25+
26+
/**
27+
* The object used to localize this component.
28+
* For changing the default localization, change the entire
29+
* `i18n` object.
30+
*
31+
* The object has the following JSON structure and default values:
32+
*
33+
* ```
34+
* {
35+
* // Used as the button label
36+
* send: 'Send',
37+
*
38+
* // Used as the input field's placeholder and aria-label
39+
* message: 'Message'
40+
* }
41+
* ```
42+
*
43+
* @type {!MessageInputI18n}
44+
* @default {English}
45+
*/
46+
i18n: {
47+
type: Object,
48+
value: () => ({
49+
send: 'Send',
50+
message: 'Message',
51+
}),
52+
},
53+
54+
/**
55+
* Set to true to disable this element.
56+
* @type {boolean}
57+
*/
58+
disabled: {
59+
type: Boolean,
60+
value: false,
61+
reflectToAttribute: true,
62+
},
63+
64+
/** @private */
65+
_button: {
66+
type: Object,
67+
},
68+
69+
/** @private */
70+
_textArea: {
71+
type: Object,
72+
},
73+
};
74+
}
75+
76+
static get observers() {
77+
return [
78+
'__buttonPropsChanged(_button, disabled, i18n)',
79+
'__textAreaPropsChanged(_textArea, disabled, i18n, value)',
80+
];
81+
}
82+
83+
/** @protected */
84+
ready() {
85+
super.ready();
86+
87+
this._buttonController = new SlotController(this, 'button', 'vaadin-button', {
88+
initializer: (btn) => {
89+
btn.setAttribute('theme', 'primary contained');
90+
91+
btn.addEventListener('click', () => {
92+
this.__submit();
93+
});
94+
95+
this._button = btn;
96+
},
97+
});
98+
this.addController(this._buttonController);
99+
100+
this._textAreaController = new SlotController(this, 'textarea', 'vaadin-text-area', {
101+
initializer: (textarea) => {
102+
textarea.addEventListener('value-changed', (event) => {
103+
this.value = event.detail.value;
104+
});
105+
106+
textarea.addEventListener('keydown', (event) => {
107+
if (event.key === 'Enter' && !event.shiftKey) {
108+
event.preventDefault();
109+
event.stopImmediatePropagation();
110+
this.__submit();
111+
}
112+
});
113+
114+
const input = textarea.inputElement;
115+
input.removeAttribute('aria-labelledby');
116+
117+
// Set initial height to one row
118+
input.setAttribute('rows', 1);
119+
input.style.minHeight = '0';
120+
121+
this._textArea = textarea;
122+
},
123+
});
124+
this.addController(this._textAreaController);
125+
126+
this._tooltipController = new TooltipController(this);
127+
this.addController(this._tooltipController);
128+
}
129+
130+
focus() {
131+
if (this._textArea) {
132+
this._textArea.focus();
133+
}
134+
}
135+
136+
/** @private */
137+
__buttonPropsChanged(button, disabled, i18n) {
138+
if (button) {
139+
button.disabled = disabled;
140+
button.textContent = i18n.send;
141+
}
142+
}
143+
144+
/** @private */
145+
__textAreaPropsChanged(textArea, disabled, i18n, value) {
146+
if (textArea) {
147+
textArea.disabled = disabled;
148+
textArea.value = value;
149+
150+
const message = i18n.message;
151+
textArea.placeholder = message;
152+
153+
if (message) {
154+
textArea.inputElement.setAttribute('aria-label', message);
155+
} else {
156+
textArea.inputElement.removeAttribute('aria-label');
157+
}
158+
}
159+
}
160+
161+
/**
162+
* Submits the current value as an custom event named 'submit'.
163+
* It also clears the text input and refocuses it for sending another message.
164+
* In UI, can be triggered by pressing the submit button or pressing enter key when field is focused.
165+
* It does not submit anything if text is empty.
166+
*/
167+
__submit() {
168+
if (this.value !== '') {
169+
this.dispatchEvent(new CustomEvent('submit', { detail: { value: this.value } }));
170+
this.value = '';
171+
}
172+
this._textArea.focus();
173+
}
174+
175+
/**
176+
* Fired when a new message is submitted with `<vaadin-message-input>`, either
177+
* by clicking the "send" button, or pressing the Enter key.
178+
* @event submit
179+
*/
180+
};

packages/message-input/src/vaadin-message-input.d.ts

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@
33
* Copyright (c) 2021 - 2024 Vaadin Ltd.
44
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
55
*/
6-
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
76
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
87
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
8+
import { MessageInputMixin } from './vaadin-message-input-mixin.js';
99

10-
export interface MessageInputI18n {
11-
send: string;
12-
message: string;
13-
}
10+
export { MessageInputI18n } from './vaadin-message-input-mixin.js';
1411

1512
/**
1613
* Fired when a new message is submitted with `<vaadin-message-input>`, either
@@ -36,36 +33,7 @@ export type MessageInputEventMap = HTMLElementEventMap & MessageInputCustomEvent
3633
* <vaadin-message-input></vaadin-message-input>
3734
* ```
3835
*/
39-
declare class MessageInput extends ThemableMixin(ElementMixin(ControllerMixin(HTMLElement))) {
40-
/**
41-
* Current content of the text input field
42-
*/
43-
value: string | null | undefined;
44-
45-
/**
46-
* The object used to localize this component.
47-
* For changing the default localization, change the entire
48-
* `i18n` object.
49-
*
50-
* The object has the following JSON structure and default values:
51-
*
52-
* ```
53-
* {
54-
* // Used as the button label
55-
* send: 'Send',
56-
*
57-
* // Used as the input field's placeholder and aria-label
58-
* message: 'Message'
59-
* }
60-
* ```
61-
*/
62-
i18n: MessageInputI18n;
63-
64-
/**
65-
* Set to true to disable this element.
66-
*/
67-
disabled: boolean;
68-
36+
declare class MessageInput extends MessageInputMixin(ThemableMixin(ElementMixin(HTMLElement))) {
6937
addEventListener<K extends keyof MessageInputEventMap>(
7038
type: K,
7139
listener: (this: MessageInput, ev: MessageInputEventMap[K]) => void,

0 commit comments

Comments
 (0)