ag-grid-url-sync 0.5.2 → 0.5.4

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 CHANGED
@@ -63,10 +63,31 @@ Supports **all AG Grid text, number, and date filter operations** (26 total) wit
63
63
  | **Date** | Before or Equal | `f_column_beforeEq=YYYY-MM-DD` | `f_archived_beforeEq=2024-12-31` | Date is before or equal to value |
64
64
  | **Date** | After | `f_column_after=YYYY-MM-DD` | `f_created_after=2024-01-01` | Date is after value |
65
65
  | **Date** | After or Equal | `f_column_afterEq=YYYY-MM-DD` | `f_updated_afterEq=2024-06-01` | Date is after or equal to value |
66
- | **Date** | In Range | `f_column_daterange=start,end` | `f_period_daterange=2024-01-01,2024-12-31` | Date is between start and end (inclusive) |
66
+ | **Date** | In Range | `f_column_daterange=start,end` | `f_period_daterange=2024-01-01,2024-12-31` | Date is between start and end (see below) |
67
67
  | **Date** | Blank | `f_column_blank=true` | `f_deadline_blank=true` | Date field is empty/null |
68
68
  | **Date** | Not Blank | `f_column_notBlank=true` | `f_created_notBlank=true` | Date field has any value |
69
69
 
70
+ Two date operations depend on the settings of the AG Grid column:
71
+
72
+ - **`daterange`:** AG Grid's `inRange` leaves out the start and end dates. To keep them, set `filterParams: { inRangeInclusive: true }` on the column.
73
+ - **`beforeEq` and `afterEq`:** they need the AG Grid filter options `lessThanOrEqual` and `greaterThanOrEqual`. AG Grid's default date filter does not have these options, so it uses `equals` in their place. The grid then shows other rows, and the sync reports no issue. Add the options to the column:
74
+
75
+ ```ts
76
+ filterParams: {
77
+ filterOptions: [
78
+ 'equals',
79
+ 'notEqual',
80
+ 'lessThan',
81
+ 'lessThanOrEqual',
82
+ 'greaterThan',
83
+ 'greaterThanOrEqual',
84
+ 'inRange',
85
+ 'blank',
86
+ 'notBlank'
87
+ ]
88
+ }
89
+ ```
90
+
70
91
  > **🗓️ Date Format Note**: Date filters require strict ISO format (`YYYY-MM-DD`). All date values are validated for real calendar dates, including leap years and month boundaries. Invalid or non-ISO dates will be rejected.
71
92
 
72
93
  ### Filter Detection
@@ -231,6 +252,8 @@ function createShareableUrl(filters: FilterState, baseUrl: string): string {
231
252
  }
232
253
  ```
233
254
 
255
+ `generateUrl` keeps the page's own params as they are in `baseUrl`: the same text and the same order. For example, `?debug` stays `?debug`, and `%20` stays `%20`. It removes the old filter params and puts the new ones after the page's params, in both modes. With no filters, it writes no filter param. The sync, `getUrl` and the deprecated APIs write the URL in the same way.
256
+
234
257
  #### Example: Read and Write the Grid Filters
235
258
 
236
259
  ```typescript
@@ -249,7 +272,7 @@ The config form (`getFilterModel({ ...DEFAULT_CONFIG, gridApi })`) still works,
249
272
 
250
273
  ### Vanilla JS: `createGridUrlSync`
251
274
 
252
- One call gives full sync. `createGridUrlSync` applies the address bar's filters to the grid, follows the grid's filter changes, can write them to the address bar, and removes its listeners when the grid is destroyed.
275
+ One call gives full sync. `createGridUrlSync` applies the address bar's filters to the grid, follows the grid's filter changes, can write them to the address bar, and removes its listeners when the grid is destroyed (on AG Grid 28 and 29, see the note under the options).
253
276
 
254
277
  ```typescript
255
278
  import { createGridUrlSync, individual, grouped } from 'ag-grid-url-sync'
@@ -257,7 +280,7 @@ import { createGridUrlSync, individual, grouped } from 'ag-grid-url-sync'
257
280
  const sync = createGridUrlSync({
258
281
  api: gridApi,
259
282
  codec: individual({ prefix: 'f_' }), // or grouped({ param: 'gf', format: 'json' })
260
- history: 'replace', // 'none' (default) | 'replace' | 'push' | (url) => router.replace(url)
283
+ history: 'replace', // 'none' (default) | 'replace' | 'push' | (url, { replace }) => ...
261
284
  onIssue: ({ issue, source }) => {
262
285
  if (issue.severity === 'warning') showToast(issue.message)
263
286
  }
@@ -279,19 +302,75 @@ sync.destroy()
279
302
 
280
303
  `applyFilters` writes filters to the grid as the app's own write. When they land, the sync writes the URL in the `history` mode, as it does for a user edit. `applyUrl` writes no history, because the URL already holds those filters.
281
304
 
282
- | Option | Default | Meaning |
283
- | ------------------ | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
284
- | `api` | (required) | The AG Grid API |
285
- | `codec` | `individual()` | How the filters are written in the URL. `individual({ prefix })` writes one param for each filter; `grouped({ param, format })` writes all filters in one param |
286
- | `maxValueLength` | `200` | The longest text value that is kept |
287
- | `history` | `'none'` | How a filter change goes to the address bar. `'replace'` and `'push'` use `window.history`; a function gets the URL, for a router. The page's other params, the `#hash` and `history.state` stay |
288
- | `popstate` | `true` only with `'push'` | Apply the URL when the back or forward button changes it |
289
- | `applyUrlOnCreate` | `true` | Apply the address bar's filters on create. A URL with no filter params keeps the grid's filters (for example from `initialState`), and with `history` they are written to the URL with replace |
290
- | `views` | none | The saved views. Give `createViewStore(key)` from `ag-grid-url-sync/view-storage`. See [Saved Views](#saved-views) |
291
- | `throwOnError` | `false` | Throw an issue instead of reporting it, in the methods that the app calls (`applyUrl`, `getUrl`, `applyFilters`, `saveView`, `loadView`, `deleteView`). `createGridUrlSync` and its listeners never throw |
292
- | `onIssue` | none | Gets each issue one time, as `{ issue, source }`. `source` is `'url'` (the sync read the address bar), `'grid'` (it read the grid) or `'api'` (a method that the app called) |
305
+ A URL write keeps the text and the order of the page's other params, and puts the filter params after them. With no filters, the URL has no filter param.
306
+
307
+ Two grids on one page must use different URL params. A URL write removes every param with the codec's prefix and the codec's grouped param. `grouped()` has no prefix, so it always removes the `f_` params too. Give each grid its own prefix, for example `individual({ prefix: 'a' })` and `individual({ prefix: 'b' })`.
308
+
309
+ | Option | Default | Meaning |
310
+ | ------------------ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
311
+ | `api` | (required) | The AG Grid API |
312
+ | `codec` | `individual()` | How the filters are written in the URL. `individual({ prefix })` writes one param for each filter; `grouped({ param, format })` writes all filters in one param |
313
+ | `maxValueLength` | `200` | The longest text value that is kept |
314
+ | `history` | `'none'` | How a filter change goes to the address bar. `'replace'` and `'push'` use `window.history`; a function gets the absolute URL and `{ replace }`. The page's other params, `#hash` and `history.state` stay |
315
+ | `popstate` | `true` only with `'push'` | Apply the URL when the back or forward button changes it |
316
+ | `applyUrlOnCreate` | `true` | Apply the address bar's filters on create. A URL with no filter params keeps the grid's filters (for example from `initialState`), and with `history` they are written to the URL with replace. AG Grid 31 applies `initialState` filters when the first rows come: the sync writes them at that time |
317
+ | `views` | none | The saved views. Give `createViewStore(key)` from `ag-grid-url-sync/view-storage`. See [Saved Views](#saved-views) |
318
+ | `throwOnError` | `false` | Throw an issue instead of reporting it, in the methods that the app calls (`applyUrl`, `getUrl`, `applyFilters`, `saveView`, `loadView`, `deleteView`). `createGridUrlSync` and its listeners never throw |
319
+ | `onIssue` | none | Gets each issue one time, as `{ issue, source }`. `source` is `'url'` (the sync read the address bar), `'grid'` (it read the grid) or `'api'` (a method that the app called) |
320
+
321
+ The snapshot `status` is `'active'` from create, and `'destroyed'` after `destroy()` or after the grid is destroyed. A grid that cannot take the sync's listeners also gives `'destroyed'`: `createGridUrlSync` does not throw, and sends a `GRID_ERROR` to `onIssue`. After that, `applyUrl`, `applyFilters`, `saveView` and `loadView` report `NOT_READY` to `onIssue`. `deleteView` needs only storage, so it still works. `IDLE_SNAPSHOT` (`status: 'idle'`) is the snapshot for a time when there is no sync yet. `getSnapshot()` returns the same object until something changes, so it works with React's `useSyncExternalStore`. The sync writes no history for an `applyUrl` apply, because the URL holds those filters already. `applyFilters` and `loadView` write the URL as a user edit does. Nothing reads `window` when the module loads, and a sync made with no `window` does nothing to the address bar.
322
+
323
+ **AG Grid 28 and 29:** the sync does not see a grid destroy. These versions have no `gridPreDestroyed` event and no `api.isDestroyed()`. So `status` stays `'active'`, the listeners stay, and a later `applyFilters` writes to the destroyed grid and to the URL. Call `sync.destroy()` before `api.destroy()`. In React, set the hook's `api` to `null` when you remove the grid.
324
+
325
+ An issue has the source of the work that found it. A method that the app calls gives `'api'` to each issue of its work: also the grid read after its write, a later landing of that write, and the write of the stored view pointer. A back-button apply and the restore on create give `'url'`. A filter change in the grid, and the read of the grid's own filters on create, give `'grid'`.
326
+
327
+ #### History with a router
328
+
329
+ A `history` function gets two arguments: the absolute URL (`https://host/path?...`) and `{ replace }`. `replace` tells the function what to do with the history entry:
330
+
331
+ - `true`: replace the entry. This is for a write that must not add a back-button step: the create-time write of the grid's own filters, and the restore of a stored view (on create, or for a new `storageKey`).
332
+ - `false`: add an entry. This is for a user edit, `applyFilters` and `loadView`.
333
+
334
+ For a router that can push, use the two arguments. Then the history is the same as with `'push'`:
335
+
336
+ ```typescript
337
+ createGridUrlSync({
338
+ api: gridApi,
339
+ history: (url, { replace }) =>
340
+ replace ? router.replace(url) : router.push(url)
341
+ })
342
+ ```
343
+
344
+ A function with one argument also works. It does not use `replace`, so it does the same for each write. `url => router.replace(url)` is correct. Do not use `url => router.push(url)`: it adds a back-button step on page load.
345
+
346
+ The Next.js router takes the absolute URL. React Router's `navigate` takes a path: it reads an absolute URL as a relative path, so the route does not match. Give it the path, the search and the hash:
347
+
348
+ ```typescript
349
+ createGridUrlSync({
350
+ api: gridApi,
351
+ history: (url, { replace }) => {
352
+ const { pathname, search, hash } = new URL(url)
353
+ navigate(pathname + search + hash, { replace })
354
+ }
355
+ })
356
+ ```
293
357
 
294
- The snapshot `status` is `'active'` from create, and `'destroyed'` after `destroy()` or after the grid is destroyed. After that, `applyUrl`, `applyFilters`, `saveView` and `loadView` report `NOT_READY` to `onIssue`. `deleteView` needs only storage, so it still works. `IDLE_SNAPSHOT` (`status: 'idle'`) is the snapshot for a time when there is no sync yet. `getSnapshot()` returns the same object until something changes, so it works with React's `useSyncExternalStore`. The sync writes no history for an `applyUrl` apply, because the URL holds those filters already. `applyFilters` and `loadView` write the URL as a user edit does. Nothing reads `window` when the module loads, and a sync made with no `window` does nothing to the address bar.
358
+ The sync reads the address bar only on create and on `popstate`. A router navigation that keeps the page mounted (for example a Next.js `<Link>` to the same page with other params) does not fire `popstate`, so the sync does not apply its filters. The next user edit then writes the grid's filters over them. Call `applyUrl()` when the search params change. With a `history` function, `popstate` is `false` by default, so set `popstate: true` to apply the URL on back and forward:
359
+
360
+ ```tsx
361
+ const query = useSearchParams().toString()
362
+ const { status, applyUrl } = useGridUrlSync({
363
+ api,
364
+ history: url => router.replace(url),
365
+ popstate: true
366
+ })
367
+
368
+ useEffect(() => {
369
+ if (status === 'active') applyUrl()
370
+ }, [query, status, applyUrl])
371
+ ```
372
+
373
+ `'replace'` and `'push'` keep `history.state`, but they remove the Next.js keys `__NA` and `_N` from it. Next.js skips its router update for a write with these keys. With them removed, the Next.js router (`useSearchParams`) gets the new URL. A state that is not a plain object stays as it is.
295
374
 
296
375
  ### Basic Example (deprecated `createUrlSync`)
297
376
 
@@ -305,7 +384,7 @@ const urlSync = createUrlSync(gridApi)
305
384
 
306
385
  // Generate shareable URL with current filters (text and number)
307
386
  const shareableUrl = urlSync.generateUrl()
308
- // Result: https://app.com/data?f_name_contains=john&f_salary_gte=80000&f_age_range=25,45
387
+ // Result: https://app.com/data?f_name_contains=john&f_salary_gte=80000&f_age_range=25%2C45
309
388
 
310
389
  // Apply filters from URL (automatically detects text vs number columns)
311
390
  urlSync.applyFromUrl(
@@ -368,6 +447,8 @@ interface AGGridUrlSyncConfig {
368
447
 
369
448
  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.
370
449
 
450
+ With no filters, the URL has no grouped param, in each format. A link with an empty value, such as `?grid_filters=%7B%7D` (json) or `?grid_filters=e30%3D` (base64), still opens with no filters and no issue. `getFiltersAsFormat` and `serializeGrouped` do not change: with no filters, they give `'{}'` for json, `'e30='` for base64 and `''` for querystring.
451
+
371
452
  > **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.
372
453
 
373
454
  ### Format Options
@@ -640,24 +721,25 @@ The options are the [`createGridUrlSync` options](#vanilla-js-creategridurlsync)
640
721
  | `api` | none | The AG Grid API, or `null` while the grid starts |
641
722
  | `storageKey` | none | Enables saved views, kept in `localStorage` under this key (in place of the core's `views` store). With no key, the view methods do nothing |
642
723
  | `enabled` | `true` | With `false`, the hook makes no sync: `status` is `'idle'`, and a method reports `NOT_READY`. `views` still shows what is in storage. Back to `true` starts again, as a new sync does: the URL is applied again |
643
- | `throwOnError` | `false` | As in TanStack Query: the hook throws a picked issue during render, so an error boundary catches it. The methods do not throw |
724
+ | `throwOnError` | `false` | As in TanStack Query: the hook throws a picked issue of `issues` during render, so an error boundary catches it. Failures other than `HISTORY_WRITE_FAILED` go to `onIssue` only. The methods do not throw |
644
725
 
645
726
  The hook returns the snapshot and the methods of the sync:
646
727
 
647
- | Property | Description |
648
- | ------------------------------------ | -------------------------------------------------------------------------------- |
649
- | `status` | `'idle'` (no grid, no sync yet, or a server render), `'active'` or `'destroyed'` |
650
- | `url`, `hasFilters`, `issues` | The URL with the grid's filters, whether there are filters, the latest issues |
651
- | `views`, `activeViewId` | The saved views and the active one |
652
- | `applyUrl(url?)`, `applyFilters(f)` | Apply a URL or filters. They return `{ issues }` |
653
- | `getUrl(baseUrl?)` | The URL with the grid's filters, for example to share |
654
- | `saveView`, `loadView`, `deleteView` | The saved view methods of the sync |
728
+ | Property | Description |
729
+ | ------------------------------------ | --------------------------------------------------------------------------------------------- |
730
+ | `status` | `'idle'` (no grid, no sync yet, or a server render), `'active'` or `'destroyed'` |
731
+ | `url`, `hasFilters`, `issues` | The URL with the grid's filters, whether the URL holds filters (see below), the latest issues |
732
+ | `views`, `activeViewId` | The saved views and the active one |
733
+ | `applyUrl(url?)`, `applyFilters(f)` | Apply a URL or filters. They return `{ issues }` |
734
+ | `getUrl(baseUrl?)` | The URL with the grid's filters, for example to share |
735
+ | `saveView`, `loadView`, `deleteView` | The saved view methods of the sync |
655
736
 
656
737
  - **The core's rules hold.** On mount, a URL with filters wins over the stored view, a URL with no filters keeps the grid's filters, and a URL apply writes no history.
657
738
  - **Options can change after mount.** A new codec, `maxValueLength`, history mode, `popstate` or `storageKey` makes a new sync on the same grid. It does not apply the URL again, and it keeps the active view. A new `storageKey` restores that store's view. A new codec object with the same settings, a new `onIssue` or a new `history` function makes no new sync.
658
739
  - **The result is stable.** It is the same object until a snapshot value changes, and the methods never change.
740
+ - **`hasFilters` counts only the filters that the URL holds.** With `individual()`, a combined (AND/OR) filter or a set filter is not in the URL (`UNSUPPORTED_FILTER`). So `hasFilters` is `false` while the grid shows such a filter, and a "Clear" button with `disabled={!hasFilters}` stays disabled. To clear any filter, keep the button enabled.
659
741
  - **A method with no sync** (no grid yet, `enabled: false`): `applyUrl` and `applyFilters` report `NOT_READY` to `onIssue`, and so do `saveView` and `loadView` with a `storageKey`. `getUrl` returns the base URL. `deleteView` still works with storage only.
660
- - **Issues stay across a new sync.** After an option change or a `StrictMode` remount, `issues` still shows the latest apply's issues until the next apply. `onIssue` gets each issue one time.
742
+ - **Issues stay across a new sync.** After an option change or a `StrictMode` remount, `issues` still shows the latest apply's issues until the next apply. A new `storageKey` that restores its stored view clears them, as `loadView` does. `onIssue` gets each issue one time.
661
743
  - `StrictMode`, React's `<Activity>` and server rendering are supported.
662
744
 
663
745
  ### Migrating from `useAGGridUrlSync`
@@ -793,7 +875,11 @@ The hook takes the `history` and `popstate` options of [`createGridUrlSync`](#va
793
875
  useAGGridUrlSync(gridApi, { autoApplyOnMount: true, history: 'push' })
794
876
 
795
877
  // Or give the URL to a router. A new function on each render is fine.
796
- useAGGridUrlSync(gridApi, { history: url => router.replace(url) })
878
+ // `replace` is true for a write that must not add an entry (see History with a router).
879
+ useAGGridUrlSync(gridApi, {
880
+ history: (url, { replace }) =>
881
+ replace ? router.replace(url) : router.push(url)
882
+ })
797
883
  ```
798
884
 
799
885
  - A user edit, `applyFilters`, `clearFilters` and `loadView` write the URL. `'push'` adds one entry for each of them.
@@ -812,7 +898,11 @@ interface UseAGGridUrlSyncOptions {
812
898
  throwOnError?: boolean | ((issue: URLSyncError) => boolean) // Default: false
813
899
 
814
900
  // Address bar (see Address bar above)
815
- history?: 'none' | 'replace' | 'push' | ((url: string) => void) // Default: 'none'
901
+ history?:
902
+ | 'none'
903
+ | 'replace'
904
+ | 'push'
905
+ | ((url: string, write: { replace: boolean }) => void) // Default: 'none'
816
906
  popstate?: boolean // Default: true with 'push', else false
817
907
 
818
908
  // React-specific options
@@ -827,21 +917,21 @@ interface UseAGGridUrlSyncOptions {
827
917
 
828
918
  #### Hook Return Values
829
919
 
830
- | Property | Type | Description |
831
- | ---------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
832
- | `shareUrl` | `(baseUrl?: string) => string` | Generate shareable URL with current filters |
833
- | `getQueryParams` | `() => string` | Get filter state as query parameters |
834
- | `applyUrlFilters` | `(url?: string) => void` | Apply filters from URL to grid |
835
- | `clearFilters` | `() => void` | Clear all text filters |
836
- | `isReady` | `boolean` | Whether the grid API is available and the hook is ready. `false` after AG Grid destroys the grid. |
837
- | `currentUrl` | `string` | Current URL with filters applied |
838
- | `hasFilters` | `boolean` | Whether the grid has supported filters |
839
- | `issues` | `readonly URLSyncError[]` | Filters that the latest URL apply or URL write dropped (see Error Handling) |
840
- | `parseUrlFilters` | `(url: string) => FilterState` | Parse filters from URL without applying |
841
- | `applyFilters` | `(filters: FilterState) => void` | Apply filter state object to grid |
842
- | `getFiltersAsFormat` | `(format: 'querystring' \| 'json' \| 'base64') => string` | Serialize filters to any supported format (for sharing/export) |
843
- | `getSerializationMode` | `() => 'individual' \| 'grouped'` | Get the current serialization mode (individual or grouped) |
844
- | `getCurrentFormat` | `() => 'individual' \| 'grouped'` | **Deprecated** (removed in 0.6.0): use `getSerializationMode` |
920
+ | Property | Type | Description |
921
+ | ---------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
922
+ | `shareUrl` | `(baseUrl?: string) => string` | Generate shareable URL with current filters |
923
+ | `getQueryParams` | `() => string` | Get filter state as query parameters |
924
+ | `applyUrlFilters` | `(url?: string) => void` | Apply filters from URL to grid |
925
+ | `clearFilters` | `() => void` | Clear all text filters |
926
+ | `isReady` | `boolean` | Whether the grid API is available and the hook is ready. `false` after AG Grid destroys the grid (AG Grid 30 and later). |
927
+ | `currentUrl` | `string` | Current URL with filters applied |
928
+ | `hasFilters` | `boolean` | Whether the grid has supported filters |
929
+ | `issues` | `readonly URLSyncError[]` | Filters that the latest URL apply or URL write dropped (see Error Handling) |
930
+ | `parseUrlFilters` | `(url: string) => FilterState` | Parse filters from URL without applying |
931
+ | `applyFilters` | `(filters: FilterState) => void` | Apply filter state object to grid |
932
+ | `getFiltersAsFormat` | `(format: 'querystring' \| 'json' \| 'base64') => string` | Serialize filters to any supported format (for sharing/export) |
933
+ | `getSerializationMode` | `() => 'individual' \| 'grouped'` | Get the current serialization mode (individual or grouped) |
934
+ | `getCurrentFormat` | `() => 'individual' \| 'grouped'` | **Deprecated** (removed in 0.6.0): use `getSerializationMode` |
845
935
 
846
936
  Saved views (inert unless `storageKey` is set):
847
937
 
@@ -897,7 +987,7 @@ Rules for the vanilla API:
897
987
  On create (when `applyUrlOnCreate` is not `false`), the sync picks the starting filters in this order:
898
988
 
899
989
  1. The URL holds the filters of the stored view (compared in URL form). The sync restores the view with its full model, and keeps `activeViewId` and the pointer. This is how a reload keeps a view that was loaded with `history` on.
900
- 2. The URL has at least one filter. The URL wins. `activeViewId` is `null` and the pointer is cleared. A URL with only bad params does not win.
990
+ 2. The URL has at least one filter. The URL wins. `activeViewId` is `null` and the pointer is cleared. A URL with only bad params does not win, and its bad params are not reported: `onIssue` gets nothing, and `issues` stays empty. With no `views`, the sync reports them.
901
991
  3. A stored view exists. The sync restores it. With `history` on, the URL is written with replace, so page load adds no back-button step.
902
992
  4. Else, the grid keeps its filters.
903
993
 
@@ -907,7 +997,8 @@ A stored pointer that names a missing view is ignored.
907
997
 
908
998
  Pass a `storageKey` to let users name and revisit filter sets. The key both enables
909
999
  the feature and namespaces its storage, so two grids on the same origin must use
910
- different keys:
1000
+ different keys. They must also use different URL params (see
1001
+ [Vanilla JS: `createGridUrlSync`](#vanilla-js-creategridurlsync)):
911
1002
 
912
1003
  ```tsx
913
1004
  const { views, activeViewId, saveView, loadView, deleteView } =
@@ -988,7 +1079,7 @@ store.saveView('Engineering', gridApi.getFilterModel())
988
1079
  retried on later renders
989
1080
  - **🧹 Cleanup**: Automatically cleans up resources when component unmounts
990
1081
  - **🛡️ Error Boundaries**: Graceful error handling with configurable callbacks
991
- - **📊 Filter Status**: `hasFilters` tracks whether any filters are active
1082
+ - **📊 Filter Status**: `hasFilters` tracks whether the grid has supported filters
992
1083
  - **💾 Saved Views**: `storageKey` enables naming and revisiting filter sets
993
1084
 
994
1085
  ### React with Grouped Serialization
@@ -1131,7 +1222,7 @@ The library generates clean, human-readable URLs:
1131
1222
  ```
1132
1223
  Base URL: https://app.com/page
1133
1224
  With text filters: https://app.com/page?f_name_contains=john&f_status_eq=active
1134
- With number filters: https://app.com/page?f_salary_gte=75000&f_age_range=25,45
1225
+ With number filters: https://app.com/page?f_salary_gte=75000&f_age_range=25%2C45
1135
1226
  Mixed filters: https://app.com/page?f_name_contains=john&f_salary_gte=75000&f_age_lt=50
1136
1227
  ```
1137
1228
 
@@ -1142,7 +1233,7 @@ Parameter structure:
1142
1233
  - **Text Operations**: `contains`, `eq`, `neq`, `startsWith`, `endsWith`, `notContains`, `blank`, `notBlank`
1143
1234
  - **Number Operations**: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `range`, `blank`, `notBlank`
1144
1235
  - **Date Operations**: `eq`, `neq`, `before`, `beforeEq`, `after`, `afterEq`, `daterange`, `blank`, `notBlank`
1145
- - **Range Format**: `f_column_range=min,max` (comma-separated values)
1236
+ - **Range Format**: `f_column_range=min,max` (comma-separated values). The library writes the comma as `%2C`. A link with a raw comma (`f_age_range=25,45`) also works.
1146
1237
  - Standard URL encoding for special characters
1147
1238
  - Supports column names with underscores (e.g., `user_id`, `salary_base`, `created_date`)
1148
1239
 
@@ -1,5 +1,11 @@
1
1
  import type { AGGridUrlSyncConfig, FilterState, InternalConfig } from './types.js';
2
- import type { FilterModel, GridApi } from 'ag-grid-community';
2
+ import type { GridApi } from 'ag-grid-community';
3
+ /**
4
+ * The grid's filter model, as `api.getFilterModel()` gives it. This is AG
5
+ * Grid's `FilterModel`, which AG Grid 28 to 30 do not export (#300). The
6
+ * package does not export this type.
7
+ */
8
+ export type FilterModel = ReturnType<GridApi['getFilterModel']>;
3
9
  /**
4
10
  * Detects the expected filter type for a column based on AG Grid configuration
5
11
  */
@@ -28,12 +28,16 @@ function toIsoDateString(val) {
28
28
  const match = val.match(/^(\d{4}-\d{2}-\d{2})/);
29
29
  return match?.[1] ? match[1] : val || '';
30
30
  }
31
+ function findColumnDef(gridApi, columnId) {
32
+ if (gridApi.getColumn === undefined)
33
+ return gridApi.getColumnDef(columnId);
34
+ return gridApi.getColumn(columnId)?.getColDef() ?? null;
35
+ }
31
36
  export function detectColumnFilterType(gridApi, columnId) {
32
37
  try {
33
- const column = gridApi.getColumn(columnId);
34
- if (!column)
38
+ const colDef = findColumnDef(gridApi, columnId);
39
+ if (!colDef)
35
40
  return 'text';
36
- const colDef = column.getColDef();
37
41
  if (colDef.filter === 'agDateColumnFilter')
38
42
  return 'date';
39
43
  if (colDef.filter === 'agNumberColumnFilter')
@@ -1 +1 @@
1
- {"version":3,"file":"grid-integration.js","sourceRoot":"","sources":["../../src/core/grid-integration.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grid-integration.js","sourceRoot":"","sources":["../../src/core/grid-integration.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,11 @@ import { URLSyncError } from './types.js';
3
3
  import type { OwnWrite } from './own-write.js';
4
4
  /** The snapshot when there is no sync yet, for the React hook (#222) */
5
5
  export declare const IDLE_SNAPSHOT: GridUrlSyncSnapshot;
6
+ /**
7
+ * Two issue lists with the same code, column and message in each place. Not
8
+ * public: the React hook's holder uses it too (#311).
9
+ */
10
+ export declare function sameIssues(a: readonly URLSyncError[], b: readonly URLSyncError[]): boolean;
6
11
  /**
7
12
  * Makes a sync between one grid and the URL. It applies the address bar's
8
13
  * filters to the grid (a URL with no filters keeps the grid's filters), then
@@ -26,6 +31,17 @@ export interface Handover {
26
31
  readonly keys: string;
27
32
  /** The issues of the latest apply (#271 review) */
28
33
  readonly applyIssues: readonly URLSyncError[];
34
+ /**
35
+ * The issues of the latest grid read, which onIssue got (#305). The next
36
+ * sync does not report the same ones again. It reports a new one.
37
+ */
38
+ readonly readIssues: readonly URLSyncError[];
39
+ /**
40
+ * The key of the GRID_ERROR that onIssue got when the grid could not take
41
+ * the old sync's listeners, or null (#286). The next sync does not report
42
+ * the same failure again. A different failure is reported.
43
+ */
44
+ readonly listenerFailure: string | null;
29
45
  }
30
46
  /**
31
47
  * Not public: the state of a sync that the next sync on the same grid can
@@ -54,6 +70,19 @@ export interface SyncStart {
54
70
  * replaces them.
55
71
  */
56
72
  readonly keepApplyIssues?: boolean | undefined;
73
+ /**
74
+ * The kept view comes from a new view store (a new storageKey in the
75
+ * hook). Its restore replaces what the grid showed, so it clears the
76
+ * apply issues, as loadView does (#285). The same store's kept view
77
+ * keeps them: the grid shows what it showed.
78
+ */
79
+ readonly newStore?: boolean | undefined;
80
+ /**
81
+ * The deprecated hook: a grid read and a pointer clear report with source
82
+ * 'grid', whatever started the work, as before #313. The hook gives them
83
+ * the 0.5.1 contexts by that source.
84
+ */
85
+ readonly gridSourceForGridWork?: boolean | undefined;
57
86
  }
58
87
  /**
59
88
  * Not public: createGridUrlSync, and the React hook's sync (#270), which