@@ -6,7 +6,16 @@ import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
66import { Unparseable } from '@lion/validate' ;
77
88/**
9- * @polymerMixin
9+ * @desc Designed to be applied on top of a LionField
10+ *
11+ * FormatMixin supports these two main flows:
12+ * 1) Application Developer sets `.modelValue`:
13+ * Flow: `.modelValue` -> `.formattedValue` -> `.inputElement.value`
14+ * -> `.serializedValue`
15+ * 2) End user interacts with field:
16+ * Flow: `@user-input-changed` -> `.modelValue` -> `.formattedValue` - (debounce till reflect condition (formatOn) is met) -> `.inputElement.value`
17+ * -> `.serializedValue`
18+ *
1019 * @mixinFunction
1120 */
1221export const FormatMixin = dedupeMixin (
@@ -26,7 +35,8 @@ export const FormatMixin = dedupeMixin(
2635 *
2736 * Examples:
2837 * - For a date input: a String '20/01/1999' will be converted to new Date('1999/01/20')
29- * - For a number input: a formatted String '1.234,56' will be converted to a Number: 1234.56
38+ * - For a number input: a formatted String '1.234,56' will be converted to a Number:
39+ * 1234.56
3040 */
3141 modelValue : {
3242 type : Object ,
@@ -48,13 +58,13 @@ export const FormatMixin = dedupeMixin(
4858 /**
4959 * The serialized version of the model value.
5060 * This value exists for maximal compatibility with the platform API.
51- * The serialized value can be an interface in context where data binding is not supported
52- * and a serialized string needs to be set.
61+ * The serialized value can be an interface in context where data binding is not
62+ * supported and a serialized string needs to be set.
5363 *
5464 * Examples:
5565 * - For a date input, this would be the iso format of a date, e.g. '1999-01-20'.
56- * - For a number input this would be the String representation of a float ('1234.56' instead
57- * of 1234.56)
66+ * - For a number input this would be the String representation of a float ('1234.56'
67+ * instead of 1234.56)
5868 *
5969 * When no parser is available, the value is usually the same as the formattedValue
6070 * (being inputElement.value)
@@ -139,15 +149,16 @@ export const FormatMixin = dedupeMixin(
139149 }
140150
141151 /**
142- * Responsible for storing all representations(modelValue, serializedValue, formattedValue and
143- * value) of the input value.
144- * Prevents infinite loops, so all value observers can be treated like they will only be called
145- * once, without indirectly calling other observers.
146- * (in fact, some are called twice, but the __preventRecursiveTrigger lock prevents the second
147- * call from having effect).
152+ * Responsible for storing all representations(modelValue, serializedValue, formattedValue
153+ * and value) of the input value.
154+ * Prevents infinite loops, so all value observers can be treated like they will only be
155+ * called once, without indirectly calling other observers.
156+ * (in fact, some are called twice, but the __preventRecursiveTrigger lock prevents the
157+ * second call from having effect).
148158 *
149- * @param {string } source - the type of value that triggered this method. It should not be set
150- * again, so that its observer won't be triggered. Can be: 'model'|'formatted'|'serialized'.
159+ * @param {string } source - the type of value that triggered this method. It should not be
160+ * set again, so that its observer won't be triggered. Can be:
161+ * 'model'|'formatted'|'serialized'.
151162 */
152163 _calculateValues ( { source } = { } ) {
153164 if ( this . __preventRecursiveTrigger ) return ; // prevent infinite loops
@@ -182,7 +193,19 @@ export const FormatMixin = dedupeMixin(
182193 if ( this . modelValue instanceof Unparseable ) {
183194 return this . modelValue . viewValue ;
184195 }
185- if ( this . errorState ) {
196+
197+ // - Why check for this.errorState?
198+ // We only want to format values that are considered valid. For best UX,
199+ // we only 'reward' valid inputs.
200+ // - Why check for __isHandlingUserInput?
201+ // Downwards sync is prevented whenever we are in a `@user-input-changed` flow.
202+ // If we are in a 'imperatively set `.modelValue`' flow, we want to reflect back
203+ // the value, no matter what.
204+ // This means, whenever we are in errorState, we and modelValue is set
205+ // imperatively, we DO want to format a value (it is the only way to get meaningful
206+ // input into `.inputElement` with modelValue as input)
207+
208+ if ( this . __isHandlingUserInput && this . errorState ) {
186209 return this . inputElement ? this . value : undefined ;
187210 }
188211 return this . formatter ( this . modelValue , this . formatOptions ) ;
@@ -231,21 +254,19 @@ export const FormatMixin = dedupeMixin(
231254 */
232255 _syncValueUpwards ( ) {
233256 // Downwards syncing should only happen for <lion-field>.value changes from 'above'
234- this . __preventDownwardsSync = true ;
235257 // This triggers _onModelValueChanged and connects user input to the
236258 // parsing/formatting/serializing loop
237259 this . modelValue = this . __callParser ( this . value ) ;
238- this . __preventDownwardsSync = false ;
239260 }
240261
241262 /**
242263 * Synchronization from <lion-field>.value to <input>.value
243264 */
244265 _reflectBackFormattedValueToUser ( ) {
245266 // Downwards syncing 'back and forth' prevents change event from being fired in IE.
246- // So only sync when the source of new <lion-field>.value change was not the 'input' event of
247- // inputElement
248- if ( ! this . __preventDownwardsSync ) {
267+ // So only sync when the source of new <lion-field>.value change was not the 'input' event
268+ // of inputElement
269+ if ( ! this . __isHandlingUserInput ) {
249270 // Text 'undefined' should not end up in <input>
250271 this . value = typeof this . formattedValue !== 'undefined' ? this . formattedValue : '' ;
251272 }
@@ -266,9 +287,12 @@ export const FormatMixin = dedupeMixin(
266287 }
267288
268289 _onUserInputChanged ( ) {
269- // Upwards syncing. Most properties are delegated right away, value is synced to <lion-field>,
270- // to be able to act on (imperatively set) value changes
290+ // Upwards syncing. Most properties are delegated right away, value is synced to
291+ // <lion-field>, to be able to act on (imperatively set) value changes
292+
293+ this . __isHandlingUserInput = true ;
271294 this . _syncValueUpwards ( ) ;
295+ this . __isHandlingUserInput = false ;
272296 }
273297
274298 constructor ( ) {
@@ -289,11 +313,11 @@ export const FormatMixin = dedupeMixin(
289313 this . inputElement . addEventListener ( this . formatOn , this . _reflectBackFormattedValueDebounced ) ;
290314 this . inputElement . addEventListener ( 'input' , this . _proxyInputEvent ) ;
291315 this . addEventListener ( 'user-input-changed' , this . _onUserInputChanged ) ;
292- // Connect the value found in <input> to the formatting/parsing/serializing loop as a fallback
293- // mechanism. Assume the user uses the value property of the <lion-field>(recommended api) as
294- // the api (this is a downwards sync).
295- // However, when no value is specified on <lion-field>, have support for sync of the real input
296- // to the <lion-field> (upwards sync).
316+ // Connect the value found in <input> to the formatting/parsing/serializing loop as a
317+ // fallback mechanism. Assume the user uses the value property of the
318+ // <lion-field>(recommended api) as the api (this is a downwards sync).
319+ // However, when no value is specified on <lion-field>, have support for sync of the real
320+ // input to the <lion-field> (upwards sync).
297321 if ( typeof this . modelValue === 'undefined' ) {
298322 this . _syncValueUpwards ( ) ;
299323 }
0 commit comments