1
0
mirror of https://github.com/morris/vanilla-todo.git synced 2025-08-20 04:41:26 +02:00
Files
vanilla-todo/public/scripts/TodoItem.js
2023-12-06 19:14:05 +01:00

161 lines
3.4 KiB
JavaScript

import { AppDraggable } from './AppDraggable.js';
import { AppIcon } from './AppIcon.js';
/**
* @param {HTMLElement} el
*/
export function TodoItem(el) {
let item;
let editing = false;
let startEditing = false;
let saveOnBlur = true;
el.innerHTML = /* html */ `
<div class="checkbox">
<input type="checkbox" aria-label="Done">
</div>
<p class="label"></p>
<p class="form">
<input type="text" class="input use-focus-other" aria-label="Label">
<button class="app-button save" title="Save">
<i class="app-icon" data-id="check-16"></i>
</button>
</p>
`;
const checkboxEl = el.querySelector('.checkbox');
const labelEl = el.querySelector('.label');
const inputEl = el.querySelector('.input');
const saveEl = el.querySelector('.save');
AppDraggable(el, {
dropSelector: '.todo-list > .items',
});
el.querySelectorAll('.app-icon').forEach(AppIcon);
checkboxEl.addEventListener(
'touchstart',
() => {
saveOnBlur = false;
},
{ passive: true },
);
checkboxEl.addEventListener('mousedown', () => {
saveOnBlur = false;
});
checkboxEl.addEventListener('click', () => {
if (editing) save();
el.dispatchEvent(
new CustomEvent('checkTodoItem', {
detail: {
...item,
done: !item.done,
},
bubbles: true,
}),
);
});
labelEl.addEventListener('click', () => {
startEditing = true;
editing = true;
update();
});
inputEl.addEventListener('keyup', (e) => {
switch (e.keyCode) {
case 13: // Enter
save();
break;
case 27: // Escape
cancelEdit();
break;
}
});
inputEl.addEventListener('blur', () => {
if (saveOnBlur) save();
saveOnBlur = true;
});
inputEl.addEventListener('focusOther', () => {
if (editing) save();
});
saveEl.addEventListener('mousedown', () => {
saveOnBlur = false;
});
saveEl.addEventListener('click', save);
el.addEventListener('draggableStart', (e) => {
e.detail.data.item = item;
e.detail.data.key = item.id;
});
el.addEventListener('todoItem', (e) => {
item = e.detail;
update();
});
function save() {
const label = inputEl.value.trim();
if (label === '') {
// Deferred deletion prevents a bug at reconciliation in TodoList:
// Failed to execute 'removeChild' on 'Node': The node to be removed is
// no longer a child of this node. Perhaps it was moved in a 'blur'
// event handler?
requestAnimationFrame(() => {
el.dispatchEvent(
new CustomEvent('deleteTodoItem', {
detail: item,
bubbles: true,
}),
);
});
return;
}
el.dispatchEvent(
new CustomEvent('editTodoItem', {
detail: {
...item,
label,
},
bubbles: true,
}),
);
editing = false;
update();
}
function cancelEdit() {
saveOnBlur = false;
editing = false;
update();
}
function update() {
el.classList.toggle('-done', item.done);
checkboxEl.querySelector('input').checked = item.done;
labelEl.innerText = item.label;
el.classList.toggle('-editing', editing);
el.classList.toggle('_nodrag', editing);
if (editing && startEditing) {
inputEl.value = item.label;
inputEl.focus();
inputEl.select();
startEditing = false;
}
}
}