@delightstack/components 0.1.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 (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +136 -0
  3. package/SKILL.md +149 -0
  4. package/bin/agents.js +63 -0
  5. package/dist/actions/Alert.svelte +202 -0
  6. package/dist/actions/Alert.svelte.d.ts +36 -0
  7. package/dist/actions/Alert.svelte.d.ts.map +1 -0
  8. package/dist/actions/Button.svelte +1450 -0
  9. package/dist/actions/Button.svelte.d.ts +56 -0
  10. package/dist/actions/Button.svelte.d.ts.map +1 -0
  11. package/dist/actions/ButtonGroup.svelte +111 -0
  12. package/dist/actions/ButtonGroup.svelte.d.ts +41 -0
  13. package/dist/actions/ButtonGroup.svelte.d.ts.map +1 -0
  14. package/dist/actions/CommandPalette.svelte +939 -0
  15. package/dist/actions/CommandPalette.svelte.d.ts +37 -0
  16. package/dist/actions/CommandPalette.svelte.d.ts.map +1 -0
  17. package/dist/actions/ContextMenu.svelte +138 -0
  18. package/dist/actions/ContextMenu.svelte.d.ts +54 -0
  19. package/dist/actions/ContextMenu.svelte.d.ts.map +1 -0
  20. package/dist/actions/Modal.svelte +474 -0
  21. package/dist/actions/Modal.svelte.d.ts +28 -0
  22. package/dist/actions/Modal.svelte.d.ts.map +1 -0
  23. package/dist/actions/Popover.svelte +1214 -0
  24. package/dist/actions/Popover.svelte.d.ts +31 -0
  25. package/dist/actions/Popover.svelte.d.ts.map +1 -0
  26. package/dist/actions/Portal.svelte +80 -0
  27. package/dist/actions/Portal.svelte.d.ts +17 -0
  28. package/dist/actions/Portal.svelte.d.ts.map +1 -0
  29. package/dist/actions/ThemeToggle.svelte +345 -0
  30. package/dist/actions/ThemeToggle.svelte.d.ts +15 -0
  31. package/dist/actions/ThemeToggle.svelte.d.ts.map +1 -0
  32. package/dist/actions/index.d.ts +13 -0
  33. package/dist/actions/index.d.ts.map +1 -0
  34. package/dist/actions/index.js +10 -0
  35. package/dist/actions/scrollbar.d.ts +48 -0
  36. package/dist/actions/scrollbar.d.ts.map +1 -0
  37. package/dist/actions/scrollbar.js +404 -0
  38. package/dist/display/Accordion.svelte +586 -0
  39. package/dist/display/Accordion.svelte.d.ts +41 -0
  40. package/dist/display/Accordion.svelte.d.ts.map +1 -0
  41. package/dist/display/Avatar.svelte +527 -0
  42. package/dist/display/Avatar.svelte.d.ts +22 -0
  43. package/dist/display/Avatar.svelte.d.ts.map +1 -0
  44. package/dist/display/AvatarGroup.svelte +298 -0
  45. package/dist/display/AvatarGroup.svelte.d.ts +31 -0
  46. package/dist/display/AvatarGroup.svelte.d.ts.map +1 -0
  47. package/dist/display/Calendar.svelte +1366 -0
  48. package/dist/display/Calendar.svelte.d.ts +58 -0
  49. package/dist/display/Calendar.svelte.d.ts.map +1 -0
  50. package/dist/display/Chart.svelte +1426 -0
  51. package/dist/display/Chart.svelte.d.ts +35 -0
  52. package/dist/display/Chart.svelte.d.ts.map +1 -0
  53. package/dist/display/Code.svelte +780 -0
  54. package/dist/display/Code.svelte.d.ts +19 -0
  55. package/dist/display/Code.svelte.d.ts.map +1 -0
  56. package/dist/display/Comparison.svelte +686 -0
  57. package/dist/display/Comparison.svelte.d.ts +22 -0
  58. package/dist/display/Comparison.svelte.d.ts.map +1 -0
  59. package/dist/display/Counter.svelte +285 -0
  60. package/dist/display/Counter.svelte.d.ts +21 -0
  61. package/dist/display/Counter.svelte.d.ts.map +1 -0
  62. package/dist/display/Expand.svelte +48 -0
  63. package/dist/display/Expand.svelte.d.ts +9 -0
  64. package/dist/display/Expand.svelte.d.ts.map +1 -0
  65. package/dist/display/List.svelte +294 -0
  66. package/dist/display/List.svelte.d.ts +40 -0
  67. package/dist/display/List.svelte.d.ts.map +1 -0
  68. package/dist/display/ListContextReset.svelte +19 -0
  69. package/dist/display/ListContextReset.svelte.d.ts +7 -0
  70. package/dist/display/ListContextReset.svelte.d.ts.map +1 -0
  71. package/dist/display/ListItem.svelte +834 -0
  72. package/dist/display/ListItem.svelte.d.ts +22 -0
  73. package/dist/display/ListItem.svelte.d.ts.map +1 -0
  74. package/dist/display/QR.svelte +1193 -0
  75. package/dist/display/QR.svelte.d.ts +23 -0
  76. package/dist/display/QR.svelte.d.ts.map +1 -0
  77. package/dist/display/SplitPane.svelte +744 -0
  78. package/dist/display/SplitPane.svelte.d.ts +25 -0
  79. package/dist/display/SplitPane.svelte.d.ts.map +1 -0
  80. package/dist/display/Stat.svelte +439 -0
  81. package/dist/display/Stat.svelte.d.ts +24 -0
  82. package/dist/display/Stat.svelte.d.ts.map +1 -0
  83. package/dist/display/Table.svelte +4654 -0
  84. package/dist/display/Table.svelte.d.ts +249 -0
  85. package/dist/display/Table.svelte.d.ts.map +1 -0
  86. package/dist/display/TableCellEditor.svelte +935 -0
  87. package/dist/display/TableCellEditor.svelte.d.ts +58 -0
  88. package/dist/display/TableCellEditor.svelte.d.ts.map +1 -0
  89. package/dist/display/Timeline.svelte +1258 -0
  90. package/dist/display/Timeline.svelte.d.ts +43 -0
  91. package/dist/display/Timeline.svelte.d.ts.map +1 -0
  92. package/dist/display/Tree.svelte +1740 -0
  93. package/dist/display/Tree.svelte.d.ts +74 -0
  94. package/dist/display/Tree.svelte.d.ts.map +1 -0
  95. package/dist/display/Typewriter.svelte +338 -0
  96. package/dist/display/Typewriter.svelte.d.ts +22 -0
  97. package/dist/display/Typewriter.svelte.d.ts.map +1 -0
  98. package/dist/display/index.d.ts +24 -0
  99. package/dist/display/index.d.ts.map +1 -0
  100. package/dist/display/index.js +18 -0
  101. package/dist/feedback/Callout.svelte +529 -0
  102. package/dist/feedback/Callout.svelte.d.ts +24 -0
  103. package/dist/feedback/Callout.svelte.d.ts.map +1 -0
  104. package/dist/feedback/Confetti.svelte +631 -0
  105. package/dist/feedback/Confetti.svelte.d.ts +90 -0
  106. package/dist/feedback/Confetti.svelte.d.ts.map +1 -0
  107. package/dist/feedback/Progress.svelte +382 -0
  108. package/dist/feedback/Progress.svelte.d.ts +25 -0
  109. package/dist/feedback/Progress.svelte.d.ts.map +1 -0
  110. package/dist/feedback/Toast.svelte +967 -0
  111. package/dist/feedback/Toast.svelte.d.ts +54 -0
  112. package/dist/feedback/Toast.svelte.d.ts.map +1 -0
  113. package/dist/feedback/index.d.ts +7 -0
  114. package/dist/feedback/index.d.ts.map +1 -0
  115. package/dist/feedback/index.js +4 -0
  116. package/dist/form/Checkbox.svelte +449 -0
  117. package/dist/form/Checkbox.svelte.d.ts +27 -0
  118. package/dist/form/Checkbox.svelte.d.ts.map +1 -0
  119. package/dist/form/Fieldset.svelte +410 -0
  120. package/dist/form/Fieldset.svelte.d.ts +22 -0
  121. package/dist/form/Fieldset.svelte.d.ts.map +1 -0
  122. package/dist/form/FileUpload.svelte +934 -0
  123. package/dist/form/FileUpload.svelte.d.ts +41 -0
  124. package/dist/form/FileUpload.svelte.d.ts.map +1 -0
  125. package/dist/form/Form.svelte +530 -0
  126. package/dist/form/Form.svelte.d.ts +120 -0
  127. package/dist/form/Form.svelte.d.ts.map +1 -0
  128. package/dist/form/Input.svelte +2858 -0
  129. package/dist/form/Input.svelte.d.ts +66 -0
  130. package/dist/form/Input.svelte.d.ts.map +1 -0
  131. package/dist/form/Radio.svelte +507 -0
  132. package/dist/form/Radio.svelte.d.ts +39 -0
  133. package/dist/form/Radio.svelte.d.ts.map +1 -0
  134. package/dist/form/Range.svelte +912 -0
  135. package/dist/form/Range.svelte.d.ts +33 -0
  136. package/dist/form/Range.svelte.d.ts.map +1 -0
  137. package/dist/form/Rating.svelte +429 -0
  138. package/dist/form/Rating.svelte.d.ts +28 -0
  139. package/dist/form/Rating.svelte.d.ts.map +1 -0
  140. package/dist/form/Select.svelte +1933 -0
  141. package/dist/form/Select.svelte.d.ts +54 -0
  142. package/dist/form/Select.svelte.d.ts.map +1 -0
  143. package/dist/form/Toggle.svelte +645 -0
  144. package/dist/form/Toggle.svelte.d.ts +50 -0
  145. package/dist/form/Toggle.svelte.d.ts.map +1 -0
  146. package/dist/form/index.d.ts +15 -0
  147. package/dist/form/index.d.ts.map +1 -0
  148. package/dist/form/index.js +10 -0
  149. package/dist/index.d.ts +7 -0
  150. package/dist/index.d.ts.map +1 -0
  151. package/dist/index.js +6 -0
  152. package/dist/layout/README.md +172 -0
  153. package/dist/media/Carousel.svelte +2424 -0
  154. package/dist/media/Carousel.svelte.d.ts +47 -0
  155. package/dist/media/Carousel.svelte.d.ts.map +1 -0
  156. package/dist/media/Gallery.svelte +2881 -0
  157. package/dist/media/Gallery.svelte.d.ts +82 -0
  158. package/dist/media/Gallery.svelte.d.ts.map +1 -0
  159. package/dist/media/Image.svelte +389 -0
  160. package/dist/media/Image.svelte.d.ts +33 -0
  161. package/dist/media/Image.svelte.d.ts.map +1 -0
  162. package/dist/media/PDF.svelte +1793 -0
  163. package/dist/media/PDF.svelte.d.ts +44 -0
  164. package/dist/media/PDF.svelte.d.ts.map +1 -0
  165. package/dist/media/Panorama.svelte +1391 -0
  166. package/dist/media/Panorama.svelte.d.ts +47 -0
  167. package/dist/media/Panorama.svelte.d.ts.map +1 -0
  168. package/dist/media/Video.svelte +2501 -0
  169. package/dist/media/Video.svelte.d.ts +58 -0
  170. package/dist/media/Video.svelte.d.ts.map +1 -0
  171. package/dist/media/carousel.d.ts +211 -0
  172. package/dist/media/carousel.d.ts.map +1 -0
  173. package/dist/media/carousel.js +408 -0
  174. package/dist/media/index.d.ts +11 -0
  175. package/dist/media/index.d.ts.map +1 -0
  176. package/dist/media/index.js +5 -0
  177. package/dist/navigation/BottomSheet.svelte +636 -0
  178. package/dist/navigation/BottomSheet.svelte.d.ts +27 -0
  179. package/dist/navigation/BottomSheet.svelte.d.ts.map +1 -0
  180. package/dist/navigation/Breadcrumbs.svelte +611 -0
  181. package/dist/navigation/Breadcrumbs.svelte.d.ts +28 -0
  182. package/dist/navigation/Breadcrumbs.svelte.d.ts.map +1 -0
  183. package/dist/navigation/Pagination.svelte +641 -0
  184. package/dist/navigation/Pagination.svelte.d.ts +27 -0
  185. package/dist/navigation/Pagination.svelte.d.ts.map +1 -0
  186. package/dist/navigation/Steps.svelte +965 -0
  187. package/dist/navigation/Steps.svelte.d.ts +43 -0
  188. package/dist/navigation/Steps.svelte.d.ts.map +1 -0
  189. package/dist/navigation/Tabs.svelte +698 -0
  190. package/dist/navigation/Tabs.svelte.d.ts +41 -0
  191. package/dist/navigation/Tabs.svelte.d.ts.map +1 -0
  192. package/dist/navigation/index.d.ts +8 -0
  193. package/dist/navigation/index.d.ts.map +1 -0
  194. package/dist/navigation/index.js +5 -0
  195. package/package.json +139 -0
@@ -0,0 +1,22 @@
1
+ type $$ComponentProps = {
2
+ before: string;
3
+ after: string;
4
+ before_alt?: string;
5
+ after_alt?: string;
6
+ position?: number;
7
+ vertical?: boolean;
8
+ show_labels?: boolean;
9
+ label_before?: string;
10
+ label_after?: string;
11
+ skeleton?: boolean;
12
+ snaps?: number[];
13
+ id?: string;
14
+ class?: string;
15
+ onchange?: (detail: {
16
+ position: number;
17
+ }) => void;
18
+ };
19
+ declare const Comparison: import("svelte").Component<$$ComponentProps, {}, "position">;
20
+ type Comparison = ReturnType<typeof Comparison>;
21
+ export default Comparison;
22
+ //# sourceMappingURL=Comparison.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Comparison.svelte.d.ts","sourceRoot":"","sources":["../../src/display/Comparison.svelte.ts"],"names":[],"mappings":"AAGC,KAAK,gBAAgB,GAAI;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAClD,CAAC;AA0VH,QAAA,MAAM,UAAU,8DAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
@@ -0,0 +1,285 @@
1
+ <script lang="ts">
2
+ import { intersectionObserver } from '@delightstack/utilities';
3
+ import { untrack } from 'svelte';
4
+
5
+ type EasingFunction = (t: number) => number;
6
+
7
+ const propId = $props.id();
8
+ let {
9
+ /** The target numeric value to display. May be assigned later (e.g.
10
+ * while stats load) — pair with `skeleton` to show a placeholder
11
+ * until it arrives. */
12
+ value = undefined as number | undefined,
13
+
14
+ /** Animation duration in milliseconds */
15
+ duration = 5000,
16
+
17
+ /** Delay before animation starts in milliseconds */
18
+ delay = 0,
19
+
20
+ /** Number of decimal places to display */
21
+ decimals = 0,
22
+
23
+ /** Text to display before the number (rendered smaller, top-aligned) */
24
+ prefix = undefined as string | undefined,
25
+
26
+ /** Text to display after the number (rendered smaller, top-aligned) */
27
+ suffix = undefined as string | undefined,
28
+
29
+ /** Custom format function — takes precedence over Intl.NumberFormat */
30
+ format = undefined as ((value: number) => string) | undefined,
31
+
32
+ /** Whether to show thousands separators */
33
+ separator = true,
34
+
35
+ /** BCP 47 locale for number formatting */
36
+ locale = undefined as string | undefined,
37
+
38
+ /** Custom easing function (receives t in 0..1, returns 0..1) */
39
+ easing = undefined as EasingFunction | undefined,
40
+
41
+ /** Show a number-shaped shimmer placeholder while `value` is not yet
42
+ * available. It dismisses itself (and the count-up starts) as soon as
43
+ * `value` arrives. Width can be tuned via `--counter-skeleton-width`. */
44
+ skeleton = false,
45
+
46
+ /** Callback fired when the animation completes */
47
+ oncomplete = undefined as (() => void) | undefined,
48
+
49
+ /** Element ID */
50
+ id = propId,
51
+
52
+ /** Additional CSS classes */
53
+ class: class_name = '',
54
+ } = $props();
55
+
56
+ const easeFn = $derived(
57
+ easing ??
58
+ /** Heavy ease-out: fast start, slow finish. */
59
+ ((t: number) => 1 - Math.pow(1 - t, Math.max(5, Math.min(50, duration * 0.006)))),
60
+ );
61
+
62
+ let has_animated = $state(false);
63
+ let is_animating = $state(false);
64
+ let has_intersected = $state(false);
65
+
66
+ /** Placeholder shows until the value exists; the count-up is deferred so
67
+ * it doesn't run invisibly behind the shimmer. */
68
+ const show_skeleton = $derived(skeleton && value == null);
69
+ /** Start at 0 so SSR and the initial client render match (no hydration jump):
70
+ * the count then animates up from 0 once the element scrolls into view. The
71
+ * true value is always exposed via `aria-label` for assistive tech. */
72
+ let display_value = $state(0);
73
+ let raf_id = $state(0);
74
+
75
+ let prefers_reduced_motion = $state(false);
76
+ $effect(() => {
77
+ if (typeof window !== 'undefined') {
78
+ const mql = window.matchMedia('(prefers-reduced-motion: reduce)');
79
+ prefers_reduced_motion = mql.matches;
80
+ const handler = (e: MediaQueryListEvent) => {
81
+ prefers_reduced_motion = e.matches;
82
+ };
83
+ mql.addEventListener('change', handler);
84
+ return () => mql.removeEventListener('change', handler);
85
+ }
86
+ });
87
+
88
+ function formatNumber(n: number): string {
89
+ if (format) return format(n);
90
+ const options: Intl.NumberFormatOptions = {
91
+ minimumFractionDigits: decimals,
92
+ maximumFractionDigits: decimals,
93
+ useGrouping: separator,
94
+ };
95
+ return new Intl.NumberFormat(locale, options).format(n);
96
+ }
97
+
98
+ const formatted_value = $derived(formatNumber(display_value));
99
+
100
+ const aria_label_text = $derived(
101
+ value == null
102
+ ? 'Loading'
103
+ : (prefix ?? '') + formatNumber(value) + (suffix ? ` ${suffix}` : ''),
104
+ );
105
+
106
+ function animateCount(from: number, to: number) {
107
+ if (raf_id) cancelAnimationFrame(raf_id);
108
+ is_animating = true;
109
+ const start_time = performance.now() + delay;
110
+
111
+ function step(now: number) {
112
+ const elapsed = now - start_time;
113
+ if (elapsed < 0) {
114
+ raf_id = requestAnimationFrame(step);
115
+ return;
116
+ }
117
+ const progress = Math.min(elapsed / duration, 1);
118
+ const eased = easeFn(progress);
119
+ display_value = from + (to - from) * eased;
120
+
121
+ if (progress < 1) {
122
+ raf_id = requestAnimationFrame(step);
123
+ } else {
124
+ display_value = to;
125
+ is_animating = false;
126
+ raf_id = 0;
127
+ oncomplete?.();
128
+ }
129
+ }
130
+
131
+ raf_id = requestAnimationFrame(step);
132
+ }
133
+
134
+ function startAnimation() {
135
+ if (has_animated || value == null) return;
136
+ has_animated = true;
137
+
138
+ if (prefers_reduced_motion) {
139
+ display_value = value;
140
+ oncomplete?.();
141
+ return;
142
+ }
143
+
144
+ animateCount(0, value);
145
+ }
146
+
147
+ // Kick off the count-up once the counter is both on screen and has a real
148
+ // value (the skeleton may still be covering it when it first intersects).
149
+ // The animation itself is untracked: animateCount reads AND writes raf_id
150
+ // (and display_value), which would otherwise re-trigger this effect
151
+ // forever (effect_update_depth_exceeded).
152
+ $effect(() => {
153
+ if (!has_intersected || show_skeleton) return;
154
+ untrack(() => startAnimation());
155
+ });
156
+
157
+ $effect(() => {
158
+ const current_target = value;
159
+ const animated = untrack(() => has_animated);
160
+ const prev = untrack(() => display_value);
161
+
162
+ if (!animated || current_target == null) return;
163
+
164
+ if (prefers_reduced_motion) {
165
+ display_value = current_target;
166
+ return;
167
+ }
168
+
169
+ untrack(() => animateCount(prev, current_target));
170
+ });
171
+
172
+ $effect(() => {
173
+ return () => {
174
+ if (raf_id) cancelAnimationFrame(raf_id);
175
+ };
176
+ });
177
+
178
+ export function restart() {
179
+ if (raf_id) cancelAnimationFrame(raf_id);
180
+ display_value = 0;
181
+ has_animated = false;
182
+ is_animating = false;
183
+ startAnimation();
184
+ }
185
+ </script>
186
+
187
+ <span
188
+ class={['counter', class_name].filter(Boolean).join(' ')}
189
+ class:skeleton={show_skeleton}
190
+ {id}
191
+ role="img"
192
+ aria-live="polite"
193
+ aria-busy={show_skeleton || undefined}
194
+ aria-label={aria_label_text}
195
+ {@attach intersectionObserver({ onintersectonce: () => (has_intersected = true) })}>
196
+ {#if prefix}<span class="affix prefix">{prefix}</span>{/if}
197
+
198
+ {#if show_skeleton}
199
+ <!-- Known affixes render for real; only the unknown number is a pill. -->
200
+ <span class="skeleton-pill"></span>
201
+ {:else}
202
+ <span class="value">{formatted_value}</span>
203
+ {/if}
204
+
205
+ {#if suffix}<span class="affix suffix">{suffix}</span>{/if}
206
+ </span>
207
+
208
+ <style>
209
+ .counter {
210
+ display: inline-flex;
211
+ align-items: flex-start;
212
+ font-variant-numeric: tabular-nums;
213
+ white-space: nowrap;
214
+ line-height: 1;
215
+
216
+ &.skeleton {
217
+ user-select: none;
218
+ }
219
+
220
+ .affix {
221
+ display: inline-block;
222
+ font-size: 0.5em;
223
+ line-height: 1;
224
+ font-weight: 500;
225
+ opacity: 0.85;
226
+ align-self: flex-start;
227
+ padding-top: 0.15em;
228
+ }
229
+ .prefix {
230
+ margin-right: 0.1em;
231
+ }
232
+ .suffix {
233
+ margin-left: 0.1em;
234
+ }
235
+
236
+ .value {
237
+ display: inline;
238
+ }
239
+ }
240
+
241
+ /* A digits-sized pill standing in for the unknown number. `ch` units track
242
+ the counter's own font, so a 3rem stat gets a proportionally large pill. */
243
+ .skeleton-pill {
244
+ display: inline-block;
245
+ width: var(--counter-skeleton-width, 4ch);
246
+ height: 0.8em;
247
+ margin-top: 0.1em;
248
+ border-radius: var(--radius-full, 1e5px);
249
+ position: relative;
250
+ overflow: hidden;
251
+ background: var(--skeleton-bg, rgb(from var(--color-text, #888) r g b / 0.1));
252
+
253
+ &::after {
254
+ content: '';
255
+ position: absolute;
256
+ inset: 0;
257
+ transform: translateX(-100%);
258
+ background-image: linear-gradient(
259
+ 105deg,
260
+ transparent 25%,
261
+ var(--skeleton-sheen, rgb(from var(--color-text, #888) r g b / 0.12)) 50%,
262
+ transparent 75%
263
+ );
264
+ animation: delight-skeleton-shimmer var(--skeleton-duration, 2.4s) ease-in-out
265
+ infinite;
266
+ animation-delay: var(--shimmer-delay, 0s);
267
+ }
268
+ }
269
+
270
+ @keyframes -global-delight-skeleton-shimmer {
271
+ 0% {
272
+ transform: translateX(-100%);
273
+ }
274
+ 55%,
275
+ 100% {
276
+ transform: translateX(100%);
277
+ }
278
+ }
279
+
280
+ @media (prefers-reduced-motion: reduce) {
281
+ .skeleton-pill::after {
282
+ animation: none;
283
+ }
284
+ }
285
+ </style>
@@ -0,0 +1,21 @@
1
+ declare const Counter: import("svelte").Component<{
2
+ value?: number | undefined;
3
+ duration?: number;
4
+ delay?: number;
5
+ decimals?: number;
6
+ prefix?: string | undefined;
7
+ suffix?: string | undefined;
8
+ format?: ((value: number) => string) | undefined;
9
+ separator?: boolean;
10
+ locale?: string | undefined;
11
+ easing?: ((t: number) => number) | undefined;
12
+ skeleton?: boolean;
13
+ oncomplete?: (() => void) | undefined;
14
+ id?: string;
15
+ class?: string;
16
+ }, {
17
+ restart: () => void;
18
+ }, "">;
19
+ type Counter = ReturnType<typeof Counter>;
20
+ export default Counter;
21
+ //# sourceMappingURL=Counter.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Counter.svelte.d.ts","sourceRoot":"","sources":["../../src/display/Counter.svelte.ts"],"names":[],"mappings":"AAiNA,QAAA,MAAM,OAAO;YArMoE,MAAM,GAAG,SAAS;eAAa,MAAM;YAAU,MAAM;eAAa,MAAM;aAAW,MAAM,GAAG,SAAS;aAAW,MAAM,GAAG,SAAS;aAAW,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,SAAS;gBAAc,OAAO;aAAW,MAAM,GAAG,SAAS;aAAW,KAFvS,MAAM,KAAK,MAAM,IAEuS,SAAS;eAAa,OAAO;iBAAe,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS;;YAA8B,MAAM;;;MAqMtY,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -0,0 +1,48 @@
1
+ <script lang="ts">
2
+ import { type Snippet } from 'svelte';
3
+
4
+ let {
5
+ /** Whether the content is expanded (visible). Bindable */
6
+ show = $bindable(false),
7
+
8
+ /** Additional inline styles applied to the expanding container */
9
+ style = '',
10
+
11
+ /** The content to expand/collapse */
12
+ children = undefined as undefined | Snippet,
13
+ } = $props();
14
+ </script>
15
+
16
+ {#if children}
17
+ <div class="expand" class:show inert={!show} {style}>
18
+ <div>{@render children()}</div>
19
+ </div>
20
+ {/if}
21
+
22
+ <style>
23
+ .expand {
24
+ display: grid;
25
+ grid-template-rows: min-content 0fr;
26
+ transition:
27
+ grid-template-rows 300ms cubic-bezier(0.23, 1, 0.32, 1),
28
+ opacity 200ms;
29
+ opacity: 0;
30
+ &::before {
31
+ content: '';
32
+ }
33
+ :global(> *) {
34
+ visibility: hidden;
35
+ overflow: hidden;
36
+ transition-behavior: allow-discrete;
37
+ transition: visibility 0ms 200ms;
38
+ }
39
+ &.show {
40
+ opacity: 1;
41
+ grid-template-rows: min-content 1fr;
42
+ :global(> *) {
43
+ visibility: visible;
44
+ transition: visibility 0ms;
45
+ }
46
+ }
47
+ }
48
+ </style>
@@ -0,0 +1,9 @@
1
+ import { type Snippet } from 'svelte';
2
+ declare const Expand: import("svelte").Component<{
3
+ show?: boolean;
4
+ style?: string;
5
+ children?: undefined | Snippet;
6
+ }, {}, "show">;
7
+ type Expand = ReturnType<typeof Expand>;
8
+ export default Expand;
9
+ //# sourceMappingURL=Expand.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Expand.svelte.d.ts","sourceRoot":"","sources":["../../src/display/Expand.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AA2BtC,QAAA,MAAM,MAAM;WAxByC,OAAO;YAAU,MAAM;eAAa,SAAS,GAAG,OAAO;cAwBxD,CAAC;AACrD,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AACxC,eAAe,MAAM,CAAC"}
@@ -0,0 +1,294 @@
1
+ <script module lang="ts">
2
+ export interface ListContext {
3
+ /** How items in the list behave (plain text, buttons, or selection controls) */
4
+ type: 'button' | 'text' | 'radio' | 'checkbox' | 'toggle';
5
+ /** Indices of the currently selected items */
6
+ value: number[];
7
+ /** Whether the list uses dense (compact) spacing */
8
+ dense: boolean;
9
+ /** Whether the list uses comfortable (roomy) spacing */
10
+ comfortable: boolean;
11
+ /** Whether the whole list is disabled */
12
+ disabled: boolean;
13
+ /** The nesting depth of this list (drives indentation of nested lists) */
14
+ level: number;
15
+ /** Unique ID of the list element */
16
+ id: string;
17
+ }
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ import { onFocusWithin } from '@delightstack/utilities';
22
+ import { getContext, setContext, type Snippet } from 'svelte';
23
+
24
+ const propId = $props.id();
25
+ let {
26
+ /** Whether the list should display in a condensed view (less padding) */
27
+ dense = false,
28
+
29
+ /** Whether the form should display in an expanded view (more padding) */
30
+ comfortable = false,
31
+
32
+ /** Whether the list sits on a subtle filled surface (the iOS "inset
33
+ * grouped" look). Off by default — the list is transparent so it
34
+ * composes onto any surface. Combine with `outline` for a defined card. */
35
+ filled = false,
36
+
37
+ /** Whether the list has a 1px outline + rounded corners (transparent
38
+ * fill). Gives visible rounded corners without imposing a surface fill. */
39
+ outline = false,
40
+
41
+ /** Whether the buttons/checkboxes/radios should be disabled */
42
+ disabled = false,
43
+
44
+ /** Whether the field has been touched (and blurred) */
45
+ touched = $bindable(false) as boolean,
46
+
47
+ /**
48
+ * The type of items in the list.
49
+ * @default 'button'
50
+ * `button` - A list of buttons that can be clicked
51
+ * `text` - A list of non-interactive text
52
+ * `radio` - A list of radio buttons (only one can be selected)
53
+ * `checkbox` - A list of checkboxes (multiple can be selected)
54
+ * `toggle` - A list of toggle switches (multiple can be selected)
55
+ */
56
+ type = 'button' as 'button' | 'text' | 'radio' | 'checkbox' | 'toggle',
57
+
58
+ /** The list of indexes that have been selected (can be multiple for checkbox and only one for radio)*/
59
+ value = $bindable([]) as number[],
60
+
61
+ /** The css amount (@example '16px') to pad the list items in the X direction */
62
+ padding_x = undefined as string | undefined,
63
+
64
+ /** The css amount (@example '16px') to pad the list items in the Y direction */
65
+ padding_y = undefined as string | undefined,
66
+
67
+ /** Whether to show a skeleton loading placeholder instead of the items */
68
+ skeleton = false,
69
+
70
+ /** Number of skeleton rows to render when `skeleton` is true */
71
+ skeleton_count = 5,
72
+
73
+ /** The css style string added to the component from the parent */
74
+ style = '',
75
+
76
+ /** The ID of the select element. @defaults to a random ID */
77
+ id = propId,
78
+
79
+ /** Specifies a custom class name for the container element */
80
+ class: class_name = '',
81
+
82
+ /** The child elements to display inside the element */
83
+ children = undefined as undefined | Snippet,
84
+
85
+ /** Called when the field is touched */
86
+ ontouch = undefined as (() => void) | undefined,
87
+
88
+ /** Called when the value changes */
89
+ onchange = undefined as ((val: number[]) => void) | undefined,
90
+ } = $props();
91
+
92
+ let element = $state<HTMLElement | undefined>(undefined);
93
+ const parentContext = getContext<ListContext | undefined>('list');
94
+
95
+ // Emit the necessary events when the field is touched or dirty or value changes
96
+ $effect(() => {
97
+ if (touched) ontouch?.();
98
+ });
99
+
100
+ const context = $state({
101
+ type,
102
+ value,
103
+ dense,
104
+ comfortable,
105
+ disabled,
106
+ id,
107
+ ...parentContext,
108
+ level: (parentContext?.level || 0) + 1,
109
+ });
110
+ setContext<ListContext>('list', context);
111
+ $effect(() => {
112
+ context.type = parentContext?.type ?? type;
113
+ context.value = parentContext?.value ?? value;
114
+ context.dense = parentContext?.dense ?? dense;
115
+ context.comfortable = parentContext?.comfortable ?? comfortable;
116
+ context.disabled = parentContext?.disabled ?? disabled;
117
+ context.id = parentContext?.id ?? id;
118
+ context.level = (parentContext?.level || 0) + 1;
119
+ });
120
+
121
+ function handleChangeEvent(e: Event) {
122
+ const target = e.target as HTMLInputElement;
123
+ if (!target || (target.type !== 'checkbox' && target.type !== 'radio')) return;
124
+ let hostChild: HTMLElement | null = target;
125
+ while (hostChild && hostChild.parentElement !== element) {
126
+ hostChild = hostChild.parentElement;
127
+ }
128
+ if (!hostChild || !element) return;
129
+ const index = Array.from(element.children).indexOf(hostChild);
130
+ let tempSelected = type === 'radio' ? [index] : [...value].filter((i) => i !== index);
131
+ if (type !== 'radio' && target.checked) {
132
+ tempSelected = [...tempSelected, index];
133
+ }
134
+ if (JSON.stringify(tempSelected) === JSON.stringify(value)) return;
135
+ value = tempSelected;
136
+ if (onchange) onchange(value);
137
+ }
138
+ </script>
139
+
140
+ {#if skeleton && !parentContext?.level}
141
+ <ul
142
+ class={['list', 'skeleton', class_name].filter(Boolean).join(' ')}
143
+ class:dense
144
+ class:comfortable
145
+ class:filled
146
+ class:outline
147
+ style:--list-pad-x={padding_x}
148
+ style:--list-pad-y={padding_y}
149
+ {style}
150
+ aria-hidden="true">
151
+ {#each { length: skeleton_count } as _, i}
152
+ <li class="skeleton-item" style:--shimmer-delay="{i * 120}ms">
153
+ <span class="skeleton-bar" style:width={`${55 + ((i * 37) % 35)}%`}></span>
154
+ </li>
155
+ {/each}
156
+ </ul>
157
+ {:else if !parentContext?.level}
158
+ <ul
159
+ class={['list', class_name].filter(Boolean).join(' ')}
160
+ class:dense
161
+ class:comfortable
162
+ class:filled
163
+ class:outline
164
+ class:disabled
165
+ style:--list-pad-x={padding_x}
166
+ style:--list-pad-y={padding_y}
167
+ {@attach onFocusWithin({
168
+ onfocuswithin: () => touched || (touched = true),
169
+ })}
170
+ {style}
171
+ bind:this={element}
172
+ onchange={handleChangeEvent}>
173
+ {#if children}{@render children()}{/if}
174
+ </ul>
175
+ {:else if children}{@render children()}{/if}
176
+
177
+ <style>
178
+ ul {
179
+ --_radius: calc(var(--radius-lg) * 1.5);
180
+ --border-inset: 6px;
181
+ border-radius: var(--_radius);
182
+ @supports (corner-shape: superellipse(var(--squircle-ratio, 2))) {
183
+ corner-shape: superellipse(var(--squircle-ratio, 2));
184
+ border-radius: calc(var(--_radius) * var(--squircle-ratio, 2));
185
+ }
186
+ padding: 0;
187
+ margin: 0;
188
+ /* Transparent by default so the list composes onto any surface; the
189
+ rounded corners only become visible with `filled` or `outline`. The
190
+ item hover/active highlights stay rounded regardless. */
191
+ background-color: transparent;
192
+ perspective: 100px;
193
+ &.filled {
194
+ background-color: var(--color-bg-active);
195
+ }
196
+ &.outline {
197
+ border: 1px solid var(--color-border);
198
+ }
199
+ &.disabled {
200
+ color: var(--color-text-disabled);
201
+ cursor: not-allowed;
202
+ }
203
+ &.dense {
204
+ --border-inset: 4px;
205
+ --_radius: var(--radius-lg);
206
+
207
+ .skeleton-item {
208
+ min-height: 2.5rem;
209
+ padding: 0 1rem;
210
+ }
211
+ }
212
+ &.comfortable {
213
+ --border-inset: 8px;
214
+ --_radius: var(--radius-xl);
215
+
216
+ .skeleton-item {
217
+ min-height: 3.5rem;
218
+ padding: 0 calc(2rem + var(--list-pad-x, 0px));
219
+ }
220
+ }
221
+ &.skeleton {
222
+ /* Lists fill their container. In a definite-width parent this spans the
223
+ * full width (so skeleton %-width bars and rows lay out correctly); in a
224
+ * shrink-to-fit parent (e.g. a popover) it collapses to content width. */
225
+ width: 100%;
226
+ }
227
+
228
+ :global(> li:first-child) {
229
+ border-top-left-radius: var(--_radius);
230
+ border-top-right-radius: var(--_radius);
231
+ @supports (corner-shape: superellipse(var(--squircle-ratio, 2))) {
232
+ corner-shape: superellipse(var(--squircle-ratio, 2));
233
+ border-top-left-radius: calc(var(--_radius) * var(--squircle-ratio, 2));
234
+ border-top-right-radius: calc(var(--_radius) * var(--squircle-ratio, 2));
235
+ }
236
+ }
237
+ :global(> li:last-child) {
238
+ border-bottom-left-radius: var(--_radius);
239
+ border-bottom-right-radius: var(--_radius);
240
+ @supports (corner-shape: superellipse(var(--squircle-ratio, 2))) {
241
+ corner-shape: superellipse(var(--squircle-ratio, 2));
242
+ border-bottom-left-radius: calc(var(--_radius) * var(--squircle-ratio, 2));
243
+ border-bottom-right-radius: calc(var(--_radius) * var(--squircle-ratio, 2));
244
+ }
245
+ }
246
+ }
247
+
248
+ /* Mirrors the real ListItem row metrics (min-height + inline padding,
249
+ incl. dense/comfortable) so the swap to loaded items doesn't shift. */
250
+ .skeleton-item {
251
+ list-style: none;
252
+ display: flex;
253
+ align-items: center;
254
+ min-height: 3rem;
255
+ padding: 0 calc(1.5rem + var(--list-pad-x, 0px));
256
+ }
257
+ .skeleton-bar {
258
+ height: 0.7em;
259
+ border-radius: var(--radius-full, 1e5px);
260
+ position: relative;
261
+ overflow: hidden;
262
+ background: var(--skeleton-bg, rgb(from var(--color-text, #888) r g b / 0.1));
263
+
264
+ &::after {
265
+ content: '';
266
+ position: absolute;
267
+ inset: 0;
268
+ transform: translateX(-100%);
269
+ background-image: linear-gradient(
270
+ 105deg,
271
+ transparent 25%,
272
+ var(--skeleton-sheen, rgb(from var(--color-text, #888) r g b / 0.12)) 50%,
273
+ transparent 75%
274
+ );
275
+ animation: delight-skeleton-shimmer var(--skeleton-duration, 2.4s) ease-in-out
276
+ infinite;
277
+ animation-delay: var(--shimmer-delay, 0s);
278
+ }
279
+ }
280
+ @keyframes -global-delight-skeleton-shimmer {
281
+ 0% {
282
+ transform: translateX(-100%);
283
+ }
284
+ 55%,
285
+ 100% {
286
+ transform: translateX(100%);
287
+ }
288
+ }
289
+ @media (prefers-reduced-motion: reduce) {
290
+ .skeleton-bar::after {
291
+ animation: none;
292
+ }
293
+ }
294
+ </style>