@svgrid/grid 3.0.4 → 3.0.5
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/README.md +4 -4
- package/dist/SvGrid.controller.svelte.d.ts +12 -2
- package/dist/SvGrid.controller.svelte.js +192 -8
- package/dist/SvGrid.css +73 -0
- package/dist/SvGrid.svelte +128 -3
- package/dist/SvGrid.types.d.ts +356 -3
- package/dist/build-api.js +11 -6
- package/dist/cdn/{GridMenus-BL7ZgQvU.js → GridMenus-CID62rlI.js} +11 -11
- package/dist/cdn/{GridMenus-7kbpnnBW.js → GridMenus-DLL-qZeu.js} +11 -11
- package/dist/cdn/server-block-cache-CGoWz-87.js +237 -0
- package/dist/cdn/{src-BKhZ6eXd.js → src-Bklofsad.js} +8818 -8798
- package/dist/cdn/{src-V1uu8iE9.js → src-jcFMkBnN.js} +4282 -4262
- package/dist/cdn/svgrid.js +27 -26
- package/dist/cdn/svgrid.svelte-external.js +27 -26
- package/dist/cdn/validate-C7Rn_Bns.js +76 -0
- package/dist/core.js +36 -13
- package/dist/editing.js +10 -0
- package/dist/gantt-view.svelte.d.ts +24 -0
- package/dist/gantt-view.svelte.js +13 -0
- package/dist/grid-icons.d.ts +2 -2
- package/dist/grid-icons.js +2 -2
- package/dist/grid-messages.d.ts +6 -0
- package/dist/grid-messages.js +5 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -3
- package/dist/row-model.d.ts +133 -0
- package/dist/row-model.js +16 -0
- package/dist/selection.d.ts +2 -1
- package/dist/selection.js +30 -2
- package/dist/server-block-cache.d.ts +214 -0
- package/dist/server-block-cache.js +531 -0
- package/dist/server-data-source.d.ts +240 -6
- package/dist/server-data-source.js +220 -20
- package/dist/server.d.ts +13 -0
- package/dist/server.js +13 -0
- package/dist/validate.d.ts +4 -0
- package/dist/validate.js +21 -3
- package/package.json +6 -1
- package/src/SvGrid.controller.svelte.ts +191 -8
- package/src/SvGrid.css +73 -0
- package/src/SvGrid.svelte +128 -3
- package/src/SvGrid.types.ts +376 -3
- package/src/build-api.ts +11 -4
- package/src/core.rowmodel-cache.test.ts +51 -0
- package/src/core.ts +33 -12
- package/src/editing.ts +8 -0
- package/src/gantt-stub.test.svelte +38 -0
- package/src/gantt-view.svelte.ts +35 -0
- package/src/grid-icons.ts +2 -2
- package/src/grid-messages.ts +12 -0
- package/src/icon-seam.test.ts +2 -4
- package/src/index.ts +36 -10
- package/src/row-model.ts +146 -0
- package/src/selection.test.ts +3 -0
- package/src/selection.ts +32 -2
- package/src/server-block-cache.test.ts +645 -0
- package/src/server-block-cache.ts +677 -0
- package/src/server-data-source.infinite.test.ts +343 -0
- package/src/server-data-source.ts +450 -27
- package/src/server.ts +47 -0
- package/src/svgrid.gantt-seam.test.ts +199 -0
- package/src/svgrid.row-model-prop.svelte.test.ts +352 -0
- package/src/svgrid.row-model-seam.svelte.test.ts +289 -0
- package/src/svgrid.upsell-license.test.ts +7 -4
- package/src/validate.test.ts +29 -0
- package/src/validate.ts +28 -3
- package/dist/SvGroupCell.svelte +0 -141
- package/dist/SvGroupCell.svelte.d.ts +0 -49
- package/dist/SvRowGroupPanel.svelte +0 -186
- package/dist/SvRowGroupPanel.svelte.d.ts +0 -25
- package/dist/cdn/validate-_CDJzgIo.js +0 -75
- package/dist/server-group-model.d.ts +0 -98
- package/dist/server-group-model.js +0 -263
- package/src/SvGroupCell.svelte +0 -141
- package/src/SvRowGroupPanel.svelte +0 -186
- package/src/server-group-model.test.ts +0 -294
- package/src/server-group-model.ts +0 -370
package/dist/server.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@svgrid/grid/server` - the server-side data primitives, with no Svelte in
|
|
3
|
+
* the import graph.
|
|
4
|
+
*
|
|
5
|
+
* Everything here is also on the package root. This entry point exists for
|
|
6
|
+
* code that must not pull `SvGrid.svelte` into its bundle or its test
|
|
7
|
+
* runner: a SvelteKit `+server.ts` shaping a `ServerRequest` into a query,
|
|
8
|
+
* a worker feeding a block cache, or `@svgrid/enterprise`'s own row model,
|
|
9
|
+
* whose unit tests run without the Svelte compiler.
|
|
10
|
+
*/
|
|
11
|
+
export { createServerDataSource, } from './server-data-source.js';
|
|
12
|
+
export { createBlockCache, createRowPlaceholder, rowPlaceholderState, } from './server-block-cache.js';
|
|
13
|
+
export { toServerFilterColumns, } from './row-model.js';
|
package/dist/validate.d.ts
CHANGED
|
@@ -35,6 +35,10 @@ export type ValidateInput<TFeatures extends TableFeatures, TData extends RowData
|
|
|
35
35
|
idField?: string;
|
|
36
36
|
column?: string;
|
|
37
37
|
};
|
|
38
|
+
/** Gantt mode. Only its presence matters here - see rule 6b. */
|
|
39
|
+
gantt?: unknown;
|
|
40
|
+
/** Server-side grouping: group rows carry their key and aggregates, not the leaf fields. */
|
|
41
|
+
serverGroup?: unknown;
|
|
38
42
|
initialColumnPinning?: {
|
|
39
43
|
left?: ReadonlyArray<string>;
|
|
40
44
|
right?: ReadonlyArray<string>;
|
package/dist/validate.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { rowPlaceholderState } from './server-block-cache.js';
|
|
1
2
|
/** How many rows to sample when deciding whether a `field` exists. */
|
|
2
3
|
const FIELD_SAMPLE_ROWS = 10;
|
|
3
4
|
const DOCS = 'https://svgrid.com/docs/getting-started/3-data-and-columns/';
|
|
@@ -17,9 +18,13 @@ export function validateGridConfig(input) {
|
|
|
17
18
|
// ---- 1. A `field` that does not exist on the data -------------------------
|
|
18
19
|
// The single most expensive silent failure: the column renders, and every
|
|
19
20
|
// cell in it is blank. Sampled across several rows so genuinely sparse data
|
|
20
|
-
// (a key absent from row 0 but present later) does not trip it.
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
// (a key absent from row 0 but present later) does not trip it. A row
|
|
22
|
+
// model shows placeholder rows before its first block lands; those carry
|
|
23
|
+
// no data keys at all and say nothing about the columns.
|
|
24
|
+
const sample = (input.data ?? []).filter((row) => !rowPlaceholderState(row)).slice(0, FIELD_SAMPLE_ROWS);
|
|
25
|
+
// Under server-side grouping the first rows are group rows, which carry
|
|
26
|
+
// a key and aggregates and none of the leaf fields; nothing to judge by.
|
|
27
|
+
if (sample.length && !input.serverGroup) {
|
|
23
28
|
const known = new Set();
|
|
24
29
|
for (const row of sample) {
|
|
25
30
|
if (row && typeof row === 'object')
|
|
@@ -122,6 +127,19 @@ export function validateGridConfig(input) {
|
|
|
122
127
|
`data, so parent lookups cannot match and the tree stays flat.`);
|
|
123
128
|
}
|
|
124
129
|
}
|
|
130
|
+
// ---- 6b. gantt + treeData: two trees over one set of rows -----------------
|
|
131
|
+
// The Gantt builds its own work-breakdown tree from `gantt.parentField`,
|
|
132
|
+
// because it needs every descendant - a collapsed phase still contributes to
|
|
133
|
+
// its summary bar. `treeData` hides collapsed children from the view before
|
|
134
|
+
// the renderer sees them, so the two together silently drop tasks out of
|
|
135
|
+
// their phase's rollup the moment anything is collapsed.
|
|
136
|
+
if (input.gantt && input.treeData) {
|
|
137
|
+
messages.push('[svgrid] `gantt` and `treeData` are both set. The Gantt builds its own ' +
|
|
138
|
+
'work-breakdown tree from `gantt.parentField` and needs every ' +
|
|
139
|
+
'descendant row, but `treeData` hides collapsed children from the view, ' +
|
|
140
|
+
'so summary bars will under-report. Remove `treeData` and set ' +
|
|
141
|
+
'`gantt.parentField` instead.');
|
|
142
|
+
}
|
|
125
143
|
// ---- 7. Pinning that column virtualization will hide ----------------------
|
|
126
144
|
// Documented incompatibility: the virtualizer recycles column DOM nodes, so
|
|
127
145
|
// sticky pinning cannot survive it. `columnVirtualization` defaults to ON,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@svgrid/grid",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.5",
|
|
4
4
|
"description": "Svelte 5-native data grid and data table. Headless-first engine with virtual scrolling for 100k+ rows, Excel-style filtering, inline editing, grouping, tree data, pivot, and server-side data. Drop-in <SvGrid> component. TypeScript, SSR-ready.",
|
|
5
5
|
"author": "jQWidgets <boikom@jqwidgets.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -99,6 +99,11 @@
|
|
|
99
99
|
"svelte": "./dist/shortcut-registry.js",
|
|
100
100
|
"import": "./dist/shortcut-registry.js"
|
|
101
101
|
},
|
|
102
|
+
"./server": {
|
|
103
|
+
"types": "./dist/server.d.ts",
|
|
104
|
+
"svelte": "./dist/server.js",
|
|
105
|
+
"import": "./dist/server.js"
|
|
106
|
+
},
|
|
102
107
|
"./flex-render": {
|
|
103
108
|
"types": "./dist/flex-render.d.ts",
|
|
104
109
|
"svelte": "./dist/flex-render.js",
|
|
@@ -309,6 +309,60 @@ export function createSvGridController<
|
|
|
309
309
|
// preserves reference identity for non-overridden keys, which the columns-identity
|
|
310
310
|
// column cache and the `data`/`columns` sync effects rely on.
|
|
311
311
|
let optionOverrides = $state<Record<string, unknown>>({});
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* A `rowModel` answers for the props it can, so a server-backed grid is
|
|
315
|
+
* one prop rather than a dozen.
|
|
316
|
+
*
|
|
317
|
+
* Bumped by the model itself through `subscribe` (see the effect below),
|
|
318
|
+
* which is what makes `getRows()` and `isLoading()` - plain method calls,
|
|
319
|
+
* invisible to the reactive system - re-read when the model changes.
|
|
320
|
+
*/
|
|
321
|
+
let rowModelVersion = $state(0);
|
|
322
|
+
const rowModelProps = $derived.by(function rowModelProps_d() {
|
|
323
|
+
const model = rawProps.rowModel;
|
|
324
|
+
if (!model) return null;
|
|
325
|
+
rowModelVersion; // read, so a notification re-derives
|
|
326
|
+
const paging = model.pagination;
|
|
327
|
+
return {
|
|
328
|
+
data: model.getRows(),
|
|
329
|
+
loading: model.isLoading(),
|
|
330
|
+
loadingOverlay: true,
|
|
331
|
+
getRowId: model.getRowId,
|
|
332
|
+
// A model that sorts or filters server-side is telling the grid not
|
|
333
|
+
// to do it locally; one that does neither leaves the grid in charge.
|
|
334
|
+
externalSort: model.setSort ? true : undefined,
|
|
335
|
+
externalFilter: model.setFilter ? true : undefined,
|
|
336
|
+
onSortingChange: model.setSort ? (s: any) => model.setSort!(s) : undefined,
|
|
337
|
+
onFiltersChange: model.setFilter ? (f: any) => model.setFilter!(f) : undefined,
|
|
338
|
+
onVisibleRangeChange: model.setViewport
|
|
339
|
+
? (r: { startIndex: number; endIndex: number }) =>
|
|
340
|
+
model.setViewport!(r.startIndex, r.endIndex)
|
|
341
|
+
: undefined,
|
|
342
|
+
rowPlaceholder: model.rowPlaceholder,
|
|
343
|
+
onRetryRow: model.retryRow,
|
|
344
|
+
serverGroup: model.group,
|
|
345
|
+
rowSelectionModel: model.selection,
|
|
346
|
+
serverFilterValues: model.filterValues
|
|
347
|
+
? (columnId: string) => model.filterValues!(columnId)
|
|
348
|
+
: undefined,
|
|
349
|
+
pinnedTopRows: model.pinnedTopRows,
|
|
350
|
+
pinnedBottomRows: model.pinnedBottomRows,
|
|
351
|
+
pivotResultColumns: model.pivotResultColumns ?? undefined,
|
|
352
|
+
externalPagination: paging ? true : undefined,
|
|
353
|
+
rowCount: paging?.rowCount,
|
|
354
|
+
pageIndex: paging?.pageIndex,
|
|
355
|
+
pageSize: paging?.pageSize,
|
|
356
|
+
pageSizeOptions: paging?.pageSizes,
|
|
357
|
+
onPaginationChange: paging
|
|
358
|
+
? (p: { pageIndex: number; pageSize: number }) => {
|
|
359
|
+
if (p.pageSize !== paging.pageSize) paging.setPageSize(p.pageSize);
|
|
360
|
+
else paging.setPage(p.pageIndex);
|
|
361
|
+
}
|
|
362
|
+
: undefined,
|
|
363
|
+
} as Partial<Props<TFeatures, TData>>;
|
|
364
|
+
});
|
|
365
|
+
|
|
312
366
|
const props = new Proxy(rawProps, {
|
|
313
367
|
// Read the override as a PROPERTY (tracked by `$state`), not via `in` (the `has`
|
|
314
368
|
// trap is not tracked) - so a reactive read of `props.X` re-runs when an override
|
|
@@ -318,7 +372,21 @@ export function createSvGridController<
|
|
|
318
372
|
get: (t, k) => {
|
|
319
373
|
const v =
|
|
320
374
|
typeof k === "string" ? optionOverrides[k] : undefined;
|
|
321
|
-
|
|
375
|
+
if (v !== undefined) return v;
|
|
376
|
+
// Pivot mode swaps the column list: `pivotResultColumns` (the prop, or
|
|
377
|
+
// the row model's) stands in for `columns` while it is set, and
|
|
378
|
+
// `columns` itself stays the app's own list for when pivot ends.
|
|
379
|
+
if (k === "columns") {
|
|
380
|
+
const pivot =
|
|
381
|
+
Reflect.get(t, "pivotResultColumns") ??
|
|
382
|
+
(rowModelProps as Record<string, unknown> | null)?.pivotResultColumns;
|
|
383
|
+
if (pivot) return pivot;
|
|
384
|
+
}
|
|
385
|
+
const own = Reflect.get(t, k);
|
|
386
|
+
// A prop written on the grid always beats the row model, so adopting
|
|
387
|
+
// `rowModel` is never all-or-nothing.
|
|
388
|
+
if (own !== undefined || typeof k !== "string") return own;
|
|
389
|
+
return (rowModelProps as Record<string, unknown> | null)?.[k];
|
|
322
390
|
},
|
|
323
391
|
}) as Props<TFeatures, TData>;
|
|
324
392
|
|
|
@@ -739,7 +807,10 @@ export function createSvGridController<
|
|
|
739
807
|
// re-synced whenever the parent passes a new array; the imperative API
|
|
740
808
|
// mutates these so add/remove operations don't need a callback round-trip.
|
|
741
809
|
// svelte-ignore state_referenced_locally
|
|
742
|
-
|
|
810
|
+
// `data` is optional on the type (a `rowModel` may supply the rows
|
|
811
|
+
// instead), so every read falls back to empty rather than to undefined.
|
|
812
|
+
const EMPTY_DATA: ReadonlyArray<TData> = [];
|
|
813
|
+
let internalData = $state.raw<ReadonlyArray<TData>>(props.data ?? EMPTY_DATA);
|
|
743
814
|
// Resolve `cellDataType` / `inferColumnTypes` into concrete editorType +
|
|
744
815
|
// format defaults once, up front, so every downstream reader sees a normal
|
|
745
816
|
// column. Explicit fields on the ColumnDef always win.
|
|
@@ -791,7 +862,7 @@ export function createSvGridController<
|
|
|
791
862
|
// accumulated cell-edit overrides - otherwise `getCellDisplayValue`
|
|
792
863
|
// would keep returning the old edited values from `editedCellValues`
|
|
793
864
|
// even though the underlying data has been replaced.
|
|
794
|
-
internalData = props.data;
|
|
865
|
+
internalData = props.data ?? EMPTY_DATA;
|
|
795
866
|
editedCellValues = {};
|
|
796
867
|
});
|
|
797
868
|
$effect(() => {
|
|
@@ -1037,6 +1108,20 @@ export function createSvGridController<
|
|
|
1037
1108
|
*
|
|
1038
1109
|
* Returns null when the cell should render nothing.
|
|
1039
1110
|
*/
|
|
1111
|
+
/**
|
|
1112
|
+
* Why a row has no data yet, or null when it does.
|
|
1113
|
+
*
|
|
1114
|
+
* Group rows are never placeholders, so the check is skipped for them:
|
|
1115
|
+
* their `original` is a synthetic bucket, not a row the consumer handed
|
|
1116
|
+
* us, and passing it to `rowPlaceholder` would ask a question about a row
|
|
1117
|
+
* that does not exist.
|
|
1118
|
+
*/
|
|
1119
|
+
function placeholderStateOf(row: Row<TData>): "loading" | "failed" | null {
|
|
1120
|
+
const ask = props.rowPlaceholder;
|
|
1121
|
+
if (!ask || isGroupRow(row)) return null;
|
|
1122
|
+
return ask(row.original, row.index);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1040
1125
|
function autoGroupCell(
|
|
1041
1126
|
row: Row<TData>,
|
|
1042
1127
|
columnId: string,
|
|
@@ -1805,15 +1890,42 @@ export function createSvGridController<
|
|
|
1805
1890
|
const rows: TData[] = [];
|
|
1806
1891
|
const ids: string[] = [];
|
|
1807
1892
|
if (!selectionBarOn) return { rows, ids };
|
|
1893
|
+
// Under an external selection model the loaded rows are the ones the
|
|
1894
|
+
// model says are selected; rows that never loaded are not in `allRows`
|
|
1895
|
+
// and are counted by `selectionBarCount` below instead. This walks
|
|
1896
|
+
// every loaded row, so it runs on every block a row model delivers:
|
|
1897
|
+
// a model that can say "nothing is selected" spares the walk, and the
|
|
1898
|
+
// props it needs are read once, not through the proxy per row.
|
|
1899
|
+
const model = props.rowSelectionModel;
|
|
1900
|
+
if (model && model.selectedCount?.() === 0) return { rows, ids };
|
|
1901
|
+
const ask = props.rowPlaceholder;
|
|
1902
|
+
const isPlaceholder = ask
|
|
1903
|
+
? (row: Row<TData>) => !isGroupRow(row) && !!ask(row.original as TData, row.index)
|
|
1904
|
+
: () => false;
|
|
1808
1905
|
for (const row of allRows) {
|
|
1809
|
-
if (isGroupRow(row)) continue;
|
|
1810
|
-
|
|
1906
|
+
if (isGroupRow(row) || isPlaceholder(row)) continue;
|
|
1907
|
+
const selected = model
|
|
1908
|
+
? model.isSelected(row.id, row.original as TData)
|
|
1909
|
+
: !!rowSelectionState[row.id];
|
|
1910
|
+
if (selected) {
|
|
1811
1911
|
rows.push(row.original as TData);
|
|
1812
1912
|
ids.push(row.id);
|
|
1813
1913
|
}
|
|
1814
1914
|
}
|
|
1815
1915
|
return { rows, ids };
|
|
1816
1916
|
});
|
|
1917
|
+
/**
|
|
1918
|
+
* What the selection bar's count chip says. A model that stores the rule
|
|
1919
|
+
* ("everything except these") knows about rows the grid never loaded, so
|
|
1920
|
+
* its number wins over counting ticks on screen; `null` from it means it
|
|
1921
|
+
* cannot say, and the bar falls back to the loaded count.
|
|
1922
|
+
*/
|
|
1923
|
+
const selectionBarCount = $derived.by(function selectionBarCount_d() {
|
|
1924
|
+
const fromModel = props.rowSelectionModel?.selectedCount?.();
|
|
1925
|
+
return fromModel ?? selectionBarTarget.ids.length;
|
|
1926
|
+
});
|
|
1927
|
+
/** Whether the bar has anything to show: the model's count when it has one, else the loaded ticks. */
|
|
1928
|
+
const selectionBarVisible = $derived(selectionBarOn && selectionBarCount > 0);
|
|
1817
1929
|
|
|
1818
1930
|
// Forward selection changes to the consumer. Skips the very first invocation
|
|
1819
1931
|
// (the initial empty state) so consumers don't get a spurious callback on mount.
|
|
@@ -2833,6 +2945,55 @@ export function createSvGridController<
|
|
|
2833
2945
|
Math.max(virtualRowTotalSize - virtualRowEnd, 0),
|
|
2834
2946
|
);
|
|
2835
2947
|
|
|
2948
|
+
// A row model tells us when to re-read it. Subscribing here (rather than
|
|
2949
|
+
// polling, or asking the consumer to wrap the model in a rune) is what lets
|
|
2950
|
+
// `getRows()` be a plain method call on a plain object.
|
|
2951
|
+
$effect(() => {
|
|
2952
|
+
const model = rawProps.rowModel;
|
|
2953
|
+
if (!model) return;
|
|
2954
|
+
return model.subscribe(() => {
|
|
2955
|
+
rowModelVersion += 1;
|
|
2956
|
+
});
|
|
2957
|
+
});
|
|
2958
|
+
|
|
2959
|
+
// The index range on screen, for data sources that fetch by block.
|
|
2960
|
+
// `virtualRowStart` / `virtualRowEnd` above are PIXEL offsets; a fetcher
|
|
2961
|
+
// needs row numbers, and needs them whether or not virtualization is on.
|
|
2962
|
+
const visibleRowRange = $derived.by(function visibleRowRange_d() {
|
|
2963
|
+
if (!rowVirtualizationEnabled) {
|
|
2964
|
+
return { startIndex: 0, endIndex: Math.max(0, allRows.length - 1) };
|
|
2965
|
+
}
|
|
2966
|
+
const first = virtualRows[0];
|
|
2967
|
+
const last = virtualRows[virtualRows.length - 1];
|
|
2968
|
+
if (!first || !last) return { startIndex: 0, endIndex: 0 };
|
|
2969
|
+
return { startIndex: first.index, endIndex: last.index };
|
|
2970
|
+
});
|
|
2971
|
+
// Emitted from an effect rather than the derived so a consumer that
|
|
2972
|
+
// fetches (and therefore changes `data`) cannot re-enter the derivation.
|
|
2973
|
+
let lastReportedRange = "";
|
|
2974
|
+
$effect(() => {
|
|
2975
|
+
const report = props.onVisibleRangeChange;
|
|
2976
|
+
if (!report) return;
|
|
2977
|
+
const range = visibleRowRange;
|
|
2978
|
+
const key = range.startIndex + ":" + range.endIndex;
|
|
2979
|
+
if (key === lastReportedRange) return;
|
|
2980
|
+
lastReportedRange = key;
|
|
2981
|
+
report(range);
|
|
2982
|
+
});
|
|
2983
|
+
|
|
2984
|
+
// A paging row model that asks for `autoPageSize` gets as many rows as
|
|
2985
|
+
// the body shows without scrolling, re-measured as the viewport changes.
|
|
2986
|
+
$effect(() => {
|
|
2987
|
+
rowModelVersion;
|
|
2988
|
+
const paging = rawProps.rowModel?.pagination;
|
|
2989
|
+
if (!paging?.autoPageSize) return;
|
|
2990
|
+
const height = viewportHeight;
|
|
2991
|
+
if (height <= 0) return;
|
|
2992
|
+
const rowPx = rowSizePxOf(0) || 30;
|
|
2993
|
+
const fit = Math.max(1, Math.floor(height / rowPx));
|
|
2994
|
+
if (fit !== paging.pageSize) paging.setPageSize(fit);
|
|
2995
|
+
});
|
|
2996
|
+
|
|
2836
2997
|
// --- Huge-list scroll scaling -----------------------------------------
|
|
2837
2998
|
// Browsers cap how tall a single element may be, and mobile caps sit well
|
|
2838
2999
|
// below desktop. Past a few hundred thousand rows the true content height
|
|
@@ -3596,7 +3757,13 @@ export function createSvGridController<
|
|
|
3596
3757
|
// rows (10k iterations on the large-dataset demo, 100k on its top tier) to
|
|
3597
3758
|
// recount a selection that had not changed.
|
|
3598
3759
|
const headerSelectionState = $derived.by(function headerSelectionState_d() {
|
|
3599
|
-
|
|
3760
|
+
// An external model knows about rows the grid has never seen, so it is
|
|
3761
|
+
// the only thing that can answer honestly once the data is a window
|
|
3762
|
+
// onto a server.
|
|
3763
|
+
if (props.rowSelectionModel) return props.rowSelectionModel.headerState();
|
|
3764
|
+
const selectable = allRows.filter(
|
|
3765
|
+
(row) => !isGroupRow(row) && !placeholderStateOf(row),
|
|
3766
|
+
);
|
|
3600
3767
|
if (!selectable.length) return "none";
|
|
3601
3768
|
let selected = 0;
|
|
3602
3769
|
for (const row of selectable) if (rowSelectionState[row.id]) selected += 1;
|
|
@@ -3820,7 +3987,12 @@ export function createSvGridController<
|
|
|
3820
3987
|
for (const column of allColumns) {
|
|
3821
3988
|
const meta = isBucketableColumn(column);
|
|
3822
3989
|
if (!meta) continue;
|
|
3823
|
-
const buckets = buildBuckets(
|
|
3990
|
+
const buckets = buildBuckets(
|
|
3991
|
+
column,
|
|
3992
|
+
meta.isDate,
|
|
3993
|
+
props.data ?? EMPTY_DATA,
|
|
3994
|
+
getColumnAccessorValue,
|
|
3995
|
+
);
|
|
3824
3996
|
if (buckets) map.set(column.id, buckets);
|
|
3825
3997
|
}
|
|
3826
3998
|
return map;
|
|
@@ -4329,6 +4501,8 @@ export function createSvGridController<
|
|
|
4329
4501
|
get selectionBarMaxVisible() { return selectionBarMaxVisible; },
|
|
4330
4502
|
get selectionBarHideClear() { return selectionBarHideClear; },
|
|
4331
4503
|
get selectionBarTarget() { return selectionBarTarget; },
|
|
4504
|
+
get selectionBarCount() { return selectionBarCount; },
|
|
4505
|
+
get selectionBarVisible() { return selectionBarVisible; },
|
|
4332
4506
|
get lastSelectionSerialized() { return lastSelectionSerialized; },
|
|
4333
4507
|
set lastSelectionSerialized(v) { lastSelectionSerialized = v as never; },
|
|
4334
4508
|
get lastCellRangeSerialized() { return lastCellRangeSerialized; },
|
|
@@ -4664,7 +4838,14 @@ export function createSvGridController<
|
|
|
4664
4838
|
get isRowSelected() { return isRowSelected; },
|
|
4665
4839
|
get toggleRowSelectionById() { return toggleRowSelectionById; },
|
|
4666
4840
|
get headerSelectionState() { return headerSelectionState; },
|
|
4841
|
+
get visibleRowRange() { return visibleRowRange; },
|
|
4842
|
+
placeholderStateOf,
|
|
4843
|
+
/** Retry a failed placeholder row. No-op without `onRetryRow`. */
|
|
4844
|
+
retryRow(row: Row<TData>) {
|
|
4845
|
+
props.onRetryRow?.(row.original, row.index);
|
|
4846
|
+
},
|
|
4667
4847
|
get toggleSelectAllRows() { return toggleSelectAllRows; },
|
|
4848
|
+
get setSelectAllRows() { return setSelectAllRows; },
|
|
4668
4849
|
get userHasActivatedCell() { return userHasActivatedCell; },
|
|
4669
4850
|
set userHasActivatedCell(v) { userHasActivatedCell = v as never; },
|
|
4670
4851
|
get tabRunOrigin() { return tabRunOrigin; },
|
|
@@ -4814,7 +4995,7 @@ export function createSvGridController<
|
|
|
4814
4995
|
const { updateFilterRow, updateFilterOperator, updateFilterMenuValue, updateFilterMenuValueTo, addFilterToken, removeFilterToken, toggleFilterToken, toggleCheckboxWithKeyboard, isColumnFiltered, closeMenus, openInSuggest, closeInSuggest, openChooseColumns, openColumnMenu, openFilterMenu, openOperatorMenu, sortColumnFromMenu, clearColumnSort, groupByColumnFromMenu, clearGroupingFromMenu, isFacetChecked, toggleFacetValue, setFacetSelection, setFilterTokens, isAllFacetsChecked, toggleAllFacets, clearColumnFilter, changePage, goToPage, setPageSize, openContextMenu, closeContextMenu, contextMenuItems, saveComment, removeComment, closeCommentEditor } = createMenus<TFeatures, TData>(ctx);
|
|
4815
4996
|
const { cellConditionalFormat, computeRowClass, computeCellClass, computeCellTooltip, computeCellValidity, computeCellNote, getColumnEditorOptions, areEditorOptionsLoading, formatListCellValue, formatCellValue, formatPinnedValue, computePinnedCellClass } = createCellRender<TFeatures, TData>(ctx);
|
|
4816
4997
|
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, commitAndMoveByTab, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste, armPasteFallback } = createEditing<TFeatures, TData>(ctx);
|
|
4817
|
-
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, activeRangeRect, cellSelectionState, getSelectionRects, isInFillPreview, dragPreviewRect, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection<TFeatures, TData>(ctx);
|
|
4998
|
+
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, activeRangeRect, cellSelectionState, getSelectionRects, isInFillPreview, dragPreviewRect, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection<TFeatures, TData>(ctx);
|
|
4818
4999
|
const { cellPinStyle, isColumnPinned, getCurrentColumnOrder, emitColumnOrder, setColumnOrderInternal, applyColumnDrop, onColumnHeaderDragStart, onColumnHeaderDragOver, onColumnHeaderDragLeave, onColumnHeaderDrop, onColumnHeaderDragEnd, pinColumnLeft, pinColumnRight, unpinColumn, toggleColumnVisibleInPanel, moveColumnInPanel, toggleGroupInPanel, getColumnBaseWidth, getColumnWidth, measureText, autosizeColumn, autosizeAllColumns, resetColumns } = createColumns<TFeatures, TData>(ctx);
|
|
4819
5000
|
const { onRowDragStart, onRowDragOver, onRowDragLeave, onRowDrop, onRowsContainerDragOver, onRowsContainerDrop, onRowDragEnd, onRowPointerDown, destroyRowDrag } = createRowDrag<TFeatures, TData>(ctx);
|
|
4820
5001
|
const { register: registerAlignedGrid, broadcastScroll: broadcastAlignedScroll, broadcastWidths: broadcastAlignedWidths } = createAlignedGrids<TFeatures, TData>(ctx);
|
|
@@ -4854,6 +5035,8 @@ export function createSvGridController<
|
|
|
4854
5035
|
pageSize: props.pageSize,
|
|
4855
5036
|
groupBy: props.groupBy,
|
|
4856
5037
|
treeData: props.treeData,
|
|
5038
|
+
gantt: props.gantt,
|
|
5039
|
+
serverGroup: props.serverGroup,
|
|
4857
5040
|
initialColumnPinning: props.initialColumnPinning,
|
|
4858
5041
|
columnVirtualization: props.columnVirtualization,
|
|
4859
5042
|
externalPagination: props.externalPagination,
|
package/src/SvGrid.css
CHANGED
|
@@ -463,6 +463,79 @@
|
|
|
463
463
|
user-select: text;
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
+
/* Rows whose data has not arrived yet (server-side / infinite loading).
|
|
467
|
+
|
|
468
|
+
The shimmer is per CELL, not per row, so the columns keep their widths
|
|
469
|
+
and the table does not reflow as blocks land. Widths vary a little by
|
|
470
|
+
column position so a screenful of them reads as text rather than as a
|
|
471
|
+
barcode - the same trick every skeleton screen uses. */
|
|
472
|
+
.sv-grid-placeholder-cell {
|
|
473
|
+
user-select: none;
|
|
474
|
+
cursor: default;
|
|
475
|
+
}
|
|
476
|
+
/* The per-cell shimmer of a placeholder row. Its own name: `.sv-grid-skeleton`
|
|
477
|
+
above is the full-body loading overlay, absolutely positioned under the
|
|
478
|
+
header, and sharing it pulled every placeholder cell's bar up there. */
|
|
479
|
+
.sv-grid-placeholder-skeleton {
|
|
480
|
+
display: inline-block;
|
|
481
|
+
width: 72%;
|
|
482
|
+
height: 9px;
|
|
483
|
+
border-radius: 5px;
|
|
484
|
+
vertical-align: middle;
|
|
485
|
+
background: linear-gradient(
|
|
486
|
+
90deg,
|
|
487
|
+
color-mix(in srgb, var(--sg-fg, #0f172a) 8%, transparent) 25%,
|
|
488
|
+
color-mix(in srgb, var(--sg-fg, #0f172a) 3%, transparent) 50%,
|
|
489
|
+
color-mix(in srgb, var(--sg-fg, #0f172a) 8%, transparent) 75%
|
|
490
|
+
);
|
|
491
|
+
background-size: 200% 100%;
|
|
492
|
+
animation: sv-grid-skeleton-shimmer 1.2s linear infinite;
|
|
493
|
+
}
|
|
494
|
+
.sv-grid-placeholder-cell:nth-child(2n) .sv-grid-placeholder-skeleton { width: 54%; }
|
|
495
|
+
.sv-grid-placeholder-cell:nth-child(3n) .sv-grid-placeholder-skeleton { width: 84%; }
|
|
496
|
+
@keyframes sv-grid-skeleton-shimmer {
|
|
497
|
+
from { background-position: 200% 0; }
|
|
498
|
+
to { background-position: -200% 0; }
|
|
499
|
+
}
|
|
500
|
+
@media (prefers-reduced-motion: reduce) {
|
|
501
|
+
.sv-grid-placeholder-skeleton { animation: none; }
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/* A block that failed to load: one message across the row, and a way out.
|
|
505
|
+
The cell stays a table cell so its colspan holds; the flex box is inside
|
|
506
|
+
it (a flex `td` stops spanning and the tint shrank to the text). Rows
|
|
507
|
+
that continue a failed run are the same band without the message. */
|
|
508
|
+
.sv-grid-placeholder-failed .sv-grid-placeholder-cell {
|
|
509
|
+
background: color-mix(in srgb, var(--sg-danger, #dc2626) 6%, transparent);
|
|
510
|
+
}
|
|
511
|
+
.sv-grid-placeholder-failed-body {
|
|
512
|
+
display: flex;
|
|
513
|
+
align-items: center;
|
|
514
|
+
gap: 10px;
|
|
515
|
+
min-height: 100%;
|
|
516
|
+
padding-left: var(--sg-cell-px, 7px);
|
|
517
|
+
}
|
|
518
|
+
.sv-grid-placeholder-failed-cont .sv-grid-placeholder-cell {
|
|
519
|
+
border-top-color: transparent;
|
|
520
|
+
}
|
|
521
|
+
.sv-grid-placeholder-error {
|
|
522
|
+
color: var(--sg-danger, #dc2626);
|
|
523
|
+
font-size: 13px;
|
|
524
|
+
}
|
|
525
|
+
.sv-grid-placeholder-retry {
|
|
526
|
+
background: none;
|
|
527
|
+
border: 1px solid var(--sg-border, #e2e8f0);
|
|
528
|
+
border-radius: var(--sg-radius, 6px);
|
|
529
|
+
padding: 2px 10px;
|
|
530
|
+
font: inherit;
|
|
531
|
+
font-size: 12px;
|
|
532
|
+
color: var(--sg-fg, #0f172a);
|
|
533
|
+
cursor: pointer;
|
|
534
|
+
}
|
|
535
|
+
.sv-grid-placeholder-retry:hover {
|
|
536
|
+
background: color-mix(in srgb, var(--sg-fg, #0f172a) 6%, transparent);
|
|
537
|
+
}
|
|
538
|
+
|
|
466
539
|
/* Pinned columns read as "frozen" via three layered cues:
|
|
467
540
|
1. A distinct fill (slightly stronger than the header) so the
|
|
468
541
|
pinned strip is visible even before you scroll. Themes with a
|
package/src/SvGrid.svelte
CHANGED
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
import { getSchedulerView } from "./scheduler-view.svelte";
|
|
55
55
|
import { getSelectionBarView } from "./selection-bar-view.svelte";
|
|
56
56
|
import { getBoardView } from "./board-view.svelte";
|
|
57
|
+
import { getGanttView } from "./gantt-view.svelte";
|
|
57
58
|
import { getChartView } from "./chart-view.svelte";
|
|
58
59
|
let props: Props<TFeatures, TData> = $props();
|
|
59
60
|
// Per-instance base for every DOM id the grid mints. `$props.id()` is stable
|
|
@@ -78,6 +79,9 @@
|
|
|
78
79
|
const boardConfig = $derived(opt.board);
|
|
79
80
|
// Scheduler / calendar mode: same "view of the grid" seam as the board.
|
|
80
81
|
const schedulerConfig = $derived(opt.scheduler);
|
|
82
|
+
// Gantt mode: a task table beside a time chart. Same seam again; the renderer
|
|
83
|
+
// ships in @svgrid/enterprise (enableGanttView).
|
|
84
|
+
const ganttConfig = $derived(opt.gantt);
|
|
81
85
|
// Chart mode: another "view of the grid". Unlike board/scheduler, the renderer
|
|
82
86
|
// is free - a built-in default (SvGridChartView, lazy-loaded below) wraps the
|
|
83
87
|
// standalone SvChart, overridable via the `chart-view` seam (getChartView).
|
|
@@ -408,6 +412,11 @@
|
|
|
408
412
|
ctrl.columnVirtualizationEnabled,
|
|
409
413
|
);
|
|
410
414
|
const virtualRows = $derived(ctrl.virtualRows);
|
|
415
|
+
// The first body row on screen: where a failed run that started above
|
|
416
|
+
// the window repeats its message.
|
|
417
|
+
const firstRenderedRowIndex = $derived(
|
|
418
|
+
rowVirtualizationEnabled ? (virtualRows[0]?.index ?? 0) : 0,
|
|
419
|
+
);
|
|
411
420
|
const frozenRowCount = $derived(ctrl.frozenRowCount);
|
|
412
421
|
const frozenRowList = $derived(ctrl.frozenRowList);
|
|
413
422
|
// Merged cells. A merge is drawn once per band (the frozen rows, the
|
|
@@ -1050,6 +1059,48 @@
|
|
|
1050
1059
|
{/if}
|
|
1051
1060
|
</div>
|
|
1052
1061
|
</div>
|
|
1062
|
+
{:else if ganttConfig}
|
|
1063
|
+
<div
|
|
1064
|
+
class="sv-grid-root sv-grid-gantt-root"
|
|
1065
|
+
class:sv-grid-root-fill={opt.containerHeight === "100%"}
|
|
1066
|
+
style={`height: ${
|
|
1067
|
+
typeof opt.containerHeight === "string"
|
|
1068
|
+
? opt.containerHeight
|
|
1069
|
+
: `${opt.containerHeight ?? 520}px`
|
|
1070
|
+
}; display: flex; flex-direction: column;`}
|
|
1071
|
+
>
|
|
1072
|
+
{#if ganttConfig.searchable !== false}
|
|
1073
|
+
<label class="sv-grid-board-search">
|
|
1074
|
+
{@render icon("search")}
|
|
1075
|
+
<input
|
|
1076
|
+
type="search"
|
|
1077
|
+
placeholder={ganttConfig.searchPlaceholder ??
|
|
1078
|
+
messages.ganttSearchPlaceholder}
|
|
1079
|
+
bind:value={ctrl.globalFilter}
|
|
1080
|
+
aria-label="Search tasks"
|
|
1081
|
+
/>
|
|
1082
|
+
</label>
|
|
1083
|
+
{/if}
|
|
1084
|
+
<div style="flex: 1 1 auto; min-height: 0;">
|
|
1085
|
+
{#if getGanttView()}
|
|
1086
|
+
{@const GanttView = getGanttView()}
|
|
1087
|
+
<GanttView
|
|
1088
|
+
data={boardData}
|
|
1089
|
+
columns={opt.columns}
|
|
1090
|
+
gantt={ganttConfig}
|
|
1091
|
+
getRowId={opt.getRowId}
|
|
1092
|
+
/>
|
|
1093
|
+
{:else}
|
|
1094
|
+
<!-- The Gantt renderer ships in @svgrid/enterprise. Call
|
|
1095
|
+
`enableGanttView()` (or `installEnterprise(api)`) to register it. -->
|
|
1096
|
+
<div class="sv-grid-scheduler-upsell sv-grid-gantt-upsell" role="note">
|
|
1097
|
+
<strong>{messages.ganttUpsellTitle}</strong>
|
|
1098
|
+
<p>{messages.ganttUpsellBody}</p>
|
|
1099
|
+
{@render enterpriseLicenseNote()}
|
|
1100
|
+
</div>
|
|
1101
|
+
{/if}
|
|
1102
|
+
</div>
|
|
1103
|
+
</div>
|
|
1053
1104
|
{:else if chartViewConfig}
|
|
1054
1105
|
<div
|
|
1055
1106
|
class="sv-grid-root sv-grid-chart-root"
|
|
@@ -1461,6 +1512,73 @@
|
|
|
1461
1512
|
</tr>
|
|
1462
1513
|
{/snippet}
|
|
1463
1514
|
|
|
1515
|
+
<!-- A row whose data has not arrived.
|
|
1516
|
+
|
|
1517
|
+
"loading" keeps the real columns and draws a shimmer bar in each,
|
|
1518
|
+
so the table keeps its shape while blocks stream in - a full-width
|
|
1519
|
+
bar would make the whole grid jump every time one landed. "failed"
|
|
1520
|
+
is the opposite case: there is nothing per-column to say, just one
|
|
1521
|
+
message and a way to try again, so it spans the row like a detail
|
|
1522
|
+
cell does. Neither is selectable or editable; there is nothing
|
|
1523
|
+
there to act on.
|
|
1524
|
+
|
|
1525
|
+
A failed block is a run of failed rows, one per row it claimed,
|
|
1526
|
+
and one message serves the run: the first failed row on screen
|
|
1527
|
+
carries it (the run's first row, or the top of the window when
|
|
1528
|
+
the run started above it) and the rows under it are a plain
|
|
1529
|
+
tinted band. A hundred "Could not load" lines for one block
|
|
1530
|
+
was a wall of red. -->
|
|
1531
|
+
{#snippet placeholderRowMarkup(
|
|
1532
|
+
phRow: Row<TData>,
|
|
1533
|
+
phRowIndex: number,
|
|
1534
|
+
phState: "loading" | "failed",
|
|
1535
|
+
phStyle: string,
|
|
1536
|
+
phContinues: boolean,
|
|
1537
|
+
)}
|
|
1538
|
+
<tr
|
|
1539
|
+
class="sv-grid-row sv-grid-placeholder-row"
|
|
1540
|
+
class:sv-grid-placeholder-failed={phState === "failed"}
|
|
1541
|
+
class:sv-grid-placeholder-failed-cont={phContinues}
|
|
1542
|
+
aria-busy={phState === "loading" ? "true" : undefined}
|
|
1543
|
+
{...getGridRowA11yProps(phRowIndex + 1)}
|
|
1544
|
+
style={phStyle}
|
|
1545
|
+
>
|
|
1546
|
+
{#if phState === "failed"}
|
|
1547
|
+
<td
|
|
1548
|
+
class="sv-grid-cell sv-grid-placeholder-cell"
|
|
1549
|
+
colspan={allColumns.length +
|
|
1550
|
+
(showRowNumbersEffective ? 1 : 0) +
|
|
1551
|
+
(showRowSelectionEffective ? 1 : 0)}
|
|
1552
|
+
>
|
|
1553
|
+
{#if !phContinues}
|
|
1554
|
+
<div class="sv-grid-placeholder-failed-body">
|
|
1555
|
+
<span class="sv-grid-placeholder-error">{messages.rowLoadFailed}</span>
|
|
1556
|
+
{#if opt.onRetryRow}
|
|
1557
|
+
<button
|
|
1558
|
+
type="button"
|
|
1559
|
+
class="sv-grid-placeholder-retry"
|
|
1560
|
+
onclick={() => ctrl.retryRow(phRow)}>{messages.rowLoadRetry}</button
|
|
1561
|
+
>
|
|
1562
|
+
{/if}
|
|
1563
|
+
</div>
|
|
1564
|
+
{/if}
|
|
1565
|
+
</td>
|
|
1566
|
+
{:else}
|
|
1567
|
+
{#if showRowNumbersEffective}
|
|
1568
|
+
<td class="sv-grid-cell sv-grid-rownum-cell"></td>
|
|
1569
|
+
{/if}
|
|
1570
|
+
{#if showRowSelectionEffective}
|
|
1571
|
+
<td class="sv-grid-cell sv-grid-select-cell"></td>
|
|
1572
|
+
{/if}
|
|
1573
|
+
{#each allColumns as col (col.id)}
|
|
1574
|
+
<td class="sv-grid-cell sv-grid-placeholder-cell">
|
|
1575
|
+
<span class="sv-grid-placeholder-skeleton" aria-hidden="true"></span>
|
|
1576
|
+
</td>
|
|
1577
|
+
{/each}
|
|
1578
|
+
{/if}
|
|
1579
|
+
</tr>
|
|
1580
|
+
{/snippet}
|
|
1581
|
+
|
|
1464
1582
|
<!-- A single pinned row (top or bottom). Read-only by design: no
|
|
1465
1583
|
inline editing, no row-selection checkbox, no fill handle.
|
|
1466
1584
|
Position-sticky CSS keeps it anchored to the top of the body or
|
|
@@ -1474,7 +1592,14 @@
|
|
|
1474
1592
|
the virtualizer's measured size, or the declared one.
|
|
1475
1593
|
-->
|
|
1476
1594
|
{#snippet bodyRow(row: Row<TData>, rowIndex: number, rowStyle: string)}
|
|
1477
|
-
{
|
|
1595
|
+
{@const phState = ctrl.placeholderStateOf(row)}
|
|
1596
|
+
{#if phState}
|
|
1597
|
+
{@const phContinues =
|
|
1598
|
+
phState === "failed" &&
|
|
1599
|
+
rowIndex > firstRenderedRowIndex &&
|
|
1600
|
+
ctrl.placeholderStateOf(allRows[rowIndex - 1]!) === "failed"}
|
|
1601
|
+
{@render placeholderRowMarkup(row, rowIndex, phState, rowStyle, phContinues)}
|
|
1602
|
+
{:else if opt.isDetailRow?.(row.original as TData, rowIndex)}
|
|
1478
1603
|
{@render detailRowMarkup(row, rowIndex)}
|
|
1479
1604
|
{:else if isGroupRow(row) && !groupColumnMode}
|
|
1480
1605
|
<tr
|
|
@@ -1827,7 +1952,7 @@
|
|
|
1827
1952
|
style={chartDockReserveStyle}
|
|
1828
1953
|
data-move-grab={ctrl.moveGrabHover || ctrl.moveDrag ? "true" : undefined}
|
|
1829
1954
|
data-selbar={
|
|
1830
|
-
ctrl.
|
|
1955
|
+
ctrl.selectionBarVisible
|
|
1831
1956
|
? (getSelectionBarView() ? ctrl.selectionBarPosition : `upsell-${ctrl.selectionBarPosition}`)
|
|
1832
1957
|
: undefined
|
|
1833
1958
|
}
|
|
@@ -3004,7 +3129,7 @@
|
|
|
3004
3129
|
`absolute` against it, so moving it outside would anchor it to the
|
|
3005
3130
|
page. The renderer is Enterprise and arrives through the
|
|
3006
3131
|
selection-bar-view seam; without it, a short note in its place. -->
|
|
3007
|
-
{#if ctrl.
|
|
3132
|
+
{#if ctrl.selectionBarVisible}
|
|
3008
3133
|
{#if getSelectionBarView()}
|
|
3009
3134
|
{@const SelectionBarView = getSelectionBarView()}
|
|
3010
3135
|
<SelectionBarView {ctrl} />
|