diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/.gitignore b/yongjae/projects/FEDC5-3_VanillaJS_1/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/index.html b/yongjae/projects/FEDC5-3_VanillaJS_1/index.html
new file mode 100644
index 0000000..3f32d0c
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ TS Todo
+
+
+
+
+
+
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/package-lock.json b/yongjae/projects/FEDC5-3_VanillaJS_1/package-lock.json
new file mode 100644
index 0000000..cffe8f2
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/package-lock.json
@@ -0,0 +1,732 @@
+{
+ "name": "fedc5-3-vanillajs-1",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "fedc5-3-vanillajs-1",
+ "version": "0.0.0",
+ "devDependencies": {
+ "typescript": "^5.2.2",
+ "vite": "^5.0.0"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.8.tgz",
+ "integrity": "sha512-31E2lxlGM1KEfivQl8Yf5aYU/mflz9g06H6S15ITUFQueMFtFjESRMoDSkvMo8thYvLBax+VKTPlpnx+sPicOA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.8.tgz",
+ "integrity": "sha512-B8JbS61bEunhfx8kasogFENgQfr/dIp+ggYXwTqdbMAgGDhRa3AaPpQMuQU0rNxDLECj6FhDzk1cF9WHMVwrtA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.8.tgz",
+ "integrity": "sha512-rdqqYfRIn4jWOp+lzQttYMa2Xar3OK9Yt2fhOhzFXqg0rVWEfSclJvZq5fZslnz6ypHvVf3CT7qyf0A5pM682A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.8.tgz",
+ "integrity": "sha512-RQw9DemMbIq35Bprbboyf8SmOr4UXsRVxJ97LgB55VKKeJOOdvsIPy0nFyF2l8U+h4PtBx/1kRf0BelOYCiQcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.8.tgz",
+ "integrity": "sha512-3sur80OT9YdeZwIVgERAysAbwncom7b4bCI2XKLjMfPymTud7e/oY4y+ci1XVp5TfQp/bppn7xLw1n/oSQY3/Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.8.tgz",
+ "integrity": "sha512-WAnPJSDattvS/XtPCTj1tPoTxERjcTpH6HsMr6ujTT+X6rylVe8ggxk8pVxzf5U1wh5sPODpawNicF5ta/9Tmw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.8.tgz",
+ "integrity": "sha512-ICvZyOplIjmmhjd6mxi+zxSdpPTKFfyPPQMQTK/w+8eNK6WV01AjIztJALDtwNNfFhfZLux0tZLC+U9nSyA5Zg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.8.tgz",
+ "integrity": "sha512-H4vmI5PYqSvosPaTJuEppU9oz1dq2A7Mr2vyg5TF9Ga+3+MGgBdGzcyBP7qK9MrwFQZlvNyJrvz6GuCaj3OukQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.8.tgz",
+ "integrity": "sha512-z1zMZivxDLHWnyGOctT9JP70h0beY54xDDDJt4VpTX+iwA77IFsE1vCXWmprajJGa+ZYSqkSbRQ4eyLCpCmiCQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.8.tgz",
+ "integrity": "sha512-1a8suQiFJmZz1khm/rDglOc8lavtzEMRo0v6WhPgxkrjcU0LkHj+TwBrALwoz/OtMExvsqbbMI0ChyelKabSvQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.8.tgz",
+ "integrity": "sha512-fHZWS2JJxnXt1uYJsDv9+b60WCc2RlvVAy1F76qOLtXRO+H4mjt3Tr6MJ5l7Q78X8KgCFudnTuiQRBhULUyBKQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.8.tgz",
+ "integrity": "sha512-Wy/z0EL5qZYLX66dVnEg9riiwls5IYnziwuju2oUiuxVc+/edvqXa04qNtbrs0Ukatg5HEzqT94Zs7J207dN5Q==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.8.tgz",
+ "integrity": "sha512-ETaW6245wK23YIEufhMQ3HSeHO7NgsLx8gygBVldRHKhOlD1oNeNy/P67mIh1zPn2Hr2HLieQrt6tWrVwuqrxg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.8.tgz",
+ "integrity": "sha512-T2DRQk55SgoleTP+DtPlMrxi/5r9AeFgkhkZ/B0ap99zmxtxdOixOMI570VjdRCs9pE4Wdkz7JYrsPvsl7eESg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.8.tgz",
+ "integrity": "sha512-NPxbdmmo3Bk7mbNeHmcCd7R7fptJaczPYBaELk6NcXxy7HLNyWwCyDJ/Xx+/YcNH7Im5dHdx9gZ5xIwyliQCbg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.8.tgz",
+ "integrity": "sha512-lytMAVOM3b1gPypL2TRmZ5rnXl7+6IIk8uB3eLsV1JwcizuolblXRrc5ShPrO9ls/b+RTp+E6gbsuLWHWi2zGg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.8.tgz",
+ "integrity": "sha512-hvWVo2VsXz/8NVt1UhLzxwAfo5sioj92uo0bCfLibB0xlOmimU/DeAEsQILlBQvkhrGjamP0/el5HU76HAitGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.8.tgz",
+ "integrity": "sha512-/7Y7u77rdvmGTxR83PgaSvSBJCC2L3Kb1M/+dmSIvRvQPXXCuC97QAwMugBNG0yGcbEGfFBH7ojPzAOxfGNkwQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.8.tgz",
+ "integrity": "sha512-9Lc4s7Oi98GqFA4HzA/W2JHIYfnXbUYgekUP/Sm4BG9sfLjyv6GKKHKKVs83SMicBF2JwAX6A1PuOLMqpD001w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.8.tgz",
+ "integrity": "sha512-rq6WzBGjSzihI9deW3fC2Gqiak68+b7qo5/3kmB6Gvbh/NYPA0sJhrnp7wgV4bNwjqM+R2AApXGxMO7ZoGhIJg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.8.tgz",
+ "integrity": "sha512-AIAbverbg5jMvJznYiGhrd3sumfwWs8572mIJL5NQjJa06P8KfCPWZQ0NwZbPQnbQi9OWSZhFVSUWjjIrn4hSw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.8.tgz",
+ "integrity": "sha512-bfZ0cQ1uZs2PqpulNL5j/3w+GDhP36k1K5c38QdQg+Swy51jFZWWeIkteNsufkQxp986wnqRRsb/bHbY1WQ7TA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.6.1.tgz",
+ "integrity": "sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.6.1.tgz",
+ "integrity": "sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.6.1.tgz",
+ "integrity": "sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.6.1.tgz",
+ "integrity": "sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.6.1.tgz",
+ "integrity": "sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.6.1.tgz",
+ "integrity": "sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.6.1.tgz",
+ "integrity": "sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz",
+ "integrity": "sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.6.1.tgz",
+ "integrity": "sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.6.1.tgz",
+ "integrity": "sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.6.1.tgz",
+ "integrity": "sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.6.1.tgz",
+ "integrity": "sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/esbuild": {
+ "version": "0.19.8",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.8.tgz",
+ "integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.19.8",
+ "@esbuild/android-arm64": "0.19.8",
+ "@esbuild/android-x64": "0.19.8",
+ "@esbuild/darwin-arm64": "0.19.8",
+ "@esbuild/darwin-x64": "0.19.8",
+ "@esbuild/freebsd-arm64": "0.19.8",
+ "@esbuild/freebsd-x64": "0.19.8",
+ "@esbuild/linux-arm": "0.19.8",
+ "@esbuild/linux-arm64": "0.19.8",
+ "@esbuild/linux-ia32": "0.19.8",
+ "@esbuild/linux-loong64": "0.19.8",
+ "@esbuild/linux-mips64el": "0.19.8",
+ "@esbuild/linux-ppc64": "0.19.8",
+ "@esbuild/linux-riscv64": "0.19.8",
+ "@esbuild/linux-s390x": "0.19.8",
+ "@esbuild/linux-x64": "0.19.8",
+ "@esbuild/netbsd-x64": "0.19.8",
+ "@esbuild/openbsd-x64": "0.19.8",
+ "@esbuild/sunos-x64": "0.19.8",
+ "@esbuild/win32-arm64": "0.19.8",
+ "@esbuild/win32-ia32": "0.19.8",
+ "@esbuild/win32-x64": "0.19.8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "node_modules/postcss": {
+ "version": "8.4.32",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
+ "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.6.1.tgz",
+ "integrity": "sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.6.1",
+ "@rollup/rollup-android-arm64": "4.6.1",
+ "@rollup/rollup-darwin-arm64": "4.6.1",
+ "@rollup/rollup-darwin-x64": "4.6.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.6.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.6.1",
+ "@rollup/rollup-linux-arm64-musl": "4.6.1",
+ "@rollup/rollup-linux-x64-gnu": "4.6.1",
+ "@rollup/rollup-linux-x64-musl": "4.6.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.6.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.6.1",
+ "@rollup/rollup-win32-x64-msvc": "4.6.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
+ "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.6.tgz",
+ "integrity": "sha512-MD3joyAEBtV7QZPl2JVVUai6zHms3YOmLR+BpMzLlX2Yzjfcc4gTgNi09d/Rua3F4EtC8zdwPU8eQYyib4vVMQ==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.19.3",
+ "postcss": "^8.4.32",
+ "rollup": "^4.2.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/package.json b/yongjae/projects/FEDC5-3_VanillaJS_1/package.json
new file mode 100644
index 0000000..d35041d
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "fedc5-3-vanillajs-1",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "typescript": "^5.2.2",
+ "vite": "^5.0.0"
+ }
+}
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/public/vite.svg b/yongjae/projects/FEDC5-3_VanillaJS_1/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/App.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/App.ts
new file mode 100644
index 0000000..9cf336a
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/App.ts
@@ -0,0 +1,64 @@
+import { AppProps } from "../types/components";
+import { Todos } from "../types/states";
+import { setItem } from "../utils/storage";
+import { validateState } from "../utils/validateState";
+import Header from "./Header";
+import TodoCount from "./TodoCount";
+import TodoForm from "./TodoForm";
+import TodoList from "./TodoList";
+
+interface AppContext {}
+const App = function (this: AppContext, { $target, initialState }: AppProps) {
+ // ...
+ initialState = validateState(initialState);
+
+ const syncState = (state: Todos) => {
+ const validatedState = validateState(state);
+ todoList.setState(validatedState);
+ todoCount.setState(validatedState);
+ };
+ new Header({
+ $target,
+ text: "Renewed Todo List",
+ });
+
+ new TodoForm({
+ $target,
+ onSubmit: (text: string) => {
+ const nextState = [
+ ...todoList.state,
+ { id: String(Date.now()), text, isCompleted: false },
+ ];
+
+ syncState(nextState);
+
+ setItem("todos", nextState);
+ },
+ });
+
+ const todoList = new TodoList({
+ $target,
+ initialState,
+ onToggle: (todoId: string) => {
+ const nextState = todoList.state.map((todo) => {
+ if (todo.id === todoId) todo.isCompleted = !todo.isCompleted;
+ return todo;
+ });
+ syncState(nextState);
+ },
+ onDelete: (todoId: string) => {
+ const nextState = todoList.state.filter((todo) => {
+ if (todo.id === todoId) return false;
+ return true;
+ });
+ syncState(nextState);
+ },
+ });
+
+ const todoCount = new TodoCount({
+ $target,
+ initialState,
+ });
+} as any as { new (props: AppProps): AppContext };
+
+export default App;
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/Header.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/Header.ts
new file mode 100644
index 0000000..d2f1b47
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/Header.ts
@@ -0,0 +1,21 @@
+import {
+ HeaderProps,
+ TodoComponentStatelessContext,
+} from "../types/components";
+
+const Header = function (
+ this: TodoComponentStatelessContext,
+ { $target, text }: HeaderProps
+) {
+ const $header = document.createElement("h1");
+
+ $target.appendChild($header);
+
+ this.render = () => {
+ $header.textContent = text;
+ };
+
+ this.render();
+} as any as { new (props: HeaderProps): TodoComponentStatelessContext };
+
+export default Header;
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/TodoCount.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/TodoCount.ts
new file mode 100644
index 0000000..8fd4d98
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/TodoCount.ts
@@ -0,0 +1,35 @@
+import {
+ TodoComponentStatefulContext,
+ TodoCountProps,
+} from "../types/components";
+import { Todos } from "../types/states";
+
+const TodoCount = function (
+ this: TodoComponentStatefulContext,
+ { $target, initialState }: TodoCountProps
+) {
+ this.state = initialState;
+
+ const $todoCount = document.createElement("h3");
+ $target.appendChild($todoCount);
+
+ this.setState = (nextState: Todos) => {
+ this.state = nextState;
+ this.render();
+ };
+
+ this.render = () => {
+ const completedNum = this.state.filter(
+ ({ isCompleted }) => isCompleted
+ ).length;
+
+ const totalNum = this.state.length;
+ $todoCount.innerHTML = `${completedNum} / ${totalNum}`;
+ };
+
+ this.render();
+} as any as {
+ new (props: TodoCountProps): TodoComponentStatefulContext;
+};
+
+export default TodoCount;
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/TodoForm.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/TodoForm.ts
new file mode 100644
index 0000000..bbaa05c
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/TodoForm.ts
@@ -0,0 +1,42 @@
+import {
+ TodoComponentStatelessContext,
+ TodoFormProps,
+} from "../types/components";
+
+const TodoForm = function (
+ this: TodoComponentStatelessContext,
+ { $target, onSubmit }: TodoFormProps
+) {
+ const $form = document.createElement("form");
+ let isInit = false;
+ $target.appendChild($form);
+
+ this.render = () => {
+ $form.innerHTML = `
+
+ `;
+ if (!isInit) {
+ $form.addEventListener("submit", (e) => {
+ e.preventDefault();
+
+ const $todo = $form.querySelector(`input[name=todo]`);
+
+ let text = "";
+ if ($todo && $todo.value) {
+ text = $todo.value;
+ }
+
+ if (text.length > 1 && text.trim()) {
+ if ($todo) {
+ $todo.value = "";
+ onSubmit(text);
+ }
+ }
+ });
+ }
+ };
+
+ this.render();
+} as any as { new (props: TodoFormProps): TodoComponentStatelessContext };
+
+export default TodoForm;
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/TodoItem.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/TodoItem.ts
new file mode 100644
index 0000000..0118e2f
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/TodoItem.ts
@@ -0,0 +1,32 @@
+import {
+ TodoComponentStatelessContext,
+ TodoItemProps,
+} from "../types/components";
+
+const TodoItem = function (
+ this: TodoComponentStatelessContext,
+ { $target, initialValue, onToggle, onDelete }: TodoItemProps
+) {
+ const { id, text, isCompleted } = initialValue;
+
+ const $todoItem = document.createElement("li");
+ $todoItem.textContent = text;
+ $todoItem.style.textDecoration = isCompleted ? "line-through" : "none";
+ const $todoButton = document.createElement("button");
+ $todoButton.textContent = "삭제";
+
+ $todoItem.appendChild($todoButton);
+ $target.appendChild($todoItem);
+
+ this.render = () => {
+ $todoButton.addEventListener("click", () => {
+ onDelete(id);
+ });
+ $todoItem.addEventListener("click", () => {
+ onToggle(id);
+ });
+ };
+ this.render();
+} as any as { new (props: TodoItemProps): TodoComponentStatelessContext };
+
+export default TodoItem;
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/TodoList.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/TodoList.ts
new file mode 100644
index 0000000..f0e833c
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/components/TodoList.ts
@@ -0,0 +1,39 @@
+import {
+ TodoComponentStatefulContext,
+ TodoListProps,
+} from "../types/components";
+import { Todos } from "../types/states";
+import { setItem } from "../utils/storage";
+import TodoItem from "./TodoItem";
+
+const TodoList = function (
+ this: TodoComponentStatefulContext,
+ { initialState, $target, onToggle, onDelete }: TodoListProps
+) {
+ this.state = initialState;
+
+ const $todoList = document.createElement("ul");
+ $target.appendChild($todoList);
+
+ this.setState = (nextState: Todos) => {
+ this.state = nextState;
+ setItem("todos", this.state);
+ this.render();
+ };
+
+ this.render = () => {
+ $todoList.innerHTML = "";
+ this.state.map(
+ (itemContext) =>
+ new TodoItem({
+ $target: $todoList,
+ initialValue: itemContext,
+ onToggle,
+ onDelete,
+ })
+ );
+ };
+ this.render();
+} as any as { new (props: TodoListProps): TodoComponentStatefulContext };
+
+export default TodoList;
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/constants.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/constants.ts
new file mode 100644
index 0000000..f9c8627
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/constants.ts
@@ -0,0 +1,14 @@
+export const STORAGE_KEY = "todos";
+
+const KNOWN_ERROR_MESSAGES = {
+ invalidState: "올바르지 않은 상태 형식입니다",
+};
+
+export const ERROR_MESSAGES = new Proxy(KNOWN_ERROR_MESSAGES, {
+ get: function (target: { [key: string]: string }, prop: string) {
+ if (prop.length < 1) {
+ return "알 수 없는 에러입니다.";
+ }
+ return target[prop] ?? prop;
+ },
+});
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/main.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/main.ts
new file mode 100644
index 0000000..75b21cd
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/main.ts
@@ -0,0 +1,11 @@
+import { getItem } from "./utils/storage";
+import App from "./components/App.ts";
+
+const initialState = getItem("todos", []);
+
+const $app = document.querySelector("#app")!;
+
+new App({
+ $target: $app,
+ initialState,
+});
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/types/components.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/types/components.ts
new file mode 100644
index 0000000..d4160e0
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/types/components.ts
@@ -0,0 +1,46 @@
+import { Todo, Todos } from "./states";
+// 컴포넌트 구조 typing
+// 컴포넌트 Context
+
+export type TodoComponentContext = {
+ state?: Todos;
+ setState?: (nextState: Todos) => void;
+ render: () => void;
+};
+export type TodoComponentStatefulContext = {
+ [K in keyof TodoComponentContext]-?: TodoComponentContext[K];
+};
+
+export type TodoComponentStatelessContext = Omit<
+ TodoComponentContext,
+ "state" | "setState"
+>;
+
+// 컴포넌트 Props
+export interface CoreComponentProps {
+ $target: HTMLElement;
+}
+
+interface TodoStatefulComponentProps extends CoreComponentProps {
+ initialState: Todos;
+}
+export interface TodoItemProps extends CoreComponentProps {
+ initialValue: Todo;
+ onToggle: (id: string) => void;
+ onDelete: (id: string) => void;
+}
+export interface TodoListProps extends TodoStatefulComponentProps {
+ onToggle: (id: string) => void;
+ onDelete: (id: string) => void;
+}
+
+export interface TodoCountProps extends TodoStatefulComponentProps {}
+
+export interface TodoFormProps extends CoreComponentProps {
+ onSubmit: (text: string) => void;
+}
+
+export interface AppProps extends TodoStatefulComponentProps {}
+export interface HeaderProps extends CoreComponentProps {
+ text: string;
+}
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/types/states.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/types/states.ts
new file mode 100644
index 0000000..f20587b
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/types/states.ts
@@ -0,0 +1,6 @@
+export interface Todo {
+ text: string;
+ id: string;
+ isCompleted: boolean;
+}
+export type Todos = Todo[];
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/typescript.svg b/yongjae/projects/FEDC5-3_VanillaJS_1/src/typescript.svg
new file mode 100644
index 0000000..d91c910
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/typescript.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/utils/storage.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/utils/storage.ts
new file mode 100644
index 0000000..3f77718
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/utils/storage.ts
@@ -0,0 +1,32 @@
+import { ERROR_MESSAGES } from "../constants";
+import { Todos } from "../types/states";
+
+export type setStorage = (key: string, value: T) => void;
+export type getStorage = (key: string, defaultValue: T) => T;
+
+const storage = window.localStorage;
+
+export const setItem: setStorage = (key: string, value: Todos) => {
+ try {
+ storage.setItem(key, JSON.stringify(value));
+ } catch (e: unknown) {
+ if (e instanceof Error) {
+ console.warn(ERROR_MESSAGES[e.message]);
+ }
+ }
+};
+
+export const getItem: getStorage = (key: string, defaultValue = []) => {
+ try {
+ const storedValue = storage.getItem(key);
+ if (storedValue) {
+ return JSON.parse(storedValue);
+ }
+ return defaultValue;
+ } catch (e: unknown) {
+ if (e instanceof Error) {
+ console.warn(ERROR_MESSAGES[e.message]);
+ return defaultValue;
+ }
+ }
+};
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/utils/validateState.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/utils/validateState.ts
new file mode 100644
index 0000000..48089c3
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/utils/validateState.ts
@@ -0,0 +1,45 @@
+import { ERROR_MESSAGES } from "../constants";
+import { Todos } from "../types/states";
+// todo 상태의 유효검사 함수
+export function validateState(state: Todos, origin: Todos = []) {
+ if (
+ Array.isArray(state) &&
+ state.every(
+ (val) =>
+ val &&
+ Object.hasOwn(val, "text") &&
+ val.text.split(" ").join("").length &&
+ Object.hasOwn(val, "isCompleted") &&
+ typeof val.isCompleted === "boolean" &&
+ Object.hasOwn(val, "id") &&
+ typeof val.id === "string"
+ )
+ ) {
+ return state;
+ }
+ console.warn(ERROR_MESSAGES["invalidState"]);
+ return origin;
+}
+
+export function filterValidStorageState(
+ state: Todos,
+ defaultState: Todos = []
+) {
+ try {
+ return state.filter(
+ (val) =>
+ val &&
+ Object.hasOwn(val, "text") &&
+ val.text.split(" ").join("").length &&
+ Object.hasOwn(val, "isCompleted") &&
+ typeof val.isCompleted === "boolean" &&
+ Object.hasOwn(val, "id") &&
+ typeof val.id === "string"
+ );
+ } catch (e) {
+ if (e instanceof Error) {
+ console.warn(ERROR_MESSAGES[e.message]);
+ }
+ return defaultState;
+ }
+}
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/src/vite-env.d.ts b/yongjae/projects/FEDC5-3_VanillaJS_1/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/yongjae/projects/FEDC5-3_VanillaJS_1/tsconfig.json b/yongjae/projects/FEDC5-3_VanillaJS_1/tsconfig.json
new file mode 100644
index 0000000..f28fcc0
--- /dev/null
+++ b/yongjae/projects/FEDC5-3_VanillaJS_1/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"]
+}