Skip to content

Commit

Permalink
Merge pull request #855 from tradingstrategy-ai/853-svelte-5-pt1
Browse files Browse the repository at this point in the history
Svelte 5 upgrade (part 1)
  • Loading branch information
kenkunz authored Nov 22, 2024
2 parents 57e72eb + 86b795e commit 7eb5c4b
Show file tree
Hide file tree
Showing 95 changed files with 847 additions and 925 deletions.
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ TS_PUBLIC_BACKEND_URL=http://localhost:4173/api
TS_PUBLIC_TYPESENSE_API_URL=http://localhost:4173/api/typesense
TS_PUBLIC_STRATEGIES='[{"id":"enzyme-polygon-matic-usdc","name":"MATIC-USD breakout on Uniswap v3","url":"http://localhost:4173/api/strategies/enzyme-polygon-matic-usdc"},{"id":"enzyme-polygon-multipair","name":"Multipair breakout strategy on Uniswap v3","url":"http://localhost:4173/api/strategies/enzyme-polygon-multipair"}]'
TS_PRIVATE_ADMIN_PW=secret
TS_PUBLIC_ANNOUNCEMENT='{"title":"Example announcement","description":"This is an example announcement. Check out our <a href='/blog/latest-post'>latest blog post</a>!","ctaLabel":"View blog post","href":"/blog/latest-post","publishAt":"2024-11-01T00:00:00Z","expireAt":"2024-12-01T00:00:00Z"}'
398 changes: 172 additions & 226 deletions package-lock.json

Large diffs are not rendered by default.

25 changes: 14 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
"devDependencies": {
"@csstools/postcss-global-data": "^3.0.0",
"@playwright/test": "^1.47.2",
"@sveltejs/adapter-node": "~5.2.5",
"@sveltejs/enhanced-img": "^0.3.8",
"@sveltejs/kit": "~2.6.1",
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@sveltejs/adapter-node": "~5.2.9",
"@sveltejs/enhanced-img": "^0.4.1",
"@sveltejs/kit": "~2.8.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/svelte": "^5.2.1",
"@testing-library/svelte": "^5.2.4",
"@testing-library/user-event": "^14.5.2",
"@types/d3-array": "^3.2.1",
"@types/d3-time": "^3.0.3",
Expand All @@ -37,20 +37,20 @@
"@typescript-eslint/parser": "^8.7.0",
"eslint": "^9.11.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.44.1",
"eslint-plugin-svelte": "^2.46.0",
"jsdom": "^25.0.1",
"postcss": "^8.4.47",
"postcss-dark-theme-class": "^1.3.0",
"postcss-preset-env": "^10.0.5",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7",
"svelte": "^4.2.19",
"svelte-check": "^4.0.4",
"svelte": "^5.2.3",
"svelte-check": "^4.0.6",
"svelte-preprocess": "^6.0.3",
"tslib": "^2.7.0",
"typescript": "^5.6.2",
"typesense": "^1.8.2",
"unplugin-icons": "^0.19.3",
"unplugin-icons": "^0.20.1",
"vite": "^5.4.8",
"vite-plugin-simple-json-server": "^0.6.2",
"vitest": "^2.1.1",
Expand Down Expand Up @@ -79,9 +79,9 @@
"node-html-parser": "^6.1.13",
"sitemap": "^8.0.0",
"svelte-fsm": "^1.2.0",
"svelte-headless-table": "^0.18.2",
"svelte-headless-table": "^0.18.3",
"svelte-highlight": "^7.7.0",
"svelte-inview": "^4.0.3",
"svelte-inview": "^4.0.4",
"viem": "^2.21.16",
"zod": "^3.23.8"
},
Expand All @@ -91,6 +91,9 @@
"overrides": {
"vite-plugin-simple-json-server": {
"vite": "^5.4.8"
},
"svelte-headless-table": {
"svelte": "^4.0.0 || ^5.0.0"
}
}
}
20 changes: 15 additions & 5 deletions src/lib/chart/ChartIQ.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Dynamically ChartIQ modules (if available) and render chart element.
let cursor: ChartCursor = { position: {} };
function chartIQ(node: HTMLElement, deps: any) {
function chartIQ(node: HTMLElement, initialArg: any[]) {
let chartTracker;
let chartEngine = new CIQ.ChartEngine({ container: node, ...options });
Expand Down Expand Up @@ -203,7 +203,17 @@ Dynamically ChartIQ modules (if available) and render chart element.
chartEngine = null;
}
function update(...args: any) {
let lastArg: any[] | undefined = undefined;
function update(updateArg: any[]) {
// Svelte 5 has an apparent bug where the update function of an action is invoked even when
// the action's arg has not changed. This is mitigated below by comparing the current arg to
// the last arg and aborting if they match. We intentionaly manually invoke update on action
// initialization (see below) - the comparison evaluates to false in this case (as desired)
// because lastArg is undefined prior the first invocation of update.
if (updateArg === lastArg) return;
lastArg = updateArg;
updating = true;
// clear attached studies
Expand All @@ -215,9 +225,9 @@ Dynamically ChartIQ modules (if available) and render chart element.
}
// invoke updateCallback function returned from init callback (if provided)
updateCallback?.(...args);
updateCallback?.(updateArg);
}
update(deps);
update(initialArg);
return { destroy, update };
}
Expand All @@ -235,7 +245,7 @@ Dynamically ChartIQ modules (if available) and render chart element.
</div>

{#await initialize() then}
<div use:chartIQ={invalidate} data-testid="chartIQ" />
<div use:chartIQ={invalidate} data-testid="chartIQ"></div>
{:catch}
<div class="error">
<Alert size="md" status="warning" title="ChartIQ Error">
Expand Down
14 changes: 5 additions & 9 deletions src/lib/components/ContentTile.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<!--
@component
Display a content tile that links to additional content, such as the blog tiles
on the blog roll. If `href` is included, the entire tile is a targetable CTA.
A `ctaLabel` or `cta` slot may also be provided to include an explicit button target.
on the blog roll.
@example
Expand All @@ -27,16 +26,13 @@ A `ctaLabel` or `cta` slot may also be provided to include an explicit button ta
export let ctaLabel = '';
export let date: ComponentProps<Timestamp>['date'];
export let description = '';
export let href: string | undefined = undefined;
export let href: string;
export let mediaSrc = '';
export let mediaAlt = '';
export let title = '';
$: tag = href ? 'a' : 'div';
$: anchorProps = { href };
</script>

<svelte:element this={tag} class="content-tile tile a {classes}" {...anchorProps}>
<a class="content-tile tile a {classes}" {href}>
<img src={mediaSrc} alt={mediaAlt} />

<div class="content">
Expand All @@ -59,12 +55,12 @@ A `ctaLabel` or `cta` slot may also be provided to include an explicit button ta
{#if $$slots.cta || ctaLabel}
<div class="cta">
<slot name="cta">
<Button label={ctaLabel} {href} />
<Button label={ctaLabel} />
</slot>
</div>
{/if}
</div>
</svelte:element>
</a>

<style>
.content-tile {
Expand Down
16 changes: 9 additions & 7 deletions src/lib/components/CopyWidget.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<!--
@component
Display a copy icon that copies the provided text to the clipboard. The component exports
a `copier` store with a `copy` method – bind this to a variable and call `copier.copy()`
a `copy` method – bind a variable to a component instance and call `copyWidget.copy()`
(from a button, for instance).
@example
```svelte
<button title="copy to clipboard" on:click={() => copier.copy("This will be copied to clipboard")}>
<CopyWidget bind:copier />
<button title="copy to clipboard" on:click={() => copyWidget.copy("This will be copied to clipboard")}>
<CopyWidget bind:this={copyWidget} />
</button>
```
-->
Expand All @@ -18,7 +18,7 @@ a `copier` store with a `copy` method – bind this to a variable and call `copi
import IconCheckSquare from '~icons/local/check-square';
import { fade } from 'svelte/transition';
export const copier = fsm('idle', {
const copier = fsm('idle', {
idle: {
copy(text: string) {
navigator.clipboard.writeText(text).then(this.success);
Expand All @@ -33,9 +33,11 @@ a `copier` store with a `copy` method – bind this to a variable and call `copi
complete: 'idle'
}
});
export const { copy } = copier;
</script>

<div class="copy-widget">
<span class="copy-widget">
{#key $copier}
<span transition:fade={{ duration: 100 }}>
{#if $copier === 'idle'}
Expand All @@ -45,11 +47,11 @@ a `copier` store with a `copy` method – bind this to a variable and call `copi
{/if}
</span>
{/key}
</div>
</span>

<style>
.copy-widget {
display: grid;
display: inline-grid;
span {
grid-area: 1 / -1;
Expand Down
11 changes: 5 additions & 6 deletions src/lib/components/CryptoAddressWidget.svelte
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
<script lang="ts">
import type { ComponentProps } from 'svelte';
import { CopyWidget, HashAddress } from '$lib/components';
export let address: Address;
export let clipboardCopier = true;
export let href: string;
export let size: 'sm' | 'md' | 'lg' = 'md';
let copier: ComponentProps<CopyWidget>['copier'];
let copyWidget: CopyWidget;
</script>

<address class="crypto-address-widget size-{size} tile b">
<span class="crypto-address-widget size-{size} tile b">
<slot name="icon" />
<a {href} rel="noreferrer" target="_blank">
<HashAddress {address} endChars={7} />
</a>
{#if clipboardCopier}
<button title="Copy to clipboard" on:click={() => copier?.copy(address)}>
<CopyWidget bind:copier />
<button title="Copy to clipboard" on:click={() => copyWidget.copy(address)}>
<CopyWidget bind:this={copyWidget} />
</button>
{/if}
</address>
</span>

<style>
.crypto-address-widget {
Expand Down
20 changes: 10 additions & 10 deletions src/lib/components/HeroBanner.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ Hero banner used as heading on various pages (Community, Trading data, Blog roll
```
-->
<script lang="ts">
export let contentFullWidth = false;
export let image: string | undefined = undefined;
export let title: string;
export let subtitle = '';
export let hr = false;
</script>

<div class="hero-banner" class:has-image={image} class:has-fullwidth-content={contentFullWidth}>
<div class="hero-banner" class:has-image={image}>
<div class="content">
<h1>{@html title}</h1>
<p><slot name="subtitle">{@html subtitle}</slot></p>
<div class="subtitle">
<slot name="subtitle">{@html subtitle}</slot>
</div>
{#if hr}
<hr />
{/if}
Expand Down Expand Up @@ -49,16 +50,15 @@ Hero banner used as heading on various pages (Community, Trading data, Blog roll
}
&.has-image {
@media (--viewport-md) {
min-height: max(40vh, 32rem);
}
}
&:not(.has-fullwidth-content) {
place-content: center;
@media (--viewport-md-up) {
grid-template-columns: 1fr minmax(12rem, min(28vw, 32rem));
}
@media (--viewport-md) {
min-height: max(40vh, 32rem);
}
}
}
Expand All @@ -77,7 +77,7 @@ Hero banner used as heading on various pages (Community, Trading data, Blog roll
}
}
p {
.subtitle {
margin: 0;
font: var(--f-ui-xl-roman);
letter-spacing: var(--f-ui-xl-spacing, normal);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/HeroVideo.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</script>

<section class="ds-container hero-video">
<iframe src="https://www.youtube.com/embed/{youTubeId}" {title} frameborder="0" allowfullscreen />
<iframe src="https://www.youtube.com/embed/{youTubeId}" {title} frameborder="0" allowfullscreen></iframe>
</section>

<style>
Expand Down
51 changes: 51 additions & 0 deletions src/lib/components/TargetableLink.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!--
@component
Create a link for a "targetable" container, such as a content tile or table row.
The link is positioned to cover the entire container, making the whole area act
as the link target.
@param label screen reader label (not visibly displayed)
@example
```svelte
<div class="content-tile targetable">
<TargetableLink href="http://example.com" label="View details">
<span>Some other tile content</span>
<button class="targetable-above">Above tile target</button>
</div>
```
-->
<script lang="ts">
import type { HTMLAnchorAttributes } from 'svelte/elements';
type Props = HTMLAnchorAttributes & {
label: string;
};
let { label, ...rest }: Props = $props();
</script>

<a {...rest}>
<span class="sr-only">{label}</span>
</a>

<style>
/* positioned anchor element covers whole taregetable container */
a {
position: absolute;
inset: 0;
z-index: 1;
}
/* intentionally unscoped global - apply to targetable container */
:global(.targetable) {
position: relative;
/* raise nested targetable elements above positioned anchor */
:global(.targetable-above) {
position: relative;
z-index: 2;
}
}
</style>
15 changes: 8 additions & 7 deletions src/lib/components/Timestamp.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { vi } from 'vitest';
import { render } from '@testing-library/svelte';
import { render, screen } from '@testing-library/svelte';
import Timestamp from './Timestamp.svelte';

describe('Timestamp component', () => {
const date = new Date('2023-01-01T12:00Z');

test('should render fallback string if no valid date supplied', () => {
const { getByText } = render(Timestamp, { date: undefined });
getByText('---');
render(Timestamp, { date: undefined });
screen.getByText('---');
});

test('should render iso date string by default', () => {
const { getByText, queryByText } = render(Timestamp, { date });
getByText('2023-01-01');
expect(queryByText('12:00')).toBeNull();
render(Timestamp, { date });
screen.getByText('2023-01-01');
expect(screen.queryByText('12:00')).toBeNull();
});

test('should render iso date with time', () => {
Expand All @@ -36,7 +36,8 @@ describe('Timestamp component', () => {
vi.useFakeTimers();
vi.setSystemTime('2023-01-15T12:00Z');

const timeEl = render(Timestamp, { date, relative: true }).container.querySelector('time');
const { container } = render(Timestamp, { date, relative: true });
const timeEl = container.querySelector('time');

expect(timeEl).toHaveAttribute('datetime', date.toISOString());
expect(timeEl).toHaveTextContent('14 days ago');
Expand Down
Loading

0 comments on commit 7eb5c4b

Please sign in to comment.