ag-grid-url-sync 0.1.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.
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ ![CI](https://github.com/nearform/ag-grid-url-sync/actions/workflows/ci.yml/badge.svg?event=push)
2
+
3
+ # AG Grid URL Sync
4
+
5
+ A lightweight TypeScript library for synchronizing AG Grid text filters with URL parameters, enabling shareable filter states through clean, human-readable URLs.
6
+
7
+ ## Features
8
+
9
+ - ๐Ÿ” Text filter synchronization (`contains` and `equals` operations)
10
+ - ๐Ÿ”— Manual URL generation for sharing filter states
11
+ - โ†”๏ธ Bidirectional sync between grid and URL
12
+ - ๐Ÿ› ๏ธ Framework agnostic - works with any AG Grid setup
13
+ - ๐Ÿ“ Full TypeScript support with strict mode compliance
14
+ - ๐Ÿšฆ Graceful error handling with configurable error callbacks
15
+ - ๐Ÿงน Clean, human-readable URL format
16
+ - โšก High performance - handles 100+ filters efficiently (<20ms)
17
+ - ๐Ÿ”ง Configurable URL prefixes for multi-grid scenarios
18
+ - ๐Ÿ›ก๏ธ Robust edge case handling (special characters, malformed URLs)
19
+ - ๐Ÿ“ฆ Lightweight bundle size (~3KB gzipped)
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install ag-grid-url-sync
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Basic Example
30
+
31
+ ```typescript
32
+ import { createUrlSync } from 'ag-grid-url-sync'
33
+
34
+ // Initialize with AG Grid API
35
+ const urlSync = createUrlSync(gridApi)
36
+
37
+ // Generate shareable URL with current filters
38
+ const shareableUrl = urlSync.generateUrl()
39
+
40
+ // Apply filters from URL
41
+ urlSync.applyFromUrl()
42
+
43
+ // Clear all text filters
44
+ urlSync.clearFilters()
45
+ ```
46
+
47
+ ### React Integration
48
+
49
+ ```tsx
50
+ import { createUrlSync } from 'ag-grid-url-sync'
51
+ import { useCallback, useMemo } from 'react'
52
+
53
+ function useGridUrlSync(gridApi) {
54
+ const urlSync = useMemo(
55
+ () => (gridApi ? createUrlSync(gridApi) : null),
56
+ [gridApi]
57
+ )
58
+
59
+ const shareUrl = useCallback(() => urlSync?.generateUrl() || '', [urlSync])
60
+
61
+ const applyUrlFilters = useCallback(() => urlSync?.applyFromUrl(), [urlSync])
62
+
63
+ return { shareUrl, applyUrlFilters }
64
+ }
65
+
66
+ // In your component
67
+ function GridComponent() {
68
+ const { shareUrl, applyUrlFilters } = useGridUrlSync(gridApi)
69
+
70
+ return (
71
+ <div>
72
+ <button
73
+ onClick={() => {
74
+ const url = shareUrl()
75
+ navigator.clipboard.writeText(url)
76
+ }}
77
+ >
78
+ Share Filters
79
+ </button>
80
+ <button onClick={applyUrlFilters}>Apply URL Filters</button>
81
+ </div>
82
+ )
83
+ }
84
+ ```
85
+
86
+ ### URL Format
87
+
88
+ The library generates clean, human-readable URLs:
89
+
90
+ ```
91
+ Base URL: https://app.com/page
92
+ With filters: https://app.com/page?f_name_contains=john&f_status_eq=active
93
+ ```
94
+
95
+ Parameter structure:
96
+
97
+ - Prefix: `f_` (configurable - useful for multi-grid scenarios)
98
+ - Format: `f_{columnName}_{operation}={value}`
99
+ - Operations: `contains`, `eq` (equals)
100
+ - Standard URL encoding for special characters
101
+ - Supports column names with underscores (e.g., `user_id`, `created_date`)
102
+
103
+ ## API Reference
104
+
105
+ ### `createUrlSync(gridApi, config?)`
106
+
107
+ Factory function to create a new AGGridUrlSync instance.
108
+
109
+ ```typescript
110
+ import { createUrlSync } from 'ag-grid-url-sync'
111
+
112
+ const urlSync = createUrlSync(gridApi, {
113
+ paramPrefix: 'f_', // Default: 'f_'
114
+ maxValueLength: 200, // Default: 200
115
+ onParseError: err => console.warn(err)
116
+ })
117
+ ```
118
+
119
+ ### `AGGridUrlSync` Class
120
+
121
+ #### Constructor
122
+
123
+ ```typescript
124
+ new AGGridUrlSync(gridApi: GridApi, config?: AGGridUrlSyncConfig)
125
+ ```
126
+
127
+ #### Methods
128
+
129
+ ##### `generateUrl(baseUrl?: string): string`
130
+
131
+ Generates a URL with the current filter state.
132
+
133
+ ```typescript
134
+ const url = urlSync.generateUrl('https://app.com/page')
135
+ // https://app.com/page?f_name_contains=john
136
+ ```
137
+
138
+ ##### `getQueryParams(): string`
139
+
140
+ Gets the current filter state as URL query parameters.
141
+
142
+ ```typescript
143
+ const params = urlSync.getQueryParams()
144
+ // ?f_name_contains=john
145
+ ```
146
+
147
+ ##### `applyFromUrl(url?: string): void`
148
+
149
+ Applies filters from a URL to the grid.
150
+
151
+ ```typescript
152
+ urlSync.applyFromUrl('https://app.com/page?f_name_contains=john')
153
+ ```
154
+
155
+ ##### `applyFilters(filterState: FilterState): void`
156
+
157
+ Applies a filter state object to the grid.
158
+
159
+ ```typescript
160
+ urlSync.applyFilters({
161
+ name: {
162
+ filterType: 'text',
163
+ type: 'contains',
164
+ filter: 'john'
165
+ }
166
+ })
167
+ ```
168
+
169
+ ##### `clearFilters(): void`
170
+
171
+ Clears all text filters from the grid.
172
+
173
+ ```typescript
174
+ urlSync.clearFilters()
175
+ ```
176
+
177
+ ##### `destroy(): void`
178
+
179
+ Cleans up any resources or event listeners.
180
+
181
+ ```typescript
182
+ urlSync.destroy()
183
+ ```
184
+
185
+ ### Configuration
186
+
187
+ ```typescript
188
+ interface AGGridUrlSyncConfig {
189
+ // Prefix for URL parameters (default: 'f_')
190
+ // Useful for multi-grid scenarios: 'emp_', 'proj_', etc.
191
+ paramPrefix?: string
192
+
193
+ // Maximum length for filter values (default: 200)
194
+ maxValueLength?: number
195
+
196
+ // Optional error handler for parsing errors
197
+ // Called when URLs contain invalid filter parameters
198
+ onParseError?: (error: Error) => void
199
+ }
200
+ ```
201
+
202
+ ## Examples
203
+
204
+ Check out the [examples](./examples) directory for comprehensive working demos:
205
+
206
+ ### ๐Ÿ“ [Basic Example](./examples/basic-example.html)
207
+
208
+ Simple HTML/JS implementation showing core functionality:
209
+
210
+ - Initialize URL sync with AG Grid
211
+ - Generate shareable URLs
212
+ - Apply filters from URLs
213
+ - Handle filter changes
214
+
215
+ ### ๐Ÿš€ [Advanced Demo](./examples/advanced-demo.html)
216
+
217
+ Feature-rich demonstration including:
218
+
219
+ - Performance monitoring and benchmarks
220
+ - Multiple filter scenarios (Sales, Engineering, Executive views)
221
+ - URL sharing workflow with copy/email/Slack functionality
222
+ - Error testing with malformed URLs and invalid filters
223
+ - Memory and stress testing capabilities
224
+
225
+ ### ๐Ÿ”— [Multi-Grid Demo](./examples/multi-grid-demo.html)
226
+
227
+ Complex example with multiple independent grids:
228
+
229
+ - Four separate grids (Employees, Projects, Departments, Metrics)
230
+ - Individual URL namespacing with different prefixes
231
+ - Combined URL generation merging all grid states
232
+ - Independent grid controls and clearing functions
233
+
234
+ All examples work out-of-the-box by opening the HTML files in your browser.
235
+
236
+ ## Contributing
237
+
238
+ 1. Fork the repository
239
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
240
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
241
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
242
+ 5. Open a Pull Request
243
+
244
+ ## License
245
+
246
+ This project is licensed under the ISC License - see the [LICENSE](LICENSE) file for details.
247
+
248
+ [![banner](https://raw.githubusercontent.com/nearform/.github/refs/heads/master/assets/os-banner-green.svg)](https://www.nearform.com/contact/?utm_source=open-source&utm_medium=banner&utm_campaign=os-project-pages)
@@ -0,0 +1,50 @@
1
+ import type { GridApi } from 'ag-grid-community';
2
+ import type { AGGridUrlSyncConfig, FilterState } from './types.js';
3
+ /**
4
+ * AGGridUrlSync class for synchronizing AG Grid text filters with URL parameters
5
+ */
6
+ export declare class AGGridUrlSync {
7
+ private config;
8
+ /**
9
+ * Creates a new instance of AGGridUrlSync
10
+ * @param gridApi - AG Grid API instance
11
+ * @param config - Optional configuration options
12
+ */
13
+ constructor(gridApi: GridApi, config?: AGGridUrlSyncConfig);
14
+ /**
15
+ * Generates a URL with the current filter state
16
+ * @param baseUrl - Optional base URL (defaults to current URL)
17
+ * @returns URL string with filter parameters
18
+ */
19
+ generateUrl(baseUrl?: string): string;
20
+ /**
21
+ * Gets the current filter state as URL query parameters
22
+ * @returns Query parameter string
23
+ */
24
+ getQueryParams(): string;
25
+ /**
26
+ * Applies filters from a URL to the grid
27
+ * @param url - Optional URL to parse (defaults to current URL)
28
+ */
29
+ applyFromUrl(url?: string): void;
30
+ /**
31
+ * Applies a filter state object to the grid
32
+ * @param filterState - Filter state to apply
33
+ */
34
+ applyFilters(filterState: FilterState): void;
35
+ /**
36
+ * Clears all text filters from the grid
37
+ */
38
+ clearFilters(): void;
39
+ /**
40
+ * Cleans up any resources or event listeners
41
+ */
42
+ destroy(): void;
43
+ }
44
+ /**
45
+ * Factory function to create a new AGGridUrlSync instance
46
+ * @param gridApi - AG Grid API instance
47
+ * @param config - Optional configuration options
48
+ * @returns AGGridUrlSync instance
49
+ */
50
+ export declare function createUrlSync(gridApi: GridApi, config?: AGGridUrlSyncConfig): AGGridUrlSync;
@@ -0,0 +1,78 @@
1
+ import { DEFAULT_CONFIG } from './utils.js';
2
+ import { parseUrlFilters, generateUrl, getFilterModel, applyFilterModel } from './utils.js';
3
+ /**
4
+ * AGGridUrlSync class for synchronizing AG Grid text filters with URL parameters
5
+ */
6
+ export class AGGridUrlSync {
7
+ config;
8
+ /**
9
+ * Creates a new instance of AGGridUrlSync
10
+ * @param gridApi - AG Grid API instance
11
+ * @param config - Optional configuration options
12
+ */
13
+ constructor(gridApi, config = {}) {
14
+ this.config = {
15
+ gridApi,
16
+ paramPrefix: config.paramPrefix ?? DEFAULT_CONFIG.paramPrefix,
17
+ maxValueLength: config.maxValueLength ?? DEFAULT_CONFIG.maxValueLength,
18
+ onParseError: config.onParseError ?? (() => { })
19
+ };
20
+ }
21
+ /**
22
+ * Generates a URL with the current filter state
23
+ * @param baseUrl - Optional base URL (defaults to current URL)
24
+ * @returns URL string with filter parameters
25
+ */
26
+ generateUrl(baseUrl) {
27
+ const filterState = getFilterModel(this.config);
28
+ const url = baseUrl ?? window.location.href;
29
+ return generateUrl(url, filterState, this.config);
30
+ }
31
+ /**
32
+ * Gets the current filter state as URL query parameters
33
+ * @returns Query parameter string
34
+ */
35
+ getQueryParams() {
36
+ const url = this.generateUrl();
37
+ const urlObj = new URL(url);
38
+ return urlObj.search;
39
+ }
40
+ /**
41
+ * Applies filters from a URL to the grid
42
+ * @param url - Optional URL to parse (defaults to current URL)
43
+ */
44
+ applyFromUrl(url) {
45
+ const urlToParse = url ?? window.location.href;
46
+ const filterState = parseUrlFilters(urlToParse, this.config);
47
+ this.applyFilters(filterState);
48
+ }
49
+ /**
50
+ * Applies a filter state object to the grid
51
+ * @param filterState - Filter state to apply
52
+ */
53
+ applyFilters(filterState) {
54
+ applyFilterModel(filterState, this.config);
55
+ }
56
+ /**
57
+ * Clears all text filters from the grid
58
+ */
59
+ clearFilters() {
60
+ this.applyFilters({});
61
+ }
62
+ /**
63
+ * Cleans up any resources or event listeners
64
+ */
65
+ destroy() {
66
+ // Currently no cleanup needed, but included for future use
67
+ }
68
+ }
69
+ /**
70
+ * Factory function to create a new AGGridUrlSync instance
71
+ * @param gridApi - AG Grid API instance
72
+ * @param config - Optional configuration options
73
+ * @returns AGGridUrlSync instance
74
+ */
75
+ export function createUrlSync(gridApi, config) {
76
+ return new AGGridUrlSync(gridApi, config);
77
+ }
78
+ //# sourceMappingURL=ag-grid-url-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ag-grid-url-sync.js","sourceRoot":"","sources":["../src/ag-grid-url-sync.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EACL,eAAe,EACf,WAAW,EACX,cAAc,EACd,gBAAgB,EACjB,MAAM,YAAY,CAAA;AAEnB;;GAEG;AACH,MAAM,OAAO,aAAa;IAChB,MAAM,CAAgB;IAE9B;;;;OAIG;IACH,YAAY,OAAgB,EAAE,SAA8B,EAAE;QAC5D,IAAI,CAAC,MAAM,GAAG;YACZ,OAAO;YACP,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,cAAc,CAAC,WAAW;YAC7D,cAAc,EAAE,MAAM,CAAC,cAAc,IAAI,cAAc,CAAC,cAAc;YACtE,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;SAChD,CAAA;IACH,CAAC;IAED;;;;OAIG;IACH,WAAW,CAAC,OAAgB;QAC1B,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAC/C,MAAM,GAAG,GAAG,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAA;QAC3C,OAAO,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IACnD,CAAC;IAED;;;OAGG;IACH,cAAc;QACZ,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;QAC9B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;QAC3B,OAAO,MAAM,CAAC,MAAM,CAAA;IACtB,CAAC;IAED;;;OAGG;IACH,YAAY,CAAC,GAAY;QACvB,MAAM,UAAU,GAAG,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAA;QAC9C,MAAM,WAAW,GAAG,eAAe,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QAC5D,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAA;IAChC,CAAC;IAED;;;OAGG;IACH,YAAY,CAAC,WAAwB;QACnC,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;IAC5C,CAAC;IAED;;OAEG;IACH,YAAY;QACV,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,CAAA;IACvB,CAAC;IAED;;OAEG;IACH,OAAO;QACL,2DAA2D;IAC7D,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAgB,EAChB,MAA4B;IAE5B,OAAO,IAAI,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;AAC3C,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { AGGridUrlSync, createUrlSync } from './ag-grid-url-sync.js';
2
+ export * from './types.js';
3
+ export { parseUrlFilters, serializeFilters, generateUrl, getFilterModel, applyFilterModel, validateFilterValue, parseFilterParam, DEFAULT_CONFIG } from './utils.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { AGGridUrlSync, createUrlSync } from './ag-grid-url-sync.js';
2
+ export * from './types.js';
3
+ export { parseUrlFilters, serializeFilters, generateUrl, getFilterModel, applyFilterModel, validateFilterValue, parseFilterParam, DEFAULT_CONFIG } from './utils.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAEpE,cAAc,YAAY,CAAA;AAE1B,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACf,MAAM,YAAY,CAAA"}
@@ -0,0 +1,69 @@
1
+ import type { GridApi as AGGridApi } from 'ag-grid-community';
2
+ /**
3
+ * Configuration options for AG Grid URL Sync
4
+ */
5
+ export interface AGGridUrlSyncConfig {
6
+ /**
7
+ * Prefix for URL parameters. Default: 'f_'
8
+ */
9
+ paramPrefix?: string;
10
+ /**
11
+ * Maximum length for filter values. Default: 200
12
+ */
13
+ maxValueLength?: number;
14
+ /**
15
+ * Optional error handler for parsing errors
16
+ */
17
+ onParseError?: (error: Error) => void;
18
+ }
19
+ /**
20
+ * Supported filter operations
21
+ */
22
+ export type FilterOperation = 'contains' | 'eq';
23
+ /**
24
+ * Filter state for a single column
25
+ */
26
+ export interface ColumnFilter {
27
+ filterType: 'text';
28
+ type: FilterOperation;
29
+ filter: string;
30
+ }
31
+ /**
32
+ * Complete filter state mapping
33
+ */
34
+ export interface FilterState {
35
+ [columnId: string]: ColumnFilter;
36
+ }
37
+ /**
38
+ * Internal configuration with defaults
39
+ */
40
+ export interface InternalConfig extends Required<AGGridUrlSyncConfig> {
41
+ gridApi: AGGridApi;
42
+ }
43
+ /**
44
+ * Error types for URL sync operations
45
+ */
46
+ export declare class URLSyncError extends Error {
47
+ constructor(message: string);
48
+ }
49
+ export declare class InvalidFilterError extends URLSyncError {
50
+ constructor(message: string);
51
+ }
52
+ export declare class InvalidURLError extends URLSyncError {
53
+ constructor(message: string);
54
+ }
55
+ export type ParsedFilterParam = {
56
+ columnName: string;
57
+ operation: 'contains' | 'equals';
58
+ value: string;
59
+ action: 'apply' | 'remove';
60
+ };
61
+ export type GridApi = AGGridApi;
62
+ export declare const OPERATION_MAP: {
63
+ readonly contains: "contains";
64
+ readonly eq: "equals";
65
+ };
66
+ export declare const REVERSE_OPERATION_MAP: {
67
+ readonly contains: "contains";
68
+ readonly equals: "eq";
69
+ };
package/dist/types.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Error types for URL sync operations
3
+ */
4
+ export class URLSyncError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = 'URLSyncError';
8
+ }
9
+ }
10
+ export class InvalidFilterError extends URLSyncError {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = 'InvalidFilterError';
14
+ }
15
+ }
16
+ export class InvalidURLError extends URLSyncError {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = 'InvalidURLError';
20
+ }
21
+ }
22
+ // Internal operation mapping
23
+ export const OPERATION_MAP = {
24
+ contains: 'contains',
25
+ eq: 'equals'
26
+ };
27
+ export const REVERSE_OPERATION_MAP = {
28
+ contains: 'contains',
29
+ equals: 'eq'
30
+ };
31
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAkDA;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,cAAc,CAAA;IAC5B,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,YAAY;IAClD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAA;IAClC,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,YAAY;IAC/C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;IAC/B,CAAC;CACF;AAYD,6BAA6B;AAC7B,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,QAAQ,EAAE,UAAU;IACpB,EAAE,EAAE,QAAQ;CACJ,CAAA;AAEV,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,IAAI;CACJ,CAAA"}
@@ -0,0 +1,36 @@
1
+ import type { FilterState, ColumnFilter, InternalConfig } from './types.js';
2
+ /**
3
+ * Default configuration values
4
+ */
5
+ export declare const DEFAULT_CONFIG: {
6
+ readonly paramPrefix: "f_";
7
+ readonly maxValueLength: 200;
8
+ };
9
+ /**
10
+ * Validates a filter value against configuration constraints
11
+ */
12
+ export declare function validateFilterValue(value: string, config: InternalConfig): string;
13
+ /**
14
+ * Parses a URL parameter into a column filter
15
+ */
16
+ export declare function parseFilterParam(param: string, value: string, expectedPrefix?: string): ColumnFilter;
17
+ /**
18
+ * Converts URL search params into a filter state object
19
+ */
20
+ export declare function parseUrlFilters(url: string, config: InternalConfig): FilterState;
21
+ /**
22
+ * Converts a filter state object into URL search parameters
23
+ */
24
+ export declare function serializeFilters(filterState: FilterState, config: InternalConfig): URLSearchParams;
25
+ /**
26
+ * Generates a URL with the current filter state
27
+ */
28
+ export declare function generateUrl(baseUrl: string, filterState: FilterState, config: InternalConfig): string;
29
+ /**
30
+ * Gets the current filter model from AG Grid
31
+ */
32
+ export declare function getFilterModel(config: InternalConfig): FilterState;
33
+ /**
34
+ * Applies a filter state to AG Grid
35
+ */
36
+ export declare function applyFilterModel(filterState: FilterState, config: InternalConfig): void;
package/dist/utils.js ADDED
@@ -0,0 +1,188 @@
1
+ import { InvalidFilterError, InvalidURLError } from './types.js';
2
+ /**
3
+ * Default configuration values
4
+ */
5
+ export const DEFAULT_CONFIG = {
6
+ paramPrefix: 'f_',
7
+ maxValueLength: 200
8
+ };
9
+ /**
10
+ * Operation mapping between URL parameters and AG Grid filter types
11
+ */
12
+ const OPERATION_MAP = {
13
+ contains: 'contains',
14
+ eq: 'eq'
15
+ };
16
+ /**
17
+ * Validates a filter value against configuration constraints
18
+ */
19
+ export function validateFilterValue(value, config) {
20
+ if (value.length > config.maxValueLength) {
21
+ throw new InvalidFilterError(`Filter value exceeds maximum length of ${config.maxValueLength} characters`);
22
+ }
23
+ return value;
24
+ }
25
+ /**
26
+ * Extracts column name and operation from a filter parameter
27
+ */
28
+ function extractColumnAndOperation(param, expectedPrefix) {
29
+ const ensureTrailingUnderscore = (prefix) => prefix.endsWith('_') ? prefix : prefix + '_';
30
+ const sanitizedPrefix = expectedPrefix
31
+ ? ensureTrailingUnderscore(expectedPrefix)
32
+ : ensureTrailingUnderscore(DEFAULT_CONFIG.paramPrefix);
33
+ const prefixToCheck = sanitizedPrefix.slice(0, -1);
34
+ if (!param.startsWith(prefixToCheck + '_')) {
35
+ throw new InvalidFilterError(`Invalid filter prefix in parameter: ${param}`);
36
+ }
37
+ // Remove prefix and find the last underscore to separate column from operation
38
+ const withoutPrefix = param.substring(prefixToCheck.length + 1);
39
+ const lastUnderscoreIndex = withoutPrefix.lastIndexOf('_');
40
+ if (lastUnderscoreIndex === -1) {
41
+ throw new InvalidFilterError(`Invalid filter parameter format: ${param}`);
42
+ }
43
+ const column = withoutPrefix.substring(0, lastUnderscoreIndex);
44
+ const operation = withoutPrefix.substring(lastUnderscoreIndex + 1);
45
+ if (!column.trim()) {
46
+ throw new InvalidFilterError(`Empty column name in parameter: ${param}`);
47
+ }
48
+ return { column, operation };
49
+ }
50
+ /**
51
+ * Parses a URL parameter into a column filter
52
+ */
53
+ export function parseFilterParam(param, value, expectedPrefix) {
54
+ const { operation } = extractColumnAndOperation(param, expectedPrefix);
55
+ const filterOp = OPERATION_MAP[operation];
56
+ if (!filterOp) {
57
+ throw new InvalidFilterError(`Unsupported filter operation: ${operation}`);
58
+ }
59
+ return {
60
+ filterType: 'text',
61
+ type: filterOp,
62
+ filter: value || ''
63
+ };
64
+ }
65
+ /**
66
+ * Converts URL search params into a filter state object
67
+ */
68
+ export function parseUrlFilters(url, config) {
69
+ try {
70
+ const urlObj = new URL(url);
71
+ const filterState = {};
72
+ for (const [param, value] of urlObj.searchParams.entries()) {
73
+ if (!param.startsWith(config.paramPrefix))
74
+ continue;
75
+ try {
76
+ // Extract column name and parse the filter parameter
77
+ const { column: columnName } = extractColumnAndOperation(param, config.paramPrefix);
78
+ const filterParam = parseFilterParam(param, value, config.paramPrefix);
79
+ const validatedValue = validateFilterValue(value, config);
80
+ filterState[columnName] = {
81
+ ...filterParam,
82
+ filter: validatedValue
83
+ };
84
+ }
85
+ catch (err) {
86
+ if (config.onParseError) {
87
+ config.onParseError(err);
88
+ }
89
+ // Skip invalid filters but continue processing
90
+ continue;
91
+ }
92
+ }
93
+ return filterState;
94
+ }
95
+ catch (err) {
96
+ throw new InvalidURLError(`Failed to parse URL: ${err.message}`);
97
+ }
98
+ }
99
+ /**
100
+ * Converts a filter state object into URL search parameters
101
+ */
102
+ export function serializeFilters(filterState, config) {
103
+ const params = new URLSearchParams();
104
+ for (const [column, filter] of Object.entries(filterState)) {
105
+ if (filter.filterType !== 'text')
106
+ continue;
107
+ const operation = filter.type === 'contains' ? 'contains' : 'eq';
108
+ const paramName = `${config.paramPrefix}${column}_${operation}`;
109
+ const value = validateFilterValue(filter.filter, config);
110
+ // URLSearchParams already handles encoding, so don't double-encode
111
+ params.append(paramName, value);
112
+ }
113
+ return params;
114
+ }
115
+ /**
116
+ * Generates a URL with the current filter state
117
+ */
118
+ export function generateUrl(baseUrl, filterState, config) {
119
+ const url = new URL(baseUrl);
120
+ const filterParams = serializeFilters(filterState, config);
121
+ // Preserve existing non-filter parameters
122
+ for (const [key, value] of url.searchParams.entries()) {
123
+ if (!key.startsWith(config.paramPrefix)) {
124
+ filterParams.append(key, value);
125
+ }
126
+ }
127
+ url.search = filterParams.toString();
128
+ return url.toString();
129
+ }
130
+ /**
131
+ * Gets the current filter model from AG Grid
132
+ */
133
+ export function getFilterModel(config) {
134
+ try {
135
+ const model = config.gridApi.getFilterModel();
136
+ const filterState = {};
137
+ if (!model || typeof model !== 'object') {
138
+ return filterState;
139
+ }
140
+ for (const [column, filter] of Object.entries(model)) {
141
+ if (!filter || typeof filter !== 'object')
142
+ continue;
143
+ const { filterType, type, filter: value } = filter;
144
+ if (filterType !== 'text' || !value)
145
+ continue;
146
+ if (type === 'contains' || type === 'equals') {
147
+ filterState[column] = {
148
+ filterType: 'text',
149
+ type: type === 'equals' ? 'eq' : 'contains',
150
+ filter: value
151
+ };
152
+ }
153
+ }
154
+ return filterState;
155
+ }
156
+ catch (error) {
157
+ if (config.onParseError) {
158
+ config.onParseError(error);
159
+ }
160
+ return {};
161
+ }
162
+ }
163
+ /**
164
+ * Applies a filter state to AG Grid
165
+ */
166
+ export function applyFilterModel(filterState, config) {
167
+ try {
168
+ const model = {};
169
+ for (const [column, filter] of Object.entries(filterState)) {
170
+ if (filter &&
171
+ typeof filter === 'object' &&
172
+ filter.filterType === 'text') {
173
+ model[column] = {
174
+ filterType: filter.filterType,
175
+ type: filter.type === 'eq' ? 'equals' : filter.type,
176
+ filter: filter.filter
177
+ };
178
+ }
179
+ }
180
+ config.gridApi.setFilterModel(model);
181
+ }
182
+ catch (error) {
183
+ if (config.onParseError) {
184
+ config.onParseError(error);
185
+ }
186
+ }
187
+ }
188
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "ag-grid-url-sync",
3
+ "version": "0.1.1",
4
+ "description": "AG Grid URL Sync - Synchronize AG Grid text filters with URL parameters",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "lint": "eslint .",
9
+ "lint:fix": "eslint . --fix",
10
+ "test": "vitest run",
11
+ "test:watch": "vitest",
12
+ "test:coverage": "vitest run --coverage",
13
+ "clean": "rimraf dist",
14
+ "compile": "tsc",
15
+ "build": "npm run clean && npm run compile && npm run validate",
16
+ "validate": "npm run size-check && npm run type-check",
17
+ "size-check": "bundlesize",
18
+ "type-check": "tsc --noEmit --strict",
19
+ "ci": "npm run build && npm run check-exports && npm run lint && npm run test",
20
+ "check-exports": "attw --pack . --ignore-rules=cjs-resolves-to-esm",
21
+ "prepare": "husky"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/nearform/ag-grid-url-state-sync.git"
26
+ },
27
+ "keywords": [
28
+ "ag-grid",
29
+ "url",
30
+ "state",
31
+ "sync",
32
+ "filter",
33
+ "share",
34
+ "typescript"
35
+ ],
36
+ "author": "NearForm",
37
+ "license": "ISC",
38
+ "bugs": {
39
+ "url": "https://github.com/nearform/ag-grid-url-state-sync/issues"
40
+ },
41
+ "files": [
42
+ "dist"
43
+ ],
44
+ "homepage": "https://github.com/nearform/ag-grid-url-state-sync#readme",
45
+ "devDependencies": {
46
+ "@arethetypeswrong/cli": "^0.18.2",
47
+ "@commitlint/cli": "^19.8.1",
48
+ "@commitlint/config-conventional": "^19.8.1",
49
+ "@eslint/js": "^9.29.0",
50
+ "@types/jsdom": "^21.1.7",
51
+ "@vitest/coverage-v8": "^3.2.4",
52
+ "ag-grid-community": "^33.3.2",
53
+ "bundlesize": "^0.18.2",
54
+ "eslint": "^9.29.0",
55
+ "eslint-config-prettier": "^10.1.5",
56
+ "eslint-plugin-prettier": "^5.5.0",
57
+ "globals": "^16.2.0",
58
+ "husky": "^9.1.7",
59
+ "jsdom": "^26.1.0",
60
+ "lint-staged": "^16.1.2",
61
+ "prettier": "^3.6.0",
62
+ "rimraf": "^6.0.1",
63
+ "typescript": "^5.8.3",
64
+ "vitest": "^3.2.4"
65
+ },
66
+ "peerDependencies": {
67
+ "ag-grid-community": ">=28.0.0"
68
+ },
69
+ "lint-staged": {
70
+ "*.{js,jsx,ts,tsx}": "eslint --cache --fix"
71
+ },
72
+ "bundlesize": [
73
+ {
74
+ "path": "./dist/index.js",
75
+ "maxSize": "5KB"
76
+ },
77
+ {
78
+ "path": "./dist/ag-grid-url-sync.js",
79
+ "maxSize": "4KB"
80
+ },
81
+ {
82
+ "path": "./dist/utils.js",
83
+ "maxSize": "4KB"
84
+ }
85
+ ]
86
+ }