@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
240 lines (239 loc) • 17.7 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 { keyed } from "lit-html/directives/keyed.js";
import { ref } from "lit-html/directives/ref.js";
import { html, nothing } from "lit";
import { LitElement, createEvent, safeClassMap } from "@arcgis/lumina";
import { h as focusFirstTabbable, a as slotChangeHasAssignedElement } from "../../chunks/dom.js";
import { u as updateHostInteraction, I as InteractiveContainer } from "../../chunks/interactive.js";
import { H as Heading } from "../../chunks/Heading.js";
import { c as componentFocusable } from "../../chunks/component.js";
import { o as onToggleOpenCloseComponent } from "../../chunks/openCloseComponent.js";
import { e as defaultEndMenuPlacement } from "../../chunks/floating-ui.js";
import { u as useT9n } from "../../chunks/useT9n.js";
import { l as logger } from "../../chunks/logger.js";
import { s as styles$1 } from "../../chunks/_sortable.js";
import { css } from "@lit/reactive-element/css-tag.js";
const IDS = {
content: "content",
toggle: "toggle",
header: "header"
};
const CSS = {
actionsEnd: "actions-end",
container: "container",
content: "content",
contentStart: "content-start",
controlContainer: "control-container",
description: "description",
header: "header",
headerContainer: "header-container",
headerHasText: "header--has-text",
heading: "heading",
icon: "icon",
iconStart: "icon--start",
iconEnd: "icon--end",
iconEndContainer: "icon-end-container",
invalid: "invalid",
statusIcon: "status-icon",
title: "title",
toggle: "toggle",
toggleIcon: "toggle-icon",
valid: "valid"
};
const SLOTS = {
actionsEnd: "actions-end",
contentStart: "content-start",
control: "control",
headerMenuActions: "header-menu-actions",
icon: "icon"
};
const ICONS = {
expanded: "chevron-up",
collapsed: "chevron-down",
valid: "check-circle",
invalid: "exclamation-mark-triangle"
};
const styles = css`:host{box-sizing:border-box;background-color:var(--calcite-color-foreground-1);color:var(--calcite-color-text-2);font-size:var(--calcite-font-size--1)}:host *{box-sizing:border-box}:host{--calcite-icon-size: 1rem;--calcite-spacing-eighth: .125rem;--calcite-spacing-quarter: .25rem;--calcite-spacing-half: .5rem;--calcite-spacing-three-quarters: .75rem;--calcite-spacing: 1rem;--calcite-spacing-plus-quarter: 1.25rem;--calcite-spacing-plus-half: 1.5rem;--calcite-spacing-double: 2rem;--calcite-menu-min-width: 10rem;--calcite-header-min-height: 3rem;--calcite-footer-min-height: 3rem}: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:flex;flex-shrink:0;flex-grow:0;flex-direction:column;border-width:0px;border-block-end-width:1px;border-style:solid;padding:0;transition-property:margin;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transition-timing-function:cubic-bezier(.215,.44,.42,.88);flex-basis:auto;transition-duration:var(--calcite-animation-timing);border-color:var(--calcite-block-border-color, var(--calcite-color-border-3))}:host([disabled]) ::slotted([calcite-hydrated][disabled]),:host([disabled]) [calcite-hydrated][disabled]{opacity:1}.interaction-container{display:contents}.header{margin:0;display:flex;align-content:space-between;align-items:center;fill:var(--calcite-color-text-2);color:var(--calcite-color-text-2)}.heading{margin:0;padding:0;font-weight:var(--calcite-font-weight-medium)}.header .heading{flex:1 1 auto;padding:.5rem}.header{justify-content:flex-start}.header--has-text{padding:var(--calcite-spacing-md)}.header,.toggle{grid-area:header}.header-container{display:grid;align-items:stretch;grid-template:auto/auto 1fr auto auto;grid-template-areas:"handle header control menu actions-end";grid-column:header-start/actions-end;grid-row:1/2}.content-start,.icon,.icon--start,.icon--end{margin-inline-end:var(--calcite-spacing-md)}.icon calcite-loader{margin-inline-end:var(--calcite-spacing-xxxs)}.icon--start,.icon--end{color:var(--calcite-block-icon-color, var(--calcite-color-text-3))}.actions-end{grid-area:actions-end}.toggle{margin:0;display:flex;cursor:pointer;flex-wrap:nowrap;align-items:center;justify-content:space-between;border-style:none;padding:0;font-family:var(--calcite-font-family);outline-color:transparent;text-align:initial;background-color:var(--calcite-block-header-background-color, transparent)}.toggle:hover{background-color:var(--calcite-block-header-background-color-hover, var(--calcite-color-foreground-2))}.toggle: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))))}.toggle:active{background-color:var(--calcite-block-header-background-color-press, var(--calcite-color-foreground-3))}calcite-loader[inline]{grid-area:control;align-self:center}calcite-handle{grid-area:handle}.title{margin:0}.header .title .heading{padding:0;font-size:var(--calcite-font-size--1);font-weight:var(--calcite-font-weight-medium);line-height:1.25;transition-property:color;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);word-wrap:break-word;word-break:break-word;color:var(--calcite-block-heading-text-color, var(--calcite-color-text-2))}.description{margin-block-start:.125rem;padding:0;font-size:var(--calcite-font-size--2);line-height:1.375;word-wrap:break-word;word-break:break-word;color:var(--calcite-block-description-text-color, var(--calcite-color-text-3))}.icon{display:flex}.status-icon.valid{color:var(--calcite-color-status-success)}.status-icon.invalid{color:var(--calcite-color-status-danger)}@keyframes spin{0%{transform:rotate(0)}50%{transform:rotate(180deg)}to{transform:rotate(360deg)}}.icon-end-container{display:flex;align-items:center;margin-inline-start:auto}.toggle-icon{align-self:center;justify-self:end;transition-property:color;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1);margin-inline-end:var(--calcite-spacing-md);color:var(--calcite-block-icon-color, var(--calcite-color-text-3))}.toggle:hover .toggle-icon{color:var(--calcite-block-icon-color-hover, var(--calcite-color-text-1))}.container{position:relative;display:flex;block-size:100%;flex-direction:column}.content{position:relative;min-block-size:0px;flex:1 1 0%}@keyframes in{0%{opacity:0}to{opacity:1}}.content{animation:in var(--calcite-internal-animation-timing-slow) ease-in-out;padding-block:var(--calcite-block-padding, var(--calcite-spacing-sm));padding-inline:var(--calcite-block-padding, var(--calcite-spacing-md))}.content-start{display:flex;align-items:center;color:var(--calcite-block-text-color, var(--calcite-color-text-3))}.control-container{margin:0;display:flex;grid-area:control}calcite-action-menu{grid-area:menu}.actions-end{display:flex;align-items:stretch}:host([expanded]){margin-block:.5rem}:host([expanded]) .header .title .heading{color:var(--calcite-block-heading-text-color-press, var(--calcite-color-text-1))}:host([expanded]) .description{color:var(--calcite-block-description-text-color, var(--calcite-color-text-2))}:host([expanded]) .icon--start,:host([expanded]) .icon--end{color:var(--calcite-block-icon-color, var(--calcite-color-text-1))}:host([hidden]){display:none}[hidden]{display:none}`;
class Block extends LitElement {
constructor() {
super(...arguments);
this.transitionProp = "margin-top";
this.messages = useT9n();
this.hasContentStart = false;
this.hasControl = false;
this.hasEndActions = false;
this.hasIcon = false;
this.hasMenuActions = false;
this.collapsible = false;
this.disabled = false;
this.dragDisabled = false;
this.dragHandle = false;
this.expanded = false;
this.loading = false;
this.menuPlacement = defaultEndMenuPlacement;
this.moveToItems = [];
this.overlayPositioning = "absolute";
this.setPosition = null;
this.setSize = null;
this.sortHandleOpen = false;
this.calciteBlockBeforeClose = createEvent({ cancelable: false });
this.calciteBlockBeforeOpen = createEvent({ cancelable: false });
this.calciteBlockClose = createEvent({ cancelable: false });
this.calciteBlockOpen = createEvent({ cancelable: false });
this.calciteBlockSortHandleBeforeClose = createEvent({ cancelable: false });
this.calciteBlockSortHandleBeforeOpen = createEvent({ cancelable: false });
this.calciteBlockSortHandleClose = createEvent({ cancelable: false });
this.calciteBlockSortHandleOpen = createEvent({ cancelable: false });
this.calciteBlockToggle = createEvent({ cancelable: false });
}
static {
this.properties = { hasContentStart: [16, {}, { state: true }], hasControl: [16, {}, { state: true }], hasEndActions: [16, {}, { state: true }], hasIcon: [16, {}, { state: true }], hasMenuActions: [16, {}, { state: true }], collapsible: [7, {}, { reflect: true, type: Boolean }], description: 1, disabled: [7, {}, { reflect: true, type: Boolean }], dragDisabled: [7, {}, { reflect: true, type: Boolean }], dragHandle: [7, {}, { reflect: true, type: Boolean }], expanded: [7, {}, { reflect: true, type: Boolean }], heading: 1, headingLevel: [11, {}, { type: Number, reflect: true }], iconEnd: [3, {}, { reflect: true }], iconFlipRtl: [3, {}, { reflect: true }], iconStart: [3, {}, { reflect: true }], loading: [7, {}, { reflect: true, type: Boolean }], label: 1, menuFlipPlacements: [0, {}, { attribute: false }], menuPlacement: [3, {}, { reflect: true }], messageOverrides: [0, {}, { attribute: false }], moveToItems: [0, {}, { attribute: false }], open: [7, {}, { reflect: true, type: Boolean }], overlayPositioning: [3, {}, { reflect: true }], setPosition: [9, {}, { type: Number }], setSize: [9, {}, { type: Number }], sortHandleOpen: [7, {}, { reflect: true, type: Boolean }], status: [3, {}, { reflect: true }] };
}
static {
this.styles = [styles, styles$1];
}
get open() {
return this.expanded;
}
set open(value) {
logger.deprecated("property", {
name: "open",
removalVersion: 4,
suggested: "expanded"
});
this.expanded = value;
}
async setFocus() {
await componentFocusable(this);
focusFirstTabbable(this.el);
}
connectedCallback() {
super.connectedCallback();
this.transitionEl = this.el;
}
load() {
if (!this.heading && !this.label) {
logger.warn(`${this.el.tagName} is missing both heading & label. Please provide a heading or label for the component to be accessible.`);
}
}
willUpdate(changes) {
if (changes.has("expanded") && (this.hasUpdated || this.expanded !== false)) {
onToggleOpenCloseComponent(this);
}
if (changes.has("sortHandleOpen") && (this.hasUpdated || this.sortHandleOpen !== false)) {
this.sortHandleOpenHandler();
}
}
updated() {
updateHostInteraction(this);
}
onBeforeOpen() {
this.calciteBlockBeforeOpen.emit();
}
onOpen() {
this.calciteBlockOpen.emit();
}
onBeforeClose() {
this.calciteBlockBeforeClose.emit();
}
onClose() {
this.calciteBlockClose.emit();
}
sortHandleOpenHandler() {
if (!this.sortHandleEl) {
return;
}
this.sortHandleEl.open = this.sortHandleOpen;
}
setSortHandleEl(el) {
this.sortHandleEl = el;
this.sortHandleOpenHandler();
}
handleSortHandleBeforeOpen(event) {
event.stopPropagation();
this.calciteBlockSortHandleBeforeOpen.emit();
}
handleSortHandleBeforeClose(event) {
event.stopPropagation();
this.calciteBlockSortHandleBeforeClose.emit();
}
handleSortHandleClose(event) {
event.stopPropagation();
this.sortHandleOpen = false;
this.calciteBlockSortHandleClose.emit();
}
handleSortHandleOpen(event) {
event.stopPropagation();
this.sortHandleOpen = true;
this.calciteBlockSortHandleOpen.emit();
}
onHeaderClick() {
this.expanded = !this.expanded;
this.calciteBlockToggle.emit();
}
controlSlotChangeHandler(event) {
this.hasControl = slotChangeHasAssignedElement(event);
}
menuActionsSlotChangeHandler(event) {
this.hasMenuActions = slotChangeHasAssignedElement(event);
}
iconSlotChangeHandler(event) {
this.hasIcon = slotChangeHasAssignedElement(event);
}
actionsEndSlotChangeHandler(event) {
this.hasEndActions = slotChangeHasAssignedElement(event);
}
handleContentStartSlotChange(event) {
this.hasContentStart = slotChangeHasAssignedElement(event);
}
renderScrim() {
const { loading } = this;
const defaultSlot = html`<slot></slot>`;
return [loading ? html`<calcite-scrim .loading=${loading}></calcite-scrim>` : null, defaultSlot];
}
renderLoaderStatusIcon() {
const { loading, messages, status } = this;
return loading ? keyed("loader", html`<div class=${safeClassMap(CSS.icon)}><calcite-loader inline .label=${messages.loading}></calcite-loader></div>`) : status ? keyed("status-icon", html`<div class=${safeClassMap(CSS.icon)}><calcite-icon class=${safeClassMap({
[CSS.statusIcon]: true,
[CSS.valid]: status == "valid",
[CSS.invalid]: status == "invalid"
})} .icon=${ICONS[status]} scale=s></calcite-icon></div>`) : keyed("icon-slot", html`<div class=${safeClassMap(CSS.icon)} .hidden=${!this.hasIcon}>${keyed("icon-slot", html`<slot name=${SLOTS.icon} @slotchange=${this.iconSlotChangeHandler}></slot>`)}</div>`);
}
renderActionsEnd() {
return html`<div class=${safeClassMap(CSS.actionsEnd)} .hidden=${!this.hasEndActions}><slot name=${SLOTS.actionsEnd} @slotchange=${this.actionsEndSlotChangeHandler}></slot></div>`;
}
renderContentStart() {
return html`<div class=${safeClassMap(CSS.contentStart)} .hidden=${!this.hasContentStart}><slot name=${SLOTS.contentStart} @slotchange=${this.handleContentStartSlotChange}></slot></div>`;
}
renderTitle() {
const { heading, headingLevel, description } = this;
return heading || description ? html`<div class=${safeClassMap(CSS.title)}>${Heading({ class: CSS.heading, level: headingLevel, children: heading })}${description ? html`<div class=${safeClassMap(CSS.description)}>${description}</div>` : null}</div>` : null;
}
renderIcon(position) {
const { iconFlipRtl } = this;
const flipRtl = iconFlipRtl === "both" || position === "start" ? iconFlipRtl === "start" : iconFlipRtl === "end";
const iconValue = position === "start" ? this.iconStart : this.iconEnd;
const iconClass = position === "start" ? CSS.iconStart : CSS.iconEnd;
if (!iconValue) {
return void 0;
}
return keyed(iconValue, html`<calcite-icon class=${safeClassMap(iconClass)} .flipRtl=${flipRtl} .icon=${iconValue} scale=s></calcite-icon>`);
}
render() {
const { collapsible, loading, expanded, label, heading, messages, description, menuFlipPlacements, menuPlacement, moveToItems, setPosition, setSize, dragDisabled } = this;
const toggleLabel = expanded ? messages.collapse : messages.expand;
const headerContent = html`<header class=${safeClassMap({ [CSS.header]: true, [CSS.headerHasText]: !!(heading || description) })} id=${IDS.header}>${this.renderIcon("start")}${this.renderContentStart()}${this.renderLoaderStatusIcon()}${this.renderTitle()}</header>`;
const collapseIcon = expanded ? ICONS.expanded : ICONS.collapsed;
const headerNode = html`<div class=${safeClassMap(CSS.headerContainer)}>${this.dragHandle ? html`<calcite-sort-handle .disabled=${dragDisabled} .label=${heading || label} .moveToItems=${moveToItems} @calciteSortHandleBeforeClose=${this.handleSortHandleBeforeClose} @calciteSortHandleBeforeOpen=${this.handleSortHandleBeforeOpen} @calciteSortHandleClose=${this.handleSortHandleClose} @calciteSortHandleOpen=${this.handleSortHandleOpen} overlay-positioning=fixed .setPosition=${setPosition} .setSize=${setSize} ${ref(this.setSortHandleEl)}></calcite-sort-handle>` : null}${collapsible ? html`<button aria-controls=${IDS.content} aria-describedby=${IDS.header} .ariaExpanded=${collapsible ? expanded : null} class=${safeClassMap(CSS.toggle)} id=${IDS.toggle} @click=${this.onHeaderClick} title=${toggleLabel ?? nothing}>${headerContent}<div class=${safeClassMap(CSS.iconEndContainer)}>${this.renderIcon("end")}<calcite-icon class=${safeClassMap(CSS.toggleIcon)} .icon=${collapseIcon} scale=s></calcite-icon></div></button>` : this.iconEnd ? html`<div>${headerContent}<div class=${safeClassMap(CSS.iconEndContainer)}>${this.renderIcon("end")}</div></div>` : headerContent}<div aria-labelledby=${IDS.header} class=${safeClassMap(CSS.controlContainer)} .hidden=${!this.hasControl}><slot name=${SLOTS.control} @slotchange=${this.controlSlotChangeHandler}></slot></div><calcite-action-menu .flipPlacements=${menuFlipPlacements ?? ["top", "bottom"]} .hidden=${!this.hasMenuActions} .label=${messages.options} .overlayPositioning=${this.overlayPositioning} .placement=${menuPlacement}><slot name=${SLOTS.headerMenuActions} @slotchange=${this.menuActionsSlotChangeHandler}></slot></calcite-action-menu>${this.renderActionsEnd()}</div>`;
return InteractiveContainer({ disabled: this.disabled, children: html`<article aria-label=${label ?? nothing} .ariaBusy=${loading} class=${safeClassMap({
[CSS.container]: true
})}>${headerNode}<section aria-labelledby=${IDS.toggle} class=${safeClassMap(CSS.content)} .hidden=${!expanded} id=${IDS.content}>${this.renderScrim()}</section></article>` });
}
}
customElement("calcite-block", Block);
export {
Block
};