1- import { useState , useRef , useEffect } from 'preact/hooks' ;
1+ import { useRef , useEffect } from 'preact/hooks' ;
22import { EditorView } from 'codemirror' ;
33import { lineNumbers , keymap , highlightActiveLineGutter , highlightActiveLine } from '@codemirror/view' ;
4- import { EditorState } from '@codemirror/state' ;
4+ import { EditorState , Transaction } from '@codemirror/state' ;
55import { defaultKeymap , history , historyKeymap , indentWithTab } from '@codemirror/commands' ;
66import { javascript } from '@codemirror/lang-javascript' ;
77import { syntaxHighlighting , HighlightStyle , indentUnit , bracketMatching } from '@codemirror/language' ;
@@ -26,16 +26,34 @@ const highlightStyle = HighlightStyle.define([
2626 { tag : tags . invalid , class : 'cm-invalid' }
2727] ) ;
2828
29+ /**
30+ * @param {object } props
31+ * @param {string } props.value
32+ * @param {(value: string) => void } props.onInput
33+ * @param {string } props.slug
34+ * @param {string } [props.class]
35+ */
2936export default function CodeEditor ( props ) {
3037 const editorParent = useRef ( null ) ;
38+ /** @type {{ current: EditorView | null } } */
3139 const editor = useRef ( null ) ;
32- // eslint-disable-next-line no-unused-vars
33- const [ _ , setEditor ] = useState ( null ) ;
40+
41+ const routeHasChanged = useRef ( false ) ;
42+
43+ useEffect ( ( ) => {
44+ if ( props . slug || ! editor . current ) routeHasChanged . current = true ;
45+ } , [ props . slug ] ) ;
3446
3547 useEffect ( ( ) => {
36- console . log ( 'editor code:\n' , props . value ) ;
37- if ( editor . current && ! props . baseExampleSlug ) return ;
38- if ( editor . current ) editor . current . destroy ( ) ;
48+ if ( routeHasChanged . current === false ) return ;
49+ routeHasChanged . current = false ;
50+
51+ if ( editor . current ) {
52+ editor . current . dispatch ( {
53+ changes : { from : 0 , to : editor . current . state . doc . length , insert : props . value }
54+ } ) ;
55+ return ;
56+ }
3957
4058 const theme = EditorView . theme ( { } , { dark : true } ) ;
4159
@@ -54,8 +72,9 @@ export default function CodeEditor(props) {
5472 keymap . of ( [ indentWithTab , ...defaultKeymap , ...historyKeymap ] ) ,
5573 [ theme , syntaxHighlighting ( highlightStyle , { fallback : true } ) ] ,
5674 EditorView . updateListener . of ( update => {
57- if ( update . docChanged ) {
58- if ( props . onInput ) props . onInput ( { value : update . state . doc . toString ( ) } ) ;
75+ // Ignores changes from swapping out the editor code programmatically
76+ if ( isViewUpdateFromUserInput ( update ) ) {
77+ props . onInput ( update . state . doc . toString ( ) ) ;
5978 }
6079 } )
6180 ]
@@ -65,16 +84,23 @@ export default function CodeEditor(props) {
6584 state,
6685 parent : editorParent . current
6786 } ) ;
68-
69- setEditor ( editor . current ) ;
70- } , [ props . baseExampleSlug ] ) ;
87+ } , [ props . value ] ) ;
7188
7289 useEffect ( ( ) => (
7390 ( ) => {
74- editor . current . destroy ( ) ;
75- setEditor ( null ) ;
91+ if ( editor . current ) editor . current . destroy ( ) ;
7692 }
7793 ) , [ ] ) ;
7894
7995 return < div ref = { editorParent } class = { cx ( style . codeEditor , props . class ) } /> ;
8096}
97+
98+ /** @param {import('@codemirror/view').ViewUpdate } viewUpdate */
99+ function isViewUpdateFromUserInput ( viewUpdate ) {
100+ if ( viewUpdate . docChanged ) {
101+ for ( const transaction of viewUpdate . transactions ) {
102+ if ( transaction . annotation ( Transaction . userEvent ) ) return true ;
103+ }
104+ }
105+ return false ;
106+ }
0 commit comments