@svgrid/grid 2.2.0 → 2.2.1
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/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +178 -178
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvColorInput.svelte +187 -187
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvDrawer.svelte +250 -250
- package/dist/SvGrid.svelte +3131 -3131
- package/dist/SvGridBoard.svelte +2339 -2339
- package/dist/SvGridChart.svelte +1724 -1724
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridDropdown.svelte +689 -689
- package/dist/SvGroupCell.svelte +109 -109
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +117 -117
- package/dist/SvNumberInput.svelte +131 -131
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPopover.svelte +139 -139
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvTooltip.svelte +127 -127
- package/dist/SvTour.svelte +204 -204
- package/dist/SvTree.svelte +377 -377
- package/dist/SvTreeSelect.svelte +230 -230
- package/dist/cdn/svgrid.js +8 -8
- package/dist/cdn/svgrid.svelte-external.js +8 -8
- package/dist/chart-export.js +8 -8
- package/package.json +10 -10
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +178 -178
- package/src/SvCarousel.svelte +141 -141
- package/src/SvColorInput.svelte +187 -187
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvDrawer.svelte +250 -250
- package/src/SvGrid.controller.svelte.ts +3158 -3158
- package/src/SvGrid.svelte +3131 -3131
- package/src/SvGrid.types.ts +1489 -1489
- package/src/SvGridBoard.svelte +2339 -2339
- package/src/SvGridChart.svelte +1724 -1724
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridDropdown.svelte +689 -689
- package/src/SvGroupCell.svelte +109 -109
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +117 -117
- package/src/SvNumberInput.svelte +131 -131
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPopover.svelte +139 -139
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvTooltip.svelte +127 -127
- package/src/SvTour.svelte +204 -204
- package/src/SvTree.svelte +377 -377
- package/src/SvTreeSelect.svelte +230 -230
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/build-api.ts +870 -870
- package/src/cell-formatting.ts +169 -169
- package/src/cell-render.ts +469 -469
- package/src/chart-export.ts +201 -201
- package/src/chart.ts +2296 -2296
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1111 -1111
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +179 -179
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +162 -162
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createMenu.svelte.ts +202 -202
- package/src/createPopoverSelect.svelte.ts +206 -206
- package/src/createTree.svelte.ts +319 -319
- package/src/editing.test.ts +859 -859
- package/src/editing.ts +675 -675
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +598 -598
- package/src/flex-render.ts +3 -3
- package/src/index.ts +623 -623
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/menus.ts +639 -639
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/render-component.ts +28 -28
- package/src/selection.test.ts +754 -754
- package/src/selection.ts +600 -600
- package/src/server-data-source.test.ts +289 -289
- package/src/server-data-source.ts +413 -413
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +489 -489
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/svgrid-wrapper.types.ts +501 -501
- package/src/svgrid.behavior.test.ts +706 -706
- package/src/svgrid.charting.test.ts +527 -527
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.wrapper.test.ts +40 -40
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +159 -159
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
package/src/GridFooter.svelte
CHANGED
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
<script lang="ts" generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData">
|
|
2
|
-
import {
|
|
3
|
-
getGridCellA11yProps,
|
|
4
|
-
getGridCellDomId,
|
|
5
|
-
getGridHeaderA11yProps,
|
|
6
|
-
getGridRootA11yProps,
|
|
7
|
-
getGridRowA11yProps,
|
|
8
|
-
type EditorContext,
|
|
9
|
-
type CellEditorOption,
|
|
10
|
-
type Column,
|
|
11
|
-
type Row,
|
|
12
|
-
type RowData,
|
|
13
|
-
type TableFeatures,
|
|
14
|
-
} from "./index";
|
|
15
|
-
import "./sv-grid-scrollbar";
|
|
16
|
-
import type { Snippet } from "svelte";
|
|
17
|
-
import {
|
|
18
|
-
RenderSnippetConfig,
|
|
19
|
-
RenderComponentConfig,
|
|
20
|
-
} from "./render-component";
|
|
21
|
-
import {
|
|
22
|
-
buildSparkline,
|
|
23
|
-
toSparklineValues,
|
|
24
|
-
} from "./sparkline";
|
|
25
|
-
import SvGridDropdown from "./SvGridDropdown.svelte";
|
|
26
|
-
import type {
|
|
27
|
-
Props,
|
|
28
|
-
SelectionPoint,
|
|
29
|
-
SelectionRange,
|
|
30
|
-
CellEditState,
|
|
31
|
-
FilterOperator,
|
|
32
|
-
FilterOption,
|
|
33
|
-
MenuPosition,
|
|
34
|
-
} from "./SvGrid.types";
|
|
35
|
-
import {
|
|
36
|
-
cfTextStyle,
|
|
37
|
-
fmtStat,
|
|
38
|
-
getEditableInputValue,
|
|
39
|
-
getEditorInputType,
|
|
40
|
-
toValueArray,
|
|
41
|
-
getOptionLabel,
|
|
42
|
-
getOptionColor,
|
|
43
|
-
colorfulChipStyle,
|
|
44
|
-
getEditorClass,
|
|
45
|
-
} from "./SvGrid.helpers";
|
|
46
|
-
import {
|
|
47
|
-
} from "./SvGrid.controller.svelte";
|
|
48
|
-
import type { SvGridController } from "./SvGrid.controller.svelte";
|
|
49
|
-
|
|
50
|
-
let {
|
|
51
|
-
ctrl,
|
|
52
|
-
pager = true,
|
|
53
|
-
showStatus = true,
|
|
54
|
-
top = false,
|
|
55
|
-
pageSizeOptions,
|
|
56
|
-
}: {
|
|
57
|
-
ctrl: SvGridController<TFeatures, TData>;
|
|
58
|
-
/** Render the pagination controls in this instance. */
|
|
59
|
-
pager?: boolean;
|
|
60
|
-
/** Render the status bar in this instance. */
|
|
61
|
-
showStatus?: boolean;
|
|
62
|
-
/** This is a top-positioned footer (styling only). */
|
|
63
|
-
top?: boolean;
|
|
64
|
-
/** Page-size choices for the selector. Defaults to `[10, 25, 50, 100]`. */
|
|
65
|
-
pageSizeOptions?: number[];
|
|
66
|
-
} = $props();
|
|
67
|
-
|
|
68
|
-
// View facade: re-bind the controller's reactive members so the markup
|
|
69
|
-
// (moved verbatim from SvGrid.svelte) stays identical.
|
|
70
|
-
const paginationEnabled = $derived(ctrl.paginationEnabled);
|
|
71
|
-
const grid = $derived(ctrl.grid);
|
|
72
|
-
const paginationState = $derived(ctrl.paginationState);
|
|
73
|
-
const allRowsBeforePagination = $derived(ctrl.allRowsBeforePagination);
|
|
74
|
-
const statusBarEnabled = $derived(ctrl.statusBarEnabled);
|
|
75
|
-
const statusBarAggregates = $derived(ctrl.statusBarAggregates);
|
|
76
|
-
const statusBarStats = $derived(ctrl.statusBarStats);
|
|
77
|
-
const changePage = $derived(ctrl.changePage);
|
|
78
|
-
const goToPage = $derived(ctrl.goToPage);
|
|
79
|
-
const setPageSize = $derived(ctrl.setPageSize);
|
|
80
|
-
</script>
|
|
81
|
-
|
|
82
|
-
{#if showStatus && statusBarEnabled && statusBarStats}
|
|
83
|
-
{@const s = statusBarStats}
|
|
84
|
-
<div class="sv-grid-status-bar" role="status" aria-live="polite">
|
|
85
|
-
{#each statusBarAggregates as agg (agg)}
|
|
86
|
-
{#if agg === "count"}
|
|
87
|
-
<span class="sv-grid-status-item"
|
|
88
|
-
><span class="sv-grid-status-label">Count</span>{fmtStat(s.count)}</span
|
|
89
|
-
>
|
|
90
|
-
{:else if agg === "numericCount"}
|
|
91
|
-
<span class="sv-grid-status-item"
|
|
92
|
-
><span class="sv-grid-status-label">Numeric</span>{fmtStat(s.numericCount)}</span
|
|
93
|
-
>
|
|
94
|
-
{:else if s.numericCount > 0 && agg === "sum"}
|
|
95
|
-
<span class="sv-grid-status-item"
|
|
96
|
-
><span class="sv-grid-status-label">Sum</span>{fmtStat(s.sum)}</span
|
|
97
|
-
>
|
|
98
|
-
{:else if s.numericCount > 0 && agg === "avg"}
|
|
99
|
-
<span class="sv-grid-status-item"
|
|
100
|
-
><span class="sv-grid-status-label">Avg</span>{fmtStat(s.avg)}</span
|
|
101
|
-
>
|
|
102
|
-
{:else if s.numericCount > 0 && agg === "min"}
|
|
103
|
-
<span class="sv-grid-status-item"
|
|
104
|
-
><span class="sv-grid-status-label">Min</span>{fmtStat(s.min)}</span
|
|
105
|
-
>
|
|
106
|
-
{:else if s.numericCount > 0 && agg === "max"}
|
|
107
|
-
<span class="sv-grid-status-item"
|
|
108
|
-
><span class="sv-grid-status-label">Max</span>{fmtStat(s.max)}</span
|
|
109
|
-
>
|
|
110
|
-
{/if}
|
|
111
|
-
{/each}
|
|
112
|
-
</div>
|
|
113
|
-
{/if}
|
|
114
|
-
|
|
115
|
-
{#if pager && paginationEnabled}
|
|
116
|
-
{@const totalRows = ctrl.paginationTotalRows}
|
|
117
|
-
{@const pageSize = ctrl.paginationPageSize}
|
|
118
|
-
{@const pageCount = Math.max(1, Math.ceil(totalRows / pageSize))}
|
|
119
|
-
{@const currentPage = Math.min(ctrl.paginationPageIndex + 1, pageCount)}
|
|
120
|
-
{@const rangeStart =
|
|
121
|
-
totalRows === 0 ? 0 : (currentPage - 1) * pageSize + 1}
|
|
122
|
-
{@const rangeEnd = Math.min(totalRows, currentPage * pageSize)}
|
|
123
|
-
{@const onFirst = currentPage <= 1}
|
|
124
|
-
{@const onLast = currentPage >= pageCount}
|
|
125
|
-
{@const sizes = pageSizeOptions && pageSizeOptions.length ? pageSizeOptions : [10, 25, 50, 100]}
|
|
126
|
-
<div class="sv-grid-pagination" class:sv-grid-pagination-top={top} role="navigation" aria-label="Pagination">
|
|
127
|
-
<div class="sv-grid-pagination-pagesize">
|
|
128
|
-
<span>Page Size:</span>
|
|
129
|
-
<div class="sv-grid-pagination-pagesize-dd">
|
|
130
|
-
<SvGridDropdown
|
|
131
|
-
options={sizes.map((n) => ({ value: n, label: String(n) }))}
|
|
132
|
-
value={pageSize}
|
|
133
|
-
autoOpen={false}
|
|
134
|
-
onChange={(v) => setPageSize(Number(v))}
|
|
135
|
-
/>
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
<span class="sv-grid-pagination-range">
|
|
139
|
-
<strong>{rangeStart.toLocaleString()}</strong> to
|
|
140
|
-
<strong>{rangeEnd.toLocaleString()}</strong> of
|
|
141
|
-
<strong>{totalRows.toLocaleString()}</strong>
|
|
142
|
-
</span>
|
|
143
|
-
<div class="sv-grid-pagination-nav">
|
|
144
|
-
<button
|
|
145
|
-
type="button"
|
|
146
|
-
class="sv-grid-pagination-btn"
|
|
147
|
-
disabled={onFirst}
|
|
148
|
-
onclick={() => goToPage(0)}
|
|
149
|
-
aria-label="First page">⇤</button
|
|
150
|
-
>
|
|
151
|
-
<button
|
|
152
|
-
type="button"
|
|
153
|
-
class="sv-grid-pagination-btn"
|
|
154
|
-
disabled={onFirst}
|
|
155
|
-
onclick={() => changePage(-1)}
|
|
156
|
-
aria-label="Previous page">‹</button
|
|
157
|
-
>
|
|
158
|
-
<span class="sv-grid-pagination-label">
|
|
159
|
-
Page <strong>{currentPage.toLocaleString()}</strong> of
|
|
160
|
-
<strong>{pageCount.toLocaleString()}</strong>
|
|
161
|
-
</span>
|
|
162
|
-
<button
|
|
163
|
-
type="button"
|
|
164
|
-
class="sv-grid-pagination-btn"
|
|
165
|
-
disabled={onLast}
|
|
166
|
-
onclick={() => changePage(1)}
|
|
167
|
-
aria-label="Next page">›</button
|
|
168
|
-
>
|
|
169
|
-
<button
|
|
170
|
-
type="button"
|
|
171
|
-
class="sv-grid-pagination-btn"
|
|
172
|
-
disabled={onLast}
|
|
173
|
-
onclick={() => goToPage(pageCount - 1)}
|
|
174
|
-
aria-label="Last page">⇥</button
|
|
175
|
-
>
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
{/if}
|
|
1
|
+
<script lang="ts" generics="TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData">
|
|
2
|
+
import {
|
|
3
|
+
getGridCellA11yProps,
|
|
4
|
+
getGridCellDomId,
|
|
5
|
+
getGridHeaderA11yProps,
|
|
6
|
+
getGridRootA11yProps,
|
|
7
|
+
getGridRowA11yProps,
|
|
8
|
+
type EditorContext,
|
|
9
|
+
type CellEditorOption,
|
|
10
|
+
type Column,
|
|
11
|
+
type Row,
|
|
12
|
+
type RowData,
|
|
13
|
+
type TableFeatures,
|
|
14
|
+
} from "./index";
|
|
15
|
+
import "./sv-grid-scrollbar";
|
|
16
|
+
import type { Snippet } from "svelte";
|
|
17
|
+
import {
|
|
18
|
+
RenderSnippetConfig,
|
|
19
|
+
RenderComponentConfig,
|
|
20
|
+
} from "./render-component";
|
|
21
|
+
import {
|
|
22
|
+
buildSparkline,
|
|
23
|
+
toSparklineValues,
|
|
24
|
+
} from "./sparkline";
|
|
25
|
+
import SvGridDropdown from "./SvGridDropdown.svelte";
|
|
26
|
+
import type {
|
|
27
|
+
Props,
|
|
28
|
+
SelectionPoint,
|
|
29
|
+
SelectionRange,
|
|
30
|
+
CellEditState,
|
|
31
|
+
FilterOperator,
|
|
32
|
+
FilterOption,
|
|
33
|
+
MenuPosition,
|
|
34
|
+
} from "./SvGrid.types";
|
|
35
|
+
import {
|
|
36
|
+
cfTextStyle,
|
|
37
|
+
fmtStat,
|
|
38
|
+
getEditableInputValue,
|
|
39
|
+
getEditorInputType,
|
|
40
|
+
toValueArray,
|
|
41
|
+
getOptionLabel,
|
|
42
|
+
getOptionColor,
|
|
43
|
+
colorfulChipStyle,
|
|
44
|
+
getEditorClass,
|
|
45
|
+
} from "./SvGrid.helpers";
|
|
46
|
+
import {
|
|
47
|
+
} from "./SvGrid.controller.svelte";
|
|
48
|
+
import type { SvGridController } from "./SvGrid.controller.svelte";
|
|
49
|
+
|
|
50
|
+
let {
|
|
51
|
+
ctrl,
|
|
52
|
+
pager = true,
|
|
53
|
+
showStatus = true,
|
|
54
|
+
top = false,
|
|
55
|
+
pageSizeOptions,
|
|
56
|
+
}: {
|
|
57
|
+
ctrl: SvGridController<TFeatures, TData>;
|
|
58
|
+
/** Render the pagination controls in this instance. */
|
|
59
|
+
pager?: boolean;
|
|
60
|
+
/** Render the status bar in this instance. */
|
|
61
|
+
showStatus?: boolean;
|
|
62
|
+
/** This is a top-positioned footer (styling only). */
|
|
63
|
+
top?: boolean;
|
|
64
|
+
/** Page-size choices for the selector. Defaults to `[10, 25, 50, 100]`. */
|
|
65
|
+
pageSizeOptions?: number[];
|
|
66
|
+
} = $props();
|
|
67
|
+
|
|
68
|
+
// View facade: re-bind the controller's reactive members so the markup
|
|
69
|
+
// (moved verbatim from SvGrid.svelte) stays identical.
|
|
70
|
+
const paginationEnabled = $derived(ctrl.paginationEnabled);
|
|
71
|
+
const grid = $derived(ctrl.grid);
|
|
72
|
+
const paginationState = $derived(ctrl.paginationState);
|
|
73
|
+
const allRowsBeforePagination = $derived(ctrl.allRowsBeforePagination);
|
|
74
|
+
const statusBarEnabled = $derived(ctrl.statusBarEnabled);
|
|
75
|
+
const statusBarAggregates = $derived(ctrl.statusBarAggregates);
|
|
76
|
+
const statusBarStats = $derived(ctrl.statusBarStats);
|
|
77
|
+
const changePage = $derived(ctrl.changePage);
|
|
78
|
+
const goToPage = $derived(ctrl.goToPage);
|
|
79
|
+
const setPageSize = $derived(ctrl.setPageSize);
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
{#if showStatus && statusBarEnabled && statusBarStats}
|
|
83
|
+
{@const s = statusBarStats}
|
|
84
|
+
<div class="sv-grid-status-bar" role="status" aria-live="polite">
|
|
85
|
+
{#each statusBarAggregates as agg (agg)}
|
|
86
|
+
{#if agg === "count"}
|
|
87
|
+
<span class="sv-grid-status-item"
|
|
88
|
+
><span class="sv-grid-status-label">Count</span>{fmtStat(s.count)}</span
|
|
89
|
+
>
|
|
90
|
+
{:else if agg === "numericCount"}
|
|
91
|
+
<span class="sv-grid-status-item"
|
|
92
|
+
><span class="sv-grid-status-label">Numeric</span>{fmtStat(s.numericCount)}</span
|
|
93
|
+
>
|
|
94
|
+
{:else if s.numericCount > 0 && agg === "sum"}
|
|
95
|
+
<span class="sv-grid-status-item"
|
|
96
|
+
><span class="sv-grid-status-label">Sum</span>{fmtStat(s.sum)}</span
|
|
97
|
+
>
|
|
98
|
+
{:else if s.numericCount > 0 && agg === "avg"}
|
|
99
|
+
<span class="sv-grid-status-item"
|
|
100
|
+
><span class="sv-grid-status-label">Avg</span>{fmtStat(s.avg)}</span
|
|
101
|
+
>
|
|
102
|
+
{:else if s.numericCount > 0 && agg === "min"}
|
|
103
|
+
<span class="sv-grid-status-item"
|
|
104
|
+
><span class="sv-grid-status-label">Min</span>{fmtStat(s.min)}</span
|
|
105
|
+
>
|
|
106
|
+
{:else if s.numericCount > 0 && agg === "max"}
|
|
107
|
+
<span class="sv-grid-status-item"
|
|
108
|
+
><span class="sv-grid-status-label">Max</span>{fmtStat(s.max)}</span
|
|
109
|
+
>
|
|
110
|
+
{/if}
|
|
111
|
+
{/each}
|
|
112
|
+
</div>
|
|
113
|
+
{/if}
|
|
114
|
+
|
|
115
|
+
{#if pager && paginationEnabled}
|
|
116
|
+
{@const totalRows = ctrl.paginationTotalRows}
|
|
117
|
+
{@const pageSize = ctrl.paginationPageSize}
|
|
118
|
+
{@const pageCount = Math.max(1, Math.ceil(totalRows / pageSize))}
|
|
119
|
+
{@const currentPage = Math.min(ctrl.paginationPageIndex + 1, pageCount)}
|
|
120
|
+
{@const rangeStart =
|
|
121
|
+
totalRows === 0 ? 0 : (currentPage - 1) * pageSize + 1}
|
|
122
|
+
{@const rangeEnd = Math.min(totalRows, currentPage * pageSize)}
|
|
123
|
+
{@const onFirst = currentPage <= 1}
|
|
124
|
+
{@const onLast = currentPage >= pageCount}
|
|
125
|
+
{@const sizes = pageSizeOptions && pageSizeOptions.length ? pageSizeOptions : [10, 25, 50, 100]}
|
|
126
|
+
<div class="sv-grid-pagination" class:sv-grid-pagination-top={top} role="navigation" aria-label="Pagination">
|
|
127
|
+
<div class="sv-grid-pagination-pagesize">
|
|
128
|
+
<span>Page Size:</span>
|
|
129
|
+
<div class="sv-grid-pagination-pagesize-dd">
|
|
130
|
+
<SvGridDropdown
|
|
131
|
+
options={sizes.map((n) => ({ value: n, label: String(n) }))}
|
|
132
|
+
value={pageSize}
|
|
133
|
+
autoOpen={false}
|
|
134
|
+
onChange={(v) => setPageSize(Number(v))}
|
|
135
|
+
/>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
<span class="sv-grid-pagination-range">
|
|
139
|
+
<strong>{rangeStart.toLocaleString()}</strong> to
|
|
140
|
+
<strong>{rangeEnd.toLocaleString()}</strong> of
|
|
141
|
+
<strong>{totalRows.toLocaleString()}</strong>
|
|
142
|
+
</span>
|
|
143
|
+
<div class="sv-grid-pagination-nav">
|
|
144
|
+
<button
|
|
145
|
+
type="button"
|
|
146
|
+
class="sv-grid-pagination-btn"
|
|
147
|
+
disabled={onFirst}
|
|
148
|
+
onclick={() => goToPage(0)}
|
|
149
|
+
aria-label="First page">⇤</button
|
|
150
|
+
>
|
|
151
|
+
<button
|
|
152
|
+
type="button"
|
|
153
|
+
class="sv-grid-pagination-btn"
|
|
154
|
+
disabled={onFirst}
|
|
155
|
+
onclick={() => changePage(-1)}
|
|
156
|
+
aria-label="Previous page">‹</button
|
|
157
|
+
>
|
|
158
|
+
<span class="sv-grid-pagination-label">
|
|
159
|
+
Page <strong>{currentPage.toLocaleString()}</strong> of
|
|
160
|
+
<strong>{pageCount.toLocaleString()}</strong>
|
|
161
|
+
</span>
|
|
162
|
+
<button
|
|
163
|
+
type="button"
|
|
164
|
+
class="sv-grid-pagination-btn"
|
|
165
|
+
disabled={onLast}
|
|
166
|
+
onclick={() => changePage(1)}
|
|
167
|
+
aria-label="Next page">›</button
|
|
168
|
+
>
|
|
169
|
+
<button
|
|
170
|
+
type="button"
|
|
171
|
+
class="sv-grid-pagination-btn"
|
|
172
|
+
disabled={onLast}
|
|
173
|
+
onclick={() => goToPage(pageCount - 1)}
|
|
174
|
+
aria-label="Last page">⇥</button
|
|
175
|
+
>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
{/if}
|
package/src/SvCarousel.svelte
CHANGED
|
@@ -1,141 +1,141 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
/**
|
|
3
|
-
* SvCarousel - a slideshow: a sliding track with prev/next arrows, dot
|
|
4
|
-
* indicators, optional autoplay (pauses on hover/focus) and swipe. Slides are
|
|
5
|
-
* rendered by the `slide` snippet (index in, your content out). Parity: Smart
|
|
6
|
-
* carousel.
|
|
7
|
-
*
|
|
8
|
-
* ```svelte
|
|
9
|
-
* <SvCarousel count={imgs.length} autoplay={4000}>
|
|
10
|
-
* {#snippet slide(i)}<img src={imgs[i]} alt="" />{/snippet}
|
|
11
|
-
* </SvCarousel>
|
|
12
|
-
* ```
|
|
13
|
-
*/
|
|
14
|
-
import type { Snippet } from 'svelte'
|
|
15
|
-
import { type EditorDir } from './editor-contract'
|
|
16
|
-
import { createCarousel } from './createCarousel.svelte'
|
|
17
|
-
|
|
18
|
-
type Props = {
|
|
19
|
-
count: number
|
|
20
|
-
slide?: Snippet<[number]>
|
|
21
|
-
/** Active slide index (bindable). */
|
|
22
|
-
current?: number
|
|
23
|
-
/** Autoplay interval in ms; 0 = off. */
|
|
24
|
-
autoplay?: number
|
|
25
|
-
loop?: boolean
|
|
26
|
-
arrows?: boolean
|
|
27
|
-
dots?: boolean
|
|
28
|
-
ariaLabel?: string
|
|
29
|
-
/** Text direction (rtl mirrors the prev/next arrows + slide direction). */
|
|
30
|
-
dir?: EditorDir
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let {
|
|
34
|
-
count,
|
|
35
|
-
slide,
|
|
36
|
-
current = $bindable(0),
|
|
37
|
-
autoplay = 0,
|
|
38
|
-
loop = true,
|
|
39
|
-
arrows = true,
|
|
40
|
-
dots = true,
|
|
41
|
-
ariaLabel = 'Carousel',
|
|
42
|
-
dir,
|
|
43
|
-
}: Props = $props()
|
|
44
|
-
|
|
45
|
-
// Navigation, autoplay (pause on hover/focus + WCAG 2.2.2 stop toggle) and the
|
|
46
|
-
// rtl-aware arrow wiring live in the shared headless core; this component keeps
|
|
47
|
-
// only the visual track + swipe. See createCarousel.svelte.ts.
|
|
48
|
-
const car = createCarousel({
|
|
49
|
-
count: () => count,
|
|
50
|
-
current: () => current,
|
|
51
|
-
onChange: (i) => (current = i),
|
|
52
|
-
loop: () => loop,
|
|
53
|
-
autoplay: () => autoplay,
|
|
54
|
-
dir: () => (dir === 'ltr' || dir === 'rtl' ? dir : undefined),
|
|
55
|
-
})
|
|
56
|
-
const rtl = $derived(car.rtl())
|
|
57
|
-
|
|
58
|
-
// Swipe (a DOM gesture the renderer owns; delegates to the core's next/prev).
|
|
59
|
-
let dragging = $state(false)
|
|
60
|
-
let startX = 0
|
|
61
|
-
let dx = $state(0)
|
|
62
|
-
function down(e: PointerEvent) {
|
|
63
|
-
dragging = true
|
|
64
|
-
startX = e.clientX
|
|
65
|
-
;(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId)
|
|
66
|
-
}
|
|
67
|
-
function move(e: PointerEvent) { if (dragging) dx = e.clientX - startX }
|
|
68
|
-
function up() {
|
|
69
|
-
if (!dragging) return
|
|
70
|
-
dragging = false
|
|
71
|
-
if (Math.abs(dx) > 50) (dx < 0 ? car.next() : car.prev())
|
|
72
|
-
dx = 0
|
|
73
|
-
}
|
|
74
|
-
</script>
|
|
75
|
-
|
|
76
|
-
<div class="sv-carousel" {...car.rootProps(ariaLabel)}>
|
|
77
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
78
|
-
<div class="sv-carousel__viewport" onpointerdown={down} onpointermove={move} onpointerup={up} onpointercancel={up}>
|
|
79
|
-
<div class="sv-carousel__track" class:is-dragging={dragging} style:transform={`translateX(calc(${(rtl ? current : -current) * 100}% + ${dx}px))`}>
|
|
80
|
-
{#each Array(count) as _, i (i)}
|
|
81
|
-
<div class="sv-carousel__slide" {...car.slideProps(i)}>
|
|
82
|
-
{@render slide?.(i)}
|
|
83
|
-
</div>
|
|
84
|
-
{/each}
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
|
|
88
|
-
{#if arrows && count > 1}
|
|
89
|
-
<button type="button" class="sv-carousel__arrow is-prev" {...car.prevArrowProps()}>{rtl ? '›' : '‹'}</button>
|
|
90
|
-
<button type="button" class="sv-carousel__arrow is-next" {...car.nextArrowProps()}>{rtl ? '‹' : '›'}</button>
|
|
91
|
-
{/if}
|
|
92
|
-
|
|
93
|
-
{#if dots && count > 1}
|
|
94
|
-
<div class="sv-carousel__dots">
|
|
95
|
-
{#each Array(count) as _, i (i)}
|
|
96
|
-
<button type="button" class="sv-carousel__dot" class:is-active={car.isActive(i)} {...car.dotProps(i)}></button>
|
|
97
|
-
{/each}
|
|
98
|
-
</div>
|
|
99
|
-
{/if}
|
|
100
|
-
|
|
101
|
-
{#if autoplay > 0}
|
|
102
|
-
<button type="button" class="sv-carousel__playpause" {...car.playPauseProps()}>{car.userPaused ? '▶' : '⏸'}</button>
|
|
103
|
-
{/if}
|
|
104
|
-
</div>
|
|
105
|
-
|
|
106
|
-
<style>
|
|
107
|
-
.sv-carousel { position: relative; border-radius: 12px; overflow: hidden; }
|
|
108
|
-
.sv-carousel__viewport { overflow: hidden; touch-action: pan-y; }
|
|
109
|
-
.sv-carousel__track { display: flex; transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1); }
|
|
110
|
-
.sv-carousel__track.is-dragging { transition: none; }
|
|
111
|
-
.sv-carousel__slide { flex: 0 0 100%; min-width: 0; }
|
|
112
|
-
.sv-carousel__slide :global(img) { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
113
|
-
|
|
114
|
-
.sv-carousel__arrow {
|
|
115
|
-
position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
|
|
116
|
-
width: 34px; height: 34px; display: grid; place-items: center; border-radius: 50%;
|
|
117
|
-
background: color-mix(in srgb, var(--sg-bg, #fff) 82%, transparent); color: var(--sg-fg, #0f172a);
|
|
118
|
-
border: 1px solid var(--sg-border, #e2e8f0); cursor: pointer; font-size: 20px; line-height: 1;
|
|
119
|
-
box-shadow: 0 4px 12px -4px rgba(15, 23, 42, 0.3);
|
|
120
|
-
}
|
|
121
|
-
.sv-carousel__arrow.is-prev { inset-inline-start: 10px; }
|
|
122
|
-
.sv-carousel__arrow.is-next { inset-inline-end: 10px; }
|
|
123
|
-
.sv-carousel__arrow:hover:not(:disabled) { background: var(--sg-bg, #fff); }
|
|
124
|
-
.sv-carousel__arrow:disabled { opacity: 0.4; cursor: default; }
|
|
125
|
-
|
|
126
|
-
.sv-carousel__dots { position: absolute; inset-inline: 0; bottom: 10px; display: flex; justify-content: center; gap: 6px; z-index: 2; }
|
|
127
|
-
.sv-carousel__dot {
|
|
128
|
-
width: 8px; height: 8px; border-radius: 50%; border: 0; cursor: pointer; padding: 0;
|
|
129
|
-
background: color-mix(in srgb, var(--sg-fg, #0f172a) 30%, var(--sg-bg, #fff)); transition: background 0.15s, width 0.15s;
|
|
130
|
-
}
|
|
131
|
-
.sv-carousel__dot.is-active { width: 20px; border-radius: 4px; background: var(--sg-accent, #2563eb); }
|
|
132
|
-
|
|
133
|
-
.sv-carousel__playpause {
|
|
134
|
-
position: absolute; top: 10px; inset-inline-end: 10px; z-index: 2;
|
|
135
|
-
width: 28px; height: 28px; display: grid; place-items: center; border-radius: 50%;
|
|
136
|
-
background: color-mix(in srgb, var(--sg-bg, #fff) 82%, transparent); color: var(--sg-fg, #0f172a);
|
|
137
|
-
border: 1px solid var(--sg-border, #e2e8f0); cursor: pointer; font-size: 12px; line-height: 1;
|
|
138
|
-
box-shadow: 0 4px 12px -4px rgba(15, 23, 42, 0.3);
|
|
139
|
-
}
|
|
140
|
-
.sv-carousel__playpause:hover { background: var(--sg-bg, #fff); }
|
|
141
|
-
</style>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvCarousel - a slideshow: a sliding track with prev/next arrows, dot
|
|
4
|
+
* indicators, optional autoplay (pauses on hover/focus) and swipe. Slides are
|
|
5
|
+
* rendered by the `slide` snippet (index in, your content out). Parity: Smart
|
|
6
|
+
* carousel.
|
|
7
|
+
*
|
|
8
|
+
* ```svelte
|
|
9
|
+
* <SvCarousel count={imgs.length} autoplay={4000}>
|
|
10
|
+
* {#snippet slide(i)}<img src={imgs[i]} alt="" />{/snippet}
|
|
11
|
+
* </SvCarousel>
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
import type { Snippet } from 'svelte'
|
|
15
|
+
import { type EditorDir } from './editor-contract'
|
|
16
|
+
import { createCarousel } from './createCarousel.svelte'
|
|
17
|
+
|
|
18
|
+
type Props = {
|
|
19
|
+
count: number
|
|
20
|
+
slide?: Snippet<[number]>
|
|
21
|
+
/** Active slide index (bindable). */
|
|
22
|
+
current?: number
|
|
23
|
+
/** Autoplay interval in ms; 0 = off. */
|
|
24
|
+
autoplay?: number
|
|
25
|
+
loop?: boolean
|
|
26
|
+
arrows?: boolean
|
|
27
|
+
dots?: boolean
|
|
28
|
+
ariaLabel?: string
|
|
29
|
+
/** Text direction (rtl mirrors the prev/next arrows + slide direction). */
|
|
30
|
+
dir?: EditorDir
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
count,
|
|
35
|
+
slide,
|
|
36
|
+
current = $bindable(0),
|
|
37
|
+
autoplay = 0,
|
|
38
|
+
loop = true,
|
|
39
|
+
arrows = true,
|
|
40
|
+
dots = true,
|
|
41
|
+
ariaLabel = 'Carousel',
|
|
42
|
+
dir,
|
|
43
|
+
}: Props = $props()
|
|
44
|
+
|
|
45
|
+
// Navigation, autoplay (pause on hover/focus + WCAG 2.2.2 stop toggle) and the
|
|
46
|
+
// rtl-aware arrow wiring live in the shared headless core; this component keeps
|
|
47
|
+
// only the visual track + swipe. See createCarousel.svelte.ts.
|
|
48
|
+
const car = createCarousel({
|
|
49
|
+
count: () => count,
|
|
50
|
+
current: () => current,
|
|
51
|
+
onChange: (i) => (current = i),
|
|
52
|
+
loop: () => loop,
|
|
53
|
+
autoplay: () => autoplay,
|
|
54
|
+
dir: () => (dir === 'ltr' || dir === 'rtl' ? dir : undefined),
|
|
55
|
+
})
|
|
56
|
+
const rtl = $derived(car.rtl())
|
|
57
|
+
|
|
58
|
+
// Swipe (a DOM gesture the renderer owns; delegates to the core's next/prev).
|
|
59
|
+
let dragging = $state(false)
|
|
60
|
+
let startX = 0
|
|
61
|
+
let dx = $state(0)
|
|
62
|
+
function down(e: PointerEvent) {
|
|
63
|
+
dragging = true
|
|
64
|
+
startX = e.clientX
|
|
65
|
+
;(e.currentTarget as HTMLElement).setPointerCapture?.(e.pointerId)
|
|
66
|
+
}
|
|
67
|
+
function move(e: PointerEvent) { if (dragging) dx = e.clientX - startX }
|
|
68
|
+
function up() {
|
|
69
|
+
if (!dragging) return
|
|
70
|
+
dragging = false
|
|
71
|
+
if (Math.abs(dx) > 50) (dx < 0 ? car.next() : car.prev())
|
|
72
|
+
dx = 0
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<div class="sv-carousel" {...car.rootProps(ariaLabel)}>
|
|
77
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
78
|
+
<div class="sv-carousel__viewport" onpointerdown={down} onpointermove={move} onpointerup={up} onpointercancel={up}>
|
|
79
|
+
<div class="sv-carousel__track" class:is-dragging={dragging} style:transform={`translateX(calc(${(rtl ? current : -current) * 100}% + ${dx}px))`}>
|
|
80
|
+
{#each Array(count) as _, i (i)}
|
|
81
|
+
<div class="sv-carousel__slide" {...car.slideProps(i)}>
|
|
82
|
+
{@render slide?.(i)}
|
|
83
|
+
</div>
|
|
84
|
+
{/each}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
{#if arrows && count > 1}
|
|
89
|
+
<button type="button" class="sv-carousel__arrow is-prev" {...car.prevArrowProps()}>{rtl ? '›' : '‹'}</button>
|
|
90
|
+
<button type="button" class="sv-carousel__arrow is-next" {...car.nextArrowProps()}>{rtl ? '‹' : '›'}</button>
|
|
91
|
+
{/if}
|
|
92
|
+
|
|
93
|
+
{#if dots && count > 1}
|
|
94
|
+
<div class="sv-carousel__dots">
|
|
95
|
+
{#each Array(count) as _, i (i)}
|
|
96
|
+
<button type="button" class="sv-carousel__dot" class:is-active={car.isActive(i)} {...car.dotProps(i)}></button>
|
|
97
|
+
{/each}
|
|
98
|
+
</div>
|
|
99
|
+
{/if}
|
|
100
|
+
|
|
101
|
+
{#if autoplay > 0}
|
|
102
|
+
<button type="button" class="sv-carousel__playpause" {...car.playPauseProps()}>{car.userPaused ? '▶' : '⏸'}</button>
|
|
103
|
+
{/if}
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<style>
|
|
107
|
+
.sv-carousel { position: relative; border-radius: 12px; overflow: hidden; }
|
|
108
|
+
.sv-carousel__viewport { overflow: hidden; touch-action: pan-y; }
|
|
109
|
+
.sv-carousel__track { display: flex; transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1); }
|
|
110
|
+
.sv-carousel__track.is-dragging { transition: none; }
|
|
111
|
+
.sv-carousel__slide { flex: 0 0 100%; min-width: 0; }
|
|
112
|
+
.sv-carousel__slide :global(img) { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
113
|
+
|
|
114
|
+
.sv-carousel__arrow {
|
|
115
|
+
position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
|
|
116
|
+
width: 34px; height: 34px; display: grid; place-items: center; border-radius: 50%;
|
|
117
|
+
background: color-mix(in srgb, var(--sg-bg, #fff) 82%, transparent); color: var(--sg-fg, #0f172a);
|
|
118
|
+
border: 1px solid var(--sg-border, #e2e8f0); cursor: pointer; font-size: 20px; line-height: 1;
|
|
119
|
+
box-shadow: 0 4px 12px -4px rgba(15, 23, 42, 0.3);
|
|
120
|
+
}
|
|
121
|
+
.sv-carousel__arrow.is-prev { inset-inline-start: 10px; }
|
|
122
|
+
.sv-carousel__arrow.is-next { inset-inline-end: 10px; }
|
|
123
|
+
.sv-carousel__arrow:hover:not(:disabled) { background: var(--sg-bg, #fff); }
|
|
124
|
+
.sv-carousel__arrow:disabled { opacity: 0.4; cursor: default; }
|
|
125
|
+
|
|
126
|
+
.sv-carousel__dots { position: absolute; inset-inline: 0; bottom: 10px; display: flex; justify-content: center; gap: 6px; z-index: 2; }
|
|
127
|
+
.sv-carousel__dot {
|
|
128
|
+
width: 8px; height: 8px; border-radius: 50%; border: 0; cursor: pointer; padding: 0;
|
|
129
|
+
background: color-mix(in srgb, var(--sg-fg, #0f172a) 30%, var(--sg-bg, #fff)); transition: background 0.15s, width 0.15s;
|
|
130
|
+
}
|
|
131
|
+
.sv-carousel__dot.is-active { width: 20px; border-radius: 4px; background: var(--sg-accent, #2563eb); }
|
|
132
|
+
|
|
133
|
+
.sv-carousel__playpause {
|
|
134
|
+
position: absolute; top: 10px; inset-inline-end: 10px; z-index: 2;
|
|
135
|
+
width: 28px; height: 28px; display: grid; place-items: center; border-radius: 50%;
|
|
136
|
+
background: color-mix(in srgb, var(--sg-bg, #fff) 82%, transparent); color: var(--sg-fg, #0f172a);
|
|
137
|
+
border: 1px solid var(--sg-border, #e2e8f0); cursor: pointer; font-size: 12px; line-height: 1;
|
|
138
|
+
box-shadow: 0 4px 12px -4px rgba(15, 23, 42, 0.3);
|
|
139
|
+
}
|
|
140
|
+
.sv-carousel__playpause:hover { background: var(--sg-bg, #fff); }
|
|
141
|
+
</style>
|