Slider: improved value fallback handling + validation (#20228)
* term example * better localization options * localize range * ensure range value handling * extract lox high from value setting * further improvements * Update src/Umbraco.Web.UI.Client/src/assets/lang/en.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -2228,7 +2228,8 @@ export default {
|
||||
legacyOptionDescription: 'This option is no longer supported, please select something else',
|
||||
numberMinimum: "Value must be greater than or equal to '%0%'.",
|
||||
numberMaximum: "Value must be less than or equal to '%0%'.",
|
||||
numberMisconfigured: "Minimum value '%0%'must be less than the maximum value '%1%'.",
|
||||
numberMisconfigured: "Minimum value '%0%' must be less than the maximum value '%1%'.",
|
||||
rangeExceeds: 'The low value must not exceed the high value.',
|
||||
invalidExtensions: 'One or more of the extensions are invalid.',
|
||||
allowedExtensions: 'Allowed extensions are:',
|
||||
disallowedExtensions: 'Disallowed extensions are:',
|
||||
|
||||
@@ -114,6 +114,15 @@ export class UmbLocalizationController<LocalizationSetType extends UmbLocalizati
|
||||
* @param {string} key - the localization key, the indicator of what localization entry you want to retrieve.
|
||||
* @param {...any} args - the arguments to parse for this localization entry.
|
||||
* @returns {string} - the translated term as a string.
|
||||
* @example
|
||||
* Retrieving a term without any arguments:
|
||||
* ```ts
|
||||
* this.localize.term('area_term');
|
||||
* ```
|
||||
* Retrieving a term with arguments:
|
||||
* ```ts
|
||||
* this.localize.term('general_greeting', ['John']);
|
||||
* ```
|
||||
*/
|
||||
term<K extends keyof LocalizationSetType>(key: K, ...args: FunctionParams<LocalizationSetType[K]>): string {
|
||||
if (!this.#usedKeys.includes(key)) {
|
||||
|
||||
@@ -93,7 +93,7 @@ export class UmbInputNumberRangeElement extends UmbFormControlMixin(UmbLitElemen
|
||||
this.addValidator(
|
||||
'patternMismatch',
|
||||
() => {
|
||||
return 'The low value must not be exceed the high value';
|
||||
return '#validation_rangeExceeds';
|
||||
},
|
||||
() => {
|
||||
return this._minValue !== undefined && this._maxValue !== undefined ? this._minValue > this._maxValue : false;
|
||||
|
||||
@@ -1,11 +1,42 @@
|
||||
import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UUIFormControlMixin } from '@umbraco-cms/backoffice/external/uui';
|
||||
import type { UUISliderEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbFormControlMixin } from '../../validation/mixins/index.js';
|
||||
|
||||
function splitString(value: string | undefined): Partial<[number | undefined, number | undefined]> {
|
||||
const [from, to] = (value ?? ',').split(',');
|
||||
const fromNumber = makeNumberOrUndefined(from);
|
||||
return [fromNumber, makeNumberOrUndefined(to, fromNumber)];
|
||||
}
|
||||
|
||||
function makeNumberOrUndefined(value: string | undefined, fallback?: undefined | number) {
|
||||
if (value === undefined) {
|
||||
return fallback;
|
||||
}
|
||||
const n = Number(value);
|
||||
if (isNaN(n)) {
|
||||
return fallback;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function undefinedFallbackToString(value: number | undefined, fallback: number): string {
|
||||
return (value === undefined ? fallback : value).toString();
|
||||
}
|
||||
|
||||
@customElement('umb-input-slider')
|
||||
export class UmbInputSliderElement extends UUIFormControlMixin(UmbLitElement, '') {
|
||||
export class UmbInputSliderElement extends UmbFormControlMixin<string, typeof UmbLitElement, ''>(UmbLitElement, '') {
|
||||
override set value(value: string) {
|
||||
const [from, to] = splitString(value);
|
||||
this.#valueLow = from;
|
||||
this.#valueHigh = to;
|
||||
super.value = value;
|
||||
}
|
||||
override get value() {
|
||||
return super.value;
|
||||
}
|
||||
|
||||
@property()
|
||||
label: string = '';
|
||||
|
||||
@@ -19,10 +50,32 @@ export class UmbInputSliderElement extends UUIFormControlMixin(UmbLitElement, ''
|
||||
step = 1;
|
||||
|
||||
@property({ type: Number })
|
||||
valueLow = 0;
|
||||
public get valueLow(): number | undefined {
|
||||
return this.#valueLow;
|
||||
}
|
||||
public set valueLow(value: number | undefined) {
|
||||
this.#valueLow = value;
|
||||
this.#setValueFromLowHigh();
|
||||
}
|
||||
#valueLow?: number | undefined;
|
||||
|
||||
@property({ type: Number })
|
||||
valueHigh = 0;
|
||||
public get valueHigh(): number | undefined {
|
||||
return this.#valueHigh;
|
||||
}
|
||||
public set valueHigh(value: number | undefined) {
|
||||
this.#valueHigh = value;
|
||||
this.#setValueFromLowHigh();
|
||||
}
|
||||
#valueHigh?: number | undefined;
|
||||
|
||||
#setValueFromLowHigh() {
|
||||
if (this.enableRange) {
|
||||
super.value = `${undefinedFallbackToString(this.valueLow, this.min)},${undefinedFallbackToString(this.valueHigh, this.max)}`;
|
||||
} else {
|
||||
super.value = `${undefinedFallbackToString(this.valueLow, this.min)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@property({ type: Boolean, attribute: 'enable-range' })
|
||||
enableRange = false;
|
||||
@@ -36,6 +89,62 @@ export class UmbInputSliderElement extends UUIFormControlMixin(UmbLitElement, ''
|
||||
@property({ type: Boolean, reflect: true })
|
||||
readonly = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.addValidator(
|
||||
'rangeUnderflow',
|
||||
() => {
|
||||
return this.localize.term('validation_numberMinimum', [this.min?.toString()]);
|
||||
},
|
||||
() => {
|
||||
if (this.min !== undefined) {
|
||||
const [from, to] = splitString(this.value);
|
||||
if (to !== undefined && to < this.min) {
|
||||
return true;
|
||||
}
|
||||
if (from !== undefined && from < this.min) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
this.addValidator(
|
||||
'rangeOverflow',
|
||||
() => {
|
||||
return this.localize.term('validation_numberMaximum', [this.max?.toString()]);
|
||||
},
|
||||
() => {
|
||||
if (this.max !== undefined) {
|
||||
const [from, to] = splitString(this.value);
|
||||
if (to !== undefined && to > this.max) {
|
||||
return true;
|
||||
}
|
||||
if (from !== undefined && from > this.max) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
this.addValidator(
|
||||
'patternMismatch',
|
||||
() => {
|
||||
return this.localize.term('validation_rangeExceeds');
|
||||
},
|
||||
() => {
|
||||
const [from, to] = splitString(this.value);
|
||||
if (to !== undefined && from !== undefined) {
|
||||
return from > to;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
protected override getFormElement() {
|
||||
return undefined;
|
||||
}
|
||||
@@ -57,7 +166,7 @@ export class UmbInputSliderElement extends UUIFormControlMixin(UmbLitElement, ''
|
||||
.min=${this.min}
|
||||
.max=${this.max}
|
||||
.step=${this.step}
|
||||
.value=${this.valueLow.toString()}
|
||||
.value=${undefinedFallbackToString(this.valueLow, this.min).toString()}
|
||||
@change=${this.#onChange}
|
||||
?readonly=${this.readonly}>
|
||||
</uui-slider>
|
||||
@@ -71,7 +180,10 @@ export class UmbInputSliderElement extends UUIFormControlMixin(UmbLitElement, ''
|
||||
.min=${this.min}
|
||||
.max=${this.max}
|
||||
.step=${this.step}
|
||||
.value="${this.valueLow},${this.valueHigh}"
|
||||
.value="${undefinedFallbackToString(this.valueLow, this.min).toString()},${undefinedFallbackToString(
|
||||
this.valueHigh,
|
||||
this.max,
|
||||
).toString()}"
|
||||
@change=${this.#onChange}
|
||||
?readonly=${this.readonly}>
|
||||
</uui-range-slider>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UmbSliderPropertyEditorUiValue } from './types.js';
|
||||
import type { UmbSliderPropertyEditorUiValue, UmbSliderPropertyEditorUiValueObject } from './types.js';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import type { UmbInputSliderElement } from '@umbraco-cms/backoffice/components';
|
||||
import { customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
@@ -8,15 +8,37 @@ import type {
|
||||
UmbPropertyEditorConfigCollection,
|
||||
UmbPropertyEditorUiElement,
|
||||
} from '@umbraco-cms/backoffice/property-editor';
|
||||
import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation';
|
||||
|
||||
function stringToValueObject(value: string | undefined): Partial<UmbSliderPropertyEditorUiValueObject> {
|
||||
const [from, to] = (value ?? ',').split(',');
|
||||
const fromNumber = makeNumberOrUndefined(from);
|
||||
return { from: fromNumber, to: makeNumberOrUndefined(to, fromNumber) };
|
||||
}
|
||||
|
||||
function makeNumberOrUndefined(value: string | undefined, fallback?: undefined | number) {
|
||||
if (value === undefined) {
|
||||
return fallback;
|
||||
}
|
||||
const n = Number(value);
|
||||
if (isNaN(n)) {
|
||||
return fallback;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function undefinedFallback(value: number | undefined, fallback: number) {
|
||||
return value === undefined ? fallback : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @element umb-property-editor-ui-slider
|
||||
*/
|
||||
@customElement('umb-property-editor-ui-slider')
|
||||
export class UmbPropertyEditorUISliderElement extends UmbLitElement implements UmbPropertyEditorUiElement {
|
||||
@property({ type: Object })
|
||||
value: UmbSliderPropertyEditorUiValue | undefined;
|
||||
|
||||
export class UmbPropertyEditorUISliderElement
|
||||
extends UmbFormControlMixin<UmbSliderPropertyEditorUiValue, typeof UmbLitElement>(UmbLitElement)
|
||||
implements UmbPropertyEditorUiElement
|
||||
{
|
||||
/**
|
||||
* Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content.
|
||||
* @type {boolean}
|
||||
@@ -82,6 +104,7 @@ export class UmbPropertyEditorUISliderElement extends UmbLitElement implements U
|
||||
}
|
||||
|
||||
protected override firstUpdated() {
|
||||
this.addFormControlElement(this.shadowRoot!.querySelector('umb-input-slider')!);
|
||||
if (this._min && this._max && this._min > this._max) {
|
||||
console.warn(
|
||||
`Property '${this._label}' (Slider) has been misconfigured, 'min' is greater than 'max'. Please correct your data type configuration.`,
|
||||
@@ -95,13 +118,13 @@ export class UmbPropertyEditorUISliderElement extends UmbLitElement implements U
|
||||
return Number.isNaN(num) ? undefined : num;
|
||||
}
|
||||
|
||||
#getValueObject(value: string) {
|
||||
const [from, to] = value.split(',').map(Number);
|
||||
return { from, to: to ?? from };
|
||||
}
|
||||
|
||||
#onChange(event: CustomEvent & { target: UmbInputSliderElement }) {
|
||||
this.value = this.#getValueObject(event.target.value as string);
|
||||
const partialValue = stringToValueObject(event.target.value as string);
|
||||
const handledFrom = undefinedFallback(partialValue.from, this._initVal1);
|
||||
this.value = {
|
||||
from: handledFrom,
|
||||
to: this._enableRange ? undefinedFallback(partialValue.to, this._initVal2) : handledFrom,
|
||||
};
|
||||
this.dispatchEvent(new UmbChangeEvent());
|
||||
}
|
||||
|
||||
@@ -109,8 +132,8 @@ export class UmbPropertyEditorUISliderElement extends UmbLitElement implements U
|
||||
return html`
|
||||
<umb-input-slider
|
||||
.label=${this._label ?? 'Slider'}
|
||||
.valueLow=${this.value?.from ?? this._initVal1}
|
||||
.valueHigh=${this.value?.to ?? this._initVal2}
|
||||
.valueLow=${undefinedFallback(this.value?.from, this._initVal1)}
|
||||
.valueHigh=${undefinedFallback(this.value?.to, this._initVal2)}
|
||||
.step=${this._step}
|
||||
.min=${this._min}
|
||||
.max=${this._max}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export type UmbSliderPropertyEditorUiValue = { from: number; to: number } | undefined;
|
||||
export interface UmbSliderPropertyEditorUiValueObject {
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
||||
|
||||
export type UmbSliderPropertyEditorUiValue = UmbSliderPropertyEditorUiValueObject | undefined;
|
||||
|
||||
/**
|
||||
* @deprecated this type will be removed in v.17.0, use `UmbPropertyEditorUISliderValue` instead
|
||||
|
||||
Reference in New Issue
Block a user