UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

240 lines (239 loc) • 17.7 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 { 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 };