ag-grid-url-sync 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -14
- package/dist/core/ag-grid-url-sync.d.ts +13 -0
- package/dist/core/ag-grid-url-sync.js +10 -55
- package/dist/core/ag-grid-url-sync.js.map +1 -1
- package/dist/core/grid-integration.js +5 -67
- package/dist/core/grid-integration.js.map +1 -1
- package/dist/core/index.js +0 -5
- package/dist/core/index.js.map +1 -1
- package/dist/core/individual-params.d.ts +19 -0
- package/dist/core/individual-params.js +260 -0
- package/dist/core/individual-params.js.map +1 -0
- package/dist/core/serialization/formats.d.ts +8 -9
- package/dist/core/serialization/formats.js +63 -65
- package/dist/core/serialization/formats.js.map +1 -1
- package/dist/core/serialization/grouped.d.ts +10 -5
- package/dist/core/serialization/grouped.js +4 -51
- package/dist/core/serialization/grouped.js.map +1 -1
- package/dist/core/serialization/index.js +0 -4
- package/dist/core/serialization/index.js.map +1 -1
- package/dist/core/types.d.ts +42 -9
- package/dist/core/types.js +0 -118
- package/dist/core/types.js.map +1 -1
- package/dist/core/url-generator.d.ts +10 -7
- package/dist/core/url-generator.js +25 -165
- package/dist/core/url-generator.js.map +1 -1
- package/dist/core/url-parser.d.ts +19 -7
- package/dist/core/url-parser.js +28 -294
- package/dist/core/url-parser.js.map +1 -1
- package/dist/core/validation.d.ts +6 -6
- package/dist/core/validation.js +1 -78
- package/dist/core/validation.js.map +1 -1
- package/dist/core/view-storage.d.ts +63 -0
- package/dist/core/view-storage.js +112 -0
- package/dist/core/view-storage.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +0 -6
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/types.d.ts +103 -1
- package/dist/react/use-ag-grid-url-sync.js +406 -46
- package/dist/react/use-ag-grid-url-sync.js.map +1 -1
- package/package.json +98 -31
package/README.md
CHANGED
|
@@ -196,12 +196,11 @@ import type {
|
|
|
196
196
|
```typescript
|
|
197
197
|
import { parseUrlFilters } from 'ag-grid-url-sync/url-parser'
|
|
198
198
|
import { DEFAULT_CONFIG } from 'ag-grid-url-sync/validation'
|
|
199
|
-
import type { FilterState } from 'ag-grid-url-sync/types'
|
|
199
|
+
import type { FilterState, ParseConfig } from 'ag-grid-url-sync/types'
|
|
200
200
|
|
|
201
201
|
function getFiltersFromUrl(url: string): FilterState {
|
|
202
|
-
const config = {
|
|
203
|
-
|
|
204
|
-
...DEFAULT_CONFIG,
|
|
202
|
+
const config: ParseConfig = {
|
|
203
|
+
...DEFAULT_CONFIG, // gridApi is not needed for parsing
|
|
205
204
|
onParseError: error => console.error('Parse error:', error)
|
|
206
205
|
}
|
|
207
206
|
|
|
@@ -209,18 +208,32 @@ function getFiltersFromUrl(url: string): FilterState {
|
|
|
209
208
|
}
|
|
210
209
|
```
|
|
211
210
|
|
|
211
|
+
`parseUrlFilters` reads the input with these rules, in this order:
|
|
212
|
+
|
|
213
|
+
| # | Input | Result | Example |
|
|
214
|
+
| --- | --------------------------- | ------------------------ | ----------------------------------------- |
|
|
215
|
+
| 1 | A host with no scheme | Throws `InvalidURLError` | `example.com?…`, `localhost:3000?…` |
|
|
216
|
+
| 2 | Starts with a scheme | Absolute URL | `https://example.com/users?f_name_eq=bob` |
|
|
217
|
+
| 3 | Starts with `/`, `?` or `#` | Relative URL | `/users?f_name_eq=bob`, `?f_name_eq=bob` |
|
|
218
|
+
| 4 | Anything else | Raw query string | `f_name_eq=bob` |
|
|
219
|
+
|
|
220
|
+
- A relative path must start with `/`. `users?f_name_eq=bob` matches rule 4, so it gives no filters.
|
|
221
|
+
- A bare `localhost?…` with no port cannot be told apart from a raw query, so it matches rule 4.
|
|
222
|
+
- In a raw query string, a `?` or `#` is part of a param name or value.
|
|
223
|
+
|
|
224
|
+
`InvalidURLError` is thrown for rule 1, and for any input that the `URL` constructor rejects under rules 2 and 3 (for example `http://` or `//[`). A param that is not valid is skipped and sent to `onParseError`.
|
|
225
|
+
|
|
212
226
|
#### Example: Custom URL Generator
|
|
213
227
|
|
|
214
228
|
```typescript
|
|
215
|
-
import { generateUrl
|
|
216
|
-
import
|
|
229
|
+
import { generateUrl } from 'ag-grid-url-sync/url-generator'
|
|
230
|
+
import { DEFAULT_CONFIG } from 'ag-grid-url-sync/validation'
|
|
231
|
+
import type { FilterState, ParseConfig } from 'ag-grid-url-sync/types'
|
|
217
232
|
|
|
218
233
|
function createShareableUrl(filters: FilterState, baseUrl: string): string {
|
|
219
|
-
const config = {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
maxValueLength: 200,
|
|
223
|
-
onParseError: () => {}
|
|
234
|
+
const config: ParseConfig = {
|
|
235
|
+
...DEFAULT_CONFIG, // gridApi is not needed for generation
|
|
236
|
+
maxValueLength: 500
|
|
224
237
|
}
|
|
225
238
|
|
|
226
239
|
return generateUrl(baseUrl, filters, config)
|
|
@@ -300,6 +313,10 @@ interface AGGridUrlSyncConfig {
|
|
|
300
313
|
}
|
|
301
314
|
```
|
|
302
315
|
|
|
316
|
+
When the library reads a URL, it reads only the `groupedParam` name and decodes it with the configured `format`. It does not guess the format. This works in both modes, so a grouped link opens in an app in individual mode when the name and format match. If the param is present and not empty, the prefixed params are not read.
|
|
317
|
+
|
|
318
|
+
> **Note:** A URL written with a different `groupedParam` or `format` does not decode. If you change either option, links that were shared before the change stop working.
|
|
319
|
+
|
|
303
320
|
### Format Options
|
|
304
321
|
|
|
305
322
|
#### QueryString Format (Default)
|
|
@@ -420,7 +437,7 @@ import {
|
|
|
420
437
|
|
|
421
438
|
### Error Handling for Grouped Serialization
|
|
422
439
|
|
|
423
|
-
You can provide a custom error handler to gracefully handle malformed grouped parameters or serialization errors.
|
|
440
|
+
You can provide a custom error handler to gracefully handle malformed grouped parameters or serialization errors. In grouped mode, `onParseError` receives an `InvalidSerializationError` for invalid formats:
|
|
424
441
|
|
|
425
442
|
```typescript
|
|
426
443
|
import { InvalidSerializationError } from 'ag-grid-url-sync/types'
|
|
@@ -440,6 +457,18 @@ const urlSync = createUrlSync(gridApi, {
|
|
|
440
457
|
})
|
|
441
458
|
```
|
|
442
459
|
|
|
460
|
+
In every grouped format (`querystring`, `json`, `base64`), a text value over `maxValueLength` (default 200) is dropped and reported through `onParseError`, both when the URL is generated and when it is parsed.
|
|
461
|
+
|
|
462
|
+
In grouped mode, an error that comes from the grouped URL value is an `InvalidSerializationError`. This includes an error for one bad parameter inside a grouped querystring value, such as a value over `maxValueLength`. The original error is in `error.cause`. In individual mode, the same failure arrives as the original error, with no wrapper. To handle a specific error in both modes, check the error and its cause:
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
import { InvalidFilterError } from 'ag-grid-url-sync/types'
|
|
466
|
+
|
|
467
|
+
const isFilterError = (error: Error) =>
|
|
468
|
+
error instanceof InvalidFilterError ||
|
|
469
|
+
error.cause instanceof InvalidFilterError
|
|
470
|
+
```
|
|
471
|
+
|
|
443
472
|
### Exports
|
|
444
473
|
|
|
445
474
|
The package supports granular subpath exports for advanced usage and optimal bundle size:
|
|
@@ -450,6 +479,7 @@ The package supports granular subpath exports for advanced usage and optimal bun
|
|
|
450
479
|
- `ag-grid-url-sync/url-parser` – URL parsing utilities
|
|
451
480
|
- `ag-grid-url-sync/url-generator` – URL generation utilities
|
|
452
481
|
- `ag-grid-url-sync/grid-integration` – AG Grid integration helpers
|
|
482
|
+
- `ag-grid-url-sync/view-storage` – localStorage-backed saved views
|
|
453
483
|
- `ag-grid-url-sync/types` – TypeScript types
|
|
454
484
|
|
|
455
485
|
Refer to the [package.json](./package.json) for the full export map.
|
|
@@ -577,6 +607,9 @@ interface UseAGGridUrlSyncOptions {
|
|
|
577
607
|
autoApplyOnMount?: boolean // Default: false
|
|
578
608
|
enabledWhenReady?: boolean // Default: true
|
|
579
609
|
onError?: (error: Error, context: string) => void // Advanced error handling
|
|
610
|
+
|
|
611
|
+
// Saved views: setting a key enables the feature and namespaces its storage
|
|
612
|
+
storageKey?: string // Default: undefined (saved views disabled)
|
|
580
613
|
}
|
|
581
614
|
```
|
|
582
615
|
|
|
@@ -590,22 +623,110 @@ interface UseAGGridUrlSyncOptions {
|
|
|
590
623
|
| `clearFilters` | `() => void` | Clear all text filters |
|
|
591
624
|
| `isReady` | `boolean` | Whether grid API is available and hook is ready |
|
|
592
625
|
| `currentUrl` | `string` | Current URL with filters applied |
|
|
593
|
-
| `hasFilters` | `boolean` | Whether grid has
|
|
626
|
+
| `hasFilters` | `boolean` | Whether the grid has supported filters |
|
|
594
627
|
| `parseUrlFilters` | `(url: string) => FilterState` | Parse filters from URL without applying |
|
|
595
628
|
| `applyFilters` | `(filters: FilterState) => void` | Apply filter state object to grid |
|
|
596
629
|
| `getFiltersAsFormat` | `(format: 'querystring' | 'json' | 'base64') => string` | Serialize filters to any supported format (for sharing/export) |
|
|
597
630
|
| `getCurrentFormat` | `() => 'individual' | 'grouped'` | Get the current serialization mode (individual or grouped) |
|
|
598
631
|
|
|
632
|
+
Saved views (inert unless `storageKey` is set):
|
|
633
|
+
|
|
634
|
+
| Property | Type | Description |
|
|
635
|
+
| -------------- | ------------------------------------ | ----------------------------------------------------------------------- |
|
|
636
|
+
| `views` | `GridView[]` | Saved views for this grid, in save order. Empty when views are disabled |
|
|
637
|
+
| `activeViewId` | `string \| null` | Id of the view currently applied to the grid, or `null` when none is |
|
|
638
|
+
| `saveView` | `(name: string) => GridView \| null` | Save current filters under a name; overwrites an existing name in place |
|
|
639
|
+
| `loadView` | `(id: string \| null) => void` | Apply a saved view, or pass `null` to reset to unfiltered |
|
|
640
|
+
| `deleteView` | `(id: string) => void` | Delete a saved view |
|
|
641
|
+
|
|
599
642
|
> **Note:** `getFiltersAsFormat` and `getCurrentFormat` are especially useful for grouped serialization, format conversion, and advanced sharing scenarios.
|
|
600
643
|
|
|
644
|
+
### Saved Views
|
|
645
|
+
|
|
646
|
+
Pass a `storageKey` to let users name and revisit filter sets. The key both enables
|
|
647
|
+
the feature and namespaces its storage, so two grids on the same origin must use
|
|
648
|
+
different keys:
|
|
649
|
+
|
|
650
|
+
```tsx
|
|
651
|
+
const { views, activeViewId, saveView, loadView, deleteView } =
|
|
652
|
+
useAGGridUrlSync(gridApi, { storageKey: 'employee-grid' })
|
|
653
|
+
|
|
654
|
+
// One handler each, as a user drives them: save whatever is currently filtered,
|
|
655
|
+
// then apply or remove a view by id in a later interaction.
|
|
656
|
+
const handleSave = () => saveView('Engineering, high salary')
|
|
657
|
+
const handleLoad = (id: string) => loadView(id)
|
|
658
|
+
const handleDelete = (id: string) => deleteView(id)
|
|
659
|
+
const handleReset = () => loadView(null) // back to an unfiltered grid
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
Render the list from `views` and drive the buttons from it. `views[i].id` is only
|
|
663
|
+
stale immediately after a mutation, not during render. If you do need the new
|
|
664
|
+
view's id in the same handler that saved it, use `saveView`'s return value rather
|
|
665
|
+
than reading it back out of `views`: that array is React state, so it does not
|
|
666
|
+
update until the next render.
|
|
667
|
+
|
|
668
|
+
Only the filter model is stored, using AG Grid's native format so set filters and
|
|
669
|
+
combined conditions survive a round trip. Column order, row selection and expanded
|
|
670
|
+
groups are not stored.
|
|
671
|
+
|
|
672
|
+
Behaviour worth knowing:
|
|
673
|
+
|
|
674
|
+
- **Names are unique.** Saving over an existing name updates that view in place,
|
|
675
|
+
keeping its id and list position, so "load, adjust, save under the same name" is
|
|
676
|
+
how you update one. Names are trimmed and must not be empty.
|
|
677
|
+
- **`activeViewId` describes the live grid**, not what is persisted. A stored view
|
|
678
|
+
is only applied on mount when `autoApplyOnMount` is set, so with it off this
|
|
679
|
+
stays `null` until you call `loadView` or `saveView`.
|
|
680
|
+
- **Filtering away from a view unloads it.** `activeViewId` returns to `null` as
|
|
681
|
+
soon as the grid stops showing that view, including through routes that never
|
|
682
|
+
mention views: `clearFilters`, `applyFilters`, `applyUrlFilters`, or the user
|
|
683
|
+
editing a filter in the grid's own UI. The stored pointer is cleared with it,
|
|
684
|
+
so `autoApplyOnMount` will not bring the view back on the next load. The grid
|
|
685
|
+
is compared against what it reported once the view was applied, so a view AG
|
|
686
|
+
Grid normalises (one naming a column the grid no longer has, say) stays loaded
|
|
687
|
+
and keeps its stored pointer. A write AG Grid defers, such as a restore made
|
|
688
|
+
before column data types are inferred, is recognised when it lands by the
|
|
689
|
+
event's `source: 'api'`; on AG Grid versions that do not report a source, a
|
|
690
|
+
deferred view the grid also normalises unloads when it lands. An API change
|
|
691
|
+
that filters a column the view does not, such as your own `setFilterModel`
|
|
692
|
+
over a view the grid ignored, is not taken for the view and unloads it. One
|
|
693
|
+
that filters only the view's columns cannot be told from the view landing.
|
|
694
|
+
Render the
|
|
695
|
+
active state from `activeViewId` and it follows the grid on its own. Replacing
|
|
696
|
+
`gridApi` also clears `activeViewId`, but keeps the stored pointer, so with
|
|
697
|
+
`autoApplyOnMount` the view is restored against the new grid.
|
|
698
|
+
- **URL filters win on mount.** With `autoApplyOnMount` set, an incoming URL that
|
|
699
|
+
carries filters takes precedence over the stored view, so a shared link shows the
|
|
700
|
+
sender's filters rather than the recipient's saved default. It wins once per
|
|
701
|
+
grid: switching `storageKey` later restores the new namespace's stored view
|
|
702
|
+
instead of applying the link again.
|
|
703
|
+
- **Deleting is conservative.** `deleteView` clears the grid only if it still shows
|
|
704
|
+
exactly that view's filters, so hand-edited filters survive.
|
|
705
|
+
- **Failures are reported, not silent.** A save that cannot be persisted (quota
|
|
706
|
+
exhausted, storage blocked by policy) returns `null` and reports through
|
|
707
|
+
`onError` with context `'save-view'`. Reads degrade to an empty list instead of
|
|
708
|
+
throwing, so server rendering is safe.
|
|
709
|
+
|
|
710
|
+
For non-React consumers the same store is available directly:
|
|
711
|
+
|
|
712
|
+
```ts
|
|
713
|
+
import { createViewStore } from 'ag-grid-url-sync/view-storage'
|
|
714
|
+
|
|
715
|
+
const store = createViewStore('employee-grid')
|
|
716
|
+
store.saveView('Engineering', gridApi.getFilterModel())
|
|
717
|
+
```
|
|
718
|
+
|
|
601
719
|
### React Features
|
|
602
720
|
|
|
603
721
|
- **🔄 Automatic State Management**: Tracks grid state and filter changes automatically
|
|
604
722
|
- **⚡ Ready State**: `isReady` indicates when the grid API is available
|
|
605
|
-
- **🎯 Auto-Apply**: `autoApplyOnMount` applies URL filters when component mounts
|
|
723
|
+
- **🎯 Auto-Apply**: `autoApplyOnMount` applies URL filters when component mounts.
|
|
724
|
+
It is attempted once per grid: a failure is reported through `onError` and
|
|
725
|
+
`onParseError` and is not retried on later renders
|
|
606
726
|
- **🧹 Cleanup**: Automatically cleans up resources when component unmounts
|
|
607
727
|
- **🛡️ Error Boundaries**: Graceful error handling with configurable callbacks
|
|
608
728
|
- **📊 Filter Status**: `hasFilters` tracks whether any filters are active
|
|
729
|
+
- **💾 Saved Views**: `storageKey` enables naming and revisiting filter sets
|
|
609
730
|
|
|
610
731
|
### React with Grouped Serialization
|
|
611
732
|
|
|
@@ -797,6 +918,15 @@ const url = urlSync.generateUrl('https://app.com/page')
|
|
|
797
918
|
// https://app.com/page?f_name_contains=john
|
|
798
919
|
```
|
|
799
920
|
|
|
921
|
+
##### `getUrlState(baseUrl?: string): { url: string; hasFilters: boolean }`
|
|
922
|
+
|
|
923
|
+
Generates the same URL as `generateUrl`, and tells if the grid has filters that the library supports. It reads the grid one time for both values. `hasFilters` comes from the filter state, not from the URL, because the URL also keeps the page's own params.
|
|
924
|
+
|
|
925
|
+
```typescript
|
|
926
|
+
const { url, hasFilters } = urlSync.getUrlState('https://app.com/page?tab=1')
|
|
927
|
+
// url: https://app.com/page?tab=1 (no filters), hasFilters: false
|
|
928
|
+
```
|
|
929
|
+
|
|
800
930
|
##### `getQueryParams(): string`
|
|
801
931
|
|
|
802
932
|
Gets the current filter state as URL query parameters.
|
|
@@ -17,6 +17,19 @@ export declare class AGGridUrlSync {
|
|
|
17
17
|
* @returns URL string with filter parameters
|
|
18
18
|
*/
|
|
19
19
|
generateUrl(baseUrl?: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Generates a URL with the current filter state, and tells if the grid has
|
|
22
|
+
* filters that the library supports.
|
|
23
|
+
*
|
|
24
|
+
* Reads the grid filter model one time, so an error in it is reported one
|
|
25
|
+
* time. `hasFilters` comes from the filter state, not from the URL: the URL
|
|
26
|
+
* also keeps the page's own params (#215).
|
|
27
|
+
* @param baseUrl - Optional base URL (defaults to current URL)
|
|
28
|
+
*/
|
|
29
|
+
getUrlState(baseUrl?: string): {
|
|
30
|
+
url: string;
|
|
31
|
+
hasFilters: boolean;
|
|
32
|
+
};
|
|
20
33
|
/**
|
|
21
34
|
* Gets the current filter state as URL query parameters
|
|
22
35
|
* @returns Query parameter string
|
|
@@ -3,107 +3,62 @@ import { parseUrlFilters } from './url-parser.js';
|
|
|
3
3
|
import { generateUrl } from './url-generator.js';
|
|
4
4
|
import { getFilterModel, applyFilterModel } from './grid-integration.js';
|
|
5
5
|
import { serializeGrouped } from './serialization/grouped.js';
|
|
6
|
-
/**
|
|
7
|
-
* AGGridUrlSync class for synchronizing AG Grid text filters with URL parameters
|
|
8
|
-
*/
|
|
9
6
|
export class AGGridUrlSync {
|
|
10
7
|
config;
|
|
11
|
-
/**
|
|
12
|
-
* Creates a new instance of AGGridUrlSync
|
|
13
|
-
* @param gridApi - AG Grid API instance
|
|
14
|
-
* @param config - Optional configuration options
|
|
15
|
-
*/
|
|
16
8
|
constructor(gridApi, config = {}) {
|
|
9
|
+
const definedConfig = Object.fromEntries(Object.entries(config).filter(([, value]) => value !== undefined));
|
|
17
10
|
this.config = {
|
|
18
11
|
gridApi,
|
|
19
12
|
...DEFAULT_CONFIG,
|
|
20
|
-
...
|
|
13
|
+
...definedConfig
|
|
21
14
|
};
|
|
22
15
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Generates a URL with the current filter state
|
|
25
|
-
* @param baseUrl - Optional base URL (defaults to current URL)
|
|
26
|
-
* @returns URL string with filter parameters
|
|
27
|
-
*/
|
|
28
16
|
generateUrl(baseUrl) {
|
|
17
|
+
return this.getUrlState(baseUrl).url;
|
|
18
|
+
}
|
|
19
|
+
getUrlState(baseUrl) {
|
|
29
20
|
const filterState = getFilterModel(this.config);
|
|
30
21
|
const url = baseUrl ?? window.location.href;
|
|
31
|
-
return
|
|
22
|
+
return {
|
|
23
|
+
url: generateUrl(url, filterState, this.config),
|
|
24
|
+
hasFilters: Object.keys(filterState).length > 0
|
|
25
|
+
};
|
|
32
26
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Gets the current filter state as URL query parameters
|
|
35
|
-
* @returns Query parameter string
|
|
36
|
-
*/
|
|
37
27
|
getQueryParams() {
|
|
38
28
|
const url = this.generateUrl();
|
|
39
29
|
const urlObj = new URL(url);
|
|
40
30
|
return urlObj.search;
|
|
41
31
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Applies filters from a URL to the grid
|
|
44
|
-
* @param url - Optional URL to parse (defaults to current URL)
|
|
45
|
-
*/
|
|
46
32
|
applyFromUrl(url) {
|
|
47
33
|
const urlToParse = url ?? window.location.href;
|
|
48
34
|
const filterState = parseUrlFilters(urlToParse, this.config);
|
|
49
35
|
this.applyFilters(filterState);
|
|
50
36
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Applies a filter state object to the grid
|
|
53
|
-
* @param filterState - Filter state to apply
|
|
54
|
-
*/
|
|
55
37
|
applyFilters(filterState) {
|
|
56
38
|
applyFilterModel(filterState, this.config);
|
|
57
39
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Clears all text filters from the grid
|
|
60
|
-
*/
|
|
61
40
|
clearFilters() {
|
|
62
41
|
this.applyFilters({});
|
|
63
42
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Cleans up any resources or event listeners
|
|
66
|
-
*/
|
|
67
43
|
destroy() {
|
|
68
|
-
// Currently no cleanup needed, but included for future use
|
|
69
44
|
}
|
|
70
|
-
/**
|
|
71
|
-
* Gets the current serialization mode
|
|
72
|
-
* @returns The current serialization mode
|
|
73
|
-
*/
|
|
74
45
|
getSerializationMode() {
|
|
75
46
|
return this.config.serialization;
|
|
76
47
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Gets the current format (for grouped mode)
|
|
79
|
-
* @returns The current format
|
|
80
|
-
*/
|
|
81
48
|
getCurrentFormat() {
|
|
82
49
|
return this.config.format;
|
|
83
|
-
}
|
|
84
|
-
* Gets filters in a specific format (useful for sharing/export)
|
|
85
|
-
* @param format - The format to serialize to
|
|
86
|
-
* @returns Serialized filter string
|
|
87
|
-
*/
|
|
50
|
+
}
|
|
88
51
|
getFiltersAsFormat(format) {
|
|
89
52
|
const filterState = getFilterModel(this.config);
|
|
90
|
-
// Create temporary config for the requested format
|
|
91
53
|
const tempConfig = {
|
|
92
54
|
...this.config,
|
|
93
55
|
format,
|
|
94
56
|
serialization: 'grouped'
|
|
95
57
|
};
|
|
96
|
-
// Use the serialization module
|
|
97
58
|
const result = serializeGrouped(filterState, tempConfig);
|
|
98
59
|
return result.value;
|
|
99
60
|
}
|
|
100
61
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Factory function to create a new AGGridUrlSync instance
|
|
103
|
-
* @param gridApi - AG Grid API instance
|
|
104
|
-
* @param config - Optional configuration options
|
|
105
|
-
* @returns AGGridUrlSync instance
|
|
106
|
-
*/
|
|
107
62
|
export function createUrlSync(gridApi, config) {
|
|
108
63
|
return new AGGridUrlSync(gridApi, config);
|
|
109
64
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ag-grid-url-sync.js","sourceRoot":"","sources":["../../src/core/ag-grid-url-sync.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"ag-grid-url-sync.js","sourceRoot":"","sources":["../../src/core/ag-grid-url-sync.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAK7D,MAAM,OAAO,aAAa;IAChB,MAAM,CAAgB;IAO9B,YAAY,OAAgB,EAAE,MAAM,GAAwB,EAAE;QAI5D,MAAM,aAAa,GAAG,MAAM,CAAC,WAAW,CACtC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAC3C,CAAA;QAExB,IAAI,CAAC,MAAM,GAAG;YACZ,OAAO;YACP,GAAG,cAAc;YACjB,GAAG,aAAa;SACjB,CAAA;IACH,CAAC;IAOD,WAAW,CAAC,OAAgB;QAC1B,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,GAAG,CAAA;IACtC,CAAC;IAWD,WAAW,CAAC,OAAgB;QAC1B,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC/C,MAAM,GAAG,GAAG,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAA;QAC3C,OAAO;YACL,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC;YAC/C,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC;SAChD,CAAA;IACH,CAAC;IAMD,cAAc;QACZ,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;QAC9B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;QAC3B,OAAO,MAAM,CAAC,MAAM,CAAA;IACtB,CAAC;IAMD,YAAY,CAAC,GAAY;QACvB,MAAM,UAAU,GAAG,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAA;QAC9C,MAAM,WAAW,GAAG,eAAe,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QAC5D,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAA;IAChC,CAAC;IAMD,YAAY,CAAC,WAAwB;QACnC,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IAC5C,CAAC;IAKD,YAAY;QACV,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAA;IACvB,CAAC;IAKD,OAAO;IAEP,CAAC;IAMD,oBAAoB;QAClB,OAAO,IAAI,CAAC,MAAM,CAAC,aAAa,CAAA;IAClC,CAAC;IAMD,gBAAgB;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAA;IAC3B,CAAC;IAKD,kBAAkB,CAAC,MAA2B;QAC5C,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAG/C,MAAM,UAAU,GAAG;YACjB,GAAG,IAAI,CAAC,MAAM;YACd,MAAM;YACN,aAAa,EAAE,SAAkB;SAClC,CAAA;QAGD,MAAM,MAAM,GAAG,gBAAgB,CAAC,WAAW,EAAE,UAAU,CAAC,CAAA;QAExD,OAAO,MAAM,CAAC,KAAK,CAAA;IACrB,CAAC;CACF;AAQD,MAAM,UAAU,aAAa,CAC3B,OAAgB,EAChB,MAA4B;IAE5B,OAAO,IAAI,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;AAC3C,CAAC"}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { AG_GRID_OPERATION_NAMES, REVERSE_AG_GRID_OPERATION_NAMES, TEXT_FILTER_OPERATIONS, NUMBER_FILTER_OPERATIONS, DATE_FILTER_OPERATIONS } from './types.js';
|
|
2
|
-
// Map AG Grid number operations to our internal number operations
|
|
3
|
-
// Since AG Grid uses generic names, we need to be explicit about number operations
|
|
4
2
|
const AG_GRID_TO_NUMBER_OPERATION_MAP = {
|
|
5
3
|
lessThan: 'lessThan',
|
|
6
4
|
lessThanOrEqual: 'lessThanOrEqual',
|
|
@@ -12,74 +10,48 @@ const AG_GRID_TO_NUMBER_OPERATION_MAP = {
|
|
|
12
10
|
blank: 'blank',
|
|
13
11
|
notBlank: 'notBlank'
|
|
14
12
|
};
|
|
15
|
-
/**
|
|
16
|
-
* Type guard to check if a value is a valid number operation key
|
|
17
|
-
*/
|
|
18
13
|
function isValidNumberOperation(operation) {
|
|
19
14
|
return Boolean(operation && operation in AG_GRID_TO_NUMBER_OPERATION_MAP);
|
|
20
15
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Converts a date string to ISO format (YYYY-MM-DD)
|
|
23
|
-
* Handles both date strings and undefined values
|
|
24
|
-
* Returns an empty string if the input is undefined or invalid
|
|
25
|
-
*/
|
|
26
16
|
function toIsoDateString(val) {
|
|
27
17
|
if (!val)
|
|
28
18
|
return '';
|
|
29
19
|
const match = val.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
30
20
|
return match?.[1] ? match[1] : val || '';
|
|
31
21
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Type predicate to check if an operation is a valid text filter operation
|
|
34
|
-
*/
|
|
35
22
|
function isTextFilterOperation(operation) {
|
|
36
23
|
return TEXT_FILTER_OPERATIONS.includes(operation);
|
|
37
24
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Type predicate to check if an operation is a valid number filter operation
|
|
40
|
-
*/
|
|
41
25
|
function isNumberFilterOperation(operation) {
|
|
42
26
|
return NUMBER_FILTER_OPERATIONS.includes(operation);
|
|
43
27
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Type predicate to check if an operation is a valid date filter operation
|
|
46
|
-
*/
|
|
47
28
|
function isDateFilterOperation(operation) {
|
|
48
29
|
return DATE_FILTER_OPERATIONS.includes(operation);
|
|
49
30
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Detects the expected filter type for a column based on AG Grid configuration
|
|
52
|
-
*/
|
|
53
31
|
export function detectColumnFilterType(gridApi, columnId) {
|
|
54
32
|
try {
|
|
55
33
|
const column = gridApi.getColumn(columnId);
|
|
56
34
|
if (!column)
|
|
57
|
-
return 'text';
|
|
35
|
+
return 'text';
|
|
58
36
|
const colDef = column.getColDef();
|
|
59
|
-
// 1. Explicit filter configuration takes priority
|
|
60
37
|
if (colDef.filter === 'agDateColumnFilter')
|
|
61
38
|
return 'date';
|
|
62
39
|
if (colDef.filter === 'agNumberColumnFilter')
|
|
63
40
|
return 'number';
|
|
64
41
|
if (colDef.filter === 'agTextColumnFilter')
|
|
65
42
|
return 'text';
|
|
66
|
-
// 2. Cell data type configuration
|
|
67
43
|
if (colDef.cellDataType === 'date')
|
|
68
44
|
return 'date';
|
|
69
45
|
if (colDef.cellDataType === 'number')
|
|
70
46
|
return 'number';
|
|
71
47
|
if (colDef.cellDataType === 'text')
|
|
72
48
|
return 'text';
|
|
73
|
-
// 3. Default to text for backward compatibility
|
|
74
49
|
return 'text';
|
|
75
50
|
}
|
|
76
51
|
catch {
|
|
77
|
-
return 'text';
|
|
52
|
+
return 'text';
|
|
78
53
|
}
|
|
79
54
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Gets the current filter model from AG Grid
|
|
82
|
-
*/
|
|
83
55
|
export function getFilterModel(config) {
|
|
84
56
|
try {
|
|
85
57
|
const model = config.gridApi.getFilterModel();
|
|
@@ -91,28 +63,20 @@ export function getFilterModel(config) {
|
|
|
91
63
|
if (!filter || typeof filter !== 'object')
|
|
92
64
|
continue;
|
|
93
65
|
const { filterType, type, filter: value, filterTo, dateFrom, dateTo } = filter;
|
|
94
|
-
// Handle text filters
|
|
95
66
|
if (filterType === 'text') {
|
|
96
|
-
// Map AG Grid operations back to our internal operations using centralized mapping
|
|
97
67
|
const internalOperation = REVERSE_AG_GRID_OPERATION_NAMES[type];
|
|
98
|
-
// Use type predicate to validate and narrow the type
|
|
99
68
|
if (internalOperation && isTextFilterOperation(internalOperation)) {
|
|
100
69
|
filterState[column] = {
|
|
101
70
|
filterType: 'text',
|
|
102
|
-
type: internalOperation,
|
|
71
|
+
type: internalOperation,
|
|
103
72
|
filter: String(value || '')
|
|
104
73
|
};
|
|
105
74
|
}
|
|
106
75
|
}
|
|
107
|
-
// Handle number filters
|
|
108
76
|
else if (filterType === 'number') {
|
|
109
|
-
// For number filters, we need to handle the mapping carefully since
|
|
110
|
-
// AG Grid uses the same operation names for numbers and dates
|
|
111
|
-
// Use the explicit number mapping instead of the reverse AG Grid mapping
|
|
112
77
|
const internalOperation = isValidNumberOperation(type)
|
|
113
78
|
? AG_GRID_TO_NUMBER_OPERATION_MAP[type]
|
|
114
79
|
: null;
|
|
115
|
-
// If the operation type is not recognized, report the error
|
|
116
80
|
if (!internalOperation) {
|
|
117
81
|
if (config.onParseError) {
|
|
118
82
|
config.onParseError(new Error(`Unknown number filter operation '${type}' for column '${column}'. ` +
|
|
@@ -120,20 +84,16 @@ export function getFilterModel(config) {
|
|
|
120
84
|
}
|
|
121
85
|
continue;
|
|
122
86
|
}
|
|
123
|
-
// Use type predicate to validate and narrow the type
|
|
124
87
|
if (internalOperation && isNumberFilterOperation(internalOperation)) {
|
|
125
|
-
// Validate that the filter value is actually a number
|
|
126
88
|
const numericValue = typeof value === 'number' ? value : Number(value);
|
|
127
|
-
// Skip invalid number filters
|
|
128
89
|
if (isNaN(numericValue)) {
|
|
129
90
|
continue;
|
|
130
91
|
}
|
|
131
92
|
const numberFilter = {
|
|
132
93
|
filterType: 'number',
|
|
133
|
-
type: internalOperation,
|
|
94
|
+
type: internalOperation,
|
|
134
95
|
filter: numericValue
|
|
135
96
|
};
|
|
136
|
-
// Handle range operations
|
|
137
97
|
if (internalOperation === 'inRange' && typeof filterTo === 'number') {
|
|
138
98
|
;
|
|
139
99
|
numberFilter.filterTo = filterTo;
|
|
@@ -141,14 +101,9 @@ export function getFilterModel(config) {
|
|
|
141
101
|
filterState[column] = numberFilter;
|
|
142
102
|
}
|
|
143
103
|
}
|
|
144
|
-
// Handle date filters
|
|
145
104
|
else if (filterType === 'date') {
|
|
146
|
-
// Map AG Grid date operations back to our internal operations
|
|
147
105
|
const internalOperation = REVERSE_AG_GRID_OPERATION_NAMES[type];
|
|
148
|
-
// For date operations, we need to handle the special case where AG Grid uses
|
|
149
|
-
// generic operation names but we have date-specific internal names
|
|
150
106
|
let mappedOperation = internalOperation;
|
|
151
|
-
// Map AG Grid generic date operations to our date-specific operations
|
|
152
107
|
const AG_GRID_TO_DATE_OPERATION_MAP = {
|
|
153
108
|
lessThan: 'dateBefore',
|
|
154
109
|
lessThanOrEqual: 'dateBeforeOrEqual',
|
|
@@ -159,14 +114,12 @@ export function getFilterModel(config) {
|
|
|
159
114
|
mappedOperation = type
|
|
160
115
|
? AG_GRID_TO_DATE_OPERATION_MAP[type] || internalOperation
|
|
161
116
|
: internalOperation;
|
|
162
|
-
// Use type predicate to validate and narrow the type
|
|
163
117
|
if (mappedOperation && isDateFilterOperation(mappedOperation)) {
|
|
164
118
|
const dateFilter = {
|
|
165
119
|
filterType: 'date',
|
|
166
120
|
type: mappedOperation,
|
|
167
121
|
filter: toIsoDateString(dateFrom || String(value || ''))
|
|
168
122
|
};
|
|
169
|
-
// Handle date range operations (AG Grid uses dateFrom/dateTo)
|
|
170
123
|
if (mappedOperation === 'dateRange' && dateTo) {
|
|
171
124
|
;
|
|
172
125
|
dateFilter.filterTo = toIsoDateString(dateTo);
|
|
@@ -184,21 +137,16 @@ export function getFilterModel(config) {
|
|
|
184
137
|
return {};
|
|
185
138
|
}
|
|
186
139
|
}
|
|
187
|
-
/**
|
|
188
|
-
* Converts internal filter to AG Grid filter format
|
|
189
|
-
*/
|
|
190
140
|
function convertToAGGridFilter(filter) {
|
|
191
141
|
if (filter.filterType === 'date') {
|
|
192
|
-
// Handle date range operations
|
|
193
142
|
if (filter.type === 'dateRange') {
|
|
194
143
|
return {
|
|
195
144
|
filterType: 'date',
|
|
196
|
-
type: 'inRange',
|
|
145
|
+
type: 'inRange',
|
|
197
146
|
dateFrom: filter.filter,
|
|
198
147
|
dateTo: filter.filterTo || null
|
|
199
148
|
};
|
|
200
149
|
}
|
|
201
|
-
// Handle blank operations for dates
|
|
202
150
|
if (filter.type === 'blank' || filter.type === 'notBlank') {
|
|
203
151
|
return {
|
|
204
152
|
filterType: 'date',
|
|
@@ -207,7 +155,6 @@ function convertToAGGridFilter(filter) {
|
|
|
207
155
|
dateTo: null
|
|
208
156
|
};
|
|
209
157
|
}
|
|
210
|
-
// Map date-specific operations to AG Grid generic operations
|
|
211
158
|
const dateOperationMapping = {
|
|
212
159
|
dateBefore: 'lessThan',
|
|
213
160
|
dateBeforeOrEqual: 'lessThanOrEqual',
|
|
@@ -231,7 +178,6 @@ function convertToAGGridFilter(filter) {
|
|
|
231
178
|
type: agGridOperation,
|
|
232
179
|
filter: filter.filter
|
|
233
180
|
};
|
|
234
|
-
// Only include filterTo if it's defined
|
|
235
181
|
return filter.filterTo !== undefined
|
|
236
182
|
? { ...baseFilter, filterTo: filter.filterTo }
|
|
237
183
|
: baseFilter;
|
|
@@ -242,7 +188,6 @@ function convertToAGGridFilter(filter) {
|
|
|
242
188
|
filter: filter.filter
|
|
243
189
|
};
|
|
244
190
|
}
|
|
245
|
-
// Text filter handling
|
|
246
191
|
const agGridOperation = AG_GRID_OPERATION_NAMES[filter.type];
|
|
247
192
|
return {
|
|
248
193
|
filterType: 'text',
|
|
@@ -250,18 +195,13 @@ function convertToAGGridFilter(filter) {
|
|
|
250
195
|
filter: filter.filter
|
|
251
196
|
};
|
|
252
197
|
}
|
|
253
|
-
/**
|
|
254
|
-
* Applies a filter state to AG Grid
|
|
255
|
-
*/
|
|
256
198
|
export function applyFilterModel(filterState, config) {
|
|
257
199
|
try {
|
|
258
200
|
const model = {};
|
|
259
201
|
for (const [column, filter] of Object.entries(filterState)) {
|
|
260
202
|
if (!filter || typeof filter !== 'object')
|
|
261
203
|
continue;
|
|
262
|
-
// Detect expected filter type for this column
|
|
263
204
|
const expectedType = detectColumnFilterType(config.gridApi, column);
|
|
264
|
-
// Validate filter type compatibility
|
|
265
205
|
if (filter.filterType === 'date' && expectedType !== 'date') {
|
|
266
206
|
console.warn(`Column '${column}' expects a ${expectedType} filter but received a date filter. This may indicate a configuration mismatch in the column definition or filter application.`);
|
|
267
207
|
continue;
|
|
@@ -270,13 +210,11 @@ export function applyFilterModel(filterState, config) {
|
|
|
270
210
|
console.warn(`Column '${column}' expects a ${expectedType} filter but received a number filter. This may indicate a configuration mismatch in the column definition or filter application.`);
|
|
271
211
|
continue;
|
|
272
212
|
}
|
|
273
|
-
// Convert to AG Grid filter model
|
|
274
213
|
const agGridFilter = convertToAGGridFilter(filter);
|
|
275
214
|
if (agGridFilter) {
|
|
276
215
|
model[column] = agGridFilter;
|
|
277
216
|
}
|
|
278
217
|
}
|
|
279
|
-
// Apply the filter model to AG Grid
|
|
280
218
|
config.gridApi.setFilterModel(model);
|
|
281
219
|
}
|
|
282
220
|
catch (error) {
|