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