zustand-querystring 0.5.0 → 0.6.0

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 CHANGED
@@ -171,12 +171,15 @@ Dot notation for nesting, comma-separated arrays.
171
171
  import { createFormat } from 'zustand-querystring/format/plain';
172
172
 
173
173
  const format = createFormat({
174
- entrySeparator: ',', // between entries in namespaced mode
175
- nestingSeparator: '.', // for nested keys
176
- arraySeparator: ',', // or 'repeat' for ?tags=a&tags=b&tags=c
174
+ entrySeparator: ',', // between entries in namespaced mode
175
+ nestingSeparator: '.', // for nested keys
176
+ arraySeparator: ',', // or 'repeat' for ?tags=a&tags=b&tags=c
177
177
  escapeChar: '_',
178
178
  nullString: 'null',
179
179
  undefinedString: 'undefined',
180
+ infinityString: 'Infinity', // string representation of Infinity
181
+ negativeInfinityString: '-Infinity',
182
+ nanString: 'NaN',
180
183
  });
181
184
  ```
182
185
 
@@ -31,6 +31,12 @@ interface PlainFormatOptions {
31
31
  nullString?: string;
32
32
  /** String representation of undefined @default 'undefined' */
33
33
  undefinedString?: string;
34
+ /** String representation of Infinity @default 'Infinity' */
35
+ infinityString?: string;
36
+ /** String representation of -Infinity @default '-Infinity' */
37
+ negativeInfinityString?: string;
38
+ /** String representation of NaN @default 'NaN' */
39
+ nanString?: string;
34
40
  }
35
41
  /**
36
42
  * Create a plain format with custom configuration.
@@ -41,7 +47,7 @@ declare function createFormat(options?: PlainFormatOptions): QueryStringFormat;
41
47
  *
42
48
  * - Entry separator: `,`
43
49
  * - Nesting separator: `.`
44
- * - Array separator: `,` (comma-separated values)
50
+ * - Array separator: `repeat` (repeated keys, e.g. `?tag=a&tag=b`)
45
51
  * - Escape character: `_`
46
52
  */
47
53
  declare const plain: QueryStringFormat;
@@ -31,6 +31,12 @@ interface PlainFormatOptions {
31
31
  nullString?: string;
32
32
  /** String representation of undefined @default 'undefined' */
33
33
  undefinedString?: string;
34
+ /** String representation of Infinity @default 'Infinity' */
35
+ infinityString?: string;
36
+ /** String representation of -Infinity @default '-Infinity' */
37
+ negativeInfinityString?: string;
38
+ /** String representation of NaN @default 'NaN' */
39
+ nanString?: string;
34
40
  }
35
41
  /**
36
42
  * Create a plain format with custom configuration.
@@ -41,7 +47,7 @@ declare function createFormat(options?: PlainFormatOptions): QueryStringFormat;
41
47
  *
42
48
  * - Entry separator: `,`
43
49
  * - Nesting separator: `.`
44
- * - Array separator: `,` (comma-separated values)
50
+ * - Array separator: `repeat` (repeated keys, e.g. `?tag=a&tag=b`)
45
51
  * - Escape character: `_`
46
52
  */
47
53
  declare const plain: QueryStringFormat;
@@ -24,14 +24,17 @@ __export(plain_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(plain_exports);
26
26
  function resolveOptions(opts = {}) {
27
- var _a, _b, _c, _d, _e, _f;
27
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
28
28
  return {
29
29
  entrySep: (_a = opts.entrySeparator) != null ? _a : ",",
30
30
  nestingSep: (_b = opts.nestingSeparator) != null ? _b : ".",
31
- arraySep: (_c = opts.arraySeparator) != null ? _c : ",",
31
+ arraySep: (_c = opts.arraySeparator) != null ? _c : "repeat",
32
32
  escape: (_d = opts.escapeChar) != null ? _d : "_",
33
33
  nullStr: (_e = opts.nullString) != null ? _e : "null",
34
- undefStr: (_f = opts.undefinedString) != null ? _f : "undefined"
34
+ undefStr: (_f = opts.undefinedString) != null ? _f : "undefined",
35
+ infStr: (_g = opts.infinityString) != null ? _g : "Infinity",
36
+ negInfStr: (_h = opts.negativeInfinityString) != null ? _h : "-Infinity",
37
+ nanStr: (_i = opts.nanString) != null ? _i : "NaN"
35
38
  };
36
39
  }
37
40
  function validateOptions(opts) {
@@ -89,13 +92,16 @@ function isObject(value) {
89
92
  return value !== null && typeof value === "object" && !Array.isArray(value) && !isDate(value);
90
93
  }
91
94
  var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
92
- var NUMBER_RE = /^-?\d+(\.\d+)?$/;
95
+ var NUMBER_RE = /^-?(\d+\.?\d*|\d*\.?\d+)([eE][+-]?\d+)?$/;
93
96
  function tryParseBoolean(str) {
94
97
  if (str === "true") return true;
95
98
  if (str === "false") return false;
96
99
  return null;
97
100
  }
98
- function tryParseNumber(str) {
101
+ function tryParseNumber(str, opts) {
102
+ if (str === opts.infStr) return Infinity;
103
+ if (str === opts.negInfStr) return -Infinity;
104
+ if (str === opts.nanStr) return NaN;
99
105
  if (!NUMBER_RE.test(str)) return null;
100
106
  const n = parseFloat(str);
101
107
  return isFinite(n) ? n : null;
@@ -109,7 +115,12 @@ function serializeValue(value, opts) {
109
115
  if (value === null) return opts.nullStr;
110
116
  if (value === void 0) return opts.undefStr;
111
117
  if (typeof value === "boolean") return String(value);
112
- if (typeof value === "number") return String(value);
118
+ if (typeof value === "number") {
119
+ if (Number.isNaN(value)) return opts.nanStr;
120
+ if (value === Infinity) return opts.infStr;
121
+ if (value === -Infinity) return opts.negInfStr;
122
+ return String(value);
123
+ }
113
124
  if (isDate(value)) return value.toISOString();
114
125
  return String(value);
115
126
  }
@@ -120,8 +131,8 @@ function parseValue(str, hint, opts) {
120
131
  if (hint !== null && hint !== void 0) {
121
132
  if (typeof hint === "string") return str;
122
133
  if (typeof hint === "number") {
123
- const n2 = tryParseNumber(str);
124
- if (n2 !== null) return n2;
134
+ const n2 = tryParseNumber(str, opts);
135
+ if (n2 !== null || Number.isNaN(n2)) return n2;
125
136
  }
126
137
  if (typeof hint === "boolean") {
127
138
  const b2 = tryParseBoolean(str);
@@ -139,8 +150,8 @@ function parseValue(str, hint, opts) {
139
150
  if (b !== null) return b;
140
151
  const d = tryParseDate(str);
141
152
  if (d !== null) return d;
142
- const n = tryParseNumber(str);
143
- if (n !== null) return n;
153
+ const n = tryParseNumber(str, opts);
154
+ if (n !== null || Number.isNaN(n)) return n;
144
155
  return str;
145
156
  }
146
157
  function escape(str, chars, esc) {
@@ -334,22 +345,25 @@ function unflatten(entries, initialState, opts) {
334
345
  if (opts.arraySep !== "repeat") {
335
346
  valueSpecials.push(opts.arraySep);
336
347
  }
337
- let values = rawValues.map((v) => unescape(v, opts.escape, valueSpecials));
348
+ const unescapeValue = (v) => unescape(v, opts.escape, valueSpecials);
349
+ let values;
350
+ if (opts.arraySep !== "repeat" && rawValues.length === 1) {
351
+ const parts = splitEscaped(rawValues[0], opts.arraySep, opts.escape);
352
+ const isArray = parts.length > 1 || isArrayHint;
353
+ values = isArray ? parts.map(unescapeValue) : [unescapeValue(rawValues[0])];
354
+ } else {
355
+ values = rawValues.map(unescapeValue);
356
+ }
338
357
  if (values.length === 1 && values[0] === "" && isArrayHint) {
339
358
  setAtPath(result, path, []);
340
359
  continue;
341
360
  }
342
- if (opts.arraySep !== "repeat" && isArrayHint && values.length === 1) {
343
- values = splitEscaped(values[0], opts.arraySep, opts.escape).map((v) => unescape(v, opts.escape, valueSpecials));
344
- }
345
361
  if (values.length === 1 && !isArrayHint) {
346
- const parsed = parseValue(values[0], hint, opts);
347
- setAtPath(result, path, parsed);
362
+ setAtPath(result, path, parseValue(values[0], hint, opts));
348
363
  continue;
349
364
  }
350
- const elementHint = Array.isArray(hint) ? hint[0] : void 0;
351
- const parsedArray = values.map((v) => parseValue(v, elementHint, opts));
352
- setAtPath(result, path, parsedArray);
365
+ const elementHint = isArrayHint ? hint[0] : void 0;
366
+ setAtPath(result, path, values.map((v) => parseValue(v, elementHint, opts)));
353
367
  }
354
368
  return result;
355
369
  }
@@ -441,6 +455,5 @@ var plain = createFormat();
441
455
  createFormat,
442
456
  plain
443
457
  });
444
- /* v8 ignore next -- @preserve: defensive - regex already filters non-finite number strings */
445
458
  /* v8 ignore next 3 -- @preserve: defensive code - unflatten always extracts hint[0] before calling parseValue */
446
459
  /* v8 ignore next 3 -- @preserve: defensive - handles non-serializable types like Symbol, Set, Map */
@@ -1,13 +1,16 @@
1
1
  // src/format/plain.ts
2
2
  function resolveOptions(opts = {}) {
3
- var _a, _b, _c, _d, _e, _f;
3
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
4
4
  return {
5
5
  entrySep: (_a = opts.entrySeparator) != null ? _a : ",",
6
6
  nestingSep: (_b = opts.nestingSeparator) != null ? _b : ".",
7
- arraySep: (_c = opts.arraySeparator) != null ? _c : ",",
7
+ arraySep: (_c = opts.arraySeparator) != null ? _c : "repeat",
8
8
  escape: (_d = opts.escapeChar) != null ? _d : "_",
9
9
  nullStr: (_e = opts.nullString) != null ? _e : "null",
10
- undefStr: (_f = opts.undefinedString) != null ? _f : "undefined"
10
+ undefStr: (_f = opts.undefinedString) != null ? _f : "undefined",
11
+ infStr: (_g = opts.infinityString) != null ? _g : "Infinity",
12
+ negInfStr: (_h = opts.negativeInfinityString) != null ? _h : "-Infinity",
13
+ nanStr: (_i = opts.nanString) != null ? _i : "NaN"
11
14
  };
12
15
  }
13
16
  function validateOptions(opts) {
@@ -65,13 +68,16 @@ function isObject(value) {
65
68
  return value !== null && typeof value === "object" && !Array.isArray(value) && !isDate(value);
66
69
  }
67
70
  var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
68
- var NUMBER_RE = /^-?\d+(\.\d+)?$/;
71
+ var NUMBER_RE = /^-?(\d+\.?\d*|\d*\.?\d+)([eE][+-]?\d+)?$/;
69
72
  function tryParseBoolean(str) {
70
73
  if (str === "true") return true;
71
74
  if (str === "false") return false;
72
75
  return null;
73
76
  }
74
- function tryParseNumber(str) {
77
+ function tryParseNumber(str, opts) {
78
+ if (str === opts.infStr) return Infinity;
79
+ if (str === opts.negInfStr) return -Infinity;
80
+ if (str === opts.nanStr) return NaN;
75
81
  if (!NUMBER_RE.test(str)) return null;
76
82
  const n = parseFloat(str);
77
83
  return isFinite(n) ? n : null;
@@ -85,7 +91,12 @@ function serializeValue(value, opts) {
85
91
  if (value === null) return opts.nullStr;
86
92
  if (value === void 0) return opts.undefStr;
87
93
  if (typeof value === "boolean") return String(value);
88
- if (typeof value === "number") return String(value);
94
+ if (typeof value === "number") {
95
+ if (Number.isNaN(value)) return opts.nanStr;
96
+ if (value === Infinity) return opts.infStr;
97
+ if (value === -Infinity) return opts.negInfStr;
98
+ return String(value);
99
+ }
89
100
  if (isDate(value)) return value.toISOString();
90
101
  return String(value);
91
102
  }
@@ -96,8 +107,8 @@ function parseValue(str, hint, opts) {
96
107
  if (hint !== null && hint !== void 0) {
97
108
  if (typeof hint === "string") return str;
98
109
  if (typeof hint === "number") {
99
- const n2 = tryParseNumber(str);
100
- if (n2 !== null) return n2;
110
+ const n2 = tryParseNumber(str, opts);
111
+ if (n2 !== null || Number.isNaN(n2)) return n2;
101
112
  }
102
113
  if (typeof hint === "boolean") {
103
114
  const b2 = tryParseBoolean(str);
@@ -115,8 +126,8 @@ function parseValue(str, hint, opts) {
115
126
  if (b !== null) return b;
116
127
  const d = tryParseDate(str);
117
128
  if (d !== null) return d;
118
- const n = tryParseNumber(str);
119
- if (n !== null) return n;
129
+ const n = tryParseNumber(str, opts);
130
+ if (n !== null || Number.isNaN(n)) return n;
120
131
  return str;
121
132
  }
122
133
  function escape(str, chars, esc) {
@@ -310,22 +321,25 @@ function unflatten(entries, initialState, opts) {
310
321
  if (opts.arraySep !== "repeat") {
311
322
  valueSpecials.push(opts.arraySep);
312
323
  }
313
- let values = rawValues.map((v) => unescape(v, opts.escape, valueSpecials));
324
+ const unescapeValue = (v) => unescape(v, opts.escape, valueSpecials);
325
+ let values;
326
+ if (opts.arraySep !== "repeat" && rawValues.length === 1) {
327
+ const parts = splitEscaped(rawValues[0], opts.arraySep, opts.escape);
328
+ const isArray = parts.length > 1 || isArrayHint;
329
+ values = isArray ? parts.map(unescapeValue) : [unescapeValue(rawValues[0])];
330
+ } else {
331
+ values = rawValues.map(unescapeValue);
332
+ }
314
333
  if (values.length === 1 && values[0] === "" && isArrayHint) {
315
334
  setAtPath(result, path, []);
316
335
  continue;
317
336
  }
318
- if (opts.arraySep !== "repeat" && isArrayHint && values.length === 1) {
319
- values = splitEscaped(values[0], opts.arraySep, opts.escape).map((v) => unescape(v, opts.escape, valueSpecials));
320
- }
321
337
  if (values.length === 1 && !isArrayHint) {
322
- const parsed = parseValue(values[0], hint, opts);
323
- setAtPath(result, path, parsed);
338
+ setAtPath(result, path, parseValue(values[0], hint, opts));
324
339
  continue;
325
340
  }
326
- const elementHint = Array.isArray(hint) ? hint[0] : void 0;
327
- const parsedArray = values.map((v) => parseValue(v, elementHint, opts));
328
- setAtPath(result, path, parsedArray);
341
+ const elementHint = isArrayHint ? hint[0] : void 0;
342
+ setAtPath(result, path, values.map((v) => parseValue(v, elementHint, opts)));
329
343
  }
330
344
  return result;
331
345
  }
@@ -416,6 +430,5 @@ export {
416
430
  createFormat,
417
431
  plain
418
432
  };
419
- /* v8 ignore next -- @preserve: defensive - regex already filters non-finite number strings */
420
433
  /* v8 ignore next 3 -- @preserve: defensive code - unflatten always extracts hint[0] before calling parseValue */
421
434
  /* v8 ignore next 3 -- @preserve: defensive - handles non-serializable types like Symbol, Set, Map */
package/dist/index.js CHANGED
@@ -520,6 +520,7 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
520
520
  get,
521
521
  api
522
522
  );
523
+ let previouslyManagedKeys = /* @__PURE__ */ new Set();
523
524
  const setQuery = () => {
524
525
  const url = new URL(window.location.href);
525
526
  const selectedState = getSelectedState(get(), url.pathname);
@@ -534,7 +535,9 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
534
535
  let managedKeys;
535
536
  if (standalone) {
536
537
  const allParams = format.stringifyStandalone(selectedState);
537
- managedKeys = new Set(Object.keys(allParams).map((k) => defaultedOptions.prefix + k));
538
+ const currentKeys = new Set(Object.keys(allParams).map((k) => defaultedOptions.prefix + k));
539
+ managedKeys = /* @__PURE__ */ new Set([...Array.from(currentKeys), ...Array.from(previouslyManagedKeys)]);
540
+ previouslyManagedKeys = currentKeys;
538
541
  const compactedParams = format.stringifyStandalone(newCompacted);
539
542
  stateParams = {};
540
543
  for (const [key, values] of Object.entries(compactedParams)) {
@@ -600,6 +603,11 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
600
603
  setQuery();
601
604
  };
602
605
  const initialized = initialize(new URL(window.location.href), initialState);
606
+ if (standalone) {
607
+ const initSelected = getSelectedState(initialized, new URL(window.location.href).pathname);
608
+ const initParams = format.stringifyStandalone(initSelected);
609
+ previouslyManagedKeys = new Set(Object.keys(initParams).map((k) => defaultedOptions.prefix + k));
610
+ }
603
611
  api.getInitialState = () => initialized;
604
612
  return initialized;
605
613
  } else if (defaultedOptions.url) {
package/dist/index.mjs CHANGED
@@ -141,6 +141,7 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
141
141
  get,
142
142
  api
143
143
  );
144
+ let previouslyManagedKeys = /* @__PURE__ */ new Set();
144
145
  const setQuery = () => {
145
146
  const url = new URL(window.location.href);
146
147
  const selectedState = getSelectedState(get(), url.pathname);
@@ -155,7 +156,9 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
155
156
  let managedKeys;
156
157
  if (standalone) {
157
158
  const allParams = format.stringifyStandalone(selectedState);
158
- managedKeys = new Set(Object.keys(allParams).map((k) => defaultedOptions.prefix + k));
159
+ const currentKeys = new Set(Object.keys(allParams).map((k) => defaultedOptions.prefix + k));
160
+ managedKeys = /* @__PURE__ */ new Set([...Array.from(currentKeys), ...Array.from(previouslyManagedKeys)]);
161
+ previouslyManagedKeys = currentKeys;
159
162
  const compactedParams = format.stringifyStandalone(newCompacted);
160
163
  stateParams = {};
161
164
  for (const [key, values] of Object.entries(compactedParams)) {
@@ -221,6 +224,11 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
221
224
  setQuery();
222
225
  };
223
226
  const initialized = initialize(new URL(window.location.href), initialState);
227
+ if (standalone) {
228
+ const initSelected = getSelectedState(initialized, new URL(window.location.href).pathname);
229
+ const initParams = format.stringifyStandalone(initSelected);
230
+ previouslyManagedKeys = new Set(Object.keys(initParams).map((k) => defaultedOptions.prefix + k));
231
+ }
224
232
  api.getInitialState = () => initialized;
225
233
  return initialized;
226
234
  } else if (defaultedOptions.url) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zustand-querystring",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",