UNPKG

@kelvininc/ui-components

Version:
313 lines (312 loc) 10.6 kB
import { Host, h, Fragment } from "@stencil/core"; import { EActionButtonType } from "../action-button/action-button.types"; import { EIconName } from "../icon/icon.types"; import { DEFAULT_MAX_LENGTH, DELAYED_BLUR_MS } from "./inline-editable-field.config"; export class KvInlineEditableField { constructor() { /** * The maximum length of the editable field. */ this.maxLength = DEFAULT_MAX_LENGTH; this.isHovering = false; this.isEditing = false; this.isSaving = false; this.isSaveDisabled = false; this.discardContent = () => { this.slotEl.innerText = this.value; }; this.saveChanges = () => { this.slotEl.blur(); const newValue = this.slotEl.innerText; if (newValue.length === 0) return; if (newValue !== this.value) { this.isSaving = true; this.contentEdited.emit(newValue); } }; this.handleBlur = () => { this.slotEl.innerText = this.slotEl.innerText.trim(); this.timeoutID = window.setTimeout(() => { // if saving button hasn't clicked, discard content if (!this.isSaving) { this.discardContent(); } this.isEditing = false; this.isSaving = false; this.contentBlured.emit(); }, DELAYED_BLUR_MS); }; this.handleFocus = () => { clearTimeout(this.timeoutID); this.isEditing = true; this.contentFocused.emit(); this.checkSaveBtnDisabled(); }; this.handleMaxLength = (event) => { if (this.maxLength && this.slotEl.innerText.length >= this.maxLength) { event.preventDefault(); } }; this.checkSaveBtnDisabled = () => { this.isSaveDisabled = this.slotEl.innerText.trim().length === 0; }; } /** * Resets the content of the editable field. */ async resetContent() { this.discardContent(); this.slotEl.blur(); } handleMouseHover() { this.isHovering = true; } handleMouseLeave() { this.isHovering = false; } handleKeyDown(event) { if (!this.isEditing || this.disabled) return; switch (event.key) { case 'Escape': this.slotEl.blur(); break; case 'Enter': this.saveChanges(); break; case 'Backspace': break; default: this.handleMaxLength(event); } } handleDisableChange(state) { if (state) { this.destroyEditableContent(); } else { this.initializeEditableContent(); } } initializeEditableContent() { this.slotEl.classList.add('inline-editable-field-slot'); this.slotEl.setAttribute('contenteditable', 'true'); this.slotEl.addEventListener('blur', this.handleBlur); this.slotEl.addEventListener('focus', this.handleFocus); this.slotEl.addEventListener('input', this.checkSaveBtnDisabled); } destroyEditableContent() { if (!this.slotEl) return; this.slotEl.classList.remove('inline-editable-field-slot'); this.slotEl.removeAttribute('contenteditable'); this.slotEl.removeEventListener('blur', this.handleBlur); this.slotEl.removeEventListener('focus', this.handleFocus); this.slotEl.removeEventListener('input', this.checkSaveBtnDisabled); } connectedCallback() { if (this.disabled) { return; } if (this.el.children.length !== 1) { throw new Error('Inline editable field must have exactly one child element to be editable'); } this.slotEl = this.el.children[0]; this.initializeEditableContent(); } disconnectedCallback() { this.destroyEditableContent(); } render() { if (this.disabled) { return (h(Host, null, h("slot", null))); } return (h(Host, { class: { 'inline-editable-field-container': true, 'inline-editable-field-container__hover': this.isHovering } }, h("slot", null), h("div", { class: { 'inline-editable-field-actions': true, 'inline-editable-field-actions__focus': this.isEditing } }, this.isEditing && (h(Fragment, null, h("kv-action-button-icon", { type: EActionButtonType.Ghost, icon: EIconName.Close, onClickButton: this.discardContent }), h("kv-action-button-icon", { type: EActionButtonType.Ghost, icon: EIconName.DoneAll, disabled: this.isSaveDisabled, onClickButton: this.saveChanges })))))); } static get is() { return "kv-inline-editable-field"; } static get originalStyleUrls() { return { "$": ["inline-editable-field.scss"] }; } static get styleUrls() { return { "$": ["inline-editable-field.css"] }; } static get properties() { return { "value": { "type": "string", "attribute": "value", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The value of the field." }, "getter": false, "setter": false, "reflect": true }, "disabled": { "type": "boolean", "attribute": "disabled", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Indicates whether the editable field is disabled." }, "getter": false, "setter": false, "reflect": true }, "maxLength": { "type": "number", "attribute": "max-length", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The maximum length of the editable field." }, "getter": false, "setter": false, "reflect": true, "defaultValue": "DEFAULT_MAX_LENGTH" } }; } static get states() { return { "isHovering": {}, "isEditing": {}, "isSaving": {}, "isSaveDisabled": {}, "timeoutID": {} }; } static get events() { return [{ "method": "contentEdited", "name": "contentEdited", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when the content is edited." }, "complexType": { "original": "string", "resolved": "string", "references": {} } }, { "method": "contentFocused", "name": "contentFocused", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when the content is focused." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "contentBlured", "name": "contentBlured", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when the content is blurred." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }]; } static get methods() { return { "resetContent": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global", "id": "global::Promise" } }, "return": "Promise<void>" }, "docs": { "text": "Resets the content of the editable field.", "tags": [] } } }; } static get elementRef() { return "el"; } static get watchers() { return [{ "propName": "disabled", "methodName": "handleDisableChange" }]; } static get listeners() { return [{ "name": "mouseenter", "method": "handleMouseHover", "target": undefined, "capture": false, "passive": true }, { "name": "mouseleave", "method": "handleMouseLeave", "target": undefined, "capture": false, "passive": true }, { "name": "keydown", "method": "handleKeyDown", "target": "document", "capture": false, "passive": false }]; } }