{"_id":"@neftaly/editcontext-polyfill","_rev":"2-a0bc9c1b535ef6551cd2467d071f3921","name":"@neftaly/editcontext-polyfill","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@neftaly/editcontext-polyfill","version":"0.1.0","keywords":["polyfill","contenteditable","ponyfill","editcontext"],"license":"MIT","_id":"@neftaly/editcontext-polyfill@0.1.0","maintainers":[{"name":"neftaly","email":"neftaly.hernandez@gmail.com"}],"homepage":"https://neftaly.github.io/editcontext-polyfill/","bugs":{"url":"https://github.com/neftaly/editcontext-polyfill/issues"},"dist":{"shasum":"143c9b6cfd553cda1b7e2918676949c0714a236c","tarball":"https://registry.npmjs.org/@neftaly/editcontext-polyfill/-/editcontext-polyfill-0.1.0.tgz","fileCount":22,"integrity":"sha512-8warz0zj3pnU7wG15RWv4j8R0jGt34jtgcsihlVClO1NpxrupxyF2mzKMkt03Aed900/odQUvXs962u9eL7zGw==","signatures":[{"sig":"MEYCIQCoeAoQnXduGgwMbcf+JO9HuTo3lSjwIxiv6BZVlrGzzAIhAOuh+igq1K0U3RSNO9G+OXzwCpeTAbqpiYuQ114D4kjW","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":381250},"main":"./dist/index.mjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs"}},"gitHead":"fa7158db74b66efe770a2810e18017be1b51f994","scripts":{"dev":"node dev.mjs","lint":"biome check src/ tests/ *.ts *.mjs","test":"docker build --load -t editcontext-test -f Containerfile . && docker run --rm editcontext-test","build":"node build.mjs && tsc --emitDeclarationOnly","lint:fix":"biome check --write src/ tests/ *.ts *.mjs","test:fuzz":"docker build --load -t editcontext-test -f Containerfile . && docker run --rm editcontext-test pnpm exec playwright test tests/fuzz/fuzz.spec.ts tests/fuzz/fuzz-shadow.spec.ts tests/fuzz/fuzz-multi.spec.ts --project=chromium-native","test:local":"pnpm build && pnpm exec playwright test","build:pages":"pnpm build && mkdir -p docs && cp dist/editcontext-polyfill.iife.js docs/ && cp example/*.html docs/","test:fuzz:ime":"docker build --load -t editcontext-test -f Containerfile . && docker run --rm editcontext-test pnpm exec playwright test tests/fuzz/fuzz-ime.spec.ts --project=chromium-native --workers=1","test:workflow:firefox":"docker build --load -t editcontext-test -f Containerfile . && docker run --rm editcontext-test pnpm exec playwright test tests/api/workflow.spec.ts --project=firefox-polyfill"},"_npmUser":{"name":"neftaly","email":"neftaly.hernandez@gmail.com"},"repository":{"url":"git+https://github.com/neftaly/editcontext-polyfill.git","type":"git"},"_npmVersion":"11.6.2","description":"Polyfill for the EditContext API","directories":{},"sideEffects":true,"_nodeVersion":"24.11.1","_hasShrinkwrap":false,"packageManager":"pnpm@10.24.0","devDependencies":{"esbuild":"^0.27.3","typescript":"^5.9.3","@biomejs/biome":"^2.3.15","@playwright/test":"^1.58.2"},"_npmOperationalInternal":{"tmp":"tmp/editcontext-polyfill_0.1.0_1771159712695_0.8340763529251363","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@neftaly/editcontext-polyfill","version":"0.1.1","description":"Polyfill for the EditContext API","packageManager":"pnpm@10.24.0","type":"module","main":"./dist/index.mjs","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs"}},"sideEffects":true,"license":"MIT","repository":{"type":"git","url":"git+https://github.com/neftaly/editcontext-polyfill.git"},"homepage":"https://neftaly.github.io/editcontext-polyfill/","keywords":["polyfill","contenteditable","ponyfill","editcontext"],"scripts":{"dev":"node dev.mjs","build":"node build.mjs && tsc --emitDeclarationOnly","build:pages":"pnpm build && mkdir -p docs && cp dist/editcontext-polyfill.iife.js docs/ && cp example/*.html docs/","lint":"biome check src/ tests/ *.ts *.mjs","lint:fix":"biome check --write src/ tests/ *.ts *.mjs","test":"docker build --load -t editcontext-test -f Containerfile . && docker run --rm editcontext-test","test:fuzz":"docker build --load -t editcontext-test -f Containerfile . && docker run --rm editcontext-test pnpm exec playwright test tests/fuzz/fuzz.spec.ts tests/fuzz/fuzz-shadow.spec.ts tests/fuzz/fuzz-multi.spec.ts --project=chromium-native","test:fuzz:ime":"docker build --load -t editcontext-test -f Containerfile . && docker run --rm editcontext-test pnpm exec playwright test tests/fuzz/fuzz-ime.spec.ts --project=chromium-native --workers=1","test:workflow:firefox":"docker build --load -t editcontext-test -f Containerfile . && docker run --rm editcontext-test pnpm exec playwright test tests/api/workflow.spec.ts --project=firefox-polyfill","test:local":"pnpm build && pnpm exec playwright test"},"devDependencies":{"@biomejs/biome":"^2.3.15","@playwright/test":"^1.58.2","esbuild":"^0.27.3","typescript":"^5.9.3"},"gitHead":"7010055a9fcfd3e0655d6f75d119de7d95e9ec6a","_id":"@neftaly/editcontext-polyfill@0.1.1","bugs":{"url":"https://github.com/neftaly/editcontext-polyfill/issues"},"_nodeVersion":"24.11.1","_npmVersion":"11.6.2","dist":{"integrity":"sha512-QLxxfcywSLYEUb5GgkPHniTmacu26kYElFV01qsWtq9/HIolfkgaTbNOtxhVd49k4v1g2+31P8fsk3eDgGhlAg==","shasum":"495d4e96610b009a7eddd5d79f05c1d7c1e31415","tarball":"https://registry.npmjs.org/@neftaly/editcontext-polyfill/-/editcontext-polyfill-0.1.1.tgz","fileCount":22,"unpackedSize":383425,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEg5/TcqQqBilaj2yHjPRIbSwtvt6PywRrF3rqeuCBILAiAoqGCZJ6eHtuyTdLhJItK+55zxkODG+k4vNOoe0/C/1w=="}]},"_npmUser":{"name":"neftaly","email":"neftaly.hernandez@gmail.com"},"directories":{},"maintainers":[{"name":"neftaly","email":"neftaly.hernandez@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/editcontext-polyfill_0.1.1_1771163520731_0.36200612742628935"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-15T12:48:32.695Z","modified":"2026-02-15T13:52:01.093Z","0.1.0":"2026-02-15T12:48:32.882Z","0.1.1":"2026-02-15T13:52:00.968Z"},"bugs":{"url":"https://github.com/neftaly/editcontext-polyfill/issues"},"license":"MIT","homepage":"https://neftaly.github.io/editcontext-polyfill/","keywords":["polyfill","contenteditable","ponyfill","editcontext"],"repository":{"type":"git","url":"git+https://github.com/neftaly/editcontext-polyfill.git"},"description":"Polyfill for the EditContext API","maintainers":[{"name":"neftaly","email":"neftaly.hernandez@gmail.com"}],"readme":"# editcontext-polyfill\n\nPolyfill for the [EditContext API](https://developer.mozilla.org/en-US/docs/Web/API/EditContext) ([caniuse](https://caniuse.com/mdn-api_editcontext)), a replacement for `contenteditable`.\n\nThis polyfill was written by a LLM (Claude Opus 4.6) using TDD & fuzzing, against Chrome's native EditContext implementation.\nThe test suite includes ports of the [Web Platform Tests](https://github.com/web-platform-tests/wpt/tree/master/editing/edit-context).\n\n## Browser support\n\n| Browser        | Support                        |\n| -------------- | ------------------------------ |\n| Chrome / Edge  | 121+ native (no polyfill)      |\n| Firefox        | 125+ via polyfill              |\n| Safari         | 15.4+ via polyfill             |\n\n## Install\n\n```sh\nnpm install @neftaly/editcontext-polyfill\n```\n\nOr via script tag:\n\n```html\n<script src=\"https://cdn.jsdelivr.net/gh/neftaly/editcontext-polyfill/dist/editcontext-polyfill.iife.js\"></script>\n```\n\nBy default, the polyfill is bypassed for browsers with native EditContext support. To force it on regardless:\n\n```html\n<script src=\"editcontext-polyfill.iife.js\" data-force></script>\n```\n\nOr with the ESM API:\n\n```js\nimport { install } from \"@neftaly/editcontext-polyfill\";\ninstall({ force: true });\n```\n\n## Usage\n\n```js\nimport \"@neftaly/editcontext-polyfill\";\n\nconst el = document.querySelector(\"#editor\");\nconst ec = new EditContext({ text: \"\" });\nel.editContext = ec;\n\nec.addEventListener(\"textupdate\", (e) => {\n  console.log(e.text, e.updateRangeStart, e.updateRangeEnd);\n  // Re-render your editor view here\n});\n\nel.focus();\n```\n\n## Compatibility\n\nThis polyfill uses a hidden textarea for input capture. Most of the EditContext API works identically to Chrome's native implementation, with some exceptions.\n\n### Unsupported features\n\n- **`updateControlBounds()`** is a no-op. Logs a one-time warning in the IIFE build (warnings are removed from the ESM build at compile time).\n- **CSS `:focus` on `<canvas>`** doesn't work (the hidden textarea is appended to `document.body`, not the canvas). Toggle a CSS class in your `focus`/`blur` handlers instead. All other elements support `:focus` via shadow DOM.\n- **`isTrusted`** on re-dispatched events (keyboard, clipboard, composition) is `false`.\n\n### Approximate features\n\n- **`textformatupdate`** is dispatched during composition with a default format (solid thin underline over the full composition range). The polyfill cannot access OS-level IME format data, so individual clause styling is not available.\n- **`characterboundsupdate`** is dispatched during composition for the full composition range. IME popups use `updateSelectionBounds()` position instead of per-character bounds.\n\n### Architectural limitations\n\nThe hidden textarea architecture means some behaviors cannot match Chrome native:\n\n- **IME composition + `updateSelection`**: If `updateSelection()` is called programmatically and then an IME composition begins immediately, the hidden textarea's cursor may not match the EditContext's selection, causing composition text to land at the wrong position. \n- **Sequential IME composition edge cases**: Rapid sequential `imeSetComposition` calls can occasionally desync the textarea's native IME tracking from the polyfill's internal state, causing intermediate composition text to not be fully replaced. The polyfill skips `textarea.value` sync during composition to mitigate this, but edge cases remain.\n- **`compositionend.data` on blur**: When blur interrupts an active IME composition, Chrome's native `compositionend.data` behavior is inconsistent (sometimes empty string, sometimes the composed text). The polyfill may not match in all cases.\n\n### Notes\n- **Firefox insertLineBreak**: On `Enter` key, Firefox's normal `insertLineBreak` event is blocked. Instead, an `insertParagraph` event is fired, to match Chrome. `Shift+Enter` behaves the same (`insertLineBreak`).\n- **Detach+reattach focus**: Setting `el.editContext = null` then `el.editContext = new EditContext()` across separate calls does not restore focus. Chrome retains element focus natively, but the polyfill loses it when the hidden textarea is destroyed on detach. Swapping contexts in a single call (`el.editContext = newCtx`) works correctly.\n\n## Testing\n\n- **`tests/unit/`** — pure unit tests for `EditContextState` transitions, runnable without a browser.\n- **`tests/api/`** — deterministic tests for each EditContext method and event, run on both `chromium-native` and `chromium-polyfill`.\n- **`tests/wpt/`** — ports of the [Web Platform Tests](https://github.com/web-platform-tests/wpt/tree/master/editing/edit-context) for EditContext.\n- **`tests/fuzz/`** — seeded fuzzers (`pnpm test:fuzz`) that run the same random action sequence on Chrome native and the polyfill, then compare final state and event logs. Includes an IME composition fuzzer (`pnpm test:fuzz:ime`) that uses CDP `Input.imeSetComposition`.\n","readmeFilename":"README.md"}