@@ -3,7 +3,7 @@ import { EventDelegator } from './Events/EventDelegator';
3
3
import { LogicalElement , PermutationListEntry , toLogicalElement , insertLogicalChild , removeLogicalChild , getLogicalParent , getLogicalChild , createAndInsertLogicalContainer , isSvgElement , getLogicalChildrenArray , getLogicalSiblingEnd , permuteLogicalChildren , getClosestDomElement } from './LogicalElements' ;
4
4
import { applyCaptureIdToElement } from './ElementReferenceCapture' ;
5
5
import { attachToEventDelegator as attachNavigationManagerToEventDelegator } from '../Services/NavigationManager' ;
6
- const selectValuePropname = '_blazorSelectValue ' ;
6
+ const deferredValuePropname = '_blazorDeferredValue ' ;
7
7
const sharedTemplateElemForParsing = document . createElement ( 'template' ) ;
8
8
const sharedSvgElemForParsing = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'g' ) ;
9
9
const rootComponentsPendingFirstRender : { [ componentId : number ] : LogicalElement } = { } ;
@@ -257,21 +257,26 @@ export class BrowserRenderer {
257
257
// [3] In case the the value of the select and the option value is changed in the same batch.
258
258
// We just receive an attribute frame and have to set the select value afterwards.
259
259
260
+ // We also defer setting the 'value' property for <input> because certain types of inputs have
261
+ // default attribute values that may incorrectly constain the specified 'value'.
262
+ // For example, range inputs have default 'min' and 'max' attributes that may incorrectly
263
+ // clamp the 'value' property if it is applied before custom 'min' and 'max' attributes.
264
+
260
265
if ( newDomElementRaw instanceof HTMLOptionElement ) {
261
266
// Situation 1
262
267
this . trySetSelectValueFromOptionElement ( newDomElementRaw ) ;
263
- } else if ( newDomElementRaw instanceof HTMLSelectElement && selectValuePropname in newDomElementRaw ) {
268
+ } else if ( deferredValuePropname in newDomElementRaw ) {
264
269
// Situation 2
265
- const selectValue : string | null = newDomElementRaw [ selectValuePropname ] ;
266
- setSelectElementValue ( newDomElementRaw , selectValue ) ;
270
+ const deferredValue : string | null = newDomElementRaw [ deferredValuePropname ] ;
271
+ setDeferredElementValue ( newDomElementRaw , deferredValue ) ;
267
272
}
268
273
}
269
274
270
275
private trySetSelectValueFromOptionElement ( optionElement : HTMLOptionElement ) {
271
276
const selectElem = this . findClosestAncestorSelectElement ( optionElement ) ;
272
- if ( selectElem && ( selectValuePropname in selectElem ) && selectElem [ selectValuePropname ] === optionElement . value ) {
273
- setSelectElementValue ( selectElem , optionElement . value ) ;
274
- delete selectElem [ selectValuePropname ] ;
277
+ if ( selectElem && ( deferredValuePropname in selectElem ) && selectElem [ deferredValuePropname ] === optionElement . value ) {
278
+ setDeferredElementValue ( selectElem , optionElement . value ) ;
279
+ delete selectElem [ deferredValuePropname ] ;
275
280
return true ;
276
281
}
277
282
return false ;
@@ -375,18 +380,18 @@ export class BrowserRenderer {
375
380
case 'TEXTAREA' : {
376
381
const value = attributeFrame ? frameReader . attributeValue ( attributeFrame ) : null ;
377
382
378
- if ( element instanceof HTMLSelectElement ) {
379
- setSelectElementValue ( element , value ) ;
383
+ // <select> is special, in that anything we write to .value will be lost if there
384
+ // isn't yet a matching <option>. To maintain the expected behavior no matter the
385
+ // element insertion/update order, preserve the desired value separately so
386
+ // we can recover it when inserting any matching <option> or after inserting an
387
+ // entire markup block of descendants.
380
388
381
- // <select> is special, in that anything we write to .value will be lost if there
382
- // isn't yet a matching <option>. To maintain the expected behavior no matter the
383
- // element insertion/update order, preserve the desired value separately so
384
- // we can recover it when inserting any matching <option> or after inserting an
385
- // entire markup block of descendants.
386
- element [ selectValuePropname ] = value ;
387
- } else {
388
- ( element as any ) . value = value ;
389
- }
389
+ // We also defer setting the 'value' property for <input> because certain types of inputs have
390
+ // default attribute values that may incorrectly constain the specified 'value'.
391
+ // For example, range inputs have default 'min' and 'max' attributes that may incorrectly
392
+ // clamp the 'value' property if it is applied before custom 'min' and 'max' attributes.
393
+ element [ deferredValuePropname ] = value ;
394
+ setDeferredElementValue ( element , value ) ;
390
395
391
396
return true ;
392
397
}
@@ -510,14 +515,18 @@ function stripOnPrefix(attributeName: string) {
510
515
throw new Error ( `Attribute should be an event name, but doesn't start with 'on'. Value: '${ attributeName } '` ) ;
511
516
}
512
517
513
- function setSelectElementValue ( element : HTMLSelectElement , value : string | null ) {
514
- // There's no sensible way to represent a select option with value 'null', because
515
- // (1) HTML attributes can't have null values - the closest equivalent is absence of the attribute
516
- // (2) When picking an <option> with no 'value' attribute, the browser treats the value as being the
517
- // *text content* on that <option> element. Trying to suppress that default behavior would involve
518
- // a long chain of special-case hacks, as well as being breaking vs 3.x.
519
- // So, the most plausible 'null' equivalent is an empty string. It's unfortunate that people can't
520
- // write <option value=@someNullVariable>, and that we can never distinguish between null and empty
521
- // string in a bound <select>, but that's a limit in the representational power of HTML.
522
- element . value = value || '' ;
518
+ function setDeferredElementValue ( element : Element , value : string | null ) {
519
+ if ( element instanceof HTMLSelectElement ) {
520
+ // There's no sensible way to represent a select option with value 'null', because
521
+ // (1) HTML attributes can't have null values - the closest equivalent is absence of the attribute
522
+ // (2) When picking an <option> with no 'value' attribute, the browser treats the value as being the
523
+ // *text content* on that <option> element. Trying to suppress that default behavior would involve
524
+ // a long chain of special-case hacks, as well as being breaking vs 3.x.
525
+ // So, the most plausible 'null' equivalent is an empty string. It's unfortunate that people can't
526
+ // write <option value=@someNullVariable>, and that we can never distinguish between null and empty
527
+ // string in a bound <select>, but that's a limit in the representational power of HTML.
528
+ element . value = value || '' ;
529
+ } else {
530
+ ( element as any ) . value = value ;
531
+ }
523
532
}
0 commit comments