import { PropertyValues, css as litCss, LitElement } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { nothing, html } from 'lit/html.js';
import { css, theme } from 'twind/css';
import { createUUID } from './utils/UniqueId';
import { MonoTextComp } from './MonoTextComp';
import { fromOptionalConverter, spread } from './utils/LitHelper';
import { SpreadController } from './utils/SpreadController';
import { TailwindStylesController } from './utils/TailwindStylesController';
import { RoundedCorners } from './utils/CommonTypes';
@customElement('mono-rangeslider')
export class MonoRangeSliderComp extends LitElement {
static styles = litCss`
::slotted(p:first-of-type) {
margin: 0;
}
`;
private _spreadController: SpreadController = new SpreadController(this);
private __stylesController: TailwindStylesController = new TailwindStylesController(
this,
);
@property({ type: String, reflect: true })
value: string = '';
@property({ type: String, reflect: true })
min: string = '';
@property({ type: String, reflect: true })
max: string = '';
@property({ type: String, reflect: true })
step: string = '';
@property({ type: String, reflect: true }) id: string = createUUID();
@property({ type: String, reflect: true }) name: string = '';
@property({ type: String, reflect: true, converter: fromOptionalConverter })
error?: string;
@property({ type: Boolean, reflect: true }) disabled: boolean = false;
@property({ type: Boolean, reflect: true }) required: boolean = false;
@property({ type: Boolean, reflect: true }) currency: boolean = false;
@property({ type: String, reflect: true }) corners: RoundedCorners = 'none';
@query('input', true)
__inputEl!: HTMLInputElement;
@query('label', true)
__labelEl!: HTMLLabelElement;
@query('[id*="-errors"]', true)
__errorEl!: MonoTextComp;
private __currencyFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currencyDisplay: 'symbol',
currency: 'USD',
maximumSignificantDigits: 3,
});
private __onInput(_event: Event) {
this.value = this.__inputEl.value;
}
private __onChange(event: Event) {
event.preventDefault();
event.stopPropagation();
this.value = this.__inputEl.value;
const changeEvent = new CustomEvent('change', {
detail: { value: this.value },
bubbles: true,
composed: true,
});
this.dispatchEvent(changeEvent);
}
private __hasError() {
return this.error && this.error.length > 0;
}
private __renderError(ariaDescribedBy: string) {
if (this.__hasError()) {
return html`
${this.error}
`;
}
return nothing;
}
private __computeCurrentValue() {
const minValue = Number(this.min);
const maxValue = Number(this.max);
let value = Number(this.value);
value = Math.max(value, minValue);
value = Math.min(value, maxValue);
return ((value - minValue) / (maxValue - minValue)) * 100;
}
private __computeToneColor() {
if (this.disabled) {
return theme('colors.neutral-3');
}
if (this.__hasError()) {
return theme('colors.alert');
}
return theme('colors.highlight-2');
}
private __computeTooltipColor() {
if (this.disabled) {
return 'neutral-3';
}
if (this.__hasError()) {
return 'alert';
}
return 'highlight-2';
}
private __computeHighlightThumbColor() {
if (this.disabled) {
return theme('colors.neutral-3');
}
if (this.__hasError()) {
return theme('colors.primary-dark');
}
return theme('colors.highlight-2-dark');
}
private __formatValue(value: string | number | undefined) {
return this.currency
? this.__currencyFormatter.format(Number(value))
: value;
}
private __renderTooltip(currentValue: number) {
const tooltipPosition = 10 - currentValue * 0.2;
return html`
${this.__formatValue(this.value)}
`;
}
firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
// reuse platform default
this.min = this.__inputEl.min;
this.max = this.__inputEl.max;
this.step = this.__inputEl.step;
if (this.value && this.value !== '') {
// the input will have the adjusted value, will make it fit into the step value
this.value = this.__inputEl.value;
} else {
// by default when provided no value, the input will set itself to 50
this.value = this.min;
}
}
render() {
const webkitRangeOverrides = css({
'&::-webkit-slider-thumb': {
border: 'none',
width: '18px',
height: '18px',
'border-radius': '50%',
'background-color': this.__computeToneColor(),
'-webkit-appearance': 'none',
cursor: 'pointer',
},
'&:not(:disabled)::-webkit-slider-thumb:hover': {
background: this.__computeHighlightThumbColor(),
},
'&:not(:disabled):focus::-webkit-slider-thumb': {
background: this.__computeHighlightThumbColor(),
outline: '2px dotted black',
'outline-offset': '2px',
},
'&:not(:disabled)::-webkit-slider-thumb:active': {
'background-color': this.__computeToneColor(),
cursor: 'grabbing',
},
'&:disabled::-webkit-slider-thumb': {
cursor: 'not-allowed',
},
});
const mozRangeOverrides = css({
'&::-moz-focus-outer': {
border: '0',
},
'&::-moz-range-thumb': {
border: 'none',
width: '18px',
height: '18px',
'border-radius': '50%',
'background-color': this.__computeToneColor(),
'-webkit-appearance': 'none',
cursor: 'pointer',
},
'&:not(:disabled)::-moz-range-thumb:hover': {
background: this.__computeHighlightThumbColor(),
},
'&:not(:disabled):focus::-moz-range-thumb': {
background: this.__computeHighlightThumbColor(),
},
'&:not(:disabled)::-moz-range-thumb:active': {
'background-color': this.__computeToneColor(),
cursor: 'grabbing',
},
'&:disabled::-moz-range-thumb': {
cursor: 'not-allowed',
},
});
const currentValue = this.__computeCurrentValue();
const rangeBackgroudOverrides = css`
background: linear-gradient(
to right,
${this.__computeToneColor()} 0%,
${this.__computeToneColor()} ${currentValue + 0.0001}%,
${theme('colors.neutral-3')} ${currentValue + 0.0001}%,
${theme('colors.neutral-3')} 100%
);
`;
const ariaDescribedBy = this.__hasError() ? `${this.id}-errors` : '';
const attributesToSpread = this._spreadController.buildSpreadAttributesIgnoring(
[
'as',
'style',
'class',
'slot',
'value',
'min',
'max',
'step',
'id',
'name',
'error',
'disabled',
'required',
'currency',
'type',
'aria-describedby',
'corners',
],
);
return html`
${this.__renderError(ariaDescribedBy)}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'mono-rangeslider': MonoRangeSliderComp;
}
}