{"id":47730051,"url":"https://github.com/quentinroy/word-cloud","last_synced_at":"2026-04-09T15:01:12.620Z","repository":{"id":347852962,"uuid":"1193737465","full_name":"QuentinRoy/word-cloud","owner":"QuentinRoy","description":null,"archived":false,"fork":false,"pushed_at":"2026-04-02T15:46:46.000Z","size":165,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-03T06:39:36.104Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://quentinroy.github.io/word-cloud/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/QuentinRoy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-27T14:28:45.000Z","updated_at":"2026-04-02T15:46:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/QuentinRoy/word-cloud","commit_stats":null,"previous_names":["quentinroy/word-cloud"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/QuentinRoy/word-cloud","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QuentinRoy%2Fword-cloud","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QuentinRoy%2Fword-cloud/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QuentinRoy%2Fword-cloud/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QuentinRoy%2Fword-cloud/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/QuentinRoy","download_url":"https://codeload.github.com/QuentinRoy/word-cloud/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QuentinRoy%2Fword-cloud/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31513382,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2026-04-02T21:21:13.751Z","updated_at":"2026-04-07T13:01:20.053Z","avatar_url":"https://github.com/QuentinRoy.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# word-cloud\n\n[![Test](https://github.com/QuentinRoy/word-cloud/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/QuentinRoy/word-cloud/actions/workflows/test.yml)\n[![npm version](https://img.shields.io/npm/v/%40quentinroy%2Fword-cloud?logo=npm)](https://www.npmjs.com/package/@quentinroy/word-cloud)\n\nInteractive word cloud custom element powered by Matter.js. Check out the [demo](https://quentinroy.github.io/word-cloud/)!\n\n## Library\n\nThis package exports the `HTMLWordCloudElement` class, the public event\nclasses, and the `WordHandle` / `WordData` / `WordCloudWordAction` types. It does not auto-register a custom element tag for you.\n\n## Installation\n\n```sh\n# Using npm:\nnpm install @quentinroy/word-cloud\n\n# Using pnpm:\npnpm install @quentinroy/word-cloud\n\n# Using yarn:\nyarn add @quentinroy/word-cloud\n\n# Using deno:\ndeno add npm:@quentinroy/word-cloud\n\n# Using bun:\nbun add @quentinroy/word-cloud\n```\n\n## Register the element\n\nConsumers are expected to register their own custom element tag:\n\n```ts\nimport { HTMLWordCloudElement } from \"@quentinroy/word-cloud\"\n\ncustomElements.define(\"x-word-cloud\", HTMLWordCloudElement)\n```\n\n## Basic usage\n\nThe component fills the size of its host element, so give it an explicit width and height.\n\n```html\n\u003cstyle\u003e\n  x-word-cloud {\n    display: block;\n    width: 100%;\n    height: 70vh;\n  }\n\u003c/style\u003e\n\n\u003cx-word-cloud word-action=\"drag\" has-input\u003e\u003c/x-word-cloud\u003e\n```\n\n```ts\nimport { HTMLWordCloudElement } from \"@quentinroy/word-cloud\"\n\ncustomElements.define(\"x-word-cloud\", HTMLWordCloudElement)\n\nconst wordCloud = document.querySelector(\"x-word-cloud\")\n\nif (!(wordCloud instanceof HTMLWordCloudElement)) {\n  throw new Error(\"x-word-cloud not found\")\n}\n\nwordCloud.setWords([\n  { word: \"TypeScript\", x: 160, y: 120 },\n  { word: \"Web Components\", x: 320, y: 180, checked: true },\n  { word: \"Matter.js\", x: 240, y: 260, angle: 0.15 },\n])\n```\n\n## Interaction Settings\n\nThe element uses three independent attributes:\n\n- `word-action`: controls how words react to user interaction.\n- `has-input`: boolean, controls whether the built-in input form is shown and active.\n- `show-framerate`: boolean, controls whether the framerate display is shown.\n\n\n\nSupported `word-action` values:\n\n- `none`: default, words are passive.\n- `drag`: words can be dragged.\n- `check`: clicking a word toggles its checked state.\n- `delete`: clicking a word removes it.\n\nSet `has-input` to show the built-in input form:\n\n```html\n\u003cx-word-cloud word-action=\"check\" has-input\u003e\u003c/x-word-cloud\u003e\n```\n\n\nEach of these can also be read or set via the corresponding property on the element instance. Instance properties use camelCase instead of kebab-case. For example, the above configuration can be achieved with:\n\n```ts\nwordCloud.wordAction = \"check\"\nwordCloud.hasInput = true\nwordCloud.showFramerate = false\n```\n\n## Public API\n\n### `addWord(options)` → `WordHandle`\n\nAdds a word to the cloud and returns a live [`WordHandle`](#wordhandle) handle.\n\n```ts\nconst entry = wordCloud.addWord({\n  word: \"Custom Element\",\n  x: 200,\n  y: 150,\n  angle: 0,\n  checked: false,\n  velocity: { x: 10, y: -15 },\n  entryAnimation: \"fade\",\n})\n\n// Remove it later (fires word-delete):\nentry.remove()\n```\n\nAdding a word also fires `word-add` with the created `WordHandle`.\n\nSupported options:\n\n- `word`: displayed text.\n- `x`: initial horizontal position in pixels.\n- `y`: initial vertical position in pixels.\n- `angle` _(optional)_: initial rotation in radians. Defaults to `0`.\n- `checked` _(optional)_: initial checked state. Defaults to `false`.\n- `velocity` _(optional)_: initial physics velocity `{ x, y }`.\n- `entryAnimation` _(optional)_: entry animation to run when the word is created. Supported values are `\"fade\"`, `\"chip-fade\"`, and `\"none\"`. Defaults to `\"fade\"`.\n\n### `clear()`\n\nRemoves all words from the cloud.\n\n```ts\nwordCloud.clear()\n```\n\n### `getWords()` → `Iterable\u003cWordHandle\u003e`\n\nReturns live [`WordHandle`](#wordhandle) handles for all words currently in the\ncloud. Each property read reflects the real-time state (position, angle,\nchecked). Useful for persistence:\n\n```ts\nconst snapshot = Array.from(wordCloud.getWords())\n```\n\n### `setWords(words)`\n\nClears the cloud and populates it from an array of [`WordData`](#worddata)\nobjects. Because `WordHandle` is structurally compatible with `WordData`, you can\npass the output of `getWords()` directly:\n\n```ts\nwordCloud.setWords([\n  { word: \"Saved\", x: 120, y: 100, angle: 0, checked: false },\n  { word: \"State\", x: 280, y: 220, angle: 0.2, checked: true },\n])\n\n// Restore a previously obtained snapshot:\nwordCloud.setWords(Array.from(wordCloud.getWords()))\n```\n\n## WordHandle\n\nA `WordHandle` is a live handle to a word in the cloud, returned by `addWord`\nand `getWords`. Its properties are always up to date — they read directly from\nthe underlying physics body and DOM element.\n\n| Property / method | Description                                              |\n| ----------------- | -------------------------------------------------------- |\n| `entry.word`      | The displayed text, readable and writable.               |\n| `entry.x`         | Current horizontal center position in pixels.            |\n| `entry.y`         | Current vertical center position in pixels.              |\n| `entry.angle`     | Current rotation in radians.                             |\n| `entry.checked`   | Checked state — readable and writable.                   |\n| `entry.remove()`  | Removes the word from the cloud and fires `word-delete`. |\n\n```ts\nconst entry = wordCloud.addWord({ word: \"Hello\", x: 100, y: 100 })\n\n// Read live state:\nconsole.log(entry.x, entry.y, entry.checked)\n\n// Rename the word (fires word-value-change):\nentry.word = \"Hello again\"\n\n// Toggle checked programmatically (fires word-checked-change):\nentry.checked = !entry.checked\n\n// Remove it:\nentry.remove()\n```\n\n## WordData\n\nPlain serializable object describing a word. Accepted by `addWord` and\n`setWords`. `WordHandle` is structurally compatible with `WordData`, so handles\nobtained from `getWords()` can be passed directly to `setWords()`.\n\n```ts\ninterface WordData {\n  word: string\n  x: number\n  y: number\n  angle?: number\n  checked?: boolean\n}\n```\n\n## Persisting state\n\n```ts\n// Save\nconst saved = Array.from(wordCloud.getWords(), ({ word, x, y, angle, checked }) =\u003e {\n  return { word, x, y, angle, checked }\n})\nlocalStorage.setItem(\"words\", JSON.stringify(saved))\n\n// Restore\nconst saved = JSON.parse(localStorage.getItem(\"words\") ?? \"[]\")\nwordCloud.setWords(saved)\n```\n\n## Events\n\n`HTMLWordCloudElement` dispatches the following bubbling events:\n\n- **`word-add`** — fired when a word is added to the cloud, including through\n  `addWord()`, `setWords()`, or the built-in input form.\n- **`word-value-change`** — fired when a word's text changes, including\n  programmatic assignment to `handle.word`.\n- **`word-checked-change`** — fired when a word's checked state changes (user\n  interaction while `wordAction` is `check`, or programmatic assignment to\n  `handle.checked`).\n- **`word-delete`** — fired when the user deletes a word while `wordAction` is\n  `delete`,\n  just before the word is removed.\n- **`word-action-change`** — fired when the element `wordAction` changes.\n  Includes `wordAction` and `oldWordAction`.\n- **`has-input-change`** — fired when the element `hasInput` setting changes.\n  Includes `hasInput` and `oldHasInput`.\n\nThe word-specific events carry a `handle` property: a live\n[`WordHandle`](#wordhandle) for the affected word. The setting-change events\ninstead carry their old and new values.\n\nListen using the string literal or the static `.type` property of the event classes:\n\n```ts\nwordCloud.addEventListener(\"word-add\", (event) =\u003e {\n  console.log(`added word: \"${event.handle.word}\" at ${event.handle.x}, ${event.handle.y}`)\n})\n\nwordCloud.addEventListener(\"word-value-change\", (event) =\u003e {\n  console.log(`renamed word: \"${event.oldValue}\" -\u003e \"${event.value}\"`)\n})\n\nwordCloud.addEventListener(\"word-checked-change\", (event) =\u003e {\n  console.log(`\"${event.handle.word}\" checked: ${event.checked}`)\n})\n\nwordCloud.addEventListener(\"word-delete\", (event) =\u003e {\n  console.log(`deleted word: \"${event.handle.word}\"`)\n})\n\nwordCloud.addEventListener(\"word-action-change\", (event) =\u003e {\n  console.log(`word action: ${event.oldWordAction} -\u003e ${event.wordAction}`)\n})\n\nwordCloud.addEventListener(\"has-input-change\", (event) =\u003e {\n  console.log(`has-input: ${event.oldHasInput} -\u003e ${event.hasInput}`)\n})\n```\n\n## Styling\n\nThe component exposes CSS custom properties on the host. Example:\n\n```css\nx-word-cloud {\n  --font-family: \"Georgia\", serif;\n  --font-size: 1.25rem;\n  --input-padding-y: 0.4rem;\n  --input-padding-x: 1rem;\n  --word-padding-y: 0.35rem;\n  --word-padding-x: 0.9rem;\n  --line-width: 3px;\n  --word-text-color: #1f2937;\n  --word-background-color: #f3f4f6;\n  --word-border-color: #d1d5db;\n  --word-checked-text-color: #6b7280;\n  --word-checked-background-color: #e5e7eb;\n  --word-delete-hover-text-color: #991b1b;\n  --word-delete-hover-background-color: #fee2e2;\n  --word-dragged-background-color: #dbeafe;\n  --word-dragged-border-color: #bfdbfe;\n  --word-dragged-text-color: #1d4ed8;\n  --word-dragged-shadow-blur: 8px;\n  --word-dragged-shadow-color: rgba(0, 0, 0, 0.15);\n  --word-dragged-scale-factor: 1.05;\n  --word-dragged-scaling-duration: 80ms;\n  --input-background-color: #ffffff;\n  --input-text-color: #111827;\n  --input-border-color: #9ca3af;\n  --input-hover-text-color: #111827;\n  --input-hover-border-color: #6b7280;\n  --input-hover-background-color: #f9fafb;\n  --input-hover-shadow-color: transparent;\n  --input-focus-text-color: #0f172a;\n  --input-focus-border-color: #2563eb;\n  --input-focus-background-color: #eff6ff;\n  --input-focus-shadow-color: #93c5fd;\n  --word-focus-outline-color: #2563eb;\n  --fast-animation: 50ms;\n  --slow-animation: 150ms;\n  --extra-slow-animation: 1s;\n  --word-chip-fade-duration: 1s;\n  --word-fade-in-duration: 0.5s;\n  --word-fade-out-duration: 0.5s;\n  --word-state-transition-duration: 150ms;\n  --input-state-transition-duration: 150ms;\n  width: 100%;\n  height: 70vh;\n}\n```\n\nSupported variables:\n\n| Variable                               | Default                           | Used for                                                         |\n| -------------------------------------- | --------------------------------- | ---------------------------------------------------------------- |\n| `--space-s`                            | `0.5rem`                          | Shared compact spacing token used by the default padding vars.   |\n| `--space-m`                            | `1rem`                            | Shared roomy spacing token used by the default padding vars.     |\n| `--input-padding-y`                    | `var(--space-s)`                  | Input vertical padding.                                          |\n| `--input-padding-x`                    | `var(--space-m)`                  | Input horizontal padding.                                        |\n| `--word-padding-y`                     | `var(--space-s)`                  | Word vertical padding.                                           |\n| `--word-padding-x`                     | `var(--space-m)`                  | Word horizontal padding.                                         |\n| `--fast-animation`                     | `50ms`                            | Shared fast timing token used by default animation durations.    |\n| `--slow-animation`                     | `150ms`                           | Shared medium timing token used by default animation durations.  |\n| `--extra-slow-animation`               | `1s`                              | Shared long timing token used by the chip fade animation.        |\n| `--line-width`                         | `2px`                             | Border width and strike line thickness.                          |\n| `--font-size`                          | `1.5rem`                          | Input and word font size.                                        |\n| `--font-family`                        | `Arial`                           | Input and word font family.                                      |\n| `--input-text-color`                   | `black`                           | Input text color.                                                |\n| `--input-background-color`             | `hwb(0 93% 7%)`                   | Input background while the built-in input is enabled.            |\n| `--input-border-color`                 | `hwb(0 27% 73%)`                  | Input border color.                                              |\n| `--input-hover-text-color`             | `var(--input-text-color)`         | Input text color while hovered.                                  |\n| `--input-hover-border-color`           | `hwb(0 20% 66%)`                  | Input border color while hovered.                                |\n| `--input-hover-background-color`       | `hwb(0 96% 4%)`                   | Input background while hovered.                                  |\n| `--input-hover-shadow-color`           | `transparent`                     | Input hover drop-shadow color.                                   |\n| `--input-focus-text-color`             | `hwb(212 2% 88%)`                 | Input text color while focused.                                  |\n| `--input-focus-border-color`           | `hwb(212 16% 22%)`                | Input border and default word focus outline color while focused. |\n| `--input-focus-shadow-color`           | `hwb(212 76% 0%)`                 | Input focus drop-shadow color.                                   |\n| `--input-focus-background-color`       | `hwb(212 95% 0%)`                 | Input background while focused.                                  |\n| `--word-focus-outline-color`           | `var(--input-focus-border-color)` | Keyboard focus outline for words.                                |\n| `--word-text-color`                    | `hwb(276 2% 80%)`                 | Default word text color.                                         |\n| `--word-background-color`              | `hwb(276 96% 0%)`                 | Default word background.                                         |\n| `--word-border-color`                  | `var(--word-background-color)`    | Default word border color.                                       |\n| `--word-delete-hover-text-color`       | `hwb(357 45% 11%)`                | Word text color on delete hover.                                 |\n| `--word-delete-hover-background-color` | `hwb(351 99% 0%)`                 | Word background and border on delete hover.                      |\n| `--word-checked-text-color`            | `hwb(276 54% 31%)`                | Checked word text color.                                         |\n| `--word-checked-background-color`      | `hwb(276 98% 0%)`                 | Checked word background and border color.                        |\n| `--word-checked-hover-text-color`      | `hwb(276 21% 21%)`                | Word text color while hovered in check mode.                     |\n| `--word-dragged-background-color`      | `hwb(212 90% 0%)`                 | Dragged word background.                                         |\n| `--word-dragged-border-color`          | `hwb(212 76% 0%)`                 | Dragged word border.                                             |\n| `--word-dragged-text-color`            | `hwb(211 5% 70%)`                 | Dragged word text color.                                         |\n| `--word-dragged-shadow-blur`           | `5px`                             | Blur radius of the drop-shadow on a dragged word.                |\n| `--word-dragged-shadow-color`          | `hwb(0 0% 100% / 0.05)`           | Drop-shadow color on a dragged word.                             |\n| `--word-dragged-scale-factor`          | `1.1`                             | Scale applied to a word while it is being dragged.               |\n| `--word-dragged-scaling-duration`      | `var(--fast-animation)`           | Transition duration for the drag scale-up / scale-down effect.   |\n| `--word-chip-fade-duration`            | `var(--extra-slow-animation)`     | Chip color fade duration for words created with `\"chip-fade\"`.   |\n| `--word-fade-in-duration`              | `var(--slow-animation)`           | Opacity fade-in duration for newly created words.                |\n| `--word-fade-out-duration`             | `var(--slow-animation)`           | Opacity fade-out duration for deleted words.                     |\n| `--word-state-transition-duration`     | `var(--slow-animation)`           | Checked and hover state transition duration for words.           |\n| `--input-state-transition-duration`    | `var(--slow-animation)`           | Hover and focus transition duration for the built-in input.      |\n\n## Notes\n\n- The library exports constructors and types, not a pre-registered tag name.\n- The host element needs a real size; if its height is `0`, nothing useful will render.\n- Words are positioned using the host element’s content box, so restoring saved coordinates works best when the element has a stable size.\n\n## Local demo\n\n```sh\npnpm install\npnpm dev\n```\n\nThe demo lives in `demo/` and is served by `index.html` during local development.\n\n## Build\n\n```sh\npnpm build\n```\n\nThis produces the publishable library in `dist/`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquentinroy%2Fword-cloud","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fquentinroy%2Fword-cloud","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquentinroy%2Fword-cloud/lists"}