@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,82 @@
1
+ import type { Component } from 'svelte';
2
+ import type { CarouselItem } from './carousel';
3
+ /** A single action button that can be shown on a gallery item or in the carousel header. */
4
+ export interface GalleryItemAction {
5
+ /** The icon component to show for the action */
6
+ icon?: Component<Record<string, unknown>>;
7
+ /** The main action text - e.g. 'Download' or 'Pay now' */
8
+ name?: string;
9
+ /** A short descriptor of the action - e.g. file size or filename */
10
+ tooltip?: string;
11
+ /** The link that the button should go to */
12
+ href?: string;
13
+ /** Called when the button is clicked */
14
+ click?: (event: Event) => unknown;
15
+ /** Anchor target (only used if href is provided) */
16
+ target?: '_blank' | '_self';
17
+ /** The list of subactions (shown in a context menu) */
18
+ actions?: GalleryItemAction[];
19
+ }
20
+ export type GalleryDisplay = 'grid' | 'masonry' | 'masonry-row' | 'list' | 'slider' | 'slideshow' | 'lightbox';
21
+ /**
22
+ * Thumbnail size on the delightstack numeric scale: `'1'` is the standard,
23
+ * lower (`'0'`, `'00'`) is smaller, higher (`'2'`, `'3'`) is larger.
24
+ */
25
+ export type GallerySize = '00' | '0' | '1' | '2' | '3';
26
+ /**
27
+ * Gap between gallery items on the delightstack numeric scale: `'0'` removes
28
+ * the gap, `'1'` is the standard, `'2'`/`'3'` are progressively larger.
29
+ */
30
+ export type GallerySpacing = '0' | '1' | '2' | '3';
31
+ /**
32
+ * Corner radius of gallery items on the delightstack numeric scale: `'0'` is
33
+ * square, `'1'` is the standard, `'2'`/`'3'` are progressively rounder.
34
+ */
35
+ export type GalleryRadius = '0' | '1' | '2' | '3';
36
+ export type GalleryItem = string | (Partial<CarouselItem> & {
37
+ favorite?: boolean;
38
+ });
39
+ import { type Snippet } from 'svelte';
40
+ declare const Gallery: Component<{
41
+ display?: GalleryDisplay;
42
+ size?: GallerySize;
43
+ spacing?: GallerySpacing;
44
+ radius?: GalleryRadius;
45
+ slide?: number;
46
+ fit?: "cover" | "contain";
47
+ items?: GalleryItem[];
48
+ duration?: number;
49
+ autoplay?: boolean;
50
+ autoplay_video?: boolean;
51
+ aspect_ratio?: string | undefined;
52
+ disable_fullscreen?: boolean;
53
+ inline?: boolean | undefined;
54
+ controls?: "default" | "inline" | "overlay" | "disable";
55
+ page?: number;
56
+ num_pages?: number;
57
+ meta_display?: "none" | "always" | "hover";
58
+ meta_display_fullscreen?: "none" | "always";
59
+ action_display?: "none" | "always" | "hover";
60
+ actions?: GalleryItemAction[][];
61
+ custom?: Snippet<[{
62
+ item: CarouselItem;
63
+ onload: () => void;
64
+ onerror: (err: unknown) => void;
65
+ active: boolean;
66
+ gesture_disabled: boolean;
67
+ }]> | undefined;
68
+ onclick?: undefined | ((event: MouseEvent | KeyboardEvent, index: number) => void | false);
69
+ style?: string;
70
+ }, {
71
+ open: (index: number, from?: HTMLElement) => void;
72
+ close: () => void;
73
+ goto: (i: number) => void;
74
+ next: (amount?: number) => void;
75
+ prev: (amount?: number) => void;
76
+ play: () => void;
77
+ pause: () => void;
78
+ toggleFullscreen: () => void;
79
+ }, "page" | "slide" | "num_pages">;
80
+ type Gallery = ReturnType<typeof Gallery>;
81
+ export default Gallery;
82
+ //# sourceMappingURL=Gallery.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Gallery.svelte.d.ts","sourceRoot":"","sources":["../../src/media/Gallery.svelte.ts"],"names":[],"mappings":"AAGC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,4FAA4F;AAC5F,MAAM,WAAW,iBAAiB;IACjC,gDAAgD;IAChD,IAAI,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAE1C,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,4CAA4C;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd,wCAAwC;IACxC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;IAElC,oDAAoD;IACpD,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;IAE5B,uDAAuD;IACvD,OAAO,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED,MAAM,MAAM,cAAc,GACvB,MAAM,GACN,SAAS,GACT,aAAa,GACb,MAAM,GACN,QAAQ,GACR,WAAW,GACX,UAAU,CAAC;AAEd;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEvD;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEnD;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAElD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAKrF,OAAO,EAA+B,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AA40BnE,QAAA,MAAM,OAAO;cA1hB2C,cAAc;WAAS,WAAW;cAAY,cAAc;aAAW,aAAa;YAAU,MAAM;UAAQ,OAAO,GAAG,SAAS;YAAU,WAAW,EAAE;eAAa,MAAM;eAAa,OAAO;qBAAmB,OAAO;mBAAiB,MAAM,GAAG,SAAS;yBAAuB,OAAO;aAAW,OAAO,GAAG,SAAS;eAAa,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS;WAAS,MAAM;gBAAc,MAAM;mBAAiB,MAAM,GAAG,QAAQ,GAAG,OAAO;8BAA4B,MAAM,GAAG,QAAQ;qBAAmB,MAAM,GAAG,QAAQ,GAAG,OAAO;cAAY,iBAAiB,EAAE,EAAE;aAAa,OAAO,CACjoB,CACC;QACC,IAAI,EAAE,YAAY,CAAC;QACnB,MAAM,EAAE,MAAM,IAAI,CAAC;QACnB,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;QAChC,MAAM,EAAE,OAAO,CAAC;QAChB,gBAAgB,EAAE,OAAO,CAAC;KAC1B,CACD,CACA,GACD,SAAS;cAAc,SAAS,GAChC,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,KAAK,CAAC;YAAU,MAAM;;kBAwNlE,MAAM,SAAS,WAAW;;cAqB9B,MAAM;;;;;;kCAiS4B,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -0,0 +1,389 @@
1
+ <script lang="ts">
2
+ import { decodeThumbHash } from './carousel';
3
+
4
+ type ImageState = 'loading' | 'loaded' | 'error';
5
+
6
+ let {
7
+ /** The image source URL (required) */
8
+ src,
9
+
10
+ /** The alt text for the image (required) */
11
+ alt,
12
+
13
+ /** The intrinsic width of the image in pixels */
14
+ width = undefined as number | undefined,
15
+
16
+ /** The intrinsic height of the image in pixels */
17
+ height = undefined as number | undefined,
18
+
19
+ /** The aspect ratio of the container (e.g. '16/9') to prevent CLS */
20
+ aspect_ratio = undefined as string | undefined,
21
+
22
+ /** How the image should be scaled within its container */
23
+ fit = 'cover' as 'cover' | 'contain' | 'fill' | 'none',
24
+
25
+ /** The position of the image within its container */
26
+ position = 'center',
27
+
28
+ /** Whether the image should be lazily loaded */
29
+ lazy = true,
30
+
31
+ /**
32
+ * A base64-encoded [ThumbHash](https://evanw.github.io/thumbhash/) used as
33
+ * a tiny blurred placeholder while the full image loads. The component
34
+ * decodes this internally. Takes precedence over {@link placeholder}.
35
+ */
36
+ thumbhash = undefined as string | undefined,
37
+
38
+ /** A small placeholder image URL for the blur-up effect (used when no thumbhash) */
39
+ placeholder = undefined as string | undefined,
40
+
41
+ /**
42
+ * A solid background colour painted on the container — paints before any
43
+ * image data arrives (no JS needed). Useful for SSR where a dominant
44
+ * colour avoids any flash of empty box even before the thumbhash decodes.
45
+ * Accepts any CSS colour value.
46
+ */
47
+ bg_color = undefined as string | undefined,
48
+
49
+ /**
50
+ * Mark this image as above-the-fold for faster initial paint. When true,
51
+ * the image uses `loading="eager"` + `fetchpriority="high"`.
52
+ */
53
+ priority = false,
54
+
55
+ /**
56
+ * Retry-on-error. `true` retries 3 times with exponential backoff
57
+ * (1s, 4s, 9s); a number sets the max retry count; `false` (default)
58
+ * disables retries and transitions to the error state immediately.
59
+ */
60
+ retry = false as boolean | number,
61
+
62
+ /** Error fallback: true = built-in broken-image SVG, string = fallback image URL, false = disabled */
63
+ fallback = false as string | boolean,
64
+
65
+ /** Responsive srcset attribute passed through to the img element */
66
+ srcset = undefined as string | undefined,
67
+
68
+ /**
69
+ * Responsive sizes attribute. If omitted and the image is lazy with a
70
+ * srcset, defaults to `auto, 100vw` — modern browsers use the actual
71
+ * rendered width to pick a variant, older browsers fall back to 100vw.
72
+ */
73
+ sizes = undefined as string | undefined,
74
+
75
+ /** Whether to show a skeleton shimmer animation while loading */
76
+ skeleton = false,
77
+
78
+ /** The ID of the element */
79
+ id = undefined as string | undefined,
80
+
81
+ /** Specifies a custom class name for the container element */
82
+ class: class_name = '',
83
+
84
+ /** A bindable reference to the root HTML element */
85
+ element = $bindable(undefined as HTMLElement | undefined),
86
+
87
+ /** Callback fired when the full image loads */
88
+ onload = undefined as
89
+ | undefined
90
+ | ((detail: { natural_width: number; natural_height: number }) => void),
91
+
92
+ /** Callback fired when the image fails to load */
93
+ onerror = undefined as undefined | ((detail: { error: Event }) => void),
94
+ }: {
95
+ src: string;
96
+ alt: string;
97
+ width?: number;
98
+ height?: number;
99
+ aspect_ratio?: string;
100
+ fit?: 'cover' | 'contain' | 'fill' | 'none';
101
+ position?: string;
102
+ lazy?: boolean;
103
+ thumbhash?: string;
104
+ placeholder?: string;
105
+ bg_color?: string;
106
+ priority?: boolean;
107
+ retry?: boolean | number;
108
+ fallback?: string | boolean;
109
+ srcset?: string;
110
+ sizes?: string;
111
+ skeleton?: boolean;
112
+ id?: string;
113
+ class?: string;
114
+ element?: HTMLElement | undefined;
115
+ onload?: (detail: { natural_width: number; natural_height: number }) => void;
116
+ onerror?: (detail: { error: Event }) => void;
117
+ } = $props();
118
+
119
+ let load_state = $state<ImageState>('loading');
120
+ let fading = $state(false);
121
+ let img_el = $state<HTMLImageElement | undefined>(undefined);
122
+ let retry_count = $state(0);
123
+ let retry_timer: ReturnType<typeof setTimeout> | undefined;
124
+
125
+ const max_retries = $derived(
126
+ retry === true ? 3 : typeof retry === 'number' ? Math.max(0, retry) : 0,
127
+ );
128
+
129
+ const placeholder_src = $derived(thumbhash ? decodeThumbHash(thumbhash) : placeholder);
130
+ const has_placeholder = $derived(!!placeholder_src);
131
+ const show_skeleton = $derived(
132
+ skeleton && load_state === 'loading' && !has_placeholder,
133
+ );
134
+ const show_placeholder = $derived(has_placeholder && load_state !== 'error');
135
+ const show_fallback = $derived(load_state === 'error' && fallback !== false);
136
+ const fallback_is_url = $derived(typeof fallback === 'string');
137
+
138
+ const computed_sizes = $derived(sizes ?? (lazy && srcset ? 'auto, 100vw' : undefined));
139
+ const computed_loading = $derived(priority ? 'eager' : lazy ? 'lazy' : 'eager');
140
+
141
+ const container_style = $derived.by(() => {
142
+ const parts: string[] = [];
143
+ if (aspect_ratio) parts.push(`aspect-ratio: ${aspect_ratio}`);
144
+ if (width) parts.push(`width: ${width}px`);
145
+ if (height && !aspect_ratio) parts.push(`height: ${height}px`);
146
+ if (bg_color) parts.push(`background-color: ${bg_color}`);
147
+ return parts.join('; ') || undefined;
148
+ });
149
+
150
+ function handleLoad(e: Event) {
151
+ load_state = 'loaded';
152
+ fading = false;
153
+ retry_count = 0;
154
+ clearTimeout(retry_timer);
155
+ if (onload) {
156
+ const img = e.target as HTMLImageElement;
157
+ onload({
158
+ natural_width: img.naturalWidth,
159
+ natural_height: img.naturalHeight,
160
+ });
161
+ }
162
+ }
163
+
164
+ function handleError(e: Event) {
165
+ // Opt-in retry — schedule another attempt with exponential backoff
166
+ // before transitioning to the error state.
167
+ if (retry_count < max_retries) {
168
+ retry_count++;
169
+ clearTimeout(retry_timer);
170
+ const delay = retry_count ** 2 * 1000; // 1s, 4s, 9s, …
171
+ retry_timer = setTimeout(() => {
172
+ if (!img_el || load_state === 'loaded') return;
173
+ const current = img_el.src;
174
+ img_el.src = '';
175
+ img_el.src = current;
176
+ }, delay);
177
+ return;
178
+ }
179
+ load_state = 'error';
180
+ fading = false;
181
+ if (onerror) {
182
+ onerror({ error: e });
183
+ }
184
+ }
185
+
186
+ // Reset retry counter when src changes so a new src gets its own retry budget.
187
+ $effect(() => {
188
+ void src;
189
+ retry_count = 0;
190
+ clearTimeout(retry_timer);
191
+ });
192
+
193
+ // Cancel any pending retry on unmount.
194
+ $effect(() => () => clearTimeout(retry_timer));
195
+
196
+ /**
197
+ * Sync load_state with the actual `<img>` load load_state. Runs once on mount and again
198
+ * whenever `src` changes. For cached images, `el.complete` is `true`
199
+ * synchronously — without this, the SSR/hydration race causes cached images
200
+ * to stay invisible because the load event fires before Svelte attaches its
201
+ * onload listener.
202
+ */
203
+ $effect(() => {
204
+ // No src yet (e.g. progressively assigned) — stay in the loading state so
205
+ // the placeholder/skeleton shows rather than falling through to `error`.
206
+ if (!src) {
207
+ load_state = 'loading';
208
+ fading = true;
209
+ return;
210
+ }
211
+ if (!img_el) return;
212
+ if (img_el.complete && img_el.naturalWidth > 0) {
213
+ load_state = 'loaded';
214
+ fading = false;
215
+ } else if (img_el.complete && img_el.naturalWidth === 0) {
216
+ load_state = 'error';
217
+ fading = false;
218
+ } else {
219
+ load_state = 'loading';
220
+ fading = true;
221
+ }
222
+ });
223
+ </script>
224
+
225
+ <div
226
+ {id}
227
+ class={['image', class_name].filter(Boolean).join(' ')}
228
+ style={container_style}
229
+ bind:this={element}>
230
+ {#if show_skeleton}
231
+ <div class="skeleton"></div>
232
+ {/if}
233
+
234
+ {#if show_placeholder}
235
+ <img
236
+ class="placeholder"
237
+ src={placeholder_src}
238
+ alt=""
239
+ aria-hidden="true"
240
+ style:object-fit={fit}
241
+ style:object-position={position} />
242
+ {/if}
243
+
244
+ {#if src && load_state !== 'error'}
245
+ <img
246
+ class="main"
247
+ class:fading
248
+ bind:this={img_el}
249
+ {src}
250
+ {alt}
251
+ {width}
252
+ {height}
253
+ {srcset}
254
+ sizes={computed_sizes}
255
+ loading={computed_loading}
256
+ fetchpriority={priority ? 'high' : undefined}
257
+ style:object-fit={fit}
258
+ style:object-position={position}
259
+ onload={handleLoad}
260
+ onerror={handleError} />
261
+ {/if}
262
+
263
+ {#if show_fallback}
264
+ {#if fallback_is_url}
265
+ <img
266
+ class="fallback-img"
267
+ src={fallback as string}
268
+ {alt}
269
+ style:object-fit={fit}
270
+ style:object-position={position} />
271
+ {:else}
272
+ <div class="fallback">
273
+ <svg
274
+ xmlns="http://www.w3.org/2000/svg"
275
+ viewBox="0 0 24 24"
276
+ fill="none"
277
+ stroke="currentColor"
278
+ stroke-width="1.5"
279
+ stroke-linecap="round"
280
+ stroke-linejoin="round"
281
+ class="fallback-icon"
282
+ aria-hidden="true">
283
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
284
+ <circle cx="8.5" cy="8.5" r="1.5" />
285
+ <polyline points="21 15 16 10 5 21" />
286
+ <line x1="2" y1="2" x2="22" y2="22" />
287
+ </svg>
288
+ </div>
289
+ {/if}
290
+ {/if}
291
+ </div>
292
+
293
+ <style>
294
+ .image {
295
+ position: relative;
296
+ overflow: hidden;
297
+ display: grid;
298
+ width: 100%;
299
+ }
300
+
301
+ .image > * {
302
+ grid-row: 1 / 1;
303
+ grid-column: 1 / 1;
304
+ }
305
+
306
+ /* Skeleton shimmer */
307
+ .skeleton {
308
+ position: relative;
309
+ overflow: hidden;
310
+ background: var(--skeleton-bg, rgb(from var(--color-text, #888) r g b / 0.1));
311
+ z-index: 0;
312
+
313
+ &::after {
314
+ content: '';
315
+ position: absolute;
316
+ inset: 0;
317
+ transform: translateX(-100%);
318
+ background-image: linear-gradient(
319
+ 105deg,
320
+ transparent 25%,
321
+ var(--skeleton-sheen, rgb(from var(--color-text, #888) r g b / 0.12)) 50%,
322
+ transparent 75%
323
+ );
324
+ animation: delight-skeleton-shimmer var(--skeleton-duration, 2.4s) ease-in-out
325
+ infinite;
326
+ }
327
+ }
328
+
329
+ @keyframes -global-delight-skeleton-shimmer {
330
+ 0% {
331
+ transform: translateX(-100%);
332
+ }
333
+ 55%,
334
+ 100% {
335
+ transform: translateX(100%);
336
+ }
337
+ }
338
+
339
+ /* Blur-up placeholder — always renders underneath the main image so it shows
340
+ through while the main image is loading, and is covered once it paints.
341
+ `transform` makes it form a stacking context, so an explicit z-index is
342
+ required to keep it below the (later-painted but un-transformed) main image. */
343
+ .placeholder {
344
+ display: block;
345
+ width: 100%;
346
+ height: 100%;
347
+ filter: blur(20px);
348
+ transform: scale(1.1);
349
+ pointer-events: none;
350
+ z-index: 0;
351
+ }
352
+
353
+ /* Main image — defaults to opacity 1 so cached images paint instantly during
354
+ SSR. The `fading` class is added by JS only when the image isn't already
355
+ loaded; `onload` removes it, triggering the fade-in over the placeholder. */
356
+ .main {
357
+ display: block;
358
+ width: 100%;
359
+ height: 100%;
360
+ transition: opacity 300ms ease;
361
+ z-index: 1;
362
+ }
363
+
364
+ .main.fading {
365
+ opacity: 0;
366
+ transition: none;
367
+ }
368
+
369
+ .fallback-img {
370
+ display: block;
371
+ width: 100%;
372
+ height: 100%;
373
+ z-index: 2;
374
+ }
375
+
376
+ .fallback {
377
+ z-index: 2;
378
+ display: flex;
379
+ align-items: center;
380
+ justify-content: center;
381
+ background-color: var(--color-bg-muted, rgba(128, 128, 128, 0.1));
382
+ color: var(--color-text-muted, rgba(128, 128, 128, 0.6));
383
+ }
384
+
385
+ .fallback-icon {
386
+ width: 2.5em;
387
+ height: 2.5em;
388
+ }
389
+ </style>
@@ -0,0 +1,33 @@
1
+ type $$ComponentProps = {
2
+ src: string;
3
+ alt: string;
4
+ width?: number;
5
+ height?: number;
6
+ aspect_ratio?: string;
7
+ fit?: 'cover' | 'contain' | 'fill' | 'none';
8
+ position?: string;
9
+ lazy?: boolean;
10
+ thumbhash?: string;
11
+ placeholder?: string;
12
+ bg_color?: string;
13
+ priority?: boolean;
14
+ retry?: boolean | number;
15
+ fallback?: string | boolean;
16
+ srcset?: string;
17
+ sizes?: string;
18
+ skeleton?: boolean;
19
+ id?: string;
20
+ class?: string;
21
+ element?: HTMLElement | undefined;
22
+ onload?: (detail: {
23
+ natural_width: number;
24
+ natural_height: number;
25
+ }) => void;
26
+ onerror?: (detail: {
27
+ error: Event;
28
+ }) => void;
29
+ };
30
+ declare const Image: import("svelte").Component<$$ComponentProps, {}, "element">;
31
+ type Image = ReturnType<typeof Image>;
32
+ export default Image;
33
+ //# sourceMappingURL=Image.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Image.svelte.d.ts","sourceRoot":"","sources":["../../src/media/Image.svelte.ts"],"names":[],"mappings":"AAOC,KAAK,gBAAgB,GAAI;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC7E,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,KAAK,CAAA;KAAE,KAAK,IAAI,CAAC;CAC7C,CAAC;AA0OH,QAAA,MAAM,KAAK,6DAAwC,CAAC;AACpD,KAAK,KAAK,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AACtC,eAAe,KAAK,CAAC"}