spaper 1.4.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.
Files changed (206) hide show
  1. package/README.md +20 -59
  2. package/dist/components/Alert.svelte +83 -0
  3. package/dist/components/Alert.svelte.d.ts +15 -0
  4. package/dist/components/Article.svelte +63 -0
  5. package/dist/components/Article.svelte.d.ts +17 -0
  6. package/dist/components/Avatar/Avatar.svelte +102 -0
  7. package/dist/components/Avatar/Avatar.svelte.d.ts +16 -0
  8. package/dist/components/Avatar/AvatarGroup.svelte +21 -0
  9. package/dist/components/Avatar/AvatarGroup.svelte.d.ts +9 -0
  10. package/dist/components/Avatar/index.d.ts +2 -0
  11. package/dist/components/Avatar/index.js +2 -0
  12. package/dist/components/Breadcrumb.svelte +33 -0
  13. package/dist/components/Breadcrumb.svelte.d.ts +12 -0
  14. package/dist/components/Button.svelte +174 -0
  15. package/dist/components/Button.svelte.d.ts +33 -0
  16. package/dist/components/Card.svelte +101 -0
  17. package/dist/components/Card.svelte.d.ts +22 -0
  18. package/dist/components/Carousel/Carousel.svelte +204 -0
  19. package/dist/components/Carousel/Carousel.svelte.d.ts +27 -0
  20. package/dist/components/Carousel/CarouselItem.svelte +48 -0
  21. package/dist/components/Carousel/CarouselItem.svelte.d.ts +7 -0
  22. package/{components → dist/components}/Carousel/index.d.ts +1 -0
  23. package/dist/components/Carousel/types.d.ts +22 -0
  24. package/dist/components/CloseButton.svelte +34 -0
  25. package/dist/components/CloseButton.svelte.d.ts +8 -0
  26. package/dist/components/Collapsible.svelte +64 -0
  27. package/dist/components/Collapsible.svelte.d.ts +16 -0
  28. package/dist/components/CopyButton.svelte +54 -0
  29. package/dist/components/CopyButton.svelte.d.ts +13 -0
  30. package/dist/components/Divider.svelte +9 -0
  31. package/dist/components/Divider.svelte.d.ts +6 -0
  32. package/dist/components/Form/Checkbox.svelte +54 -0
  33. package/dist/components/Form/Checkbox.svelte.d.ts +15 -0
  34. package/dist/components/Form/Input.svelte +105 -0
  35. package/dist/components/Form/Input.svelte.d.ts +21 -0
  36. package/dist/components/Form/NumberInput/DecrementButton.svelte +14 -0
  37. package/dist/components/Form/NumberInput/DecrementButton.svelte.d.ts +9 -0
  38. package/dist/components/Form/NumberInput/IncrementButton.svelte +14 -0
  39. package/dist/components/Form/NumberInput/IncrementButton.svelte.d.ts +9 -0
  40. package/dist/components/Form/NumberInput/NumberInput.svelte +227 -0
  41. package/dist/components/Form/NumberInput/NumberInput.svelte.d.ts +34 -0
  42. package/dist/components/Form/Radio.svelte +42 -0
  43. package/dist/components/Form/Radio.svelte.d.ts +13 -0
  44. package/dist/components/Form/Select.svelte +61 -0
  45. package/dist/components/Form/Select.svelte.d.ts +12 -0
  46. package/dist/components/Form/Slider.svelte +129 -0
  47. package/dist/components/Form/Slider.svelte.d.ts +22 -0
  48. package/dist/components/Form/Switch.svelte +101 -0
  49. package/dist/components/Form/Switch.svelte.d.ts +22 -0
  50. package/dist/components/Form/index.d.ts +9 -0
  51. package/{components → dist/components}/Form/index.js +1 -2
  52. package/dist/components/Hero.svelte +78 -0
  53. package/dist/components/Hero.svelte.d.ts +17 -0
  54. package/dist/components/Icon.svelte +102 -0
  55. package/dist/components/Icon.svelte.d.ts +27 -0
  56. package/dist/components/Modal/Modal.svelte +120 -0
  57. package/dist/components/Modal/Modal.svelte.d.ts +19 -0
  58. package/dist/components/Modal/index.d.ts +12 -0
  59. package/dist/components/Modal/index.js +30 -0
  60. package/dist/components/Navbar.svelte +98 -0
  61. package/dist/components/Navbar.svelte.d.ts +11 -0
  62. package/dist/components/Pagination/Arrow.svelte +29 -0
  63. package/dist/components/Pagination/Arrow.svelte.d.ts +8 -0
  64. package/dist/components/Pagination/NavigationButton.svelte +33 -0
  65. package/dist/components/Pagination/NavigationButton.svelte.d.ts +13 -0
  66. package/dist/components/Pagination/Pagination.svelte +209 -0
  67. package/dist/components/Pagination/Pagination.svelte.d.ts +32 -0
  68. package/{components → dist/components}/Pagination/arrow-dimension.js +3 -3
  69. package/{components → dist/components}/Pagination/index.d.ts +1 -1
  70. package/dist/components/Popover.svelte +64 -0
  71. package/dist/components/Popover.svelte.d.ts +17 -0
  72. package/dist/components/Progress.svelte +87 -0
  73. package/dist/components/Progress.svelte.d.ts +19 -0
  74. package/dist/components/ScrollTop.svelte +79 -0
  75. package/dist/components/ScrollTop.svelte.d.ts +17 -0
  76. package/dist/components/Skeleton.svelte +104 -0
  77. package/dist/components/Skeleton.svelte.d.ts +21 -0
  78. package/dist/components/Spinner.svelte +59 -0
  79. package/dist/components/Spinner.svelte.d.ts +16 -0
  80. package/dist/components/Table/Table.svelte +292 -0
  81. package/dist/components/Table/Table.svelte.d.ts +25 -0
  82. package/dist/components/Table/TableColumn.svelte +62 -0
  83. package/dist/components/Table/TableColumn.svelte.d.ts +15 -0
  84. package/dist/components/Table/context.d.ts +13 -0
  85. package/dist/components/Table/context.js +1 -0
  86. package/dist/components/Table/index.d.ts +2 -0
  87. package/dist/components/Table/index.js +2 -0
  88. package/dist/components/Tabs/Tab.svelte +90 -0
  89. package/dist/components/Tabs/Tab.svelte.d.ts +14 -0
  90. package/dist/components/Tabs/Tabs.svelte +138 -0
  91. package/dist/components/Tabs/Tabs.svelte.d.ts +13 -0
  92. package/{components → dist/components}/Tabs/utils.d.ts +4 -3
  93. package/dist/components/Tag.svelte +38 -0
  94. package/dist/components/Tag.svelte.d.ts +10 -0
  95. package/dist/components/Toast/Toast.svelte +105 -0
  96. package/dist/components/Toast/Toast.svelte.d.ts +6 -0
  97. package/dist/components/Toast/ToastContainer.svelte +101 -0
  98. package/dist/components/Toast/ToastContainer.svelte.d.ts +8 -0
  99. package/{components → dist/components}/Toast/index.d.ts +19 -5
  100. package/dist/components/Toast/index.js +100 -0
  101. package/dist/icons.d.ts +3 -0
  102. package/dist/icons.js +2 -0
  103. package/dist/index.d.ts +31 -0
  104. package/{index.js → dist/index.js} +10 -5
  105. package/dist/type-icons.d.ts +4 -0
  106. package/dist/type-icons.js +9 -0
  107. package/dist/types.d.ts +29 -0
  108. package/dist/utils.d.ts +16 -0
  109. package/dist/utils.js +44 -0
  110. package/package.json +70 -68
  111. package/components/Alert.svelte +0 -34
  112. package/components/Alert.svelte.d.ts +0 -21
  113. package/components/Article.svelte +0 -39
  114. package/components/Article.svelte.d.ts +0 -26
  115. package/components/Badge.svelte +0 -16
  116. package/components/Badge.svelte.d.ts +0 -20
  117. package/components/Breadcrumb.svelte +0 -17
  118. package/components/Breadcrumb.svelte.d.ts +0 -21
  119. package/components/Button.svelte +0 -41
  120. package/components/Button.svelte.d.ts +0 -30
  121. package/components/Card.svelte +0 -70
  122. package/components/Card.svelte.d.ts +0 -32
  123. package/components/Carousel/Carousel.svelte +0 -128
  124. package/components/Carousel/Carousel.svelte.d.ts +0 -35
  125. package/components/Carousel/CarouselItem.svelte +0 -32
  126. package/components/Carousel/CarouselItem.svelte.d.ts +0 -18
  127. package/components/Carousel/types.d.ts +0 -14
  128. package/components/CloseButton.svelte +0 -23
  129. package/components/CloseButton.svelte.d.ts +0 -19
  130. package/components/Collapsible.svelte +0 -49
  131. package/components/Collapsible.svelte.d.ts +0 -21
  132. package/components/CopyButton.svelte +0 -43
  133. package/components/CopyButton.svelte.d.ts +0 -24
  134. package/components/Form/Checkbox.svelte +0 -37
  135. package/components/Form/Checkbox.svelte.d.ts +0 -26
  136. package/components/Form/Form.svelte +0 -22
  137. package/components/Form/Form.svelte.d.ts +0 -26
  138. package/components/Form/Input.svelte +0 -67
  139. package/components/Form/Input.svelte.d.ts +0 -28
  140. package/components/Form/NumberInput/DecrementButton.svelte +0 -9
  141. package/components/Form/NumberInput/DecrementButton.svelte.d.ts +0 -19
  142. package/components/Form/NumberInput/IncrementButton.svelte +0 -9
  143. package/components/Form/NumberInput/IncrementButton.svelte.d.ts +0 -19
  144. package/components/Form/NumberInput/NumberInput.svelte +0 -129
  145. package/components/Form/NumberInput/NumberInput.svelte.d.ts +0 -36
  146. package/components/Form/Radio.svelte +0 -26
  147. package/components/Form/Radio.svelte.d.ts +0 -25
  148. package/components/Form/Select.svelte +0 -40
  149. package/components/Form/Select.svelte.d.ts +0 -24
  150. package/components/Form/Slider.svelte +0 -124
  151. package/components/Form/Slider.svelte.d.ts +0 -30
  152. package/components/Form/Switch.svelte +0 -71
  153. package/components/Form/index.d.ts +0 -10
  154. package/components/Form/style.scss +0 -4
  155. package/components/Hero.svelte +0 -78
  156. package/components/Hero.svelte.d.ts +0 -26
  157. package/components/Layout/Col.svelte +0 -25
  158. package/components/Layout/Col.svelte.d.ts +0 -27
  159. package/components/Layout/Row.svelte +0 -24
  160. package/components/Layout/Row.svelte.d.ts +0 -26
  161. package/components/Layout/index.d.ts +0 -2
  162. package/components/Layout/index.js +0 -2
  163. package/components/Modal/Modal.svelte +0 -62
  164. package/components/Modal/Modal.svelte.d.ts +0 -28
  165. package/components/Modal/index.d.ts +0 -13
  166. package/components/Modal/index.js +0 -17
  167. package/components/Navbar.svelte +0 -40
  168. package/components/Navbar.svelte.d.ts +0 -22
  169. package/components/Pagination/Arrow.svelte +0 -17
  170. package/components/Pagination/Arrow.svelte.d.ts +0 -19
  171. package/components/Pagination/NavigationButton.svelte +0 -23
  172. package/components/Pagination/NavigationButton.svelte.d.ts +0 -23
  173. package/components/Pagination/Pagination.svelte +0 -137
  174. package/components/Pagination/Pagination.svelte.d.ts +0 -36
  175. package/components/Popover.svelte +0 -62
  176. package/components/Popover.svelte.d.ts +0 -25
  177. package/components/Progress.svelte +0 -39
  178. package/components/Progress.svelte.d.ts +0 -25
  179. package/components/Skeleton.svelte +0 -55
  180. package/components/Skeleton.svelte.d.ts +0 -22
  181. package/components/Table.svelte +0 -73
  182. package/components/Table.svelte.d.ts +0 -24
  183. package/components/Tabs/Tab.svelte +0 -52
  184. package/components/Tabs/Tab.svelte.d.ts +0 -20
  185. package/components/Tabs/Tabs.svelte +0 -90
  186. package/components/Tabs/Tabs.svelte.d.ts +0 -25
  187. package/components/Toast/Toast.svelte +0 -91
  188. package/components/Toast/Toast.svelte.d.ts +0 -26
  189. package/components/Toast/ToastContainer.svelte +0 -37
  190. package/components/Toast/ToastContainer.svelte.d.ts +0 -18
  191. package/components/Toast/index.js +0 -37
  192. package/index.d.ts +0 -23
  193. package/types.d.ts +0 -16
  194. package/utils.d.ts +0 -16
  195. package/utils.js +0 -34
  196. /package/{components → dist/components}/Carousel/index.js +0 -0
  197. /package/{components → dist/components}/Carousel/types.js +0 -0
  198. /package/{components → dist/components}/Form/NumberInput/index.d.ts +0 -0
  199. /package/{components → dist/components}/Form/NumberInput/index.js +0 -0
  200. /package/{components → dist/components}/Pagination/arrow-dimension.d.ts +0 -0
  201. /package/{components → dist/components}/Pagination/index.js +0 -0
  202. /package/{components → dist/components}/Tabs/index.d.ts +0 -0
  203. /package/{components → dist/components}/Tabs/index.js +0 -0
  204. /package/{components → dist/components}/Tabs/utils.js +0 -0
  205. /package/{components → dist/components}/style.scss +0 -0
  206. /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;