@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
255 lines (254 loc) • 22.2 kB
JavaScript
/* COPYRIGHT Esri - https://js.arcgis.com/5.0/LICENSE.txt */
import { c as customElement } from "../../chunks/runtime.js";
import { keyed } from "lit/directives/keyed.js";
import { css, html, nothing } from "lit";
import { LitElement, createEvent, safeClassMap } from "@arcgis/lumina";
import { createRef, ref } from "lit/directives/ref.js";
import { s as slotChangeHasAssignedElement, b as slotChangeGetAssignedElements } from "../../chunks/dom.js";
import { g as getIconScale } from "../../chunks/component.js";
import { c as createObserver, u as updateRefObserver } from "../../chunks/observers.js";
import { S as SLOTS$1 } from "../../chunks/resources2.js";
import { H as Heading } from "../../chunks/Heading.js";
import { b as defaultEndMenuPlacement } from "../../chunks/floating-ui.js";
import { u as useT9n } from "../../chunks/useT9n.js";
import { u as useSetFocus } from "../../chunks/useSetFocus.js";
import { s as styles$1 } from "../../chunks/header.js";
import { u as useInteractive } from "../../chunks/useInteractive.js";
import { C as CSS, S as SLOTS, I as ICONS, a as IDS } from "../../chunks/resources6.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;border-radius:var(--calcite-panel-corner-radius, var(--calcite-corner-radius-sharp))}slot[name=alerts]::slotted(calcite-alert){block-size:0}:host([disabled]) ::slotted([calcite-hydrated][disabled]),:host([disabled]) [calcite-hydrated][disabled]{opacity:1}.interaction-container{display:contents}: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=s]) .header-content .icon{margin-inline-end:var(--calcite-spacing-sm)}: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=m]) .header-content .icon{margin-inline-end:var(--calcite-spacing-md)}:host(:is([scale=s],[scale=m])){--calcite-internal-panel-action-spacing: var(--calcite-spacing-xxs)}:host([scale=l]){--calcite-internal-panel-action-spacing: var(--calcite-spacing-xs);--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)}:host([scale=l]) .header-content .icon{margin-inline-end:var(--calcite-spacing-lg)}.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;font-size:var(--calcite-font-size-relative-base);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}.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-text-content{flex:1 1 auto;overflow:hidden}.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,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)}.header-actions{display:flex;flex-direction:row;flex-wrap:nowrap;margin:auto;gap:var(--calcite-internal-panel-action-spacing)}.header-actions--start{margin-inline-start:var(--calcite-internal-panel-action-spacing)}.header-actions--end{margin-inline-end:var(--calcite-internal-panel-action-spacing)}.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:var(--calcite-border-width-md) solid var(--calcite-color-focus, var(--calcite-ui-focus-color, var(--calcite-color-brand)));outline-offset:calc(calc(-1 * var(--calcite-spacing-base)) * calc(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))}.header-content.header--non-slotted-content{align-items:center;flex-direction:row}.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-relative-sm);line-height:var(--calcite-font-line-height-relative-snug);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-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}calcite-icon{--calcite-icon-color: var(--calcite-panel-icon-color, var(--calcite-ui-icon-color, var(--calcite-color-text-1)))}:host([hidden]){display:none}[hidden]{display:none}`;
class Panel extends LitElement {
constructor() {
super();
this.containerRef = createRef();
this.resizeObserver = createObserver("resize", () => this.resizeHandler());
this.messages = useT9n();
this._closed = false;
this.focusSetter = useSetFocus()(this);
this.interactiveContainer = useInteractive(this);
this.hasActionBar = false;
this.hasContentBottom = false;
this.hasContentTop = false;
this.hasEndActions = false;
this.hasFab = false;
this.hasFooterContent = false;
this.hasFooterEndContent = false;
this.hasFooterStartContent = false;
this.hasHeaderContent = false;
this.hasMenuItems = false;
this.hasStartActions = false;
this.showHeaderContent = false;
this.closable = false;
this.collapseDirection = "down";
this.collapsed = false;
this.collapsible = false;
this.disabled = false;
this.iconFlipRtl = false;
this.loading = false;
this.menuOpen = false;
this.menuPlacement = defaultEndMenuPlacement;
this.overlayPositioning = "absolute";
this.scale = "m";
this.topLayerDisabled = false;
this.calcitePanelClose = createEvent({ cancelable: true });
this.calcitePanelCollapse = createEvent({ cancelable: false });
this.calcitePanelExpand = createEvent({ cancelable: false });
this.calcitePanelScroll = createEvent({ cancelable: false });
this.calcitePanelToggle = createEvent({ cancelable: false });
this.listen("keydown", this.panelKeyDownHandler);
this.listen("calcitePanelClose", this.panelCloseHandler);
}
static {
this.properties = { hasActionBar: [16, {}, { state: true }], hasContentBottom: [16, {}, { state: true }], hasContentTop: [16, {}, { state: true }], hasEndActions: [16, {}, { state: true }], hasFab: [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 }], 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 }], icon: [3, { type: String }, { reflect: true }], iconFlipRtl: [7, {}, { reflect: true, type: Boolean }], 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 }], topLayerDisabled: [7, {}, { reflect: true, type: Boolean }] };
}
static {
this.styles = [styles$1, styles];
}
get closed() {
return this._closed;
}
set closed(value) {
const oldValue = this._closed;
if (value !== oldValue) {
this.setClosedState(value);
}
}
async scrollContentTo(options) {
this.panelScrollEl?.scrollTo(options);
}
async setFocus(options) {
return this.focusSetter(() => this.containerRef.value, options);
}
willUpdate(changes) {
if (changes.has("collapsed") && this.hasUpdated) {
if (this.collapsed) {
this.calcitePanelCollapse.emit();
} else {
this.calcitePanelExpand.emit();
}
}
}
disconnectedCallback() {
super.disconnectedCallback();
this.resizeObserver?.disconnect();
}
async setClosedState(value) {
if (this.beforeClose && value) {
try {
await this.beforeClose?.();
} catch {
return;
}
}
this._closed = value;
}
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");
}
}
closeClickHandler() {
this.emitCloseEvent();
}
emitCloseEvent() {
this.calcitePanelClose.emit();
}
panelKeyDownHandler(event) {
if (this.closable && event.key === "Escape" && !event.defaultPrevented) {
event.preventDefault();
this.emitCloseEvent();
}
}
panelCloseHandler(event) {
if (event.defaultPrevented || event.target !== this.el) {
return;
}
this.closed = 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);
}
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) {
updateRefObserver(this.resizeObserver, this.panelScrollEl, el);
this.panelScrollEl = el;
}
handleAlertsSlotChange(event) {
slotChangeGetAssignedElements(event)?.map((el) => {
if (el.nodeName === "CALCITE-ALERT") {
el.embedded = true;
}
});
}
renderHeaderContent() {
const { heading, headingLevel, description, hasHeaderContent, icon, scale } = this;
const iconNode = icon ? html`<calcite-icon class=${safeClassMap(CSS.icon)} .flipRtl=${this.iconFlipRtl} .icon=${icon} .scale=${getIconScale(scale)}></calcite-icon>` : null;
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]: true, [CSS.headerNonSlottedContent]: true })}>${iconNode}<div class=${safeClassMap(CSS.headingTextContent)}>${headingNode}${descriptionNode}</div></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 .aria=${{ expanded: !collapsed }} .icon=${collapsed ? icons[0] : icons[1]} id=${IDS.collapse} .label=${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.closeClickHandler} .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, scale } = this;
return keyed("menu", html`<calcite-action-menu .flipPlacements=${menuFlipPlacements ?? ["top", "bottom"]} .hidden=${!hasMenuItems} .label=${messages.options} .open=${menuOpen} .overlayPositioning=${this.overlayPositioning} .placement=${menuPlacement} .scale=${scale} .topLayerDisabled=${this.topLayerDisabled}><calcite-action class=${safeClassMap(CSS.menuAction)} .icon=${ICONS.menu} .scale=${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, hasContentTop } = this;
const headerContentNode = this.renderHeaderContent();
const showHeaderContent = hasHeaderContent || !!headerContentNode || hasStartActions || hasEndActions || collapsible || closable || hasMenuItems || hasActionBar || hasContentTop;
this.showHeaderContent = showHeaderContent;
return html`<header class=${safeClassMap(CSS.header)} .hidden=${!(showHeaderContent || hasActionBar || hasContentTop)}><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 } = this;
const showFooter = hasFooterStartContent || hasFooterEndContent || hasFooterContent;
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></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, closed } = this;
const panelNode = html`<article .ariaBusy=${loading} class=${safeClassMap(CSS.container)} .hidden=${closed} ${ref(this.containerRef)}>${this.renderHeaderNode()}${this.renderContent()}${this.renderContentBottom()}${this.renderFooterNode()}${keyed("alerts", html`<slot name=${SLOTS.alerts} @slotchange=${this.handleAlertsSlotChange}></slot>`)}</article>`;
return this.interactiveContainer({ disabled, children: html`${loading ? html`<calcite-scrim .loading=${loading}></calcite-scrim>` : null}${panelNode}` });
}
}
customElement("calcite-panel", Panel);
export {
Panel
};