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)