@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
329 lines (328 loc) • 12.4 kB
JavaScript
/* 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 { c as createObserver } from "../../chunks/observers.js";
import { d as disconnectSortableComponent, c as connectSortableComponent } from "../../chunks/sortableComponent.js";
import { h as getRootNode, b as slotChangeGetAssignedElements } from "../../chunks/dom.js";
import { g as guid } from "../../chunks/guid.js";
import { u as useSetFocus } from "../../chunks/useSetFocus.js";
import { u as useCancelable } from "../../chunks/useCancelable.js";
import { u as useInteractive } from "../../chunks/useInteractive.js";
function isBlock(element) {
return element.tagName === "CALCITE-BLOCK";
}
const CSS = {
container: "container",
groupContainer: "group-container",
scrim: "scrim",
assistiveText: "assistive-text"
};
const blockGroupSelector = "calcite-block-group";
const blockSelector = "calcite-block";
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{display:block}.container{position:relative}.assistive-text{position:absolute;inline-size:1px;block-size:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}:host([hidden]){display:none}[hidden]{display:none}:host([disabled]) ::slotted([calcite-hydrated][disabled]),:host([disabled]) [calcite-hydrated][disabled]{opacity:1}.interaction-container{display:contents}`;
function updateBlockChildren(blockChildren) {
blockChildren.forEach((block) => {
block.setPosition = blockChildren.indexOf(block) + 1;
block.setSize = blockChildren.length;
});
}
class BlockGroup extends LitElement {
constructor() {
super();
this.dragSelector = blockSelector;
this.handleSelector = "calcite-sort-handle";
this.mutationObserver = createObserver("mutation", () => {
this.updateBlockItemsDebounced();
});
this.blockAndGroups = [];
this.cancelable = useCancelable()(this);
this.focusSetter = useSetFocus()(this);
this.updateBlockItemsDebounced = debounce(this.updateBlockItems, DEBOUNCE.nextTick);
this.interactiveContainer = useInteractive(this);
this.sortHandleMenuItems = [];
this.disabled = false;
this.dragEnabled = false;
this.loading = false;
this.scale = "m";
this.sortDisabled = false;
this.calciteBlockGroupDragEnd = createEvent({ cancelable: false });
this.calciteBlockGroupDragStart = createEvent({ cancelable: false });
this.calciteBlockGroupMoveHalt = createEvent({ cancelable: false });
this.calciteBlockGroupOrderChange = createEvent({ cancelable: false });
this.listen("calciteInternalAssistiveTextChange", this.handleCalciteInternalAssistiveTextChange);
this.listen("calciteBlockSortHandleBeforeOpen", this.updateBlockItemsDebounced);
this.listen("calciteSortHandleReorder", this.handleSortReorder);
this.listen("calciteSortHandleMove", this.handleSortMove);
this.listen("calciteSortHandleAdd", this.handleSortAdd);
}
static {
this.properties = { assistiveText: [16, {}, { state: true }], sortHandleMenuItems: [16, {}, { state: true }], canPull: [0, {}, { attribute: false }], canPut: [0, {}, { attribute: false }], disabled: [7, {}, { reflect: true, type: Boolean }], dragEnabled: [7, {}, { reflect: true, type: Boolean }], group: [3, {}, { reflect: true }], label: 1, loading: [7, {}, { reflect: true, type: Boolean }], scale: [3, {}, { reflect: true }], sortDisabled: [7, {}, { reflect: true, type: Boolean }] };
}
static {
this.styles = styles;
}
emitOrderChangeEvent(detail) {
this.calciteBlockGroupOrderChange.emit(detail);
}
async setFocus(options) {
return this.focusSetter(() => this.el, options);
}
connectedCallback() {
super.connectedCallback();
this.connectObserver();
this.updateBlockItemsDebounced();
this.setUpSorting();
this.setParentBlockGroup();
this.cancelable.add(this.updateBlockItemsDebounced);
}
willUpdate(changes) {
if (changes.has("group") || changes.has("canPull") && this.hasUpdated || changes.has("canPut") && this.hasUpdated || changes.has("dragEnabled") && (this.hasUpdated || this.dragEnabled !== false) || changes.has("sortDisabled") && (this.hasUpdated || this.sortDisabled !== false)) {
this.updateBlockItemsDebounced();
}
if (changes.has("scale") && this.hasUpdated) {
this.updateBlockAndGroupScale();
}
}
disconnectedCallback() {
super.disconnectedCallback();
this.disconnectObserver();
disconnectSortableComponent(this);
}
updateBlockItems() {
this.updateGroupItems();
const { dragEnabled, el, sortDisabled, sortHandleMenuItems } = this;
const items = Array.from(this.el.querySelectorAll(blockSelector));
const fromEl = el;
const fromElItems = Array.from(fromEl.children).filter(isBlock);
items.forEach((item) => {
if (item.closest(blockGroupSelector) === el) {
item.moveToItems = sortHandleMenuItems.filter((moveToItem) => this.validateSortMenuItem({
type: "move",
fromEl,
toEl: moveToItem.element,
dragEl: item,
newIndex: 0,
oldIndex: fromElItems.indexOf(item)
}));
item.addToItems = this.sortHandleMenuItems.filter((moveToItem) => this.validateSortMenuItem({
type: "add",
fromEl,
toEl: moveToItem.element,
dragEl: item,
newIndex: 0,
oldIndex: fromElItems.indexOf(item)
}));
item.dragHandle = dragEnabled;
item.sortDisabled = sortDisabled;
}
});
this.setUpSorting();
}
updateGroupItems() {
const { el, group } = this;
const rootNode = getRootNode(el);
const blockGroups = group ? Array.from(rootNode.querySelectorAll(`${blockGroupSelector}[group="${group}"]`)).filter((blockGroup) => !blockGroup.disabled && blockGroup.dragEnabled) : [];
this.sortHandleMenuItems = blockGroups.map((element) => ({
element,
label: element.label ?? element.id,
id: guid()
}));
}
handleCalciteInternalAssistiveTextChange(event) {
this.assistiveText = event.detail.message;
event.stopPropagation();
}
handleSortReorder(event) {
if (this.parentBlockGroupEl || event.defaultPrevented) {
return;
}
event.preventDefault();
this.handleReorder(event);
}
handleSortAdd(event) {
if (this.parentBlockGroupEl || event.defaultPrevented) {
return;
}
event.preventDefault();
this.handleAdd(event);
}
handleSortMove(event) {
if (this.parentBlockGroupEl || event.defaultPrevented) {
return;
}
event.preventDefault();
this.handleMove(event);
}
connectObserver() {
this.mutationObserver?.observe(this.el, { childList: true, subtree: true });
}
disconnectObserver() {
this.mutationObserver?.disconnect();
}
setUpSorting() {
const { dragEnabled } = this;
if (!dragEnabled) {
return;
}
connectSortableComponent(this);
}
onGlobalDragStart() {
this.disconnectObserver();
}
onGlobalDragEnd() {
this.connectObserver();
}
onDragEnd(detail) {
this.calciteBlockGroupDragEnd.emit(detail);
}
onDragStart(detail) {
detail.dragEl.sortHandleOpen = false;
this.calciteBlockGroupDragStart.emit(detail);
}
onDragSort(detail) {
this.setParentBlockGroup();
this.updateBlockItemsDebounced();
this.calciteBlockGroupOrderChange.emit(detail);
}
setParentBlockGroup() {
this.parentBlockGroupEl = this.el.parentElement?.closest(blockGroupSelector);
}
handleDefaultSlotChange(event) {
const blockChildren = [];
this.blockAndGroups = slotChangeGetAssignedElements(event).filter((el) => {
if (el.matches(blockSelector)) {
blockChildren.push(el);
}
return el.matches(blockSelector) || el.matches(blockGroupSelector);
});
updateBlockChildren(blockChildren);
this.updateBlockAndGroupScale();
}
updateBlockAndGroupScale() {
this.blockAndGroups.forEach((el) => {
el.scale = this.scale;
});
}
validateSortMenuItem({ fromEl, toEl, dragEl, newIndex, oldIndex, type }) {
if (!fromEl || !toEl || toEl === fromEl || dragEl.contains(toEl)) {
return false;
}
const canPull = fromEl.canPull?.({
toEl,
fromEl,
dragEl,
newIndex,
oldIndex
}) ?? true;
const canPut = toEl.canPut?.({
toEl,
fromEl,
dragEl,
newIndex,
oldIndex
}) ?? true;
return (type === "add" ? canPull === "clone" : canPull === true) && canPut;
}
handleAdd(event) {
const { addTo } = event.detail;
const dragEl = event.target;
const fromEl = dragEl?.parentElement;
const toEl = addTo.element;
const fromElItems = Array.from(fromEl.children).filter(isBlock);
const oldIndex = fromElItems.indexOf(dragEl);
const newIndex = 0;
if (!this.validateSortMenuItem({ type: "add", fromEl, toEl, dragEl, newIndex, oldIndex })) {
return;
}
dragEl.sortHandleOpen = false;
this.disconnectObserver();
const newEl = dragEl.cloneNode();
toEl.prepend(newEl);
this.updateBlockItemsDebounced();
this.connectObserver();
const eventDetail = {
dragEl,
fromEl,
toEl,
newIndex,
oldIndex
};
this.calciteBlockGroupOrderChange.emit(eventDetail);
toEl.emitOrderChangeEvent(eventDetail);
}
handleMove(event) {
const { moveTo } = event.detail;
const dragEl = event.target;
const fromEl = dragEl?.parentElement;
const toEl = moveTo.element;
const fromElItems = Array.from(fromEl.children).filter(isBlock);
const oldIndex = fromElItems.indexOf(dragEl);
const newIndex = 0;
if (!this.validateSortMenuItem({ type: "move", fromEl, toEl, dragEl, newIndex, oldIndex })) {
return;
}
dragEl.sortHandleOpen = false;
this.disconnectObserver();
toEl.prepend(dragEl);
this.updateBlockItemsDebounced();
this.connectObserver();
const eventDetail = {
dragEl,
fromEl,
toEl,
newIndex,
oldIndex
};
this.calciteBlockGroupOrderChange.emit(eventDetail);
toEl.emitOrderChangeEvent(eventDetail);
}
handleReorder(event) {
const { reorder } = event.detail;
const dragEl = event.target;
const parentEl = dragEl?.parentElement;
if (!parentEl) {
return;
}
dragEl.sortHandleOpen = false;
const sameParentItems = Array.from(parentEl.children).filter(isBlock);
const lastIndex = sameParentItems.length - 1;
const oldIndex = sameParentItems.indexOf(dragEl);
let newIndex = oldIndex;
switch (reorder) {
case "top":
newIndex = 0;
break;
case "bottom":
newIndex = lastIndex;
break;
case "up":
newIndex = oldIndex === 0 ? 0 : oldIndex - 1;
break;
case "down":
newIndex = oldIndex === lastIndex ? lastIndex : oldIndex + 1;
break;
}
this.disconnectObserver();
const referenceEl = reorder === "up" || reorder === "top" ? sameParentItems[newIndex] : sameParentItems[newIndex].nextSibling;
parentEl.insertBefore(dragEl, referenceEl);
this.updateBlockItemsDebounced();
this.connectObserver();
this.calciteBlockGroupOrderChange.emit({
dragEl,
fromEl: parentEl,
toEl: parentEl,
newIndex,
oldIndex
});
}
render() {
const { loading, label } = this;
return this.interactiveContainer({ disabled: this.disabled, children: html`<div class=${safeClassMap(CSS.container)}>${this.dragEnabled ? html`<span aria-live=assertive class=${safeClassMap(CSS.assistiveText)}>${this.assistiveText}</span>` : null}${loading ? html`<calcite-scrim class=${safeClassMap(CSS.scrim)} .loading=${loading}></calcite-scrim>` : null}<div .ariaBusy=${loading} .ariaLabel=${label || ""} class=${safeClassMap(CSS.groupContainer)} role=group><slot =${this.handleDefaultSlotChange}></slot></div></div>` });
}
}
customElement("calcite-block-group", BlockGroup);
export {
BlockGroup
};