ag-grid-url-sync 0.5.0 → 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 (83) hide show
  1. package/README.md +339 -66
  2. package/dist/core/ag-grid-url-sync.d.ts +16 -9
  3. package/dist/core/ag-grid-url-sync.js +23 -19
  4. package/dist/core/ag-grid-url-sync.js.map +1 -1
  5. package/dist/core/codecs.d.ts +28 -0
  6. package/dist/core/codecs.js +39 -0
  7. package/dist/core/codecs.js.map +1 -0
  8. package/dist/core/data-issue-codes.d.ts +3 -0
  9. package/dist/core/data-issue-codes.js +10 -0
  10. package/dist/core/data-issue-codes.js.map +1 -0
  11. package/dist/core/grid-integration.d.ts +38 -2
  12. package/dist/core/grid-integration.js +169 -128
  13. package/dist/core/grid-integration.js.map +1 -1
  14. package/dist/core/grid-url-sync.d.ts +62 -0
  15. package/dist/core/grid-url-sync.js +738 -0
  16. package/dist/core/grid-url-sync.js.map +1 -0
  17. package/dist/core/index.d.ts +4 -1
  18. package/dist/core/index.js +4 -1
  19. package/dist/core/index.js.map +1 -1
  20. package/dist/core/individual-params.d.ts +12 -5
  21. package/dist/core/individual-params.js +38 -62
  22. package/dist/core/individual-params.js.map +1 -1
  23. package/dist/core/issue-columns.d.ts +2 -0
  24. package/dist/core/issue-columns.js +11 -0
  25. package/dist/core/issue-columns.js.map +1 -0
  26. package/dist/core/issues.d.ts +34 -0
  27. package/dist/core/issues.js +51 -0
  28. package/dist/core/issues.js.map +1 -0
  29. package/dist/core/operators.d.ts +189 -0
  30. package/dist/core/operators.js +60 -0
  31. package/dist/core/operators.js.map +1 -0
  32. package/dist/core/options.d.ts +21 -0
  33. package/dist/core/options.js +21 -0
  34. package/dist/core/options.js.map +1 -0
  35. package/dist/core/own-write.d.ts +138 -0
  36. package/dist/core/own-write.js +133 -0
  37. package/dist/core/own-write.js.map +1 -0
  38. package/dist/core/schemas.d.ts +51 -0
  39. package/dist/core/schemas.js +59 -0
  40. package/dist/core/schemas.js.map +1 -0
  41. package/dist/core/serialization/formats.d.ts +7 -7
  42. package/dist/core/serialization/formats.js +34 -28
  43. package/dist/core/serialization/formats.js.map +1 -1
  44. package/dist/core/serialization/grouped.d.ts +9 -12
  45. package/dist/core/serialization/grouped.js +24 -17
  46. package/dist/core/serialization/grouped.js.map +1 -1
  47. package/dist/core/to-error.d.ts +5 -0
  48. package/dist/core/to-error.js +4 -0
  49. package/dist/core/to-error.js.map +1 -0
  50. package/dist/core/types.d.ts +296 -43
  51. package/dist/core/types.js +39 -108
  52. package/dist/core/types.js.map +1 -1
  53. package/dist/core/url-generator.d.ts +4 -4
  54. package/dist/core/url-generator.js +9 -7
  55. package/dist/core/url-generator.js.map +1 -1
  56. package/dist/core/url-parser.d.ts +4 -4
  57. package/dist/core/url-parser.js +6 -4
  58. package/dist/core/url-parser.js.map +1 -1
  59. package/dist/core/validation.d.ts +21 -14
  60. package/dist/core/validation.js +58 -54
  61. package/dist/core/validation.js.map +1 -1
  62. package/dist/core/view-storage.js +21 -19
  63. package/dist/core/view-storage.js.map +1 -1
  64. package/dist/index.d.ts +4 -1
  65. package/dist/index.js +4 -1
  66. package/dist/index.js.map +1 -1
  67. package/dist/react/index.d.ts +3 -2
  68. package/dist/react/index.js +1 -0
  69. package/dist/react/index.js.map +1 -1
  70. package/dist/react/sync-holder.d.ts +22 -0
  71. package/dist/react/sync-holder.js +68 -0
  72. package/dist/react/sync-holder.js.map +1 -0
  73. package/dist/react/sync-options.d.ts +17 -0
  74. package/dist/react/sync-options.js +22 -0
  75. package/dist/react/sync-options.js.map +1 -0
  76. package/dist/react/types.d.ts +101 -14
  77. package/dist/react/use-ag-grid-url-sync.d.ts +3 -0
  78. package/dist/react/use-ag-grid-url-sync.js +344 -499
  79. package/dist/react/use-ag-grid-url-sync.js.map +1 -1
  80. package/dist/react/use-grid-url-sync.d.ts +14 -0
  81. package/dist/react/use-grid-url-sync.js +206 -0
  82. package/dist/react/use-grid-url-sync.js.map +1 -0
  83. package/package.json +35 -15
@@ -1,588 +1,425 @@
1
1
  import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
2
- import { AGGridUrlSync } from '../core/ag-grid-url-sync.js';
2
+ import { useSyncExternalStore } from 'use-sync-external-store/shim';
3
+ import { createSync, handOver, IDLE_SNAPSHOT } from '../core/grid-url-sync.js';
3
4
  import { parseUrlFilters as parseFilters } from '../core/url-parser.js';
5
+ import { generateUrl } from '../core/url-generator.js';
6
+ import { getFilterModel } from '../core/grid-integration.js';
7
+ import { serializeGrouped } from '../core/serialization/grouped.js';
4
8
  import { createViewStore } from '../core/view-storage.js';
5
- import { DEFAULT_CONFIG } from '../core/validation.js';
6
- function sameViews(a, b) {
7
- return a.length === b.length && JSON.stringify(a) === JSON.stringify(b);
9
+ import { DEFAULT_CONFIG, resolveOptions } from '../core/options.js';
10
+ import { toError } from '../core/to-error.js';
11
+ import { isDataIssue, shouldThrow } from '../core/issues.js';
12
+ import { URLSyncError } from '../core/types.js';
13
+ import { createSyncHolder } from './sync-holder.js';
14
+ import { gridUrlSyncOptions } from './sync-options.js';
15
+ const NO_ISSUES = [];
16
+ const getServerSnapshot = () => IDLE_SNAPSHOT;
17
+ const WRITE_CODES = new Set([
18
+ 'STORAGE_WRITE_FAILED',
19
+ 'STORAGE_FULL',
20
+ 'STORAGE_UNAVAILABLE',
21
+ 'HISTORY_WRITE_FAILED'
22
+ ]);
23
+ const LOAD_STOP_CODES = new Set([
24
+ 'VIEW_NOT_FOUND',
25
+ 'GRID_ERROR'
26
+ ]);
27
+ function newSession(grid, storageKey) {
28
+ return {
29
+ grid,
30
+ storageKey,
31
+ autoApplied: false,
32
+ urlApplied: false,
33
+ activeViewId: null,
34
+ applyIssues: NO_ISSUES,
35
+ handover: null
36
+ };
8
37
  }
9
- function sameFilterModel(a, b) {
10
- const keys = Object.keys(a);
11
- return (keys.length === Object.keys(b).length &&
12
- keys.every(key => JSON.stringify(a[key]) === JSON.stringify(b[key])));
38
+ function dataIssues(issues) {
39
+ const data = issues.filter(isDataIssue);
40
+ return data.length ? data : NO_ISSUES;
13
41
  }
14
- function applicableModel(gridApi, model) {
15
- if (typeof gridApi.getColumn !== 'function')
16
- return model;
17
- return Object.fromEntries(Object.entries(model).filter(([colId]) => gridApi.getColumn(colId)?.isFilterAllowed() ?? false));
42
+ function pageGivesFilters(options) {
43
+ try {
44
+ const filters = parseFilters(pageUrl(), {
45
+ ...options,
46
+ onParseError: () => { }
47
+ });
48
+ return Object.keys(filters).length > 0;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
18
53
  }
19
- function couldBeLanding(live, expected, before) {
20
- return Object.keys(live).every(colId => Object.hasOwn(expected, colId) ||
21
- (before !== null &&
22
- Object.hasOwn(before, colId) &&
23
- JSON.stringify(live[colId]) === JSON.stringify(before[colId])));
54
+ function urlWithFilters(api, base, options) {
55
+ return generateUrl(base, getFilterModel(api, options), options);
56
+ }
57
+ function filtersAsFormat(api, format, options) {
58
+ const filters = getFilterModel(api, options);
59
+ return serializeGrouped(filters, {
60
+ ...options,
61
+ format,
62
+ serialization: 'grouped'
63
+ }).value;
64
+ }
65
+ function hasWindow() {
66
+ return typeof window !== 'undefined';
67
+ }
68
+ function pageUrl() {
69
+ return hasWindow() ? window.location.href : '';
24
70
  }
25
71
  export function useAGGridUrlSync(gridApi, options = {}) {
26
- const { autoApplyOnMount = false, enabledWhenReady = true, onError, storageKey, ...coreOptions } = options;
27
- const [isReady, setIsReady] = useState(false);
28
- const [currentUrl, setCurrentUrl] = useState('');
29
- const [hasFilters, setHasFilters] = useState(false);
72
+ const { autoApplyOnMount = false, enabledWhenReady = true, onError, storageKey, history = 'none', popstate, throwOnError, ...coreOptions } = options;
73
+ const resolved = resolveOptions(coreOptions);
74
+ const { paramPrefix, maxValueLength, serialization, groupedParam, format } = resolved;
75
+ const urlOptions = useMemo(() => ({
76
+ paramPrefix,
77
+ maxValueLength,
78
+ serialization,
79
+ groupedParam,
80
+ format
81
+ }), [paramPrefix, maxValueLength, serialization, groupedParam, format]);
82
+ const [holder] = useState(createSyncHolder);
83
+ const snapshot = useSyncExternalStore(holder.subscribe, holder.getSnapshot, getServerSnapshot);
84
+ const isReady = snapshot.status === 'active';
85
+ const currentUrl = isReady ? snapshot.url : '';
86
+ const hasFilters = isReady && snapshot.hasFilters;
30
87
  const viewStore = useMemo(() => (storageKey ? createViewStore(storageKey) : null), [storageKey]);
31
- const [views, setViews] = useState([]);
32
- const [activeViewId, setActiveViewId] = useState(null);
33
- const activeViewIdRef = useRef(null);
34
- const appliedModelRef = useRef(null);
35
- const pendingWriteRef = useRef(null);
36
- const writingRef = useRef(false);
37
- const commitActiveViewId = useCallback((id) => {
38
- activeViewIdRef.current = id;
39
- setActiveViewId(id);
40
- if (id === null) {
41
- appliedModelRef.current = null;
42
- pendingWriteRef.current = null;
43
- }
44
- }, []);
45
- const applyModelToGrid = useCallback((api, model) => {
46
- let before = null;
47
- try {
48
- before = api.getFilterModel() ?? {};
49
- }
50
- catch {
51
- }
52
- pendingWriteRef.current = {
53
- before,
54
- expected: applicableModel(api, model)
88
+ const callbacksRef = useRef({
89
+ onError,
90
+ onParseError: coreOptions.onParseError,
91
+ history
92
+ });
93
+ useEffect(() => {
94
+ callbacksRef.current = {
95
+ onError,
96
+ onParseError: coreOptions.onParseError,
97
+ history
55
98
  };
56
- writingRef.current = true;
57
- try {
58
- api.setFilterModel(model);
59
- }
60
- catch (error) {
61
- pendingWriteRef.current = null;
62
- throw error;
63
- }
64
- finally {
65
- writingRef.current = false;
66
- }
67
- if (!pendingWriteRef.current)
68
- return;
69
- let live = null;
70
- try {
71
- live = api.getFilterModel() ?? {};
72
- }
73
- catch {
99
+ });
100
+ const historyKind = history instanceof Function ? 'function' : history;
101
+ const followsPopstate = popstate ?? historyKind === 'push';
102
+ const addressBar = useMemo(() => ({
103
+ history: historyKind === 'function'
104
+ ? url => {
105
+ const write = callbacksRef.current.history;
106
+ if (write instanceof Function)
107
+ write(url);
108
+ }
109
+ : historyKind,
110
+ popstate: followsPopstate
111
+ }), [historyKind, followsPopstate]);
112
+ const contextRef = useRef('initialization');
113
+ const sessionRef = useRef(newSession(null, undefined));
114
+ const liveSyncRef = useRef(null);
115
+ const loadStoppedRef = useRef(false);
116
+ const reportFailure = useCallback((cause, context) => {
117
+ const error = toError(cause);
118
+ const callbacks = callbacksRef.current;
119
+ if (isDataIssue(error)) {
120
+ callbacks.onParseError?.(error);
74
121
  }
75
- if (live && (!before || !sameFilterModel(live, before))) {
76
- pendingWriteRef.current = null;
77
- appliedModelRef.current = live;
122
+ else {
123
+ callbacks.onError?.(error, context);
78
124
  }
79
125
  }, []);
80
- const writeOverPendingView = useCallback((write) => {
81
- const pending = pendingWriteRef.current;
82
- if (!pending || !gridApi) {
83
- write();
126
+ const reportNotReady = useCallback((operation, context) => {
127
+ reportFailure(new URLSyncError(`${operation} called while the hook is not ready.`, undefined, { code: 'NOT_READY' }), context);
128
+ }, [reportFailure]);
129
+ const dropSync = useCallback((current) => {
130
+ sessionRef.current.activeViewId = current.sync.getSnapshot().activeViewId;
131
+ sessionRef.current.handover = handOver(current.sync);
132
+ current.stopPopstate();
133
+ current.sync.destroy();
134
+ holder.set(null);
135
+ liveSyncRef.current = null;
136
+ }, [holder]);
137
+ const activeSync = useCallback(() => {
138
+ const sync = holder.get();
139
+ return sync?.getSnapshot().status === 'active' ? sync : null;
140
+ }, [holder]);
141
+ const keepApplyIssues = useCallback((issues) => {
142
+ sessionRef.current.applyIssues = dataIssues(issues);
143
+ holder.setCarriedIssues(NO_ISSUES);
144
+ }, [holder]);
145
+ const loadAndKeep = useCallback((sync, id) => {
146
+ loadStoppedRef.current = false;
147
+ sync.loadView(id);
148
+ if (!loadStoppedRef.current)
149
+ keepApplyIssues(NO_ISSUES);
150
+ }, [keepApplyIssues]);
151
+ useEffect(() => {
152
+ const live = gridApi && enabledWhenReady ? gridApi : null;
153
+ const current = liveSyncRef.current;
154
+ const newGrid = live !== sessionRef.current.grid;
155
+ if (newGrid) {
156
+ if (current)
157
+ dropSync(current);
158
+ sessionRef.current = newSession(live, storageKey);
159
+ }
160
+ const session = sessionRef.current;
161
+ const sameKey = session.storageKey === storageKey;
162
+ const autoApply = autoApplyOnMount &&
163
+ (!session.autoApplied || (!sameKey && viewStore !== null));
164
+ if (current &&
165
+ !newGrid &&
166
+ current.urlOptions === urlOptions &&
167
+ current.addressBar === addressBar &&
168
+ current.viewStore === viewStore &&
169
+ !autoApply) {
84
170
  return;
85
171
  }
86
- const applied = appliedModelRef.current;
87
- const activeId = activeViewIdRef.current;
88
- let before = null;
89
- try {
90
- before = gridApi.getFilterModel() ?? {};
91
- }
92
- catch {
172
+ if (current && !newGrid)
173
+ dropSync(current);
174
+ if (!live) {
175
+ holder.setIdleViews(viewStore?.listViews() ?? []);
176
+ return;
93
177
  }
94
- pendingWriteRef.current = null;
95
- appliedModelRef.current = pending.expected;
96
- const reinstate = () => {
97
- if (pendingWriteRef.current || activeViewIdRef.current !== activeId) {
98
- return;
178
+ const kept = sameKey ? session.activeViewId : null;
179
+ session.storageKey = storageKey;
180
+ session.activeViewId = null;
181
+ if (!sameKey && viewStore)
182
+ session.autoApplied = false;
183
+ const landsOnCreate = autoApply && viewStore !== null && !session.urlApplied;
184
+ if (autoApply)
185
+ session.autoApplied = true;
186
+ if (landsOnCreate)
187
+ session.urlApplied = true;
188
+ let urlIssues = [];
189
+ let urlPhase = 'create';
190
+ const keptView = autoApply ? null : kept;
191
+ const createContext = keptView === null ? 'auto-apply-filters' : 'initialization';
192
+ const onIssue = ({ issue, source }) => {
193
+ if (source === 'api' && LOAD_STOP_CODES.has(issue.code)) {
194
+ loadStoppedRef.current = true;
195
+ }
196
+ if (source === 'url' && urlPhase !== 'live' && isDataIssue(issue)) {
197
+ urlIssues.push(issue);
99
198
  }
100
- pendingWriteRef.current = pending;
101
- appliedModelRef.current = applied;
199
+ const urlContext = urlPhase === 'popstate'
200
+ ? 'popstate'
201
+ : issue.code === 'HISTORY_WRITE_FAILED'
202
+ ? 'filter-change'
203
+ : createContext;
204
+ reportFailure(issue, source === 'grid'
205
+ ? WRITE_CODES.has(issue.code)
206
+ ? 'filter-change'
207
+ : 'update-state'
208
+ : source === 'url'
209
+ ? urlContext
210
+ : contextRef.current);
211
+ };
212
+ const beforePopstate = () => {
213
+ urlPhase = 'popstate';
214
+ urlIssues = [];
215
+ };
216
+ const afterPopstate = () => {
217
+ urlPhase = 'live';
218
+ keepApplyIssues(urlIssues);
102
219
  };
220
+ const listens = addressBar.popstate && hasWindow();
221
+ if (listens)
222
+ window.addEventListener('popstate', beforePopstate);
223
+ const stopPopstate = () => {
224
+ if (!listens)
225
+ return;
226
+ window.removeEventListener('popstate', beforePopstate);
227
+ window.removeEventListener('popstate', afterPopstate);
228
+ };
229
+ let sync;
230
+ contextRef.current = 'initialization';
231
+ holder.setCarriedIssues(session.applyIssues);
232
+ const { handover } = session;
233
+ session.handover = null;
103
234
  try {
104
- write();
235
+ sync = createSync(gridUrlSyncOptions(live, urlOptions, onIssue, viewStore ?? undefined, landsOnCreate, addressBar), { keptViewId: keptView, handover });
105
236
  }
106
237
  catch (error) {
107
- reinstate();
108
- throw error;
109
- }
110
- if (!before)
238
+ stopPopstate();
239
+ reportFailure(error, 'initialization');
111
240
  return;
112
- try {
113
- const live = gridApi.getFilterModel() ?? {};
114
- if (sameFilterModel(live, before))
115
- reinstate();
116
241
  }
117
- catch {
242
+ if (sync.getSnapshot().status !== 'active') {
243
+ stopPopstate();
118
244
  }
119
- }, [gridApi]);
120
- const syncViewsFromStore = useCallback(() => {
121
- const nextViews = viewStore ? viewStore.listViews() : [];
122
- setViews(prev => (sameViews(prev, nextViews) ? prev : nextViews));
123
- }, [viewStore]);
124
- const urlSyncRef = useRef(null);
125
- const autoAppliedRef = useRef(false);
126
- const urlAppliedRef = useRef(false);
127
- const lastGridApiRef = useRef(null);
128
- useEffect(() => {
129
- syncViewsFromStore();
130
- commitActiveViewId(null);
131
- if (viewStore) {
132
- autoAppliedRef.current = false;
245
+ else if (listens) {
246
+ window.addEventListener('popstate', afterPopstate);
133
247
  }
134
- }, [syncViewsFromStore, viewStore, commitActiveViewId]);
135
- const handleError = useCallback((error, context) => {
136
- const errorObj = error instanceof Error ? error : new Error(String(error));
137
- if (onError) {
138
- onError(errorObj, context);
139
- }
140
- else if (process.env.NODE_ENV === 'development') {
141
- console.error(`AG Grid URL Sync Error [${context}]:`, errorObj);
142
- }
143
- }, [onError]);
144
- useEffect(() => {
145
- if (gridApi && enabledWhenReady) {
146
- if (lastGridApiRef.current && lastGridApiRef.current !== gridApi) {
147
- urlSyncRef.current?.destroy();
148
- urlSyncRef.current = null;
149
- autoAppliedRef.current = false;
150
- urlAppliedRef.current = false;
151
- commitActiveViewId(null);
152
- }
153
- if (!urlSyncRef.current) {
154
- try {
155
- urlSyncRef.current = new AGGridUrlSync(gridApi, coreOptions);
156
- setIsReady(true);
157
- lastGridApiRef.current = gridApi;
158
- }
159
- catch (error) {
160
- handleError(error, 'initialization');
161
- setIsReady(false);
162
- }
163
- }
164
- }
165
- else {
166
- if (urlSyncRef.current) {
167
- urlSyncRef.current.destroy();
168
- urlSyncRef.current = null;
169
- autoAppliedRef.current = false;
170
- urlAppliedRef.current = false;
171
- commitActiveViewId(null);
248
+ holder.set(sync);
249
+ liveSyncRef.current = {
250
+ sync,
251
+ urlOptions,
252
+ addressBar,
253
+ viewStore,
254
+ stopPopstate
255
+ };
256
+ if (sync.getSnapshot().status === 'active') {
257
+ if (autoApply)
258
+ contextRef.current = 'auto-apply-filters';
259
+ if (landsOnCreate &&
260
+ (sync.getSnapshot().activeViewId !== null ||
261
+ pageGivesFilters(urlOptions))) {
262
+ keepApplyIssues(urlIssues);
172
263
  }
173
- setIsReady(false);
174
- lastGridApiRef.current = null;
175
- }
176
- }, [gridApi, enabledWhenReady, coreOptions, handleError, commitActiveViewId]);
177
- const urlHasFilterParams = useCallback(() => {
178
- if (typeof window === 'undefined')
179
- return false;
180
- const rawPrefix = coreOptions.paramPrefix || DEFAULT_CONFIG.paramPrefix;
181
- const prefix = rawPrefix.endsWith('_') ? rawPrefix : `${rawPrefix}_`;
182
- const groupedParam = coreOptions.groupedParam ?? DEFAULT_CONFIG.groupedParam;
183
- const search = window.location.search;
184
- const params = new URLSearchParams(search);
185
- let hasFilterParam = false;
186
- for (const key of params.keys()) {
187
- if (key.startsWith(prefix) || key === groupedParam) {
188
- hasFilterParam = true;
189
- break;
264
+ else if (autoApply && !viewStore) {
265
+ keepApplyIssues(sync.applyUrl().issues);
190
266
  }
191
- }
192
- if (!hasFilterParam)
193
- return false;
194
- const probeConfig = {
195
- paramPrefix: coreOptions.paramPrefix ?? DEFAULT_CONFIG.paramPrefix,
196
- maxValueLength: coreOptions.maxValueLength ?? DEFAULT_CONFIG.maxValueLength,
197
- onParseError: () => { },
198
- serialization: coreOptions.serialization ?? DEFAULT_CONFIG.serialization,
199
- groupedParam: coreOptions.groupedParam ?? DEFAULT_CONFIG.groupedParam,
200
- format: coreOptions.format ?? DEFAULT_CONFIG.format
201
- };
202
- try {
203
- return Object.keys(parseFilters(search, probeConfig)).length > 0;
204
- }
205
- catch {
206
- return false;
207
- }
208
- }, [coreOptions]);
209
- useEffect(() => {
210
- if (isReady &&
211
- autoApplyOnMount &&
212
- !autoAppliedRef.current &&
213
- urlSyncRef.current) {
214
- autoAppliedRef.current = true;
215
- try {
216
- if (!viewStore ||
217
- !gridApi ||
218
- (!urlAppliedRef.current && urlHasFilterParams())) {
219
- urlSyncRef.current.applyFromUrl();
220
- if (viewStore && gridApi) {
221
- urlAppliedRef.current = true;
222
- }
223
- commitActiveViewId(null);
224
- try {
225
- viewStore?.persistActiveViewId(null);
226
- }
227
- catch (error) {
228
- handleError(error, 'auto-apply-filters');
229
- }
230
- return;
231
- }
232
- const storedId = viewStore.getActiveViewId();
233
- const stored = storedId
234
- ? viewStore.listViews().find(view => view.id === storedId)
235
- : undefined;
236
- if (stored) {
237
- try {
238
- applyModelToGrid(gridApi, stored.filterModel);
239
- commitActiveViewId(stored.id);
240
- }
241
- catch (error) {
242
- handleError(error, 'auto-apply-filters');
243
- }
267
+ else if (autoApply && viewStore && !landsOnCreate) {
268
+ const pointer = viewStore.getActiveViewId();
269
+ if (sync.getSnapshot().views.some(view => view.id === pointer)) {
270
+ loadAndKeep(sync, pointer);
244
271
  }
245
272
  }
246
- catch (error) {
247
- handleError(error, 'auto-apply-filters');
248
- coreOptions.onParseError?.(error);
249
- }
250
273
  }
274
+ urlPhase = 'live';
251
275
  }, [
252
- isReady,
253
- autoApplyOnMount,
254
- coreOptions,
255
- handleError,
256
- urlHasFilterParams,
257
- viewStore,
258
276
  gridApi,
259
- commitActiveViewId,
260
- applyModelToGrid
277
+ enabledWhenReady,
278
+ urlOptions,
279
+ addressBar,
280
+ viewStore,
281
+ storageKey,
282
+ autoApplyOnMount,
283
+ holder,
284
+ reportFailure,
285
+ dropSync,
286
+ keepApplyIssues,
287
+ loadAndKeep
261
288
  ]);
262
- useEffect(() => {
263
- if (!isReady || !urlSyncRef.current || !gridApi) {
264
- setCurrentUrl('');
265
- setHasFilters(false);
266
- return;
267
- }
268
- const syncActiveViewToGrid = (fromEvent, source) => {
269
- const activeId = activeViewIdRef.current;
270
- let applied = appliedModelRef.current;
271
- if (!pendingWriteRef.current && (!activeId || !applied || !viewStore)) {
272
- return;
273
- }
274
- let live;
275
- try {
276
- live = gridApi.getFilterModel() ?? {};
277
- }
278
- catch {
279
- return;
280
- }
281
- const pending = pendingWriteRef.current;
282
- if (pending) {
283
- if (!fromEvent)
284
- return;
285
- const inWrite = writingRef.current;
286
- if (!inWrite &&
287
- pending.before &&
288
- sameFilterModel(live, pending.before)) {
289
- return;
290
- }
291
- pendingWriteRef.current = null;
292
- if (inWrite ||
293
- (source === 'api' &&
294
- couldBeLanding(live, pending.expected, pending.before)) ||
295
- sameFilterModel(live, pending.expected)) {
296
- appliedModelRef.current = live;
297
- return;
298
- }
299
- applied = pending.expected;
300
- }
301
- if (!activeId || !applied || !viewStore)
302
- return;
303
- if (sameFilterModel(live, applied))
304
- return;
305
- commitActiveViewId(null);
306
- try {
307
- if (viewStore.getActiveViewId() === activeId) {
308
- viewStore.persistActiveViewId(null);
309
- }
310
- }
311
- catch (error) {
312
- handleError(error, 'filter-change');
313
- }
314
- };
315
- const updateState = (fromEvent, source) => {
316
- syncActiveViewToGrid(fromEvent, source);
317
- try {
318
- const { url, hasFilters } = urlSyncRef.current.getUrlState();
319
- setCurrentUrl(url);
320
- setHasFilters(hasFilters);
321
- }
322
- catch (error) {
323
- handleError(error, 'update-state');
324
- }
325
- };
326
- const onFilterChanged = (event) => updateState(true, event?.source);
327
- gridApi.addEventListener('filterChanged', onFilterChanged);
328
- updateState(false);
329
- return () => {
330
- gridApi.removeEventListener('filterChanged', onFilterChanged);
331
- };
332
- }, [isReady, gridApi, handleError, viewStore, commitActiveViewId]);
333
- useEffect(() => {
334
- return () => {
335
- if (urlSyncRef.current) {
336
- urlSyncRef.current.destroy();
337
- urlSyncRef.current = null;
338
- }
339
- };
340
- }, []);
289
+ useEffect(() => () => {
290
+ const current = liveSyncRef.current;
291
+ if (current)
292
+ dropSync(current);
293
+ }, [dropSync]);
294
+ const readOptions = useCallback((context) => resolveOptions({
295
+ ...urlOptions,
296
+ onParseError: issue => reportFailure(issue, context)
297
+ }), [urlOptions, reportFailure]);
341
298
  const shareUrl = useCallback((baseUrl) => {
342
- if (!urlSyncRef.current) {
343
- return (baseUrl ?? (typeof window !== 'undefined' ? window.location.href : ''));
344
- }
299
+ const fallback = baseUrl ?? pageUrl();
300
+ if (!gridApi || !activeSync())
301
+ return fallback;
345
302
  try {
346
- return urlSyncRef.current.generateUrl(baseUrl);
303
+ return urlWithFilters(gridApi, fallback, readOptions('generate-share-url'));
347
304
  }
348
305
  catch (error) {
349
- handleError(error, 'generate-share-url');
350
- return (baseUrl ?? (typeof window !== 'undefined' ? window.location.href : ''));
306
+ reportFailure(error, 'generate-share-url');
307
+ return fallback;
351
308
  }
352
- }, [handleError]);
309
+ }, [gridApi, activeSync, readOptions, reportFailure]);
353
310
  const getQueryParams = useCallback(() => {
354
- if (!urlSyncRef.current) {
311
+ if (!gridApi || !activeSync())
355
312
  return '';
356
- }
357
313
  try {
358
- return urlSyncRef.current.getQueryParams();
314
+ const url = urlWithFilters(gridApi, pageUrl(), readOptions('get-query-params'));
315
+ return new URL(url).search;
359
316
  }
360
317
  catch (error) {
361
- handleError(error, 'get-query-params');
318
+ reportFailure(error, 'get-query-params');
362
319
  return '';
363
320
  }
364
- }, [handleError]);
321
+ }, [gridApi, activeSync, readOptions, reportFailure]);
365
322
  const applyUrlFilters = useCallback((url) => {
366
- if (!urlSyncRef.current) {
367
- const warningMessage = 'applyUrlFilters called while the hook is not ready.';
368
- console.warn(warningMessage);
369
- coreOptions.onParseError?.(new Error(warningMessage));
323
+ const sync = activeSync();
324
+ if (!sync) {
325
+ reportNotReady('applyUrlFilters', 'apply-url-filters');
370
326
  return;
371
327
  }
372
- try {
373
- const sync = urlSyncRef.current;
374
- writeOverPendingView(() => sync.applyFromUrl(url));
375
- }
376
- catch (error) {
377
- handleError(error, 'apply-url-filters');
378
- coreOptions.onParseError?.(error);
379
- }
380
- }, [coreOptions, handleError, writeOverPendingView]);
328
+ contextRef.current = 'apply-url-filters';
329
+ keepApplyIssues(sync.applyUrl(url).issues);
330
+ }, [activeSync, reportNotReady, keepApplyIssues]);
381
331
  const clearFilters = useCallback(() => {
382
- if (!urlSyncRef.current) {
383
- const warningMessage = 'clearFilters called while the hook is not ready.';
384
- console.warn(warningMessage);
385
- coreOptions.onParseError?.(new Error(warningMessage));
332
+ const sync = activeSync();
333
+ if (!sync) {
334
+ reportNotReady('clearFilters', 'clear-filters');
386
335
  return;
387
336
  }
388
- try {
389
- const sync = urlSyncRef.current;
390
- writeOverPendingView(() => sync.clearFilters());
391
- }
392
- catch (error) {
393
- handleError(error, 'clear-filters');
394
- }
395
- }, [handleError, coreOptions, writeOverPendingView]);
337
+ contextRef.current = 'clear-filters';
338
+ keepApplyIssues(sync.applyFilters({}).issues);
339
+ }, [activeSync, reportNotReady, keepApplyIssues]);
396
340
  const parseUrlFilters = useCallback((url) => {
397
341
  if (!gridApi) {
398
342
  return {};
399
343
  }
400
344
  try {
401
- const config = {
402
- gridApi,
403
- paramPrefix: coreOptions.paramPrefix ?? DEFAULT_CONFIG.paramPrefix,
404
- maxValueLength: coreOptions.maxValueLength ?? DEFAULT_CONFIG.maxValueLength,
405
- onParseError: coreOptions.onParseError ?? (() => { }),
406
- serialization: coreOptions.serialization ?? DEFAULT_CONFIG.serialization,
407
- groupedParam: coreOptions.groupedParam ?? DEFAULT_CONFIG.groupedParam,
408
- format: coreOptions.format ?? DEFAULT_CONFIG.format
409
- };
410
- return parseFilters(url, config);
345
+ return parseFilters(url, {
346
+ ...urlOptions,
347
+ onParseError: issue => callbacksRef.current.onParseError?.(issue)
348
+ });
411
349
  }
412
350
  catch (error) {
413
- handleError(error, 'parse-url-filters');
414
- coreOptions.onParseError?.(error);
351
+ reportFailure(error, 'parse-url-filters');
415
352
  return {};
416
353
  }
417
- }, [coreOptions, gridApi, handleError]);
354
+ }, [urlOptions, gridApi, reportFailure]);
418
355
  const applyFilters = useCallback((filters) => {
419
- if (!urlSyncRef.current) {
420
- const warningMessage = 'applyFilters called while the hook is not ready.';
421
- console.warn(warningMessage);
422
- coreOptions.onParseError?.(new Error(warningMessage));
356
+ const sync = activeSync();
357
+ if (!sync) {
358
+ reportNotReady('applyFilters', 'apply-filters');
423
359
  return;
424
360
  }
425
- try {
426
- const sync = urlSyncRef.current;
427
- writeOverPendingView(() => sync.applyFilters(filters));
428
- }
429
- catch (error) {
430
- handleError(error, 'apply-filters');
431
- }
432
- }, [handleError, coreOptions, writeOverPendingView]);
433
- const getFiltersAsFormat = useCallback((format) => {
434
- if (!urlSyncRef.current) {
361
+ contextRef.current = 'apply-filters';
362
+ keepApplyIssues(sync.applyFilters(filters).issues);
363
+ }, [activeSync, reportNotReady, keepApplyIssues]);
364
+ const getFiltersAsFormat = useCallback((asFormat) => {
365
+ if (!gridApi || !activeSync())
435
366
  return '';
436
- }
437
367
  try {
438
- return urlSyncRef.current.getFiltersAsFormat(format);
368
+ return filtersAsFormat(gridApi, asFormat, readOptions('get-filters-as-format'));
439
369
  }
440
370
  catch (error) {
441
- handleError(error, 'get-filters-as-format');
371
+ reportFailure(error, 'get-filters-as-format');
442
372
  return '';
443
373
  }
444
- }, [handleError]);
445
- const getCurrentFormat = useCallback(() => {
446
- if (!urlSyncRef.current) {
447
- return DEFAULT_CONFIG.serialization;
448
- }
449
- try {
450
- return urlSyncRef.current.getSerializationMode();
451
- }
452
- catch (error) {
453
- handleError(error, 'get-current-format');
454
- return DEFAULT_CONFIG.serialization;
455
- }
456
- }, [handleError]);
457
- const reportNotReady = useCallback((operation, context) => {
458
- handleError(new Error(`${operation} called while the hook is not ready.`), context);
459
- }, [handleError]);
374
+ }, [gridApi, activeSync, readOptions, reportFailure]);
375
+ const getSerializationMode = useCallback(() => activeSync() ? serialization : DEFAULT_CONFIG.serialization, [activeSync, serialization]);
376
+ const getCurrentFormat = getSerializationMode;
460
377
  const saveView = useCallback((name) => {
461
- if (!viewStore || !enabledWhenReady) {
378
+ if (!viewStore || !enabledWhenReady)
462
379
  return null;
463
- }
464
- if (!urlSyncRef.current || !gridApi) {
380
+ const sync = activeSync();
381
+ if (!sync) {
465
382
  reportNotReady('saveView', 'save-view');
466
383
  return null;
467
384
  }
468
- try {
469
- const view = viewStore.saveView(name, gridApi.getFilterModel());
470
- syncViewsFromStore();
471
- commitActiveViewId(view.id);
472
- appliedModelRef.current = view.filterModel;
473
- pendingWriteRef.current = null;
474
- return view;
475
- }
476
- catch (error) {
477
- handleError(error, 'save-view');
478
- return null;
479
- }
480
- }, [
481
- viewStore,
482
- gridApi,
483
- handleError,
484
- syncViewsFromStore,
485
- reportNotReady,
486
- enabledWhenReady,
487
- commitActiveViewId
488
- ]);
385
+ contextRef.current = 'save-view';
386
+ return sync.saveView(name);
387
+ }, [viewStore, enabledWhenReady, activeSync, reportNotReady]);
489
388
  const loadView = useCallback((id) => {
490
- if (!viewStore || !enabledWhenReady) {
389
+ if (!viewStore || !enabledWhenReady)
491
390
  return;
492
- }
493
- if (!urlSyncRef.current || !gridApi) {
391
+ const sync = activeSync();
392
+ if (!sync) {
494
393
  reportNotReady('loadView', 'load-view');
495
394
  return;
496
395
  }
497
- try {
498
- const previous = activeViewIdRef.current;
499
- const previousApplied = appliedModelRef.current;
500
- const rollBack = () => {
501
- commitActiveViewId(previous);
502
- appliedModelRef.current = previousApplied;
503
- };
504
- if (id == null) {
505
- commitActiveViewId(null);
506
- try {
507
- applyModelToGrid(gridApi, {});
508
- }
509
- catch (error) {
510
- rollBack();
511
- throw error;
512
- }
513
- viewStore.persistActiveViewId(null);
514
- return;
515
- }
516
- const view = viewStore
517
- .listViews()
518
- .find(candidate => candidate.id === id);
519
- if (!view) {
520
- syncViewsFromStore();
521
- handleError(new Error(`No saved view with id "${id}"`), 'load-view');
522
- return;
523
- }
524
- commitActiveViewId(view.id);
525
- try {
526
- applyModelToGrid(gridApi, view.filterModel);
527
- }
528
- catch (error) {
529
- rollBack();
530
- throw error;
531
- }
532
- if (activeViewIdRef.current === view.id) {
533
- viewStore.persistActiveViewId(view.id);
534
- }
535
- }
536
- catch (error) {
537
- handleError(error, 'load-view');
538
- }
539
- }, [
540
- gridApi,
541
- viewStore,
542
- handleError,
543
- reportNotReady,
544
- syncViewsFromStore,
545
- enabledWhenReady,
546
- commitActiveViewId,
547
- applyModelToGrid
548
- ]);
396
+ contextRef.current = 'load-view';
397
+ loadAndKeep(sync, id);
398
+ }, [viewStore, enabledWhenReady, activeSync, reportNotReady, loadAndKeep]);
549
399
  const deleteView = useCallback((id) => {
550
- if (!viewStore || !enabledWhenReady) {
400
+ if (!viewStore || !enabledWhenReady)
401
+ return;
402
+ const sync = holder.get();
403
+ if (sync) {
404
+ contextRef.current = 'delete-view';
405
+ sync.deleteView(id);
551
406
  return;
552
407
  }
553
408
  try {
554
- const wasActive = activeViewIdRef.current === id;
555
- const view = viewStore.listViews().find(entry => entry.id === id);
556
- const applied = appliedModelRef.current ?? view?.filterModel;
557
409
  viewStore.deleteView(id);
558
- syncViewsFromStore();
559
- if (wasActive) {
560
- commitActiveViewId(null);
561
- }
562
- if (wasActive && applied !== undefined && gridApi !== null) {
563
- try {
564
- const current = gridApi.getFilterModel();
565
- if (current && sameFilterModel(current, applied)) {
566
- gridApi.setFilterModel({});
567
- }
568
- }
569
- catch (error) {
570
- handleError(error, 'delete-view');
571
- }
572
- }
573
410
  }
574
411
  catch (error) {
575
- handleError(error, 'delete-view');
576
- }
577
- }, [
578
- viewStore,
579
- gridApi,
580
- handleError,
581
- syncViewsFromStore,
582
- enabledWhenReady,
583
- commitActiveViewId
584
- ]);
585
- return useMemo(() => ({
412
+ reportFailure(error, 'delete-view');
413
+ }
414
+ holder.setIdleViews(viewStore.listViews());
415
+ }, [viewStore, enabledWhenReady, holder, reportFailure]);
416
+ const views = useMemo(() => [...snapshot.views], [snapshot.views]);
417
+ const { activeViewId } = snapshot;
418
+ const issues = useMemo(() => {
419
+ const data = isReady ? snapshot.issues.filter(isDataIssue) : NO_ISSUES;
420
+ return data.length ? data : NO_ISSUES;
421
+ }, [isReady, snapshot.issues]);
422
+ const hookApi = useMemo(() => ({
586
423
  shareUrl,
587
424
  getQueryParams,
588
425
  applyUrlFilters,
@@ -590,9 +427,11 @@ export function useAGGridUrlSync(gridApi, options = {}) {
590
427
  isReady,
591
428
  currentUrl,
592
429
  hasFilters,
430
+ issues,
593
431
  parseUrlFilters,
594
432
  applyFilters,
595
433
  getFiltersAsFormat,
434
+ getSerializationMode,
596
435
  getCurrentFormat,
597
436
  views,
598
437
  activeViewId,
@@ -607,9 +446,11 @@ export function useAGGridUrlSync(gridApi, options = {}) {
607
446
  isReady,
608
447
  currentUrl,
609
448
  hasFilters,
449
+ issues,
610
450
  parseUrlFilters,
611
451
  applyFilters,
612
452
  getFiltersAsFormat,
453
+ getSerializationMode,
613
454
  getCurrentFormat,
614
455
  views,
615
456
  activeViewId,
@@ -617,5 +458,9 @@ export function useAGGridUrlSync(gridApi, options = {}) {
617
458
  loadView,
618
459
  deleteView
619
460
  ]);
461
+ const thrownIssue = issues.find(issue => shouldThrow(throwOnError, issue));
462
+ if (thrownIssue)
463
+ throw thrownIssue;
464
+ return hookApi;
620
465
  }
621
466
  //# sourceMappingURL=use-ag-grid-url-sync.js.map