@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.
Files changed (20) hide show
  1. package/dist/components/grid-list/__tests__/GridListTest.svelte +1 -1
  2. package/dist/components/grid-list/components/grid-list-root.svelte +2 -2
  3. package/dist/components/grid-list/components/grid-list-root.svelte.d.ts +23 -2
  4. package/dist/components/grid-list/components/grid-list-root.svelte.d.ts.map +1 -1
  5. package/dist/components/sortable-list/__tests__/NestedSortableListTest.svelte +49 -0
  6. package/dist/components/sortable-list/__tests__/SortableListTest.svelte +1 -1
  7. package/dist/components/sortable-list/__tests__/sortable-list.nested.test.svelte.js +112 -0
  8. package/dist/components/sortable-list/components/sortable-list-root.svelte +2 -2
  9. package/dist/components/sortable-list/components/sortable-list-root.svelte.d.ts +23 -2
  10. package/dist/components/sortable-list/components/sortable-list-root.svelte.d.ts.map +1 -1
  11. package/dist/components/virtualizer/components/virtualizer-root.svelte +2 -2
  12. package/dist/components/virtualizer/components/virtualizer-root.svelte.d.ts +23 -2
  13. package/dist/components/virtualizer/components/virtualizer-root.svelte.d.ts.map +1 -1
  14. package/dist/internal/drag-drop/__tests__/drag-session.test.svelte.js +99 -13
  15. package/dist/internal/drag-drop/__tests__/drag-source.test.svelte.js +130 -0
  16. package/dist/internal/drag-drop/drag-session.svelte.d.ts.map +1 -1
  17. package/dist/internal/drag-drop/drag-session.svelte.js +14 -3
  18. package/dist/internal/drag-drop/drag-source.svelte.d.ts.map +1 -1
  19. package/dist/internal/drag-drop/drag-source.svelte.js +4 -0
  20. package/package.json +1 -1
@@ -38,7 +38,7 @@
38
38
  {onSelectionChange}
39
39
  aria-label="Test grid"
40
40
  >
41
- {#snippet item(itemData: TestItem)}
41
+ {#snippet item(itemData)}
42
42
  <Item id={itemData.id} textValue={itemData.label}>
43
43
  {itemData.label}
44
44
  </Item>
@@ -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<GridListItemData> = $props()
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 const GridListRoot: import("svelte").Component<GridListProps<GridListItemData>, {}, "selectedKeys">;
3
- type GridListRoot = ReturnType<typeof GridListRoot>;
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;AA2KlE,QAAA,MAAM,YAAY,iFAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
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>
@@ -33,7 +33,7 @@
33
33
  {onReorder}
34
34
  aria-label="Sortable test"
35
35
  >
36
- {#snippet item(data: Item)}
36
+ {#snippet item(data)}
37
37
  <SortableList.Item id={data.id}>
38
38
  <SortableList.Handle>&#x22EE;&#x22EE;</SortableList.Handle>
39
39
  {data.label}
@@ -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<SortableItem> = $props()
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 const SortableListRoot: import("svelte").Component<SortableListProps<SortableItem>, {}, "items">;
3
- type SortableListRoot = ReturnType<typeof SortableListRoot>;
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;AA0ElE,QAAA,MAAM,gBAAgB,0EAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
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<SizedItem> = $props()
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 const VirtualizerRoot: import("svelte").Component<VirtualizerProps<SizedItem>, {}, "">;
3
- type VirtualizerRoot = ReturnType<typeof VirtualizerRoot>;
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;AA2FpE,QAAA,MAAM,eAAe,iEAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
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
- // Polyfill PointerEvent for jsdom
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;gCAqG7B,MAAM,GAAG,IAAI;IAazC,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"}
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
- if (_isDragging || !isBrowser)
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
- if (_isDragging || !isBrowser)
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
- if (_isDragging) {
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;CA2Bf"}
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) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@spectral_labs/ui",
3
3
  "type": "module",
4
- "version": "1.3.0",
4
+ "version": "1.3.1",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",
7
7
  "access": "public"