UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

314 lines (313 loc) • 21.1 kB
/*! 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 } from "lit"; import { LitElement, createEvent, stringOrBoolean, safeClassMap } from "@arcgis/lumina"; import { h as focusFirstTabbable, a as slotChangeHasAssignedElement, v as setRequestedIcon } from "../../chunks/dom.js"; import { c as componentFocusable, g as getIconScale } from "../../chunks/component.js"; import { N as NumberStringFormat } from "../../chunks/locale.js"; import { o as onToggleOpenCloseComponent } from "../../chunks/openCloseComponent.js"; import { K as KindIcons } from "../../chunks/resources4.js"; import { u as useT9n } from "../../chunks/useT9n.js"; import { css } from "@lit/reactive-element/css-tag.js"; const DURATIONS = { slow: 14e3, medium: 1e4, fast: 6e3 }; const SLOTS = { actionsEnd: "actions-end", title: "title", message: "message", link: "link" }; const CSS = { actionsEnd: "actions-end", close: "close", container: "container", containerActive: "container--active", dismissProgress: "dismiss-progress", icon: "icon", containerEmbedded: "container--embedded", queueCount: "queue-count", queueCountActive: "queue-count--active", textContainer: "text-container", focused: "focused" }; const alertQueueTimeoutMs = 300; class AlertManager { constructor() { this.registeredElements = []; this.queueTimeoutId = null; } // -------------------------------------------------------------------------- // // Public Methods // // -------------------------------------------------------------------------- registerElement(alert) { const { registeredElements } = this; if (!registeredElements.includes(alert)) { switch (alert.queue) { case "immediate": registeredElements.unshift(alert); break; case "next": registeredElements.splice(1, 0, alert); break; case "last": registeredElements.push(alert); break; } this.updateAlerts(); } } unregisterElement(alert) { const { registeredElements } = this; const index = registeredElements.indexOf(alert); if (index !== -1) { registeredElements.splice(index, 1); } alert.active = false; this.updateAlerts(); } // -------------------------------------------------------------------------- // // Private Methods // // -------------------------------------------------------------------------- updateAlerts() { window.clearTimeout(this.queueTimeoutId); this.queueTimeoutId = null; this.registeredElements.forEach((alert, index) => { alert.openAlertCount = this.registeredElements.length; if (index === 0) { this.queueTimeoutId = window.setTimeout(() => alert.active = true, alertQueueTimeoutMs); } else { alert.active = false; } }); } } const styles = css`:host{display:block;inline-size:var(--calcite-alert-width)}.container{pointer-events:none;position:fixed;z-index:var(--calcite-z-index-toast);margin-inline:auto;margin-block:0px;box-sizing:border-box;display:flex;inline-size:100%;min-inline-size:min-content;align-items:center;justify-content:center;text-align:start;opacity:0;--tw-shadow: 0 6px 20px -4px rgba(0, 0, 0, .1), 0 4px 12px -2px rgba(0, 0, 0, .08);--tw-shadow-colored: 0 6px 20px -4px var(--tw-shadow-color), 0 4px 12px -2px var(--tw-shadow-color);box-shadow:var(--calcite-alert-shadow, var(--tw-ring-offset-shadow, 0 0 rgba(0, 0, 0, 0)), var(--tw-ring-shadow, 0 0 rgba(0, 0, 0, 0)), var(--tw-shadow));background-color:var(--calcite-alert-background-color, var(--calcite-color-foreground-1));border-radius:var(--calcite-alert-corner-radius, var(--calcite-border-radius));border-block-start:0 solid transparent;border-inline:1px solid var(--calcite-color-border-3);border-block-end:1px solid var(--calcite-color-border-3);max-inline-size:calc(100% - var(--calcite-alert-offset-size, 2rem) * 2);transition:opacity var(--calcite-internal-animation-timing-slow) cubic-bezier(.215,.44,.42,.88),all var(--calcite-animation-timing) ease-in-out}.container--bottom,.container--top{inset-inline-end:0;inset-inline-start:0}.container[class*=bottom]{transform:translate3d(0,var(--calcite-alert-offset-size, 2rem),0);inset-block-end:var(--calcite-alert-offset-size, 2rem)}.container[class*=top]{transform:translate3d(0,calc(-1 * var(--calcite-alert-offset-size, 2rem)),0);inset-block-start:var(--calcite-alert-offset-size, 2rem)}.container[class*=start]{inset-inline-start:var(--calcite-alert-offset-size, 2rem);inset-inline-end:auto}.container[class*=end]{inset-inline-end:var(--calcite-alert-offset-size, 2rem);inset-inline-start:auto}.icon{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:0;margin-block:auto;margin-inline-end:auto}.close{display:flex;cursor:pointer;align-items:center;justify-content:flex-end;align-self:stretch;border-style:none;background-color:transparent;color:var(--calcite-color-text-3);outline:2px solid transparent;outline-offset:2px;-webkit-appearance:none;border-start-end-radius:var(--calcite-alert-corner-radius, var(--calcite-border-radius));border-end-end-radius:var(--calcite-alert-corner-radius, var(--calcite-border-radius));outline-color:transparent}.close:focus{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))))}.close:hover,.close:focus{color:var(--calcite-color-text-1);background-color:var(--calcite-color-foreground-2)}.close:active{background-color:var(--calcite-color-foreground-3)}.queue-count{visibility:hidden;display:flex;min-inline-size:min-content;cursor:default;align-items:center;justify-content:space-around;align-self:stretch;overflow:hidden;text-align:center;font-weight:var(--calcite-font-weight-medium);color:var(--calcite-color-text-2);opacity:0;transition-property:background-color,block-size,border-color,box-shadow,color,inset-block-end,inset-block-start,inset-inline-end,inset-inline-start,inset-size,opacity,outline-color,transform;transition-duration:var(--calcite-animation-timing);transition-timing-function:ease-in-out;border-inline:0 solid transparent;border-start-end-radius:0}.queue-count--active{visibility:visible;opacity:1}.dismiss-progress{position:absolute;display:block;inline-size:100%;overflow:hidden;inset-inline:0;inset-block-start:-2px;block-size:2px;border-radius:var(--calcite-border-radius) var(--calcite-border-radius) 0 0}.dismiss-progress:after{position:absolute;inset-block-start:0px;display:block;block-size:2px;content:"";background-color:var(--calcite-color-transparent-tint);inset-inline-end:0}.actions-end{display:flex;align-self:stretch}.text-container{box-sizing:border-box;display:flex;min-inline-size:0px;flex:1 1 auto;flex-direction:column;overflow-wrap:break-word}.footer{position:relative;display:flex;inline-size:auto;justify-content:flex-end;align-self:stretch;padding-block-start:1px;block-size:inherit}:host([scale=s]) slot[name=title]::slotted(*),:host([scale=s]) *::slotted([slot=title]){font-size:var(--calcite-font-size--1);line-height:1.375}:host([scale=s]) slot[name=message]::slotted(*),:host([scale=s]) *::slotted([slot=message]){font-size:var(--calcite-font-size--2);line-height:1.375}:host([scale=s]) slot[name=link]::slotted(*),:host([scale=s]) *::slotted([slot=link]){font-size:var(--calcite-font-size--2);line-height:1.375}:host([scale=s]) .queue-count{margin-inline:.5rem}:host([scale=s]) .container{--calcite-internal-alert-min-height: 3.5rem;inline-size:var(--calcite-alert-width, 40em)}:host([scale=s]) .close{padding:.75rem}:host([scale=s]) .icon{padding-inline-start:.75rem}:host([scale=s]) .text-container{padding-block:.5rem;padding-inline:.75rem .5rem}:host([scale=m]) slot[name=title]::slotted(*),:host([scale=m]) *::slotted([slot=title]){font-size:var(--calcite-font-size-0);line-height:1.375}:host([scale=m]) slot[name=message]::slotted(*),:host([scale=m]) *::slotted([slot=message]){font-size:var(--calcite-font-size--1);line-height:1.375}:host([scale=m]) slot[name=link]::slotted(*),:host([scale=m]) *::slotted([slot=link]){font-size:var(--calcite-font-size--1);line-height:1.375}:host([scale=m]) .queue-count{margin-inline:.75rem}:host([scale=m]) .container{--calcite-internal-alert-min-height: 4.1875rem;inline-size:var(--calcite-alert-width, 50em)}:host([scale=m]) .close{padding:1rem}:host([scale=m]) .icon{padding-inline-start:1rem}:host([scale=m]) .text-container{padding-block:.75rem;padding-inline:1rem .75rem}:host([scale=l]) slot[name=title]::slotted(*),:host([scale=l]) *::slotted([slot=title]){margin-block-end:.25rem;font-size:var(--calcite-font-size-1);line-height:1.375}:host([scale=l]) slot[name=message]::slotted(*),:host([scale=l]) *::slotted([slot=message]){font-size:var(--calcite-font-size-0);line-height:1.375}:host([scale=l]) slot[name=link]::slotted(*),:host([scale=l]) *::slotted([slot=link]){font-size:var(--calcite-font-size-0);line-height:1.375}:host([scale=l]) .queue-count{margin-inline:1rem}:host([scale=l]) .container{--calcite-internal-alert-min-height: 5.625rem;inline-size:var(--calcite-alert-width, 60em)}:host([scale=l]) .close{padding:1.25rem}:host([scale=l]) .icon{padding-inline-start:1.25rem}:host([scale=l]) .text-container{padding-block:1rem;padding-inline:1.25rem 1rem}:host([open]) .container--active{border-block-start-width:2px;opacity:1;pointer-events:initial}:host([open]) .container--active[class*=bottom]{transform:translate3d(0,calc(-1 * var(--calcite-alert-offset-size, 2rem)),inherit)}:host([open]) .container--active[class*=top]{transform:translate3d(0,var(--calcite-alert-offset-size, 2rem),inherit)}:host([auto-close])>.queue-count{border-inline-end:0 solid transparent}slot[name=title]::slotted(*),*::slotted([slot=title]){font-size:var(--calcite-font-size-0);line-height:1.375;font-weight:var(--calcite-font-weight-medium);color:var(--calcite-color-text-1)}slot[name=message]::slotted(*),*::slotted([slot=message]){margin:0;display:inline;font-size:var(--calcite-font-size--1);line-height:1.375;font-weight:var(--calcite-font-weight-normal);margin-inline-end:.5rem;color:var(--calcite-color-text-2)}slot[name=link]::slotted(*),*::slotted([slot=link]){display:inline-flex;color:var(--calcite-color-text-link)}:host([kind=brand]) .container{border-block-start-color:var(--calcite-color-brand)}:host([kind=brand]) .container .icon{color:var(--calcite-color-brand)}:host([kind=info]) .container{border-block-start-color:var(--calcite-color-status-info)}:host([kind=info]) .container .icon{color:var(--calcite-color-status-info)}:host([kind=danger]) .container{border-block-start-color:var(--calcite-color-status-danger)}:host([kind=danger]) .container .icon{color:var(--calcite-color-status-danger)}:host([kind=success]) .container{border-block-start-color:var(--calcite-color-status-success)}:host([kind=success]) .container .icon{color:var(--calcite-color-status-success)}:host([kind=warning]) .container{border-block-start-color:var(--calcite-color-status-warning)}:host([kind=warning]) .container .icon{color:var(--calcite-color-status-warning)}:host([auto-close-duration=fast]) .dismiss-progress:after{animation:dismissProgress 6s ease-out}:host(:hover[auto-close-duration=fast]) .dismiss-progress:after,:host(:focus[auto-close-duration=fast]) .dismiss-progress:after{animation-play-state:paused}:host([auto-close-duration=medium]) .dismiss-progress:after{animation:dismissProgress 10s ease-out}:host(:hover[auto-close-duration=medium]) .dismiss-progress:after,:host(:focus[auto-close-duration=medium]) .dismiss-progress:after{animation-play-state:paused}:host([auto-close-duration=slow]) .dismiss-progress:after{animation:dismissProgress 14s ease-out}:host(:hover[auto-close-duration=slow]) .dismiss-progress:after,:host(:focus[auto-close-duration=slow]) .dismiss-progress:after{animation-play-state:paused}.container.focused .dismiss-progress:after{animation-play-state:paused}@keyframes dismissProgress{0%{inline-size:0px;opacity:.75}to{inline-size:100%;opacity:1}}.container--embedded{position:absolute}:host([hidden]){display:none}[hidden]{display:none}:host([calcite-hydrated-hidden]){visibility:hidden!important;pointer-events:none}`; const manager = new AlertManager(); class Alert extends LitElement { constructor() { super(...arguments); this.autoCloseTimeoutId = null; this.transitionProp = "opacity"; this.totalHoverTime = 0; this.totalOpenTime = 0; this.messages = useT9n(); this.hasEndActions = false; this.isFocused = false; this.numberStringFormatter = new NumberStringFormat(); this.active = false; this.autoClose = false; this.autoCloseDuration = "medium"; this.embedded = false; this.iconFlipRtl = false; this.kind = "brand"; this.open = false; this.openAlertCount = 0; this.placement = "bottom"; this.queue = "last"; this.scale = "m"; this.calciteAlertBeforeClose = createEvent({ cancelable: false }); this.calciteAlertBeforeOpen = createEvent({ cancelable: false }); this.calciteAlertClose = createEvent({ cancelable: false }); this.calciteAlertOpen = createEvent({ cancelable: false }); } static { this.properties = { hasEndActions: [16, {}, { state: true }], isFocused: [16, {}, { state: true }], numberStringFormatter: [16, {}, { state: true }], active: [5, {}, { type: Boolean }], autoClose: [7, {}, { reflect: true, type: Boolean }], autoCloseDuration: [3, {}, { reflect: true }], embedded: [5, {}, { type: Boolean }], icon: [3, { converter: stringOrBoolean }, { reflect: true }], iconFlipRtl: [7, {}, { reflect: true, type: Boolean }], kind: [3, {}, { reflect: true }], label: 1, messageOverrides: [0, {}, { attribute: false }], numberingSystem: [3, {}, { reflect: true }], open: [7, {}, { reflect: true, type: Boolean }], openAlertCount: [9, {}, { type: Number }], placement: [3, {}, { reflect: true }], queue: [3, {}, { reflect: true }], scale: [3, {}, { reflect: true }] }; } static { this.styles = styles; } async setFocus() { await componentFocusable(this); focusFirstTabbable(this.el); } connectedCallback() { super.connectedCallback(); const open = this.open; if (open) { manager.registerElement(this.el); } this.numberStringFormatter.numberFormatOptions = { locale: this.messages._lang, numberingSystem: this.numberingSystem, signDisplay: "always" }; } willUpdate(changes) { if (changes.has("open") && (this.hasUpdated || this.open !== false)) { this.openHandler(); } if (changes.has("active") && (this.hasUpdated || this.active !== false)) { this.handleActiveChange(); } if (changes.has("autoCloseDuration") && (this.hasUpdated || this.autoCloseDuration !== "medium")) { this.updateDuration(); } if (changes.has("queue") && (this.hasUpdated || this.queue !== "last")) { this.handleQueueChange(); } if (changes.has("numberingSystem")) { this.numberingSystemChange(); } if (changes.has("messages")) { this.effectiveLocaleChange(); } } disconnectedCallback() { super.disconnectedCallback(); manager.unregisterElement(this.el); this.clearAutoCloseTimeout(); this.embedded = false; } handleActiveChange() { onToggleOpenCloseComponent(this); this.clearAutoCloseTimeout(); if (this.active && this.autoClose && !this.autoCloseTimeoutId) { this.initialOpenTime = Date.now(); this.autoCloseTimeoutId = window.setTimeout(() => this.closeAlert(), DURATIONS[this.autoCloseDuration]); } } openHandler() { if (this.open) { manager.registerElement(this.el); } else { manager.unregisterElement(this.el); } } updateDuration() { if (this.autoClose && this.autoCloseTimeoutId) { this.clearAutoCloseTimeout(); this.autoCloseTimeoutId = window.setTimeout(() => this.closeAlert(), DURATIONS[this.autoCloseDuration]); } } handleQueueChange() { if (this.open) { manager.unregisterElement(this.el); manager.registerElement(this.el); } } handleKeyBoardFocus() { this.isFocused = true; this.handleFocus(); } handleKeyBoardBlur() { this.isFocused = false; if (!this.isHovered) { this.handleBlur(); } } effectiveLocaleChange() { this.numberStringFormatter.numberFormatOptions = { locale: this.messages._lang, numberingSystem: this.numberingSystem, signDisplay: "always" }; } numberingSystemChange() { this.numberStringFormatter.numberFormatOptions = { locale: this.messages._lang, numberingSystem: this.numberingSystem, signDisplay: "always" }; } clearAutoCloseTimeout() { window.clearTimeout(this.autoCloseTimeoutId); this.autoCloseTimeoutId = null; } setTransitionEl(el) { if (!el) { return; } this.transitionEl = el; } closeAlert() { this.open = false; this.clearAutoCloseTimeout(); } onBeforeOpen() { this.calciteAlertBeforeOpen.emit(); } onOpen() { this.calciteAlertOpen.emit(); } onBeforeClose() { this.calciteAlertBeforeClose.emit(); } onClose() { this.calciteAlertClose.emit(); } actionsEndSlotChangeHandler(event) { this.hasEndActions = slotChangeHasAssignedElement(event); } handleMouseOver() { this.isHovered = true; this.handleFocus(); } handleMouseLeave() { this.isHovered = false; if (!this.isFocused) { this.handleBlur(); } } handleFocus() { this.clearAutoCloseTimeout(); this.totalOpenTime = Date.now() - this.initialOpenTime; this.lastMouseOverBegin = Date.now(); } handleBlur() { const hoverDuration = Date.now() - this.lastMouseOverBegin; const timeRemaining = DURATIONS[this.autoCloseDuration] - this.totalOpenTime + this.totalHoverTime; this.totalHoverTime = this.totalHoverTime ? hoverDuration + this.totalHoverTime : hoverDuration; this.autoCloseTimeoutId = window.setTimeout(() => this.closeAlert(), timeRemaining); } render() { const { open, autoClose, label, placement, active, openAlertCount } = this; const role = autoClose ? "alert" : "alertdialog"; const hidden = !open; const effectiveIcon = setRequestedIcon(KindIcons, this.icon, this.kind); const hasQueuedAlerts = openAlertCount > 1; this.el.inert = hidden; this.el.ariaLabel = label; this.el.toggleAttribute("calcite-hydrated-hidden", hidden); this.el.role = role; return html`<div class=${safeClassMap({ [CSS.container]: true, [CSS.containerActive]: active, [`${CSS.container}--${placement}`]: true, [CSS.containerEmbedded]: this.embedded, [CSS.focused]: this.isFocused })} @pointerenter=${this.autoClose && this.autoCloseTimeoutId ? this.handleMouseOver : null} @pointerleave=${this.autoClose ? this.handleMouseLeave : null} ${ref(this.setTransitionEl)}>${effectiveIcon && this.renderIcon(effectiveIcon) || ""}<div class=${safeClassMap(CSS.textContainer)} @focusin=${this.autoClose && this.autoCloseTimeoutId ? this.handleKeyBoardFocus : null} @focusout=${this.autoClose ? this.handleKeyBoardBlur : null}><slot name=${SLOTS.title}></slot><slot name=${SLOTS.message}></slot><slot name=${SLOTS.link}></slot></div>${this.renderActionsEnd()}${hasQueuedAlerts ? this.renderQueueCount() : null}${this.renderCloseButton()}${open && active && autoClose ? html`<div class=${safeClassMap(CSS.dismissProgress)}></div>` : null}</div>`; } renderCloseButton() { return keyed("close", html`<button .ariaLabel=${this.messages.close} class=${safeClassMap(CSS.close)} @click=${this.closeAlert} @focusin=${this.autoClose ? this.handleKeyBoardFocus : null} @focusout=${this.autoClose ? this.handleKeyBoardBlur : null} type=button><calcite-icon icon=x .scale=${getIconScale(this.scale)}></calcite-icon></button>`); } renderQueueCount() { const { openAlertCount } = this; const queueNumber = openAlertCount > 2 ? openAlertCount - 1 : 1; const queueText = this.numberStringFormatter.numberFormatter.format(queueNumber); return keyed("queue-count", html`<div class=${safeClassMap({ [CSS.queueCount]: true, [CSS.queueCountActive]: openAlertCount > 1 })}><calcite-chip .label=${queueText} .scale=${this.scale} .value=${queueText}>${queueText}</calcite-chip></div>`); } renderActionsEnd() { return html`<div class=${safeClassMap(CSS.actionsEnd)}><slot name=${SLOTS.actionsEnd} @slotchange=${this.actionsEndSlotChangeHandler}></slot></div>`; } renderIcon(icon) { return html`<div class=${safeClassMap(CSS.icon)}><calcite-icon .flipRtl=${this.iconFlipRtl} .icon=${icon} .scale=${getIconScale(this.scale)}></calcite-icon></div>`; } } customElement("calcite-alert", Alert); export { Alert };