@lion/field
Advanced tools
Comparing version 0.1.22 to 0.1.23
@@ -6,2 +6,13 @@ # Change Log | ||
## [0.1.23](https://github.com/ing-bank/lion/compare/@lion/field@0.1.22...@lion/field@0.1.23) (2019-07-02) | ||
### Bug Fixes | ||
* **field:** format conditionally on user input only ([af8046c](https://github.com/ing-bank/lion/commit/af8046c)) | ||
## [0.1.22](https://github.com/ing-bank/lion/compare/@lion/field@0.1.21...@lion/field@0.1.22) (2019-06-27) | ||
@@ -8,0 +19,0 @@ |
{ | ||
"name": "@lion/field", | ||
"version": "0.1.22", | ||
"version": "0.1.23", | ||
"description": "Fields are the most fundamental building block of the Form System", | ||
@@ -44,3 +44,3 @@ "author": "ing-bank", | ||
}, | ||
"gitHead": "187d50b6bc14b5c9a2fec3e4908a242513257617" | ||
"gitHead": "e2a1986630108e02ecbfbb742bab6df6d5dfd32e" | ||
} |
@@ -9,3 +9,12 @@ /* eslint-disable class-methods-use-this */ | ||
/** | ||
* @polymerMixin | ||
* @desc Designed to be applied on top of a LionField | ||
* | ||
* FormatMixin supports these two main flows: | ||
* 1) Application Developer sets `.modelValue`: | ||
* Flow: `.modelValue` -> `.formattedValue` -> `.inputElement.value` | ||
* -> `.serializedValue` | ||
* 2) End user interacts with field: | ||
* Flow: `@user-input-changed` -> `.modelValue` -> `.formattedValue` - (debounce till reflect condition (formatOn) is met) -> `.inputElement.value` | ||
* -> `.serializedValue` | ||
* | ||
* @mixinFunction | ||
@@ -30,3 +39,4 @@ */ | ||
* - For a date input: a String '20/01/1999' will be converted to new Date('1999/01/20') | ||
* - For a number input: a formatted String '1.234,56' will be converted to a Number: 1234.56 | ||
* - For a number input: a formatted String '1.234,56' will be converted to a Number: | ||
* 1234.56 | ||
*/ | ||
@@ -53,9 +63,9 @@ modelValue: { | ||
* This value exists for maximal compatibility with the platform API. | ||
* The serialized value can be an interface in context where data binding is not supported | ||
* and a serialized string needs to be set. | ||
* The serialized value can be an interface in context where data binding is not | ||
* supported and a serialized string needs to be set. | ||
* | ||
* Examples: | ||
* - For a date input, this would be the iso format of a date, e.g. '1999-01-20'. | ||
* - For a number input this would be the String representation of a float ('1234.56' instead | ||
* of 1234.56) | ||
* - For a number input this would be the String representation of a float ('1234.56' | ||
* instead of 1234.56) | ||
* | ||
@@ -145,11 +155,12 @@ * When no parser is available, the value is usually the same as the formattedValue | ||
/** | ||
* Responsible for storing all representations(modelValue, serializedValue, formattedValue and | ||
* value) of the input value. | ||
* Prevents infinite loops, so all value observers can be treated like they will only be called | ||
* once, without indirectly calling other observers. | ||
* (in fact, some are called twice, but the __preventRecursiveTrigger lock prevents the second | ||
* call from having effect). | ||
* Responsible for storing all representations(modelValue, serializedValue, formattedValue | ||
* and value) of the input value. | ||
* Prevents infinite loops, so all value observers can be treated like they will only be | ||
* called once, without indirectly calling other observers. | ||
* (in fact, some are called twice, but the __preventRecursiveTrigger lock prevents the | ||
* second call from having effect). | ||
* | ||
* @param {string} source - the type of value that triggered this method. It should not be set | ||
* again, so that its observer won't be triggered. Can be: 'model'|'formatted'|'serialized'. | ||
* @param {string} source - the type of value that triggered this method. It should not be | ||
* set again, so that its observer won't be triggered. Can be: | ||
* 'model'|'formatted'|'serialized'. | ||
*/ | ||
@@ -189,3 +200,15 @@ _calculateValues({ source } = {}) { | ||
} | ||
if (this.errorState) { | ||
// - Why check for this.errorState? | ||
// We only want to format values that are considered valid. For best UX, | ||
// we only 'reward' valid inputs. | ||
// - Why check for __isHandlingUserInput? | ||
// Downwards sync is prevented whenever we are in a `@user-input-changed` flow. | ||
// If we are in a 'imperatively set `.modelValue`' flow, we want to reflect back | ||
// the value, no matter what. | ||
// This means, whenever we are in errorState, we and modelValue is set | ||
// imperatively, we DO want to format a value (it is the only way to get meaningful | ||
// input into `.inputElement` with modelValue as input) | ||
if (this.__isHandlingUserInput && this.errorState) { | ||
return this.inputElement ? this.value : undefined; | ||
@@ -239,7 +262,5 @@ } | ||
// Downwards syncing should only happen for <lion-field>.value changes from 'above' | ||
this.__preventDownwardsSync = true; | ||
// This triggers _onModelValueChanged and connects user input to the | ||
// parsing/formatting/serializing loop | ||
this.modelValue = this.__callParser(this.value); | ||
this.__preventDownwardsSync = false; | ||
} | ||
@@ -252,5 +273,5 @@ | ||
// Downwards syncing 'back and forth' prevents change event from being fired in IE. | ||
// So only sync when the source of new <lion-field>.value change was not the 'input' event of | ||
// inputElement | ||
if (!this.__preventDownwardsSync) { | ||
// So only sync when the source of new <lion-field>.value change was not the 'input' event | ||
// of inputElement | ||
if (!this.__isHandlingUserInput) { | ||
// Text 'undefined' should not end up in <input> | ||
@@ -276,5 +297,8 @@ this.value = typeof this.formattedValue !== 'undefined' ? this.formattedValue : ''; | ||
_onUserInputChanged() { | ||
// Upwards syncing. Most properties are delegated right away, value is synced to <lion-field>, | ||
// to be able to act on (imperatively set) value changes | ||
// Upwards syncing. Most properties are delegated right away, value is synced to | ||
// <lion-field>, to be able to act on (imperatively set) value changes | ||
this.__isHandlingUserInput = true; | ||
this._syncValueUpwards(); | ||
this.__isHandlingUserInput = false; | ||
} | ||
@@ -300,7 +324,7 @@ | ||
this.addEventListener('user-input-changed', this._onUserInputChanged); | ||
// Connect the value found in <input> to the formatting/parsing/serializing loop as a fallback | ||
// mechanism. Assume the user uses the value property of the <lion-field>(recommended api) as | ||
// the api (this is a downwards sync). | ||
// However, when no value is specified on <lion-field>, have support for sync of the real input | ||
// to the <lion-field> (upwards sync). | ||
// Connect the value found in <input> to the formatting/parsing/serializing loop as a | ||
// fallback mechanism. Assume the user uses the value property of the | ||
// <lion-field>(recommended api) as the api (this is a downwards sync). | ||
// However, when no value is specified on <lion-field>, have support for sync of the real | ||
// input to the <lion-field> (upwards sync). | ||
if (typeof this.modelValue === 'undefined') { | ||
@@ -307,0 +331,0 @@ this._syncValueUpwards(); |
@@ -160,2 +160,21 @@ import { expect, fixture, html, aTimeout, defineCE, unsafeStatic } from '@open-wc/testing'; | ||
it('reflects back .formattedValue immediately when .modelValue changed imperatively', async () => { | ||
const el = await fixture(html` | ||
<${elem} .formatter="${value => `foo: ${value}`}"> | ||
<input slot="input" /> | ||
</${elem}> | ||
`); | ||
// The FormatMixin can be used in conjunction with the ValidateMixin, in which case | ||
// it can hold errorState (affecting the formatting) | ||
el.errorState = true; | ||
// users types value 'test' | ||
mimicUserInput(el, 'test'); | ||
expect(el.inputElement.value).to.not.equal('foo: test'); | ||
// Now see the difference for an imperative change | ||
el.modelValue = 'test2'; | ||
expect(el.inputElement.value).to.equal('foo: test2'); | ||
}); | ||
describe('parsers/formatters/serializers', () => { | ||
@@ -210,3 +229,3 @@ it('should call the parser|formatter|serializer provided by user', async () => { | ||
it('will only call the formatter for valid values', async () => { | ||
it('will only call the formatter for valid values on `user-input-changed` ', async () => { | ||
const formatterSpy = sinon.spy(value => `foo: ${value}`); | ||
@@ -221,8 +240,8 @@ const el = await fixture(html` | ||
el.errorState = true; | ||
el.modelValue = 'bar'; | ||
mimicUserInput(el, 'bar'); | ||
expect(formatterSpy.callCount).to.equal(1); | ||
expect(el.formattedValue).to.equal('foo: init-string'); | ||
expect(el.formattedValue).to.equal('bar'); | ||
el.errorState = false; | ||
el.modelValue = 'bar2'; | ||
mimicUserInput(el, 'bar2'); | ||
expect(formatterSpy.callCount).to.equal(2); | ||
@@ -229,0 +248,0 @@ |
@@ -22,2 +22,7 @@ import { | ||
function mimicUserInput(formControl, newViewValue) { | ||
formControl.value = newViewValue; // eslint-disable-line no-param-reassign | ||
formControl.inputElement.dispatchEvent(new CustomEvent('input', { bubbles: true })); | ||
} | ||
beforeEach(() => { | ||
@@ -332,3 +337,3 @@ localizeTearDown(); | ||
it('will only update formattedValue the value when valid', async () => { | ||
it('will only update formattedValue when valid on `user-input-changed`', async () => { | ||
const formatterSpy = sinon.spy(value => `foo: ${value}`); | ||
@@ -353,5 +358,5 @@ function isBarValidator(value) { | ||
lionField.modelValue = 'foo'; | ||
mimicUserInput(lionField, 'foo'); | ||
expect(formatterSpy.callCount).to.equal(1); | ||
expect(lionField.formattedValue).to.equal('foo: bar'); | ||
expect(lionField.formattedValue).to.equal('foo'); | ||
}); | ||
@@ -358,0 +363,0 @@ }); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
171816
2161