@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
720 lines (719 loc) • 41.8 kB
JavaScript
/*! All material copyright ESRI, All Rights Reserved, unless otherwise specified.
See https://github.com/Esri/calcite-design-system/blob/dev/LICENSE.md for details.
v3.2.1 */
import { c as customElement } from "../../chunks/runtime.js";
import { ref } from "lit-html/directives/ref.js";
import { keyed } from "lit-html/directives/keyed.js";
import { html, nothing } from "lit";
import { LitElement, createEvent, stringOrBoolean, setAttribute, safeClassMap, safeStyleMap } from "@arcgis/lumina";
import { g as guid } from "../../chunks/guid.js";
import { i as isPrimaryPointerButton, j as intersects } from "../../chunks/dom.js";
import { V as Validation } from "../../chunks/Validation.js";
import { c as connectForm, a as afterConnectDefaultValueSet, d as disconnectForm, H as HiddenFormInputSlot } from "../../chunks/form.js";
import { u as updateHostInteraction, I as InteractiveContainer } from "../../chunks/interactive.js";
import { i as isActivationKey } from "../../chunks/key.js";
import { c as connectLabel, d as disconnectLabel, g as getLabelText } from "../../chunks/label.js";
import { c as componentFocusable } from "../../chunks/component.js";
import { n as numberStringFormatter, B as BigDecimal } from "../../chunks/locale.js";
import { d as decimalPlaces, c as clamp } from "../../chunks/math.js";
import { u as useT9n } from "../../chunks/useT9n.js";
import { css } from "@lit/reactive-element/css-tag.js";
const CSS = {
container: "container",
containerRange: "container--range",
graph: "graph",
handle: "handle",
handleExtension: "handle-extension",
handleLabel: "handle__label",
handleLabelMinValue: "handle__label--minValue",
handleLabelValue: "handle__label--value",
hyphen: "hyphen",
hyphenWrap: "hyphen--wrap",
static: "static",
thumb: "thumb",
thumbActive: "thumb--active",
thumbContainer: "thumb-container",
thumbMinValue: "thumb--minValue",
thumbPrecise: "thumb--precise",
thumbValue: "thumb--value",
tick: "tick",
tickActive: "tick--active",
tickLabel: "tick__label",
tickMax: "tick__label--max",
tickMin: "tick__label--min",
ticks: "ticks",
track: "track",
trackRange: "track__range",
transformed: "transformed"
};
const IDS = {
validationMessage: "validationMessage"
};
const maxTickElementThreshold = 250;
const styles = css`@charset "UTF-8";:host([disabled]){cursor:default;-webkit-user-select:none;user-select:none;opacity:var(--calcite-opacity-disabled)}:host([disabled]) *,:host([disabled]) ::slotted(*){pointer-events:none}.scale--s{--calcite-slider-handle-size: .625rem;--calcite-slider-handle-extension-height: .4rem;--calcite-slider-container-font-size: var(--calcite-font-size--3)}.scale--s .handle__label,.scale--s .tick__label{line-height:.75rem}.scale--m{--calcite-slider-handle-size: .875rem;--calcite-slider-handle-extension-height: .5rem;--calcite-slider-container-font-size: var(--calcite-font-size--2)}.scale--m .handle__label,.scale--m .tick__label{line-height:1rem}.scale--l{--calcite-slider-handle-size: 1rem;--calcite-slider-handle-extension-height: .65rem;--calcite-slider-container-font-size: var(--calcite-font-size--1)}.scale--l .handle__label,.scale--l .tick__label{line-height:1rem}.handle__label,.tick__label{font-weight:var(--calcite-font-weight-medium);color:var(--calcite-slider-text-color, var(--calcite-color-text-2));font-size:var(--calcite-slider-container-font-size)}:host{display:block}:host .validation-container{padding-block-start:0 }.container{position:relative;display:block;overflow-wrap:normal;word-break:normal;padding-inline:calc(var(--calcite-slider-handle-size) * .5);padding-block:calc(var(--calcite-slider-handle-size) * .5);margin-block:calc(var(--calcite-slider-handle-size) * .5);margin-inline:0;--calcite-slider-full-handle-height: calc( var(--calcite-slider-handle-size) + var(--calcite-slider-handle-extension-height) );touch-action:none;-webkit-user-select:none;user-select:none}:host([disabled]) .track__range,:host([disabled]) .tick--active{background-color:var(--calcite-color-text-3)}:host([disabled]) ::slotted([calcite-hydrated][disabled]),:host([disabled]) [calcite-hydrated][disabled]{opacity:1}.interaction-container{display:contents}.scale--s .thumb:not(.thumb--precise){--calcite-slider-thumb-y-offset: -.375rem}.scale--m .thumb:not(.thumb--precise){--calcite-slider-thumb-y-offset: -.5rem}.scale--l .thumb:not(.thumb--precise){--calcite-slider-thumb-y-offset: -.55rem}:host([precise]:not([has-histogram])) .container .thumb--value{--calcite-slider-thumb-y-offset: calc(var(--calcite-slider-full-handle-height) * -1)}.thumb-container{position:relative;max-inline-size:100%}.thumb{--calcite-slider-thumb-x-offset: calc(var(--calcite-slider-handle-size) * .5);position:absolute;margin:0;display:flex;cursor:pointer;flex-direction:column;align-items:center;border-style:none;background-color:transparent;padding:0;font-family:inherit;outline:2px solid transparent;outline-offset:2px;transform:translate(var(--calcite-slider-thumb-x-offset),var(--calcite-slider-thumb-y-offset))}.thumb .handle__label{white-space:nowrap}.thumb .handle__label.static,.thumb .handle__label.transformed{position:absolute;inset-block:0px;opacity:0}.thumb .handle__label.hyphen:after{content:"\u2014";display:inline-block;inline-size:1em}.thumb .handle__label.hyphen--wrap{display:flex}.thumb .handle{box-sizing:border-box;border-radius:9999px;outline-color:transparent;background-color:var(--calcite-slider-handle-fill-color, var(--calcite-color-foreground-1));block-size:var(--calcite-slider-handle-size);inline-size:var(--calcite-slider-handle-size);box-shadow:0 0 0 2px var(--calcite-color-text-3) inset;transition:border var(--calcite-internal-animation-timing-medium) ease,background-color var(--calcite-internal-animation-timing-medium) ease,box-shadow var(--calcite-animation-timing) ease}.thumb .handle-extension{inline-size:.125rem;block-size:var(--calcite-slider-handle-extension-height);background-color:var(--calcite-slider-handle-extension-color, var(--calcite-color-text-3))}.thumb:hover .handle{box-shadow:0 0 0 3px var(--calcite-color-brand) inset}.thumb:hover .handle-extension{background-color:var(--calcite-slider-accent-color, var(--calcite-color-brand))}.thumb:focus .handle{outline:2px solid var(--calcite-color-focus, var(--calcite-ui-focus-color, var(--calcite-color-brand)));outline-offset:calc(2px*(1 - (2*clamp(0,var(--calcite-offset-invert-focus),1))))}.thumb:focus .handle-extension{background-color:var(--calcite-slider-accent-color, var(--calcite-color-brand))}.thumb.thumb--minValue{transform:translate(calc(var(--calcite-slider-thumb-x-offset) * -1),var(--calcite-slider-thumb-y-offset))}.thumb.thumb--precise{--calcite-slider-thumb-y-offset: -.125rem}:host([label-handles]) .thumb{--calcite-slider-thumb-x-offset: 50%}:host([label-handles]):host(:not([has-histogram])) .scale--s .thumb:not(.thumb--precise){--calcite-slider-thumb-y-offset: -1.4375rem}:host([label-handles]):host(:not([has-histogram])) .scale--m .thumb:not(.thumb--precise){--calcite-slider-thumb-y-offset: -1.875rem}:host([label-handles]):host(:not([has-histogram])) .scale--l .thumb:not(.thumb--precise){--calcite-slider-thumb-y-offset: -2rem}:host([has-histogram][label-handles]) .handle__label,:host([label-handles]:not([has-histogram])) .thumb--minValue.thumb--precise .handle__label{margin-block-start:.5em}:host(:not([has-histogram]):not([precise])) .handle__label,:host([label-handles]:not([has-histogram])) .thumb--value .handle__label{margin-block-end:.5em}:host([label-handles][precise]):host(:not([has-histogram])) .scale--s .thumb--value{--calcite-slider-thumb-y-offset: -2.075rem}:host([label-handles][precise]):host(:not([has-histogram])) .scale--m .thumb--value{--calcite-slider-thumb-y-offset: -2.75rem}:host([label-handles][precise]):host(:not([has-histogram])) .scale--l .thumb--value{--calcite-slider-thumb-y-offset: -3.0625rem}.thumb:focus .handle,.thumb--active .handle{background-color:var(--calcite-slider-accent-color, var(--calcite-color-brand));box-shadow:0 0 8px #00000029}.thumb:hover.thumb--precise:after,.thumb:focus.thumb--precise:after,.thumb--active.thumb--precise:after{background-color:var(--calcite-slider-accent-color, var(--calcite-color-brand))}.track{position:relative;block-size:.125rem;border-radius:0;background-color:var(--calcite-slider-track-color, var(--calcite-color-border-2));transition:all var(--calcite-internal-animation-timing-medium) ease-in}.track__range{position:absolute;inset-block-start:0px;block-size:.125rem;background-color:var(--calcite-slider-track-fill-color, var(--calcite-color-brand))}.container--range .track__range:hover{cursor:ew-resize}.container--range .track__range:after{position:absolute;inline-size:100%;content:"";inset-block-start:calc(var(--calcite-slider-full-handle-height) * .5 * -1);block-size:calc(var(--calcite-slider-handle-size) + var(--calcite-slider-handle-extension-height))}@media (forced-colors: active){.thumb{outline-width:0;outline-offset:0}.handle{outline:2px solid transparent;outline-offset:2px}.thumb:focus .handle,.thumb .handle-extension,.thumb:hover .handle-extension,.thumb:focus .handle-extension,.thumb:active .handle-extension{background-color:canvasText}.track{background-color:canvasText}.track__range{background-color:highlight}}.tick{position:absolute;block-size:.25rem;inline-size:.125rem;border-width:1px;border-style:solid;background-color:var(--calcite-slider-tick-color, var(--calcite-color-border-input));border-color:var(--calcite-slider-tick-border-color, var(--calcite-color-foreground-1));inset-block-start:-2px;pointer-events:none;margin-inline-start:-.125rem}.tick--active{background-color:var(--calcite-slider-tick-selected-color, var(--calcite-color-brand))}.tick__label{pointer-events:none;margin-block-start:.875rem;display:flex;justify-content:center}.tick__label--min{transition:opacity var(--calcite-animation-timing)}.tick__label--max{transition:opacity var(--calcite-internal-animation-timing-fast)}:host([has-histogram][label-handles]) .tick__label--min,:host([has-histogram][label-handles]) .tick__label--max,:host([has-histogram][precise]) .tick__label--min,:host([has-histogram][precise]) .tick__label--max{font-weight:var(--calcite-font-weight-normal);color:var(--calcite-color-text-3)}.graph{color:var(--calcite-slider-graph-color, var(--calcite-color-foreground-3));block-size:48px}:host([label-ticks][ticks]) .container{padding-block-end:calc(.875rem + var(--calcite-slider-container-font-size))}:host([has-histogram]):host([precise][label-handles]) .container{padding-block-end:calc(var(--calcite-slider-full-handle-height) + 1em)}:host([has-histogram]):host([label-handles]:not([precise])) .container{padding-block-end:calc(var(--calcite-slider-handle-size) * .5 + 1em)}:host([has-histogram]):host([precise]:not([label-handles])) .container{padding-block-end:var(--calcite-slider-full-handle-height)}:host(:not([has-histogram])):host([precise]:not([label-handles])) .container{padding-block-start:var(--calcite-slider-full-handle-height)}:host(:not([has-histogram])):host([precise]:not([label-handles])) .container--range{padding-block-end:var(--calcite-slider-full-handle-height)}:host(:not([has-histogram])):host([label-handles]:not([precise])) .container{padding-block-start:calc(var(--calcite-slider-full-handle-height) + 4px)}:host(:not([has-histogram])):host([label-handles][precise]) .container{padding-block-start:calc(var(--calcite-slider-full-handle-height) + var(--calcite-slider-container-font-size) + 4px)}:host(:not([has-histogram])):host([label-handles][precise]) .container--range{padding-block-end:calc(var(--calcite-slider-full-handle-height) + var(--calcite-slider-container-font-size) + 4px)}.validation-container{display:flex;flex-direction:column;align-items:flex-start;align-self:stretch}:host([scale=m]) .validation-container,:host([scale=l]) .validation-container{padding-block-start:.5rem}:host([scale=s]) .validation-container{padding-block-start:.25rem}::slotted(input[slot=hidden-form-input]){margin:0 ;opacity:0 ;outline:none ;padding:0 ;position:absolute ;inset:0 ;transform:none ;-webkit-appearance:none ;z-index:-1 }:host([hidden]){display:none}[hidden]{display:none}`;
function isRange(value) {
return Array.isArray(value);
}
class Slider extends LitElement {
constructor() {
super();
this.activeProp = "value";
this.dragEnd = (event) => {
if (this.disabled) {
return;
}
this.removeDragListeners();
this.focusActiveHandle(event.clientX);
if (this.lastDragPropValue != this[this.dragProp]) {
this.emitChange();
}
this.dragProp = null;
this.lastDragPropValue = null;
this.minValueDragRange = null;
this.maxValueDragRange = null;
this.minMaxValueRange = null;
};
this.dragUpdate = (event) => {
if (this.disabled) {
return;
}
event.preventDefault();
if (this.dragProp) {
const value = this.mapToRange(event.clientX || event.pageX);
if (isRange(this.value) && this.dragProp === "minMaxValue") {
if (this.minValueDragRange && this.maxValueDragRange && this.minMaxValueRange) {
const newMinValue = value - this.minValueDragRange;
const newMaxValue = value + this.maxValueDragRange;
if (newMaxValue <= this.max && newMinValue >= this.min && newMaxValue - newMinValue === this.minMaxValueRange) {
this.setValue({
minValue: this.clamp(newMinValue, "minValue"),
maxValue: this.clamp(newMaxValue, "maxValue")
});
}
} else {
this.minValueDragRange = value - this.minValue;
this.maxValueDragRange = this.maxValue - value;
this.minMaxValueRange = this.maxValue - this.minValue;
}
} else {
this.setValue({ [this.dragProp]: this.clamp(value, this.dragProp) });
}
}
};
this.formatValue = (value) => {
numberStringFormatter.numberFormatOptions = {
locale: this.messages._lang,
numberingSystem: this.numberingSystem,
useGrouping: this.groupSeparator
};
return numberStringFormatter.localize(value.toString());
};
this.guid = `calcite-slider-${guid()}`;
this.messages = useT9n({ name: null });
this.pointerUpDragEnd = (event) => {
if (this.disabled || !isPrimaryPointerButton(event)) {
return;
}
this.dragEnd(event);
};
this.maxValueDragRange = null;
this.minMaxValueRange = null;
this.minValueDragRange = null;
this.tickValues = [];
this.disabled = false;
this.fillPlacement = "start";
this.groupSeparator = false;
this.hasHistogram = false;
this.labelHandles = false;
this.labelTicks = false;
this.max = 100;
this.min = 0;
this.mirrored = false;
this.precise = false;
this.required = false;
this.scale = "m";
this.snap = false;
this.status = "idle";
this.step = 1;
this.validity = {
valid: false,
badInput: false,
customError: false,
patternMismatch: false,
rangeOverflow: false,
rangeUnderflow: false,
stepMismatch: false,
tooLong: false,
tooShort: false,
typeMismatch: false,
valueMissing: false
};
this.value = 0;
this.calciteSliderChange = createEvent({ cancelable: false });
this.calciteSliderInput = createEvent({ cancelable: false });
this.listen("pointerdown", this.pointerDownHandler);
this.listen("keydown", this.handleKeyDown);
this.listen("touchstart", this.handleTouchStart);
}
static {
this.properties = { maxValueDragRange: [16, {}, { state: true }], minMaxValueRange: [16, {}, { state: true }], minValueDragRange: [16, {}, { state: true }], tickValues: [16, {}, { state: true }], disabled: [7, {}, { reflect: true, type: Boolean }], fillPlacement: [3, {}, { reflect: true }], form: [3, {}, { reflect: true }], groupSeparator: [7, {}, { reflect: true, type: Boolean }], hasHistogram: [7, {}, { reflect: true, type: Boolean }], histogram: [0, {}, { attribute: false }], histogramStops: [0, {}, { attribute: false }], labelFormatter: [0, {}, { attribute: false }], labelHandles: [7, {}, { reflect: true, type: Boolean }], labelTicks: [7, {}, { reflect: true, type: Boolean }], max: [11, {}, { reflect: true, type: Number }], maxLabel: 1, maxValue: [9, {}, { type: Number }], min: [11, {}, { reflect: true, type: Number }], minLabel: 1, minValue: [9, {}, { type: Number }], mirrored: [7, {}, { reflect: true, type: Boolean }], name: [3, {}, { reflect: true }], numberingSystem: 1, pageStep: [11, {}, { reflect: true, type: Number }], precise: [7, {}, { reflect: true, type: Boolean }], required: [7, {}, { reflect: true, type: Boolean }], scale: [3, {}, { reflect: true }], snap: [7, {}, { reflect: true, type: Boolean }], status: [3, {}, { reflect: true }], step: [11, {}, { reflect: true, type: Number }], ticks: [11, {}, { reflect: true, type: Number }], validationIcon: [3, { converter: stringOrBoolean }, { reflect: true }], validationMessage: 1, validity: [0, {}, { attribute: false }], value: [11, {}, { type: Number, reflect: true }] };
}
static {
this.shadowRootOptions = { mode: "open", delegatesFocus: true };
}
static {
this.styles = styles;
}
async setFocus() {
await componentFocusable(this);
const handle = this.minHandle ? this.minHandle : this.maxHandle;
handle?.focus();
}
connectedCallback() {
super.connectedCallback();
this.setMinMaxFromValue();
this.setValueFromMinMax();
connectLabel(this);
connectForm(this);
}
load() {
if (!isRange(this.value)) {
this.value = this.snap ? this.getClosestStep(this.value) : this.clamp(this.value);
}
afterConnectDefaultValueSet(this, this.value);
}
willUpdate(changes) {
if (changes.has("histogram")) {
this.hasHistogram = !!this.histogram;
}
if (changes.has("ticks")) {
this.tickValues = this.generateTickValues();
}
if (changes.has("value") && (this.hasUpdated || this.value !== 0)) {
this.setMinMaxFromValue();
}
if (changes.has("minValue") || changes.has("maxValue")) {
this.setValueFromMinMax();
}
}
updated() {
if (this.labelHandles) {
this.adjustHostObscuredHandleLabel("value");
if (isRange(this.value)) {
this.adjustHostObscuredHandleLabel("minValue");
if (!(this.precise && !this.hasHistogram)) {
this.hyphenateCollidingRangeHandleLabels();
}
}
}
this.hideObscuredBoundingTickLabels();
updateHostInteraction(this);
}
disconnectedCallback() {
super.disconnectedCallback();
disconnectLabel(this);
disconnectForm(this);
this.removeDragListeners();
}
handleKeyDown(event) {
const mirror = this.shouldMirror();
const { activeProp, max, min, pageStep, step } = this;
const value = this[activeProp];
const { key } = event;
if (isActivationKey(key)) {
event.preventDefault();
return;
}
let adjustment;
if (key === "ArrowUp" || key === "ArrowRight") {
const directionFactor = mirror && key === "ArrowRight" ? -1 : 1;
adjustment = value + step * directionFactor;
} else if (key === "ArrowDown" || key === "ArrowLeft") {
const directionFactor = mirror && key === "ArrowLeft" ? -1 : 1;
adjustment = value - step * directionFactor;
} else if (key === "PageUp") {
if (pageStep) {
adjustment = value + pageStep;
}
} else if (key === "PageDown") {
if (pageStep) {
adjustment = value - pageStep;
}
} else if (key === "Home") {
adjustment = min;
} else if (key === "End") {
adjustment = max;
}
if (isNaN(adjustment)) {
return;
}
event.preventDefault();
const fixedDecimalAdjustment = Number(adjustment.toFixed(decimalPlaces(step)));
this.setValue({
[activeProp]: this.clamp(fixedDecimalAdjustment, activeProp)
});
}
pointerDownHandler(event) {
if (this.disabled || !isPrimaryPointerButton(event)) {
return;
}
const x = event.clientX || event.pageX;
const position = this.mapToRange(x);
let prop = "value";
if (isRange(this.value)) {
const inRange = position >= this.minValue && position <= this.maxValue;
if (inRange && this.lastDragProp === "minMaxValue") {
prop = "minMaxValue";
} else {
const closerToMax = Math.abs(this.maxValue - position) < Math.abs(this.minValue - position);
prop = closerToMax || position >= this.maxValue ? "maxValue" : "minValue";
}
}
this.lastDragPropValue = this[prop];
this.dragStart(prop);
const isThumbActive = this.el.shadowRoot.querySelector(`.${CSS.thumb}:active`);
if (!isThumbActive) {
this.setValue({ [prop]: this.clamp(position, prop) });
}
this.focusActiveHandle(x);
}
handleTouchStart(event) {
event.preventDefault();
}
buildThumbType(type) {
const thumbTypeParts = [type];
if (this.labelHandles) {
thumbTypeParts.push("labeled");
}
if (this.precise) {
thumbTypeParts.push("precise");
}
if (this.hasHistogram) {
thumbTypeParts.push("histogram");
}
return thumbTypeParts.join("-");
}
setValueFromMinMax() {
const { minValue, maxValue } = this;
if (typeof minValue === "number" && typeof maxValue === "number") {
this.value = [minValue, maxValue];
}
}
setMinMaxFromValue() {
const { value } = this;
if (isRange(value)) {
this.minValue = value[0];
this.maxValue = value[1];
}
}
onLabelClick() {
this.setFocus();
}
shouldMirror() {
return this.mirrored && !this.hasHistogram;
}
shouldUseMinValue() {
if (!isRange(this.value)) {
return false;
}
return this.hasHistogram && this.maxValue === 0 || !this.hasHistogram && this.minValue === 0;
}
getTickDensity() {
const density = (this.max - this.min) / this.ticks / maxTickElementThreshold;
return density < 1 ? 1 : density;
}
generateTickValues() {
const tickInterval = this.ticks ?? 0;
if (tickInterval <= 0) {
return [];
}
const ticks = [this.min];
const density = this.getTickDensity();
const tickOffset = tickInterval * density;
let current = this.min;
while (current < this.max) {
current += tickOffset;
ticks.push(Math.min(current, this.max));
}
if (!ticks.includes(this.max)) {
ticks.push(this.max);
}
return ticks;
}
onThumbBlur() {
this.activeProp = null;
}
onThumbFocus(event) {
const thumb = event.currentTarget;
this.activeProp = thumb.getAttribute("data-value-prop");
}
onThumbPointerDown(event) {
const thumb = event.currentTarget;
this.pointerDownDragStart(event, thumb.getAttribute("data-value-prop"));
}
onTrackPointerDown(event) {
this.pointerDownDragStart(event, "minMaxValue");
}
pointerDownDragStart(event, prop) {
if (!isPrimaryPointerButton(event)) {
return;
}
this.dragStart(prop);
}
dragStart(prop) {
this.dragProp = prop;
this.lastDragProp = this.dragProp;
this.activeProp = prop;
window.addEventListener("pointermove", this.dragUpdate);
window.addEventListener("pointerup", this.pointerUpDragEnd);
window.addEventListener("pointercancel", this.dragEnd);
}
focusActiveHandle(valueX) {
if (this.dragProp === "minValue") {
this.minHandle.focus();
} else if (this.dragProp === "maxValue" || this.dragProp === "value") {
this.maxHandle.focus();
} else if (this.dragProp === "minMaxValue") {
this.getClosestHandle(valueX).focus();
}
}
emitInput() {
this.calciteSliderInput.emit();
}
emitChange() {
this.calciteSliderChange.emit();
}
removeDragListeners() {
window.removeEventListener("pointermove", this.dragUpdate);
window.removeEventListener("pointerup", this.pointerUpDragEnd);
window.removeEventListener("pointercancel", this.dragEnd);
}
setValue(values) {
let valueChanged;
Object.keys(values).forEach((propName) => {
const newValue = values[propName];
if (!valueChanged) {
const oldValue = this[propName];
valueChanged = oldValue !== newValue;
}
this[propName] = newValue;
});
if (!valueChanged) {
return;
}
const dragging = this.dragProp;
if (!dragging) {
this.emitChange();
}
this.emitInput();
}
storeTrackRef(node) {
this.trackEl = node;
}
storeThumbRef(el) {
if (!el) {
return;
}
const valueProp = el.getAttribute("data-value-prop");
if (valueProp === "minValue") {
this.minHandle = el;
} else {
this.maxHandle = el;
}
}
clamp(value, prop) {
value = clamp(value, this.min, this.max);
if (prop === "maxValue") {
value = Math.max(value, this.minValue);
}
if (prop === "minValue") {
value = Math.min(value, this.maxValue);
}
return value;
}
mapToRange(x) {
const range = this.max - this.min;
const { left, width } = this.trackEl.getBoundingClientRect();
const percent = (x - left) / width;
const mirror = this.shouldMirror();
const clampedValue = this.clamp(this.min + range * (mirror ? 1 - percent : percent));
const value = Number(clampedValue.toFixed(decimalPlaces(this.step)));
return !(this.snap && this.step) ? value : this.getClosestStep(value);
}
getClosestStep(value) {
const { max, min, step } = this;
const bigDecimalString = new BigDecimal(`${Math.floor((value - min) / step)}`).multiply(`${step}`).add(`${min}`).toString();
let snappedValue = this.clamp(Number(bigDecimalString));
if (snappedValue > max) {
snappedValue -= step;
}
return snappedValue;
}
getClosestHandle(valueX) {
return this.getDistanceX(this.maxHandle, valueX) > this.getDistanceX(this.minHandle, valueX) ? this.minHandle : this.maxHandle;
}
getDistanceX(el, valueX) {
return Math.abs(el.getBoundingClientRect().left - valueX);
}
getFontSizeForElement(element) {
return Number(window.getComputedStyle(element).getPropertyValue("font-size").match(/\d+/)[0]);
}
getUnitInterval(num) {
num = this.clamp(num);
const range = this.max - this.min;
return (num - this.min) / range;
}
adjustHostObscuredHandleLabel(name) {
const label = this.el.shadowRoot.querySelector(`.handle__label--${name}`);
const labelStatic = this.el.shadowRoot.querySelector(`.handle__label--${name}.static`);
const labelTransformed = this.el.shadowRoot.querySelector(`.handle__label--${name}.transformed`);
const labelStaticBounds = labelStatic.getBoundingClientRect();
const labelStaticOffset = this.getHostOffset(labelStaticBounds.left, labelStaticBounds.right);
label.style.transform = `translateX(${labelStaticOffset}px)`;
labelTransformed.style.transform = `translateX(${labelStaticOffset}px)`;
}
hyphenateCollidingRangeHandleLabels() {
const { shadowRoot } = this.el;
const mirror = this.shouldMirror();
const leftModifier = mirror ? "value" : "minValue";
const rightModifier = mirror ? "minValue" : "value";
const leftValueLabel = shadowRoot.querySelector(`.handle__label--${leftModifier}`);
const leftValueLabelStatic = shadowRoot.querySelector(`.handle__label--${leftModifier}.static`);
const leftValueLabelTransformed = shadowRoot.querySelector(`.handle__label--${leftModifier}.transformed`);
const leftValueLabelStaticHostOffset = this.getHostOffset(leftValueLabelStatic.getBoundingClientRect().left, leftValueLabelStatic.getBoundingClientRect().right);
const rightValueLabel = shadowRoot.querySelector(`.handle__label--${rightModifier}`);
const rightValueLabelStatic = shadowRoot.querySelector(`.handle__label--${rightModifier}.static`);
const rightValueLabelTransformed = shadowRoot.querySelector(`.handle__label--${rightModifier}.transformed`);
const rightValueLabelStaticHostOffset = this.getHostOffset(rightValueLabelStatic.getBoundingClientRect().left, rightValueLabelStatic.getBoundingClientRect().right);
const labelFontSize = this.getFontSizeForElement(leftValueLabel);
const labelTransformedOverlap = this.getRangeLabelOverlap(leftValueLabelTransformed, rightValueLabelTransformed);
const hyphenLabel = leftValueLabel;
const labelOffset = labelFontSize / 2;
if (labelTransformedOverlap > 0) {
hyphenLabel.classList.add(CSS.hyphen, CSS.hyphenWrap);
if (rightValueLabelStaticHostOffset === 0 && leftValueLabelStaticHostOffset === 0) {
let leftValueLabelTranslate = labelTransformedOverlap / 2 - labelOffset;
leftValueLabelTranslate = Math.sign(leftValueLabelTranslate) === -1 ? Math.abs(leftValueLabelTranslate) : -leftValueLabelTranslate;
const leftValueLabelTransformedHostOffset = this.getHostOffset(leftValueLabelTransformed.getBoundingClientRect().left + leftValueLabelTranslate - labelOffset, leftValueLabelTransformed.getBoundingClientRect().right + leftValueLabelTranslate - labelOffset);
let rightValueLabelTranslate = labelTransformedOverlap / 2;
const rightValueLabelTransformedHostOffset = this.getHostOffset(rightValueLabelTransformed.getBoundingClientRect().left + rightValueLabelTranslate, rightValueLabelTransformed.getBoundingClientRect().right + rightValueLabelTranslate);
if (leftValueLabelTransformedHostOffset !== 0) {
leftValueLabelTranslate += leftValueLabelTransformedHostOffset;
rightValueLabelTranslate += leftValueLabelTransformedHostOffset;
}
if (rightValueLabelTransformedHostOffset !== 0) {
leftValueLabelTranslate += rightValueLabelTransformedHostOffset;
rightValueLabelTranslate += rightValueLabelTransformedHostOffset;
}
leftValueLabel.style.transform = `translateX(${leftValueLabelTranslate}px)`;
leftValueLabelTransformed.style.transform = `translateX(${leftValueLabelTranslate - labelOffset}px)`;
rightValueLabel.style.transform = `translateX(${rightValueLabelTranslate}px)`;
rightValueLabelTransformed.style.transform = `translateX(${rightValueLabelTranslate}px)`;
} else if (leftValueLabelStaticHostOffset > 0 || rightValueLabelStaticHostOffset > 0) {
leftValueLabel.style.transform = `translateX(${leftValueLabelStaticHostOffset + labelOffset}px)`;
rightValueLabel.style.transform = `translateX(${labelTransformedOverlap + rightValueLabelStaticHostOffset}px)`;
rightValueLabelTransformed.style.transform = `translateX(${labelTransformedOverlap + rightValueLabelStaticHostOffset}px)`;
} else if (leftValueLabelStaticHostOffset < 0 || rightValueLabelStaticHostOffset < 0) {
let leftValueLabelTranslate = Math.abs(leftValueLabelStaticHostOffset) + labelTransformedOverlap - labelOffset;
leftValueLabelTranslate = Math.sign(leftValueLabelTranslate) === -1 ? Math.abs(leftValueLabelTranslate) : -leftValueLabelTranslate;
leftValueLabel.style.transform = `translateX(${leftValueLabelTranslate}px)`;
leftValueLabelTransformed.style.transform = `translateX(${leftValueLabelTranslate - labelOffset}px)`;
}
} else {
hyphenLabel.classList.remove(CSS.hyphen, CSS.hyphenWrap);
leftValueLabel.style.transform = `translateX(${leftValueLabelStaticHostOffset}px)`;
leftValueLabelTransformed.style.transform = `translateX(${leftValueLabelStaticHostOffset}px)`;
rightValueLabel.style.transform = `translateX(${rightValueLabelStaticHostOffset}px)`;
rightValueLabelTransformed.style.transform = `translateX(${rightValueLabelStaticHostOffset}px)`;
}
}
hideObscuredBoundingTickLabels() {
const valueIsRange = isRange(this.value);
if (!this.hasHistogram && !valueIsRange && !this.labelHandles && !this.precise) {
return;
}
if (!this.hasHistogram && !valueIsRange && this.labelHandles && !this.precise) {
return;
}
if (!this.hasHistogram && !valueIsRange && !this.labelHandles && this.precise) {
return;
}
if (!this.hasHistogram && !valueIsRange && this.labelHandles && this.precise) {
return;
}
if (!this.hasHistogram && valueIsRange && !this.precise) {
return;
}
if (this.hasHistogram && !this.precise && !this.labelHandles) {
return;
}
const minHandle = this.el.shadowRoot.querySelector(`.${CSS.thumbMinValue}`);
const maxHandle = this.el.shadowRoot.querySelector(`.${CSS.thumbValue}`);
const minTickLabel = this.el.shadowRoot.querySelector(`.${CSS.tickMin}`);
const maxTickLabel = this.el.shadowRoot.querySelector(`.${CSS.tickMax}`);
if (!minHandle && maxHandle && minTickLabel && maxTickLabel) {
minTickLabel.style.opacity = this.isMinTickLabelObscured(minTickLabel, maxHandle) ? "0" : "1";
maxTickLabel.style.opacity = this.isMaxTickLabelObscured(maxTickLabel, maxHandle) ? "0" : "1";
}
if (minHandle && maxHandle && minTickLabel && maxTickLabel) {
minTickLabel.style.opacity = this.isMinTickLabelObscured(minTickLabel, minHandle) || this.isMinTickLabelObscured(minTickLabel, maxHandle) ? "0" : "1";
maxTickLabel.style.opacity = this.isMaxTickLabelObscured(maxTickLabel, minHandle) || this.isMaxTickLabelObscured(maxTickLabel, maxHandle) && this.hasHistogram ? "0" : "1";
}
}
getHostOffset(leftBounds, rightBounds) {
const { left, right } = this.el.getBoundingClientRect();
if (leftBounds < left) {
return left - leftBounds;
}
if (rightBounds > right) {
return -(rightBounds - right);
}
return 0;
}
getRangeLabelOverlap(leftLabel, rightLabel) {
const leftLabelBounds = leftLabel.getBoundingClientRect();
const rightLabelBounds = rightLabel.getBoundingClientRect();
const leftLabelFontSize = this.getFontSizeForElement(leftLabel);
const rangeLabelOverlap = leftLabelBounds.right + leftLabelFontSize - rightLabelBounds.left;
return Math.max(rangeLabelOverlap, 0);
}
isMinTickLabelObscured(minLabel, handle) {
const minLabelBounds = minLabel.getBoundingClientRect();
const handleBounds = handle.getBoundingClientRect();
return intersects(minLabelBounds, handleBounds);
}
isMaxTickLabelObscured(maxLabel, handle) {
const maxLabelBounds = maxLabel.getBoundingClientRect();
const handleBounds = handle.getBoundingClientRect();
return intersects(maxLabelBounds, handleBounds);
}
internalLabelFormatter(value, type) {
const customFormatter = this.labelFormatter;
if (!customFormatter) {
return this.formatValue(value);
}
const formattedValue = customFormatter(value, type, this.formatValue);
if (formattedValue == null) {
return this.formatValue(value);
}
return formattedValue;
}
render() {
const id = this.el.id || this.guid;
const value = isRange(this.value) ? this.maxValue : this.value;
const min = this.minValue || this.min;
const useMinValue = this.shouldUseMinValue();
const minInterval = this.getUnitInterval(useMinValue ? this.minValue : min) * 100;
const maxInterval = this.getUnitInterval(value) * 100;
const mirror = this.shouldMirror();
const valueIsRange = isRange(this.value);
const thumbTypes = this.buildThumbType("max");
const thumb = this.renderThumb({
type: thumbTypes,
thumbPlacement: thumbTypes.includes("histogram") ? "below" : "above",
maxInterval,
minInterval,
mirror
});
const minThumbTypes = this.buildThumbType("min");
const minThumb = valueIsRange ? this.renderThumb({
type: minThumbTypes,
thumbPlacement: minThumbTypes.includes("histogram") || minThumbTypes.includes("precise") ? "below" : "above",
maxInterval,
minInterval,
mirror
}) : null;
const fillPlacement = valueIsRange ? "start" : this.fillPlacement;
const trackRangePlacementStyles = fillPlacement === "none" ? {
left: `unset`,
right: `unset`
} : fillPlacement === "end" ? {
left: `${mirror ? minInterval : maxInterval}%`,
right: `${mirror ? maxInterval : minInterval}%`
} : (
/* default */
{
left: `${mirror ? 100 - maxInterval : minInterval}%`,
right: `${mirror ? minInterval : 100 - maxInterval}%`
}
);
setAttribute(this.el, "id", id);
return InteractiveContainer({ disabled: this.disabled, children: html`<div aria-errormessage=${IDS.validationMessage} .ariaInvalid=${this.status === "invalid"} .ariaLabel=${getLabelText(this)} class=${safeClassMap({
[CSS.container]: true,
[CSS.containerRange]: valueIsRange,
[`scale--${this.scale}`]: true
})}>${this.renderGraph()}<div class=${safeClassMap(CSS.track)} ${ref(this.storeTrackRef)}><div class=${safeClassMap(CSS.trackRange)} @pointerdown=${this.onTrackPointerDown} style=${safeStyleMap(trackRangePlacementStyles)}></div><div class=${safeClassMap(CSS.ticks)}>${this.tickValues.map((tick) => {
const tickOffset = `${this.getUnitInterval(tick) * 100}%`;
let activeTicks = false;
if (fillPlacement === "start" || fillPlacement === "end") {
if (useMinValue) {
activeTicks = tick >= this.minValue && tick <= this.maxValue;
} else {
const rangeStart = fillPlacement === "start" ? min : value;
const rangeEnd = fillPlacement === "start" ? value : this.max;
activeTicks = tick >= rangeStart && tick <= rangeEnd;
}
}
return html`<span class=${safeClassMap({
[CSS.tick]: true,
[CSS.tickActive]: activeTicks
})} style=${safeStyleMap({
left: mirror ? "" : tickOffset,
right: mirror ? tickOffset : ""
})}>${this.renderTickLabel(tick)}</span>`;
})}</div></div><div class=${safeClassMap(CSS.thumbContainer)}>${minThumb}${thumb}${HiddenFormInputSlot({ component: this })}</div></div>${this.validationMessage && this.status === "invalid" ? Validation({ icon: this.validationIcon, id: IDS.validationMessage, message: this.validationMessage, scale: this.scale, status: this.status }) : null}` });
}
renderThumb({ type, mirror, thumbPlacement, minInterval, maxInterval }) {
const isLabeled = type.includes("labeled");
const isPrecise = type.includes("precise");
const isMinThumb = type.includes("min");
const valueIsRange = isRange(this.value);
const value = isMinThumb ? this.minValue : valueIsRange ? this.maxValue : this.value;
const valueProp = isMinThumb ? "minValue" : valueIsRange ? "maxValue" : "value";
const ariaLabel = isMinThumb ? this.minLabel : valueIsRange ? this.maxLabel : this.minLabel;
const ariaValuenow = isMinThumb ? this.minValue : value;
const displayedValue = valueProp === "minValue" ? this.internalLabelFormatter(this.minValue, "min") : valueProp === "maxValue" ? this.internalLabelFormatter(this.maxValue, "max") : this.internalLabelFormatter(value, "value");
const thumbStyle = isMinThumb ? { left: `${mirror ? 100 - minInterval : minInterval}%` } : { right: `${mirror ? maxInterval : 100 - maxInterval}%` };
const thumbLabelClasses = `${CSS.handleLabel} ${isMinThumb ? CSS.handleLabelMinValue : CSS.handleLabelValue}`;
const labels = isLabeled ? [
html`<span aria-hidden=true class=${safeClassMap(thumbLabelClasses)}>${displayedValue}</span>`,
html`<span aria-hidden=true class=${`${thumbLabelClasses} ${CSS.static}`}>${displayedValue}</span>`,
html`<span aria-hidden=true class=${`${thumbLabelClasses} ${CSS.transformed}`}>${displayedValue}</span>`
] : [];
const thumbContent = [
...labels,
html`<div class=${safeClassMap(CSS.handle)}></div>`,
isPrecise && html`<div class=${safeClassMap(CSS.handleExtension)}></div>` || ""
];
if (thumbPlacement === "below") {
thumbContent.reverse();
}
return keyed(type, html`<div .ariaLabel=${ariaLabel} aria-orientation=horizontal .ariaValueMax=${this.max} .ariaValueMin=${this.min} .ariaValueNow=${ariaValuenow} class=${safeClassMap({
[CSS.thumb]: true,
[CSS.thumbValue]: !isMinThumb,
[CSS.thumbActive]: this.lastDragProp !== "minMaxValue" && this.dragProp === valueProp,
[CSS.thumbPrecise]: isPrecise,
[CSS.thumbMinValue]: isMinThumb
})} data-value-prop=${valueProp ?? nothing} @blur=${this.onThumbBlur} @focus=${this.onThumbFocus} @pointerdown=${this.onThumbPointerDown} role=slider style=${safeStyleMap(thumbStyle)} tabindex=0 ${ref(this.storeThumbRef)}>${thumbContent}</div>`);
}
renderGraph() {
return this.histogram ? html`<calcite-graph class=${safeClassMap(CSS.graph)} .colorStops=${this.histogramStops} .data=${this.histogram} .highlightMax=${isRange(this.value) ? this.maxValue : this.value} .highlightMin=${isRange(this.value) ? this.minValue : this.min} .max=${this.max} .min=${this.min}></calcite-graph>` : null;
}
renderTickLabel(tick) {
const { hasHistogram, labelHandles, labelTicks, max, min, precise, value } = this;
const valueIsRange = isRange(value);
const isMinTickLabel = tick === min;
const isMaxTickLabel = tick === max;
const isAtEdge = isMinTickLabel || isMaxTickLabel;
const shouldDisplayLabel = labelTicks && (!hasHistogram && (isAtEdge || !precise || !valueIsRange) || hasHistogram && (isAtEdge || !precise && !labelHandles));
return shouldDisplayLabel ? html`<span class=${safeClassMap({
[CSS.tickLabel]: true,
[CSS.tickMin]: isMinTickLabel,
[CSS.tickMax]: isMaxTickLabel
})}>${this.internalLabelFormatter(tick, "tick")}</span>` : null;
}
}
customElement("calcite-slider", Slider);
export {
Slider
};