From 212ecc161514fdf4d209929ff0d1fee8197da542 Mon Sep 17 00:00:00 2001
From: Simon Jockers <449739+sjockers@users.noreply.github.com>
Date: Thu, 9 Oct 2025 11:03:42 +0200
Subject: [PATCH 11/13] Use `#snippets`/`@render` instead of (deprecated)
`slot`
---
.../src/Scroller/Scroller.stories.svelte | 15 +++---
components/src/Scroller/Scroller.svelte | 52 ++++++++++---------
2 files changed, 35 insertions(+), 32 deletions(-)
diff --git a/components/src/Scroller/Scroller.stories.svelte b/components/src/Scroller/Scroller.stories.svelte
index ae057e85..7015ad72 100644
--- a/components/src/Scroller/Scroller.stories.svelte
+++ b/components/src/Scroller/Scroller.stories.svelte
@@ -31,13 +31,13 @@
});
await step('Foreground content is rendered', async () => {
- const sections = canvasElement.querySelectorAll('[slot="foreground"] section');
+ const sections = canvasElement.querySelectorAll('svelte-scroller-foreground section');
expect(sections).toHaveLength(5);
expect(sections[0]).toHaveTextContent('This is the first section.');
});
await step('Background content is rendered', async () => {
- const background = canvasElement.querySelector('[slot="background"]');
+ const background = canvasElement.querySelector('svelte-scroller-background');
expect(background).toBeDefined();
expect(background).toHaveTextContent('This is the background content');
});
@@ -55,14 +55,14 @@
// Add the event listener with the stored function reference
window.addEventListener('scrollend', scrollEndHandler);
- const sections = canvasElement.querySelectorAll('[slot="foreground"] section');
+ const sections = canvasElement.querySelectorAll('svelte-scroller-foreground section');
sections[3].scrollIntoView();
});
}}
>
-
+ {#snippet background()}
This is the background content. It will stay fixed in place while the foreground scrolls
over the top.
@@ -74,14 +74,15 @@
index / {index}
offset / {offset}
progress / {progress}
-
-
+ {/snippet}
+
+ {#snippet foreground()}
This is the first section.
This is the second section.
This is the third section.
This is the fourth section.
This is the fifth section.
-
+ {/snippet}
diff --git a/components/src/Scroller/Scroller.svelte b/components/src/Scroller/Scroller.svelte
index 256c9295..55bdb7d4 100644
--- a/components/src/Scroller/Scroller.svelte
+++ b/components/src/Scroller/Scroller.svelte
@@ -4,7 +4,7 @@
}
interface ScrollerInstance {
- outer: HTMLElement;
+ outerWrapper: HTMLElement;
update: ScrollHandler;
}
@@ -56,26 +56,26 @@
);
manager = {
- add: ({ outer, update }: ScrollerInstance): void => {
- const { top, bottom } = outer.getBoundingClientRect();
+ add: ({ outerWrapper, update }: ScrollerInstance): void => {
+ const { top, bottom } = outerWrapper.getBoundingClientRect();
// Add handler if element is initially visible
if (top < window.innerHeight && bottom > 0) {
handlers.push(update);
}
- handlerMap.set(outer, update);
- observer.observe(outer);
+ handlerMap.set(outerWrapper, update);
+ observer.observe(outerWrapper);
},
- remove: ({ outer, update }: ScrollerInstance): void => {
+ remove: ({ outerWrapper, update }: ScrollerInstance): void => {
const handlerIndex = handlers.indexOf(update);
if (handlerIndex !== -1) {
handlers.splice(handlerIndex, 1);
}
- handlerMap.delete(outer);
- observer.unobserve(outer);
+ handlerMap.delete(outerWrapper);
+ observer.unobserve(outerWrapper);
}
};
} else {
@@ -180,13 +180,15 @@
count = $bindable(0),
offset = $bindable(0),
progress = $bindable(0),
- visible = $bindable(false)
+ visible = $bindable(false),
+ foreground = null,
+ background = null
}: ScrollerProps = $props();
// Element bindings
- let outer = $state
();
- let foreground = $state();
- let background = $state();
+ let outerWrapper = $state();
+ let foregroundWrapper = $state();
+ let backgroundWrapper = $state();
// Internal state
let sections = $state>();
@@ -213,14 +215,14 @@
});
onMount(() => {
- if (!foreground) return;
+ if (!foregroundWrapper) return;
- sections = foreground.querySelectorAll(query);
+ sections = foregroundWrapper.querySelectorAll(query);
count = sections.length;
update();
- const scrollerInstance: ScrollerInstance = { outer, update };
+ const scrollerInstance: ScrollerInstance = { outerWrapper, update };
manager.add(scrollerInstance);
return () => {
@@ -229,7 +231,7 @@
});
function update(): void {
- if (!foreground || !background || !outer) return;
+ if (!foregroundWrapper || !backgroundWrapper || !outerWrapper) return;
updateContainerMeasurements();
updateScrollProgress();
@@ -237,14 +239,14 @@
}
function updateContainerMeasurements(): void {
- const outerRect = outer.getBoundingClientRect();
+ const outerRect = outerWrapper.getBoundingClientRect();
containerLeft = outerRect.left;
containerWidth = outerRect.right - outerRect.left;
}
function updateScrollProgress(): void {
- const foregroundRect = foreground.getBoundingClientRect();
- const backgroundRect = background.getBoundingClientRect();
+ const foregroundRect = foregroundWrapper.getBoundingClientRect();
+ const backgroundRect = backgroundWrapper.getBoundingClientRect();
visible = foregroundRect.top < windowHeight && foregroundRect.bottom > 0;
@@ -262,7 +264,7 @@
function updateActiveSection(): void {
if (!sections?.length) return;
- const foregroundRect = foreground.getBoundingClientRect();
+ const foregroundRect = foregroundWrapper.getBoundingClientRect();
for (let i = 0; i < sections.length; i++) {
const section = sections[i];
@@ -286,15 +288,15 @@
-
+
-
-
+
+ {@render background()}
-
-
+
+ {@render foreground()}
From 27103fe5fefdac6d372e28d6769662fbfdbe0881 Mon Sep 17 00:00:00 2001
From: Simon Jockers <449739+sjockers@users.noreply.github.com>
Date: Thu, 9 Oct 2025 11:22:44 +0200
Subject: [PATCH 12/13] Update docs
---
components/src/Scroller/Scroller.svelte | 29 +++++++++++++------------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/components/src/Scroller/Scroller.svelte b/components/src/Scroller/Scroller.svelte
index 55bdb7d4..f6ed8daa 100644
--- a/components/src/Scroller/Scroller.svelte
+++ b/components/src/Scroller/Scroller.svelte
@@ -102,87 +102,88 @@
* Props interface for the Scroller component
*/
interface ScrollerProps {
+ /**
+ * The content to show in the foreground, to be set via `{#snippet foreground()}`. This should contain multiple sections, each matching the `query` selector.
+ */
+ foreground?: Snippet;
+
+ /**
+ * The content to show in the background, to be set via `{#snippet background()}`. This will be fixed in place while the foreground scrolls over it.
+ */
+ background?: Snippet;
+
/**
* The vertical position that the top of the foreground must scroll past before the background becomes fixed,
* as a proportion of window height (0 = top of viewport, 1 = bottom of viewport)
- * @default 0
*/
top?: number;
/**
* The inverse of top — once the bottom of the foreground passes this point, the background becomes unfixed.
* As a proportion of window height (0 = top of viewport, 1 = bottom of viewport)
- * @default 1
*/
bottom?: number;
/**
* Once a section crosses this point, it becomes 'active'.
* As a proportion of window height (0 = top of viewport, 1 = bottom of viewport)
- * @default 0.5
*/
threshold?: number;
/**
* A CSS selector that describes the individual sections of your foreground
- * @default 'section'
*/
query?: string;
/**
* If true, the background will scroll such that the bottom edge reaches the bottom at the same time as the foreground.
* This effect can be unpleasant for people with high motion sensitivity, so use it advisedly.
- * @default false
*/
parallax?: boolean;
/**
* The index of the currently active section (bindable)
- * @default 0
*/
index?: number;
/**
* The total number of sections (bindable)
- * @default 0
*/
count?: number;
/**
* How far the section has scrolled past the threshold, as a value between 0 and 1 (bindable)
- * @default 0
*/
offset?: number;
/**
* How far the foreground has travelled, where 0 is the top of the foreground crossing top,
* and 1 is the bottom crossing bottom (bindable)
- * @default 0
*/
progress?: number;
/**
* Whether the scroller is currently visible in the viewport (bindable)
- * @default false
*/
visible?: boolean;
}
- // Configuration props (read-only)
let {
+ // Configuration props (read-only)
+ foreground = null,
+ background = null,
top = 0,
bottom = 1,
threshold = 0.5,
query = 'section',
parallax = false,
+
// Binding props (two-way binding)
index = $bindable(0),
count = $bindable(0),
offset = $bindable(0),
progress = $bindable(0),
- visible = $bindable(false),
- foreground = null,
- background = null
+ visible = $bindable(false)
}: ScrollerProps = $props();
// Element bindings
From 7676813760e31b3dd545f0c5e741e849559526fb Mon Sep 17 00:00:00 2001
From: Simon Jockers <449739+sjockers@users.noreply.github.com>
Date: Thu, 9 Oct 2025 14:40:42 +0200
Subject: [PATCH 13/13] Add component export
---
components/src/index.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/components/src/index.js b/components/src/index.js
index 347844e9..b93bed42 100644
--- a/components/src/index.js
+++ b/components/src/index.js
@@ -11,6 +11,7 @@ export { default as Note } from './Note/Note.svelte';
// Display
export { default as Card } from './Card/Card.svelte';
+export { default as Scroller } from './Scroller/Scroller.svelte';
// Chart
export { default as ChartHeader } from './ChartHeader/ChartHeader.svelte';