zustand-querystring 0.6.1 → 0.6.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.
- package/README.md +102 -38
- package/dist/{chunk-TQQUWVLF.mjs → chunk-M3ILBO2T.mjs} +19 -5
- package/dist/format/marked.js +19 -5
- package/dist/format/marked.mjs +1 -1
- package/dist/format/plain.js +32 -15
- package/dist/format/plain.mjs +32 -15
- package/dist/index.d.mts +20 -4
- package/dist/index.d.ts +20 -4
- package/dist/index.js +58 -14
- package/dist/index.mjs +40 -10
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -14,16 +14,16 @@ import { querystring } from 'zustand-querystring';
|
|
|
14
14
|
|
|
15
15
|
const useStore = create(
|
|
16
16
|
querystring(
|
|
17
|
-
|
|
17
|
+
set => ({
|
|
18
18
|
search: '',
|
|
19
19
|
page: 1,
|
|
20
|
-
setSearch:
|
|
21
|
-
setPage:
|
|
20
|
+
setSearch: search => set({ search }),
|
|
21
|
+
setPage: page => set({ page }),
|
|
22
22
|
}),
|
|
23
23
|
{
|
|
24
24
|
select: () => ({ search: true, page: true }),
|
|
25
|
-
}
|
|
26
|
-
)
|
|
25
|
+
},
|
|
26
|
+
),
|
|
27
27
|
);
|
|
28
28
|
// URL: ?search=hello&page=2
|
|
29
29
|
```
|
|
@@ -34,14 +34,15 @@ const useStore = create(
|
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
36
|
querystring(storeCreator, {
|
|
37
|
-
select: undefined,
|
|
38
|
-
key: false,
|
|
39
|
-
prefix: '',
|
|
40
|
-
format: marked,
|
|
41
|
-
|
|
37
|
+
select: undefined, // which fields to sync
|
|
38
|
+
key: false, // false | 'state'
|
|
39
|
+
prefix: '', // prefix for URL params
|
|
40
|
+
format: marked, // serialization format
|
|
41
|
+
map: undefined, // bidirectional store ↔ URL mapping
|
|
42
|
+
syncNull: false, // sync null values
|
|
42
43
|
syncUndefined: false, // sync undefined values
|
|
43
|
-
url: undefined,
|
|
44
|
-
})
|
|
44
|
+
url: undefined, // request URL for SSR
|
|
45
|
+
});
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
### `select`
|
|
@@ -50,14 +51,14 @@ Controls which state fields sync to URL. Receives pathname, returns object with
|
|
|
50
51
|
|
|
51
52
|
```ts
|
|
52
53
|
// All fields
|
|
53
|
-
select: () => ({ search: true, page: true, filters: true })
|
|
54
|
+
select: () => ({ search: true, page: true, filters: true });
|
|
54
55
|
|
|
55
56
|
// Route-based
|
|
56
|
-
select:
|
|
57
|
+
select: pathname => ({
|
|
57
58
|
search: true,
|
|
58
59
|
filters: pathname.startsWith('/products'),
|
|
59
60
|
adminSettings: pathname.startsWith('/admin'),
|
|
60
|
-
})
|
|
61
|
+
});
|
|
61
62
|
|
|
62
63
|
// Nested fields
|
|
63
64
|
select: () => ({
|
|
@@ -65,7 +66,7 @@ select: () => ({
|
|
|
65
66
|
name: true,
|
|
66
67
|
settings: { theme: true },
|
|
67
68
|
},
|
|
68
|
-
})
|
|
69
|
+
});
|
|
69
70
|
```
|
|
70
71
|
|
|
71
72
|
### `key`
|
|
@@ -84,8 +85,8 @@ select: () => ({
|
|
|
84
85
|
Adds prefix to all params. Use when multiple stores share URL.
|
|
85
86
|
|
|
86
87
|
```ts
|
|
87
|
-
querystring(storeA, { prefix: 'a_', select: () => ({ search: true }) })
|
|
88
|
-
querystring(storeB, { prefix: 'b_', select: () => ({ filter: true }) })
|
|
88
|
+
querystring(storeA, { prefix: 'a_', select: () => ({ search: true }) });
|
|
89
|
+
querystring(storeB, { prefix: 'b_', select: () => ({ filter: true }) });
|
|
89
90
|
// URL: ?a_search=hello&b_filter=active
|
|
90
91
|
```
|
|
91
92
|
|
|
@@ -93,12 +94,68 @@ querystring(storeB, { prefix: 'b_', select: () => ({ filter: true }) })
|
|
|
93
94
|
|
|
94
95
|
By default, `null` and `undefined` reset to initial state (removed from URL). Set to `true` to write them.
|
|
95
96
|
|
|
97
|
+
### `map`
|
|
98
|
+
|
|
99
|
+
Bidirectional mapping between store state and URL state. Use when the URL should represent a different shape than the store — e.g., a store keyed by dynamic IDs where the URL should only reflect the active entry.
|
|
100
|
+
|
|
101
|
+
`to` receives the selected state and pathname, returns the URL shape. `from` receives the parsed URL state and pathname, returns store state to merge. Types flow automatically: `from`'s parameter type is inferred from `to`'s return type.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
interface Store {
|
|
105
|
+
filtersByOperation: Record<string, { filters: string[] }>;
|
|
106
|
+
aggregationByOperation: Record<string, string>;
|
|
107
|
+
setFilters: (opId: string, filters: string[]) => void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const useStore = create<Store>()(
|
|
111
|
+
querystring(
|
|
112
|
+
set => ({
|
|
113
|
+
filtersByOperation: {},
|
|
114
|
+
aggregationByOperation: {},
|
|
115
|
+
setFilters: (opId, filters) =>
|
|
116
|
+
set(s => ({
|
|
117
|
+
filtersByOperation: { ...s.filtersByOperation, [opId]: { filters } },
|
|
118
|
+
})),
|
|
119
|
+
}),
|
|
120
|
+
{
|
|
121
|
+
key: 'state',
|
|
122
|
+
select: pathname => ({
|
|
123
|
+
filtersByOperation: pathname.startsWith('/view/'),
|
|
124
|
+
aggregationByOperation: pathname.startsWith('/view/'),
|
|
125
|
+
}),
|
|
126
|
+
map: {
|
|
127
|
+
to: (state, pathname) => {
|
|
128
|
+
const id = pathname.split('/').pop()!;
|
|
129
|
+
return {
|
|
130
|
+
filters: state.filtersByOperation?.[id]?.filters,
|
|
131
|
+
aggregation: state.aggregationByOperation?.[id],
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
from: (urlState, pathname) => {
|
|
135
|
+
// urlState is typed as { filters: string[] | undefined, aggregation: string | undefined }
|
|
136
|
+
const id = pathname.split('/').pop()!;
|
|
137
|
+
return {
|
|
138
|
+
...(urlState.filters
|
|
139
|
+
? { filtersByOperation: { [id]: { filters: urlState.filters } } }
|
|
140
|
+
: {}),
|
|
141
|
+
...(urlState.aggregation
|
|
142
|
+
? { aggregationByOperation: { [id]: urlState.aggregation } }
|
|
143
|
+
: {}),
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
// On /view/DAM_v1 → URL: ?state=filters@price_>10~,aggregation=daily
|
|
151
|
+
```
|
|
152
|
+
|
|
96
153
|
### `url`
|
|
97
154
|
|
|
98
155
|
For SSR, pass the request URL:
|
|
99
156
|
|
|
100
157
|
```ts
|
|
101
|
-
querystring(store, { url: request.url, select: () => ({ search: true }) })
|
|
158
|
+
querystring(store, { url: request.url, select: () => ({ search: true }) });
|
|
102
159
|
```
|
|
103
160
|
|
|
104
161
|
---
|
|
@@ -118,6 +175,7 @@ Only values **different from initial state** are written to URL:
|
|
|
118
175
|
```
|
|
119
176
|
|
|
120
177
|
Type handling:
|
|
178
|
+
|
|
121
179
|
- Objects: recursively diffed
|
|
122
180
|
- Arrays, Dates: compared as whole values
|
|
123
181
|
- Functions: never synced
|
|
@@ -128,18 +186,18 @@ Type handling:
|
|
|
128
186
|
|
|
129
187
|
Three built-in formats:
|
|
130
188
|
|
|
131
|
-
| Format
|
|
132
|
-
|
|
133
|
-
| `marked` | `count:5,tags@a,b~`
|
|
134
|
-
| `plain`
|
|
135
|
-
| `json`
|
|
189
|
+
| Format | Example Output |
|
|
190
|
+
| -------- | ---------------------------- |
|
|
191
|
+
| `marked` | `count:5,tags@a,b~` |
|
|
192
|
+
| `plain` | `count=5&tags=a&tags=b` |
|
|
193
|
+
| `json` | `count=5&tags=%5B%22a%22%5D` |
|
|
136
194
|
|
|
137
195
|
```ts
|
|
138
196
|
import { marked } from 'zustand-querystring/format/marked';
|
|
139
197
|
import { plain } from 'zustand-querystring/format/plain';
|
|
140
198
|
import { json } from 'zustand-querystring/format/json';
|
|
141
199
|
|
|
142
|
-
querystring(store, { format: plain })
|
|
200
|
+
querystring(store, { format: plain });
|
|
143
201
|
```
|
|
144
202
|
|
|
145
203
|
### Marked Format (default)
|
|
@@ -171,13 +229,13 @@ Dot notation for nesting, repeated keys for arrays.
|
|
|
171
229
|
import { createFormat } from 'zustand-querystring/format/plain';
|
|
172
230
|
|
|
173
231
|
const format = createFormat({
|
|
174
|
-
entrySeparator: ',',
|
|
175
|
-
nestingSeparator: '.',
|
|
176
|
-
arraySeparator: 'repeat',
|
|
232
|
+
entrySeparator: ',', // between entries in namespaced mode
|
|
233
|
+
nestingSeparator: '.', // for nested keys
|
|
234
|
+
arraySeparator: 'repeat', // 'repeat' for ?tags=a&tags=b, or ',' for ?tags=a,b
|
|
177
235
|
escapeChar: '_',
|
|
178
236
|
nullString: 'null',
|
|
179
237
|
undefinedString: 'undefined',
|
|
180
|
-
infinityString: 'Infinity',
|
|
238
|
+
infinityString: 'Infinity', // string representation of Infinity
|
|
181
239
|
negativeInfinityString: '-Infinity',
|
|
182
240
|
nanString: 'NaN',
|
|
183
241
|
});
|
|
@@ -196,7 +254,11 @@ URL-encoded JSON. No configuration.
|
|
|
196
254
|
Implement `QueryStringFormat`:
|
|
197
255
|
|
|
198
256
|
```ts
|
|
199
|
-
import type {
|
|
257
|
+
import type {
|
|
258
|
+
QueryStringFormat,
|
|
259
|
+
QueryStringParams,
|
|
260
|
+
ParseContext,
|
|
261
|
+
} from 'zustand-querystring';
|
|
200
262
|
|
|
201
263
|
const myFormat: QueryStringFormat = {
|
|
202
264
|
// For key: 'state' (namespaced mode)
|
|
@@ -224,10 +286,11 @@ const myFormat: QueryStringFormat = {
|
|
|
224
286
|
},
|
|
225
287
|
};
|
|
226
288
|
|
|
227
|
-
querystring(store, { format: myFormat })
|
|
289
|
+
querystring(store, { format: myFormat });
|
|
228
290
|
```
|
|
229
291
|
|
|
230
292
|
Types:
|
|
293
|
+
|
|
231
294
|
- `QueryStringParams` = `Record<string, string[]>` (values always arrays)
|
|
232
295
|
- `ctx.initialState` available for type coercion
|
|
233
296
|
|
|
@@ -240,14 +303,14 @@ Types:
|
|
|
240
303
|
```ts
|
|
241
304
|
const useStore = create(
|
|
242
305
|
querystring(
|
|
243
|
-
|
|
306
|
+
set => ({
|
|
244
307
|
query: '',
|
|
245
308
|
page: 1,
|
|
246
|
-
setQuery:
|
|
247
|
-
setPage:
|
|
309
|
+
setQuery: query => set({ query, page: 1 }), // reset page on new query
|
|
310
|
+
setPage: page => set({ page }),
|
|
248
311
|
}),
|
|
249
|
-
{ select: () => ({ query: true, page: true }) }
|
|
250
|
-
)
|
|
312
|
+
{ select: () => ({ query: true, page: true }) },
|
|
313
|
+
),
|
|
251
314
|
);
|
|
252
315
|
```
|
|
253
316
|
|
|
@@ -258,14 +321,14 @@ const useFilters = create(
|
|
|
258
321
|
querystring(filtersStore, {
|
|
259
322
|
prefix: 'f_',
|
|
260
323
|
select: () => ({ category: true, price: true }),
|
|
261
|
-
})
|
|
324
|
+
}),
|
|
262
325
|
);
|
|
263
326
|
|
|
264
327
|
const usePagination = create(
|
|
265
328
|
querystring(paginationStore, {
|
|
266
329
|
prefix: 'p_',
|
|
267
330
|
select: () => ({ page: true, limit: true }),
|
|
268
|
-
})
|
|
331
|
+
}),
|
|
269
332
|
);
|
|
270
333
|
// URL: ?f_category=shoes&f_price=100&p_page=2&p_limit=20
|
|
271
334
|
```
|
|
@@ -295,6 +358,7 @@ import { json } from 'zustand-querystring/format/json';
|
|
|
295
358
|
// Types
|
|
296
359
|
import type {
|
|
297
360
|
QueryStringOptions,
|
|
361
|
+
QueryStringMap,
|
|
298
362
|
QueryStringFormat,
|
|
299
363
|
QueryStringParams,
|
|
300
364
|
ParseContext,
|
|
@@ -38,7 +38,9 @@ function validateOptions(opts) {
|
|
|
38
38
|
seen.add(token);
|
|
39
39
|
}
|
|
40
40
|
if (seen.has(opts.datePrefix)) {
|
|
41
|
-
throw new Error(
|
|
41
|
+
throw new Error(
|
|
42
|
+
`datePrefix '${opts.datePrefix}' conflicts with another token`
|
|
43
|
+
);
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
function escapeRegex(str) {
|
|
@@ -50,7 +52,9 @@ function buildKeyStopPattern(opts) {
|
|
|
50
52
|
);
|
|
51
53
|
}
|
|
52
54
|
function buildValueStopPattern(opts) {
|
|
53
|
-
return new RegExp(
|
|
55
|
+
return new RegExp(
|
|
56
|
+
`[${escapeRegex(opts.separator)}${escapeRegex(opts.terminator)}]`
|
|
57
|
+
);
|
|
54
58
|
}
|
|
55
59
|
function buildKeyEscapePattern(opts) {
|
|
56
60
|
return new RegExp(
|
|
@@ -59,7 +63,10 @@ function buildKeyEscapePattern(opts) {
|
|
|
59
63
|
);
|
|
60
64
|
}
|
|
61
65
|
function buildValueEscapePattern(opts) {
|
|
62
|
-
return new RegExp(
|
|
66
|
+
return new RegExp(
|
|
67
|
+
`([${escapeRegex(opts.separator)}${escapeRegex(opts.terminator)}])`,
|
|
68
|
+
"g"
|
|
69
|
+
);
|
|
63
70
|
}
|
|
64
71
|
function buildDatePattern(opts) {
|
|
65
72
|
return new RegExp(`^${escapeRegex(opts.datePrefix)}-?\\d+$`);
|
|
@@ -94,7 +101,9 @@ function cleanResult(str, standalone, opts) {
|
|
|
94
101
|
while (str.endsWith(opts.terminator)) {
|
|
95
102
|
str = str.slice(0, -opts.terminator.length);
|
|
96
103
|
}
|
|
97
|
-
const datePattern = new RegExp(
|
|
104
|
+
const datePattern = new RegExp(
|
|
105
|
+
`^${escapeRegex(opts.typeString)}${escapeRegex(opts.datePrefix)}-?\\d+$`
|
|
106
|
+
);
|
|
98
107
|
if (standalone && datePattern.test(str)) {
|
|
99
108
|
return str.slice(opts.typeString.length);
|
|
100
109
|
}
|
|
@@ -312,7 +321,12 @@ function parse(input, standalone = false, options = {}) {
|
|
|
312
321
|
}
|
|
313
322
|
return result;
|
|
314
323
|
} else {
|
|
315
|
-
const escapedMarkers = [
|
|
324
|
+
const escapedMarkers = [
|
|
325
|
+
opts.typeString,
|
|
326
|
+
opts.typePrimitive,
|
|
327
|
+
opts.typeArray,
|
|
328
|
+
opts.typeObject
|
|
329
|
+
].map(escapeRegex).join("");
|
|
316
330
|
const escapedEscape = escapeRegex(opts.escape);
|
|
317
331
|
const escapedSeparator = escapeRegex(opts.separator);
|
|
318
332
|
const escapedTerminator = escapeRegex(opts.terminator);
|
package/dist/format/marked.js
CHANGED
|
@@ -65,7 +65,9 @@ function validateOptions(opts) {
|
|
|
65
65
|
seen.add(token);
|
|
66
66
|
}
|
|
67
67
|
if (seen.has(opts.datePrefix)) {
|
|
68
|
-
throw new Error(
|
|
68
|
+
throw new Error(
|
|
69
|
+
`datePrefix '${opts.datePrefix}' conflicts with another token`
|
|
70
|
+
);
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
function escapeRegex(str) {
|
|
@@ -77,7 +79,9 @@ function buildKeyStopPattern(opts) {
|
|
|
77
79
|
);
|
|
78
80
|
}
|
|
79
81
|
function buildValueStopPattern(opts) {
|
|
80
|
-
return new RegExp(
|
|
82
|
+
return new RegExp(
|
|
83
|
+
`[${escapeRegex(opts.separator)}${escapeRegex(opts.terminator)}]`
|
|
84
|
+
);
|
|
81
85
|
}
|
|
82
86
|
function buildKeyEscapePattern(opts) {
|
|
83
87
|
return new RegExp(
|
|
@@ -86,7 +90,10 @@ function buildKeyEscapePattern(opts) {
|
|
|
86
90
|
);
|
|
87
91
|
}
|
|
88
92
|
function buildValueEscapePattern(opts) {
|
|
89
|
-
return new RegExp(
|
|
93
|
+
return new RegExp(
|
|
94
|
+
`([${escapeRegex(opts.separator)}${escapeRegex(opts.terminator)}])`,
|
|
95
|
+
"g"
|
|
96
|
+
);
|
|
90
97
|
}
|
|
91
98
|
function buildDatePattern(opts) {
|
|
92
99
|
return new RegExp(`^${escapeRegex(opts.datePrefix)}-?\\d+$`);
|
|
@@ -121,7 +128,9 @@ function cleanResult(str, standalone, opts) {
|
|
|
121
128
|
while (str.endsWith(opts.terminator)) {
|
|
122
129
|
str = str.slice(0, -opts.terminator.length);
|
|
123
130
|
}
|
|
124
|
-
const datePattern = new RegExp(
|
|
131
|
+
const datePattern = new RegExp(
|
|
132
|
+
`^${escapeRegex(opts.typeString)}${escapeRegex(opts.datePrefix)}-?\\d+$`
|
|
133
|
+
);
|
|
125
134
|
if (standalone && datePattern.test(str)) {
|
|
126
135
|
return str.slice(opts.typeString.length);
|
|
127
136
|
}
|
|
@@ -339,7 +348,12 @@ function parse(input, standalone = false, options = {}) {
|
|
|
339
348
|
}
|
|
340
349
|
return result;
|
|
341
350
|
} else {
|
|
342
|
-
const escapedMarkers = [
|
|
351
|
+
const escapedMarkers = [
|
|
352
|
+
opts.typeString,
|
|
353
|
+
opts.typePrimitive,
|
|
354
|
+
opts.typeArray,
|
|
355
|
+
opts.typeObject
|
|
356
|
+
].map(escapeRegex).join("");
|
|
343
357
|
const escapedEscape = escapeRegex(opts.escape);
|
|
344
358
|
const escapedSeparator = escapeRegex(opts.separator);
|
|
345
359
|
const escapedTerminator = escapeRegex(opts.terminator);
|
package/dist/format/marked.mjs
CHANGED
package/dist/format/plain.js
CHANGED
|
@@ -40,16 +40,24 @@ function resolveOptions(opts = {}) {
|
|
|
40
40
|
function validateOptions(opts) {
|
|
41
41
|
const { entrySep, nestingSep, arraySep, escape: escape2 } = opts;
|
|
42
42
|
if (entrySep === nestingSep) {
|
|
43
|
-
throw new Error(
|
|
43
|
+
throw new Error(
|
|
44
|
+
`entrySeparator and nestingSeparator cannot be the same: '${entrySep}'`
|
|
45
|
+
);
|
|
44
46
|
}
|
|
45
47
|
if (escape2 === entrySep || escape2 === nestingSep) {
|
|
46
|
-
throw new Error(
|
|
48
|
+
throw new Error(
|
|
49
|
+
`escapeChar cannot be the same as a separator: '${escape2}'`
|
|
50
|
+
);
|
|
47
51
|
}
|
|
48
52
|
if (arraySep !== "repeat" && arraySep === nestingSep) {
|
|
49
|
-
throw new Error(
|
|
53
|
+
throw new Error(
|
|
54
|
+
`arraySeparator cannot be the same as nestingSeparator: '${arraySep}'`
|
|
55
|
+
);
|
|
50
56
|
}
|
|
51
57
|
if (arraySep !== "repeat" && arraySep === escape2) {
|
|
52
|
-
throw new Error(
|
|
58
|
+
throw new Error(
|
|
59
|
+
`arraySeparator cannot be the same as escapeChar: '${arraySep}'`
|
|
60
|
+
);
|
|
53
61
|
}
|
|
54
62
|
if (entrySep.length === 0 || nestingSep.length === 0 || escape2.length === 0) {
|
|
55
63
|
throw new Error("Separators and escape character cannot be empty");
|
|
@@ -59,12 +67,7 @@ function validateOptions(opts) {
|
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
function encodePreservingMarkers(str, opts) {
|
|
62
|
-
const markers = /* @__PURE__ */ new Set([
|
|
63
|
-
opts.entrySep,
|
|
64
|
-
opts.nestingSep,
|
|
65
|
-
opts.escape,
|
|
66
|
-
"="
|
|
67
|
-
]);
|
|
70
|
+
const markers = /* @__PURE__ */ new Set([opts.entrySep, opts.nestingSep, opts.escape, "="]);
|
|
68
71
|
if (opts.arraySep !== "repeat") {
|
|
69
72
|
markers.add(opts.arraySep);
|
|
70
73
|
}
|
|
@@ -180,7 +183,9 @@ function unescape(str, esc, chars) {
|
|
|
180
183
|
while (i < str.length) {
|
|
181
184
|
if (str.substring(i, i + esc.length) === esc) {
|
|
182
185
|
const nextPos = i + esc.length;
|
|
183
|
-
const isEscapeSequence = chars.some(
|
|
186
|
+
const isEscapeSequence = chars.some(
|
|
187
|
+
(c) => str.substring(nextPos, nextPos + c.length) === c
|
|
188
|
+
);
|
|
184
189
|
if (isEscapeSequence && nextPos < str.length) {
|
|
185
190
|
i = nextPos;
|
|
186
191
|
for (const special of chars) {
|
|
@@ -311,7 +316,11 @@ function flatten(obj, prefix, opts) {
|
|
|
311
316
|
Object.assign(result, flatten(item, indexedKey, opts));
|
|
312
317
|
} else {
|
|
313
318
|
const serialized = serializeValue(item, opts);
|
|
314
|
-
const escapedValue = escape(
|
|
319
|
+
const escapedValue = escape(
|
|
320
|
+
serialized,
|
|
321
|
+
valueEscapeChars,
|
|
322
|
+
opts.escape
|
|
323
|
+
);
|
|
315
324
|
result[indexedKey] = [escapedValue];
|
|
316
325
|
}
|
|
317
326
|
}
|
|
@@ -363,7 +372,11 @@ function unflatten(entries, initialState, opts) {
|
|
|
363
372
|
continue;
|
|
364
373
|
}
|
|
365
374
|
const elementHint = isArrayHint ? hint[0] : void 0;
|
|
366
|
-
setAtPath(
|
|
375
|
+
setAtPath(
|
|
376
|
+
result,
|
|
377
|
+
path,
|
|
378
|
+
values.map((v) => parseValue(v, elementHint, opts))
|
|
379
|
+
);
|
|
367
380
|
}
|
|
368
381
|
return result;
|
|
369
382
|
}
|
|
@@ -399,10 +412,14 @@ function stringifyNamespaced(state, opts) {
|
|
|
399
412
|
for (const [key, values] of Object.entries(entries)) {
|
|
400
413
|
if (opts.arraySep === "repeat") {
|
|
401
414
|
for (const value of values) {
|
|
402
|
-
parts.push(
|
|
415
|
+
parts.push(
|
|
416
|
+
encodePreservingMarkers(key, opts) + "=" + encodePreservingMarkers(value, opts)
|
|
417
|
+
);
|
|
403
418
|
}
|
|
404
419
|
} else {
|
|
405
|
-
parts.push(
|
|
420
|
+
parts.push(
|
|
421
|
+
encodePreservingMarkers(key, opts) + "=" + encodePreservingMarkers(values.join(opts.arraySep), opts)
|
|
422
|
+
);
|
|
406
423
|
}
|
|
407
424
|
}
|
|
408
425
|
return parts.join(opts.entrySep);
|
package/dist/format/plain.mjs
CHANGED
|
@@ -16,16 +16,24 @@ function resolveOptions(opts = {}) {
|
|
|
16
16
|
function validateOptions(opts) {
|
|
17
17
|
const { entrySep, nestingSep, arraySep, escape: escape2 } = opts;
|
|
18
18
|
if (entrySep === nestingSep) {
|
|
19
|
-
throw new Error(
|
|
19
|
+
throw new Error(
|
|
20
|
+
`entrySeparator and nestingSeparator cannot be the same: '${entrySep}'`
|
|
21
|
+
);
|
|
20
22
|
}
|
|
21
23
|
if (escape2 === entrySep || escape2 === nestingSep) {
|
|
22
|
-
throw new Error(
|
|
24
|
+
throw new Error(
|
|
25
|
+
`escapeChar cannot be the same as a separator: '${escape2}'`
|
|
26
|
+
);
|
|
23
27
|
}
|
|
24
28
|
if (arraySep !== "repeat" && arraySep === nestingSep) {
|
|
25
|
-
throw new Error(
|
|
29
|
+
throw new Error(
|
|
30
|
+
`arraySeparator cannot be the same as nestingSeparator: '${arraySep}'`
|
|
31
|
+
);
|
|
26
32
|
}
|
|
27
33
|
if (arraySep !== "repeat" && arraySep === escape2) {
|
|
28
|
-
throw new Error(
|
|
34
|
+
throw new Error(
|
|
35
|
+
`arraySeparator cannot be the same as escapeChar: '${arraySep}'`
|
|
36
|
+
);
|
|
29
37
|
}
|
|
30
38
|
if (entrySep.length === 0 || nestingSep.length === 0 || escape2.length === 0) {
|
|
31
39
|
throw new Error("Separators and escape character cannot be empty");
|
|
@@ -35,12 +43,7 @@ function validateOptions(opts) {
|
|
|
35
43
|
}
|
|
36
44
|
}
|
|
37
45
|
function encodePreservingMarkers(str, opts) {
|
|
38
|
-
const markers = /* @__PURE__ */ new Set([
|
|
39
|
-
opts.entrySep,
|
|
40
|
-
opts.nestingSep,
|
|
41
|
-
opts.escape,
|
|
42
|
-
"="
|
|
43
|
-
]);
|
|
46
|
+
const markers = /* @__PURE__ */ new Set([opts.entrySep, opts.nestingSep, opts.escape, "="]);
|
|
44
47
|
if (opts.arraySep !== "repeat") {
|
|
45
48
|
markers.add(opts.arraySep);
|
|
46
49
|
}
|
|
@@ -156,7 +159,9 @@ function unescape(str, esc, chars) {
|
|
|
156
159
|
while (i < str.length) {
|
|
157
160
|
if (str.substring(i, i + esc.length) === esc) {
|
|
158
161
|
const nextPos = i + esc.length;
|
|
159
|
-
const isEscapeSequence = chars.some(
|
|
162
|
+
const isEscapeSequence = chars.some(
|
|
163
|
+
(c) => str.substring(nextPos, nextPos + c.length) === c
|
|
164
|
+
);
|
|
160
165
|
if (isEscapeSequence && nextPos < str.length) {
|
|
161
166
|
i = nextPos;
|
|
162
167
|
for (const special of chars) {
|
|
@@ -287,7 +292,11 @@ function flatten(obj, prefix, opts) {
|
|
|
287
292
|
Object.assign(result, flatten(item, indexedKey, opts));
|
|
288
293
|
} else {
|
|
289
294
|
const serialized = serializeValue(item, opts);
|
|
290
|
-
const escapedValue = escape(
|
|
295
|
+
const escapedValue = escape(
|
|
296
|
+
serialized,
|
|
297
|
+
valueEscapeChars,
|
|
298
|
+
opts.escape
|
|
299
|
+
);
|
|
291
300
|
result[indexedKey] = [escapedValue];
|
|
292
301
|
}
|
|
293
302
|
}
|
|
@@ -339,7 +348,11 @@ function unflatten(entries, initialState, opts) {
|
|
|
339
348
|
continue;
|
|
340
349
|
}
|
|
341
350
|
const elementHint = isArrayHint ? hint[0] : void 0;
|
|
342
|
-
setAtPath(
|
|
351
|
+
setAtPath(
|
|
352
|
+
result,
|
|
353
|
+
path,
|
|
354
|
+
values.map((v) => parseValue(v, elementHint, opts))
|
|
355
|
+
);
|
|
343
356
|
}
|
|
344
357
|
return result;
|
|
345
358
|
}
|
|
@@ -375,10 +388,14 @@ function stringifyNamespaced(state, opts) {
|
|
|
375
388
|
for (const [key, values] of Object.entries(entries)) {
|
|
376
389
|
if (opts.arraySep === "repeat") {
|
|
377
390
|
for (const value of values) {
|
|
378
|
-
parts.push(
|
|
391
|
+
parts.push(
|
|
392
|
+
encodePreservingMarkers(key, opts) + "=" + encodePreservingMarkers(value, opts)
|
|
393
|
+
);
|
|
379
394
|
}
|
|
380
395
|
} else {
|
|
381
|
-
parts.push(
|
|
396
|
+
parts.push(
|
|
397
|
+
encodePreservingMarkers(key, opts) + "=" + encodePreservingMarkers(values.join(opts.arraySep), opts)
|
|
398
|
+
);
|
|
382
399
|
}
|
|
383
400
|
}
|
|
384
401
|
return parts.join(opts.entrySep);
|
package/dist/index.d.mts
CHANGED
|
@@ -3,6 +3,10 @@ import { StoreMutatorIdentifier, StateCreator } from 'zustand/vanilla';
|
|
|
3
3
|
type DeepSelect<T> = T extends object ? {
|
|
4
4
|
[P in keyof T]?: DeepSelect<T[P]> | boolean;
|
|
5
5
|
} : boolean;
|
|
6
|
+
/** Maps T to only the fields marked as selected in S. `false` excludes, everything else includes. */
|
|
7
|
+
type ApplySelect<T, S> = [S] extends [false] ? never : S extends object ? T extends object ? {
|
|
8
|
+
[P in keyof S & keyof T as [ApplySelect<T[P], S[P]>] extends [never] ? never : P]: ApplySelect<T[P], S[P]>;
|
|
9
|
+
} : never : T;
|
|
6
10
|
interface QueryStringParam {
|
|
7
11
|
key: string;
|
|
8
12
|
value: string | string[];
|
|
@@ -32,16 +36,28 @@ interface QueryStringFormatStandalone {
|
|
|
32
36
|
type QueryStringFormat = QueryStringFormatNamespaced & QueryStringFormatStandalone;
|
|
33
37
|
/** Conditional format type based on key option */
|
|
34
38
|
type QueryStringFormatFor<K extends string | false> = K extends false ? QueryStringFormatStandalone : QueryStringFormatNamespaced;
|
|
35
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Bidirectional mapping between store state and URL state.
|
|
41
|
+
* `to` runs before serialization (store → URL), `from` after deserialization (URL → store).
|
|
42
|
+
* S is inferred from `select` via `ApplySelect` — only selected fields are visible.
|
|
43
|
+
* U is inferred from `to`'s return type and flows into `from`'s parameter.
|
|
44
|
+
*/
|
|
45
|
+
interface QueryStringMap<S, U extends object = Record<string, unknown>> {
|
|
46
|
+
to: (state: S, pathname: string) => U;
|
|
47
|
+
from: (urlState: U, pathname: string) => Partial<S>;
|
|
48
|
+
}
|
|
49
|
+
interface QueryStringOptions<T, K extends string | false = string | false, S extends DeepSelect<T> = DeepSelect<T>, U extends object = Record<string, unknown>> {
|
|
36
50
|
url?: string;
|
|
37
|
-
select?: (pathname: string) =>
|
|
51
|
+
select?: (pathname: string) => S;
|
|
38
52
|
key?: K;
|
|
39
53
|
prefix?: string;
|
|
40
54
|
format?: QueryStringFormatFor<K>;
|
|
41
55
|
syncNull?: boolean;
|
|
42
56
|
syncUndefined?: boolean;
|
|
57
|
+
/** Bidirectional mapping between store state shape and URL state shape. */
|
|
58
|
+
map?: QueryStringMap<ApplySelect<T, S>, U>;
|
|
43
59
|
}
|
|
44
|
-
type QueryString = <T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, Mps, Mcs>, options?: QueryStringOptions<T>) => StateCreator<T, Mps, Mcs>;
|
|
60
|
+
type QueryString = <T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = [], const S extends DeepSelect<T> = DeepSelect<T>, U extends object = Record<string, unknown>>(initializer: StateCreator<T, Mps, Mcs>, options?: QueryStringOptions<T, string | false, S, U>) => StateCreator<T, Mps, Mcs>;
|
|
45
61
|
declare const querystring: QueryString;
|
|
46
62
|
|
|
47
|
-
export { type ParseContext, type QueryStringFormat, type QueryStringFormatFor, type QueryStringFormatNamespaced, type QueryStringFormatStandalone, type QueryStringOptions, type QueryStringParam, type QueryStringParams, querystring };
|
|
63
|
+
export { type ParseContext, type QueryStringFormat, type QueryStringFormatFor, type QueryStringFormatNamespaced, type QueryStringFormatStandalone, type QueryStringMap, type QueryStringOptions, type QueryStringParam, type QueryStringParams, querystring };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ import { StoreMutatorIdentifier, StateCreator } from 'zustand/vanilla';
|
|
|
3
3
|
type DeepSelect<T> = T extends object ? {
|
|
4
4
|
[P in keyof T]?: DeepSelect<T[P]> | boolean;
|
|
5
5
|
} : boolean;
|
|
6
|
+
/** Maps T to only the fields marked as selected in S. `false` excludes, everything else includes. */
|
|
7
|
+
type ApplySelect<T, S> = [S] extends [false] ? never : S extends object ? T extends object ? {
|
|
8
|
+
[P in keyof S & keyof T as [ApplySelect<T[P], S[P]>] extends [never] ? never : P]: ApplySelect<T[P], S[P]>;
|
|
9
|
+
} : never : T;
|
|
6
10
|
interface QueryStringParam {
|
|
7
11
|
key: string;
|
|
8
12
|
value: string | string[];
|
|
@@ -32,16 +36,28 @@ interface QueryStringFormatStandalone {
|
|
|
32
36
|
type QueryStringFormat = QueryStringFormatNamespaced & QueryStringFormatStandalone;
|
|
33
37
|
/** Conditional format type based on key option */
|
|
34
38
|
type QueryStringFormatFor<K extends string | false> = K extends false ? QueryStringFormatStandalone : QueryStringFormatNamespaced;
|
|
35
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Bidirectional mapping between store state and URL state.
|
|
41
|
+
* `to` runs before serialization (store → URL), `from` after deserialization (URL → store).
|
|
42
|
+
* S is inferred from `select` via `ApplySelect` — only selected fields are visible.
|
|
43
|
+
* U is inferred from `to`'s return type and flows into `from`'s parameter.
|
|
44
|
+
*/
|
|
45
|
+
interface QueryStringMap<S, U extends object = Record<string, unknown>> {
|
|
46
|
+
to: (state: S, pathname: string) => U;
|
|
47
|
+
from: (urlState: U, pathname: string) => Partial<S>;
|
|
48
|
+
}
|
|
49
|
+
interface QueryStringOptions<T, K extends string | false = string | false, S extends DeepSelect<T> = DeepSelect<T>, U extends object = Record<string, unknown>> {
|
|
36
50
|
url?: string;
|
|
37
|
-
select?: (pathname: string) =>
|
|
51
|
+
select?: (pathname: string) => S;
|
|
38
52
|
key?: K;
|
|
39
53
|
prefix?: string;
|
|
40
54
|
format?: QueryStringFormatFor<K>;
|
|
41
55
|
syncNull?: boolean;
|
|
42
56
|
syncUndefined?: boolean;
|
|
57
|
+
/** Bidirectional mapping between store state shape and URL state shape. */
|
|
58
|
+
map?: QueryStringMap<ApplySelect<T, S>, U>;
|
|
43
59
|
}
|
|
44
|
-
type QueryString = <T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, Mps, Mcs>, options?: QueryStringOptions<T>) => StateCreator<T, Mps, Mcs>;
|
|
60
|
+
type QueryString = <T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = [], const S extends DeepSelect<T> = DeepSelect<T>, U extends object = Record<string, unknown>>(initializer: StateCreator<T, Mps, Mcs>, options?: QueryStringOptions<T, string | false, S, U>) => StateCreator<T, Mps, Mcs>;
|
|
45
61
|
declare const querystring: QueryString;
|
|
46
62
|
|
|
47
|
-
export { type ParseContext, type QueryStringFormat, type QueryStringFormatFor, type QueryStringFormatNamespaced, type QueryStringFormatStandalone, type QueryStringOptions, type QueryStringParam, type QueryStringParams, querystring };
|
|
63
|
+
export { type ParseContext, type QueryStringFormat, type QueryStringFormatFor, type QueryStringFormatNamespaced, type QueryStringFormatStandalone, type QueryStringMap, type QueryStringOptions, type QueryStringParam, type QueryStringParams, querystring };
|
package/dist/index.js
CHANGED
|
@@ -66,7 +66,9 @@ function validateOptions(opts) {
|
|
|
66
66
|
seen.add(token);
|
|
67
67
|
}
|
|
68
68
|
if (seen.has(opts.datePrefix)) {
|
|
69
|
-
throw new Error(
|
|
69
|
+
throw new Error(
|
|
70
|
+
`datePrefix '${opts.datePrefix}' conflicts with another token`
|
|
71
|
+
);
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
function escapeRegex(str) {
|
|
@@ -78,7 +80,9 @@ function buildKeyStopPattern(opts) {
|
|
|
78
80
|
);
|
|
79
81
|
}
|
|
80
82
|
function buildValueStopPattern(opts) {
|
|
81
|
-
return new RegExp(
|
|
83
|
+
return new RegExp(
|
|
84
|
+
`[${escapeRegex(opts.separator)}${escapeRegex(opts.terminator)}]`
|
|
85
|
+
);
|
|
82
86
|
}
|
|
83
87
|
function buildKeyEscapePattern(opts) {
|
|
84
88
|
return new RegExp(
|
|
@@ -87,7 +91,10 @@ function buildKeyEscapePattern(opts) {
|
|
|
87
91
|
);
|
|
88
92
|
}
|
|
89
93
|
function buildValueEscapePattern(opts) {
|
|
90
|
-
return new RegExp(
|
|
94
|
+
return new RegExp(
|
|
95
|
+
`([${escapeRegex(opts.separator)}${escapeRegex(opts.terminator)}])`,
|
|
96
|
+
"g"
|
|
97
|
+
);
|
|
91
98
|
}
|
|
92
99
|
function buildDatePattern(opts) {
|
|
93
100
|
return new RegExp(`^${escapeRegex(opts.datePrefix)}-?\\d+$`);
|
|
@@ -122,7 +129,9 @@ function cleanResult(str, standalone, opts) {
|
|
|
122
129
|
while (str.endsWith(opts.terminator)) {
|
|
123
130
|
str = str.slice(0, -opts.terminator.length);
|
|
124
131
|
}
|
|
125
|
-
const datePattern = new RegExp(
|
|
132
|
+
const datePattern = new RegExp(
|
|
133
|
+
`^${escapeRegex(opts.typeString)}${escapeRegex(opts.datePrefix)}-?\\d+$`
|
|
134
|
+
);
|
|
126
135
|
if (standalone && datePattern.test(str)) {
|
|
127
136
|
return str.slice(opts.typeString.length);
|
|
128
137
|
}
|
|
@@ -340,7 +349,12 @@ function parse(input, standalone = false, options = {}) {
|
|
|
340
349
|
}
|
|
341
350
|
return result;
|
|
342
351
|
} else {
|
|
343
|
-
const escapedMarkers = [
|
|
352
|
+
const escapedMarkers = [
|
|
353
|
+
opts.typeString,
|
|
354
|
+
opts.typePrimitive,
|
|
355
|
+
opts.typeArray,
|
|
356
|
+
opts.typeObject
|
|
357
|
+
].map(escapeRegex).join("");
|
|
344
358
|
const escapedEscape = escapeRegex(opts.escape);
|
|
345
359
|
const escapedSeparator = escapeRegex(opts.separator);
|
|
346
360
|
const escapedTerminator = escapeRegex(opts.terminator);
|
|
@@ -487,13 +501,25 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
487
501
|
}
|
|
488
502
|
return state != null ? state : {};
|
|
489
503
|
};
|
|
504
|
+
const map = defaultedOptions.map;
|
|
505
|
+
const applyMapTo = (state, pathname) => {
|
|
506
|
+
return map ? map.to(state, pathname) : state;
|
|
507
|
+
};
|
|
508
|
+
const applyMapFrom = (urlState, pathname) => {
|
|
509
|
+
return map ? map.from(urlState, pathname) : urlState;
|
|
510
|
+
};
|
|
490
511
|
const initialize = (url, initialState) => {
|
|
491
512
|
try {
|
|
492
|
-
const
|
|
513
|
+
const mappedInitial = applyMapTo(
|
|
514
|
+
getSelectedState(initialState, url.pathname),
|
|
515
|
+
url.pathname
|
|
516
|
+
);
|
|
517
|
+
const stateFromUrl = getStateFromUrl(url, mappedInitial);
|
|
493
518
|
if (!stateFromUrl) {
|
|
494
519
|
return initialState;
|
|
495
520
|
}
|
|
496
|
-
const
|
|
521
|
+
const storeState = applyMapFrom(stateFromUrl, url.pathname);
|
|
522
|
+
const selected = getSelectedState(storeState, url.pathname);
|
|
497
523
|
const merged = (0, import_lodash_es.mergeWith)(
|
|
498
524
|
{},
|
|
499
525
|
initialState,
|
|
@@ -521,12 +547,20 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
521
547
|
api
|
|
522
548
|
);
|
|
523
549
|
let previouslyManagedKeys = /* @__PURE__ */ new Set();
|
|
550
|
+
const getMappedInitial = (pathname) => {
|
|
551
|
+
if (defaultedOptions.map) {
|
|
552
|
+
return applyMapTo(getSelectedState(initialState, pathname), pathname);
|
|
553
|
+
}
|
|
554
|
+
return initialState;
|
|
555
|
+
};
|
|
524
556
|
const setQuery = () => {
|
|
525
557
|
const url = new URL(window.location.href);
|
|
526
558
|
const selectedState = getSelectedState(get(), url.pathname);
|
|
559
|
+
const stateForUrl = applyMapTo(selectedState, url.pathname);
|
|
560
|
+
const mappedInitial = getMappedInitial(url.pathname);
|
|
527
561
|
const { output: newCompacted } = compact(
|
|
528
|
-
|
|
529
|
-
|
|
562
|
+
stateForUrl,
|
|
563
|
+
mappedInitial,
|
|
530
564
|
defaultedOptions.syncNull,
|
|
531
565
|
defaultedOptions.syncUndefined
|
|
532
566
|
);
|
|
@@ -534,9 +568,14 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
534
568
|
let stateParams;
|
|
535
569
|
let managedKeys;
|
|
536
570
|
if (standalone) {
|
|
537
|
-
const allParams = format.stringifyStandalone(
|
|
538
|
-
const currentKeys = new Set(
|
|
539
|
-
|
|
571
|
+
const allParams = format.stringifyStandalone(stateForUrl);
|
|
572
|
+
const currentKeys = new Set(
|
|
573
|
+
Object.keys(allParams).map((k) => defaultedOptions.prefix + k)
|
|
574
|
+
);
|
|
575
|
+
managedKeys = /* @__PURE__ */ new Set([
|
|
576
|
+
...Array.from(currentKeys),
|
|
577
|
+
...Array.from(previouslyManagedKeys)
|
|
578
|
+
]);
|
|
540
579
|
previouslyManagedKeys = currentKeys;
|
|
541
580
|
const compactedParams = format.stringifyStandalone(newCompacted);
|
|
542
581
|
stateParams = {};
|
|
@@ -604,9 +643,14 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
604
643
|
};
|
|
605
644
|
const initialized = initialize(new URL(window.location.href), initialState);
|
|
606
645
|
if (standalone) {
|
|
607
|
-
const initSelected = getSelectedState(
|
|
646
|
+
const initSelected = getSelectedState(
|
|
647
|
+
initialized,
|
|
648
|
+
new URL(window.location.href).pathname
|
|
649
|
+
);
|
|
608
650
|
const initParams = format.stringifyStandalone(initSelected);
|
|
609
|
-
previouslyManagedKeys = new Set(
|
|
651
|
+
previouslyManagedKeys = new Set(
|
|
652
|
+
Object.keys(initParams).map((k) => defaultedOptions.prefix + k)
|
|
653
|
+
);
|
|
610
654
|
}
|
|
611
655
|
api.getInitialState = () => initialized;
|
|
612
656
|
return initialized;
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
marked
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-M3ILBO2T.mjs";
|
|
4
4
|
|
|
5
5
|
// src/middleware.ts
|
|
6
6
|
import { isEqual, mergeWith } from "lodash-es";
|
|
@@ -108,13 +108,25 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
108
108
|
}
|
|
109
109
|
return state != null ? state : {};
|
|
110
110
|
};
|
|
111
|
+
const map = defaultedOptions.map;
|
|
112
|
+
const applyMapTo = (state, pathname) => {
|
|
113
|
+
return map ? map.to(state, pathname) : state;
|
|
114
|
+
};
|
|
115
|
+
const applyMapFrom = (urlState, pathname) => {
|
|
116
|
+
return map ? map.from(urlState, pathname) : urlState;
|
|
117
|
+
};
|
|
111
118
|
const initialize = (url, initialState) => {
|
|
112
119
|
try {
|
|
113
|
-
const
|
|
120
|
+
const mappedInitial = applyMapTo(
|
|
121
|
+
getSelectedState(initialState, url.pathname),
|
|
122
|
+
url.pathname
|
|
123
|
+
);
|
|
124
|
+
const stateFromUrl = getStateFromUrl(url, mappedInitial);
|
|
114
125
|
if (!stateFromUrl) {
|
|
115
126
|
return initialState;
|
|
116
127
|
}
|
|
117
|
-
const
|
|
128
|
+
const storeState = applyMapFrom(stateFromUrl, url.pathname);
|
|
129
|
+
const selected = getSelectedState(storeState, url.pathname);
|
|
118
130
|
const merged = mergeWith(
|
|
119
131
|
{},
|
|
120
132
|
initialState,
|
|
@@ -142,12 +154,20 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
142
154
|
api
|
|
143
155
|
);
|
|
144
156
|
let previouslyManagedKeys = /* @__PURE__ */ new Set();
|
|
157
|
+
const getMappedInitial = (pathname) => {
|
|
158
|
+
if (defaultedOptions.map) {
|
|
159
|
+
return applyMapTo(getSelectedState(initialState, pathname), pathname);
|
|
160
|
+
}
|
|
161
|
+
return initialState;
|
|
162
|
+
};
|
|
145
163
|
const setQuery = () => {
|
|
146
164
|
const url = new URL(window.location.href);
|
|
147
165
|
const selectedState = getSelectedState(get(), url.pathname);
|
|
166
|
+
const stateForUrl = applyMapTo(selectedState, url.pathname);
|
|
167
|
+
const mappedInitial = getMappedInitial(url.pathname);
|
|
148
168
|
const { output: newCompacted } = compact(
|
|
149
|
-
|
|
150
|
-
|
|
169
|
+
stateForUrl,
|
|
170
|
+
mappedInitial,
|
|
151
171
|
defaultedOptions.syncNull,
|
|
152
172
|
defaultedOptions.syncUndefined
|
|
153
173
|
);
|
|
@@ -155,9 +175,14 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
155
175
|
let stateParams;
|
|
156
176
|
let managedKeys;
|
|
157
177
|
if (standalone) {
|
|
158
|
-
const allParams = format.stringifyStandalone(
|
|
159
|
-
const currentKeys = new Set(
|
|
160
|
-
|
|
178
|
+
const allParams = format.stringifyStandalone(stateForUrl);
|
|
179
|
+
const currentKeys = new Set(
|
|
180
|
+
Object.keys(allParams).map((k) => defaultedOptions.prefix + k)
|
|
181
|
+
);
|
|
182
|
+
managedKeys = /* @__PURE__ */ new Set([
|
|
183
|
+
...Array.from(currentKeys),
|
|
184
|
+
...Array.from(previouslyManagedKeys)
|
|
185
|
+
]);
|
|
161
186
|
previouslyManagedKeys = currentKeys;
|
|
162
187
|
const compactedParams = format.stringifyStandalone(newCompacted);
|
|
163
188
|
stateParams = {};
|
|
@@ -225,9 +250,14 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
225
250
|
};
|
|
226
251
|
const initialized = initialize(new URL(window.location.href), initialState);
|
|
227
252
|
if (standalone) {
|
|
228
|
-
const initSelected = getSelectedState(
|
|
253
|
+
const initSelected = getSelectedState(
|
|
254
|
+
initialized,
|
|
255
|
+
new URL(window.location.href).pathname
|
|
256
|
+
);
|
|
229
257
|
const initParams = format.stringifyStandalone(initSelected);
|
|
230
|
-
previouslyManagedKeys = new Set(
|
|
258
|
+
previouslyManagedKeys = new Set(
|
|
259
|
+
Object.keys(initParams).map((k) => defaultedOptions.prefix + k)
|
|
260
|
+
);
|
|
231
261
|
}
|
|
232
262
|
api.getInitialState = () => initialized;
|
|
233
263
|
return initialized;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zustand-querystring",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -13,12 +13,6 @@
|
|
|
13
13
|
"type": "github",
|
|
14
14
|
"url": "https://github.com/nitedani/zustand-querystring"
|
|
15
15
|
},
|
|
16
|
-
"scripts": {
|
|
17
|
-
"build": "tsup",
|
|
18
|
-
"dev": "tsup --watch",
|
|
19
|
-
"test": "vitest run",
|
|
20
|
-
"test:watch": "vitest"
|
|
21
|
-
},
|
|
22
16
|
"exports": {
|
|
23
17
|
".": {
|
|
24
18
|
"types": "./dist/index.d.ts",
|
|
@@ -98,5 +92,11 @@
|
|
|
98
92
|
"vitest": "^4.0.12",
|
|
99
93
|
"vitest-browser-react": "^2.0.2",
|
|
100
94
|
"zustand": "^5.0.3"
|
|
95
|
+
},
|
|
96
|
+
"scripts": {
|
|
97
|
+
"build": "tsup",
|
|
98
|
+
"dev": "tsup --watch",
|
|
99
|
+
"test": "vitest run",
|
|
100
|
+
"test:watch": "vitest"
|
|
101
101
|
}
|
|
102
|
-
}
|
|
102
|
+
}
|