Initial
This commit is contained in:
111
src/view/TaskListComponent.ts
Normal file
111
src/view/TaskListComponent.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import Sortable from 'sortablejs';
|
||||
import type { Task } from '../types';
|
||||
|
||||
export interface TaskListCallbacks {
|
||||
onComplete: (index: number) => void;
|
||||
onEdit: (index: number) => void;
|
||||
onReorder: (oldIndex: number, newIndex: number) => void;
|
||||
onAdd: (text: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and manages the sortable task list DOM.
|
||||
*/
|
||||
export class TaskListComponent {
|
||||
private container: HTMLElement;
|
||||
private callbacks: TaskListCallbacks;
|
||||
private sortable: Sortable | null = null;
|
||||
|
||||
constructor(container: HTMLElement, callbacks: TaskListCallbacks) {
|
||||
this.container = container;
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
render(tasks: Task[]): void {
|
||||
this.destroy();
|
||||
this.container.empty();
|
||||
|
||||
// Add-task row — always first, outside the sortable list
|
||||
const addItem = this.container.createDiv({ cls: 'yaotp-task-item yaotp-add-task-item' });
|
||||
|
||||
const addHandle = addItem.createDiv({ cls: 'yaotp-drag-handle yaotp-drag-handle-disabled' });
|
||||
addHandle.innerHTML = '⋮';
|
||||
|
||||
addItem.createEl('input', {
|
||||
cls: 'yaotp-checkbox',
|
||||
type: 'checkbox',
|
||||
attr: { disabled: true, 'aria-hidden': 'true' },
|
||||
});
|
||||
|
||||
const input = addItem.createEl('input', {
|
||||
cls: 'yaotp-new-task-input',
|
||||
type: 'text',
|
||||
attr: { placeholder: 'Enter new task...' },
|
||||
}) as HTMLInputElement;
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
const text = input.value.trim();
|
||||
if (text) {
|
||||
this.callbacks.onAdd(text);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (tasks.length === 0) return;
|
||||
|
||||
const list = this.container.createEl('ul', { cls: 'yaotp-task-list' });
|
||||
|
||||
tasks.forEach((task, index) => {
|
||||
const item = list.createEl('li', { cls: 'yaotp-task-item' });
|
||||
if (task.completed) item.addClass('yaotp-task-completed');
|
||||
item.dataset.index = String(index);
|
||||
|
||||
// Drag handle
|
||||
const handle = item.createDiv({ cls: 'yaotp-drag-handle' });
|
||||
handle.innerHTML = '⋮'; // vertical ellipsis ⋮
|
||||
|
||||
// Checkbox
|
||||
const checkbox = item.createEl('input', {
|
||||
cls: 'yaotp-checkbox',
|
||||
type: 'checkbox',
|
||||
attr: { 'aria-label': 'Complete task' },
|
||||
}) as HTMLInputElement;
|
||||
checkbox.checked = task.completed;
|
||||
checkbox.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.callbacks.onComplete(index);
|
||||
});
|
||||
|
||||
// Task text (wraps)
|
||||
const textEl = item.createDiv({ cls: 'yaotp-task-text' });
|
||||
textEl.setText(task.text);
|
||||
if (task.notes.length > 0) {
|
||||
textEl.addClass('yaotp-task-has-notes');
|
||||
}
|
||||
textEl.addEventListener('click', () => {
|
||||
this.callbacks.onEdit(index);
|
||||
});
|
||||
});
|
||||
|
||||
this.sortable = Sortable.create(list, {
|
||||
handle: '.yaotp-drag-handle',
|
||||
animation: 150,
|
||||
delay: 300,
|
||||
delayOnTouchOnly: true,
|
||||
onEnd: (evt) => {
|
||||
const oldIndex = evt.oldIndex;
|
||||
const newIndex = evt.newIndex;
|
||||
if (oldIndex !== undefined && newIndex !== undefined && oldIndex !== newIndex) {
|
||||
this.callbacks.onReorder(oldIndex, newIndex);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (this.sortable) {
|
||||
this.sortable.destroy();
|
||||
this.sortable = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user