@fluid-topics/ftds-reorderable-list 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/build/define.d.ts +1 -0
- package/build/define.js +3 -0
- package/build/definitions.d.ts +5 -0
- package/build/definitions.js +4 -0
- package/build/event.d.ts +13 -0
- package/build/event.js +15 -0
- package/build/ftds-reorderable-list.d.ts +31 -0
- package/build/ftds-reorderable-list.js +268 -0
- package/build/ftds-reorderable-list.light.js +1745 -0
- package/build/ftds-reorderable-list.min.js +1859 -0
- package/build/ftds-reorderable-list.properties.d.ts +20 -0
- package/build/ftds-reorderable-list.properties.js +1 -0
- package/build/ftds-reorderable-list.styles.d.ts +4 -0
- package/build/ftds-reorderable-list.styles.js +70 -0
- package/build/index.d.ts +4 -0
- package/build/index.js +4 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
The design system reorderable list component
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```shell
|
|
6
|
+
npm install @fluid-topics/ftds-reorderable-list
|
|
7
|
+
yarn add @fluid-topics/ftds-reorderable-list
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { html } from "lit"
|
|
14
|
+
import "@fluid-topics/ftds-reorderable-list"
|
|
15
|
+
|
|
16
|
+
function render() {
|
|
17
|
+
return html` <ftds-reorderable-list foo="bar"></ftds-reorderable-list> `
|
|
18
|
+
}
|
|
19
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/build/define.js
ADDED
package/build/event.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class RemoveItemEvent extends CustomEvent<{
|
|
2
|
+
id: string;
|
|
3
|
+
}> {
|
|
4
|
+
constructor(id: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class EditItemEvent extends CustomEvent<{
|
|
7
|
+
id: string;
|
|
8
|
+
}> {
|
|
9
|
+
constructor(id: string);
|
|
10
|
+
}
|
|
11
|
+
export declare class OrderChangeEvent extends CustomEvent<void> {
|
|
12
|
+
constructor();
|
|
13
|
+
}
|
package/build/event.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class RemoveItemEvent extends CustomEvent {
|
|
2
|
+
constructor(id) {
|
|
3
|
+
super("reorderable-list-item-remove", { detail: { id: id }, bubbles: true, composed: true });
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export class EditItemEvent extends CustomEvent {
|
|
7
|
+
constructor(id) {
|
|
8
|
+
super("reorderable-list-item-edit", { detail: { id: id }, bubbles: true, composed: true });
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class OrderChangeEvent extends CustomEvent {
|
|
12
|
+
constructor() {
|
|
13
|
+
super("reorderable-list-order-change", { bubbles: true, composed: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ElementDefinitionsMap, FtLitElement } from "@fluid-topics/ft-wc-utils";
|
|
2
|
+
import { FtdsReorderableListItem, FtdsReorderableListProperties } from "./ftds-reorderable-list.properties";
|
|
3
|
+
export declare class FtdsReorderableList extends FtLitElement implements FtdsReorderableListProperties {
|
|
4
|
+
static elementDefinitions: ElementDefinitionsMap;
|
|
5
|
+
static styles: import("lit").CSSResult;
|
|
6
|
+
editable: boolean;
|
|
7
|
+
removable: boolean;
|
|
8
|
+
emptyListMessage: string;
|
|
9
|
+
items: FtdsReorderableListItem[];
|
|
10
|
+
list?: HTMLElement;
|
|
11
|
+
draggableItems?: HTMLElement[];
|
|
12
|
+
notDraggedItems?: HTMLElement[];
|
|
13
|
+
dragGrips?: HTMLElement[];
|
|
14
|
+
private draggingItem?;
|
|
15
|
+
protected render(): import("lit-html").TemplateResult<1>;
|
|
16
|
+
private renderItemLeftItems;
|
|
17
|
+
private renderItemLeftActions;
|
|
18
|
+
private onDragButtonKeyDown;
|
|
19
|
+
private keepFocusOnItem;
|
|
20
|
+
private moveItemUp;
|
|
21
|
+
private moveItemDown;
|
|
22
|
+
private swapItems;
|
|
23
|
+
private onDragStart;
|
|
24
|
+
private onDragOver;
|
|
25
|
+
private onDragEnd;
|
|
26
|
+
private syncReferenceList;
|
|
27
|
+
private getDragAfterElement;
|
|
28
|
+
private moveFocusToPreviousItem;
|
|
29
|
+
private moveFocusToNextItem;
|
|
30
|
+
private getSelectedOption;
|
|
31
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { html, nothing, } from "lit";
|
|
8
|
+
import { ifDefined } from "lit/directives/if-defined.js";
|
|
9
|
+
import { property, query, queryAll, } from "lit/decorators.js";
|
|
10
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
11
|
+
import { live } from "lit/directives/live.js";
|
|
12
|
+
import { DesignSystemFamily, DesignSystemSize, FtLitElement, } from "@fluid-topics/ft-wc-utils";
|
|
13
|
+
import { styles } from "./ftds-reorderable-list.styles";
|
|
14
|
+
import { FtdsTypography, FtdsTypographyVariants, } from "@fluid-topics/ftds-typography";
|
|
15
|
+
import { FtdsButton } from "@fluid-topics/ftds-button";
|
|
16
|
+
import { FtdsIcon, FtdsIcons, FtdsIconVariants, } from "@fluid-topics/ftds-icon";
|
|
17
|
+
import { FtdsNotice } from "@fluid-topics/ftds-notice";
|
|
18
|
+
import { EditItemEvent, OrderChangeEvent, RemoveItemEvent, } from "./event";
|
|
19
|
+
export class FtdsReorderableList extends FtLitElement {
|
|
20
|
+
constructor() {
|
|
21
|
+
super(...arguments);
|
|
22
|
+
this.editable = false;
|
|
23
|
+
this.removable = false;
|
|
24
|
+
this.emptyListMessage = "";
|
|
25
|
+
this.items = [];
|
|
26
|
+
this.draggingItem = undefined;
|
|
27
|
+
}
|
|
28
|
+
render() {
|
|
29
|
+
return html `
|
|
30
|
+
<div class="ftds-reorderable-list--container">
|
|
31
|
+
<ol role="listbox"
|
|
32
|
+
aria-activedescendant=""
|
|
33
|
+
@dragstart=${this.onDragStart}
|
|
34
|
+
@dragover=${this.onDragOver}
|
|
35
|
+
@dragend=${this.onDragEnd}>
|
|
36
|
+
${this.items.length > 0 ? repeat(this.items, (item) => live(item.id), (item) => {
|
|
37
|
+
return html `
|
|
38
|
+
<li role="option"
|
|
39
|
+
draggable="true"
|
|
40
|
+
id="${item.id}">
|
|
41
|
+
<div class="ftds-reorderable-list-item--left">
|
|
42
|
+
${this.renderItemLeftItems(item)}
|
|
43
|
+
${this.renderItemLeftActions(item)}
|
|
44
|
+
${item.warningMessage ? html `
|
|
45
|
+
<ftds-notice dense variant="warning"
|
|
46
|
+
size="${DesignSystemSize.large}">
|
|
47
|
+
${item.warningMessage}
|
|
48
|
+
</ftds-notice>` : nothing}
|
|
49
|
+
</div>
|
|
50
|
+
<div class="ftds-reorderable-list-item--right">
|
|
51
|
+
${this.editable ? html `
|
|
52
|
+
<ftds-button icon="${FtdsIcons.GEAR}"
|
|
53
|
+
family="${DesignSystemFamily.brand}"
|
|
54
|
+
size="${DesignSystemSize.large}"
|
|
55
|
+
@click=${() => this.dispatchEvent(new EditItemEvent(item.id))}
|
|
56
|
+
></ftds-button>
|
|
57
|
+
` : nothing}
|
|
58
|
+
${this.removable ? html `
|
|
59
|
+
<ftds-button icon="${FtdsIcons.TRASH}"
|
|
60
|
+
family="${DesignSystemFamily.brand}"
|
|
61
|
+
size="${DesignSystemSize.large}"
|
|
62
|
+
@click=${() => this.dispatchEvent(new RemoveItemEvent(item.id))}
|
|
63
|
+
></ftds-button>
|
|
64
|
+
` : nothing}
|
|
65
|
+
</div>
|
|
66
|
+
</li>`;
|
|
67
|
+
}) : html `
|
|
68
|
+
<ftds-typography variant="${FtdsTypographyVariants.body2semibold}">${this.emptyListMessage}</ftds-typography>`}
|
|
69
|
+
</ol>
|
|
70
|
+
<slot part="actions"></slot>
|
|
71
|
+
</div>
|
|
72
|
+
`;
|
|
73
|
+
}
|
|
74
|
+
renderItemLeftItems(item) {
|
|
75
|
+
return html `
|
|
76
|
+
<ftds-icon class="ftds-reorderable-list-item--drag-grip"
|
|
77
|
+
icon="${FtdsIcons.GRIP_LINES}"
|
|
78
|
+
variant="${FtdsIconVariants.solid}"
|
|
79
|
+
data-item-id="${item.id}"
|
|
80
|
+
tabindex="1"
|
|
81
|
+
@keydown=${this.onDragButtonKeyDown}
|
|
82
|
+
@focus=${(event) => {
|
|
83
|
+
var _a;
|
|
84
|
+
const selected = this.getSelectedOption(event);
|
|
85
|
+
selected.setAttribute("aria-selected", "true");
|
|
86
|
+
(_a = this.list) === null || _a === void 0 ? void 0 : _a.setAttribute("aria-activedescendant", `option-${item.id}`);
|
|
87
|
+
}}
|
|
88
|
+
@focusout=${(event) => {
|
|
89
|
+
var _a;
|
|
90
|
+
const selected = this.getSelectedOption(event);
|
|
91
|
+
selected.setAttribute("aria-selected", "false");
|
|
92
|
+
(_a = this.list) === null || _a === void 0 ? void 0 : _a.setAttribute("aria-activedescendant", "");
|
|
93
|
+
}}
|
|
94
|
+
@click=${(e) => {
|
|
95
|
+
e.target.focus();
|
|
96
|
+
}}
|
|
97
|
+
|
|
98
|
+
></ftds-icon>
|
|
99
|
+
<ftds-typography variant="${FtdsTypographyVariants.body2semibold}">
|
|
100
|
+
${item.label}
|
|
101
|
+
</ftds-typography>
|
|
102
|
+
`;
|
|
103
|
+
}
|
|
104
|
+
renderItemLeftActions(item) {
|
|
105
|
+
if (item.actions) {
|
|
106
|
+
return html `
|
|
107
|
+
<div class="ftds-reorderable-list-item--left-actions">
|
|
108
|
+
${repeat(item.actions, (action) => html `
|
|
109
|
+
<ftds-button icon="${action.icon}"
|
|
110
|
+
iconVariant="${action.iconVariant}"
|
|
111
|
+
?disabled=${action.disabled}
|
|
112
|
+
family="${DesignSystemFamily.neutral}"
|
|
113
|
+
size="${DesignSystemSize.large}"
|
|
114
|
+
label="${ifDefined(action.label)}"
|
|
115
|
+
@click=${action.run}>
|
|
116
|
+
${ifDefined(action.text)}
|
|
117
|
+
</ftds-button>
|
|
118
|
+
`)}
|
|
119
|
+
</div>`;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
return nothing;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async onDragButtonKeyDown(event) {
|
|
126
|
+
const itemId = event.target.dataset.itemId;
|
|
127
|
+
const itemIndex = this.items.findIndex((e) => e.id === itemId);
|
|
128
|
+
this.dragGrips[itemIndex].classList.remove("dragging");
|
|
129
|
+
if (itemIndex > -1 && ["PageUp", "PageDown", "ArrowUp", "ArrowDown"].includes(event.key)) {
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
const move = event.altKey || event.key.startsWith("Page");
|
|
132
|
+
const up = event.key.includes("Up");
|
|
133
|
+
if (move) {
|
|
134
|
+
if (up) {
|
|
135
|
+
this.moveItemUp(itemIndex);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
this.moveItemDown(itemIndex);
|
|
139
|
+
}
|
|
140
|
+
await this.keepFocusOnItem(itemId);
|
|
141
|
+
this.dispatchEvent(new OrderChangeEvent());
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
if (up) {
|
|
145
|
+
this.moveFocusToPreviousItem(itemIndex);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
this.moveFocusToNextItem(itemIndex);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async keepFocusOnItem(itemId) {
|
|
154
|
+
await this.updateComplete;
|
|
155
|
+
const gripButton = this.shadowRoot.querySelector(`ftds-icon[data-item-id="${itemId}"]`);
|
|
156
|
+
gripButton.classList.add("dragging");
|
|
157
|
+
gripButton.focus();
|
|
158
|
+
}
|
|
159
|
+
moveItemUp(itemIndex) {
|
|
160
|
+
if (itemIndex > 0) {
|
|
161
|
+
this.items = this.swapItems(itemIndex, itemIndex - 1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
moveItemDown(itemIndex) {
|
|
165
|
+
if (itemIndex < this.items.length - 1) {
|
|
166
|
+
this.items = this.swapItems(itemIndex, itemIndex + 1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
swapItems(index1, index2) {
|
|
170
|
+
const reorderedItems = [...this.items];
|
|
171
|
+
const temp = reorderedItems[index1];
|
|
172
|
+
reorderedItems[index1] = reorderedItems[index2];
|
|
173
|
+
reorderedItems[index2] = temp;
|
|
174
|
+
return reorderedItems;
|
|
175
|
+
}
|
|
176
|
+
onDragStart(event) {
|
|
177
|
+
event.dataTransfer.effectAllowed = "move";
|
|
178
|
+
this.draggingItem = event.target;
|
|
179
|
+
this.draggingItem.classList.add("dragging");
|
|
180
|
+
}
|
|
181
|
+
onDragOver(event) {
|
|
182
|
+
var _a;
|
|
183
|
+
event.preventDefault();
|
|
184
|
+
const draggingOverItem = this.getDragAfterElement(this.list, event.clientY);
|
|
185
|
+
this.draggingItem.style.cursor = "grabbing";
|
|
186
|
+
(_a = this.draggableItems) === null || _a === void 0 ? void 0 : _a.forEach((item) => item.classList.remove("over"));
|
|
187
|
+
if (draggingOverItem) {
|
|
188
|
+
this.list.insertBefore(this.draggingItem, draggingOverItem);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
this.list.appendChild(this.draggingItem);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
onDragEnd(event) {
|
|
195
|
+
var _a;
|
|
196
|
+
this.syncReferenceList();
|
|
197
|
+
event.target.classList.remove("dragging");
|
|
198
|
+
(_a = this.draggableItems) === null || _a === void 0 ? void 0 : _a.forEach((item) => item.classList.remove("over"));
|
|
199
|
+
this.draggingItem.remove();
|
|
200
|
+
this.draggingItem = undefined;
|
|
201
|
+
this.dispatchEvent(new OrderChangeEvent());
|
|
202
|
+
}
|
|
203
|
+
syncReferenceList() {
|
|
204
|
+
const newOrder = [...this.dragGrips].map((e) => e.dataset.itemId);
|
|
205
|
+
this.items = newOrder.map((id) => this.items.find((item) => item.id === id));
|
|
206
|
+
}
|
|
207
|
+
getDragAfterElement(container, y) {
|
|
208
|
+
const draggableElements = [...this.notDraggedItems];
|
|
209
|
+
let closest = { offset: Number.NEGATIVE_INFINITY, element: null };
|
|
210
|
+
for (const child of draggableElements) {
|
|
211
|
+
const box = child.getBoundingClientRect();
|
|
212
|
+
const offset = y - box.top - box.height / 2;
|
|
213
|
+
if (offset < 0 && offset > closest.offset) {
|
|
214
|
+
closest = { offset: offset, element: child };
|
|
215
|
+
return closest.element;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
moveFocusToPreviousItem(itemIndex) {
|
|
221
|
+
if (itemIndex > 0) {
|
|
222
|
+
const buttonToFocus = this.dragGrips[itemIndex - 1];
|
|
223
|
+
buttonToFocus === null || buttonToFocus === void 0 ? void 0 : buttonToFocus.focus();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
moveFocusToNextItem(itemIndex) {
|
|
227
|
+
if (itemIndex < this.items.length - 1) {
|
|
228
|
+
const buttonToFocus = this.dragGrips[itemIndex + 1];
|
|
229
|
+
buttonToFocus === null || buttonToFocus === void 0 ? void 0 : buttonToFocus.focus();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
getSelectedOption(event) {
|
|
233
|
+
const target = event.target;
|
|
234
|
+
const itemId = target.dataset.itemId;
|
|
235
|
+
return [...this.draggableItems].find((e) => e.id === itemId);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
FtdsReorderableList.elementDefinitions = {
|
|
239
|
+
"ftds-typography": FtdsTypography,
|
|
240
|
+
"ftds-button": FtdsButton,
|
|
241
|
+
"ftds-icon": FtdsIcon,
|
|
242
|
+
"ftds-notice": FtdsNotice,
|
|
243
|
+
};
|
|
244
|
+
FtdsReorderableList.styles = styles;
|
|
245
|
+
__decorate([
|
|
246
|
+
property({ type: Boolean })
|
|
247
|
+
], FtdsReorderableList.prototype, "editable", void 0);
|
|
248
|
+
__decorate([
|
|
249
|
+
property({ type: Boolean })
|
|
250
|
+
], FtdsReorderableList.prototype, "removable", void 0);
|
|
251
|
+
__decorate([
|
|
252
|
+
property()
|
|
253
|
+
], FtdsReorderableList.prototype, "emptyListMessage", void 0);
|
|
254
|
+
__decorate([
|
|
255
|
+
property()
|
|
256
|
+
], FtdsReorderableList.prototype, "items", void 0);
|
|
257
|
+
__decorate([
|
|
258
|
+
query("ol")
|
|
259
|
+
], FtdsReorderableList.prototype, "list", void 0);
|
|
260
|
+
__decorate([
|
|
261
|
+
queryAll("li")
|
|
262
|
+
], FtdsReorderableList.prototype, "draggableItems", void 0);
|
|
263
|
+
__decorate([
|
|
264
|
+
queryAll("li:not(.dragging)")
|
|
265
|
+
], FtdsReorderableList.prototype, "notDraggedItems", void 0);
|
|
266
|
+
__decorate([
|
|
267
|
+
queryAll("ftds-icon[data-item-id]")
|
|
268
|
+
], FtdsReorderableList.prototype, "dragGrips", void 0);
|