@autumnsgrove/groveengine 0.6.2 → 0.6.3

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 (77) hide show
  1. package/dist/auth/jwt.d.ts +10 -4
  2. package/dist/auth/jwt.js +18 -4
  3. package/dist/auth/session.d.ts +22 -15
  4. package/dist/auth/session.js +35 -16
  5. package/dist/components/admin/GutterManager.svelte +81 -139
  6. package/dist/components/admin/GutterManager.svelte.d.ts +6 -6
  7. package/dist/components/admin/MarkdownEditor.svelte +80 -23
  8. package/dist/components/admin/MarkdownEditor.svelte.d.ts +14 -8
  9. package/dist/components/admin/composables/useAmbientSounds.svelte.d.ts +52 -2
  10. package/dist/components/admin/composables/useAmbientSounds.svelte.js +38 -4
  11. package/dist/components/admin/composables/useCommandPalette.svelte.d.ts +80 -10
  12. package/dist/components/admin/composables/useCommandPalette.svelte.js +45 -5
  13. package/dist/components/admin/composables/useDraftManager.svelte.d.ts +76 -14
  14. package/dist/components/admin/composables/useDraftManager.svelte.js +44 -10
  15. package/dist/components/admin/composables/useEditorTheme.svelte.d.ts +168 -2
  16. package/dist/components/admin/composables/useEditorTheme.svelte.js +40 -7
  17. package/dist/components/admin/composables/useSlashCommands.svelte.d.ts +94 -22
  18. package/dist/components/admin/composables/useSlashCommands.svelte.js +58 -9
  19. package/dist/components/admin/composables/useSnippets.svelte.d.ts +51 -2
  20. package/dist/components/admin/composables/useSnippets.svelte.js +35 -3
  21. package/dist/components/admin/composables/useWritingSession.svelte.d.ts +64 -6
  22. package/dist/components/admin/composables/useWritingSession.svelte.js +42 -5
  23. package/dist/components/custom/ContentWithGutter.svelte +53 -23
  24. package/dist/components/custom/ContentWithGutter.svelte.d.ts +6 -14
  25. package/dist/components/custom/GutterItem.svelte +1 -1
  26. package/dist/components/custom/LeftGutter.svelte +43 -13
  27. package/dist/components/custom/LeftGutter.svelte.d.ts +6 -6
  28. package/dist/config/ai-models.js +1 -1
  29. package/dist/groveauth/client.js +11 -11
  30. package/dist/index.d.ts +3 -1
  31. package/dist/index.js +2 -2
  32. package/dist/server/logger.d.ts +74 -26
  33. package/dist/server/logger.js +133 -184
  34. package/dist/server/services/cache.js +1 -10
  35. package/dist/ui/components/charts/ActivityOverview.svelte +14 -3
  36. package/dist/ui/components/charts/ActivityOverview.svelte.d.ts +10 -7
  37. package/dist/ui/components/charts/RepoBreakdown.svelte +9 -3
  38. package/dist/ui/components/charts/RepoBreakdown.svelte.d.ts +12 -11
  39. package/dist/ui/components/charts/Sparkline.svelte +18 -7
  40. package/dist/ui/components/charts/Sparkline.svelte.d.ts +21 -2
  41. package/dist/ui/components/gallery/ImageGallery.svelte +12 -8
  42. package/dist/ui/components/gallery/ImageGallery.svelte.d.ts +2 -2
  43. package/dist/ui/components/gallery/Lightbox.svelte +5 -2
  44. package/dist/ui/components/gallery/ZoomableImage.svelte +8 -5
  45. package/dist/ui/components/primitives/accordion/index.d.ts +1 -1
  46. package/dist/ui/components/primitives/input/input.svelte.d.ts +1 -1
  47. package/dist/ui/components/primitives/tabs/index.d.ts +1 -1
  48. package/dist/ui/components/primitives/textarea/textarea.svelte.d.ts +1 -1
  49. package/dist/ui/components/ui/Button.svelte +5 -0
  50. package/dist/ui/components/ui/Button.svelte.d.ts +4 -1
  51. package/dist/ui/components/ui/Input.svelte +4 -0
  52. package/dist/ui/components/ui/Input.svelte.d.ts +3 -1
  53. package/dist/ui/components/ui/Logo.svelte +86 -0
  54. package/dist/ui/components/ui/Logo.svelte.d.ts +25 -0
  55. package/dist/ui/components/ui/LogoLoader.svelte +71 -0
  56. package/dist/ui/components/ui/LogoLoader.svelte.d.ts +9 -0
  57. package/dist/ui/components/ui/index.d.ts +2 -0
  58. package/dist/ui/components/ui/index.js +2 -0
  59. package/dist/ui/tailwind.preset.js +8 -8
  60. package/dist/utils/api.js +2 -1
  61. package/dist/utils/debounce.d.ts +4 -3
  62. package/dist/utils/debounce.js +10 -6
  63. package/dist/utils/gallery.d.ts +58 -32
  64. package/dist/utils/gallery.js +111 -129
  65. package/dist/utils/gutter.d.ts +47 -26
  66. package/dist/utils/gutter.js +116 -124
  67. package/dist/utils/imageProcessor.d.ts +66 -19
  68. package/dist/utils/imageProcessor.js +31 -10
  69. package/dist/utils/index.d.ts +11 -11
  70. package/dist/utils/index.js +4 -3
  71. package/dist/utils/json.js +1 -1
  72. package/dist/utils/markdown.d.ts +183 -103
  73. package/dist/utils/markdown.js +517 -678
  74. package/dist/utils/sanitize.d.ts +22 -12
  75. package/dist/utils/sanitize.js +268 -282
  76. package/dist/utils/validation.js +4 -3
  77. package/package.json +3 -2
@@ -12,12 +12,12 @@
12
12
  * { url: 'https://...', alt: 'Description', caption: 'Photo caption' }
13
13
  * ]} />
14
14
  */
15
- let { images = [] } = $props();
15
+ let { images = /** @type {Array<{url: string, alt: string, caption?: string}>} */ ([]) } = $props();
16
16
 
17
17
  let currentIndex = $state(0);
18
18
  let touchStartX = $state(0);
19
19
  let touchEndX = $state(0);
20
- let galleryElement = $state();
20
+ let galleryElement = $state(/** @type {HTMLDivElement | undefined} */ (undefined));
21
21
 
22
22
  // Lightbox state
23
23
  let lightboxOpen = $state(false);
@@ -77,7 +77,7 @@
77
77
  }, NAVIGATION_COOLDOWN_MS);
78
78
  }
79
79
 
80
- function goToIndex(index) {
80
+ function goToIndex(/** @type {number} */ index) {
81
81
  if (isNavigating || index < 0 || index >= images.length || index === currentIndex) return;
82
82
 
83
83
  isNavigating = true;
@@ -102,7 +102,7 @@
102
102
  }
103
103
 
104
104
  // Keyboard navigation
105
- function handleKeydown(event) {
105
+ function handleKeydown(/** @type {KeyboardEvent} */ event) {
106
106
  // Handle Escape to close lightbox
107
107
  if (event.key === 'Escape' && lightboxOpen) {
108
108
  closeLightbox();
@@ -117,11 +117,11 @@
117
117
  }
118
118
 
119
119
  // Touch/swipe support
120
- function handleTouchStart(event) {
120
+ function handleTouchStart(/** @type {TouchEvent} */ event) {
121
121
  touchStartX = event.touches[0].clientX;
122
122
  }
123
123
 
124
- function handleTouchMove(event) {
124
+ function handleTouchMove(/** @type {TouchEvent} */ event) {
125
125
  touchEndX = event.touches[0].clientX;
126
126
  }
127
127
 
@@ -160,6 +160,7 @@
160
160
  <svelte:window onkeydown={handleKeydown} />
161
161
 
162
162
  {#if images && images.length > 0}
163
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
163
164
  <div
164
165
  class="gallery-container"
165
166
  bind:this={galleryElement}
@@ -262,12 +263,14 @@
262
263
 
263
264
  <!-- Lightbox modal -->
264
265
  {#if lightboxOpen}
266
+ <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events a11y_interactive_supports_focus -->
265
267
  <div
266
268
  class="lightbox-backdrop"
267
- onclick={(e) => e.target === e.currentTarget && closeLightbox()}
269
+ onclick={(/** @type {MouseEvent} */ e) => e.target === e.currentTarget && closeLightbox()}
268
270
  role="dialog"
269
271
  aria-modal="true"
270
272
  aria-label="Image viewer"
273
+ tabindex="-1"
271
274
  >
272
275
  <button class="lightbox-close" onclick={closeLightbox} aria-label="Close">
273
276
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -276,7 +279,8 @@
276
279
  </svg>
277
280
  </button>
278
281
 
279
- <div class="lightbox-content" onclick={(e) => e.target === e.currentTarget && closeLightbox()}>
282
+ <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
283
+ <div class="lightbox-content" onclick={(/** @type {MouseEvent} */ e) => e.target === e.currentTarget && closeLightbox()}>
280
284
  <ZoomableImage
281
285
  src={currentImage.url}
282
286
  alt={currentImage.alt || `Image ${currentIndex + 1}`}
@@ -4,8 +4,8 @@ type ImageGallery = {
4
4
  $set?(props: Partial<$$ComponentProps>): void;
5
5
  };
6
6
  declare const ImageGallery: import("svelte").Component<{
7
- images?: any[];
7
+ images?: any;
8
8
  }, {}, "">;
9
9
  type $$ComponentProps = {
10
- images?: any[];
10
+ images?: any;
11
11
  };
@@ -8,13 +8,13 @@
8
8
  */
9
9
  let { src = '', alt = '', caption = '', isOpen = false, onClose = () => {} } = $props();
10
10
 
11
- function handleKeydown(event) {
11
+ function handleKeydown(/** @type {KeyboardEvent} */ event) {
12
12
  if (event.key === 'Escape') {
13
13
  onClose();
14
14
  }
15
15
  }
16
16
 
17
- function handleBackdropClick(event) {
17
+ function handleBackdropClick(/** @type {MouseEvent} */ event) {
18
18
  if (event.target === event.currentTarget) {
19
19
  onClose();
20
20
  }
@@ -24,12 +24,14 @@
24
24
  <svelte:window onkeydown={handleKeydown} />
25
25
 
26
26
  {#if isOpen}
27
+ <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events a11y_interactive_supports_focus -->
27
28
  <div
28
29
  class="lightbox-backdrop"
29
30
  onclick={handleBackdropClick}
30
31
  role="dialog"
31
32
  aria-modal="true"
32
33
  aria-label="Image viewer"
34
+ tabindex="-1"
33
35
  >
34
36
  <button class="close-button" onclick={onClose} aria-label="Close">
35
37
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -37,6 +39,7 @@
37
39
  <line x1="6" y1="6" x2="18" y2="18"></line>
38
40
  </svg>
39
41
  </button>
42
+ <!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
40
43
  <div class="lightbox-content" onclick={handleBackdropClick}>
41
44
  <ZoomableImage {src} {alt} isActive={isOpen} class="lightbox-image" />
42
45
  </div>
@@ -56,7 +56,7 @@
56
56
  }
57
57
 
58
58
  // Mouse event handlers for drag/pan
59
- function handleMouseDown(event) {
59
+ function handleMouseDown(/** @type {MouseEvent} */ event) {
60
60
  if (zoomLevel === 0) return;
61
61
 
62
62
  isDragging = true;
@@ -68,7 +68,7 @@
68
68
  event.preventDefault();
69
69
  }
70
70
 
71
- function handleMouseMove(event) {
71
+ function handleMouseMove(/** @type {MouseEvent} */ event) {
72
72
  if (!isDragging) return;
73
73
 
74
74
  const deltaX = event.clientX - dragStartX;
@@ -84,7 +84,7 @@
84
84
  }
85
85
 
86
86
  // Touch event handlers for drag/pan on mobile
87
- function handleTouchStart(event) {
87
+ function handleTouchStart(/** @type {TouchEvent} */ event) {
88
88
  if (zoomLevel === 0) return;
89
89
 
90
90
  // Only handle single touch for panning
@@ -99,7 +99,7 @@
99
99
  }
100
100
  }
101
101
 
102
- function handleTouchMove(event) {
102
+ function handleTouchMove(/** @type {TouchEvent} */ event) {
103
103
  if (!isDragging || event.touches.length !== 1) return;
104
104
 
105
105
  const deltaX = event.touches[0].clientX - dragStartX;
@@ -116,7 +116,7 @@
116
116
  }
117
117
 
118
118
  // Click handler that distinguishes between click and drag
119
- function handleClick(event) {
119
+ function handleClick(/** @type {MouseEvent} */ event) {
120
120
  // If we dragged more than 5px, don't treat as click
121
121
  if (totalDragDistance > 5) {
122
122
  totalDragDistance = 0;
@@ -128,6 +128,7 @@
128
128
 
129
129
  <svelte:window onmousemove={handleMouseMove} onmouseup={handleMouseUp} />
130
130
 
131
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions a11y_click_events_have_key_events a11y_no_noninteractive_element_to_interactive_role -->
131
132
  <img
132
133
  {src}
133
134
  {alt}
@@ -140,6 +141,8 @@
140
141
  ontouchmove={handleTouchMove}
141
142
  ontouchend={handleTouchEnd}
142
143
  onclick={handleClick}
144
+ role="button"
145
+ tabindex="0"
143
146
  />
144
147
 
145
148
  <style>
@@ -2,5 +2,5 @@ import { Accordion as AccordionPrimitive } from "bits-ui";
2
2
  import Content from "./accordion-content.svelte";
3
3
  import Item from "./accordion-item.svelte";
4
4
  import Trigger from "./accordion-trigger.svelte";
5
- declare const Root: import("svelte").Component<AccordionPrimitive.RootProps, {}, "value" | "ref">;
5
+ declare const Root: import("svelte").Component<AccordionPrimitive.RootProps, {}, "ref" | "value">;
6
6
  export { Root, Content, Item, Trigger, Root as Accordion, Content as AccordionContent, Item as AccordionItem, Trigger as AccordionTrigger, };
@@ -8,6 +8,6 @@ type Props = WithElementRef<Omit<HTMLInputAttributes, "type"> & ({
8
8
  type?: InputType;
9
9
  files?: undefined;
10
10
  })>;
11
- declare const Input: import("svelte").Component<Props, {}, "value" | "ref" | "files">;
11
+ declare const Input: import("svelte").Component<Props, {}, "ref" | "value" | "files">;
12
12
  type Input = ReturnType<typeof Input>;
13
13
  export default Input;
@@ -2,5 +2,5 @@ import { Tabs as TabsPrimitive } from "bits-ui";
2
2
  import Content from "./tabs-content.svelte";
3
3
  import List from "./tabs-list.svelte";
4
4
  import Trigger from "./tabs-trigger.svelte";
5
- declare const Root: import("svelte").Component<TabsPrimitive.RootProps, {}, "value" | "ref">;
5
+ declare const Root: import("svelte").Component<TabsPrimitive.RootProps, {}, "ref" | "value">;
6
6
  export { Root, Content, List, Trigger, Root as Tabs, Content as TabsContent, List as TabsList, Trigger as TabsTrigger, };
@@ -1,6 +1,6 @@
1
1
  import type { HTMLTextareaAttributes } from "svelte/elements";
2
2
  import type { WithElementRef } from "bits-ui";
3
3
  type Props = WithElementRef<HTMLTextareaAttributes>;
4
- declare const Textarea: import("svelte").Component<Props, {}, "value" | "ref">;
4
+ declare const Textarea: import("svelte").Component<Props, {}, "ref" | "value">;
5
5
  type Textarea = ReturnType<typeof Textarea>;
6
6
  export default Textarea;
@@ -16,6 +16,7 @@
16
16
  * @prop {string} [href] - Optional link href (renders as anchor element)
17
17
  * @prop {string} [class] - Additional CSS classes to apply
18
18
  * @prop {Snippet} [children] - Button content (text/icons/etc)
19
+ * @prop {HTMLButtonElement} [ref] - Reference to the underlying button element (bindable)
19
20
  *
20
21
  * @example
21
22
  * <Button variant="primary" size="lg">Save Changes</Button>
@@ -30,8 +31,10 @@
30
31
  variant?: ButtonVariant;
31
32
  size?: ButtonSize;
32
33
  disabled?: boolean;
34
+ href?: string;
33
35
  class?: string;
34
36
  children?: Snippet;
37
+ ref?: HTMLButtonElement | null;
35
38
  }
36
39
 
37
40
  let {
@@ -40,6 +43,7 @@
40
43
  disabled = false,
41
44
  class: className,
42
45
  children,
46
+ ref = $bindable(null),
43
47
  ...restProps
44
48
  }: Props = $props();
45
49
 
@@ -67,6 +71,7 @@
67
71
  </script>
68
72
 
69
73
  <ShadcnButton
74
+ bind:ref={ref}
70
75
  variant={shadcnVariant}
71
76
  size={shadcnSize}
72
77
  disabled={disabled}
@@ -12,6 +12,7 @@ type ButtonSize = "sm" | "md" | "lg" | "icon";
12
12
  * @prop {string} [href] - Optional link href (renders as anchor element)
13
13
  * @prop {string} [class] - Additional CSS classes to apply
14
14
  * @prop {Snippet} [children] - Button content (text/icons/etc)
15
+ * @prop {HTMLButtonElement} [ref] - Reference to the underlying button element (bindable)
15
16
  *
16
17
  * @example
17
18
  * <Button variant="primary" size="lg">Save Changes</Button>
@@ -26,9 +27,11 @@ interface Props extends Omit<HTMLButtonAttributes, "class"> {
26
27
  variant?: ButtonVariant;
27
28
  size?: ButtonSize;
28
29
  disabled?: boolean;
30
+ href?: string;
29
31
  class?: string;
30
32
  children?: Snippet;
33
+ ref?: HTMLButtonElement | null;
31
34
  }
32
- declare const Button: import("svelte").Component<Props, {}, "">;
35
+ declare const Button: import("svelte").Component<Props, {}, "ref">;
33
36
  type Button = ReturnType<typeof Button>;
34
37
  export default Button;
@@ -14,6 +14,7 @@
14
14
  * @prop {boolean} [required=false] - Whether input is required (shows asterisk)
15
15
  * @prop {boolean} [disabled=false] - Whether input is disabled
16
16
  * @prop {string} [class] - Additional CSS classes to apply
17
+ * @prop {HTMLInputElement} [ref] - Reference to the underlying input element (bindable)
17
18
  *
18
19
  * @example
19
20
  * <Input label="Email" type="email" bind:value={email} required />
@@ -33,6 +34,7 @@
33
34
  required?: boolean;
34
35
  disabled?: boolean;
35
36
  class?: string;
37
+ ref?: HTMLInputElement | null;
36
38
  }
37
39
 
38
40
  let {
@@ -44,6 +46,7 @@
44
46
  required = false,
45
47
  disabled = false,
46
48
  class: className,
49
+ ref = $bindable(null),
47
50
  ...restProps
48
51
  }: Props = $props();
49
52
 
@@ -66,6 +69,7 @@
66
69
  {/if}
67
70
 
68
71
  <ShadcnInput
72
+ bind:ref={ref}
69
73
  bind:value
70
74
  {type}
71
75
  {placeholder}
@@ -10,6 +10,7 @@ import type { HTMLInputAttributes } from "svelte/elements";
10
10
  * @prop {boolean} [required=false] - Whether input is required (shows asterisk)
11
11
  * @prop {boolean} [disabled=false] - Whether input is disabled
12
12
  * @prop {string} [class] - Additional CSS classes to apply
13
+ * @prop {HTMLInputElement} [ref] - Reference to the underlying input element (bindable)
13
14
  *
14
15
  * @example
15
16
  * <Input label="Email" type="email" bind:value={email} required />
@@ -29,7 +30,8 @@ interface Props extends Omit<HTMLInputAttributes, "class"> {
29
30
  required?: boolean;
30
31
  disabled?: boolean;
31
32
  class?: string;
33
+ ref?: HTMLInputElement | null;
32
34
  }
33
- declare const Input: import("svelte").Component<Props, {}, "value">;
35
+ declare const Input: import("svelte").Component<Props, {}, "ref" | "value">;
34
36
  type Input = ReturnType<typeof Input>;
35
37
  export default Input;
@@ -0,0 +1,86 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Grove Logo Component
4
+ *
5
+ * A logo that respects the user's accent color by default.
6
+ * The foliage uses `currentColor` which inherits from --accent-color
7
+ * when placed in an accent-colored context, or can be overridden.
8
+ *
9
+ * The trunk defaults to Grove's classic bark brown (#5d4037).
10
+ */
11
+
12
+ interface Props {
13
+ class?: string;
14
+ /** Foliage color - defaults to currentColor (inherits accent) */
15
+ color?: string;
16
+ /** Trunk color - defaults to classic bark brown */
17
+ trunkColor?: string;
18
+ /** Whether foliage and trunk should be the same color */
19
+ monochrome?: boolean;
20
+ /** Add subtle sway animation */
21
+ animate?: boolean;
22
+ /** Add breathing animation (for loading states) */
23
+ breathing?: boolean;
24
+ }
25
+
26
+ let {
27
+ class: className = 'w-6 h-6',
28
+ color,
29
+ trunkColor,
30
+ monochrome = false,
31
+ animate = false,
32
+ breathing = false
33
+ }: Props = $props();
34
+
35
+ // Classic bark brown from the nature palette
36
+ const BARK_BROWN = '#5d4037';
37
+
38
+ // Compute actual colors
39
+ const foliageColor = color ?? 'currentColor';
40
+ const actualTrunkColor = monochrome
41
+ ? foliageColor
42
+ : (trunkColor ?? BARK_BROWN);
43
+
44
+ // Build animation classes (using $derived for reactivity)
45
+ const animationClass = $derived(breathing ? 'grove-logo-breathe' : (animate ? 'grove-logo-sway' : ''));
46
+ </script>
47
+
48
+ <svg
49
+ class="{className} {animationClass}"
50
+ xmlns="http://www.w3.org/2000/svg"
51
+ viewBox="0 0 417 512.238"
52
+ aria-label="Grove logo"
53
+ >
54
+ <!-- Trunk -->
55
+ <path fill={actualTrunkColor} d="M171.274 344.942h74.09v167.296h-74.09V344.942z"/>
56
+ <!-- Foliage -->
57
+ <path fill={foliageColor} d="M0 173.468h126.068l-89.622-85.44 49.591-50.985 85.439 87.829V0h74.086v124.872L331 37.243l49.552 50.785-89.58 85.24H417v70.502H290.252l90.183 87.629L331 381.192 208.519 258.11 86.037 381.192l-49.591-49.591 90.218-87.631H0v-70.502z"/>
58
+ </svg>
59
+
60
+ <style>
61
+ @keyframes grove-logo-sway {
62
+ 0%, 100% { transform: rotate(0deg); }
63
+ 50% { transform: rotate(1deg); }
64
+ }
65
+
66
+ @keyframes grove-logo-breathe {
67
+ 0%, 100% {
68
+ transform: scale(1);
69
+ opacity: 0.7;
70
+ }
71
+ 50% {
72
+ transform: scale(1.05);
73
+ opacity: 1;
74
+ }
75
+ }
76
+
77
+ .grove-logo-sway {
78
+ transform-origin: center bottom;
79
+ animation: grove-logo-sway 4s ease-in-out infinite;
80
+ }
81
+
82
+ .grove-logo-breathe {
83
+ transform-origin: center center;
84
+ animation: grove-logo-breathe 2s ease-in-out infinite;
85
+ }
86
+ </style>
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Grove Logo Component
3
+ *
4
+ * A logo that respects the user's accent color by default.
5
+ * The foliage uses `currentColor` which inherits from --accent-color
6
+ * when placed in an accent-colored context, or can be overridden.
7
+ *
8
+ * The trunk defaults to Grove's classic bark brown (#5d4037).
9
+ */
10
+ interface Props {
11
+ class?: string;
12
+ /** Foliage color - defaults to currentColor (inherits accent) */
13
+ color?: string;
14
+ /** Trunk color - defaults to classic bark brown */
15
+ trunkColor?: string;
16
+ /** Whether foliage and trunk should be the same color */
17
+ monochrome?: boolean;
18
+ /** Add subtle sway animation */
19
+ animate?: boolean;
20
+ /** Add breathing animation (for loading states) */
21
+ breathing?: boolean;
22
+ }
23
+ declare const Logo: import("svelte").Component<Props, {}, "">;
24
+ type Logo = ReturnType<typeof Logo>;
25
+ export default Logo;
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Logo Loader Component
4
+ *
5
+ * A branded loading indicator using the Grove logo with breathing animation.
6
+ * Use instead of Spinner for a more on-brand loading experience.
7
+ *
8
+ * @prop {string} [size="md"] - Size variant (sm|md|lg|xl)
9
+ * @prop {string} [class] - Additional CSS classes
10
+ * @prop {string} [label] - Optional loading message
11
+ */
12
+ import Logo from './Logo.svelte';
13
+
14
+ type LoaderSize = 'sm' | 'md' | 'lg' | 'xl';
15
+
16
+ interface Props {
17
+ size?: LoaderSize;
18
+ class?: string;
19
+ label?: string;
20
+ }
21
+
22
+ let { size = 'md', class: className = '', label }: Props = $props();
23
+
24
+ const sizeClasses: Record<LoaderSize, string> = {
25
+ sm: 'w-6 h-6',
26
+ md: 'w-10 h-10',
27
+ lg: 'w-16 h-16',
28
+ xl: 'w-24 h-24'
29
+ };
30
+ </script>
31
+
32
+ <div
33
+ class="logo-loader {className}"
34
+ role="status"
35
+ aria-label={label || 'Loading'}
36
+ >
37
+ <Logo class={sizeClasses[size]} breathing monochrome />
38
+ {#if label}
39
+ <span class="loader-label">{label}</span>
40
+ {/if}
41
+ <span class="sr-only">{label || 'Loading...'}</span>
42
+ </div>
43
+
44
+ <style>
45
+ .logo-loader {
46
+ display: flex;
47
+ flex-direction: column;
48
+ align-items: center;
49
+ justify-content: center;
50
+ gap: 0.75rem;
51
+ color: var(--accent-color, var(--editor-accent, #8bc48b));
52
+ }
53
+
54
+ .loader-label {
55
+ font-size: 0.85rem;
56
+ color: var(--color-text-muted, #6a6a6a);
57
+ font-family: "JetBrains Mono", "Fira Code", monospace;
58
+ }
59
+
60
+ .sr-only {
61
+ position: absolute;
62
+ width: 1px;
63
+ height: 1px;
64
+ padding: 0;
65
+ margin: -1px;
66
+ overflow: hidden;
67
+ clip: rect(0, 0, 0, 0);
68
+ white-space: nowrap;
69
+ border-width: 0;
70
+ }
71
+ </style>
@@ -0,0 +1,9 @@
1
+ type LoaderSize = 'sm' | 'md' | 'lg' | 'xl';
2
+ interface Props {
3
+ size?: LoaderSize;
4
+ class?: string;
5
+ label?: string;
6
+ }
7
+ declare const LogoLoader: import("svelte").Component<Props, {}, "">;
8
+ type LogoLoader = ReturnType<typeof LogoLoader>;
9
+ export default LogoLoader;
@@ -13,6 +13,8 @@ export { default as Skeleton } from './Skeleton.svelte';
13
13
  export { default as Spinner } from './Spinner.svelte';
14
14
  export { default as Table } from './Table.svelte';
15
15
  export { default as CollapsibleSection } from './CollapsibleSection.svelte';
16
+ export { default as Logo } from './Logo.svelte';
17
+ export { default as LogoLoader } from './LogoLoader.svelte';
16
18
  export { TableHeader, TableBody, TableRow, TableCell, TableHead, TableFooter, TableCaption, } from '../primitives/table';
17
19
  export * from './toast.js';
18
20
  export declare const UI_VERSION = "0.2.0";
@@ -21,6 +21,8 @@ export { default as Skeleton } from './Skeleton.svelte';
21
21
  export { default as Spinner } from './Spinner.svelte';
22
22
  export { default as Table } from './Table.svelte';
23
23
  export { default as CollapsibleSection } from './CollapsibleSection.svelte';
24
+ export { default as Logo } from './Logo.svelte';
25
+ export { default as LogoLoader } from './LogoLoader.svelte';
24
26
  // Table sub-components (from primitives)
25
27
  export { TableHeader, TableBody, TableRow, TableCell, TableHead, TableFooter, TableCaption, } from '../primitives/table';
26
28
  // Toast utility
@@ -333,27 +333,27 @@ export default {
333
333
  // Base prose styling
334
334
  '.grove-prose': {
335
335
  color: theme('colors.bark.DEFAULT'),
336
- fontSize: theme('fontSize.body')[0],
337
- lineHeight: theme('fontSize.body')[1].lineHeight,
336
+ fontSize: /** @type {[string, {lineHeight: string}]} */ (theme('fontSize.body'))[0],
337
+ lineHeight: /** @type {[string, {lineHeight: string}]} */ (theme('fontSize.body'))[1].lineHeight,
338
338
  '& h1, & h2, & h3, & h4, & h5, & h6': {
339
339
  fontFamily: 'Georgia, Cambria, "Times New Roman", Times, serif',
340
340
  fontWeight: '400',
341
341
  color: theme('colors.bark.DEFAULT'),
342
342
  },
343
343
  '& h1': {
344
- fontSize: theme('fontSize.display')[0],
345
- lineHeight: theme('fontSize.display')[1].lineHeight,
344
+ fontSize: /** @type {[string, {lineHeight: string}]} */ (theme('fontSize.display'))[0],
345
+ lineHeight: /** @type {[string, {lineHeight: string}]} */ (theme('fontSize.display'))[1].lineHeight,
346
346
  marginBottom: '1.5rem',
347
347
  },
348
348
  '& h2': {
349
- fontSize: theme('fontSize.display-sm')[0],
350
- lineHeight: theme('fontSize.display-sm')[1].lineHeight,
349
+ fontSize: /** @type {[string, {lineHeight: string}]} */ (theme('fontSize.display-sm'))[0],
350
+ lineHeight: /** @type {[string, {lineHeight: string}]} */ (theme('fontSize.display-sm'))[1].lineHeight,
351
351
  marginTop: '2.5rem',
352
352
  marginBottom: '1rem',
353
353
  },
354
354
  '& h3': {
355
- fontSize: theme('fontSize.heading-lg')[0],
356
- lineHeight: theme('fontSize.heading-lg')[1].lineHeight,
355
+ fontSize: /** @type {[string, {lineHeight: string}]} */ (theme('fontSize.heading-lg'))[0],
356
+ lineHeight: /** @type {[string, {lineHeight: string}]} */ (theme('fontSize.heading-lg'))[1].lineHeight,
357
357
  marginTop: '2rem',
358
358
  marginBottom: '0.75rem',
359
359
  },
package/dist/utils/api.js CHANGED
@@ -46,8 +46,9 @@ export async function apiRequest(url, options = {}) {
46
46
  }
47
47
 
48
48
  // Build headers - don't set Content-Type for FormData (browser sets it with boundary)
49
+ /** @type {Record<string, string>} */
49
50
  const headers = {
50
- ...options.headers,
51
+ ...(/** @type {Record<string, string>} */ (options.headers || {})),
51
52
  };
52
53
 
53
54
  // Only add Content-Type if not FormData
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Debounce function calls
3
- * @param {Function} fn - Function to debounce
3
+ * @template {(...args: any[]) => any} T
4
+ * @param {T} fn - Function to debounce
4
5
  * @param {number} delay - Delay in milliseconds
5
- * @returns {Function} Debounced function
6
+ * @returns {(...args: Parameters<T>) => void} Debounced function
6
7
  */
7
- export function debounce(fn: Function, delay?: number): Function;
8
+ export function debounce<T extends (...args: any[]) => any>(fn: T, delay?: number): (...args: Parameters<T>) => void;
@@ -1,14 +1,18 @@
1
1
  /**
2
2
  * Debounce function calls
3
- * @param {Function} fn - Function to debounce
3
+ * @template {(...args: any[]) => any} T
4
+ * @param {T} fn - Function to debounce
4
5
  * @param {number} delay - Delay in milliseconds
5
- * @returns {Function} Debounced function
6
+ * @returns {(...args: Parameters<T>) => void} Debounced function
6
7
  */
7
8
  export function debounce(fn, delay = 300) {
8
- let timeoutId;
9
+ /** @type {ReturnType<typeof setTimeout> | null} */
10
+ let timeoutId = null;
9
11
 
10
- return function (...args) {
11
- clearTimeout(timeoutId);
12
- timeoutId = setTimeout(() => fn.apply(this, args), delay);
12
+ return (/** @type {Parameters<T>} */ ...args) => {
13
+ if (timeoutId !== null) {
14
+ clearTimeout(timeoutId);
15
+ }
16
+ timeoutId = setTimeout(() => fn(...args), delay);
13
17
  };
14
18
  }