Skip to content

Commit

Permalink
refactor: extract message-input logic into reusable mixin (#8249)
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan authored Nov 29, 2024
1 parent 86e36af commit eae2558
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 205 deletions.
1 change: 1 addition & 0 deletions packages/message-input/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"polymer"
],
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@polymer/polymer": "^3.0.0",
"@vaadin/button": "24.6.0-beta1",
"@vaadin/component-base": "24.6.0-beta1",
Expand Down
47 changes: 47 additions & 0 deletions packages/message-input/src/vaadin-message-input-mixin.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @license
* Copyright (c) 2021 - 2024 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import type { Constructor } from '@open-wc/dedupe-mixin';
import type { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js';

export interface MessageInputI18n {
send: string;
message: string;
}

export declare function MessageInputMixin<T extends Constructor<HTMLElement>>(
base: T,
): Constructor<ControllerMixinClass> & Constructor<MessageInputMixinClass> & T;

export declare class MessageInputMixinClass {
/**
* Current content of the text input field
*/
value: string | null | undefined;

/**
* The object used to localize this component.
* For changing the default localization, change the entire
* `i18n` object.
*
* The object has the following JSON structure and default values:
*
* ```
* {
* // Used as the button label
* send: 'Send',
*
* // Used as the input field's placeholder and aria-label
* message: 'Message'
* }
* ```
*/
i18n: MessageInputI18n;

/**
* Set to true to disable this element.
*/
disabled: boolean;
}
180 changes: 180 additions & 0 deletions packages/message-input/src/vaadin-message-input-mixin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* @license
* Copyright (c) 2021 - 2024 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';

/**
* @polymerMixin
* @mixes ControllerMixin
*/
export const MessageInputMixin = (superClass) =>
class MessageInputMixinClass extends ControllerMixin(superClass) {
static get properties() {
return {
/**
* Current content of the text input field
*/
value: {
type: String,
value: '',
},

/**
* The object used to localize this component.
* For changing the default localization, change the entire
* `i18n` object.
*
* The object has the following JSON structure and default values:
*
* ```
* {
* // Used as the button label
* send: 'Send',
*
* // Used as the input field's placeholder and aria-label
* message: 'Message'
* }
* ```
*
* @type {!MessageInputI18n}
* @default {English}
*/
i18n: {
type: Object,
value: () => ({
send: 'Send',
message: 'Message',
}),
},

/**
* Set to true to disable this element.
* @type {boolean}
*/
disabled: {
type: Boolean,
value: false,
reflectToAttribute: true,
},

/** @private */
_button: {
type: Object,
},

/** @private */
_textArea: {
type: Object,
},
};
}

static get observers() {
return [
'__buttonPropsChanged(_button, disabled, i18n)',
'__textAreaPropsChanged(_textArea, disabled, i18n, value)',
];
}

/** @protected */
ready() {
super.ready();

this._buttonController = new SlotController(this, 'button', 'vaadin-button', {
initializer: (btn) => {
btn.setAttribute('theme', 'primary contained');

btn.addEventListener('click', () => {
this.__submit();
});

this._button = btn;
},
});
this.addController(this._buttonController);

this._textAreaController = new SlotController(this, 'textarea', 'vaadin-text-area', {
initializer: (textarea) => {
textarea.addEventListener('value-changed', (event) => {
this.value = event.detail.value;
});

textarea.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
event.stopImmediatePropagation();
this.__submit();
}
});

const input = textarea.inputElement;
input.removeAttribute('aria-labelledby');

// Set initial height to one row
input.setAttribute('rows', 1);
input.style.minHeight = '0';

this._textArea = textarea;
},
});
this.addController(this._textAreaController);

this._tooltipController = new TooltipController(this);
this.addController(this._tooltipController);
}

focus() {
if (this._textArea) {
this._textArea.focus();
}
}

/** @private */
__buttonPropsChanged(button, disabled, i18n) {
if (button) {
button.disabled = disabled;
button.textContent = i18n.send;
}
}

/** @private */
__textAreaPropsChanged(textArea, disabled, i18n, value) {
if (textArea) {
textArea.disabled = disabled;
textArea.value = value;

const message = i18n.message;
textArea.placeholder = message;

if (message) {
textArea.inputElement.setAttribute('aria-label', message);
} else {
textArea.inputElement.removeAttribute('aria-label');
}
}
}

/**
* Submits the current value as an custom event named 'submit'.
* It also clears the text input and refocuses it for sending another message.
* In UI, can be triggered by pressing the submit button or pressing enter key when field is focused.
* It does not submit anything if text is empty.
*/
__submit() {
if (this.value !== '') {
this.dispatchEvent(new CustomEvent('submit', { detail: { value: this.value } }));
this.value = '';
}
this._textArea.focus();
}

/**
* Fired when a new message is submitted with `<vaadin-message-input>`, either
* by clicking the "send" button, or pressing the Enter key.
* @event submit
*/
};
38 changes: 3 additions & 35 deletions packages/message-input/src/vaadin-message-input.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
* Copyright (c) 2021 - 2024 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
import { MessageInputMixin } from './vaadin-message-input-mixin.js';

export interface MessageInputI18n {
send: string;
message: string;
}
export { MessageInputI18n } from './vaadin-message-input-mixin.js';

/**
* Fired when a new message is submitted with `<vaadin-message-input>`, either
Expand All @@ -36,36 +33,7 @@ export type MessageInputEventMap = HTMLElementEventMap & MessageInputCustomEvent
* <vaadin-message-input></vaadin-message-input>
* ```
*/
declare class MessageInput extends ThemableMixin(ElementMixin(ControllerMixin(HTMLElement))) {
/**
* Current content of the text input field
*/
value: string | null | undefined;

/**
* The object used to localize this component.
* For changing the default localization, change the entire
* `i18n` object.
*
* The object has the following JSON structure and default values:
*
* ```
* {
* // Used as the button label
* send: 'Send',
*
* // Used as the input field's placeholder and aria-label
* message: 'Message'
* }
* ```
*/
i18n: MessageInputI18n;

/**
* Set to true to disable this element.
*/
disabled: boolean;

declare class MessageInput extends MessageInputMixin(ThemableMixin(ElementMixin(HTMLElement))) {
addEventListener<K extends keyof MessageInputEventMap>(
type: K,
listener: (this: MessageInput, ev: MessageInputEventMap[K]) => void,
Expand Down
Loading

0 comments on commit eae2558

Please sign in to comment.