Skip to content

Commit b1a41c4

Browse files
author
terrakuh
committed
Add store grouping
1 parent 1e104c7 commit b1a41c4

File tree

15 files changed

+3863
-141
lines changed

15 files changed

+3863
-141
lines changed

package-lock.json

Lines changed: 3549 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/lib/components/SortableList.svelte

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
areColliding,
4444
getClosestScrollableAncestor,
4545
getCollidingItem,
46+
getGroupSelector,
4647
getId,
4748
getIndex,
4849
getItemRects,
@@ -74,11 +75,12 @@
7475
export let isLocked: $$Props['isLocked'] = false;
7576
export let isDisabled: $$Props['isDisabled'] = false;
7677
export let announcements: $$Props['announcements'] = undefined;
78+
export let group: $$Props['group'] = undefined;
7779
7880
$: _transition = { duration: 240, easing: 'cubic-bezier(0.2, 1, 0.1, 1)', ...transition };
7981
$: _announcements = announcements || announce;
8082
81-
const rootProps = setRootProps({
83+
const rootProps = setRootProps(group, {
8284
gap,
8385
direction,
8486
transition: _transition,
@@ -107,24 +109,24 @@
107109
announcements: _announcements,
108110
};
109111
110-
const root = setRoot(null);
112+
const root = setRoot(group, null);
111113
let ghostStatus: GhostProps['status'] = 'unset';
112-
const pointer = setPointer(null);
113-
const pointerOrigin = setPointerOrigin(null);
114-
const itemRects = setItemRects(null);
115-
const draggedItem = setDraggedItem(null);
116-
const targetItem = setTargetItem(null);
117-
const focusedItem = setFocusedItem(null);
114+
const pointer = setPointer(group, null);
115+
const pointerOrigin = setPointerOrigin(group, null);
116+
const itemRects = setItemRects(group, null);
117+
const draggedItem = setDraggedItem(group, null);
118+
const targetItem = setTargetItem(group, null);
119+
const focusedItem = setFocusedItem(group, null);
118120
let liveText: string = '';
119121
120-
const isPointerDragging = setIsPointerDragging(false);
121-
const isPointerDropping = setIsPointerDropping(false);
122-
const isKeyboardDragging = setIsKeyboardDragging(false);
123-
const isKeyboardDropping = setIsKeyboardDropping(false);
124-
const isPointerCanceling = setIsPointerCanceling(false);
125-
const isKeyboardCanceling = setIsKeyboardCanceling(false);
126-
const isBetweenBounds = setIsBetweenBounds(true);
127-
const isRTL = setIsRTL(false);
122+
const isPointerDragging = setIsPointerDragging(group, false);
123+
const isPointerDropping = setIsPointerDropping(group, false);
124+
const isKeyboardDragging = setIsKeyboardDragging(group, false);
125+
const isKeyboardDropping = setIsKeyboardDropping(group, false);
126+
const isPointerCanceling = setIsPointerCanceling(group, false);
127+
const isKeyboardCanceling = setIsKeyboardCanceling(group, false);
128+
const isBetweenBounds = setIsBetweenBounds(group, true);
129+
const isRTL = setIsRTL(group, false);
128130
129131
const dispatch = createEventDispatcher<{
130132
mounted: MountedEventDetail;
@@ -201,7 +203,7 @@
201203
return;
202204
203205
const target = event.target as HTMLElement;
204-
const currItem = target.closest<HTMLLIElement>('.ssl-item');
206+
const currItem = target.closest<HTMLLIElement>('.ssl-item' + getGroupSelector(group));
205207
if (!currItem) return;
206208
207209
if (
@@ -226,8 +228,10 @@
226228
event.preventDefault();
227229
228230
// Prevent dragging if the current list item contains a handle, but we’re not dragging from it.
229-
const hasHandle = !!currItem.querySelector('[data-role="handle"]');
230-
const targetIsOrResidesInHandle = target.closest('[data-role="handle"]');
231+
const hasHandle = !!currItem.querySelector('[data-role="handle"]' + getGroupSelector(group));
232+
const targetIsOrResidesInHandle = target.closest(
233+
'[data-role="handle"]' + getGroupSelector(group)
234+
);
231235
if (hasHandle && !targetIsOrResidesInHandle) return;
232236
233237
// Prevent dragging if the current list item contains an interactive element
@@ -241,7 +245,7 @@
241245
$pointer = { x: event.clientX, y: event.clientY };
242246
$pointerOrigin = { x: event.clientX, y: event.clientY };
243247
$draggedItem = currItem;
244-
$itemRects = getItemRects(rootRef);
248+
$itemRects = getItemRects(group, rootRef);
245249
ghostStatus = 'init';
246250
await tick();
247251
$isPointerDragging = true;
@@ -291,12 +295,12 @@
291295
292296
// Re-set itemRects only during scrolling.
293297
// (setting it here instead of in the `scroll()` function to reduce the performance impact)
294-
if (scrollingSpeed !== 0) $itemRects = getItemRects(rootRef);
298+
if (scrollingSpeed !== 0) $itemRects = getItemRects(group, rootRef);
295299
await tick();
296300
const collidingItemRect = getCollidingItem(ghostRef, $itemRects);
297301
if (collidingItemRect)
298302
$targetItem = rootRef.querySelector<HTMLLIElement>(
299-
`.ssl-item[data-item-id="${collidingItemRect.id}"]`
303+
`.ssl-item${getGroupSelector(group)}[data-item-id="${collidingItemRect.id}"]`
300304
);
301305
else if (canClearOnDragOut || (canRemoveOnDropOut && !$isBetweenBounds)) $targetItem = null;
302306
@@ -343,7 +347,7 @@
343347
await tick();
344348
$draggedItem = $focusedItem;
345349
const draggedIndex = getIndex($focusedItem);
346-
$itemRects = getItemRects(rootRef);
350+
$itemRects = getItemRects(group, rootRef);
347351
dispatch('dragstart', {
348352
deviceType: 'keyboard',
349353
draggedItem: $draggedItem,
@@ -369,7 +373,9 @@
369373
370374
if (!$isKeyboardDragging) {
371375
if (!$focusedItem || focusedIndex === null) {
372-
const firstItem = rootRef.querySelector<HTMLLIElement>('.ssl-item');
376+
const firstItem = rootRef.querySelector<HTMLLIElement>(
377+
'.ssl-item' + getGroupSelector(group)
378+
);
373379
if (!firstItem) return;
374380
firstItem.focus({ preventScroll: true });
375381
if (scrollableAncestor && !isFullyVisible(firstItem, scrollableAncestor))
@@ -379,7 +385,9 @@
379385
380386
// Prevent focusing the previous item if the current one is the first,
381387
// and focusing the next item if the current one is the last.
382-
const items = rootRef.querySelectorAll<HTMLLIElement>('.ssl-item');
388+
const items = rootRef.querySelectorAll<HTMLLIElement>(
389+
'.ssl-item' + getGroupSelector(group)
390+
);
383391
if (
384392
(step === -1 && focusedIndex === 0) ||
385393
(step === 1 && focusedIndex === items.length - 1)
@@ -447,7 +455,9 @@
447455
if (key === 'Home' || key === 'End') {
448456
event.preventDefault();
449457
450-
const items = rootRef.querySelectorAll<HTMLLIElement>('.ssl-item');
458+
const items = rootRef.querySelectorAll<HTMLLIElement>(
459+
'.ssl-item' + getGroupSelector(group)
460+
);
451461
const focusedIndex = ($focusedItem && getIndex($focusedItem)) ?? null;
452462
453463
if (!$isKeyboardDragging) {
@@ -607,6 +617,7 @@
607617
style:--ssl-wrap={hasWrapping ? 'wrap' : 'nowrap'}
608618
style:--ssl-transition-duration="{_transition.duration}ms"
609619
style:pointer-events={$focusedItem ? 'none' : 'auto'}
620+
data-group={group}
610621
data-has-drop-marker={hasDropMarker}
611622
data-can-remove-on-drop-out={canRemoveOnDropOut}
612623
data-is-locked={isLocked}
@@ -622,10 +633,11 @@
622633
aria-orientation={direction}
623634
aria-activedescendant={$focusedItem ? $focusedItem.id : undefined}
624635
aria-disabled={isDisabled}
625-
on:pointerdown={handlePointerDown}
626-
on:pointercancel={handlePointerCancel}
627-
on:keydown={handleKeyDown}
628-
on:itemfocusout={(event) => handlePointerAndKeyboardDrop(event.detail.item, 'keyboard-cancel')}
636+
on:pointerdown|stopPropagation={handlePointerDown}
637+
on:pointercancel|stopPropagation={handlePointerCancel}
638+
on:keydown|stopPropagation={handleKeyDown}
639+
on:itemfocusout|stopPropagation={(event) =>
640+
handlePointerAndKeyboardDrop(event.detail.item, 'keyboard-cancel')}
629641
>
630642
<slot>
631643
<p>
@@ -634,7 +646,7 @@
634646
</p>
635647
</slot>
636648
</ul>
637-
<SortableListGhost bind:ghostRef status={ghostStatus} />
649+
<SortableListGhost bind:ghostRef status={ghostStatus} {group} />
638650
<div class="ssl-live-region" aria-live="assertive" aria-atomic="true">{liveText}</div>
639651

640652
<!--

src/lib/components/SortableListGhost.svelte

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,20 @@
2525
2626
export let ghostRef: $$Props['ghostRef'];
2727
export let status: $$Props['status'];
28+
export let group: $$Props['group'] = undefined;
2829
29-
const rootProps = getRootProps();
30+
const rootProps = getRootProps(group);
3031
31-
const root = getRoot();
32-
const pointer = getPointer();
33-
const pointerOrigin = getPointerOrigin();
34-
const itemRects = getItemRects();
35-
const draggedItem = getDraggedItem();
36-
const targetItem = getTargetItem();
32+
const root = getRoot(group);
33+
const pointer = getPointer(group);
34+
const pointerOrigin = getPointerOrigin(group);
35+
const itemRects = getItemRects(group);
36+
const draggedItem = getDraggedItem(group);
37+
const targetItem = getTargetItem(group);
3738
38-
const isPointerDragging = getIsPointerDragging();
39-
const isPointerDropping = getIsPointerDropping();
40-
const isBetweenBounds = getIsBetweenBounds();
39+
const isPointerDragging = getIsPointerDragging(group);
40+
const isPointerDropping = getIsPointerDropping(group);
41+
const isBetweenBounds = getIsBetweenBounds(group);
4142
4243
$: draggedItemId = $draggedItem ? getId($draggedItem) : null;
4344
let lastCloneId: string | null = null;
@@ -250,6 +251,7 @@
250251
style:transition={styleTransition}
251252
style:visibility={$isPointerDragging || $isPointerDropping ? 'visible' : 'hidden'}
252253
style:z-index={styleZIndex}
254+
data-group={group}
253255
data-is-pointer-dragging={$isPointerDragging}
254256
data-is-pointer-dropping={$isPointerDropping}
255257
data-is-between-bounds={$isBetweenBounds}

src/lib/components/SortableListItem.svelte

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
calculateTranslate,
2323
calculateTranslateWithAlignment,
2424
dispatch,
25+
getGroupSelector,
2526
getId,
2627
getIndex,
2728
isInSameRow,
@@ -37,34 +38,35 @@
3738
export let isDisabled: $$Props['isDisabled'] = false;
3839
export let transitionIn: $$Props['transitionIn'] = undefined;
3940
export let transitionOut: $$Props['transitionOut'] = undefined;
41+
export let group: $$Props['group'] = undefined;
4042
4143
$: _transitionIn = transitionIn || scaleFade;
4244
$: _transitionOut = transitionOut || scaleFade;
4345
44-
const rootProps = getRootProps();
46+
const rootProps = getRootProps(group);
4547
46-
const root = getRoot();
47-
const itemRects = getItemRects();
48-
const draggedItem = getDraggedItem();
49-
const targetItem = getTargetItem();
50-
const focusedItem = getFocusedItem();
48+
const root = getRoot(group);
49+
const itemRects = getItemRects(group);
50+
const draggedItem = getDraggedItem(group);
51+
const targetItem = getTargetItem(group);
52+
const focusedItem = getFocusedItem(group);
5153
52-
const isPointerDragging = getIsPointerDragging();
53-
const isPointerDropping = getIsPointerDropping();
54-
const isKeyboardDragging = getIsKeyboardDragging();
55-
const isKeyboardDropping = getIsKeyboardDropping();
56-
const isPointerCanceling = getIsPointerCanceling();
57-
const isKeyboardCanceling = getIsKeyboardCanceling();
58-
const isBetweenBounds = getIsBetweenBounds();
59-
const isRTL = getIsRTL();
54+
const isPointerDragging = getIsPointerDragging(group);
55+
const isPointerDropping = getIsPointerDropping(group);
56+
const isKeyboardDragging = getIsKeyboardDragging(group);
57+
const isKeyboardDropping = getIsKeyboardDropping(group);
58+
const isPointerCanceling = getIsPointerCanceling(group);
59+
const isKeyboardCanceling = getIsKeyboardCanceling(group);
60+
const isBetweenBounds = getIsBetweenBounds(group);
61+
const isRTL = getIsRTL(group);
6062
6163
let hasHandle = false;
6264
$: {
6365
setInteractiveElementsTabIndex($isKeyboardDragging, focusedId);
6466
}
6567
6668
onMount(() => {
67-
hasHandle = !!itemRef?.querySelector('[data-role="handle"]');
69+
hasHandle = !!itemRef?.querySelector('[data-role="handle"]' + getGroupSelector(group));
6870
setInteractiveElementsTabIndex();
6971
});
7072
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -224,7 +226,10 @@
224226
// on the current element and it’s descendants too.
225227
async function handleFocusOut(event: FocusEvent) {
226228
const relatedTarget = event.relatedTarget as HTMLElement | null;
227-
if (!relatedTarget || (relatedTarget && !relatedTarget.closest('.ssl-item'))) {
229+
if (
230+
!relatedTarget ||
231+
(relatedTarget && !relatedTarget.closest('.ssl-item' + getGroupSelector(group)))
232+
) {
228233
if (!$focusedItem) return;
229234
dispatch(itemRef, 'itemfocusout', { item: $focusedItem });
230235
await tick();
@@ -269,6 +274,7 @@ Serves as an individual item within `<SortableList.Root>`. Holds the data and co
269274
style:transition={styleTransition}
270275
data-item-id={id}
271276
data-item-index={index}
277+
data-group={group}
272278
data-is-pointer-dragging={$isPointerDragging && draggedId === String(id)}
273279
data-is-pointer-dropping={$isPointerDropping && draggedId === String(id)}
274280
data-is-keyboard-dragging={$isKeyboardDragging && draggedId === String(id)}
@@ -281,8 +287,8 @@ Serves as an individual item within `<SortableList.Root>`. Holds the data and co
281287
aria-labelledby={$$restProps['aria-labelledby'] || undefined}
282288
aria-selected={focusedId === String(id)}
283289
aria-disabled={$rootProps.isDisabled || isDisabled}
284-
on:focus={handleFocus}
285-
on:focusout={handleFocusOut}
290+
on:focus|stopPropagation={handleFocus}
291+
on:focusout|stopPropagation={handleFocusOut}
286292
in:_transitionIn
287293
out:_transitionOut
288294
>

src/lib/components/SortableListItemHandle.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
<script lang="ts">
22
import Icon from '$lib/components/Icon.svelte';
33
import { getIsPointerDragging } from '$lib/stores/index.js';
4+
import type { SortableListItemHandleProps as ItemHandleProps } from '$lib/types/props.js';
45
5-
const isPointerDragging = getIsPointerDragging();
6+
type $$Props = ItemHandleProps;
7+
8+
export let group: $$Props['group'] = undefined;
9+
10+
const isPointerDragging = getIsPointerDragging(group);
611
712
const classes = ['ssl-item-handle', ...($$restProps.class ? [$$restProps.class] : [])].join(' ');
813
</script>
914

1015
<span
1116
style:cursor={$isPointerDragging ? 'grabbing' : 'grab'}
1217
data-role="handle"
18+
data-group={group}
1319
aria-hidden="true"
1420
{...$$restProps}
1521
class={classes}

src/lib/components/SortableListItemRemove.svelte

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
<script lang="ts">
22
import Icon from '$lib/components/Icon.svelte';
33
import { getFocusedItem, getRoot } from '$lib/stores/index.js';
4-
import { getIndex } from '$lib/utils/index.js';
4+
import type { SortableListItemRemoveProps as ItemRemoveProps } from '$lib/types/props.js';
5+
import { getGroupSelector, getIndex } from '$lib/utils/index.js';
56
6-
const root = getRoot();
7-
const focusedItem = getFocusedItem();
7+
type $$Props = ItemRemoveProps;
8+
9+
export let group: $$Props['group'] = undefined;
10+
11+
const root = getRoot(group);
12+
const focusedItem = getFocusedItem(group);
813
914
function handleClick() {
1015
if ($focusedItem && $root) {
11-
const items = $root.querySelectorAll<HTMLLIElement>('.ssl-item');
16+
const items = $root.querySelectorAll<HTMLLIElement>('.ssl-item' + getGroupSelector(group));
1217
if (items.length > 1) {
1318
// Focus the next/previous item (if it exists) before removing.
1419
const step = getIndex($focusedItem) !== items.length - 1 ? 1 : -1;
@@ -41,7 +46,14 @@ Serves as a `<button>` element that (when pressed) removes an item. Including it
4146
```
4247
-->
4348

44-
<button data-role="remove" on:click={handleClick} on:click {...$$restProps} class={classes}>
49+
<button
50+
data-role="remove"
51+
data-group={group}
52+
on:click={handleClick}
53+
on:click
54+
{...$$restProps}
55+
class={classes}
56+
>
4557
<slot>
4658
<Icon name="remove" />
4759
</slot>

0 commit comments

Comments
 (0)