@dorsk/tsumikit 0.71.0 → 0.72.0
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/README.md +33 -5
- package/dist/autoresize.js +13 -0
- package/dist/clipboard.js +12 -0
- package/dist/components/atoms/Artwork.svelte +4 -2
- package/dist/components/atoms/Avatar.svelte +4 -3
- package/dist/components/atoms/Badge.svelte +13 -0
- package/dist/components/atoms/Button.svelte +25 -9
- package/dist/components/atoms/Card.svelte +2 -0
- package/dist/components/atoms/Dot.svelte +1 -1
- package/dist/components/atoms/Icon.svelte +2 -0
- package/dist/components/atoms/Icon.svelte.d.ts +2 -0
- package/dist/components/atoms/Kbd.svelte +28 -16
- package/dist/components/atoms/Link.svelte +6 -4
- package/dist/components/atoms/Scrim.svelte +18 -1
- package/dist/components/atoms/Slider.svelte +11 -1
- package/dist/components/atoms/Swatch.svelte +12 -0
- package/dist/components/layouts/AppShell.svelte +70 -39
- package/dist/components/layouts/Container.svelte +25 -3
- package/dist/components/layouts/MasterDetail.svelte +59 -33
- package/dist/components/layouts/MasterDetail.svelte.d.ts +1 -1
- package/dist/components/layouts/NavItem.svelte +8 -4
- package/dist/components/layouts/NavSection.svelte +8 -2
- package/dist/components/layouts/ResizablePanel.svelte +14 -0
- package/dist/components/layouts/TabBar.svelte +217 -0
- package/dist/components/layouts/TabBar.svelte.d.ts +27 -0
- package/dist/components/layouts/Toolbar.svelte +5 -0
- package/dist/components/layouts/toolbar-roving.d.ts +16 -0
- package/dist/components/layouts/toolbar-roving.js +47 -1
- package/dist/components/molecules/FilterInput.svelte +7 -0
- package/dist/components/molecules/FilterInput.svelte.d.ts +2 -0
- package/dist/components/molecules/PullToRefresh.svelte +205 -0
- package/dist/components/molecules/PullToRefresh.svelte.d.ts +18 -0
- package/dist/components/molecules/SwipeRow.svelte +349 -0
- package/dist/components/molecules/SwipeRow.svelte.d.ts +37 -0
- package/dist/components/organisms/ConversationFeed.svelte +22 -8
- package/dist/components/organisms/ConversationFeed.svelte.d.ts +2 -0
- package/dist/components/organisms/DataTable.svelte +22 -5
- package/dist/components/organisms/DataTable.svelte.d.ts +4 -2
- package/dist/components/organisms/FilterSearchBar.svelte +4 -0
- package/dist/components/organisms/FilterSearchBar.svelte.d.ts +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/markdown.js +15 -7
- package/dist/styles/reset.css +30 -0
- package/dist/styles/syntax.css +0 -29
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { IconName } from '../atoms/Icon.svelte';
|
|
2
|
+
export interface TabBarItem {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
icon?: IconName;
|
|
6
|
+
/** Renders an `<a>`; without it the item is a `<button role="tab">`. */
|
|
7
|
+
href?: string;
|
|
8
|
+
/** Count (capped at `99+`) or short text pinned to the glyph. 0 / '' render nothing. */
|
|
9
|
+
badge?: number | string;
|
|
10
|
+
/** Id of the panel a tab shows (`aria-controls`). */
|
|
11
|
+
controls?: string;
|
|
12
|
+
}
|
|
13
|
+
type $$ComponentProps = {
|
|
14
|
+
items: TabBarItem[];
|
|
15
|
+
/** `id` of the selected item. */
|
|
16
|
+
value?: string;
|
|
17
|
+
onselect?: (id: string, item: TabBarItem) => void;
|
|
18
|
+
/** Accessible name of the bar. */
|
|
19
|
+
label?: string;
|
|
20
|
+
/** 'bar' = fixed to the bottom edge; 'inline' = flows with its parent. */
|
|
21
|
+
placement?: 'bar' | 'inline';
|
|
22
|
+
class?: string;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
};
|
|
25
|
+
declare const TabBar: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
26
|
+
type TabBar = ReturnType<typeof TabBar>;
|
|
27
|
+
export default TabBar;
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
consumesArrowKeys,
|
|
17
17
|
isToolbarNavKey,
|
|
18
18
|
nextToolbarStop,
|
|
19
|
+
radioGroupPeers,
|
|
19
20
|
TOOLBAR_STOP_ATTR,
|
|
20
21
|
toolbarStops
|
|
21
22
|
} from './toolbar-roving.js';
|
|
@@ -93,6 +94,10 @@
|
|
|
93
94
|
list.forEach((node, i) => {
|
|
94
95
|
node.setAttribute(TOOLBAR_STOP_ATTR, '');
|
|
95
96
|
node.tabIndex = i === index ? 0 : -1;
|
|
97
|
+
for (const peer of radioGroupPeers(node, el)) {
|
|
98
|
+
peer.setAttribute(TOOLBAR_STOP_ATTR, '');
|
|
99
|
+
peer.tabIndex = -1;
|
|
100
|
+
}
|
|
96
101
|
});
|
|
97
102
|
}
|
|
98
103
|
|
|
@@ -38,6 +38,22 @@ export function acceptsRovingStop(el: {
|
|
|
38
38
|
* @returns {HTMLElement[]}
|
|
39
39
|
*/
|
|
40
40
|
export function toolbarStops(container: Element | null, isHidden: (node: HTMLElement) => boolean): HTMLElement[];
|
|
41
|
+
/**
|
|
42
|
+
* A named radio group is a single stop, like a native one: its checked radio,
|
|
43
|
+
* else its first. The arrows inside it stay the browser's.
|
|
44
|
+
*
|
|
45
|
+
* @param {HTMLElement[]} stops
|
|
46
|
+
* @returns {HTMLElement[]}
|
|
47
|
+
*/
|
|
48
|
+
export function oneStopPerRadioGroup(stops: HTMLElement[]): HTMLElement[];
|
|
49
|
+
/**
|
|
50
|
+
* The other radios sharing `node`'s group inside `container`.
|
|
51
|
+
*
|
|
52
|
+
* @param {HTMLElement} node
|
|
53
|
+
* @param {Element | null} container
|
|
54
|
+
* @returns {HTMLElement[]}
|
|
55
|
+
*/
|
|
56
|
+
export function radioGroupPeers(node: HTMLElement, container: Element | null): HTMLElement[];
|
|
41
57
|
/**
|
|
42
58
|
* Arrows wrap; Home/End jump to the ends. `undefined` means "not ours" — the
|
|
43
59
|
* caller must leave the event alone.
|
|
@@ -20,6 +20,7 @@ const ARROW_CONSUMING_INPUTS = new Set([
|
|
|
20
20
|
'week',
|
|
21
21
|
'time',
|
|
22
22
|
'range',
|
|
23
|
+
'radio',
|
|
23
24
|
]);
|
|
24
25
|
|
|
25
26
|
/** @param {string} key */
|
|
@@ -67,7 +68,7 @@ export function acceptsRovingStop(el) {
|
|
|
67
68
|
*/
|
|
68
69
|
export function toolbarStops(container, isHidden) {
|
|
69
70
|
if (!container) return [];
|
|
70
|
-
|
|
71
|
+
const stops = [
|
|
71
72
|
.../** @type {NodeListOf<HTMLElement>} */ (container.querySelectorAll(TOOLBAR_STOP_SELECTOR)),
|
|
72
73
|
].filter((node) =>
|
|
73
74
|
acceptsRovingStop({
|
|
@@ -79,6 +80,51 @@ export function toolbarStops(container, isHidden) {
|
|
|
79
80
|
claimed: node.hasAttribute(TOOLBAR_STOP_ATTR),
|
|
80
81
|
}),
|
|
81
82
|
);
|
|
83
|
+
return oneStopPerRadioGroup(stops);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** @param {HTMLElement} node */
|
|
87
|
+
function radioName(node) {
|
|
88
|
+
const input = /** @type {HTMLInputElement} */ (node);
|
|
89
|
+
return node.tagName === 'INPUT' && input.type === 'radio' && input.name ? input.name : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A named radio group is a single stop, like a native one: its checked radio,
|
|
94
|
+
* else its first. The arrows inside it stay the browser's.
|
|
95
|
+
*
|
|
96
|
+
* @param {HTMLElement[]} stops
|
|
97
|
+
* @returns {HTMLElement[]}
|
|
98
|
+
*/
|
|
99
|
+
export function oneStopPerRadioGroup(stops) {
|
|
100
|
+
/** @type {Map<string, HTMLElement>} */
|
|
101
|
+
const picked = new Map();
|
|
102
|
+
for (const node of stops) {
|
|
103
|
+
const name = radioName(node);
|
|
104
|
+
if (name === null) continue;
|
|
105
|
+
const current = /** @type {HTMLInputElement | undefined} */ (picked.get(name));
|
|
106
|
+
if (!current || (!current.checked && /** @type {HTMLInputElement} */ (node).checked))
|
|
107
|
+
picked.set(name, node);
|
|
108
|
+
}
|
|
109
|
+
return stops.filter((node) => {
|
|
110
|
+
const name = radioName(node);
|
|
111
|
+
return name === null || picked.get(name) === node;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The other radios sharing `node`'s group inside `container`.
|
|
117
|
+
*
|
|
118
|
+
* @param {HTMLElement} node
|
|
119
|
+
* @param {Element | null} container
|
|
120
|
+
* @returns {HTMLElement[]}
|
|
121
|
+
*/
|
|
122
|
+
export function radioGroupPeers(node, container) {
|
|
123
|
+
const name = radioName(node);
|
|
124
|
+
if (name === null || !container) return [];
|
|
125
|
+
return [
|
|
126
|
+
.../** @type {NodeListOf<HTMLElement>} */ (container.querySelectorAll('input[type="radio"]')),
|
|
127
|
+
].filter((peer) => peer !== node && radioName(peer) === name);
|
|
82
128
|
}
|
|
83
129
|
|
|
84
130
|
/**
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
size = 'md',
|
|
61
61
|
shape = 'square',
|
|
62
62
|
surface = 'base',
|
|
63
|
+
font = 'mono',
|
|
63
64
|
hotkey,
|
|
64
65
|
showHotkey = false,
|
|
65
66
|
grow = false,
|
|
@@ -106,6 +107,8 @@
|
|
|
106
107
|
shape?: 'square' | 'pill';
|
|
107
108
|
/** Theme-aware surface shade of the bar (mirrors Card). */
|
|
108
109
|
surface?: 'base' | 'raised' | 'sunken';
|
|
110
|
+
/** Typeface of the typed text and placeholder; `sans` for plain search boxes. */
|
|
111
|
+
font?: 'mono' | 'sans';
|
|
109
112
|
/** Key (e.g. `'/'`) that focuses the input when pressed outside an editable
|
|
110
113
|
* element and without modifiers. */
|
|
111
114
|
hotkey?: string;
|
|
@@ -360,6 +363,7 @@
|
|
|
360
363
|
class:surface-raised={surface === 'raised'}
|
|
361
364
|
class:surface-sunken={surface === 'sunken'}
|
|
362
365
|
class:fi--grow={grow}
|
|
366
|
+
class:fi--sans={font === 'sans'}
|
|
363
367
|
data-tsu="FilterInput"
|
|
364
368
|
>
|
|
365
369
|
<div class="fi__bar">
|
|
@@ -532,6 +536,9 @@
|
|
|
532
536
|
font-size: max(16px, var(--fs-sm));
|
|
533
537
|
}
|
|
534
538
|
}
|
|
539
|
+
.fi--sans .fi__input {
|
|
540
|
+
font-family: var(--font-sans);
|
|
541
|
+
}
|
|
535
542
|
.fi__input::placeholder {
|
|
536
543
|
color: var(--text-faint);
|
|
537
544
|
}
|
|
@@ -54,6 +54,8 @@ type Own = {
|
|
|
54
54
|
shape?: 'square' | 'pill';
|
|
55
55
|
/** Theme-aware surface shade of the bar (mirrors Card). */
|
|
56
56
|
surface?: 'base' | 'raised' | 'sunken';
|
|
57
|
+
/** Typeface of the typed text and placeholder; `sans` for plain search boxes. */
|
|
58
|
+
font?: 'mono' | 'sans';
|
|
57
59
|
/** Key (e.g. `'/'`) that focuses the input when pressed outside an editable
|
|
58
60
|
* element and without modifiers. */
|
|
59
61
|
hotkey?: string;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// Pull-down-to-refresh scroll container. The pull only starts from
|
|
3
|
+
// `scrollTop === 0` with a touch or pen pointer, the distance is damped so
|
|
4
|
+
// the indicator asymptotically approaches twice the threshold, and a release
|
|
5
|
+
// past the threshold awaits `onrefresh` while a Spinner holds the slot.
|
|
6
|
+
// Mouse and keyboard users refresh through the page's own control.
|
|
7
|
+
import type { Snippet } from 'svelte';
|
|
8
|
+
import Icon from '../atoms/Icon.svelte';
|
|
9
|
+
import Spinner from '../atoms/Spinner.svelte';
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
children,
|
|
13
|
+
onrefresh,
|
|
14
|
+
threshold = 72,
|
|
15
|
+
label = 'Refreshing…',
|
|
16
|
+
pullLabel = 'Pull to refresh',
|
|
17
|
+
releaseLabel = 'Release to refresh',
|
|
18
|
+
disabled = false,
|
|
19
|
+
class: klass = '',
|
|
20
|
+
...rest
|
|
21
|
+
}: {
|
|
22
|
+
children?: Snippet;
|
|
23
|
+
/** Awaited after a release past the threshold; the spinner shows until it settles. */
|
|
24
|
+
onrefresh?: () => Promise<void> | void;
|
|
25
|
+
/** Damped pull distance (px) that arms the refresh. */
|
|
26
|
+
threshold?: number;
|
|
27
|
+
/** Announced through the live region while `onrefresh` is pending. */
|
|
28
|
+
label?: string;
|
|
29
|
+
pullLabel?: string;
|
|
30
|
+
releaseLabel?: string;
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
class?: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
} = $props();
|
|
35
|
+
|
|
36
|
+
const SLOP = 6;
|
|
37
|
+
|
|
38
|
+
let scroller: HTMLElement | undefined;
|
|
39
|
+
let offset = $state(0);
|
|
40
|
+
let pending = $state(false);
|
|
41
|
+
let pulling = $state(false);
|
|
42
|
+
let press: { id: number; y: number } | null = null;
|
|
43
|
+
|
|
44
|
+
const ready = $derived(pulling && offset >= threshold);
|
|
45
|
+
const phase = $derived(pending ? 'refreshing' : ready ? 'ready' : pulling ? 'pulling' : 'idle');
|
|
46
|
+
const progress = $derived(Math.min(1, offset / Math.max(1, threshold)));
|
|
47
|
+
|
|
48
|
+
function damp(dy: number) {
|
|
49
|
+
const max = threshold * 2;
|
|
50
|
+
return max * (1 - Math.exp(-dy / max));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function onpointerdown(e: PointerEvent) {
|
|
54
|
+
if (disabled || pending || press) return;
|
|
55
|
+
if (e.pointerType !== 'touch' && e.pointerType !== 'pen') return;
|
|
56
|
+
if (!scroller || scroller.scrollTop > 0) return;
|
|
57
|
+
press = { id: e.pointerId, y: e.clientY };
|
|
58
|
+
addEventListener('pointermove', onpointermove);
|
|
59
|
+
addEventListener('pointerup', onpointerup);
|
|
60
|
+
addEventListener('pointercancel', onpointercancel);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function onpointermove(e: PointerEvent) {
|
|
64
|
+
if (!press || e.pointerId !== press.id) return;
|
|
65
|
+
const dy = e.clientY - press.y;
|
|
66
|
+
if (!pulling) {
|
|
67
|
+
if (dy < -SLOP || (scroller && scroller.scrollTop > 0)) return release();
|
|
68
|
+
if (dy < SLOP) return;
|
|
69
|
+
pulling = true;
|
|
70
|
+
}
|
|
71
|
+
offset = dy > 0 ? damp(dy) : 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function onpointerup(e: PointerEvent) {
|
|
75
|
+
if (!press || e.pointerId !== press.id) return;
|
|
76
|
+
const fire = ready;
|
|
77
|
+
release();
|
|
78
|
+
if (fire) void refresh();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function onpointercancel(e: PointerEvent) {
|
|
82
|
+
if (!press || e.pointerId !== press.id) return;
|
|
83
|
+
release();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function release() {
|
|
87
|
+
press = null;
|
|
88
|
+
pulling = false;
|
|
89
|
+
if (!pending) offset = 0;
|
|
90
|
+
removeEventListener('pointermove', onpointermove);
|
|
91
|
+
removeEventListener('pointerup', onpointerup);
|
|
92
|
+
removeEventListener('pointercancel', onpointercancel);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function refresh() {
|
|
96
|
+
if (pending) return;
|
|
97
|
+
pending = true;
|
|
98
|
+
offset = threshold;
|
|
99
|
+
try {
|
|
100
|
+
await onrefresh?.();
|
|
101
|
+
} finally {
|
|
102
|
+
pending = false;
|
|
103
|
+
offset = 0;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function scrollRoot(el: HTMLElement) {
|
|
108
|
+
scroller = el;
|
|
109
|
+
const block = (e: TouchEvent) => {
|
|
110
|
+
if (pulling) e.preventDefault();
|
|
111
|
+
};
|
|
112
|
+
el.addEventListener('touchmove', block, { passive: false });
|
|
113
|
+
return () => {
|
|
114
|
+
el.removeEventListener('touchmove', block);
|
|
115
|
+
release();
|
|
116
|
+
scroller = undefined;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
</script>
|
|
120
|
+
|
|
121
|
+
<div
|
|
122
|
+
data-tsu="PullToRefresh"
|
|
123
|
+
data-state={phase}
|
|
124
|
+
class="ptr {klass}"
|
|
125
|
+
class:pulling
|
|
126
|
+
class:pending
|
|
127
|
+
style:--ptr-offset="{offset}px"
|
|
128
|
+
style:--ptr-progress={progress}
|
|
129
|
+
aria-busy={pending || undefined}
|
|
130
|
+
{onpointerdown}
|
|
131
|
+
{@attach scrollRoot}
|
|
132
|
+
{...rest}
|
|
133
|
+
>
|
|
134
|
+
<div class="ptr-indicator" aria-hidden="true">
|
|
135
|
+
{#if pending}
|
|
136
|
+
<Spinner label={undefined} />
|
|
137
|
+
{:else}
|
|
138
|
+
<Icon name="arrow-down" class="ptr-arrow" />
|
|
139
|
+
{/if}
|
|
140
|
+
</div>
|
|
141
|
+
<div class="ptr-status sr-only" role="status" aria-live="polite">
|
|
142
|
+
{#if pending}{label}{:else if ready}{releaseLabel}{:else if pulling}{pullLabel}{/if}
|
|
143
|
+
</div>
|
|
144
|
+
<div class="ptr-content">
|
|
145
|
+
{@render children?.()}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<style>
|
|
150
|
+
.ptr {
|
|
151
|
+
position: relative;
|
|
152
|
+
overflow-y: auto;
|
|
153
|
+
overscroll-behavior-y: contain;
|
|
154
|
+
-webkit-overflow-scrolling: touch;
|
|
155
|
+
touch-action: pan-y;
|
|
156
|
+
}
|
|
157
|
+
.ptr-content {
|
|
158
|
+
translate: 0 var(--ptr-offset, 0px);
|
|
159
|
+
transition: translate 200ms var(--ease);
|
|
160
|
+
}
|
|
161
|
+
.pulling .ptr-content {
|
|
162
|
+
transition: none;
|
|
163
|
+
}
|
|
164
|
+
.ptr-indicator {
|
|
165
|
+
position: absolute;
|
|
166
|
+
inset: 0 0 auto;
|
|
167
|
+
display: flex;
|
|
168
|
+
align-items: center;
|
|
169
|
+
justify-content: center;
|
|
170
|
+
height: var(--ptr-offset, 0px);
|
|
171
|
+
overflow: hidden;
|
|
172
|
+
color: var(--text-muted);
|
|
173
|
+
opacity: var(--ptr-progress, 0);
|
|
174
|
+
transition: height 200ms var(--ease);
|
|
175
|
+
pointer-events: none;
|
|
176
|
+
}
|
|
177
|
+
.pulling .ptr-indicator {
|
|
178
|
+
transition: none;
|
|
179
|
+
}
|
|
180
|
+
.ptr-indicator :global(.ptr-arrow) {
|
|
181
|
+
rotate: calc(180deg * var(--ptr-progress, 0));
|
|
182
|
+
transition: rotate 150ms var(--ease);
|
|
183
|
+
}
|
|
184
|
+
.pending .ptr-indicator {
|
|
185
|
+
color: var(--accent);
|
|
186
|
+
}
|
|
187
|
+
.sr-only {
|
|
188
|
+
position: absolute;
|
|
189
|
+
width: 1px;
|
|
190
|
+
height: 1px;
|
|
191
|
+
overflow: hidden;
|
|
192
|
+
clip: rect(0 0 0 0);
|
|
193
|
+
white-space: nowrap;
|
|
194
|
+
}
|
|
195
|
+
@media (prefers-reduced-motion: reduce) {
|
|
196
|
+
.ptr-content,
|
|
197
|
+
.ptr-indicator,
|
|
198
|
+
.ptr-indicator :global(.ptr-arrow) {
|
|
199
|
+
transition: none;
|
|
200
|
+
}
|
|
201
|
+
.ptr-indicator :global(.ptr-arrow) {
|
|
202
|
+
rotate: none;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
children?: Snippet;
|
|
4
|
+
/** Awaited after a release past the threshold; the spinner shows until it settles. */
|
|
5
|
+
onrefresh?: () => Promise<void> | void;
|
|
6
|
+
/** Damped pull distance (px) that arms the refresh. */
|
|
7
|
+
threshold?: number;
|
|
8
|
+
/** Announced through the live region while `onrefresh` is pending. */
|
|
9
|
+
label?: string;
|
|
10
|
+
pullLabel?: string;
|
|
11
|
+
releaseLabel?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
class?: string;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
declare const PullToRefresh: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
17
|
+
type PullToRefresh = ReturnType<typeof PullToRefresh>;
|
|
18
|
+
export default PullToRefresh;
|