@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.
Files changed (77) hide show
  1. package/README.md +4 -4
  2. package/dist/SvGrid.controller.svelte.d.ts +12 -2
  3. package/dist/SvGrid.controller.svelte.js +192 -8
  4. package/dist/SvGrid.css +73 -0
  5. package/dist/SvGrid.svelte +128 -3
  6. package/dist/SvGrid.types.d.ts +356 -3
  7. package/dist/build-api.js +11 -6
  8. package/dist/cdn/{GridMenus-BL7ZgQvU.js → GridMenus-CID62rlI.js} +11 -11
  9. package/dist/cdn/{GridMenus-7kbpnnBW.js → GridMenus-DLL-qZeu.js} +11 -11
  10. package/dist/cdn/server-block-cache-CGoWz-87.js +237 -0
  11. package/dist/cdn/{src-BKhZ6eXd.js → src-Bklofsad.js} +8818 -8798
  12. package/dist/cdn/{src-V1uu8iE9.js → src-jcFMkBnN.js} +4282 -4262
  13. package/dist/cdn/svgrid.js +27 -26
  14. package/dist/cdn/svgrid.svelte-external.js +27 -26
  15. package/dist/cdn/validate-C7Rn_Bns.js +76 -0
  16. package/dist/core.js +36 -13
  17. package/dist/editing.js +10 -0
  18. package/dist/gantt-view.svelte.d.ts +24 -0
  19. package/dist/gantt-view.svelte.js +13 -0
  20. package/dist/grid-icons.d.ts +2 -2
  21. package/dist/grid-icons.js +2 -2
  22. package/dist/grid-messages.d.ts +6 -0
  23. package/dist/grid-messages.js +5 -0
  24. package/dist/index.d.ts +5 -4
  25. package/dist/index.js +3 -3
  26. package/dist/row-model.d.ts +133 -0
  27. package/dist/row-model.js +16 -0
  28. package/dist/selection.d.ts +2 -1
  29. package/dist/selection.js +30 -2
  30. package/dist/server-block-cache.d.ts +214 -0
  31. package/dist/server-block-cache.js +531 -0
  32. package/dist/server-data-source.d.ts +240 -6
  33. package/dist/server-data-source.js +220 -20
  34. package/dist/server.d.ts +13 -0
  35. package/dist/server.js +13 -0
  36. package/dist/validate.d.ts +4 -0
  37. package/dist/validate.js +21 -3
  38. package/package.json +6 -1
  39. package/src/SvGrid.controller.svelte.ts +191 -8
  40. package/src/SvGrid.css +73 -0
  41. package/src/SvGrid.svelte +128 -3
  42. package/src/SvGrid.types.ts +376 -3
  43. package/src/build-api.ts +11 -4
  44. package/src/core.rowmodel-cache.test.ts +51 -0
  45. package/src/core.ts +33 -12
  46. package/src/editing.ts +8 -0
  47. package/src/gantt-stub.test.svelte +38 -0
  48. package/src/gantt-view.svelte.ts +35 -0
  49. package/src/grid-icons.ts +2 -2
  50. package/src/grid-messages.ts +12 -0
  51. package/src/icon-seam.test.ts +2 -4
  52. package/src/index.ts +36 -10
  53. package/src/row-model.ts +146 -0
  54. package/src/selection.test.ts +3 -0
  55. package/src/selection.ts +32 -2
  56. package/src/server-block-cache.test.ts +645 -0
  57. package/src/server-block-cache.ts +677 -0
  58. package/src/server-data-source.infinite.test.ts +343 -0
  59. package/src/server-data-source.ts +450 -27
  60. package/src/server.ts +47 -0
  61. package/src/svgrid.gantt-seam.test.ts +199 -0
  62. package/src/svgrid.row-model-prop.svelte.test.ts +352 -0
  63. package/src/svgrid.row-model-seam.svelte.test.ts +289 -0
  64. package/src/svgrid.upsell-license.test.ts +7 -4
  65. package/src/validate.test.ts +29 -0
  66. package/src/validate.ts +28 -3
  67. package/dist/SvGroupCell.svelte +0 -141
  68. package/dist/SvGroupCell.svelte.d.ts +0 -49
  69. package/dist/SvRowGroupPanel.svelte +0 -186
  70. package/dist/SvRowGroupPanel.svelte.d.ts +0 -25
  71. package/dist/cdn/validate-_CDJzgIo.js +0 -75
  72. package/dist/server-group-model.d.ts +0 -98
  73. package/dist/server-group-model.js +0 -263
  74. package/src/SvGroupCell.svelte +0 -141
  75. package/src/SvRowGroupPanel.svelte +0 -186
  76. package/src/server-group-model.test.ts +0 -294
  77. package/src/server-group-model.ts +0 -370
@@ -106,9 +106,16 @@ export type GridChromeMessages = {
106
106
  bulkEditCancel: string
107
107
  bulkEditNoFields: string
108
108
  chartRange: string
109
+ // Rows whose data has not arrived (server-side / infinite loading)
110
+ rowLoadFailed: string
111
+ rowLoadRetry: string
109
112
  // Upsell notes (enterprise views not installed)
110
113
  pivotUpsellTitle: string
111
114
  pivotUpsellBody: string
115
+ ganttUpsellTitle: string
116
+ ganttUpsellBody: string
117
+ /** Placeholder in the Gantt view's search box. */
118
+ ganttSearchPlaceholder: string
112
119
  /** Shared licensing line under every Enterprise upsell note. */
113
120
  upsellLicense: string
114
121
  upsellLicenseLink: string
@@ -185,8 +192,13 @@ export const defaultGridMessages: GridChromeMessages = {
185
192
  opBefore: 'Before',
186
193
  opAfter: 'After',
187
194
  chartRange: 'Chart selected range',
195
+ rowLoadFailed: 'Could not load these rows.',
196
+ rowLoadRetry: 'Retry',
188
197
  pivotUpsellTitle: 'Pivot mode',
189
198
  pivotUpsellBody: 'Pivot mode is an Enterprise feature. Install @svgrid/enterprise and call enablePivot() to use it.',
199
+ ganttUpsellTitle: 'Gantt view',
200
+ ganttUpsellBody: 'The Gantt view is an Enterprise feature. Install @svgrid/enterprise and call enableGanttView() to render it.',
201
+ ganttSearchPlaceholder: 'Search tasks...',
190
202
  upsellLicense:
191
203
  'A license key is required for production use. Without one the feature still works, but the grid renders an unlicensed watermark.',
192
204
  upsellLicenseLink: 'View licensing',
@@ -20,18 +20,16 @@ const SRC = join(process.cwd(), 'src')
20
20
  const read = (f: string) => readFileSync(join(SRC, f), 'utf8')
21
21
 
22
22
  /** Files that render grid chrome and therefore must go through the seam. */
23
- const CHROME = ['SvGrid.svelte', 'GridMenus.svelte', 'GridFooter.svelte', 'SvRowGroupPanel.svelte']
23
+ const CHROME = ['SvGrid.svelte', 'GridMenus.svelte', 'GridFooter.svelte']
24
24
 
25
25
  /**
26
26
  * Drop the regions allowed to contain raw icon markup: the `icon` snippet's own
27
- * body (which IS the built-in set), the `ic` snippet in SvRowGroupPanel, and
28
- * every `<script>` block.
27
+ * body (which IS the built-in set) and every `<script>` block.
29
28
  */
30
29
  function stripAllowedRegions(src: string): string {
31
30
  let out = src.replace(/<script[\s\S]*?<\/script>/g, '')
32
31
  // The built-in catalogue: from the snippet header to its closing tag.
33
32
  out = out.replace(/\{#snippet icon\(name: GridIconName\)\}[\s\S]*?\n\{\/snippet\}/g, '')
34
- out = out.replace(/\{#snippet ic\(name: GridIconName\)\}[\s\S]*?\{\/snippet\}/g, '')
35
33
  return out
36
34
  }
37
35
 
package/src/index.ts CHANGED
@@ -121,6 +121,23 @@ export type {
121
121
  SchedulerOccurrenceChangeEvent,
122
122
  } from './SvGrid.types'
123
123
  export { registerSchedulerView, getSchedulerView, hasSchedulerView } from './scheduler-view.svelte'
124
+ // Gantt view. Same split as the scheduler: the `gantt` prop and its config
125
+ // types live in the free grid, the RENDERER ships in @svgrid/enterprise and
126
+ // registers itself through this seam (see gantt-view.svelte.ts). Unlike the
127
+ // scheduler, the Gantt's layout model lives in enterprise too - only the
128
+ // renderer reads it.
129
+ export type {
130
+ GanttConfig,
131
+ GanttDrawerConfig,
132
+ GanttTaskMoveEvent,
133
+ GanttTaskResizeEvent,
134
+ GanttProgressChangeEvent,
135
+ GanttTaskCommitEvent,
136
+ GanttDependency,
137
+ GanttDependencyType,
138
+ GanttZoom,
139
+ } from './SvGrid.types'
140
+ export { registerGanttView, getGanttView, hasGanttView } from './gantt-view.svelte'
124
141
  // Bulk-action bar over the row selection. Prop + types free, renderer Pro.
125
142
  export { registerSelectionBarView, getSelectionBarView, hasSelectionBarView } from './selection-bar-view.svelte'
126
143
  // Chart view. The `chart` prop + its config live in the free grid AND so does the
@@ -754,9 +771,11 @@ export {
754
771
  export {
755
772
  createServerDataSource,
756
773
  type ServerDataSource,
774
+ type ServerSelectionRule,
757
775
  type ServerRequest,
758
776
  type ServerResult,
759
777
  type ServerController,
778
+ type ServerControllerOptions,
760
779
  type ServerState,
761
780
  type ServerSortModel,
762
781
  type ServerFilterModel,
@@ -766,19 +785,26 @@ export {
766
785
  type ServerMoreRow,
767
786
  type ServerFooterRow,
768
787
  type ServerSkeletonRow,
788
+ type ServerGrandTotalRow,
789
+ type ServerPlaceholderRow,
769
790
  type ServerDisplayRow,
770
791
  } from './server-data-source'
771
792
  export {
772
- createServerGroupModel,
773
- serverGroupRows,
774
- serverGroupNav,
775
- type ServerGroupController,
776
- type ServerGroupControllerOptions,
777
- type ServerGroupState,
778
- type ServerGroupGridRow,
779
- } from './server-group-model'
780
- export { default as SvGroupCell } from './SvGroupCell.svelte'
781
- export { default as SvRowGroupPanel } from './SvRowGroupPanel.svelte'
793
+ createBlockCache,
794
+ createRowPlaceholder,
795
+ rowPlaceholderState,
796
+ type BlockCache,
797
+ type BlockCacheOptions,
798
+ type BlockCacheState,
799
+ type BlockFetchResult,
800
+ type BlockState,
801
+ } from './server-block-cache'
802
+ export {
803
+ toServerFilterColumns,
804
+ type GridRowModel,
805
+ type GridFilterState,
806
+ type GridRowModelSort,
807
+ } from './row-model'
782
808
  export {
783
809
  createNamedViews,
784
810
  memoryViews,
@@ -0,0 +1,146 @@
1
+ /**
2
+ * `GridRowModel` - one prop instead of eleven.
3
+ *
4
+ * A grid backed by a server needs the same wiring every time: hand it the
5
+ * rows, tell it not to sort or filter locally, forward the sort and the filter
6
+ * to the backend, report which rows are on screen, say which rows have not
7
+ * loaded, and delegate selection. Written out by hand that is a dozen props
8
+ * and a filter-shape conversion that every app gets slightly differently:
9
+ *
10
+ * ```svelte
11
+ * <SvGrid data={s.rows} externalSort externalFilter loading={s.loading}
12
+ * onSortingChange={(x) => ctl.setSort(x)}
13
+ * onFiltersChange={(f) => ctl.setFilter({ global: f.global,
14
+ * columns: Object.fromEntries(f.columns.map((c) => [c.id, { ... }])) })}
15
+ * onVisibleRangeChange={(r) => ctl.setViewport(r.startIndex, r.endIndex)}
16
+ * ... />
17
+ * ```
18
+ *
19
+ * An object that implements this interface carries all of it, so the same grid
20
+ * becomes:
21
+ *
22
+ * ```svelte
23
+ * <SvGrid rowModel={ctl} {columns} />
24
+ * ```
25
+ *
26
+ * `createServerDataSource` returns one, and so does the Enterprise server row
27
+ * model - which is the point of the interface rather than a concrete type: the
28
+ * grid depends on the SHAPE, never on either implementation, and an app with
29
+ * its own fetching can satisfy it too.
30
+ *
31
+ * Every member except `subscribe` / `getRows` / `isLoading` is optional, and a
32
+ * prop written explicitly on `<SvGrid>` always wins over the model's answer -
33
+ * so adopting this is never all-or-nothing.
34
+ */
35
+ import type { FilterOperator } from './SvGrid.types'
36
+
37
+ /** The filter state as the grid reports it, straight from `onFiltersChange`. */
38
+ export type GridFilterState = {
39
+ global: string
40
+ columns: Array<{
41
+ id: string
42
+ operator: FilterOperator
43
+ value: string
44
+ valueTo?: string
45
+ selectedValues?: Array<string>
46
+ }>
47
+ }
48
+
49
+ /** The sort the grid hands a row model: column id and direction, in priority order. */
50
+ export type GridRowModelSort = Array<{ id: string; desc: boolean }>
51
+
52
+ /**
53
+ * What `<SvGrid rowModel>` consumes: rows, a loading flag, a change
54
+ * subscription, and whichever optional parts the model implements - sort,
55
+ * filter, viewport, placeholders, group accessors, selection, filter
56
+ * values, paging, pinned rows and pivot columns. `createServerDataSource`
57
+ * and the Enterprise `createServerRowModel` both satisfy it.
58
+ */
59
+ export type GridRowModel<TData> = {
60
+ /**
61
+ * Register a callback for "something changed, re-read me". Returns an
62
+ * unsubscribe function, which the grid calls on unmount.
63
+ */
64
+ subscribe(onChange: () => void): () => void
65
+ /** The rows to render, including any placeholders for unloaded ones. */
66
+ getRows(): ReadonlyArray<TData>
67
+ /** True while a fetch is in flight, for the loading overlay. */
68
+ isLoading(): boolean
69
+ /** Stable row id. Required once selection or editing is involved. */
70
+ getRowId?(row: TData, index: number): string
71
+ /** Sort on the server. Present means "do not sort locally". */
72
+ setSort?(sort: GridRowModelSort): void
73
+ /** Filter on the server. Present means "do not filter locally". */
74
+ setFilter?(filters: GridFilterState): void
75
+ /** Which rows are on screen, so a block-loading model knows what to fetch. */
76
+ setViewport?(startIndex: number, endIndex: number): void
77
+ /** Why a row has no data yet, or null when it does. */
78
+ rowPlaceholder?(row: TData, rowIndex: number): 'loading' | 'failed' | null
79
+ /** Retry the failed block a placeholder row belongs to. */
80
+ retryRow?(row: TData, rowIndex: number): void
81
+ /** Group / tree accessors, for the grid's treegrid keyboard and ARIA. */
82
+ group?: {
83
+ isGroup: (row: TData) => boolean
84
+ level: (row: TData) => number
85
+ expanded?: (row: TData) => boolean
86
+ onToggle: (row: TData) => void
87
+ }
88
+ /** Selection the grid does not own - see the `rowSelectionModel` prop. */
89
+ selection?: {
90
+ isSelected: (rowId: string, row: TData) => boolean
91
+ headerState: () => 'none' | 'some' | 'all'
92
+ toggle: (rowId: string, row: TData, next: boolean) => void
93
+ toggleAll: (next: boolean) => void
94
+ selectedCount?: () => number | null
95
+ bulkUpdate?: (patch: Record<string, unknown>) => Promise<number>
96
+ }
97
+ /** Distinct values for a column's set filter, fetched from the server. */
98
+ filterValues?(columnId: string): Promise<Array<string>>
99
+ /** Server-side paging. Omit it for infinite scrolling. */
100
+ pagination?: {
101
+ pageIndex: number
102
+ pageSize: number
103
+ rowCount: number
104
+ setPage(pageIndex: number): void
105
+ setPageSize(pageSize: number): void
106
+ /** Choices for the footer's page-size selector. */
107
+ pageSizes?: number[]
108
+ /**
109
+ * Ask the grid for as many rows as its body fits: it measures and calls
110
+ * `setPageSize` whenever the fit changes.
111
+ */
112
+ autoPageSize?: boolean
113
+ }
114
+ /** Rows to pin above / below the scrolling body - a grand total, say. */
115
+ readonly pinnedTopRows?: ReadonlyArray<TData>
116
+ readonly pinnedBottomRows?: ReadonlyArray<TData>
117
+ /**
118
+ * Columns that replace the grid's `columns` while the model is in pivot
119
+ * mode, or null. Read after every change notification.
120
+ */
121
+ readonly pivotResultColumns?: ReadonlyArray<unknown> | null
122
+ }
123
+
124
+ /**
125
+ * Turn the grid's filter payload into the column-keyed map a `ServerRequest`
126
+ * carries. Exported because a hand-written `GridRowModel` needs exactly this
127
+ * conversion, and copying it is how the two shapes drift apart.
128
+ */
129
+ export function toServerFilterColumns(
130
+ filters: GridFilterState,
131
+ ): Record<
132
+ string,
133
+ { operator: string; value: string; valueTo?: string; selectedValues?: Array<string> }
134
+ > {
135
+ return Object.fromEntries(
136
+ filters.columns.map((c) => [
137
+ c.id,
138
+ {
139
+ operator: c.operator,
140
+ value: c.value,
141
+ ...(c.valueTo !== undefined ? { valueTo: c.valueTo } : {}),
142
+ ...(c.selectedValues !== undefined ? { selectedValues: c.selectedValues } : {}),
143
+ },
144
+ ]),
145
+ )
146
+ }
@@ -39,6 +39,9 @@ function makeCtx(overrides: any = {}) {
39
39
  allRows: [],
40
40
  allColumns: [],
41
41
  headerSelectionState: 'none',
42
+ // The real controller always supplies this; a fake that omits it would
43
+ // make every select-all path throw rather than assert.
44
+ placeholderStateOf: () => null,
42
45
  userHasActivatedCell: false,
43
46
  enableCellSelectionEffective: true,
44
47
  selectionRange: { anchor: null, focus: null },
package/src/selection.ts CHANGED
@@ -21,16 +21,45 @@ export function createSelection<
21
21
  TData extends RowData = RowData,
22
22
  >(ctx: any) {
23
23
  function isRowSelected(rowId: string) {
24
+ const model = ctx.props.rowSelectionModel;
25
+ if (model) {
26
+ const row = ctx.allRows.find((r: any) => r.id === rowId);
27
+ return row ? model.isSelected(rowId, row.original) : false;
28
+ }
24
29
  return Boolean(ctx.rowSelectionState[rowId]);
25
30
  }
26
31
 
27
32
  function toggleRowSelectionById(rowId: string) {
33
+ const model = ctx.props.rowSelectionModel;
34
+ if (model) {
35
+ const row = ctx.allRows.find((r: any) => r.id === rowId);
36
+ if (!row) return;
37
+ model.toggle(rowId, row.original, !model.isSelected(rowId, row.original));
38
+ return;
39
+ }
28
40
  ctx.grid.setRowSelection((prev: any) => ({ ...prev, [rowId]: !prev[rowId] }));
29
41
  }
30
42
 
31
43
  function toggleSelectAllRows() {
32
- const selectable = ctx.allRows.filter((row: any) => !isGroupRow(row));
33
- const select = ctx.headerSelectionState !== "all";
44
+ setSelectAllRows(ctx.headerSelectionState !== "all");
45
+ }
46
+
47
+ /**
48
+ * Select or clear every row, for the header checkbox and for
49
+ * `api.selectAllRows()`.
50
+ */
51
+ function setSelectAllRows(select: boolean) {
52
+ // An external model decides what "all" means - possibly a million rows
53
+ // the grid has never seen - so it gets the intent, not a row list.
54
+ if (ctx.props.rowSelectionModel) {
55
+ ctx.props.rowSelectionModel.toggleAll(select);
56
+ return;
57
+ }
58
+ // Placeholder rows are excluded: there is no row there to select yet,
59
+ // and ticking one would leave a selected id that never resolves.
60
+ const selectable = ctx.allRows.filter(
61
+ (row: any) => !isGroupRow(row) && !ctx.placeholderStateOf(row),
62
+ );
34
63
  ctx.grid.setRowSelection((prev: any) => {
35
64
  const next = { ...prev };
36
65
  for (const row of selectable) {
@@ -687,6 +716,7 @@ export function createSelection<
687
716
  isRowSelected,
688
717
  toggleRowSelectionById,
689
718
  toggleSelectAllRows,
719
+ setSelectAllRows,
690
720
  setActiveCell,
691
721
  scrollActiveCellIntoView,
692
722
  setSelection,