{"id":25936180,"url":"https://github.com/untemps/svelte-readotron","last_synced_at":"2025-03-04T01:50:31.383Z","repository":{"id":41892434,"uuid":"327133830","full_name":"untemps/svelte-readotron","owner":"untemps","description":"Svelte component to display an estimated reading time","archived":false,"fork":false,"pushed_at":"2023-10-03T20:44:35.000Z","size":1300,"stargazers_count":41,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-02-09T10:15:38.759Z","etag":null,"topics":["component","javascript","read-o-meter","reading-rates","reading-time","reading-time-estimator","speed-reading-time","svelte","svelte-component"],"latest_commit_sha":null,"homepage":"https://svelte-readotron.vercel.app/","language":"JavaScript","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/untemps.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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":"2021-01-05T22:20:00.000Z","updated_at":"2024-08-01T04:01:30.000Z","dependencies_parsed_at":"2024-06-21T15:36:27.190Z","dependency_job_id":null,"html_url":"https://github.com/untemps/svelte-readotron","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/untemps%2Fsvelte-readotron","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/untemps%2Fsvelte-readotron/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/untemps%2Fsvelte-readotron/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/untemps%2Fsvelte-readotron/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/untemps","download_url":"https://codeload.github.com/untemps/svelte-readotron/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241768273,"owners_count":20017129,"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":["component","javascript","read-o-meter","reading-rates","reading-time","reading-time-estimator","speed-reading-time","svelte","svelte-component"],"created_at":"2025-03-04T01:50:30.936Z","updated_at":"2025-03-04T01:50:31.362Z","avatar_url":"https://github.com/untemps.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/svelte-readotron.png\" alt=\"svelte-readotron\" height=\"200\"/\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n    Svelte component to display an estimated reading time\n\u003c/p\u003e\n\n---\n\n[![npm](https://img.shields.io/npm/v/@untemps/svelte-readotron?style=for-the-badge)](https://www.npmjs.com/package/@untemps/svelte-readotron)\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/untemps/svelte-readotron/index.yml?style=for-the-badge)](https://github.com/untemps/svelte-readotron/actions)\n[![Codecov](https://img.shields.io/codecov/c/github/untemps/svelte-readotron?style=for-the-badge)](https://codecov.io/gh/untemps/svelte-readotron)\n\n## Demo\n\n\u003cp align=\"center\"\u003e\n    :red_circle:\u0026nbsp;\u0026nbsp;\u003cbig\u003e\u003ca href=\"https://svelte-readotron.vercel.app\" target=\"_blank\" rel=\"noopener\"\u003eLIVE\n    DEMO\u003c/a\u003e\u003c/big\u003e\u0026nbsp;:red_circle:\n    \u003cbr/\u003e\u003cbr/\u003e\n    \u003cimg src=\"assets/example.png\" alt=\"Example\" style=\"max-width: 100%\"/\u003e\n\u003c/p\u003e\n\n## Installation\n\n```bash\nyarn add @untemps/svelte-readotron\n```\n\n## Usage\n\n### Basic usage\n\n```html\n\u003cscript\u003e\n\timport Readotron from '@untemps/svelte-readotron'\n\u003c/script\u003e\n\n\u003cmain\u003e\n\t\u003cReadotron selector=\".text\" /\u003e\n\t\u003csection class=\"text\"\u003e...\u003c/section\u003e\n\u003c/main\u003e\n```\n\n`selector` prop is mandatory as it points to which element contains the text content to parse.\nYou can utilize any selector supported by the Web API [querySelector](https://developer.mozilla.org/fr/docs/Web/API/Document/querySelector) function.  \nIf several elements match the selector, it only parses the text of the first element in the array.\n\nThe component will wait for the element to be present in the DOM before parsing its content. If the element is not found after 1000ms, an error is raised and displayed.  \nThis is achieved with the [@untemps/dom-observer](https://github.com/untemps/dom-observer) package.\n\n### Lang\n\n'lang' is an optional prop that designates the language of the text to parse. The component implements the [@untemps/read-per-minute](https://github.com/untemps/read-per-minute) underhand package which returns an estimation based on the _lang_ (language).  \nReading rates by _lang_ come from [\"How many words do we read per minute? A review and meta-analysis of reading rate\"](https://osf.io/4nv9f/) by Marc Brysbaert - Department of Experimental Psychology Ghent University\n\n| Lang    | Rate |\n| ------- | ---- |\n| default | 200  |\n| ar      | 181  |\n| zh      | 260  |\n| nl      | 228  |\n| en      | 236  |\n| fi      | 195  |\n| fr      | 214  |\n| de      | 260  |\n| he      | 224  |\n| it      | 285  |\n| ko      | 226  |\n| es      | 278  |\n| sv      | 218  |\n\nIf a lang is not defined or the provided lang is not listed, the **default** value (200) will be applied.\n\n#### Example\n\n```html\n\u003cscript\u003e\n\timport Readotron from '@untemps/svelte-readotron'\n\u003c/script\u003e\n\n\u003cmain\u003e\n\t\u003cReadotron selector=\".text\" lang=\"en\" /\u003e\n\t\u003csection class=\"text\"\u003e...\u003c/section\u003e\n\u003c/main\u003e\n```\n\n### Template\n\nYou can customize the Readotron display by using the `template` prop.\n\n-   A template can be a string with one or more tokens delimited with `%`\n\n#### Example\n\n```html\n\u003cscript\u003e\n\timport Readotron from '@untemps/svelte-readotron'\n\u003c/script\u003e\n\n\u003cmain\u003e\n\t\u003cReadotron selector=\".text\" template=\"Reading Time: %time% minutes (%words% words)\" /\u003e\n\t\u003csection class=\"text\"\u003e...\u003c/section\u003e\n\u003c/main\u003e\n```\n\n-   A template can be a function with `time` and `words` as arguments.  \n    The function should return a template literal with the markup to display using optionally arguments as placeholders. But it may return any displayable type as well.\n\n\u003e :warning: The string will be parsed with the `{@html}` expression: **Be very careful with the content you pass or allow to pass in to this prop!**\n\n#### Example\n\n```html\n\u003cscript\u003e\n    import Readotron from '@untemps/svelte-readotron'\n\u003c/script\u003e\n\n\u003cmain\u003e\n    \u003cReadotron selector=\".text\" template={(time, words) =\u003e `\u003cIcon name='clock'\u003e \u003cstrong\u003eReading Time: ${time} minutes\u003c/strong\u003e (${words} words)`}/\u003e\n    \u003csection class=\"text\"\u003e\n        ...\n    \u003c/section\u003e\n\u003c/main\u003e\n```\n\n#### Avalaible tokens/arguments\n\n| Token   | Description                         |\n| ------- | ----------------------------------- |\n| `time`  | Estimated reading time (in minutes) |\n| `words` | Number of words                     |\n\n### Scroll Support\n\nYou are able to track and update component values by opting in the `withScroll` flag.\nThis will change the `time` (remaining time to read) and `words` (number of remaining words) as the user scroll the document.\n\n\u003e Note: There is no support for `element` scrolling so far, `document` only.\n\nThe component uses the [scrollProgress](https://github.com/jeremenichelli/scrollProgress) underhand package to track document scrolling.\n\n#### Example\n\n```html\n\u003cscript\u003e\n\timport Readotron from '@untemps/svelte-readotron'\n\u003c/script\u003e\n\n\u003cmain\u003e\n\t\u003cReadotron selector=\".text\" withScroll /\u003e\n\t\u003csection class=\"text\"\u003e...\u003c/section\u003e\n\u003c/main\u003e\n```\n\n### Change Event\n\nIf you need to be notified whenever values change, you may attach a listener to the `change` event emitted by the component.\n\n---\n\n_Note: The change event will be dispatched only if the `withScroll` prop is set to `true` since this is the only use case that triggers the change event so far_\n\n---\n\nThe handler will be triggered for the first time during the mounting phase with the initial values.\n\n#### Handler Signature\n\nHere are the properties available inside the `event.detail` sent with the event:\n\n| Props      | Type   | Description                                   |\n| ---------- | ------ | --------------------------------------------- |\n| `time`     | number | Estimated remaining reading time (in minutes) |\n| `words`    | number | Number of remaining words                     |\n| `progress` | number | Ratio of progression (between 0 and 1)        |\n\n#### Example\n\n```html\n\u003cscript\u003e\n    import Readotron from '@untemps/svelte-readotron'\n\u003c/script\u003e\n\n\u003cmain\u003e\n    \u003cReadotron selector=\".text\" withScroll on:change={(event) =\u003e {\n        const {detail: {time, words, progress}} = event\n        console.log('Time:', time + ' minutes')\n        console.log('Words:', words + ' remaining words')\n        console.log('Progress:', progress * 100 + '%')\n    }}/\u003e\n    \u003csection class=\"text\"\u003e\n        ...\n    \u003c/section\u003e\n\u003c/main\u003e\n```\n\n#### Recipe\n\nThe event dispatching allows to animate a progress bar in sync with the reading status:\n\n```html\n\u003cscript\u003e\n    import Readotron from '@untemps/svelte-readotron'\n\n    let readingProgress = 0\n\u003c/script\u003e\n\n\u003cmain\u003e\n    \u003cReadotron selector=\".text\" withScroll on:change={(event) =\u003e {\n        readingProgress = event.detail.progress\n    }}/\u003e\n    \u003csection class=\"text\"\u003e\n        ...\n    \u003c/section\u003e\n    \u003cdiv class=\"progress-bar\" style=\"width: {readingProgress * 100}%\"\u003e\u003c/div\u003e\n\u003c/main\u003e\n\n\u003cstyle\u003e\n    .progress-bar {\n        background-color: #0075ff;\n        height: 20px;\n        position: fixed;\n        left: 0;\n        bottom: 0;\n    }\n\u003c/style\u003e\n```\n\n### Slot\n\nAnother way to customize the display is to use the `\u003cslot\u003e` element.  \nIf a `\u003cslot\u003e` is passed in as Readotron child and correctly set (see Constraints below), it will be rendered instead of the default layout. This has precedence over the `template` prop.  \nThis allows to set a specific tag as parent if needed.\n\n#### Constraints:\n\nThe `\u003cslot\u003e` element has to be set with the prop `slot=\"content\"`\n\n#### Avalaible tokens\n\nLike `template`, tokens are passed back to the component to display dynamic values (see [Svelte API documentation](https://svelte.dev/docs#slot_let)):\n\n| Token   | Description                         |\n| ------- | ----------------------------------- |\n| `time`  | Estimated reading time (in minutes) |\n| `words` | Number of words                     |\n\n#### Example\n\n```html\n\u003cscript\u003e\n\timport Readotron from '@untemps/svelte-readotron'\n\u003c/script\u003e\n\n\u003cmain\u003e\n\t\u003cReadotron selector=\".text\"\u003e\n\t\t\u003cspan slot=\"content\" let:time let:words\u003e{time} min ({words} words)\u003c/span\u003e\n\t\u003c/Readotron\u003e\n\t\u003csection class=\"text\"\u003e...\u003c/section\u003e\n\u003c/main\u003e\n```\n\nPlease see the [Svelte API documentation](https://svelte.dev/docs#slot) to know more about the `\u003cslot\u003e` element.\n\n### Styles\n\nAll HTML attributes are automatically passed to the parent element of the component (`span`).  \nThat means you can query the `class` attribute to customize the style of the component.\n\n#### Example\n\n```html\n\u003cscript\u003e\n\timport Readotron from '@untemps/svelte-readotron'\n\u003c/script\u003e\n\n\u003cmain\u003e\n\t\u003cReadotron selector=\".text\" class=\"readotron\" /\u003e\n\t\u003csection class=\"text\"\u003e...\u003c/section\u003e\n\u003c/main\u003e\n\n\u003cstyle\u003e\n\t.readotron {\n\t\tcolor: #0075ff;\n\t\tfont-weight: 600;\n\t}\n\u003c/style\u003e\n```\n\nIf you use a `\u003cslot\u003e` element, as it will replace the original layout, you have to switch the class name from the `\u003cReadotron\u003e` element to the `\u003cslot\u003e` element.\n\n```html\n\u003cscript\u003e\n\timport Readotron from '@untemps/svelte-readotron'\n\u003c/script\u003e\n\n\u003cmain\u003e\n\t\u003cReadotron selector=\".text\"\u003e\n\t\t\u003cspan class=\"readotron\" slot=\"content\" let:time\u003e{time} min\u003c/span\u003e\n\t\u003c/Readotron\u003e\n\t\u003csection class=\"text\"\u003e...\u003c/section\u003e\n\u003c/main\u003e\n```\n\n### Error\n\nIf an error occurs during the parsing phase, the component catches and exposes it through an `error` variable which is displayed as is it by default. That means the error message uses the exact same styles as time value.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/error-default.png\" alt=\"Default error display\" style=\"max-width: 100%\"/\u003e\n\u003c/p\u003e\n\nYou may want to customize this error message, so the component provides a `slot`, named `error`, which replaces the default display if passed in.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/error-slot.png\" alt=\"Slot error display\" style=\"max-width: 100%\"/\u003e\n\u003c/p\u003e\n\n#### Example\n\n```html\n\u003cscript\u003e\n\timport Readotron from '@untemps/svelte-readotron'\n\u003c/script\u003e\n\n\u003cmain\u003e\n\t\u003cReadotron selector=\".text\"\u003e\n\t\t\u003cspan class=\"readotron\" slot=\"content\" let:time\u003e{time} min\u003c/span\u003e\n\t\t\u003cspan class=\"error\" slot=\"error\" let:error\u003eOops!\u003c/span\u003e\n\t\u003c/Readotron\u003e\n\t\u003csection class=\"text\"\u003e...\u003c/section\u003e\n\u003c/main\u003e\n\n\u003cstyle\u003e\n\t.readotron {\n\t\tcolor: #0075ff;\n\t\tfont-weight: 600;\n\t}\n\n\t.error {\n\t\tcolor: #ff0000;\n\t\tfont-weight: 600;\n\t}\n\u003c/style\u003e\n```\n\n#### Constraints:\n\nThe `\u003cslot\u003e` element has to be set with the prop `slot=\"error\"`\n\n#### Avalaible tokens\n\nError message is passed back to the component for display purpose if needed (see [Svelte API documentation](https://svelte.dev/docs#slot_let)):\n\n| Token   | Description            |\n| ------- | ---------------------- |\n| `error` | Original error message |\n\n### API\n\n| Props        | Type               | Default           | Description                                                                                                                                                     |\n| ------------ | ------------------ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `selector`   | string             | (required)        | Selector of the element which contains the content to parse. See [document.querySelector](https://developer.mozilla.org/fr/docs/Web/API/Document/querySelector) |\n| `lang`       | string             | 'en'              | Lang of the content [\"\"ar', 'zh', 'nl', 'en', 'fi', 'fr', 'de', 'he', 'it', 'ko', 'es', 'sv']                                                                   |\n| `template`   | string or function | '%time% min read' | Display template which contains dynamic tokens to be replaced by the parsed values. See [Template](#template)                                                   |\n| `withScroll` | boolean            | false             | Enable updates on scroll. If true, `time` and `words` values will reflect the document scroll position                                                          |\n\n### Events\n\n| Props    | Arguments  | Type   | Description                                                                                 |\n| -------- | ---------- | ------ | ------------------------------------------------------------------------------------------- |\n| `change` |            |        | **Dispatches whenever time and words have changed**                                         |\n|          | `time`     | number | Estimated remaining reading time (in minutes) Estimated remaining reading time (in minutes) |\n|          | `words`    | number | Number of remaining words                                                                   |\n|          | `progress` | number | Ratio of progression (between 0 and 1)                                                      |\n\n## Development\n\nThe component can be served for development purpose on `http://localhost:10001/` running:\n\n```\nyarn dev\n```\n\n## Contributing\n\nContributions are warmly welcomed:\n\n-   Fork the repository\n-   Create a feature branch\n-   Develop the feature AND write the tests (or write the tests AND develop the feature)\n-   Commit your changes\n    using [Angular Git Commit Guidelines](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines)\n-   Submit a Pull Request\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funtemps%2Fsvelte-readotron","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Funtemps%2Fsvelte-readotron","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funtemps%2Fsvelte-readotron/lists"}