By qbane ([the thread 🧵](https://twitter.com/QbaneAP/status/1545441954678259715))
2022/8
> **Update** 2023/8/18
>
> The author has found a leaner approach: https://github.com/PuruVJ/neocodemirror.
tl;dr. It provides an action and let you optionally create a specific store for passing in. The data would be notified via the store only if a store is present.
>
> Comment: The "use:action" syntax may be more mighty than one might have thought!
Minimal PoC: https://svelte.dev/repl/dfa4759d5bdc46a4ae4a18a6c71ade17?version=3.48.0
## The "reactivity"
First thoughts:
* A `<textarea>` but fancier! Get/set its state in Svelte's pattern.
* Forward `on:` bound events?
We can see from the docs:
### EditorStateConfig
* doc: can be either a plain string or [`Text`](https://codemirror.net/docs/ref/#state.Text)
* selection: the initial selection, can be a `{anchor, head?}`
* extensions: all other extension points; some presets are `basicSetup` or `minimalSetup`.
### EditorViewConfig
* state: populated from the exact same object if not given
* parent: parent DOM
* root: document or shadow root
* scrollTo: initial position
* dispatchTransactions: [default to `(trs, view) => view.update(trs)`](https://github.com/codemirror/view/blob/013b14311519080f34035a29da1c166cf2c36fc2/src/editorview.ts#L196).
Some insights of mine:
* The state itself is immutable upon built. Once it's changed, the view must be notified at idle phase (!) to reflect the change ("`setState`", `reconfigure`, ...). CodeMirror does have it's own lifecycle and updating the view wrongly [will throw errors](https://discuss.codemirror.net/t/should-dispatched-transactions-be-added-to-a-queue/4610/2).
* The initial `selection` depends on the `doc`. Making it reactive to `doc` change is convoluted. I am not going to implement it as a prop.
* In the view config, it makes little sense to specify `parent` and `root` for making bindings for Svelte.
* We may roll our own `dispatch` to pull off the reactivity. It is crucial that this is synchronous, as opposed to the one `EditorView.updateListener` (receives a `ViewUpdate` with zero or more transactions), which is asynchronous. Svelte's store contract also mandates some operations to be synchorous.
## Performance in mind
Making `doc` bindable may harm the performance! To make it reactive, we have to monitor updates to the editor and extract the string (`.toString()`) in bulk at every trigger. Furthermore, the redundant string would linger in the prop. Better **implement it as a store to read** (and also cache) the content **on demand**.
A "bindable" value is a nice-to-have if the user thinks the performance impact is tolerable. And this is more like a traditional `<textarea>`. CM 5 did have this feature via [`fromTextArea`](https://codemirror.net/5/doc/manual.html#fromTextArea). The abovementioned store is pretty neat for our need. The "on demand" means that the extra work is hidden until the store has at least one subscription.
```javascript=
function editorTxHandler(tr) {
this.update([tr])
/* ... */
if (tr.docChanged) {
__docCached = null
if (subscribers.size > 0) { // <---
updateDocStore(__docCached = tr.newDoc.toString())
}
dispatch('change', {view: this, tr})
}
}
```
**TODO:** How to handle the undo history when the store itself is updated?
If you try making a browser-native input field or textarea with ad-hoc text-editing commands, you will be surprised that the undo history is sometimes completely erased by a single programmatic update `foo.value = 'bar'` and there is no way to recover it.
But here, the default behavior will leave a trace each time the store is written. We can choose what to do with the undo stack CM provides.
## Final design
https://svelte.dev/repl/91649ba3e0ce4122b3b34f3a95a00104?version=3.49.0