UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

341 lines (340 loc) • 19.6 kB
/* COPYRIGHT Esri - https://js.arcgis.com/5.0/LICENSE.txt */ import { c as customElement } from "../../chunks/runtime.js"; import { css, html } from "lit"; import { LitElement, createEvent, safeClassMap } from "@arcgis/lumina"; import { createRef, ref } from "lit/directives/ref.js"; import { i as isValidNumber } from "../../chunks/locale.js"; import { c as componentFocusable, g as getIconScale } from "../../chunks/component.js"; import { d as decimalPlaces } from "../../chunks/math.js"; import { g as getElementDir } from "../../chunks/dom.js"; import { u as useT9n } from "../../chunks/useT9n.js"; import { u as useSetFocus } from "../../chunks/useSetFocus.js"; import { u as useTime } from "../../chunks/useTime.js"; const CSS = { button: "button", buttonBottomLeft: "button--bottom-left", buttonBottomRight: "button--bottom-right", buttonFractionalSecondDown: "button--fractionalSecond-down", buttonFractionalSecondUp: "button--fractionalSecond-up", buttonHourDown: "button--hour-down", buttonHourUp: "button--hour-up", buttonMeridiemDown: "button--meridiem-down", buttonMeridiemUp: "button--meridiem-up", buttonMinuteDown: "button--minute-down", buttonMinuteUp: "button--minute-up", buttonSecondDown: "button--second-down", buttonSecondUp: "button--second-up", buttonTopLeft: "button--top-left", buttonTopRight: "button--top-right", column: "column", decimalSeparator: "decimal-separator", delimiter: "delimiter", fractionalSecond: "fractionalSecond", hour: "hour", hourSuffix: "hour-suffix", input: "input", inputFocus: "inputFocus", meridiem: "meridiem", minute: "minute", minuteSuffix: "minute-suffix", second: "second", secondSuffix: "second-suffix", showMeridiem: "show-meridiem", showSecond: "show-second", scale: (scale) => `scale-${scale}`, timePicker: "time-picker", meridiemStart: "meridiem--start" }; const ICONS = { chevronUp: "chevron-up", chevronDown: "chevron-down" }; const styles = css`:host{display:inline-block}.time-picker{display:flex;-webkit-user-select:none;user-select:none;align-items:center;border-width:1px;border-style:solid;font-weight:var(--calcite-font-weight-medium);border-color:var(--calcite-time-picker-border-color, var(--calcite-color-border-3));border-radius:var(--calcite-time-picker-corner-radius, var(--calcite-border-radius-round));color:var(--calcite-time-picker-color, var(--calcite-color-text-1));background-color:var(--calcite-time-picker-background-color, var(--calcite-color-foreground-1));overflow:hidden}.time-picker .column{display:flex;flex-direction:column}.time-picker .meridiem--start{order:-1}.time-picker .button{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;background-color:var(--calcite-time-picker-background-color, var(--calcite-color-foreground-1))}.time-picker .button:hover,.time-picker .button:focus{outline:2px solid transparent;outline-offset:2px;z-index:var(--calcite-z-index-header);outline-offset:0;background-color:var(--calcite-time-picker-button-background-color-hover, var(--calcite-color-foreground-2))}.time-picker .button:active{background-color:var(--calcite-time-picker-button-background-color-press, var(--calcite-color-foreground-3))}.time-picker .button.top-left{border-start-start-radius:var(--calcite-time-picker-corner-radius, var(--calcite-border-radius-round))}.time-picker .button.bottom-left{border-end-start-radius:var(--calcite-time-picker-corner-radius, var(--calcite-border-radius-round))}.time-picker .button.top-right{border-start-end-radius:var(--calcite-time-picker-corner-radius, var(--calcite-border-radius-round))}.time-picker .button.bottom-right{border-end-end-radius:var(--calcite-time-picker-corner-radius, var(--calcite-border-radius-round))}.time-picker .button calcite-icon{color:var(--calcite-time-picker-icon-color, var(--calcite-color-text-3))}.time-picker .input{display:inline-flex;cursor:pointer;align-items:center;justify-content:center;font-weight:var(--calcite-font-weight-medium);background-color:var(--calcite-time-picker-background-color, var(--calcite-color-foreground-1))}.time-picker .input:hover{box-shadow:inset 0 0 0 2px var(--calcite-time-picker-input-border-color-hover, var(--calcite-color-foreground-2));z-index:var(--calcite-z-index-header)}.time-picker .input:focus,.time-picker .input:hover:focus{outline:2px solid transparent;outline-offset:2px;outline-offset:0}.time-picker .input.inputFocus,.time-picker .input:hover.inputFocus{box-shadow:inset 0 0 0 2px var(--calcite-time-picker-input-border-color-press, var(--calcite-color-brand));z-index:var(--calcite-z-index-header)}.time-picker.scale-s{font-size:var(--calcite-font-size-relative-base)}.time-picker.scale-s .button,.time-picker.scale-s .input{padding-inline:var(--calcite-spacing-sm);padding-block:var(--calcite-spacing-xxs)}.time-picker.scale-s:not(.show-meridiem) .delimiter:last-child{padding-inline-end:var(--calcite-spacing-md)}.time-picker.scale-m{font-size:var(--calcite-font-size-relative-md)}.time-picker.scale-m .button,.time-picker.scale-m .input{padding-inline:var(--calcite-spacing-md);padding-block:var(--calcite-spacing-sm)}.time-picker.scale-m:not(.show-meridiem) .delimiter:last-child{padding-inline-end:var(--calcite-spacing-xl)}.time-picker.scale-l{font-size:var(--calcite-font-size-relative-lg)}.time-picker.scale-l .button,.time-picker.scale-l .input{padding-inline:var(--calcite-spacing-lg);padding-block:var(--calcite-spacing-md)}.time-picker.scale-l:not(.show-meridiem) .delimiter:last-child{padding-inline-end:var(--calcite-spacing-xxl)}:host([hidden]){display:none}[hidden]{display:none}`; class TimePicker extends LitElement { constructor() { super(); this.fractionalSecondRef = createRef(); this.hourRef = createRef(); this.meridiemRef = createRef(); this.minuteRef = createRef(); this.pointerActivated = false; this.secondRef = createRef(); this.messages = useT9n(); this.focusSetter = useSetFocus()(this); this.time = useTime(this); this.hourFormat = "user"; this.scale = "m"; this.step = 60; this.value = null; this.calciteTimePickerChange = createEvent({ cancelable: false }); this.listen("blur", this.blurHandler); this.listen("calciteTimeChange", this.timeChangeHandler); this.listen("keydown", this.keyDownHandler); this.listen("pointerdown", this.pointerDownHandler); } static { this.properties = { activeEl: [16, {}, { state: true }], showFractionalSecond: [16, {}, { state: true }], showSecond: [16, {}, { state: true }], time: [0, {}, { attribute: false }], hourFormat: [3, {}, { reflect: true }], messageOverrides: [0, {}, { attribute: false }], numberingSystem: 1, scale: [3, {}, { reflect: true }], step: [11, {}, { reflect: true, type: Number }], value: 1 }; } static { this.shadowRootOptions = { mode: "open", delegatesFocus: true }; } static { this.styles = styles; } async setFocus(options) { return this.focusSetter(() => this.el, options); } connectedCallback() { super.connectedCallback(); this.toggleSecond(); } willUpdate(changes) { if (changes.has("step") && (this.hasUpdated || this.step !== 60)) { this.toggleSecond(); } if (changes.has("value") && (this.hasUpdated || this.value !== null)) { this.time.setValue(this.value); } } blurHandler() { this.activeEl = void 0; this.pointerActivated = false; } keyDownHandler(event) { this.pointerActivated = false; const { defaultPrevented, key } = event; if (defaultPrevented) { return; } const { hourFormat } = this.time; switch (this.activeEl) { case this.hourRef.value: if (key === "ArrowRight") { this.focusPart("minute"); event.preventDefault(); } break; case this.minuteRef.value: switch (key) { case "ArrowLeft": this.focusPart("hour"); event.preventDefault(); break; case "ArrowRight": if (this.step !== 60) { this.focusPart("second"); event.preventDefault(); } else if (hourFormat === "12") { this.focusPart("meridiem"); event.preventDefault(); } break; } break; case this.secondRef.value: switch (key) { case "ArrowLeft": this.focusPart("minute"); event.preventDefault(); break; case "ArrowRight": if (this.showFractionalSecond) { this.focusPart("fractionalSecond"); } else if (hourFormat === "12") { this.focusPart("meridiem"); event.preventDefault(); } break; } break; case this.fractionalSecondRef.value: switch (key) { case "ArrowLeft": this.focusPart("second"); event.preventDefault(); break; case "ArrowRight": if (hourFormat === "12") { this.focusPart("meridiem"); event.preventDefault(); } break; } break; case this.meridiemRef.value: switch (key) { case "ArrowLeft": if (this.showFractionalSecond) { this.focusPart("fractionalSecond"); } else if (this.step !== 60) { this.focusPart("second"); event.preventDefault(); } else { this.focusPart("minute"); event.preventDefault(); } break; } break; } } pointerDownHandler() { this.pointerActivated = true; } async focusPart(target) { await componentFocusable(this); const ref2 = target === "hour" ? this.hourRef : target === "minute" ? this.minuteRef : target === "second" ? this.secondRef : target === "fractionalSecond" ? this.fractionalSecondRef : this.meridiemRef; ref2.value?.focus(); } focusHandler(event) { if (this.pointerActivated) { return; } this.activeEl = event.currentTarget; } fractionalSecondDownClickHandler() { this.activeEl = this.fractionalSecondRef.value; this.activeEl.focus(); this.time.nudgeFractionalSecond("down"); } fractionalSecondUpClickHandler() { this.activeEl = this.fractionalSecondRef.value; this.activeEl.focus(); this.time.nudgeFractionalSecond("up"); } hourDownClickHandler() { this.activeEl = this.hourRef.value; this.activeEl.focus(); this.time.decrementHour(); } hourUpClickHandler() { this.activeEl = this.hourRef.value; this.activeEl.focus(); this.time.incrementHour(); } inputClickHandler(event) { this.activeEl = event.target; } meridiemUpClickHandler() { this.activeEl = this.meridiemRef.value; this.activeEl.focus(); this.time.toggleMeridiem("up"); } meridiemDownClickHandler() { this.activeEl = this.meridiemRef.value; this.activeEl.focus(); this.time.toggleMeridiem("down"); } minuteDownClickHandler() { this.activeEl = this.minuteRef.value; this.activeEl.focus(); this.time.decrementMinute(); } minuteUpClickHandler() { this.activeEl = this.minuteRef.value; this.activeEl.focus(); this.time.incrementMinute(); } secondDownClickHandler() { this.activeEl = this.secondRef.value; this.activeEl.focus(); this.time.decrementSecond(); } secondUpClickHandler() { this.activeEl = this.secondRef.value; this.activeEl.focus(); this.time.incrementSecond(); } timeChangeHandler(event) { event.stopPropagation(); const newValue = event.detail; if (newValue !== this.value) { this.value = newValue; } this.calciteTimePickerChange.emit(); } toggleSecond() { this.showSecond = this.step < 60; this.stepPrecision = decimalPlaces(this.step); this.showFractionalSecond = this.stepPrecision > 0; } render() { const { activeEl, messages, scale } = this; const { _lang: locale } = messages; const { fractionalSecond, handleFractionalSecondKeyDownEvent, handleHourKeyDownEvent, handleMeridiemKeyDownEvent, handleMinuteKeyDownEvent, handleSecondKeyDownEvent, hour, hourFormat, localizedDecimalSeparator, localizedFractionalSecond, localizedHour, localizedHourSuffix, localizedMeridiem, localizedMinute, localizedMinuteSuffix, localizedSecond, localizedSecondSuffix, meridiem, meridiemOrder, minute, second } = this.time; const hourIsNumber = isValidNumber(hour); const iconScale = getIconScale(scale); const minuteIsNumber = isValidNumber(minute); const secondIsNumber = isValidNumber(second); const fractionalSecondIsNumber = isValidNumber(fractionalSecond); const showSecondSuffix = locale !== "bg" && localizedSecondSuffix; const showMeridiem = hourFormat === "12"; return html`<div class=${safeClassMap({ [CSS.timePicker]: true, [CSS.showMeridiem]: showMeridiem, [CSS.showSecond]: this.showSecond, [CSS.scale(scale)]: true })} dir=ltr><div class=${safeClassMap(CSS.column)} role=group><span .ariaLabel=${messages.hourUp} class=${safeClassMap({ [CSS.button]: true, [CSS.buttonHourUp]: true, [CSS.buttonTopLeft]: true })} @click=${this.hourUpClickHandler} role=button><calcite-icon .icon=${ICONS.chevronUp} .scale=${iconScale}></calcite-icon></span><span .ariaLabel=${messages.hour} aria-valuemax=23 aria-valuemin=1 .ariaValueNow=${hourIsNumber && parseInt(hour) || "0"} .ariaValueText=${hour} class=${safeClassMap({ [CSS.input]: true, [CSS.hour]: true, [CSS.inputFocus]: activeEl && activeEl === this.hourRef.value })} @click=${this.inputClickHandler} @focus=${this.focusHandler} @keydown=${handleHourKeyDownEvent} role=spinbutton tabindex=0 ${ref(this.hourRef)}>${localizedHour || "--"}</span><span .ariaLabel=${messages.hourDown} class=${safeClassMap({ [CSS.button]: true, [CSS.buttonHourDown]: true, [CSS.buttonBottomLeft]: true })} @click=${this.hourDownClickHandler} role=button><calcite-icon .icon=${ICONS.chevronDown} .scale=${iconScale}></calcite-icon></span></div><span class=${safeClassMap({ [CSS.delimiter]: true, [CSS.hourSuffix]: true })}>${localizedHourSuffix}</span><div class=${safeClassMap(CSS.column)} role=group><span .ariaLabel=${messages.minuteUp} class=${safeClassMap({ [CSS.button]: true, [CSS.buttonMinuteUp]: true })} @click=${this.minuteUpClickHandler} role=button><calcite-icon .icon=${ICONS.chevronUp} .scale=${iconScale}></calcite-icon></span><span .ariaLabel=${messages.minute} aria-valuemax=12 aria-valuemin=1 .ariaValueNow=${minuteIsNumber && parseInt(minute) || "0"} .ariaValueText=${minute} class=${safeClassMap({ [CSS.input]: true, [CSS.minute]: true, [CSS.inputFocus]: activeEl && activeEl === this.minuteRef.value })} @click=${this.inputClickHandler} @focus=${this.focusHandler} @keydown=${handleMinuteKeyDownEvent} role=spinbutton tabindex=0 ${ref(this.minuteRef)}>${localizedMinute || "--"}</span><span .ariaLabel=${messages.minuteDown} class=${safeClassMap({ [CSS.button]: true, [CSS.buttonMinuteDown]: true })} @click=${this.minuteDownClickHandler} role=button><calcite-icon .icon=${ICONS.chevronDown} .scale=${iconScale}></calcite-icon></span></div>${this.showSecond && html`<span class=${safeClassMap({ [CSS.delimiter]: true, [CSS.minuteSuffix]: true })}>${localizedMinuteSuffix}</span>` || ""}${this.showSecond && html`<div class=${safeClassMap(CSS.column)} role=group><span .ariaLabel=${messages.secondUp} class=${safeClassMap({ [CSS.button]: true, [CSS.buttonSecondUp]: true })} @click=${this.secondUpClickHandler} role=button><calcite-icon .icon=${ICONS.chevronUp} .scale=${iconScale}></calcite-icon></span><span .ariaLabel=${messages.second} aria-valuemax=59 aria-valuemin=0 .ariaValueNow=${secondIsNumber && parseInt(second) || "0"} .ariaValueText=${second} class=${safeClassMap({ [CSS.input]: true, [CSS.second]: true, [CSS.inputFocus]: activeEl && activeEl === this.secondRef.value })} @click=${this.inputClickHandler} @focus=${this.focusHandler} @keydown=${handleSecondKeyDownEvent} role=spinbutton tabindex=0 ${ref(this.secondRef)}>${localizedSecond || "--"}</span><span .ariaLabel=${messages.secondDown} class=${safeClassMap({ [CSS.button]: true, [CSS.buttonSecondDown]: true })} @click=${this.secondDownClickHandler} role=button><calcite-icon .icon=${ICONS.chevronDown} .scale=${iconScale}></calcite-icon></span></div>` || ""}${this.showFractionalSecond && html`<span class=${safeClassMap({ [CSS.delimiter]: true, [CSS.decimalSeparator]: true })}>${localizedDecimalSeparator}</span>` || ""}${this.showFractionalSecond && html`<div class=${safeClassMap(CSS.column)} role=group><span .ariaLabel=${messages.fractionalSecondUp} class=${safeClassMap({ [CSS.button]: true, [CSS.buttonFractionalSecondUp]: true })} @click=${this.fractionalSecondUpClickHandler} role=button><calcite-icon .icon=${ICONS.chevronUp} .scale=${iconScale}></calcite-icon></span><span .ariaLabel=${messages.fractionalSecond} aria-valuemax=999 aria-valuemin=1 .ariaValueNow=${fractionalSecondIsNumber && parseInt(fractionalSecond) || "0"} .ariaValueText=${localizedFractionalSecond} class=${safeClassMap({ [CSS.input]: true, [CSS.fractionalSecond]: true, [CSS.inputFocus]: activeEl && activeEl === this.fractionalSecondRef.value })} @click=${this.inputClickHandler} @focus=${this.focusHandler} @keydown=${handleFractionalSecondKeyDownEvent} role=spinbutton tabindex=0 ${ref(this.fractionalSecondRef)}>${localizedFractionalSecond || "".padStart(this.stepPrecision, "-")}</span><span .ariaLabel=${messages.fractionalSecondDown} class=${safeClassMap({ [CSS.button]: true, [CSS.buttonFractionalSecondDown]: true })} @click=${this.fractionalSecondDownClickHandler} role=button><calcite-icon .icon=${ICONS.chevronDown} .scale=${iconScale}></calcite-icon></span></div>` || ""}${showSecondSuffix && html`<span class=${safeClassMap({ [CSS.delimiter]: true, [CSS.secondSuffix]: true })}>${localizedSecondSuffix.trim()}</span>` || ""}${showMeridiem && html`<div class=${safeClassMap({ [CSS.column]: true, [CSS.meridiemStart]: meridiemOrder === 0 || getElementDir(this.el) === "rtl" })} role=group><span .ariaLabel=${messages.meridiemUp} class=${safeClassMap({ [CSS.button]: true, [CSS.buttonMeridiemUp]: true, [CSS.buttonTopRight]: true })} @click=${this.meridiemUpClickHandler} role=button><calcite-icon .icon=${ICONS.chevronUp} .scale=${iconScale}></calcite-icon></span><span .ariaLabel=${messages.meridiem} aria-valuemax=2 aria-valuemin=1 .ariaValueNow=${meridiem === "PM" && "2" || "1"} .ariaValueText=${meridiem} class=${safeClassMap({ [CSS.input]: true, [CSS.meridiem]: true, [CSS.inputFocus]: activeEl && activeEl === this.meridiemRef.value })} @click=${this.inputClickHandler} @focus=${this.focusHandler} @keydown=${handleMeridiemKeyDownEvent} role=spinbutton tabindex=0 ${ref(this.meridiemRef)}>${localizedMeridiem || "--"}</span><span .ariaLabel=${messages.meridiemDown} class=${safeClassMap({ [CSS.button]: true, [CSS.buttonMeridiemDown]: true, [CSS.buttonBottomRight]: true })} @click=${this.meridiemDownClickHandler} role=button><calcite-icon .icon=${ICONS.chevronDown} .scale=${iconScale}></calcite-icon></span></div>` || ""}</div>`; } } customElement("calcite-time-picker", TimePicker); export { TimePicker };