@dojo-ng/data-grid-drag 0.0.0-stage → 0.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 +65 -2
- package/dist/drag-plugin.d.ts +48 -0
- package/dist/drag-plugin.js +128 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +20 -4
package/README.md
CHANGED
|
@@ -1,3 +1,66 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @dojo-ng/data-grid-drag
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Drag rows out of a `<dj-data-grid>` with native HTML5 drag and drop, for example onto a `<dj-tree>` folder list.
|
|
4
|
+
|
|
5
|
+
Part of [Dojo NG](../../README.md), a framework-agnostic web component library. BSD-3-Clause.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @dojo-ng/data-grid-drag
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
The grid writes the dragged rows' ids under its own MIME type; the tree accepts only that type, and `dj-drop` names the folder.
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<dj-tree id="folders"></dj-tree>
|
|
19
|
+
<dj-data-grid id="mail" height="320px" activation="click" selection-mode="multiple"></dj-data-grid>
|
|
20
|
+
<script type="module">
|
|
21
|
+
import "@dojo-ng/tree";
|
|
22
|
+
import "@dojo-ng/data-grid";
|
|
23
|
+
import { selectColumnPlugin } from "@dojo-ng/data-grid-select";
|
|
24
|
+
import { rowDragPlugin } from "@dojo-ng/data-grid-drag";
|
|
25
|
+
const TYPE = "application/x-demo-messages";
|
|
26
|
+
|
|
27
|
+
const tree = document.getElementById("folders");
|
|
28
|
+
tree.nodes = [{ id: "inbox", label: "Inbox" }, { id: "archive", label: "Archive" }];
|
|
29
|
+
tree.canDrop = (id, types) => types.includes(TYPE);
|
|
30
|
+
tree.addEventListener("dj-drop", (e) => {
|
|
31
|
+
const ids = JSON.parse(e.detail.dataTransfer.getData(TYPE));
|
|
32
|
+
move(ids, e.detail.id);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const grid = document.getElementById("mail");
|
|
36
|
+
grid.columns = [{ id: "from", header: "From" }, { id: "subject", header: "Subject" }];
|
|
37
|
+
grid.getRowId = (m) => String(m.id);
|
|
38
|
+
grid.data = [
|
|
39
|
+
{ id: 1, from: "Ada", subject: "Analytical engine" },
|
|
40
|
+
{ id: 2, from: "Grace", subject: "Compiler notes" },
|
|
41
|
+
];
|
|
42
|
+
grid.plugins = [
|
|
43
|
+
selectColumnPlugin({ label: (m) => `Select ${m.subject}` }),
|
|
44
|
+
rowDragPlugin({
|
|
45
|
+
type: TYPE,
|
|
46
|
+
data: (rows) => JSON.stringify(rows.map((m) => m.id)),
|
|
47
|
+
label: (rows) => (rows.length === 1 ? rows[0].subject : `${rows.length} messages`),
|
|
48
|
+
}),
|
|
49
|
+
];
|
|
50
|
+
</script>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## How it works
|
|
54
|
+
|
|
55
|
+
- Every row becomes `draggable`. Dragging a selected row drags every selected row; dragging an unselected row drags it alone and leaves the selection as it was.
|
|
56
|
+
- The payload is yours: `data(rows)` is written to the drag under the MIME type you name in `type`. A drop target checks for that type, which is the only part of a payload the browser shows before the drop.
|
|
57
|
+
- The drag image is a small chip. `label(rows)` sets its text (default "1 row" / "N rows").
|
|
58
|
+
- Clicking, double clicking, and the checkbox column work as before: a drag only starts once the pointer moves, and a drag that starts on a control inside a cell is canceled.
|
|
59
|
+
|
|
60
|
+
## Events
|
|
61
|
+
|
|
62
|
+
- `dj-row-dragstart` (cancelable, detail `{ rows }`) and `dj-row-dragend` (detail `{ rows, dropEffect }`). Dragged rows carry the `row--dragging` part until the drag ends.
|
|
63
|
+
|
|
64
|
+
## Accessibility
|
|
65
|
+
|
|
66
|
+
- Dragging has no keyboard form. Offer the same action another way, such as a Move command with a picker (WCAG 2.5.7).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type DataGridPlugin, type Row } from "@dojo-ng/data-grid";
|
|
2
|
+
export interface RowDragOptions {
|
|
3
|
+
/** The MIME type the payload is written under, e.g. `"application/x-myapp-items"`. A drop
|
|
4
|
+
* target checks for it in `dataTransfer.types`, the only part of a payload a browser shows
|
|
5
|
+
* before the drop. */
|
|
6
|
+
type: string;
|
|
7
|
+
/** Build the payload from the dragged rows' data. Written under `type`. */
|
|
8
|
+
data: (rows: Row[]) => string;
|
|
9
|
+
/** Text for the drag image, and the `text/plain` fallback. Defaults to "1 row" / "N rows". */
|
|
10
|
+
label?: (rows: Row[]) => string;
|
|
11
|
+
}
|
|
12
|
+
/** Detail of `dj-row-dragstart`. Cancel the event to refuse the drag. */
|
|
13
|
+
export interface RowDragStartDetail {
|
|
14
|
+
rows: Row[];
|
|
15
|
+
}
|
|
16
|
+
/** Detail of `dj-row-dragend`. `dropEffect` is `"none"` when the drag was dropped nowhere or
|
|
17
|
+
* canceled with Escape. */
|
|
18
|
+
export interface RowDragEndDetail {
|
|
19
|
+
rows: Row[];
|
|
20
|
+
dropEffect: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `rowDragPlugin(options)` — makes `<dj-data-grid>` rows drag sources for native HTML5 drag and
|
|
24
|
+
* drop.
|
|
25
|
+
*
|
|
26
|
+
* Every row is `draggable`. Dragging a selected row drags every selected row; dragging an
|
|
27
|
+
* unselected row drags that row alone and leaves the selection as it was. The payload is yours:
|
|
28
|
+
* `data(rows)` is written under `type`, so a drop target (such as `dj-tree` with `canDrop`) can
|
|
29
|
+
* accept exactly the drags it understands. The drag image is a small chip naming what moves.
|
|
30
|
+
*
|
|
31
|
+
* A drag only starts after the pointer moves, so clicking, double clicking, and the checkbox
|
|
32
|
+
* column behave as without the plugin. A drag that starts on a control inside a cell (a checkbox,
|
|
33
|
+
* button, or link) is canceled and stays a click on that control.
|
|
34
|
+
*
|
|
35
|
+
* Events, from the grid: `dj-row-dragstart` (cancelable; detail `{ rows }`), then
|
|
36
|
+
* `dj-row-dragend` (detail `{ rows, dropEffect }`). Dragged rows carry the `row--dragging` part
|
|
37
|
+
* until the drag ends.
|
|
38
|
+
*
|
|
39
|
+
* Dragging has no keyboard form. Offer the same action another way (WCAG 2.5.7), such as a
|
|
40
|
+
* command that opens a picker.
|
|
41
|
+
*/
|
|
42
|
+
export declare function rowDragPlugin(options: RowDragOptions): DataGridPlugin;
|
|
43
|
+
declare global {
|
|
44
|
+
interface GlobalEventHandlersEventMap {
|
|
45
|
+
"dj-row-dragstart": CustomEvent<RowDragStartDetail>;
|
|
46
|
+
"dj-row-dragend": CustomEvent<RowDragEndDetail>;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { defineDataGridPlugin } from "@dojo-ng/data-grid";
|
|
2
|
+
/** Starting a drag on one of these keeps it a click on the control instead. */
|
|
3
|
+
const INTERACTIVE = "input, button, a[href], select, textarea, [contenteditable]:not([contenteditable=false])";
|
|
4
|
+
const defaultLabel = (rows) => (rows.length === 1 ? "1 row" : `${rows.length} rows`);
|
|
5
|
+
/**
|
|
6
|
+
* `rowDragPlugin(options)` — makes `<dj-data-grid>` rows drag sources for native HTML5 drag and
|
|
7
|
+
* drop.
|
|
8
|
+
*
|
|
9
|
+
* Every row is `draggable`. Dragging a selected row drags every selected row; dragging an
|
|
10
|
+
* unselected row drags that row alone and leaves the selection as it was. The payload is yours:
|
|
11
|
+
* `data(rows)` is written under `type`, so a drop target (such as `dj-tree` with `canDrop`) can
|
|
12
|
+
* accept exactly the drags it understands. The drag image is a small chip naming what moves.
|
|
13
|
+
*
|
|
14
|
+
* A drag only starts after the pointer moves, so clicking, double clicking, and the checkbox
|
|
15
|
+
* column behave as without the plugin. A drag that starts on a control inside a cell (a checkbox,
|
|
16
|
+
* button, or link) is canceled and stays a click on that control.
|
|
17
|
+
*
|
|
18
|
+
* Events, from the grid: `dj-row-dragstart` (cancelable; detail `{ rows }`), then
|
|
19
|
+
* `dj-row-dragend` (detail `{ rows, dropEffect }`). Dragged rows carry the `row--dragging` part
|
|
20
|
+
* until the drag ends.
|
|
21
|
+
*
|
|
22
|
+
* Dragging has no keyboard form. Offer the same action another way (WCAG 2.5.7), such as a
|
|
23
|
+
* command that opens a picker.
|
|
24
|
+
*/
|
|
25
|
+
export function rowDragPlugin(options) {
|
|
26
|
+
const label = options.label ?? defaultLabel;
|
|
27
|
+
/** Row ids being dragged, for the `row--dragging` part. */
|
|
28
|
+
let dragging = new Set();
|
|
29
|
+
let draggedData = [];
|
|
30
|
+
return defineDataGridPlugin({
|
|
31
|
+
name: "drag",
|
|
32
|
+
rowAttributes(row) {
|
|
33
|
+
const attrs = { draggable: "true" };
|
|
34
|
+
if (dragging.has(row.id))
|
|
35
|
+
attrs.part = "row--dragging";
|
|
36
|
+
return attrs;
|
|
37
|
+
},
|
|
38
|
+
setup(ctx) {
|
|
39
|
+
const host = ctx.host;
|
|
40
|
+
const root = host.renderRoot;
|
|
41
|
+
/** The table row a drag started on, from its composed path; null when it started on a
|
|
42
|
+
* control or outside a body row. */
|
|
43
|
+
const rowOf = (e) => {
|
|
44
|
+
for (const n of e.composedPath()) {
|
|
45
|
+
if (n === host)
|
|
46
|
+
break;
|
|
47
|
+
const el = n;
|
|
48
|
+
if (!el.matches)
|
|
49
|
+
continue;
|
|
50
|
+
if (el.matches(INTERACTIVE))
|
|
51
|
+
return null;
|
|
52
|
+
if (el.classList.contains("vrow")) {
|
|
53
|
+
const index = Number((el.id || "").replace(/^r-/, ""));
|
|
54
|
+
return Number.isInteger(index) ? (ctx.table.getRowModel().rows[index] ?? null) : null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
};
|
|
59
|
+
const onStart = (e) => {
|
|
60
|
+
const de = e;
|
|
61
|
+
const row = rowOf(de);
|
|
62
|
+
if (!row) {
|
|
63
|
+
de.preventDefault();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const rows = row.getIsSelected() ? ctx.table.getSelectedRowModel().flatRows : [row];
|
|
67
|
+
const data = rows.map((r) => r.original);
|
|
68
|
+
const start = new CustomEvent("dj-row-dragstart", {
|
|
69
|
+
bubbles: true, composed: true, cancelable: true, detail: { rows: data },
|
|
70
|
+
});
|
|
71
|
+
if (!host.dispatchEvent(start)) {
|
|
72
|
+
de.preventDefault();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const dt = de.dataTransfer;
|
|
76
|
+
const text = label(data);
|
|
77
|
+
if (dt) {
|
|
78
|
+
dt.setData(options.type, options.data(data));
|
|
79
|
+
dt.setData("text/plain", text);
|
|
80
|
+
dt.effectAllowed = "move";
|
|
81
|
+
setDragImage(dt, text);
|
|
82
|
+
}
|
|
83
|
+
dragging = new Set(rows.map((r) => r.id));
|
|
84
|
+
draggedData = data;
|
|
85
|
+
ctx.refresh();
|
|
86
|
+
};
|
|
87
|
+
const onEnd = (e) => {
|
|
88
|
+
if (!dragging.size)
|
|
89
|
+
return;
|
|
90
|
+
const dropEffect = e.dataTransfer?.dropEffect ?? "none";
|
|
91
|
+
const rows = draggedData;
|
|
92
|
+
dragging = new Set();
|
|
93
|
+
draggedData = [];
|
|
94
|
+
ctx.refresh();
|
|
95
|
+
host.dispatchEvent(new CustomEvent("dj-row-dragend", {
|
|
96
|
+
bubbles: true, composed: true, detail: { rows, dropEffect },
|
|
97
|
+
}));
|
|
98
|
+
};
|
|
99
|
+
root.addEventListener("dragstart", onStart);
|
|
100
|
+
root.addEventListener("dragend", onEnd);
|
|
101
|
+
return () => {
|
|
102
|
+
root.removeEventListener("dragstart", onStart);
|
|
103
|
+
root.removeEventListener("dragend", onEnd);
|
|
104
|
+
dragging = new Set();
|
|
105
|
+
draggedData = [];
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** A chip naming what moves, used as the drag image. The browser snapshots it during
|
|
111
|
+
* `dragstart`, so it can be removed on the next tick. */
|
|
112
|
+
function setDragImage(dt, text) {
|
|
113
|
+
if (typeof dt.setDragImage !== "function" || typeof document === "undefined")
|
|
114
|
+
return;
|
|
115
|
+
const chip = document.createElement("div");
|
|
116
|
+
chip.textContent = text;
|
|
117
|
+
chip.setAttribute("aria-hidden", "true");
|
|
118
|
+
chip.style.cssText = [
|
|
119
|
+
"position:fixed", "top:-1000px", "left:-1000px", "max-width:20rem",
|
|
120
|
+
"overflow:hidden", "text-overflow:ellipsis", "white-space:nowrap",
|
|
121
|
+
"padding:0.25rem 0.6rem", "border-radius:999px",
|
|
122
|
+
"font:500 0.8125rem/1.4 var(--dj-font-sans, system-ui, sans-serif)",
|
|
123
|
+
"background:var(--dj-color-primary-600, #2563eb)", "color:var(--dj-color-text-on-primary, #fff)",
|
|
124
|
+
].join(";");
|
|
125
|
+
document.body.appendChild(chip);
|
|
126
|
+
dt.setDragImage(chip, 12, 12);
|
|
127
|
+
setTimeout(() => chip.remove(), 0);
|
|
128
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dojo-ng/data-grid-drag",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "BSD-3-Clause",
|
|
5
|
+
"description": "Row drag source plugin for @dojo-ng/data-grid (native HTML5 drag, drags the selection)",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"!dist/**/*.map"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -p tsconfig.json",
|
|
16
|
+
"clean": "rm -rf dist"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@dojo-ng/data-grid": "^0.2.0",
|
|
20
|
+
"@tanstack/table-core": "^8.0.0"
|
|
21
|
+
}
|
|
22
|
+
}
|