@spectral_labs/ui 1.3.0 → 1.3.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/dist/components/grid-list/__tests__/GridListTest.svelte +1 -1
- package/dist/components/grid-list/components/grid-list-root.svelte +2 -2
- package/dist/components/grid-list/components/grid-list-root.svelte.d.ts +23 -2
- package/dist/components/grid-list/components/grid-list-root.svelte.d.ts.map +1 -1
- package/dist/components/sortable-list/__tests__/NestedSortableListTest.svelte +49 -0
- package/dist/components/sortable-list/__tests__/SortableListTest.svelte +1 -1
- package/dist/components/sortable-list/__tests__/sortable-list.nested.test.svelte.js +112 -0
- package/dist/components/sortable-list/components/sortable-list-root.svelte +2 -2
- package/dist/components/sortable-list/components/sortable-list-root.svelte.d.ts +23 -2
- package/dist/components/sortable-list/components/sortable-list-root.svelte.d.ts.map +1 -1
- package/dist/components/virtualizer/components/virtualizer-root.svelte +2 -2
- package/dist/components/virtualizer/components/virtualizer-root.svelte.d.ts +23 -2
- package/dist/components/virtualizer/components/virtualizer-root.svelte.d.ts.map +1 -1
- package/dist/internal/drag-drop/__tests__/drag-session.test.svelte.js +99 -13
- package/dist/internal/drag-drop/__tests__/drag-source.test.svelte.js +130 -0
- package/dist/internal/drag-drop/drag-session.svelte.d.ts.map +1 -1
- package/dist/internal/drag-drop/drag-session.svelte.js +14 -3
- package/dist/internal/drag-drop/drag-source.svelte.d.ts.map +1 -1
- package/dist/internal/drag-drop/drag-source.svelte.js +4 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="T extends GridListItemData">
|
|
2
2
|
import type { GridListItemData, GridListProps } from '../types.js'
|
|
3
3
|
import { dragSession } from '../../../internal/drag-drop/index.js'
|
|
4
4
|
import { isBrowser } from '../../../internal/helpers/index.js'
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
onSelectionChange,
|
|
28
28
|
item: itemSnippet,
|
|
29
29
|
empty: emptySnippet,
|
|
30
|
-
}: GridListProps<
|
|
30
|
+
}: GridListProps<T> = $props()
|
|
31
31
|
|
|
32
32
|
let containerEl: HTMLElement | undefined = $state()
|
|
33
33
|
let indicatorPortalEl: HTMLDivElement | null = null
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import type { GridListItemData, GridListProps } from '../types.js';
|
|
2
|
-
declare
|
|
3
|
-
|
|
2
|
+
declare function $$render<T extends GridListItemData>(): {
|
|
3
|
+
props: GridListProps<T>;
|
|
4
|
+
exports: {};
|
|
5
|
+
bindings: "selectedKeys";
|
|
6
|
+
slots: {};
|
|
7
|
+
events: {};
|
|
8
|
+
};
|
|
9
|
+
declare class __sveltets_Render<T extends GridListItemData> {
|
|
10
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
11
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
12
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
13
|
+
bindings(): "selectedKeys";
|
|
14
|
+
exports(): {};
|
|
15
|
+
}
|
|
16
|
+
interface $$IsomorphicComponent {
|
|
17
|
+
new <T extends GridListItemData>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
18
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
19
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
20
|
+
<T extends GridListItemData>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
21
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
22
|
+
}
|
|
23
|
+
declare const GridListRoot: $$IsomorphicComponent;
|
|
24
|
+
type GridListRoot<T extends GridListItemData> = InstanceType<typeof GridListRoot<T>>;
|
|
4
25
|
export default GridListRoot;
|
|
5
26
|
//# sourceMappingURL=grid-list-root.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grid-list-root.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/grid-list/components/grid-list-root.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"grid-list-root.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/grid-list/components/grid-list-root.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAIlE,iBAAS,QAAQ,CAAC,CAAC,SAAS,gBAAgB;WAsKf,aAAa,CAAC,CAAC,CAAC;;;;;EAAwF;AACrI,cAAM,iBAAiB,CAAC,CAAC,SAAS,gBAAgB;IAC9C,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,MAAM,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClD,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,QAAQ;IACR,OAAO;CACV;AAED,UAAU,qBAAqB;IAC3B,KAAK,CAAC,SAAS,gBAAgB,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IACrZ,CAAC,CAAC,SAAS,gBAAgB,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IACpJ,YAAY,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CACjE;AACD,QAAA,MAAM,YAAY,EAAE,qBAAmC,CAAC;AACtC,KAAK,YAAY,CAAC,CAAC,SAAS,gBAAgB,IAAI,YAAY,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;AACvF,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as SortableList from '../index.js'
|
|
3
|
+
|
|
4
|
+
interface Block {
|
|
5
|
+
id: string
|
|
6
|
+
label: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface OuterBlock extends Block {
|
|
10
|
+
children?: Block[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
items = $bindable<OuterBlock[]>([
|
|
15
|
+
{ id: 'title', label: 'Title' },
|
|
16
|
+
{
|
|
17
|
+
id: 'container',
|
|
18
|
+
label: 'Container',
|
|
19
|
+
children: [
|
|
20
|
+
{ id: 'inner-1', label: 'Inner 1' },
|
|
21
|
+
{ id: 'inner-2', label: 'Inner 2' },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{ id: 'author', label: 'Author' },
|
|
25
|
+
{ id: 'place', label: 'Place' },
|
|
26
|
+
]),
|
|
27
|
+
}: {
|
|
28
|
+
items?: OuterBlock[]
|
|
29
|
+
} = $props()
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<SortableList.Root bind:items group="nested-blocks" aria-label="Outer list">
|
|
33
|
+
{#snippet item(data: OuterBlock)}
|
|
34
|
+
<SortableList.Item id={data.id}>
|
|
35
|
+
{data.label}
|
|
36
|
+
{#if data.children}
|
|
37
|
+
<SortableList.Root
|
|
38
|
+
items={data.children}
|
|
39
|
+
group="nested-blocks"
|
|
40
|
+
aria-label="Inner list"
|
|
41
|
+
>
|
|
42
|
+
{#snippet item(child: Block)}
|
|
43
|
+
<SortableList.Item id={child.id}>{child.label}</SortableList.Item>
|
|
44
|
+
{/snippet}
|
|
45
|
+
</SortableList.Root>
|
|
46
|
+
{/if}
|
|
47
|
+
</SortableList.Item>
|
|
48
|
+
{/snippet}
|
|
49
|
+
</SortableList.Root>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SortableList nested lists — regression lock for #190.
|
|
3
|
+
*
|
|
4
|
+
* A Root nested inside an Item (same group) used to freeze the ghost:
|
|
5
|
+
* the pointerdown bubbled from the inner item to the outer item (both drag
|
|
6
|
+
* sources), and the second beginPointerDrag clobbered the pending session
|
|
7
|
+
* before the movement threshold was met.
|
|
8
|
+
*
|
|
9
|
+
* These tests drive the pointer path far enough in jsdom to lock the claim
|
|
10
|
+
* semantics (innermost source wins) and the clean end of the session.
|
|
11
|
+
* Full spatial behavior (drop positions, indicators) stays in Playwright.
|
|
12
|
+
*/
|
|
13
|
+
import { flushSync, mount, unmount } from 'svelte';
|
|
14
|
+
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
|
15
|
+
import { dragSession } from '../../../internal/drag-drop/index.js';
|
|
16
|
+
import NestedSortableListTest from './NestedSortableListTest.svelte';
|
|
17
|
+
// Polyfill browser APIs absent from jsdom
|
|
18
|
+
if (typeof PointerEvent === 'undefined') {
|
|
19
|
+
;
|
|
20
|
+
globalThis.PointerEvent = class PointerEvent extends (MouseEvent) {
|
|
21
|
+
pointerId;
|
|
22
|
+
pointerType;
|
|
23
|
+
constructor(type, init = {}) {
|
|
24
|
+
super(type, init);
|
|
25
|
+
this.pointerId = init.pointerId ?? 0;
|
|
26
|
+
this.pointerType = init.pointerType ?? '';
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function createMousePointerEvent(type, x, y) {
|
|
31
|
+
return new PointerEvent(type, {
|
|
32
|
+
clientX: x,
|
|
33
|
+
clientY: y,
|
|
34
|
+
pointerId: 1,
|
|
35
|
+
pointerType: 'mouse',
|
|
36
|
+
bubbles: true,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
describe('sortableList — nested Root inside an Item (#190)', () => {
|
|
40
|
+
let target;
|
|
41
|
+
let instance;
|
|
42
|
+
beforeAll(() => {
|
|
43
|
+
if (!HTMLElement.prototype.setPointerCapture) {
|
|
44
|
+
HTMLElement.prototype.setPointerCapture = () => { };
|
|
45
|
+
HTMLElement.prototype.releasePointerCapture = () => { };
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
function setup() {
|
|
49
|
+
dragSession._resetForTests();
|
|
50
|
+
target = document.createElement('div');
|
|
51
|
+
document.body.appendChild(target);
|
|
52
|
+
instance = mount(NestedSortableListTest, { target, props: {} });
|
|
53
|
+
flushSync();
|
|
54
|
+
}
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
if (instance) {
|
|
57
|
+
unmount(instance);
|
|
58
|
+
}
|
|
59
|
+
instance = undefined;
|
|
60
|
+
target?.remove();
|
|
61
|
+
dragSession._resetForTests();
|
|
62
|
+
});
|
|
63
|
+
function getItem(id) {
|
|
64
|
+
const el = target.querySelector(`[data-dnd-key="${id}"]`);
|
|
65
|
+
if (!el)
|
|
66
|
+
throw new Error(`item ${id} not found`);
|
|
67
|
+
return el;
|
|
68
|
+
}
|
|
69
|
+
it('renders both lists (outer 4 items, inner 2 items)', () => {
|
|
70
|
+
setup();
|
|
71
|
+
const lists = target.querySelectorAll('.sp-sortable-list');
|
|
72
|
+
expect(lists).toHaveLength(2);
|
|
73
|
+
expect(target.querySelectorAll('[data-dnd-key]')).toHaveLength(6);
|
|
74
|
+
});
|
|
75
|
+
it('pointerdown on an inner item claims the inner source, not the outer item', () => {
|
|
76
|
+
setup();
|
|
77
|
+
const inner = getItem('inner-1');
|
|
78
|
+
inner.dispatchEvent(createMousePointerEvent('pointerdown', 100, 100));
|
|
79
|
+
expect(dragSession.sourceId).not.toBeNull();
|
|
80
|
+
expect(dragSession.sourceId.endsWith('-item-inner-1')).toBe(true);
|
|
81
|
+
dragSession.cancel();
|
|
82
|
+
});
|
|
83
|
+
it('dragging an inner item past the threshold starts and ends a clean session', () => {
|
|
84
|
+
setup();
|
|
85
|
+
const inner = getItem('inner-1');
|
|
86
|
+
inner.dispatchEvent(createMousePointerEvent('pointerdown', 100, 100));
|
|
87
|
+
inner.dispatchEvent(createMousePointerEvent('pointermove', 112, 112));
|
|
88
|
+
expect(dragSession.isDragging).toBe(true);
|
|
89
|
+
expect(dragSession.sourceId.endsWith('-item-inner-1')).toBe(true);
|
|
90
|
+
expect(dragSession.draggingKeys.has('inner-1')).toBe(true);
|
|
91
|
+
expect(document.querySelector('.sp-drag-ghost')).not.toBeNull();
|
|
92
|
+
// Releasing the pointer must end the session — this is where the ghost
|
|
93
|
+
// used to stay frozen on screen before the fix.
|
|
94
|
+
inner.dispatchEvent(createMousePointerEvent('pointerup', 112, 112));
|
|
95
|
+
expect(dragSession.isDragging).toBe(false);
|
|
96
|
+
expect(dragSession.sourceId).toBeNull();
|
|
97
|
+
expect(document.querySelector('.sp-drag-ghost')).toBeNull();
|
|
98
|
+
});
|
|
99
|
+
it('dragging an outer item (the container itself) still works', () => {
|
|
100
|
+
setup();
|
|
101
|
+
const container = getItem('container');
|
|
102
|
+
// pointerdown directly on the container item, outside the inner list
|
|
103
|
+
container.dispatchEvent(createMousePointerEvent('pointerdown', 100, 100));
|
|
104
|
+
expect(dragSession.sourceId.endsWith('-item-container')).toBe(true);
|
|
105
|
+
container.dispatchEvent(createMousePointerEvent('pointermove', 112, 112));
|
|
106
|
+
expect(dragSession.isDragging).toBe(true);
|
|
107
|
+
expect(dragSession.draggingKeys.has('container')).toBe(true);
|
|
108
|
+
container.dispatchEvent(createMousePointerEvent('pointerup', 112, 112));
|
|
109
|
+
expect(dragSession.isDragging).toBe(false);
|
|
110
|
+
expect(document.querySelector('.sp-drag-ghost')).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="T extends SortableItem">
|
|
2
2
|
import type { SortableItem, SortableListProps } from '../types.js'
|
|
3
3
|
import { dragSession } from '../../../internal/drag-drop/index.js'
|
|
4
4
|
import { createSortableList, setSortableListContext } from '../sortable-list.svelte.js'
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
onRemove,
|
|
21
21
|
item: itemSnippet,
|
|
22
22
|
empty: emptySnippet,
|
|
23
|
-
}: SortableListProps<
|
|
23
|
+
}: SortableListProps<T> = $props()
|
|
24
24
|
|
|
25
25
|
let containerEl: HTMLElement | undefined = $state()
|
|
26
26
|
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import type { SortableItem, SortableListProps } from '../types.js';
|
|
2
|
-
declare
|
|
3
|
-
|
|
2
|
+
declare function $$render<T extends SortableItem>(): {
|
|
3
|
+
props: SortableListProps<T>;
|
|
4
|
+
exports: {};
|
|
5
|
+
bindings: "items";
|
|
6
|
+
slots: {};
|
|
7
|
+
events: {};
|
|
8
|
+
};
|
|
9
|
+
declare class __sveltets_Render<T extends SortableItem> {
|
|
10
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
11
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
12
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
13
|
+
bindings(): "items";
|
|
14
|
+
exports(): {};
|
|
15
|
+
}
|
|
16
|
+
interface $$IsomorphicComponent {
|
|
17
|
+
new <T extends SortableItem>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
18
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
19
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
20
|
+
<T extends SortableItem>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
21
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
22
|
+
}
|
|
23
|
+
declare const SortableListRoot: $$IsomorphicComponent;
|
|
24
|
+
type SortableListRoot<T extends SortableItem> = InstanceType<typeof SortableListRoot<T>>;
|
|
4
25
|
export default SortableListRoot;
|
|
5
26
|
//# sourceMappingURL=sortable-list-root.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sortable-list-root.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/sortable-list/components/sortable-list-root.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"sortable-list-root.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/sortable-list/components/sortable-list-root.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAGlE,iBAAS,QAAQ,CAAC,CAAC,SAAS,YAAY;WAsEX,iBAAiB,CAAC,CAAC,CAAC;;;;;EAAiF;AAClI,cAAM,iBAAiB,CAAC,CAAC,SAAS,YAAY;IAC1C,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,MAAM,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClD,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,QAAQ;IACR,OAAO;CACV;AAED,UAAU,qBAAqB;IAC3B,KAAK,CAAC,SAAS,YAAY,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IACjZ,CAAC,CAAC,SAAS,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAChJ,YAAY,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CACjE;AACD,QAAA,MAAM,gBAAgB,EAAE,qBAAmC,CAAC;AAC1C,KAAK,gBAAgB,CAAC,CAAC,SAAS,YAAY,IAAI,YAAY,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3F,eAAe,gBAAgB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="T extends SizedItem">
|
|
2
2
|
import type { Rect, SizedItem, VirtualizerProps } from '../types.js'
|
|
3
3
|
import { isBrowser } from '../../../internal/helpers/index.js'
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
overscan = 300,
|
|
9
9
|
class: className,
|
|
10
10
|
item: itemSnippet,
|
|
11
|
-
}: VirtualizerProps<
|
|
11
|
+
}: VirtualizerProps<T> = $props()
|
|
12
12
|
|
|
13
13
|
let containerEl: HTMLElement | undefined = $state()
|
|
14
14
|
let containerWidth = $state(0)
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import type { SizedItem, VirtualizerProps } from '../types.js';
|
|
2
|
-
declare
|
|
3
|
-
|
|
2
|
+
declare function $$render<T extends SizedItem>(): {
|
|
3
|
+
props: VirtualizerProps<T>;
|
|
4
|
+
exports: {};
|
|
5
|
+
bindings: "";
|
|
6
|
+
slots: {};
|
|
7
|
+
events: {};
|
|
8
|
+
};
|
|
9
|
+
declare class __sveltets_Render<T extends SizedItem> {
|
|
10
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
11
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
12
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
13
|
+
bindings(): "";
|
|
14
|
+
exports(): {};
|
|
15
|
+
}
|
|
16
|
+
interface $$IsomorphicComponent {
|
|
17
|
+
new <T extends SizedItem>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
18
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
19
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
20
|
+
<T extends SizedItem>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
21
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
22
|
+
}
|
|
23
|
+
declare const VirtualizerRoot: $$IsomorphicComponent;
|
|
24
|
+
type VirtualizerRoot<T extends SizedItem> = InstanceType<typeof VirtualizerRoot<T>>;
|
|
4
25
|
export default VirtualizerRoot;
|
|
5
26
|
//# sourceMappingURL=virtualizer-root.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualizer-root.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/virtualizer/components/virtualizer-root.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAQ,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"virtualizer-root.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/virtualizer/components/virtualizer-root.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAQ,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEpE,iBAAS,QAAQ,CAAC,CAAC,SAAS,SAAS;WAwFR,gBAAgB,CAAC,CAAC,CAAC;;;;;EAA4E;AAC5H,cAAM,iBAAiB,CAAC,CAAC,SAAS,SAAS;IACvC,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,MAAM,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClD,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChD,QAAQ;IACR,OAAO;CACV;AAED,UAAU,qBAAqB;IAC3B,KAAK,CAAC,SAAS,SAAS,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC9Y,CAAC,CAAC,SAAS,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC7I,YAAY,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CACjE;AACD,QAAA,MAAM,eAAe,EAAE,qBAAmC,CAAC;AACzC,KAAK,eAAe,CAAC,CAAC,SAAS,SAAS,IAAI,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AACtF,eAAe,eAAe,CAAC"}
|
|
@@ -34,6 +34,21 @@ function createElement() {
|
|
|
34
34
|
document.body.appendChild(el);
|
|
35
35
|
return el;
|
|
36
36
|
}
|
|
37
|
+
/** Polyfill PointerEvent for jsdom. */
|
|
38
|
+
function ensurePointerEventPolyfill() {
|
|
39
|
+
if (typeof globalThis.PointerEvent === 'undefined') {
|
|
40
|
+
;
|
|
41
|
+
globalThis.PointerEvent = class PointerEvent extends MouseEvent {
|
|
42
|
+
pointerId;
|
|
43
|
+
pointerType;
|
|
44
|
+
constructor(type, init = {}) {
|
|
45
|
+
super(type, init);
|
|
46
|
+
this.pointerId = init.pointerId ?? 0;
|
|
47
|
+
this.pointerType = init.pointerType ?? '';
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
37
52
|
describe('dragSession', () => {
|
|
38
53
|
beforeEach(() => {
|
|
39
54
|
dragSession._resetForTests();
|
|
@@ -265,19 +280,7 @@ describe('dragSession', () => {
|
|
|
265
280
|
describe('touch long-press', () => {
|
|
266
281
|
beforeEach(() => {
|
|
267
282
|
vi.useFakeTimers();
|
|
268
|
-
|
|
269
|
-
if (typeof globalThis.PointerEvent === 'undefined') {
|
|
270
|
-
;
|
|
271
|
-
globalThis.PointerEvent = class PointerEvent extends (MouseEvent) {
|
|
272
|
-
pointerId;
|
|
273
|
-
pointerType;
|
|
274
|
-
constructor(type, init = {}) {
|
|
275
|
-
super(type, init);
|
|
276
|
-
this.pointerId = init.pointerId ?? 0;
|
|
277
|
-
this.pointerType = init.pointerType ?? '';
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
}
|
|
283
|
+
ensurePointerEventPolyfill();
|
|
281
284
|
});
|
|
282
285
|
afterEach(() => {
|
|
283
286
|
vi.useRealTimers();
|
|
@@ -432,6 +435,89 @@ describe('dragSession', () => {
|
|
|
432
435
|
expect(dragSession.isDragging).toBe(false);
|
|
433
436
|
});
|
|
434
437
|
});
|
|
438
|
+
describe('pointer drag re-entrance (#190)', () => {
|
|
439
|
+
beforeEach(() => {
|
|
440
|
+
ensurePointerEventPolyfill();
|
|
441
|
+
});
|
|
442
|
+
function createMousePointerEvent(type, x, y) {
|
|
443
|
+
return new PointerEvent(type, {
|
|
444
|
+
clientX: x,
|
|
445
|
+
clientY: y,
|
|
446
|
+
pointerId: 1,
|
|
447
|
+
pointerType: 'mouse',
|
|
448
|
+
bubbles: true,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
function registerSource(id) {
|
|
452
|
+
const config = createSourceConfig(id);
|
|
453
|
+
const el = createElement();
|
|
454
|
+
el.setPointerCapture = vi.fn();
|
|
455
|
+
el.releasePointerCapture = vi.fn();
|
|
456
|
+
dragSession.registerSource(config, el);
|
|
457
|
+
return { config, el };
|
|
458
|
+
}
|
|
459
|
+
it('a second beginPointerDrag while one is pending does not clobber the first', () => {
|
|
460
|
+
const inner = registerSource('inner-src');
|
|
461
|
+
const outer = registerSource('outer-src');
|
|
462
|
+
// Same pointerdown delivered to both sources (bubbling in a nested DOM)
|
|
463
|
+
const down = createMousePointerEvent('pointerdown', 100, 100);
|
|
464
|
+
dragSession.beginPointerDrag('inner-src', down);
|
|
465
|
+
expect(dragSession.sourceId).toBe('inner-src');
|
|
466
|
+
dragSession.beginPointerDrag('outer-src', down);
|
|
467
|
+
expect(dragSession.sourceId).toBe('inner-src'); // first claim wins
|
|
468
|
+
// Crossing the threshold activates the drag for the FIRST source
|
|
469
|
+
inner.el.dispatchEvent(createMousePointerEvent('pointermove', 110, 110));
|
|
470
|
+
expect(dragSession.isDragging).toBe(true);
|
|
471
|
+
expect(dragSession.sourceId).toBe('inner-src');
|
|
472
|
+
expect(inner.config.onDragStart).toHaveBeenCalled();
|
|
473
|
+
expect(outer.config.onDragStart).not.toHaveBeenCalled();
|
|
474
|
+
// And the session ends cleanly — no stuck ghost
|
|
475
|
+
inner.el.dispatchEvent(createMousePointerEvent('pointerup', 110, 110));
|
|
476
|
+
expect(dragSession.isDragging).toBe(false);
|
|
477
|
+
expect(dragSession.sourceId).toBeNull();
|
|
478
|
+
expect(document.querySelector('.sp-drag-ghost')).toBeNull();
|
|
479
|
+
inner.el.remove();
|
|
480
|
+
outer.el.remove();
|
|
481
|
+
});
|
|
482
|
+
it('releasing before the threshold clears the pending claim for the next drag', () => {
|
|
483
|
+
const a = registerSource('pending-a');
|
|
484
|
+
const b = registerSource('pending-b');
|
|
485
|
+
dragSession.beginPointerDrag('pending-a', createMousePointerEvent('pointerdown', 100, 100));
|
|
486
|
+
a.el.dispatchEvent(createMousePointerEvent('pointerup', 100, 100));
|
|
487
|
+
expect(dragSession.sourceId).toBeNull();
|
|
488
|
+
dragSession.beginPointerDrag('pending-b', createMousePointerEvent('pointerdown', 100, 100));
|
|
489
|
+
expect(dragSession.sourceId).toBe('pending-b');
|
|
490
|
+
dragSession.cancel();
|
|
491
|
+
a.el.remove();
|
|
492
|
+
b.el.remove();
|
|
493
|
+
});
|
|
494
|
+
it('keyboard drag cannot start while a pointer drag is pending', () => {
|
|
495
|
+
const a = registerSource('kb-block-a');
|
|
496
|
+
const b = registerSource('kb-block-b');
|
|
497
|
+
dragSession.beginPointerDrag('kb-block-a', createMousePointerEvent('pointerdown', 100, 100));
|
|
498
|
+
dragSession.beginKeyboardDrag('kb-block-b');
|
|
499
|
+
expect(dragSession.isDragging).toBe(false);
|
|
500
|
+
expect(dragSession.sourceId).toBe('kb-block-a');
|
|
501
|
+
dragSession.cancel();
|
|
502
|
+
a.el.remove();
|
|
503
|
+
b.el.remove();
|
|
504
|
+
});
|
|
505
|
+
it('pagehide during a pending claim resets it so future drags are not blocked', () => {
|
|
506
|
+
const src = registerSource('ph-pending');
|
|
507
|
+
// Pointer down, threshold NOT crossed — claim is pending
|
|
508
|
+
dragSession.beginPointerDrag('ph-pending', createMousePointerEvent('pointerdown', 100, 100));
|
|
509
|
+
expect(dragSession.isDragging).toBe(false);
|
|
510
|
+
expect(dragSession.sourceId).toBe('ph-pending');
|
|
511
|
+
// Page navigates during the pending claim
|
|
512
|
+
window.dispatchEvent(new Event('pagehide'));
|
|
513
|
+
expect(dragSession.sourceId).toBeNull();
|
|
514
|
+
// The re-entrance guard must not block the next drag
|
|
515
|
+
dragSession.beginPointerDrag('ph-pending', createMousePointerEvent('pointerdown', 100, 100));
|
|
516
|
+
expect(dragSession.sourceId).toBe('ph-pending');
|
|
517
|
+
dragSession.cancel();
|
|
518
|
+
src.el.remove();
|
|
519
|
+
});
|
|
520
|
+
});
|
|
435
521
|
describe('async onDrop (F-001)', () => {
|
|
436
522
|
it('awaits async onDrop callback before cleanup', async () => {
|
|
437
523
|
let dropResolved = false;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { dragSession } from '../drag-session.svelte.js';
|
|
3
|
+
import { DragSourceState } from '../drag-source.svelte.js';
|
|
4
|
+
function createSourceConfig(id, overrides = {}) {
|
|
5
|
+
return {
|
|
6
|
+
id,
|
|
7
|
+
getItems: () => [{ 'test-type': id, 'text/plain': `Item ${id}` }],
|
|
8
|
+
onDragStart: vi.fn(),
|
|
9
|
+
onDragEnd: vi.fn(),
|
|
10
|
+
...overrides,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function createElement(parent = document.body) {
|
|
14
|
+
const el = document.createElement('div');
|
|
15
|
+
Object.defineProperty(el, 'getBoundingClientRect', {
|
|
16
|
+
value: () => ({
|
|
17
|
+
top: 0,
|
|
18
|
+
left: 0,
|
|
19
|
+
bottom: 100,
|
|
20
|
+
right: 200,
|
|
21
|
+
width: 200,
|
|
22
|
+
height: 100,
|
|
23
|
+
x: 0,
|
|
24
|
+
y: 0,
|
|
25
|
+
toJSON() { },
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
el.setPointerCapture = vi.fn();
|
|
29
|
+
el.releasePointerCapture = vi.fn();
|
|
30
|
+
parent.appendChild(el);
|
|
31
|
+
return el;
|
|
32
|
+
}
|
|
33
|
+
/** Polyfill PointerEvent for jsdom. */
|
|
34
|
+
function ensurePointerEventPolyfill() {
|
|
35
|
+
if (typeof globalThis.PointerEvent === 'undefined') {
|
|
36
|
+
;
|
|
37
|
+
globalThis.PointerEvent = class PointerEvent extends MouseEvent {
|
|
38
|
+
pointerId;
|
|
39
|
+
pointerType;
|
|
40
|
+
constructor(type, init = {}) {
|
|
41
|
+
super(type, init);
|
|
42
|
+
this.pointerId = init.pointerId ?? 0;
|
|
43
|
+
this.pointerType = init.pointerType ?? '';
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function createMousePointerEvent(type, x, y) {
|
|
49
|
+
return new PointerEvent(type, {
|
|
50
|
+
clientX: x,
|
|
51
|
+
clientY: y,
|
|
52
|
+
pointerId: 1,
|
|
53
|
+
pointerType: 'mouse',
|
|
54
|
+
bubbles: true,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
describe('dragSourceState', () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
dragSession._resetForTests();
|
|
60
|
+
ensurePointerEventPolyfill();
|
|
61
|
+
});
|
|
62
|
+
describe('nested sources (#190)', () => {
|
|
63
|
+
it('the innermost source claims the pointerdown — the ancestor never sees it', () => {
|
|
64
|
+
const outerEl = createElement();
|
|
65
|
+
const innerEl = createElement(outerEl);
|
|
66
|
+
const outerConfig = createSourceConfig('outer');
|
|
67
|
+
const innerConfig = createSourceConfig('inner');
|
|
68
|
+
const outer = new DragSourceState(outerConfig);
|
|
69
|
+
const inner = new DragSourceState(innerConfig);
|
|
70
|
+
outer.attach(outerEl);
|
|
71
|
+
inner.attach(innerEl);
|
|
72
|
+
// pointerdown on the inner element bubbles through the outer element
|
|
73
|
+
innerEl.dispatchEvent(createMousePointerEvent('pointerdown', 100, 100));
|
|
74
|
+
expect(dragSession.sourceId).toBe('inner');
|
|
75
|
+
// Crossing the threshold starts the drag for the inner source only
|
|
76
|
+
innerEl.dispatchEvent(createMousePointerEvent('pointermove', 110, 110));
|
|
77
|
+
expect(dragSession.isDragging).toBe(true);
|
|
78
|
+
expect(dragSession.sourceId).toBe('inner');
|
|
79
|
+
expect(inner.isDragging).toBe(true);
|
|
80
|
+
expect(outer.isDragging).toBe(false);
|
|
81
|
+
expect(innerConfig.onDragStart).toHaveBeenCalled();
|
|
82
|
+
expect(outerConfig.onDragStart).not.toHaveBeenCalled();
|
|
83
|
+
// The session ends cleanly on pointerup — no stuck ghost
|
|
84
|
+
innerEl.dispatchEvent(createMousePointerEvent('pointerup', 110, 110));
|
|
85
|
+
expect(dragSession.isDragging).toBe(false);
|
|
86
|
+
expect(dragSession.sourceId).toBeNull();
|
|
87
|
+
expect(document.querySelector('.sp-drag-ghost')).toBeNull();
|
|
88
|
+
inner.detach();
|
|
89
|
+
outer.detach();
|
|
90
|
+
outerEl.remove();
|
|
91
|
+
});
|
|
92
|
+
it('dragging the outer source directly still works', () => {
|
|
93
|
+
const outerEl = createElement();
|
|
94
|
+
const innerEl = createElement(outerEl);
|
|
95
|
+
const outerConfig = createSourceConfig('outer-direct');
|
|
96
|
+
const innerConfig = createSourceConfig('inner-direct');
|
|
97
|
+
const outer = new DragSourceState(outerConfig);
|
|
98
|
+
const inner = new DragSourceState(innerConfig);
|
|
99
|
+
outer.attach(outerEl);
|
|
100
|
+
inner.attach(innerEl);
|
|
101
|
+
// pointerdown on the outer element itself (not inside the inner source)
|
|
102
|
+
outerEl.dispatchEvent(createMousePointerEvent('pointerdown', 100, 100));
|
|
103
|
+
expect(dragSession.sourceId).toBe('outer-direct');
|
|
104
|
+
outerEl.dispatchEvent(createMousePointerEvent('pointermove', 110, 110));
|
|
105
|
+
expect(dragSession.isDragging).toBe(true);
|
|
106
|
+
expect(outer.isDragging).toBe(true);
|
|
107
|
+
outerEl.dispatchEvent(createMousePointerEvent('pointerup', 110, 110));
|
|
108
|
+
expect(dragSession.isDragging).toBe(false);
|
|
109
|
+
inner.detach();
|
|
110
|
+
outer.detach();
|
|
111
|
+
outerEl.remove();
|
|
112
|
+
});
|
|
113
|
+
it('a disabled inner source lets the outer source claim the event', () => {
|
|
114
|
+
const outerEl = createElement();
|
|
115
|
+
const innerEl = createElement(outerEl);
|
|
116
|
+
const outerConfig = createSourceConfig('outer-en');
|
|
117
|
+
const innerConfig = createSourceConfig('inner-dis', { isDisabled: true });
|
|
118
|
+
const outer = new DragSourceState(outerConfig);
|
|
119
|
+
const inner = new DragSourceState(innerConfig);
|
|
120
|
+
outer.attach(outerEl);
|
|
121
|
+
inner.attach(innerEl);
|
|
122
|
+
innerEl.dispatchEvent(createMousePointerEvent('pointerdown', 100, 100));
|
|
123
|
+
expect(dragSession.sourceId).toBe('outer-en');
|
|
124
|
+
dragSession.cancel();
|
|
125
|
+
inner.detach();
|
|
126
|
+
outer.detach();
|
|
127
|
+
outerEl.remove();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drag-session.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/internal/drag-drop/drag-session.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,gBAAgB,EAGhB,UAAU,EACV,gBAAgB,EAEjB,MAAM,YAAY,CAAA;AAOnB,UAAU,gBAAgB;IACxB,MAAM,EAAE,gBAAgB,CAAA;IACxB,OAAO,EAAE,WAAW,CAAA;CACrB;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,gBAAgB,CAAA;IACxB,OAAO,EAAE,WAAW,CAAA;IACpB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CACjD;AA4QD,eAAO,MAAM,WAAW;;;;;;;;;;;2BAmCC,gBAAgB,WAAW,WAAW,GAAG,MAAM,IAAI;2BAQhE,gBAAgB,WACf,WAAW,oBACF,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,GAC/C,MAAM,IAAI;kBASC,MAAM,IAAI,GAAG,MAAM,IAAI;+BASV,MAAM,KAAK,YAAY,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"drag-session.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/internal/drag-drop/drag-session.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,gBAAgB,EAGhB,UAAU,EACV,gBAAgB,EAEjB,MAAM,YAAY,CAAA;AAOnB,UAAU,gBAAgB;IACxB,MAAM,EAAE,gBAAgB,CAAA;IACxB,OAAO,EAAE,WAAW,CAAA;CACrB;AAED,UAAU,gBAAgB;IACxB,MAAM,EAAE,gBAAgB,CAAA;IACxB,OAAO,EAAE,WAAW,CAAA;IACpB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CACjD;AA4QD,eAAO,MAAM,WAAW;;;;;;;;;;;2BAmCC,gBAAgB,WAAW,WAAW,GAAG,MAAM,IAAI;2BAQhE,gBAAgB,WACf,WAAW,oBACF,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,GAC/C,MAAM,IAAI;kBASC,MAAM,IAAI,GAAG,MAAM,IAAI;+BASV,MAAM,KAAK,YAAY,GAAG,IAAI;gCA4G7B,MAAM,GAAG,IAAI;IAezC,gDAAgD;uBAC7B,IAAI;IAYvB,0DAA0D;uBACvC,IAAI;IAYvB,mFAAmF;0BAC7D,UAAU,GAAG,IAAI;YAQ/B,OAAO,CAAC,IAAI,CAAC;cAIX,IAAI;IAMd,uDAAuD;sCACrB,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI;IAKlE,+FAA+F;iCAClE,MAAM,GAAG,OAAO;IAI7C,+FAA+F;6CACtD,MAAM,GAAG,OAAO;IAIzD,mCAAmC;kBACrB,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAInD,mCAAmC;kBACrB,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAInD,gBAAgB;sBACE,IAAI;CAMvB,CAAA"}
|
|
@@ -304,7 +304,10 @@ export const dragSession = {
|
|
|
304
304
|
},
|
|
305
305
|
// ── Start drag (pointer) ─────────────────────────────────────────────
|
|
306
306
|
beginPointerDrag(sourceId, e) {
|
|
307
|
-
|
|
307
|
+
// _sourceId is non-null while a pointer drag is still pending (before the
|
|
308
|
+
// movement threshold) — refuse re-entry so a second claim cannot clobber
|
|
309
|
+
// the pending session's source and leave a stuck ghost.
|
|
310
|
+
if (_isDragging || _sourceId !== null || !isBrowser)
|
|
308
311
|
return;
|
|
309
312
|
const source = registeredSources.get(sourceId);
|
|
310
313
|
if (!source || source.config.isDisabled)
|
|
@@ -314,6 +317,8 @@ export const dragSession = {
|
|
|
314
317
|
_startY = e.clientY;
|
|
315
318
|
_dragThresholdMet = false;
|
|
316
319
|
_sourceId = sourceId;
|
|
320
|
+
// Guard the pending claim against page navigation (see onPageHide)
|
|
321
|
+
startPageHideListener();
|
|
317
322
|
const el = source.element;
|
|
318
323
|
el.setPointerCapture(e.pointerId);
|
|
319
324
|
function cleanup() {
|
|
@@ -354,6 +359,7 @@ export const dragSession = {
|
|
|
354
359
|
}
|
|
355
360
|
cleanup();
|
|
356
361
|
_sourceId = null;
|
|
362
|
+
stopPageHideListener();
|
|
357
363
|
return;
|
|
358
364
|
}
|
|
359
365
|
// Still waiting for long-press timer — don't do anything yet
|
|
@@ -383,6 +389,7 @@ export const dragSession = {
|
|
|
383
389
|
}
|
|
384
390
|
else {
|
|
385
391
|
_sourceId = null;
|
|
392
|
+
stopPageHideListener();
|
|
386
393
|
}
|
|
387
394
|
};
|
|
388
395
|
const onCancel = () => {
|
|
@@ -395,7 +402,9 @@ export const dragSession = {
|
|
|
395
402
|
},
|
|
396
403
|
// ── Start drag (keyboard) ───────────────────────────────────────────
|
|
397
404
|
beginKeyboardDrag(sourceId) {
|
|
398
|
-
|
|
405
|
+
// Same re-entrance rule as beginPointerDrag: a pending pointer drag
|
|
406
|
+
// (pointer down, threshold not yet met) blocks a keyboard start.
|
|
407
|
+
if (_isDragging || _sourceId !== null || !isBrowser)
|
|
399
408
|
return;
|
|
400
409
|
const source = registeredSources.get(sourceId);
|
|
401
410
|
if (!source || source.config.isDisabled)
|
|
@@ -479,7 +488,9 @@ export const dragSession = {
|
|
|
479
488
|
// If the page navigates during a drag (SvelteKit routing), the module-level
|
|
480
489
|
// state would be left in a broken state. Listen for pagehide to force cleanup.
|
|
481
490
|
function onPageHide() {
|
|
482
|
-
|
|
491
|
+
// Also reset a PENDING claim (_sourceId set, threshold not met): leaving it
|
|
492
|
+
// behind would make the re-entrance guard block every future drag.
|
|
493
|
+
if (_isDragging || _sourceId !== null) {
|
|
483
494
|
resetSession();
|
|
484
495
|
}
|
|
485
496
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drag-source.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/internal/drag-drop/drag-source.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAIlD,qBAAa,eAAe;;gBAKd,MAAM,EAAE,gBAAgB;IAIpC,+DAA+D;IAC/D,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,4BAA4B;IAC5B,IAAI,EAAE,IAAI,MAAM,CAEf;IAED;;;OAGG;IACH,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAmBlC;;OAEG;IACH,MAAM,IAAI,IAAI;
|
|
1
|
+
{"version":3,"file":"drag-source.svelte.d.ts","sourceRoot":"","sources":["../../../src/lib/internal/drag-drop/drag-source.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAIlD,qBAAa,eAAe;;gBAKd,MAAM,EAAE,gBAAgB;IAIpC,+DAA+D;IAC/D,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,4BAA4B;IAC5B,IAAI,EAAE,IAAI,MAAM,CAEf;IAED;;;OAGG;IACH,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAmBlC;;OAEG;IACH,MAAM,IAAI,IAAI;CA+Bf"}
|
|
@@ -61,6 +61,10 @@ export class DragSourceState {
|
|
|
61
61
|
// Only primary button
|
|
62
62
|
if (e.button !== 0)
|
|
63
63
|
return;
|
|
64
|
+
// Claim the gesture: with nested sources (e.g. a sortable list inside
|
|
65
|
+
// another list's item) the same pointerdown bubbles to every ancestor
|
|
66
|
+
// source — the innermost one wins, ancestors must never see it.
|
|
67
|
+
e.stopPropagation();
|
|
64
68
|
dragSession.beginPointerDrag(this.#config.id, e);
|
|
65
69
|
};
|
|
66
70
|
#onKeyDown = (e) => {
|