@svelte-lean/table 0.15.5 → 0.16.1

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 CHANGED
@@ -1,5 +1,28 @@
1
1
  # @svelte-lean/table
2
2
 
3
+ ## 0.16.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @svelte-lean/core@0.16.1
8
+
9
+ ## 0.16.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 12ca0ce: The live row drag (`onRowMove`) runs on the shared reorder engine of `@svelte-lean/core/reorder`
14
+ (ADR 0012), which the new sortable primitive uses too; `@svelte-lean/table` now depends on
15
+ `@svelte-lean/core` and imports only that subpath. The behavior, the attributes
16
+ (`data-row-dragging`, `data-row-settling`, `data-lifted`) and the keyboard are unchanged, and the
17
+ per-move cost is unchanged within measurement noise; the renderer grows by 366 B gzip (23742 B →
18
+ 24108 B), and its budget is raised from 23900 B to 24300 B.
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [8a774c6]
23
+ - Updated dependencies [12ca0ce]
24
+ - @svelte-lean/core@0.16.0
25
+
3
26
  ## 0.15.5
4
27
 
5
28
  No changes in this release.
@@ -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 { dragRow } from './row-drag.js';
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
- dragRow({
1393
+ reorder({
1393
1394
  event: e,
1394
- grip: e.target as HTMLElement,
1395
- row,
1395
+ handle: e.target as HTMLElement,
1396
+ item: row,
1396
1397
  root,
1397
1398
  viewport,
1398
- rows: () => [...ownRows()],
1399
- move: (source, target) => {
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);
@@ -1,18 +1,22 @@
1
- export interface RowDragOptions {
2
- /** The `pointerdown` on a grip. */
3
- event: PointerEvent;
4
- grip: HTMLElement;
5
- /** The data row of the grip. */
6
- row: HTMLTableRowElement;
7
- /** The `.slean-table` root: `data-row-dragging`, `data-row-settling`. */
8
- root: HTMLElement;
9
- /** The `.slean-table-scroll` region. */
10
- viewport: HTMLElement;
11
- /** The table's own data rows, in document order (never the rows of a nested table). */
12
- rows: () => HTMLTableRowElement[];
13
- /** Reports a drop on another place, by `data-row-id`. */
14
- move: (source: string, target: string) => void;
15
- /** Resolves once the application's answer to `move` is in the document (Svelte's `tick`). */
16
- update: () => Promise<void>;
17
- }
18
- export declare function dragRow(options: RowDragOptions): void;
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[];
@@ -1,247 +1,27 @@
1
1
  /**
2
- * Live row dragging for `<DataTable />` (`onRowMove`). DOM only: no Svelte state changes while the
3
- * pointer moves; transforms are written straight to the row elements, and only when they change.
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 `ACTIVATION` pixels. The lifted row (and its detail rows)
6
- * follows the pointer; the rows between its place and the place under it slide out of its way, so
7
- * the drop position is visible before release. Release reports the move (`move`), waits for the
8
- * application's update (`update`) and lets the row glide from where it was dropped into the place
9
- * it now has. A cancel (Escape, `pointercancel`, a lost capture), or a move the application did not
10
- * make, slides every row back. Near the visible edge of the scrolling container the container
11
- * scrolls, on a timer (a hidden tab runs no animation frames).
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
- const ACTIVATION = 3;
17
- const EDGE = 48;
18
- const SPEED = 20;
19
- const INTERVAL = 16;
20
- /** The slide of the rows (`style.css`), and a little more, before the end state is cleaned up. */
21
- const SETTLE_MS = 200;
22
- /** The cleanup of a drop still sliding into place, per table root: a new press runs it first. */
23
- const finishing = new WeakMap();
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.15.5",
3
+ "version": "0.16.1",
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.1",
94
95
  "esm-env": "^1.2.2"
95
96
  }
96
97
  }