@svadmin/ui 0.0.3 → 0.3.0
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/package.json +3 -2
- package/src/components/AdminApp.svelte +31 -21
- package/src/components/Authenticated.svelte +57 -0
- package/src/components/AutoForm.svelte +5 -5
- package/src/components/AutoSaveIndicator.svelte +30 -0
- package/src/components/AutoTable.svelte +81 -10
- package/src/components/Breadcrumbs.svelte +2 -0
- package/src/components/CatchAllNavigate.svelte +21 -0
- package/src/components/ConfigErrorScreen.svelte +224 -0
- package/src/components/ConfirmDialog.svelte +1 -1
- package/src/components/CreatePage.svelte +42 -0
- package/src/components/DevTools.svelte +83 -34
- package/src/components/EditPage.svelte +52 -0
- package/src/components/EmptyState.svelte +17 -31
- package/src/components/ErrorComponent.svelte +35 -0
- package/src/components/ForgotPasswordPage.svelte +12 -22
- package/src/components/Header.svelte +28 -59
- package/src/components/InferencerPanel.svelte +166 -0
- package/src/components/ListPage.svelte +47 -0
- package/src/components/LiveIndicator.svelte +115 -0
- package/src/components/LoginPage.svelte +13 -22
- package/src/components/NavigateToResource.svelte +20 -0
- package/src/components/PageSkeleton.svelte +70 -0
- package/src/components/RegisterPage.svelte +12 -23
- package/src/components/ShowPage.svelte +3 -3
- package/src/components/Sidebar.svelte +29 -10
- package/src/components/StatsCard.svelte +23 -2
- package/src/components/ThemedTitle.svelte +32 -0
- package/src/components/Toast.svelte +21 -11
- package/src/components/UpdatePasswordPage.svelte +217 -0
- package/src/components/buttons/CloneButton.svelte +30 -0
- package/src/components/buttons/CreateButton.svelte +29 -0
- package/src/components/buttons/DeleteButton.svelte +59 -0
- package/src/components/buttons/EditButton.svelte +30 -0
- package/src/components/buttons/ExportButton.svelte +24 -0
- package/src/components/buttons/ImportButton.svelte +50 -0
- package/src/components/buttons/ListButton.svelte +23 -0
- package/src/components/buttons/RefreshButton.svelte +31 -0
- package/src/components/buttons/SaveButton.svelte +27 -0
- package/src/components/buttons/ShowButton.svelte +24 -0
- package/src/components/buttons/index.ts +10 -0
- package/src/components/fields/BooleanField.svelte +13 -0
- package/src/components/fields/DateField.svelte +6 -0
- package/src/components/fields/EmailField.svelte +15 -0
- package/src/components/fields/FileField.svelte +19 -0
- package/src/components/fields/ImageField.svelte +14 -0
- package/src/components/fields/MarkdownField.svelte +19 -0
- package/src/components/fields/NumberField.svelte +6 -0
- package/src/components/fields/TagField.svelte +17 -0
- package/src/components/fields/TextField.svelte +16 -0
- package/src/components/fields/UrlField.svelte +20 -0
- package/src/components/ui/avatar/avatar.svelte +2 -1
- package/src/index.ts +32 -0
- package/src/router-state.svelte.ts +33 -7
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { t } from '@svadmin/core';
|
|
3
|
+
import { Button } from '../ui/button/index.js';
|
|
4
|
+
import { Save, Loader2 } from 'lucide-svelte';
|
|
5
|
+
|
|
6
|
+
let { loading = false, hideText = false, type = 'submit', class: className = '' } = $props<{
|
|
7
|
+
loading?: boolean;
|
|
8
|
+
hideText?: boolean;
|
|
9
|
+
type?: 'submit' | 'button';
|
|
10
|
+
class?: string;
|
|
11
|
+
}>();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<Button
|
|
15
|
+
{type}
|
|
16
|
+
variant="default"
|
|
17
|
+
size={hideText ? 'icon' : 'default'}
|
|
18
|
+
class={className}
|
|
19
|
+
disabled={loading}
|
|
20
|
+
>
|
|
21
|
+
{#if loading}
|
|
22
|
+
<Loader2 class="h-4 w-4 animate-spin" />
|
|
23
|
+
{:else}
|
|
24
|
+
<Save class="h-4 w-4" />
|
|
25
|
+
{/if}
|
|
26
|
+
{#if !hideText}<span class="ml-1">{t('common.save')}</span>{/if}
|
|
27
|
+
</Button>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { useNavigation, t } from '@svadmin/core';
|
|
3
|
+
import { Button } from '../ui/button/index.js';
|
|
4
|
+
import { Eye } from 'lucide-svelte';
|
|
5
|
+
|
|
6
|
+
let { resource, recordItemId, hideText = false, class: className = '' } = $props<{
|
|
7
|
+
resource: string;
|
|
8
|
+
recordItemId: string | number;
|
|
9
|
+
hideText?: boolean;
|
|
10
|
+
class?: string;
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const nav = useNavigation();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<Button
|
|
17
|
+
variant="ghost"
|
|
18
|
+
size={hideText ? 'icon' : 'sm'}
|
|
19
|
+
class={className}
|
|
20
|
+
onclick={() => nav.show(resource, recordItemId)}
|
|
21
|
+
>
|
|
22
|
+
<Eye class="h-4 w-4" />
|
|
23
|
+
{#if !hideText}<span class="ml-1">{t('common.detail')}</span>{/if}
|
|
24
|
+
</Button>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { default as CreateButton } from './CreateButton.svelte';
|
|
2
|
+
export { default as EditButton } from './EditButton.svelte';
|
|
3
|
+
export { default as DeleteButton } from './DeleteButton.svelte';
|
|
4
|
+
export { default as ShowButton } from './ShowButton.svelte';
|
|
5
|
+
export { default as ListButton } from './ListButton.svelte';
|
|
6
|
+
export { default as RefreshButton } from './RefreshButton.svelte';
|
|
7
|
+
export { default as ExportButton } from './ExportButton.svelte';
|
|
8
|
+
export { default as ImportButton } from './ImportButton.svelte';
|
|
9
|
+
export { default as SaveButton } from './SaveButton.svelte';
|
|
10
|
+
export { default as CloneButton } from './CloneButton.svelte';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props { value: boolean | null | undefined; trueIcon?: string; falseIcon?: string }
|
|
3
|
+
let { value, trueIcon = '✓', falseIcon = '✗' }: Props = $props();
|
|
4
|
+
</script>
|
|
5
|
+
<span class="field-boolean" class:is-true={!!value} class:is-false={!value}>
|
|
6
|
+
{value ? trueIcon : falseIcon}
|
|
7
|
+
</span>
|
|
8
|
+
|
|
9
|
+
<style>
|
|
10
|
+
.field-boolean { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 50%; font-size: 14px; }
|
|
11
|
+
.is-true { background: var(--color-success-bg, #dcfce7); color: var(--color-success, #16a34a); }
|
|
12
|
+
.is-false { background: var(--color-error-bg, #fef2f2); color: var(--color-error, #dc2626); }
|
|
13
|
+
</style>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props { value: string | number | Date | null | undefined; locale?: string; options?: Intl.DateTimeFormatOptions }
|
|
3
|
+
let { value, locale = 'en-US', options = { dateStyle: 'medium' } }: Props = $props();
|
|
4
|
+
const formatted = $derived(value != null ? new Intl.DateTimeFormat(locale, options).format(new Date(value as string | number | Date)) : '—');
|
|
5
|
+
</script>
|
|
6
|
+
<span class="field-date">{formatted}</span>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props { value: string | null | undefined }
|
|
3
|
+
let { value }: Props = $props();
|
|
4
|
+
</script>
|
|
5
|
+
{#if value}
|
|
6
|
+
<a href="mailto:{value}" class="field-email">{value}</a>
|
|
7
|
+
{:else}
|
|
8
|
+
<span class="field-email field-empty">—</span>
|
|
9
|
+
{/if}
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
.field-email { color: var(--color-primary, #3b82f6); text-decoration: none; }
|
|
13
|
+
.field-email:hover { text-decoration: underline; }
|
|
14
|
+
.field-empty { color: var(--color-text-muted, #9ca3af); }
|
|
15
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props { value: string | null | undefined; title?: string }
|
|
3
|
+
let { value, title }: Props = $props();
|
|
4
|
+
const fileName = $derived(value ? value.split('/').pop() ?? value : null);
|
|
5
|
+
</script>
|
|
6
|
+
{#if value}
|
|
7
|
+
<a href={value} download class="field-file" title={title ?? fileName ?? ''}>
|
|
8
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
|
9
|
+
{fileName}
|
|
10
|
+
</a>
|
|
11
|
+
{:else}
|
|
12
|
+
<span class="field-file field-empty">—</span>
|
|
13
|
+
{/if}
|
|
14
|
+
|
|
15
|
+
<style>
|
|
16
|
+
.field-file { color: var(--color-primary, #3b82f6); text-decoration: none; display: inline-flex; align-items: center; gap: 4px; }
|
|
17
|
+
.field-file:hover { text-decoration: underline; }
|
|
18
|
+
.field-empty { color: var(--color-text-muted, #9ca3af); }
|
|
19
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props { value: string | null | undefined; alt?: string; width?: number; height?: number }
|
|
3
|
+
let { value, alt = '', width = 80, height = 80 }: Props = $props();
|
|
4
|
+
</script>
|
|
5
|
+
{#if value}
|
|
6
|
+
<img src={value} {alt} {width} {height} class="field-image" />
|
|
7
|
+
{:else}
|
|
8
|
+
<span class="field-image field-empty">—</span>
|
|
9
|
+
{/if}
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
.field-image { border-radius: 8px; object-fit: cover; border: 1px solid var(--color-border, #e5e7eb); }
|
|
13
|
+
.field-empty { color: var(--color-text-muted, #9ca3af); display: inline-flex; align-items: center; justify-content: center; width: 80px; height: 80px; background: var(--color-surface, #f3f4f6); border-radius: 8px; }
|
|
14
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props { value: string | null | undefined }
|
|
3
|
+
let { value }: Props = $props();
|
|
4
|
+
</script>
|
|
5
|
+
{#if value}
|
|
6
|
+
<div class="field-markdown">{@html value}</div>
|
|
7
|
+
{:else}
|
|
8
|
+
<span class="field-empty">—</span>
|
|
9
|
+
{/if}
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
.field-markdown { line-height: 1.6; }
|
|
13
|
+
.field-markdown :global(h1), .field-markdown :global(h2), .field-markdown :global(h3) { margin-top: 0.5em; margin-bottom: 0.25em; }
|
|
14
|
+
.field-markdown :global(p) { margin: 0.5em 0; }
|
|
15
|
+
.field-markdown :global(code) { background: var(--color-surface, #f3f4f6); padding: 2px 6px; border-radius: 4px; font-size: 0.875em; }
|
|
16
|
+
.field-markdown :global(pre) { background: var(--color-surface, #f3f4f6); padding: 12px; border-radius: 8px; overflow-x: auto; }
|
|
17
|
+
.field-markdown :global(a) { color: var(--color-primary, #3b82f6); }
|
|
18
|
+
.field-empty { color: var(--color-text-muted, #9ca3af); }
|
|
19
|
+
</style>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props { value: number | string | null | undefined; locale?: string; options?: Intl.NumberFormatOptions }
|
|
3
|
+
let { value, locale = 'en-US', options = {} }: Props = $props();
|
|
4
|
+
const formatted = $derived(value != null ? new Intl.NumberFormat(locale, options).format(Number(value)) : '—');
|
|
5
|
+
</script>
|
|
6
|
+
<span class="field-number">{formatted}</span>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props { value: string | string[] | null | undefined; color?: string }
|
|
3
|
+
let { value, color = 'var(--color-primary, #3b82f6)' }: Props = $props();
|
|
4
|
+
const tags = $derived(Array.isArray(value) ? value : value ? [value] : []);
|
|
5
|
+
</script>
|
|
6
|
+
<span class="field-tags">
|
|
7
|
+
{#each tags as tag}
|
|
8
|
+
<span class="tag" style="--tag-color: {color}">{tag}</span>
|
|
9
|
+
{/each}
|
|
10
|
+
{#if tags.length === 0}<span class="field-empty">—</span>{/if}
|
|
11
|
+
</span>
|
|
12
|
+
|
|
13
|
+
<style>
|
|
14
|
+
.field-tags { display: inline-flex; flex-wrap: wrap; gap: 4px; }
|
|
15
|
+
.tag { display: inline-block; padding: 2px 8px; border-radius: 9999px; font-size: 12px; background: color-mix(in srgb, var(--tag-color) 15%, transparent); color: var(--tag-color); border: 1px solid color-mix(in srgb, var(--tag-color) 30%, transparent); }
|
|
16
|
+
.field-empty { color: var(--color-text-muted, #9ca3af); }
|
|
17
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props { value: string | null | undefined; maxLength?: number }
|
|
3
|
+
let { value, maxLength }: Props = $props();
|
|
4
|
+
const display = $derived(value && maxLength && value.length > maxLength ? value.slice(0, maxLength) + '…' : value);
|
|
5
|
+
const needsTooltip = $derived(value && maxLength && value.length > maxLength);
|
|
6
|
+
</script>
|
|
7
|
+
{#if value}
|
|
8
|
+
<span class="field-text" title={needsTooltip ? value : undefined}>{display}</span>
|
|
9
|
+
{:else}
|
|
10
|
+
<span class="field-text field-empty">—</span>
|
|
11
|
+
{/if}
|
|
12
|
+
|
|
13
|
+
<style>
|
|
14
|
+
.field-text { display: inline-block; }
|
|
15
|
+
.field-empty { color: var(--color-text-muted, #9ca3af); }
|
|
16
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props { value: string | null | undefined; label?: string }
|
|
3
|
+
let { value, label }: Props = $props();
|
|
4
|
+
const display = $derived(label ?? value ?? '');
|
|
5
|
+
</script>
|
|
6
|
+
{#if value}
|
|
7
|
+
<a href={value} target="_blank" rel="noopener noreferrer" class="field-url">
|
|
8
|
+
{display}
|
|
9
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
|
10
|
+
</a>
|
|
11
|
+
{:else}
|
|
12
|
+
<span class="field-url field-empty">—</span>
|
|
13
|
+
{/if}
|
|
14
|
+
|
|
15
|
+
<style>
|
|
16
|
+
.field-url { color: var(--color-primary, #3b82f6); text-decoration: none; display: inline-flex; align-items: center; gap: 4px; }
|
|
17
|
+
.field-url:hover { text-decoration: underline; }
|
|
18
|
+
.field-url svg { opacity: 0.6; }
|
|
19
|
+
.field-empty { color: var(--color-text-muted, #9ca3af); }
|
|
20
|
+
</style>
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
defaultVariants: { size: "default" },
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
// AvatarSize type is exported from the package index.ts
|
|
20
|
+
type AvatarSize = VariantProps<typeof avatarVariants>["size"];
|
|
20
21
|
|
|
21
22
|
type Props = WithElementRef<HTMLImgAttributes> & {
|
|
22
23
|
size?: AvatarSize;
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,38 @@ export { default as UndoableNotification } from './components/UndoableNotificati
|
|
|
26
26
|
export { default as ModalForm } from './components/ModalForm.svelte';
|
|
27
27
|
export { default as DrawerForm } from './components/DrawerForm.svelte';
|
|
28
28
|
export { default as DevTools } from './components/DevTools.svelte';
|
|
29
|
+
export { default as Authenticated } from './components/Authenticated.svelte';
|
|
30
|
+
export { default as UpdatePasswordPage } from './components/UpdatePasswordPage.svelte';
|
|
31
|
+
export { default as ConfigErrorScreen } from './components/ConfigErrorScreen.svelte';
|
|
32
|
+
export { default as InferencerPanel } from './components/InferencerPanel.svelte';
|
|
33
|
+
export { default as LiveIndicator } from './components/LiveIndicator.svelte';
|
|
34
|
+
export { default as ListPage } from './components/ListPage.svelte';
|
|
35
|
+
export { default as CreatePage } from './components/CreatePage.svelte';
|
|
36
|
+
export { default as EditPage } from './components/EditPage.svelte';
|
|
37
|
+
export { default as AutoSaveIndicator } from './components/AutoSaveIndicator.svelte';
|
|
38
|
+
export { default as ErrorComponent } from './components/ErrorComponent.svelte';
|
|
39
|
+
export { default as PageSkeleton } from './components/PageSkeleton.svelte';
|
|
40
|
+
export { default as NavigateToResource } from './components/NavigateToResource.svelte';
|
|
41
|
+
export { default as CatchAllNavigate } from './components/CatchAllNavigate.svelte';
|
|
42
|
+
export { default as ThemedTitle } from './components/ThemedTitle.svelte';
|
|
43
|
+
|
|
44
|
+
// Field display components
|
|
45
|
+
export { default as NumberField } from './components/fields/NumberField.svelte';
|
|
46
|
+
export { default as DateField } from './components/fields/DateField.svelte';
|
|
47
|
+
export { default as EmailField } from './components/fields/EmailField.svelte';
|
|
48
|
+
export { default as UrlField } from './components/fields/UrlField.svelte';
|
|
49
|
+
export { default as BooleanField } from './components/fields/BooleanField.svelte';
|
|
50
|
+
export { default as TagField } from './components/fields/TagField.svelte';
|
|
51
|
+
export { default as FileField } from './components/fields/FileField.svelte';
|
|
52
|
+
export { default as MarkdownField } from './components/fields/MarkdownField.svelte';
|
|
53
|
+
export { default as TextField } from './components/fields/TextField.svelte';
|
|
54
|
+
export { default as ImageField } from './components/fields/ImageField.svelte';
|
|
55
|
+
|
|
56
|
+
// CRUD Buttons
|
|
57
|
+
export {
|
|
58
|
+
CreateButton, EditButton, DeleteButton, ShowButton, ListButton,
|
|
59
|
+
RefreshButton, ExportButton, ImportButton, SaveButton, CloneButton,
|
|
60
|
+
} from './components/buttons/index.js';
|
|
29
61
|
|
|
30
62
|
// Base UI components (shadcn-svelte)
|
|
31
63
|
export { Button, buttonVariants } from './components/ui/button/index.js';
|
|
@@ -1,33 +1,55 @@
|
|
|
1
1
|
// Shared reactive hash router state — .svelte.ts enables $state at module level
|
|
2
|
-
import { matchRoute } from '@svadmin/core/router';
|
|
2
|
+
import { matchRoute, setActiveRouterProvider } from '@svadmin/core/router';
|
|
3
|
+
import type { RouterProvider } from '@svadmin/core';
|
|
3
4
|
|
|
4
5
|
let _route = $state('/');
|
|
5
6
|
let _params: Record<string, string> = $state({});
|
|
6
7
|
let _initialized = false;
|
|
8
|
+
let _provider: RouterProvider | undefined;
|
|
7
9
|
|
|
8
10
|
const ROUTES = [
|
|
9
11
|
'/login',
|
|
10
12
|
'/register',
|
|
11
13
|
'/forgot-password',
|
|
14
|
+
'/update-password',
|
|
12
15
|
'/',
|
|
13
16
|
'/:resource',
|
|
14
17
|
'/:resource/create',
|
|
15
18
|
'/:resource/edit/:id',
|
|
16
19
|
'/:resource/show/:id',
|
|
20
|
+
'/:parent/:parentId/:resource',
|
|
21
|
+
'/:parent/:parentId/:resource/create',
|
|
22
|
+
'/:parent/:parentId/:resource/edit/:id',
|
|
23
|
+
'/:parent/:parentId/:resource/show/:id',
|
|
17
24
|
];
|
|
18
25
|
|
|
19
26
|
function sync() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
let path: string;
|
|
28
|
+
|
|
29
|
+
if (_provider) {
|
|
30
|
+
const parsed = _provider.parse();
|
|
31
|
+
path = parsed.pathname || '/';
|
|
32
|
+
const m = matchRoute(path, ROUTES);
|
|
33
|
+
_route = m?.route ?? '/';
|
|
34
|
+
_params = { ...(m?.params ?? {}), ...parsed.params };
|
|
35
|
+
} else {
|
|
36
|
+
path = typeof window !== 'undefined' ? window.location.hash.replace(/^#/, '') || '/' : '/';
|
|
37
|
+
const m = matchRoute(path, ROUTES);
|
|
38
|
+
_route = m?.route ?? '/';
|
|
39
|
+
_params = m?.params ?? {};
|
|
40
|
+
}
|
|
24
41
|
}
|
|
25
42
|
|
|
26
|
-
export function initRouter() {
|
|
43
|
+
export function initRouter(provider?: RouterProvider) {
|
|
27
44
|
if (_initialized) return;
|
|
28
45
|
_initialized = true;
|
|
46
|
+
_provider = provider;
|
|
47
|
+
setActiveRouterProvider(provider);
|
|
29
48
|
sync();
|
|
30
|
-
window
|
|
49
|
+
if (typeof window !== 'undefined') {
|
|
50
|
+
window.addEventListener('hashchange', sync);
|
|
51
|
+
window.addEventListener('popstate', sync);
|
|
52
|
+
}
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
export function getRoute(): string {
|
|
@@ -37,3 +59,7 @@ export function getRoute(): string {
|
|
|
37
59
|
export function getParams(): Record<string, string> {
|
|
38
60
|
return _params;
|
|
39
61
|
}
|
|
62
|
+
|
|
63
|
+
export function getRouterProviderInstance(): RouterProvider | undefined {
|
|
64
|
+
return _provider;
|
|
65
|
+
}
|