Skip to content
3 changes: 2 additions & 1 deletion src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Check failure on line 5 in src/Umbraco.Web.UI.Client/src/packages/core/components/input-slider/input-slider.element.ts

View workflow job for this annotation

GitHub Actions / build

UmbFormControlMixin not found in '../../validation/mixins'

Check failure on line 5 in src/Umbraco.Web.UI.Client/src/packages/core/components/input-slider/input-slider.element.ts

View workflow job for this annotation

GitHub Actions / build

Relative imports should use the ".js" file extension

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 = '';

Expand All @@ -19,10 +50,32 @@
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;
Expand All @@ -36,6 +89,62 @@
@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;
},
);
}

Check warning on line 146 in src/Umbraco.Web.UI.Client/src/packages/core/components/input-slider/input-slider.element.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ New issue: Complex Method

UmbInputSliderElement.constructor has a cyclomatic complexity of 15, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

Check warning on line 146 in src/Umbraco.Web.UI.Client/src/packages/core/components/input-slider/input-slider.element.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (main)

❌ New issue: Bumpy Road Ahead

UmbInputSliderElement.constructor has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

protected override getFormElement() {
return undefined;
}
Expand All @@ -57,7 +166,7 @@
.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>
Expand All @@ -71,7 +180,10 @@
.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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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}
Expand Down Expand Up @@ -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.`,
Expand All @@ -95,22 +118,22 @@ 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());
}

override render() {
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}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading