spaper 1.3.0 → 2.0.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 +20 -58
- package/dist/components/Alert.svelte +83 -0
- package/dist/components/Alert.svelte.d.ts +15 -0
- package/dist/components/Article.svelte +63 -0
- package/dist/components/Article.svelte.d.ts +17 -0
- package/dist/components/Avatar/Avatar.svelte +102 -0
- package/dist/components/Avatar/Avatar.svelte.d.ts +16 -0
- package/dist/components/Avatar/AvatarGroup.svelte +21 -0
- package/dist/components/Avatar/AvatarGroup.svelte.d.ts +9 -0
- package/dist/components/Avatar/index.d.ts +2 -0
- package/dist/components/Avatar/index.js +2 -0
- package/dist/components/Breadcrumb.svelte +33 -0
- package/dist/components/Breadcrumb.svelte.d.ts +12 -0
- package/dist/components/Button.svelte +174 -0
- package/dist/components/Button.svelte.d.ts +33 -0
- package/dist/components/Card.svelte +101 -0
- package/dist/components/Card.svelte.d.ts +22 -0
- package/dist/components/Carousel/Carousel.svelte +204 -0
- package/dist/components/Carousel/Carousel.svelte.d.ts +27 -0
- package/dist/components/Carousel/CarouselItem.svelte +48 -0
- package/dist/components/Carousel/CarouselItem.svelte.d.ts +7 -0
- package/{components → dist/components}/Carousel/index.d.ts +1 -0
- package/dist/components/Carousel/types.d.ts +22 -0
- package/dist/components/CloseButton.svelte +34 -0
- package/dist/components/CloseButton.svelte.d.ts +8 -0
- package/dist/components/Collapsible.svelte +64 -0
- package/dist/components/Collapsible.svelte.d.ts +16 -0
- package/dist/components/CopyButton.svelte +54 -0
- package/dist/components/CopyButton.svelte.d.ts +13 -0
- package/dist/components/Divider.svelte +9 -0
- package/dist/components/Divider.svelte.d.ts +6 -0
- package/dist/components/Form/Checkbox.svelte +54 -0
- package/dist/components/Form/Checkbox.svelte.d.ts +15 -0
- package/dist/components/Form/Input.svelte +105 -0
- package/dist/components/Form/Input.svelte.d.ts +21 -0
- package/dist/components/Form/NumberInput/DecrementButton.svelte +14 -0
- package/dist/components/Form/NumberInput/DecrementButton.svelte.d.ts +9 -0
- package/dist/components/Form/NumberInput/IncrementButton.svelte +14 -0
- package/dist/components/Form/NumberInput/IncrementButton.svelte.d.ts +9 -0
- package/dist/components/Form/NumberInput/NumberInput.svelte +227 -0
- package/dist/components/Form/NumberInput/NumberInput.svelte.d.ts +34 -0
- package/dist/components/Form/NumberInput/index.d.ts +3 -0
- package/dist/components/Form/NumberInput/index.js +3 -0
- package/dist/components/Form/Radio.svelte +42 -0
- package/dist/components/Form/Radio.svelte.d.ts +13 -0
- package/dist/components/Form/Select.svelte +61 -0
- package/dist/components/Form/Select.svelte.d.ts +12 -0
- package/dist/components/Form/Slider.svelte +129 -0
- package/dist/components/Form/Slider.svelte.d.ts +22 -0
- package/dist/components/Form/Switch.svelte +101 -0
- package/dist/components/Form/Switch.svelte.d.ts +22 -0
- package/dist/components/Form/index.d.ts +9 -0
- package/{components → dist/components}/Form/index.js +2 -2
- package/dist/components/Hero.svelte +78 -0
- package/dist/components/Hero.svelte.d.ts +17 -0
- package/dist/components/Icon.svelte +102 -0
- package/dist/components/Icon.svelte.d.ts +27 -0
- package/dist/components/Modal/Modal.svelte +120 -0
- package/dist/components/Modal/Modal.svelte.d.ts +19 -0
- package/dist/components/Modal/index.d.ts +12 -0
- package/dist/components/Modal/index.js +30 -0
- package/dist/components/Navbar.svelte +98 -0
- package/dist/components/Navbar.svelte.d.ts +11 -0
- package/dist/components/Pagination/Arrow.svelte +29 -0
- package/dist/components/Pagination/Arrow.svelte.d.ts +8 -0
- package/dist/components/Pagination/NavigationButton.svelte +33 -0
- package/dist/components/Pagination/NavigationButton.svelte.d.ts +13 -0
- package/dist/components/Pagination/Pagination.svelte +209 -0
- package/dist/components/Pagination/Pagination.svelte.d.ts +32 -0
- package/{components → dist/components}/Pagination/arrow-dimension.js +3 -3
- package/{components → dist/components}/Pagination/index.d.ts +1 -1
- package/dist/components/Popover.svelte +64 -0
- package/dist/components/Popover.svelte.d.ts +17 -0
- package/dist/components/Progress.svelte +87 -0
- package/dist/components/Progress.svelte.d.ts +19 -0
- package/dist/components/ScrollTop.svelte +79 -0
- package/dist/components/ScrollTop.svelte.d.ts +17 -0
- package/dist/components/Skeleton.svelte +104 -0
- package/dist/components/Skeleton.svelte.d.ts +21 -0
- package/dist/components/Spinner.svelte +59 -0
- package/dist/components/Spinner.svelte.d.ts +16 -0
- package/dist/components/Table/Table.svelte +292 -0
- package/dist/components/Table/Table.svelte.d.ts +25 -0
- package/dist/components/Table/TableColumn.svelte +62 -0
- package/dist/components/Table/TableColumn.svelte.d.ts +15 -0
- package/dist/components/Table/context.d.ts +13 -0
- package/dist/components/Table/context.js +1 -0
- package/dist/components/Table/index.d.ts +2 -0
- package/dist/components/Table/index.js +2 -0
- package/dist/components/Tabs/Tab.svelte +90 -0
- package/dist/components/Tabs/Tab.svelte.d.ts +14 -0
- package/dist/components/Tabs/Tabs.svelte +138 -0
- package/dist/components/Tabs/Tabs.svelte.d.ts +13 -0
- package/{components → dist/components}/Tabs/utils.d.ts +4 -3
- package/dist/components/Tag.svelte +38 -0
- package/dist/components/Tag.svelte.d.ts +10 -0
- package/dist/components/Toast/Toast.svelte +105 -0
- package/dist/components/Toast/Toast.svelte.d.ts +6 -0
- package/dist/components/Toast/ToastContainer.svelte +101 -0
- package/dist/components/Toast/ToastContainer.svelte.d.ts +8 -0
- package/{components → dist/components}/Toast/index.d.ts +19 -5
- package/dist/components/Toast/index.js +100 -0
- package/dist/icons.d.ts +3 -0
- package/dist/icons.js +2 -0
- package/dist/index.d.ts +31 -0
- package/{index.js → dist/index.js} +10 -5
- package/dist/type-icons.d.ts +4 -0
- package/dist/type-icons.js +9 -0
- package/dist/types.d.ts +29 -0
- package/dist/utils.d.ts +16 -0
- package/dist/utils.js +44 -0
- package/package.json +70 -64
- package/components/Alert.svelte +0 -34
- package/components/Alert.svelte.d.ts +0 -21
- package/components/Article.svelte +0 -39
- package/components/Article.svelte.d.ts +0 -26
- package/components/Badge.svelte +0 -16
- package/components/Badge.svelte.d.ts +0 -20
- package/components/Breadcrumb.svelte +0 -17
- package/components/Breadcrumb.svelte.d.ts +0 -21
- package/components/Button.svelte +0 -41
- package/components/Button.svelte.d.ts +0 -30
- package/components/Card.svelte +0 -70
- package/components/Card.svelte.d.ts +0 -32
- package/components/Carousel/Carousel.svelte +0 -128
- package/components/Carousel/Carousel.svelte.d.ts +0 -35
- package/components/Carousel/CarouselItem.svelte +0 -32
- package/components/Carousel/CarouselItem.svelte.d.ts +0 -18
- package/components/Carousel/types.d.ts +0 -14
- package/components/CloseButton.svelte +0 -23
- package/components/CloseButton.svelte.d.ts +0 -19
- package/components/Collapsible.svelte +0 -49
- package/components/Collapsible.svelte.d.ts +0 -21
- package/components/CopyButton.svelte +0 -43
- package/components/CopyButton.svelte.d.ts +0 -24
- package/components/Form/Checkbox.svelte +0 -37
- package/components/Form/Checkbox.svelte.d.ts +0 -26
- package/components/Form/Form.svelte +0 -11
- package/components/Form/Form.svelte.d.ts +0 -26
- package/components/Form/Input.svelte +0 -74
- package/components/Form/Input.svelte.d.ts +0 -28
- package/components/Form/Radio.svelte +0 -26
- package/components/Form/Radio.svelte.d.ts +0 -25
- package/components/Form/Select.svelte +0 -40
- package/components/Form/Select.svelte.d.ts +0 -24
- package/components/Form/Slider.svelte +0 -124
- package/components/Form/Slider.svelte.d.ts +0 -30
- package/components/Form/Switch.svelte +0 -71
- package/components/Form/index.d.ts +0 -9
- package/components/Form/style.scss +0 -4
- package/components/Hero.svelte +0 -78
- package/components/Hero.svelte.d.ts +0 -26
- package/components/Layout/Col.svelte +0 -25
- package/components/Layout/Col.svelte.d.ts +0 -27
- package/components/Layout/Row.svelte +0 -24
- package/components/Layout/Row.svelte.d.ts +0 -26
- package/components/Layout/index.d.ts +0 -2
- package/components/Layout/index.js +0 -2
- package/components/Modal/Modal.svelte +0 -62
- package/components/Modal/Modal.svelte.d.ts +0 -28
- package/components/Modal/index.d.ts +0 -13
- package/components/Modal/index.js +0 -17
- package/components/Navbar.svelte +0 -40
- package/components/Navbar.svelte.d.ts +0 -22
- package/components/Pagination/Arrow.svelte +0 -17
- package/components/Pagination/Arrow.svelte.d.ts +0 -19
- package/components/Pagination/NavigationButton.svelte +0 -23
- package/components/Pagination/NavigationButton.svelte.d.ts +0 -23
- package/components/Pagination/Pagination.svelte +0 -137
- package/components/Pagination/Pagination.svelte.d.ts +0 -36
- package/components/Popover.svelte +0 -57
- package/components/Popover.svelte.d.ts +0 -25
- package/components/Progress.svelte +0 -39
- package/components/Progress.svelte.d.ts +0 -25
- package/components/Skeleton.svelte +0 -55
- package/components/Skeleton.svelte.d.ts +0 -22
- package/components/Table.svelte +0 -73
- package/components/Table.svelte.d.ts +0 -24
- package/components/Tabs/Tab.svelte +0 -52
- package/components/Tabs/Tab.svelte.d.ts +0 -20
- package/components/Tabs/Tabs.svelte +0 -90
- package/components/Tabs/Tabs.svelte.d.ts +0 -25
- package/components/Toast/Toast.svelte +0 -91
- package/components/Toast/Toast.svelte.d.ts +0 -26
- package/components/Toast/ToastContainer.svelte +0 -37
- package/components/Toast/ToastContainer.svelte.d.ts +0 -18
- package/components/Toast/index.js +0 -37
- package/index.d.ts +0 -23
- package/types.d.ts +0 -16
- package/utils.d.ts +0 -16
- package/utils.js +0 -34
- /package/{components → dist/components}/Carousel/index.js +0 -0
- /package/{components → dist/components}/Carousel/types.js +0 -0
- /package/{components → dist/components}/Pagination/arrow-dimension.d.ts +0 -0
- /package/{components → dist/components}/Pagination/index.js +0 -0
- /package/{components → dist/components}/Tabs/index.d.ts +0 -0
- /package/{components → dist/components}/Tabs/index.js +0 -0
- /package/{components → dist/components}/Tabs/utils.js +0 -0
- /package/{components → dist/components}/style.scss +0 -0
- /package/{types.js → dist/types.js} +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fade } from 'svelte/transition'
|
|
3
|
+
import Button from './Button.svelte'
|
|
4
|
+
import Icon from './Icon.svelte'
|
|
5
|
+
import { ArrowUp, type IconNode } from '../icons'
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Element to scroll. The page, when unset. */
|
|
9
|
+
target?: HTMLElement | null
|
|
10
|
+
/** Scroll animation. */
|
|
11
|
+
behavior?: ScrollBehavior
|
|
12
|
+
rounded?: boolean
|
|
13
|
+
/** Icon geometry. `ArrowUp` when unset. */
|
|
14
|
+
icon?: IconNode | null
|
|
15
|
+
ariaLabel?: string
|
|
16
|
+
onclick?: (e: MouseEvent) => void
|
|
17
|
+
children?: import('svelte').Snippet
|
|
18
|
+
[key: string]: any
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
target = null,
|
|
23
|
+
behavior = 'smooth',
|
|
24
|
+
rounded = false,
|
|
25
|
+
icon = ArrowUp,
|
|
26
|
+
ariaLabel = 'To top',
|
|
27
|
+
onclick,
|
|
28
|
+
children,
|
|
29
|
+
class: className = '',
|
|
30
|
+
...rest
|
|
31
|
+
}: Props = $props()
|
|
32
|
+
|
|
33
|
+
let scrolled = $state(false)
|
|
34
|
+
|
|
35
|
+
$effect(() => {
|
|
36
|
+
const node = target ?? window
|
|
37
|
+
|
|
38
|
+
function read() {
|
|
39
|
+
return target ? target.scrollTop : window.scrollY
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function onScroll() {
|
|
43
|
+
scrolled = read() > 0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onScroll()
|
|
47
|
+
node.addEventListener('scroll', onScroll, { passive: true })
|
|
48
|
+
return () => node.removeEventListener('scroll', onScroll)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
function scrollTop(e: MouseEvent) {
|
|
52
|
+
onclick?.(e)
|
|
53
|
+
;(target ?? window).scrollTo({ top: 0, behavior })
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
{#if scrolled}
|
|
58
|
+
<div transition:fade={{ duration: 200 }}>
|
|
59
|
+
<Button
|
|
60
|
+
{...rest}
|
|
61
|
+
class={[
|
|
62
|
+
'fixed right-6 bottom-6 inline-flex items-center justify-center',
|
|
63
|
+
{
|
|
64
|
+
'aspect-square rounded-paper-circle!': rounded,
|
|
65
|
+
},
|
|
66
|
+
className,
|
|
67
|
+
]}
|
|
68
|
+
aria-label={ariaLabel}
|
|
69
|
+
nativeType="button"
|
|
70
|
+
onclick={scrollTop}
|
|
71
|
+
>
|
|
72
|
+
{#if children}
|
|
73
|
+
{@render children()}
|
|
74
|
+
{:else}
|
|
75
|
+
<Icon name={icon} size={22} />
|
|
76
|
+
{/if}
|
|
77
|
+
</Button>
|
|
78
|
+
</div>
|
|
79
|
+
{/if}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type IconNode } from '../icons';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Element to scroll. The page, when unset. */
|
|
4
|
+
target?: HTMLElement | null;
|
|
5
|
+
/** Scroll animation. */
|
|
6
|
+
behavior?: ScrollBehavior;
|
|
7
|
+
rounded?: boolean;
|
|
8
|
+
/** Icon geometry. `ArrowUp` when unset. */
|
|
9
|
+
icon?: IconNode | null;
|
|
10
|
+
ariaLabel?: string;
|
|
11
|
+
onclick?: (e: MouseEvent) => void;
|
|
12
|
+
children?: import('svelte').Snippet;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
declare const ScrollTop: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type ScrollTop = ReturnType<typeof ScrollTop>;
|
|
17
|
+
export default ScrollTop;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
type Shape = 'rectangle' | 'square' | 'circle'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
active?: boolean
|
|
6
|
+
count?: number
|
|
7
|
+
animated?: boolean
|
|
8
|
+
shape?: Shape
|
|
9
|
+
/** Width and height for `square` and `circle`. */
|
|
10
|
+
size?: string | number | null
|
|
11
|
+
/** @deprecated Use `shape="circle"`. */
|
|
12
|
+
circle?: boolean
|
|
13
|
+
inline?: boolean
|
|
14
|
+
/** Class on each skeleton. When `count` is greater than 1, `class` is on the group. */
|
|
15
|
+
itemClass?: string
|
|
16
|
+
ariaLabel?: string
|
|
17
|
+
/** @deprecated Use `ariaLabel`. */
|
|
18
|
+
ariaValueText?: string
|
|
19
|
+
[key: string]: any
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
active = true,
|
|
24
|
+
count = 1,
|
|
25
|
+
animated = true,
|
|
26
|
+
shape = 'rectangle',
|
|
27
|
+
size = null,
|
|
28
|
+
circle = false,
|
|
29
|
+
inline = false,
|
|
30
|
+
itemClass = '',
|
|
31
|
+
ariaLabel,
|
|
32
|
+
ariaValueText,
|
|
33
|
+
class: className = '',
|
|
34
|
+
...rest
|
|
35
|
+
}: Props = $props()
|
|
36
|
+
|
|
37
|
+
const statusLabel = $derived(ariaLabel ?? ariaValueText ?? 'Loading...')
|
|
38
|
+
|
|
39
|
+
const circleClasses = [
|
|
40
|
+
'skeleton-paper-circle-1',
|
|
41
|
+
'skeleton-paper-circle-2',
|
|
42
|
+
'skeleton-paper-circle-3',
|
|
43
|
+
'skeleton-paper-circle-4',
|
|
44
|
+
'skeleton-paper-circle-5',
|
|
45
|
+
'skeleton-paper-circle-6',
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
const resolvedShape = $derived<Shape>(
|
|
49
|
+
shape === 'rectangle' && circle ? 'circle' : shape,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const cssSize = $derived.by(() => {
|
|
53
|
+
if (resolvedShape === 'rectangle' || size == null || size === '') return null
|
|
54
|
+
return typeof size === 'number' ? `${size}px` : size
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const boxStyle = $derived(
|
|
58
|
+
cssSize
|
|
59
|
+
? `--paper-skeleton-width: ${cssSize}; --paper-skeleton-height: ${cssSize};`
|
|
60
|
+
: `--paper-skeleton-width: var(--width, auto); --paper-skeleton-height: var(--height, 100%);`,
|
|
61
|
+
)
|
|
62
|
+
const mergedStyle = $derived(
|
|
63
|
+
typeof rest.style === 'string' && rest.style
|
|
64
|
+
? `${rest.style}; ${boxStyle}`
|
|
65
|
+
: boxStyle,
|
|
66
|
+
)
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
{#if active}
|
|
70
|
+
<div
|
|
71
|
+
class={[
|
|
72
|
+
count > 1 && (inline ? 'inline-flex flex-wrap' : 'flex flex-col'),
|
|
73
|
+
count > 1 && resolvedShape !== 'circle' && 'skeleton-paper-group',
|
|
74
|
+
count > 1 ? className : inline ? 'inline-block h-full' : 'h-full',
|
|
75
|
+
]}
|
|
76
|
+
role="status"
|
|
77
|
+
aria-busy="true"
|
|
78
|
+
aria-label={statusLabel}
|
|
79
|
+
>
|
|
80
|
+
{#each { length: count } as _, i}
|
|
81
|
+
{@const shapeVariant =
|
|
82
|
+
resolvedShape === 'circle'
|
|
83
|
+
? circleClasses[i % 6]
|
|
84
|
+
: count === 1
|
|
85
|
+
? 'skeleton-paper-1'
|
|
86
|
+
: ''}
|
|
87
|
+
<div
|
|
88
|
+
{...rest}
|
|
89
|
+
aria-hidden="true"
|
|
90
|
+
class={[
|
|
91
|
+
'skeleton-paper',
|
|
92
|
+
shapeVariant,
|
|
93
|
+
{
|
|
94
|
+
'skeleton-paper-static': !animated,
|
|
95
|
+
'skeleton-paper-inline': inline,
|
|
96
|
+
},
|
|
97
|
+
itemClass,
|
|
98
|
+
count === 1 && className,
|
|
99
|
+
]}
|
|
100
|
+
style={mergedStyle}
|
|
101
|
+
></div>
|
|
102
|
+
{/each}
|
|
103
|
+
</div>
|
|
104
|
+
{/if}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type Shape = 'rectangle' | 'square' | 'circle';
|
|
2
|
+
interface Props {
|
|
3
|
+
active?: boolean;
|
|
4
|
+
count?: number;
|
|
5
|
+
animated?: boolean;
|
|
6
|
+
shape?: Shape;
|
|
7
|
+
/** Width and height for `square` and `circle`. */
|
|
8
|
+
size?: string | number | null;
|
|
9
|
+
/** @deprecated Use `shape="circle"`. */
|
|
10
|
+
circle?: boolean;
|
|
11
|
+
inline?: boolean;
|
|
12
|
+
/** Class on each skeleton. When `count` is greater than 1, `class` is on the group. */
|
|
13
|
+
itemClass?: string;
|
|
14
|
+
ariaLabel?: string;
|
|
15
|
+
/** @deprecated Use `ariaLabel`. */
|
|
16
|
+
ariaValueText?: string;
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
}
|
|
19
|
+
declare const Skeleton: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type Skeleton = ReturnType<typeof Skeleton>;
|
|
21
|
+
export default Skeleton;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { PaperSize, PaperColor } from '../types'
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
/** When false, the spinner is not rendered. */
|
|
6
|
+
active?: boolean
|
|
7
|
+
/** When false, the spinner is not animated. */
|
|
8
|
+
animated?: boolean
|
|
9
|
+
/** Sits inside a line of text. */
|
|
10
|
+
inline?: boolean
|
|
11
|
+
type?: PaperColor
|
|
12
|
+
size?: PaperSize
|
|
13
|
+
ariaLabel?: string
|
|
14
|
+
[key: string]: any
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
active = true,
|
|
19
|
+
animated = true,
|
|
20
|
+
inline = false,
|
|
21
|
+
type = 'secondary',
|
|
22
|
+
size = 'default',
|
|
23
|
+
ariaLabel = 'Loading...',
|
|
24
|
+
...rest
|
|
25
|
+
}: Props = $props()
|
|
26
|
+
|
|
27
|
+
const sizeClass: Record<PaperSize, string> = {
|
|
28
|
+
small: 'spinner-paper-sm',
|
|
29
|
+
default: '',
|
|
30
|
+
large: 'spinner-paper-lg',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const typeClass: Record<PaperColor, string> = {
|
|
34
|
+
primary: 'spinner-paper-primary',
|
|
35
|
+
secondary: 'spinner-paper-secondary',
|
|
36
|
+
success: 'spinner-paper-success',
|
|
37
|
+
warning: 'spinner-paper-warning',
|
|
38
|
+
danger: 'spinner-paper-danger',
|
|
39
|
+
muted: 'spinner-paper-muted',
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
{#if active}
|
|
44
|
+
<div
|
|
45
|
+
{...rest}
|
|
46
|
+
role="status"
|
|
47
|
+
aria-label={ariaLabel}
|
|
48
|
+
class={[
|
|
49
|
+
'spinner-paper',
|
|
50
|
+
typeClass[type],
|
|
51
|
+
sizeClass[size],
|
|
52
|
+
{
|
|
53
|
+
'spinner-paper-static': !animated,
|
|
54
|
+
'spinner-paper-inline': inline,
|
|
55
|
+
},
|
|
56
|
+
rest.class,
|
|
57
|
+
]}
|
|
58
|
+
></div>
|
|
59
|
+
{/if}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PaperSize, PaperColor } from '../types';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** When false, the spinner is not rendered. */
|
|
4
|
+
active?: boolean;
|
|
5
|
+
/** When false, the spinner is not animated. */
|
|
6
|
+
animated?: boolean;
|
|
7
|
+
/** Sits inside a line of text. */
|
|
8
|
+
inline?: boolean;
|
|
9
|
+
type?: PaperColor;
|
|
10
|
+
size?: PaperSize;
|
|
11
|
+
ariaLabel?: string;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
declare const Spinner: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type Spinner = ReturnType<typeof Spinner>;
|
|
16
|
+
export default Spinner;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { setContext } from 'svelte'
|
|
3
|
+
import { writable } from 'svelte/store'
|
|
4
|
+
import type {
|
|
5
|
+
TableColumnObj as ColumnObj,
|
|
6
|
+
TableColumn as Column,
|
|
7
|
+
TableSort,
|
|
8
|
+
} from '../../types'
|
|
9
|
+
import Icon from '../Icon.svelte'
|
|
10
|
+
import {
|
|
11
|
+
ArrowDownWideNarrow,
|
|
12
|
+
ArrowUpDown,
|
|
13
|
+
ArrowUpNarrowWide,
|
|
14
|
+
} from '../../icons'
|
|
15
|
+
import {
|
|
16
|
+
TABLE_CONTEXT,
|
|
17
|
+
type RegisteredColumn,
|
|
18
|
+
type TableContext,
|
|
19
|
+
} from './context'
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
columns?: Column[]
|
|
23
|
+
data?: Record<string, any>[]
|
|
24
|
+
striped?: boolean
|
|
25
|
+
hoverable?: boolean
|
|
26
|
+
showHeader?: boolean
|
|
27
|
+
/** Header clicks cycle the sort. */
|
|
28
|
+
sortable?: boolean
|
|
29
|
+
/**
|
|
30
|
+
* Active sort. `1` is ascending, `-1` is descending.
|
|
31
|
+
* Click a header to cycle ascending, descending, then unsorted.
|
|
32
|
+
*/
|
|
33
|
+
sort?: TableSort | null
|
|
34
|
+
headerCLass?: string
|
|
35
|
+
cellCLass?: string
|
|
36
|
+
children?: import('svelte').Snippet<[any]>
|
|
37
|
+
header?: import('svelte').Snippet<[any]>
|
|
38
|
+
cell?: import('svelte').Snippet<[any]>
|
|
39
|
+
empty?: import('svelte').Snippet
|
|
40
|
+
[key: string]: any
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let {
|
|
44
|
+
columns = [],
|
|
45
|
+
data = [],
|
|
46
|
+
striped = false,
|
|
47
|
+
hoverable = false,
|
|
48
|
+
showHeader = true,
|
|
49
|
+
sortable = false,
|
|
50
|
+
sort = $bindable(null),
|
|
51
|
+
headerCLass = '',
|
|
52
|
+
cellCLass = '',
|
|
53
|
+
children,
|
|
54
|
+
header,
|
|
55
|
+
cell,
|
|
56
|
+
empty,
|
|
57
|
+
...rest
|
|
58
|
+
}: Props = $props()
|
|
59
|
+
|
|
60
|
+
const registeredColumns = writable<RegisteredColumn[]>([])
|
|
61
|
+
const counts = new Map<string, number>()
|
|
62
|
+
const keyToField = new Map<object, string>()
|
|
63
|
+
|
|
64
|
+
const tableContext: TableContext = {
|
|
65
|
+
columns: registeredColumns,
|
|
66
|
+
registerColumn(col) {
|
|
67
|
+
keyToField.set(col.key, col.field)
|
|
68
|
+
const n = (counts.get(col.field) ?? 0) + 1
|
|
69
|
+
counts.set(col.field, n)
|
|
70
|
+
if (n === 1) {
|
|
71
|
+
registeredColumns.update((list) => [...list, col])
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
unregisterColumn(key) {
|
|
75
|
+
const field = keyToField.get(key)
|
|
76
|
+
if (!field) return
|
|
77
|
+
keyToField.delete(key)
|
|
78
|
+
|
|
79
|
+
const n = (counts.get(field) ?? 1) - 1
|
|
80
|
+
if (n <= 0) {
|
|
81
|
+
counts.delete(field)
|
|
82
|
+
registeredColumns.update((list) => list.filter((c) => c.field !== field))
|
|
83
|
+
} else {
|
|
84
|
+
counts.set(field, n)
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setContext(TABLE_CONTEXT, tableContext)
|
|
90
|
+
|
|
91
|
+
function isColumnObj(arg: Column): arg is ColumnObj {
|
|
92
|
+
return typeof arg === 'object' && arg !== null && 'field' in arg
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function columnField(column: Column | RegisteredColumn): string {
|
|
96
|
+
return typeof column === 'string' ? column : column.field
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function columnLabel(column: Column | RegisteredColumn): string {
|
|
100
|
+
if (typeof column === 'string') return column
|
|
101
|
+
return column.label ?? column.field
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function columnSortable(column: Column | RegisteredColumn): boolean {
|
|
105
|
+
if (typeof column === 'string') return sortable
|
|
106
|
+
return column.sortable ?? sortable
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const deprecatedColumnFields = new Set<string>()
|
|
110
|
+
|
|
111
|
+
$effect(() => {
|
|
112
|
+
for (const column of columns) {
|
|
113
|
+
if (!isColumnObj(column) || (!column.content && !column.html)) continue
|
|
114
|
+
if (deprecatedColumnFields.has(column.field)) continue
|
|
115
|
+
deprecatedColumnFields.add(column.field)
|
|
116
|
+
console.warn(
|
|
117
|
+
`[spaper] Table column "${column.field}": \`content\` and \`html\` are deprecated. Use <TableColumn> instead.`,
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
let useColumnChildren = $derived(!!children)
|
|
123
|
+
let propColumns = $derived(
|
|
124
|
+
columns.length ? columns : data[0] ? Object.keys(data[0]) : [],
|
|
125
|
+
)
|
|
126
|
+
let headerColumns = $derived(
|
|
127
|
+
useColumnChildren ? $registeredColumns : propColumns,
|
|
128
|
+
)
|
|
129
|
+
let columnCount = $derived(
|
|
130
|
+
Math.max(headerColumns.length, propColumns.length, 1),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
function compareValues(a: unknown, b: unknown): number {
|
|
134
|
+
if (typeof a === 'number' && typeof b === 'number') return a - b
|
|
135
|
+
return String(a ?? '').localeCompare(String(b ?? ''), undefined, {
|
|
136
|
+
numeric: true,
|
|
137
|
+
sensitivity: 'base',
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let sortedData = $derived.by(() => {
|
|
142
|
+
if (!sort) return data
|
|
143
|
+
const { field, order } = sort
|
|
144
|
+
return data.toSorted((a, b) => compareValues(a[field], b[field]) * order)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
/** Columns opened by a presort stay clickable after the sort is cleared. */
|
|
148
|
+
let stickySortable = $state<string[]>([])
|
|
149
|
+
|
|
150
|
+
$effect(() => {
|
|
151
|
+
const field = sort?.field
|
|
152
|
+
if (!field || stickySortable.includes(field)) return
|
|
153
|
+
stickySortable.push(field)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
function toggleSort(field: string) {
|
|
157
|
+
if (sort?.field !== field) {
|
|
158
|
+
sort = { field, order: 1 }
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
if (sort.order === 1) {
|
|
162
|
+
sort = { field, order: -1 }
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
sort = null
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function ariaSort(field: string): 'ascending' | 'descending' | 'none' {
|
|
169
|
+
if (sort?.field !== field) return 'none'
|
|
170
|
+
return sort.order === 1 ? 'ascending' : 'descending'
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function sortIcon(field: string) {
|
|
174
|
+
if (sort?.field !== field) return ArrowUpDown
|
|
175
|
+
return sort.order === 1 ? ArrowUpNarrowWide : ArrowDownWideNarrow
|
|
176
|
+
}
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
<table
|
|
180
|
+
{...rest}
|
|
181
|
+
class={[
|
|
182
|
+
'table-paper',
|
|
183
|
+
{
|
|
184
|
+
'table-paper-alternating': striped,
|
|
185
|
+
'table-paper-hover': hoverable,
|
|
186
|
+
},
|
|
187
|
+
rest.class,
|
|
188
|
+
]}
|
|
189
|
+
>
|
|
190
|
+
{#if showHeader}
|
|
191
|
+
<thead>
|
|
192
|
+
<tr>
|
|
193
|
+
{#each headerColumns as column, columnIndex}
|
|
194
|
+
{@const field = columnField(column)}
|
|
195
|
+
{@const canSort =
|
|
196
|
+
columnSortable(column) ||
|
|
197
|
+
stickySortable.includes(field) ||
|
|
198
|
+
sort?.field === field}
|
|
199
|
+
<th
|
|
200
|
+
class={[
|
|
201
|
+
{
|
|
202
|
+
'hover:bg-(--paper-table-header-hover,var(--paper-table-hover-bg))':
|
|
203
|
+
canSort,
|
|
204
|
+
},
|
|
205
|
+
headerCLass,
|
|
206
|
+
]}
|
|
207
|
+
aria-sort={canSort ? ariaSort(field) : undefined}
|
|
208
|
+
>
|
|
209
|
+
{#if canSort}
|
|
210
|
+
<button
|
|
211
|
+
type="button"
|
|
212
|
+
class="inline-flex w-full cursor-pointer items-center gap-1 bg-transparent p-0 text-left font-[inherit] text-inherit"
|
|
213
|
+
onclick={() => toggleSort(field)}
|
|
214
|
+
>
|
|
215
|
+
{#if header}
|
|
216
|
+
{@render header?.({ column, columnIndex })}
|
|
217
|
+
{:else}
|
|
218
|
+
{columnLabel(column)}
|
|
219
|
+
{/if}
|
|
220
|
+
<Icon
|
|
221
|
+
name={sortIcon(field)}
|
|
222
|
+
size={16}
|
|
223
|
+
aria-hidden="true"
|
|
224
|
+
type={sort?.field === field ? null : 'muted'}
|
|
225
|
+
/>
|
|
226
|
+
</button>
|
|
227
|
+
{:else if header}
|
|
228
|
+
{@render header?.({ column, columnIndex })}
|
|
229
|
+
{:else}
|
|
230
|
+
{columnLabel(column)}
|
|
231
|
+
{/if}
|
|
232
|
+
</th>
|
|
233
|
+
{/each}
|
|
234
|
+
</tr>
|
|
235
|
+
</thead>
|
|
236
|
+
{/if}
|
|
237
|
+
<tbody>
|
|
238
|
+
{#if useColumnChildren}
|
|
239
|
+
{#if !data.length}
|
|
240
|
+
<tr class="hidden" aria-hidden="true">
|
|
241
|
+
{@render children?.({ row: {}, rowIndex: -1 })}
|
|
242
|
+
</tr>
|
|
243
|
+
{/if}
|
|
244
|
+
{#each sortedData as row, rowIndex}
|
|
245
|
+
<tr>
|
|
246
|
+
{@render children?.({ row, rowIndex })}
|
|
247
|
+
</tr>
|
|
248
|
+
{/each}
|
|
249
|
+
{:else}
|
|
250
|
+
{#each sortedData as row, rowIndex}
|
|
251
|
+
<tr>
|
|
252
|
+
{#each propColumns as column, columnIndex}
|
|
253
|
+
{@const field = columnField(column)}
|
|
254
|
+
{@const value = row[field]}
|
|
255
|
+
<td class={cellCLass}>
|
|
256
|
+
{#if cell}
|
|
257
|
+
{@render cell?.({
|
|
258
|
+
row,
|
|
259
|
+
column,
|
|
260
|
+
value,
|
|
261
|
+
field,
|
|
262
|
+
rowIndex,
|
|
263
|
+
columnIndex,
|
|
264
|
+
})}
|
|
265
|
+
{:else}
|
|
266
|
+
{@const colObj = isColumnObj(column) ? column : null}
|
|
267
|
+
{#if colObj?.content}
|
|
268
|
+
{#if colObj.html}
|
|
269
|
+
{@html colObj.content(value)}
|
|
270
|
+
{:else}
|
|
271
|
+
{colObj.content(value)}
|
|
272
|
+
{/if}
|
|
273
|
+
{:else if colObj?.html}
|
|
274
|
+
{@html value}
|
|
275
|
+
{:else}
|
|
276
|
+
{value}
|
|
277
|
+
{/if}
|
|
278
|
+
{/if}
|
|
279
|
+
</td>
|
|
280
|
+
{/each}
|
|
281
|
+
</tr>
|
|
282
|
+
{/each}
|
|
283
|
+
{/if}
|
|
284
|
+
{#if !data.length && empty}
|
|
285
|
+
<tr>
|
|
286
|
+
<td colspan={columnCount}>
|
|
287
|
+
{@render empty()}
|
|
288
|
+
</td>
|
|
289
|
+
</tr>
|
|
290
|
+
{/if}
|
|
291
|
+
</tbody>
|
|
292
|
+
</table>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TableColumn as Column, TableSort } from '../../types';
|
|
2
|
+
interface Props {
|
|
3
|
+
columns?: Column[];
|
|
4
|
+
data?: Record<string, any>[];
|
|
5
|
+
striped?: boolean;
|
|
6
|
+
hoverable?: boolean;
|
|
7
|
+
showHeader?: boolean;
|
|
8
|
+
/** Header clicks cycle the sort. */
|
|
9
|
+
sortable?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Active sort. `1` is ascending, `-1` is descending.
|
|
12
|
+
* Click a header to cycle ascending, descending, then unsorted.
|
|
13
|
+
*/
|
|
14
|
+
sort?: TableSort | null;
|
|
15
|
+
headerCLass?: string;
|
|
16
|
+
cellCLass?: string;
|
|
17
|
+
children?: import('svelte').Snippet<[any]>;
|
|
18
|
+
header?: import('svelte').Snippet<[any]>;
|
|
19
|
+
cell?: import('svelte').Snippet<[any]>;
|
|
20
|
+
empty?: import('svelte').Snippet;
|
|
21
|
+
[key: string]: any;
|
|
22
|
+
}
|
|
23
|
+
declare const Table: import("svelte").Component<Props, {}, "sort">;
|
|
24
|
+
type Table = ReturnType<typeof Table>;
|
|
25
|
+
export default Table;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
|
+
import { TABLE_CONTEXT, type TableContext } from './context'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
field: string
|
|
7
|
+
label?: string
|
|
8
|
+
row?: Record<string, any>
|
|
9
|
+
rowIndex?: number
|
|
10
|
+
/** Overrides the table `sortable` prop for this column. */
|
|
11
|
+
sortable?: boolean
|
|
12
|
+
html?: boolean
|
|
13
|
+
content?: ((val?: any) => string) | null
|
|
14
|
+
cellClass?: string
|
|
15
|
+
children?: import('svelte').Snippet<[any]>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
field,
|
|
20
|
+
label = undefined,
|
|
21
|
+
row = undefined,
|
|
22
|
+
rowIndex = undefined,
|
|
23
|
+
sortable = undefined,
|
|
24
|
+
html = false,
|
|
25
|
+
content = null,
|
|
26
|
+
cellClass = '',
|
|
27
|
+
children,
|
|
28
|
+
}: Props = $props()
|
|
29
|
+
|
|
30
|
+
const table = getContext<TableContext>(TABLE_CONTEXT)
|
|
31
|
+
|
|
32
|
+
if (!table) throw new Error('<TableColumn> must be used inside <Table>')
|
|
33
|
+
|
|
34
|
+
const key = {}
|
|
35
|
+
|
|
36
|
+
let value = $derived(row?.[field])
|
|
37
|
+
let headerLabel = $derived(label ?? field)
|
|
38
|
+
|
|
39
|
+
onMount(() => {
|
|
40
|
+
table.registerColumn({ field, label: headerLabel, sortable, key })
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
onDestroy(() => {
|
|
44
|
+
table.unregisterColumn(key)
|
|
45
|
+
})
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<td class={cellClass}>
|
|
49
|
+
{#if children}
|
|
50
|
+
{@render children?.({ row, value, rowIndex, field })}
|
|
51
|
+
{:else if content}
|
|
52
|
+
{#if html}
|
|
53
|
+
{@html content(value)}
|
|
54
|
+
{:else}
|
|
55
|
+
{content(value)}
|
|
56
|
+
{/if}
|
|
57
|
+
{:else if html}
|
|
58
|
+
{@html value}
|
|
59
|
+
{:else}
|
|
60
|
+
{value}
|
|
61
|
+
{/if}
|
|
62
|
+
</td>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
field: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
row?: Record<string, any>;
|
|
5
|
+
rowIndex?: number;
|
|
6
|
+
/** Overrides the table `sortable` prop for this column. */
|
|
7
|
+
sortable?: boolean;
|
|
8
|
+
html?: boolean;
|
|
9
|
+
content?: ((val?: any) => string) | null;
|
|
10
|
+
cellClass?: string;
|
|
11
|
+
children?: import('svelte').Snippet<[any]>;
|
|
12
|
+
}
|
|
13
|
+
declare const TableColumn: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type TableColumn = ReturnType<typeof TableColumn>;
|
|
15
|
+
export default TableColumn;
|