@x33025/sveltely 0.0.32 → 0.0.34
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/ChipInput.svelte +69 -3
- package/dist/components/Pagination.svelte +93 -0
- package/dist/components/Pagination.svelte.d.ts +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/style/index.css +11 -3
- package/dist/style.css +12 -0
- package/package.json +1 -1
|
@@ -23,6 +23,12 @@
|
|
|
23
23
|
let editingTag = $state<string | null>(null);
|
|
24
24
|
let editingValue = $state('');
|
|
25
25
|
let editingEl = $state<HTMLInputElement | null>(null);
|
|
26
|
+
let rootEl = $state<HTMLElement | null>(null);
|
|
27
|
+
let dragStartIndex = $state<number | null>(null);
|
|
28
|
+
let dragHoverIndex = $state<number | null>(null);
|
|
29
|
+
let isPointerSelecting = $state(false);
|
|
30
|
+
let rangeSelectionMoved = $state(false);
|
|
31
|
+
let suppressNextClick = $state(false);
|
|
26
32
|
const selectionEnabled = $derived(selection !== undefined);
|
|
27
33
|
|
|
28
34
|
const addTag = (rawValue: string) => {
|
|
@@ -42,6 +48,53 @@
|
|
|
42
48
|
selection = [...selection, tag];
|
|
43
49
|
};
|
|
44
50
|
|
|
51
|
+
const applySelectionRange = (start: number, end: number) => {
|
|
52
|
+
if (!selection) return;
|
|
53
|
+
const from = Math.min(start, end);
|
|
54
|
+
const to = Math.max(start, end);
|
|
55
|
+
selection = tags.slice(from, to + 1);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const beginRangeSelection = (event: PointerEvent, index: number) => {
|
|
59
|
+
if (!selectionEnabled || event.button !== 0) return;
|
|
60
|
+
dragStartIndex = index;
|
|
61
|
+
dragHoverIndex = index;
|
|
62
|
+
isPointerSelecting = true;
|
|
63
|
+
rangeSelectionMoved = false;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const extendRangeSelection = (index: number) => {
|
|
67
|
+
if (!selectionEnabled || !isPointerSelecting || dragStartIndex === null) return;
|
|
68
|
+
dragHoverIndex = index;
|
|
69
|
+
if (dragStartIndex === index && !rangeSelectionMoved) return;
|
|
70
|
+
rangeSelectionMoved = true;
|
|
71
|
+
applySelectionRange(dragStartIndex, index);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const endRangeSelection = () => {
|
|
75
|
+
if (!isPointerSelecting) return;
|
|
76
|
+
suppressNextClick = rangeSelectionMoved;
|
|
77
|
+
isPointerSelecting = false;
|
|
78
|
+
rangeSelectionMoved = false;
|
|
79
|
+
dragStartIndex = null;
|
|
80
|
+
dragHoverIndex = null;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const onChipClick = (tag: string) => {
|
|
84
|
+
if (suppressNextClick) {
|
|
85
|
+
suppressNextClick = false;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
toggleSelected(tag);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleWindowPointerDown = (event: PointerEvent) => {
|
|
92
|
+
if (!selectionEnabled || !selection || !rootEl) return;
|
|
93
|
+
const target = event.target as Node | null;
|
|
94
|
+
if (target && rootEl.contains(target)) return;
|
|
95
|
+
selection = [];
|
|
96
|
+
};
|
|
97
|
+
|
|
45
98
|
const onKeydown = (event: KeyboardEvent) => {
|
|
46
99
|
if ((event.key === 'Enter' || event.key === ',') && inputValue.trim()) {
|
|
47
100
|
event.preventDefault();
|
|
@@ -157,8 +210,14 @@
|
|
|
157
210
|
});
|
|
158
211
|
</script>
|
|
159
212
|
|
|
160
|
-
<
|
|
161
|
-
{
|
|
213
|
+
<svelte:window
|
|
214
|
+
onpointerup={endRangeSelection}
|
|
215
|
+
onpointercancel={endRangeSelection}
|
|
216
|
+
onpointerdown={handleWindowPointerDown}
|
|
217
|
+
/>
|
|
218
|
+
|
|
219
|
+
<div bind:this={rootEl} class="chip-row flex w-full flex-wrap items-center">
|
|
220
|
+
{#each tags as tag, index (tag)}
|
|
162
221
|
{#if editingTag === tag}
|
|
163
222
|
<input
|
|
164
223
|
bind:this={editingEl}
|
|
@@ -173,7 +232,10 @@
|
|
|
173
232
|
type="button"
|
|
174
233
|
class="chip-surface inline-flex items-center gap-2"
|
|
175
234
|
class:chip-selected={selection?.includes(tag)}
|
|
176
|
-
|
|
235
|
+
class:chip-hovered={isPointerSelecting && dragHoverIndex === index}
|
|
236
|
+
onpointerdown={(event) => beginRangeSelection(event, index)}
|
|
237
|
+
onpointerenter={() => extendRangeSelection(index)}
|
|
238
|
+
onclick={() => onChipClick(tag)}
|
|
177
239
|
ondblclick={() => startEditing(tag)}
|
|
178
240
|
aria-pressed={selection?.includes(tag)}
|
|
179
241
|
>
|
|
@@ -237,6 +299,10 @@
|
|
|
237
299
|
background: var(--chip-input-hover);
|
|
238
300
|
}
|
|
239
301
|
|
|
302
|
+
.chip-hovered {
|
|
303
|
+
background: var(--chip-input-hover);
|
|
304
|
+
}
|
|
305
|
+
|
|
240
306
|
.chip-input-field:hover {
|
|
241
307
|
background: var(--chip-input-background);
|
|
242
308
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { goto } from '$app/navigation';
|
|
3
|
+
|
|
4
|
+
type PaginationData = {
|
|
5
|
+
view: string;
|
|
6
|
+
page: number;
|
|
7
|
+
totalPages: number;
|
|
8
|
+
hasNext?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
data,
|
|
13
|
+
onPageChange,
|
|
14
|
+
class: className = ''
|
|
15
|
+
}: {
|
|
16
|
+
data: PaginationData;
|
|
17
|
+
onPageChange?: (page: number, href: string) => void | Promise<void>;
|
|
18
|
+
class?: string;
|
|
19
|
+
} = $props();
|
|
20
|
+
|
|
21
|
+
const clampedPage = (targetPage: number) => Math.min(Math.max(targetPage, 1), maxPage());
|
|
22
|
+
|
|
23
|
+
const toQuery = (targetPage: number) => {
|
|
24
|
+
const params = new URLSearchParams({
|
|
25
|
+
view: data.view,
|
|
26
|
+
page: String(clampedPage(targetPage))
|
|
27
|
+
});
|
|
28
|
+
return `?${params.toString()}`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const maxPage = () => Math.max(1, data.totalPages);
|
|
32
|
+
const safePage = () => Math.min(Math.max(data.page, 1), maxPage());
|
|
33
|
+
const hasNext = () => (typeof data.hasNext === 'boolean' ? data.hasNext : safePage() < maxPage());
|
|
34
|
+
|
|
35
|
+
const goToPage = (targetPage: number) => {
|
|
36
|
+
const clamped = clampedPage(targetPage);
|
|
37
|
+
const href = toQuery(clamped);
|
|
38
|
+
if (onPageChange) {
|
|
39
|
+
void onPageChange(clamped, href);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
void goto(href);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleSubmit = (event: SubmitEvent) => {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
const form = event.currentTarget as HTMLFormElement;
|
|
48
|
+
const input = form.elements.namedItem('page') as HTMLInputElement | null;
|
|
49
|
+
if (!input) return;
|
|
50
|
+
|
|
51
|
+
const parsed = Number.parseInt(input.value, 10);
|
|
52
|
+
const fallback = safePage();
|
|
53
|
+
const next = Number.isFinite(parsed) ? parsed : fallback;
|
|
54
|
+
const clamped = Math.min(Math.max(next, 1), maxPage());
|
|
55
|
+
input.value = String(clamped);
|
|
56
|
+
goToPage(clamped);
|
|
57
|
+
};
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<div class={`hstack items-center ${className}`} style="gap: var(--pagination-gap);">
|
|
61
|
+
{#if safePage() > 1}
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
class="pagination-button action padding-sm"
|
|
65
|
+
onclick={() => goToPage(safePage() - 1)}
|
|
66
|
+
>
|
|
67
|
+
Previous
|
|
68
|
+
</button>
|
|
69
|
+
{:else}
|
|
70
|
+
<span class="pagination-button action padding-sm bg-zinc-400">Previous</span>
|
|
71
|
+
{/if}
|
|
72
|
+
|
|
73
|
+
<span>Page</span>
|
|
74
|
+
|
|
75
|
+
<form method="GET" style="display: inline;" novalidate onsubmit={handleSubmit}>
|
|
76
|
+
<input type="hidden" name="view" value={data.view} />
|
|
77
|
+
<input id="page" name="page" type="number" class="pagination-input" value={safePage()} />
|
|
78
|
+
</form>
|
|
79
|
+
|
|
80
|
+
<span>of {maxPage()}</span>
|
|
81
|
+
|
|
82
|
+
{#if hasNext()}
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
class="pagination-button action padding-sm"
|
|
86
|
+
onclick={() => goToPage(safePage() + 1)}
|
|
87
|
+
>
|
|
88
|
+
Next
|
|
89
|
+
</button>
|
|
90
|
+
{:else}
|
|
91
|
+
<span class="pagination-button action padding-sm bg-zinc-400">Next</span>
|
|
92
|
+
{/if}
|
|
93
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type PaginationData = {
|
|
2
|
+
view: string;
|
|
3
|
+
page: number;
|
|
4
|
+
totalPages: number;
|
|
5
|
+
hasNext?: boolean;
|
|
6
|
+
};
|
|
7
|
+
type $$ComponentProps = {
|
|
8
|
+
data: PaginationData;
|
|
9
|
+
onPageChange?: (page: number, href: string) => void | Promise<void>;
|
|
10
|
+
class?: string;
|
|
11
|
+
};
|
|
12
|
+
declare const Pagination: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
13
|
+
type Pagination = ReturnType<typeof Pagination>;
|
|
14
|
+
export default Pagination;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { default as AnimatedNumber } from './components/AnimatedNumber.svelte';
|
|
|
4
4
|
export { default as GlowEffect } from './components/GlowEffect.svelte';
|
|
5
5
|
export { default as NavigationStack } from './components/NavigationStack';
|
|
6
6
|
export { default as SegmentedPicker } from './components/SegmentedPicker.svelte';
|
|
7
|
+
export { default as Pagination } from './components/Pagination.svelte';
|
|
7
8
|
export { default as Slider } from './components/Slider.svelte';
|
|
8
9
|
export { default as Sheet } from './components/Sheet';
|
|
9
10
|
export { default as Spinner } from './components/Spinner.svelte';
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { default as AnimatedNumber } from './components/AnimatedNumber.svelte';
|
|
|
4
4
|
export { default as GlowEffect } from './components/GlowEffect.svelte';
|
|
5
5
|
export { default as NavigationStack } from './components/NavigationStack';
|
|
6
6
|
export { default as SegmentedPicker } from './components/SegmentedPicker.svelte';
|
|
7
|
+
export { default as Pagination } from './components/Pagination.svelte';
|
|
7
8
|
export { default as Slider } from './components/Slider.svelte';
|
|
8
9
|
export { default as Sheet } from './components/Sheet';
|
|
9
10
|
export { default as Spinner } from './components/Spinner.svelte';
|
package/dist/style/index.css
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
@layer utilities {
|
|
11
11
|
.layout {
|
|
12
|
-
@apply flex size-full min-h-0 flex-col;
|
|
12
|
+
@apply flex size-full min-h-0 min-w-0 shrink-0 flex-col;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
.center {
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
.vstack {
|
|
20
|
-
@apply flex min-h-0 flex-col;
|
|
20
|
+
@apply flex min-h-0 shrink-0 flex-col;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
.hstack {
|
|
24
|
-
@apply flex min-w-0 flex-row;
|
|
24
|
+
@apply flex min-w-0 shrink-0 flex-row;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
.spacer {
|
|
@@ -55,6 +55,12 @@
|
|
|
55
55
|
.dropdown-item:hover {
|
|
56
56
|
background: var(--dropdown-item-highlight);
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
.pagination-button {
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.pagination-input {
|
|
63
|
+
}
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
@layer theme {
|
|
@@ -109,5 +115,7 @@
|
|
|
109
115
|
--chip-input-border-radius: 9999px;
|
|
110
116
|
--chip-input-border-color: transparent;
|
|
111
117
|
--chip-input-highlight: var(--color-zinc-300);
|
|
118
|
+
|
|
119
|
+
--pagination-gap: calc(var(--spacing) * 4);
|
|
112
120
|
}
|
|
113
121
|
}
|
package/dist/style.css
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
--color-zinc-100: oklch(96.7% 0.001 286.375);
|
|
16
16
|
--color-zinc-200: oklch(92% 0.004 286.32);
|
|
17
17
|
--color-zinc-300: oklch(87.1% 0.006 286.286);
|
|
18
|
+
--color-zinc-400: oklch(70.5% 0.015 286.067);
|
|
18
19
|
--color-zinc-500: oklch(55.2% 0.016 285.938);
|
|
19
20
|
--color-zinc-700: oklch(37% 0.013 285.805);
|
|
20
21
|
--color-zinc-900: oklch(21% 0.006 285.885);
|
|
@@ -233,6 +234,9 @@
|
|
|
233
234
|
.flex {
|
|
234
235
|
display: flex;
|
|
235
236
|
}
|
|
237
|
+
.hidden {
|
|
238
|
+
display: none;
|
|
239
|
+
}
|
|
236
240
|
.inline-block {
|
|
237
241
|
display: inline-block;
|
|
238
242
|
}
|
|
@@ -364,6 +368,9 @@
|
|
|
364
368
|
.bg-zinc-50 {
|
|
365
369
|
background-color: var(--color-zinc-50);
|
|
366
370
|
}
|
|
371
|
+
.bg-zinc-400 {
|
|
372
|
+
background-color: var(--color-zinc-400);
|
|
373
|
+
}
|
|
367
374
|
.p-1\.5 {
|
|
368
375
|
padding: calc(var(--spacing) * 1.5);
|
|
369
376
|
}
|
|
@@ -540,6 +547,8 @@
|
|
|
540
547
|
width: 100%;
|
|
541
548
|
height: 100%;
|
|
542
549
|
min-height: calc(var(--spacing) * 0);
|
|
550
|
+
min-width: calc(var(--spacing) * 0);
|
|
551
|
+
flex-shrink: 0;
|
|
543
552
|
flex-direction: column;
|
|
544
553
|
}
|
|
545
554
|
.center {
|
|
@@ -549,11 +558,13 @@
|
|
|
549
558
|
.vstack {
|
|
550
559
|
display: flex;
|
|
551
560
|
min-height: calc(var(--spacing) * 0);
|
|
561
|
+
flex-shrink: 0;
|
|
552
562
|
flex-direction: column;
|
|
553
563
|
}
|
|
554
564
|
.hstack {
|
|
555
565
|
display: flex;
|
|
556
566
|
min-width: calc(var(--spacing) * 0);
|
|
567
|
+
flex-shrink: 0;
|
|
557
568
|
flex-direction: row;
|
|
558
569
|
}
|
|
559
570
|
.spacer {
|
|
@@ -633,6 +644,7 @@
|
|
|
633
644
|
--chip-input-border-radius: 9999px;
|
|
634
645
|
--chip-input-border-color: transparent;
|
|
635
646
|
--chip-input-highlight: var(--color-zinc-300);
|
|
647
|
+
--pagination-gap: calc(var(--spacing) * 4);
|
|
636
648
|
}
|
|
637
649
|
}
|
|
638
650
|
@property --tw-rotate-x {
|