From 59f19687166b86be349a9b8dae603df5c73c6bca Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Fri, 2 Jun 2023 17:13:27 -0700 Subject: [PATCH 01/31] WIP --- src/components/list-item/list-item.tsx | 16 ++++ src/components/list/list.tsx | 73 ++++++++++++++++++- .../sortable-list/sortable-list.tsx | 47 +++++------- src/components/value-list/value-list.tsx | 44 ++++++----- src/utils/sortableComponent.ts | 65 +++++++++++++++-- 5 files changed, 185 insertions(+), 60 deletions(-) diff --git a/src/components/list-item/list-item.tsx b/src/components/list-item/list-item.tsx index c5df924156f..f2b29ac9775 100644 --- a/src/components/list-item/list-item.tsx +++ b/src/components/list-item/list-item.tsx @@ -91,6 +91,13 @@ export class ListItem */ @Prop({ reflect: true }) disabled = false; + /** + * When `true`, the component is sortable via a draggable button. + * + * @internal + */ + @Prop() dragEnabled = false; + /** * The label text of the component. Displays above the description text. */ @@ -333,6 +340,14 @@ export class ListItem ); } + renderDragHandle(): VNode { + return this.dragEnabled ? ( + + + + ) : null; + } + renderOpen(): VNode { const { el, open, openable, parentListEl } = this; const dir = getElementDir(el); @@ -512,6 +527,7 @@ export class ListItem // eslint-disable-next-line react/jsx-sort-props ref={(el) => (this.containerEl = el)} > + {this.renderDragHandle()} {this.renderSelected()} {this.renderOpen()} {this.renderActionsStart()} diff --git a/src/components/list/list.tsx b/src/components/list/list.tsx index 490b777d9a0..0937322b493 100755 --- a/src/components/list/list.tsx +++ b/src/components/list/list.tsx @@ -11,6 +11,7 @@ import { VNode, Watch } from "@stencil/core"; +import Sortable from "sortablejs"; import { debounce } from "lodash-es"; import { toAriaBoolean } from "../../utils/dom"; import { InteractiveComponent, updateHostInteraction } from "../../utils/interactive"; @@ -20,6 +21,11 @@ import { ItemData } from "../list-item/interfaces"; import { MAX_COLUMNS } from "../list-item/resources"; import { getListItemChildren, updateListItemChildren } from "../list-item/utils"; import { CSS, debounceTimeout, SelectionAppearance } from "./resources"; +import { + connectSortableComponent, + disconnectSortableComponent, + SortableComponent +} from "../../utils/sortableComponent"; const listItemSelector = "calcite-list-item"; const parentSelector = "calcite-list-item-group, calcite-list-item"; @@ -41,7 +47,7 @@ import { styleUrl: "list.scss", shadow: true }) -export class List implements InteractiveComponent, LoadableComponent { +export class List implements InteractiveComponent, LoadableComponent, SortableComponent { // -------------------------------------------------------------------------- // // Properties @@ -53,6 +59,18 @@ export class List implements InteractiveComponent, LoadableComponent { */ @Prop({ reflect: true }) disabled = false; + /** + * When `true`, `calcite-list-item`s are sortable via a draggable button. + */ + @Prop({ reflect: true }) dragEnabled = false; + + /** + * The list's group identifier. + * + * To drag elements from one list into another, both lists must have the same group value. + */ + @Prop({ reflect: true }) group?: string; + /** * When `true`, an input appears at the top of the component that can be used by end users to filter `calcite-list-item`s. */ @@ -124,6 +142,7 @@ export class List implements InteractiveComponent, LoadableComponent { */ @Prop({ reflect: true }) selectionAppearance: SelectionAppearance = "icon"; + @Watch("dragEnabled") @Watch("selectionMode") @Watch("selectionAppearance") handleSelectionAppearanceChange(): void { @@ -146,6 +165,11 @@ export class List implements InteractiveComponent, LoadableComponent { */ @Event({ cancelable: false }) calciteListFilter: EventEmitter; + /** + * Emitted when the order of the list has changed. + */ + @Event({ cancelable: false }) calciteListOrderChange: EventEmitter; + @Listen("calciteInternalFocusPreviousItem") handleCalciteInternalFocusPreviousItem(event: CustomEvent): void { event.stopPropagation(); @@ -201,12 +225,14 @@ export class List implements InteractiveComponent, LoadableComponent { //-------------------------------------------------------------------------- connectedCallback(): void { - this.mutationObserver?.observe(this.el, { childList: true, subtree: true }); + this.connectObserver(); this.updateListItems(); + this.setUpSorting(); } disconnectedCallback(): void { - this.mutationObserver?.disconnect(); + this.disconnectObserver(); + disconnectSortableComponent(this); } componentWillLoad(): void { @@ -237,6 +263,12 @@ export class List implements InteractiveComponent, LoadableComponent { @Element() el: HTMLCalciteListElement; + sortable: Sortable; + + handleSelector = "calcite-handle"; + + dragSelector: "calcite-list-item"; + listItems: HTMLCalciteListItemElement[] = []; enabledListItems: HTMLCalciteListItemElement[] = []; @@ -318,6 +350,37 @@ export class List implements InteractiveComponent, LoadableComponent { // // -------------------------------------------------------------------------- + connectObserver(): void { + this.mutationObserver?.observe(this.el, { childList: true, subtree: true }); + } + + disconnectObserver(): void { + this.mutationObserver?.disconnect(); + } + + setUpSorting(): void { + const { dragEnabled } = this; + + if (!dragEnabled) { + return; + } + + connectSortableComponent(this); + } + + onDragStart(): void { + this.disconnectObserver(); + } + + onDragEnd(): void { + this.connectObserver(); + } + + onDragUpdate(): void { + this.updateListItems(); + this.calciteListOrderChange.emit(); + } + handleDefaultSlotChange = (event: Event): void => { updateListItemChildren(getListItemChildren(event)); }; @@ -415,11 +478,12 @@ export class List implements InteractiveComponent, LoadableComponent { }; private updateListItems = debounce((emit = false): void => { - const { selectionAppearance, selectionMode } = this; + const { selectionAppearance, selectionMode, dragEnabled } = this; const items = this.queryListItems(); items.forEach((item) => { item.selectionAppearance = selectionAppearance; item.selectionMode = selectionMode; + item.dragEnabled = dragEnabled; }); this.listItems = items; if (this.filterEnabled) { @@ -432,6 +496,7 @@ export class List implements InteractiveComponent, LoadableComponent { this.enabledListItems = items.filter((item) => !item.disabled && !item.closed); this.setActiveListItem(); this.updateSelectedItems(emit); + this.setUpSorting(); }, debounceTimeout); queryListItems = (): HTMLCalciteListItemElement[] => { diff --git a/src/components/sortable-list/sortable-list.tsx b/src/components/sortable-list/sortable-list.tsx index 6eb6ace2e11..415e9eced7d 100644 --- a/src/components/sortable-list/sortable-list.tsx +++ b/src/components/sortable-list/sortable-list.tsx @@ -8,9 +8,7 @@ import { CSS } from "./resources"; import { connectSortableComponent, disconnectSortableComponent, - onSortingStart, - SortableComponent, - onSortingEnd + SortableComponent } from "../../utils/sortableComponent"; import { focusElement } from "../../utils/dom"; @@ -77,6 +75,10 @@ export class SortableList implements InteractiveComponent, SortableComponent { sortable: Sortable; + dragEnabled = true; + + draggableSelector = "calcite-value-list-item"; + // -------------------------------------------------------------------------- // // Lifecycle @@ -119,6 +121,19 @@ export class SortableList implements InteractiveComponent, SortableComponent { // // -------------------------------------------------------------------------- + onDragStart(): void { + this.endObserving(); + } + + onDragEnd(): void { + this.beginObserving(); + } + + onDragUpdate(): void { + this.items = Array.from(this.el.children); + this.calciteListOrderChange.emit(); + } + handleNudgeEvent(event: CustomEvent): void { const { direction } = event.detail; @@ -170,33 +185,9 @@ export class SortableList implements InteractiveComponent, SortableComponent { } setUpSorting(): void { - const { dragSelector, group, handleSelector } = this; - this.items = Array.from(this.el.children); - const sortableOptions: Sortable.Options = { - dataIdAttr: "id", - group, - handle: handleSelector, - onStart: () => { - this.endObserving(); - onSortingStart(this); - }, - onEnd: () => { - onSortingEnd(this); - this.beginObserving(); - }, - onUpdate: () => { - this.items = Array.from(this.el.children); - this.calciteListOrderChange.emit(); - } - }; - - if (dragSelector) { - sortableOptions.draggable = dragSelector; - } - - connectSortableComponent(this, sortableOptions); + connectSortableComponent(this); } beginObserving(): void { diff --git a/src/components/value-list/value-list.tsx b/src/components/value-list/value-list.tsx index 18ee29427a8..e20c748c8d2 100644 --- a/src/components/value-list/value-list.tsx +++ b/src/components/value-list/value-list.tsx @@ -59,9 +59,7 @@ import { getHandleAndItemElement, getScreenReaderText } from "./utils"; import { connectSortableComponent, disconnectSortableComponent, - onSortingStart, - SortableComponent, - onSortingEnd + SortableComponent } from "../../utils/sortableComponent"; import { focusElement } from "../../utils/dom"; @@ -213,6 +211,10 @@ export class ValueList< assistiveTextEl: HTMLSpanElement; + handleSelector = `.${CSS.handle}`; + + dragSelector = "calcite-value-list-item"; + // -------------------------------------------------------------------------- // // Lifecycle @@ -304,6 +306,20 @@ export class ValueList< // // -------------------------------------------------------------------------- + onDragStart(): void { + cleanUpObserver.call(this); + } + + onDragEnd(): void { + initializeObserver.call(this); + } + + onDragUpdate(): void { + this.items = Array.from(this.el.querySelectorAll("calcite-value-list-item")); + const values = this.items.map((item) => item.value); + this.calciteListOrderChange.emit(values); + } + getItems(): ItemElement[] { return Array.from(this.el.querySelectorAll("calcite-value-list-item")); } @@ -328,31 +344,13 @@ export class ValueList< }; setUpSorting(): void { - const { dragEnabled, group } = this; + const { dragEnabled } = this; if (!dragEnabled) { return; } - connectSortableComponent(this, { - dataIdAttr: "id", - group, - handle: `.${CSS.handle}`, - draggable: "calcite-value-list-item", - onStart: () => { - cleanUpObserver.call(this); - onSortingStart(this); - }, - onEnd: () => { - onSortingEnd(this); - initializeObserver.call(this); - }, - onUpdate: () => { - this.items = Array.from(this.el.querySelectorAll("calcite-value-list-item")); - const values = this.items.map((item) => item.value); - this.calciteListOrderChange.emit(values); - } - }); + connectSortableComponent(this); } deselectRemovedItems = deselectRemovedItems.bind(this); diff --git a/src/utils/sortableComponent.ts b/src/utils/sortableComponent.ts index 15843b69167..0be21f59587 100644 --- a/src/utils/sortableComponent.ts +++ b/src/utils/sortableComponent.ts @@ -12,19 +12,53 @@ export interface SortableComponent { */ readonly el: HTMLElement; + /** + * When `true`, dragging is enabled. + */ + dragEnabled: boolean; + + /** + * Specifies which items inside the element should be draggable. + */ + dragSelector?: string; + + /** + * The list's group identifier. + */ + group?: string; + + /** + * The selector for the handle elements. + */ + handleSelector: string; + /** * The Sortable instance. */ sortable: Sortable; + + /** + * + */ + onDragStart: (event: Sortable.SortableEvent) => void; + + /** + * + */ + onDragEnd: (event: Sortable.SortableEvent) => void; + + /** + * + */ + onDragUpdate: (event: Sortable.SortableEvent) => void; } /** * Helper to keep track of a SortableComponent. This should be called in the `connectedCallback` lifecycle method as well as any other method necessary to rebuild the sortable instance. * * @param {SortableComponent} component - The sortable component. - * @param {SortableComponent} [options] - Sortable options object. */ -export function connectSortableComponent(component: SortableComponent, options?: Sortable.Options): void { +export function connectSortableComponent(component: SortableComponent): void { disconnectSortableComponent(component); sortableComponentSet.add(component); @@ -32,7 +66,28 @@ export function connectSortableComponent(component: SortableComponent, options?: return; } - component.sortable = Sortable.create(component.el, options); + const sortableOptions: Sortable.Options = { + dataIdAttr: "id", + group: component.group, + handle: component.handleSelector, + onStart: (event) => { + onSortingStart(component); + component.onDragStart(event); + }, + onEnd: (event) => { + onSortingEnd(component); + component.onDragEnd(event); + }, + onUpdate: (event) => { + component.onDragUpdate(event); + } + }; + + if (component.dragSelector) { + sortableOptions.draggable = component.dragSelector; + } + + component.sortable = Sortable.create(component.el, sortableOptions); } /** @@ -62,7 +117,7 @@ function getNestedSortableComponents(activeComponent: SortableComponent): Sortab * * @param {SortableComponent} activeComponent - The active sortable component. */ -export function onSortingStart(activeComponent: SortableComponent): void { +function onSortingStart(activeComponent: SortableComponent): void { getNestedSortableComponents(activeComponent).forEach((component) => inactiveSortableComponentSet.add(component)); } @@ -71,6 +126,6 @@ export function onSortingStart(activeComponent: SortableComponent): void { * * @param {SortableComponent} activeComponent - The active sortable component. */ -export function onSortingEnd(activeComponent: SortableComponent): void { +function onSortingEnd(activeComponent: SortableComponent): void { getNestedSortableComponents(activeComponent).forEach((component) => inactiveSortableComponentSet.delete(component)); } From ceea05122cc1451012855a1b5b2cdbc8a3eb498e Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Fri, 2 Jun 2023 17:16:16 -0700 Subject: [PATCH 02/31] feat(list): Add support for sorting and dragging items. #6554 --- src/utils/sortableComponent.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/utils/sortableComponent.ts b/src/utils/sortableComponent.ts index 0be21f59587..3a75010dd7b 100644 --- a/src/utils/sortableComponent.ts +++ b/src/utils/sortableComponent.ts @@ -66,10 +66,13 @@ export function connectSortableComponent(component: SortableComponent): void { return; } + const dataIdAttr = "id"; + const { group, handleSelector: handle, dragSelector: draggable } = component; + const sortableOptions: Sortable.Options = { - dataIdAttr: "id", - group: component.group, - handle: component.handleSelector, + dataIdAttr, + group, + handle, onStart: (event) => { onSortingStart(component); component.onDragStart(event); @@ -83,8 +86,8 @@ export function connectSortableComponent(component: SortableComponent): void { } }; - if (component.dragSelector) { - sortableOptions.draggable = component.dragSelector; + if (draggable) { + sortableOptions.draggable = draggable; } component.sortable = Sortable.create(component.el, sortableOptions); From 0478bb04d9a49cae7b98bed86641eebfc9cbb15d Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Fri, 2 Jun 2023 17:30:46 -0700 Subject: [PATCH 03/31] cleanup --- src/components/list/list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/list/list.tsx b/src/components/list/list.tsx index 0937322b493..023d2fe14ca 100755 --- a/src/components/list/list.tsx +++ b/src/components/list/list.tsx @@ -267,7 +267,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo handleSelector = "calcite-handle"; - dragSelector: "calcite-list-item"; + dragSelector = "calcite-list-item"; listItems: HTMLCalciteListItemElement[] = []; From befbfea856330150aa1fe21e5b8b88e2dac2062b Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Tue, 6 Jun 2023 17:00:25 -0700 Subject: [PATCH 04/31] cleanup --- .../calcite-components/src/components/list/list.tsx | 7 ++----- .../src/utils/sortableComponent.ts | 13 ++++--------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 023d2fe14ca..1bcfde68e78 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -76,11 +76,6 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo */ @Prop({ reflect: true }) filterEnabled = false; - @Watch("filterEnabled") - handleFilterEnabledChange(): void { - this.updateListItems(); - } - /** * The currently filtered `calcite-list-item`s. * @@ -142,6 +137,8 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo */ @Prop({ reflect: true }) selectionAppearance: SelectionAppearance = "icon"; + @Watch("filterEnabled") + @Watch("group") @Watch("dragEnabled") @Watch("selectionMode") @Watch("selectionAppearance") diff --git a/packages/calcite-components/src/utils/sortableComponent.ts b/packages/calcite-components/src/utils/sortableComponent.ts index 3a75010dd7b..a82207119b5 100644 --- a/packages/calcite-components/src/utils/sortableComponent.ts +++ b/packages/calcite-components/src/utils/sortableComponent.ts @@ -69,9 +69,10 @@ export function connectSortableComponent(component: SortableComponent): void { const dataIdAttr = "id"; const { group, handleSelector: handle, dragSelector: draggable } = component; - const sortableOptions: Sortable.Options = { + component.sortable = Sortable.create(component.el, { dataIdAttr, - group, + ...(draggable && { draggable }), + ...(group && { group }), handle, onStart: (event) => { onSortingStart(component); @@ -84,13 +85,7 @@ export function connectSortableComponent(component: SortableComponent): void { onUpdate: (event) => { component.onDragUpdate(event); } - }; - - if (draggable) { - sortableOptions.draggable = draggable; - } - - component.sortable = Sortable.create(component.el, sortableOptions); + }); } /** From bf8ecada5b6f009ae16114ca8b3ca6bd89862993 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Wed, 7 Jun 2023 10:59:59 -0700 Subject: [PATCH 05/31] add pull/put events --- .../src/utils/sortableComponent.ts | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/utils/sortableComponent.ts b/packages/calcite-components/src/utils/sortableComponent.ts index a82207119b5..64eecabf336 100644 --- a/packages/calcite-components/src/utils/sortableComponent.ts +++ b/packages/calcite-components/src/utils/sortableComponent.ts @@ -3,6 +3,12 @@ import { containsCrossShadowBoundary } from "./dom"; const sortableComponentSet = new Set(); const inactiveSortableComponentSet = new WeakSet(); +interface CanDragEvent { + toEl: HTMLElement; + fromEl: HTMLElement; + dragEl: HTMLElement; +} + /** * Defines interface for components with sorting functionality. */ @@ -51,6 +57,17 @@ export interface SortableComponent { * */ onDragUpdate: (event: Sortable.SortableEvent) => void; + + /** + * + */ + onCanPull?: (event: CanDragEvent) => boolean; + + // todo + /** + * + */ + onCanPut?: (event: CanDragEvent) => boolean; } /** @@ -71,8 +88,18 @@ export function connectSortableComponent(component: SortableComponent): void { component.sortable = Sortable.create(component.el, { dataIdAttr, - ...(draggable && { draggable }), - ...(group && { group }), + ...(!!draggable && { draggable }), + ...(!!group && { + group: { + name: group, + ...(!!component.onCanPull && { + pull: (to, from, dragEl) => component.onCanPull({ toEl: to.el, fromEl: from.el, dragEl }) + }), + ...(!!component.onCanPut && { + put: (to, from, dragEl) => component.onCanPut({ toEl: to.el, fromEl: from.el, dragEl }) + }) + } + }), handle, onStart: (event) => { onSortingStart(component); From 84502efa00f16209fc0daa75697aec8188e0327b Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Wed, 7 Jun 2023 14:16:43 -0700 Subject: [PATCH 06/31] feat(list): Add support for sorting and dragging items. #6554 --- .../src/components/list-item/list-item.tsx | 2 +- .../src/components/list-item/resources.ts | 5 +++-- .../src/components/list/list.tsx | 2 +- .../components/sortable-list/sortable-list.tsx | 2 +- .../src/components/value-list/value-list.tsx | 2 +- .../src/utils/sortableComponent.ts | 17 ++++++++--------- 6 files changed, 15 insertions(+), 15 deletions(-) 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 f2b29ac9775..2ebb563a389 100644 --- a/packages/calcite-components/src/components/list-item/list-item.tsx +++ b/packages/calcite-components/src/components/list-item/list-item.tsx @@ -342,7 +342,7 @@ export class ListItem renderDragHandle(): VNode { return this.dragEnabled ? ( - + ) : null; diff --git a/packages/calcite-components/src/components/list-item/resources.ts b/packages/calcite-components/src/components/list-item/resources.ts index 1eeacbfdd6f..f37cbe09cde 100644 --- a/packages/calcite-components/src/components/list-item/resources.ts +++ b/packages/calcite-components/src/components/list-item/resources.ts @@ -16,7 +16,8 @@ export const CSS = { contentEnd: "content-end", actionsEnd: "actions-end", selectionContainer: "selection-container", - openContainer: "open-container" + openContainer: "open-container", + dragContainer: "drag-container" }; export const SLOTS = { @@ -27,7 +28,7 @@ export const SLOTS = { actionsEnd: "actions-end" }; -export const MAX_COLUMNS = 5; +export const MAX_COLUMNS = 0; export const ICONS = { selectedMultiple: "check-circle-f", diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 1bcfde68e78..3d95fb69583 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -373,7 +373,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo this.connectObserver(); } - onDragUpdate(): void { + onDragSort(): void { this.updateListItems(); this.calciteListOrderChange.emit(); } diff --git a/packages/calcite-components/src/components/sortable-list/sortable-list.tsx b/packages/calcite-components/src/components/sortable-list/sortable-list.tsx index 415e9eced7d..7d7d13bda2e 100644 --- a/packages/calcite-components/src/components/sortable-list/sortable-list.tsx +++ b/packages/calcite-components/src/components/sortable-list/sortable-list.tsx @@ -129,7 +129,7 @@ export class SortableList implements InteractiveComponent, SortableComponent { this.beginObserving(); } - onDragUpdate(): void { + onDragSort(): void { this.items = Array.from(this.el.children); this.calciteListOrderChange.emit(); } diff --git a/packages/calcite-components/src/components/value-list/value-list.tsx b/packages/calcite-components/src/components/value-list/value-list.tsx index e20c748c8d2..63bd7d57779 100644 --- a/packages/calcite-components/src/components/value-list/value-list.tsx +++ b/packages/calcite-components/src/components/value-list/value-list.tsx @@ -314,7 +314,7 @@ export class ValueList< initializeObserver.call(this); } - onDragUpdate(): void { + onDragSort(): void { this.items = Array.from(this.el.querySelectorAll("calcite-value-list-item")); const values = this.items.map((item) => item.value); this.calciteListOrderChange.emit(values); diff --git a/packages/calcite-components/src/utils/sortableComponent.ts b/packages/calcite-components/src/utils/sortableComponent.ts index 64eecabf336..83d731a2a89 100644 --- a/packages/calcite-components/src/utils/sortableComponent.ts +++ b/packages/calcite-components/src/utils/sortableComponent.ts @@ -44,28 +44,27 @@ export interface SortableComponent { sortable: Sortable; /** - * + * Element dragging started. */ onDragStart: (event: Sortable.SortableEvent) => void; /** - * + * Element dragging ended. */ onDragEnd: (event: Sortable.SortableEvent) => void; /** - * + * Called by any change to the list (add / update / remove). */ - onDragUpdate: (event: Sortable.SortableEvent) => void; + onDragSort: (event: Sortable.SortableEvent) => void; /** - * + * Ability to move from the list. */ onCanPull?: (event: CanDragEvent) => boolean; - // todo /** - * + * Whether elements can be added from other lists. */ onCanPut?: (event: CanDragEvent) => boolean; } @@ -109,8 +108,8 @@ export function connectSortableComponent(component: SortableComponent): void { onSortingEnd(component); component.onDragEnd(event); }, - onUpdate: (event) => { - component.onDragUpdate(event); + onSort: (event) => { + component.onDragSort(event); } }); } From 88a9258b716dd3ae8c5e223bda23d41bff5b53b0 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Wed, 7 Jun 2023 14:19:52 -0700 Subject: [PATCH 07/31] add comment --- .../calcite-components/src/components/list-item/resources.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/calcite-components/src/components/list-item/resources.ts b/packages/calcite-components/src/components/list-item/resources.ts index f37cbe09cde..bfeecaacea9 100644 --- a/packages/calcite-components/src/components/list-item/resources.ts +++ b/packages/calcite-components/src/components/list-item/resources.ts @@ -28,6 +28,7 @@ export const SLOTS = { actionsEnd: "actions-end" }; +// Set to zero to extend until the end of the table section. export const MAX_COLUMNS = 0; export const ICONS = { From 9a7b80dd60e4b107c90bc99d7c8baef4d0310832 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Wed, 7 Jun 2023 15:20:37 -0700 Subject: [PATCH 08/31] cleanup --- .../src/components/list-item/list-item.tsx | 25 +++++++++++++++--- .../src/components/list/list.tsx | 18 +++++++++++-- .../sortable-list/sortable-list.tsx | 11 ++++++++ .../src/components/value-list/value-list.tsx | 11 ++++++++ .../src/utils/sortableComponent.ts | 26 +++++++++---------- 5 files changed, 73 insertions(+), 18 deletions(-) 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 2ebb563a389..a48f6de14a8 100644 --- a/packages/calcite-components/src/components/list-item/list-item.tsx +++ b/packages/calcite-components/src/components/list-item/list-item.tsx @@ -75,6 +75,12 @@ export class ListItem } } + /** + * When `true`, the component's child list-items are sortable via a draggable button. + * + */ + @Prop() childrenDragEnabled = false; + /** When `true`, a close button is added to the component. */ @Prop({ reflect: true }) closable = false; @@ -92,11 +98,18 @@ export class ListItem @Prop({ reflect: true }) disabled = false; /** - * When `true`, the component is sortable via a draggable button. + * When `true`, the component displays a draggable button. + * + * @internal + */ + @Prop() dragHandle = false; + + /** + * The list's group identifier. * * @internal */ - @Prop() dragEnabled = false; + @Prop({ reflect: true }) group?: string; /** * The label text of the component. Displays above the description text. @@ -257,6 +270,12 @@ export class ListItem actionsEndEl: HTMLTableCellElement; + // -------------------------------------------------------------------------- + // + // Lifecycle + // + // -------------------------------------------------------------------------- + connectedCallback(): void { connectLocalized(this); connectMessages(this); @@ -341,7 +360,7 @@ export class ListItem } renderDragHandle(): VNode { - return this.dragEnabled ? ( + return this.dragHandle ? ( diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 3d95fb69583..c4e6cc1edde 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -22,6 +22,7 @@ import { MAX_COLUMNS } from "../list-item/resources"; import { getListItemChildren, updateListItemChildren } from "../list-item/utils"; import { CSS, debounceTimeout, SelectionAppearance } from "./resources"; import { + CanDragEvent, connectSortableComponent, disconnectSortableComponent, SortableComponent @@ -37,6 +38,8 @@ import { setUpLoadableComponent } from "../../utils/loadable"; +// todo: keyboard nav sorting + /** * A general purpose list that enables users to construct list items that conform to Calcite styling. * @@ -59,6 +62,16 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo */ @Prop({ reflect: true }) disabled = false; + /** + * When provided, the method will be called to determine whether the element can move from the list. + */ + @Prop() dragCanPull: (event: CanDragEvent) => boolean; + + /** + * When provided, the method will be called to determine whether the element can be added from another list. + */ + @Prop() dragCanPut: (event: CanDragEvent) => boolean; + /** * When `true`, `calcite-list-item`s are sortable via a draggable button. */ @@ -475,12 +488,13 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo }; private updateListItems = debounce((emit = false): void => { - const { selectionAppearance, selectionMode, dragEnabled } = this; + const { selectionAppearance, selectionMode, dragEnabled, group } = this; const items = this.queryListItems(); items.forEach((item) => { item.selectionAppearance = selectionAppearance; item.selectionMode = selectionMode; - item.dragEnabled = dragEnabled; + item.dragHandle = dragEnabled; // todo: get from parent. + item.group = group; }); this.listItems = items; if (this.filterEnabled) { diff --git a/packages/calcite-components/src/components/sortable-list/sortable-list.tsx b/packages/calcite-components/src/components/sortable-list/sortable-list.tsx index 7d7d13bda2e..36e1407c7d8 100644 --- a/packages/calcite-components/src/components/sortable-list/sortable-list.tsx +++ b/packages/calcite-components/src/components/sortable-list/sortable-list.tsx @@ -6,6 +6,7 @@ import { HandleNudge } from "../handle/interfaces"; import { Layout } from "../interfaces"; import { CSS } from "./resources"; import { + CanDragEvent, connectSortableComponent, disconnectSortableComponent, SortableComponent @@ -27,6 +28,16 @@ export class SortableList implements InteractiveComponent, SortableComponent { // // -------------------------------------------------------------------------- + /** + * When provided, the method will be called to determine whether the element can move from the list. + */ + @Prop() dragCanPull: (event: CanDragEvent) => boolean; + + /** + * When provided, the method will be called to determine whether the element can be added from another list. + */ + @Prop() dragCanPut: (event: CanDragEvent) => boolean; + /** * Specifies which items inside the element should be draggable. */ diff --git a/packages/calcite-components/src/components/value-list/value-list.tsx b/packages/calcite-components/src/components/value-list/value-list.tsx index 63bd7d57779..46e9642ee53 100644 --- a/packages/calcite-components/src/components/value-list/value-list.tsx +++ b/packages/calcite-components/src/components/value-list/value-list.tsx @@ -57,6 +57,7 @@ import { ValueListMessages } from "./assets/value-list/t9n"; import { CSS, ICON_TYPES } from "./resources"; import { getHandleAndItemElement, getScreenReaderText } from "./utils"; import { + CanDragEvent, connectSortableComponent, disconnectSortableComponent, SortableComponent @@ -94,6 +95,16 @@ export class ValueList< */ @Prop({ reflect: true }) disabled = false; + /** + * When provided, the method will be called to determine whether the element can move from the list. + */ + @Prop() dragCanPull: (event: CanDragEvent) => boolean; + + /** + * When provided, the method will be called to determine whether the element can be added from another list. + */ + @Prop() dragCanPut: (event: CanDragEvent) => boolean; + /** * When `true`, `calcite-value-list-item`s are sortable via a draggable button. */ diff --git a/packages/calcite-components/src/utils/sortableComponent.ts b/packages/calcite-components/src/utils/sortableComponent.ts index 83d731a2a89..6f36c6e71a0 100644 --- a/packages/calcite-components/src/utils/sortableComponent.ts +++ b/packages/calcite-components/src/utils/sortableComponent.ts @@ -3,7 +3,7 @@ import { containsCrossShadowBoundary } from "./dom"; const sortableComponentSet = new Set(); const inactiveSortableComponentSet = new WeakSet(); -interface CanDragEvent { +export interface CanDragEvent { toEl: HTMLElement; fromEl: HTMLElement; dragEl: HTMLElement; @@ -44,14 +44,14 @@ export interface SortableComponent { sortable: Sortable; /** - * Element dragging started. + * Whether the element can move from the list. */ - onDragStart: (event: Sortable.SortableEvent) => void; + dragCanPull: (event: CanDragEvent) => boolean; /** - * Element dragging ended. + * Whether the element can be added from another list. */ - onDragEnd: (event: Sortable.SortableEvent) => void; + dragCanPut: (event: CanDragEvent) => boolean; /** * Called by any change to the list (add / update / remove). @@ -59,14 +59,14 @@ export interface SortableComponent { onDragSort: (event: Sortable.SortableEvent) => void; /** - * Ability to move from the list. + * Element dragging started. */ - onCanPull?: (event: CanDragEvent) => boolean; + onDragStart?: (event: Sortable.SortableEvent) => void; /** - * Whether elements can be added from other lists. + * Element dragging ended. */ - onCanPut?: (event: CanDragEvent) => boolean; + onDragEnd?: (event: Sortable.SortableEvent) => void; } /** @@ -91,11 +91,11 @@ export function connectSortableComponent(component: SortableComponent): void { ...(!!group && { group: { name: group, - ...(!!component.onCanPull && { - pull: (to, from, dragEl) => component.onCanPull({ toEl: to.el, fromEl: from.el, dragEl }) + ...(!!component.dragCanPull && { + pull: (to, from, dragEl) => component.dragCanPull({ toEl: to.el, fromEl: from.el, dragEl }) }), - ...(!!component.onCanPut && { - put: (to, from, dragEl) => component.onCanPut({ toEl: to.el, fromEl: from.el, dragEl }) + ...(!!component.dragCanPut && { + put: (to, from, dragEl) => component.dragCanPut({ toEl: to.el, fromEl: from.el, dragEl }) }) } }), From 5ef19b25f230320a1da64e3c60c4693c3c460457 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Wed, 7 Jun 2023 15:33:32 -0700 Subject: [PATCH 09/31] cleanup --- packages/calcite-components/src/components/list/list.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index c4e6cc1edde..5645ff8acba 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -39,6 +39,7 @@ import { } from "../../utils/loadable"; // todo: keyboard nav sorting +// todo: child sorting /** * A general purpose list that enables users to construct list items that conform to Calcite styling. @@ -493,7 +494,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo items.forEach((item) => { item.selectionAppearance = selectionAppearance; item.selectionMode = selectionMode; - item.dragHandle = dragEnabled; // todo: get from parent. + item.dragHandle = dragEnabled; item.group = group; }); this.listItems = items; From ccc748e20124fdcd502f7bdc0ce76578760105e2 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Wed, 7 Jun 2023 16:31:09 -0700 Subject: [PATCH 10/31] drag handle alignment --- .../calcite-components/src/components/list-item/list-item.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/calcite-components/src/components/list-item/list-item.scss b/packages/calcite-components/src/components/list-item/list-item.scss index b7ca72bbe77..ad0dc09d90f 100755 --- a/packages/calcite-components/src/components/list-item/list-item.scss +++ b/packages/calcite-components/src/components/list-item/list-item.scss @@ -113,6 +113,7 @@ td:focus { .content-start, .content-end, .selection-container, +.drag-container, .open-container { @apply flex items-center; } From 8ab64febc4401f83050cbc5bc0d58eed902837c1 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Wed, 7 Jun 2023 17:04:19 -0700 Subject: [PATCH 11/31] cleanup --- .../src/components/list-item/list-item.tsx | 13 ------------- .../calcite-components/src/components/list/list.tsx | 3 +-- .../src/components/sortable-list/sortable-list.tsx | 2 -- 3 files changed, 1 insertion(+), 17 deletions(-) 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 a48f6de14a8..f2285ff2444 100644 --- a/packages/calcite-components/src/components/list-item/list-item.tsx +++ b/packages/calcite-components/src/components/list-item/list-item.tsx @@ -75,12 +75,6 @@ export class ListItem } } - /** - * When `true`, the component's child list-items are sortable via a draggable button. - * - */ - @Prop() childrenDragEnabled = false; - /** When `true`, a close button is added to the component. */ @Prop({ reflect: true }) closable = false; @@ -104,13 +98,6 @@ export class ListItem */ @Prop() dragHandle = false; - /** - * The list's group identifier. - * - * @internal - */ - @Prop({ reflect: true }) group?: string; - /** * The label text of the component. Displays above the description text. */ diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 5645ff8acba..c772753c2e2 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -489,13 +489,12 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo }; private updateListItems = debounce((emit = false): void => { - const { selectionAppearance, selectionMode, dragEnabled, group } = this; + const { selectionAppearance, selectionMode, dragEnabled } = this; const items = this.queryListItems(); items.forEach((item) => { item.selectionAppearance = selectionAppearance; item.selectionMode = selectionMode; item.dragHandle = dragEnabled; - item.group = group; }); this.listItems = items; if (this.filterEnabled) { diff --git a/packages/calcite-components/src/components/sortable-list/sortable-list.tsx b/packages/calcite-components/src/components/sortable-list/sortable-list.tsx index 36e1407c7d8..41a551203a7 100644 --- a/packages/calcite-components/src/components/sortable-list/sortable-list.tsx +++ b/packages/calcite-components/src/components/sortable-list/sortable-list.tsx @@ -88,8 +88,6 @@ export class SortableList implements InteractiveComponent, SortableComponent { dragEnabled = true; - draggableSelector = "calcite-value-list-item"; - // -------------------------------------------------------------------------- // // Lifecycle From a2acfdbf55a0bccfcca686764be24ebb14363299 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Thu, 15 Jun 2023 16:36:51 -0700 Subject: [PATCH 12/31] wip --- .../calcite-components/src/components/list-item/utils.ts | 7 ++++++- packages/calcite-components/src/components/list/list.tsx | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/list-item/utils.ts b/packages/calcite-components/src/components/list-item/utils.ts index 943236d9724..5ba8940cf0f 100644 --- a/packages/calcite-components/src/components/list-item/utils.ts +++ b/packages/calcite-components/src/components/list-item/utils.ts @@ -1,5 +1,6 @@ import { Build } from "@stencil/core"; +const listSelector = "calcite-list"; const listItemGroupSelector = "calcite-list-item-group"; const listItemSelector = "calcite-list-item"; @@ -16,7 +17,11 @@ export function getListItemChildren(event: Event): HTMLCalciteListItemElement[] el?.matches(listItemSelector) ) as HTMLCalciteListItemElement[]; - return [...listItemGroupChildren, ...listItemChildren]; + const listItemListChildren = assignedElements.filter((el) => + el?.matches(listSelector) + ) as HTMLCalciteListItemElement[]; + + return [...listItemListChildren, ...listItemGroupChildren, ...listItemChildren]; } export function updateListItemChildren(listItemChildren: HTMLCalciteListItemElement[]): void { diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index c772753c2e2..57a04453806 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -40,6 +40,8 @@ import { // todo: keyboard nav sorting // todo: child sorting +// todo: set group, pull,put,selectionMode, seelctionAppearance, on children +// todo: disable filtering on children /** * A general purpose list that enables users to construct list items that conform to Calcite styling. From 9b6dd249c72c3b1fd0c4bc45856e4517d7f99949 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Mon, 3 Jul 2023 09:59:11 -0700 Subject: [PATCH 13/31] cleanup --- .../src/components/list/list.tsx | 2 -- .../calcite-components/src/demos/list.html | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 970bc887240..0b425dad89c 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -46,8 +46,6 @@ import { // todo: keyboard nav sorting // todo: child sorting -// todo: set group, pull,put,selectionMode, seelctionAppearance, on children -// todo: disable filtering on children /** * A general purpose list that enables users to construct list items that conform to Calcite styling. diff --git a/packages/calcite-components/src/demos/list.html b/packages/calcite-components/src/demos/list.html index e440e0bc0fb..897a0de21e4 100644 --- a/packages/calcite-components/src/demos/list.html +++ b/packages/calcite-components/src/demos/list.html @@ -576,6 +576,25 @@

List

+ + +
+
nested & draggable
+ +
+ + + + + + + + + + + +
+
From f010af2d1141c4492b9b2cb00c529a2d9632c8b4 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Mon, 3 Jul 2023 09:59:40 -0700 Subject: [PATCH 14/31] cleanup --- packages/calcite-components/src/components/list/list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 0b425dad89c..a76886e9235 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -45,7 +45,7 @@ import { } from "../../utils/loadable"; // todo: keyboard nav sorting -// todo: child sorting +// todo: disable child list functionality /** * A general purpose list that enables users to construct list items that conform to Calcite styling. From 19e9eff60df3d0bd93212e877e013f683d618051 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Fri, 14 Jul 2023 13:54:53 -0700 Subject: [PATCH 15/31] event detail --- .../src/components/list/list.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index ccab3577eff..16af3b7f3e2 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -11,7 +11,7 @@ import { VNode, Watch } from "@stencil/core"; -import Sortable from "sortablejs"; +import Sortable, { SortableEvent } from "sortablejs"; import { debounce } from "lodash-es"; import { slotChangeHasAssignedElement, toAriaBoolean } from "../../utils/dom"; import { @@ -192,7 +192,9 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo /** * Emitted when the order of the list has changed. */ - @Event({ cancelable: false }) calciteListOrderChange: EventEmitter; + @Event({ cancelable: false }) calciteListOrderChange: EventEmitter< + Pick + >; @Listen("calciteInternalFocusPreviousItem") handleCalciteInternalFocusPreviousItem(event: CustomEvent): void { @@ -413,9 +415,17 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo this.connectObserver(); } - onDragSort(): void { + onDragSort(event: SortableEvent): void { this.updateListItems(); - this.calciteListOrderChange.emit(); + + const { from, item, items, to } = event; + + this.calciteListOrderChange.emit({ + from, + item, + items, + to + }); } private handleDefaultSlotChange = (event: Event): void => { From d06d16f4e3593a1c48dcd6c427da4d75f3332f73 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Fri, 14 Jul 2023 14:55:27 -0700 Subject: [PATCH 16/31] cleanup --- .../src/components/list/list.tsx | 64 ++++++++++++++----- .../sortable-list/sortable-list.tsx | 6 +- .../src/components/value-list/value-list.tsx | 6 +- .../src/utils/sortableComponent.ts | 6 +- 4 files changed, 58 insertions(+), 24 deletions(-) diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 16af3b7f3e2..2dcc97a4d84 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -27,7 +27,7 @@ import { MAX_COLUMNS } from "../list-item/resources"; import { getListItemChildren, updateListItemChildren } from "../list-item/utils"; import { CSS, debounceTimeout, SelectionAppearance, SLOTS } from "./resources"; import { - CanDragEvent, + DragEvent, connectSortableComponent, disconnectSortableComponent, SortableComponent @@ -35,6 +35,7 @@ import { import { SLOTS as STACK_SLOTS } from "../stack/resources"; const listItemSelector = "calcite-list-item"; +const listItemSelectorDirect = `:scope > calcite-list-item`; const parentSelector = "calcite-list-item-group, calcite-list-item"; import { @@ -45,7 +46,6 @@ import { } from "../../utils/loadable"; // todo: keyboard nav sorting -// todo: disable child list functionality /** * A general purpose list that enables users to construct list items that conform to Calcite styling. @@ -74,12 +74,12 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo /** * When provided, the method will be called to determine whether the element can move from the list. */ - @Prop() dragCanPull: (event: CanDragEvent) => boolean; + @Prop() dragCanPull: (event: DragEvent) => boolean; /** * When provided, the method will be called to determine whether the element can be added from another list. */ - @Prop() dragCanPut: (event: CanDragEvent) => boolean; + @Prop() dragCanPut: (event: DragEvent) => boolean; /** * When `true`, `calcite-list-item`s are sortable via a draggable button. @@ -192,12 +192,14 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo /** * Emitted when the order of the list has changed. */ - @Event({ cancelable: false }) calciteListOrderChange: EventEmitter< - Pick - >; + @Event({ cancelable: false }) calciteListOrderChange: EventEmitter; @Listen("calciteInternalFocusPreviousItem") handleCalciteInternalFocusPreviousItem(event: CustomEvent): void { + if (this.isChildList) { + return; + } + event.stopPropagation(); const { enabledListItems } = this; @@ -212,6 +214,10 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo @Listen("calciteInternalListItemActive") handleCalciteInternalListItemActive(event: CustomEvent): void { + if (this.isChildList) { + return; + } + event.stopPropagation(); const target = event.target as HTMLCalciteListItemElement; const { listItems } = this; @@ -223,11 +229,19 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo @Listen("calciteListItemSelect") handleCalciteListItemSelect(): void { + if (this.isChildList) { + return; + } + this.updateSelectedItems(true); } @Listen("calciteInternalListItemSelect") handleCalciteInternalListItemSelect(event: CustomEvent): void { + if (this.isChildList) { + return; + } + event.stopPropagation(); const target = event.target as HTMLCalciteListItemElement; const { listItems, selectionMode } = this; @@ -241,6 +255,10 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo @Listen("calciteInternalListItemChange") handleCalciteInternalListItemChange(event: CustomEvent): void { + if (this.isChildList) { + return; + } + event.stopPropagation(); this.updateListItems(true); } @@ -256,6 +274,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo this.updateListItems(); this.setUpSorting(); connectInteractive(this); + this.isChildList = !!this.el.closest("calcite-list"); } disconnectedCallback(): void { @@ -304,6 +323,8 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo filterEl: HTMLCalciteFilterElement; + isChildList = false; + // -------------------------------------------------------------------------- // // Public Methods @@ -418,13 +439,12 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo onDragSort(event: SortableEvent): void { this.updateListItems(); - const { from, item, items, to } = event; + const { from, item, to } = event; this.calciteListOrderChange.emit({ - from, - item, - items, - to + dragEl: item, + fromEl: from, + toEl: to }); } @@ -564,6 +584,20 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo private updateListItems = debounce((emit = false): void => { const { selectionAppearance, selectionMode, dragEnabled } = this; + + if (this.isChildList) { + const items = this.queryListItems(true); + + items.forEach((item) => { + item.selectionAppearance = selectionAppearance; + item.selectionMode = selectionMode; + item.dragHandle = dragEnabled; + }); + + this.setUpSorting(); + return; + } + const items = this.queryListItems(); items.forEach((item) => { item.selectionAppearance = selectionAppearance; @@ -584,8 +618,8 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo this.setUpSorting(); }, debounceTimeout); - private queryListItems = (): HTMLCalciteListItemElement[] => { - return Array.from(this.el.querySelectorAll(listItemSelector)); + private queryListItems = (direct = false): HTMLCalciteListItemElement[] => { + return Array.from(this.el.querySelectorAll(direct ? listItemSelectorDirect : listItemSelector)); }; private focusRow = (focusEl: HTMLCalciteListItemElement): void => { @@ -611,7 +645,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo }; private handleListKeydown = (event: KeyboardEvent): void => { - if (event.defaultPrevented) { + if (event.defaultPrevented || this.isChildList) { return; } diff --git a/packages/calcite-components/src/components/sortable-list/sortable-list.tsx b/packages/calcite-components/src/components/sortable-list/sortable-list.tsx index d799978708b..0c40429f177 100644 --- a/packages/calcite-components/src/components/sortable-list/sortable-list.tsx +++ b/packages/calcite-components/src/components/sortable-list/sortable-list.tsx @@ -11,7 +11,7 @@ import { HandleNudge } from "../handle/interfaces"; import { Layout } from "../interfaces"; import { CSS } from "./resources"; import { - CanDragEvent, + DragEvent, connectSortableComponent, disconnectSortableComponent, SortableComponent @@ -36,12 +36,12 @@ export class SortableList implements InteractiveComponent, SortableComponent { /** * When provided, the method will be called to determine whether the element can move from the list. */ - @Prop() dragCanPull: (event: CanDragEvent) => boolean; + @Prop() dragCanPull: (event: DragEvent) => boolean; /** * When provided, the method will be called to determine whether the element can be added from another list. */ - @Prop() dragCanPut: (event: CanDragEvent) => boolean; + @Prop() dragCanPut: (event: DragEvent) => boolean; /** * Specifies which items inside the element should be draggable. diff --git a/packages/calcite-components/src/components/value-list/value-list.tsx b/packages/calcite-components/src/components/value-list/value-list.tsx index d18fe2dfb73..8748ebc82c2 100644 --- a/packages/calcite-components/src/components/value-list/value-list.tsx +++ b/packages/calcite-components/src/components/value-list/value-list.tsx @@ -62,7 +62,7 @@ import { ValueListMessages } from "./assets/value-list/t9n"; import { CSS, ICON_TYPES } from "./resources"; import { getHandleAndItemElement, getScreenReaderText } from "./utils"; import { - CanDragEvent, + DragEvent, connectSortableComponent, disconnectSortableComponent, SortableComponent @@ -103,12 +103,12 @@ export class ValueList< /** * When provided, the method will be called to determine whether the element can move from the list. */ - @Prop() dragCanPull: (event: CanDragEvent) => boolean; + @Prop() dragCanPull: (event: DragEvent) => boolean; /** * When provided, the method will be called to determine whether the element can be added from another list. */ - @Prop() dragCanPut: (event: CanDragEvent) => boolean; + @Prop() dragCanPut: (event: DragEvent) => boolean; /** * When `true`, `calcite-value-list-item`s are sortable via a draggable button. diff --git a/packages/calcite-components/src/utils/sortableComponent.ts b/packages/calcite-components/src/utils/sortableComponent.ts index 6f36c6e71a0..9b78724743b 100644 --- a/packages/calcite-components/src/utils/sortableComponent.ts +++ b/packages/calcite-components/src/utils/sortableComponent.ts @@ -3,7 +3,7 @@ import { containsCrossShadowBoundary } from "./dom"; const sortableComponentSet = new Set(); const inactiveSortableComponentSet = new WeakSet(); -export interface CanDragEvent { +export interface DragEvent { toEl: HTMLElement; fromEl: HTMLElement; dragEl: HTMLElement; @@ -46,12 +46,12 @@ export interface SortableComponent { /** * Whether the element can move from the list. */ - dragCanPull: (event: CanDragEvent) => boolean; + dragCanPull: (event: DragEvent) => boolean; /** * Whether the element can be added from another list. */ - dragCanPut: (event: CanDragEvent) => boolean; + dragCanPut: (event: DragEvent) => boolean; /** * Called by any change to the list (add / update / remove). From 980a144f082bf06ee52c88bf838bdafaa9ec56c0 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Fri, 14 Jul 2023 15:40:27 -0700 Subject: [PATCH 17/31] cleanup --- .../src/components/list/list.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 2dcc97a4d84..43876c5d089 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -196,7 +196,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo @Listen("calciteInternalFocusPreviousItem") handleCalciteInternalFocusPreviousItem(event: CustomEvent): void { - if (this.isChildList) { + if (this.parentListEl) { return; } @@ -214,7 +214,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo @Listen("calciteInternalListItemActive") handleCalciteInternalListItemActive(event: CustomEvent): void { - if (this.isChildList) { + if (!!this.parentListEl) { return; } @@ -229,7 +229,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo @Listen("calciteListItemSelect") handleCalciteListItemSelect(): void { - if (this.isChildList) { + if (!!this.parentListEl) { return; } @@ -238,7 +238,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo @Listen("calciteInternalListItemSelect") handleCalciteInternalListItemSelect(event: CustomEvent): void { - if (this.isChildList) { + if (!!this.parentListEl) { return; } @@ -255,7 +255,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo @Listen("calciteInternalListItemChange") handleCalciteInternalListItemChange(event: CustomEvent): void { - if (this.isChildList) { + if (!!this.parentListEl) { return; } @@ -274,7 +274,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo this.updateListItems(); this.setUpSorting(); connectInteractive(this); - this.isChildList = !!this.el.closest("calcite-list"); + this.parentListEl = this.el.closest("calcite-list"); } disconnectedCallback(): void { @@ -323,7 +323,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo filterEl: HTMLCalciteFilterElement; - isChildList = false; + parentListEl: HTMLCalciteListElement; // -------------------------------------------------------------------------- // @@ -585,7 +585,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo private updateListItems = debounce((emit = false): void => { const { selectionAppearance, selectionMode, dragEnabled } = this; - if (this.isChildList) { + if (!!this.parentListEl) { const items = this.queryListItems(true); items.forEach((item) => { @@ -645,7 +645,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo }; private handleListKeydown = (event: KeyboardEvent): void => { - if (event.defaultPrevented || this.isChildList) { + if (event.defaultPrevented || !!this.parentListEl) { return; } From 9b6c5eb7929f2752f2daf24b27063d897805b029 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Mon, 17 Jul 2023 09:06:37 -0700 Subject: [PATCH 18/31] test app --- packages/calcite-components/src/demos/list.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/calcite-components/src/demos/list.html b/packages/calcite-components/src/demos/list.html index 897a0de21e4..7158b726a3e 100644 --- a/packages/calcite-components/src/demos/list.html +++ b/packages/calcite-components/src/demos/list.html @@ -584,10 +584,12 @@

List

- + - - + + + + From d20bea86d6315395281b382a55cb121ef9445778 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Thu, 27 Jul 2023 09:45:31 -0700 Subject: [PATCH 19/31] cleanup slot changes for openable --- .../list-item-group/list-item-group.tsx | 36 +++++++++++++++++-- .../src/components/list-item/list-item.tsx | 27 ++++++++++++-- .../src/components/list-item/utils.ts | 10 +++--- .../src/components/list/list.tsx | 17 +++++++-- 4 files changed, 78 insertions(+), 12 deletions(-) diff --git a/packages/calcite-components/src/components/list-item-group/list-item-group.tsx b/packages/calcite-components/src/components/list-item-group/list-item-group.tsx index 333d56c75ca..328590d20ae 100644 --- a/packages/calcite-components/src/components/list-item-group/list-item-group.tsx +++ b/packages/calcite-components/src/components/list-item-group/list-item-group.tsx @@ -1,4 +1,14 @@ -import { Component, Element, h, Host, Prop, State, VNode } from "@stencil/core"; +import { + Component, + Element, + Event, + EventEmitter, + h, + Host, + Prop, + State, + VNode, +} from "@stencil/core"; import { connectInteractive, disconnectInteractive, @@ -34,6 +44,18 @@ export class ListItemGroup implements InteractiveComponent { */ @Prop({ reflect: true }) heading: string; + //-------------------------------------------------------------------------- + // + // Events + // + //-------------------------------------------------------------------------- + + /** + * Emitted when the default slot has changes in order to notify parent lists. + */ + @Event({ cancelable: false }) + calciteInternalListItemGroupDefaultSlotChange: EventEmitter; + // -------------------------------------------------------------------------- // // Lifecycle @@ -82,8 +104,18 @@ export class ListItemGroup implements InteractiveComponent { {heading} - + ); } + + // -------------------------------------------------------------------------- + // + // Private Methods + // + // -------------------------------------------------------------------------- + + private handleDefaultSlotChange = (): void => { + this.calciteInternalListItemGroupDefaultSlotChange.emit(); + }; } 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 8ede5fe9648..70a299e1de7 100644 --- a/packages/calcite-components/src/components/list-item/list-item.tsx +++ b/packages/calcite-components/src/components/list-item/list-item.tsx @@ -5,6 +5,7 @@ import { EventEmitter, h, Host, + Listen, Method, Prop, State, @@ -233,6 +234,13 @@ export class ListItem */ @Event({ cancelable: false }) calciteInternalListItemChange: EventEmitter; + @Listen("calciteInternalListItemGroupDefaultSlotChange") + @Listen("calciteInternalListDefaultSlotChange") + handleCalciteInternalListDefaultSlotChanges(event: CustomEvent): void { + event.stopPropagation(); + this.handleOpenableChange(this.defaultSlotEl); + } + // -------------------------------------------------------------------------- // // Private Properties @@ -276,6 +284,8 @@ export class ListItem actionsEndEl: HTMLTableCellElement; + defaultSlotEl: HTMLSlotElement; + // -------------------------------------------------------------------------- // // Lifecycle @@ -567,7 +577,10 @@ export class ListItem [CSS.nestedContainerHidden]: openable && !open, }} > - + (this.defaultSlotEl = el)} + />
); @@ -624,9 +637,13 @@ export class ListItem } } - handleDefaultSlotChange = (event: Event): void => { + handleOpenableChange(slotEl: HTMLSlotElement): void { + if (!slotEl) { + return; + } + const { parentListEl } = this; - const listItemChildren = getListItemChildren(event); + const listItemChildren = getListItemChildren(slotEl); updateListItemChildren(listItemChildren); const openable = !!listItemChildren.length; @@ -639,6 +656,10 @@ export class ListItem if (!openable) { this.open = false; } + } + + handleDefaultSlotChange = (event: Event): void => { + this.handleOpenableChange(event.target as HTMLSlotElement); }; toggleOpen = (): void => { diff --git a/packages/calcite-components/src/components/list-item/utils.ts b/packages/calcite-components/src/components/list-item/utils.ts index 5ba8940cf0f..94520de9ca7 100644 --- a/packages/calcite-components/src/components/list-item/utils.ts +++ b/packages/calcite-components/src/components/list-item/utils.ts @@ -4,8 +4,8 @@ const listSelector = "calcite-list"; const listItemGroupSelector = "calcite-list-item-group"; const listItemSelector = "calcite-list-item"; -export function getListItemChildren(event: Event): HTMLCalciteListItemElement[] { - const assignedElements = (event.target as HTMLSlotElement).assignedElements({ flatten: true }); +export function getListItemChildren(slotEl: HTMLSlotElement): HTMLCalciteListItemElement[] { + const assignedElements = slotEl.assignedElements({ flatten: true }); const listItemGroupChildren = ( assignedElements.filter((el) => el?.matches(listItemGroupSelector)) as HTMLCalciteListItemGroupElement[] @@ -17,9 +17,9 @@ export function getListItemChildren(event: Event): HTMLCalciteListItemElement[] el?.matches(listItemSelector) ) as HTMLCalciteListItemElement[]; - const listItemListChildren = assignedElements.filter((el) => - el?.matches(listSelector) - ) as HTMLCalciteListItemElement[]; + const listItemListChildren = (assignedElements.filter((el) => el?.matches(listSelector)) as HTMLCalciteListElement[]) + .map((list) => Array.from(list.querySelectorAll(listItemSelector))) + .reduce((previousValue, currentValue) => [...previousValue, ...currentValue], []); return [...listItemListChildren, ...listItemGroupChildren, ...listItemChildren]; } diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 15936a0083a..c3d2c79568c 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -169,7 +169,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo @Watch("dragEnabled") @Watch("selectionMode") @Watch("selectionAppearance") - handleSelectionAppearanceChange(): void { + handleListItemChange(): void { this.updateListItems(); } @@ -194,6 +194,11 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo */ @Event({ cancelable: false }) calciteListOrderChange: EventEmitter; + /** + * Emitted when the default slot has changes in order to notify parent lists. + */ + @Event({ cancelable: false }) calciteInternalListDefaultSlotChange: EventEmitter; + @Listen("calciteInternalFocusPreviousItem") handleCalciteInternalFocusPreviousItem(event: CustomEvent): void { if (this.parentListEl) { @@ -263,6 +268,11 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo this.updateListItems(true); } + @Listen("calciteInternalListItemGroupDefaultSlotChange") + handleCalciteInternalListItemGroupDefaultSlotChange(event: CustomEvent): void { + event.stopPropagation(); + } + //-------------------------------------------------------------------------- // // Lifecycle @@ -449,7 +459,10 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo } private handleDefaultSlotChange = (event: Event): void => { - updateListItemChildren(getListItemChildren(event)); + updateListItemChildren(getListItemChildren(event.target as HTMLSlotElement)); + if (this.parentListEl) { + this.calciteInternalListDefaultSlotChange.emit(); + } }; private handleFilterActionsStartSlotChange = (event: Event): void => { From 99e5e12763fa621d49ebbc93f5f370cca2b5337e Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Thu, 27 Jul 2023 10:33:01 -0700 Subject: [PATCH 20/31] WIP sorting keyboard --- .../src/components/list/list.tsx | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index c3d2c79568c..0b4a078d1d0 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -44,6 +44,7 @@ import { setComponentLoaded, setUpLoadableComponent, } from "../../utils/loadable"; +import { HandleNudge } from "../handle/interfaces"; // todo: keyboard nav sorting @@ -241,6 +242,15 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo this.updateSelectedItems(true); } + @Listen("calciteHandleNudge") + calciteHandleNudgeNextHandler(event: CustomEvent): void { + if (!!this.parentListEl) { + return; + } + + this.handleNudgeEvent(event); + } + @Listen("calciteInternalListItemSelect") handleCalciteInternalListItemSelect(event: CustomEvent): void { if (!!this.parentListEl) { @@ -284,7 +294,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo this.updateListItems(); this.setUpSorting(); connectInteractive(this); - this.parentListEl = this.el.closest("calcite-list"); + this.parentListEl = this.el.parentElement.closest("calcite-list"); } disconnectedCallback(): void { @@ -696,4 +706,58 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo } } }; + + handleNudgeEvent(event: CustomEvent): void { + const { direction } = event.detail; + + const composedPath = event.composedPath(); + + const handle = composedPath.find( + (el: HTMLElement) => "matches" in el && el.matches("calcite-handle") + ) as HTMLCalciteHandleElement; + + const sortItem = composedPath.find( + (el: HTMLElement) => "matches" in el && el.matches("calcite-list-item") + ) as HTMLCalciteListItemElement; + + console.log({ direction, composedPath, sortItem }); + + const { enabledListItems } = this; + + const lastIndex = enabledListItems.length - 1; + const startingIndex = enabledListItems.indexOf(sortItem); + let appendInstead = false; + let buddyIndex: number; + + if (direction === "up") { + if (startingIndex === 0) { + appendInstead = true; + } else { + buddyIndex = startingIndex - 1; + } + } else { + if (startingIndex === lastIndex) { + buddyIndex = 0; + } else if (startingIndex === lastIndex - 1) { + appendInstead = true; + } else { + buddyIndex = startingIndex + 2; + } + } + + this.disconnectObserver(); + + if (appendInstead) { + sortItem.parentElement.appendChild(sortItem); + } else { + sortItem.parentElement.insertBefore(sortItem, enabledListItems[buddyIndex]); + } + + this.updateListItems(); + + this.connectObserver(); + requestAnimationFrame(() => handle.setFocus()); + + handle.activated = true; + } } From f97ceea8ef26103b6028ef43ca998bf9890bbc07 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Thu, 27 Jul 2023 12:08:14 -0700 Subject: [PATCH 21/31] WIP --- .../src/components/list/list.tsx | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 0b4a078d1d0..4c6041354ec 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -323,6 +323,8 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo @Element() el: HTMLCalciteListElement; + assistiveTextEl: HTMLSpanElement; + sortable: Sortable; handleSelector = "calcite-handle"; @@ -378,6 +380,14 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo } = this; return (
+ {this.dragEnabled ? ( + + ) : null} {loading ? : null} item.parentElement === sortItem.parentElement + ); + + const lastIndex = sameParentItems.length - 1; + const startingIndex = sameParentItems.indexOf(sortItem); let appendInstead = false; let buddyIndex: number; @@ -750,7 +764,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo if (appendInstead) { sortItem.parentElement.appendChild(sortItem); } else { - sortItem.parentElement.insertBefore(sortItem, enabledListItems[buddyIndex]); + sortItem.parentElement.insertBefore(sortItem, sameParentItems[buddyIndex]); } this.updateListItems(); @@ -760,4 +774,12 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo handle.activated = true; } + + storeAssistiveEl = (el: HTMLSpanElement): void => { + this.assistiveTextEl = el; + }; + + updateScreenReaderText(text: string): void { + this.assistiveTextEl.textContent = text; + } } From 358092046590587c221d95577a77caf6d7f1ccbf Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Thu, 27 Jul 2023 15:16:03 -0700 Subject: [PATCH 22/31] WIP --- .../handle/assets/handle/t9n/messages.json | 6 +- .../handle/assets/handle/t9n/messages_en.json | 6 +- .../src/components/handle/handle.tsx | 64 ++++++++++++++++++- .../src/components/handle/interfaces.d.ts | 4 ++ .../src/components/list-item/list-item.tsx | 2 +- .../src/components/list/list.tsx | 35 ++++------ 6 files changed, 91 insertions(+), 26 deletions(-) diff --git a/packages/calcite-components/src/components/handle/assets/handle/t9n/messages.json b/packages/calcite-components/src/components/handle/assets/handle/t9n/messages.json index 42ca69fde8e..47a39263892 100644 --- a/packages/calcite-components/src/components/handle/assets/handle/t9n/messages.json +++ b/packages/calcite-components/src/components/handle/assets/handle/t9n/messages.json @@ -1,3 +1,7 @@ { - "dragHandle": "Drag handle" + "dragHandle": "Drag handle", + "dragHandleActive": "Reordering {itemLabel}, current position {position} of {total}.", + "dragHandleChange": "{itemLabel}, new position {position} of {total}. Press space to confirm.", + "dragHandleCommit": "{itemLabel}, current position {position} of {total}.", + "dragHandleIdle": "{itemLabel}, press space and use arrow keys to reorder content. Current position {position} of {total}." } diff --git a/packages/calcite-components/src/components/handle/assets/handle/t9n/messages_en.json b/packages/calcite-components/src/components/handle/assets/handle/t9n/messages_en.json index 42ca69fde8e..47a39263892 100644 --- a/packages/calcite-components/src/components/handle/assets/handle/t9n/messages_en.json +++ b/packages/calcite-components/src/components/handle/assets/handle/t9n/messages_en.json @@ -1,3 +1,7 @@ { - "dragHandle": "Drag handle" + "dragHandle": "Drag handle", + "dragHandleActive": "Reordering {itemLabel}, current position {position} of {total}.", + "dragHandleChange": "{itemLabel}, new position {position} of {total}. Press space to confirm.", + "dragHandleCommit": "{itemLabel}, current position {position} of {total}.", + "dragHandleIdle": "{itemLabel}, press space and use arrow keys to reorder content. Current position {position} of {total}." } diff --git a/packages/calcite-components/src/components/handle/handle.tsx b/packages/calcite-components/src/components/handle/handle.tsx index 5cafcce4198..7fdeb8ef51a 100644 --- a/packages/calcite-components/src/components/handle/handle.tsx +++ b/packages/calcite-components/src/components/handle/handle.tsx @@ -26,7 +26,7 @@ import { updateMessages, } from "../../utils/t9n"; import { HandleMessages } from "./assets/handle/t9n"; -import { HandleNudge } from "./interfaces"; +import { HandleChange, HandleNudge } from "./interfaces"; import { CSS, ICONS } from "./resources"; @Component({ @@ -45,8 +45,22 @@ export class Handle implements LoadableComponent, T9nComponent { /** * @internal */ + // todo: rename to active @Prop({ mutable: true, reflect: true }) activated = false; + @Watch("activated") + @Watch("setPosition") + @Watch("setSize") + handleActivatedChange(): void { + const message = this.getAriaText("live"); + + if (message) { + this.calciteInternalHandleChange.emit({ + message, + }); + } + } + /** * Value for the button title attribute */ @@ -59,6 +73,27 @@ export class Handle implements LoadableComponent, T9nComponent { */ @Prop() messages: HandleMessages; + /** + * + * + * @internal + */ + @Prop() setPosition: number; + + /** + * + * + * @internal + */ + @Prop() setSize: number; + + /** + * + * + * @internal + */ + @Prop() label: string; + /** * Use this property to override individual strings used by the component. */ @@ -124,6 +159,11 @@ export class Handle implements LoadableComponent, T9nComponent { */ @Event({ cancelable: false }) calciteHandleNudge: EventEmitter; + /** + * Emitted when the handle is activated or deactivated. + */ + @Event({ cancelable: false }) calciteInternalHandleChange: EventEmitter; + // -------------------------------------------------------------------------- // // Methods @@ -144,6 +184,27 @@ export class Handle implements LoadableComponent, T9nComponent { // // -------------------------------------------------------------------------- + getAriaText(type: "label" | "live"): string { + const { setPosition, setSize, label, messages, activated } = this; + + if (!label || typeof setSize !== "number" || typeof setPosition !== "number") { + return null; + } + + const text = + type === "label" + ? activated + ? messages.dragHandleChange + : messages.dragHandleIdle + : activated + ? messages.dragHandleActive + : messages.dragHandleCommit; + + const replacePosition = text.replace("{position}", setPosition.toString()); + const replaceLabel = replacePosition.replace("{itemLabel}", label); + return replaceLabel.replace("{total}", setSize.toString()); + } + handleKeyDown = (event: KeyboardEvent): void => { switch (event.key) { case " ": @@ -181,6 +242,7 @@ export class Handle implements LoadableComponent, T9nComponent { return ( // Needs to be a span because of https://github.com/SortableJS/Sortable/issues/1486 - + ) : null; } diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 4c6041354ec..249a70aa770 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -46,8 +46,6 @@ import { } from "../../utils/loadable"; import { HandleNudge } from "../handle/interfaces"; -// todo: keyboard nav sorting - /** * A general purpose list that enables users to construct list items that conform to Calcite styling. * @@ -242,6 +240,12 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo this.updateSelectedItems(true); } + @Listen("calciteInternalHandleChange") + handleCalciteInternalHandleChange(event: CustomEvent): void { + this.assistiveText = event.detail.message; + event.stopPropagation(); + } + @Listen("calciteHandleNudge") calciteHandleNudgeNextHandler(event: CustomEvent): void { if (!!this.parentListEl) { @@ -323,7 +327,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo @Element() el: HTMLCalciteListElement; - assistiveTextEl: HTMLSpanElement; + @State() assistiveText: string; sortable: Sortable; @@ -381,12 +385,9 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo return (
{this.dragEnabled ? ( - + + {this.assistiveText} + ) : null} {loading ? : null}
"matches" in el && el.matches("calcite-list-item") ) as HTMLCalciteListItemElement; - console.log({ direction, composedPath, sortItem }); - const { enabledListItems } = this; const sameParentItems = enabledListItems.filter( @@ -768,18 +767,10 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo } this.updateListItems(); - this.connectObserver(); - requestAnimationFrame(() => handle.setFocus()); - handle.activated = true; - } - - storeAssistiveEl = (el: HTMLSpanElement): void => { - this.assistiveTextEl = el; - }; - - updateScreenReaderText(text: string): void { - this.assistiveTextEl.textContent = text; + handle.setFocus().then(() => { + handle.activated = true; + }); } } From f1f94c7aa30f3befe283a7d822b091f075e24d8e Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Thu, 27 Jul 2023 15:35:27 -0700 Subject: [PATCH 23/31] cleanup --- .../src/components/handle/handle.tsx | 1 - .../src/components/list/list.scss | 4 ++ .../src/components/list/list.tsx | 2 +- .../src/components/list/resources.ts | 1 + .../calcite-components/src/demos/list.html | 60 ++++++++++++++++++- 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/packages/calcite-components/src/components/handle/handle.tsx b/packages/calcite-components/src/components/handle/handle.tsx index 7fdeb8ef51a..86abd29bc6d 100644 --- a/packages/calcite-components/src/components/handle/handle.tsx +++ b/packages/calcite-components/src/components/handle/handle.tsx @@ -45,7 +45,6 @@ export class Handle implements LoadableComponent, T9nComponent { /** * @internal */ - // todo: rename to active @Prop({ mutable: true, reflect: true }) activated = false; @Watch("activated") diff --git a/packages/calcite-components/src/components/list/list.scss b/packages/calcite-components/src/components/list/list.scss index 0ac94efeb3f..3d450907e49 100755 --- a/packages/calcite-components/src/components/list/list.scss +++ b/packages/calcite-components/src/components/list/list.scss @@ -48,4 +48,8 @@ } } +.assistive-text { + @apply sr-only; +} + @include base-component(); diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 249a70aa770..5821b452444 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -385,7 +385,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo return (
{this.dragEnabled ? ( - + {this.assistiveText} ) : null} diff --git a/packages/calcite-components/src/components/list/resources.ts b/packages/calcite-components/src/components/list/resources.ts index ab763af1d33..38640166296 100644 --- a/packages/calcite-components/src/components/list/resources.ts +++ b/packages/calcite-components/src/components/list/resources.ts @@ -5,6 +5,7 @@ export const CSS = { stack: "stack", tableContainer: "table-container", sticky: "sticky-pos", + assistiveText: "assistive-text", }; export const debounceTimeout = 0; diff --git a/packages/calcite-components/src/demos/list.html b/packages/calcite-components/src/demos/list.html index 7158b726a3e..a0599d68b90 100644 --- a/packages/calcite-components/src/demos/list.html +++ b/packages/calcite-components/src/demos/list.html @@ -577,23 +577,77 @@

List

+ +
+
simple list drag enabled
+ +
+ + + + + + + + + + + + + + + + + + +
+
+
nested & draggable
- + - + - + + + + +
From ca77444e824d5c633dded5aa6674b355c97dcb07 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Thu, 27 Jul 2023 17:12:37 -0700 Subject: [PATCH 24/31] cleanup --- .../src/components/list-item/list-item.e2e.ts | 12 ++++++++++++ .../src/components/list/list.e2e.ts | 4 ++++ .../calcite-components/src/components/list/list.tsx | 8 +++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/list-item/list-item.e2e.ts b/packages/calcite-components/src/components/list-item/list-item.e2e.ts index 3e2741b081b..6db230b4072 100755 --- a/packages/calcite-components/src/components/list-item/list-item.e2e.ts +++ b/packages/calcite-components/src/components/list-item/list-item.e2e.ts @@ -43,6 +43,10 @@ describe("calcite-list-item", () => { propertyName: "open", defaultValue: false, }, + { + propertyName: "dragHandle", + defaultValue: false, + }, ]); }); @@ -54,6 +58,14 @@ describe("calcite-list-item", () => { disabled(``); }); + it("renders dragHandle when property is true", async () => { + const page = await newE2EPage({ html: `` }); + + const contentNode = await page.find("calcite-list-item >>> calcite-handle"); + + expect(contentNode).not.toBeNull(); + }); + it("renders content node when label is provided", async () => { const page = await newE2EPage({ html: `` }); diff --git a/packages/calcite-components/src/components/list/list.e2e.ts b/packages/calcite-components/src/components/list/list.e2e.ts index 88db7c9b0dc..d5ebb6a504a 100755 --- a/packages/calcite-components/src/components/list/list.e2e.ts +++ b/packages/calcite-components/src/components/list/list.e2e.ts @@ -61,6 +61,10 @@ describe("calcite-list", () => { propertyName: "filterPlaceholder", defaultValue: undefined, }, + { + propertyName: "dragEnabled", + defaultValue: false, + }, ]); }); diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index 4593e9b3e74..a3d5d6aac43 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -742,7 +742,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo (el: HTMLElement) => "matches" in el && el.matches("calcite-list-item") ) as HTMLCalciteListItemElement; - const { enabledListItems } = this; + const { enabledListItems, el } = this; const sameParentItems = enabledListItems.filter( (item) => item.parentElement === sortItem.parentElement @@ -780,6 +780,12 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo this.updateListItems(); this.connectObserver(); + this.calciteListOrderChange.emit({ + dragEl: sortItem, + fromEl: el, + toEl: el, + }); + handle.setFocus().then(() => { handle.activated = true; }); From 83fb4c02bd7be64025c2c502690a8dd6d8ff8a8c Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Fri, 28 Jul 2023 11:45:20 -0700 Subject: [PATCH 25/31] cleanup --- .../src/components/handle/handle.tsx | 8 +- .../src/components/list/list.e2e.ts | 119 +++++++++++++++++- 2 files changed, 123 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/components/handle/handle.tsx b/packages/calcite-components/src/components/handle/handle.tsx index 86abd29bc6d..cb906853596 100644 --- a/packages/calcite-components/src/components/handle/handle.tsx +++ b/packages/calcite-components/src/components/handle/handle.tsx @@ -47,10 +47,12 @@ export class Handle implements LoadableComponent, T9nComponent { */ @Prop({ mutable: true, reflect: true }) activated = false; + @Watch("messages") + @Watch("label") @Watch("activated") @Watch("setPosition") @Watch("setSize") - handleActivatedChange(): void { + handleAriaTextChange(): void { const message = this.getAriaText("live"); if (message) { @@ -186,7 +188,7 @@ export class Handle implements LoadableComponent, T9nComponent { getAriaText(type: "label" | "live"): string { const { setPosition, setSize, label, messages, activated } = this; - if (!label || typeof setSize !== "number" || typeof setPosition !== "number") { + if (!messages || !label || typeof setSize !== "number" || typeof setPosition !== "number") { return null; } @@ -248,7 +250,7 @@ export class Handle implements LoadableComponent, T9nComponent { onKeyDown={this.handleKeyDown} role="button" tabindex="0" - title={this.messages.dragHandle} + title={this.messages?.dragHandle} // eslint-disable-next-line react/jsx-sort-props ref={(el): void => { this.handleButton = el; diff --git a/packages/calcite-components/src/components/list/list.e2e.ts b/packages/calcite-components/src/components/list/list.e2e.ts index d5ebb6a504a..2c3e63d6732 100755 --- a/packages/calcite-components/src/components/list/list.e2e.ts +++ b/packages/calcite-components/src/components/list/list.e2e.ts @@ -1,7 +1,7 @@ import { accessible, hidden, renders, focusable, disabled, defaults } from "../../tests/commonTests"; import { placeholderImage } from "../../../.storybook/placeholderImage"; import { html } from "../../../support/formatting"; -import { newE2EPage } from "@stencil/core/testing"; +import { E2EPage, newE2EPage } from "@stencil/core/testing"; import { debounceTimeout } from "./resources"; import { CSS } from "../list-item/resources"; import { DEBOUNCE_TIMEOUT as FILTER_DEBOUNCE_TIMEOUT } from "../filter/resources"; @@ -455,4 +455,121 @@ describe("calcite-list", () => { expect(await isElementFocused(page, "calcite-filter", { shadowed: true })).toBe(true); }); }); + + describe("drag and drop", () => { + async function createSimpleList(): Promise { + const page = await newE2EPage(); + await page.setContent(` + + + + `); + await page.waitForChanges(); + await page.waitForTimeout(listDebounceTimeout); + return page; + } + + it("works using a keyboard", async () => { + const page = await createSimpleList(); + + const handle = await page.find(`calcite-list-item[value="one"] >>> calcite-handle`); + + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Space"); + expect(await handle.getProperty("activated")).toBe(true); + await page.waitForChanges(); + + let totalMoves = 0; + + const listOrderChangeSpy = await page.spyOnEvent("calciteListOrderChange"); + + async function assertKeyboardMove( + arrowKey: "ArrowDown" | "ArrowUp", + expectedValueOrder: string[] + ): Promise { + await page.keyboard.press(arrowKey); + await page.waitForTimeout(listDebounceTimeout); + const itemsAfter = await page.findAll("calcite-list-item"); + expect(itemsAfter.length).toBe(3); + + for (let i = 0; i < itemsAfter.length; i++) { + expect(await itemsAfter[i].getProperty("value")).toBe(expectedValueOrder[i]); + } + + expect(listOrderChangeSpy).toHaveReceivedEventTimes(++totalMoves); + } + + await assertKeyboardMove("ArrowDown", ["two", "one", "three"]); + await assertKeyboardMove("ArrowDown", ["two", "three", "one"]); + + await assertKeyboardMove("ArrowUp", ["two", "one", "three"]); + await assertKeyboardMove("ArrowUp", ["one", "two", "three"]); + }); + + it("is drag and drop list accessible", async () => { + const page = await createSimpleList(); + let startIndex = 0; + + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.waitForChanges(); + + const items = await page.findAll("calcite-list-item"); + const item = await page.find('calcite-list-item[value="one"]'); + const handle = await page.find('calcite-list-item[value="one"] >>> calcite-handle'); + const assistiveTextElement = await page.find("calcite-list >>> .assistive-text"); + + async function getAriaLabel(): Promise { + return page.$eval("calcite-list-item[value='one']", (el: HTMLCalciteListItemElement) => { + return el.shadowRoot + .querySelector("calcite-handle") + .shadowRoot.querySelector("span") + .getAttribute("aria-label"); + }); + } + + const handleAriaLabel = await getAriaLabel(); + const itemLabel = await item.getProperty("label"); + + expect(handleAriaLabel).toBe( + `${itemLabel}, press space and use arrow keys to reorder content. Current position ${startIndex + 1} of ${ + items.length + }.` + ); + + await page.keyboard.press("Space"); + expect(await handle.getProperty("activated")).toBe(true); + await page.waitForChanges(); + + expect(assistiveTextElement.textContent).toBe( + `Reordering ${itemLabel}, current position ${startIndex + 1} of ${items.length}.` + ); + + await page.keyboard.press("ArrowDown"); + await page.waitForChanges(); + expect(await handle.getProperty("activated")).toBe(true); + await page.waitForTimeout(debounceTimeout); + + startIndex += 1; + const changeHandleLabel = await getAriaLabel(); + + expect(changeHandleLabel).toBe( + `${itemLabel}, new position ${startIndex + 1} of ${items.length}. Press space to confirm.` + ); + await page.keyboard.press("Space"); + await page.waitForChanges(); + + expect(assistiveTextElement.textContent).toBe( + `${itemLabel}, current position ${startIndex + 1} of ${items.length}.` + ); + + await page.keyboard.press("Space"); + await page.waitForChanges(); + await page.keyboard.press("ArrowUp"); + await page.keyboard.press("Space"); + await page.waitForChanges(); + expect(await handle.getProperty("activated")).toBe(false); + }); + }); }); From 98acf7d0b330efad773cb8f909a27f220b8ad1f5 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Fri, 28 Jul 2023 12:03:44 -0700 Subject: [PATCH 26/31] cleanup --- .../src/components/list-item/list-item.e2e.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/calcite-components/src/components/list-item/list-item.e2e.ts b/packages/calcite-components/src/components/list-item/list-item.e2e.ts index 6db230b4072..c2737843953 100755 --- a/packages/calcite-components/src/components/list-item/list-item.e2e.ts +++ b/packages/calcite-components/src/components/list-item/list-item.e2e.ts @@ -61,6 +61,8 @@ describe("calcite-list-item", () => { it("renders dragHandle when property is true", async () => { const page = await newE2EPage({ html: `` }); + await page.waitForChanges(); + const contentNode = await page.find("calcite-list-item >>> calcite-handle"); expect(contentNode).not.toBeNull(); From b7eb5381c0429d383c66f53a0d5b62c1bf424470 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Fri, 28 Jul 2023 14:08:10 -0700 Subject: [PATCH 27/31] cleanup --- packages/calcite-components/src/components/list/list.e2e.ts | 6 ++++-- packages/calcite-components/src/components/list/list.tsx | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/calcite-components/src/components/list/list.e2e.ts b/packages/calcite-components/src/components/list/list.e2e.ts index 2c3e63d6732..98e57b23686 100755 --- a/packages/calcite-components/src/components/list/list.e2e.ts +++ b/packages/calcite-components/src/components/list/list.e2e.ts @@ -469,7 +469,7 @@ describe("calcite-list", () => { return page; } - it("works using a keyboard", async () => { + it.skip("works using a keyboard", async () => { const page = await createSimpleList(); const handle = await page.find(`calcite-list-item[value="one"] >>> calcite-handle`); @@ -488,8 +488,10 @@ describe("calcite-list", () => { arrowKey: "ArrowDown" | "ArrowUp", expectedValueOrder: string[] ): Promise { + const event = page.waitForEvent("calciteListOrderChange"); + await page.waitForChanges(); await page.keyboard.press(arrowKey); - await page.waitForTimeout(listDebounceTimeout); + await event; const itemsAfter = await page.findAll("calcite-list-item"); expect(itemsAfter.length).toBe(3); diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index a3d5d6aac43..f28d272cafe 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -735,11 +735,11 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo const composedPath = event.composedPath(); const handle = composedPath.find( - (el: HTMLElement) => "matches" in el && el.matches("calcite-handle") + (el: HTMLElement) => el.tagName === "CALCITE-HANDLE" ) as HTMLCalciteHandleElement; const sortItem = composedPath.find( - (el: HTMLElement) => "matches" in el && el.matches("calcite-list-item") + (el: HTMLElement) => el.tagName === "CALCITE-LIST-ITEM" ) as HTMLCalciteListItemElement; const { enabledListItems, el } = this; From e174a9454db9297289e4058b0f921c6bf8fa794e Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Fri, 28 Jul 2023 14:48:36 -0700 Subject: [PATCH 28/31] examples, tests --- .../src/components/list-item/list-item.e2e.ts | 6 +- .../src/components/list/list.stories.ts | 71 +++++++++++++++++++ .../src/components/list/usage/DragEnabled.md | 16 +++++ .../list/usage/DragEnabledNested.md | 19 +++++ 4 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 packages/calcite-components/src/components/list/usage/DragEnabled.md create mode 100644 packages/calcite-components/src/components/list/usage/DragEnabledNested.md diff --git a/packages/calcite-components/src/components/list-item/list-item.e2e.ts b/packages/calcite-components/src/components/list-item/list-item.e2e.ts index c2737843953..ad79abff406 100755 --- a/packages/calcite-components/src/components/list-item/list-item.e2e.ts +++ b/packages/calcite-components/src/components/list-item/list-item.e2e.ts @@ -59,8 +59,12 @@ describe("calcite-list-item", () => { }); it("renders dragHandle when property is true", async () => { - const page = await newE2EPage({ html: `` }); + const page = await newE2EPage(); + await page.setContent(``); + await page.waitForChanges(); + const item = await page.find("calcite-list-item"); + item.setProperty("dragHandle", true); await page.waitForChanges(); const contentNode = await page.find("calcite-list-item >>> calcite-handle"); diff --git a/packages/calcite-components/src/components/list/list.stories.ts b/packages/calcite-components/src/components/list/list.stories.ts index 83825e5c966..f6b392bb04e 100644 --- a/packages/calcite-components/src/components/list/list.stories.ts +++ b/packages/calcite-components/src/components/list/list.stories.ts @@ -590,3 +590,74 @@ export const filterActions_TestOnly = (): string => html` `; + +export const sortableList_TestOnly = (): string => html` + + + + + + + + + + + + + + + + +`; + +export const sortableNestedList_TestOnly = (): string => html` + + + + + + + + + + + + + + + +`; diff --git a/packages/calcite-components/src/components/list/usage/DragEnabled.md b/packages/calcite-components/src/components/list/usage/DragEnabled.md new file mode 100644 index 00000000000..8699e110e8f --- /dev/null +++ b/packages/calcite-components/src/components/list/usage/DragEnabled.md @@ -0,0 +1,16 @@ +```html + + + + + + + + + + + + + + +``` diff --git a/packages/calcite-components/src/components/list/usage/DragEnabledNested.md b/packages/calcite-components/src/components/list/usage/DragEnabledNested.md new file mode 100644 index 00000000000..bcc553fc4ee --- /dev/null +++ b/packages/calcite-components/src/components/list/usage/DragEnabledNested.md @@ -0,0 +1,19 @@ +```html + + + + + + + + + + + + + + + + + +``` From 162cd5e2988fc8547a69c14c864ade5197e85d5f Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Fri, 28 Jul 2023 15:04:59 -0700 Subject: [PATCH 29/31] cleanup --- .../src/components/list/usage/DragEnabled.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/calcite-components/src/components/list/usage/DragEnabled.md b/packages/calcite-components/src/components/list/usage/DragEnabled.md index 8699e110e8f..0009ab0e079 100644 --- a/packages/calcite-components/src/components/list/usage/DragEnabled.md +++ b/packages/calcite-components/src/components/list/usage/DragEnabled.md @@ -1,13 +1,13 @@ ```html - + - + - + From f820923d6d022b2ee2749d8dd02da9c4e5ee6ca3 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Tue, 1 Aug 2023 11:43:04 -0700 Subject: [PATCH 30/31] fix test --- .../src/components/list/list.e2e.ts | 2 +- .../src/components/list/list.tsx | 22 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/calcite-components/src/components/list/list.e2e.ts b/packages/calcite-components/src/components/list/list.e2e.ts index 98e57b23686..91d7122e36a 100755 --- a/packages/calcite-components/src/components/list/list.e2e.ts +++ b/packages/calcite-components/src/components/list/list.e2e.ts @@ -469,7 +469,7 @@ describe("calcite-list", () => { return page; } - it.skip("works using a keyboard", async () => { + it("works using a keyboard", async () => { const page = await createSimpleList(); const handle = await page.find(`calcite-list-item[value="one"] >>> calcite-handle`); diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index f28d272cafe..f5d83ac53b9 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -247,7 +247,7 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo } @Listen("calciteHandleNudge") - calciteHandleNudgeNextHandler(event: CustomEvent): void { + handleCalciteHandleNudge(event: CustomEvent): void { if (!!this.parentListEl) { return; } @@ -742,11 +742,15 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo (el: HTMLElement) => el.tagName === "CALCITE-LIST-ITEM" ) as HTMLCalciteListItemElement; - const { enabledListItems, el } = this; + const parentEl = sortItem?.parentElement; - const sameParentItems = enabledListItems.filter( - (item) => item.parentElement === sortItem.parentElement - ); + if (!parentEl) { + return; + } + + const { enabledListItems } = this; + + const sameParentItems = enabledListItems.filter((item) => item.parentElement === parentEl); const lastIndex = sameParentItems.length - 1; const startingIndex = sameParentItems.indexOf(sortItem); @@ -772,9 +776,9 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo this.disconnectObserver(); if (appendInstead) { - sortItem.parentElement.appendChild(sortItem); + parentEl.appendChild(sortItem); } else { - sortItem.parentElement.insertBefore(sortItem, sameParentItems[buddyIndex]); + parentEl.insertBefore(sortItem, sameParentItems[buddyIndex]); } this.updateListItems(); @@ -782,8 +786,8 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo this.calciteListOrderChange.emit({ dragEl: sortItem, - fromEl: el, - toEl: el, + fromEl: null, + toEl: null, }); handle.setFocus().then(() => { From f826ce065d27753c4da2fa6975ea6d5ab328f4c2 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Wed, 2 Aug 2023 09:17:43 -0700 Subject: [PATCH 31/31] review fixes --- .../src/components/list-item/list-item.e2e.ts | 8 ++- .../src/components/list/list.e2e.ts | 54 ++++++++++++++++--- .../src/components/list/list.tsx | 22 ++++---- .../sortable-list/sortable-list.tsx | 4 +- .../src/components/value-list/value-list.tsx | 4 +- .../calcite-components/src/demos/list.html | 2 - .../src/utils/sortableComponent.ts | 20 +++---- 7 files changed, 77 insertions(+), 37 deletions(-) diff --git a/packages/calcite-components/src/components/list-item/list-item.e2e.ts b/packages/calcite-components/src/components/list-item/list-item.e2e.ts index ad79abff406..759485f4070 100755 --- a/packages/calcite-components/src/components/list-item/list-item.e2e.ts +++ b/packages/calcite-components/src/components/list-item/list-item.e2e.ts @@ -63,13 +63,17 @@ describe("calcite-list-item", () => { await page.setContent(``); await page.waitForChanges(); + let handleNode = await page.find("calcite-list-item >>> calcite-handle"); + + expect(handleNode).toBeNull(); + const item = await page.find("calcite-list-item"); item.setProperty("dragHandle", true); await page.waitForChanges(); - const contentNode = await page.find("calcite-list-item >>> calcite-handle"); + handleNode = await page.find("calcite-list-item >>> calcite-handle"); - expect(contentNode).not.toBeNull(); + expect(handleNode).not.toBeNull(); }); it("renders content node when label is provided", async () => { diff --git a/packages/calcite-components/src/components/list/list.e2e.ts b/packages/calcite-components/src/components/list/list.e2e.ts index 91d7122e36a..b9e51b0d17b 100755 --- a/packages/calcite-components/src/components/list/list.e2e.ts +++ b/packages/calcite-components/src/components/list/list.e2e.ts @@ -488,10 +488,10 @@ describe("calcite-list", () => { arrowKey: "ArrowDown" | "ArrowUp", expectedValueOrder: string[] ): Promise { - const event = page.waitForEvent("calciteListOrderChange"); + const calciteListOrderChangeEvent = page.waitForEvent("calciteListOrderChange"); await page.waitForChanges(); await page.keyboard.press(arrowKey); - await event; + await calciteListOrderChangeEvent; const itemsAfter = await page.findAll("calcite-list-item"); expect(itemsAfter.length).toBe(3); @@ -534,10 +534,33 @@ describe("calcite-list", () => { const handleAriaLabel = await getAriaLabel(); const itemLabel = await item.getProperty("label"); + /* eslint-disable import/no-dynamic-require -- allowing dynamic asset path for maintainability */ + const langTranslations = await import(`../handle/assets/handle/t9n/messages.json`); + /* eslint-enable import/no-dynamic-require */ + + function messageSubstitute({ + text, + setPosition, + label, + setSize, + }: { + text: string; + setPosition: number; + label: string; + setSize: number; + }): string { + const replacePosition = text.replace("{position}", setPosition.toString()); + const replaceLabel = replacePosition.replace("{itemLabel}", label); + return replaceLabel.replace("{total}", setSize.toString()); + } + expect(handleAriaLabel).toBe( - `${itemLabel}, press space and use arrow keys to reorder content. Current position ${startIndex + 1} of ${ - items.length - }.` + messageSubstitute({ + text: langTranslations.dragHandleIdle, + setPosition: startIndex + 1, + label: itemLabel, + setSize: items.length, + }) ); await page.keyboard.press("Space"); @@ -545,7 +568,12 @@ describe("calcite-list", () => { await page.waitForChanges(); expect(assistiveTextElement.textContent).toBe( - `Reordering ${itemLabel}, current position ${startIndex + 1} of ${items.length}.` + messageSubstitute({ + text: langTranslations.dragHandleActive, + setPosition: startIndex + 1, + label: itemLabel, + setSize: items.length, + }) ); await page.keyboard.press("ArrowDown"); @@ -557,13 +585,23 @@ describe("calcite-list", () => { const changeHandleLabel = await getAriaLabel(); expect(changeHandleLabel).toBe( - `${itemLabel}, new position ${startIndex + 1} of ${items.length}. Press space to confirm.` + messageSubstitute({ + text: langTranslations.dragHandleChange, + setPosition: startIndex + 1, + label: itemLabel, + setSize: items.length, + }) ); await page.keyboard.press("Space"); await page.waitForChanges(); expect(assistiveTextElement.textContent).toBe( - `${itemLabel}, current position ${startIndex + 1} of ${items.length}.` + messageSubstitute({ + text: langTranslations.dragHandleCommit, + setPosition: startIndex + 1, + label: itemLabel, + setSize: items.length, + }) ); await page.keyboard.press("Space"); diff --git a/packages/calcite-components/src/components/list/list.tsx b/packages/calcite-components/src/components/list/list.tsx index f5d83ac53b9..4d9757f1724 100755 --- a/packages/calcite-components/src/components/list/list.tsx +++ b/packages/calcite-components/src/components/list/list.tsx @@ -73,12 +73,12 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo /** * When provided, the method will be called to determine whether the element can move from the list. */ - @Prop() dragCanPull: (event: DragEvent) => boolean; + @Prop() canPull: (event: DragEvent) => boolean; /** * When provided, the method will be called to determine whether the element can be added from another list. */ - @Prop() dragCanPut: (event: DragEvent) => boolean; + @Prop() canPut: (event: DragEvent) => boolean; /** * When `true`, `calcite-list-item`s are sortable via a draggable button. @@ -329,28 +329,28 @@ export class List implements InteractiveComponent, LoadableComponent, SortableCo @State() assistiveText: string; - sortable: Sortable; - - handleSelector = "calcite-handle"; + @State() dataForFilter: ItemData = []; dragSelector = "calcite-list-item"; - listItems: HTMLCalciteListItemElement[] = []; - enabledListItems: HTMLCalciteListItemElement[] = []; - mutationObserver = createObserver("mutation", () => this.updateListItems()); + filterEl: HTMLCalciteFilterElement; - @State() dataForFilter: ItemData = []; + handleSelector = "calcite-handle"; + + @State() hasFilterActionsEnd = false; @State() hasFilterActionsStart = false; - @State() hasFilterActionsEnd = false; + listItems: HTMLCalciteListItemElement[] = []; - filterEl: HTMLCalciteFilterElement; + mutationObserver = createObserver("mutation", () => this.updateListItems()); parentListEl: HTMLCalciteListElement; + sortable: Sortable; + // -------------------------------------------------------------------------- // // Public Methods diff --git a/packages/calcite-components/src/components/sortable-list/sortable-list.tsx b/packages/calcite-components/src/components/sortable-list/sortable-list.tsx index 6afdf5f8251..5883145855a 100644 --- a/packages/calcite-components/src/components/sortable-list/sortable-list.tsx +++ b/packages/calcite-components/src/components/sortable-list/sortable-list.tsx @@ -36,12 +36,12 @@ export class SortableList implements InteractiveComponent, SortableComponent { /** * When provided, the method will be called to determine whether the element can move from the list. */ - @Prop() dragCanPull: (event: DragEvent) => boolean; + @Prop() canPull: (event: DragEvent) => boolean; /** * When provided, the method will be called to determine whether the element can be added from another list. */ - @Prop() dragCanPut: (event: DragEvent) => boolean; + @Prop() canPut: (event: DragEvent) => boolean; /** * Specifies which items inside the element should be draggable. diff --git a/packages/calcite-components/src/components/value-list/value-list.tsx b/packages/calcite-components/src/components/value-list/value-list.tsx index ccaef669e41..90f261abdb6 100644 --- a/packages/calcite-components/src/components/value-list/value-list.tsx +++ b/packages/calcite-components/src/components/value-list/value-list.tsx @@ -103,12 +103,12 @@ export class ValueList< /** * When provided, the method will be called to determine whether the element can move from the list. */ - @Prop() dragCanPull: (event: DragEvent) => boolean; + @Prop() canPull: (event: DragEvent) => boolean; /** * When provided, the method will be called to determine whether the element can be added from another list. */ - @Prop() dragCanPut: (event: DragEvent) => boolean; + @Prop() canPut: (event: DragEvent) => boolean; /** * When `true`, `calcite-value-list-item`s are sortable via a draggable button. diff --git a/packages/calcite-components/src/demos/list.html b/packages/calcite-components/src/demos/list.html index a0599d68b90..f66931df7b9 100644 --- a/packages/calcite-components/src/demos/list.html +++ b/packages/calcite-components/src/demos/list.html @@ -577,7 +577,6 @@

List

-
simple list drag enabled
@@ -627,7 +626,6 @@

List

-
nested & draggable
diff --git a/packages/calcite-components/src/utils/sortableComponent.ts b/packages/calcite-components/src/utils/sortableComponent.ts index 9b78724743b..c4133c9e3d7 100644 --- a/packages/calcite-components/src/utils/sortableComponent.ts +++ b/packages/calcite-components/src/utils/sortableComponent.ts @@ -44,14 +44,14 @@ export interface SortableComponent { sortable: Sortable; /** - * Whether the element can move from the list. + * Whether the element can move from the list. */ - dragCanPull: (event: DragEvent) => boolean; + canPull: (event: DragEvent) => boolean; /** * Whether the element can be added from another list. */ - dragCanPut: (event: DragEvent) => boolean; + canPut: (event: DragEvent) => boolean; /** * Called by any change to the list (add / update / remove). @@ -91,13 +91,13 @@ export function connectSortableComponent(component: SortableComponent): void { ...(!!group && { group: { name: group, - ...(!!component.dragCanPull && { - pull: (to, from, dragEl) => component.dragCanPull({ toEl: to.el, fromEl: from.el, dragEl }) + ...(!!component.canPull && { + pull: (to, from, dragEl) => component.canPull({ toEl: to.el, fromEl: from.el, dragEl }), }), - ...(!!component.dragCanPut && { - put: (to, from, dragEl) => component.dragCanPut({ toEl: to.el, fromEl: from.el, dragEl }) - }) - } + ...(!!component.canPut && { + put: (to, from, dragEl) => component.canPut({ toEl: to.el, fromEl: from.el, dragEl }), + }), + }, }), handle, onStart: (event) => { @@ -110,7 +110,7 @@ export function connectSortableComponent(component: SortableComponent): void { }, onSort: (event) => { component.onDragSort(event); - } + }, }); }