const textField = document.createElement('template') textField.innerHTML = `
Edit Save
` customElements.define('text-field', class extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).append(textField.content.cloneNode(true)) this.textField = this.shadowRoot.querySelector('.text-field') this.textContainer = this.textField.children[0] this.iconsContainer = this.textField.children[1] this.editButton = this.textField.querySelector('.edit-button') this.saveButton = this.textField.querySelector('.save-button') this.isTextEditable = false this.isDisabled = false } static get observedAttributes() { return ['disable'] } get value() { return this.text } set value(val) { this.text = val this.textContainer.textContent = val this.setAttribute('value', val) } set disabled(val) { this.isDisabled = val if (this.isDisabled) this.setAttribute('disable', '') else this.removeAttribute('disable') } fireEvent = (value) => { let event = new CustomEvent('contentchanged', { bubbles: true, cancelable: true, composed: true, detail: { value } }); this.dispatchEvent(event); } setEditable = () => { if (this.isTextEditable) return this.textContainer.contentEditable = true this.textContainer.classList.add('editable') this.textContainer.focus() document.execCommand('selectAll', false, null); this.editButton.animate(this.rotateOut, this.animOptions).onfinish = () => { this.editButton.classList.add('hide') } setTimeout(() => { this.saveButton.classList.remove('hide') this.saveButton.animate(this.rotateIn, this.animOptions) }, 100); this.isTextEditable = true } setNonEditable = () => { if (!this.isTextEditable) return this.textContainer.contentEditable = false this.textContainer.classList.remove('editable') if (this.text !== this.textContainer.textContent.trim()) { this.setAttribute('value', this.textContainer.textContent) this.text = this.textContainer.textContent.trim() this.fireEvent(this.text) } this.saveButton.animate(this.rotateOut, this.animOptions).onfinish = () => { this.saveButton.classList.add('hide') } setTimeout(() => { this.editButton.classList.remove('hide') this.editButton.animate(this.rotateIn, this.animOptions) }, 100); this.isTextEditable = false } revert = () => { if (this.textContainer.isContentEditable) { this.value = this.text this.setNonEditable() } } connectedCallback() { this.text if (this.hasAttribute('value')) { this.text = this.getAttribute('value') this.textContainer.textContent = this.text } if (this.hasAttribute('disable')) this.isDisabled = true else this.isDisabled = false this.rotateOut = [ { transform: 'rotate(0)', opacity: 1 }, { transform: 'rotate(90deg)', opacity: 0 }, ] this.rotateIn = [ { transform: 'rotate(-90deg)', opacity: 0 }, { transform: 'rotate(0)', opacity: 1 }, ] this.animOptions = { duration: 300, easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', fill: 'forwards' } if (!this.isDisabled) { this.iconsContainer.classList.remove('hide') this.textContainer.addEventListener('dblclick', this.setEditable) this.editButton.addEventListener('click', this.setEditable) this.saveButton.addEventListener('click', this.setNonEditable) } } attributeChangedCallback(name) { if (name === 'disable') { if (this.hasAttribute('disable')) { this.iconsContainer.classList.add('hide') this.textContainer.removeEventListener('dblclick', this.setEditable) this.editButton.removeEventListener('click', this.setEditable) this.saveButton.removeEventListener('click', this.setNonEditable) this.revert() } else { this.iconsContainer.classList.remove('hide') this.textContainer.addEventListener('dblclick', this.setEditable) this.editButton.addEventListener('click', this.setEditable) this.saveButton.addEventListener('click', this.setNonEditable) } } } disconnectedCallback() { this.textContainer.removeEventListener('dblclick', this.setEditable) this.editButton.removeEventListener('click', this.setEditable) this.saveButton.removeEventListener('click', this.setNonEditable) } })