{"id":13601899,"url":"https://github.com/FormidableLabs/use-editable","last_synced_at":"2025-04-11T04:31:52.626Z","repository":{"id":42365922,"uuid":"297045708","full_name":"FormidableLabs/use-editable","owner":"FormidableLabs","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":false,"pushed_at":"2025-01-23T21:04:06.000Z","size":1103,"stargazers_count":597,"open_issues_count":16,"forks_count":14,"subscribers_count":40,"default_branch":"main","last_synced_at":"2025-04-06T07:38:00.923Z","etag":null,"topics":["editor","hook","react"],"latest_commit_sha":null,"homepage":"","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/FormidableLabs.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":"2020-09-20T09:49:12.000Z","updated_at":"2025-04-04T21:41:48.000Z","dependencies_parsed_at":"2024-08-12T18:39:31.672Z","dependency_job_id":"2480d1c2-602b-44fa-b3db-86bc27080feb","html_url":"https://github.com/FormidableLabs/use-editable","commit_stats":{"total_commits":80,"total_committers":4,"mean_commits":20.0,"dds":"0.050000000000000044","last_synced_commit":"69db4747eb92f5886db24dd4aa06fbe30b73eb9c"},"previous_names":["kitten/use-editable"],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FormidableLabs%2Fuse-editable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FormidableLabs%2Fuse-editable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FormidableLabs%2Fuse-editable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FormidableLabs%2Fuse-editable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FormidableLabs","download_url":"https://codeload.github.com/FormidableLabs/use-editable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247451603,"owners_count":20940946,"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":["editor","hook","react"],"created_at":"2024-08-01T18:01:09.747Z","updated_at":"2025-04-11T04:31:52.135Z","avatar_url":"https://github.com/FormidableLabs.png","language":"TypeScript","readme":"\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://formidable.com/open-source/\" target=\"_blank\"\u003e\n    \u003cimg alt=\"Use Editable — Formidable, We build the modern web\" src=\"https://raw.githubusercontent.com/FormidableLabs/use-editable/master/use-editable-Hero.png\" /\u003e\n  \u003c/a\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\nread 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\nWhen `options.indentation` is set then `useEditable` will prevent the insertion of tab characters and\nwill instead insert the specified amount of whitespaces, which makes handling of columns much easier.\n\nAdditionally the `useEditable` hook returns an `Edit` handle with several methods, as documented below.\n\n#### Edit.update\n\n`Edit.update(content: string): void`\n\nReplaces the entire content of the editable while adjusting the caret position.\nThis will shift the caret by the difference in length between the current content and the passed content.\n\n#### Edit.insert\n\n`Edit.insert(append: string, offset?: number): void`\n\nInserts new text at the caret position while deleting text in range of the offset (which accepts negative offsets).\nFor example, when `offset` is set to `-1` then a single character is deleted to the left of the caret before\ninserting any new text. When it's set to `2` then two characters to the right of the carets are deleted.\nThe `append` text may also be set to an empty string to only apply deletions without inserting any text.\nWhen any text is selected then it's simply erased first and `offset` is ignored.\n\n#### Edit.move\n\n`Edit.move(pos: number | { row: number; column: number }): void`\n\nThis moves the caret to the specified position. The position may either be a character index (a `number`)\nor coordinates specifying a `row` and `column` separately.\n\n#### Edit.getState\n\n`Edit.getState(): { text: string; position: Position }`\n\nThis method allows getting the current state of the editable, which is the same as what `onChange` usually\nreceives. This is useful when adding custom editing actions in a key down handler or when programmatically\nimitating `onChange` otherwise, while the editable is selected.\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\n\n## Maintenance Status\n\n**Stable:** Formidable is not planning to develop any new features for this project. We are still responding to bug reports and security concerns. We are still welcoming PRs for this project, but PRs that include new features should be small and easy to integrate and should not include breaking changes.\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFormidableLabs%2Fuse-editable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FFormidableLabs%2Fuse-editable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFormidableLabs%2Fuse-editable/lists"}