@viniaraujo68/plinth 0.3.1 → 0.6.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.
Files changed (107) hide show
  1. package/README.md +197 -19
  2. package/dist/components/DateRangePicker.svelte +93 -57
  3. package/dist/components/DateRangePicker.svelte.d.ts +32 -15
  4. package/dist/components/ErrorDisplay.svelte +30 -16
  5. package/dist/components/ErrorDisplay.svelte.d.ts +4 -0
  6. package/dist/components/JsonView.svelte +43 -0
  7. package/dist/components/JsonView.svelte.d.ts +4 -0
  8. package/dist/components/JsonViewFrame.svelte +654 -0
  9. package/dist/components/JsonViewFrame.svelte.d.ts +31 -0
  10. package/dist/components/Paginator.svelte +34 -8
  11. package/dist/components/Paginator.svelte.d.ts +10 -0
  12. package/dist/components/RequestErrorDisplay.svelte +43 -0
  13. package/dist/components/RequestErrorDisplay.svelte.d.ts +11 -0
  14. package/dist/components/Tabs.svelte +204 -0
  15. package/dist/components/Tabs.svelte.d.ts +76 -0
  16. package/dist/components/VirtualJsonView.svelte +67 -0
  17. package/dist/components/VirtualJsonView.svelte.d.ts +4 -0
  18. package/dist/components/date-range.d.ts +82 -14
  19. package/dist/components/date-range.js +86 -5
  20. package/dist/components/index.d.ts +9 -2
  21. package/dist/components/index.js +15 -9
  22. package/dist/components/json-view.d.ts +70 -0
  23. package/dist/components/json-view.js +302 -0
  24. package/dist/components/request-error.d.ts +8 -0
  25. package/dist/components/request-error.js +55 -0
  26. package/dist/components/tabs.d.ts +5 -0
  27. package/dist/components/tabs.js +1 -0
  28. package/dist/http.d.ts +10 -5
  29. package/dist/http.js +34 -6
  30. package/dist/i18n/LocaleToggle.svelte +45 -0
  31. package/dist/i18n/LocaleToggle.svelte.d.ts +48 -0
  32. package/dist/i18n/i18n.svelte.d.ts +56 -0
  33. package/dist/i18n/i18n.svelte.js +94 -0
  34. package/dist/i18n/index.d.ts +3 -0
  35. package/dist/i18n/index.js +7 -0
  36. package/dist/i18n/translate.d.ts +30 -0
  37. package/dist/i18n/translate.js +38 -0
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.js +1 -1
  40. package/dist/redirect.d.ts +40 -0
  41. package/dist/redirect.js +59 -0
  42. package/dist/routing.svelte.d.ts +1 -0
  43. package/dist/routing.svelte.js +3 -0
  44. package/dist/shell/AppShell.svelte +265 -93
  45. package/dist/shell/AppShell.svelte.d.ts +3 -2
  46. package/dist/shell/NavigationProgress.svelte +85 -0
  47. package/dist/shell/NavigationProgress.svelte.d.ts +31 -0
  48. package/dist/shell/TopBar.svelte +191 -0
  49. package/dist/shell/TopBar.svelte.d.ts +41 -0
  50. package/dist/shell/UserAvatar.svelte +50 -0
  51. package/dist/shell/UserAvatar.svelte.d.ts +14 -0
  52. package/dist/shell/index.d.ts +2 -0
  53. package/dist/shell/index.js +7 -3
  54. package/dist/shell/initials.d.ts +6 -0
  55. package/dist/shell/initials.js +13 -0
  56. package/dist/table/DataTable.svelte +199 -9
  57. package/dist/table/DataTable.svelte.d.ts +28 -5
  58. package/dist/table/FilterBar.svelte +417 -0
  59. package/dist/table/FilterBar.svelte.d.ts +34 -0
  60. package/dist/table/FilterRuleEditor.svelte +290 -0
  61. package/dist/table/FilterRuleEditor.svelte.d.ts +33 -0
  62. package/dist/table/filter-labels.d.ts +23 -0
  63. package/dist/table/filter-labels.js +30 -0
  64. package/dist/table/filter.d.ts +27 -0
  65. package/dist/table/filter.js +279 -0
  66. package/dist/table/index.d.ts +4 -1
  67. package/dist/table/index.js +5 -2
  68. package/dist/table/types.d.ts +49 -0
  69. package/dist/toast/Toaster.svelte +7 -3
  70. package/dist/toast/index.d.ts +1 -1
  71. package/dist/toast/toast-manager.svelte.d.ts +29 -1
  72. package/dist/toast/toast-manager.svelte.js +61 -1
  73. package/dist/url-state/codec.d.ts +15 -0
  74. package/dist/url-state/codec.js +1 -0
  75. package/dist/url-state/index.d.ts +5 -0
  76. package/dist/url-state/index.js +4 -0
  77. package/dist/url-state/params-codec.d.ts +73 -0
  78. package/dist/url-state/params-codec.js +116 -0
  79. package/dist/url-state/search-params.d.ts +4 -0
  80. package/dist/url-state/search-params.js +28 -0
  81. package/dist/url-state/synced-state.svelte.d.ts +20 -0
  82. package/dist/url-state/synced-state.svelte.js +88 -0
  83. package/dist/url-state/url-store.svelte.d.ts +12 -0
  84. package/dist/url-state/url-store.svelte.js +56 -0
  85. package/dist/virtual/DataList.svelte +215 -0
  86. package/dist/virtual/DataList.svelte.d.ts +52 -0
  87. package/dist/virtual/VirtualList.svelte +150 -0
  88. package/dist/virtual/VirtualList.svelte.d.ts +42 -0
  89. package/dist/virtual/index.d.ts +7 -0
  90. package/dist/virtual/index.js +6 -0
  91. package/dist/virtual/keyed-cache.d.ts +7 -0
  92. package/dist/virtual/keyed-cache.js +20 -0
  93. package/dist/virtual/labels.d.ts +12 -0
  94. package/dist/virtual/labels.js +6 -0
  95. package/dist/virtual/paging.d.ts +5 -0
  96. package/dist/virtual/paging.js +11 -0
  97. package/dist/virtual/range.d.ts +9 -0
  98. package/dist/virtual/range.js +31 -0
  99. package/dist/virtual/size-tree.d.ts +10 -0
  100. package/dist/virtual/size-tree.js +56 -0
  101. package/dist/virtual/virtualizer.svelte.d.ts +38 -0
  102. package/dist/virtual/virtualizer.svelte.js +295 -0
  103. package/dist/virtual/windowed-data.svelte.d.ts +38 -0
  104. package/dist/virtual/windowed-data.svelte.js +139 -0
  105. package/dist/virtual/windowed-virtualizer.svelte.d.ts +9 -0
  106. package/dist/virtual/windowed-virtualizer.svelte.js +22 -0
  107. package/package.json +13 -1
@@ -0,0 +1,12 @@
1
+ import type { ExternalStore, UrlStateCodec } from "./codec.js";
2
+ import { SyncedState } from "./synced-state.svelte.js";
3
+ export type ReplaceHistory = boolean | ((previous: URLSearchParams, next: URLSearchParams) => boolean);
4
+ export interface UrlStoreOptions {
5
+ replace?: ReplaceHistory;
6
+ }
7
+ export declare const urlStore: (ownedParams: readonly string[], options?: UrlStoreOptions) => ExternalStore<URLSearchParams>;
8
+ export interface UrlStateOptions<State> extends UrlStoreOptions {
9
+ resetOnCommit?: readonly (keyof State)[];
10
+ onSynced?: () => void;
11
+ }
12
+ export declare const createUrlState: <State>(codec: UrlStateCodec<State>, options?: UrlStateOptions<State>) => SyncedState<State, URLSearchParams>;
@@ -0,0 +1,56 @@
1
+ import { goto } from "$app/navigation";
2
+ import { page } from "$app/state";
3
+ import { BROWSER } from "esm-env";
4
+ import { untrack } from "svelte";
5
+ import { pickParams, sameLocation, withOwnedParams } from "./search-params.js";
6
+ import { SyncedState } from "./synced-state.svelte.js";
7
+ let pendingWrite;
8
+ const currentUrl = () => new URL(pendingWrite?.href ?? page.url.href);
9
+ export const urlStore = (ownedParams, options = {}) => {
10
+ const { replace = false } = options;
11
+ let lastSeenHref;
12
+ const replaces = (previous, next) => typeof replace === "function" ? replace(previous, next) : replace;
13
+ return {
14
+ read: () => currentUrl().searchParams,
15
+ write: (params) => {
16
+ if (!BROWSER)
17
+ return;
18
+ const base = currentUrl();
19
+ const target = withOwnedParams(base, ownedParams, params);
20
+ if (sameLocation(base, target))
21
+ return;
22
+ const write = {
23
+ href: target.href,
24
+ replace: replaces(pickParams(base.searchParams, ownedParams), params) &&
25
+ (pendingWrite?.replace ?? true),
26
+ };
27
+ lastSeenHref = write.href;
28
+ pendingWrite = write;
29
+ void goto(write.href, {
30
+ keepFocus: true,
31
+ noScroll: true,
32
+ replaceState: write.replace,
33
+ }).finally(() => {
34
+ if (pendingWrite === write)
35
+ pendingWrite = undefined;
36
+ });
37
+ },
38
+ subscribe: (onChange) => {
39
+ $effect(() => {
40
+ const href = page.url.href;
41
+ if (pendingWrite?.href === href)
42
+ pendingWrite = undefined;
43
+ if (href === lastSeenHref)
44
+ return;
45
+ lastSeenHref = href;
46
+ untrack(onChange);
47
+ });
48
+ },
49
+ };
50
+ };
51
+ export const createUrlState = (codec, options = {}) => new SyncedState({
52
+ codec,
53
+ store: urlStore(codec.ownedParams(), { replace: options.replace }),
54
+ resetOnCommit: options.resetOnCommit,
55
+ onSynced: options.onSynced,
56
+ });
@@ -0,0 +1,215 @@
1
+ <script lang="ts" generics="Row">
2
+ import { untrack, type Snippet } from "svelte";
3
+ import type { ClassValue } from "svelte/elements";
4
+ import Skeleton from "../components/Skeleton.svelte";
5
+ import { DEFAULT_DATA_LIST_LABELS, type DataListFailure, type DataListLabels } from "./labels.js";
6
+ import type { ScrollAlign } from "./range.js";
7
+ import VirtualList from "./VirtualList.svelte";
8
+ import type { WindowedData } from "./windowed-data.svelte.js";
9
+ import { WindowedVirtualizer } from "./windowed-virtualizer.svelte.js";
10
+
11
+ type PlaceholderStatus = "loading" | "error";
12
+
13
+ interface Props {
14
+ data: WindowedData<Row>;
15
+ row: Snippet<[Row, number]>;
16
+ placeholder?: Snippet<[number, PlaceholderStatus]>;
17
+ empty?: Snippet;
18
+ error?: Snippet<[DataListFailure]>;
19
+ itemSize?: number | ((index: number) => number);
20
+ overscan?: number;
21
+ placeholderCount?: number;
22
+ class?: ClassValue;
23
+ crossSize?: number | string;
24
+ label?: string;
25
+ labels?: Partial<DataListLabels>;
26
+ }
27
+
28
+ let {
29
+ data,
30
+ row,
31
+ placeholder,
32
+ empty,
33
+ error,
34
+ itemSize = 48,
35
+ overscan = 6,
36
+ placeholderCount = 8,
37
+ class: className,
38
+ crossSize,
39
+ label,
40
+ labels,
41
+ }: Props = $props();
42
+
43
+ const text = $derived({ ...DEFAULT_DATA_LIST_LABELS, ...labels });
44
+
45
+ const estimate = (index: number) => (typeof itemSize === "function" ? itemSize(index) : itemSize);
46
+
47
+ const virtualizer = untrack(
48
+ () => new WindowedVirtualizer({ data, estimateSize: estimate, overscan: () => overscan }),
49
+ );
50
+
51
+ const retryPages = () => data.retry();
52
+
53
+ const failure: DataListFailure | null = $derived(
54
+ data.failures.length === 0
55
+ ? null
56
+ : { error: data.error, pages: data.failedPages, retry: retryPages },
57
+ );
58
+
59
+ const phase = $derived.by(() => {
60
+ if (data.resolved) return data.count === 0 ? "empty" : "ready";
61
+ return failure ? "failed" : "pending";
62
+ });
63
+
64
+ const placeholderIndices = $derived(
65
+ Array.from({ length: Math.max(0, placeholderCount) }, (_, index) => index),
66
+ );
67
+
68
+ export function scrollToIndex(index: number, align?: ScrollAlign): void {
69
+ virtualizer.scrollToIndex(index, align);
70
+ }
71
+
72
+ export function scrollToOffset(offset: number): void {
73
+ virtualizer.scrollToOffset(offset);
74
+ }
75
+
76
+ export function scrollToEnd(): void {
77
+ virtualizer.scrollToEnd();
78
+ }
79
+
80
+ export function retry(): void {
81
+ retryPages();
82
+ }
83
+ </script>
84
+
85
+ {#snippet placeholderView(index: number, status: PlaceholderStatus)}
86
+ {#if placeholder}
87
+ {@render placeholder(index, status)}
88
+ {:else}
89
+ <div
90
+ class="plinth-data-placeholder"
91
+ data-status={status}
92
+ style:block-size="{estimate(index)}px"
93
+ >
94
+ {#if status === "error"}
95
+ <span class="plinth-data-failed"></span>
96
+ {:else}
97
+ <Skeleton class="plinth-data-skeleton" />
98
+ {/if}
99
+ </div>
100
+ {/if}
101
+ {/snippet}
102
+
103
+ {#snippet failureView(current: DataListFailure)}
104
+ {#if error}
105
+ {@render error(current)}
106
+ {:else}
107
+ <span>{text.error}</span>
108
+ <button type="button" class="btn btn-ghost btn-xs" onclick={current.retry}>{text.retry}</button>
109
+ {/if}
110
+ {/snippet}
111
+
112
+ <div
113
+ class={["plinth-data-list", className]}
114
+ aria-busy={phase === "pending" || data.loading}
115
+ data-phase={phase}
116
+ >
117
+ {#if phase === "failed" && failure}
118
+ <div class="plinth-data-state plinth-data-error" role="alert">
119
+ {@render failureView(failure)}
120
+ </div>
121
+ {:else if phase === "pending"}
122
+ <div class="plinth-data-placeholders" role="status" aria-label={text.loading}>
123
+ {#each placeholderIndices as index (index)}
124
+ {@render placeholderView(index, "loading")}
125
+ {/each}
126
+ </div>
127
+ {:else if phase === "empty"}
128
+ <div class="plinth-data-state">
129
+ {#if empty}{@render empty()}{:else}{text.empty}{/if}
130
+ </div>
131
+ {:else}
132
+ {#if failure}
133
+ <div class="plinth-data-banner plinth-data-error" role="alert">
134
+ {@render failureView(failure)}
135
+ </div>
136
+ {/if}
137
+ <VirtualList {virtualizer} class="plinth-data-viewport" {crossSize} {label}>
138
+ {#snippet item(index)}
139
+ {@const value = data.get(index)}
140
+ {#if value !== undefined}
141
+ {@render row(value, index)}
142
+ {:else}
143
+ {@render placeholderView(index, data.statusOf(index) === "error" ? "error" : "loading")}
144
+ {/if}
145
+ {/snippet}
146
+ </VirtualList>
147
+ {/if}
148
+ </div>
149
+
150
+ <style>
151
+ .plinth-data-list {
152
+ display: flex;
153
+ flex-direction: column;
154
+ min-height: 0;
155
+ overflow: hidden;
156
+ }
157
+
158
+ .plinth-data-list > :global(.plinth-data-viewport) {
159
+ flex: 1 1 auto;
160
+ min-height: 0;
161
+ }
162
+
163
+ .plinth-data-placeholders {
164
+ flex: 1 1 auto;
165
+ min-height: 0;
166
+ overflow: hidden;
167
+ }
168
+
169
+ .plinth-data-placeholder {
170
+ display: flex;
171
+ align-items: center;
172
+ padding-inline: 1rem;
173
+ }
174
+
175
+ .plinth-data-placeholder :global(.plinth-data-skeleton) {
176
+ inline-size: 60%;
177
+ block-size: 0.75rem;
178
+ }
179
+
180
+ .plinth-data-failed {
181
+ display: block;
182
+ inline-size: 60%;
183
+ block-size: 0.75rem;
184
+ border: 1px dashed color-mix(in oklch, var(--color-error) 45%, transparent);
185
+ border-radius: var(--radius-field, 0.5rem);
186
+ }
187
+
188
+ .plinth-data-state {
189
+ display: flex;
190
+ flex: 1 1 auto;
191
+ flex-direction: column;
192
+ align-items: center;
193
+ justify-content: center;
194
+ gap: 0.75rem;
195
+ padding: 2rem 1rem;
196
+ color: color-mix(in oklch, var(--color-base-content) 60%, transparent);
197
+ text-align: center;
198
+ }
199
+
200
+ .plinth-data-banner {
201
+ display: flex;
202
+ flex: none;
203
+ align-items: center;
204
+ justify-content: space-between;
205
+ gap: 0.75rem;
206
+ padding: 0.375rem 0.75rem;
207
+ border-bottom: 1px solid color-mix(in oklch, var(--color-error) 30%, transparent);
208
+ background: color-mix(in oklch, var(--color-error) 10%, transparent);
209
+ font-size: 0.875rem;
210
+ }
211
+
212
+ .plinth-data-state.plinth-data-error {
213
+ color: var(--color-base-content);
214
+ }
215
+ </style>
@@ -0,0 +1,52 @@
1
+ import { type Snippet } from "svelte";
2
+ import type { ClassValue } from "svelte/elements";
3
+ import { type DataListFailure, type DataListLabels } from "./labels.js";
4
+ import type { ScrollAlign } from "./range.js";
5
+ import type { WindowedData } from "./windowed-data.svelte.js";
6
+ declare function $$render<Row>(): {
7
+ props: {
8
+ data: WindowedData<Row>;
9
+ row: Snippet<[Row, number]>;
10
+ placeholder?: Snippet<[number, "error" | "loading"]>;
11
+ empty?: Snippet;
12
+ error?: Snippet<[DataListFailure]>;
13
+ itemSize?: number | ((index: number) => number);
14
+ overscan?: number;
15
+ placeholderCount?: number;
16
+ class?: ClassValue;
17
+ crossSize?: number | string;
18
+ label?: string;
19
+ labels?: Partial<DataListLabels>;
20
+ };
21
+ exports: {
22
+ scrollToIndex: (index: number, align?: ScrollAlign) => void;
23
+ scrollToOffset: (offset: number) => void;
24
+ scrollToEnd: () => void;
25
+ retry: () => void;
26
+ };
27
+ bindings: "";
28
+ slots: {};
29
+ events: {};
30
+ };
31
+ declare class __sveltets_Render<Row> {
32
+ props(): ReturnType<typeof $$render<Row>>['props'];
33
+ events(): ReturnType<typeof $$render<Row>>['events'];
34
+ slots(): ReturnType<typeof $$render<Row>>['slots'];
35
+ bindings(): "";
36
+ exports(): {
37
+ scrollToIndex: (index: number, align?: ScrollAlign) => void;
38
+ scrollToOffset: (offset: number) => void;
39
+ scrollToEnd: () => void;
40
+ retry: () => void;
41
+ };
42
+ }
43
+ interface $$IsomorphicComponent {
44
+ new <Row>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Row>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Row>['props']>, ReturnType<__sveltets_Render<Row>['events']>, ReturnType<__sveltets_Render<Row>['slots']>> & {
45
+ $$bindings?: ReturnType<__sveltets_Render<Row>['bindings']>;
46
+ } & ReturnType<__sveltets_Render<Row>['exports']>;
47
+ <Row>(internal: unknown, props: ReturnType<__sveltets_Render<Row>['props']> & {}): ReturnType<__sveltets_Render<Row>['exports']>;
48
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
49
+ }
50
+ declare const DataList: $$IsomorphicComponent;
51
+ type DataList<Row> = InstanceType<typeof DataList<Row>>;
52
+ export default DataList;
@@ -0,0 +1,150 @@
1
+ <script lang="ts">
2
+ import { untrack, type Snippet } from "svelte";
3
+ import type { ClassValue } from "svelte/elements";
4
+ import type { ScrollAlign } from "./range.js";
5
+ import { Virtualizer, type ItemKey, type ScrollAnchor } from "./virtualizer.svelte.js";
6
+
7
+ interface SharedProps {
8
+ item: Snippet<[number]>;
9
+ class?: ClassValue;
10
+ crossSize?: number | string;
11
+ label?: string;
12
+ }
13
+
14
+ interface OwnedProps {
15
+ count: number;
16
+ itemSize?: number | ((index: number) => number);
17
+ overscan?: number;
18
+ getItemKey?: (index: number) => ItemKey;
19
+ horizontal?: boolean;
20
+ anchorTo?: ScrollAnchor;
21
+ followOnAppend?: boolean;
22
+ initialOffset?: number;
23
+ virtualizer?: never;
24
+ }
25
+
26
+ interface ExternalProps {
27
+ virtualizer: Virtualizer;
28
+ count?: never;
29
+ itemSize?: never;
30
+ overscan?: never;
31
+ getItemKey?: never;
32
+ horizontal?: never;
33
+ anchorTo?: never;
34
+ followOnAppend?: never;
35
+ initialOffset?: never;
36
+ }
37
+
38
+ type Props = SharedProps & (OwnedProps | ExternalProps);
39
+
40
+ let {
41
+ item,
42
+ class: className,
43
+ crossSize,
44
+ label,
45
+ virtualizer: external,
46
+ count,
47
+ itemSize,
48
+ overscan,
49
+ getItemKey,
50
+ horizontal,
51
+ anchorTo,
52
+ followOnAppend,
53
+ initialOffset,
54
+ }: Props = $props();
55
+
56
+ const owned = () =>
57
+ new Virtualizer({
58
+ count: () => count ?? 0,
59
+ estimateSize: (index) =>
60
+ typeof itemSize === "function" ? itemSize(index) : (itemSize ?? 48),
61
+ overscan: () => overscan ?? 6,
62
+ getItemKey: (index) => getItemKey?.(index) ?? index,
63
+ horizontal,
64
+ anchorTo,
65
+ followOnAppend,
66
+ initialOffset,
67
+ });
68
+
69
+ const virtualizer = untrack(() => external ?? owned());
70
+
71
+ const crossLength = $derived(typeof crossSize === "number" ? `${crossSize}px` : crossSize);
72
+ const axis = $derived(virtualizer.horizontal ? "X" : "Y");
73
+
74
+ export function scrollToIndex(index: number, align?: ScrollAlign): void {
75
+ virtualizer.scrollToIndex(index, align);
76
+ }
77
+
78
+ export function scrollToOffset(offset: number): void {
79
+ virtualizer.scrollToOffset(offset);
80
+ }
81
+
82
+ export function scrollToEnd(): void {
83
+ virtualizer.scrollToEnd();
84
+ }
85
+
86
+ export function prepend(count: number): void {
87
+ virtualizer.prepend(count);
88
+ }
89
+
90
+ export function clearMeasurements(): void {
91
+ virtualizer.clearMeasurements();
92
+ }
93
+ </script>
94
+
95
+ <div
96
+ class={["plinth-virtual", className]}
97
+ class:plinth-virtual-horizontal={virtualizer.horizontal}
98
+ role={label === undefined ? undefined : "region"}
99
+ aria-label={label}
100
+ {@attach virtualizer.viewport}
101
+ >
102
+ <div
103
+ class="plinth-virtual-content"
104
+ style:block-size={virtualizer.horizontal ? undefined : `${virtualizer.totalSize}px`}
105
+ style:inline-size={virtualizer.horizontal ? `${virtualizer.totalSize}px` : undefined}
106
+ style:min-inline-size={virtualizer.horizontal ? undefined : crossLength}
107
+ style:min-block-size={virtualizer.horizontal ? crossLength : undefined}
108
+ >
109
+ {#each virtualizer.items as entry (entry.key)}
110
+ <div
111
+ class="plinth-virtual-item"
112
+ data-index={entry.index}
113
+ style:transform="translate{axis}({entry.start}px)"
114
+ {@attach virtualizer.measure(entry.index)}
115
+ >
116
+ {@render item(entry.index)}
117
+ </div>
118
+ {/each}
119
+ </div>
120
+ </div>
121
+
122
+ <style>
123
+ .plinth-virtual {
124
+ display: block;
125
+ position: relative;
126
+ overflow: auto;
127
+ overflow-anchor: none;
128
+ }
129
+
130
+ .plinth-virtual-content {
131
+ position: relative;
132
+ inline-size: 100%;
133
+ }
134
+
135
+ .plinth-virtual-horizontal > .plinth-virtual-content {
136
+ block-size: 100%;
137
+ }
138
+
139
+ .plinth-virtual-item {
140
+ position: absolute;
141
+ top: 0;
142
+ left: 0;
143
+ right: 0;
144
+ }
145
+
146
+ .plinth-virtual-horizontal .plinth-virtual-item {
147
+ right: auto;
148
+ bottom: 0;
149
+ }
150
+ </style>
@@ -0,0 +1,42 @@
1
+ import { type Snippet } from "svelte";
2
+ import type { ClassValue } from "svelte/elements";
3
+ import type { ScrollAlign } from "./range.js";
4
+ import { Virtualizer, type ItemKey, type ScrollAnchor } from "./virtualizer.svelte.js";
5
+ interface SharedProps {
6
+ item: Snippet<[number]>;
7
+ class?: ClassValue;
8
+ crossSize?: number | string;
9
+ label?: string;
10
+ }
11
+ interface OwnedProps {
12
+ count: number;
13
+ itemSize?: number | ((index: number) => number);
14
+ overscan?: number;
15
+ getItemKey?: (index: number) => ItemKey;
16
+ horizontal?: boolean;
17
+ anchorTo?: ScrollAnchor;
18
+ followOnAppend?: boolean;
19
+ initialOffset?: number;
20
+ virtualizer?: never;
21
+ }
22
+ interface ExternalProps {
23
+ virtualizer: Virtualizer;
24
+ count?: never;
25
+ itemSize?: never;
26
+ overscan?: never;
27
+ getItemKey?: never;
28
+ horizontal?: never;
29
+ anchorTo?: never;
30
+ followOnAppend?: never;
31
+ initialOffset?: never;
32
+ }
33
+ type Props = SharedProps & (OwnedProps | ExternalProps);
34
+ declare const VirtualList: import("svelte").Component<Props, {
35
+ scrollToIndex: (index: number, align?: ScrollAlign) => void;
36
+ scrollToOffset: (offset: number) => void;
37
+ scrollToEnd: () => void;
38
+ prepend: (count: number) => void;
39
+ clearMeasurements: () => void;
40
+ }, "">;
41
+ type VirtualList = ReturnType<typeof VirtualList>;
42
+ export default VirtualList;
@@ -0,0 +1,7 @@
1
+ export { Virtualizer, type ItemKey, type ScrollAnchor, type VirtualItem, type VirtualizerOptions, } from "./virtualizer.svelte.js";
2
+ export type { ScrollAlign } from "./range.js";
3
+ export { WindowedData, type PageFailure, type PageRequest, type PageStatus, type WindowedDataOptions, type WindowedPage, } from "./windowed-data.svelte.js";
4
+ export { WindowedVirtualizer, type WindowedVirtualizerOptions, } from "./windowed-virtualizer.svelte.js";
5
+ export { DEFAULT_DATA_LIST_LABELS, type DataListFailure, type DataListLabels } from "./labels.js";
6
+ export { default as VirtualList } from "./VirtualList.svelte";
7
+ export { default as DataList } from "./DataList.svelte";
@@ -0,0 +1,6 @@
1
+ export { Virtualizer, } from "./virtualizer.svelte.js";
2
+ export { WindowedData, } from "./windowed-data.svelte.js";
3
+ export { WindowedVirtualizer, } from "./windowed-virtualizer.svelte.js";
4
+ export { DEFAULT_DATA_LIST_LABELS } from "./labels.js";
5
+ export { default as VirtualList } from "./VirtualList.svelte";
6
+ export { default as DataList } from "./DataList.svelte";
@@ -0,0 +1,7 @@
1
+ export declare class KeyedCache<K, V> {
2
+ #private;
3
+ get(key: K): V | undefined;
4
+ set(key: K, value: V): void;
5
+ getOrCreate(key: K, create: (key: K) => V): V;
6
+ clear(): void;
7
+ }
@@ -0,0 +1,20 @@
1
+ export class KeyedCache {
2
+ #entries = new Map();
3
+ get(key) {
4
+ return this.#entries.get(key);
5
+ }
6
+ set(key, value) {
7
+ this.#entries.set(key, value);
8
+ }
9
+ getOrCreate(key, create) {
10
+ const existing = this.#entries.get(key);
11
+ if (existing !== undefined)
12
+ return existing;
13
+ const created = create(key);
14
+ this.#entries.set(key, created);
15
+ return created;
16
+ }
17
+ clear() {
18
+ this.#entries.clear();
19
+ }
20
+ }
@@ -0,0 +1,12 @@
1
+ export interface DataListLabels {
2
+ loading: string;
3
+ empty: string;
4
+ error: string;
5
+ retry: string;
6
+ }
7
+ export declare const DEFAULT_DATA_LIST_LABELS: DataListLabels;
8
+ export interface DataListFailure {
9
+ error: unknown;
10
+ pages: number[];
11
+ retry: () => void;
12
+ }
@@ -0,0 +1,6 @@
1
+ export const DEFAULT_DATA_LIST_LABELS = {
2
+ loading: "Loading",
3
+ empty: "Nothing to show",
4
+ error: "Some rows could not be loaded.",
5
+ retry: "Retry",
6
+ };
@@ -0,0 +1,5 @@
1
+ export interface PageSpan {
2
+ first: number;
3
+ last: number;
4
+ }
5
+ export declare const pageSpan: (start: number, end: number, pageSize: number, knownCount: number | null) => PageSpan | null;
@@ -0,0 +1,11 @@
1
+ export const pageSpan = (start, end, pageSize, knownCount) => {
2
+ const first = Math.floor(Math.max(0, start) / pageSize);
3
+ const last = Math.max(first, Math.floor(Math.max(0, end) / pageSize));
4
+ if (knownCount === null)
5
+ return { first, last };
6
+ if (knownCount <= 0)
7
+ return null;
8
+ const lastPage = Math.ceil(knownCount / pageSize) - 1;
9
+ const cappedLast = Math.min(last, lastPage);
10
+ return { first: Math.min(first, cappedLast), last: cappedLast };
11
+ };
@@ -0,0 +1,9 @@
1
+ import type { SizeTree } from "./size-tree.js";
2
+ export type ScrollAlign = "start" | "center" | "end" | "auto";
3
+ export interface IndexRange {
4
+ from: number;
5
+ to: number;
6
+ }
7
+ export declare const visibleRange: (tree: SizeTree, scrollOffset: number, viewportSize: number, overscan: number) => IndexRange | null;
8
+ export declare const alignedOffset: (start: number, size: number, viewportSize: number, scrollOffset: number, align: ScrollAlign) => number | null;
9
+ export declare const clampOffset: (offset: number, totalSize: number, viewportSize: number) => number;
@@ -0,0 +1,31 @@
1
+ export const visibleRange = (tree, scrollOffset, viewportSize, overscan) => {
2
+ if (tree.length === 0)
3
+ return null;
4
+ const first = tree.indexAt(scrollOffset);
5
+ const viewEnd = scrollOffset + viewportSize;
6
+ let last = first;
7
+ let edge = tree.startOf(first);
8
+ while (last < tree.length && edge < viewEnd) {
9
+ edge += tree.sizeAt(last);
10
+ last++;
11
+ }
12
+ const extra = Math.max(0, Math.floor(overscan));
13
+ return {
14
+ from: Math.max(0, first - extra),
15
+ to: Math.min(tree.length, Math.max(last, first + 1) + extra),
16
+ };
17
+ };
18
+ export const alignedOffset = (start, size, viewportSize, scrollOffset, align) => {
19
+ if (align === "start")
20
+ return start;
21
+ if (align === "end")
22
+ return start + size - viewportSize;
23
+ if (align === "center")
24
+ return start + size / 2 - viewportSize / 2;
25
+ if (start < scrollOffset)
26
+ return start;
27
+ if (start + size > scrollOffset + viewportSize)
28
+ return start + size - viewportSize;
29
+ return null;
30
+ };
31
+ export const clampOffset = (offset, totalSize, viewportSize) => Math.min(Math.max(0, offset), Math.max(0, totalSize - viewportSize));
@@ -0,0 +1,10 @@
1
+ export declare class SizeTree {
2
+ #private;
3
+ readonly length: number;
4
+ constructor(sizes: ArrayLike<number>);
5
+ get total(): number;
6
+ sizeAt(index: number): number;
7
+ set(index: number, size: number): boolean;
8
+ startOf(index: number): number;
9
+ indexAt(offset: number): number;
10
+ }