chore: refactor some more

This commit is contained in:
Joren Broekema 2020-10-01 14:24:05 +02:00
parent 0aa4480e0c
commit 2f48f59244
15 changed files with 152 additions and 90 deletions

View file

@ -122,6 +122,23 @@ const FormatMixinImplementation = superclass =>
} }
} }
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;
}
}
/** /**
* Converts formattedValue to modelValue * Converts formattedValue to modelValue
* For instance, a localized date to a Date Object * For instance, a localized date to a Date Object

View file

@ -38,10 +38,6 @@ export class LionField extends FormControlMixin(
}; };
} }
get _inputNode() {
return /** @type {HTMLElement} */ (super._inputNode); // casts type
}
constructor() { constructor() {
super(); super();
this.name = ''; this.name = '';

View file

@ -1,18 +1,50 @@
import { dedupeMixin } from '@lion/core'; import { dedupeMixin } from '@lion/core';
/** /**
* @typedef {import('../types/ValueMixinTypes').ValueMixin} ValueMixin * @typedef {import('../types/NativeTextFieldMixinTypes').NativeTextFieldMixin} NativeTextFieldMixin
* @type {ValueMixin} * @type {NativeTextFieldMixin}
* @param {import('@open-wc/dedupe-mixin').Constructor<import('../types/ValueMixinTypes').LionFieldWithValue>} superclass} superclass * @param {import('@open-wc/dedupe-mixin').Constructor<import('../types/NativeTextFieldMixinTypes').NativeTextField>} superclass} superclass
*/ */
const ValueMixinImplementation = superclass => const NativeTextFieldMixinImplementation = superclass =>
class ValueMixin extends superclass { class NativeTextFieldMixin extends superclass {
/** @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;
}
}
get value() { get value() {
return (this._inputNode && this._inputNode.value) || this.__value || ''; return (this._inputNode && this._inputNode.value) || this.__value || '';
} }
// We don't delegate, because we want to preserve caret position via _setValueAndPreserveCaret // We don't delegate, because we want to preserve caret position via _setValueAndPreserveCaret
/** @type {string} */ /** @param {string} value */
set value(value) { set value(value) {
// if not yet connected to dom can't change the value // if not yet connected to dom can't change the value
if (this._inputNode) { if (this._inputNode) {
@ -54,4 +86,4 @@ const ValueMixinImplementation = superclass =>
} }
}; };
export const ValueMixin = dedupeMixin(ValueMixinImplementation); export const NativeTextFieldMixin = dedupeMixin(NativeTextFieldMixinImplementation);

View file

@ -125,6 +125,9 @@ const FormGroupMixinImplementation = superclass =>
constructor() { constructor() {
super(); super();
// inputNode = this, which always requires a value prop
this.value = '';
this.disabled = false; this.disabled = false;
this.submitted = false; this.submitted = false;
this.dirty = false; this.dirty = false;

View file

@ -6,6 +6,10 @@ import { DisabledHost } from '@lion/core/types/DisabledMixinTypes';
import { LionValidationFeedback } from '../src/validate/LionValidationFeedback'; import { LionValidationFeedback } from '../src/validate/LionValidationFeedback';
import { FormRegisteringHost } from './registration/FormRegisteringMixinTypes'; import { FormRegisteringHost } from './registration/FormRegisteringMixinTypes';
declare interface HTMLElementWithValue extends HTMLElement {
value: string;
}
export class FormControlHost { export class FormControlHost {
static get styles(): CSSResult | CSSResult[]; static get styles(): CSSResult | CSSResult[];
/** /**
@ -52,7 +56,7 @@ export class FormControlHost {
get fieldName(): string; get fieldName(): string;
__fieldName: string | undefined; __fieldName: string | undefined;
get slots(): SlotsMap; get slots(): SlotsMap;
get _inputNode(): HTMLElement; get _inputNode(): HTMLElementWithValue;
get _labelNode(): HTMLElement; get _labelNode(): HTMLElement;
get _helpTextNode(): HTMLElement; get _helpTextNode(): HTMLElement;
get _feedbackNode(): LionValidationFeedback | undefined; get _feedbackNode(): LionValidationFeedback | undefined;

View file

@ -9,7 +9,6 @@ export declare class FormatHost {
serializedValue: string; serializedValue: string;
formatOn: string; formatOn: string;
formatOptions: FormatNumberOptions; formatOptions: FormatNumberOptions;
value: string;
__preventRecursiveTrigger: boolean; __preventRecursiveTrigger: boolean;
__isHandlingUserInput: boolean; __isHandlingUserInput: boolean;
@ -18,6 +17,9 @@ export declare class FormatHost {
serializer(v: unknown): string; serializer(v: unknown): string;
deserializer(v: string): unknown; deserializer(v: string): unknown;
get value(): string;
set value(value: string);
_calculateValues(opts: { source: 'model' | 'serialized' | 'formatted' | null }): void; _calculateValues(opts: { source: 'model' | 'serialized' | 'formatted' | null }): void;
__callParser(value: string | undefined): object; __callParser(value: string | undefined): object;
__callFormatter(): string; __callFormatter(): string;

View file

@ -0,0 +1,19 @@
import { Constructor } from '@open-wc/dedupe-mixin';
import { LionField } from '@lion/form-core/src/LionField';
export declare class NativeTextField extends LionField {
get _inputNode(): HTMLTextAreaElement | HTMLInputElement;
}
export declare class NativeTextFieldHost {
get selectionStart(): number;
set selectionStart(value: number);
get selectionEnd(): number;
set selectionEnd(value: number);
}
export declare function NativeTextFieldImplementation<T extends Constructor<NativeTextField>>(
superclass: T,
): T & Constructor<NativeTextFieldHost> & NativeTextFieldHost;
export type NativeTextFieldMixin = typeof NativeTextFieldImplementation;

View file

@ -1,18 +0,0 @@
import { Constructor } from '@open-wc/dedupe-mixin';
import { LionField } from '@lion/form-core/src/LionField';
export declare class LionFieldWithValue extends LionField {
get _inputNode(): HTMLTextAreaElement | HTMLInputElement | HTMLSelectElement;
}
export declare class ValueHost {
get value(): string;
set value(value: string);
_setValueAndPreserveCaret(newValue: string): void;
}
export declare function ValueImplementation<T extends Constructor<LionFieldWithValue>>(
superclass: T,
): T & Constructor<ValueHost> & ValueHost;
export type ValueMixin = typeof ValueImplementation;

View file

@ -53,14 +53,11 @@ export class LionInputAmount extends LocalizeMixin(LionInput) {
} }
static get styles() { static get styles() {
return [ return css`
super.styles,
css`
.input-group__container > .input-group__input ::slotted(.form-control) { .input-group__container > .input-group__input ::slotted(.form-control) {
text-align: right; text-align: right;
} }
`, `;
];
} }
constructor() { constructor() {

View file

@ -10,14 +10,11 @@ import { IsNumber, MinNumber, MaxNumber } from '@lion/form-core';
// @ts-expect-error false positive for incompatible static get properties. Lit-element merges super properties already for you. // @ts-expect-error false positive for incompatible static get properties. Lit-element merges super properties already for you.
export class LionInputStepper extends LionInput { export class LionInputStepper extends LionInput {
static get styles() { static get styles() {
return [ return css`
super.styles,
css`
.input-group__container > .input-group__input ::slotted(.form-control) { .input-group__container > .input-group__input ::slotted(.form-control) {
text-align: center; text-align: center;
} }
`, `;
];
} }
static get properties() { static get properties() {

View file

@ -1,14 +1,13 @@
import { LionField } from '@lion/form-core'; import { LionField } from '@lion/form-core';
import { ValueMixin } from '@lion/form-core/src/ValueMixin'; import { NativeTextFieldMixin } from '@lion/form-core/src/NativeTextFieldMixin';
/** /**
* LionInput: extension of lion-field with native input element in place and user friendly API. * LionInput: extension of lion-field with native input element in place and user friendly API.
* *
* @customElement lion-input * @customElement lion-input
* @extends {LionField}
*/ */
// @ts-expect-error false positive for incompatible static get properties. Lit-element merges super properties already for you. // @ts-expect-error false positive for incompatible static get properties. Lit-element merges super properties already for you.
export class LionInput extends ValueMixin(LionField) { export class LionInput extends NativeTextFieldMixin(LionField) {
static get properties() { static get properties() {
return { return {
/** /**
@ -54,38 +53,6 @@ export class LionInput extends ValueMixin(LionField) {
return /** @type {HTMLInputElement} */ (super._inputNode); // casts type 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;
}
}
constructor() { constructor() {
super(); super();
this.readOnly = false; this.readOnly = false;

View file

@ -9,6 +9,7 @@ import { LionOptions } from './LionOptions.js';
// list items that can be found via MutationObserver or registration (.formElements) // list items that can be found via MutationObserver or registration (.formElements)
/** /**
* @typedef {import('@lion/form-core/types/FormControlMixinTypes').HTMLElementWithValue} HTMLElementWithValue
* @typedef {import('./LionOption').LionOption} LionOption * @typedef {import('./LionOption').LionOption} LionOption
* @typedef {import('../types/ListboxMixinTypes').ListboxMixin} ListboxMixin * @typedef {import('../types/ListboxMixinTypes').ListboxMixin} ListboxMixin
* @typedef {import('../types/ListboxMixinTypes').ListboxHost} ListboxHost * @typedef {import('../types/ListboxMixinTypes').ListboxHost} ListboxHost
@ -131,15 +132,15 @@ const ListboxMixinImplementation = superclass =>
* @configure FormControlMixin * @configure FormControlMixin
*/ */
get _inputNode() { get _inputNode() {
return /** @type {HTMLElement} */ (this.querySelector('[slot="input"]')); return /** @type {HTMLElementWithValue} */ (this.querySelector('[slot="input"]'));
} }
/** /**
* @overridable * @overridable
* @type {LionOptions}
*/ */
get _listboxNode() { get _listboxNode() {
return /** @type {LionOptions} */ (this._inputNode); // Cast to unknown first, since HTMLElementWithValue is not compatible with LionOptions
return /** @type {LionOptions} */ (/** @type {unknown} */ (this._inputNode));
} }
/** /**

View file

@ -1,6 +1,5 @@
/* eslint-disable max-classes-per-file */ /* eslint-disable max-classes-per-file */
import { LionField } from '@lion/form-core'; import { LionField } from '@lion/form-core';
import { ValueMixin } from '@lion/form-core/src/ValueMixin';
class LionFieldWithSelect extends LionField { class LionFieldWithSelect extends LionField {
/** /**
@ -38,14 +37,48 @@ class LionFieldWithSelect extends LionField {
* usability for keyboard and screen reader users. * usability for keyboard and screen reader users.
* *
* @customElement lion-select * @customElement lion-select
* @extends {LionField}
*/ */
export class LionSelect extends ValueMixin(LionFieldWithSelect) { export class LionSelect extends LionFieldWithSelect {
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
this._inputNode.addEventListener('change', this._proxyChangeEvent); this._inputNode.addEventListener('change', this._proxyChangeEvent);
} }
// FIXME: For some reason we have to override this FormatMixin getter/setter pair for the tests to pass
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;
}
}
/** @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);
}
}
disconnectedCallback() { disconnectedCallback() {
super.disconnectedCallback(); super.disconnectedCallback();
this._inputNode.removeEventListener('change', this._proxyChangeEvent); this._inputNode.removeEventListener('change', this._proxyChangeEvent);

View file

@ -72,6 +72,9 @@ export class LionSwitchButton extends DisabledWithTabIndexMixin(LitElement) {
constructor() { constructor() {
super(); super();
// inputNode = this, which always requires a value prop
this.value = '';
this.role = 'switch'; this.role = 'switch';
this.checked = false; this.checked = false;
this.__toggleChecked = this.__toggleChecked.bind(this); this.__toggleChecked = this.__toggleChecked.bind(this);

View file

@ -3,7 +3,7 @@
import autosize from 'autosize/src/autosize.js'; import autosize from 'autosize/src/autosize.js';
import { LionField } from '@lion/form-core'; import { LionField } from '@lion/form-core';
import { css } from '@lion/core'; import { css } from '@lion/core';
import { ValueMixin } from '@lion/form-core/src/ValueMixin'; import { NativeTextFieldMixin } from '@lion/form-core/src/NativeTextFieldMixin';
class LionFieldWithTextArea extends LionField { class LionFieldWithTextArea extends LionField {
/** /**
@ -22,7 +22,7 @@ class LionFieldWithTextArea extends LionField {
* @customElement lion-textarea * @customElement lion-textarea
*/ */
// @ts-expect-error false positive, parent properties get merged by lit-element already // @ts-expect-error false positive, parent properties get merged by lit-element already
export class LionTextarea extends ValueMixin(LionFieldWithTextArea) { export class LionTextarea extends NativeTextFieldMixin(LionFieldWithTextArea) {
static get properties() { static get properties() {
return { return {
maxRows: { maxRows: {
@ -78,6 +78,15 @@ export class LionTextarea extends ValueMixin(LionFieldWithTextArea) {
/** @param {import('lit-element').PropertyValues } changedProperties */ /** @param {import('lit-element').PropertyValues } changedProperties */
updated(changedProperties) { updated(changedProperties) {
super.updated(changedProperties); super.updated(changedProperties);
if (changedProperties.has('name')) {
this._inputNode.name = this.name;
}
if (changedProperties.has('autocomplete')) {
this._inputNode.autocomplete = /** @type {string} */ (this.autocomplete);
}
if (changedProperties.has('disabled')) { if (changedProperties.has('disabled')) {
this._inputNode.disabled = this.disabled; this._inputNode.disabled = this.disabled;
this.validate(); this.validate();