UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

254 lines (253 loc) • 21.4 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, 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 };