Socket
Socket
Sign inDemoInstall

@lion/field

Package Overview
Dependencies
Maintainers
1
Versions
103
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lion/field - npm Package Compare versions

Comparing version 0.1.28 to 0.1.29

8

CHANGELOG.md

@@ -6,2 +6,10 @@ # Change Log

## [0.1.29](https://github.com/ing-bank/lion/compare/@lion/field@0.1.28...@lion/field@0.1.29) (2019-07-16)
**Note:** Version bump only for package @lion/field
## [0.1.28](https://github.com/ing-bank/lion/compare/@lion/field@0.1.27...@lion/field@0.1.28) (2019-07-15)

@@ -8,0 +16,0 @@

29

docs/FormatMixin.md

@@ -7,16 +7,19 @@ # FormatMixin

### modelValue
The model value is the result of the parser function.
It should be considered as the internal value used for validation and reasoning/logic.
### model value
The model value is the result of the parser function. It will be stored as `.modelValue`
and should be considered the internal value used for validation and reasoning/logic.
The model value is 'ready for consumption' by the outside world (think of a Date object
or a float). The modelValue can(and is recommended to) be used as both input value and
output value of the `<lion-field>`
or a float). It can(and is recommended to) be used as both input value and
output value of the `LionField`.
Examples:
- 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 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`
### formattedValue
The view value is the result of the formatter function (when available).
The result will be stored in the native inputElement (usually an input[type=text]).
### view value
The view value is the result of the formatter function.
It will be stored as `.formattedValue` and synchronized to `.value` (a viewValue setter that
allows to synchronize to `.inputElement`).
Synchronization happens conditionally and is (by default) the result of a blur. Other conditions
(like error state/validity and whether the a model value was set programatically) also play a role.

@@ -28,5 +31,5 @@ Examples:

### serializedValue
The serialized version of the model value.
This value exists for maximal compatibility with the platform API.
### serialized value
This is the serialized version of the model value.
It exists for maximal compatibility with the platform API.
The serialized value can be an interface in context where data binding is not supported

@@ -33,0 +36,0 @@ and a serialized string needs to be set.

{
"name": "@lion/field",
"version": "0.1.28",
"version": "0.1.29",
"description": "Fields are the most fundamental building block of the Form System",

@@ -44,3 +44,3 @@ "author": "ing-bank",

},
"gitHead": "482bcbeaeda6aa9c931327883b894f442cb23d05"
"gitHead": "22256eed84f2fa5a6466ee59e9b5c7730f8fd7a2"
}

@@ -7,14 +7,44 @@ /* eslint-disable class-methods-use-this */

// For a future breaking release:
// - do not allow the private `.formattedValue` as property that can be set to
// trigger a computation loop.
// - do not fire events for those private and protected concepts
// - simplify _calculateValues: recursive trigger lock can be omitted, since need for connecting
// the loop via sync observers is not needed anymore.
// - consider `formatOn` as an overridable function, by default something like:
// `(!__isHandlingUserInput || !errorState) && !focused`
// This would allow for more advanced scenarios, like formatting an input whenever it becomes valid.
// This would make formattedValue as a concept obsolete, since for maximum flexibility, the
// formattedValue condition needs to be evaluated right before syncing back to the view
/**
* @desc Designed to be applied on top of a LionField
* @desc Designed to be applied on top of a LionField.
* To understand all concepts within the Mixin, please consult the flow diagram in the
* documentation.
*
* ## Flows
* 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`
* [1] Application Developer sets `.modelValue`:
* Flow: `.modelValue` (formatter) -> `.formattedValue` -> `.inputElement.value`
* (serializer) -> `.serializedValue`
* [2] End user interacts with field:
* Flow: `@user-input-changed` (parser) -> `.modelValue` (formatter) -> `.formattedValue` - (debounce till reflect condition (formatOn) is met) -> `.inputElement.value`
* (serializer) -> `.serializedValue`
*
* @mixinFunction
* For backwards compatibility with the platform, we also support `.value` as an api. In that case
* the flow will be like [2], without the debounce.
*
* ## Difference between value, viewValue and formattedValue
* A viewValue is a concept rather than a property. To be compatible with the platform api, the
* property for the concept of viewValue is thus called `.value`.
* When reading code and docs, one should be aware that the term viewValue is mostly used, but the
* terms can be used interchangeably.
* The `.formattedValue` should be seen as the 'scheduled' viewValue. It is computed realtime and
* stores the output of formatter. It will replace viewValue. once condition `formatOn` is met.
* Another difference is that formattedValue lives on `LionField`, whereas viewValue is shared
* across `LionField` and `.inputElement`.
*
* For restoring serialized values fetched from a server, we could consider one extra flow:
* [3] Application Developer sets `.serializedValue`:
* Flow: serializedValue (deserializer) -> `.modelValue` (formatter) -> `.formattedValue` -> `.inputElement.value`
*/

@@ -32,5 +62,5 @@ export const FormatMixin = dedupeMixin(

* It should be considered as the internal value used for validation and reasoning/logic.
* The model value is 'ready for consumption' by the outside world (think of a Date object
* or a float). The modelValue can(and is recommended to) be used as both input value and
* output value of the <lion-field>
* The model value is 'ready for consumption' by the outside world (think of a Date
* object or a float). The modelValue can(and is recommended to) be used as both input
* value and output value of the `LionField`.
*

@@ -54,2 +84,4 @@ * Examples:

* 1234.56)
*
* @private
*/

@@ -73,2 +105,3 @@ formattedValue: {

* (being inputElement.value)
*
*/

@@ -107,7 +140,2 @@ serializedValue: {

/**
* === Formatting and parsing ====
* To understand all concepts below, please consult the flow diagrams in the documentation.
*/
/**
* Converts formattedValue to modelValue

@@ -123,6 +151,7 @@ * For instance, a localized date to a Date Object

/**
* Converts modelValue to formattedValue (formattedValue will be synced with <input>.value)
* For instance, a Date object to a localized date
* @param {Object} value - modelValue: can be an Object, Number, String depending on the input
* type(date, number, email etc)
* Converts modelValue to formattedValue (formattedValue will be synced with
* `.inputElement.value`)
* For instance, a Date object to a localized date.
* @param {Object} value - modelValue: can be an Object, Number, String depending on the
* input type(date, number, email etc)
* @returns {String} formattedValue

@@ -135,6 +164,6 @@ */

/**
* Converts modelValue to serializedValue (<lion-field>.value).
* Converts `.modelValue` to `.serializedValue`
* For instance, a Date object to an iso formatted date string
* @param {Object} value - modelValue: can be an Object, Number, String depending on the input
* type(date, number, email etc)
* @param {Object} value - modelValue: can be an Object, Number, String depending on the
* input type(date, number, email etc)
* @returns {String} serializedValue

@@ -147,6 +176,6 @@ */

/**
* Converts <lion-field>.value to modelValue
* Converts `LionField.value` to `.modelValue`
* For instance, an iso formatted date string to a Date object
* @param {Object} value - modelValue: can be an Object, Number, String depending on the input
* type(date, number, email etc)
* @param {Object} value - modelValue: can be an Object, Number, String depending on the
* input type(date, number, email etc)
* @returns {Object} modelValue

@@ -160,5 +189,4 @@ */

* 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.
* 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

@@ -201,3 +229,3 @@ * second call from having effect).

// - it can be expected by 3rd parties (for instance unit tests)
// TODO: In a breaking refactor of the Validation System, this behaviot can be corrected.
// TODO: In a breaking refactor of the Validation System, this behavior can be corrected.
return '';

@@ -210,4 +238,4 @@ }

// This means there is nothing to find inside the view that can be of
// interest to the Application Developer or needed to store for future form state
// retrieval.
// interest to the Application Developer or needed to store for future
// form state retrieval.
return undefined;

@@ -229,6 +257,2 @@ }

__callFormatter() {
if (this.modelValue instanceof Unparseable) {
return this.modelValue.viewValue;
}
// - Why check for this.errorState?

@@ -238,6 +262,6 @@ // We only want to format values that are considered valid. For best UX,

// - 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
// Downwards sync is prevented whenever we are in an `@user-input-changed` flow, [2].
// If we are in a 'imperatively set `.modelValue`' flow, [1], we want to reflect back
// the value, no matter what.
// This means, whenever we are in errorState, we and modelValue is set
// This means, whenever we are in errorState and modelValue is set
// imperatively, we DO want to format a value (it is the only way to get meaningful

@@ -249,2 +273,10 @@ // input into `.inputElement` with modelValue as input)

}
if (this.modelValue instanceof Unparseable) {
// When the modelValue currently is unparseable, we need to sync back the supplied
// viewValue. In flow [2], this should not be needed.
// In flow [1] (we restore a previously stored modelValue) we should sync down, however.
return this.modelValue.viewValue;
}
return this.formatter(this.modelValue, this.formatOptions);

@@ -259,9 +291,8 @@ }

// TODO: investigate if this also can be solved by using 'hasChanged' on property accessor
// inside choiceInputs
/**
* This is wrapped in a distinct method, so that parents can control when the changed event is
* fired. For instance: when modelValue is an object, a deep comparison is needed first
* This is wrapped in a distinct method, so that parents can control when the changed event
* is fired. For objects, a deep comparison might be needed.
*/
_dispatchModelValueChangedEvent() {
/** @event model-value-changed */
this.dispatchEvent(

@@ -273,2 +304,3 @@ new CustomEvent('model-value-changed', { bubbles: true, composed: true }),

_onFormattedValueChanged() {
/** @deprecated */
this.dispatchEvent(

@@ -284,2 +316,3 @@ new CustomEvent('formatted-value-changed', {

_onSerializedValueChanged() {
/** @deprecated */
this.dispatchEvent(

@@ -295,6 +328,6 @@ new CustomEvent('serialized-value-changed', {

/**
* Synchronization from <input>.value to <lion-field>.formattedValue
* Synchronization from `.inputElement.value` to `LionField` (flow [2])
*/
_syncValueUpwards() {
// Downwards syncing should only happen for <lion-field>.value changes from 'above'
// Downwards syncing should only happen for `LionField`.value changes from 'above'
// This triggers _onModelValueChanged and connects user input to the

@@ -306,8 +339,8 @@ // parsing/formatting/serializing loop

/**
* Synchronization from <lion-field>.value to <input>.value
* Synchronization from `LionField.value` to `.inputElement.value`
* - flow [1] will always be reflected back
* - flow [2] will not be reflected back when this flow was triggered via
* `@user-input-changed` (this will happen later, when `formatOn` condition is met)
*/
_reflectBackFormattedValueToUser() {
// 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.__isHandlingUserInput) {

@@ -319,7 +352,6 @@ // Text 'undefined' should not end up in <input>

// TODO: rename to __dispatchNormalizedInputEvent?
// This can be called whenever the view value should be updated. Dependent on component type
// ("input" for <input> or "change" for <select>(mainly for IE)) a different event should be used
// as "source" for the "user-input-changed" event (which can be seen as an abstraction layer on
// top of other events (input, change, whatever))
// ("input" for <input> or "change" for <select>(mainly for IE)) a different event should be
// used as source for the "user-input-changed" event (which can be seen as an abstraction
// layer on top of other events (input, change, whatever))
_proxyInputEvent() {

@@ -336,4 +368,3 @@ this.dispatchEvent(

// Upwards syncing. Most properties are delegated right away, value is synced to
// <lion-field>, to be able to act on (imperatively set) value changes
// `LionField`, to be able to act on (imperatively set) value changes
this.__isHandlingUserInput = true;

@@ -362,5 +393,5 @@ this._syncValueUpwards();

// 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).
// `LionField`(recommended api) as the api (this is a downwards sync).
// However, when no value is specified on `LionField`, have support for sync of the real
// input to the `LionField` (upwards sync).
if (typeof this.modelValue === 'undefined') {

@@ -367,0 +398,0 @@ this._syncValueUpwards();

@@ -1,3 +0,2 @@

import { DelegateMixin, SlotMixin } from '@lion/core';
import { LionLitElement } from '@lion/core/src/LionLitElement.js';
import { DelegateMixin, SlotMixin, LitElement } from '@lion/core';
import { ElementMixin } from '@lion/core/src/ElementMixin.js';

@@ -7,3 +6,2 @@ import { CssClassMixin } from '@lion/core/src/CssClassMixin.js';

import { ValidateMixin } from '@lion/validate';
import { FormControlMixin } from './FormControlMixin.js';

@@ -14,7 +12,19 @@ import { InteractionStateMixin } from './InteractionStateMixin.js'; // applies FocusMixin

/* eslint-disable wc/guard-super-call */
// TODO:
// - Consider exporting as FieldMixin
// - Add submitted prop to InteractionStateMixin
// - Find a better way to do value delegation via attr
/**
* LionField: wraps components input, textarea and select and potentially others
* (checkbox group, radio group)
* `LionField`: wraps <input>, <textarea>, <select> and other interactable elements.
* Also it would follow a nice hierarchy: lion-form -> lion-fieldset -> lion-field
*
* Note: We don't support placeholders, because we have a helper text and
* placeholders confuse the user with accessibility needs.
*
* Please see the docs for in depth information.
*
* @example
* <lion-field name="myName">

@@ -25,10 +35,4 @@ * <label slot="label">My Input</label>

*
* Note: We do not support placeholders, because we have a helper text and
* placeholders confuse the user with accessibility needs.
*
* @customElement
*/
// TODO: Consider exporting as FieldMixin
// eslint-disable-next-line max-len, no-unused-vars
export class LionField extends FormControlMixin(

@@ -39,3 +43,3 @@ InteractionStateMixin(

ValidateMixin(
CssClassMixin(ElementMixin(DelegateMixin(SlotMixin(ObserverMixin(LionLitElement))))),
CssClassMixin(ElementMixin(DelegateMixin(SlotMixin(ObserverMixin(LitElement))))),
),

@@ -72,10 +76,3 @@ ),

static get asyncObservers() {
return {
...super.asyncObservers,
_setDisabledClass: ['disabled'],
};
}
// We don't delegate, because we want to 'preprocess' via _setValueAndPreserveCaret
// We don't delegate, because we want to preserve caret position via _setValueAndPreserveCaret
set value(value) {

@@ -93,21 +90,17 @@ // if not yet connected to dom can't change the value

_setDisabledClass() {
this.classList[this.disabled ? 'add' : 'remove']('state-disabled');
static get asyncObservers() {
return {
...super.asyncObservers,
_setDisabledClass: ['disabled'],
};
}
resetInteractionState() {
if (super.resetInteractionState) super.resetInteractionState();
// TODO: add submitted prop to InteractionStateMixin ?
this.submitted = false;
}
/* * * * * * * *
Lifecycle */
connectedCallback() {
super.connectedCallback();
this._onChange = this._onChange.bind(this);
this.inputElement.addEventListener('change', this._onChange);
this._delegateInitialValueAttr(); // TODO: find a better way to do this
this._delegateInitialValueAttr();
this._setDisabledClass();
this.classList.add('form-field');
this.classList.add('form-field'); // eslint-disable-line
}

@@ -117,2 +110,3 @@

super.disconnectedCallback();
if (this.__parentFormGroup) {

@@ -128,2 +122,6 @@ const event = new CustomEvent('form-element-unregister', {

_setDisabledClass() {
this.classList[this.disabled ? 'add' : 'remove']('state-disabled');
}
/**

@@ -140,17 +138,22 @@ * This is not done via 'get delegations', because this.inputElement.setAttribute('value')

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Public Methods (also notice delegated methods that are available on host) */
resetInteractionState() {
if (super.resetInteractionState) {
super.resetInteractionState();
}
this.submitted = false;
}
clear() {
// Let validationMixin and interactionStateMixin clear their invalid and dirty/touched states
// respectively
if (super.clear) super.clear();
if (super.clear) {
// Let validationMixin and interactionStateMixin clear their
// invalid and dirty/touched states respectively
super.clear();
}
this.value = ''; // can't set null here, because IE11 treats it as a string
}
/* * * * * * * * * *
Event Handlers */
_onChange() {
if (super._onChange) super._onChange();
if (super._onChange) {
super._onChange();
}
this.dispatchEvent(

@@ -161,9 +164,8 @@ new CustomEvent('user-input-changed', {

);
this.modelValue = this.parser(this.value);
}
/* * * * * * * * * * * *
Observer Handlers */
_onValueChanged({ value }) {
if (super._onValueChanged) super._onValueChanged();
if (super._onValueChanged) {
super._onValueChanged();
}
// For styling purposes, make it known the input field is not empty

@@ -170,0 +172,0 @@ this.classList[value ? 'add' : 'remove']('state-filled');

@@ -8,2 +8,3 @@ import {

triggerBlurFor,
aTimeout,
} from '@open-wc/testing';

@@ -156,21 +157,20 @@ import { unsafeHTML } from '@lion/core';

// TODO: add pointerEvents test
// TODO: why is this a describe?
describe(`<lion-field> with <input disabled>${nameSuffix}`, () => {
it('has a class "state-disabled"', async () => {
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
expect(lionField.classList.contains('state-disabled')).to.equal(false);
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(false);
// TODO: add pointerEvents test for disabled
it('has a class "state-disabled"', async () => {
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
expect(lionField.classList.contains('state-disabled')).to.equal(false);
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(false);
lionField.disabled = true;
await lionField.updateComplete;
expect(lionField.classList.contains('state-disabled')).to.equal(true);
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(true);
lionField.disabled = true;
await lionField.updateComplete;
await aTimeout();
const disabledlionField = await fixture(
`<${tagString} disabled>${inputSlotString}</${tagString}>`,
);
expect(disabledlionField.classList.contains('state-disabled')).to.equal(true);
expect(disabledlionField.inputElement.hasAttribute('disabled')).to.equal(true);
});
expect(lionField.classList.contains('state-disabled')).to.equal(true);
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(true);
const disabledlionField = await fixture(
`<${tagString} disabled>${inputSlotString}</${tagString}>`,
);
expect(disabledlionField.classList.contains('state-disabled')).to.equal(true);
expect(disabledlionField.inputElement.hasAttribute('disabled')).to.equal(true);
});

@@ -360,3 +360,3 @@

expect(formatterSpy.callCount).to.equal(1);
expect(lionField.formattedValue).to.equal('foo');
expect(lionField.value).to.equal('foo');
});

@@ -363,0 +363,0 @@ });

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc