UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

294 lines (293 loc) • 15.7 kB
/* COPYRIGHT Esri - https://js.arcgis.com/5.0/LICENSE.txt */ import { D as DEBOUNCE, c as customElement } from "../../chunks/runtime.js"; import { debounce } from "es-toolkit"; import { css, html } from "lit"; import { LitElement, createEvent, safeClassMap } from "@arcgis/lumina"; import { createRef, ref } from "lit/directives/ref.js"; import { a as getStylePixelValue, s as slotChangeHasAssignedElement, b as slotChangeGetAssignedElements, f as focusElementInGroup } from "../../chunks/dom.js"; import { c as createObserver } from "../../chunks/observers.js"; import { o as overflowActions, q as queryActions, t as toggleChildActionText, E as ExpandToggle } from "../../chunks/ExpandToggle.js"; import { u as useT9n } from "../../chunks/useT9n.js"; import { u as useCancelable } from "../../chunks/useCancelable.js"; import { u as useSetFocus } from "../../chunks/useSetFocus.js"; import { i as isAction } from "../../chunks/resources.js"; const calculateMaxItems = ({ bufferSize = 0, containerSize, itemSizes }) => { const maxSize = containerSize - bufferSize; let breakpoint = itemSizes.length; let sizeSum = 0; for (const [index, size] of itemSizes.entries()) { sizeSum = sizeSum + size; if (sizeSum > maxSize) { breakpoint = index; break; } else { continue; } } return breakpoint; }; const getOverflowCount = ({ bufferSize = 0, containerSize, itemSizes }) => { return Math.max(itemSizes.length - calculateMaxItems({ bufferSize, itemSizes, containerSize }), 0); }; const CSS = { container: "container", actionGroupEnd: "action-group--end" }; const SLOTS = { actionsEnd: "actions-end", expandTooltip: "expand-tooltip" }; 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([scale=s]){--calcite-internal-action-bar-gap: var(--calcite-action-bar-items-space, var(--calcite-spacing-xxs));--calcite-internal-action-bar-padding: var(--calcite-spacing-xxs)}:host([scale=m]){--calcite-internal-action-bar-gap: var(--calcite-action-bar-items-space, var(--calcite-spacing-sm));--calcite-internal-action-bar-padding: var(--calcite-spacing-sm)}:host([scale=l]){--calcite-internal-action-bar-gap: var(--calcite-action-bar-items-space, var(--calcite-space-sm-plus));--calcite-internal-action-bar-padding: var(--calcite-spacing-sm-plus)}:host{display:inline-flex;align-self:stretch;background:transparent;--calcite-internal-action-group-dividing-border-margin: calc(var(--calcite-spacing-base) + 1px)}.container{display:inline-flex;flex:1 1 auto;flex-direction:column;background-color:var(--calcite-action-bar-background-color, var(--calcite-color-foreground-1));gap:var(--calcite-internal-action-bar-gap);padding:var(--calcite-internal-action-bar-padding)}@keyframes in{0%{opacity:0}to{opacity:1}}:host([floating]) .container{animation:in var(--calcite-internal-animation-timing-slow) ease-in-out;overflow:hidden;border-radius:var(--calcite-action-bar-corner-radius, var(--calcite-corner-radius-round));--tw-shadow: 0 6px 20px -4px rgba(0, 0, 0, .1), 0 4px 12px -2px rgba(0, 0, 0, .08);--tw-shadow-colored: 0 6px 20px -4px var(--tw-shadow-color), 0 4px 12px -2px var(--tw-shadow-color);box-shadow:var(--calcite-action-bar-shadow, var(--tw-ring-offset-shadow, 0 0 rgba(0, 0, 0, 0)), var(--tw-ring-shadow, 0 0 rgba(0, 0, 0, 0)), var(--tw-shadow))}:host([layout=vertical]){flex-direction:column}:host([layout=vertical]):host([overflow-actions-disabled]) .container{overflow-y:auto}:host([layout=vertical]):host([expanded]) .container{max-inline-size:var(--calcite-action-bar-expanded-max-width, auto)}:host([layout=vertical]) .action-group--end{margin-block-start:auto}:host([layout=vertical]) ::slotted(calcite-action-group:not(:last-of-type)){border-block-end-width:var(--calcite-border-width-sm);border-image:linear-gradient(to right,transparent var(--calcite-internal-action-group-dividing-border-margin),var(--calcite-action-group-border-color, var(--calcite-color-border-3)) var(--calcite-internal-action-group-dividing-border-margin),var(--calcite-action-group-border-color, var(--calcite-color-border-3)) calc(100% - var(--calcite-internal-action-group-dividing-border-margin)),transparent var(--calcite-internal-action-group-dividing-border-margin));border-image-slice:1;padding-block-end:var(--calcite-internal-action-bar-padding)}:host([layout=horizontal]){flex-direction:row}:host([layout=horizontal]) .container{flex-direction:row}:host([layout=horizontal]):host([overflow-actions-disabled]) .container{overflow-x:auto}:host([layout=horizontal]) .action-group--end{margin-inline-start:auto}:host([layout=horizontal]) ::slotted(calcite-action-group:not(:last-of-type)){border-image:linear-gradient(transparent var(--calcite-internal-action-group-dividing-border-margin),var(--calcite-action-group-border-color, var(--calcite-color-border-3)) var(--calcite-internal-action-group-dividing-border-margin),var(--calcite-action-group-border-color, var(--calcite-color-border-3)) calc(100% - var(--calcite-internal-action-group-dividing-border-margin)),transparent var(--calcite-internal-action-group-dividing-border-margin));border-image-slice:1;border-inline-end-width:var(--calcite-border-width-sm);padding-inline-end:var(--calcite-internal-action-bar-padding)}.action-group--end{justify-content:flex-end}:host([hidden]){display:none}[hidden]{display:none}`; class ActionBar extends LitElement { constructor() { super(); this.actions = []; this.containerRef = createRef(); this.mutationObserver = createObserver("mutation", () => this.mutationObserverHandler()); this.cancelable = useCancelable()(this); this.resize = debounce(({ width, height }) => { const { expanded, expandDisabled, layout, overflowActionsDisabled, actionGroups } = this; if (overflowActionsDisabled || layout === "vertical" && !height || layout === "horizontal" && !width) { return; } const itemSizes = this.getItemSizes(); this.updateGroups(); const groupCount = this.hasActionsEnd || !expandDisabled ? actionGroups.length + 1 : actionGroups.length; let bufferSize = groupCount; const actionBarContainerStyle = getComputedStyle(this.containerRef.value); bufferSize += getStylePixelValue(layout === "horizontal" ? actionBarContainerStyle.paddingInlineStart : actionBarContainerStyle.paddingBlockStart) + getStylePixelValue(layout === "horizontal" ? actionBarContainerStyle.paddingInlineEnd : actionBarContainerStyle.paddingBlockEnd); if (actionGroups.length > 0) { actionGroups.forEach((actionGroup, i) => { const actionGroupStyle = getComputedStyle(actionGroup); const actionGroupGap = getStylePixelValue(actionGroupStyle.gap); const actionGroupGapQuantity = actionGroup.childElementCount - 1; bufferSize += actionGroupGap * actionGroupGapQuantity; if (i < actionGroups.length - 1) { bufferSize += getStylePixelValue(layout === "horizontal" ? actionGroupStyle.paddingInlineEnd : actionGroupStyle.paddingBlockEnd); bufferSize += getStylePixelValue(layout === "horizontal" ? actionGroupStyle.borderInlineEndWidth : actionGroupStyle.borderBlockEndWidth); } }); } if (groupCount > 0) { for (let i = 1; i < groupCount; i++) { bufferSize += getStylePixelValue(actionBarContainerStyle.gap); } } const overflowCount = getOverflowCount({ bufferSize, containerSize: layout === "horizontal" ? width : height, itemSizes }); overflowActions({ actionGroups, expanded, overflowCount }); }, DEBOUNCE.resize); this.resizeHandler = (entry) => { const { width, height } = entry.contentRect; this.resize({ width, height }); }; this.resizeObserver = createObserver("resize", (entries) => this.resizeHandlerEntries(entries)); this.toggleExpand = () => { this.expanded = !this.expanded; this.calciteActionBarToggle.emit(); }; this.messages = useT9n(); this.focusSetter = useSetFocus()(this); this.setExpandToggleEl = (el) => { this.expandToggleEl = el; }; this.hasActionsEnd = false; this.floating = false; this.expandDisabled = false; this.expanded = false; this.layout = "vertical"; this.overflowActionsDisabled = false; this.overlayPositioning = "absolute"; this.scale = "m"; this.selectionAppearance = "neutral"; this.calciteActionBarCollapse = createEvent({ cancelable: false }); this.calciteActionBarExpand = createEvent({ cancelable: false }); this.calciteActionBarToggle = createEvent({ cancelable: false }); this.listen("calciteActionMenuOpen", this.actionMenuOpenHandler); this.listen("keydown", this.handleKeyDown); } static { this.properties = { expandTooltip: [16, {}, { state: true }], hasActionsEnd: [16, {}, { state: true }], actionsEndGroupLabel: 1, floating: [7, {}, { reflect: true, type: Boolean }], expandDisabled: [7, {}, { reflect: true, type: Boolean }], expanded: [7, {}, { reflect: true, type: Boolean }], layout: [3, {}, { reflect: true }], messageOverrides: [0, {}, { attribute: false }], overflowActionsDisabled: [7, {}, { reflect: true, type: Boolean }], overlayPositioning: [3, {}, { reflect: true }], position: [3, {}, { reflect: true }], scale: [3, {}, { reflect: true }], selectionAppearance: [3, {}, { reflect: true }] }; } static { this.styles = styles; } async overflowActions() { this.resize({ width: this.el.clientWidth, height: this.el.clientHeight }); } async setFocus(options) { return this.focusSetter(() => this.el, options); } connectedCallback() { super.connectedCallback(); this.updateGroups(); this.overflowActions(); this.updateActions(); this.mutationObserver?.observe(this.el, { childList: true, subtree: true }); this.overflowActionsDisabledHandler(this.overflowActionsDisabled); this.cancelable.add(this.resize); } willUpdate(changes) { if (changes.has("expandDisabled") && (this.hasUpdated || this.expandDisabled !== false)) { this.overflowActions(); } if (changes.has("layout") && (this.hasUpdated || this.layout !== "vertical")) { this.updateGroups(); } if (changes.has("overflowActionsDisabled") && (this.hasUpdated || this.overflowActionsDisabled !== false)) { this.overflowActionsDisabledHandler(this.overflowActionsDisabled); } if (changes.has("expanded") && this.hasUpdated) { this.expandedHandler(); if (this.expanded) { this.calciteActionBarExpand.emit(); } else { this.calciteActionBarCollapse.emit(); } } if (changes.has("selectionAppearance") && (this.hasUpdated || this.selectionAppearance !== "neutral")) { this.updateActions(); } } loaded() { this.overflowActions(); } disconnectedCallback() { super.disconnectedCallback(); this.mutationObserver?.disconnect(); this.resizeObserver?.disconnect(); } getItemSizes() { const { el, layout, expandToggleEl } = this; const actions = queryActions(el); if (expandToggleEl) { actions.push(expandToggleEl); } const clientSize = layout === "horizontal" ? "clientWidth" : "clientHeight"; const fallbackSize = Math.max(...actions.map((action) => action[clientSize] || 0)); return actions.map((action) => action[clientSize] || fallbackSize); } expandedHandler() { const { el, expanded } = this; toggleChildActionText({ el, expanded }); this.overflowActions(); } overflowActionsDisabledHandler(overflowActionsDisabled) { if (overflowActionsDisabled) { this.resizeObserver?.disconnect(); return; } this.resizeObserver?.observe(this.el); this.overflowActions(); } actionMenuOpenHandler(event) { if (event.target.menuOpen) { const composedPath = event.composedPath(); this.actionGroups?.forEach((group) => { if (!composedPath.includes(group)) { group.menuOpen = false; } }); } } mutationObserverHandler() { this.updateGroups(); this.overflowActions(); this.queryAndStoreActions(); this.updateActions(); } resizeHandlerEntries(entries) { entries.forEach(this.resizeHandler); } updateGroups() { const groups = Array.from(this.el.querySelectorAll("calcite-action-group")); this.actionGroups = groups; groups.forEach((group) => { group.layout = this.layout; group.scale = this.scale; }); } handleDefaultSlotChange() { this.updateGroups(); this.queryAndStoreActions(); this.updateActions(); } handleActionsEndSlotChange(event) { this.hasActionsEnd = slotChangeHasAssignedElement(event); } handleTooltipSlotChange(event) { const tooltips = slotChangeGetAssignedElements(event).filter((el) => el?.matches("calcite-tooltip")); this.expandTooltip = tooltips[0]; } updateActions() { this.actions.forEach((action) => { action.selectionAppearance = this.selectionAppearance; }); } queryAndStoreActions() { this.actions = Array.from(this.el.querySelectorAll("calcite-action")); } handleKeyDown(event) { this.queryAndStoreActions(); const actions = this.actions.filter((action) => !action.disabled); const current = document.activeElement; if (!isAction(current)) { return; } switch (event.key) { case "ArrowRight": case "ArrowDown": focusElementInGroup(actions, current, "next", true); event.preventDefault(); break; case "ArrowLeft": case "ArrowUp": focusElementInGroup(actions, current, "previous", true); event.preventDefault(); break; case "Home": focusElementInGroup(actions, current, "first", true); event.preventDefault(); break; case "End": focusElementInGroup(actions, current, "last", true); event.preventDefault(); break; case "Tab": this.setActionTabIndexes(current); break; } } setActionTabIndexes(active) { this.actions.forEach((action) => { action.tabIndex = !action.disabled && action === active ? 0 : -1; }); } renderBottomActionGroup() { const { expanded, expandDisabled, el, position, toggleExpand, scale, layout, messages, actionsEndGroupLabel, overlayPositioning } = this; const expandToggleNode = !expandDisabled ? ExpandToggle({ collapseLabel: messages.collapseLabel, collapseText: messages.collapse, el, expandLabel: messages.expandLabel, expandText: messages.expand, expanded, position, ref: this.setExpandToggleEl, scale, toggle: toggleExpand, tooltip: this.expandTooltip }) : null; return html`<calcite-action-group class=${safeClassMap(CSS.actionGroupEnd)} .hidden=${this.expandDisabled && !this.hasActionsEnd} .label=${actionsEndGroupLabel} .layout=${layout} .overlayPositioning=${overlayPositioning} .scale=${scale}><slot name=${SLOTS.actionsEnd} @slotchange=${this.handleActionsEndSlotChange}></slot><slot name=${SLOTS.expandTooltip} @slotchange=${this.handleTooltipSlotChange}></slot>${expandToggleNode}</calcite-action-group>`; } render() { return html`<div .ariaOrientation=${this.layout === "horizontal" ? "horizontal" : "vertical"} class=${safeClassMap(CSS.container)} role=toolbar ${ref(this.containerRef)}><slot @slotchange=${this.handleDefaultSlotChange}></slot>${this.renderBottomActionGroup()}</div>`; } } customElement("calcite-action-bar", ActionBar); export { ActionBar };