66
77import * as browserEvents from '../browser_events.js' ;
88import * as css from '../css.js' ;
9+ import type { IFocusableNode } from '../interfaces/i_focusable_node' ;
910import { IRenderedElement } from '../interfaces/i_rendered_element.js' ;
1011import * as layers from '../layers.js' ;
1112import * as touch from '../touch.js' ;
@@ -47,11 +48,8 @@ export class CommentView implements IRenderedElement {
4748 /** The resize handle element. */
4849 private resizeHandle : SVGImageElement ;
4950
50- /** The foreignObject containing the HTML text area. */
51- private foreignObject : SVGForeignObjectElement ;
52-
53- /** The text area where the user can type. */
54- private textArea : HTMLTextAreaElement ;
51+ /** The part of the comment view that contains the textarea to edit the comment. */
52+ private commentEditor : CommentEditor ;
5553
5654 /** The current size of the comment in workspace units. */
5755 private size : Size ;
@@ -65,14 +63,6 @@ export class CommentView implements IRenderedElement {
6563 /** The current location of the comment in workspace coordinates. */
6664 private location : Coordinate = new Coordinate ( 0 , 0 ) ;
6765
68- /** The current text of the comment. Updates on text area change. */
69- private text : string = '' ;
70-
71- /** Listeners for changes to text. */
72- private textChangeListeners : Array <
73- ( oldText : string , newText : string ) => void
74- > = [ ] ;
75-
7666 /** Listeners for changes to size. */
7767 private sizeChangeListeners : Array < ( oldSize : Size , newSize : Size ) => void > =
7868 [ ] ;
@@ -107,8 +97,6 @@ export class CommentView implements IRenderedElement {
10797 /** The default size of newly created comments. */
10898 static defaultCommentSize = new Size ( 120 , 100 ) ;
10999
110- public commentEditor : CommentEditor | undefined ;
111-
112100 constructor (
113101 readonly workspace : WorkspaceSvg ,
114102 private commentId ?: string ,
@@ -128,8 +116,7 @@ export class CommentView implements IRenderedElement {
128116 textPreviewNode : this . textPreviewNode ,
129117 } = this . createTopBar ( this . svgRoot , workspace ) ) ;
130118
131- ( { foreignObject : this . foreignObject , textArea : this . textArea } =
132- this . createTextArea ( this . svgRoot ) ) ;
119+ this . commentEditor = this . createTextArea ( this . svgRoot ) ;
133120
134121 this . resizeHandle = this . createResizeHandle ( this . svgRoot , workspace ) ;
135122
@@ -242,19 +229,26 @@ export class CommentView implements IRenderedElement {
242229 /**
243230 * Creates the text area where users can type. Registers event listeners.
244231 */
245- private createTextArea ( svgRoot : SVGGElement ) : {
246- foreignObject : SVGForeignObjectElement ;
247- textArea : HTMLTextAreaElement ;
248- } {
249- this . commentEditor = new CommentEditor (
250- this . workspace ,
251- svgRoot ,
252- this . commentId ,
253- ) ;
254- const { foreignObject, textArea} = this . commentEditor ;
255- browserEvents . conditionalBind ( textArea , 'change' , this , this . onTextChange ) ;
232+ private createTextArea ( svgRoot : SVGGElement ) {
233+ const commentEditor = new CommentEditor ( this . workspace , this . commentId ) ;
234+
235+ this . svgRoot . appendChild ( commentEditor . getDom ( ) ) ;
256236
257- return { foreignObject, textArea} ;
237+ commentEditor . addTextChangeListener ( ( oldText , newText ) => {
238+ this . updateTextPreview ( newText ) ;
239+ // Update size in case our minimum size increased.
240+ this . setSize ( this . size ) ;
241+ } ) ;
242+
243+ return commentEditor ;
244+ }
245+
246+ /**
247+ *
248+ * @returns The FocusableNode representing the editor portion of this comment.
249+ */
250+ getEditorFocusableNode ( ) : IFocusableNode {
251+ return this . commentEditor ;
258252 }
259253
260254 /** Creates the DOM elements for the comment resize handle. */
@@ -316,7 +310,7 @@ export class CommentView implements IRenderedElement {
316310
317311 this . updateHighlightRect ( size ) ;
318312 this . updateTopBarSize ( size ) ;
319- this . updateTextAreaSize ( size , topBarSize ) ;
313+ this . commentEditor . updateSize ( size , topBarSize ) ;
320314 this . updateDeleteIconPosition ( size , topBarSize , deleteSize ) ;
321315 this . updateFoldoutIconPosition ( topBarSize , foldoutSize ) ;
322316 this . updateTextPreviewSize (
@@ -352,7 +346,7 @@ export class CommentView implements IRenderedElement {
352346 foldoutSize : Size ,
353347 deleteSize : Size ,
354348 ) : Size {
355- this . updateTextPreview ( this . textArea . value ?? '' ) ;
349+ this . updateTextPreview ( this . commentEditor . getText ( ) ?? '' ) ;
356350 const textPreviewWidth = dom . getTextWidth ( this . textPreview ) ;
357351
358352 const foldoutMargin = this . calcFoldoutMargin ( topBarSize , foldoutSize ) ;
@@ -400,19 +394,6 @@ export class CommentView implements IRenderedElement {
400394 this . topBarBackground . setAttribute ( 'width' , `${ size . width } ` ) ;
401395 }
402396
403- /** Updates the size of the text area elements to reflect the new size. */
404- private updateTextAreaSize ( size : Size , topBarSize : Size ) {
405- this . foreignObject . setAttribute (
406- 'height' ,
407- `${ size . height - topBarSize . height } ` ,
408- ) ;
409- this . foreignObject . setAttribute ( 'width' , `${ size . width } ` ) ;
410- this . foreignObject . setAttribute ( 'y' , `${ topBarSize . height } ` ) ;
411- if ( this . workspace . RTL ) {
412- this . foreignObject . setAttribute ( 'x' , `${ - size . width } ` ) ;
413- }
414- }
415-
416397 /**
417398 * Updates the position of the delete icon elements to reflect the new size.
418399 */
@@ -644,12 +625,11 @@ export class CommentView implements IRenderedElement {
644625 if ( this . editable ) {
645626 dom . addClass ( this . svgRoot , 'blocklyEditable' ) ;
646627 dom . removeClass ( this . svgRoot , 'blocklyReadonly' ) ;
647- this . textArea . removeAttribute ( 'readonly' ) ;
648628 } else {
649629 dom . removeClass ( this . svgRoot , 'blocklyEditable' ) ;
650630 dom . addClass ( this . svgRoot , 'blocklyReadonly' ) ;
651- this . textArea . setAttribute ( 'readonly' , 'true' ) ;
652631 }
632+ this . commentEditor . setEditable ( editable ) ;
653633 }
654634
655635 /** Returns the current location of the comment in workspace coordinates. */
@@ -670,49 +650,29 @@ export class CommentView implements IRenderedElement {
670650 ) ;
671651 }
672652
673- /** Retursn the current text of the comment. */
653+ /** Returns the current text of the comment. */
674654 getText ( ) {
675- return this . text ;
655+ return this . commentEditor . getText ( ) ;
676656 }
677657
678658 /** Sets the current text of the comment. */
679659 setText ( text : string ) {
680- this . textArea . value = text ;
681- this . onTextChange ( ) ;
660+ this . commentEditor . setText ( text ) ;
682661 }
683662
684663 /** Sets the placeholder text displayed for an empty comment. */
685664 setPlaceholderText ( text : string ) {
686- this . textArea . placeholder = text ;
665+ this . commentEditor . setPlaceholderText ( text ) ;
687666 }
688667
689- /** Registers a callback that listens for text changes. */
668+ /** Registers a callback that listens for text changes on the comment editor . */
690669 addTextChangeListener ( listener : ( oldText : string , newText : string ) => void ) {
691- this . textChangeListeners . push ( listener ) ;
670+ this . commentEditor . addTextChangeListener ( listener ) ;
692671 }
693672
694- /** Removes the given listener from the list of text change listeners . */
673+ /** Removes the given listener from the comment editor . */
695674 removeTextChangeListener ( listener : ( ) => void ) {
696- this . textChangeListeners . splice (
697- this . textChangeListeners . indexOf ( listener ) ,
698- 1 ,
699- ) ;
700- }
701-
702- /**
703- * Triggers listeners when the text of the comment changes, either
704- * programmatically or manually by the user.
705- */
706- private onTextChange ( ) {
707- const oldText = this . text ;
708- this . text = this . textArea . value ;
709- this . updateTextPreview ( this . text ) ;
710- // Update size in case our minimum size increased.
711- this . setSize ( this . size ) ;
712- // Loop through listeners backwards in case they remove themselves.
713- for ( let i = this . textChangeListeners . length - 1 ; i >= 0 ; i -- ) {
714- this . textChangeListeners [ i ] ( oldText , this . text ) ;
715- }
675+ this . commentEditor . removeTextChangeListener ( listener ) ;
716676 }
717677
718678 /** Updates the preview text element to reflect the given text. */
@@ -876,6 +836,11 @@ css.register(`
876836 fill: none;
877837}
878838
839+ .blocklyCommentText.blocklyActiveFocus {
840+ border-color: #fc3;
841+ border-width: 2px;
842+ }
843+
879844.blocklySelected .blocklyCommentHighlight {
880845 stroke: #fc3;
881846 stroke-width: 3px;
0 commit comments