@svelte-lean/table 0.15.4 → 0.16.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/CHANGELOG.md +21 -0
- package/dist/table/data-table.svelte +11 -7
- package/dist/table/row-drag.d.ts +22 -18
- package/dist/table/row-drag.js +19 -239
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @svelte-lean/table
|
|
2
2
|
|
|
3
|
+
## 0.16.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 12ca0ce: The live row drag (`onRowMove`) runs on the shared reorder engine of `@svelte-lean/core/reorder`
|
|
8
|
+
(ADR 0012), which the new sortable primitive uses too; `@svelte-lean/table` now depends on
|
|
9
|
+
`@svelte-lean/core` and imports only that subpath. The behavior, the attributes
|
|
10
|
+
(`data-row-dragging`, `data-row-settling`, `data-lifted`) and the keyboard are unchanged, and the
|
|
11
|
+
per-move cost is unchanged within measurement noise; the renderer grows by 366 B gzip (23742 B →
|
|
12
|
+
24108 B), and its budget is raised from 23900 B to 24300 B.
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Updated dependencies [8a774c6]
|
|
17
|
+
- Updated dependencies [12ca0ce]
|
|
18
|
+
- @svelte-lean/core@0.16.0
|
|
19
|
+
|
|
20
|
+
## 0.15.5
|
|
21
|
+
|
|
22
|
+
No changes in this release.
|
|
23
|
+
|
|
3
24
|
## 0.15.4
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
|
@@ -82,7 +82,8 @@
|
|
|
82
82
|
import { en } from '../messages.js';
|
|
83
83
|
import { parseEdit } from '../editing.js';
|
|
84
84
|
import { horizontalCell, verticalCell } from './navigation.js';
|
|
85
|
-
import {
|
|
85
|
+
import { reorder } from '@svelte-lean/core/reorder';
|
|
86
|
+
import { ROW_DRAG, rowBlock } from './row-drag.js';
|
|
86
87
|
let {
|
|
87
88
|
data,
|
|
88
89
|
columns,
|
|
@@ -1385,18 +1386,21 @@
|
|
|
1385
1386
|
);
|
|
1386
1387
|
return true;
|
|
1387
1388
|
}
|
|
1388
|
-
/** A press on a grip starts a live drag (`row-drag.ts`); one delegated listener for the table. */
|
|
1389
|
+
/** A press on a grip starts a live drag (`row-drag.ts`, `@svelte-lean/core/reorder`); one delegated listener for the table. */
|
|
1389
1390
|
function bodyPointerDown(e: PointerEvent) {
|
|
1390
1391
|
const row = gripRow(e.target);
|
|
1391
1392
|
if (!movable || !row) return;
|
|
1392
|
-
|
|
1393
|
+
reorder({
|
|
1393
1394
|
event: e,
|
|
1394
|
-
|
|
1395
|
-
row,
|
|
1395
|
+
handle: e.target as HTMLElement,
|
|
1396
|
+
item: row,
|
|
1396
1397
|
root,
|
|
1397
1398
|
viewport,
|
|
1398
|
-
|
|
1399
|
-
|
|
1399
|
+
items: () => [...ownRows()],
|
|
1400
|
+
key: (tr) => tr.getAttribute('data-row-id') ?? '',
|
|
1401
|
+
block: rowBlock,
|
|
1402
|
+
attributes: ROW_DRAG,
|
|
1403
|
+
drop: ({ source, target }) => {
|
|
1400
1404
|
const a = nodes.get(source)?.id;
|
|
1401
1405
|
const b = nodes.get(target)?.id;
|
|
1402
1406
|
if (a !== undefined && b !== undefined) onRowMove?.(a, b);
|
package/dist/table/row-drag.d.ts
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Live row dragging for `<DataTable />` (`onRowMove`) runs on the shared reorder engine of
|
|
3
|
+
* `@svelte-lean/core/reorder` (ADR 0012), which the sortable primitive uses too; the renderer's
|
|
4
|
+
* `bodyPointerDown` starts it. This module holds what is the table's own: which elements move with
|
|
5
|
+
* a row, and the attribute names `style.css` reads.
|
|
6
|
+
*
|
|
7
|
+
* A press on a grip lifts its row after a few pixels. The lifted row (and its detail rows) follows
|
|
8
|
+
* the pointer; the rows between its place and the place under it slide out of its way, so the drop
|
|
9
|
+
* position is visible before release. Release reports the move (`onRowMove`), waits for the
|
|
10
|
+
* application's update (Svelte's `tick`) and lets the row glide from where it was dropped into the
|
|
11
|
+
* place it now has. A cancel (Escape, `pointercancel`, a lost capture), or a move the application
|
|
12
|
+
* did not make, slides every row back. Near the visible edge of the scrolling container the
|
|
13
|
+
* container scrolls, on a timer. Every listener exists only for the length of one press.
|
|
14
|
+
*/
|
|
15
|
+
/** `data-row-dragging` and `data-row-settling` on the `.slean-table` root, `data-lifted` on the rows. */
|
|
16
|
+
export declare const ROW_DRAG: {
|
|
17
|
+
dragging: string;
|
|
18
|
+
settling: string;
|
|
19
|
+
lifted: string;
|
|
20
|
+
};
|
|
21
|
+
/** A data row and the detail rows that follow it: they move as one. */
|
|
22
|
+
export declare function rowBlock(row: Element): HTMLElement[];
|
package/dist/table/row-drag.js
CHANGED
|
@@ -1,247 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Live row dragging for `<DataTable />` (`onRowMove`)
|
|
3
|
-
*
|
|
2
|
+
* Live row dragging for `<DataTable />` (`onRowMove`) runs on the shared reorder engine of
|
|
3
|
+
* `@svelte-lean/core/reorder` (ADR 0012), which the sortable primitive uses too; the renderer's
|
|
4
|
+
* `bodyPointerDown` starts it. This module holds what is the table's own: which elements move with
|
|
5
|
+
* a row, and the attribute names `style.css` reads.
|
|
4
6
|
*
|
|
5
|
-
* A press on a grip lifts its row after
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* application's update (`
|
|
9
|
-
* it now has. A cancel (Escape, `pointercancel`, a lost capture), or a move the application
|
|
10
|
-
* make, slides every row back. Near the visible edge of the scrolling container the
|
|
11
|
-
* scrolls, on a timer
|
|
12
|
-
*
|
|
13
|
-
* Every listener exists only for the length of one press and is removed through one
|
|
14
|
-
* `AbortController`. Nothing here runs at module evaluation.
|
|
7
|
+
* A press on a grip lifts its row after a few pixels. The lifted row (and its detail rows) follows
|
|
8
|
+
* the pointer; the rows between its place and the place under it slide out of its way, so the drop
|
|
9
|
+
* position is visible before release. Release reports the move (`onRowMove`), waits for the
|
|
10
|
+
* application's update (Svelte's `tick`) and lets the row glide from where it was dropped into the
|
|
11
|
+
* place it now has. A cancel (Escape, `pointercancel`, a lost capture), or a move the application
|
|
12
|
+
* did not make, slides every row back. Near the visible edge of the scrolling container the
|
|
13
|
+
* container scrolls, on a timer. Every listener exists only for the length of one press.
|
|
15
14
|
*/
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
function blockOf(row) {
|
|
15
|
+
/** `data-row-dragging` and `data-row-settling` on the `.slean-table` root, `data-lifted` on the rows. */
|
|
16
|
+
export const ROW_DRAG = {
|
|
17
|
+
dragging: 'data-row-dragging',
|
|
18
|
+
settling: 'data-row-settling',
|
|
19
|
+
lifted: 'data-lifted'
|
|
20
|
+
};
|
|
21
|
+
/** A data row and the detail rows that follow it: they move as one. */
|
|
22
|
+
export function rowBlock(row) {
|
|
25
23
|
const rows = [row];
|
|
26
24
|
for (let next = row.nextElementSibling; next?.matches('tr.slean-table-detail'); next = next.nextElementSibling)
|
|
27
25
|
rows.push(next);
|
|
28
26
|
return rows;
|
|
29
27
|
}
|
|
30
|
-
function keyOf(row) {
|
|
31
|
-
return row.getAttribute('data-row-id') ?? '';
|
|
32
|
-
}
|
|
33
|
-
function translate(rows, y) {
|
|
34
|
-
for (const row of rows)
|
|
35
|
-
row.style.transform = y ? `translateY(${y}px)` : '';
|
|
36
|
-
}
|
|
37
|
-
/** The nearest container above the table that scrolls vertically; the document otherwise. */
|
|
38
|
-
function scrollerOf(viewport) {
|
|
39
|
-
const doc = viewport.ownerDocument;
|
|
40
|
-
for (let element = viewport; element && element !== doc.body && element !== doc.documentElement; element = element.parentElement) {
|
|
41
|
-
if (element.scrollHeight > element.clientHeight + 1 &&
|
|
42
|
-
/auto|scroll|overlay/.test(getComputedStyle(element).overflowY))
|
|
43
|
-
return element;
|
|
44
|
-
}
|
|
45
|
-
return doc.scrollingElement ?? doc.documentElement;
|
|
46
|
-
}
|
|
47
|
-
export function dragRow(options) {
|
|
48
|
-
const { event, grip, root } = options;
|
|
49
|
-
if (event.button !== 0 || !event.isPrimary)
|
|
50
|
-
return;
|
|
51
|
-
finishing.get(root)?.();
|
|
52
|
-
const doc = grip.ownerDocument;
|
|
53
|
-
const startX = event.clientX;
|
|
54
|
-
const startY = event.clientY;
|
|
55
|
-
let y = startY;
|
|
56
|
-
const controller = new AbortController();
|
|
57
|
-
const { signal } = controller;
|
|
58
|
-
let blocks = [];
|
|
59
|
-
let dragged;
|
|
60
|
-
let from = -1;
|
|
61
|
-
let target = -1;
|
|
62
|
-
let scroller = doc.documentElement;
|
|
63
|
-
let page = false;
|
|
64
|
-
let scrollStart = 0;
|
|
65
|
-
let written = 0;
|
|
66
|
-
let timer;
|
|
67
|
-
try {
|
|
68
|
-
grip.setPointerCapture(event.pointerId);
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
// No capture (an engine or a test environment without it): the document listeners below
|
|
72
|
-
// still receive the moves while the pointer stays over the page.
|
|
73
|
-
}
|
|
74
|
-
function lift() {
|
|
75
|
-
blocks = options.rows().map((row) => {
|
|
76
|
-
const rows = blockOf(row);
|
|
77
|
-
const first = rows[0].getBoundingClientRect();
|
|
78
|
-
const last = rows[rows.length - 1].getBoundingClientRect();
|
|
79
|
-
return { key: keyOf(row), rows, top: first.top, height: last.bottom - first.top, shift: 0 };
|
|
80
|
-
});
|
|
81
|
-
from = target = blocks.findIndex((block) => block.rows[0] === options.row);
|
|
82
|
-
dragged = blocks[from];
|
|
83
|
-
if (!dragged)
|
|
84
|
-
return false;
|
|
85
|
-
scroller = scrollerOf(options.viewport);
|
|
86
|
-
page = scroller === doc.scrollingElement || scroller === doc.documentElement;
|
|
87
|
-
scrollStart = scroller.scrollTop;
|
|
88
|
-
root.setAttribute('data-row-dragging', '');
|
|
89
|
-
for (const row of dragged.rows) {
|
|
90
|
-
row.setAttribute('data-lifted', '');
|
|
91
|
-
// The lifted row follows the pointer exactly; the others slide (`style.css`).
|
|
92
|
-
row.style.transition = 'none';
|
|
93
|
-
}
|
|
94
|
-
doc.addEventListener('keydown', escape, { signal, capture: true });
|
|
95
|
-
(page ? doc : scroller).addEventListener('scroll', frame, { signal, passive: true });
|
|
96
|
-
timer = setInterval(autoScroll, INTERVAL);
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
function frame() {
|
|
100
|
-
if (!dragged)
|
|
101
|
-
return;
|
|
102
|
-
const dy = y - startY + scroller.scrollTop - scrollStart;
|
|
103
|
-
if (dy !== written) {
|
|
104
|
-
written = dy;
|
|
105
|
-
translate(dragged.rows, dy);
|
|
106
|
-
}
|
|
107
|
-
const centre = dragged.top + dy + dragged.height / 2;
|
|
108
|
-
const middle = (block) => block.top + block.height / 2;
|
|
109
|
-
let next = from;
|
|
110
|
-
while (next + 1 < blocks.length && centre > middle(blocks[next + 1]))
|
|
111
|
-
next++;
|
|
112
|
-
if (next === from)
|
|
113
|
-
while (next > 0 && centre < middle(blocks[next - 1]))
|
|
114
|
-
next--;
|
|
115
|
-
target = next;
|
|
116
|
-
blocks.forEach((block, i) => {
|
|
117
|
-
if (i === from)
|
|
118
|
-
return;
|
|
119
|
-
const shift = i > from && i <= next ? -dragged.height : i < from && i >= next ? dragged.height : 0;
|
|
120
|
-
if (shift === block.shift)
|
|
121
|
-
return;
|
|
122
|
-
block.shift = shift;
|
|
123
|
-
translate(block.rows, shift);
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
/** Scrolls while the pointer is within `EDGE` of the container's visible top or bottom. */
|
|
127
|
-
function autoScroll() {
|
|
128
|
-
const height = doc.documentElement.clientHeight;
|
|
129
|
-
const box = page ? { top: 0, bottom: height } : scroller.getBoundingClientRect();
|
|
130
|
-
const top = Math.max(box.top, 0);
|
|
131
|
-
const bottom = Math.min(box.bottom, height);
|
|
132
|
-
const speed = (distance) => Math.ceil(SPEED * (1 - Math.max(0, distance) / EDGE));
|
|
133
|
-
const step = y - top < EDGE ? -speed(y - top) : bottom - y < EDGE ? speed(bottom - y) : 0;
|
|
134
|
-
if (!step)
|
|
135
|
-
return;
|
|
136
|
-
const before = scroller.scrollTop;
|
|
137
|
-
scroller.scrollTop = before + step;
|
|
138
|
-
if (scroller.scrollTop !== before)
|
|
139
|
-
frame();
|
|
140
|
-
}
|
|
141
|
-
function stop() {
|
|
142
|
-
controller.abort();
|
|
143
|
-
clearInterval(timer);
|
|
144
|
-
}
|
|
145
|
-
/** Leaves the rows as they are now, after the slide back or into place. */
|
|
146
|
-
function later(rows) {
|
|
147
|
-
const done = () => {
|
|
148
|
-
clearTimeout(handle);
|
|
149
|
-
finishing.delete(root);
|
|
150
|
-
for (const row of rows) {
|
|
151
|
-
row.removeAttribute('data-lifted');
|
|
152
|
-
row.style.transform = '';
|
|
153
|
-
row.style.transition = '';
|
|
154
|
-
}
|
|
155
|
-
root.removeAttribute('data-row-settling');
|
|
156
|
-
root.removeAttribute('data-row-dragging');
|
|
157
|
-
};
|
|
158
|
-
const handle = setTimeout(done, SETTLE_MS);
|
|
159
|
-
finishing.set(root, done);
|
|
160
|
-
}
|
|
161
|
-
/** Every row slides back to its place. */
|
|
162
|
-
function back() {
|
|
163
|
-
for (const row of dragged.rows)
|
|
164
|
-
row.style.transition = '';
|
|
165
|
-
for (const block of blocks) {
|
|
166
|
-
block.shift = 0;
|
|
167
|
-
translate(block.rows, 0);
|
|
168
|
-
}
|
|
169
|
-
later(blocks.flatMap((block) => block.rows));
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* The application has answered the move. Positions are reset with transitions off; the dragged
|
|
173
|
-
* row (perhaps a new element: it is found again by its id) starts where it was dropped and
|
|
174
|
-
* slides into its new place.
|
|
175
|
-
*/
|
|
176
|
-
function settle(dropped) {
|
|
177
|
-
const key = dragged.key;
|
|
178
|
-
const now = options.rows();
|
|
179
|
-
const row = now.find((element) => keyOf(element) === key);
|
|
180
|
-
if (row && now.indexOf(row) === from)
|
|
181
|
-
return back();
|
|
182
|
-
root.setAttribute('data-row-settling', '');
|
|
183
|
-
for (const block of blocks)
|
|
184
|
-
for (const element of block.rows) {
|
|
185
|
-
element.removeAttribute('data-lifted');
|
|
186
|
-
element.style.transform = '';
|
|
187
|
-
element.style.transition = '';
|
|
188
|
-
}
|
|
189
|
-
if (!row)
|
|
190
|
-
return later([]);
|
|
191
|
-
const rows = blockOf(row);
|
|
192
|
-
const offset = dropped - row.getBoundingClientRect().top;
|
|
193
|
-
for (const element of rows)
|
|
194
|
-
element.setAttribute('data-lifted', '');
|
|
195
|
-
translate(rows, offset);
|
|
196
|
-
void row.offsetHeight;
|
|
197
|
-
root.removeAttribute('data-row-settling');
|
|
198
|
-
translate(rows, 0);
|
|
199
|
-
later(rows);
|
|
200
|
-
}
|
|
201
|
-
function drop() {
|
|
202
|
-
stop();
|
|
203
|
-
if (target === from)
|
|
204
|
-
return back();
|
|
205
|
-
const dropped = dragged.rows[0].getBoundingClientRect().top;
|
|
206
|
-
options.move(dragged.key, blocks[target].key);
|
|
207
|
-
void options.update().then(() => settle(dropped));
|
|
208
|
-
}
|
|
209
|
-
function cancel() {
|
|
210
|
-
stop();
|
|
211
|
-
if (dragged)
|
|
212
|
-
back();
|
|
213
|
-
}
|
|
214
|
-
function escape(e) {
|
|
215
|
-
if (e.key !== 'Escape')
|
|
216
|
-
return;
|
|
217
|
-
e.preventDefault();
|
|
218
|
-
e.stopPropagation();
|
|
219
|
-
cancel();
|
|
220
|
-
}
|
|
221
|
-
doc.addEventListener('pointermove', (e) => {
|
|
222
|
-
if (e.pointerId !== event.pointerId)
|
|
223
|
-
return;
|
|
224
|
-
y = e.clientY;
|
|
225
|
-
if (!dragged) {
|
|
226
|
-
if (Math.hypot(e.clientX - startX, e.clientY - startY) < ACTIVATION)
|
|
227
|
-
return;
|
|
228
|
-
if (!lift())
|
|
229
|
-
return stop();
|
|
230
|
-
}
|
|
231
|
-
e.preventDefault();
|
|
232
|
-
frame();
|
|
233
|
-
}, { signal });
|
|
234
|
-
doc.addEventListener('pointerup', (e) => {
|
|
235
|
-
if (e.pointerId !== event.pointerId)
|
|
236
|
-
return;
|
|
237
|
-
if (dragged)
|
|
238
|
-
drop();
|
|
239
|
-
else
|
|
240
|
-
stop();
|
|
241
|
-
}, { signal });
|
|
242
|
-
doc.addEventListener('pointercancel', (e) => {
|
|
243
|
-
if (e.pointerId === event.pointerId)
|
|
244
|
-
cancel();
|
|
245
|
-
}, { signal });
|
|
246
|
-
grip.addEventListener('lostpointercapture', cancel, { signal });
|
|
247
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svelte-lean/table",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "A Svelte-native table and data-grid engine that ships only the features you use.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ömer Say",
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"headless"
|
|
92
92
|
],
|
|
93
93
|
"dependencies": {
|
|
94
|
+
"@svelte-lean/core": "^0.16.0",
|
|
94
95
|
"esm-env": "^1.2.2"
|
|
95
96
|
}
|
|
96
97
|
}
|