@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.
- package/dist/auth/jwt.d.ts +10 -4
- package/dist/auth/jwt.js +18 -4
- package/dist/auth/session.d.ts +22 -15
- package/dist/auth/session.js +35 -16
- package/dist/components/admin/GutterManager.svelte +81 -139
- package/dist/components/admin/GutterManager.svelte.d.ts +6 -6
- package/dist/components/admin/MarkdownEditor.svelte +80 -23
- package/dist/components/admin/MarkdownEditor.svelte.d.ts +14 -8
- package/dist/components/admin/composables/useAmbientSounds.svelte.d.ts +52 -2
- package/dist/components/admin/composables/useAmbientSounds.svelte.js +38 -4
- package/dist/components/admin/composables/useCommandPalette.svelte.d.ts +80 -10
- package/dist/components/admin/composables/useCommandPalette.svelte.js +45 -5
- package/dist/components/admin/composables/useDraftManager.svelte.d.ts +76 -14
- package/dist/components/admin/composables/useDraftManager.svelte.js +44 -10
- package/dist/components/admin/composables/useEditorTheme.svelte.d.ts +168 -2
- package/dist/components/admin/composables/useEditorTheme.svelte.js +40 -7
- package/dist/components/admin/composables/useSlashCommands.svelte.d.ts +94 -22
- package/dist/components/admin/composables/useSlashCommands.svelte.js +58 -9
- package/dist/components/admin/composables/useSnippets.svelte.d.ts +51 -2
- package/dist/components/admin/composables/useSnippets.svelte.js +35 -3
- package/dist/components/admin/composables/useWritingSession.svelte.d.ts +64 -6
- package/dist/components/admin/composables/useWritingSession.svelte.js +42 -5
- package/dist/components/custom/ContentWithGutter.svelte +53 -23
- package/dist/components/custom/ContentWithGutter.svelte.d.ts +6 -14
- package/dist/components/custom/GutterItem.svelte +1 -1
- package/dist/components/custom/LeftGutter.svelte +43 -13
- package/dist/components/custom/LeftGutter.svelte.d.ts +6 -6
- package/dist/config/ai-models.js +1 -1
- package/dist/groveauth/client.js +11 -11
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -2
- package/dist/server/logger.d.ts +74 -26
- package/dist/server/logger.js +133 -184
- package/dist/server/services/cache.js +1 -10
- package/dist/ui/components/charts/ActivityOverview.svelte +14 -3
- package/dist/ui/components/charts/ActivityOverview.svelte.d.ts +10 -7
- package/dist/ui/components/charts/RepoBreakdown.svelte +9 -3
- package/dist/ui/components/charts/RepoBreakdown.svelte.d.ts +12 -11
- package/dist/ui/components/charts/Sparkline.svelte +18 -7
- package/dist/ui/components/charts/Sparkline.svelte.d.ts +21 -2
- package/dist/ui/components/gallery/ImageGallery.svelte +12 -8
- package/dist/ui/components/gallery/ImageGallery.svelte.d.ts +2 -2
- package/dist/ui/components/gallery/Lightbox.svelte +5 -2
- package/dist/ui/components/gallery/ZoomableImage.svelte +8 -5
- package/dist/ui/components/primitives/accordion/index.d.ts +1 -1
- package/dist/ui/components/primitives/input/input.svelte.d.ts +1 -1
- package/dist/ui/components/primitives/tabs/index.d.ts +1 -1
- package/dist/ui/components/primitives/textarea/textarea.svelte.d.ts +1 -1
- package/dist/ui/components/ui/Button.svelte +5 -0
- package/dist/ui/components/ui/Button.svelte.d.ts +4 -1
- package/dist/ui/components/ui/Input.svelte +4 -0
- package/dist/ui/components/ui/Input.svelte.d.ts +3 -1
- package/dist/ui/components/ui/Logo.svelte +86 -0
- package/dist/ui/components/ui/Logo.svelte.d.ts +25 -0
- package/dist/ui/components/ui/LogoLoader.svelte +71 -0
- package/dist/ui/components/ui/LogoLoader.svelte.d.ts +9 -0
- package/dist/ui/components/ui/index.d.ts +2 -0
- package/dist/ui/components/ui/index.js +2 -0
- package/dist/ui/tailwind.preset.js +8 -8
- package/dist/utils/api.js +2 -1
- package/dist/utils/debounce.d.ts +4 -3
- package/dist/utils/debounce.js +10 -6
- package/dist/utils/gallery.d.ts +58 -32
- package/dist/utils/gallery.js +111 -129
- package/dist/utils/gutter.d.ts +47 -26
- package/dist/utils/gutter.js +116 -124
- package/dist/utils/imageProcessor.d.ts +66 -19
- package/dist/utils/imageProcessor.js +31 -10
- package/dist/utils/index.d.ts +11 -11
- package/dist/utils/index.js +4 -3
- package/dist/utils/json.js +1 -1
- package/dist/utils/markdown.d.ts +183 -103
- package/dist/utils/markdown.js +517 -678
- package/dist/utils/sanitize.d.ts +22 -12
- package/dist/utils/sanitize.js +268 -282
- package/dist/utils/validation.js +4 -3
- 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
|
-
|
|
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}`}
|
|
@@ -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, {}, "
|
|
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, {}, "
|
|
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, {}, "
|
|
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, {}, "
|
|
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
|
package/dist/utils/debounce.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Debounce function calls
|
|
3
|
-
* @
|
|
3
|
+
* @template {(...args: any[]) => any} T
|
|
4
|
+
* @param {T} fn - Function to debounce
|
|
4
5
|
* @param {number} delay - Delay in milliseconds
|
|
5
|
-
* @returns {
|
|
6
|
+
* @returns {(...args: Parameters<T>) => void} Debounced function
|
|
6
7
|
*/
|
|
7
|
-
export function debounce(fn:
|
|
8
|
+
export function debounce<T extends (...args: any[]) => any>(fn: T, delay?: number): (...args: Parameters<T>) => void;
|
package/dist/utils/debounce.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Debounce function calls
|
|
3
|
-
* @
|
|
3
|
+
* @template {(...args: any[]) => any} T
|
|
4
|
+
* @param {T} fn - Function to debounce
|
|
4
5
|
* @param {number} delay - Delay in milliseconds
|
|
5
|
-
* @returns {
|
|
6
|
+
* @returns {(...args: Parameters<T>) => void} Debounced function
|
|
6
7
|
*/
|
|
7
8
|
export function debounce(fn, delay = 300) {
|
|
8
|
-
|
|
9
|
+
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
10
|
+
let timeoutId = null;
|
|
9
11
|
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
return (/** @type {Parameters<T>} */ ...args) => {
|
|
13
|
+
if (timeoutId !== null) {
|
|
14
|
+
clearTimeout(timeoutId);
|
|
15
|
+
}
|
|
16
|
+
timeoutId = setTimeout(() => fn(...args), delay);
|
|
13
17
|
};
|
|
14
18
|
}
|