Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(list)!: Support keyboard dragging between different Lists #10480

Draft
wants to merge 40 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8d10fe0
WIP
driskull Oct 1, 2024
265ad5a
WIP
driskull Oct 1, 2024
8bee155
WIP
driskull Oct 2, 2024
510e74f
cleanup
driskull Oct 2, 2024
7e81d90
WIP
driskull Oct 2, 2024
039d3a1
wip
driskull Oct 2, 2024
154c437
wip
driskull Oct 2, 2024
3453946
rename
driskull Oct 2, 2024
cc36b04
cleanup
driskull Oct 2, 2024
2a5acc0
doc first take
driskull Oct 2, 2024
887f392
add todo
driskull Oct 2, 2024
1f61359
cleanup
driskull Oct 2, 2024
bcfa7ba
css
driskull Oct 2, 2024
af88fe1
cleanup
driskull Oct 2, 2024
6801d32
WIP
driskull Oct 3, 2024
c0cc794
fix selector
driskull Oct 3, 2024
1ba5f13
WIP
driskull Oct 3, 2024
46767ec
Update .stylelintrc.cjs
driskull Oct 3, 2024
2b36b7d
interfaces
driskull Oct 3, 2024
98fc6ff
demo cleanup
driskull Oct 3, 2024
ca2eff7
tests
driskull Oct 3, 2024
5b325a3
cleanup
driskull Oct 3, 2024
925bcc5
moar tests
driskull Oct 3, 2024
1fac4b8
cleanup
driskull Oct 3, 2024
e0157aa
tests
driskull Oct 3, 2024
01b6eb0
WIP
driskull Oct 3, 2024
947e399
Update .stylelintrc.cjs
driskull Oct 3, 2024
8e6d9fa
cleanup
driskull Oct 3, 2024
408fb2a
fix filtering
driskull Oct 3, 2024
62c5290
insert at top
driskull Oct 3, 2024
378fab8
cleanup
driskull Oct 4, 2024
6959ca3
WIP tests
driskull Oct 7, 2024
163db6b
tests
driskull Oct 7, 2024
66dfaea
tests
driskull Oct 7, 2024
f85f05c
Merge branch 'dev' into dris0000/sort-dropdown
driskull Oct 7, 2024
ceea563
fix tests
driskull Oct 7, 2024
a63c3a9
cleanup
driskull Oct 7, 2024
ec2410d
cleanup
driskull Oct 7, 2024
13f1ed7
cleanup
driskull Oct 8, 2024
6233dac
drag handle icon blank when disabled
driskull Oct 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 206 additions & 4 deletions packages/calcite-components/src/components.d.ts

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/calcite-components/src/components/action/action.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

:host {
@extend %component-host;
@apply flex bg-transparent;
@apply flex bg-transparent cursor-pointer;
}

:host,
Expand Down Expand Up @@ -62,7 +62,6 @@ button {
m-0
flex
w-auto
cursor-pointer
items-center
justify-start
border-none
Expand All @@ -73,6 +72,7 @@ button {
color: var(--calcite-action-text-color, var(--calcite-color-text-3));
text-align: unset;
flex: 1 0 auto;
cursor: inherit;

&:hover,
&:focus {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,15 @@ describe("calcite-list-item", () => {
await page.setContent(`<calcite-list-item></calcite-list-item>`);
await page.waitForChanges();

let handleNode = await page.find("calcite-list-item >>> calcite-handle");
let handleNode = await page.find("calcite-list-item >>> calcite-sort-handle");

expect(handleNode).toBeNull();

const item = await page.find("calcite-list-item");
item.setProperty("dragHandle", true);
await page.waitForChanges();

handleNode = await page.find("calcite-list-item >>> calcite-handle");
handleNode = await page.find("calcite-list-item >>> calcite-sort-handle");

expect(handleNode).not.toBeNull();
});
Expand Down Expand Up @@ -343,29 +343,4 @@ describe("calcite-list-item", () => {
expect(await listItem.getProperty("open")).toBe(false);
expect(calciteListItemToggle).toHaveReceivedEventTimes(2);
});

it("should fire calciteListItemDragHandleChange event when drag handle is clicked", async () => {
const page = await newE2EPage({
html: html`<calcite-list-item drag-handle></calcite-list-item>`,
});

const listItem = await page.find("calcite-list-item");
const calciteListItemDragHandleChange = await page.spyOnEvent("calciteListItemDragHandleChange", "window");

expect(await listItem.getProperty("dragSelected")).toBe(false);

const dragHandle = await page.find(`calcite-list-item >>> calcite-handle`);
await dragHandle.callMethod("setFocus");
await page.waitForChanges();

await dragHandle.press("Space");
await page.waitForChanges();
expect(await listItem.getProperty("dragSelected")).toBe(true);
expect(calciteListItemDragHandleChange).toHaveReceivedEventTimes(1);

await dragHandle.press("Space");
await page.waitForChanges();
expect(await listItem.getProperty("dragSelected")).toBe(false);
expect(calciteListItemDragHandleChange).toHaveReceivedEventTimes(2);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ td:focus {
@apply flex items-center;

calcite-action,
calcite-handle {
calcite-sort-handle {
@apply self-stretch;
}
}
Expand All @@ -208,7 +208,7 @@ td:focus {
@apply p-0 relative;
::slotted(calcite-action),
::slotted(calcite-action-menu),
::slotted(calcite-handle),
::slotted(calcite-sort-handle),
::slotted(calcite-dropdown) {
@apply self-stretch;

Expand All @@ -223,7 +223,7 @@ tr:focus {
.close,
::slotted(calcite-action),
::slotted(calcite-action-menu),
::slotted(calcite-handle),
::slotted(calcite-sort-handle),
::slotted(calcite-dropdown) {
block-size: calc(100% - theme("spacing[1]"));
}
Expand Down
74 changes: 49 additions & 25 deletions packages/calcite-components/src/components/list-item/list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,9 @@ import {
setUpLoadableComponent,
} from "../../utils/loadable";
import { SortableComponentItem } from "../../utils/sortableComponent";
import { MoveTo } from "../sort-handle/interfaces";
import { ListItemMessages } from "./assets/list-item/t9n";
import {
getDepth,
getListItemChildren,
getListItemChildLists,
updateListItemChildren,
} from "./utils";
import { getDepth, hasListItemChildren } from "./utils";
import { CSS, activeCellTestAttribute, ICONS, SLOTS } from "./resources";

const focusMap = new Map<HTMLCalciteListElement, number>();
Expand Down Expand Up @@ -142,8 +138,10 @@ export class ListItem

/**
* When `true`, the component's drag handle is selected.
*
* @deprecated no longer necessary.
*/
@Prop({ mutable: true, reflect: true }) dragSelected = false;
@Prop({ reflect: true }) dragSelected = false;

/**
* Hides the component when filtered.
Expand All @@ -162,6 +160,13 @@ export class ListItem
*/
@Prop() metadata: Record<string, unknown>;

/**
* Sets the item to display a border.
*
* @internal
*/
@Prop() moveToItems: MoveTo[] = [];

/**
* When `true`, the item is open to show child components.
*/
Expand All @@ -173,14 +178,14 @@ export class ListItem
}

/**
* Used to specify the aria-setsize attribute to define the number of items in the current set of list for accessibility.
* Used to determine what menu options are available in the sort-handle
*
* @internal
*/
@Prop() setSize: number = null;

/**
* Used to specify the aria-posinset attribute to define the number or position in the current set of list items for accessibility.
* Used to determine what menu options are available in the sort-handle
*
* @internal
*/
Expand Down Expand Up @@ -223,6 +228,13 @@ export class ListItem
*/
@Prop({ mutable: true }) selectionAppearance: SelectionAppearance = null;

/**
* When `true`, displays and positions the sort handle.
*
* @internal
*/
@Prop({ mutable: true }) sortHandleOpen = false;

/**
* Use this property to override individual strings used by the component.
*/
Expand Down Expand Up @@ -260,6 +272,8 @@ export class ListItem

/**
* Fires when the drag handle is selected.
*
* @deprecated no longer necessary.
*/
@Event({ cancelable: false }) calciteListItemDragHandleChange: EventEmitter<void>;

Expand Down Expand Up @@ -460,7 +474,8 @@ export class ListItem
}

renderDragHandle(): VNode {
const { label, dragHandle, dragSelected, dragDisabled, setPosition, setSize } = this;
const { label, dragHandle, dragDisabled, setPosition, setSize, sortHandleOpen, moveToItems } =
this;

return dragHandle ? (
<td
Expand All @@ -471,11 +486,16 @@ export class ListItem
ref={(el) => (this.handleGridEl = el)}
role="gridcell"
>
<calcite-handle
<calcite-sort-handle
disabled={dragDisabled}
label={label}
onCalciteHandleChange={this.dragHandleSelectedChangeHandler}
selected={dragSelected}
moveToItems={moveToItems}
onCalciteSortHandleBeforeClose={this.handleSortHandleBeforeClose}
onCalciteSortHandleBeforeOpen={this.handleSortHandleBeforeOpen}
onCalciteSortHandleClose={this.handleSortHandleClose}
onCalciteSortHandleOpen={this.handleSortHandleOpen}
open={sortHandleOpen}
overlayPositioning="fixed"
setPosition={setPosition}
setSize={setSize}
/>
Expand Down Expand Up @@ -652,8 +672,6 @@ export class ListItem
openable,
open,
level,
setPosition,
setSize,
active,
label,
selected,
Expand All @@ -677,9 +695,7 @@ export class ListItem
aria-expanded={openable ? toAriaBoolean(open) : null}
aria-label={label}
aria-level={level}
aria-posinset={setPosition}
aria-selected={toAriaBoolean(selected)}
aria-setsize={setSize}
class={{
[CSS.container]: true,
[CSS.containerHover]: true,
Expand Down Expand Up @@ -716,10 +732,22 @@ export class ListItem
//
// --------------------------------------------------------------------------

private dragHandleSelectedChangeHandler = (event: CustomEvent<void>): void => {
this.dragSelected = (event.target as HTMLCalciteHandleElement).selected;
this.calciteListItemDragHandleChange.emit();
private handleSortHandleBeforeOpen = (event: CustomEvent<void>): void => {
event.stopPropagation();
};

private handleSortHandleBeforeClose = (event: CustomEvent<void>): void => {
event.stopPropagation();
};

private handleSortHandleClose = (event: CustomEvent<void>): void => {
event.stopPropagation();
this.sortHandleOpen = false;
};

private handleSortHandleOpen = (event: CustomEvent<void>): void => {
event.stopPropagation();
this.sortHandleOpen = true;
};

private emitInternalListItemActive = (): void => {
Expand Down Expand Up @@ -800,11 +828,7 @@ export class ListItem
return;
}

const listItemChildren = getListItemChildren(slotEl);
const listItemChildLists = getListItemChildLists(slotEl);
updateListItemChildren(listItemChildren);

this.openable = !!listItemChildren.length || !!listItemChildLists.length;
this.openable = hasListItemChildren(slotEl);
}

private handleDefaultSlotChange = (event: Event): void => {
Expand Down
27 changes: 11 additions & 16 deletions packages/calcite-components/src/components/list-item/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,28 @@ const listSelector = "calcite-list";
const listItemGroupSelector = "calcite-list-item-group";
const listItemSelector = "calcite-list-item";

export function getListItemChildLists(slotEl: HTMLSlotElement): HTMLCalciteListElement[] {
return Array.from(
slotEl.assignedElements({ flatten: true }).filter((el): el is HTMLCalciteListElement => el.matches(listSelector)),
);
}

export function getListItemChildren(slotEl: HTMLSlotElement): HTMLCalciteListItemElement[] {
export function hasListItemChildren(slotEl: HTMLSlotElement): boolean {
const assignedElements = slotEl.assignedElements({ flatten: true });

const listItemGroupChildren = assignedElements
const groupChildren = assignedElements
.filter((el): el is HTMLCalciteListItemGroupElement => el?.matches(listItemGroupSelector))
.map((group) => Array.from(group.querySelectorAll(listItemSelector)))
.reduce((previousValue, currentValue) => [...previousValue, ...currentValue], []);
.map((group) => Array.from(group.querySelectorAll<HTMLCalciteListItemElement>(listItemSelector)))
.flat();

const listItemChildren = assignedElements.filter((el): el is HTMLCalciteListItemElement =>
el?.matches(listItemSelector),
);

const listItemListChildren = assignedElements
.filter((el): el is HTMLCalciteListElement => el?.matches(listSelector))
.map((list) => Array.from(list.querySelectorAll(listItemSelector)))
.reduce((previousValue, currentValue) => [...previousValue, ...currentValue], []);
const listChildren = assignedElements.filter((el): el is HTMLCalciteListElement => el?.matches(listSelector));

return [...listItemListChildren, ...listItemGroupChildren, ...listItemChildren];
return [...listChildren, ...groupChildren, ...listItemChildren].length > 0;
}

export function updateListItemChildren(listItemChildren: HTMLCalciteListItemElement[]): void {
export function updateListItemChildren(slotEl: HTMLSlotElement): void {
const listItemChildren = slotEl
.assignedElements({ flatten: true })
.filter((el): el is HTMLCalciteListItemElement => el?.matches(listItemSelector));

listItemChildren.forEach((listItem) => {
listItem.setPosition = listItemChildren.indexOf(listItem) + 1;
listItem.setSize = listItemChildren.length;
Expand Down
Loading
Loading