Share register dialogs with Bits UI and fix Woodpecker E2E image #36
No files matched your search
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@hledger-reports/web': patch
|
||||
---
|
||||
|
||||
Use a small shared Bits UI shell for Cash transaction breakdowns and Investment details. Keep native financial content and payload-bound selection in their features while the shell handles the portal, focus trapping, scroll lock, close control and return focus.
|
||||
|
||||
Preserve Escape/close-button dismissal and non-dismissive backdrop clicks, clear selections on navigation or changed page data, and retain readable report tables without JavaScript. No new financial transformations or unrelated UI controls are introduced.
|
||||
|
||||
Use the private `git.eleith.com/eleith.private/playwright` image for Woodpecker E2E runs, matching the approved reference pipelines and the pinned Playwright version.
|
||||
+1
-1
@@ -82,7 +82,7 @@ steps:
|
||||
- pnpm --filter @hledger-reports/worker build
|
||||
|
||||
e2e:
|
||||
image: ghcr.io/eleith/containers-playwright:1.61.1-node26
|
||||
image: git.eleith.com/eleith.private/playwright:1.61.1-node26
|
||||
commands:
|
||||
- pnpm config set store-dir .pnpm-store
|
||||
- export PATH="$PWD/.cache/ci-bin:$PATH"
|
||||
|
||||
@@ -18,7 +18,7 @@ The hledger library reads the journal and organizes neutral, table/chart-ready `
|
||||
|
||||
**`/queries` remains a separate live report:** each Run executes native balance at recorded cost against the journal current at execution. Exact results persist in SQLite as operational workflow output, but the page displays only its latest response; reload clears it. Neither page offers user-facing run history.
|
||||
|
||||
Sign-in is required, but this remains loopback-only development software. Do not expose it publicly or through a reverse proxy. See [shared access and authentication](docs/authentication.md). General report catalogs and a transaction-detail UI are not implemented.
|
||||
Sign-in is required, but this remains loopback-only development software. Do not expose it publicly or through a reverse proxy. See [shared access and authentication](docs/authentication.md). Curated report catalogs and search remain deferred.
|
||||
|
||||
## Setup and run
|
||||
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ RUN pnpm --filter "@hledger-reports/web..." build
|
||||
FROM builder AS pruner
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm --config.hoist-workspace-packages=false --filter @hledger-reports/web --prod deploy --legacy /pruned
|
||||
|
||||
# Match When's browser image and the @playwright/test version together.
|
||||
# Keep the browser image and @playwright/test versions aligned.
|
||||
FROM ghcr.io/eleith/containers-playwright:1.61.1-node26 AS e2e
|
||||
WORKDIR /m
|
||||
ENV CI=true
|
||||
|
||||
@@ -69,6 +69,18 @@ labels, split flags, changes and attached running balances survive pagination.
|
||||
Empty accounts have no invented transaction; ambiguous native selectors show no
|
||||
account amounts. First/Previous/Next/Last are ordinary links, including without JS.
|
||||
|
||||
## Transaction dialogs
|
||||
|
||||
Cash breakdowns and Investment details share a small Bits UI shell. Their financial
|
||||
content and selection stay in the owning feature. Escape and the close button dismiss;
|
||||
backdrop clicks do not. Closing restores focus to the opener when available, otherwise
|
||||
to main content. Navigation or replacement page data invalidates the selected record.
|
||||
|
||||
The portal inherits document theme/typography and keeps the desktop dialog and narrow
|
||||
bottom-sheet layout. Bits UI supplies focus trapping and body scroll lock. Detail
|
||||
buttons remain disabled before hydration; the underlying financial tables work
|
||||
without JavaScript. There are no forced SSR dialog contents or unrelated Bits controls.
|
||||
|
||||
## Files and state
|
||||
|
||||
Compose shares ignored **`apps/web/data/`** at `/app/data` in both processes; no
|
||||
|
||||
@@ -15,12 +15,23 @@ test('Cash register opens saved activity and a transaction breakdown', async ({
|
||||
'8520.00 USD'
|
||||
);
|
||||
|
||||
await activity.getByRole('button', { name: 'Breakdown', exact: true }).first().click();
|
||||
const opener = activity.getByRole('button', { name: 'Breakdown', exact: true }).first();
|
||||
await opener.click();
|
||||
const dialog = page.getByRole('dialog', { name: 'Transaction breakdown', exact: true });
|
||||
await expect(dialog).toBeVisible();
|
||||
const splits = dialog.getByRole('table', { name: 'Split details', exact: true });
|
||||
await expect(splits).toContainText('Brokerage');
|
||||
await expect(splits.getByRole('cell', { name: '10.00 VTI', exact: true })).toBeVisible();
|
||||
await dialog.getByRole('button', { name: 'Close breakdown', exact: true }).click();
|
||||
const close = dialog.getByRole('button', { name: 'Close breakdown', exact: true });
|
||||
await expect(close).toBeFocused();
|
||||
await page.mouse.click(1, 1);
|
||||
await expect(dialog).toBeVisible();
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(dialog).not.toBeVisible();
|
||||
await expect(opener).toBeFocused();
|
||||
await opener.press('Enter');
|
||||
await expect(dialog).toBeVisible();
|
||||
await close.click();
|
||||
await expect(dialog).not.toBeVisible();
|
||||
await expect(opener).toBeFocused();
|
||||
});
|
||||
@@ -20,7 +20,8 @@ test('Investment register shows cash once per transaction and opens details', as
|
||||
await expect(purchase.locator('[data-column="cash-balance"]')).toHaveCount(1);
|
||||
await expect(groups.last().locator('[data-column="cash-balance"]')).toHaveText('200.00 USD');
|
||||
|
||||
await purchase.getByRole('button', { name: 'Details', exact: true }).click();
|
||||
const opener = purchase.getByRole('button', { name: 'Details', exact: true });
|
||||
await opener.click();
|
||||
const details = page.getByRole('dialog', { name: 'Investment details', exact: true });
|
||||
await expect(details).toBeVisible();
|
||||
await expect(details.getByRole('table', { name: 'Related activity', exact: true })).toContainText(
|
||||
@@ -28,4 +29,12 @@ test('Investment register shows cash once per transaction and opens details', as
|
||||
);
|
||||
await details.getByRole('button', { name: 'Close details', exact: true }).click();
|
||||
await expect(details).not.toBeVisible();
|
||||
await expect(opener).toBeFocused();
|
||||
await opener.click();
|
||||
await expect(details).toBeVisible();
|
||||
await page.goBack();
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Investment register', exact: true })
|
||||
).toBeVisible();
|
||||
await expect(details).not.toBeVisible();
|
||||
});
|
||||
@@ -28,6 +28,7 @@
|
||||
"@hledger-reports/jobs": "workspace:*",
|
||||
"@hledger-reports/snapshots": "workspace:*",
|
||||
"better-auth": "1.7.14",
|
||||
"bits-ui": "2.18.1",
|
||||
"chart.js": "4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
import { Dialog } from 'bits-ui';
|
||||
|
||||
let {
|
||||
open,
|
||||
title,
|
||||
label,
|
||||
closeLabel,
|
||||
onClose,
|
||||
returnFocus = null,
|
||||
children
|
||||
}: {
|
||||
open: boolean;
|
||||
title: string;
|
||||
label: string;
|
||||
closeLabel: string;
|
||||
onClose: () => void;
|
||||
returnFocus?: HTMLElement | null;
|
||||
children: Snippet;
|
||||
} = $props();
|
||||
|
||||
function restoreFocus(event: Event) {
|
||||
event.preventDefault();
|
||||
const target =
|
||||
returnFocus?.isConnected && !returnFocus.matches(':disabled')
|
||||
? returnFocus
|
||||
: document.querySelector<HTMLElement>('#main');
|
||||
target?.focus({ preventScroll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root
|
||||
bind:open={
|
||||
() => open,
|
||||
(next) => {
|
||||
if (!next) onClose();
|
||||
}
|
||||
}
|
||||
>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay>
|
||||
{#snippet child({ props })}
|
||||
<div {...props} class="overlay"></div>
|
||||
{/snippet}
|
||||
</Dialog.Overlay>
|
||||
<Dialog.Content interactOutsideBehavior="ignore" onCloseAutoFocus={restoreFocus}>
|
||||
{#snippet child({ props })}
|
||||
<section {...props} class="content" aria-label={label} aria-labelledby={undefined}>
|
||||
<header>
|
||||
<Dialog.Title level={2}>
|
||||
{#snippet child({ props: titleProps })}
|
||||
<h2 {...titleProps} class="section-title">{title}</h2>
|
||||
{/snippet}
|
||||
</Dialog.Title>
|
||||
<Dialog.Close>
|
||||
{#snippet child({ props: closeProps })}
|
||||
<button
|
||||
{...closeProps}
|
||||
type="button"
|
||||
class="ui-button ui-button--icon"
|
||||
aria-label={closeLabel}
|
||||
title={closeLabel}
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
{/snippet}
|
||||
</Dialog.Close>
|
||||
</header>
|
||||
{@render children()}
|
||||
</section>
|
||||
{/snippet}
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
|
||||
<style>
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10;
|
||||
background: var(--color-backdrop);
|
||||
}
|
||||
.content {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 11;
|
||||
transform: translate(-50%, -50%);
|
||||
width: min(var(--dialog-width), calc(100% - var(--space-medium)));
|
||||
max-height: calc(100dvh - var(--space-large));
|
||||
padding: var(--space-medium);
|
||||
border: var(--border-width) solid var(--color-border);
|
||||
border-radius: var(--radius-panel);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-small);
|
||||
}
|
||||
h2 {
|
||||
min-width: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
@media (max-width: 40rem) {
|
||||
.content {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
transform: none;
|
||||
width: 100%;
|
||||
max-height: 90dvh;
|
||||
padding-bottom: max(var(--space-medium), env(safe-area-inset-bottom));
|
||||
border-radius: var(--radius-panel) var(--radius-panel) 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -10,6 +10,7 @@
|
||||
let { data, labels = [] }: { data: CashRegisterData; labels?: readonly AccountLabel[] } =
|
||||
$props();
|
||||
let hydrated = $state(false);
|
||||
let opener = $state<HTMLButtonElement | null>(null);
|
||||
let selection = $state.raw<{ context: CashRegisterData; record: CashBreakdown } | null>(null);
|
||||
// Invalidate synchronously with a replacement payload, before rendering its rows.
|
||||
let selectedRecord = $derived(selection?.context === data ? selection.record : null);
|
||||
@@ -18,6 +19,10 @@
|
||||
onMount(() => {
|
||||
hydrated = true;
|
||||
});
|
||||
function openBreakdown(record: CashBreakdown, button: HTMLButtonElement) {
|
||||
opener = button;
|
||||
selection = { context: data, record };
|
||||
}
|
||||
function closeBreakdown() {
|
||||
selection = null;
|
||||
}
|
||||
@@ -113,8 +118,9 @@
|
||||
class="ui-button ui-button--compact"
|
||||
type="button"
|
||||
disabled={!hydrated}
|
||||
onclick={() => {
|
||||
if (row.breakdown) selection = { context: data, record: row.breakdown };
|
||||
aria-haspopup="dialog"
|
||||
onclick={(event) => {
|
||||
if (row.breakdown) openBreakdown(row.breakdown, event.currentTarget);
|
||||
}}>Breakdown</button
|
||||
>{/if}</td
|
||||
>
|
||||
@@ -159,7 +165,12 @@
|
||||
</nav>
|
||||
{/if}
|
||||
|
||||
<TransactionBreakdown {labels} selected={selectedRecord} onClose={closeBreakdown} />
|
||||
<TransactionBreakdown
|
||||
{labels}
|
||||
selected={selectedRecord}
|
||||
onClose={closeBreakdown}
|
||||
returnFocus={opener}
|
||||
/>
|
||||
|
||||
<style>
|
||||
nav {
|
||||
|
||||
@@ -1,42 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { accountLabel, type AccountLabel } from '#lib/accounts.js';
|
||||
import DetailDialog from '#lib/DetailDialog.svelte';
|
||||
import type { CashBreakdown } from './data.js';
|
||||
|
||||
let {
|
||||
selected,
|
||||
onClose,
|
||||
returnFocus = null,
|
||||
labels = []
|
||||
}: {
|
||||
selected: CashBreakdown | null;
|
||||
onClose: () => void;
|
||||
returnFocus?: HTMLElement | null;
|
||||
labels?: readonly AccountLabel[];
|
||||
} = $props();
|
||||
let dialog: HTMLDialogElement;
|
||||
|
||||
$effect(() => {
|
||||
if (selected) {
|
||||
if (!dialog.open) dialog.showModal();
|
||||
} else if (dialog.open) dialog.close();
|
||||
});
|
||||
|
||||
function close() {
|
||||
dialog.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
<dialog bind:this={dialog} aria-label="Transaction breakdown" onclose={onClose}>
|
||||
<DetailDialog
|
||||
open={selected !== null}
|
||||
title={selected?.payee || 'Transaction breakdown'}
|
||||
label="Transaction breakdown"
|
||||
closeLabel="Close breakdown"
|
||||
{onClose}
|
||||
{returnFocus}
|
||||
>
|
||||
{#if selected}
|
||||
<header>
|
||||
<h2 class="text section-title">{selected.payee || 'Transaction breakdown'}</h2>
|
||||
<!-- Native showModal focuses this first focusable control and restores the opener on close. -->
|
||||
<button
|
||||
class="ui-button ui-button--icon"
|
||||
type="button"
|
||||
onclick={close}
|
||||
aria-label="Close breakdown"
|
||||
title="Close breakdown"><span aria-hidden="true">×</span></button
|
||||
>
|
||||
</header>
|
||||
<p class="context">
|
||||
{selected.date} ·
|
||||
{#if selected.direction === 'unreported' || !selected.amount}
|
||||
@@ -101,35 +89,9 @@
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
</dialog>
|
||||
</DetailDialog>
|
||||
|
||||
<style>
|
||||
dialog {
|
||||
width: min(var(--dialog-width), calc(100% - var(--space-medium)));
|
||||
max-width: none;
|
||||
max-height: calc(100dvh - var(--space-large));
|
||||
margin: auto;
|
||||
padding: var(--space-medium);
|
||||
border: var(--border-width) solid var(--color-border);
|
||||
border-radius: var(--radius-panel);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
font: inherit;
|
||||
overflow: auto;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
dialog::backdrop {
|
||||
background: var(--color-backdrop);
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-small);
|
||||
}
|
||||
h2 {
|
||||
min-width: 0;
|
||||
}
|
||||
h3 {
|
||||
margin-block: var(--space-small);
|
||||
font-size: inherit;
|
||||
@@ -174,13 +136,4 @@
|
||||
display: block;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
@media (max-width: 40rem) {
|
||||
dialog {
|
||||
width: 100%;
|
||||
max-height: 90dvh;
|
||||
padding-bottom: max(var(--space-medium), env(safe-area-inset-bottom));
|
||||
margin: auto 0 0;
|
||||
border-radius: var(--radius-panel) var(--radius-panel) 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { accountLabel, type AccountLabel } from '#lib/accounts.js';
|
||||
import DetailDialog from '#lib/DetailDialog.svelte';
|
||||
import type { InvestmentIssue, ReportedAmount } from '@hledger-reports/hledger';
|
||||
import type { InvestmentTransaction } from './data.js';
|
||||
import { securityActionLabels } from './action-labels.js';
|
||||
@@ -7,13 +8,14 @@
|
||||
let {
|
||||
selectedTransaction,
|
||||
labels = [],
|
||||
returnFocus = null,
|
||||
onClose
|
||||
}: {
|
||||
selectedTransaction: InvestmentTransaction | null;
|
||||
labels?: readonly AccountLabel[];
|
||||
onClose: () => void;
|
||||
returnFocus?: HTMLElement | null;
|
||||
} = $props();
|
||||
let dialog: HTMLDialogElement;
|
||||
const roles = { cash: 'Cash', income: 'Income', expense: 'Expense', gain: 'Gain' };
|
||||
const issues: Record<InvestmentIssue, string> = {
|
||||
'unclassified-commodity': 'Some amounts could not be classified as cash or securities.',
|
||||
@@ -25,20 +27,6 @@
|
||||
'virtual-postings': 'Virtual postings affect this activity.',
|
||||
'unavailable-transacted-values': 'Some investment amounts are unavailable.'
|
||||
};
|
||||
|
||||
$effect(() => {
|
||||
if (selectedTransaction) {
|
||||
if (!dialog.open) {
|
||||
dialog.showModal();
|
||||
}
|
||||
} else if (dialog.open) {
|
||||
dialog.close();
|
||||
}
|
||||
});
|
||||
|
||||
function closeDetails() {
|
||||
dialog.close();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#snippet amountList(amounts: ReportedAmount[] | null)}
|
||||
@@ -53,22 +41,16 @@
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<dialog bind:this={dialog} aria-label="Investment details" onclose={onClose}>
|
||||
<DetailDialog
|
||||
open={selectedTransaction !== null}
|
||||
title={selectedTransaction?.payee || 'Investment details'}
|
||||
label="Investment details"
|
||||
closeLabel="Close details"
|
||||
{onClose}
|
||||
{returnFocus}
|
||||
>
|
||||
{#if selectedTransaction}
|
||||
{@const cash = selectedTransaction.cash}
|
||||
<header>
|
||||
<h2 class="text section-title">{selectedTransaction.payee || 'Investment details'}</h2>
|
||||
<!-- Native showModal focuses this control and restores the opener on close. -->
|
||||
<button
|
||||
class="ui-button ui-button--icon"
|
||||
type="button"
|
||||
onclick={closeDetails}
|
||||
aria-label="Close details"
|
||||
title="Close details"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</header>
|
||||
<p class="context">{selectedTransaction.date}</p>
|
||||
{#if selectedTransaction.notes.some((note) => note.trim().length > 0)}
|
||||
<section aria-label="Notes">
|
||||
@@ -189,35 +171,9 @@
|
||||
</ul>
|
||||
{/if}
|
||||
{/if}
|
||||
</dialog>
|
||||
</DetailDialog>
|
||||
|
||||
<style>
|
||||
dialog {
|
||||
width: min(var(--dialog-width), calc(100% - var(--space-medium)));
|
||||
max-width: none;
|
||||
max-height: calc(100dvh - var(--space-large));
|
||||
margin: auto;
|
||||
padding: var(--space-medium);
|
||||
border: var(--border-width) solid var(--color-border);
|
||||
border-radius: var(--radius-panel);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
font: inherit;
|
||||
overflow: auto;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
dialog::backdrop {
|
||||
background: var(--color-backdrop);
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-small);
|
||||
}
|
||||
h2 {
|
||||
min-width: 0;
|
||||
}
|
||||
h3 {
|
||||
margin-block: var(--space-small);
|
||||
font-size: inherit;
|
||||
@@ -278,13 +234,4 @@
|
||||
dd {
|
||||
margin: 0;
|
||||
}
|
||||
@media (max-width: 40rem) {
|
||||
dialog {
|
||||
width: 100%;
|
||||
max-height: 90dvh;
|
||||
padding-bottom: max(var(--space-medium), env(safe-area-inset-bottom));
|
||||
margin: auto 0 0;
|
||||
border-radius: var(--radius-panel) var(--radius-panel) 0 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -11,6 +11,7 @@
|
||||
let { data, labels = [] }: { data: InvestmentRegisterData; labels?: readonly AccountLabel[] } =
|
||||
$props();
|
||||
let hydrated = $state(false);
|
||||
let opener = $state<HTMLButtonElement | null>(null);
|
||||
let selection = $state.raw<{
|
||||
pageData: InvestmentRegisterData;
|
||||
transaction: InvestmentTransaction;
|
||||
@@ -22,7 +23,8 @@
|
||||
hydrated = true;
|
||||
});
|
||||
|
||||
function openDetails(transaction: InvestmentTransaction) {
|
||||
function openDetails(transaction: InvestmentTransaction, button: HTMLButtonElement) {
|
||||
opener = button;
|
||||
selection = { pageData: data, transaction };
|
||||
}
|
||||
|
||||
@@ -205,7 +207,8 @@
|
||||
class="ui-button ui-button--compact"
|
||||
type="button"
|
||||
disabled={!hydrated}
|
||||
onclick={() => openDetails(transaction)}
|
||||
aria-haspopup="dialog"
|
||||
onclick={(event) => openDetails(transaction, event.currentTarget)}
|
||||
>
|
||||
Details
|
||||
</button>
|
||||
@@ -268,7 +271,7 @@
|
||||
</a>
|
||||
{/if}
|
||||
</nav>
|
||||
<InvestmentDetails {labels} {selectedTransaction} onClose={closeDetails} />
|
||||
<InvestmentDetails {labels} {selectedTransaction} onClose={closeDetails} returnFocus={opener} />
|
||||
|
||||
<style>
|
||||
nav {
|
||||
|
||||
@@ -116,6 +116,12 @@ layout imports `lib/styles/{reset,theme,layout,controls}.css` once. Page/panel s
|
||||
focus and scrolling mechanics use classes, not wrapper components. Financial cell
|
||||
alignment, transaction grouping and dialog content remain feature-owned.
|
||||
|
||||
`DetailDialog.svelte` is the shared interactive shell for Cash/Investment details.
|
||||
Bits UI owns the portal, focus trap, Escape dismissal and scroll lock; the shell
|
||||
keeps outside clicks non-dismissive and restores focus to the opener or main content.
|
||||
Registers retain their payload-bound selections and clear them on navigation. The
|
||||
shell does not know about transactions, postings or financial values.
|
||||
|
||||
CSS owns chart colors and font choices. `ChartCanvas` resolves them after mounting
|
||||
and on system-theme changes; pure chart builders receive that resolved theme while
|
||||
retaining their existing native-value and geometry rules. Server-rendered exact
|
||||
|
||||
Generated
+120
-2
@@ -83,6 +83,9 @@ importers:
|
||||
better-auth:
|
||||
specifier: 1.7.14
|
||||
version: 1.7.14(@sveltejs/kit@3.1.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.57.3(@typescript-eslint/types@8.60.0))(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))(vitest@4.1.7(@types/node@26.0.0)(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))
|
||||
bits-ui:
|
||||
specifier: 2.18.1
|
||||
version: 2.18.1(@internationalized/date@3.12.4)(@sveltejs/kit@3.1.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.57.3(@typescript-eslint/types@8.60.0))(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))
|
||||
chart.js:
|
||||
specifier: 4.5.1
|
||||
version: 4.5.1
|
||||
@@ -542,6 +545,15 @@ packages:
|
||||
resolution: {integrity: sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==}
|
||||
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||
|
||||
'@floating-ui/core@1.8.0':
|
||||
resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
|
||||
|
||||
'@floating-ui/dom@1.8.0':
|
||||
resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==}
|
||||
|
||||
'@floating-ui/utils@0.2.12':
|
||||
resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
|
||||
|
||||
'@humanfs/core@0.19.2':
|
||||
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
@@ -562,6 +574,9 @@ packages:
|
||||
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
|
||||
engines: {node: '>=18.18'}
|
||||
|
||||
'@internationalized/date@3.12.4':
|
||||
resolution: {integrity: sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||
|
||||
@@ -768,6 +783,9 @@ packages:
|
||||
svelte: ^5.46.4
|
||||
vite: ^8.0.0-beta.7 || ^8.0.0
|
||||
|
||||
'@swc/helpers@0.5.23':
|
||||
resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==}
|
||||
|
||||
'@tybys/wasm-util@0.10.4':
|
||||
resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==}
|
||||
|
||||
@@ -973,6 +991,13 @@ packages:
|
||||
zod:
|
||||
optional: true
|
||||
|
||||
bits-ui@2.18.1:
|
||||
resolution: {integrity: sha512-KkemzKFH4T3gt3H+P86JcnAWExjByv/6vlwjm/BoCwTPHu03yiCdxbghdJLvFReQTe0acCAiRcKfmixxD6XvlA==}
|
||||
engines: {node: '>=20'}
|
||||
peerDependencies:
|
||||
'@internationalized/date': ^3.8.1
|
||||
svelte: ^5.33.0
|
||||
|
||||
brace-expansion@5.0.12:
|
||||
resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==}
|
||||
engines: {node: 20 || >=22}
|
||||
@@ -1036,6 +1061,10 @@ packages:
|
||||
defu@6.1.7:
|
||||
resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
|
||||
|
||||
dequal@2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1221,6 +1250,9 @@ packages:
|
||||
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
||||
engines: {node: '>=0.8.19'}
|
||||
|
||||
inline-style-parser@0.2.7:
|
||||
resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==}
|
||||
|
||||
is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1359,6 +1391,10 @@ packages:
|
||||
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
lz-string@1.5.0:
|
||||
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
||||
hasBin: true
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
@@ -1523,6 +1559,15 @@ packages:
|
||||
rou3@0.9.2:
|
||||
resolution: {integrity: sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ==}
|
||||
|
||||
runed@0.35.1:
|
||||
resolution: {integrity: sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==}
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^2.21.0
|
||||
svelte: ^5.7.0
|
||||
peerDependenciesMeta:
|
||||
'@sveltejs/kit':
|
||||
optional: true
|
||||
|
||||
sade@1.8.1:
|
||||
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1567,6 +1612,9 @@ packages:
|
||||
std-env@4.3.0:
|
||||
resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==}
|
||||
|
||||
style-to-object@1.0.14:
|
||||
resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
|
||||
|
||||
svelte-check@4.7.7:
|
||||
resolution: {integrity: sha512-Ge2IMdeTpbJuzNFGRiiMg0RWgbScysKQu7xwcyFJ22+DU9F5nPX51+Q3gHmGqRxYQMx2knreVWt5oCtfe0vf2g==}
|
||||
engines: {node: '>= 18.0.0'}
|
||||
@@ -1584,10 +1632,19 @@ packages:
|
||||
svelte:
|
||||
optional: true
|
||||
|
||||
svelte-toolbelt@0.10.6:
|
||||
resolution: {integrity: sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==}
|
||||
engines: {node: '>=18', pnpm: '>=8.7.0'}
|
||||
peerDependencies:
|
||||
svelte: ^5.30.2
|
||||
|
||||
svelte@5.57.3:
|
||||
resolution: {integrity: sha512-5mAftM3SoDV9X0zdF386F9p2pv8iKOwi/KfBS1XnBAkVCKFzVOLHUoa8iGWAt4buoicLmyWVRpuuD5wplXOQsg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tabbable@6.5.1:
|
||||
resolution: {integrity: sha512-dMCuwDUY6Noz3zVKLefBC4wq+aJV20yc7vWmM7uTNGwtQSFF5kX7OUIUsxuVajz/bdt5Xqdah3/0NLjth+cM5A==}
|
||||
|
||||
tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
@@ -1990,6 +2047,17 @@ snapshots:
|
||||
'@eslint/core': 1.2.1
|
||||
levn: 0.4.1
|
||||
|
||||
'@floating-ui/core@1.8.0':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.12
|
||||
|
||||
'@floating-ui/dom@1.8.0':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.8.0
|
||||
'@floating-ui/utils': 0.2.12
|
||||
|
||||
'@floating-ui/utils@0.2.12': {}
|
||||
|
||||
'@humanfs/core@0.19.2':
|
||||
dependencies:
|
||||
'@humanfs/types': 0.15.0
|
||||
@@ -2006,6 +2074,10 @@ snapshots:
|
||||
|
||||
'@humanwhocodes/retry@0.4.3': {}
|
||||
|
||||
'@internationalized/date@3.12.4':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.23
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.6.0
|
||||
@@ -2155,6 +2227,10 @@ snapshots:
|
||||
vite: 8.0.16(@types/node@26.0.0)(yaml@2.9.0)
|
||||
vitefu: 1.1.3(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0))
|
||||
|
||||
'@swc/helpers@0.5.23':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@tybys/wasm-util@0.10.4':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -2365,6 +2441,19 @@ snapshots:
|
||||
optionalDependencies:
|
||||
zod: 4.6.7
|
||||
|
||||
bits-ui@2.18.1(@internationalized/date@3.12.4)(@sveltejs/kit@3.1.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.57.3(@typescript-eslint/types@8.60.0))(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0)):
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.8.0
|
||||
'@floating-ui/dom': 1.8.0
|
||||
'@internationalized/date': 3.12.4
|
||||
esm-env: 1.2.2
|
||||
runed: 0.35.1(@sveltejs/kit@3.1.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.57.3(@typescript-eslint/types@8.60.0))(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))
|
||||
svelte: 5.57.3(@typescript-eslint/types@8.60.0)
|
||||
svelte-toolbelt: 0.10.6(@sveltejs/kit@3.1.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.57.3(@typescript-eslint/types@8.60.0))(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))
|
||||
tabbable: 6.5.1
|
||||
transitivePeerDependencies:
|
||||
- '@sveltejs/kit'
|
||||
|
||||
brace-expansion@5.0.12:
|
||||
dependencies:
|
||||
balanced-match: 4.0.4
|
||||
@@ -2409,6 +2498,8 @@ snapshots:
|
||||
|
||||
defu@6.1.7: {}
|
||||
|
||||
dequal@2.0.3: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
devalue@5.9.4: {}
|
||||
@@ -2590,6 +2681,8 @@ snapshots:
|
||||
|
||||
imurmurhash@0.1.4: {}
|
||||
|
||||
inline-style-parser@0.2.7: {}
|
||||
|
||||
is-extglob@2.1.1: {}
|
||||
|
||||
is-glob@4.0.3:
|
||||
@@ -2691,6 +2784,8 @@ snapshots:
|
||||
|
||||
luxon@3.7.2: {}
|
||||
|
||||
lz-string@1.5.0: {}
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.6.0
|
||||
@@ -2822,6 +2917,15 @@ snapshots:
|
||||
|
||||
rou3@0.9.2: {}
|
||||
|
||||
runed@0.35.1(@sveltejs/kit@3.1.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.57.3(@typescript-eslint/types@8.60.0))(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0)):
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
esm-env: 1.2.2
|
||||
lz-string: 1.5.0
|
||||
svelte: 5.57.3(@typescript-eslint/types@8.60.0)
|
||||
optionalDependencies:
|
||||
'@sveltejs/kit': 3.1.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.57.3(@typescript-eslint/types@8.60.0))(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0))
|
||||
|
||||
sade@1.8.1:
|
||||
dependencies:
|
||||
mri: 1.2.0
|
||||
@@ -2854,6 +2958,10 @@ snapshots:
|
||||
|
||||
std-env@4.3.0: {}
|
||||
|
||||
style-to-object@1.0.14:
|
||||
dependencies:
|
||||
inline-style-parser: 0.2.7
|
||||
|
||||
svelte-check@4.7.7(picomatch@4.0.7)(svelte@5.57.3(@typescript-eslint/types@8.60.0))(typescript@6.0.3):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
@@ -2879,6 +2987,15 @@ snapshots:
|
||||
optionalDependencies:
|
||||
svelte: 5.57.3(@typescript-eslint/types@8.60.0)
|
||||
|
||||
svelte-toolbelt@0.10.6(@sveltejs/kit@3.1.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.57.3(@typescript-eslint/types@8.60.0))(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0)):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
runed: 0.35.1(@sveltejs/kit@3.1.1(@sveltejs/vite-plugin-svelte@7.1.2(svelte@5.57.3(@typescript-eslint/types@8.60.0))(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(yaml@2.9.0)))(svelte@5.57.3(@typescript-eslint/types@8.60.0))
|
||||
style-to-object: 1.0.14
|
||||
svelte: 5.57.3(@typescript-eslint/types@8.60.0)
|
||||
transitivePeerDependencies:
|
||||
- '@sveltejs/kit'
|
||||
|
||||
svelte@5.57.3(@typescript-eslint/types@8.60.0):
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
@@ -2899,6 +3016,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@typescript-eslint/types'
|
||||
|
||||
tabbable@6.5.1: {}
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinyexec@1.3.1: {}
|
||||
@@ -2916,8 +3035,7 @@ snapshots:
|
||||
dependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
tslib@2.8.1:
|
||||
optional: true
|
||||
tslib@2.8.1: {}
|
||||
|
||||
type-check@0.4.0:
|
||||
dependencies:
|
||||
|
||||
Reference in new issue
Block a user