{"id":16229239,"url":"https://github.com/morris/exdom","last_synced_at":"2025-03-19T13:31:39.097Z","repository":{"id":57231654,"uuid":"168724483","full_name":"morris/exdom","owner":"morris","description":"Essential DOM utilities","archived":false,"fork":false,"pushed_at":"2025-02-15T19:30:57.000Z","size":763,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-17T07:44:07.557Z","etag":null,"topics":["dom"],"latest_commit_sha":null,"homepage":"https://morris.github.io/exdom","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/morris.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-02-01T16:13:39.000Z","updated_at":"2025-03-10T15:24:20.000Z","dependencies_parsed_at":"2024-10-27T20:36:21.421Z","dependency_job_id":"dd7570cb-96f2-4da2-835d-3e4ea7c37c81","html_url":"https://github.com/morris/exdom","commit_stats":{"total_commits":52,"total_committers":1,"mean_commits":52.0,"dds":0.0,"last_synced_commit":"e3031f8fe30f29f100da738a39835d6872dfed07"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morris%2Fexdom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morris%2Fexdom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morris%2Fexdom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morris%2Fexdom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/morris","download_url":"https://codeload.github.com/morris/exdom/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244434860,"owners_count":20452273,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dom"],"created_at":"2024-10-10T12:57:45.593Z","updated_at":"2025-03-19T13:31:39.092Z","avatar_url":"https://github.com/morris.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# exdom\n\nEssential DOM utilities.\n\n- Query selector shortcuts\n- Soft HTML, text, attribute, and input value setters\n- Reconciliation\n- Type-safe DOM event management\n\nInspired by learnings from\n[VANILLA TODO](https://github.com/morris/vanilla-todo), a case study on viable\ntechniques for vanilla web development.\n\n## Installation\n\nVia npm:\n\n```sh\nnpm install exdom\n```\n\nWhen not using a bundler, exdom can be imported via CDN:\n\n```ts\n// src/exdom.js or src/exdom.ts\nexport * from 'https://cdn.jsdelivr.net/npm/exdom@2/dist/exdom.min.js';\n\n// src/exdom.d.ts\ndeclare module 'https://cdn.jsdelivr.net/npm/exdom@2/dist/exdom.min.js' {\n  export * from 'exdom';\n}\n\n// src/app.js or src/app.ts\nimport { ... } from './exdom.js';\n\n// ...\n```\n\n## Usage\n\nA very basic to-do app with exdom could look like this (in TypeScript, but\nJavaScript is equally possible) and covers most of exdom's\n[functions](https://morris.github.io/exdom):\n\n```ts\nimport {\n  CustomEventElement,\n  qsr,\n  reconcile,\n  requestAnimationFrameOnce,\n  setText,\n  setValue,\n  TypedCustomEvent,\n} from 'exdom';\n\n// Type-safe custom DOM event details\n\ninterface TodoAppEvents {\n  addTodoItem: string;\n  toggleTodoItem: string;\n  todoItem: TodoItemData;\n}\n\ninterface TodoItemData {\n  id: string;\n  label: string;\n  done: boolean;\n}\n\n// Component functions\n\nfunction TodoApp(el: CustomEventElement\u003cTodoAppEvents\u003e) {\n  // Base HTML\n\n  el.innerHTML = /* html */ `\n    \u003ch1\u003eTo-Do\u003c/h1\u003e\n    \u003cp\u003e\n      \u003cinput type=\"text\" name=\"label\"\u003e\n      \u003cbutton type=\"button\" class=\"add\"\u003eAdd\u003c/button\u003e\n    \u003c/p\u003e\n    \u003cul class=\"items\"\u003e\u003c/ul\u003e\n  `;\n\n  // Initial data\n\n  let items: TodoItemData[] = [\n    {\n      id: crypto.randomUUID(),\n      label: 'Hello, world!',\n      done: false,\n    },\n  ];\n\n  // Idempotent update\n\n  function update() {\n    // Reconcile todo item components with item list\n    reconcile({\n      container: qsr(el, '.items'),\n      items,\n      create: () =\u003e TodoItem(document.createElement('li')),\n      update: (el, item) =\u003e\n        el.dispatchEvent(new TypedCustomEvent('todoItem', { detail: item })),\n    });\n  }\n\n  // Action handlers\n\n  el.addEventListener('addTodoItem', (e) =\u003e {\n    const newItem = {\n      id: crypto.randomUUID(),\n      label: e.detail,\n      done: false,\n    };\n\n    items = [...items, newItem];\n\n    requestAnimationFrameOnce(update);\n  });\n\n  el.addEventListener('toggleTodoItem', (e) =\u003e {\n    items = items.map((item) =\u003e\n      item.id === e.detail ? { ...item, done: !item.done } : item,\n    );\n\n    requestAnimationFrameOnce(update);\n  });\n\n  // UI Events\n\n  const labelInput = qsr\u003cHTMLInputElement\u003e(el, '[name=label]');\n\n  qsr(el, '.add').addEventListener('click', () =\u003e {\n    el.dispatchEvent(\n      new TypedCustomEvent('addTodoItem', {\n        detail: labelInput.value,\n        bubbles: true,\n      }),\n    );\n    setValue(labelInput, '');\n  });\n\n  update(); // Initial update\n\n  return el;\n}\n\nfunction TodoItem(el: CustomEventElement\u003cTodoAppEvents\u003e) {\n  el.innerHTML = /* html */ `\n    \u003cinput type=\"checkbox\"\u003e\n    \u003clabel\u003e\u003c/label\u003e\n  `;\n\n  let data: TodoItemData;\n\n  function update() {\n    setText(qsr(el, 'label'), data.label);\n    setValue(qsr(el, '[type=checkbox]'), data.done);\n  }\n\n  el.addEventListener('todoItem', (e) =\u003e {\n    data = e.detail;\n    requestAnimationFrameOnce(update);\n  });\n\n  qsr(el, '[type=checkbox]').addEventListener('click', () =\u003e {\n    el.dispatchEvent(\n      new TypedCustomEvent('toggleTodoItem', {\n        detail: data.id,\n        bubbles: true,\n      }),\n    );\n  });\n\n  return el;\n}\n\n// Mount to document\n\nTodoApp(qsr(document, '#app'));\n```\n\nSee also **[API reference](https://morris.github.io/exdom)**\n\n## Tests\n\n- `npx playwright install` (once)\n- `npm test`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorris%2Fexdom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmorris%2Fexdom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorris%2Fexdom/lists"}