33 useRef ,
44 useImperativeHandle ,
55 useEffect ,
6- useMemo
6+ useMemo ,
7+ useState
78} from 'react'
89import styles from './Template.module.scss'
910import { Icon } from '../../Icon'
@@ -16,6 +17,7 @@ import {
1617 map_display_pos_to_raw_pos ,
1718 use_symbol_deletion
1819} from '../shared/symbols'
20+ import { use_undo_redo } from '../shared/hooks/use-undo-redo'
1921
2022export type TemplateHandle = {
2123 focus : ( ) => void
@@ -36,6 +38,54 @@ export const Template = forwardRef<TemplateHandle, TemplateProps>(
3638 ( props , ref ) => {
3739 const input_ref = useRef < HTMLDivElement > ( null )
3840
41+ const raw_caret_pos_ref = useRef ( 0 )
42+
43+ const { save_history, undo, redo } = use_undo_redo ( {
44+ value : props . value || '' ,
45+ raw_caret_pos_ref,
46+ on_change : props . on_change ,
47+ on_restore : ( entry ) => {
48+ setTimeout ( ( ) => {
49+ if ( input_ref . current ) {
50+ const display_pos = map_raw_pos_to_display_pos ( {
51+ raw_pos : entry . raw_caret_pos ,
52+ raw_text : entry . value ,
53+ context_file_paths : [ ]
54+ } )
55+ set_caret_position_for_div ( input_ref . current , display_pos )
56+ }
57+ } , 0 )
58+ }
59+ } )
60+
61+ useEffect ( ( ) => {
62+ const handle_selection_change = ( ) => {
63+ if (
64+ document . activeElement === input_ref . current &&
65+ input_ref . current
66+ ) {
67+ const display_pos = get_caret_position_from_div ( input_ref . current )
68+ const raw_pos = map_display_pos_to_raw_pos ( {
69+ display_pos,
70+ raw_text : props . value || '' ,
71+ context_file_paths : [ ]
72+ } )
73+ raw_caret_pos_ref . current = raw_pos
74+ }
75+ }
76+
77+ document . addEventListener ( 'selectionchange' , handle_selection_change )
78+ if ( input_ref . current ) {
79+ input_ref . current . addEventListener ( 'focus' , handle_selection_change )
80+ }
81+ return ( ) => {
82+ document . removeEventListener ( 'selectionchange' , handle_selection_change )
83+ if ( input_ref . current ) {
84+ input_ref . current . removeEventListener ( 'focus' , handle_selection_change )
85+ }
86+ }
87+ } , [ props . value ] )
88+
3989 useImperativeHandle ( ref , ( ) => ( {
4090 focus : ( ) => input_ref . current ?. focus ( ) ,
4191 get_caret_position : ( ) => {
@@ -63,23 +113,31 @@ export const Template = forwardRef<TemplateHandle, TemplateProps>(
63113 }
64114 } ) )
65115
116+ const update_value = ( new_value : string , caret_pos ?: number ) => {
117+ if ( new_value === ( props . value || '' ) ) return
118+ save_history ( )
119+ props . on_change ( new_value )
120+ if ( caret_pos !== undefined ) {
121+ setTimeout ( ( ) => {
122+ if ( input_ref . current ) {
123+ const display_pos = map_raw_pos_to_display_pos ( {
124+ raw_pos : caret_pos ,
125+ raw_text : new_value ,
126+ context_file_paths : [ ]
127+ } )
128+ set_caret_position_for_div ( input_ref . current , display_pos )
129+ }
130+ } , 0 )
131+ }
132+ }
133+
66134 const { handle_symbol_deletion_by_click, handle_backspace_key } =
67135 use_symbol_deletion ( {
68136 value : props . value || '' ,
69137 context_file_paths : [ ] ,
70138 input_ref,
71139 on_delete : ( new_value , new_caret_pos ) => {
72- props . on_change ( new_value )
73- setTimeout ( ( ) => {
74- if ( input_ref . current ) {
75- const display_pos = map_raw_pos_to_display_pos ( {
76- raw_pos : new_caret_pos ,
77- raw_text : new_value ,
78- context_file_paths : [ ]
79- } )
80- set_caret_position_for_div ( input_ref . current , display_pos )
81- }
82- } , 0 )
140+ update_value ( new_value , new_caret_pos )
83141 }
84142 } )
85143
@@ -113,7 +171,7 @@ export const Template = forwardRef<TemplateHandle, TemplateProps>(
113171 const new_raw_value = reconstruct_raw_value_from_node ( currentTarget )
114172
115173 if ( new_raw_value !== props . value ) {
116- props . on_change ( new_raw_value )
174+ update_value ( new_raw_value )
117175 }
118176
119177 const native_event = e . nativeEvent as unknown as { inputType ?: string }
@@ -155,8 +213,33 @@ export const Template = forwardRef<TemplateHandle, TemplateProps>(
155213 }
156214 }
157215
216+ const handle_undo = ( e : React . KeyboardEvent < HTMLDivElement > ) => {
217+ e . preventDefault ( )
218+ undo ( )
219+ }
220+
221+ const handle_redo = ( e : React . KeyboardEvent < HTMLDivElement > ) => {
222+ e . preventDefault ( )
223+ redo ( )
224+ }
225+
158226 const handle_key_down = ( e : React . KeyboardEvent < HTMLDivElement > ) => {
159- if ( e . key === 'Backspace' ) {
227+ const { key, shiftKey } = e
228+
229+ if ( ( e . ctrlKey || e . metaKey ) && ! shiftKey && key . toLowerCase ( ) === 'z' ) {
230+ handle_undo ( e )
231+ return
232+ }
233+
234+ if (
235+ ( ( e . ctrlKey || e . metaKey ) && key . toLowerCase ( ) === 'y' ) ||
236+ ( ( e . ctrlKey || e . metaKey ) && shiftKey && key . toLowerCase ( ) === 'z' )
237+ ) {
238+ handle_redo ( e )
239+ return
240+ }
241+
242+ if ( key === 'Backspace' ) {
160243 handle_backspace_key ( e )
161244 }
162245 }
0 commit comments