ag-grid-url-sync 0.3.0 → 0.4.1

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.
@@ -0,0 +1,112 @@
1
+ const SCHEMA_VERSION = 'v1';
2
+ const keyFor = (storageKey) => `ag-grid-url-sync:views:${SCHEMA_VERSION}:${storageKey}`;
3
+ const empty = () => ({ views: [], activeId: null });
4
+ function isGridView(value) {
5
+ if (typeof value !== 'object' || value === null)
6
+ return false;
7
+ const candidate = value;
8
+ return (typeof candidate.id === 'string' &&
9
+ typeof candidate.name === 'string' &&
10
+ typeof candidate.updatedAt === 'number' &&
11
+ typeof candidate.filterModel === 'object' &&
12
+ candidate.filterModel !== null);
13
+ }
14
+ function createId() {
15
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
16
+ return crypto.randomUUID();
17
+ }
18
+ return `view-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
19
+ }
20
+ export function createViewStore(storageKey) {
21
+ const key = keyFor(storageKey);
22
+ const read = () => {
23
+ if (typeof window === 'undefined')
24
+ return empty();
25
+ let raw = null;
26
+ try {
27
+ raw = window.localStorage.getItem(key);
28
+ }
29
+ catch {
30
+ return empty();
31
+ }
32
+ if (!raw)
33
+ return empty();
34
+ try {
35
+ const parsed = JSON.parse(raw);
36
+ if (typeof parsed !== 'object' || parsed === null)
37
+ return empty();
38
+ const { views, activeId } = parsed;
39
+ const validViews = Array.isArray(views) ? views.filter(isGridView) : [];
40
+ return {
41
+ views: validViews,
42
+ activeId: typeof activeId === 'string' &&
43
+ validViews.some(view => view.id === activeId)
44
+ ? activeId
45
+ : null
46
+ };
47
+ }
48
+ catch {
49
+ return empty();
50
+ }
51
+ };
52
+ const write = (next) => {
53
+ if (typeof window === 'undefined') {
54
+ throw new Error('Browser storage is unavailable.');
55
+ }
56
+ try {
57
+ window.localStorage.setItem(key, JSON.stringify(next));
58
+ }
59
+ catch (error) {
60
+ const isQuota = typeof DOMException !== 'undefined' &&
61
+ error instanceof DOMException &&
62
+ (error.name === 'QuotaExceededError' ||
63
+ error.name === 'NS_ERROR_DOM_QUOTA_REACHED');
64
+ throw new Error(isQuota
65
+ ? 'Browser storage is full. Delete a saved view and try again.'
66
+ : 'Error writing to storage.', { cause: error });
67
+ }
68
+ };
69
+ return {
70
+ listViews: () => read().views,
71
+ getActiveViewId: () => read().activeId,
72
+ persistActiveViewId: (id) => {
73
+ const current = read();
74
+ if (current.activeId === id)
75
+ return;
76
+ write({ ...current, activeId: id });
77
+ },
78
+ saveView: (name, filterModel) => {
79
+ const trimmedName = name.trim();
80
+ if (!trimmedName) {
81
+ throw new Error('A view name is required.');
82
+ }
83
+ const current = read();
84
+ const existing = current.views.find(view => view.name === trimmedName);
85
+ const view = {
86
+ id: existing?.id ?? createId(),
87
+ name: trimmedName,
88
+ updatedAt: Date.now(),
89
+ filterModel: structuredClone(filterModel ?? {})
90
+ };
91
+ write({
92
+ views: existing
93
+ ? current.views.map(candidate => candidate.id === existing.id ? view : candidate)
94
+ : [...current.views, view],
95
+ activeId: view.id
96
+ });
97
+ return view;
98
+ },
99
+ deleteView: (id) => {
100
+ const current = read();
101
+ const views = current.views.filter(view => view.id !== id);
102
+ if (views.length === current.views.length && current.activeId !== id) {
103
+ return;
104
+ }
105
+ write({
106
+ views,
107
+ activeId: current.activeId === id ? null : current.activeId
108
+ });
109
+ }
110
+ };
111
+ }
112
+ //# sourceMappingURL=view-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-storage.js","sourceRoot":"","sources":["../../src/core/view-storage.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -4,5 +4,6 @@ export { validateFilterValue, DEFAULT_CONFIG } from './core/validation.js';
4
4
  export { parseUrlFilters, parseFilterParam } from './core/url-parser.js';
5
5
  export { serializeFilters, generateUrl } from './core/url-generator.js';
6
6
  export { getFilterModel, applyFilterModel } from './core/grid-integration.js';
7
+ export type { GridView, ViewStore } from './core/view-storage.js';
7
8
  export { serializeGrouped, deserializeGrouped, detectGroupedSerialization, getAvailableFormats, getFormatSerializer } from './core/serialization/grouped.js';
8
9
  export { QueryStringSerializer, JsonSerializer, Base64Serializer } from './core/serialization/formats.js';
package/dist/index.js CHANGED
@@ -1,15 +1,9 @@
1
- // Export core functionality (vanilla JS)
2
1
  export { AGGridUrlSync, createUrlSync } from './core/ag-grid-url-sync.js';
3
2
  export * from './core/types.js';
4
- // Validation utilities
5
3
  export { validateFilterValue, DEFAULT_CONFIG } from './core/validation.js';
6
- // URL parsing utilities
7
4
  export { parseUrlFilters, parseFilterParam } from './core/url-parser.js';
8
- // URL generation utilities
9
5
  export { serializeFilters, generateUrl } from './core/url-generator.js';
10
- // AG Grid integration utilities
11
6
  export { getFilterModel, applyFilterModel } from './core/grid-integration.js';
12
- // Grouped serialization utilities
13
7
  export { serializeGrouped, deserializeGrouped, detectGroupedSerialization, getAvailableFormats, getFormatSerializer } from './core/serialization/grouped.js';
14
8
  export { QueryStringSerializer, JsonSerializer, Base64Serializer } from './core/serialization/formats.js';
15
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAEzE,cAAc,iBAAiB,CAAA;AAE/B,uBAAuB;AACvB,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE1E,wBAAwB;AACxB,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAExE,2BAA2B;AAC3B,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAEvE,gCAAgC;AAChC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAE7E,kCAAkC;AAClC,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,0BAA0B,EAC1B,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EACjB,MAAM,iCAAiC,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAEzE,cAAc,iBAAiB,CAAA;AAG/B,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAG1E,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAGxE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAGvE,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAS7E,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,0BAA0B,EAC1B,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,iCAAiC,CAAA;AAExC,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EACjB,MAAM,iCAAiC,CAAA"}
@@ -1,3 +1,4 @@
1
1
  export { useAGGridUrlSync } from './use-ag-grid-url-sync.js';
2
2
  export type { UseAGGridUrlSyncOptions, UseAGGridUrlSyncReturn } from './types.js';
3
+ export type { GridView } from '../core/view-storage.js';
3
4
  export type { FilterState, ColumnFilter, FilterOperation, AGGridUrlSyncConfig, GridApi } from '../core/types.js';
@@ -1,15 +1,41 @@
1
1
  import type { AGGridUrlSyncConfig, FilterState, SerializationFormat, SerializationMode } from '../core/types.js';
2
+ import type { GridView } from '../core/view-storage.js';
2
3
  /**
3
4
  * Configuration options for the React hook
4
5
  */
5
6
  export interface UseAGGridUrlSyncOptions extends AGGridUrlSyncConfig {
7
+ /**
8
+ * Enables saved views, persisted to localStorage under this key.
9
+ *
10
+ * The key both switches the feature on and namespaces the storage, so two
11
+ * grids on the same origin must use different keys. Omit it and the view
12
+ * members of the hook's return value are inert.
13
+ *
14
+ * @example 'employee-grid'
15
+ */
16
+ storageKey?: string;
6
17
  /**
7
18
  * Automatically apply URL filters when the component mounts and grid API becomes ready
19
+ *
20
+ * Also restores the active saved view when `storageKey` is set. URL filters
21
+ * take precedence: a shared link should show the sender's filters rather than
22
+ * the recipient's stored view.
23
+ *
24
+ * Attempted once per grid. A failed attempt is reported through `onError` and
25
+ * `onParseError` and is not retried on later renders; pass a new `gridApi`, or
26
+ * call `applyUrlFilters`, to try again.
27
+ *
8
28
  * Default: false
9
29
  */
10
30
  autoApplyOnMount?: boolean;
11
31
  /**
12
32
  * Whether the hook should be enabled when the grid API is ready
33
+ *
34
+ * Set to false and every operation goes inert, saved views included: no URL is
35
+ * generated, no filters are applied, and no view is saved, loaded or deleted.
36
+ * `views` still reflects what is in storage, since reading it has no effect on
37
+ * the grid.
38
+ *
13
39
  * Default: true
14
40
  */
15
41
  enabledWhenReady?: boolean;
@@ -77,4 +103,74 @@ export interface UseAGGridUrlSyncReturn {
77
103
  * @returns The current format
78
104
  */
79
105
  getCurrentFormat: () => SerializationMode;
106
+ /**
107
+ * Saved views for this grid, in save order.
108
+ * Always an empty array when `storageKey` is not set.
109
+ */
110
+ views: GridView[];
111
+ /**
112
+ * Id of the view currently applied to the grid, or null when none is.
113
+ *
114
+ * This describes the live grid, not what is persisted. A stored view is only
115
+ * applied on mount when `autoApplyOnMount` is set, so with it off this stays
116
+ * null until `loadView` or `saveView` is called, even though a previous
117
+ * session's view is still listed in `views`.
118
+ *
119
+ * It goes back to null as soon as the grid stops showing that view, which
120
+ * includes routes that never mention views: `clearFilters`, `applyFilters`,
121
+ * `applyUrlFilters`, and the user editing a filter in the grid's own UI. That
122
+ * also clears the stored pointer, so `autoApplyOnMount` will not restore the
123
+ * view on the next load either - filtering away from a view is treated as
124
+ * leaving it, not as a detour back to it.
125
+ *
126
+ * "Showing" means the model the grid reported once the view was applied, not
127
+ * the stored one. A view AG Grid normalises on the way in, such as one naming
128
+ * a column the grid no longer has, stays loaded and keeps its stored pointer.
129
+ * A write AG Grid defers is recognised when it lands by the event's
130
+ * `source: 'api'`. On versions that report no source, a deferred view the
131
+ * grid also normalises unloads when it lands.
132
+ *
133
+ * Replacing `gridApi` clears this too, since nothing has been applied to the
134
+ * new grid yet, but leaves the stored pointer alone: with `autoApplyOnMount`
135
+ * the view is restored against the new grid and this becomes non-null again.
136
+ */
137
+ activeViewId: string | null;
138
+ /**
139
+ * Saves the grid's current filters under a name and makes it active.
140
+ *
141
+ * The name is trimmed, and must not be empty. Names are unique: saving over an
142
+ * existing name updates that view in place, keeping its id and list position,
143
+ * so "load a view, adjust the filters, save under the same name" is how you
144
+ * update one.
145
+ *
146
+ * @param name - Display name for the view
147
+ * @returns The saved view, or null if it could not be saved. An empty name, a
148
+ * failed write, and being called before the grid is ready are all reported
149
+ * through `onError`. Silent when `storageKey` is not set or the hook is
150
+ * disabled, since the feature was switched off by configuration rather than
151
+ * having failed.
152
+ */
153
+ saveView: (name: string) => GridView | null;
154
+ /**
155
+ * Applies a saved view's filters to the grid, or clears filters when passed
156
+ * null. Use `clearFilters` to reset filters independently of saved views.
157
+ *
158
+ * Silent when `storageKey` is not set or the hook is disabled; reports through
159
+ * `onError` when called before the grid is ready, or when the id is not found.
160
+ *
161
+ * @param id - Id of the view to load, or null to reset to unfiltered
162
+ */
163
+ loadView: (id: string | null) => void;
164
+ /**
165
+ * Deletes a saved view. Clears the grid's filters only if the grid still shows
166
+ * exactly that view's filters, so hand-edited filters survive.
167
+ *
168
+ * Unlike `saveView` and `loadView`, this does not need the grid: it works
169
+ * before the grid API resolves, since deleting only touches storage, so it
170
+ * never reports a not-ready error. Silent when `storageKey` is not set or the
171
+ * hook is disabled.
172
+ *
173
+ * @param id - Id of the view to delete
174
+ */
175
+ deleteView: (id: string) => void;
80
176
  }