This time I had an idea. What if instead of a new tag bringing in a totally different experience, I leveraged haxHooks to allow for modification of the element in context via contenteditable.
How this works in the video in task-list:
- listen for activeElement state change in hax
- set internal editMode state to match
- on enable, set a mutation observer and apply contenteditable to internal shadowRoot elements
- make changes by typing
- on activeElement disable state, turn off content editable, remove mutationObserver, and look at the shadowRoot and update internal state values to match.
Here's the relevant pieces of code from the video:
render() { return html` <div id="wrapper"> <h3 property="oer:name" ?contenteditable="${this.editMode}">${this.name}</h3> <ol ?contenteditable="${this.editMode}"> ${this.tasks.map( (task) => html` <li> ${task.link ? html` <a href="${task.link}" property="oer:task">${task.name}</a> ` : html` <span property="oer:task">${task.name}</span> `} </li> ` )} </ol> </div> `; } /** * Implements haxHooks to tie into life-cycle if hax exists. */ haxHooks() { return { activeElementChanged: "haxactiveElementChanged", inlineContextMenu: "haxinlineContextMenu", }; } /** * double-check that we are set to inactivate click handlers * this is for when activated in a duplicate / adding new content state */ haxactiveElementChanged(el, val) { if (this.__thereAreChanges) { this.alignState(); } this.editMode = val; } haxinlineContextMenu(ceMenu) { ceMenu.ceButtons = [ { icon: "icons:add", callback: "haxClickInlineAdd", label: "Add task", }, { icon: "icons:remove", callback: "haxClickInlineRemove", label: "Remove task", }, ]; } haxClickInlineAdd(e) { let d = this.tasks; d.push({ name: "Do this" }); this.tasks = [...d]; return true; } haxClickInlineRemove(e) { if (this.tasks.length > 0) { let d = this.tasks; d.pop(); this.tasks = [...d]; return true; } } static get tag() { return "task-list"; } alignState() { this.name = this.shadowRoot.querySelector('h3').innerText; this.__thereAreChanges = false; } updated(changedProperties) { changedProperties.forEach((oldValue, propName) => { if (propName === 'editMode') { if (!this[propName]) { if (this._observer) { this._observer.disconnect(); } } else { this._observer = new MutationObserver((mutations) => { this.__thereAreChanges = true; }); this._observer.observe(this.shadowRoot.querySelector('#wrapper'), { childList: true, subtree: true, characterData: true, }); } } }); } Next I'm going to use this state change to update the array of values in the task list, a more complex testing and iteration activity than I wanted to do via live coding :)
code repo: https://github.com/elmsln/lrnwebcomponents/blob/master/elements/task-list/src/task-list.js
will be in the next release of task-list on npm later this month
Top comments (0)