@kelvininc/ui-components
Version:
Kelvin UI Components
313 lines (312 loc) • 10.6 kB
JavaScript
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
}];
}
}