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,4 +1,6 @@
1
1
  import type { GridApi as AGGridApi, TextFilterModel, NumberFilterModel, DateFilterModel } from 'ag-grid-community';
2
+ import type { GridView, ViewStore } from './view-storage.js';
3
+ import { type Operator, type OperationsFor } from './operators.js';
2
4
  /**
3
5
  * Serialization modes for URL parameters
4
6
  */
@@ -29,62 +31,100 @@ export type SerializationFormat = 'querystring' | 'json' | 'base64';
29
31
  */
30
32
  export interface AGGridUrlSyncConfig {
31
33
  /**
32
- * Prefix for URL parameters (only used in individual mode)
34
+ * Prefix for URL parameters (only used in individual mode).
35
+ * A prefix without a trailing `_` gets one, so 'filter' and 'filter_' both
36
+ * give `?filter_name_contains=john`. An empty prefix means the default.
33
37
  * @default 'f_'
34
38
  * @example 'filter_' results in URLs like '?filter_name_contains=john'
35
39
  */
36
- paramPrefix?: string;
40
+ paramPrefix?: string | undefined;
37
41
  /**
38
42
  * Maximum length for filter values to prevent URL length issues
39
43
  * @default 200
40
44
  */
41
- maxValueLength?: number;
45
+ maxValueLength?: number | undefined;
42
46
  /**
43
- * Optional error handler for parsing errors.
44
- * Allows graceful error handling without breaking the application.
47
+ * Optional handler for issues: a filter that is dropped or not valid, in a
48
+ * URL, a value, or the grid model. The filter is skipped and the others are
49
+ * kept. Each error is a `URLSyncError` with a `code` (for example
50
+ * `VALUE_TOO_LONG`) and, when it is known, a `column`. In the core, an error
51
+ * from the grid API also comes here, as `GRID_ERROR` (the React hook sends it
52
+ * to `onError`). A URL that cannot be read is thrown as `INVALID_URL`, not
53
+ * sent here. The library never writes to the console, so an issue with no
54
+ * handler is silent (#220).
45
55
  *
46
56
  * In grouped mode, an error that comes from the grouped URL value is an
47
57
  * `InvalidSerializationError`, and the original error is in `error.cause`.
48
- * In individual mode, the same failure arrives with no wrapper.
58
+ * The wrapper has the code and the column of the original issue, so a check
59
+ * on `error.code` works the same in both modes.
49
60
  */
50
- onParseError?: (error: Error) => void;
61
+ onParseError?: ((error: Error) => void) | undefined;
51
62
  /**
52
63
  * Serialization mode for URL parameters
53
64
  * @default 'individual'
54
65
  */
55
- serialization?: SerializationMode;
66
+ serialization?: SerializationMode | undefined;
56
67
  /**
57
68
  * Parameter name for grouped serialization
58
69
  * @default 'grid_filters'
59
70
  */
60
- groupedParam?: string;
71
+ groupedParam?: string | undefined;
61
72
  /**
62
73
  * Serialization format for grouped mode
63
74
  * @default 'querystring'
64
75
  */
65
- format?: SerializationFormat;
76
+ format?: SerializationFormat | undefined;
77
+ /**
78
+ * Throw an issue instead of reporting it (#229), like TanStack Query's
79
+ * `throwOnError`. `true` throws every issue, and a function throws the
80
+ * issues for which it gives `true`. In the core, a thrown issue is not sent
81
+ * to `onParseError`, and the first one stops the call. In the React hook,
82
+ * each issue is still sent to `onParseError` one time, and the picked issue
83
+ * is thrown during render, so an error boundary catches it. This API does
84
+ * not change the address bar (only `createGridUrlSync` with `history` does),
85
+ * so remove the bad param before the boundary resets, or the same issue is
86
+ * thrown again.
87
+ *
88
+ * A function that throws makes the library throw that error.
89
+ * @default false
90
+ */
91
+ throwOnError?: boolean | ((issue: URLSyncError) => boolean) | undefined;
66
92
  }
93
+ /**
94
+ * The options with a value for each one: the config after the defaults are
95
+ * filled in. It has no `gridApi`: the grid is an argument of the functions
96
+ * that read it (#219).
97
+ *
98
+ * A caller can set an option to undefined (#258), so `Required` is not enough:
99
+ * it keeps the undefined.
100
+ */
101
+ export type ResolvedOptions = {
102
+ [K in Exclude<keyof AGGridUrlSyncConfig, 'throwOnError'>]-?: Exclude<AGGridUrlSyncConfig[K], undefined>;
103
+ } & {
104
+ /**
105
+ * Optional, so that ParseConfig and InternalConfig, which extend this type,
106
+ * get no new required key (#229). resolveOptions always sets it.
107
+ */
108
+ throwOnError?: Exclude<AGGridUrlSyncConfig['throwOnError'], undefined>;
109
+ };
67
110
  /**
68
111
  * Configuration for URL parsing and serialization operations that don't require GridApi.
69
- * Use this type when you only need to parse filters from URLs or serialize filters to URLs
70
- * without actually applying them to a grid.
71
112
  *
72
- * @example
73
- * ```typescript
74
- * // Parse filters without a grid instance
75
- * const config: ParseConfig = {
76
- * ...DEFAULT_CONFIG,
77
- * onParseError: (error) => console.error(error)
78
- * }
79
- * const filters = parseUrlFilters(url, config)
80
- * ```
113
+ * @deprecated The parse and serialize functions take a partial
114
+ * `AGGridUrlSyncConfig` and fill in the defaults, so you do not need this
115
+ * type. Use `ResolvedOptions` for a config with every option set. This type
116
+ * will be removed in 0.6.0.
81
117
  */
82
- export interface ParseConfig extends Required<AGGridUrlSyncConfig> {
118
+ export interface ParseConfig extends ResolvedOptions {
83
119
  /** AG Grid API instance - not read by parsing/serialization operations */
84
120
  gridApi?: AGGridApi | null;
85
121
  }
86
122
  /**
87
123
  * Internal configuration with all required fields and runtime dependencies
124
+ *
125
+ * @deprecated Give the grid API to `getFilterModel(gridApi, options)` and
126
+ * `applyFilterModel(filterState, gridApi, options)` as an argument. This type
127
+ * will be removed in 0.6.0.
88
128
  */
89
129
  export interface InternalConfig extends ParseConfig {
90
130
  /** AG Grid API instance for filter operations */
@@ -93,23 +133,23 @@ export interface InternalConfig extends ParseConfig {
93
133
  /**
94
134
  * Text-only filter operations for string-based filtering
95
135
  */
96
- export declare const TEXT_ONLY_OPERATIONS: readonly ['contains', 'notContains', 'startsWith', 'endsWith'];
136
+ export declare const TEXT_ONLY_OPERATIONS: readonly ["contains", "notContains", "startsWith", "endsWith"];
97
137
  /**
98
138
  * Number-only filter operations for numeric comparisons
99
139
  */
100
- export declare const NUMBER_ONLY_OPERATIONS: readonly ['lessThan', 'lessThanOrEqual', 'greaterThan', 'greaterThanOrEqual', 'inRange'];
140
+ export declare const NUMBER_ONLY_OPERATIONS: readonly ["lessThan", "lessThanOrEqual", "greaterThan", "greaterThanOrEqual", "inRange"];
101
141
  /**
102
142
  * Date-specific filter operations for temporal comparisons
103
143
  */
104
- export declare const DATE_ONLY_OPERATIONS: readonly ['dateBefore', 'dateBeforeOrEqual', 'dateAfter', 'dateAfterOrEqual', 'dateRange'];
144
+ export declare const DATE_ONLY_OPERATIONS: readonly ["dateBefore", "dateBeforeOrEqual", "dateAfter", "dateAfterOrEqual", "dateRange"];
105
145
  /**
106
146
  * Operations shared across multiple filter types
107
147
  */
108
- export declare const SHARED_OPERATIONS: readonly ['eq', 'notEqual', 'blank', 'notBlank'];
148
+ export declare const SHARED_OPERATIONS: readonly ["eq", "notEqual", "blank", "notBlank"];
109
149
  /**
110
150
  * All supported filter operations across text, number, and date filters
111
151
  */
112
- export type FilterOperation = (typeof TEXT_ONLY_OPERATIONS)[number] | (typeof NUMBER_ONLY_OPERATIONS)[number] | (typeof DATE_ONLY_OPERATIONS)[number] | (typeof SHARED_OPERATIONS)[number];
152
+ export type FilterOperation = Operator['id'];
113
153
  /**
114
154
  * Complete list of valid text filter operations
115
155
  */
@@ -119,29 +159,33 @@ export declare const TEXT_FILTER_OPERATIONS: readonly ["contains", "notContains"
119
159
  */
120
160
  export declare const NUMBER_FILTER_OPERATIONS: readonly ["lessThan", "lessThanOrEqual", "greaterThan", "greaterThanOrEqual", "inRange", "eq", "notEqual", "blank", "notBlank"];
121
161
  /**
122
- * Valid date filter operations as const array
162
+ * Valid date filter operations
123
163
  */
124
164
  export declare const DATE_FILTER_OPERATIONS: readonly ["dateBefore", "dateBeforeOrEqual", "dateAfter", "dateAfterOrEqual", "dateRange", "eq", "notEqual", "blank", "notBlank"];
125
165
  /**
126
166
  * Text-specific filter operations
127
167
  */
128
- export type TextFilterOperation = (typeof TEXT_FILTER_OPERATIONS)[number];
168
+ export type TextFilterOperation = OperationsFor<'text'>;
129
169
  /**
130
170
  * Number-specific filter operations
131
171
  */
132
- export type NumberFilterOperation = (typeof NUMBER_FILTER_OPERATIONS)[number];
172
+ export type NumberFilterOperation = OperationsFor<'number'>;
133
173
  /**
134
174
  * Date-specific filter operations
135
175
  */
136
- export type DateFilterOperation = (typeof DATE_FILTER_OPERATIONS)[number];
176
+ export type DateFilterOperation = OperationsFor<'date'>;
137
177
  /**
138
178
  * Range operations that require two values (from/to)
139
179
  */
140
- export type RangeOperation = 'inRange' | 'dateRange';
180
+ export type RangeOperation = Extract<Operator, {
181
+ arity: 2;
182
+ }>['id'];
141
183
  /**
142
184
  * Operations that don't require a filter value
143
185
  */
144
- export type BlankOperation = 'blank' | 'notBlank';
186
+ export type BlankOperation = Extract<Operator, {
187
+ arity: 0;
188
+ }>['id'];
145
189
  /**
146
190
  * Supported filter types
147
191
  */
@@ -224,35 +268,67 @@ export declare function isNumberFilter(filter: ColumnFilter): filter is NumberCo
224
268
  * Type guard to check if a filter is a date filter
225
269
  */
226
270
  export declare function isDateFilter(filter: ColumnFilter): filter is DateColumnFilter;
271
+ /**
272
+ * The reason for an issue (#220). Data issues are a filter that is dropped or
273
+ * not valid. The other codes are failures of an operation.
274
+ */
275
+ export type IssueCode = 'INVALID_PARAM' | 'INVALID_NUMBER' | 'INVALID_DATE' | 'VALUE_TOO_LONG' | 'INVALID_GROUPED' | 'TYPE_MISMATCH' | 'INVALID_URL' | 'GRID_ERROR' | 'NOT_READY' | 'VIEW_NOT_FOUND' | 'VIEW_NAME_REQUIRED' | 'STORAGE_UNAVAILABLE' | 'STORAGE_FULL' | 'STORAGE_WRITE_FAILED' | 'HISTORY_WRITE_FAILED' | 'UNKNOWN';
276
+ /**
277
+ * How bad an issue is (#221). A `'warning'` is a data issue: the sync goes on
278
+ * and the bad filter (or the bad URL) is left out. An `'error'` is an
279
+ * operation that failed.
280
+ */
281
+ export type IssueSeverity = 'error' | 'warning';
282
+ /** The code and the column of an issue, when the default is not right */
283
+ export interface IssueDetails {
284
+ code?: IssueCode | undefined;
285
+ column?: string | undefined;
286
+ }
227
287
  /**
228
288
  * Base error class for all URL sync operations
229
289
  */
230
290
  export declare class URLSyncError extends Error {
231
291
  readonly cause?: Error | undefined;
232
- constructor(message: string, cause?: Error | undefined);
292
+ /** The reason for the error */
293
+ readonly code: IssueCode;
294
+ /** The column of the filter, when it is known */
295
+ readonly column?: string;
296
+ constructor(message: string, cause?: Error | undefined, details?: IssueDetails);
297
+ /**
298
+ * How bad the issue is, from its code (#221). A getter, so it is not an own
299
+ * property and `toEqual` comparisons of errors do not change.
300
+ */
301
+ get severity(): IssueSeverity;
233
302
  }
234
303
  /**
235
304
  * Error thrown when filter validation fails
236
305
  */
237
306
  export declare class InvalidFilterError extends URLSyncError {
238
- constructor(message: string, cause?: Error);
307
+ constructor(message: string, cause?: Error, details?: IssueDetails);
239
308
  }
240
309
  /**
241
310
  * Error thrown when URL parsing fails
242
311
  */
243
312
  export declare class InvalidURLError extends URLSyncError {
244
- constructor(message: string, cause?: Error);
313
+ constructor(message: string, cause?: Error, details?: IssueDetails);
245
314
  }
246
315
  /**
247
316
  * Error thrown when date validation fails
248
317
  */
249
318
  export declare class InvalidDateError extends InvalidFilterError {
250
- constructor(message: string, cause?: Error);
319
+ constructor(message: string, cause?: Error, details?: IssueDetails);
251
320
  }
252
321
  /**
253
- * Error thrown when serialization/deserialization fails
254
- */
255
- export declare class InvalidSerializationError extends Error {
322
+ * Error thrown when serialization/deserialization fails.
323
+ *
324
+ * When the cause is an issue inside the grouped value (for example a value
325
+ * over maxValueLength), this error has the same code and column, so a filter
326
+ * gives the same code in both modes. `INVALID_GROUPED` means that the grouped
327
+ * value itself does not decode.
328
+ */
329
+ export declare class InvalidSerializationError extends URLSyncError {
330
+ /** The grouped format: querystring, json or base64 */
331
+ readonly format: string;
256
332
  constructor(format: string, cause: Error);
257
333
  }
258
334
  /**
@@ -286,6 +362,28 @@ export interface RawAGGridFilter {
286
362
  dateFrom?: string;
287
363
  dateTo?: string;
288
364
  }
365
+ /**
366
+ * The URL with the current filter state, and whether the grid has filters
367
+ */
368
+ export interface UrlState {
369
+ /** The URL with the current filter state and the page's own params */
370
+ url: string;
371
+ /** Whether the grid has filters that the library supports */
372
+ hasFilters: boolean;
373
+ /**
374
+ * The issues of this call (#229): filters that the grid read or the URL
375
+ * leaves out. The same objects also go to `onParseError`.
376
+ */
377
+ issues: readonly URLSyncError[];
378
+ }
379
+ /** The result of an apply method (#229) */
380
+ export interface ApplyResult {
381
+ /**
382
+ * The issues of this call: filters that were dropped. The same objects
383
+ * also go to `onParseError`.
384
+ */
385
+ issues: readonly URLSyncError[];
386
+ }
289
387
  /**
290
388
  * Grouped serialization result
291
389
  */
@@ -305,14 +403,14 @@ export interface FormatSerializer {
305
403
  * maxValueLength and onParseError. JSON and Base64 drop over-length text
306
404
  * values and report them through onParseError (see #213, #207).
307
405
  */
308
- serialize(filterState: FilterState, config?: ParseConfig): string;
406
+ serialize(filterState: FilterState, config?: ResolvedOptions): string;
309
407
  /**
310
408
  * Deserialize string to filter state
311
409
  * @param config - Caller's configuration. All serializers honour
312
410
  * maxValueLength and onParseError. JSON and Base64 drop over-length text
313
411
  * values and report them through onParseError (see #213, #207).
314
412
  */
315
- deserialize(value: string, config?: ParseConfig): FilterState;
413
+ deserialize(value: string, config?: ResolvedOptions): FilterState;
316
414
  /** Format identifier */
317
415
  format: SerializationFormat;
318
416
  }
@@ -329,20 +427,175 @@ export interface GroupedDetectionResult {
329
427
  /** The raw value if grouped */
330
428
  value?: string;
331
429
  }
430
+ /**
431
+ * Operation Mapping Strategy:
432
+ *
433
+ * We use three different naming conventions for better user experience:
434
+ *
435
+ * 1. URL Parameters: Short, clean names for URLs (e.g., 'eq', 'neq', 'before', 'after')
436
+ * 2. Internal Types: Consistent naming for our FilterOperation type
437
+ * 3. AG Grid Operations: Exact names expected by AG Grid API (e.g., 'equals', 'notEqual', 'lessThan')
438
+ *
439
+ * The operator table in `operators.ts` holds all three names for each
440
+ * operation (#217). The maps below are derived from it and kept only for
441
+ * callers that use them.
442
+ */
332
443
  /**
333
444
  * Maps URL operation names to internal operation types
445
+ * @deprecated Derived from the operator table. Will be removed in 0.6.0.
334
446
  */
335
447
  export declare const OPERATION_MAP: Record<string, FilterOperation>;
336
448
  /**
337
449
  * Maps internal operation types to URL operation names
450
+ * @deprecated Derived from the operator table. Will be removed in 0.6.0.
338
451
  */
339
452
  export declare const INTERNAL_TO_URL_OPERATION_MAP: Record<FilterOperation, string>;
340
453
  /**
341
454
  * Maps internal operation types to AG Grid operation names
455
+ * @deprecated Derived from the operator table. Will be removed in 0.6.0.
342
456
  */
343
457
  export declare const AG_GRID_OPERATION_NAMES: Record<string, string>;
344
458
  /**
345
- * Maps AG Grid operation names to internal operation types
459
+ * Maps AG Grid operation names to internal operation types.
460
+ *
461
+ * AG Grid uses one name for a number and a date operation, so this map is
462
+ * wrong for one of them: `lessThan` gives `dateBefore`. The last row in the
463
+ * operator table wins, as before.
464
+ * @deprecated The key needs the filter type too. Will be removed in 0.6.0.
346
465
  */
347
466
  export declare const REVERSE_AG_GRID_OPERATION_NAMES: Record<string, FilterOperation>;
467
+ declare const codecBrand: unique symbol;
468
+ /**
469
+ * How the filters are written in the URL. Make one with `individual()` or
470
+ * `grouped()`. It is opaque: only `createGridUrlSync` reads it.
471
+ */
472
+ export interface Codec {
473
+ readonly [codecBrand]: true;
474
+ }
475
+ /**
476
+ * The state of a sync. `'idle'`: there is no sync yet (the React hook uses
477
+ * it). `'active'`: the sync listens to the grid. `'destroyed'`: after
478
+ * `destroy()`, or after the grid is destroyed.
479
+ */
480
+ export type SyncStatus = 'idle' | 'active' | 'destroyed';
481
+ /**
482
+ * Where the work that found an issue started. `'url'`: the sync read the
483
+ * address bar itself (on create, or on `popstate`). `'grid'`: the sync read
484
+ * the grid after a filter change. `'api'`: a method that the app called.
485
+ */
486
+ export type IssueSource = 'url' | 'grid' | 'api';
487
+ /** The one argument of `onIssue` */
488
+ export interface IssueEvent {
489
+ issue: URLSyncError;
490
+ source: IssueSource;
491
+ }
492
+ /**
493
+ * The state of a sync, for `useSyncExternalStore` and for apps. The same
494
+ * object is returned until something in it changes.
495
+ */
496
+ export interface GridUrlSyncSnapshot {
497
+ readonly status: SyncStatus;
498
+ /** The URL with the grid's filters and the page's own params */
499
+ readonly url: string;
500
+ /** Whether the grid has filters that the library supports */
501
+ readonly hasFilters: boolean;
502
+ /** The issues of the latest apply and of the latest grid read */
503
+ readonly issues: readonly URLSyncError[];
504
+ /** The saved views, in save order. Empty with no `views` option. */
505
+ readonly views: readonly GridView[];
506
+ /** The id of the active saved view, or null */
507
+ readonly activeViewId: string | null;
508
+ }
509
+ /**
510
+ * How the sync writes a filter change to the address bar: a user edit, a
511
+ * view load or `applyFilters`. A URL apply writes nothing. `'none'` never
512
+ * writes. `'replace'` and `'push'` use `window.history`. A function gets the
513
+ * URL, for example `url => router.replace(url)`.
514
+ */
515
+ export type HistoryMode = 'none' | 'replace' | 'push' | ((url: string) => void);
516
+ /** The options of `createGridUrlSync` */
517
+ export interface GridUrlSyncOptions {
518
+ /** The AG Grid API */
519
+ api: AGGridApi;
520
+ /**
521
+ * How the filters are written in the URL
522
+ * @default individual()
523
+ */
524
+ codec?: Codec | undefined;
525
+ /**
526
+ * Maximum length for a filter value
527
+ * @default 200
528
+ */
529
+ maxValueLength?: number | undefined;
530
+ /** @default 'none' */
531
+ history?: HistoryMode | undefined;
532
+ /**
533
+ * Apply the URL when the browser's back or forward button changes it
534
+ * @default true when `history` is `'push'`, else false
535
+ */
536
+ popstate?: boolean | undefined;
537
+ /**
538
+ * Apply the address bar's filters when the sync is made. A URL with no
539
+ * filters keeps the grid's filters.
540
+ * @default true
541
+ */
542
+ applyUrlOnCreate?: boolean | undefined;
543
+ /**
544
+ * Throw an issue instead of reporting it, in the methods that the app
545
+ * calls (`applyUrl`, `applyFilters`, `getUrl`, `saveView`, `loadView`,
546
+ * `deleteView`). `createGridUrlSync` and the sync's own listeners never
547
+ * throw.
548
+ * @default false
549
+ */
550
+ throwOnError?: boolean | ((issue: URLSyncError) => boolean) | undefined;
551
+ /** Gets each issue one time */
552
+ onIssue?: ((event: IssueEvent) => void) | undefined;
553
+ /**
554
+ * The saved views. Give `createViewStore(key)` from
555
+ * `ag-grid-url-sync/view-storage`, or your own `ViewStore`. With no
556
+ * store, `views` is empty and the view methods do nothing. On create, a
557
+ * URL that holds the stored view restores it, else a URL with at least
558
+ * one filter wins, else the stored view is restored.
559
+ */
560
+ views?: ViewStore | undefined;
561
+ }
562
+ /** A sync between one grid and the URL */
563
+ export interface GridUrlSync {
564
+ /** The current state. The same object until something changes. */
565
+ getSnapshot(): GridUrlSyncSnapshot;
566
+ /** Calls the listener after each snapshot change. Returns unsubscribe. */
567
+ subscribe(listener: () => void): () => void;
568
+ /** Applies the filters of a URL (default: the address bar) to the grid */
569
+ applyUrl(url?: string): ApplyResult;
570
+ /**
571
+ * Applies filters to the grid as the app's own write. When it lands, the
572
+ * URL is written in the `history` mode, as for a user edit.
573
+ */
574
+ applyFilters(filters: FilterState): ApplyResult;
575
+ /** The URL with the grid's filters (default base: the address bar) */
576
+ getUrl(baseUrl?: string): string;
577
+ /**
578
+ * Saves the grid's filters as a view with this name, and makes it active.
579
+ * The same name updates that view. Returns null with no `views` option or
580
+ * when the save fails.
581
+ */
582
+ saveView(name: string): GridView | null;
583
+ /**
584
+ * Loads a saved view into the grid and makes it active. `null` clears the
585
+ * filters. When the view lands, the URL is written in the `history` mode,
586
+ * as for a user edit. An unknown id reports `VIEW_NOT_FOUND`.
587
+ */
588
+ loadView(id: string | null): void;
589
+ /**
590
+ * Deletes a saved view. If it is active and the grid still shows it, the
591
+ * grid is cleared.
592
+ */
593
+ deleteView(id: string): void;
594
+ /**
595
+ * Removes the listeners. The snapshot keeps the last state. After this,
596
+ * `applyUrl`, `applyFilters`, `saveView` and `loadView` report
597
+ * `NOT_READY`.
598
+ */
599
+ destroy(): void;
600
+ }
348
601
  export {};
@@ -1,55 +1,27 @@
1
- export const TEXT_ONLY_OPERATIONS = [
2
- 'contains',
3
- 'notContains',
4
- 'startsWith',
5
- 'endsWith'
6
- ];
7
- export const NUMBER_ONLY_OPERATIONS = [
8
- 'lessThan',
9
- 'lessThanOrEqual',
10
- 'greaterThan',
11
- 'greaterThanOrEqual',
12
- 'inRange'
13
- ];
14
- export const DATE_ONLY_OPERATIONS = [
15
- 'dateBefore',
16
- 'dateBeforeOrEqual',
17
- 'dateAfter',
18
- 'dateAfterOrEqual',
19
- 'dateRange'
20
- ];
21
- export const SHARED_OPERATIONS = [
22
- 'eq',
23
- 'notEqual',
24
- 'blank',
25
- 'notBlank'
26
- ];
27
- export const TEXT_FILTER_OPERATIONS = [
28
- ...TEXT_ONLY_OPERATIONS,
29
- ...SHARED_OPERATIONS
30
- ];
31
- export const NUMBER_FILTER_OPERATIONS = [
32
- ...NUMBER_ONLY_OPERATIONS,
33
- ...SHARED_OPERATIONS
34
- ];
35
- export const DATE_FILTER_OPERATIONS = [
36
- ...DATE_ONLY_OPERATIONS,
37
- ...SHARED_OPERATIONS
38
- ];
1
+ import { attachColumn } from './issue-columns.js';
2
+ import { DATA_ISSUE_CODES } from './data-issue-codes.js';
3
+ import { OPERATORS, operationsFor, operationsOnlyFor, sharedOperations, isOperationFor, operatorById } from './operators.js';
4
+ export const TEXT_ONLY_OPERATIONS = operationsOnlyFor('text');
5
+ export const NUMBER_ONLY_OPERATIONS = operationsOnlyFor('number');
6
+ export const DATE_ONLY_OPERATIONS = operationsOnlyFor('date');
7
+ export const SHARED_OPERATIONS = sharedOperations();
8
+ export const TEXT_FILTER_OPERATIONS = operationsFor('text');
9
+ export const NUMBER_FILTER_OPERATIONS = operationsFor('number');
10
+ export const DATE_FILTER_OPERATIONS = operationsFor('date');
39
11
  export function isTextFilterOperation(op) {
40
- return TEXT_FILTER_OPERATIONS.includes(op);
12
+ return isOperationFor(op, 'text');
41
13
  }
42
14
  export function isNumberFilterOperation(op) {
43
- return NUMBER_FILTER_OPERATIONS.includes(op);
15
+ return isOperationFor(op, 'number');
44
16
  }
45
17
  export function isDateFilterOperation(op) {
46
- return DATE_FILTER_OPERATIONS.includes(op);
18
+ return isOperationFor(op, 'date');
47
19
  }
48
20
  export function isRangeOperation(op) {
49
- return op === 'inRange' || op === 'dateRange';
21
+ return operatorById(op)?.arity === 2;
50
22
  }
51
23
  export function isBlankOperation(op) {
52
- return op === 'blank' || op === 'notBlank';
24
+ return operatorById(op)?.arity === 0;
53
25
  }
54
26
  export function isTextFilter(filter) {
55
27
  return filter.filterType === 'text';
@@ -62,90 +34,49 @@ export function isDateFilter(filter) {
62
34
  }
63
35
  export class URLSyncError extends Error {
64
36
  cause;
65
- constructor(message, cause) {
37
+ code;
38
+ constructor(message, cause, details = {}) {
66
39
  super(message);
67
40
  this.cause = cause;
68
41
  this.name = 'URLSyncError';
42
+ this.code = details.code ?? 'UNKNOWN';
43
+ if (details.column !== undefined)
44
+ attachColumn(this, details.column);
45
+ }
46
+ get severity() {
47
+ return DATA_ISSUE_CODES.has(this.code) ? 'warning' : 'error';
69
48
  }
70
49
  }
71
50
  export class InvalidFilterError extends URLSyncError {
72
- constructor(message, cause) {
73
- super(message, cause);
51
+ constructor(message, cause, details = {}) {
52
+ super(message, cause, { ...details, code: details.code ?? 'INVALID_PARAM' });
74
53
  this.name = 'InvalidFilterError';
75
54
  }
76
55
  }
77
56
  export class InvalidURLError extends URLSyncError {
78
- constructor(message, cause) {
79
- super(message, cause);
57
+ constructor(message, cause, details = {}) {
58
+ super(message, cause, { ...details, code: details.code ?? 'INVALID_URL' });
80
59
  this.name = 'InvalidURLError';
81
60
  }
82
61
  }
83
62
  export class InvalidDateError extends InvalidFilterError {
84
- constructor(message, cause) {
85
- super(message, cause);
63
+ constructor(message, cause, details = {}) {
64
+ super(message, cause, { ...details, code: details.code ?? 'INVALID_DATE' });
86
65
  this.name = 'InvalidDateError';
87
66
  }
88
67
  }
89
- export class InvalidSerializationError extends Error {
68
+ export class InvalidSerializationError extends URLSyncError {
69
+ format;
90
70
  constructor(format, cause) {
91
- super(`Invalid ${format} serialization: ${cause.message}`);
71
+ super(`Invalid ${format} serialization: ${cause.message}`, cause, cause instanceof URLSyncError
72
+ ? { code: cause.code, column: cause.column }
73
+ : { code: 'INVALID_GROUPED' });
92
74
  this.name = 'InvalidSerializationError';
93
- this.cause = cause;
75
+ this.format = format;
94
76
  }
95
77
  }
96
- const URL_SHORTHAND_MAPPINGS = {
97
- notEqual: 'neq',
98
- lessThan: 'lt',
99
- lessThanOrEqual: 'lte',
100
- greaterThan: 'gt',
101
- greaterThanOrEqual: 'gte',
102
- inRange: 'range',
103
- dateBefore: 'before',
104
- dateBeforeOrEqual: 'beforeEq',
105
- dateAfter: 'after',
106
- dateAfterOrEqual: 'afterEq',
107
- dateRange: 'daterange'
108
- };
109
- const AG_GRID_NAME_MAPPINGS = {
110
- eq: 'equals',
111
- dateBefore: 'lessThan',
112
- dateBeforeOrEqual: 'lessThanOrEqual',
113
- dateAfter: 'greaterThan',
114
- dateAfterOrEqual: 'greaterThanOrEqual',
115
- dateRange: 'inRange'
116
- };
117
- function createOperationMaps() {
118
- const allOperations = [
119
- ...TEXT_FILTER_OPERATIONS,
120
- ...NUMBER_FILTER_OPERATIONS,
121
- ...DATE_FILTER_OPERATIONS
122
- ];
123
- const uniqueOperations = [...new Set(allOperations)];
124
- const operationMap = {};
125
- const internalToUrlMap = {};
126
- uniqueOperations.forEach(operation => {
127
- const urlName = URL_SHORTHAND_MAPPINGS[operation] || operation;
128
- operationMap[urlName] = operation;
129
- internalToUrlMap[operation] = urlName;
130
- });
131
- const agGridMap = {};
132
- const reverseAgGridMap = {};
133
- uniqueOperations.forEach(operation => {
134
- const agGridName = AG_GRID_NAME_MAPPINGS[operation] ||
135
- operation;
136
- agGridMap[operation] = agGridName;
137
- reverseAgGridMap[agGridName] = operation;
138
- });
139
- return {
140
- operationMap,
141
- internalToUrlMap,
142
- agGridMap,
143
- reverseAgGridMap
144
- };
145
- }
146
- const mappings = createOperationMaps();
147
- export const OPERATION_MAP = mappings.operationMap;
148
- export const INTERNAL_TO_URL_OPERATION_MAP = mappings.internalToUrlMap;
149
- export const AG_GRID_OPERATION_NAMES = mappings.agGridMap;
150
- export const REVERSE_AG_GRID_OPERATION_NAMES = mappings.reverseAgGridMap;
78
+ export const OPERATION_MAP = Object.fromEntries(OPERATORS.map(op => [op.url, op.id]));
79
+ export const INTERNAL_TO_URL_OPERATION_MAP = Object.fromEntries(OPERATORS.map(op => [op.id, op.url]));
80
+ export const AG_GRID_OPERATION_NAMES = Object.fromEntries(OPERATORS.map(op => [op.id, op.agGrid]));
81
+ export const REVERSE_AG_GRID_OPERATION_NAMES = Object.fromEntries(OPERATORS.map(op => [op.agGrid, op.id]));
151
82
  //# sourceMappingURL=types.js.map