<script setup lang="ts">
-import { ref } from 'vue/vapor'
-
+import { computed } from 'vue/vapor'
+import { useLocalStorage } from '@vueuse/core'
interface Task {
title: string
completed: boolean
}
-const tasks = ref<Task[]>([])
-const value = ref('hello')
+
+const tasks = useLocalStorage<Task[]>('tasks', [])
+const value = useLocalStorage('value', '')
+
+const remaining = computed(() => {
+ return tasks.value.filter(task => !task.completed).length
+})
function handleAdd() {
tasks.value.push({
// TODO: clear input
value.value = ''
}
+
+function handleComplete(index: number, evt: Event) {
+ tasks.value[index].completed = (evt.target as HTMLInputElement).checked
+}
+
+function handleClearComplete() {
+ tasks.value = tasks.value.filter(task => !task.completed)
+}
+
+function handleClearAll() {
+ tasks.value = []
+}
</script>
<template>
+ <h1>todos</h1>
<ul>
<!-- TODO: v-for -->
- <li>
- <!-- TODO checked=false -->
- <input type="checkbox" :checked="tasks[0]?.completed" />
+ <li v-show="tasks[0]" :class="{ del: tasks[0]?.completed }">
+ <input
+ type="checkbox"
+ :checked="tasks[0]?.completed"
+ @change="handleComplete(0, $event)"
+ />
{{ tasks[0]?.title }}
</li>
- <li>
- <input type="checkbox" :checked="tasks[1]?.completed" />
+ <li v-show="tasks[1]" :class="{ del: tasks[1]?.completed }">
+ <input
+ type="checkbox"
+ :checked="tasks[1]?.completed"
+ @change="handleComplete(1, $event)"
+ />
{{ tasks[1]?.title }}
</li>
- <li>
- <input type="checkbox" :checked="tasks[2]?.completed" />
+ <li v-show="tasks[2]" :class="{ del: tasks[2]?.completed }">
+ <input
+ type="checkbox"
+ :checked="tasks[2]?.completed"
+ @change="handleComplete(2, $event)"
+ />
{{ tasks[2]?.title }}
</li>
- <li>
- <input type="checkbox" :checked="tasks[3]?.completed" />
+ <li v-show="tasks[3]" :class="{ del: tasks[3]?.completed }">
+ <input
+ type="checkbox"
+ :checked="tasks[3]?.completed"
+ @change="handleComplete(3, $event)"
+ />
{{ tasks[3]?.title }}
</li>
- <li>
- <input type="text" v-model="value" />
- <button @click="handleAdd">Add</button>
- </li>
</ul>
+ <p>
+ {{ remaining }} item{{ remaining !== 1 ? 's' : '' }} left /
+ {{ tasks.length }} item{{ tasks.length !== 1 ? 's' : '' }} in total
+ </p>
+ <div style="display: flex; gap: 8px">
+ <input
+ type="text"
+ v-model="value"
+ @keydown.enter="handleAdd"
+ placeholder="What need to be done?"
+ />
+ <button @click="handleAdd">Add</button>
+ <button @click="handleClearComplete">Clear completed</button>
+ <button @click="handleClearAll">Clear all</button>
+ </div>
</template>
+
+<style>
+.del {
+ text-decoration: line-through;
+}
+</style>
playground:
dependencies:
+ '@vueuse/core':
+ specifier: ^10.7.2
+ version: 10.7.2(vue@packages+vue)
vue:
specifier: workspace:*
version: link:../packages/vue
resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==}
dev: true
+ /@types/web-bluetooth@0.0.20:
+ resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
+ dev: false
+
/@types/yauzl@2.10.2:
resolution: {integrity: sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==}
requiresBuild: true
resolution: {integrity: sha512-BK9D7AgpYAWVrtd7Kkc3CotU/ox8l+mPjsLgK16ZP+Ldj8jXPrJtzYQ2rTQNRJOxVSVx5acftDTLDLENFhQdDw==}
dev: false
+ /@vueuse/core@10.7.2(vue@packages+vue):
+ resolution: {integrity: sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==}
+ dependencies:
+ '@types/web-bluetooth': 0.0.20
+ '@vueuse/metadata': 10.7.2
+ '@vueuse/shared': 10.7.2(vue@packages+vue)
+ vue-demi: 0.14.6(vue@packages+vue)
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - vue
+ dev: false
+
+ /@vueuse/metadata@10.7.2:
+ resolution: {integrity: sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==}
+ dev: false
+
+ /@vueuse/shared@10.7.2(vue@packages+vue):
+ resolution: {integrity: sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==}
+ dependencies:
+ vue-demi: 0.14.6(vue@packages+vue)
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - vue
+ dev: false
+
/@zeit/schemas@2.29.0:
resolution: {integrity: sha512-g5QiLIfbg3pLuYUJPlisNKY+epQJTcMDsOnVNkscrDP1oi7vmJnzOANYJI/1pZcVJ6umUkBv3aFtlg1UvUHGzA==}
dev: true
engines: {node: '>=0.10.0'}
dev: true
+ /vue-demi@0.14.6(vue@packages+vue):
+ resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==}
+ engines: {node: '>=12'}
+ hasBin: true
+ requiresBuild: true
+ peerDependencies:
+ '@vue/composition-api': ^1.0.0-rc.1
+ vue: ^3.0.0-0 || ^2.6.0
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+ dependencies:
+ vue: link:packages/vue
+ dev: false
+
/w3c-xmlserializer@5.0.0:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}