@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
254 lines (253 loc) • 21.4 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, s as slotChangeGetAssignedElements } from "../../chunks/dom.js";
import { u as updateHostInteraction, I as InteractiveContainer } from "../../chunks/interactive.js";
import { c as componentFocusable } from "../../chunks/component.js";
import { c as createObserver } from "../../chunks/observers.js";
import { S as SLOTS$1 } from "../../chunks/resources2.js";
import { H as Heading } from "../../chunks/Heading.js";
import { e as defaultEndMenuPlacement } from "../../chunks/floating-ui.js";
import { u as useT9n } from "../../chunks/useT9n.js";
import { C as CSS, S as SLOTS, I as ICONS, a as IDS } from "../../chunks/resources3.js";
import { css } from "@lit/reactive-element/css-tag.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{position:relative;box-sizing:border-box;display:flex;block-size:100%;inline-size:100%;flex:1 1 auto;overflow:hidden;--calcite-min-header-height: calc(var(--calcite-icon-size) * 3);border-radius:var(--calcite-panel-corner-radius, var(--calcite-corner-radius-sharp))}: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}:host([scale=s]){--calcite-internal-panel-default-space: var(--calcite-spacing-sm);--calcite-internal-panel-header-vertical-padding: var(--calcite-spacing-sm-plus)}:host([scale=s]) .header-content .heading{font-size:var(--calcite-font-size--1)}:host([scale=s]) .header-content .description{font-size:var(--calcite-font-size--2)}:host([scale=m]){--calcite-internal-panel-default-space: var(--calcite-spacing-md);--calcite-internal-panel-header-vertical-padding: var(--calcite-spacing-md-plus)}:host([scale=m]) .header-content .heading{font-size:var(--calcite-font-size-0)}:host([scale=m]) .header-content .description{font-size:var(--calcite-font-size--1)}:host([scale=l]){--calcite-internal-panel-default-space: var(--calcite-spacing-lg);--calcite-internal-panel-header-vertical-padding: var(--calcite-spacing-xl)}:host([scale=l]) .header-content .heading{font-size:var(--calcite-font-size-1)}:host([scale=l]) .header-content .description{font-size:var(--calcite-font-size-0)}.content-top,.content-bottom{display:flex;align-items:flex-start;align-self:stretch;padding:var(--calcite-internal-panel-default-space);border-block-start:1px solid var(--calcite-panel-border-color, var(--calcite-color-border-3));background-color:var(--calcite-panel-background-color, var(--calcite-color-foreground-1))}.container{position:relative;margin:0;display:flex;inline-size:100%;flex:1 1 auto;flex-direction:column;align-items:stretch;overflow:hidden;background-color:var(--calcite-color-background);padding:0;color:var(--calcite-color-text-2);transition:max-block-size var(--calcite-animation-timing),inline-size var(--calcite-animation-timing);box-sizing:border-box;font-size:var(--calcite-font-size--1)}.container *{box-sizing:border-box}.container[hidden]{display:none}.header{z-index:var(--calcite-z-index-header);display:flex;flex-direction:column;background-color:var(--calcite-panel-header-background-color, var(--calcite-color-foreground-1));border-block-end:1px solid var(--calcite-panel-border-color, var(--calcite-panel-header-border-block-end, var(--calcite-color-border-3)))}.header-container{display:flex;inline-size:100%;flex-direction:row;align-items:stretch;justify-content:flex-start;flex:0 0 auto}.header-container--border-end{border-block-end:1px solid var(--calcite-panel-border-color, var(--calcite-color-border-3))}.action-bar-container{inline-size:100%}.action-bar-container ::slotted(calcite-action-bar){inline-size:100%}.header-content{display:flex;flex-direction:column;overflow:hidden;padding-inline:.75rem;padding-block:.875rem;margin-inline-end:auto;justify-content:center}.header-content .heading,.header-content .description{display:block;flex:none;overflow-wrap:break-word;padding:0;line-height:var(--calcite-font-line-height-relative-snug)}.header-content .heading{font-weight:var(--calcite-font-weight-medium);color:var(--calcite-panel-heading-text-color, var(--calcite-color-text-1))}.header-content .heading:only-child{margin-block-end:0px}.header-content .description{color:var(--calcite-panel-description-text-color, var(--calcite-color-text-2))}#close,#collapse,.back-button,calcite-action-menu{--calcite-action-background-color: var(--calcite-panel-header-action-background-color);--calcite-action-background-color-hover: var(--calcite-panel-header-action-background-color-hover);--calcite-action-background-color-press: var(--calcite-panel-header-action-background-color-press);--calcite-action-text-color: var(--calcite-panel-header-action-text-color);--calcite-action-text-color-press: var(--calcite-panel-header-action-text-color-press)}.back-button{border-width:0px;border-style:solid;border-inline-end-width:1px;border-color:var(--calcite-panel-border-color, var(--calcite-color-border-3))}.header-actions{display:flex;flex-direction:row;flex-wrap:nowrap;align-items:stretch}.header-actions--end{margin-inline-start:auto}.content-wrapper{position:relative;display:flex;block-size:100%;flex:1 1 auto;flex-direction:column;flex-wrap:nowrap;align-items:stretch;overflow:auto;color:var(--calcite-color-text-2);outline-color:transparent;padding:var(--calcite-panel-space, var(--calcite-panel-content-space, 0));background:var(--calcite-panel-background-color, var(--calcite-color-background))}.content-wrapper:focus-visible{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))))}.header-content{flex:1 1 auto;justify-content:center;padding-block:var(--calcite-internal-panel-header-vertical-padding);padding-inline:var(--calcite-internal-panel-default-space)}.header-content.header-slotted-content{padding:var(--calcite-panel-header-content-space, var(--calcite-internal-panel-header-vertical-padding) var(--calcite-internal-panel-default-space))}.footer{margin-block-start:auto;display:flex;flex-direction:row;align-content:space-between;align-items:center;justify-content:center;font-size:var(--calcite-font-size--2);line-height:1.375;border-block-start:1px solid var(--calcite-panel-border-color, var(--calcite-color-border-3));padding:var(--calcite-panel-footer-space, var(--calcite-panel-footer-padding, var(--calcite-internal-panel-default-space)));background-color:var(--calcite-panel-footer-background-color, var(--calcite-color-foreground-1))}.footer-content{display:flex;flex:1 1 0%;flex-direction:row;align-items:center;justify-content:center}.footer-actions{display:flex;flex:1 1 0%;flex-direction:row;align-items:center;justify-content:space-evenly;gap:var(--calcite-internal-panel-default-space)}.footer-start{display:flex;flex:1 1 0%;flex-direction:row;align-items:center;justify-content:flex-start;margin-inline-end:auto;gap:var(--calcite-internal-panel-default-space)}.footer-end{display:flex;flex:1 1 0%;flex-direction:row;align-items:center;justify-content:flex-end;margin-inline-start:auto;gap:var(--calcite-internal-panel-default-space)}.fab-container{position:sticky;inset-block-end:0px;z-index:var(--calcite-z-index-sticky);margin-block:0px;margin-inline:auto;display:block;padding:.5rem;inset-inline:0;inline-size:fit-content}:host([hidden]){display:none}[hidden]{display:none}`;
class Panel extends LitElement {
constructor() {
super();
this.resizeObserver = createObserver("resize", () => this.resizeHandler());
this.messages = useT9n();
this.hasActionBar = false;
this.hasContentBottom = false;
this.hasContentTop = false;
this.hasEndActions = false;
this.hasFab = false;
this.hasFooterActions = false;
this.hasFooterContent = false;
this.hasFooterEndContent = false;
this.hasFooterStartContent = false;
this.hasHeaderContent = false;
this.hasMenuItems = false;
this.hasStartActions = false;
this.isClosed = false;
this.showHeaderContent = false;
this.closable = false;
this.closed = false;
this.collapseDirection = "down";
this.collapsed = false;
this.collapsible = false;
this.disabled = false;
this.loading = false;
this.menuOpen = false;
this.menuPlacement = defaultEndMenuPlacement;
this.overlayPositioning = "absolute";
this.scale = "m";
this.calcitePanelClose = createEvent({ cancelable: false });
this.calcitePanelScroll = createEvent({ cancelable: false });
this.calcitePanelToggle = createEvent({ cancelable: false });
this.listen("keydown", this.panelKeyDownHandler);
}
static {
this.properties = { hasActionBar: [16, {}, { state: true }], hasContentBottom: [16, {}, { state: true }], hasContentTop: [16, {}, { state: true }], hasEndActions: [16, {}, { state: true }], hasFab: [16, {}, { state: true }], hasFooterActions: [16, {}, { state: true }], hasFooterContent: [16, {}, { state: true }], hasFooterEndContent: [16, {}, { state: true }], hasFooterStartContent: [16, {}, { state: true }], hasHeaderContent: [16, {}, { state: true }], hasMenuItems: [16, {}, { state: true }], hasStartActions: [16, {}, { state: true }], isClosed: [16, {}, { state: true }], showHeaderContent: [16, {}, { state: true }], beforeClose: [0, {}, { attribute: false }], closable: [7, {}, { reflect: true, type: Boolean }], closed: [7, {}, { reflect: true, type: Boolean }], collapseDirection: 1, collapsed: [7, {}, { reflect: true, type: Boolean }], collapsible: [7, {}, { reflect: true, type: Boolean }], description: 1, disabled: [7, {}, { reflect: true, type: Boolean }], heading: 1, headingLevel: [11, {}, { type: Number, reflect: true }], loading: [7, {}, { reflect: true, type: Boolean }], menuFlipPlacements: [0, {}, { attribute: false }], menuOpen: [7, {}, { reflect: true, type: Boolean }], menuPlacement: [3, {}, { reflect: true }], messageOverrides: [0, {}, { attribute: false }], overlayPositioning: [3, {}, { reflect: true }], scale: [3, {}, { reflect: true }] };
}
static {
this.styles = styles;
}
async scrollContentTo(options) {
this.panelScrollEl?.scrollTo(options);
}
async setFocus() {
await componentFocusable(this);
focusFirstTabbable(this.containerEl);
}
async load() {
this.isClosed = this.closed;
}
willUpdate(changes) {
if (changes.has("closed") && this.hasUpdated) {
if (this.closed) {
this.close();
} else {
this.open();
}
}
}
updated() {
updateHostInteraction(this);
}
disconnectedCallback() {
super.disconnectedCallback();
this.resizeObserver?.disconnect();
}
resizeHandler() {
const { panelScrollEl } = this;
if (!panelScrollEl || typeof panelScrollEl.scrollHeight !== "number" || typeof panelScrollEl.offsetHeight !== "number") {
return;
}
const hasScrollingContent = panelScrollEl.scrollHeight > panelScrollEl.offsetHeight;
if (hasScrollingContent) {
panelScrollEl.setAttribute("tabindex", "0");
} else {
panelScrollEl.removeAttribute("tabindex");
}
}
setContainerRef(node) {
this.containerEl = node;
}
panelKeyDownHandler(event) {
if (this.closable && event.key === "Escape" && !event.defaultPrevented) {
this.handleUserClose();
event.preventDefault();
}
}
handleUserClose() {
this.closed = true;
this.calcitePanelClose.emit();
}
open() {
this.isClosed = false;
}
async close() {
const beforeClose = this.beforeClose ?? (() => Promise.resolve());
try {
await beforeClose();
} catch {
requestAnimationFrame(() => {
this.closed = false;
});
return;
}
this.isClosed = true;
}
collapse() {
this.collapsed = !this.collapsed;
this.calcitePanelToggle.emit();
}
panelScrollHandler() {
this.calcitePanelScroll.emit();
}
handleHeaderActionsStartSlotChange(event) {
this.hasStartActions = slotChangeHasAssignedElement(event);
}
handleHeaderActionsEndSlotChange(event) {
this.hasEndActions = slotChangeHasAssignedElement(event);
}
handleHeaderMenuActionsSlotChange(event) {
this.hasMenuItems = slotChangeHasAssignedElement(event);
}
handleActionBarSlotChange(event) {
const actionBars = slotChangeGetAssignedElements(event).filter((el) => el?.matches("calcite-action-bar"));
actionBars.forEach((actionBar) => actionBar.layout = "horizontal");
this.hasActionBar = !!actionBars.length;
}
handleHeaderContentSlotChange(event) {
this.hasHeaderContent = slotChangeHasAssignedElement(event);
}
handleFabSlotChange(event) {
this.hasFab = slotChangeHasAssignedElement(event);
}
handleFooterActionsSlotChange(event) {
this.hasFooterActions = slotChangeHasAssignedElement(event);
}
handleFooterEndSlotChange(event) {
this.hasFooterEndContent = slotChangeHasAssignedElement(event);
}
handleFooterStartSlotChange(event) {
this.hasFooterStartContent = slotChangeHasAssignedElement(event);
}
handleFooterSlotChange(event) {
this.hasFooterContent = slotChangeHasAssignedElement(event);
}
contentBottomSlotChangeHandler(event) {
this.hasContentBottom = slotChangeHasAssignedElement(event);
}
contentTopSlotChangeHandler(event) {
this.hasContentTop = slotChangeHasAssignedElement(event);
}
setPanelScrollEl(el) {
this.panelScrollEl = el;
this.resizeObserver?.disconnect();
if (el) {
this.resizeObserver?.observe(el);
this.resizeHandler();
}
}
handleAlertsSlotChange(event) {
slotChangeGetAssignedElements(event)?.map((el) => {
if (el.nodeName === "CALCITE-ALERT") {
el.embedded = true;
}
});
}
renderHeaderContent() {
const { heading, headingLevel, description, hasHeaderContent } = this;
const headingNode = heading ? Heading({ class: CSS.heading, level: headingLevel, children: heading }) : null;
const descriptionNode = description ? html`<span class=${safeClassMap(CSS.description)}>${description}</span>` : null;
return !hasHeaderContent && (headingNode || descriptionNode) ? keyed("header-content", html`<div class=${safeClassMap(CSS.headerContent)}>${headingNode}${descriptionNode}</div>`) : null;
}
renderActionBar() {
return html`<div class=${safeClassMap(CSS.actionBarContainer)} .hidden=${!this.hasActionBar}><slot name=${SLOTS.actionBar} @slotchange=${this.handleActionBarSlotChange}></slot></div>`;
}
renderHeaderSlottedContent() {
return keyed("slotted-header-content", html`<div class=${safeClassMap({ [CSS.headerContent]: true, [CSS.headerSlottedContent]: true })} .hidden=${!this.hasHeaderContent}><slot name=${SLOTS.headerContent} @slotchange=${this.handleHeaderContentSlotChange}></slot></div>`);
}
renderHeaderStartActions() {
const { hasStartActions } = this;
return keyed("header-actions-start", html`<div class=${safeClassMap({ [CSS.headerActionsStart]: true, [CSS.headerActions]: true })} .hidden=${!hasStartActions}><slot name=${SLOTS.headerActionsStart} @slotchange=${this.handleHeaderActionsStartSlotChange}></slot></div>`);
}
renderHeaderActionsEnd() {
const { hasEndActions, messages, closable, collapsed, collapseDirection, collapsible, hasMenuItems } = this;
const { collapse, expand, close } = messages;
const icons = [ICONS.expand, ICONS.collapse];
if (collapseDirection === "up") {
icons.reverse();
}
const collapseNode = collapsible ? html`<calcite-action .ariaExpanded=${!collapsed} .ariaLabel=${collapse} .icon=${collapsed ? icons[0] : icons[1]} id=${IDS.collapse} @click=${this.collapse} .scale=${this.scale} .text=${collapse} title=${(collapsed ? expand : collapse) ?? nothing}></calcite-action>` : null;
const closeNode = closable ? html`<calcite-action .ariaLabel=${close} .icon=${ICONS.close} id=${IDS.close} @click=${this.handleUserClose} .scale=${this.scale} .text=${close} title=${close ?? nothing}></calcite-action>` : null;
const slotNode = html`<slot name=${SLOTS.headerActionsEnd} @slotchange=${this.handleHeaderActionsEndSlotChange}></slot>`;
const showContainer = hasEndActions || collapseNode || closeNode || hasMenuItems;
return keyed("header-actions-end", html`<div class=${safeClassMap({ [CSS.headerActionsEnd]: true, [CSS.headerActions]: true })} .hidden=${!showContainer}>${slotNode}${this.renderMenu()}${collapseNode}${closeNode}</div>`);
}
renderMenu() {
const { hasMenuItems, messages, menuOpen, menuFlipPlacements, menuPlacement } = this;
return keyed("menu", html`<calcite-action-menu .flipPlacements=${menuFlipPlacements ?? ["top", "bottom"]} .hidden=${!hasMenuItems} .label=${messages.options} .open=${menuOpen} .overlayPositioning=${this.overlayPositioning} .placement=${menuPlacement}><calcite-action class=${safeClassMap(CSS.menuAction)} .icon=${ICONS.menu} .scale=${this.scale} slot=${SLOTS$1.trigger} .text=${messages.options}></calcite-action><slot name=${SLOTS.headerMenuActions} @slotchange=${this.handleHeaderMenuActionsSlotChange}></slot></calcite-action-menu>`);
}
renderHeaderNode() {
const { hasHeaderContent, hasStartActions, hasEndActions, closable, collapsible, hasMenuItems, hasActionBar } = this;
const headerContentNode = this.renderHeaderContent();
const showHeaderContent = hasHeaderContent || !!headerContentNode || hasStartActions || hasEndActions || collapsible || closable || hasMenuItems;
this.showHeaderContent = showHeaderContent;
return html`<header class=${safeClassMap(CSS.header)} .hidden=${!(showHeaderContent || hasActionBar)}><div class=${safeClassMap({ [CSS.headerContainer]: true, [CSS.headerContainerBorderEnd]: hasActionBar })} .hidden=${!showHeaderContent}>${this.renderHeaderStartActions()}${this.renderHeaderSlottedContent()}${headerContentNode}${this.renderHeaderActionsEnd()}</div>${this.renderActionBar()}${this.renderContentTop()}</header>`;
}
renderFooterNode() {
const { hasFooterEndContent, hasFooterStartContent, hasFooterContent, hasFooterActions } = this;
const showFooter = hasFooterStartContent || hasFooterEndContent || hasFooterContent || hasFooterActions;
return html`<footer class=${safeClassMap(CSS.footer)} .hidden=${!showFooter}><div class=${safeClassMap(CSS.footerContent)} .hidden=${!hasFooterContent}><slot name=${SLOTS.footer} @slotchange=${this.handleFooterSlotChange}></slot></div><div class=${safeClassMap(CSS.footerStart)} .hidden=${hasFooterContent || !hasFooterStartContent}><slot name=${SLOTS.footerStart} @slotchange=${this.handleFooterStartSlotChange}></slot></div><div class=${safeClassMap(CSS.footerEnd)} .hidden=${hasFooterContent || !hasFooterEndContent}><slot name=${SLOTS.footerEnd} @slotchange=${this.handleFooterEndSlotChange}></slot></div><div class=${safeClassMap(CSS.footerActions)} .hidden=${hasFooterContent || !hasFooterActions}>${keyed("footer-actions-slot", html`<slot name=${SLOTS.footerActions} @slotchange=${this.handleFooterActionsSlotChange}></slot>`)}</div></footer>`;
}
renderContent() {
return html`<div class=${safeClassMap(CSS.contentWrapper)} .hidden=${this.collapsible && this.collapsed} @scroll=${this.panelScrollHandler} ${ref(this.setPanelScrollEl)}><slot></slot>${this.renderFab()}</div>`;
}
renderContentBottom() {
return html`<div class=${safeClassMap(CSS.contentBottom)} .hidden=${!this.hasContentBottom}><slot name=${SLOTS.contentBottom} @slotchange=${this.contentBottomSlotChangeHandler}></slot></div>`;
}
renderContentTop() {
return html`<div class=${safeClassMap(CSS.contentTop)} .hidden=${!this.hasContentTop}><slot name=${SLOTS.contentTop} @slotchange=${this.contentTopSlotChangeHandler}></slot></div>`;
}
renderFab() {
return html`<div class=${safeClassMap(CSS.fabContainer)} .hidden=${!this.hasFab}><slot name=${SLOTS.fab} @slotchange=${this.handleFabSlotChange}></slot></div>`;
}
render() {
const { disabled, loading, isClosed } = this;
const panelNode = html`<article .ariaBusy=${loading} class=${safeClassMap(CSS.container)} .hidden=${isClosed} ${ref(this.setContainerRef)}>${this.renderHeaderNode()}${this.renderContent()}${this.renderContentBottom()}${this.renderFooterNode()}${keyed("alerts", html`<slot name=${SLOTS.alerts} @slotchange=${this.handleAlertsSlotChange}></slot>`)}</article>`;
return InteractiveContainer({ disabled, children: html`${loading ? html`<calcite-scrim .loading=${loading}></calcite-scrim>` : null}${panelNode}` });
}
}
customElement("calcite-panel", Panel);
export {
Panel
};