ag-grid-url-sync 0.5.1 → 0.5.2

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 (47) hide show
  1. package/README.md +244 -26
  2. package/dist/core/ag-grid-url-sync.d.ts +6 -0
  3. package/dist/core/ag-grid-url-sync.js.map +1 -1
  4. package/dist/core/codecs.d.ts +28 -0
  5. package/dist/core/codecs.js +39 -0
  6. package/dist/core/codecs.js.map +1 -0
  7. package/dist/core/data-issue-codes.d.ts +3 -0
  8. package/dist/core/data-issue-codes.js +10 -0
  9. package/dist/core/data-issue-codes.js.map +1 -0
  10. package/dist/core/grid-integration.d.ts +11 -1
  11. package/dist/core/grid-integration.js +20 -11
  12. package/dist/core/grid-integration.js.map +1 -1
  13. package/dist/core/grid-url-sync.d.ts +62 -0
  14. package/dist/core/grid-url-sync.js +738 -0
  15. package/dist/core/grid-url-sync.js.map +1 -0
  16. package/dist/core/index.d.ts +2 -0
  17. package/dist/core/index.js +2 -0
  18. package/dist/core/index.js.map +1 -1
  19. package/dist/core/issues.d.ts +2 -2
  20. package/dist/core/issues.js +2 -9
  21. package/dist/core/issues.js.map +1 -1
  22. package/dist/core/own-write.d.ts +138 -0
  23. package/dist/core/own-write.js +133 -0
  24. package/dist/core/own-write.js.map +1 -0
  25. package/dist/core/types.d.ts +151 -4
  26. package/dist/core/types.js +4 -0
  27. package/dist/core/types.js.map +1 -1
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +2 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/react/index.d.ts +3 -2
  32. package/dist/react/index.js +1 -0
  33. package/dist/react/index.js.map +1 -1
  34. package/dist/react/sync-holder.d.ts +22 -0
  35. package/dist/react/sync-holder.js +68 -0
  36. package/dist/react/sync-holder.js.map +1 -0
  37. package/dist/react/sync-options.d.ts +17 -0
  38. package/dist/react/sync-options.js +22 -0
  39. package/dist/react/sync-options.js.map +1 -0
  40. package/dist/react/types.d.ts +71 -3
  41. package/dist/react/use-ag-grid-url-sync.d.ts +3 -0
  42. package/dist/react/use-ag-grid-url-sync.js +310 -526
  43. package/dist/react/use-ag-grid-url-sync.js.map +1 -1
  44. package/dist/react/use-grid-url-sync.d.ts +14 -0
  45. package/dist/react/use-grid-url-sync.js +206 -0
  46. package/dist/react/use-grid-url-sync.js.map +1 -0
  47. package/package.json +16 -3
package/README.md CHANGED
@@ -145,7 +145,7 @@ The library provides two main entry points:
145
145
 
146
146
  ```typescript
147
147
  // Core functionality - works with any framework
148
- import { createUrlSync } from 'ag-grid-url-sync'
148
+ import { createGridUrlSync } from 'ag-grid-url-sync'
149
149
 
150
150
  // React hook - for React applications
151
151
  import { useAGGridUrlSync } from 'ag-grid-url-sync/react'
@@ -247,7 +247,55 @@ The config form (`getFilterModel({ ...DEFAULT_CONFIG, gridApi })`) still works,
247
247
 
248
248
  **💡 Pro Tip**: Use granular imports in libraries and applications where bundle size matters. All existing import patterns remain fully supported for backward compatibility.
249
249
 
250
- ### Basic Example
250
+ ### Vanilla JS: `createGridUrlSync`
251
+
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.
253
+
254
+ ```typescript
255
+ import { createGridUrlSync, individual, grouped } from 'ag-grid-url-sync'
256
+
257
+ const sync = createGridUrlSync({
258
+ api: gridApi,
259
+ codec: individual({ prefix: 'f_' }), // or grouped({ param: 'gf', format: 'json' })
260
+ history: 'replace', // 'none' (default) | 'replace' | 'push' | (url) => router.replace(url)
261
+ onIssue: ({ issue, source }) => {
262
+ if (issue.severity === 'warning') showToast(issue.message)
263
+ }
264
+ })
265
+
266
+ // The current state: { status, url, hasFilters, issues, views, activeViewId }
267
+ const { url, hasFilters } = sync.getSnapshot()
268
+
269
+ // Called after each change of the snapshot. Returns an unsubscribe function.
270
+ const unsubscribe = sync.subscribe(() => render(sync.getSnapshot()))
271
+
272
+ sync.applyUrl('https://app.com/data?f_name_contains=jo') // { issues }
273
+ sync.getUrl('https://app.com/share') // a share link for the grid's filters
274
+ sync.applyFilters({
275
+ name: { filterType: 'text', type: 'contains', filter: 'jo' }
276
+ }) // { issues }
277
+ sync.destroy()
278
+ ```
279
+
280
+ `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
+
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) |
293
+
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.
295
+
296
+ ### Basic Example (deprecated `createUrlSync`)
297
+
298
+ `createUrlSync` and `AGGridUrlSync` will be removed in 0.6.0. Use `createGridUrlSync`.
251
299
 
252
300
  ```typescript
253
301
  import { createUrlSync } from 'ag-grid-url-sync'
@@ -457,6 +505,9 @@ The library never writes to the console. Each problem goes to one callback, and
457
505
  | `NOT_READY` | A hook operation called before the grid is ready | `onError` (React) |
458
506
  | `VIEW_NOT_FOUND`, `VIEW_NAME_REQUIRED` | A saved view operation that cannot run | `onError` (React) |
459
507
  | `STORAGE_UNAVAILABLE`, `STORAGE_FULL`, `STORAGE_WRITE_FAILED` | Browser storage for saved views failed | `onError` (React) |
508
+ | `HISTORY_WRITE_FAILED` | The sync could not write the address bar | `onIssue` in the core, `onError` in the React hook |
509
+
510
+ Each error has a `severity`: `'warning'` for the first seven rows, `'error'` for the others. `createGridUrlSync` sends every issue to `onIssue`; the callbacks in the table are for the deprecated API and the React hook.
460
511
 
461
512
  The codes in the first seven rows are data issues: a filter that is dropped or is not valid. The library skips that filter and keeps the others. When the library knows the column, it is in `error.column`. `onParseError` is typed with `Error`, so check `instanceof URLSyncError` before you read `code` or `column`.
462
513
 
@@ -507,7 +558,7 @@ return issues.length > 0 ? (
507
558
 
508
559
  Like TanStack Query, the library can throw an issue instead of reporting it. Set `throwOnError: true` to throw every issue, or give a function that picks issues. In the core, a thrown issue does not go to `onParseError`, and the first one stops the call. In React, the hook keeps reporting each issue to `onParseError` one time, and then throws the picked issue during render, so an error boundary catches it. The default is `false`.
509
560
 
510
- The library does not change the address bar. Before an error boundary resets, remove the bad param from the URL (or the filter from the grid), or the same issue is thrown again.
561
+ Only `createGridUrlSync` or the hook with `history` changes the address bar. Before an error boundary resets, remove the bad param from the URL (or the filter from the grid), or the same issue is thrown again.
511
562
 
512
563
  ```tsx
513
564
  useAGGridUrlSync(gridApi, {
@@ -533,7 +584,7 @@ Refer to the [package.json](./package.json) for the full export map.
533
584
 
534
585
  ## React Integration
535
586
 
536
- For React applications, use the dedicated `useAGGridUrlSync` hook that provides a complete solution with React-specific features and state management.
587
+ For React applications, use the `useGridUrlSync` hook. It is a thin adapter on [`createGridUrlSync`](#vanilla-js-creategridurlsync): it takes the same options, and the grid behaves as with the vanilla sync. The older `useAGGridUrlSync` hook is deprecated and will be removed in 0.6.0 (see [Migrating from `useAGGridUrlSync`](#migrating-from-useaggridurlsync)).
537
588
 
538
589
  ### Installation for React
539
590
 
@@ -541,7 +592,90 @@ For React applications, use the dedicated `useAGGridUrlSync` hook that provides
541
592
  npm install ag-grid-url-sync
542
593
  ```
543
594
 
544
- ### Basic React Usage
595
+ The hook works with React 16.8 and later. It uses the `use-sync-external-store` package, which npm installs with the library. On React 18 and later, that package uses React's own `useSyncExternalStore`.
596
+
597
+ ### `useGridUrlSync({ api, ...options })`
598
+
599
+ ```tsx
600
+ import { useState } from 'react'
601
+ import { AgGridReact } from 'ag-grid-react'
602
+ import type { GridApi } from 'ag-grid-community'
603
+ import { useGridUrlSync } from 'ag-grid-url-sync/react'
604
+ import { individual } from 'ag-grid-url-sync'
605
+
606
+ function GridComponent() {
607
+ const [api, setApi] = useState<GridApi | null>(null)
608
+
609
+ const { status, hasFilters, issues, getUrl, applyFilters } = useGridUrlSync({
610
+ api,
611
+ codec: individual({ prefix: 'filter_' }),
612
+ history: 'push', // each filter change adds a history entry; back and forward apply the URL
613
+ onIssue: ({ issue, source }) => console.warn(source, issue.code)
614
+ })
615
+
616
+ return (
617
+ <div>
618
+ <button
619
+ disabled={status !== 'active'}
620
+ onClick={() => navigator.clipboard.writeText(getUrl())}
621
+ >
622
+ Share filters
623
+ </button>
624
+ <button disabled={!hasFilters} onClick={() => applyFilters({})}>
625
+ Clear filters
626
+ </button>
627
+ {issues.length > 0 && (
628
+ <p>The link leaves out {issues.length} filter(s)</p>
629
+ )}
630
+ <AgGridReact onGridReady={params => setApi(params.api)} {...gridProps} />
631
+ </div>
632
+ )
633
+ }
634
+ ```
635
+
636
+ The options are the [`createGridUrlSync` options](#vanilla-js-creategridurlsync) (`codec`, `maxValueLength`, `history`, `popstate`, `applyUrlOnCreate`, `throwOnError`, `onIssue`), with these changes:
637
+
638
+ | Option | Default | Description |
639
+ | -------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
640
+ | `api` | none | The AG Grid API, or `null` while the grid starts |
641
+ | `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
+ | `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 |
644
+
645
+ The hook returns the snapshot and the methods of the sync:
646
+
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 |
655
+
656
+ - **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
+ - **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
+ - **The result is stable.** It is the same object until a snapshot value changes, and the methods never change.
659
+ - **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.
661
+ - `StrictMode`, React's `<Activity>` and server rendering are supported.
662
+
663
+ ### Migrating from `useAGGridUrlSync`
664
+
665
+ | `useAGGridUrlSync` | `useGridUrlSync` |
666
+ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
667
+ | `useAGGridUrlSync(gridApi, options)` | `useGridUrlSync({ api: gridApi, ...options })` |
668
+ | `paramPrefix`, `serialization`, `groupedParam`, `format` | `codec: individual({ prefix })` or `grouped({ param, format })` |
669
+ | `autoApplyOnMount` (default `false`) | `applyUrlOnCreate` (default `true`). A URL with no filters keeps the grid's filters |
670
+ | `enabledWhenReady` | `enabled` |
671
+ | `onParseError`, `onError(error, context)` | `onIssue({ issue, source })` |
672
+ | `isReady` | `status === 'active'` |
673
+ | `currentUrl`, `shareUrl(baseUrl?)` | `url`, `getUrl(baseUrl?)` |
674
+ | `applyUrlFilters(url?)`, `clearFilters()` | `applyUrl(url?)`, `applyFilters({})` |
675
+ | `getQueryParams()` | `new URL(getUrl()).search`, while `status` is `'active'` |
676
+ | `parseUrlFilters`, `getFiltersAsFormat`, `getSerializationMode` | The module functions `parseUrlFilters` and `serializeGrouped` from `ag-grid-url-sync` |
677
+
678
+ ### Basic React Usage (deprecated `useAGGridUrlSync`)
545
679
 
546
680
  ```tsx
547
681
  import React, { useState } from 'react'
@@ -619,7 +753,7 @@ function GridComponent() {
619
753
  }
620
754
  ```
621
755
 
622
- ### React Hook API
756
+ ### React Hook API (deprecated `useAGGridUrlSync`)
623
757
 
624
758
  #### `useAGGridUrlSync(gridApi, options?)`
625
759
 
@@ -638,10 +772,35 @@ const {
638
772
  parseUrlFilters,
639
773
  applyFilters,
640
774
  getFiltersAsFormat,
641
- getCurrentFormat
775
+ getSerializationMode
642
776
  } = useAGGridUrlSync(gridApi, options)
643
777
  ```
644
778
 
779
+ The hook runs on [`createGridUrlSync`](#vanilla-js-creategridurlsync) and reads its state with `useSyncExternalStore`:
780
+
781
+ - **Options can change after mount.** A change to `paramPrefix`, `maxValueLength`, `serialization`, `groupedParam`, `format`, `history` (the mode) or `popstate` makes a new sync. The grid keeps its filters, and `currentUrl` uses the new format. The URL is not applied again. A new options object with the same values, or a new callback, makes no new sync.
782
+ - **The returned object is stable.** It is the same object across renders until a value in it changes.
783
+ - **A grid-read issue goes to `onParseError` one time** while it stays. It is reported again only after it goes away and comes back.
784
+ - **After AG Grid destroys the grid**, `isReady` is `false` and `currentUrl` is `''`, as with no grid. `applyUrlFilters`, `clearFilters`, `applyFilters`, `saveView` and `loadView` then report `NOT_READY` to `onError`. `shareUrl`, `getQueryParams` and `getFiltersAsFormat` return their fallback values.
785
+ - **The hook writes to the address bar only with `history`.** With the default `'none'`, use `currentUrl` or `shareUrl()` to update it.
786
+
787
+ #### Address bar (`history` and `popstate`)
788
+
789
+ The hook takes the `history` and `popstate` options of [`createGridUrlSync`](#vanilla-js-creategridurlsync), so a React app gets the same address-bar sync with one option:
790
+
791
+ ```tsx
792
+ // Each filter change adds a history entry, and back and forward apply the URL
793
+ useAGGridUrlSync(gridApi, { autoApplyOnMount: true, history: 'push' })
794
+
795
+ // Or give the URL to a router. A new function on each render is fine.
796
+ useAGGridUrlSync(gridApi, { history: url => router.replace(url) })
797
+ ```
798
+
799
+ - A user edit, `applyFilters`, `clearFilters` and `loadView` write the URL. `'push'` adds one entry for each of them.
800
+ - A URL apply writes nothing, because the URL holds those filters already: auto-apply on mount, `applyUrlFilters` and the back button.
801
+ - The restore of a stored view on mount replaces the entry. A new sync for an option change, or a `StrictMode` remount, adds no entry.
802
+ - `popstate` is `true` by default with `'push'`. On back or forward, the hook applies the new URL. The view is then no longer active, and the issues of that apply are in `issues`. A grid error goes to `onError` with the context `'popstate'`. A failed address-bar write is `HISTORY_WRITE_FAILED`, to `onError` with the context `'filter-change'`.
803
+
645
804
  #### Hook Options
646
805
 
647
806
  ```tsx
@@ -652,6 +811,10 @@ interface UseAGGridUrlSyncOptions {
652
811
  onParseError?: (error: Error) => void
653
812
  throwOnError?: boolean | ((issue: URLSyncError) => boolean) // Default: false
654
813
 
814
+ // Address bar (see Address bar above)
815
+ history?: 'none' | 'replace' | 'push' | ((url: string) => void) // Default: 'none'
816
+ popstate?: boolean // Default: true with 'push', else false
817
+
655
818
  // React-specific options
656
819
  autoApplyOnMount?: boolean // Default: false
657
820
  enabledWhenReady?: boolean // Default: true
@@ -664,20 +827,21 @@ interface UseAGGridUrlSyncOptions {
664
827
 
665
828
  #### Hook Return Values
666
829
 
667
- | Property | Type | Description |
668
- | -------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------- |
669
- | `shareUrl` | `(baseUrl?: string) => string` | Generate shareable URL with current filters |
670
- | `getQueryParams` | `() => string` | Get filter state as query parameters |
671
- | `applyUrlFilters` | `(url?: string) => void` | Apply filters from URL to grid |
672
- | `clearFilters` | `() => void` | Clear all text filters |
673
- | `isReady` | `boolean` | Whether grid API is available and hook is ready |
674
- | `currentUrl` | `string` | Current URL with filters applied |
675
- | `hasFilters` | `boolean` | Whether the grid has supported filters |
676
- | `issues` | `readonly URLSyncError[]` | Filters that the latest URL apply or URL write dropped (see Error Handling) |
677
- | `parseUrlFilters` | `(url: string) => FilterState` | Parse filters from URL without applying |
678
- | `applyFilters` | `(filters: FilterState) => void` | Apply filter state object to grid |
679
- | `getFiltersAsFormat` | `(format: 'querystring' \| 'json' \| 'base64') => string` | Serialize filters to any supported format (for sharing/export) |
680
- | `getCurrentFormat` | `() => 'individual' \| 'grouped'` | Get the current serialization mode (individual or grouped) |
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` |
681
845
 
682
846
  Saved views (inert unless `storageKey` is set):
683
847
 
@@ -689,10 +853,58 @@ Saved views (inert unless `storageKey` is set):
689
853
  | `loadView` | `(id: string \| null) => void` | Apply a saved view, or pass `null` to reset to unfiltered |
690
854
  | `deleteView` | `(id: string) => void` | Delete a saved view |
691
855
 
692
- > **Note:** `getFiltersAsFormat` and `getCurrentFormat` are especially useful for grouped serialization, format conversion, and advanced sharing scenarios.
856
+ > **Note:** `getFiltersAsFormat` and `getSerializationMode` are especially useful for grouped serialization, format conversion, and advanced sharing scenarios.
693
857
 
694
858
  ### Saved Views
695
859
 
860
+ The views are part of `createGridUrlSync`. The React hook uses the same code, so a vanilla app and a React app get the same rules.
861
+
862
+ #### Saved views in vanilla JS
863
+
864
+ Give the `views` option a view store. `createViewStore` is in its own entry, so an app with no saved views does not get the storage code.
865
+
866
+ ```ts
867
+ import { createGridUrlSync } from 'ag-grid-url-sync'
868
+ import { createViewStore } from 'ag-grid-url-sync/view-storage'
869
+
870
+ const sync = createGridUrlSync({
871
+ api: gridApi,
872
+ history: 'push',
873
+ views: createViewStore('orders')
874
+ })
875
+
876
+ const { views, activeViewId } = sync.getSnapshot()
877
+
878
+ const view = sync.saveView('Open orders') // GridView, or null if the save failed
879
+ sync.loadView(view.id)
880
+ sync.loadView(null) // clear the filters
881
+ sync.deleteView(view.id)
882
+ ```
883
+
884
+ You can also give your own `ViewStore`, for example one that talks to a server. With no `views` option, `views` is `[]`, `activeViewId` is `null`, and the three methods do nothing.
885
+
886
+ Rules for the vanilla API:
887
+
888
+ - **`activeViewId` is tracked.** `saveView`, `loadView` and the restore of the stored view set it. A load sets it before the grid write, so a snapshot never shows `null` while the write is still pending.
889
+ - **Any other filter model clears it.** A user edit, `applyUrl`, `applyFilters` and a back-button apply all set `activeViewId` to `null` and clear the stored pointer. A view that AG Grid changes when it applies it stays active.
890
+ - **A hand edit does not make a view active.** If the user types the filters of a view, `activeViewId` stays `null`. When two views have the same model, the active one is the one the user loaded.
891
+ - **A view load writes the URL as a user edit does.** With `history: 'push'`, the back button goes back to the URL from before the load. If the URL does not change, the sync writes no entry. Set and multi filters are kept in a view, but the URL cannot hold them. A view that differs only in those filters adds no entry.
892
+ - **A URL apply writes no history.** This holds for `applyUrl`, the apply on create and the back button.
893
+ - **A failed grid write restores `activeViewId`.** The failure goes to `onIssue` as `GRID_ERROR`.
894
+ - **Deleting is conservative.** If the deleted view is active and the grid still shows it, the grid is cleared. If the user edited the filters, they stay.
895
+ - **Failures go to `onIssue`**, not to `snapshot.issues`. They are `NOT_READY`, `GRID_ERROR`, `VIEW_NOT_FOUND`, `VIEW_NAME_REQUIRED`, `STORAGE_FULL`, `STORAGE_UNAVAILABLE` and `STORAGE_WRITE_FAILED`. `loadView` with an unknown id reads the store again, then reports `VIEW_NOT_FOUND`. After `destroy`, `saveView` and `loadView` report `NOT_READY`, and `deleteView` still deletes from the store.
896
+
897
+ On create (when `applyUrlOnCreate` is not `false`), the sync picks the starting filters in this order:
898
+
899
+ 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.
901
+ 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
+ 4. Else, the grid keeps its filters.
903
+
904
+ A stored pointer that names a missing view is ignored.
905
+
906
+ #### Saved views in React
907
+
696
908
  Pass a `storageKey` to let users name and revisit filter sets. The key both enables
697
909
  the feature and namespaces its storage, so two grids on the same origin must use
698
910
  different keys:
@@ -757,7 +969,7 @@ Behaviour worth knowing:
757
969
  `onError` with context `'save-view'`. Reads degrade to an empty list instead of
758
970
  throwing, so server rendering is safe.
759
971
 
760
- For non-React consumers the same store is available directly:
972
+ The store is also available directly:
761
973
 
762
974
  ```ts
763
975
  import { createViewStore } from 'ag-grid-url-sync/view-storage'
@@ -936,9 +1148,13 @@ Parameter structure:
936
1148
 
937
1149
  ## API Reference
938
1150
 
939
- ### `createUrlSync(gridApi, config?)`
1151
+ ### `createGridUrlSync(options)`
940
1152
 
941
- Factory function to create a new AGGridUrlSync instance.
1153
+ See [Vanilla JS: `createGridUrlSync`](#vanilla-js-creategridurlsync) for the options. It returns `{ getSnapshot, subscribe, applyUrl, applyFilters, getUrl, saveView, loadView, deleteView, destroy }`. The snapshot is `{ status, url, hasFilters, issues, views, activeViewId }`. See [Saved Views](#saved-views) for the view methods.
1154
+
1155
+ ### `createUrlSync(gridApi, config?)` (deprecated)
1156
+
1157
+ Factory function to create a new AGGridUrlSync instance. Deprecated: use `createGridUrlSync`. It will be removed in 0.6.0.
942
1158
 
943
1159
  ```typescript
944
1160
  import { createUrlSync } from 'ag-grid-url-sync'
@@ -950,7 +1166,9 @@ const urlSync = createUrlSync(gridApi, {
950
1166
  })
951
1167
  ```
952
1168
 
953
- ### `AGGridUrlSync` Class
1169
+ ### `AGGridUrlSync` Class (deprecated)
1170
+
1171
+ Deprecated: use `createGridUrlSync`. It will be removed in 0.6.0.
954
1172
 
955
1173
  #### Constructor
956
1174
 
@@ -2,6 +2,10 @@ import type { GridApi } from 'ag-grid-community';
2
2
  import type { AGGridUrlSyncConfig, FilterState, ApplyResult, SerializationFormat, SerializationMode, UrlState } from './types.js';
3
3
  /**
4
4
  * AGGridUrlSync class for synchronizing AG Grid text filters with URL parameters
5
+ *
6
+ * @deprecated Use `createGridUrlSync({ api })`: it also listens to the grid
7
+ * and can write the address bar (#221). `AGGridUrlSync` and `createUrlSync`
8
+ * will be removed in 0.6.0.
5
9
  */
6
10
  export declare class AGGridUrlSync {
7
11
  private readonly gridApi;
@@ -75,5 +79,7 @@ export declare class AGGridUrlSync {
75
79
  * @param gridApi - AG Grid API instance
76
80
  * @param config - Optional configuration options
77
81
  * @returns AGGridUrlSync instance
82
+ * @deprecated Use `createGridUrlSync({ api })` (#221). This function will be
83
+ * removed in 0.6.0.
78
84
  */
79
85
  export declare function createUrlSync(gridApi: GridApi, config?: AGGridUrlSyncConfig): AGGridUrlSync;
@@ -1 +1 @@
1
- {"version":3,"file":"ag-grid-url-sync.js","sourceRoot":"","sources":["../../src/core/ag-grid-url-sync.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ag-grid-url-sync.js","sourceRoot":"","sources":["../../src/core/ag-grid-url-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { Codec, ResolvedOptions, SerializationFormat } from './types.js';
2
+ /** The options that a codec sets */
3
+ export type CodecOptions = Pick<ResolvedOptions, 'serialization' | 'paramPrefix' | 'groupedParam' | 'format'>;
4
+ /**
5
+ * One URL param for each filter, for example `?f_name_contains=jo`
6
+ * @param options.prefix - The start of each param name. Default `'f_'`.
7
+ */
8
+ export declare function individual(options?: {
9
+ prefix?: string | undefined;
10
+ }): Codec;
11
+ /**
12
+ * All the filters in one URL param
13
+ * @param options.param - The param name. Default `'grid_filters'`.
14
+ * @param options.format - `'querystring'` (default), `'json'` or `'base64'`
15
+ */
16
+ export declare function grouped(options?: {
17
+ param?: string | undefined;
18
+ format?: SerializationFormat | undefined;
19
+ }): Codec;
20
+ /** The options of a codec. Throws for an object that no factory made. */
21
+ export declare function codecOptions(codec: Codec): CodecOptions;
22
+ /**
23
+ * A codec with all four settings, for the React hook (#222). Internal: the
24
+ * hook's old options name the params of both modes, and the URL must strip
25
+ * the names that the app set, as the class does. Not exported from the
26
+ * package.
27
+ */
28
+ export declare function codecFromOptions(options: CodecOptions): Codec;
@@ -0,0 +1,39 @@
1
+ import { DEFAULT_CONFIG } from './options.js';
2
+ const settings = new WeakMap();
3
+ function makeCodec(options) {
4
+ const codec = Object.freeze({});
5
+ settings.set(codec, options);
6
+ return codec;
7
+ }
8
+ export function individual(options = {}) {
9
+ return makeCodec({
10
+ serialization: 'individual',
11
+ paramPrefix: options.prefix ?? DEFAULT_CONFIG.paramPrefix,
12
+ groupedParam: DEFAULT_CONFIG.groupedParam,
13
+ format: DEFAULT_CONFIG.format
14
+ });
15
+ }
16
+ export function grouped(options = {}) {
17
+ return makeCodec({
18
+ serialization: 'grouped',
19
+ paramPrefix: DEFAULT_CONFIG.paramPrefix,
20
+ groupedParam: options.param ?? DEFAULT_CONFIG.groupedParam,
21
+ format: options.format ?? DEFAULT_CONFIG.format
22
+ });
23
+ }
24
+ export function codecOptions(codec) {
25
+ const options = settings.get(codec);
26
+ if (!options) {
27
+ throw new TypeError('codec must come from individual() or grouped()');
28
+ }
29
+ return options;
30
+ }
31
+ export function codecFromOptions(options) {
32
+ return makeCodec({
33
+ serialization: options.serialization,
34
+ paramPrefix: options.paramPrefix,
35
+ groupedParam: options.groupedParam,
36
+ format: options.format
37
+ });
38
+ }
39
+ //# sourceMappingURL=codecs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codecs.js","sourceRoot":"","sources":["../../src/core/codecs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAY7C,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAuB,CAAA;AAEnD,SAAS,SAAS,CAAC,OAAqB;IAGtC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAU,CAAA;IACxC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IAE5B,OAAO,KAAK,CAAA;AACd,CAAC;AAMD,MAAM,UAAU,UAAU,CACxB,OAAO,GAAoC,EAAE;IAE7C,OAAO,SAAS,CAAC;QACf,aAAa,EAAE,YAAY;QAC3B,WAAW,EAAE,OAAO,CAAC,MAAM,IAAI,cAAc,CAAC,WAAW;QACzD,YAAY,EAAE,cAAc,CAAC,YAAY;QACzC,MAAM,EAAE,cAAc,CAAC,MAAM;KAC9B,CAAC,CAAA;AACJ,CAAC;AAOD,MAAM,UAAU,OAAO,CACrB,OAAO,GAGH,EAAE;IAEN,OAAO,SAAS,CAAC;QACf,aAAa,EAAE,SAAS;QACxB,WAAW,EAAE,cAAc,CAAC,WAAW;QACvC,YAAY,EAAE,OAAO,CAAC,KAAK,IAAI,cAAc,CAAC,YAAY;QAC1D,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,cAAc,CAAC,MAAM;KAChD,CAAC,CAAA;AACJ,CAAC;AAGD,MAAM,UAAU,YAAY,CAAC,KAAY;IACvC,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAEnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAA;IACvE,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAQD,MAAM,UAAU,gBAAgB,CAAC,OAAqB;IACpD,OAAO,SAAS,CAAC;QACf,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { IssueCode } from './types.js';
2
+ /** The codes for a filter that is dropped or not valid */
3
+ export declare const DATA_ISSUE_CODES: ReadonlySet<IssueCode>;
@@ -0,0 +1,10 @@
1
+ export const DATA_ISSUE_CODES = new Set([
2
+ 'INVALID_PARAM',
3
+ 'INVALID_NUMBER',
4
+ 'INVALID_DATE',
5
+ 'VALUE_TOO_LONG',
6
+ 'INVALID_GROUPED',
7
+ 'TYPE_MISMATCH',
8
+ 'INVALID_URL'
9
+ ]);
10
+ //# sourceMappingURL=data-issue-codes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-issue-codes.js","sourceRoot":"","sources":["../../src/core/data-issue-codes.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,gBAAgB,GAA2B,IAAI,GAAG,CAAY;IACzE,eAAe;IACf,gBAAgB;IAChB,cAAc;IACd,gBAAgB;IAChB,iBAAiB;IACjB,eAAe;IACf,aAAa;CACd,CAAC,CAAA"}
@@ -1,5 +1,5 @@
1
1
  import type { AGGridUrlSyncConfig, FilterState, InternalConfig } from './types.js';
2
- import type { GridApi } from 'ag-grid-community';
2
+ import type { FilterModel, GridApi } from 'ag-grid-community';
3
3
  /**
4
4
  * Detects the expected filter type for a column based on AG Grid configuration
5
5
  */
@@ -20,6 +20,11 @@ export declare function getFilterModel(gridApi: GridApi, options?: AGGridUrlSync
20
20
  * `getFilterModel(gridApi, options)`. This form will be removed in 0.6.0.
21
21
  */
22
22
  export declare function getFilterModel(config: InternalConfig): FilterState;
23
+ /**
24
+ * Converts an AG Grid filter model to URL filters. It does not call the grid.
25
+ * Filter types that the library does not support (set, multi) are skipped.
26
+ */
27
+ export declare function fromGridModel(model: FilterModel | null | undefined, options?: AGGridUrlSyncConfig): FilterState;
23
28
  /**
24
29
  * Applies a filter state to AG Grid. A filter whose type does not match the
25
30
  * column is skipped with a warning. An error from the grid is reported
@@ -38,3 +43,8 @@ export declare function applyFilterModel(filterState: FilterState, gridApi: Grid
38
43
  * removed in 0.6.0.
39
44
  */
40
45
  export declare function applyFilterModel(filterState: FilterState, config: InternalConfig): void;
46
+ /**
47
+ * Converts URL filters to the AG Grid filter model that `applyFilterModel`
48
+ * would write. It does not call `setFilterModel`.
49
+ */
50
+ export declare function toGridModel(filterState: FilterState, gridApi: GridApi, options?: AGGridUrlSyncConfig): FilterModel;
@@ -67,6 +67,10 @@ function readGridFilters(gridApi, options) {
67
67
  reportIssue(options, toIssue(error, 'GRID_ERROR'));
68
68
  return {};
69
69
  }
70
+ return fromGridModel(model, options);
71
+ }
72
+ export function fromGridModel(model, options) {
73
+ const resolved = resolveOptions(options);
70
74
  const filters = new Map();
71
75
  if (!v.is(JsonObjectSchema, model)) {
72
76
  return {};
@@ -92,7 +96,7 @@ function readGridFilters(gridApi, options) {
92
96
  if (!operator ||
93
97
  !internalOperation ||
94
98
  !isNumberFilterOperation(internalOperation)) {
95
- reportIssue(options, new InvalidFilterError(`Unknown number filter operation '${filter.type}' for column '${column}'. ` +
99
+ reportIssue(resolved, new InvalidFilterError(`Unknown number filter operation '${filter.type}' for column '${column}'. ` +
96
100
  `Supported operations: ${NUMBER_FILTER_OPERATIONS.map(toAgGridOperation).join(', ')}`, undefined, { code: 'INVALID_PARAM', column }));
97
101
  continue;
98
102
  }
@@ -113,11 +117,11 @@ function readGridFilters(gridApi, options) {
113
117
  ? gridNumber(filter.filterTo, column)
114
118
  : undefined;
115
119
  if (numericValue instanceof InvalidFilterError) {
116
- reportIssue(options, numericValue);
120
+ reportIssue(resolved, numericValue);
117
121
  continue;
118
122
  }
119
123
  if (numericTo instanceof InvalidFilterError) {
120
- reportIssue(options, numericTo);
124
+ reportIssue(resolved, numericTo);
121
125
  continue;
122
126
  }
123
127
  if (isNaN(numericValue)) {
@@ -230,17 +234,27 @@ export function applyFilterModel(filterState, target, options) {
230
234
  writeGridFilters(filterState, target, resolveOptions(options));
231
235
  }
232
236
  function writeGridFilters(filterState, gridApi, options) {
237
+ const model = toGridModel(filterState, gridApi, options);
238
+ try {
239
+ gridApi.setFilterModel(model);
240
+ }
241
+ catch (error) {
242
+ reportIssue(options, toIssue(error, 'GRID_ERROR'));
243
+ }
244
+ }
245
+ export function toGridModel(filterState, gridApi, options) {
246
+ const resolved = resolveOptions(options);
233
247
  const model = new Map();
234
248
  for (const [column, filter] of Object.entries(filterState)) {
235
249
  if (!v.is(JsonObjectSchema, filter))
236
250
  continue;
237
251
  const expectedType = detectColumnFilterType(gridApi, column);
238
252
  if (filter.filterType === 'date' && expectedType !== 'date') {
239
- reportIssue(options, new InvalidFilterError(`Column '${column}' expects a ${expectedType} filter but received a date filter. This may indicate a configuration mismatch in the column definition or filter application.`, undefined, { code: 'TYPE_MISMATCH', column }));
253
+ reportIssue(resolved, new InvalidFilterError(`Column '${column}' expects a ${expectedType} filter but received a date filter. This may indicate a configuration mismatch in the column definition or filter application.`, undefined, { code: 'TYPE_MISMATCH', column }));
240
254
  continue;
241
255
  }
242
256
  if (filter.filterType === 'number' && expectedType !== 'number') {
243
- reportIssue(options, new InvalidFilterError(`Column '${column}' expects a ${expectedType} filter but received a number filter. This may indicate a configuration mismatch in the column definition or filter application.`, undefined, { code: 'TYPE_MISMATCH', column }));
257
+ reportIssue(resolved, new InvalidFilterError(`Column '${column}' expects a ${expectedType} filter but received a number filter. This may indicate a configuration mismatch in the column definition or filter application.`, undefined, { code: 'TYPE_MISMATCH', column }));
244
258
  continue;
245
259
  }
246
260
  const agGridFilter = convertToAGGridFilter(filter);
@@ -248,11 +262,6 @@ function writeGridFilters(filterState, gridApi, options) {
248
262
  model.set(column, agGridFilter);
249
263
  }
250
264
  }
251
- try {
252
- gridApi.setFilterModel(Object.fromEntries(model));
253
- }
254
- catch (error) {
255
- reportIssue(options, toIssue(error, 'GRID_ERROR'));
256
- }
265
+ return Object.fromEntries(model);
257
266
  }
258
267
  //# sourceMappingURL=grid-integration.js.map