33 BlockAddedEvent ,
44 BlockRemovedEvent ,
55 createDataKey ,
6- type DataKey ,
6+ type DataKey , DataNodeAddedEvent , DataNodeRemovedEvent ,
77 type EditorJSModel ,
88 EventAction ,
99 EventType ,
@@ -103,6 +103,8 @@ export class BlockToolAdapter implements BlockToolAdapterInterface {
103103 this . #formattingAdapter = formattingAdapter ;
104104 this . #toolName = toolName ;
105105
106+ this . #model. addEventListener ( EventType . Changed , ( event : ModelEvents ) => this . #handleModelUpdate( event ) ) ;
107+
106108 eventBus . addEventListener ( `ui:${ BeforeInputUIEventName } ` , ( event : BeforeInputUIEvent ) => {
107109 this . #processDelegatedBeforeInput( event ) ;
108110 } ) ;
@@ -124,34 +126,64 @@ export class BlockToolAdapter implements BlockToolAdapterInterface {
124126
125127 this . #attachedInputs. set ( key , input ) ;
126128
127- this . #model. addEventListener ( EventType . Changed , ( event : ModelEvents ) => this . #handleModelUpdate( event , input , key ) ) ;
129+ this . #model. createDataNode (
130+ this . #config. userId ,
131+ this . #blockIndex,
132+ key ,
133+ {
134+ $t : 't' ,
135+ value : '' ,
136+ }
137+ ) ;
128138
129139 const builder = new IndexBuilder ( ) ;
130140
131141 builder . addBlockIndex ( this . #blockIndex) . addDataKey ( key ) ;
132142
133143 this . #caretAdapter. attachInput ( input , builder . build ( ) ) ;
134144
135- try {
136- const value = this . #model. getText ( this . #blockIndex, key ) ;
137- const fragments = this . #model. getFragments ( this . #blockIndex, key ) ;
145+ const value = this . #model. getText ( this . #blockIndex, key ) ;
146+ const fragments = this . #model. getFragments ( this . #blockIndex, key ) ;
138147
139- input . textContent = value ;
148+ input . textContent = value ;
149+
150+ fragments . forEach ( fragment => {
151+ this . #formattingAdapter. formatElementContent ( input , fragment ) ;
152+ } ) ;
153+ }
154+
155+ /**
156+ * Removes the input from the DOM by key
157+ *
158+ * @param keyRaw - key of the input to remove
159+ */
160+ public detachInput ( keyRaw : string ) : void {
161+ const key = createDataKey ( keyRaw ) ;
162+ const input = this . #attachedInputs. get ( key ) ;
140163
141- fragments . forEach ( fragment => {
142- this . #formattingAdapter. formatElementContent ( input , fragment ) ;
143- } ) ;
144- } catch ( _ ) {
145- // do nothing — TextNode is not created yet as there is no initial data in the model
164+ if ( ! input ) {
165+ return ;
146166 }
167+
168+ input . remove ( ) ;
169+ this . #caretAdapter. detachInput (
170+ new IndexBuilder ( )
171+ . addBlockIndex ( this . #blockIndex)
172+ . addDataKey ( key )
173+ . build ( )
174+ ) ;
175+
176+ this . #attachedInputs. delete ( key ) ;
177+
178+ this . #model. removeDataNode ( this . #config. userId , this . #blockIndex, key ) ;
147179 }
148180
149181 /**
150182 * Check current selection and find it across all attached inputs
151183 *
152184 * @returns tuple of data key and input element or null if no focused input is found
153185 */
154- #findFocusedInput( ) : [ DataKey , HTMLElement ] | null {
186+ #findFocusedInput( ) : [ DataKey , HTMLElement ] | null {
155187 const currentInput = Array . from ( this . #attachedInputs. entries ( ) ) . find ( ( [ _ , input ] ) => {
156188 /**
157189 * Case 1: Input is a native input — check if it has selection
@@ -421,7 +453,7 @@ export class BlockToolAdapter implements BlockToolAdapterInterface {
421453 this . #config. userId ,
422454 {
423455 name : this . #toolName,
424- data : {
456+ data : {
425457 [ key ] : {
426458 $t : 't' ,
427459 value : newValueAfter ,
@@ -454,32 +486,10 @@ export class BlockToolAdapter implements BlockToolAdapterInterface {
454486 * @param key - data key input is attached to
455487 */
456488 #handleModelUpdateForNativeInput( event : ModelEvents , input : HTMLInputElement | HTMLTextAreaElement , key : DataKey ) : void {
457- if ( ! ( event instanceof TextAddedEvent ) && ! ( event instanceof TextRemovedEvent ) ) {
458- return ;
459- }
460-
461- const { textRange, dataKey, blockIndex } = event . detail . index ;
462-
463- if ( textRange === undefined ) {
464- return ;
465- }
466-
467- /**
468- * Event is not related to the attached block
469- */
470- if ( blockIndex !== this . #blockIndex) {
471- return ;
472- }
473-
474- /**
475- * Event is not related to the attached data key
476- */
477- if ( dataKey !== key ) {
478- return ;
479- }
489+ const { textRange } = event . detail . index ;
480490
481491 const currentElement = input ;
482- const [ start , end ] = textRange ;
492+ const [ start , end ] = textRange ! ;
483493
484494 const action = event . detail . action ;
485495
@@ -519,31 +529,10 @@ export class BlockToolAdapter implements BlockToolAdapterInterface {
519529 * @param key - data key input is attached to
520530 */
521531 #handleModelUpdateForContentEditableElement( event : ModelEvents , input : HTMLElement , key : DataKey ) : void {
522- if ( ! ( event instanceof TextAddedEvent ) && ! ( event instanceof TextRemovedEvent ) ) {
523- return ;
524- }
525-
526- const { textRange, dataKey, blockIndex } = event . detail . index ;
527-
528- if ( blockIndex !== this . #blockIndex) {
529- return ;
530- }
531-
532- /**
533- * Event is not related to the attached data key
534- */
535- if ( dataKey !== key ) {
536- return ;
537- }
538-
539- if ( textRange === undefined ) {
540- return ;
541- }
542-
532+ const { textRange } = event . detail . index ;
543533 const action = event . detail . action ;
544534
545- const start = textRange [ 0 ] ;
546- const end = textRange [ 1 ] ;
535+ const [ start , end ] = textRange ! ;
547536
548537 const [ startNode , startOffset ] = getBoundaryPointByAbsoluteOffset ( input , start ) ;
549538 const [ endNode , endOffset ] = getBoundaryPointByAbsoluteOffset ( input , end ) ;
@@ -589,19 +578,50 @@ export class BlockToolAdapter implements BlockToolAdapterInterface {
589578 * @param input - attached input element
590579 * @param key - data key input is attached to
591580 */
592- #handleModelUpdate( event : ModelEvents , input : HTMLElement , key : DataKey ) : void {
581+ #handleModelUpdate( event : ModelEvents ) : void {
593582 if ( event instanceof BlockAddedEvent || event instanceof BlockRemovedEvent ) {
594583 if ( event . detail . index . blockIndex ! <= this . #blockIndex) {
595584 this . #blockIndex += event . detail . action === EventAction . Added ? 1 : - 1 ;
596585 }
586+
587+ return ;
588+ }
589+
590+ const { textRange, dataKey, blockIndex } = event . detail . index ;
591+
592+ if ( blockIndex !== this . #blockIndex) {
593+ return ;
594+ }
595+
596+
597+ if ( event instanceof DataNodeRemovedEvent ) {
598+ this . detachInput ( dataKey as string ) ;
599+
600+ return ;
601+ }
602+
603+ if ( event instanceof DataNodeAddedEvent ) {
604+ /**
605+ * @todo Decide how to handle this case as only BlockTool knows how to render an input
606+ */
607+ }
608+
609+ if ( ! ( event instanceof TextAddedEvent ) && ! ( event instanceof TextRemovedEvent ) ) {
610+ return ;
611+ }
612+
613+ const input = this . #attachedInputs. get ( dataKey ! ) ;
614+
615+ if ( ! input || textRange === undefined ) {
616+ return ;
597617 }
598618
599619 const isInputNative = isNativeInput ( input ) ;
600620
601621 if ( isInputNative === true ) {
602- this . #handleModelUpdateForNativeInput( event , input as HTMLInputElement | HTMLTextAreaElement , key ) ;
622+ this . #handleModelUpdateForNativeInput( event , input as HTMLInputElement | HTMLTextAreaElement , dataKey ! ) ;
603623 } else {
604- this . #handleModelUpdateForContentEditableElement( event , input , key ) ;
624+ this . #handleModelUpdateForContentEditableElement( event , input , dataKey ! ) ;
605625 }
606626 } ;
607627}
0 commit comments