Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1ed0e97
Log debug information
sjockers Feb 23, 2026
f013be0
Refactor getDataFromUrl (less specific to domains, add error logging)
sjockers Feb 25, 2026
81ccbf2
Alternative setup: Svelte only, clientside rendering
sjockers Mar 25, 2026
1a3bb30
Run storybook workflows for components directory only
sjockers Mar 26, 2026
89de4b1
Resolve individual imports from @swr-data-lab/components
sjockers Mar 26, 2026
0450de9
Use ASSETS_PATH env variable in build process
sjockers Mar 26, 2026
f5c0bd7
Move JS to body (so that it is picked up by the sophora embedding)
sjockers Mar 26, 2026
c8275b4
Mount multiple instances of an embed on a single page.
sjockers Mar 26, 2026
541db3f
Show preview links in index page
sjockers Mar 30, 2026
92acb4b
Refactor project structure
sjockers Mar 30, 2026
c7b5f3a
Reduce boilerplate
sjockers Mar 31, 2026
7780413
Remove debugger logging
sjockers Mar 31, 2026
74df49f
Move mounting of embeds to utility function
sjockers Mar 31, 2026
1ee7fcc
Re-mount on pjax:complete
sjockers Apr 1, 2026
136df97
Add popstate handler
sjockers Apr 1, 2026
b404424
Expose mounting function via window object
sjockers Apr 1, 2026
2e02957
Use previous sibling as target, no more initialization loop
sjockers Apr 1, 2026
bf82451
Update DOM loaded handling
sjockers Apr 2, 2026
ec180fc
Move initialization for datawrapper-switcher into module
sjockers Apr 2, 2026
8fb8f1c
Add PJAX fallback for re-mounting embeds upon PJAX navigation
sjockers Apr 2, 2026
4a41d66
Defer initialization after PJAX navigation
sjockers Apr 2, 2026
8301b31
Remove debug logs and redundant styling
sjockers Apr 2, 2026
74e13fc
Add log for validating mount cycle
sjockers Apr 2, 2026
4c91240
Poll for DOM instead of simple setTimeout
sjockers Apr 2, 2026
ce7c6ce
Merge main into feat/sophora-components-clientside-only
sjockers Apr 2, 2026
54f5b5d
Simplify polling
sjockers Apr 2, 2026
2cf07d1
Remove logging, adjust delay
sjockers Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 173 additions & 82 deletions package-lock.json

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions sophora-components/datawrapper-switcher.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div data-lab-components-embed="datawrapper-switcher"></div>
<script type="module">
import mountEmbeds from '/src/lib/utils/mountEmbeds';
import Embed from '/src/components/datawrapper-switcher/embed.svelte';

mountEmbeds(Embed, '[data-lab-components-embed="datawrapper-switcher"]');
</script>
</body>
</html>
16 changes: 16 additions & 0 deletions sophora-components/highlight-cards.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div data-lab-components-embed="highlight-cards"></div>
<script type="module">
import mountEmbeds from '/src/lib/utils/mountEmbeds';
import Embed from '/src/components/highlight-cards/embed.svelte';

mountEmbeds(Embed, '[data-lab-components-embed="highlight-cards"]');
</script>
</body>
</html>
15 changes: 10 additions & 5 deletions sophora-components/src/app.html → sophora-components/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

%sveltekit.head%

<style>
@font-face {
font-family: 'SWR-VAR-Sans';
Expand All @@ -25,7 +22,15 @@
}
</style>
</head>
<body data-sveltekit-preload-data="hover">
<div id="data-lab-components-embed">%sveltekit.body%</div>
<body>
<div data-lab-components-embed="swr-sophora-components"></div>
<script type="module">
import { mount } from 'svelte';
import IndexPage from '/src/index.svelte';

mount(IndexPage, {
target: document.querySelector('[data-lab-components-embed="swr-sophora-components"]')
});
</script>
</body>
</html>
11 changes: 5 additions & 6 deletions sophora-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "svelte-kit sync && vite build",
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check . && eslint ."
},
"devDependencies": {
"@eslint/compat": "^2.0.2",
"@eslint/js": "^9.39.2",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.53.4",
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@swr-data-lab/components": "^2.43.0",
"@types/jquery": "^4.0.0",
"@types/node": "^22",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
Expand Down
13 changes: 0 additions & 13 deletions sophora-components/src/app.d.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script>
import getDataFromUrl from '$lib/utils/getDataFromUrl';
import { Switcher, DesignTokens } from '@swr-data-lab/components';
import Switcher from '@swr-data-lab/components/dist/Switcher/Switcher.svelte';
import DesignTokens from '@swr-data-lab/components/dist/DesignTokens/DesignTokens.svelte';
import { onMount } from 'svelte';

let activeIndex = $state(0);
Expand All @@ -11,11 +12,15 @@
let layout = $state('auto');

onMount(() => {
const entries = getDataFromUrl(root);
labels = entries.labels.split(',') || [];
ids = entries.ids.split(',') || [];
fixedHeight = entries.fixedHeight || null;
layout = entries.layout || 'auto';
try {
const entries = getDataFromUrl(root);
labels = entries.labels?.split(',') || [];
ids = entries.ids?.split(',') || [];
fixedHeight = entries.fixedHeight || null;
layout = entries.layout || 'auto';
} catch (e) {
console.error(e);
}
});
</script>

Expand All @@ -33,7 +38,7 @@
/>

<div class="datawrapper-chart-container">
{#each ids as id, index}
{#each ids as id, index (id)}
<div
class="datawrapper-chart"
class:datawrapper-chart-active={index === activeIndex}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script>
import { DevContainer } from '@swr-data-lab/components';
import DevContainer from '@swr-data-lab/components/dist/DevContainer/DevContainer.svelte';
import DatawrapperSwitcher from './DatawrapperSwitcher.svelte';
import { dev } from '$app/environment';

const dev = import.meta.env.DEV;
</script>

{#if dev}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script>
import getDataFromUrl from '$lib/utils/getDataFromUrl';
import { DesignTokens, HighlightCard } from '@swr-data-lab/components';
import DesignTokens from '@swr-data-lab/components/dist/DesignTokens/DesignTokens.svelte';
import HighlightCard from '@swr-data-lab/components/dist/HighlightCard/HighlightCard.svelte';
import { onMount } from 'svelte';

let root;
Expand All @@ -14,7 +15,7 @@

<div class="highlight-cards" bind:this={root}>
<DesignTokens>
{#each entries as entry}
{#each entries as entry, index (index)}
<HighlightCard
topline={entry.topline}
value={entry.value}
Expand All @@ -35,5 +36,10 @@
@media (min-width: 1200px) {
grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Explicitly set background color for dark mode, workaround for SWR Aktuell App */
@media (prefers-color-scheme: dark) {
background-color: #0c0c0c;
}
}
</style>
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script>
import DevContainer from '@swr-data-lab/components/dist/DevContainer/DevContainer.svelte';
import HighlightCards from './HighlightCards.svelte';
import { dev } from '$app/environment';
import { DevContainer } from '@swr-data-lab/components';

const dev = import.meta.env.DEV;
</script>

{#if dev}
Expand Down
15 changes: 0 additions & 15 deletions sophora-components/src/hooks.server.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
<script>
import { ChartList, DesignTokens } from '@swr-data-lab/components';
import { dev } from '$app/environment';

// Example parameters for the charts, can be used in development to prefill the charts with data
// TODO: Find a better way to manage example parameters, e.g. using Storybook or a list of example URLs
const EXAMPLE_PARAMS = {
Expand All @@ -14,19 +11,20 @@
const charts = [
{
title: 'Highlight Cards',
slug: `highlight-cards${dev ? EXAMPLE_PARAMS.HIGHLIGHT_CARDS : ''}`
slug: `highlight-cards${EXAMPLE_PARAMS.HIGHLIGHT_CARDS}`
},
{
title: 'Datawrapper Switcher',
slug: `datawrapper-switcher${dev ? EXAMPLE_PARAMS.DATAWRAPPER_SWITCHER : ''}`
slug: `datawrapper-switcher${EXAMPLE_PARAMS.DATAWRAPPER_SWITCHER}`
}
];
</script>

<DesignTokens theme="auto">
<ChartList
baseUrl="https://static.datenhub.net/apps/sophora-components/main"
project="Sophora-Components"
{charts}
/>
</DesignTokens>
<h1>Demo Embeds</h1>
{#each charts as chart}
<div class="chart-link">
<a href={`./${chart.slug}`}>
{chart.title}
</a>
</div>
{/each}
6 changes: 6 additions & 0 deletions sophora-components/src/lib/shims/app-environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Shim for $app/environment used by @swr-data-lab/components
// Replaces SvelteKit's built-in module in this pure Vite setup
export const browser = typeof window !== 'undefined';
export const dev = import.meta.env.DEV;
export const building = false;
export const version = '';
29 changes: 12 additions & 17 deletions sophora-components/src/lib/utils/getDataFromUrl.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
export default function getDataFromUrl(target: HTMLElement): Record<string, string> {
let url: URL;
if (
// SvelteKit DEV mode, preview server, or static hosting:
import.meta.env.DEV ||
window.location.origin === 'http://localhost:4173' ||
window.location.origin === 'https://static.datenhub.net' ||
window.location.href.includes('apidata.googleusercontent.com') ||
window.location.href.includes('storage.googleapis.com')
) {
const parent = target.parentNode?.parentNode as HTMLElement | null;

// Default: Embedded mode – use URL used to embed the component
// `data-url` is the embeds the grandparent element, provided by Sophora
let embedURL = parent?.dataset.url;

if (!embedURL) {
// Preview mode – use URL of current page
url = new URL(window.location.href);
} else {
// Embedded mode – use URL used to embed the component
// `data-url` is set on the grandparent element, provided by Sophora
const parent = target.parentNode?.parentNode as HTMLElement | null;
url = new URL(parent?.dataset.url || '');
embedURL = window?.location.href;
}
const params: Record<string, string> = Object.fromEntries(url.searchParams);
return params;

return URL.canParse(embedURL)
? Object.fromEntries(new URL(embedURL).searchParams.entries())
: (console.error('Could not parse Embed-URL:', embedURL), {});
}
46 changes: 46 additions & 0 deletions sophora-components/src/lib/utils/mountEmbeds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { mount } from 'svelte';
import type { SvelteComponent } from 'svelte';

declare global {
interface Window {
$?: unknown;
}
}

export default async function mountEmbeds(
Component: typeof SvelteComponent,
targetSelector: string
): Promise<void> {
const _mountEmbeds = (embedTargets: NodeListOf<Element>) => {
embedTargets.forEach((target) => {
target.innerHTML = ''; // Clear any existing content before mounting
mount(Component, { target });
});
};

// Mount the embeds on initial page load
_mountEmbeds(document.querySelectorAll(targetSelector));

// SWR.dev uses PJAX and jQuery for client-side navigation, which replaces page content without a full reload.
// This means we need to re-mount the Svelte components after PJAX updates the DOM during client-side navigation.
// https://github.com/defunkt/jquery-pjax?tab=readme-ov-file#reinitializing-pluginswidget-on-new-page-content
if (window.$) {
$(document).on('pjax:end', () => {
const maxPolls = 10;
const pollDelay = 300; // ms
let pollCount = 0;

// Delay to ensure new DOM is ready for mounting.
// PJAX triggers 'pjax:end' before the mounting targets are actually available in the DOM.
const interval = window.setInterval(() => {
const embedTargets = document.querySelectorAll(targetSelector);
if (embedTargets.length > 0) {
_mountEmbeds(embedTargets);
window.clearInterval(interval);
} else if (++pollCount >= maxPolls) {
window.clearInterval(interval);
}
}, pollDelay);
});
}
}
1 change: 0 additions & 1 deletion sophora-components/src/routes/+layout.js

This file was deleted.

17 changes: 0 additions & 17 deletions sophora-components/svelte.config.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,6 @@
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

const dev = process.argv.includes('dev');

/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
paths: {
base: dev ? '' : process.env.BASE_PATH,
assets: dev ? '' : process.env.ASSETS_PATH
},
adapter: adapter({
pages: 'build',
assets: 'build',
fallback: undefined,
precompress: false,
strict: true
})
},
preprocess: [vitePreprocess()]
};

Expand Down
11 changes: 8 additions & 3 deletions sophora-components/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"target": "es2022",
"module": "es2022",
"moduleResolution": "bundler",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
"strict": true,
"paths": {
"$lib/*": ["./src/lib/*"]
}
},
"include": ["src/**/*", "vite.config.ts"]
}
Loading
Loading