zustand-querystring 0.4.1 → 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/dist/index.js CHANGED
@@ -19,23 +19,368 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  // src/index.ts
20
20
  var src_exports = {};
21
21
  __export(src_exports, {
22
- createURL: () => createURL,
23
- parse: () => parse,
24
- querystring: () => querystring,
25
- stringify: () => stringify
22
+ querystring: () => querystring
26
23
  });
27
24
  module.exports = __toCommonJS(src_exports);
28
25
 
29
26
  // src/middleware.ts
30
27
  var import_lodash_es = require("lodash-es");
31
28
 
32
- // src/parser.ts
33
- function stringify(input) {
34
- return encodeURIComponent(JSON.stringify(input));
29
+ // src/format/marked.ts
30
+ function resolveOptions(opts = {}) {
31
+ var _a, _b, _c, _d, _e, _f, _g, _h;
32
+ return {
33
+ typeObject: (_a = opts.typeObject) != null ? _a : ".",
34
+ typeArray: (_b = opts.typeArray) != null ? _b : "@",
35
+ typeString: (_c = opts.typeString) != null ? _c : "=",
36
+ typePrimitive: (_d = opts.typePrimitive) != null ? _d : ":",
37
+ separator: (_e = opts.separator) != null ? _e : ",",
38
+ terminator: (_f = opts.terminator) != null ? _f : "~",
39
+ escape: (_g = opts.escapeChar) != null ? _g : "_",
40
+ datePrefix: (_h = opts.datePrefix) != null ? _h : "D"
41
+ };
42
+ }
43
+ function validateOptions(opts) {
44
+ const tokens = [
45
+ opts.typeObject,
46
+ opts.typeArray,
47
+ opts.typeString,
48
+ opts.typePrimitive,
49
+ opts.separator,
50
+ opts.terminator,
51
+ opts.escape
52
+ ];
53
+ for (const token of tokens) {
54
+ if (token.length === 0) {
55
+ throw new Error("All tokens must be non-empty strings");
56
+ }
57
+ }
58
+ if (opts.datePrefix.length === 0) {
59
+ throw new Error("datePrefix must be non-empty");
60
+ }
61
+ const seen = /* @__PURE__ */ new Set();
62
+ for (const token of tokens) {
63
+ if (seen.has(token)) {
64
+ throw new Error(`Duplicate token detected: '${token}'`);
65
+ }
66
+ seen.add(token);
67
+ }
68
+ if (seen.has(opts.datePrefix)) {
69
+ throw new Error(`datePrefix '${opts.datePrefix}' conflicts with another token`);
70
+ }
71
+ }
72
+ function escapeRegex(str) {
73
+ return str.replace(/[.$^*+?()[\]{}|\\-]/g, "\\$&");
74
+ }
75
+ function buildKeyStopPattern(opts) {
76
+ return new RegExp(
77
+ `[${escapeRegex(opts.typeString)}${escapeRegex(opts.typePrimitive)}${escapeRegex(opts.typeArray)}${escapeRegex(opts.typeObject)}${escapeRegex(opts.separator)}]`
78
+ );
79
+ }
80
+ function buildValueStopPattern(opts) {
81
+ return new RegExp(`[${escapeRegex(opts.separator)}${escapeRegex(opts.terminator)}]`);
82
+ }
83
+ function buildKeyEscapePattern(opts) {
84
+ return new RegExp(
85
+ `([${escapeRegex(opts.typeString)}${escapeRegex(opts.typePrimitive)}${escapeRegex(opts.typeArray)}${escapeRegex(opts.typeObject)}${escapeRegex(opts.separator)}])`,
86
+ "g"
87
+ );
88
+ }
89
+ function buildValueEscapePattern(opts) {
90
+ return new RegExp(`([${escapeRegex(opts.separator)}${escapeRegex(opts.terminator)}])`, "g");
91
+ }
92
+ function buildDatePattern(opts) {
93
+ return new RegExp(`^${escapeRegex(opts.datePrefix)}-?\\d+$`);
94
+ }
95
+ function buildDateStartPattern(opts) {
96
+ return new RegExp(`^${escapeRegex(opts.datePrefix)}-?\\d`);
35
97
  }
36
- function parse(str) {
37
- return JSON.parse(decodeURIComponent(str));
98
+ function encodePreservingMarkers(str, opts) {
99
+ const markers = /* @__PURE__ */ new Set([
100
+ opts.typeObject,
101
+ opts.typeArray,
102
+ opts.typeString,
103
+ opts.typePrimitive,
104
+ opts.separator,
105
+ opts.terminator,
106
+ opts.escape
107
+ ]);
108
+ let result = "";
109
+ for (const char of str) {
110
+ if (markers.has(char)) {
111
+ result += char;
112
+ } else {
113
+ result += encodeURIComponent(char);
114
+ }
115
+ }
116
+ return result;
117
+ }
118
+ function escapeStr(str, pattern, escape, opts) {
119
+ return encodePreservingMarkers(str.replace(pattern, `${escape}$1`), opts);
120
+ }
121
+ function cleanResult(str, standalone, opts) {
122
+ while (str.endsWith(opts.terminator)) {
123
+ str = str.slice(0, -opts.terminator.length);
124
+ }
125
+ const datePattern = new RegExp(`^${escapeRegex(opts.typeString)}${escapeRegex(opts.datePrefix)}-?\\d+$`);
126
+ if (standalone && datePattern.test(str)) {
127
+ return str.slice(opts.typeString.length);
128
+ }
129
+ if (!standalone && str.startsWith(opts.typeObject)) {
130
+ return str.slice(opts.typeObject.length);
131
+ }
132
+ return str;
133
+ }
134
+ function createSerializer(opts) {
135
+ const keyEscape = buildKeyEscapePattern(opts);
136
+ const valueEscape = buildValueEscapePattern(opts);
137
+ const dateStartPattern = buildDateStartPattern(opts);
138
+ function serialize(value, standalone, inArray = false) {
139
+ if (value === null) return `${opts.typePrimitive}null`;
140
+ if (value === void 0) return `${opts.typePrimitive}undefined`;
141
+ if (typeof value === "function") return "";
142
+ if (typeof value === "number") {
143
+ return `${opts.typePrimitive}${value}`;
144
+ }
145
+ if (typeof value === "boolean") {
146
+ return `${opts.typePrimitive}${value}`;
147
+ }
148
+ if (value instanceof Date) {
149
+ return `${opts.typeString}${opts.datePrefix}${value.getTime()}`;
150
+ }
151
+ if (Array.isArray(value)) {
152
+ const items = value.map((v) => serialize(v, standalone, true));
153
+ return `${opts.typeArray}${items.join(opts.separator)}${opts.terminator}`;
154
+ }
155
+ if (typeof value === "object") {
156
+ const entries = [];
157
+ for (const [k, v] of Object.entries(value)) {
158
+ const val = serialize(v, false, false);
159
+ if (val || v === void 0) {
160
+ entries.push(`${escapeStr(k, keyEscape, opts.escape, opts)}${val}`);
161
+ }
162
+ }
163
+ return `${opts.typeObject}${entries.join(opts.separator)}${opts.terminator}`;
164
+ }
165
+ const strVal = String(value);
166
+ let escaped = escapeStr(strVal, valueEscape, opts.escape, opts);
167
+ if (dateStartPattern.test(strVal)) {
168
+ escaped = opts.escape + escaped;
169
+ } else if ((standalone || inArray) && (strVal.startsWith(opts.typeObject) || strVal.startsWith(opts.typeArray) || strVal.startsWith(opts.typePrimitive) || strVal.startsWith(opts.typeString))) {
170
+ escaped = opts.escape + escaped;
171
+ }
172
+ return standalone || inArray ? escaped : `${opts.typeString}${escaped}`;
173
+ }
174
+ return serialize;
38
175
  }
176
+ function createParser(opts) {
177
+ const keyStop = buildKeyStopPattern(opts);
178
+ const valueStop = buildValueStopPattern(opts);
179
+ const datePattern = buildDatePattern(opts);
180
+ const tokens = {
181
+ typePrimitive: opts.typePrimitive,
182
+ typeArray: opts.typeArray,
183
+ typeObject: opts.typeObject,
184
+ typeString: opts.typeString,
185
+ separator: opts.separator,
186
+ terminator: opts.terminator,
187
+ escape: opts.escape,
188
+ datePrefix: opts.datePrefix
189
+ };
190
+ return function parseSource(source) {
191
+ let pos = 0;
192
+ function peek(len = 1) {
193
+ return source.slice(pos, pos + len);
194
+ }
195
+ function startsWith(token) {
196
+ return source.slice(pos, pos + token.length) === token;
197
+ }
198
+ function advance(len = 1) {
199
+ pos += len;
200
+ }
201
+ function readUntil(stopPattern, checkEscape = false) {
202
+ let result = "";
203
+ let wasEscaped = false;
204
+ while (pos < source.length) {
205
+ if (startsWith(tokens.escape)) {
206
+ const nextPos = pos + tokens.escape.length;
207
+ const nextChar = source.slice(nextPos, nextPos + 1);
208
+ const isEscapeSequence = stopPattern.test(nextChar);
209
+ const isDateEscape = checkEscape && source.slice(nextPos, nextPos + tokens.datePrefix.length) === tokens.datePrefix;
210
+ if (isEscapeSequence || isDateEscape) {
211
+ if (isDateEscape) {
212
+ wasEscaped = true;
213
+ }
214
+ advance(tokens.escape.length);
215
+ if (pos < source.length) {
216
+ result += peek();
217
+ advance();
218
+ }
219
+ continue;
220
+ }
221
+ }
222
+ const ch = peek();
223
+ if (stopPattern.test(ch)) break;
224
+ result += ch;
225
+ advance();
226
+ }
227
+ return { value: result, wasEscaped };
228
+ }
229
+ function parseString() {
230
+ const { value, wasEscaped } = readUntil(valueStop, true);
231
+ if (!wasEscaped && datePattern.test(value)) {
232
+ const timestamp = parseInt(value.slice(tokens.datePrefix.length), 10);
233
+ if (!isNaN(timestamp)) {
234
+ return new Date(timestamp);
235
+ }
236
+ }
237
+ return value;
238
+ }
239
+ function parsePrimitive() {
240
+ const { value } = readUntil(valueStop, false);
241
+ if (value === "null") return null;
242
+ if (value === "undefined") return void 0;
243
+ if (value === "true") return true;
244
+ if (value === "false") return false;
245
+ return parseFloat(value);
246
+ }
247
+ function parseArray() {
248
+ const result = [];
249
+ let lastWasSeparator = false;
250
+ while (pos < source.length && !startsWith(tokens.terminator)) {
251
+ if (startsWith(tokens.separator)) {
252
+ result.push("");
253
+ advance(tokens.separator.length);
254
+ lastWasSeparator = true;
255
+ continue;
256
+ }
257
+ lastWasSeparator = false;
258
+ if (startsWith(tokens.typePrimitive) || startsWith(tokens.typeArray) || startsWith(tokens.typeObject) || startsWith(tokens.typeString)) {
259
+ result.push(parseValue());
260
+ } else {
261
+ result.push(parseString());
262
+ }
263
+ if (pos < source.length && startsWith(tokens.separator)) {
264
+ advance(tokens.separator.length);
265
+ lastWasSeparator = true;
266
+ }
267
+ }
268
+ if (lastWasSeparator && (startsWith(tokens.terminator) || pos >= source.length)) {
269
+ result.push("");
270
+ }
271
+ if (startsWith(tokens.terminator)) advance(tokens.terminator.length);
272
+ return result;
273
+ }
274
+ function parseObject() {
275
+ const result = {};
276
+ while (pos < source.length && !startsWith(tokens.terminator)) {
277
+ const { value: key } = readUntil(keyStop, false);
278
+ result[key] = parseValue();
279
+ if (pos < source.length && !startsWith(tokens.terminator) && startsWith(tokens.separator)) {
280
+ advance(tokens.separator.length);
281
+ }
282
+ }
283
+ if (startsWith(tokens.terminator)) advance(tokens.terminator.length);
284
+ return result;
285
+ }
286
+ function parseValue() {
287
+ if (startsWith(tokens.typePrimitive)) {
288
+ advance(tokens.typePrimitive.length);
289
+ return parsePrimitive();
290
+ }
291
+ if (startsWith(tokens.typeArray)) {
292
+ advance(tokens.typeArray.length);
293
+ return parseArray();
294
+ }
295
+ if (startsWith(tokens.typeObject)) {
296
+ advance(tokens.typeObject.length);
297
+ return parseObject();
298
+ }
299
+ if (startsWith(tokens.typeString)) {
300
+ advance(tokens.typeString.length);
301
+ return parseString();
302
+ }
303
+ throw new Error(`Unexpected type "${peek()}" at position ${pos}`);
304
+ }
305
+ return parseValue();
306
+ };
307
+ }
308
+ function stringify(value, standalone = false, options = {}) {
309
+ const opts = resolveOptions(options);
310
+ const serialize = createSerializer(opts);
311
+ return cleanResult(serialize(value, standalone), standalone, opts);
312
+ }
313
+ function parse(input, standalone = false, options = {}) {
314
+ const opts = resolveOptions(options);
315
+ const datePattern = buildDatePattern(opts);
316
+ const str = decodeURIComponent(input);
317
+ if (str.length === 0) {
318
+ return standalone ? "" : {};
319
+ }
320
+ const hasMarker = str.startsWith(opts.typeString) || str.startsWith(opts.typePrimitive) || str.startsWith(opts.typeArray) || str.startsWith(opts.typeObject);
321
+ let source;
322
+ if (hasMarker) {
323
+ source = str;
324
+ } else if (standalone) {
325
+ if (datePattern.test(str)) {
326
+ const timestamp = parseInt(str.slice(opts.datePrefix.length), 10);
327
+ if (!isNaN(timestamp)) {
328
+ return new Date(timestamp);
329
+ }
330
+ }
331
+ let result = "";
332
+ for (let i = 0; i < str.length; i++) {
333
+ if (str.slice(i, i + opts.escape.length) === opts.escape && i + opts.escape.length < str.length) {
334
+ i += opts.escape.length - 1;
335
+ result += str[i + 1];
336
+ i++;
337
+ } else {
338
+ result += str[i];
339
+ }
340
+ }
341
+ return result;
342
+ } else {
343
+ const escapedMarkers = [opts.typeString, opts.typePrimitive, opts.typeArray, opts.typeObject].map(escapeRegex).join("");
344
+ const escapedEscape = escapeRegex(opts.escape);
345
+ const escapedSeparator = escapeRegex(opts.separator);
346
+ const escapedTerminator = escapeRegex(opts.terminator);
347
+ const objectPattern = new RegExp(
348
+ `[^${escapedEscape}${escapedMarkers}${escapedSeparator}${escapedTerminator}][${escapedMarkers}]`
349
+ );
350
+ source = objectPattern.test(str) ? `${opts.typeObject}${str}` : `${opts.typeString}${str}`;
351
+ }
352
+ const parseSource = createParser(opts);
353
+ return parseSource(source);
354
+ }
355
+ function createFormat(options = {}) {
356
+ const opts = resolveOptions(options);
357
+ validateOptions(opts);
358
+ return {
359
+ stringify(state) {
360
+ return stringify(state, false, options);
361
+ },
362
+ parse(value, _ctx) {
363
+ return parse(value, false, options);
364
+ },
365
+ stringifyStandalone(state) {
366
+ const result = {};
367
+ for (const [key, value] of Object.entries(state)) {
368
+ result[encodeURIComponent(key)] = [stringify(value, true, options)];
369
+ }
370
+ return result;
371
+ },
372
+ parseStandalone(params, _ctx) {
373
+ const result = {};
374
+ for (const [key, values] of Object.entries(params)) {
375
+ if (values.length > 0) {
376
+ result[decodeURIComponent(key)] = parse(values[0], true, options);
377
+ }
378
+ }
379
+ return result;
380
+ }
381
+ };
382
+ }
383
+ var marked = createFormat();
39
384
 
40
385
  // src/middleware.ts
41
386
  var compact = (newState, initialState, syncNull = false, syncUndefined = false) => {
@@ -79,55 +424,60 @@ var translateSelectionToState = (selection, state) => {
79
424
  return acc;
80
425
  }, {});
81
426
  };
427
+ var parseSearchString = (search) => {
428
+ const result = {};
429
+ search.slice(search.startsWith("?") ? 1 : 0).split("&").filter(Boolean).forEach((param) => {
430
+ const eqIndex = param.indexOf("=");
431
+ if (eqIndex === -1) return;
432
+ const key = param.slice(0, eqIndex);
433
+ const value = param.slice(eqIndex + 1);
434
+ if (!result[key]) {
435
+ result[key] = [];
436
+ }
437
+ result[key].push(value);
438
+ });
439
+ return result;
440
+ };
82
441
  var queryStringImpl = (fn, options) => (set, get, api) => {
83
442
  const defaultedOptions = {
84
- key: "state",
85
- format: {
86
- stringify,
87
- parse
88
- },
443
+ key: false,
444
+ prefix: "",
445
+ format: marked,
89
446
  syncNull: false,
90
447
  syncUndefined: false,
91
448
  ...options
92
449
  };
93
- const standalone = !defaultedOptions.key;
94
- if (typeof window !== "undefined" && standalone) {
95
- if (!window.__ZUSTAND_QUERYSTRING_KEYS__) {
96
- window.__ZUSTAND_QUERYSTRING_KEYS__ = /* @__PURE__ */ new Map();
97
- }
450
+ if (defaultedOptions.prefix === void 0) {
451
+ defaultedOptions.prefix = "";
98
452
  }
453
+ const format = defaultedOptions.format;
454
+ const standalone = defaultedOptions.key === false;
99
455
  const getStateFromUrl = (url, initialState) => {
456
+ const params = parseSearchString(url.search);
100
457
  if (standalone) {
101
- const params2 = url.search.slice(1).split("&").filter(Boolean);
102
- const state = {};
103
- const initialKeys = new Set(Object.keys(initialState));
104
- params2.forEach((param) => {
105
- const eqIndex = param.indexOf("=");
106
- if (eqIndex === -1) return;
107
- const key = decodeURI(param.slice(0, eqIndex));
108
- const value = param.slice(eqIndex + 1);
109
- if (!initialKeys.has(key)) return;
458
+ let filteredParams = params;
459
+ if (defaultedOptions.prefix) {
460
+ filteredParams = {};
461
+ for (const [key, values] of Object.entries(params)) {
462
+ if (key.startsWith(defaultedOptions.prefix)) {
463
+ filteredParams[key.slice(defaultedOptions.prefix.length)] = values;
464
+ }
465
+ }
466
+ }
467
+ const result = format.parseStandalone(filteredParams, { initialState });
468
+ return Object.keys(result).length > 0 ? result : null;
469
+ } else {
470
+ const fullKey = defaultedOptions.prefix + defaultedOptions.key;
471
+ const values = params[fullKey];
472
+ if (values && values.length > 0) {
110
473
  try {
111
- const parsed = defaultedOptions.format.parse(value, true);
112
- state[key] = parsed;
113
- } catch (error) {
114
- console.error("[getStateFromUrl] error parsing key:", key, error);
474
+ return format.parse(values[0], { initialState });
475
+ } catch {
476
+ return null;
115
477
  }
116
- });
117
- return Object.keys(state).length > 0 ? state : null;
118
- }
119
- const params = url.search.slice(1).split("&");
120
- for (const param of params) {
121
- const eqIndex = param.indexOf("=");
122
- if (eqIndex === -1) continue;
123
- const key = param.slice(0, eqIndex);
124
- if (key === defaultedOptions.key) {
125
- const value = param.slice(eqIndex + 1);
126
- const parsed = value ? defaultedOptions.format.parse(value, false) : null;
127
- return parsed;
128
478
  }
479
+ return null;
129
480
  }
130
- return null;
131
481
  };
132
482
  const getSelectedState = (state, pathname) => {
133
483
  if (defaultedOptions.select) {
@@ -139,11 +489,11 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
139
489
  };
140
490
  const initialize = (url, initialState) => {
141
491
  try {
142
- const stateFromURl = getStateFromUrl(url, initialState);
143
- if (!stateFromURl) {
492
+ const stateFromUrl = getStateFromUrl(url, initialState);
493
+ if (!stateFromUrl) {
144
494
  return initialState;
145
495
  }
146
- const selected = getSelectedState(stateFromURl, url.pathname);
496
+ const selected = getSelectedState(stateFromUrl, url.pathname);
147
497
  const merged = (0, import_lodash_es.mergeWith)(
148
498
  {},
149
499
  initialState,
@@ -170,29 +520,7 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
170
520
  get,
171
521
  api
172
522
  );
173
- if (standalone) {
174
- const registry = window.__ZUSTAND_QUERYSTRING_KEYS__;
175
- const stateKeys = Object.keys(initialState).filter(
176
- (k) => typeof initialState[k] !== "function"
177
- );
178
- const conflicts = [];
179
- for (const key of stateKeys) {
180
- if (registry.has(key)) {
181
- const existing = registry.get(key);
182
- const current = defaultedOptions.format;
183
- if (existing !== current) {
184
- conflicts.push(key);
185
- }
186
- } else {
187
- registry.set(key, defaultedOptions.format);
188
- }
189
- }
190
- if (conflicts.length > 0) {
191
- throw new Error(
192
- `[zustand-querystring] Standalone mode conflict: Multiple stores are using the following keys with different formats: ${conflicts.map((k) => `"${k}"`).join(", ")}. This will cause parsing errors. Please use unique state keys or the same format for all stores sharing keys.`
193
- );
194
- }
195
- }
523
+ let previouslyManagedKeys = /* @__PURE__ */ new Set();
196
524
  const setQuery = () => {
197
525
  const url = new URL(window.location.href);
198
526
  const selectedState = getSelectedState(get(), url.pathname);
@@ -203,31 +531,55 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
203
531
  defaultedOptions.syncUndefined
204
532
  );
205
533
  const previous = url.search;
534
+ let stateParams;
535
+ let managedKeys;
536
+ if (standalone) {
537
+ const allParams = format.stringifyStandalone(selectedState);
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;
541
+ const compactedParams = format.stringifyStandalone(newCompacted);
542
+ stateParams = {};
543
+ for (const [key, values] of Object.entries(compactedParams)) {
544
+ stateParams[defaultedOptions.prefix + key] = values;
545
+ }
546
+ } else {
547
+ const fullKey = defaultedOptions.prefix + defaultedOptions.key;
548
+ if (Object.keys(newCompacted).length > 0) {
549
+ stateParams = { [fullKey]: [format.stringify(newCompacted)] };
550
+ } else {
551
+ stateParams = {};
552
+ }
553
+ managedKeys = /* @__PURE__ */ new Set([fullKey]);
554
+ }
555
+ const valuesToWrite = /* @__PURE__ */ new Map();
556
+ const keyOrder = [];
557
+ for (const [key, values] of Object.entries(stateParams)) {
558
+ valuesToWrite.set(key, [...values]);
559
+ keyOrder.push(key);
560
+ }
206
561
  const params = url.search.slice(1).split("&").filter(Boolean);
207
- const managedKeys = standalone ? new Set(Object.keys(selectedState).map(encodeURI)) : /* @__PURE__ */ new Set([defaultedOptions.key]);
208
- const valuesToWrite = standalone ? new Map(
209
- Object.entries(newCompacted).map(([k, v]) => [encodeURI(k), v])
210
- ) : Object.keys(newCompacted).length ? /* @__PURE__ */ new Map([[defaultedOptions.key, newCompacted]]) : /* @__PURE__ */ new Map();
211
562
  const result = [];
212
- params.forEach((p) => {
213
- const key = p.split("=")[0];
563
+ params.forEach((param) => {
564
+ const eqIndex = param.indexOf("=");
565
+ if (eqIndex === -1) return;
566
+ const key = param.slice(0, eqIndex);
214
567
  if (!managedKeys.has(key)) {
215
- result.push(p);
568
+ result.push(param);
216
569
  return;
217
570
  }
218
- if (valuesToWrite.has(key)) {
219
- const encoded = defaultedOptions.format.stringify(
220
- valuesToWrite.get(key),
221
- standalone
222
- );
223
- result.push(`${key}=${encoded}`);
224
- valuesToWrite.delete(key);
571
+ const values = valuesToWrite.get(key);
572
+ if (values && values.length > 0) {
573
+ const value = values.shift();
574
+ result.push(`${key}=${value}`);
225
575
  }
226
576
  });
227
- valuesToWrite.forEach((value, key) => {
228
- const encoded = defaultedOptions.format.stringify(value, standalone);
229
- result.push(`${key}=${encoded}`);
230
- });
577
+ for (const key of keyOrder) {
578
+ const values = valuesToWrite.get(key);
579
+ if (values) {
580
+ values.forEach((v) => result.push(`${key}=${v}`));
581
+ }
582
+ }
231
583
  url.search = result.length ? "?" + result.join("&") : "";
232
584
  if (url.search !== previous) {
233
585
  history.replaceState(history.state, "", url);
@@ -251,6 +603,11 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
251
603
  setQuery();
252
604
  };
253
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
+ }
254
611
  api.getInitialState = () => initialized;
255
612
  return initialized;
256
613
  } else if (defaultedOptions.url) {
@@ -264,22 +621,10 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
264
621
  return fn(set, get, api);
265
622
  };
266
623
  var querystring = queryStringImpl;
267
-
268
- // src/utils.ts
269
- var createURL = ({
270
- baseUrl,
271
- key,
272
- state
273
- }) => {
274
- const url = new URL(baseUrl);
275
- const stringified = stringify(state);
276
- url.searchParams.set(key, stringified);
277
- return url.href;
278
- };
279
624
  // Annotate the CommonJS export names for ESM import in node:
280
625
  0 && (module.exports = {
281
- createURL,
282
- parse,
283
- querystring,
284
- stringify
626
+ querystring
285
627
  });
628
+ /* v8 ignore next 4 -- @preserve: defensive - handles escape char at very end of source */
629
+ /* v8 ignore next 3 -- @preserve: defensive - parseInt on digit string won't produce NaN */
630
+ /* v8 ignore next 2 -- @preserve: defensive code - all valid types are handled above */