{"id":13474663,"url":"https://github.com/tricinel/highlight-words","last_synced_at":"2025-04-05T09:10:29.132Z","repository":{"id":38420126,"uuid":"201232504","full_name":"tricinel/highlight-words","owner":"tricinel","description":"Split a piece text into multiple chunks based on a search query, allowing you to highlight the matches afterwards.","archived":false,"fork":false,"pushed_at":"2024-09-03T08:36:21.000Z","size":761,"stargazers_count":97,"open_issues_count":0,"forks_count":7,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-29T08:09:53.197Z","etag":null,"topics":["javascript","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tricinel.png","metadata":{"files":{"readme":"Readme.md","changelog":"Changelog.md","contributing":"Contributing.md","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-08-08T10:11:16.000Z","updated_at":"2025-01-06T16:01:57.000Z","dependencies_parsed_at":"2024-01-16T07:23:42.026Z","dependency_job_id":"54221c7c-a703-43de-9b67-f5624c37582f","html_url":"https://github.com/tricinel/highlight-words","commit_stats":{"total_commits":69,"total_committers":3,"mean_commits":23.0,"dds":0.2753623188405797,"last_synced_commit":"d84420e9c429fd1ac71eea8361d714e1bcd0a8d8"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tricinel%2Fhighlight-words","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tricinel%2Fhighlight-words/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tricinel%2Fhighlight-words/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tricinel%2Fhighlight-words/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tricinel","download_url":"https://codeload.github.com/tricinel/highlight-words/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247312085,"owners_count":20918344,"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":["javascript","typescript"],"created_at":"2024-07-31T16:01:13.899Z","updated_at":"2025-04-05T09:10:29.093Z","avatar_url":"https://github.com/tricinel.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"Give it a piece of text and a search query, and it splits it into chunks separating matches from non-matches, allowing you to highlight the matches, visually or otherwise, in your app.\n\n![Build status][build-status-badge] ![Node Version][node-version-badge]\n![Npm version][npm-version-badge]\n[![Npm downloads][npm-downloads-badge]][highlight-words-npm] ![License][license-badge]\n\n## Installation\n\n```\npnpm add highlight-words\n```\n\n```\nnpm i --save highlight-words\n```\n\n## Usage\n\nTo use it, give it the body of text to search in and the query to search for.\n\n```js\nimport highlightWords from 'highlight-words';\n\nconst chunks = highlightWords({\n  text: 'The quick brown fox jumped over the lazy dog',\n  query: 'over'\n});\n\nconsole.log(chunks);\n/*\n[\n  {\n    key: '62acb210-76dd-4682-b948-8d359a966dcb'\n    text: 'The brown fox jumped ',\n    match: false\n  },\n  {\n    key: '69779adf-6d7c-45ec-ae9b-49d0cb292e28';\n    text: 'over',\n    match: true\n  },\n  {\n    key: '46c5b7a0-5414-47c5-81ba-2496f33fe2f6';\n    text: ' the lazy dog',\n    match: false\n  }\n]\n*/\n```\n\n[Play with this example on Code Sandbox.][sandbox-vanilla]\n\nAlternatively, you can also use a regular expression as part of the query string to search for.\n```js\nconst chunks = highlightWords({\n  text: 'The quick brown fox jumped over the lazy dog',\n  query: '/(quick|brown|fox)/'\n});\n\nconsole.log(chunks);\n/*\n[\n  {\n    key: '62acb210-76dd-4682-b948-8d359a966dcb'\n    text: 'The ',\n    match: false\n  },\n  {\n    key: '69779adf-6d7c-45ec-ae9b-49d0cb292e28'\n    text: 'quick',\n    match: true\n  },\n  {\n    key: 'a72a6578-9111-4dca-a30d-676aaf7964c0'\n    text: ' ',\n    match: false\n  },\n  {\n    key: '24301852-f38b-4b54-b1fe-d8a82f47c49e'\n    text: 'brown',\n    match: true\n  },\n  {\n    key: 'a72a6578-9111-4dca-a30d-676aaf7964c0'\n    text: ' ',\n    match: false\n  },\n  {\n    key: '797b3bab-9244-451c-b246-4b03025b0691'\n    text: 'fox',\n    match: true\n  },\n  {\n    key: 'c86dbb8a-3e5f-4c05-a48c-684abbb517e8'\n    text: ' jumped over the lazy dog',\n    match: false\n  }\n]\n*/\n```\n\n\u003e If using a regular expresssion, you can choose to either enclose the pattern in a string or not. In both cases, you need to format the regular expression properly, i.e. enclose the pattern between slashes, **and** you need to create capture groups, so that the match is remembered for later use.\n\u003e\n\u003e **Valid regular expression usage**\n\u003e ```\n\u003e query: '/(quick|brown|fox)/'\n\u003e query: /(quick|brown|fox)/\n\u003e ```\n\u003e\n\u003e**Invalid regular expression usage**\n\u003e ```\n\u003e query: '(quick|brown|fox)'\n\u003e query: '/quick|brown|fox/'\n\u003e query: /quick|brown|fox/\n\u003e ```\n\n### Options\n\nYou can add a few options for the highlighter.\n\n- _clipBy_. If you want to clip the occurences that are not a match and display elipses around them. This can help to provide context around your matches.\n- _matchExactly_. By default, the highlighter will look for occurences of either words in your query. For example, if you have `brown fox` as your `query`, the highlighter will consider both `brown` and `fox` as separate matches. If set to `true`, then only the exact match of `brown fox` will be considered.\n\n\u003e _matchExactly_ will have no effect if you're using a regular expression as your query, since you will have full control over the query in that case.\n\n### Arguments\n\n`highlightWords` accepts an object as an argument, with the following structure:\n\n| Property       | Type    | Required? | Description                                                   | Default |\n| :------------- | :------ | :-------: | :------------------------------------------------------------ | :------ |\n| `text`         | String  |     ✓     | The body of text you want to search in.                       | `empty` |\n| `query`        | String  |     ✓     | The word(s) or regular expression you want to search for.     | `empty` |\n| `clipBy`       | Number  |           | How many words do you want to clip from the non matches.      | `null`  |\n| `matchExactly` | Boolean |           | Should we match the complete query or individual words in it? | `false` |\n\n### What it returns\n\n`highlightWords` returns an array of objects, each object with the following structure:\n\n| Property | Type    | Description                                                                                             |\n| :------- | :------ | :------------------------------------------------------------------------------------------------------ |\n| `key`    | String  | A unique key to help you when you want to use the chunks in a map function, e.g. with React or Angular. |\n| `text`   | String  | The word or words in the chunk.                                                                         |\n| `match`  | Boolean | Is this chunk a match for your search?                                                                  |\n\n## Use it with the framework of your choice\n\nBy default, the highlighter won't assume any HTML element to wrap matched text, so you can do whatever you want with the matches.\n\n### React\n\n```jsx\n\u003cp\u003e\n  {chunks.map(({ text, match, key }) =\u003e\n    match ? (\n      \u003cspan className=\"highlight\" key={key}\u003e\n        {text}\n      \u003c/span\u003e\n    ) : (\n      \u003cspan key={key}\u003e{text}\u003c/span\u003e\n    )\n  )}\n  };\n\u003c/p\u003e\n```\n\n[Play with the React example on Code Sandbox.][sandbox-react]\n\n### Angular\n\n```html\n\u003cp\u003e\n  \u003cspan *ngFor=\"let chunk of chunks; trackBy: key\" class=\"highlight\"\u003e\n    {{ chunk.text }}\n  \u003c/span\u003e\n\u003c/p\u003e\n```\n\n[Play with the Angular example on Code Sandbox.][sandbox-angular]\n\n### Vue\n\n```html\n\u003cp\u003e\n  \u003cspan\n    v-for=\"chunk in chunks\"\n    :key=\"chunk.key\"\n    v-bind:class=\"{ active: chunk.match }\"\n  \u003e\n    {{ chunk.text }}\n  \u003c/span\u003e\n\u003c/p\u003e\n```\n\n[Play with the Vue example on Code Sandbox.][sandbox-vue]\n\n### Svelte\n\n```html\n\u003cp\u003e\n  {#each chunks as chunk (chunk.key)}\n  \u003cspan class:highlight=\"{chunk.match}\"\u003e{chunk.text}\u003c/span\u003e\n  {/each}\n\u003c/p\u003e\n```\n\n[Play with the Svelte example on Code Sandbox.][sandbox-svelte]\n\n## A note on accessibility\n\nWhen we are splitting a piece of text into multiple chunks for the purpose of styling each chunk differently, and then using said chunks **instead** of the original text, we are doing a disservice to our users who might rely on a screen reader. This is because some screen readers will read out the chunks of text individually rather than in one continous flow. For example, if we were to split the text _Eeeh! Help me!_, `highlight-words` will return to us several chunks. We then might decide to wrap each chunk's text in a `span`, like so:\n\n```html\n\u003cp\u003e\n  \u003cspan\u003eE\u003c/span\u003e\n  \u003cspan\u003ee\u003c/span\u003e\n  \u003cspan\u003ee\u003c/span\u003e\n  \u003cspan\u003eh! H\u003c/span\u003e\n  \u003cspan\u003ee\u003c/span\u003e\n  \u003cspan\u003elp m\u003c/span\u003e\n  \u003cspan\u003ee\u003c/span\u003e\n  \u003cspan\u003e!\u003c/span\u003e\n\u003c/p\u003e\n```\n\nSome screen readers will announce each letter **e** individually. Not ideal!\n\nLet's make it accessible by using aria attributes to allow screen readers to correctly announce our text.\n\n```html\n\u003cp aria-label=\"Eeeh! Help me!\"\u003e\n  \u003cspan aria-hidden=\"true\"\u003eE\u003c/span\u003e\n  \u003cspan aria-hidden=\"true\"\u003ee\u003c/span\u003e\n  \u003cspan aria-hidden=\"true\"\u003ee\u003c/span\u003e\n  \u003cspan aria-hidden=\"true\"\u003eh! H\u003c/span\u003e\n  \u003cspan aria-hidden=\"true\"\u003ee\u003c/span\u003e\n  \u003cspan aria-hidden=\"true\"\u003elp m\u003c/span\u003e\n  \u003cspan aria-hidden=\"true\"\u003ee\u003c/span\u003e\n  \u003cspan aria-hidden=\"true\"\u003e!\u003c/span\u003e\n\u003c/p\u003e\n```\n\nor, for less repetition:\n\n```html\n\u003cp aria-label=\"Eeeh! Help me!\"\u003e\n  \u003cspan aria-hidden=\"true\"\u003e\n    \u003cspan\u003eE\u003c/span\u003e\n    \u003cspan\u003ee\u003c/span\u003e\n    \u003cspan\u003ee\u003c/span\u003e\n    \u003cspan\u003eh! H\u003c/span\u003e\n    \u003cspan\u003ee\u003c/span\u003e\n    \u003cspan\u003elp m\u003c/span\u003e\n    \u003cspan\u003ee\u003c/span\u003e\n    \u003cspan\u003e!\u003c/span\u003e\n  \u003c/span\u003e\n\u003c/p\u003e\n```\n\nFor a much better write-up than I could put together, have a read of [Michelle Barker's How to Accessibly Split Text](https://css-irl.info/how-to-accessibly-split-text).\n\n## License\n\nMIT License - fork, modify and use however you want.\n\n[node-version-badge]: https://img.shields.io/node/v/highlight-words.svg?style=flat-square\n[license-badge]: https://img.shields.io/npm/l/highlight-words.svg?style=flat-square\n[npm-version-badge]: https://img.shields.io/npm/v/highlight-words.svg?style=flat-square\n[highlight-words-npm]: https://www.npmjs.com/package/highlight-words\n[npm-downloads-badge]: https://img.shields.io/npm/dt/highlight-words.svg?style=flat-square\n[build-status-badge]: https://img.shields.io/circleci/build/github/tricinel/cuddy?label=circleci\u0026style=flat-square\n[sandbox-react]: https://codesandbox.io/s/highlight-words-react-1h7qw\n[sandbox-angular]: https://codesandbox.io/s/highlight-words-angular-xpp46\n[sandbox-vue]: https://codesandbox.io/s/highlight-words-vue-zopni\n[sandbox-svelte]: https://codesandbox.io/s/highlight-words-svelte-ld807\n[sandbox-vanilla]: https://codesandbox.io/s/highlight-words-vanilla-ijvkg\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftricinel%2Fhighlight-words","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftricinel%2Fhighlight-words","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftricinel%2Fhighlight-words/lists"}