@lion/form-core
Advanced tools
Comparing version 0.6.2 to 0.6.3
# Change Log | ||
## 0.6.3 | ||
### Patch Changes | ||
- d5faa459: ChoiceGroupMixin: On value change uncheck all formElements that do not meet the requested condition | ||
- 0aa4480e: Refactor of some fields to ensure that \_inputNode has the right type. It starts as HTMLElement for LionField, and all HTMLInputElement, HTMLSelectElement and HTMLTextAreaElement logic, are moved to the right places. | ||
## 0.6.2 | ||
@@ -4,0 +11,0 @@ |
{ | ||
"name": "@lion/form-core", | ||
"version": "0.6.2", | ||
"version": "0.6.3", | ||
"description": "Form-core contains all essential building blocks for creating form fields and fieldsets", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -271,2 +271,4 @@ import { dedupeMixin } from '@lion/core'; | ||
this.formElements[i].checked = true; | ||
} else { | ||
this.formElements[i].checked = false; | ||
} | ||
@@ -273,0 +275,0 @@ } |
@@ -128,2 +128,5 @@ import { dedupeMixin, html, SlotMixin } from '@lion/core'; | ||
super(); | ||
// inputNode = this, which always requires a value prop | ||
this.value = ''; | ||
this.disabled = false; | ||
@@ -130,0 +133,0 @@ this.submitted = false; |
@@ -125,2 +125,19 @@ /* eslint-disable class-methods-use-this */ | ||
get value() { | ||
return (this._inputNode && this._inputNode.value) || this.__value || ''; | ||
} | ||
// We don't delegate, because we want to preserve caret position via _setValueAndPreserveCaret | ||
/** @type {string} */ | ||
set value(value) { | ||
// if not yet connected to dom can't change the value | ||
if (this._inputNode) { | ||
this._inputNode.value = value; | ||
/** @type {string | undefined} */ | ||
this.__value = undefined; | ||
} else { | ||
this.__value = value; | ||
} | ||
} | ||
/** | ||
@@ -127,0 +144,0 @@ * Converts formattedValue to modelValue |
@@ -610,54 +610,52 @@ import { css, dedupeMixin, html, nothing, SlotMixin } from '@lion/core'; | ||
static get styles() { | ||
return [ | ||
css` | ||
/********************** | ||
return css` | ||
/********************** | ||
{block} .form-field | ||
********************/ | ||
********************/ | ||
:host { | ||
display: block; | ||
} | ||
:host { | ||
display: block; | ||
} | ||
:host([hidden]) { | ||
display: none; | ||
} | ||
:host([hidden]) { | ||
display: none; | ||
} | ||
:host([disabled]) { | ||
pointer-events: none; | ||
} | ||
:host([disabled]) { | ||
pointer-events: none; | ||
} | ||
:host([disabled]) .form-field__label ::slotted(*), | ||
:host([disabled]) .form-field__help-text ::slotted(*) { | ||
color: var(--disabled-text-color, #adadad); | ||
} | ||
:host([disabled]) .form-field__label ::slotted(*), | ||
:host([disabled]) .form-field__help-text ::slotted(*) { | ||
color: var(--disabled-text-color, #adadad); | ||
} | ||
/*********************** | ||
/*********************** | ||
{block} .input-group | ||
*********************/ | ||
*********************/ | ||
.input-group__container { | ||
display: flex; | ||
} | ||
.input-group__container { | ||
display: flex; | ||
} | ||
.input-group__input { | ||
flex: 1; | ||
display: flex; | ||
} | ||
.input-group__input { | ||
flex: 1; | ||
display: flex; | ||
} | ||
/***** {state} :disabled *****/ | ||
:host([disabled]) .input-group ::slotted(slot='input') { | ||
color: var(--disabled-text-color, #adadad); | ||
} | ||
/***** {state} :disabled *****/ | ||
:host([disabled]) .input-group ::slotted(slot='input') { | ||
color: var(--disabled-text-color, #adadad); | ||
} | ||
/*********************** | ||
/*********************** | ||
{block} .form-control | ||
**********************/ | ||
**********************/ | ||
.input-group__container > .input-group__input ::slotted(.form-control) { | ||
flex: 1 1 auto; | ||
margin: 0; /* remove input margin in Safari */ | ||
font-size: 100%; /* normalize default input font-size */ | ||
} | ||
`, | ||
]; | ||
.input-group__container > .input-group__input ::slotted(.form-control) { | ||
flex: 1 1 auto; | ||
margin: 0; /* remove input margin in Safari */ | ||
font-size: 100%; /* normalize default input font-size */ | ||
} | ||
`; | ||
} | ||
@@ -664,0 +662,0 @@ |
@@ -29,11 +29,3 @@ declare const LionField_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@lion/core/types/SlotMixinTypes").SlotHost> & import("@open-wc/dedupe-mixin").Constructor<import("../types/validate/ValidateMixinTypes.js").ValidateHost> & typeof import("../types/validate/ValidateMixinTypes.js").ValidateHost & import("@open-wc/dedupe-mixin").Constructor<import("../types/FormControlMixinTypes.js").FormControlHost> & typeof import("../types/FormControlMixinTypes.js").FormControlHost & import("@open-wc/dedupe-mixin").Constructor<import("../types/utils/SyncUpdatableMixinTypes.js").SyncUpdatableHost> & typeof import("../types/utils/SyncUpdatableMixinTypes.js").SyncUpdatableHost & import("@open-wc/dedupe-mixin").Constructor<import("@lion/core/types/DisabledMixinTypes").DisabledHost> & typeof import("@lion/core/types/DisabledMixinTypes").DisabledHost & typeof import("@lion/core/types/SlotMixinTypes").SlotHost & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/src/types").ScopedElementsHost> & typeof import("@open-wc/scoped-elements/src/types").ScopedElementsHost & import("@open-wc/dedupe-mixin").Constructor<import("../types/FormatMixinTypes.js").FormatHost> & import("../types/FormatMixinTypes.js").FormatHost & import("@open-wc/dedupe-mixin").Constructor<import("../types/FocusMixinTypes.js").FocusHost> & import("../types/FocusMixinTypes.js").FocusHost & import("@open-wc/dedupe-mixin").Constructor<import("../types/InteractionStateMixinTypes.js").InteractionStateHost> & import("../types/InteractionStateMixinTypes.js").InteractionStateHost & import("../types/FormControlMixinTypes.js").FormControlHost & import("@open-wc/dedupe-mixin").Constructor<import("../types/registration/FormRegisteringMixinTypes.js").FormRegisteringHost> & typeof import("../types/registration/FormRegisteringMixinTypes.js").FormRegisteringHost; | ||
}; | ||
set selectionStart(arg: number); | ||
/** @type {number} */ | ||
get selectionStart(): number; | ||
set selectionEnd(arg: number); | ||
/** @type {number} */ | ||
get selectionEnd(): number; | ||
/** @type {string | undefined} */ | ||
__value: string | undefined; | ||
/** @type {string | undefined} */ | ||
autocomplete: string | undefined; | ||
@@ -49,9 +41,4 @@ /** @type {any} */ | ||
clear(): void; | ||
/** | ||
* Restores the cursor to its original position after updating the value. | ||
* @param {string} newValue The value that should be saved. | ||
*/ | ||
_setValueAndPreserveCaret(newValue: string): void; | ||
} | ||
import { LitElement } from "@lion/core"; | ||
export {}; |
@@ -41,55 +41,2 @@ import { LitElement, SlotMixin } from '@lion/core'; | ||
get _inputNode() { | ||
return /** @type {HTMLInputElement} */ (super._inputNode); // casts type | ||
} | ||
/** @type {number} */ | ||
get selectionStart() { | ||
const native = this._inputNode; | ||
if (native && native.selectionStart) { | ||
return native.selectionStart; | ||
} | ||
return 0; | ||
} | ||
set selectionStart(value) { | ||
const native = this._inputNode; | ||
if (native && native.selectionStart) { | ||
native.selectionStart = value; | ||
} | ||
} | ||
/** @type {number} */ | ||
get selectionEnd() { | ||
const native = this._inputNode; | ||
if (native && native.selectionEnd) { | ||
return native.selectionEnd; | ||
} | ||
return 0; | ||
} | ||
set selectionEnd(value) { | ||
const native = this._inputNode; | ||
if (native && native.selectionEnd) { | ||
native.selectionEnd = value; | ||
} | ||
} | ||
// We don't delegate, because we want to preserve caret position via _setValueAndPreserveCaret | ||
/** @type {string} */ | ||
set value(value) { | ||
// if not yet connected to dom can't change the value | ||
if (this._inputNode) { | ||
this._setValueAndPreserveCaret(value); | ||
/** @type {string | undefined} */ | ||
this.__value = undefined; | ||
} else { | ||
this.__value = value; | ||
} | ||
} | ||
get value() { | ||
return (this._inputNode && this._inputNode.value) || this.__value || ''; | ||
} | ||
constructor() { | ||
@@ -123,22 +70,2 @@ super(); | ||
/** | ||
* @param {import('lit-element').PropertyValues } changedProperties | ||
*/ | ||
updated(changedProperties) { | ||
super.updated(changedProperties); | ||
if (changedProperties.has('disabled')) { | ||
this._inputNode.disabled = this.disabled; | ||
this.validate(); | ||
} | ||
if (changedProperties.has('name')) { | ||
this._inputNode.name = this.name; | ||
} | ||
if (changedProperties.has('autocomplete')) { | ||
this._inputNode.autocomplete = /** @type {string} */ (this.autocomplete); | ||
} | ||
} | ||
resetInteractionState() { | ||
@@ -169,28 +96,2 @@ super.resetInteractionState(); | ||
} | ||
/** | ||
* Restores the cursor to its original position after updating the value. | ||
* @param {string} newValue The value that should be saved. | ||
*/ | ||
_setValueAndPreserveCaret(newValue) { | ||
// Only preserve caret if focused (changing selectionStart will move focus in Safari) | ||
if (this.focused) { | ||
// Not all elements might have selection, and even if they have the | ||
// right properties, accessing them might throw an exception (like for | ||
// <input type=number>) | ||
try { | ||
const start = this._inputNode.selectionStart; | ||
this._inputNode.value = newValue; | ||
// The cursor automatically jumps to the end after re-setting the value, | ||
// so restore it to its original position. | ||
this._inputNode.selectionStart = start; | ||
this._inputNode.selectionEnd = start; | ||
} catch (error) { | ||
// Just set the value and give up on the caret. | ||
this._inputNode.value = newValue; | ||
} | ||
} else { | ||
this._inputNode.value = newValue; | ||
} | ||
} | ||
} |
@@ -6,3 +6,2 @@ import { unsafeHTML } from '@lion/core'; | ||
import { | ||
aTimeout, | ||
expect, | ||
@@ -127,17 +126,2 @@ fixture, | ||
it('can be disabled via attribute', async () => { | ||
const elDisabled = /** @type {LionField} */ (await fixture( | ||
html`<${tag} disabled>${inputSlot}</${tag}>`, | ||
)); | ||
expect(elDisabled.disabled).to.equal(true); | ||
expect(elDisabled._inputNode.disabled).to.equal(true); | ||
}); | ||
it('can be disabled via property', async () => { | ||
const el = /** @type {LionField} */ (await fixture(html`<${tag}>${inputSlot}</${tag}>`)); | ||
el.disabled = true; | ||
await el.updateComplete; | ||
expect(el._inputNode.disabled).to.equal(true); | ||
}); | ||
it('can be cleared which erases value, validation and interaction states', async () => { | ||
@@ -166,56 +150,2 @@ const el = /** @type {LionField} */ (await fixture( | ||
it('reads initial value from attribute value', async () => { | ||
const el = /** @type {LionField} */ (await fixture( | ||
html`<${tag} value="one">${inputSlot}</${tag}>`, | ||
)); | ||
expect(getSlot(el, 'input').value).to.equal('one'); | ||
}); | ||
it('delegates value property', async () => { | ||
const el = /** @type {LionField} */ (await fixture(html`<${tag}>${inputSlot}</${tag}>`)); | ||
expect(getSlot(el, 'input').value).to.equal(''); | ||
el.value = 'one'; | ||
expect(el.value).to.equal('one'); | ||
expect(getSlot(el, 'input').value).to.equal('one'); | ||
}); | ||
// This is necessary for security, so that _inputNodes autocomplete can be set to 'off' | ||
it('delegates autocomplete property', async () => { | ||
const el = /** @type {LionField} */ (await fixture(html`<${tag}>${inputSlot}</${tag}>`)); | ||
expect(el._inputNode.autocomplete).to.equal(''); | ||
expect(el._inputNode.hasAttribute('autocomplete')).to.be.false; | ||
el.autocomplete = 'off'; | ||
await el.updateComplete; | ||
expect(el._inputNode.autocomplete).to.equal('off'); | ||
expect(el._inputNode.getAttribute('autocomplete')).to.equal('off'); | ||
}); | ||
it('preserves the caret position on value change for native text fields (input|textarea)', async () => { | ||
const el = /** @type {LionField} */ (await fixture(html`<${tag}>${inputSlot}</${tag}>`)); | ||
await triggerFocusFor(el); | ||
await el.updateComplete; | ||
el._inputNode.value = 'hello world'; | ||
el._inputNode.selectionStart = 2; | ||
el._inputNode.selectionEnd = 2; | ||
el.value = 'hey there universe'; | ||
expect(el._inputNode.selectionStart).to.equal(2); | ||
expect(el._inputNode.selectionEnd).to.equal(2); | ||
}); | ||
// TODO: Add test that css pointerEvents is none if disabled. | ||
it('is disabled when disabled property is passed', async () => { | ||
const el = /** @type {LionField} */ (await fixture(html`<${tag}>${inputSlot}</${tag}>`)); | ||
expect(el._inputNode.hasAttribute('disabled')).to.equal(false); | ||
el.disabled = true; | ||
await el.updateComplete; | ||
await aTimeout(0); | ||
expect(el._inputNode.hasAttribute('disabled')).to.equal(true); | ||
const disabledel = /** @type {LionField} */ (await fixture( | ||
html`<${tag} disabled>${inputSlot}</${tag}>`, | ||
)); | ||
expect(disabledel._inputNode.hasAttribute('disabled')).to.equal(true); | ||
}); | ||
describe('Accessibility', () => { | ||
@@ -443,33 +373,2 @@ it(`by setting corresponding aria-labelledby (for label) and aria-describedby (for helpText, feedback) | ||
it('should remove validation when disabled state toggles', async () => { | ||
const HasX = class extends Validator { | ||
static get validatorName() { | ||
return 'HasX'; | ||
} | ||
/** | ||
* @param {string} value | ||
*/ | ||
execute(value) { | ||
const result = value.indexOf('x') === -1; | ||
return result; | ||
} | ||
}; | ||
const el = /** @type {LionField} */ (await fixture(html` | ||
<${tag} | ||
.validators=${[new HasX()]} | ||
.modelValue=${'a@b.nl'} | ||
> | ||
${inputSlot} | ||
</${tag}> | ||
`)); | ||
expect(el.hasFeedbackFor).to.deep.equal(['error']); | ||
expect(el.validationStates.error.HasX).to.exist; | ||
el.disabled = true; | ||
await el.updateComplete; | ||
expect(el.hasFeedbackFor).to.deep.equal([]); | ||
expect(el.validationStates.error).to.deep.equal({}); | ||
}); | ||
it('can be required', async () => { | ||
@@ -562,25 +461,2 @@ const el = /** @type {LionField} */ (await fixture(html` | ||
}); | ||
describe('Delegation', () => { | ||
it('delegates property value', async () => { | ||
const el = /** @type {LionField} */ (await fixture(html`<${tag}>${inputSlot}</${tag}>`)); | ||
expect(el._inputNode.value).to.equal(''); | ||
el.value = 'one'; | ||
expect(el.value).to.equal('one'); | ||
expect(el._inputNode.value).to.equal('one'); | ||
}); | ||
it('delegates property selectionStart and selectionEnd', async () => { | ||
const el = /** @type {LionField} */ (await fixture(html` | ||
<${tag} | ||
.modelValue=${'Some text to select'} | ||
>${unsafeHTML(inputSlotString)}</${tag}> | ||
`)); | ||
el.selectionStart = 5; | ||
el.selectionEnd = 12; | ||
expect(el._inputNode.selectionStart).to.equal(5); | ||
expect(el._inputNode.selectionEnd).to.equal(12); | ||
}); | ||
}); | ||
}); |
@@ -66,3 +66,3 @@ import { Constructor } from '@open-wc/dedupe-mixin'; | ||
_inputNode: HTMLInputElement; | ||
_inputNode: HTMLElement; | ||
} | ||
@@ -69,0 +69,0 @@ |
@@ -12,3 +12,2 @@ import { Constructor } from '@open-wc/dedupe-mixin'; | ||
formatOptions: FormatNumberOptions; | ||
value: string; | ||
__preventRecursiveTrigger: boolean; | ||
@@ -22,2 +21,5 @@ __isHandlingUserInput: boolean; | ||
get value(): string; | ||
set value(value: string); | ||
_calculateValues(opts: { source: 'model' | 'serialized' | 'formatted' | null }): void; | ||
@@ -24,0 +26,0 @@ __callParser(value: string | undefined): object; |
@@ -9,2 +9,6 @@ import { CSSResult, LitElement, nothing, TemplateResult } from '@lion/core'; | ||
declare interface HTMLElementWithValue extends HTMLElement { | ||
value: string; | ||
} | ||
export class FormControlHost { | ||
@@ -56,3 +60,3 @@ static get styles(): CSSResult | CSSResult[]; | ||
get slots(): SlotsMap; | ||
get _inputNode(): HTMLElement; | ||
get _inputNode(): HTMLElementWithValue; | ||
get _labelNode(): HTMLElement; | ||
@@ -59,0 +63,0 @@ get _helpTextNode(): HTMLElement; |
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
124
306139
7448