From 3e624b031a28ae62493f9251bc970f78c50f2112 Mon Sep 17 00:00:00 2001 From: Luis Pardo Date: Thu, 19 Jun 2025 23:21:42 +0000 Subject: [PATCH 01/13] Initial commite copy of updated wc implementation --- .../javascript-wc-indexeddb/.gitignore | 2 + .../javascript-wc-indexeddb/README.md | 37 + .../components/todo-app/todo-app.component.js | 138 ++++ .../components/todo-app/todo-app.template.js | 14 + .../todo-bottombar.component.js | 80 ++ .../todo-bottombar/todo-bottombar.template.js | 22 + .../todo-item/todo-item.component.js | 184 +++++ .../todo-item/todo-item.template.js | 19 + .../todo-list/todo-list.component.js | 120 +++ .../todo-list/todo-list.template.js | 8 + .../todo-topbar/todo-topbar.component.js | 131 +++ .../todo-topbar/todo-topbar.template.js | 17 + .../dist/hooks/useDoubleClick.js | 19 + .../dist/hooks/useKeyListener.js | 23 + .../dist/hooks/useRouter.js | 43 + .../javascript-wc-indexeddb/dist/index.html | 30 + .../dist/styles/app.constructable.js | 15 + .../dist/styles/bottombar.constructable.js | 158 ++++ .../dist/styles/footer.css | 26 + .../dist/styles/global.constructable.js | 86 ++ .../dist/styles/global.css | 88 ++ .../dist/styles/header.css | 21 + .../dist/styles/main.constructable.js | 11 + .../dist/styles/todo-item.constructable.js | 147 ++++ .../dist/styles/todo-list.constructable.js | 15 + .../dist/styles/topbar.constructable.js | 90 +++ .../dist/utils/nanoid.js | 41 + .../javascript-wc-indexeddb/index.html | 30 + .../javascript-wc-indexeddb/package-lock.json | 756 ++++++++++++++++++ .../javascript-wc-indexeddb/package.json | 22 + .../javascript-wc-indexeddb/scripts/build.js | 94 +++ .../components/todo-app/todo-app.component.js | 138 ++++ .../components/todo-app/todo-app.template.js | 14 + .../todo-bottombar.component.js | 80 ++ .../todo-bottombar/todo-bottombar.template.js | 22 + .../todo-item/todo-item.component.js | 184 +++++ .../todo-item/todo-item.template.js | 19 + .../todo-list/todo-list.component.js | 120 +++ .../todo-list/todo-list.template.js | 8 + .../todo-topbar/todo-topbar.component.js | 131 +++ .../todo-topbar/todo-topbar.template.js | 17 + .../src/hooks/useDoubleClick.js | 19 + .../src/hooks/useKeyListener.js | 23 + .../src/hooks/useRouter.js | 43 + .../src/utils/nanoid.js | 41 + 45 files changed, 3346 insertions(+) create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/.gitignore create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/README.md create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-app/todo-app.component.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-app/todo-app.template.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-bottombar/todo-bottombar.component.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-bottombar/todo-bottombar.template.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-item/todo-item.component.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-item/todo-item.template.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-list/todo-list.component.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-list/todo-list.template.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-topbar/todo-topbar.component.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-topbar/todo-topbar.template.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/hooks/useDoubleClick.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/hooks/useKeyListener.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/hooks/useRouter.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/index.html create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/app.constructable.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/bottombar.constructable.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/footer.css create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/global.constructable.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/global.css create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/header.css create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/main.constructable.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/todo-item.constructable.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/todo-list.constructable.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/topbar.constructable.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/utils/nanoid.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/index.html create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/package-lock.json create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/package.json create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/scripts/build.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-app/todo-app.component.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-app/todo-app.template.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.component.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.template.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-item/todo-item.component.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-item/todo-item.template.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-list/todo-list.component.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-list/todo-list.template.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-topbar/todo-topbar.component.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-topbar/todo-topbar.template.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/hooks/useDoubleClick.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/hooks/useKeyListener.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/hooks/useRouter.js create mode 100644 resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/utils/nanoid.js diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/.gitignore b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/.gitignore new file mode 100644 index 000000000..03e05e4c0 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +/node_modules diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/README.md b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/README.md new file mode 100644 index 000000000..11351f614 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/README.md @@ -0,0 +1,37 @@ +# Speedometer 3.0: TodoMVC: Web Components + +## Description + +A todoMVC application implemented with native web components. +It utilizes custom elements and html templates to build reusable components. + +In contrast to other workloads, this application uses an updated set of css rules and an optimized dom structure to ensure the application follows best practices in regards to accessibility. + +## Built steps + +A simple build script copies all necessary files to a `dist` folder. +It does not rely on compilers or transpilers and serves raw html, css and js files to the user. + +``` +npm run build +``` + +## Requirements + +The only requirement is an installation of Node, to be able to install dependencies and run scripts to serve a local server. + +``` +* Node (min version: 18.13.0) +* NPM (min version: 8.19.3) +``` + +## Local preview + +``` +terminal: +1. npm install +2. npm run dev + +browser: +1. http://localhost:7005/ +``` diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-app/todo-app.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-app/todo-app.component.js new file mode 100644 index 000000000..54a666010 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-app/todo-app.component.js @@ -0,0 +1,138 @@ +import template from "./todo-app.template.js"; +import { useRouter } from "../../hooks/useRouter.js"; + +import globalStyles from "../../styles/global.constructable.js"; +import appStyles from "../../styles/app.constructable.js"; +import mainStyles from "../../styles/main.constructable.js"; +class TodoApp extends HTMLElement { + #isReady = false; + #numberOfItems = 0; + #numberOfCompletedItems = 0; + constructor() { + super(); + + const node = document.importNode(template.content, true); + this.topbar = node.querySelector("todo-topbar"); + this.list = node.querySelector("todo-list"); + this.bottombar = node.querySelector("todo-bottombar"); + + this.shadow = this.attachShadow({ mode: "open" }); + this.htmlDirection = document.dir || "ltr"; + this.setAttribute("dir", this.htmlDirection); + this.shadow.adoptedStyleSheets = [globalStyles, appStyles, mainStyles]; + this.shadow.append(node); + + this.addItem = this.addItem.bind(this); + this.toggleItem = this.toggleItem.bind(this); + this.removeItem = this.removeItem.bind(this); + this.updateItem = this.updateItem.bind(this); + this.toggleItems = this.toggleItems.bind(this); + this.clearCompletedItems = this.clearCompletedItems.bind(this); + this.routeChange = this.routeChange.bind(this); + + this.router = useRouter(); + } + + get isReady() { + return this.#isReady; + } + + getInstance() { + return this; + } + + addItem(event) { + const { detail: item } = event; + this.list.addItem(item, this.#numberOfItems++); + this.update("add-item", item.id); + } + + toggleItem(event) { + if (event.detail.completed === "true") { + this.#numberOfCompletedItems++; + } else { + this.#numberOfCompletedItems--; + } + this.update("toggle-item", event.detail.id); + } + + removeItem(event) { + if (event.datail.completed === "true") { + this.#numberOfCompletedItems--; + } + this.#numberOfItems--; + this.update("remove-item", event.detail.id); + } + + updateItem(event) { + this.update("update-item", event.detail.id); + } + + toggleItems(event) { + this.list.toggleItems(event.detail.completed); + } + + clearCompletedItems() { + this.list.removeCompletedItems(); + } + + update() { + const totalItems = this.#numberOfItems; + const completedItems = this.#numberOfCompletedItems; + const activeItems = totalItems - completedItems; + + this.list.setAttribute("total-items", totalItems); + + this.topbar.setAttribute("total-items", totalItems); + this.topbar.setAttribute("active-items", activeItems); + this.topbar.setAttribute("completed-items", completedItems); + + this.bottombar.setAttribute("total-items", totalItems); + this.bottombar.setAttribute("active-items", activeItems); + } + + addListeners() { + this.topbar.addEventListener("toggle-all", this.toggleItems); + this.topbar.addEventListener("add-item", this.addItem); + + this.list.listNode.addEventListener("toggle-item", this.toggleItem); + this.list.listNode.addEventListener("remove-item", this.removeItem); + this.list.listNode.addEventListener("update-item", this.updateItem); + + this.bottombar.addEventListener("clear-completed-items", this.clearCompletedItems); + } + + removeListeners() { + this.topbar.removeEventListener("toggle-all", this.toggleItems); + this.topbar.removeEventListener("add-item", this.addItem); + + this.list.listNode.removeEventListener("toggle-item", this.toggleItem); + this.list.listNode.removeEventListener("remove-item", this.removeItem); + this.list.listNode.removeEventListener("update-item", this.updateItem); + + this.bottombar.removeEventListener("clear-completed-items", this.clearCompletedItems); + } + + routeChange(route) { + const routeName = route.split("/")[1] || "all"; + this.list.updateRoute(routeName); + this.bottombar.updateRoute(routeName); + this.topbar.updateRoute(routeName); + } + + connectedCallback() { + this.update("connected"); + this.addListeners(); + this.router.initRouter(this.routeChange); + this.#isReady = true; + } + + disconnectedCallback() { + this.removeListeners(); + this.#isReady = false; + } +} + +customElements.define("todo-app", TodoApp); + +export default TodoApp; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-app/todo-app.template.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-app/todo-app.template.js new file mode 100644 index 000000000..1a55a8194 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-app/todo-app.template.js @@ -0,0 +1,14 @@ +const template = document.createElement("template"); + +template.id = "todo-app-template"; +template.innerHTML = ` +
+ +
+ +
+ +
+`; + +export default template; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-bottombar/todo-bottombar.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-bottombar/todo-bottombar.component.js new file mode 100644 index 000000000..cc0203c48 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-bottombar/todo-bottombar.component.js @@ -0,0 +1,80 @@ +import template from "./todo-bottombar.template.js"; + +import globalStyles from "../../styles/global.constructable.js"; +import bottombarStyles from "../../styles/bottombar.constructable.js"; + +class TodoBottombar extends HTMLElement { + static get observedAttributes() { + return ["total-items", "active-items"]; + } + + constructor() { + super(); + + const node = document.importNode(template.content, true); + this.element = node.querySelector(".bottombar"); + this.clearCompletedButton = node.querySelector(".clear-completed-button"); + this.todoStatus = node.querySelector(".todo-status"); + this.filterLinks = node.querySelectorAll(".filter-link"); + + this.shadow = this.attachShadow({ mode: "open" }); + this.htmlDirection = document.dir || "ltr"; + this.setAttribute("dir", this.htmlDirection); + this.shadow.adoptedStyleSheets = [globalStyles, bottombarStyles]; + this.shadow.append(node); + + this.clearCompletedItems = this.clearCompletedItems.bind(this); + } + + updateDisplay() { + if (parseInt(this["total-items"]) !== 0) + this.element.style.display = "block"; + else + this.element.style.display = "none"; + + this.todoStatus.textContent = `${this["active-items"]} ${this["active-items"] === "1" ? "item" : "items"} left!`; + } + + updateRoute(route) { + this.filterLinks.forEach((link) => { + if (link.dataset.route === route) + link.classList.add("selected"); + else + link.classList.remove("selected"); + }); + } + + clearCompletedItems() { + this.dispatchEvent(new CustomEvent("clear-completed-items")); + } + + addListeners() { + this.clearCompletedButton.addEventListener("click", this.clearCompletedItems); + } + + removeListeners() { + this.clearCompletedButton.removeEventListener("click", this.clearCompletedItems); + } + + attributeChangedCallback(property, oldValue, newValue) { + if (oldValue === newValue) + return; + this[property] = newValue; + + if (this.isConnected) + this.updateDisplay(); + } + + connectedCallback() { + this.updateDisplay(); + this.addListeners(); + } + + disconnectedCallback() { + this.removeListeners(); + } +} + +customElements.define("todo-bottombar", TodoBottombar); + +export default TodoBottombar; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-bottombar/todo-bottombar.template.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-bottombar/todo-bottombar.template.js new file mode 100644 index 000000000..4f34ca92d --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-bottombar/todo-bottombar.template.js @@ -0,0 +1,22 @@ +const template = document.createElement("template"); + +template.id = "todo-bottombar-template"; +template.innerHTML = ` + +`; + +export default template; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-item/todo-item.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-item/todo-item.component.js new file mode 100644 index 000000000..2b73ef7e2 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-item/todo-item.component.js @@ -0,0 +1,184 @@ +import template from "./todo-item.template.js"; +import { useDoubleClick } from "../../hooks/useDoubleClick.js"; +import { useKeyListener } from "../../hooks/useKeyListener.js"; + +import globalStyles from "../../styles/global.constructable.js"; +import itemStyles from "../../styles/todo-item.constructable.js"; + +class TodoItem extends HTMLElement { + static get observedAttributes() { + return ["itemid", "itemtitle", "itemcompleted"]; + } + + constructor() { + super(); + + // Renamed this.id to this.itemid and this.title to this.itemtitle. + // When the component assigns to this.id or this.title, this causes the browser's implementation of the existing setters to run, which convert these property sets into internal setAttribute calls. This can have surprising consequences. + // [Issue]: https://github.com/WebKit/Speedometer/issues/313 + this.itemid = ""; + this.itemtitle = "Todo Item"; + this.itemcompleted = "false"; + + const node = document.importNode(template.content, true); + this.item = node.querySelector(".todo-item"); + this.toggleLabel = node.querySelector(".toggle-todo-label"); + this.toggleInput = node.querySelector(".toggle-todo-input"); + this.todoText = node.querySelector(".todo-item-text"); + this.todoButton = node.querySelector(".remove-todo-button"); + this.editLabel = node.querySelector(".edit-todo-label"); + this.editInput = node.querySelector(".edit-todo-input"); + + this.shadow = this.attachShadow({ mode: "open" }); + this.htmlDirection = document.dir || "ltr"; + this.setAttribute("dir", this.htmlDirection); + this.shadow.adoptedStyleSheets = [globalStyles, itemStyles]; + this.shadow.append(node); + + this.keysListeners = []; + + this.updateItem = this.updateItem.bind(this); + this.toggleItem = this.toggleItem.bind(this); + this.removeItem = this.removeItem.bind(this); + this.startEdit = this.startEdit.bind(this); + this.stopEdit = this.stopEdit.bind(this); + this.cancelEdit = this.cancelEdit.bind(this); + + if (window.extraTodoItemCssToAdopt) { + let extraAdoptedStyleSheet = new CSSStyleSheet(); + extraAdoptedStyleSheet.replaceSync(window.extraTodoItemCssToAdopt); + this.shadow.adoptedStyleSheets.push(extraAdoptedStyleSheet); + } + } + + update(...args) { + args.forEach((argument) => { + switch (argument) { + case "itemid": + if (this.itemid !== undefined) + this.item.id = `todo-item-${this.itemid}`; + break; + case "itemtitle": + if (this.itemtitle !== undefined) { + this.todoText.textContent = this.itemtitle; + this.editInput.value = this.itemtitle; + } + break; + case "itemcompleted": + this.toggleInput.checked = this.itemcompleted === "true" ? true : false; + break; + } + }); + } + + startEdit() { + this.item.classList.add("editing"); + this.editInput.value = this.itemtitle; + this.editInput.focus(); + } + + stopEdit() { + this.item.classList.remove("editing"); + } + + cancelEdit() { + this.editInput.blur(); + } + + toggleItem() { + // The todo-list checks the "completed" attribute to filter based on route + // (therefore the completed state needs to already be updated before the check) + this.setAttribute("itemcompleted", this.toggleInput.checked); + + this.dispatchEvent( + new CustomEvent("toggle-item", { + detail: { completed: this.toggleInput.checked }, + bubbles: true, + }) + ); + } + + removeItem() { + // The todo-list keeps a reference to all elements and updates the list on removal. + // (therefore the removal has to happen after the list is updated) + this.dispatchEvent( + new CustomEvent("remove-item", { + detail: { completed: this.togglegetAtInput.checked}, + bubbles: true, + }) + ); + this.remove(); + } + + updateItem(event) { + if (event.target.value !== this.itemtitle) { + if (!event.target.value.length) { + this.removeItem(); + } else { + this.setAttribute("itemtitle", event.target.value); + } + } + + this.cancelEdit(); + } + + addListeners() { + this.toggleInput.addEventListener("change", this.toggleItem); + this.todoText.addEventListener("click", useDoubleClick(this.startEdit, 500)); + this.editInput.addEventListener("blur", this.stopEdit); + this.todoButton.addEventListener("click", this.removeItem); + + this.keysListeners.forEach((listener) => listener.connect()); + } + + removeListeners() { + this.toggleInput.removeEventListener("change", this.toggleItem); + this.todoText.removeEventListener("click", this.startEdit); + this.editInput.removeEventListener("blur", this.stopEdit); + this.todoButton.removeEventListener("click", this.removeItem); + + this.keysListeners.forEach((listener) => listener.disconnect()); + } + + attributeChangedCallback(property, oldValue, newValue) { + if (oldValue === newValue) + return; + this[property] = newValue; + + if (this.isConnected) + this.update(property); + } + + connectedCallback() { + this.update("itemid", "itemtitle", "itemcompleted"); + + this.keysListeners.push( + useKeyListener({ + target: this.editInput, + event: "keyup", + callbacks: { + ["Enter"]: this.updateItem, + ["Escape"]: this.cancelEdit, + }, + }), + useKeyListener({ + target: this.todoText, + event: "keyup", + callbacks: { + [" "]: this.startEdit, // this feels weird + }, + }) + ); + + this.addListeners(); + } + + disconnectedCallback() { + this.removeListeners(); + this.keysListeners = []; + } +} + +customElements.define("todo-item", TodoItem); + +export default TodoItem; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-item/todo-item.template.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-item/todo-item.template.js new file mode 100644 index 000000000..9a67675fd --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-item/todo-item.template.js @@ -0,0 +1,19 @@ +const template = document.createElement("template"); + +template.id = "todo-item-template"; +template.innerHTML = ` +
  • +
    + + + Placeholder Text + +
    +
    + + +
    +
  • +`; + +export default template; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-list/todo-list.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-list/todo-list.component.js new file mode 100644 index 000000000..bc6d1339d --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-list/todo-list.component.js @@ -0,0 +1,120 @@ +import template from "./todo-list.template.js"; +import TodoItem from "../todo-item/todo-item.component.js"; + +import globalStyles from "../../styles/global.constructable.js"; +import listStyles from "../../styles/todo-list.constructable.js"; + + +const customListStyles = new CSSStyleSheet(); +customListStyles.replaceSync(` + .todo-list > todo-item { + display: block; + } + + .todo-list[route="completed"] > [itemcompleted="false"] { + display: none; + } + + .todo-list[route="active"] > [itemcompleted="true"] { + display: none; + } +`); + +console.log(customListStyles); + +class TodoList extends HTMLElement { + static get observedAttributes() { + return ["total-items"]; + } + + #route = undefined; + + constructor() { + super(); + + const node = document.importNode(template.content, true); + this.listNode = node.querySelector(".todo-list"); + + this.shadow = this.attachShadow({ mode: "open" }); + this.htmlDirection = document.dir || "ltr"; + this.setAttribute("dir", this.htmlDirection); + this.shadow.adoptedStyleSheets = [globalStyles, listStyles, customListStyles]; + this.shadow.append(node); + this.classList.add("show-priority"); + + if (window.extraTodoListCssToAdopt) { + let extraAdoptedStyleSheet = new CSSStyleSheet(); + extraAdoptedStyleSheet.replaceSync(window.extraTodoListCssToAdopt); + this.shadow.adoptedStyleSheets.push(extraAdoptedStyleSheet); + } + } + + addItem(entry, itemIndex) { + const { id, title, completed } = entry; + const element = new TodoItem(); + + element.setAttribute("itemid", id); + element.setAttribute("itemtitle", title); + element.setAttribute("itemcompleted", completed); + element.setAttribute("data-priority", 4 - (itemIndex % 5)); + + this.listNode.append(element); + } + + addItems(items) { + items.forEach((entry) => this.addItem(entry)); + } + + removeCompletedItems() { + Array.from(this.listNode.children).forEach((element) => { + if (element.itemcompleted === "true") + element.removeItem(); + }); + } + + toggleItems(completed) { + Array.from(this.listNode.children).forEach((element) => { + if (completed && element.itemcompleted === "false") + element.toggleInput.click(); + else if (!completed && element.itemcompleted === "true") + element.toggleInput.click(); + }); + } + + updateStyles() { + if (parseInt(this["total-items"]) !== 0) + this.listNode.style.display = "block"; + else + this.listNode.style.display = "none"; + } + + updateRoute(route) { + this.#route = route; + switch (route) { + case "completed": + this.listNode.setAttribute("route", "completed"); + break; + case "active": + this.listNode.setAttribute("rout", "active"); + break; + default: + this.listNode.setAttribute("route", "all"); + } + } + + attributeChangedCallback(property, oldValue, newValue) { + if (oldValue === newValue) + return; + this[property] = newValue; + if (this.isConnected) + this.updateStyles(); + } + + connectedCallback() { + this.updateStyles(); + } +} + +customElements.define("todo-list", TodoList); + +export default TodoList; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-list/todo-list.template.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-list/todo-list.template.js new file mode 100644 index 000000000..dac383b95 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-list/todo-list.template.js @@ -0,0 +1,8 @@ +const template = document.createElement("template"); + +template.id = "todo-list-template"; +template.innerHTML = ` + +`; + +export default template; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-topbar/todo-topbar.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-topbar/todo-topbar.component.js new file mode 100644 index 000000000..f024d7ca1 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-topbar/todo-topbar.component.js @@ -0,0 +1,131 @@ +import template from "./todo-topbar.template.js"; +import { useKeyListener } from "../../hooks/useKeyListener.js"; +import { nanoid } from "../../utils/nanoid.js"; + +import globalStyles from "../../styles/global.constructable.js"; +import topbarStyles from "../../styles/topbar.constructable.js"; + +class TodoTopbar extends HTMLElement { + static get observedAttributes() { + return ["total-items", "active-items", "completed-items"]; + } + + #route = undefined; + + constructor() { + super(); + + const node = document.importNode(template.content, true); + this.todoInput = node.querySelector("#new-todo"); + this.toggleInput = node.querySelector("#toggle-all"); + this.toggleContainer = node.querySelector(".toggle-all-container"); + + this.shadow = this.attachShadow({ mode: "open" }); + this.htmlDirection = document.dir || "ltr"; + this.setAttribute("dir", this.htmlDirection); + this.shadow.adoptedStyleSheets = [globalStyles, topbarStyles]; + this.shadow.append(node); + + this.keysListeners = []; + + this.toggleAll = this.toggleAll.bind(this); + this.addItem = this.addItem.bind(this); + } + + toggleAll(event) { + this.dispatchEvent( + new CustomEvent("toggle-all", { + detail: { completed: event.target.checked }, + }) + ); + } + + addItem(event) { + if (!event.target.value.length) + return; + + this.dispatchEvent( + new CustomEvent("add-item", { + detail: { + id: nanoid(), + title: event.target.value, + completed: false, + }, + }) + ); + + event.target.value = ""; + } + + updateDisplay() { + if (!parseInt(this["total-items"])) { + this.toggleContainer.style.display = "none"; + return; + } + + this.toggleContainer.style.display = "block"; + + switch (this.#route) { + case "active": + this.toggleInput.checked = false; + this.toggleInput.disabled = !parseInt(this["active-items"]); + break; + case "completed": + this.toggleInput.checked = parseInt(this["completed-items"]); + this.toggleInput.disabled = !parseInt(this["completed-items"]); + break; + default: + this.toggleInput.checked = this["total-items"] === this["completed-items"]; + this.toggleInput.disabled = false; + } + } + + updateRoute(route) { + this.#route = route; + this.updateDisplay(); + } + + addListeners() { + this.toggleInput.addEventListener("change", this.toggleAll); + this.keysListeners.forEach((listener) => listener.connect()); + } + + removeListeners() { + this.toggleInput.removeEventListener("change", this.toggleAll); + this.keysListeners.forEach((listener) => listener.disconnect()); + } + + attributeChangedCallback(property, oldValue, newValue) { + if (oldValue === newValue) + return; + this[property] = newValue; + + if (this.isConnected) + this.updateDisplay(); + } + + connectedCallback() { + this.keysListeners.push( + useKeyListener({ + target: this.todoInput, + event: "keyup", + callbacks: { + ["Enter"]: this.addItem, + }, + }) + ); + + this.updateDisplay(); + this.addListeners(); + this.todoInput.focus(); + } + + disconnectedCallback() { + this.removeListeners(); + this.keysListeners = []; + } +} + +customElements.define("todo-topbar", TodoTopbar); + +export default TodoTopbar; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-topbar/todo-topbar.template.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-topbar/todo-topbar.template.js new file mode 100644 index 000000000..ce323b683 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-topbar/todo-topbar.template.js @@ -0,0 +1,17 @@ +const template = document.createElement("template"); + +template.id = "todo-topbar-template"; +template.innerHTML = ` +
    +
    + + +
    + +
    +`; + +export default template; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/hooks/useDoubleClick.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/hooks/useDoubleClick.js new file mode 100644 index 000000000..a1fe952fe --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/hooks/useDoubleClick.js @@ -0,0 +1,19 @@ +/** + * A simple function to normalize a double-click and a double-tab action. + * There is currently no comparable tab action to dblclick. + * + * @param {Function} fn + * @param {number} delay + * @returns + */ +export function useDoubleClick(fn, delay) { + let last = 0; + return function (...args) { + const now = new Date().getTime(); + const difference = now - last; + if (difference < delay && difference > 0) + fn.apply(this, args); + + last = now; + }; +} diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/hooks/useKeyListener.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/hooks/useKeyListener.js new file mode 100644 index 000000000..453747d54 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/hooks/useKeyListener.js @@ -0,0 +1,23 @@ +export function useKeyListener(props) { + const { target, event, callbacks } = props; + + function handleEvent(event) { + Object.keys(callbacks).forEach((key) => { + if (event.key === key) + callbacks[key](event); + }); + } + + function connect() { + target.addEventListener(event, handleEvent); + } + + function disconnect() { + target.removeEventListener(event, handleEvent); + } + + return { + connect, + disconnect, + }; +} diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/hooks/useRouter.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/hooks/useRouter.js new file mode 100644 index 000000000..ab1ab618a --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/hooks/useRouter.js @@ -0,0 +1,43 @@ +/** + * Listens for hash change of the url and calls onChange if available. + * + * @param {Function} callback + * @returns Methods to interact with useRouter. + */ +export const useRouter = (callback) => { + let onChange = callback; + let current = ""; + + /** + * Change event handler. + */ + const handleChange = () => { + current = document.location.hash; + /* istanbul ignore else */ + if (onChange) + onChange(document.location.hash); + }; + + /** + * Initializes router and adds listeners. + * + * @param {Function} callback + */ + const initRouter = (callback) => { + onChange = callback; + window.addEventListener("hashchange", handleChange); + window.addEventListener("load", handleChange); + }; + + /** + * Removes listeners + */ + const disableRouter = () => { + window.removeEventListener("hashchange", handleChange); + window.removeEventListener("load", handleChange); + }; + + const getRoute = () => current.split("/").slice(-1)[0]; + + return { initRouter, getRoute, disableRouter }; +}; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/index.html b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/index.html new file mode 100644 index 000000000..a8fec9787 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/index.html @@ -0,0 +1,30 @@ + + + + + + + TodoMVC: JavaScript Web Components + + + + + + + + + + + +
    +

    todos

    +
    + + + + diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/app.constructable.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/app.constructable.js new file mode 100644 index 000000000..8ac77f26a --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/app.constructable.js @@ -0,0 +1,15 @@ +const sheet = new CSSStyleSheet(); +sheet.replaceSync(`:host { + display: block; + box-shadow: none !important; + min-height: 68px; +} + +.app { + background: #fff; + margin: 24px 16px 40px 16px; + position: relative; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); +} +`); +export default sheet; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/bottombar.constructable.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/bottombar.constructable.js new file mode 100644 index 000000000..46904de8a --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/bottombar.constructable.js @@ -0,0 +1,158 @@ +const sheet = new CSSStyleSheet(); +sheet.replaceSync(`:host { + display: block; + box-shadow: none !important; +} + +.bottombar { + padding: 10px 0; + height: 41px; + text-align: center; + font-size: 15px; + border-top: 1px solid #e6e6e6; + position: relative; +} + +.bottombar::before { + content: ""; + position: absolute; + right: 0; + bottom: 0; + left: 0; + height: 50px; + overflow: hidden; + pointer-events: none; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6, 0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6, 0 17px 2px -6px rgba(0, 0, 0, 0.2); +} + +.todo-status { + text-align: left; + padding: 3px; + height: 32px; + line-height: 26px; + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); +} + +.todo-count { + font-weight: 300; +} + +.filter-list { + margin: 0; + padding: 0; + list-style: none; + display: inline-block; + position: absolute; + left: 0; + right: 0; + top: 50%; + transform: translateY(-50%); +} + +.filter-item { + display: inline-block; +} + +.filter-link { + color: inherit; + margin: 3px; + padding: 0 7px; + text-decoration: none; + border: 1px solid transparent; + border-radius: 3px; + cursor: pointer; + display: block; + height: 26px; + line-height: 26px; +} + +.filter-link:hover { + border-color: #db7676; +} + +.filter-link.selected { + border-color: #ce4646; +} + +.clear-completed-button, +.clear-completed-button:active { + text-decoration: none; + cursor: pointer; + padding: 3px; + height: 32px; + line-height: 26px; + position: absolute; + right: 12px; + top: 50%; + transform: translateY(-50%); +} + +.clear-completed-button:hover { + text-decoration: underline; +} + +/* rtl support */ +html[dir="rtl"] .todo-status, +:host([dir="rtl"]) .todo-status { + right: 12px; + left: unset; +} + +html[dir="rtl"] .clear-completed-button, +:host([dir="rtl"]) .clear-completed-button { + left: 12px; + right: unset; +} + +@media (max-width: 430px) { + .bottombar { + height: 120px; + } + + .todo-status { + display: block; + text-align: center; + position: relative; + left: unset; + right: unset; + top: unset; + transform: unset; + } + + .filter-list { + display: block; + position: relative; + left: unset; + right: unset; + top: unset; + transform: unset; + } + + .clear-completed-button, + .clear-completed-button:active { + display: block; + margin: 0 auto; + position: relative; + left: unset; + right: unset; + top: unset; + transform: unset; + } + + html[dir="rtl"] .todo-status, + :host([dir="rtl"]) .todo-status { + right: unset; + left: unset; + } + + html[dir="rtl"] .clear-completed-button, + :host([dir="rtl"]) .clear-completed-button { + left: unset; + right: unset; + } +} +`); +export default sheet; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/footer.css b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/footer.css new file mode 100644 index 000000000..0ff918f43 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/footer.css @@ -0,0 +1,26 @@ +:host { + display: block; + box-shadow: none !important; +} + +.footer { + margin: 65px auto 0; + color: #4d4d4d; + font-size: 11px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); + text-align: center; +} + +.footer-text { + line-height: 1; +} + +.footer-link { + color: inherit; + text-decoration: none; + font-weight: 400; +} + +.footer-link:hover { + text-decoration: underline; +} diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/global.constructable.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/global.constructable.js new file mode 100644 index 000000000..7ff85b07f --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/global.constructable.js @@ -0,0 +1,86 @@ +const sheet = new CSSStyleSheet(); +sheet.replaceSync(`*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; +} + +body { + font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif; + line-height: 1.4em; + background: #f5f5f5; + color: #111; + min-width: 300px; + max-width: 582px; + margin: 0 auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 300; +} + +:focus { + box-shadow: inset 0 0 2px 2px #cf7d7d !important; + outline: 0 !important; +} + +button { + margin: 0; + padding: 0; + border: 0; + background: none; + font-size: 100%; + vertical-align: baseline; + font-family: inherit; + font-weight: inherit; + color: inherit; + appearance: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +input { + position: relative; + margin: 0; + font-size: inherit; + font-family: inherit; + font-weight: inherit; + color: inherit; + padding: 0; + border: 0; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +input:placeholder-shown { + text-overflow: ellipsis; +} + + +.visually-hidden { + border: 0; + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + position: absolute; + white-space: nowrap; +} + +.truncate-singleline { + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block !important; +} +`); +export default sheet; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/global.css b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/global.css new file mode 100644 index 000000000..22ee5e16c --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/global.css @@ -0,0 +1,88 @@ +/** defaults */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; +} + +body { + font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif; + line-height: 1.4em; + background: #f5f5f5; + color: #111; + min-width: 300px; + max-width: 582px; + margin: 0 auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 300; +} + +:focus { + box-shadow: inset 0 0 2px 2px #cf7d7d !important; + outline: 0 !important; +} + +/** resets */ +button { + margin: 0; + padding: 0; + border: 0; + background: none; + font-size: 100%; + vertical-align: baseline; + font-family: inherit; + font-weight: inherit; + color: inherit; + appearance: none; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +input { + position: relative; + margin: 0; + font-size: inherit; + font-family: inherit; + font-weight: inherit; + color: inherit; + padding: 0; + border: 0; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +input:placeholder-shown { + text-overflow: ellipsis; +} + +/* utility classes */ + +/* used for things that should be hidden in the ui, +but useful for people who use screen readers */ +.visually-hidden { + border: 0; + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + position: absolute; + white-space: nowrap; +} + +.truncate-singleline { + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block !important; +} diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/header.css b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/header.css new file mode 100644 index 000000000..56d2a4064 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/header.css @@ -0,0 +1,21 @@ +:host { + display: block; + box-shadow: none !important; +} + +.header { + margin-top: 27px; +} + +.title { + width: 100%; + font-size: 80px; + line-height: 80px; + margin: 0; + font-weight: 200; + text-align: center; + color: #b83f45; + -webkit-text-rendering: optimizeLegibility; + -moz-text-rendering: optimizeLegibility; + text-rendering: optimizeLegibility; +} diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/main.constructable.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/main.constructable.js new file mode 100644 index 000000000..66ea0b30c --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/main.constructable.js @@ -0,0 +1,11 @@ +const sheet = new CSSStyleSheet(); +sheet.replaceSync(`:host { + display: block; + box-shadow: none !important; +} + +.main { + position: relative; +} +`); +export default sheet; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/todo-item.constructable.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/todo-item.constructable.js new file mode 100644 index 000000000..59dba7f77 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/todo-item.constructable.js @@ -0,0 +1,147 @@ +const sheet = new CSSStyleSheet(); +sheet.replaceSync(`:host { + display: block; + box-shadow: none !important; +} + +:host(:last-child) > .todo-item { + border-bottom: none; +} + +.todo-item { + position: relative; + font-size: 24px; + border-bottom: 1px solid #ededed; + height: 60px; +} + +.todo-item.editing { + border-bottom: none; + padding: 0; +} + +.edit-todo-container { + display: none; +} + +.todo-item.editing .edit-todo-container { + display: block; +} + +.edit-todo-input { + padding: 0 16px 0 60px; + width: 100%; + height: 60px; + font-size: 24px; + line-height: 1.4em; + background: rgba(0, 0, 0, 0.003); + box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03); + background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20%20style%3D%22opacity%3A%200.2%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23949494%22%20stroke-width%3D%223%22/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center left; +} + +.display-todo { + position: relative; +} + +.todo-item.editing .display-todo { + display: none; +} + +.toggle-todo-input { + text-align: center; + width: 40px; + + height: auto; + position: absolute; + top: 0; + bottom: 0; + left: 3px; + margin: auto 0; + border: none; appearance: none; + cursor: pointer; +} + +.todo-item-text { + overflow-wrap: break-word; + padding: 0 60px; + display: block; + line-height: 60px; + transition: color 0.4s; + font-weight: 400; + color: #484848; + + background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23949494%22%20stroke-width%3D%223%22/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center left; +} + +.toggle-todo-input:checked + .todo-item-text { + color: #949494; + text-decoration: line-through; + background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%2359A193%22%20stroke-width%3D%223%22%2F%3E%3Cpath%20fill%3D%22%233EA390%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22%2F%3E%3C%2Fsvg%3E"); +} + +.remove-todo-button { + display: none; + position: absolute; + top: 0; + right: 10px; + bottom: 0; + width: 40px; + height: 40px; + margin: auto 0; + font-size: 30px; + color: #949494; + transition: color 0.2s ease-out; + cursor: pointer; +} + +.remove-todo-button:hover, +.remove-todo-button:focus { + color: #c18585; +} + +.remove-todo-button::after { + content: "×"; + display: block; + height: 100%; + line-height: 1.1; +} + +.todo-item:hover .remove-todo-button { + display: block; +} + +@media screen and (-webkit-min-device-pixel-ratio: 0) { + .toggle-todo-input { + background: none; + height: 40px; + } +} + +@media (max-width: 430px) { + .remove-todo-button { + display: block; + } +} + +html[dir="rtl"] .toggle-todo-input, +:host([dir="rtl"]) .toggle-todo-input { + right: 3px; + left: unset; +} + +html[dir="rtl"] .todo-item-text, +:host([dir="rtl"]) .todo-item-text { + background-position: center right 6px; +} + +html[dir="rtl"] .remove-todo-button, +:host([dir="rtl"]) .remove-todo-button { + left: 10px; + right: unset; +} +`); +export default sheet; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/todo-list.constructable.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/todo-list.constructable.js new file mode 100644 index 000000000..3d11133dd --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/todo-list.constructable.js @@ -0,0 +1,15 @@ +const sheet = new CSSStyleSheet(); +sheet.replaceSync(`:host { + display: block; + box-shadow: none !important; +} + +.todo-list { + margin: 0; + padding: 0; + list-style: none; + display: block; + border-top: 1px solid #e6e6e6; +} +`); +export default sheet; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/topbar.constructable.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/topbar.constructable.js new file mode 100644 index 000000000..5ecb8a231 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/styles/topbar.constructable.js @@ -0,0 +1,90 @@ +const sheet = new CSSStyleSheet(); +sheet.replaceSync(`:host { + display: block; + box-shadow: none !important; +} + +.topbar { + position: relative; +} + +.new-todo-input { + padding: 0 32px 0 60px; + width: 100%; + height: 68px; + font-size: 24px; + line-height: 1.4em; + background: rgba(0, 0, 0, 0.003); + box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03); +} + +.new-todo-input::placeholder { + font-style: italic; + font-weight: 400; + color: rgba(0, 0, 0, 0.4); +} + +.toggle-all-container { + width: 45px; + height: 68px; + position: absolute; + left: 0; + top: 0; +} + +.toggle-all-input { + width: 45px; + height: 45px; + font-size: 0; + position: absolute; + top: 11.5px; + left: 0; + border: none; + appearance: none; + cursor: pointer; +} + +.toggle-all-label { + display: flex; + align-items: center; + justify-content: center; + width: 45px; + height: 68px; + font-size: 0; + position: absolute; + top: 0; + left: 0; + cursor: pointer; +} + +.toggle-all-label::before { + content: "❯"; + display: inline-block; + font-size: 22px; + color: #949494; + padding: 10px 27px 10px 27px; + transform: rotate(90deg); +} + +.toggle-all-input:checked + .toggle-all-label::before { + color: #484848; +} + +@media screen and (-webkit-min-device-pixel-ratio: 0) { + .toggle-all-input { + background: none; + } +} + +html[dir="rtl"] .new-todo-input, +:host([dir="rtl"]) .new-todo-input { + padding: 0 60px 0 32px; +} + +html[dir="rtl"] .toggle-all-container, +:host([dir="rtl"]) .toggle-all-container { + right: 0; + left: unset; +} +`); +export default sheet; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/utils/nanoid.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/utils/nanoid.js new file mode 100644 index 000000000..5df154f1f --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/utils/nanoid.js @@ -0,0 +1,41 @@ +/* Borrowed from https://github.com/ai/nanoid/blob/3.0.2/non-secure/index.js + +The MIT License (MIT) + +Copyright 2017 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +// This alphabet uses `A-Za-z0-9_-` symbols. +// The order of characters is optimized for better gzip and brotli compression. +// References to the same file (works both for gzip and brotli): +// `'use`, `andom`, and `rict'` +// References to the brotli default dictionary: +// `-26T`, `1983`, `40px`, `75px`, `bush`, `jack`, `mind`, `very`, and `wolf` +let urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; + +export function nanoid(size = 21) { + let id = ""; + // A compact alternative for `for (var i = 0; i < step; i++)`. + let i = size; + while (i--) { + // `| 0` is more compact and faster than `Math.floor()`. + id += urlAlphabet[(Math.random() * 64) | 0]; + } + return id; +} diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/index.html b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/index.html new file mode 100644 index 000000000..6820a9346 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/index.html @@ -0,0 +1,30 @@ + + + + + + + TodoMVC: JavaScript Web Components + + + + + + + + + + + +
    +

    todos

    +
    + +
    + + + + +
    + + diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/package-lock.json b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/package-lock.json new file mode 100644 index 000000000..34dfaedc3 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/package-lock.json @@ -0,0 +1,756 @@ +{ + "name": "todomvc-javascript-web-components", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "todomvc-javascript-web-components", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "http-server": "^14.1.1", + "todomvc-css": "file:../../todomvc-css" + }, + "engines": { + "node": ">=18.13.0", + "npm": ">=8.19.3" + } + }, + "../../todomvc-css": { + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@rollup/plugin-babel": "^6.0.3", + "@rollup/plugin-commonjs": "^25.0.0", + "@rollup/plugin-html": "^1.0.2", + "@rollup/plugin-node-resolve": "^15.0.2", + "@rollup/plugin-terser": "^0.4.3", + "@rollup/pluginutils": "^5.0.2", + "fs-extra": "^11.1.1", + "globby": "^13.2.0", + "http-server": "^14.1.1", + "rollup": "^3.23.0", + "rollup-plugin-cleaner": "^1.0.0", + "rollup-plugin-copy-merge": "^1.0.2", + "rollup-plugin-import-css": "^3.2.1", + "strip-comments": "^2.0.1", + "stylelint": "^15.6.2", + "stylelint-config-standard": "^33.0.0" + }, + "engines": { + "node": ">=18.13.0", + "npm": ">=8.19.3" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/todomvc-css": { + "resolved": "../../todomvc-css", + "link": true + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + } + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "requires": { + "lodash": "^4.17.14" + } + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==" + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "requires": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "requires": { + "minimist": "^1.2.6" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" + }, + "portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "requires": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + } + }, + "qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "todomvc-css": { + "version": "file:../../todomvc-css", + "requires": { + "@rollup/plugin-babel": "^6.0.3", + "@rollup/plugin-commonjs": "^25.0.0", + "@rollup/plugin-html": "^1.0.2", + "@rollup/plugin-node-resolve": "^15.0.2", + "@rollup/plugin-terser": "^0.4.3", + "@rollup/pluginutils": "^5.0.2", + "fs-extra": "^11.1.1", + "globby": "^13.2.0", + "http-server": "^14.1.1", + "rollup": "^3.23.0", + "rollup-plugin-cleaner": "^1.0.0", + "rollup-plugin-copy-merge": "^1.0.2", + "rollup-plugin-import-css": "^3.2.1", + "strip-comments": "^2.0.1", + "stylelint": "^15.6.2", + "stylelint-config-standard": "^33.0.0" + } + }, + "union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "requires": { + "qs": "^6.4.0" + } + }, + "url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "requires": { + "iconv-lite": "0.6.3" + } + } + } +} diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/package.json b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/package.json new file mode 100644 index 000000000..a04fdab85 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/package.json @@ -0,0 +1,22 @@ +{ + "name": "todomvc-javascript-web-components", + "version": "1.0.0", + "description": "TodoMVC app written with JavaScript using web components.", + "engines": { + "node": ">=18.13.0", + "npm": ">=8.19.3" + }, + "private": true, + "scripts": { + "dev": "http-server ./ -p 7005 -c-1 --cors -o", + "build": "node scripts/build.js", + "serve": "http-server ./dist -p 7006 -c-1 --cors -o" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "http-server": "^14.1.1", + "todomvc-css": "file:../../todomvc-css" + } +} diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/scripts/build.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/scripts/build.js new file mode 100644 index 000000000..d5fa77557 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/scripts/build.js @@ -0,0 +1,94 @@ +const fs = require("fs").promises; +const getDirName = require("path").dirname; + +const rootDirectory = "./"; +const sourceDirectory = "./src"; +const targetDirectory = "./dist"; + +const htmlFile = "index.html"; + +const filesToMove = { + index: [ + { src: "node_modules/todomvc-css/dist/global.css", dest: "styles/global.css" }, + { src: "node_modules/todomvc-css/dist/header.css", dest: "styles/header.css" }, + { src: "node_modules/todomvc-css/dist/footer.css", dest: "styles/footer.css" }, + ], + app: [ + { src: "node_modules/todomvc-css/dist/global.constructable.js", dest: "styles/global.constructable.js" }, + { src: "node_modules/todomvc-css/dist/app.constructable.js", dest: "styles/app.constructable.js" }, + { src: "node_modules/todomvc-css/dist/topbar.constructable.js", dest: "styles/topbar.constructable.js" }, + { src: "node_modules/todomvc-css/dist/main.constructable.js", dest: "styles/main.constructable.js" }, + { src: "node_modules/todomvc-css/dist/bottombar.constructable.js", dest: "styles/bottombar.constructable.js" }, + { src: "node_modules/todomvc-css/dist/todo-list.constructable.js", dest: "styles/todo-list.constructable.js" }, + { src: "node_modules/todomvc-css/dist/todo-item.constructable.js", dest: "styles/todo-item.constructable.js" }, + ], +}; + +const importsToRename = { + src: "../../../node_modules/todomvc-css/dist/", + dest: "../../styles/", + files: [ + "components/todo-app/todo-app.component.js", + "components/todo-bottombar/todo-bottombar.component.js", + "components/todo-item/todo-item.component.js", + "components/todo-list/todo-list.component.js", + "components/todo-topbar/todo-topbar.component.js", + ], +}; + +const copy = async (src, dest) => { + await fs.mkdir(getDirName(dest), { recursive: true }); + await fs.copyFile(src, dest); +}; + +const copyFilesToMove = async (files) => { + for (let i = 0; i < files.length; i++) + await copy(files[i].src, `${targetDirectory}/${files[i].dest}`); +}; + +const updateImports = async ({ file, src, dest }) => { + let contents = await fs.readFile(`${targetDirectory}/${file}`, "utf8"); + contents = contents.replaceAll(src, dest); + await fs.writeFile(`${targetDirectory}/${file}`, contents); +}; + +const build = async () => { + // remove dist directory if it exists + await fs.rm(targetDirectory, { recursive: true, force: true }); + + // re-create the directory. + await fs.mkdir(targetDirectory); + + // copy src folder + await fs.cp(sourceDirectory, targetDirectory, { recursive: true }, (err) => { + if (err) + console.error(err); + }); + + // copy files to Move + for (const key in filesToMove) + copyFilesToMove(filesToMove[key]); + + // read html file + let contents = await fs.readFile(`${rootDirectory}/${htmlFile}`, "utf8"); + + // remove base paths from files to move + const filesToMoveForIndex = filesToMove.index; + for (let i = 0; i < filesToMoveForIndex.length; i++) + contents = contents.replace(filesToMoveForIndex[i].src, filesToMoveForIndex[i].dest); + + // remove basePath from source directory + const basePath = `${sourceDirectory.split("/")[1]}/`; + const re = new RegExp(basePath, "g"); + contents = contents.replace(re, ""); + + // write html files + await fs.writeFile(`${targetDirectory}/${htmlFile}`, contents); + + // rename imports in modules + importsToRename.files.forEach((file) => updateImports({ file, src: importsToRename.src, dest: importsToRename.dest })); + + console.log("done!!"); +}; + +build(); diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-app/todo-app.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-app/todo-app.component.js new file mode 100644 index 000000000..7ce108fe9 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-app/todo-app.component.js @@ -0,0 +1,138 @@ +import template from "./todo-app.template.js"; +import { useRouter } from "../../hooks/useRouter.js"; + +import globalStyles from "../../../node_modules/todomvc-css/dist/global.constructable.js"; +import appStyles from "../../../node_modules/todomvc-css/dist/app.constructable.js"; +import mainStyles from "../../../node_modules/todomvc-css/dist/main.constructable.js"; +class TodoApp extends HTMLElement { + #isReady = false; + #numberOfItems = 0; + #numberOfCompletedItems = 0; + constructor() { + super(); + + const node = document.importNode(template.content, true); + this.topbar = node.querySelector("todo-topbar"); + this.list = node.querySelector("todo-list"); + this.bottombar = node.querySelector("todo-bottombar"); + + this.shadow = this.attachShadow({ mode: "open" }); + this.htmlDirection = document.dir || "ltr"; + this.setAttribute("dir", this.htmlDirection); + this.shadow.adoptedStyleSheets = [globalStyles, appStyles, mainStyles]; + this.shadow.append(node); + + this.addItem = this.addItem.bind(this); + this.toggleItem = this.toggleItem.bind(this); + this.removeItem = this.removeItem.bind(this); + this.updateItem = this.updateItem.bind(this); + this.toggleItems = this.toggleItems.bind(this); + this.clearCompletedItems = this.clearCompletedItems.bind(this); + this.routeChange = this.routeChange.bind(this); + + this.router = useRouter(); + } + + get isReady() { + return this.#isReady; + } + + getInstance() { + return this; + } + + addItem(event) { + const { detail: item } = event; + this.list.addItem(item, this.#numberOfItems++); + this.update("add-item", item.id); + } + + toggleItem(event) { + if (event.detail.completed === "true") { + this.#numberOfCompletedItems++; + } else { + this.#numberOfCompletedItems--; + } + this.update("toggle-item", event.detail.id); + } + + removeItem(event) { + if (event.datail.completed === "true") { + this.#numberOfCompletedItems--; + } + this.#numberOfItems--; + this.update("remove-item", event.detail.id); + } + + updateItem(event) { + this.update("update-item", event.detail.id); + } + + toggleItems(event) { + this.list.toggleItems(event.detail.completed); + } + + clearCompletedItems() { + this.list.removeCompletedItems(); + } + + update() { + const totalItems = this.#numberOfItems; + const completedItems = this.#numberOfCompletedItems; + const activeItems = totalItems - completedItems; + + this.list.setAttribute("total-items", totalItems); + + this.topbar.setAttribute("total-items", totalItems); + this.topbar.setAttribute("active-items", activeItems); + this.topbar.setAttribute("completed-items", completedItems); + + this.bottombar.setAttribute("total-items", totalItems); + this.bottombar.setAttribute("active-items", activeItems); + } + + addListeners() { + this.topbar.addEventListener("toggle-all", this.toggleItems); + this.topbar.addEventListener("add-item", this.addItem); + + this.list.listNode.addEventListener("toggle-item", this.toggleItem); + this.list.listNode.addEventListener("remove-item", this.removeItem); + this.list.listNode.addEventListener("update-item", this.updateItem); + + this.bottombar.addEventListener("clear-completed-items", this.clearCompletedItems); + } + + removeListeners() { + this.topbar.removeEventListener("toggle-all", this.toggleItems); + this.topbar.removeEventListener("add-item", this.addItem); + + this.list.listNode.removeEventListener("toggle-item", this.toggleItem); + this.list.listNode.removeEventListener("remove-item", this.removeItem); + this.list.listNode.removeEventListener("update-item", this.updateItem); + + this.bottombar.removeEventListener("clear-completed-items", this.clearCompletedItems); + } + + routeChange(route) { + const routeName = route.split("/")[1] || "all"; + this.list.updateRoute(routeName); + this.bottombar.updateRoute(routeName); + this.topbar.updateRoute(routeName); + } + + connectedCallback() { + this.update("connected"); + this.addListeners(); + this.router.initRouter(this.routeChange); + this.#isReady = true; + } + + disconnectedCallback() { + this.removeListeners(); + this.#isReady = false; + } +} + +customElements.define("todo-app", TodoApp); + +export default TodoApp; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-app/todo-app.template.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-app/todo-app.template.js new file mode 100644 index 000000000..1a55a8194 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-app/todo-app.template.js @@ -0,0 +1,14 @@ +const template = document.createElement("template"); + +template.id = "todo-app-template"; +template.innerHTML = ` +
    + +
    + +
    + +
    +`; + +export default template; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.component.js new file mode 100644 index 000000000..f5b8896c5 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.component.js @@ -0,0 +1,80 @@ +import template from "./todo-bottombar.template.js"; + +import globalStyles from "../../../node_modules/todomvc-css/dist/global.constructable.js"; +import bottombarStyles from "../../../node_modules/todomvc-css/dist/bottombar.constructable.js"; + +class TodoBottombar extends HTMLElement { + static get observedAttributes() { + return ["total-items", "active-items"]; + } + + constructor() { + super(); + + const node = document.importNode(template.content, true); + this.element = node.querySelector(".bottombar"); + this.clearCompletedButton = node.querySelector(".clear-completed-button"); + this.todoStatus = node.querySelector(".todo-status"); + this.filterLinks = node.querySelectorAll(".filter-link"); + + this.shadow = this.attachShadow({ mode: "open" }); + this.htmlDirection = document.dir || "ltr"; + this.setAttribute("dir", this.htmlDirection); + this.shadow.adoptedStyleSheets = [globalStyles, bottombarStyles]; + this.shadow.append(node); + + this.clearCompletedItems = this.clearCompletedItems.bind(this); + } + + updateDisplay() { + if (parseInt(this["total-items"]) !== 0) + this.element.style.display = "block"; + else + this.element.style.display = "none"; + + this.todoStatus.textContent = `${this["active-items"]} ${this["active-items"] === "1" ? "item" : "items"} left!`; + } + + updateRoute(route) { + this.filterLinks.forEach((link) => { + if (link.dataset.route === route) + link.classList.add("selected"); + else + link.classList.remove("selected"); + }); + } + + clearCompletedItems() { + this.dispatchEvent(new CustomEvent("clear-completed-items")); + } + + addListeners() { + this.clearCompletedButton.addEventListener("click", this.clearCompletedItems); + } + + removeListeners() { + this.clearCompletedButton.removeEventListener("click", this.clearCompletedItems); + } + + attributeChangedCallback(property, oldValue, newValue) { + if (oldValue === newValue) + return; + this[property] = newValue; + + if (this.isConnected) + this.updateDisplay(); + } + + connectedCallback() { + this.updateDisplay(); + this.addListeners(); + } + + disconnectedCallback() { + this.removeListeners(); + } +} + +customElements.define("todo-bottombar", TodoBottombar); + +export default TodoBottombar; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.template.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.template.js new file mode 100644 index 000000000..4f34ca92d --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.template.js @@ -0,0 +1,22 @@ +const template = document.createElement("template"); + +template.id = "todo-bottombar-template"; +template.innerHTML = ` + +`; + +export default template; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-item/todo-item.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-item/todo-item.component.js new file mode 100644 index 000000000..d8bb5e61c --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-item/todo-item.component.js @@ -0,0 +1,184 @@ +import template from "./todo-item.template.js"; +import { useDoubleClick } from "../../hooks/useDoubleClick.js"; +import { useKeyListener } from "../../hooks/useKeyListener.js"; + +import globalStyles from "../../../node_modules/todomvc-css/dist/global.constructable.js"; +import itemStyles from "../../../node_modules/todomvc-css/dist/todo-item.constructable.js"; + +class TodoItem extends HTMLElement { + static get observedAttributes() { + return ["itemid", "itemtitle", "itemcompleted"]; + } + + constructor() { + super(); + + // Renamed this.id to this.itemid and this.title to this.itemtitle. + // When the component assigns to this.id or this.title, this causes the browser's implementation of the existing setters to run, which convert these property sets into internal setAttribute calls. This can have surprising consequences. + // [Issue]: https://github.com/WebKit/Speedometer/issues/313 + this.itemid = ""; + this.itemtitle = "Todo Item"; + this.itemcompleted = "false"; + + const node = document.importNode(template.content, true); + this.item = node.querySelector(".todo-item"); + this.toggleLabel = node.querySelector(".toggle-todo-label"); + this.toggleInput = node.querySelector(".toggle-todo-input"); + this.todoText = node.querySelector(".todo-item-text"); + this.todoButton = node.querySelector(".remove-todo-button"); + this.editLabel = node.querySelector(".edit-todo-label"); + this.editInput = node.querySelector(".edit-todo-input"); + + this.shadow = this.attachShadow({ mode: "open" }); + this.htmlDirection = document.dir || "ltr"; + this.setAttribute("dir", this.htmlDirection); + this.shadow.adoptedStyleSheets = [globalStyles, itemStyles]; + this.shadow.append(node); + + this.keysListeners = []; + + this.updateItem = this.updateItem.bind(this); + this.toggleItem = this.toggleItem.bind(this); + this.removeItem = this.removeItem.bind(this); + this.startEdit = this.startEdit.bind(this); + this.stopEdit = this.stopEdit.bind(this); + this.cancelEdit = this.cancelEdit.bind(this); + + if (window.extraTodoItemCssToAdopt) { + let extraAdoptedStyleSheet = new CSSStyleSheet(); + extraAdoptedStyleSheet.replaceSync(window.extraTodoItemCssToAdopt); + this.shadow.adoptedStyleSheets.push(extraAdoptedStyleSheet); + } + } + + update(...args) { + args.forEach((argument) => { + switch (argument) { + case "itemid": + if (this.itemid !== undefined) + this.item.id = `todo-item-${this.itemid}`; + break; + case "itemtitle": + if (this.itemtitle !== undefined) { + this.todoText.textContent = this.itemtitle; + this.editInput.value = this.itemtitle; + } + break; + case "itemcompleted": + this.toggleInput.checked = this.itemcompleted === "true" ? true : false; + break; + } + }); + } + + startEdit() { + this.item.classList.add("editing"); + this.editInput.value = this.itemtitle; + this.editInput.focus(); + } + + stopEdit() { + this.item.classList.remove("editing"); + } + + cancelEdit() { + this.editInput.blur(); + } + + toggleItem() { + // The todo-list checks the "completed" attribute to filter based on route + // (therefore the completed state needs to already be updated before the check) + this.setAttribute("itemcompleted", this.toggleInput.checked); + + this.dispatchEvent( + new CustomEvent("toggle-item", { + detail: { completed: this.toggleInput.checked }, + bubbles: true, + }) + ); + } + + removeItem() { + // The todo-list keeps a reference to all elements and updates the list on removal. + // (therefore the removal has to happen after the list is updated) + this.dispatchEvent( + new CustomEvent("remove-item", { + detail: { completed: this.togglegetAtInput.checked}, + bubbles: true, + }) + ); + this.remove(); + } + + updateItem(event) { + if (event.target.value !== this.itemtitle) { + if (!event.target.value.length) { + this.removeItem(); + } else { + this.setAttribute("itemtitle", event.target.value); + } + } + + this.cancelEdit(); + } + + addListeners() { + this.toggleInput.addEventListener("change", this.toggleItem); + this.todoText.addEventListener("click", useDoubleClick(this.startEdit, 500)); + this.editInput.addEventListener("blur", this.stopEdit); + this.todoButton.addEventListener("click", this.removeItem); + + this.keysListeners.forEach((listener) => listener.connect()); + } + + removeListeners() { + this.toggleInput.removeEventListener("change", this.toggleItem); + this.todoText.removeEventListener("click", this.startEdit); + this.editInput.removeEventListener("blur", this.stopEdit); + this.todoButton.removeEventListener("click", this.removeItem); + + this.keysListeners.forEach((listener) => listener.disconnect()); + } + + attributeChangedCallback(property, oldValue, newValue) { + if (oldValue === newValue) + return; + this[property] = newValue; + + if (this.isConnected) + this.update(property); + } + + connectedCallback() { + this.update("itemid", "itemtitle", "itemcompleted"); + + this.keysListeners.push( + useKeyListener({ + target: this.editInput, + event: "keyup", + callbacks: { + ["Enter"]: this.updateItem, + ["Escape"]: this.cancelEdit, + }, + }), + useKeyListener({ + target: this.todoText, + event: "keyup", + callbacks: { + [" "]: this.startEdit, // this feels weird + }, + }) + ); + + this.addListeners(); + } + + disconnectedCallback() { + this.removeListeners(); + this.keysListeners = []; + } +} + +customElements.define("todo-item", TodoItem); + +export default TodoItem; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-item/todo-item.template.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-item/todo-item.template.js new file mode 100644 index 000000000..9a67675fd --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-item/todo-item.template.js @@ -0,0 +1,19 @@ +const template = document.createElement("template"); + +template.id = "todo-item-template"; +template.innerHTML = ` +
  • +
    + + + Placeholder Text + +
    +
    + + +
    +
  • +`; + +export default template; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-list/todo-list.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-list/todo-list.component.js new file mode 100644 index 000000000..970c42070 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-list/todo-list.component.js @@ -0,0 +1,120 @@ +import template from "./todo-list.template.js"; +import TodoItem from "../todo-item/todo-item.component.js"; + +import globalStyles from "../../../node_modules/todomvc-css/dist/global.constructable.js"; +import listStyles from "../../../node_modules/todomvc-css/dist/todo-list.constructable.js"; + + +const customListStyles = new CSSStyleSheet(); +customListStyles.replaceSync(` + .todo-list > todo-item { + display: block; + } + + .todo-list[route="completed"] > [itemcompleted="false"] { + display: none; + } + + .todo-list[route="active"] > [itemcompleted="true"] { + display: none; + } +`); + +console.log(customListStyles); + +class TodoList extends HTMLElement { + static get observedAttributes() { + return ["total-items"]; + } + + #route = undefined; + + constructor() { + super(); + + const node = document.importNode(template.content, true); + this.listNode = node.querySelector(".todo-list"); + + this.shadow = this.attachShadow({ mode: "open" }); + this.htmlDirection = document.dir || "ltr"; + this.setAttribute("dir", this.htmlDirection); + this.shadow.adoptedStyleSheets = [globalStyles, listStyles, customListStyles]; + this.shadow.append(node); + this.classList.add("show-priority"); + + if (window.extraTodoListCssToAdopt) { + let extraAdoptedStyleSheet = new CSSStyleSheet(); + extraAdoptedStyleSheet.replaceSync(window.extraTodoListCssToAdopt); + this.shadow.adoptedStyleSheets.push(extraAdoptedStyleSheet); + } + } + + addItem(entry, itemIndex) { + const { id, title, completed } = entry; + const element = new TodoItem(); + + element.setAttribute("itemid", id); + element.setAttribute("itemtitle", title); + element.setAttribute("itemcompleted", completed); + element.setAttribute("data-priority", 4 - (itemIndex % 5)); + + this.listNode.append(element); + } + + addItems(items) { + items.forEach((entry) => this.addItem(entry)); + } + + removeCompletedItems() { + Array.from(this.listNode.children).forEach((element) => { + if (element.itemcompleted === "true") + element.removeItem(); + }); + } + + toggleItems(completed) { + Array.from(this.listNode.children).forEach((element) => { + if (completed && element.itemcompleted === "false") + element.toggleInput.click(); + else if (!completed && element.itemcompleted === "true") + element.toggleInput.click(); + }); + } + + updateStyles() { + if (parseInt(this["total-items"]) !== 0) + this.listNode.style.display = "block"; + else + this.listNode.style.display = "none"; + } + + updateRoute(route) { + this.#route = route; + switch (route) { + case "completed": + this.listNode.setAttribute("route", "completed"); + break; + case "active": + this.listNode.setAttribute("rout", "active"); + break; + default: + this.listNode.setAttribute("route", "all"); + } + } + + attributeChangedCallback(property, oldValue, newValue) { + if (oldValue === newValue) + return; + this[property] = newValue; + if (this.isConnected) + this.updateStyles(); + } + + connectedCallback() { + this.updateStyles(); + } +} + +customElements.define("todo-list", TodoList); + +export default TodoList; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-list/todo-list.template.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-list/todo-list.template.js new file mode 100644 index 000000000..dac383b95 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-list/todo-list.template.js @@ -0,0 +1,8 @@ +const template = document.createElement("template"); + +template.id = "todo-list-template"; +template.innerHTML = ` + +`; + +export default template; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-topbar/todo-topbar.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-topbar/todo-topbar.component.js new file mode 100644 index 000000000..973fb0614 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-topbar/todo-topbar.component.js @@ -0,0 +1,131 @@ +import template from "./todo-topbar.template.js"; +import { useKeyListener } from "../../hooks/useKeyListener.js"; +import { nanoid } from "../../utils/nanoid.js"; + +import globalStyles from "../../../node_modules/todomvc-css/dist/global.constructable.js"; +import topbarStyles from "../../../node_modules/todomvc-css/dist/topbar.constructable.js"; + +class TodoTopbar extends HTMLElement { + static get observedAttributes() { + return ["total-items", "active-items", "completed-items"]; + } + + #route = undefined; + + constructor() { + super(); + + const node = document.importNode(template.content, true); + this.todoInput = node.querySelector("#new-todo"); + this.toggleInput = node.querySelector("#toggle-all"); + this.toggleContainer = node.querySelector(".toggle-all-container"); + + this.shadow = this.attachShadow({ mode: "open" }); + this.htmlDirection = document.dir || "ltr"; + this.setAttribute("dir", this.htmlDirection); + this.shadow.adoptedStyleSheets = [globalStyles, topbarStyles]; + this.shadow.append(node); + + this.keysListeners = []; + + this.toggleAll = this.toggleAll.bind(this); + this.addItem = this.addItem.bind(this); + } + + toggleAll(event) { + this.dispatchEvent( + new CustomEvent("toggle-all", { + detail: { completed: event.target.checked }, + }) + ); + } + + addItem(event) { + if (!event.target.value.length) + return; + + this.dispatchEvent( + new CustomEvent("add-item", { + detail: { + id: nanoid(), + title: event.target.value, + completed: false, + }, + }) + ); + + event.target.value = ""; + } + + updateDisplay() { + if (!parseInt(this["total-items"])) { + this.toggleContainer.style.display = "none"; + return; + } + + this.toggleContainer.style.display = "block"; + + switch (this.#route) { + case "active": + this.toggleInput.checked = false; + this.toggleInput.disabled = !parseInt(this["active-items"]); + break; + case "completed": + this.toggleInput.checked = parseInt(this["completed-items"]); + this.toggleInput.disabled = !parseInt(this["completed-items"]); + break; + default: + this.toggleInput.checked = this["total-items"] === this["completed-items"]; + this.toggleInput.disabled = false; + } + } + + updateRoute(route) { + this.#route = route; + this.updateDisplay(); + } + + addListeners() { + this.toggleInput.addEventListener("change", this.toggleAll); + this.keysListeners.forEach((listener) => listener.connect()); + } + + removeListeners() { + this.toggleInput.removeEventListener("change", this.toggleAll); + this.keysListeners.forEach((listener) => listener.disconnect()); + } + + attributeChangedCallback(property, oldValue, newValue) { + if (oldValue === newValue) + return; + this[property] = newValue; + + if (this.isConnected) + this.updateDisplay(); + } + + connectedCallback() { + this.keysListeners.push( + useKeyListener({ + target: this.todoInput, + event: "keyup", + callbacks: { + ["Enter"]: this.addItem, + }, + }) + ); + + this.updateDisplay(); + this.addListeners(); + this.todoInput.focus(); + } + + disconnectedCallback() { + this.removeListeners(); + this.keysListeners = []; + } +} + +customElements.define("todo-topbar", TodoTopbar); + +export default TodoTopbar; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-topbar/todo-topbar.template.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-topbar/todo-topbar.template.js new file mode 100644 index 000000000..ce323b683 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-topbar/todo-topbar.template.js @@ -0,0 +1,17 @@ +const template = document.createElement("template"); + +template.id = "todo-topbar-template"; +template.innerHTML = ` +
    +
    + + +
    + +
    +`; + +export default template; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/hooks/useDoubleClick.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/hooks/useDoubleClick.js new file mode 100644 index 000000000..a1fe952fe --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/hooks/useDoubleClick.js @@ -0,0 +1,19 @@ +/** + * A simple function to normalize a double-click and a double-tab action. + * There is currently no comparable tab action to dblclick. + * + * @param {Function} fn + * @param {number} delay + * @returns + */ +export function useDoubleClick(fn, delay) { + let last = 0; + return function (...args) { + const now = new Date().getTime(); + const difference = now - last; + if (difference < delay && difference > 0) + fn.apply(this, args); + + last = now; + }; +} diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/hooks/useKeyListener.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/hooks/useKeyListener.js new file mode 100644 index 000000000..453747d54 --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/hooks/useKeyListener.js @@ -0,0 +1,23 @@ +export function useKeyListener(props) { + const { target, event, callbacks } = props; + + function handleEvent(event) { + Object.keys(callbacks).forEach((key) => { + if (event.key === key) + callbacks[key](event); + }); + } + + function connect() { + target.addEventListener(event, handleEvent); + } + + function disconnect() { + target.removeEventListener(event, handleEvent); + } + + return { + connect, + disconnect, + }; +} diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/hooks/useRouter.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/hooks/useRouter.js new file mode 100644 index 000000000..ab1ab618a --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/hooks/useRouter.js @@ -0,0 +1,43 @@ +/** + * Listens for hash change of the url and calls onChange if available. + * + * @param {Function} callback + * @returns Methods to interact with useRouter. + */ +export const useRouter = (callback) => { + let onChange = callback; + let current = ""; + + /** + * Change event handler. + */ + const handleChange = () => { + current = document.location.hash; + /* istanbul ignore else */ + if (onChange) + onChange(document.location.hash); + }; + + /** + * Initializes router and adds listeners. + * + * @param {Function} callback + */ + const initRouter = (callback) => { + onChange = callback; + window.addEventListener("hashchange", handleChange); + window.addEventListener("load", handleChange); + }; + + /** + * Removes listeners + */ + const disableRouter = () => { + window.removeEventListener("hashchange", handleChange); + window.removeEventListener("load", handleChange); + }; + + const getRoute = () => current.split("/").slice(-1)[0]; + + return { initRouter, getRoute, disableRouter }; +}; diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/utils/nanoid.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/utils/nanoid.js new file mode 100644 index 000000000..5df154f1f --- /dev/null +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/utils/nanoid.js @@ -0,0 +1,41 @@ +/* Borrowed from https://github.com/ai/nanoid/blob/3.0.2/non-secure/index.js + +The MIT License (MIT) + +Copyright 2017 Andrey Sitnik + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +// This alphabet uses `A-Za-z0-9_-` symbols. +// The order of characters is optimized for better gzip and brotli compression. +// References to the same file (works both for gzip and brotli): +// `'use`, `andom`, and `rict'` +// References to the brotli default dictionary: +// `-26T`, `1983`, `40px`, `75px`, `bush`, `jack`, `mind`, `very`, and `wolf` +let urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"; + +export function nanoid(size = 21) { + let id = ""; + // A compact alternative for `for (var i = 0; i < step; i++)`. + let i = size; + while (i--) { + // `| 0` is more compact and faster than `Math.floor()`. + id += urlAlphabet[(Math.random() * 64) | 0]; + } + return id; +} From 998d31e4266384b5ed185ea088bf982022c4ee21 Mon Sep 17 00:00:00 2001 From: Luis Pardo Date: Fri, 20 Jun 2025 17:27:15 +0000 Subject: [PATCH 02/13] Add buttons, and move to next page functionality --- .../todo-list/todo-list.component.js | 4 ++ .../components/todo-app/todo-app.component.js | 10 ++++ .../todo-bottombar.component.js | 53 +++++++++++++++++-- .../todo-bottombar/todo-bottombar.template.js | 4 +- .../todo-list/todo-list.component.js | 17 ++++++ 5 files changed, 84 insertions(+), 4 deletions(-) diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-list/todo-list.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-list/todo-list.component.js index bc6d1339d..77d7e0c48 100644 --- a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-list/todo-list.component.js +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/dist/components/todo-list/todo-list.component.js @@ -18,6 +18,10 @@ customListStyles.replaceSync(` .todo-list[route="active"] > [itemcompleted="true"] { display: none; } + + .todo-list > :nth-child(10) ~ todo-item { + display: none; + } `); console.log(customListStyles); diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-app/todo-app.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-app/todo-app.component.js index 7ce108fe9..230eb98a5 100644 --- a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-app/todo-app.component.js +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-app/todo-app.component.js @@ -29,6 +29,7 @@ class TodoApp extends HTMLElement { this.toggleItems = this.toggleItems.bind(this); this.clearCompletedItems = this.clearCompletedItems.bind(this); this.routeChange = this.routeChange.bind(this); + this.moveToNextPage = this.moveToNextPage.bind(this); this.router = useRouter(); } @@ -76,6 +77,13 @@ class TodoApp extends HTMLElement { this.list.removeCompletedItems(); } + moveToNextPage() { + let {totalRemoved, completedRemoved} = this.list.moveToNextPage(); + this.#numberOfItems -= totalRemoved; + this.#numberOfCompletedItems -= completedRemoved; + this.update(); + } + update() { const totalItems = this.#numberOfItems; const completedItems = this.#numberOfCompletedItems; @@ -100,6 +108,7 @@ class TodoApp extends HTMLElement { this.list.listNode.addEventListener("update-item", this.updateItem); this.bottombar.addEventListener("clear-completed-items", this.clearCompletedItems); + this.bottombar.addEventListener("next-page", this.moveToNextPage); } removeListeners() { @@ -111,6 +120,7 @@ class TodoApp extends HTMLElement { this.list.listNode.removeEventListener("update-item", this.updateItem); this.bottombar.removeEventListener("clear-completed-items", this.clearCompletedItems); + this.bottombar.removeEventListener("next-page", this.moveToNextPage); } routeChange(route) { diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.component.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.component.js index f5b8896c5..01f18756d 100644 --- a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.component.js +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.component.js @@ -3,6 +3,38 @@ import template from "./todo-bottombar.template.js"; import globalStyles from "../../../node_modules/todomvc-css/dist/global.constructable.js"; import bottombarStyles from "../../../node_modules/todomvc-css/dist/bottombar.constructable.js"; +const additionalStyles = new CSSStyleSheet(); +additionalStyles.replaceSync(` + + .clear-completed-button, .clear-completed-button:active, + .todo-status, + .filter-list + { + position: unset; + transform: unset; + } + + .bottombar { + display: grid; + grid-template-columns: repeat(7, 1fr); + align-items: center; + justify-items: center; + } + + .bottombar > * { + grid-column: span 1; + } + + .filter-list { + grid-column: span 3; + } + + .bottombar.display-none { + display: none; + } +`); + + class TodoBottombar extends HTMLElement { static get observedAttributes() { return ["total-items", "active-items"]; @@ -20,17 +52,19 @@ class TodoBottombar extends HTMLElement { this.shadow = this.attachShadow({ mode: "open" }); this.htmlDirection = document.dir || "ltr"; this.setAttribute("dir", this.htmlDirection); - this.shadow.adoptedStyleSheets = [globalStyles, bottombarStyles]; + this.shadow.adoptedStyleSheets = [globalStyles, bottombarStyles, additionalStyles]; this.shadow.append(node); this.clearCompletedItems = this.clearCompletedItems.bind(this); + this.MoveToNextPage = this.MoveToNextPage.bind(this); + this.MoveToPreviousPage = this.MoveToPreviousPage.bind(this); } updateDisplay() { if (parseInt(this["total-items"]) !== 0) - this.element.style.display = "block"; + this.element.classList.remove("display-none"); else - this.element.style.display = "none"; + this.element.classList.add("display-none"); this.todoStatus.textContent = `${this["active-items"]} ${this["active-items"] === "1" ? "item" : "items"} left!`; } @@ -47,13 +81,26 @@ class TodoBottombar extends HTMLElement { clearCompletedItems() { this.dispatchEvent(new CustomEvent("clear-completed-items")); } + + MoveToNextPage() { + console.log("Moving to next page"); + this.dispatchEvent(new CustomEvent("next-page")); + } + + MoveToPreviousPage() { + this.dispatchEvent(new CustomEvent("previous-page")); + } addListeners() { this.clearCompletedButton.addEventListener("click", this.clearCompletedItems); + this.element.querySelector(".next-page-button").addEventListener("click", this.MoveToNextPage); + this.element.querySelector(".previous-page-button").addEventListener("click", this.MoveToPreviousPage); } removeListeners() { this.clearCompletedButton.removeEventListener("click", this.clearCompletedItems); + this.getElementById("next-page-button").removeEventListener("click", this.MoveToNextPage); + this.getElementById("previous-page-button").removeEventListener("click", this.MoveToPreviousPage); } attributeChangedCallback(property, oldValue, newValue) { diff --git a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.template.js b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.template.js index 4f34ca92d..df4e21bfc 100644 --- a/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.template.js +++ b/resources/todomvc/vanilla-examples/javascript-wc-indexeddb/src/components/todo-bottombar/todo-bottombar.template.js @@ -2,7 +2,7 @@ const template = document.createElement("template"); template.id = "todo-bottombar-template"; template.innerHTML = ` -