UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

354 lines (353 loc) • 24 kB
/* COPYRIGHT Esri - https://js.arcgis.com/5.0/LICENSE.txt */ import { c as customElement } from "../../chunks/runtime.js"; import { css, nothing, html } from "lit"; import { LitElement, createEvent, stringOrBoolean, safeClassMap } from "@arcgis/lumina"; import { createRef, ref } from "lit/directives/ref.js"; import { c as connectForm, d as disconnectForm, s as submitForm, H as HiddenFormInputSlot } from "../../chunks/form.js"; import { c as connectLabel, d as disconnectLabel, g as getLabelText } from "../../chunks/label.js"; import { d as decimalPlaces } from "../../chunks/math.js"; import { g as getIconScale } from "../../chunks/component.js"; import { I as InternalLabel } from "../../chunks/InternalLabel.js"; import { V as Validation } from "../../chunks/Validation.js"; import { g as getElementDir } from "../../chunks/dom.js"; import { s as syncHiddenFormInput } from "../../chunks/input.js"; import { u as useT9n } from "../../chunks/useT9n.js"; import { i as isValidNumber } from "../../chunks/locale.js"; import { u as useSetFocus } from "../../chunks/useSetFocus.js"; import { u as useTime } from "../../chunks/useTime.js"; import { u as useInteractive } from "../../chunks/useInteractive.js"; const styles = css`:host([disabled]){cursor:default;-webkit-user-select:none;user-select:none;opacity:var(--calcite-opacity-disabled)}:host([disabled]) *,:host([disabled]) ::slotted(*){pointer-events:none}:host{display:inline-block}:host([disabled]) ::slotted([calcite-hydrated][disabled]),:host([disabled]) [calcite-hydrated][disabled]{opacity:1}.interaction-container{display:contents}::slotted(input[slot=hidden-form-input]){margin:0!important;opacity:0!important;outline:none!important;padding:0!important;position:absolute!important;inset:0!important;transform:none!important;-webkit-appearance:none!important;z-index:-1!important}calcite-time-picker{--calcite-time-picker-color: var(--calcite-input-time-picker-digit-text-color);--calcite-time-picker-icon-color: var(--calcite-input-time-picker-digit-icon-color);--calcite-time-picker-border-color: var(--calcite-input-time-picker-border-color);--calcite-time-picker-button-background-color-hover: var(--calcite-input-time-picker-action-background-color-hover);--calcite-time-picker-button-background-color-press: var(--calcite-input-time-picker-action-background-color-press);--calcite-time-picker-input-border-color-hover: var(--calcite-input-time-picker-digit-border-color-hover);--calcite-time-picker-input-border-color-press: var(--calcite-input-time-picker-digit-border-color-press)}.container{--calcite-icon-color: var( --calcite-input-time-picker-icon-color, var(--calcite-ui-icon-color, var(--calcite-color-text-3)) );align-items:center;background-color:var(--calcite-input-time-picker-input-background-color, var(--calcite-color-foreground-1));border:1px solid var(--calcite-input-time-picker-input-border-color, var(--calcite-color-border-input));border-radius:var(--calcite-input-time-picker-input-corner-radius, var(--calcite-corner-radius-none));box-shadow:var(--calcite-input-time-picker-input-shadow, var(--calcite-shadow-none));box-sizing:border-box;display:flex;color:var(--calcite-input-time-picker-input-text-color, var(--calcite-color-text-1));flex-wrap:nowrap;font-weight:var(--calcite-font-weight-normal);inline-size:100%;padding-block:var(--calcite-spacing-base);-webkit-user-select:none;user-select:none}.container:focus-within{border-color:var(--calcite-color-brand);outline:var(--calcite-border-width-md) solid var(--calcite-color-focus, var(--calcite-ui-focus-color, var(--calcite-color-brand)));outline-offset:calc(calc(-1 * var(--calcite-spacing-base)) * calc(1 - (2*clamp(0,var(--calcite-offset-invert-focus),1))))}.container.read-only{background-color:var(--calcite-color-background);font-weight:var(--calcite-font-weight-medium)}.clock-icon{--calcite-icon-color: var( --calcite-input-time-picker-icon-color, var(--calcite-ui-icon-color, var(--calcite-color-text-3)) )}.hour-suffix,.minute-suffix,.second-suffix{white-space:pre}.input-container{display:flex;flex-grow:1}.input{align-items:center;display:flex;block-size:100%;justify-content:center;min-inline-size:max-content}.input.empty{inline-size:var(--calcite-spacing-xl)}.input:focus,.input:hover:focus{background-color:Highlight;color:HighlightText;outline:2px solid transparent;outline-offset:2px}.toggle-icon{--calcite-icon-color: var(--calcite-input-time-picker-icon-color, var(--calcite-color-text-3));align-items:center;block-size:24px;cursor:pointer;display:flex;inline-size:24px;justify-content:center}.toggle-icon:hover{--calcite-icon-color: var(--calcite-input-time-picker-icon-color-hover, var(--calcite-color-text-1))}:host([scale=s]) .container{block-size:1.5rem;font-size:var(--calcite-font-size-sm);gap:var(--calcite-spacing-sm);padding-inline-start:var(--calcite-spacing-sm);padding-inline-end:var(--calcite-spacing-xxs)}:host([scale=s]) .input-container{line-height:1rem}:host([scale=m]) .container{block-size:2rem;font-size:var(--calcite-font-size);gap:var(--calcite-spacing-md);padding-inline-start:var(--calcite-spacing-md);padding-inline-end:var(--calcite-spacing-sm)}:host([scale=m]) .input-container{line-height:1.5rem}:host([scale=l]) .container{block-size:2.75rem;font-size:var(--calcite-font-size-md);gap:var(--calcite-spacing-lg);padding-inline:var(--calcite-spacing-lg)}:host([scale=l]) .input-container{line-height:2.25rem}:host([status=invalid]) .container{border-color:var(--calcite-color-status-danger)}:host([status=invalid]) .container:focus-within{outline:var(--calcite-border-width-md) solid var(--calcite-color-status-danger);outline-offset:calc(calc(-1 * var(--calcite-spacing-base)) * calc(1 - (2*clamp(0,var(--calcite-offset-invert-focus),1))))}calcite-time-picker{--calcite-time-picker-background-color: var(--calcite-input-time-picker-background-color);--calcite-time-picker-border-color: var(--calcite-input-time-picker-border-color, transparent);--calcite-time-picker-corner-radius: var( --calcite-input-time-picker-corner-radius, var(--calcite-corner-radius-round) )}calcite-popover{--calcite-popover-corner-radius: var(--calcite-input-time-picker-corner-radius, var(--calcite-corner-radius-round))}.internal-label-alignment--center{align-items:center}.internal-label-alignment--end{align-items:end}.internal-label--container{display:flex;justify-content:space-between;color:var(--calcite-color-text-1)}.internal-label-required--indicator{font-weight:var(--calcite-font-weight-medium);color:var(--calcite-color-status-danger);padding-inline:var(--calcite-spacing-base)}.internal-label-required--indicator:hover{cursor:help}.internal-label--text{line-height:1}:host([scale=s]) .internal-label-spacing--bottom{margin-block-end:var(--calcite-spacing-xxs)}:host([scale=s]) .internal-label-spacing-inline--end{margin-inline-end:var(--calcite-spacing-sm)}:host([scale=s]) .internal-label-spacing-inline--start{margin-inline-start:var(--calcite-spacing-sm)}:host([scale=s]) .internal-label--text{font-size:var(--calcite-font-size--2)}:host([scale=m]) .internal-label-spacing--bottom{margin-block-end:var(--calcite-spacing-sm)}:host([scale=m]) .internal-label-spacing-inline--end{margin-inline-end:var(--calcite-spacing-sm)}:host([scale=m]) .internal-label-spacing-inline--start{margin-inline-start:var(--calcite-spacing-sm)}:host([scale=m]) .internal-label--text{font-size:var(--calcite-font-size--1)}:host([scale=l]) .internal-label-spacing--bottom{margin-block-end:var(--calcite-spacing-sm)}:host([scale=l]) .internal-label-spacing-inline--end{margin-inline-end:var(--calcite-spacing-md)}:host([scale=l]) .internal-label-spacing-inline--start{margin-inline-start:var(--calcite-spacing-md)}:host([scale=l]) .internal-label--text{font-size:var(--calcite-font-size-0)}.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}:host([hidden]){display:none}[hidden]{display:none}`; const CSS = { clockIcon: "clock-icon", container: "container", decimalSeparator: "decimal-separator", empty: "empty", fractionalSecond: "fractional-second", hour: "hour", hourSuffix: "hour-suffix", input: "input", inputContainer: "input-container", meridiem: "meridiem", minute: "minute", minuteSuffix: "minute-suffix", readOnly: "read-only", second: "second", secondSuffix: "second-suffix", toggleIcon: "toggle-icon" }; const IDS = { inputContainer: "inputContainer", validationMessage: "inputTimePickerValidationMessage" }; const ICONS = { clock: "clock", chevronUp: "chevron-up", chevronDown: "chevron-down" }; class InputTimePicker extends LitElement { constructor() { super(); this.messages = useT9n(); this.containerRef = createRef(); this.focusSetter = useSetFocus()(this); this.fractionalSecondRef = createRef(); this.hourRef = createRef(); this.meridiemRef = createRef(); this.minuteRef = createRef(); this.secondRef = createRef(); this.time = useTime(this); this.interactiveContainer = useInteractive(this); this.timePickerRef = createRef(); this.disabled = false; this.focusTrapDisabled = false; this.hourFormat = "user"; this.open = false; this.overlayPositioning = "absolute"; this.placement = "auto"; this.readOnly = false; this.required = false; this.scale = "m"; this.status = "idle"; this.step = 60; 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.calciteInputTimePickerBeforeClose = createEvent({ cancelable: false }); this.calciteInputTimePickerBeforeOpen = createEvent({ cancelable: false }); this.calciteInputTimePickerChange = createEvent(); this.calciteInputTimePickerClose = createEvent({ cancelable: false }); this.calciteInputTimePickerOpen = createEvent({ cancelable: false }); this.listen("blur", this.blurHandler); this.listen("keydown", this.keyDownHandler); this.listen("calciteTimeChange", this.timeChangeHandler); } static { this.properties = { disabled: [7, {}, { reflect: true, type: Boolean }], focusTrapDisabled: [7, {}, { reflect: true, type: Boolean }], form: [3, {}, { reflect: true }], hourFormat: [3, {}, { reflect: true }], label: 1, labelText: 1, max: [3, {}, { reflect: true }], messageOverrides: [0, {}, { attribute: false }], min: [3, {}, { reflect: true }], name: 1, numberingSystem: [3, {}, { reflect: true }], open: [7, {}, { reflect: true, type: Boolean }], overlayPositioning: 1, placement: [3, {}, { reflect: true }], readOnly: [7, {}, { reflect: true, type: Boolean }], required: [7, {}, { reflect: true, type: Boolean }], scale: [3, {}, { reflect: true }], status: [3, {}, { reflect: true }], step: [11, {}, { reflect: true, type: Number }], validationIcon: [3, { converter: stringOrBoolean, type: String }, { reflect: true }], validationMessage: 1, validity: [0, {}, { attribute: false }], value: 1 }; } static { this.shadowRootOptions = { mode: "open", delegatesFocus: true }; } static { this.styles = styles; } async reposition(delayed = false) { this.popoverEl?.reposition(delayed); } async setFocus(options) { return this.focusSetter(() => this.el, options); } connectedCallback() { super.connectedCallback(); connectLabel(this); connectForm(this); } willUpdate(changes) { if (changes.has("open") && (this.hasUpdated || this.open !== false)) { this.openHandler(); } if (changes.has("disabled") && (this.hasUpdated || this.disabled !== false)) { if (!this.disabled) { this.open = false; } } if (changes.has("readOnly") && (this.hasUpdated || this.readOnly !== false)) { if (!this.readOnly) { this.open = false; } } if (changes.has("value")) { if (this.hasUpdated) { if (!this.time.userChangedValue) { this.previousEmittedValue = this.value; } this.time.setValue(this.value); this.requestTimePickerUpdate(); } else { this.previousEmittedValue = this.value; } } } disconnectedCallback() { super.disconnectedCallback(); disconnectLabel(this); disconnectForm(this); } blurHandler() { this.changeEventHandler(); } changeEventHandler() { const { previousEmittedValue, value } = this; if (previousEmittedValue !== value) { const changeEvent = this.calciteInputTimePickerChange.emit(); if (changeEvent.defaultPrevented) { this.time.setValue(this.previousEmittedValue); } else { this.previousEmittedValue = value; } } } keyDownHandler(event) { const { defaultPrevented, key } = event; const { hourFormat, meridiemOrder } = this.time; if (defaultPrevented) { return; } if (key === "Enter") { if (submitForm(this)) { event.preventDefault(); } this.changeEventHandler(); } else if (this.open && key === "Escape") { this.open = false; event.preventDefault(); } else { const showFractionalSecond = decimalPlaces(this.step) > 0; const showSecond = this.step < 60; switch (this.activeEl) { case this.hourRef.value: if (key === "ArrowRight") { this.setFocusPart("minute"); } else if (key === "ArrowLeft" && hourFormat === "12" && meridiemOrder === 0) { this.setFocusPart("meridiem"); } break; case this.minuteRef.value: switch (key) { case "ArrowLeft": this.setFocusPart("hour"); break; case "ArrowRight": if (this.step !== 60) { this.setFocusPart("second"); } else if (hourFormat === "12") { this.setFocusPart("meridiem"); } break; } break; case this.secondRef.value: switch (key) { case "ArrowLeft": this.setFocusPart("minute"); break; case "ArrowRight": if (decimalPlaces(this.step) > 0) { this.setFocusPart("fractionalSecond"); } else if (hourFormat === "12") { this.setFocusPart("meridiem"); } break; } break; case this.fractionalSecondRef.value: switch (key) { case "ArrowLeft": this.setFocusPart("second"); break; case "ArrowRight": if (hourFormat === "12" && meridiemOrder !== 0) { this.setFocusPart("meridiem"); } break; } break; case this.meridiemRef.value: if (key === "ArrowLeft" && meridiemOrder !== 0) { if (showFractionalSecond) { this.setFocusPart("fractionalSecond"); } else if (showSecond) { this.setFocusPart("second"); } else { this.setFocusPart("minute"); } } else if (key === "ArrowRight" && meridiemOrder === 0) { this.setFocusPart("hour"); } break; } } } onLabelClick() { this.setFocus(); } openHandler() { if (this.disabled || this.readOnly) { return; } if (this.popoverEl) { this.popoverEl.open = this.open; } } popoverBeforeOpenHandler(event) { event.stopPropagation(); this.calciteInputTimePickerBeforeOpen.emit(); } popoverOpenHandler(event) { event.stopPropagation(); this.calciteInputTimePickerOpen.emit(); } popoverBeforeCloseHandler(event) { event.stopPropagation(); this.calciteInputTimePickerBeforeClose.emit(); } popoverCloseHandler(event) { event.stopPropagation(); this.calciteInputTimePickerClose.emit(); this.open = false; } requestTimePickerUpdate() { this.timePickerRef.value.manager?.component.requestUpdate(); } setCalcitePopoverEl(el) { this.popoverEl = el; this.openHandler(); } async setFocusPart(target) { const ref2 = target === "hour" ? this.hourRef : target === "minute" ? this.minuteRef : target === "second" ? this.secondRef : target === "fractionalSecond" ? this.fractionalSecondRef : this.meridiemRef; ref2.value?.focus(); } syncHiddenFormInput(input) { syncHiddenFormInput("time", this, input); } timeChangeHandler(event) { event.stopPropagation(); if (this.disabled) { return; } const newValue = event.detail; if (newValue !== this.value) { this.value = newValue; } else { this.requestTimePickerUpdate(); } } timePartFocusHandler(event) { this.activeEl = event.currentTarget; } timePickerChangeHandler(event) { event.stopPropagation(); } toggleIconClickHandler() { this.open = !this.open; } render() { const { messages, readOnly, scale } = this; const { fractionalSecond, handleHourKeyDownEvent, handleMinuteKeyDownEvent, handleSecondKeyDownEvent, handleFractionalSecondKeyDownEvent, hour, hourFormat, localizedDecimalSeparator, localizedFractionalSecond, localizedHour, localizedHourSuffix, localizedMinute, localizedMinuteSuffix, localizedSecond, localizedSecondSuffix, meridiemOrder, minute, second } = this.time; const emptyPlaceholder = "--"; const fractionalSecondIsNumber = isValidNumber(fractionalSecond); const hourIsNumber = isValidNumber(hour); const minuteIsNumber = isValidNumber(minute); const secondIsNumber = isValidNumber(second); const showFractionalSecond = decimalPlaces(this.step) > 0; const showMeridiem = hourFormat === "12"; const showSecond = this.step < 60; const meridiemStart = meridiemOrder === 0 || getElementDir(this.el) === "rtl"; const isInteractive = !this.disabled && !this.readOnly; return this.interactiveContainer({ disabled: this.disabled, children: html`${this.labelText && InternalLabel({ labelText: this.labelText, onClick: this.onLabelClick, required: this.required, tooltipText: this.messages.required }) || ""}<div aria-controls=${IDS.inputContainer} aria-labelledby=${IDS.inputContainer} class=${safeClassMap({ [CSS.container]: true, [CSS.readOnly]: readOnly })} role=combobox ${ref(this.containerRef)}><calcite-icon class=${safeClassMap(CSS.clockIcon)} .icon=${ICONS.clock} .scale=${scale === "l" ? "m" : "s"}></calcite-icon><div aria-label=${getLabelText(this) ?? nothing} .ariaRequired=${this.required} class=${safeClassMap(CSS.inputContainer)} dir=ltr id=${IDS.inputContainer} role=group>${showMeridiem && meridiemStart && this.renderMeridiem() || ""}<span aria-label=${this.messages.hour ?? nothing} aria-valuemax=23 aria-valuemin=1 aria-valuenow=${(hourIsNumber && parseInt(hour) || "0") ?? nothing} aria-valuetext=${hour ?? nothing} class=${safeClassMap({ [CSS.empty]: !localizedHour, [CSS.hour]: true, [CSS.input]: true })} @focus=${this.timePartFocusHandler} @keydown=${isInteractive ? handleHourKeyDownEvent : void 0} role=spinbutton tabindex=0 ${ref(this.hourRef)}>${localizedHour || emptyPlaceholder}</span><span class=${safeClassMap(CSS.hourSuffix)}>${localizedHourSuffix}</span><span aria-label=${this.messages.minute ?? nothing} aria-valuemax=12 aria-valuemin=1 aria-valuenow=${(minuteIsNumber && parseInt(minute) || "0") ?? nothing} aria-valuetext=${minute ?? nothing} class=${safeClassMap({ [CSS.empty]: !localizedMinute, [CSS.input]: true, [CSS.minute]: true })} @focus=${this.timePartFocusHandler} @keydown=${isInteractive ? handleMinuteKeyDownEvent : void 0} role=spinbutton tabindex=0 ${ref(this.minuteRef)}>${localizedMinute || emptyPlaceholder}</span><span class=${safeClassMap(CSS.minuteSuffix)}>${localizedMinuteSuffix}</span>${showSecond && html`<span aria-label=${this.messages.second ?? nothing} aria-valuemax=59 aria-valuemin=0 aria-valuenow=${(secondIsNumber && parseInt(second) || "0") ?? nothing} aria-valuetext=${second ?? nothing} class=${safeClassMap({ [CSS.empty]: !localizedSecond, [CSS.input]: true, [CSS.second]: true })} @focus=${this.timePartFocusHandler} @keydown=${isInteractive ? handleSecondKeyDownEvent : void 0} role=spinbutton tabindex=0 ${ref(this.secondRef)}>${localizedSecond || emptyPlaceholder}</span>` || ""}${showFractionalSecond && html`<span class=${safeClassMap(CSS.decimalSeparator)}>${localizedDecimalSeparator}</span>` || ""}${showFractionalSecond && html`<span aria-label=${this.messages.fractionalSecond ?? nothing} aria-valuemax=999 aria-valuemin=1 aria-valuenow=${(fractionalSecondIsNumber && parseInt(fractionalSecond) || "0") ?? nothing} aria-valuetext=${localizedFractionalSecond ?? nothing} class=${safeClassMap({ [CSS.empty]: !localizedFractionalSecond, [CSS.fractionalSecond]: true, [CSS.input]: true })} @focus=${this.timePartFocusHandler} @keydown=${isInteractive ? handleFractionalSecondKeyDownEvent : void 0} role=spinbutton tabindex=0 ${ref(this.fractionalSecondRef)}>${localizedFractionalSecond || "".padStart(decimalPlaces(this.step), "-")}</span>` || ""}${localizedSecondSuffix && html`<span class=${safeClassMap(CSS.secondSuffix)}>${localizedSecondSuffix}</span>` || ""}${showMeridiem && !meridiemStart && this.renderMeridiem() || ""}</div>${!this.readOnly && this.renderToggleIcon(this.open) || ""}</div><calcite-popover auto-close .focusTrapDisabled=${this.focusTrapDisabled} .focusTrapOptions=${{ initialFocus: false }} .label=${messages.chooseTime} lang=${this.messages._lang ?? nothing} offset-distance=0 @calcitePopoverBeforeClose=${this.popoverBeforeCloseHandler} @calcitePopoverBeforeOpen=${this.popoverBeforeOpenHandler} @calcitePopoverClose=${this.popoverCloseHandler} @calcitePopoverOpen=${this.popoverOpenHandler} .overlayPositioning=${this.overlayPositioning} .placement=${this.placement} pointer-disabled .referenceElement=${this.containerRef.value} trigger-disabled ${ref(this.setCalcitePopoverEl)}><calcite-time-picker .hourFormat=${this.time.hourFormat} lang=${this.messages._lang ?? nothing} .messageOverrides=${this.messageOverrides} .numberingSystem=${this.numberingSystem} @calciteTimePickerChange=${this.timePickerChangeHandler} .scale=${this.scale} .step=${this.step} tabindex=${(this.open ? void 0 : -1) ?? nothing} .time=${this.time} .value=${this.value} ${ref(this.timePickerRef)}></calcite-time-picker></calcite-popover>${HiddenFormInputSlot({ component: this })}${this.validationMessage && this.status === "invalid" ? Validation({ icon: this.validationIcon, id: IDS.validationMessage, message: this.validationMessage, scale: this.scale, status: this.status }) : null}` }); } renderMeridiem() { const { handleMeridiemKeyDownEvent, localizedMeridiem, meridiem } = this.time; const isInteractive = !this.disabled && !this.readOnly; return html`<span aria-label=${this.messages.meridiem ?? nothing} aria-valuemax=2 aria-valuemin=1 aria-valuenow=${meridiem === "PM" && "2" || "1"} aria-valuetext=${meridiem ?? nothing} class=${safeClassMap({ [CSS.empty]: !localizedMeridiem, [CSS.input]: true, [CSS.meridiem]: true })} @focus=${this.timePartFocusHandler} @keydown=${isInteractive ? handleMeridiemKeyDownEvent : void 0} role=spinbutton tabindex=0 ${ref(this.meridiemRef)}>${localizedMeridiem || "--"}</span>`; } renderToggleIcon(open) { return html`<span class=${safeClassMap(CSS.toggleIcon)} @click=${this.toggleIconClickHandler}><calcite-icon .icon=${open ? ICONS.chevronUp : ICONS.chevronDown} .scale=${getIconScale(this.scale)}></calcite-icon></span>`; } } customElement("calcite-input-time-picker", InputTimePicker); export { InputTimePicker };