@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
314 lines (313 loc) • 21.1 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 } 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 ;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
};