@@ -15,6 +15,7 @@ import {computeAriaLabel, getBeginStackLabel} from './block_aria_composer.js';
1515import { BlockSvg } from './block_svg.js' ;
1616import * as browserEvents from './browser_events.js' ;
1717import * as bumpObjects from './bump_objects.js' ;
18+ import * as css from './css.js' ;
1819import * as dialog from './dialog.js' ;
1920import * as dropDownDiv from './dropdowndiv.js' ;
2021import { EventType } from './events/type.js' ;
@@ -78,6 +79,11 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
7879 */
7980 protected isTextValid_ = false ;
8081
82+ /**
83+ * The warning icon to display on invalid input
84+ */
85+ protected warningIcon : HTMLImageElement | null = null ;
86+
8187 /**
8288 * The intial value of the field when the user opened an editor to change its
8389 * value. When the editor is disposed, an event will be fired that uses this
@@ -195,6 +201,34 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
195201 return this . fullBlockClickTarget_ ;
196202 }
197203
204+ /** Creates the DOM elements for the invalid input warning icon. */
205+ private createWarningIcon ( parent ?: Element | null ) : HTMLImageElement | null {
206+ const sourceBlock = this . sourceBlock_ as BlockSvg ;
207+ if ( ! sourceBlock ) {
208+ return null ;
209+ }
210+
211+ const bBox = this . getScaledBBox ( ) ;
212+ const bBoxHeight = bBox . bottom - bBox . top ;
213+ const e = document . createElement ( 'img' ) ;
214+ e . setAttribute ( 'class' , 'blocklyInputWarning' ) ;
215+ e . setAttribute (
216+ 'src' ,
217+ `${ sourceBlock . workspace . options . pathToMedia } input-warning-icon.svg` ,
218+ ) ;
219+ e . setAttribute ( 'x' , `0` ) ;
220+ e . setAttribute ( 'y' , `0` ) ;
221+ e . setAttribute ( 'height' , `${ bBoxHeight } px` ) ;
222+ e . setAttribute ( 'width' , `${ bBoxHeight } px` ) ;
223+ e . setAttribute ( 'float' , 'inline-start' ) ;
224+
225+ if ( parent ) {
226+ parent . appendChild ( e ) ;
227+ }
228+
229+ return e ;
230+ }
231+
198232 /**
199233 * Called by setValue if the text input is not valid. If the field is
200234 * currently being edited it reverts value of the field to the previous
@@ -312,21 +346,52 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
312346 */
313347 protected override render_ ( ) {
314348 super . render_ ( ) ;
349+ const block = this . getSourceBlock ( ) as BlockSvg | null ;
350+ if ( ! block ) throw new UnattachedFieldError ( ) ;
351+
315352 // This logic is done in render_ rather than doValueInvalid_ or
316353 // doValueUpdate_ so that the code is more centralized.
317354 if ( this . isBeingEdited_ ) {
318355 const htmlInput = this . htmlInput_ as HTMLElement ;
319356 if ( ! this . isTextValid_ ) {
320357 dom . addClass ( htmlInput , 'blocklyInvalidInput' ) ;
321358 aria . setState ( htmlInput , aria . State . INVALID , true ) ;
359+ // insert the icon
360+ if ( this . warningIcon && htmlInput ) {
361+ const bBox = this . getScaledBBox ( ) ;
362+ const hasBorder = ! ! this . borderRect_ ;
363+ const xPadding = hasBorder
364+ ? this . getConstants ( ) ! . FIELD_BORDER_RECT_X_PADDING
365+ : ( bBox . bottom - bBox . top ) / 2 ;
366+ dom . addClass ( this . warningIcon , 'blocklyInputWarningInvalid' ) ;
367+ htmlInput . setAttribute (
368+ 'width' ,
369+ `${ htmlInput . offsetWidth + this . warningIcon . width } px` ,
370+ ) ;
371+ // If we pad by the whole icon width, it looks too far from the
372+ // text, and half the width looks too close.
373+ const iconPadding = this . warningIcon . width / 1.5 ;
374+ if ( block . RTL ) {
375+ htmlInput . style . paddingRight = `${ iconPadding } px` ;
376+ } else {
377+ htmlInput . style . paddingLeft = `${ iconPadding } px` ;
378+ }
379+ this . size_ . width = this . warningIcon . width ;
380+ const iconOffset = hasBorder
381+ ? this . getConstants ( ) ! . FIELD_TEXT_HEIGHT -
382+ this . getConstants ( ) ! . FIELD_BORDER_RECT_X_PADDING
383+ : 0 ;
384+ this . updateSize_ ( xPadding + iconOffset ) ;
385+ }
322386 } else {
323387 dom . removeClass ( htmlInput , 'blocklyInvalidInput' ) ;
388+ if ( this . warningIcon ) {
389+ dom . removeClass ( this . warningIcon , 'blocklyInputWarningInvalid' ) ;
390+ }
324391 aria . setState ( htmlInput , aria . State . INVALID , false ) ;
325392 }
326393 }
327394
328- const block = this . getSourceBlock ( ) as BlockSvg | null ;
329- if ( ! block ) throw new UnattachedFieldError ( ) ;
330395 // In general, do *not* let fields control the color of blocks. Having the
331396 // field control the color is unexpected, and could have performance
332397 // impacts.
@@ -462,6 +527,8 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
462527 'spellcheck' ,
463528 this . spellcheck_ as AnyDuringMigration ,
464529 ) ;
530+ this . warningIcon = this . createWarningIcon ( ) ;
531+
465532 const scale = this . workspace_ ! . getAbsoluteScale ( ) ;
466533 const fontSize = this . getConstants ( ) ! . FIELD_TEXT_FONTSIZE * scale + 'pt' ;
467534 div ! . style . fontSize = fontSize ;
@@ -484,9 +551,15 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
484551 div ! . style . boxShadow =
485552 'rgba(255, 255, 255, 0.3) 0 0 0 ' + 4 * scale + 'px' ;
486553 }
554+ // Adjust invalid input warning icon for full block style
555+ const paddingX = ( bBox . bottom - bBox . top ) / 4 ;
556+ this . warningIcon ! . style . paddingLeft = `${ paddingX } px` ;
557+ this . warningIcon ! . style . paddingRight = `${ paddingX } px` ;
487558 }
488559 htmlInput . style . borderRadius = borderRadius ;
489-
560+ if ( this . warningIcon ) {
561+ div ! . appendChild ( this . warningIcon ) ;
562+ }
490563 div ! . appendChild ( htmlInput ) ;
491564
492565 htmlInput . value = htmlInput . defaultValue = this . getEditorText_ ( this . value_ ) ;
@@ -921,3 +994,13 @@ export interface FieldInputConfig extends FieldConfig {
921994export type FieldInputValidator < T extends InputTypes > = FieldValidator <
922995 string | T
923996> ;
997+
998+ css . register ( `
999+ .blocklyInputWarning {
1000+ display: none;
1001+ position: absolute;
1002+ }
1003+ .blocklyInputWarning.blocklyInputWarningInvalid {
1004+ display: inline;
1005+ }
1006+ ` ) ;
0 commit comments