UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

255 lines (254 loc) • 22.2 kB
/* 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 };