diff --git a/packages/calcite-components/src/components/block-group/block-group.e2e.ts b/packages/calcite-components/src/components/block-group/block-group.e2e.ts index 1eb5b19f9fb..01d0f01e08b 100755 --- a/packages/calcite-components/src/components/block-group/block-group.e2e.ts +++ b/packages/calcite-components/src/components/block-group/block-group.e2e.ts @@ -9,6 +9,7 @@ import { Reorder } from "../sort-handle/interfaces"; import { SLOTS as BLOCK_SLOTS } from "../block/resources"; import { Block } from "../block/block"; import { mockConsole } from "../../tests/utils/logging"; +import { IDS } from "../sort-handle/resources"; import { BlockDragDetail } from "./interfaces"; import type { BlockGroup } from "./block-group"; @@ -149,9 +150,6 @@ describe("calcite-block-group", () => { endOldIndex: number; startNewIndex: number; startOldIndex: number; - moveHaltNewIndex: number; - moveHaltOldIndex: number; - moveHaltCalledTimes: number; }>; it("works using a mouse", async () => { @@ -356,11 +354,11 @@ describe("calcite-block-group", () => { it("calls canPull and canPut for move items", async () => { const page = await newE2EPage(); await page.setContent(html` - + - + @@ -368,70 +366,35 @@ describe("calcite-block-group", () => { // Workaround for page.spyOnEvent() failing due to drag event payload being serialized and there being circular JSON structures from the payload elements. See: https://github.com/Esri/calcite-design-system/issues/7643 await page.evaluate(() => { - const testWindow = window as TestWindow; - testWindow.moveHaltCalledTimes = 0; const firstLetters = document.getElementById("first-letters") as BlockGroup["el"]; - - firstLetters.addEventListener("calciteBlockGroupMoveHalt", (event: CustomEvent) => { - testWindow.moveHaltCalledTimes++; - testWindow.moveHaltNewIndex = event.detail.newIndex; - testWindow.moveHaltOldIndex = event.detail.oldIndex; - }); - firstLetters.canPull = ({ dragEl }) => dragEl.id === "b"; firstLetters.canPut = ({ dragEl }) => dragEl.id === "c"; }); await page.waitForChanges(); - async function clickMoveDropdownItem(id: string) { + async function getMoveItems(id: string) { const component = await page.find(`#${id}`); component.setProperty("sortHandleOpen", true); await page.waitForChanges(); - const dropdownItem = await page.find(`#${id} >>> calcite-dropdown-group:last-child calcite-dropdown-item`); - expect(dropdownItem).not.toBeNull(); - await dropdownItem.click(); - - await page.waitForChanges(); - } - - async function getResults() { - return await page.evaluate(() => { - const testWindow = window as TestWindow; - - return { - moveHaltCalledTimes: testWindow.moveHaltCalledTimes, - moveHaltOldIndex: testWindow.moveHaltOldIndex, - moveHaltNewIndex: testWindow.moveHaltNewIndex, - }; + return await findAll(page, `#${id} >>> calcite-dropdown-group#${IDS.move} calcite-dropdown-item`, { + allowEmpty: true, }); } - await clickMoveDropdownItem("a"); - let results = await getResults(); - - expect(results.moveHaltCalledTimes).toBe(1); - expect(results.moveHaltNewIndex).toBe(0); - expect(results.moveHaltOldIndex).toBe(0); - - await clickMoveDropdownItem("b"); - results = await getResults(); - - expect(results.moveHaltCalledTimes).toBe(1); - expect(results.moveHaltNewIndex).toBe(0); - expect(results.moveHaltNewIndex).toBe(0); - - await clickMoveDropdownItem("c"); - results = await getResults(); + const aMoveItems = await getMoveItems("a"); + expect(aMoveItems.length).toBe(0); - expect(results.moveHaltCalledTimes).toBe(1); + const bMoveItems = await getMoveItems("b"); + expect(bMoveItems.length).toBe(1); + expect(await bMoveItems[0].getProperty("label")).toBe("Second Letters"); - await clickMoveDropdownItem("d"); - results = await getResults(); + const cMoveItems = await getMoveItems("c"); + expect(cMoveItems.length).toBe(1); + expect(await cMoveItems[0].getProperty("label")).toBe("First Letters"); - expect(results.moveHaltCalledTimes).toBe(2); - expect(results.moveHaltNewIndex).toBe(0); - expect(results.moveHaltOldIndex).toBe(1); + const dMoveItems = await getMoveItems("d"); + expect(dMoveItems.length).toBe(0); }); it("reorders using a keyboard", async () => { diff --git a/packages/calcite-components/src/components/block-group/block-group.tsx b/packages/calcite-components/src/components/block-group/block-group.tsx index 9a3212e1cfb..aed7b9bb9b7 100755 --- a/packages/calcite-components/src/components/block-group/block-group.tsx +++ b/packages/calcite-components/src/components/block-group/block-group.tsx @@ -50,7 +50,7 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort handleSelector = "calcite-sort-handle"; mutationObserver = createObserver("mutation", () => { - this.updateBlockItems(); + this.updateBlockItemsDebounced(); }); private parentBlockGroupEl: BlockGroup["el"]; @@ -59,23 +59,7 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort private cancelable = useCancelable()(this); - private updateBlockItems = debounce((): void => { - this.updateGroupItems(); - const { dragEnabled, el, moveToItems } = this; - - const items = Array.from(this.el.querySelectorAll(blockSelector)); - - items.forEach((item) => { - if (item.closest(blockGroupSelector) === el) { - item.moveToItems = moveToItems.filter( - (moveToItem) => moveToItem.element !== el && !item.contains(moveToItem.element), - ); - item.dragHandle = dragEnabled; - } - }); - - this.setUpSorting(); - }, DEBOUNCE.nextTick); + private updateBlockItemsDebounced = debounce(this.updateBlockItems, DEBOUNCE.nextTick); // #endregion @@ -136,17 +120,6 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort focusFirstTabbable(this.el); } - /** - * Emits a `calciteBlockGroupMoveHalt` event. - * - * @private - * @param dragDetail - */ - @method() - putFailed(dragDetail: BlockDragDetail): void { - this.calciteBlockGroupMoveHalt.emit(dragDetail); - } - // #endregion // #region Events @@ -160,7 +133,11 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort /** Fires when the component's item order changes. */ calciteBlockGroupOrderChange = createEvent({ cancelable: false }); - /** Fires when a user attempts to move an element using the sort menu and 'canPut' or 'canPull' returns falsy. */ + /** + * Fires when a user attempts to move an element using the sort menu and 'canPut' or 'canPull' returns falsy. + * + * @deprecated No longer necessary. + */ calciteBlockGroupMoveHalt = createEvent({ cancelable: false }); // #endregion @@ -176,14 +153,15 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort ); this.listen("calciteSortHandleReorder", this.handleSortReorder); this.listen("calciteSortHandleMove", this.handleSortMove); + this.listen("calciteInternalBlockUpdateMoveToItems", this.handleUpdateMoveToItems); } override connectedCallback(): void { this.connectObserver(); - this.updateBlockItems(); + this.updateBlockItemsDebounced(); this.setUpSorting(); this.setParentBlockGroup(); - this.cancelable.add(this.updateBlockItems); + this.cancelable.add(this.updateBlockItemsDebounced); } override willUpdate(changes: PropertyValues): void { @@ -191,7 +169,7 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort changes.has("group") || (changes.has("dragEnabled") && (this.hasUpdated || this.dragEnabled !== false)) ) { - this.updateBlockItems(); + this.updateBlockItemsDebounced(); } } @@ -208,6 +186,24 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort // #region Private Methods + private updateBlockItems(): void { + this.updateGroupItems(); + const { dragEnabled, el, moveToItems } = this; + + const items = Array.from(this.el.querySelectorAll(blockSelector)); + + items.forEach((item) => { + if (item.closest(blockGroupSelector) === el) { + item.moveToItems = moveToItems.filter( + (moveToItem) => moveToItem.element !== el && !item.contains(moveToItem.element), + ); + item.dragHandle = dragEnabled; + } + }); + + this.setUpSorting(); + } + private updateGroupItems(): void { const { el, group } = this; @@ -231,6 +227,28 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort event.stopPropagation(); } + private async handleUpdateMoveToItems(event: CustomEvent): Promise { + event.stopPropagation(); + + const fromEl = this.el; + const fromElItems = Array.from(fromEl.children).filter(isBlock); + const item = event.target as Block["el"]; + + await fromEl.componentOnReady(); + await item.componentOnReady(); + this.updateBlockItems(); + + item.moveToItems = item.moveToItems.filter((moveToItem) => + this.validateMove({ + fromEl, + toEl: moveToItem.element as BlockGroup["el"], + dragEl: item, + newIndex: 0, + oldIndex: fromElItems.indexOf(item), + }), + ); + } + private handleSortReorder(event: CustomEvent): void { if (this.parentBlockGroupEl || event.defaultPrevented) { return; @@ -286,7 +304,7 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort onDragSort(detail: BlockDragDetail): void { this.setParentBlockGroup(); - this.updateBlockItems(); + this.updateBlockItemsDebounced(); this.calciteBlockGroupOrderChange.emit(detail); } @@ -299,18 +317,21 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort updateBlockChildren(event.target as HTMLSlotElement); } - private handleMove(event: CustomEvent): void { - const { moveTo } = event.detail; - - const dragEl = event.target as Block["el"]; - const fromEl = dragEl?.parentElement as BlockGroup["el"]; - const toEl = moveTo.element as BlockGroup["el"]; - const fromElItems = Array.from(fromEl.children).filter(isBlock); - const oldIndex = fromElItems.indexOf(dragEl); - const newIndex = 0; - - if (!fromEl) { - return; + private validateMove({ + fromEl, + toEl, + dragEl, + newIndex, + oldIndex, + }: { + fromEl?: BlockGroup["el"]; + toEl?: BlockGroup["el"]; + dragEl: Block["el"]; + newIndex: number; + oldIndex: number; + }): boolean { + if (!fromEl || !toEl) { + return false; } if ( @@ -322,8 +343,7 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort oldIndex, }) === false ) { - this.calciteBlockGroupMoveHalt.emit({ toEl, fromEl, dragEl, oldIndex, newIndex }); - return; + return false; } if ( @@ -335,7 +355,23 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort oldIndex, }) === false ) { - toEl.putFailed({ toEl, fromEl, dragEl, oldIndex, newIndex }); + return false; + } + + return true; + } + + private handleMove(event: CustomEvent): void { + const { moveTo } = event.detail; + + const dragEl = event.target as Block["el"]; + const fromEl = dragEl?.parentElement as BlockGroup["el"]; + const toEl = moveTo.element as BlockGroup["el"]; + const fromElItems = Array.from(fromEl.children).filter(isBlock); + const oldIndex = fromElItems.indexOf(dragEl); + const newIndex = 0; + + if (!this.validateMove({ fromEl, toEl, dragEl, newIndex, oldIndex })) { return; } @@ -345,7 +381,7 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort toEl.prepend(dragEl); - this.updateBlockItems(); + this.updateBlockItemsDebounced(); this.connectObserver(); this.calciteBlockGroupOrderChange.emit({ @@ -399,7 +435,7 @@ export class BlockGroup extends LitElement implements InteractiveComponent, Sort parentEl.insertBefore(dragEl, referenceEl); - this.updateBlockItems(); + this.updateBlockItemsDebounced(); this.connectObserver(); this.calciteBlockGroupOrderChange.emit({ diff --git a/packages/calcite-components/src/components/block/block.tsx b/packages/calcite-components/src/components/block/block.tsx index 4c09100c820..e9521c49416 100644 --- a/packages/calcite-components/src/components/block/block.tsx +++ b/packages/calcite-components/src/components/block/block.tsx @@ -211,6 +211,12 @@ export class Block extends LitElement implements InteractiveComponent, OpenClose //#region Events + /** + * + * @private + */ + calciteInternalBlockUpdateMoveToItems = createEvent({ cancelable: false }); + /** Fires when the component is requested to be closed and before the closing transition begins. */ calciteBlockBeforeClose = createEvent({ cancelable: false }); @@ -313,6 +319,7 @@ export class Block extends LitElement implements InteractiveComponent, OpenClose private handleSortHandleBeforeOpen(event: CustomEvent): void { event.stopPropagation(); this.calciteBlockSortHandleBeforeOpen.emit(); + this.calciteInternalBlockUpdateMoveToItems.emit(); } private handleSortHandleBeforeClose(event: CustomEvent): void { diff --git a/packages/calcite-components/src/components/list-item/list-item.tsx b/packages/calcite-components/src/components/list-item/list-item.tsx index 35dd6a06083..3b2df27da93 100644 --- a/packages/calcite-components/src/components/list-item/list-item.tsx +++ b/packages/calcite-components/src/components/list-item/list-item.tsx @@ -320,6 +320,12 @@ export class ListItem extends LitElement implements InteractiveComponent, Sortab */ calciteInternalListItemToggle = createEvent({ cancelable: false }); + /** + * + * @private + */ + calciteInternalListItemUpdateMoveToItems = createEvent({ cancelable: false }); + /** Fires when the close button is clicked. */ calciteListItemClose = createEvent({ cancelable: false }); @@ -455,6 +461,7 @@ export class ListItem extends LitElement implements InteractiveComponent, Sortab private handleSortHandleBeforeOpen(event: CustomEvent): void { event.stopPropagation(); this.calciteListItemSortHandleBeforeOpen.emit(); + this.calciteInternalListItemUpdateMoveToItems.emit(); } private handleSortHandleBeforeClose(event: CustomEvent): void { diff --git a/packages/calcite-components/src/components/list/list.e2e.ts b/packages/calcite-components/src/components/list/list.e2e.ts index d4e1dc9e37b..52c51246343 100755 --- a/packages/calcite-components/src/components/list/list.e2e.ts +++ b/packages/calcite-components/src/components/list/list.e2e.ts @@ -27,6 +27,7 @@ import { DEBOUNCE } from "../../utils/resources"; import { Reorder } from "../sort-handle/interfaces"; import type { ListItem } from "../list-item/list-item"; import { mockConsole } from "../../tests/utils/logging"; +import { IDS } from "../sort-handle/resources"; import { ListDragDetail } from "./interfaces"; import { CSS } from "./resources"; import type { List } from "./list"; @@ -1598,9 +1599,6 @@ describe("calcite-list", () => { endOldIndex: number; startNewIndex: number; startOldIndex: number; - moveHaltNewIndex: number; - moveHaltOldIndex: number; - moveHaltCalledTimes: number; }>; it("works using a mouse", async () => { @@ -1844,11 +1842,11 @@ describe("calcite-list", () => { it("calls canPull and canPut for move items", async () => { const page = await newE2EPage(); await page.setContent(html` - + - + @@ -1856,72 +1854,35 @@ describe("calcite-list", () => { // Workaround for page.spyOnEvent() failing due to drag event payload being serialized and there being circular JSON structures from the payload elements. See: https://github.com/Esri/calcite-design-system/issues/7643 await page.evaluate(() => { - const testWindow = window as TestWindow; - testWindow.moveHaltCalledTimes = 0; const firstLetters = document.getElementById("first-letters") as List["el"]; - - firstLetters.addEventListener("calciteListMoveHalt", (event: CustomEvent) => { - testWindow.moveHaltCalledTimes++; - testWindow.moveHaltNewIndex = event.detail.newIndex; - testWindow.moveHaltOldIndex = event.detail.oldIndex; - }); - firstLetters.canPull = ({ dragEl }) => dragEl.id === "b"; firstLetters.canPut = ({ dragEl }) => dragEl.id === "c"; }); await page.waitForChanges(); - async function clickMoveDropdownItem(id: string) { + async function getMoveItems(id: string) { const component = await page.find(`#${id}`); component.setProperty("sortHandleOpen", true); await page.waitForChanges(); - const dropdownItem = await page.find(`#${id} >>> calcite-dropdown-group:last-child calcite-dropdown-item`); - expect(dropdownItem).not.toBeNull(); - await dropdownItem.click(); - - await page.waitForChanges(); - } - - async function getResults() { - return await page.evaluate(() => { - const testWindow = window as TestWindow; - - return { - moveHaltCalledTimes: testWindow.moveHaltCalledTimes, - moveHaltOldIndex: testWindow.moveHaltOldIndex, - moveHaltNewIndex: testWindow.moveHaltNewIndex, - }; + return await findAll(page, `#${id} >>> calcite-dropdown-group#${IDS.move} calcite-dropdown-item`, { + allowEmpty: true, }); } - await clickMoveDropdownItem("a"); - let results = await getResults(); - - expect(results.moveHaltCalledTimes).toBe(1); - expect(results.moveHaltNewIndex).toBe(0); - expect(results.moveHaltOldIndex).toBe(0); - - await clickMoveDropdownItem("b"); - results = await getResults(); - - expect(results.moveHaltCalledTimes).toBe(1); - expect(results.moveHaltNewIndex).toBe(0); - expect(results.moveHaltOldIndex).toBe(0); - - await clickMoveDropdownItem("c"); - results = await getResults(); + const aMoveItems = await getMoveItems("a"); + expect(aMoveItems.length).toBe(0); - expect(results.moveHaltCalledTimes).toBe(1); - expect(results.moveHaltNewIndex).toBe(0); - expect(results.moveHaltOldIndex).toBe(0); + const bMoveItems = await getMoveItems("b"); + expect(bMoveItems.length).toBe(1); + expect(await bMoveItems[0].getProperty("label")).toBe("Second Letters"); - await clickMoveDropdownItem("d"); - results = await getResults(); + const cMoveItems = await getMoveItems("c"); + expect(cMoveItems.length).toBe(1); + expect(await cMoveItems[0].getProperty("label")).toBe("First Letters"); - expect(results.moveHaltCalledTimes).toBe(2); - expect(results.moveHaltNewIndex).toBe(0); - expect(results.moveHaltOldIndex).toBe(1); + const dMoveItems = await getMoveItems("d"); + expect(dMoveItems.length).toBe(0); }); it("reorders using a keyboard", async () => { diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index bccaecd8677..7e515157fa8 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -85,7 +85,7 @@ export class List extends LitElement implements InteractiveComponent, SortableCo mutationObserver = createObserver("mutation", () => { this.willPerformFilter = true; - this.updateListItems(); + this.updateListItemsDebounced(); }); private parentListEl: List["el"]; @@ -94,60 +94,7 @@ export class List extends LitElement implements InteractiveComponent, SortableCo private cancelable = useCancelable()(this); - private updateListItems = debounce((): void => { - this.updateGroupItems(); - - const { - selectionAppearance, - selectionMode, - interactionMode, - dragEnabled, - el, - filterEl, - moveToItems, - displayMode, - scale, - } = this; - - const items = Array.from(this.el.querySelectorAll(listItemSelector)); - - items.forEach((item) => { - item.scale = scale; - item.selectionAppearance = selectionAppearance; - item.selectionMode = selectionMode; - item.interactionMode = interactionMode; - if (item.closest(listSelector) === el) { - item.moveToItems = moveToItems.filter( - (moveToItem) => moveToItem.element !== el && !item.contains(moveToItem.element), - ); - item.dragHandle = dragEnabled; - item.displayMode = displayMode; - } - }); - - if (this.parentListEl) { - this.setUpSorting(); - return; - } - - this.listItems = items; - if (this.filterEnabled && this.willPerformFilter) { - this.willPerformFilter = false; - this.dataForFilter = this.getItemData(); - - if (filterEl) { - filterEl.items = this.dataForFilter; - this.filterAndUpdateData(); - } - } - this.visibleItems = this.listItems.filter((item) => !item.closed && !item.hidden); - this.updateFilteredItems(); - this.borderItems(); - this.focusableItems = this.filteredItems.filter((item) => !item.disabled); - this.setActiveListItem(); - this.updateSelectedItems(); - this.setUpSorting(); - }, DEBOUNCE.nextTick); + private updateListItemsDebounced = debounce(this.updateListItems, DEBOUNCE.nextTick); private visibleItems: ListItem["el"][] = []; @@ -335,17 +282,6 @@ export class List extends LitElement implements InteractiveComponent, SortableCo //#region Public Methods - /** - * Emits a `calciteListMoveHalt` event. - * - * @private - * @param dragDetail - */ - @method() - putFailed(dragDetail: ListDragDetail): void { - this.calciteListMoveHalt.emit(dragDetail); - } - /** * Sets focus on the component's first focusable element. * @@ -385,7 +321,11 @@ export class List extends LitElement implements InteractiveComponent, SortableCo /** Fires when the component's filter has changed. */ calciteListFilter = createEvent({ cancelable: false }); - /** Fires when a user attempts to move an element using the sort menu and 'canPut' or 'canPull' returns falsy. */ + /** + * Fires when a user attempts to move an element using the sort menu and 'canPut' or 'canPull' returns falsy. + * + * @deprecated No longer necessary. + */ calciteListMoveHalt = createEvent({ cancelable: false }); /** Fires when the component's item order changes. */ @@ -407,6 +347,7 @@ export class List extends LitElement implements InteractiveComponent, SortableCo ); this.listen("calciteSortHandleReorder", this.handleSortReorder); this.listen("calciteSortHandleMove", this.handleSortMove); + this.listen("calciteInternalListItemUpdateMoveToItems", this.handleUpdateMoveToItems); this.listen("calciteInternalListItemSelect", this.handleCalciteInternalListItemSelect); this.listen( "calciteInternalListItemSelectMultiple", @@ -422,11 +363,11 @@ export class List extends LitElement implements InteractiveComponent, SortableCo override connectedCallback(): void { this.connectObserver(); this.willPerformFilter = true; - this.updateListItems(); + this.updateListItemsDebounced(); this.setUpSorting(); this.setParentList(); this.setListItemGroups(); - this.cancelable.add(this.updateListItems); + this.cancelable.add(this.updateListItemsDebounced); } async load(): Promise { @@ -475,9 +416,65 @@ export class List extends LitElement implements InteractiveComponent, SortableCo //#region Private Methods + private updateListItems(): void { + this.updateGroupItems(); + + const { + selectionAppearance, + selectionMode, + interactionMode, + dragEnabled, + el, + filterEl, + moveToItems, + displayMode, + scale, + } = this; + + const items = Array.from(this.el.querySelectorAll(listItemSelector)); + + items.forEach((item) => { + item.scale = scale; + item.selectionAppearance = selectionAppearance; + item.selectionMode = selectionMode; + item.interactionMode = interactionMode; + if (item.closest(listSelector) === el) { + item.moveToItems = moveToItems.filter( + (moveToItem) => moveToItem.element !== el && !item.contains(moveToItem.element), + ); + + item.dragHandle = dragEnabled; + item.displayMode = displayMode; + } + }); + + if (this.parentListEl) { + this.setUpSorting(); + return; + } + + this.listItems = items; + if (this.filterEnabled && this.willPerformFilter) { + this.willPerformFilter = false; + this.dataForFilter = this.getItemData(); + + if (filterEl) { + filterEl.items = this.dataForFilter; + this.filterAndUpdateData(); + } + } + this.visibleItems = this.listItems.filter((item) => !item.closed && !item.hidden); + this.updateFilteredItems(); + this.borderItems(); + this.focusableItems = this.filteredItems.filter((item) => !item.disabled); + this.setActiveListItem(); + this.updateSelectedItems(); + this.setUpSorting(); + } + private handleListItemChange(): void { this.willPerformFilter = true; - this.updateListItems(); + this.updateListItemsDebounced(); } private handleCalciteListItemToggle(event: CustomEvent): void { @@ -542,6 +539,28 @@ export class List extends LitElement implements InteractiveComponent, SortableCo this.handleReorder(event); } + private async handleUpdateMoveToItems(event: CustomEvent): Promise { + event.stopPropagation(); + + const fromEl = this.el; + const fromElItems = Array.from(fromEl.children).filter(isListItem); + const item = event.target as ListItem["el"]; + + await fromEl.componentOnReady(); + await item.componentOnReady(); + this.updateListItems(); + + item.moveToItems = item.moveToItems.filter((moveToItem) => + this.validateMove({ + fromEl, + toEl: moveToItem.element as List["el"], + dragEl: item, + newIndex: 0, + oldIndex: fromElItems.indexOf(item), + }), + ); + } + private handleSortMove(event: CustomEvent): void { if (this.parentListEl || event.defaultPrevented) { return; @@ -601,7 +620,7 @@ export class List extends LitElement implements InteractiveComponent, SortableCo } event.stopPropagation(); - this.updateListItems(); + this.updateListItemsDebounced(); } private handleCalciteInternalListItemGroupDefaultSlotChange(event: CustomEvent): void { @@ -649,7 +668,7 @@ export class List extends LitElement implements InteractiveComponent, SortableCo onDragSort(detail: ListDragDetail): void { this.setParentList(); - this.updateListItems(); + this.updateListItemsDebounced(); this.calciteListOrderChange.emit(detail); } @@ -789,7 +808,7 @@ export class List extends LitElement implements InteractiveComponent, SortableCo this.filteredData = filterEl.filteredItems as ItemData[]; } - this.updateListItems(); + this.updateListItemsDebounced(); } private async filterAndUpdateData(): Promise { @@ -954,18 +973,21 @@ export class List extends LitElement implements InteractiveComponent, SortableCo } } - private handleMove(event: CustomEvent): void { - const { moveTo } = event.detail; - - const dragEl = event.target as ListItem["el"]; - const fromEl = dragEl?.parentElement as List["el"]; - const toEl = moveTo.element as List["el"]; - const fromElItems = Array.from(fromEl.children).filter(isListItem); - const oldIndex = fromElItems.indexOf(dragEl); - const newIndex = 0; - - if (!fromEl) { - return; + private validateMove({ + fromEl, + toEl, + dragEl, + newIndex, + oldIndex, + }: { + fromEl?: List["el"]; + toEl?: List["el"]; + dragEl: ListItem["el"]; + newIndex: number; + oldIndex: number; + }): boolean { + if (!fromEl || !toEl) { + return false; } if ( @@ -977,8 +999,7 @@ export class List extends LitElement implements InteractiveComponent, SortableCo oldIndex, }) === false ) { - this.calciteListMoveHalt.emit({ toEl, fromEl, dragEl, oldIndex, newIndex }); - return; + return false; } if ( @@ -990,7 +1011,23 @@ export class List extends LitElement implements InteractiveComponent, SortableCo oldIndex, }) === false ) { - toEl.putFailed({ toEl, fromEl, dragEl, oldIndex, newIndex }); + return false; + } + + return true; + } + + private handleMove(event: CustomEvent): void { + const { moveTo } = event.detail; + + const dragEl = event.target as ListItem["el"]; + const fromEl = dragEl?.parentElement as List["el"]; + const toEl = moveTo.element as List["el"]; + const fromElItems = Array.from(fromEl.children).filter(isListItem); + const oldIndex = fromElItems.indexOf(dragEl); + const newIndex = 0; + + if (!this.validateMove({ fromEl, toEl, dragEl, newIndex, oldIndex })) { return; } @@ -1000,7 +1037,7 @@ export class List extends LitElement implements InteractiveComponent, SortableCo toEl.prepend(dragEl); expandedAncestors(dragEl); - this.updateListItems(); + this.updateListItemsDebounced(); this.connectObserver(); this.calciteListOrderChange.emit({ @@ -1054,7 +1091,7 @@ export class List extends LitElement implements InteractiveComponent, SortableCo parentEl.insertBefore(dragEl, referenceEl); - this.updateListItems(); + this.updateListItemsDebounced(); this.connectObserver(); this.calciteListOrderChange.emit({ diff --git a/packages/calcite-components/src/components/sort-handle/resources.ts b/packages/calcite-components/src/components/sort-handle/resources.ts index 16fb4e6d7c8..2e39dde5d8d 100644 --- a/packages/calcite-components/src/components/sort-handle/resources.ts +++ b/packages/calcite-components/src/components/sort-handle/resources.ts @@ -21,3 +21,8 @@ export const REORDER_VALUES: Reorder[] = ["top", "up", "down", "bottom"] as cons export const SLOTS = { trigger: "trigger", }; + +export const IDS = { + move: "move", + reorder: "reorder", +}; diff --git a/packages/calcite-components/src/components/sort-handle/sort-handle.tsx b/packages/calcite-components/src/components/sort-handle/sort-handle.tsx index adf412d1e33..e5b4c0271fb 100644 --- a/packages/calcite-components/src/components/sort-handle/sort-handle.tsx +++ b/packages/calcite-components/src/components/sort-handle/sort-handle.tsx @@ -17,7 +17,7 @@ import { import { useT9n } from "../../controllers/useT9n"; import type { Dropdown } from "../dropdown/dropdown"; import T9nStrings from "./assets/t9n/messages.en.json"; -import { CSS, ICONS, REORDER_VALUES, SLOTS, SUBSTITUTIONS } from "./resources"; +import { CSS, ICONS, IDS, REORDER_VALUES, SLOTS, SUBSTITUTIONS } from "./resources"; import { MoveEventDetail, MoveTo, Reorder, ReorderEventDetail } from "./interfaces"; import { styles } from "./sort-handle.scss"; @@ -302,6 +302,7 @@ export class SortHandle extends LitElement implements InteractiveComponent { return this.hasSetInfo ? ( Group 2 + +
+
canPull & canPut
+ +
+
+
+

Block 1

+ + + + + +
+
+

Block 2

+ + + + + +
+ +
+
+
diff --git a/packages/calcite-components/src/demos/list.html b/packages/calcite-components/src/demos/list.html index 90bc53e2f34..edfdc70fa99 100644 --- a/packages/calcite-components/src/demos/list.html +++ b/packages/calcite-components/src/demos/list.html @@ -5801,6 +5801,48 @@

List

+ +
+
canPull & canPut
+ +
+
+
+

List 1

+ + + + + +
+
+

List 2

+ + + + + +
+ +
+
+