{"id":15311649,"url":"https://github.com/aslemammad/use-editable","last_synced_at":"2025-10-08T20:32:41.780Z","repository":{"id":105998227,"uuid":"300725468","full_name":"Aslemammad/use-editable","owner":"Aslemammad","description":"A small React hook to turn elements into fully renderable \u0026 editable content surfaces, like code editors, using contenteditable (and magic)","archived":false,"fork":true,"pushed_at":"2020-09-23T16:56:36.000Z","size":108,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-22T09:30:44.823Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"FormidableLabs/use-editable","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Aslemammad.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2020-10-02T20:10:19.000Z","updated_at":"2022-02-08T09:03:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"f945a28e-e76b-4d15-8c88-f22c04e58b02","html_url":"https://github.com/Aslemammad/use-editable","commit_stats":{"total_commits":38,"total_committers":1,"mean_commits":38.0,"dds":0.0,"last_synced_commit":"91e83569752469eb874fc61f9e55869b5831c290"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aslemammad%2Fuse-editable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aslemammad%2Fuse-editable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aslemammad%2Fuse-editable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aslemammad%2Fuse-editable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Aslemammad","download_url":"https://codeload.github.com/Aslemammad/use-editable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235752641,"owners_count":19039932,"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":[],"created_at":"2024-10-01T08:34:13.380Z","updated_at":"2025-10-08T20:32:36.346Z","avatar_url":"https://github.com/Aslemammad.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch2 align=\"center\"\u003euse-editable\u003c/h2\u003e\n  \u003cp align=\"center\"\u003e\u003cstrong\u003eA small React hook to turn elements into fully renderable \u0026 editable content surfaces, like code editors, using contenteditable (and magic)\u003c/strong\u003e\u003c/p\u003e\n  \u003cbr /\u003e\n  \u003ca href=\"https://npmjs.com/package/use-editable\"\u003e\n    \u003cimg alt=\"NPM Version\" src=\"https://img.shields.io/npm/v/use-editable.svg\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://npmjs.com/package/use-editable\"\u003e\n    \u003cimg alt=\"License\" src=\"https://img.shields.io/npm/l/use-editable.svg\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://bundlephobia.com/result?p=use-editable\"\u003e\n    \u003cimg alt=\"Minified gzip size\" src=\"https://img.shields.io/bundlephobia/minzip/use-editable.svg?label=gzip%20size\" /\u003e\n  \u003c/a\u003e\n  \u003cbr /\u003e\n  \u003cbr /\u003e\n\u003c/div\u003e\n\n`useEditable` is a small hook that enables elements to be `contenteditable` while still being fully renderable. This is ideal for creating small code editors or prose textareas in just `2kB`!\n\nIt aims to allow any element to be editable while still being able to render normal React elements to it — no `innerHTML` and having to deal with operating with or rendering to raw HTML, or starting a full editor project from scratch.\n\n**Check out [the full demo on CodeSandbox](https://codesandbox.io/s/use-editable-0l9kc) with `prism-react-renderer`!**\n\n## Usage\n\nFirst install `use-editable` alongside `react`:\n\n```sh\nyarn add use-editable\n# or\nnpm install --save use-editable\n```\n\nYou'll then be able to import `useEditable` and pass it an `HTMLElement` ref and an `onChange` handler.\n\n```js\nimport React, { useState, useRef } from 'react';\nimport { useEditable } from 'use-editable';\n\nconst RainbowCode = () =\u003e {\n  const [code, setCode] = useState('function test() {}\\nconsole.log(\"hello\");');\n  const editorRef = useRef(null);\n\n  useEditable(editorRef, setCode);\n\n  return (\n    \u003cdiv className=\"App\"\u003e\n      \u003cpre\n        style={{ whiteSpace: 'pre-wrap', fontFamily: 'monospace' }}\n        ref={editorRef}\n      \u003e\n        {code.split(/\\r?\\n/).map((content, i, arr) =\u003e (\n          \u003cReact.Fragment key={i}\u003e\n            \u003cspan style={{ color: `hsl(${((i % 20) * 17) | 0}, 80%, 50%)` }}\u003e\n              {content}\n            \u003c/span\u003e\n            {i \u003c arr.length - 1 ? '\\n' : null}\n          \u003c/React.Fragment\u003e\n        ))}\n      \u003c/pre\u003e\n    \u003c/div\u003e\n  );\n};\n```\n\nAnd just like that we've hooked up `useEditable` to our `editorRef`, which points to the `\u003cpre\u003e`\nelement that is being rendered, and to `setCode` which drives our state containing some code.\n\n## Browser Compatibility\n\nThis library has been tested against and **should work properly** using:\n\n- Chrome\n- Safari\n- iOS Safari\n- Firefox\n\nThere are known issues in **IE 11** due to the `MutationObserver` method being unable to\nreadd text nodes that have been removed via the `contenteditable`.\n\n## FAQ\n\n### How does it work?\n\nTraditionally, there have been three options when choosing editing surfaces in React. Either one\ncould go for a large project like ProseMirror / CodeMirror or similar which take control over much\nof the editing and rendering events and are hence rather opinionated, or it's possible to just\nuse `contenteditable` and render to raw HTML that is replaced in the element's content, or lastly one\ncould combine a `textarea` with an overlapping `div` that renders stylised content.\n\nAll three options don't allow much customisation in terms of what actually gets rendered or put\nunreasonable restrictions on how easy it is to render and manage an editable's content.\n\n**So what makes rendering to a `contenteditable` element so hard?**\n\nTypically this is tough because they edit the DOM directly. This causes most rendering libraries, like\nReact and Preact to be confused, since their underlying Virtual DOMs don't match up with the actual\nDOM structure anymore. To prevent this issue `use-editable` creates a `MutationObserver`, which watches\nover all changes that are made to the `contenteditable` element. Before it reports these changes to\nReact it first rolls back all changes to the DOM so that React sees what it expects.\n\nFurthermore it also preserves the current position of the caret, the selection, and restores it once\nReact has updated the DOM itself. This is a rather common technique for `contenteditable` editors, but\nthe `MutationObserver` addition is what enables `use-editable` to let another view library update the element's\ncontent.\n\n### What's currently possible?\n\nCurrently either the rendered elements' text content has to eventually exactly match the code input,\nor your implementation must be able to convert the rendered text content back into what you're using\nas state. This is a limitation of how `contenteditable`'s work, since they'll only capture the actual\nDOM content. Since `use-editable` doesn't aim to be a full component that manages the render cycle, it\ndoesn't have to keep any extra state, but will only pass the DOM's text back to the `onChange` callback.\n\nUsing the `onChange` callback you'll also receive a `Position` object describing the cursor position,\nthe current line number, and the line's contents up until the cursor, which is useful for auto-suggestions,\nwhich could then be applied with the `update` function that `useEditable` returns to update the cursor\nposition.\n\n## API\n\n### `useEditable`\n\nThe **first argument** is `elementRef` and accepts a ref object of type `RefObject\u003cHTMLElement\u003e` which\npoints to the element that should become editable. This ref is allowed to be `null` or change during\nthe runtime of the hook. As long as the changes of the ref are triggered by React, everything should\nbehave as expected.\n\nThe **second argument** is `onChange` and accepts a callback of type `(text: string, pos: Position) =\u003e void`\nthat's called whenever the content of the `contenteditable` changes. This needs to be set up so that\nit'll trigger a rerender of the element's contents.\n\nThe `text` that `onChange` receives is just the textual representation of the element's contents, while the\n`Position` it receives contains the current position of the cursor, the line number (zero-indexed), and\nthe content of the current line up until the cursor, which is useful for autosuggestions.\n\nThe **third argument** is an optional `options` object. This accepts currently two options to change\nthe editing behavior of the hook:\n\n- The `disabled` option disables editing on the editable by removing the `contentEditable` attribute from\n  it again.\n- The `indentation` option may be a number of displayed spaces for indentation. This also enables the\n  improved `Tab` key behavior which will indent the current line or dedent the current line when shift is\n  held (Be aware that this will make the editor act as a focus trap!)\n\nAdditionally the `useEditable` hook returns a function that may be used to update the content while\nadjusting the next render's cursor position. This is a convenience method that can come in handy for\nadding auto-suggested content in combination with a `Position`'s current line contents for instance.\n\n## Acknowledgments\n\n- [`react-live`](https://github.com/FormidableLabs/react-live/blob/v1.12.0/src/components/Editor/index.js), which I've worked on\n  had one of the early tiny `contenteditable` editors. (But with raw HTML updates)\n- [`react-simple-code-editor`](https://github.com/satya164/react-simple-code-editor) was the first (?) library to use a split textarea\n  and rendering surface implementation, which presented what a nice editing API should look like.\n- [`codejar`](https://github.com/antonmedv/codejar) contains the best tricks to manage selections, although it lacks some\n  Firefox workarounds. It also uses raw HTML highlighting / updating.\n- [`codemirror.next`](https://github.com/codemirror/codemirror.next) is an invaluable source to see different techniques when\n  handling text input and DOM update tricks.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faslemammad%2Fuse-editable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faslemammad%2Fuse-editable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faslemammad%2Fuse-editable/lists"}