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 this.fireEvent = this.fireEvent.bind(this) this.setEditable = this.setEditable.bind(this) this.setNonEditable = this.setNonEditable.bind(this) this.revert = this.revert.bind(this) } static get observedAttributes(){ return ['disabled'] } 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('disabled', '') else this.removeAttribute('disabled') } fireEvent(value){ let event = new CustomEvent('change', { 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 === 'disabled') { if (this.hasAttribute('disabled')) { this.textContainer.removeEventListener('dblclick', this.setEditable) this.editButton.removeEventListener('click', this.setEditable) this.saveButton.removeEventListener('click', this.setNonEditable) this.revert() } else { 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) } })