zustand-querystring 0.3.2 → 0.4.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 +2 -2
- package/dist/format/readable.d.mts +7 -3
- package/dist/format/readable.d.ts +7 -3
- package/dist/format/readable.js +127 -56
- package/dist/format/readable.mjs +126 -56
- package/dist/index.d.mts +6 -8
- package/dist/index.d.ts +6 -8
- package/dist/index.js +41 -19
- package/dist/index.mjs +41 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,9 +59,9 @@ export const useStore = create<Store>()(
|
|
|
59
59
|
querystring options:
|
|
60
60
|
|
|
61
61
|
- <b>select</b> - the select option controls what part of the state is synced with the query string
|
|
62
|
-
- <b>key: string</b> - the key option controls how the state is stored in the querystring (default: 'state')
|
|
62
|
+
- <b>key: string | false</b> - the key option controls how the state is stored in the querystring (default: 'state'). Set to `false` for standalone mode where each state key becomes a separate query parameter.
|
|
63
63
|
- <b>url</b> - the url option is used to provide the request url on the server side render
|
|
64
|
-
- <b>format</b> - custom format for stringify/parse (default: JSON-based format)
|
|
64
|
+
- <b>format</b> - custom format for stringify/parse (default: JSON-based format). Use `readable` from `zustand-querystring/format/readable` for human-readable URLs.
|
|
65
65
|
- <b>syncNull: boolean</b> - when true, null values that differ from initial state are synced to URL (default: false)
|
|
66
66
|
- <b>syncUndefined: boolean</b> - when true, undefined values that differ from initial state are synced to URL (default: false)
|
|
67
67
|
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
* Inspired by URLON (https://github.com/cerebral/urlon)
|
|
5
5
|
* Copyright (c) 2021 Cerebral - MIT License
|
|
6
6
|
*/
|
|
7
|
-
declare function stringify(value: unknown): string;
|
|
8
|
-
declare function parse<T = unknown>(input: string): T;
|
|
7
|
+
declare function stringify(value: unknown, standalone?: boolean): string;
|
|
8
|
+
declare function parse<T = unknown>(input: string, standalone?: boolean): T;
|
|
9
|
+
declare const readable: {
|
|
10
|
+
stringify: typeof stringify;
|
|
11
|
+
parse: typeof parse;
|
|
12
|
+
};
|
|
9
13
|
|
|
10
|
-
export { parse, stringify };
|
|
14
|
+
export { parse, readable, stringify };
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
* Inspired by URLON (https://github.com/cerebral/urlon)
|
|
5
5
|
* Copyright (c) 2021 Cerebral - MIT License
|
|
6
6
|
*/
|
|
7
|
-
declare function stringify(value: unknown): string;
|
|
8
|
-
declare function parse<T = unknown>(input: string): T;
|
|
7
|
+
declare function stringify(value: unknown, standalone?: boolean): string;
|
|
8
|
+
declare function parse<T = unknown>(input: string, standalone?: boolean): T;
|
|
9
|
+
declare const readable: {
|
|
10
|
+
stringify: typeof stringify;
|
|
11
|
+
parse: typeof parse;
|
|
12
|
+
};
|
|
9
13
|
|
|
10
|
-
export { parse, stringify };
|
|
14
|
+
export { parse, readable, stringify };
|
package/dist/format/readable.js
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
var readable_exports = {};
|
|
21
21
|
__export(readable_exports, {
|
|
22
22
|
parse: () => parse,
|
|
23
|
+
readable: () => readable,
|
|
23
24
|
stringify: () => stringify
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(readable_exports);
|
|
@@ -30,27 +31,38 @@ var TYPE_PRIMITIVE = ":";
|
|
|
30
31
|
var SEPARATOR = ",";
|
|
31
32
|
var TERMINATOR = "~";
|
|
32
33
|
var ESCAPE = "/";
|
|
33
|
-
var DATE_PREFIX = "
|
|
34
|
-
var SEP_CHAR = SEPARATOR[0];
|
|
34
|
+
var DATE_PREFIX = "D";
|
|
35
35
|
var esc = (c) => /[.$^*+?()[\]{}|\\]/.test(c) ? `\\${c}` : c;
|
|
36
|
-
var KEY_STOP = new RegExp(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
var
|
|
36
|
+
var KEY_STOP = new RegExp(
|
|
37
|
+
`[${TYPE_STRING}${TYPE_PRIMITIVE}${TYPE_ARRAY}${esc(TYPE_OBJECT)}${esc(SEPARATOR)}]`
|
|
38
|
+
);
|
|
39
|
+
var VALUE_STOP = new RegExp(`[${esc(SEPARATOR)}${TERMINATOR}]`);
|
|
40
|
+
var KEY_ESCAPE = new RegExp(
|
|
41
|
+
`([${TYPE_STRING}${TYPE_PRIMITIVE}${TYPE_ARRAY}${esc(TYPE_OBJECT)}${ESCAPE}${esc(SEPARATOR)}])`,
|
|
42
|
+
"g"
|
|
43
|
+
);
|
|
44
|
+
var VALUE_ESCAPE = new RegExp(
|
|
45
|
+
`([${esc(SEPARATOR)}${TERMINATOR}${ESCAPE}])`,
|
|
46
|
+
"g"
|
|
47
|
+
);
|
|
40
48
|
function escapeStr(str, pattern) {
|
|
41
49
|
return encodeURI(str.replace(pattern, `${ESCAPE}$1`));
|
|
42
50
|
}
|
|
43
|
-
function cleanResult(str) {
|
|
51
|
+
function cleanResult(str, standalone) {
|
|
44
52
|
while (str.endsWith(TERMINATOR)) str = str.slice(0, -1);
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
const datePattern = new RegExp(`^${esc(TYPE_STRING)}${DATE_PREFIX}-?\\d+$`);
|
|
54
|
+
if (standalone && datePattern.test(str)) {
|
|
55
|
+
return str.slice(1);
|
|
56
|
+
}
|
|
57
|
+
if (!standalone && str.startsWith(TYPE_OBJECT)) {
|
|
58
|
+
return str.slice(1);
|
|
47
59
|
}
|
|
48
60
|
return str;
|
|
49
61
|
}
|
|
50
|
-
function stringify(value) {
|
|
51
|
-
return cleanResult(serialize(value));
|
|
62
|
+
function stringify(value, standalone = false) {
|
|
63
|
+
return cleanResult(serialize(value, standalone), standalone);
|
|
52
64
|
}
|
|
53
|
-
function serialize(value) {
|
|
65
|
+
function serialize(value, standalone, inArray = false) {
|
|
54
66
|
if (value === null) return `${TYPE_PRIMITIVE}null`;
|
|
55
67
|
if (value === void 0) return `${TYPE_PRIMITIVE}undefined`;
|
|
56
68
|
if (typeof value === "function") return "";
|
|
@@ -61,107 +73,166 @@ function serialize(value) {
|
|
|
61
73
|
return `${TYPE_PRIMITIVE}${value}`;
|
|
62
74
|
}
|
|
63
75
|
if (value instanceof Date) {
|
|
64
|
-
return `${TYPE_STRING}${DATE_PREFIX}${
|
|
76
|
+
return `${TYPE_STRING}${DATE_PREFIX}${value.getTime()}`;
|
|
65
77
|
}
|
|
66
78
|
if (Array.isArray(value)) {
|
|
67
|
-
const items = value.map((v) => serialize(v));
|
|
79
|
+
const items = value.map((v) => serialize(v, standalone, true));
|
|
68
80
|
return `${TYPE_ARRAY}${items.join(SEPARATOR)}${TERMINATOR}`;
|
|
69
81
|
}
|
|
70
82
|
if (typeof value === "object") {
|
|
71
83
|
const entries = [];
|
|
72
84
|
for (const [k, v] of Object.entries(value)) {
|
|
73
|
-
const val = serialize(v);
|
|
85
|
+
const val = serialize(v, false, false);
|
|
74
86
|
if (val || v === void 0) {
|
|
75
87
|
entries.push(`${escapeStr(k, KEY_ESCAPE)}${val}`);
|
|
76
88
|
}
|
|
77
89
|
}
|
|
78
90
|
return `${TYPE_OBJECT}${entries.join(SEPARATOR)}${TERMINATOR}`;
|
|
79
91
|
}
|
|
80
|
-
|
|
92
|
+
const strVal = String(value);
|
|
93
|
+
let escaped = escapeStr(strVal, VALUE_ESCAPE);
|
|
94
|
+
const datePattern = new RegExp(`^${DATE_PREFIX}-?\\d`);
|
|
95
|
+
if (datePattern.test(strVal)) {
|
|
96
|
+
escaped = ESCAPE + escaped;
|
|
97
|
+
}
|
|
98
|
+
return standalone || inArray ? escaped : `${TYPE_STRING}${escaped}`;
|
|
81
99
|
}
|
|
82
|
-
function parse(input) {
|
|
100
|
+
function parse(input, standalone = false) {
|
|
83
101
|
const str = decodeURI(input);
|
|
102
|
+
if (str.length === 0) {
|
|
103
|
+
return standalone ? "" : {};
|
|
104
|
+
}
|
|
84
105
|
const first = str[0];
|
|
85
106
|
const hasMarker = first === TYPE_STRING || first === TYPE_PRIMITIVE || first === TYPE_ARRAY || first === TYPE_OBJECT;
|
|
107
|
+
let source;
|
|
108
|
+
if (hasMarker) {
|
|
109
|
+
source = str;
|
|
110
|
+
} else if (standalone) {
|
|
111
|
+
const datePattern = new RegExp(`^${DATE_PREFIX}-?\\d+$`);
|
|
112
|
+
if (datePattern.test(str)) {
|
|
113
|
+
const timestamp = parseInt(str.slice(DATE_PREFIX.length), 10);
|
|
114
|
+
if (!isNaN(timestamp)) {
|
|
115
|
+
return new Date(timestamp);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return str;
|
|
119
|
+
} else {
|
|
120
|
+
const markers = `${TYPE_STRING}${TYPE_PRIMITIVE}${TYPE_ARRAY}${TYPE_OBJECT}`;
|
|
121
|
+
const objectPattern = new RegExp(
|
|
122
|
+
`[^${ESCAPE}${markers}${SEPARATOR}${TERMINATOR}][${markers}]`
|
|
123
|
+
);
|
|
124
|
+
source = objectPattern.test(str) ? `${TYPE_OBJECT}${str}` : `${TYPE_STRING}${str}`;
|
|
125
|
+
}
|
|
86
126
|
let pos = 0;
|
|
87
|
-
|
|
88
|
-
|
|
127
|
+
function peek() {
|
|
128
|
+
return source[pos] || "";
|
|
129
|
+
}
|
|
130
|
+
function advance() {
|
|
131
|
+
pos++;
|
|
132
|
+
}
|
|
133
|
+
function readUntil(stopPattern, checkEscape = false) {
|
|
89
134
|
let result = "";
|
|
135
|
+
let wasEscaped = false;
|
|
90
136
|
while (pos < source.length) {
|
|
91
|
-
const
|
|
92
|
-
if (
|
|
93
|
-
pos
|
|
94
|
-
|
|
137
|
+
const ch = peek();
|
|
138
|
+
if (ch === ESCAPE) {
|
|
139
|
+
if (checkEscape && pos + 1 < source.length && source[pos + 1] === DATE_PREFIX) {
|
|
140
|
+
wasEscaped = true;
|
|
141
|
+
}
|
|
142
|
+
advance();
|
|
143
|
+
if (pos < source.length) {
|
|
144
|
+
result += peek();
|
|
145
|
+
advance();
|
|
146
|
+
}
|
|
95
147
|
continue;
|
|
96
148
|
}
|
|
97
|
-
if (
|
|
98
|
-
result +=
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
return result;
|
|
102
|
-
}
|
|
103
|
-
function skipSeparator() {
|
|
104
|
-
if (source[pos] === SEPARATOR) {
|
|
105
|
-
pos++;
|
|
149
|
+
if (stopPattern.test(ch)) break;
|
|
150
|
+
result += ch;
|
|
151
|
+
advance();
|
|
106
152
|
}
|
|
153
|
+
return { value: result, wasEscaped };
|
|
107
154
|
}
|
|
108
155
|
function parseString() {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
156
|
+
const { value, wasEscaped } = readUntil(VALUE_STOP, true);
|
|
157
|
+
const datePattern = new RegExp(`^${DATE_PREFIX}-?\\d+$`);
|
|
158
|
+
if (!wasEscaped && datePattern.test(value)) {
|
|
159
|
+
const timestamp = parseInt(value.slice(DATE_PREFIX.length), 10);
|
|
160
|
+
if (!isNaN(timestamp)) {
|
|
161
|
+
return new Date(timestamp);
|
|
162
|
+
}
|
|
112
163
|
}
|
|
113
|
-
return
|
|
164
|
+
return value;
|
|
114
165
|
}
|
|
115
166
|
function parsePrimitive() {
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
118
|
-
if (
|
|
119
|
-
if (
|
|
120
|
-
if (
|
|
121
|
-
return parseFloat(
|
|
167
|
+
const { value } = readUntil(VALUE_STOP, false);
|
|
168
|
+
if (value === "null") return null;
|
|
169
|
+
if (value === "undefined") return void 0;
|
|
170
|
+
if (value === "true") return true;
|
|
171
|
+
if (value === "false") return false;
|
|
172
|
+
return parseFloat(value);
|
|
122
173
|
}
|
|
123
174
|
function parseArray() {
|
|
124
175
|
const result = [];
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
176
|
+
let lastWasSeparator = false;
|
|
177
|
+
while (pos < source.length && peek() !== TERMINATOR) {
|
|
178
|
+
if (peek() === SEPARATOR) {
|
|
179
|
+
result.push("");
|
|
180
|
+
advance();
|
|
181
|
+
lastWasSeparator = true;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
lastWasSeparator = false;
|
|
185
|
+
const ch = peek();
|
|
186
|
+
if (ch === TYPE_PRIMITIVE || ch === TYPE_ARRAY || ch === TYPE_OBJECT || ch === TYPE_STRING) {
|
|
187
|
+
result.push(parseValue());
|
|
188
|
+
} else {
|
|
189
|
+
result.push(parseString());
|
|
190
|
+
}
|
|
191
|
+
if (pos < source.length && peek() === SEPARATOR) {
|
|
192
|
+
advance();
|
|
193
|
+
lastWasSeparator = true;
|
|
129
194
|
}
|
|
130
195
|
}
|
|
131
|
-
if (
|
|
196
|
+
if (lastWasSeparator && (peek() === TERMINATOR || pos >= source.length)) {
|
|
197
|
+
result.push("");
|
|
198
|
+
}
|
|
199
|
+
if (peek() === TERMINATOR) advance();
|
|
132
200
|
return result;
|
|
133
201
|
}
|
|
134
202
|
function parseObject() {
|
|
135
203
|
const result = {};
|
|
136
|
-
while (pos < source.length &&
|
|
137
|
-
const key = readUntil(KEY_STOP);
|
|
204
|
+
while (pos < source.length && peek() !== TERMINATOR) {
|
|
205
|
+
const { value: key } = readUntil(KEY_STOP, false);
|
|
138
206
|
result[key] = parseValue();
|
|
139
|
-
if (pos < source.length &&
|
|
140
|
-
|
|
207
|
+
if (pos < source.length && peek() !== TERMINATOR && peek() === SEPARATOR) {
|
|
208
|
+
advance();
|
|
141
209
|
}
|
|
142
210
|
}
|
|
143
|
-
if (
|
|
211
|
+
if (peek() === TERMINATOR) advance();
|
|
144
212
|
return result;
|
|
145
213
|
}
|
|
146
214
|
function parseValue() {
|
|
147
|
-
const type =
|
|
215
|
+
const type = peek();
|
|
216
|
+
advance();
|
|
148
217
|
switch (type) {
|
|
149
|
-
case TYPE_STRING:
|
|
150
|
-
return parseString();
|
|
151
218
|
case TYPE_PRIMITIVE:
|
|
152
219
|
return parsePrimitive();
|
|
153
220
|
case TYPE_ARRAY:
|
|
154
221
|
return parseArray();
|
|
155
222
|
case TYPE_OBJECT:
|
|
156
223
|
return parseObject();
|
|
224
|
+
case TYPE_STRING:
|
|
225
|
+
return parseString();
|
|
157
226
|
default:
|
|
158
|
-
throw new Error(`Unexpected type "${type}" at position ${pos
|
|
227
|
+
throw new Error(`Unexpected type "${type}" at position ${pos}`);
|
|
159
228
|
}
|
|
160
229
|
}
|
|
161
230
|
return parseValue();
|
|
162
231
|
}
|
|
232
|
+
var readable = { stringify, parse };
|
|
163
233
|
// Annotate the CommonJS export names for ESM import in node:
|
|
164
234
|
0 && (module.exports = {
|
|
165
235
|
parse,
|
|
236
|
+
readable,
|
|
166
237
|
stringify
|
|
167
238
|
});
|
package/dist/format/readable.mjs
CHANGED
|
@@ -6,27 +6,38 @@ var TYPE_PRIMITIVE = ":";
|
|
|
6
6
|
var SEPARATOR = ",";
|
|
7
7
|
var TERMINATOR = "~";
|
|
8
8
|
var ESCAPE = "/";
|
|
9
|
-
var DATE_PREFIX = "
|
|
10
|
-
var SEP_CHAR = SEPARATOR[0];
|
|
9
|
+
var DATE_PREFIX = "D";
|
|
11
10
|
var esc = (c) => /[.$^*+?()[\]{}|\\]/.test(c) ? `\\${c}` : c;
|
|
12
|
-
var KEY_STOP = new RegExp(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
var
|
|
11
|
+
var KEY_STOP = new RegExp(
|
|
12
|
+
`[${TYPE_STRING}${TYPE_PRIMITIVE}${TYPE_ARRAY}${esc(TYPE_OBJECT)}${esc(SEPARATOR)}]`
|
|
13
|
+
);
|
|
14
|
+
var VALUE_STOP = new RegExp(`[${esc(SEPARATOR)}${TERMINATOR}]`);
|
|
15
|
+
var KEY_ESCAPE = new RegExp(
|
|
16
|
+
`([${TYPE_STRING}${TYPE_PRIMITIVE}${TYPE_ARRAY}${esc(TYPE_OBJECT)}${ESCAPE}${esc(SEPARATOR)}])`,
|
|
17
|
+
"g"
|
|
18
|
+
);
|
|
19
|
+
var VALUE_ESCAPE = new RegExp(
|
|
20
|
+
`([${esc(SEPARATOR)}${TERMINATOR}${ESCAPE}])`,
|
|
21
|
+
"g"
|
|
22
|
+
);
|
|
16
23
|
function escapeStr(str, pattern) {
|
|
17
24
|
return encodeURI(str.replace(pattern, `${ESCAPE}$1`));
|
|
18
25
|
}
|
|
19
|
-
function cleanResult(str) {
|
|
26
|
+
function cleanResult(str, standalone) {
|
|
20
27
|
while (str.endsWith(TERMINATOR)) str = str.slice(0, -1);
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
const datePattern = new RegExp(`^${esc(TYPE_STRING)}${DATE_PREFIX}-?\\d+$`);
|
|
29
|
+
if (standalone && datePattern.test(str)) {
|
|
30
|
+
return str.slice(1);
|
|
31
|
+
}
|
|
32
|
+
if (!standalone && str.startsWith(TYPE_OBJECT)) {
|
|
33
|
+
return str.slice(1);
|
|
23
34
|
}
|
|
24
35
|
return str;
|
|
25
36
|
}
|
|
26
|
-
function stringify(value) {
|
|
27
|
-
return cleanResult(serialize(value));
|
|
37
|
+
function stringify(value, standalone = false) {
|
|
38
|
+
return cleanResult(serialize(value, standalone), standalone);
|
|
28
39
|
}
|
|
29
|
-
function serialize(value) {
|
|
40
|
+
function serialize(value, standalone, inArray = false) {
|
|
30
41
|
if (value === null) return `${TYPE_PRIMITIVE}null`;
|
|
31
42
|
if (value === void 0) return `${TYPE_PRIMITIVE}undefined`;
|
|
32
43
|
if (typeof value === "function") return "";
|
|
@@ -37,106 +48,165 @@ function serialize(value) {
|
|
|
37
48
|
return `${TYPE_PRIMITIVE}${value}`;
|
|
38
49
|
}
|
|
39
50
|
if (value instanceof Date) {
|
|
40
|
-
return `${TYPE_STRING}${DATE_PREFIX}${
|
|
51
|
+
return `${TYPE_STRING}${DATE_PREFIX}${value.getTime()}`;
|
|
41
52
|
}
|
|
42
53
|
if (Array.isArray(value)) {
|
|
43
|
-
const items = value.map((v) => serialize(v));
|
|
54
|
+
const items = value.map((v) => serialize(v, standalone, true));
|
|
44
55
|
return `${TYPE_ARRAY}${items.join(SEPARATOR)}${TERMINATOR}`;
|
|
45
56
|
}
|
|
46
57
|
if (typeof value === "object") {
|
|
47
58
|
const entries = [];
|
|
48
59
|
for (const [k, v] of Object.entries(value)) {
|
|
49
|
-
const val = serialize(v);
|
|
60
|
+
const val = serialize(v, false, false);
|
|
50
61
|
if (val || v === void 0) {
|
|
51
62
|
entries.push(`${escapeStr(k, KEY_ESCAPE)}${val}`);
|
|
52
63
|
}
|
|
53
64
|
}
|
|
54
65
|
return `${TYPE_OBJECT}${entries.join(SEPARATOR)}${TERMINATOR}`;
|
|
55
66
|
}
|
|
56
|
-
|
|
67
|
+
const strVal = String(value);
|
|
68
|
+
let escaped = escapeStr(strVal, VALUE_ESCAPE);
|
|
69
|
+
const datePattern = new RegExp(`^${DATE_PREFIX}-?\\d`);
|
|
70
|
+
if (datePattern.test(strVal)) {
|
|
71
|
+
escaped = ESCAPE + escaped;
|
|
72
|
+
}
|
|
73
|
+
return standalone || inArray ? escaped : `${TYPE_STRING}${escaped}`;
|
|
57
74
|
}
|
|
58
|
-
function parse(input) {
|
|
75
|
+
function parse(input, standalone = false) {
|
|
59
76
|
const str = decodeURI(input);
|
|
77
|
+
if (str.length === 0) {
|
|
78
|
+
return standalone ? "" : {};
|
|
79
|
+
}
|
|
60
80
|
const first = str[0];
|
|
61
81
|
const hasMarker = first === TYPE_STRING || first === TYPE_PRIMITIVE || first === TYPE_ARRAY || first === TYPE_OBJECT;
|
|
82
|
+
let source;
|
|
83
|
+
if (hasMarker) {
|
|
84
|
+
source = str;
|
|
85
|
+
} else if (standalone) {
|
|
86
|
+
const datePattern = new RegExp(`^${DATE_PREFIX}-?\\d+$`);
|
|
87
|
+
if (datePattern.test(str)) {
|
|
88
|
+
const timestamp = parseInt(str.slice(DATE_PREFIX.length), 10);
|
|
89
|
+
if (!isNaN(timestamp)) {
|
|
90
|
+
return new Date(timestamp);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return str;
|
|
94
|
+
} else {
|
|
95
|
+
const markers = `${TYPE_STRING}${TYPE_PRIMITIVE}${TYPE_ARRAY}${TYPE_OBJECT}`;
|
|
96
|
+
const objectPattern = new RegExp(
|
|
97
|
+
`[^${ESCAPE}${markers}${SEPARATOR}${TERMINATOR}][${markers}]`
|
|
98
|
+
);
|
|
99
|
+
source = objectPattern.test(str) ? `${TYPE_OBJECT}${str}` : `${TYPE_STRING}${str}`;
|
|
100
|
+
}
|
|
62
101
|
let pos = 0;
|
|
63
|
-
|
|
64
|
-
|
|
102
|
+
function peek() {
|
|
103
|
+
return source[pos] || "";
|
|
104
|
+
}
|
|
105
|
+
function advance() {
|
|
106
|
+
pos++;
|
|
107
|
+
}
|
|
108
|
+
function readUntil(stopPattern, checkEscape = false) {
|
|
65
109
|
let result = "";
|
|
110
|
+
let wasEscaped = false;
|
|
66
111
|
while (pos < source.length) {
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
pos
|
|
70
|
-
|
|
112
|
+
const ch = peek();
|
|
113
|
+
if (ch === ESCAPE) {
|
|
114
|
+
if (checkEscape && pos + 1 < source.length && source[pos + 1] === DATE_PREFIX) {
|
|
115
|
+
wasEscaped = true;
|
|
116
|
+
}
|
|
117
|
+
advance();
|
|
118
|
+
if (pos < source.length) {
|
|
119
|
+
result += peek();
|
|
120
|
+
advance();
|
|
121
|
+
}
|
|
71
122
|
continue;
|
|
72
123
|
}
|
|
73
|
-
if (
|
|
74
|
-
result +=
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
return result;
|
|
78
|
-
}
|
|
79
|
-
function skipSeparator() {
|
|
80
|
-
if (source[pos] === SEPARATOR) {
|
|
81
|
-
pos++;
|
|
124
|
+
if (stopPattern.test(ch)) break;
|
|
125
|
+
result += ch;
|
|
126
|
+
advance();
|
|
82
127
|
}
|
|
128
|
+
return { value: result, wasEscaped };
|
|
83
129
|
}
|
|
84
130
|
function parseString() {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
131
|
+
const { value, wasEscaped } = readUntil(VALUE_STOP, true);
|
|
132
|
+
const datePattern = new RegExp(`^${DATE_PREFIX}-?\\d+$`);
|
|
133
|
+
if (!wasEscaped && datePattern.test(value)) {
|
|
134
|
+
const timestamp = parseInt(value.slice(DATE_PREFIX.length), 10);
|
|
135
|
+
if (!isNaN(timestamp)) {
|
|
136
|
+
return new Date(timestamp);
|
|
137
|
+
}
|
|
88
138
|
}
|
|
89
|
-
return
|
|
139
|
+
return value;
|
|
90
140
|
}
|
|
91
141
|
function parsePrimitive() {
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
if (
|
|
95
|
-
if (
|
|
96
|
-
if (
|
|
97
|
-
return parseFloat(
|
|
142
|
+
const { value } = readUntil(VALUE_STOP, false);
|
|
143
|
+
if (value === "null") return null;
|
|
144
|
+
if (value === "undefined") return void 0;
|
|
145
|
+
if (value === "true") return true;
|
|
146
|
+
if (value === "false") return false;
|
|
147
|
+
return parseFloat(value);
|
|
98
148
|
}
|
|
99
149
|
function parseArray() {
|
|
100
150
|
const result = [];
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
|
|
151
|
+
let lastWasSeparator = false;
|
|
152
|
+
while (pos < source.length && peek() !== TERMINATOR) {
|
|
153
|
+
if (peek() === SEPARATOR) {
|
|
154
|
+
result.push("");
|
|
155
|
+
advance();
|
|
156
|
+
lastWasSeparator = true;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
lastWasSeparator = false;
|
|
160
|
+
const ch = peek();
|
|
161
|
+
if (ch === TYPE_PRIMITIVE || ch === TYPE_ARRAY || ch === TYPE_OBJECT || ch === TYPE_STRING) {
|
|
162
|
+
result.push(parseValue());
|
|
163
|
+
} else {
|
|
164
|
+
result.push(parseString());
|
|
165
|
+
}
|
|
166
|
+
if (pos < source.length && peek() === SEPARATOR) {
|
|
167
|
+
advance();
|
|
168
|
+
lastWasSeparator = true;
|
|
105
169
|
}
|
|
106
170
|
}
|
|
107
|
-
if (
|
|
171
|
+
if (lastWasSeparator && (peek() === TERMINATOR || pos >= source.length)) {
|
|
172
|
+
result.push("");
|
|
173
|
+
}
|
|
174
|
+
if (peek() === TERMINATOR) advance();
|
|
108
175
|
return result;
|
|
109
176
|
}
|
|
110
177
|
function parseObject() {
|
|
111
178
|
const result = {};
|
|
112
|
-
while (pos < source.length &&
|
|
113
|
-
const key = readUntil(KEY_STOP);
|
|
179
|
+
while (pos < source.length && peek() !== TERMINATOR) {
|
|
180
|
+
const { value: key } = readUntil(KEY_STOP, false);
|
|
114
181
|
result[key] = parseValue();
|
|
115
|
-
if (pos < source.length &&
|
|
116
|
-
|
|
182
|
+
if (pos < source.length && peek() !== TERMINATOR && peek() === SEPARATOR) {
|
|
183
|
+
advance();
|
|
117
184
|
}
|
|
118
185
|
}
|
|
119
|
-
if (
|
|
186
|
+
if (peek() === TERMINATOR) advance();
|
|
120
187
|
return result;
|
|
121
188
|
}
|
|
122
189
|
function parseValue() {
|
|
123
|
-
const type =
|
|
190
|
+
const type = peek();
|
|
191
|
+
advance();
|
|
124
192
|
switch (type) {
|
|
125
|
-
case TYPE_STRING:
|
|
126
|
-
return parseString();
|
|
127
193
|
case TYPE_PRIMITIVE:
|
|
128
194
|
return parsePrimitive();
|
|
129
195
|
case TYPE_ARRAY:
|
|
130
196
|
return parseArray();
|
|
131
197
|
case TYPE_OBJECT:
|
|
132
198
|
return parseObject();
|
|
199
|
+
case TYPE_STRING:
|
|
200
|
+
return parseString();
|
|
133
201
|
default:
|
|
134
|
-
throw new Error(`Unexpected type "${type}" at position ${pos
|
|
202
|
+
throw new Error(`Unexpected type "${type}" at position ${pos}`);
|
|
135
203
|
}
|
|
136
204
|
}
|
|
137
205
|
return parseValue();
|
|
138
206
|
}
|
|
207
|
+
var readable = { stringify, parse };
|
|
139
208
|
export {
|
|
140
209
|
parse,
|
|
210
|
+
readable,
|
|
141
211
|
stringify
|
|
142
212
|
};
|
package/dist/index.d.mts
CHANGED
|
@@ -3,17 +3,15 @@ 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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
interface QueryStringFormat {
|
|
7
|
+
stringify: (value: any, standalone?: boolean) => string;
|
|
8
|
+
parse: (value: string, standalone?: boolean) => any;
|
|
9
|
+
}
|
|
9
10
|
interface QueryStringOptions<T> {
|
|
10
11
|
url?: string;
|
|
11
12
|
select?: (pathname: string) => DeepSelect<T>;
|
|
12
|
-
key?: string;
|
|
13
|
-
format?:
|
|
14
|
-
stringify: (value: DeepPartial<T>) => string;
|
|
15
|
-
parse: (value: string) => DeepPartial<T>;
|
|
16
|
-
};
|
|
13
|
+
key?: string | false;
|
|
14
|
+
format?: QueryStringFormat;
|
|
17
15
|
syncNull?: boolean;
|
|
18
16
|
syncUndefined?: boolean;
|
|
19
17
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,17 +3,15 @@ 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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
interface QueryStringFormat {
|
|
7
|
+
stringify: (value: any, standalone?: boolean) => string;
|
|
8
|
+
parse: (value: string, standalone?: boolean) => any;
|
|
9
|
+
}
|
|
9
10
|
interface QueryStringOptions<T> {
|
|
10
11
|
url?: string;
|
|
11
12
|
select?: (pathname: string) => DeepSelect<T>;
|
|
12
|
-
key?: string;
|
|
13
|
-
format?:
|
|
14
|
-
stringify: (value: DeepPartial<T>) => string;
|
|
15
|
-
parse: (value: string) => DeepPartial<T>;
|
|
16
|
-
};
|
|
13
|
+
key?: string | false;
|
|
14
|
+
format?: QueryStringFormat;
|
|
17
15
|
syncNull?: boolean;
|
|
18
16
|
syncUndefined?: boolean;
|
|
19
17
|
}
|
package/dist/index.js
CHANGED
|
@@ -40,22 +40,25 @@ function parse(str) {
|
|
|
40
40
|
// src/middleware.ts
|
|
41
41
|
var compact = (newState, initialState, syncNull = false, syncUndefined = false) => {
|
|
42
42
|
const output = {};
|
|
43
|
+
const removed = [];
|
|
43
44
|
Object.keys(newState).forEach((key) => {
|
|
44
45
|
const newValue = newState[key];
|
|
45
46
|
const initialValue = initialState[key];
|
|
46
47
|
if (typeof newValue !== "function" && !(0, import_lodash_es.isEqual)(newValue, initialValue) && (syncNull || newValue !== null) && (syncUndefined || newValue !== void 0)) {
|
|
47
48
|
const isPlainObject = typeof newValue === "object" && newValue !== null && newValue !== void 0 && !Array.isArray(newValue) && newValue.constructor === Object;
|
|
48
49
|
if (isPlainObject && initialValue && typeof initialValue === "object") {
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
51
|
-
output[key] =
|
|
50
|
+
const result = compact(newValue, initialValue, syncNull, syncUndefined);
|
|
51
|
+
if (result.output && Object.keys(result.output).length > 0) {
|
|
52
|
+
output[key] = result.output;
|
|
52
53
|
}
|
|
53
54
|
} else {
|
|
54
55
|
output[key] = newValue;
|
|
55
56
|
}
|
|
57
|
+
} else {
|
|
58
|
+
removed.push(key);
|
|
56
59
|
}
|
|
57
60
|
});
|
|
58
|
-
return output;
|
|
61
|
+
return { output, removed };
|
|
59
62
|
};
|
|
60
63
|
var translateSelectionToState = (selection, state) => {
|
|
61
64
|
if (typeof state !== "object" || !state) {
|
|
@@ -87,7 +90,16 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
87
90
|
syncUndefined: false,
|
|
88
91
|
...options
|
|
89
92
|
};
|
|
93
|
+
const standalone = !defaultedOptions.key;
|
|
90
94
|
const getStateFromUrl = (url) => {
|
|
95
|
+
if (standalone) {
|
|
96
|
+
const params2 = new URLSearchParams(url.search);
|
|
97
|
+
const state = {};
|
|
98
|
+
params2.forEach((value, key) => {
|
|
99
|
+
state[key] = defaultedOptions.format.parse(value, true);
|
|
100
|
+
});
|
|
101
|
+
return Object.keys(state).length > 0 ? state : null;
|
|
102
|
+
}
|
|
91
103
|
const params = url.search.slice(1).split("&");
|
|
92
104
|
for (const param of params) {
|
|
93
105
|
const eqIndex = param.indexOf("=");
|
|
@@ -95,7 +107,7 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
95
107
|
const key = param.slice(0, eqIndex);
|
|
96
108
|
if (key === defaultedOptions.key) {
|
|
97
109
|
const value = param.slice(eqIndex + 1);
|
|
98
|
-
return value ? defaultedOptions.format.parse(value) : null;
|
|
110
|
+
return value ? defaultedOptions.format.parse(value, false) : null;
|
|
99
111
|
}
|
|
100
112
|
}
|
|
101
113
|
return null;
|
|
@@ -137,7 +149,7 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
137
149
|
const setQuery = () => {
|
|
138
150
|
const url = new URL(window.location.href);
|
|
139
151
|
const selectedState = getSelectedState(get(), url.pathname);
|
|
140
|
-
const newCompacted = compact(
|
|
152
|
+
const { output: newCompacted } = compact(
|
|
141
153
|
selectedState,
|
|
142
154
|
initialState,
|
|
143
155
|
defaultedOptions.syncNull,
|
|
@@ -145,21 +157,31 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
145
157
|
);
|
|
146
158
|
const previous = url.search;
|
|
147
159
|
const params = url.search.slice(1).split("&").filter(Boolean);
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
const managedKeys = standalone ? new Set(Object.keys(selectedState).map(encodeURI)) : /* @__PURE__ */ new Set([defaultedOptions.key]);
|
|
161
|
+
const valuesToWrite = standalone ? new Map(
|
|
162
|
+
Object.entries(newCompacted).map(([k, v]) => [encodeURI(k), v])
|
|
163
|
+
) : Object.keys(newCompacted).length ? /* @__PURE__ */ new Map([[defaultedOptions.key, newCompacted]]) : /* @__PURE__ */ new Map();
|
|
164
|
+
const result = [];
|
|
165
|
+
params.forEach((p) => {
|
|
166
|
+
const key = p.split("=")[0];
|
|
167
|
+
if (!managedKeys.has(key)) {
|
|
168
|
+
result.push(p);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (valuesToWrite.has(key)) {
|
|
172
|
+
const encoded = defaultedOptions.format.stringify(
|
|
173
|
+
valuesToWrite.get(key),
|
|
174
|
+
standalone
|
|
175
|
+
);
|
|
176
|
+
result.push(`${key}=${encoded}`);
|
|
177
|
+
valuesToWrite.delete(key);
|
|
154
178
|
}
|
|
155
|
-
return true;
|
|
156
179
|
});
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
url.search = otherParams.length ? "?" + otherParams.join("&") : "";
|
|
180
|
+
valuesToWrite.forEach((value, key) => {
|
|
181
|
+
const encoded = defaultedOptions.format.stringify(value, standalone);
|
|
182
|
+
result.push(`${key}=${encoded}`);
|
|
183
|
+
});
|
|
184
|
+
url.search = result.length ? "?" + result.join("&") : "";
|
|
163
185
|
if (url.search !== previous) {
|
|
164
186
|
history.replaceState(history.state, "", url);
|
|
165
187
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -12,22 +12,25 @@ function parse(str) {
|
|
|
12
12
|
// src/middleware.ts
|
|
13
13
|
var compact = (newState, initialState, syncNull = false, syncUndefined = false) => {
|
|
14
14
|
const output = {};
|
|
15
|
+
const removed = [];
|
|
15
16
|
Object.keys(newState).forEach((key) => {
|
|
16
17
|
const newValue = newState[key];
|
|
17
18
|
const initialValue = initialState[key];
|
|
18
19
|
if (typeof newValue !== "function" && !isEqual(newValue, initialValue) && (syncNull || newValue !== null) && (syncUndefined || newValue !== void 0)) {
|
|
19
20
|
const isPlainObject = typeof newValue === "object" && newValue !== null && newValue !== void 0 && !Array.isArray(newValue) && newValue.constructor === Object;
|
|
20
21
|
if (isPlainObject && initialValue && typeof initialValue === "object") {
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
output[key] =
|
|
22
|
+
const result = compact(newValue, initialValue, syncNull, syncUndefined);
|
|
23
|
+
if (result.output && Object.keys(result.output).length > 0) {
|
|
24
|
+
output[key] = result.output;
|
|
24
25
|
}
|
|
25
26
|
} else {
|
|
26
27
|
output[key] = newValue;
|
|
27
28
|
}
|
|
29
|
+
} else {
|
|
30
|
+
removed.push(key);
|
|
28
31
|
}
|
|
29
32
|
});
|
|
30
|
-
return output;
|
|
33
|
+
return { output, removed };
|
|
31
34
|
};
|
|
32
35
|
var translateSelectionToState = (selection, state) => {
|
|
33
36
|
if (typeof state !== "object" || !state) {
|
|
@@ -59,7 +62,16 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
59
62
|
syncUndefined: false,
|
|
60
63
|
...options
|
|
61
64
|
};
|
|
65
|
+
const standalone = !defaultedOptions.key;
|
|
62
66
|
const getStateFromUrl = (url) => {
|
|
67
|
+
if (standalone) {
|
|
68
|
+
const params2 = new URLSearchParams(url.search);
|
|
69
|
+
const state = {};
|
|
70
|
+
params2.forEach((value, key) => {
|
|
71
|
+
state[key] = defaultedOptions.format.parse(value, true);
|
|
72
|
+
});
|
|
73
|
+
return Object.keys(state).length > 0 ? state : null;
|
|
74
|
+
}
|
|
63
75
|
const params = url.search.slice(1).split("&");
|
|
64
76
|
for (const param of params) {
|
|
65
77
|
const eqIndex = param.indexOf("=");
|
|
@@ -67,7 +79,7 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
67
79
|
const key = param.slice(0, eqIndex);
|
|
68
80
|
if (key === defaultedOptions.key) {
|
|
69
81
|
const value = param.slice(eqIndex + 1);
|
|
70
|
-
return value ? defaultedOptions.format.parse(value) : null;
|
|
82
|
+
return value ? defaultedOptions.format.parse(value, false) : null;
|
|
71
83
|
}
|
|
72
84
|
}
|
|
73
85
|
return null;
|
|
@@ -109,7 +121,7 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
109
121
|
const setQuery = () => {
|
|
110
122
|
const url = new URL(window.location.href);
|
|
111
123
|
const selectedState = getSelectedState(get(), url.pathname);
|
|
112
|
-
const newCompacted = compact(
|
|
124
|
+
const { output: newCompacted } = compact(
|
|
113
125
|
selectedState,
|
|
114
126
|
initialState,
|
|
115
127
|
defaultedOptions.syncNull,
|
|
@@ -117,21 +129,31 @@ var queryStringImpl = (fn, options) => (set, get, api) => {
|
|
|
117
129
|
);
|
|
118
130
|
const previous = url.search;
|
|
119
131
|
const params = url.search.slice(1).split("&").filter(Boolean);
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
const managedKeys = standalone ? new Set(Object.keys(selectedState).map(encodeURI)) : /* @__PURE__ */ new Set([defaultedOptions.key]);
|
|
133
|
+
const valuesToWrite = standalone ? new Map(
|
|
134
|
+
Object.entries(newCompacted).map(([k, v]) => [encodeURI(k), v])
|
|
135
|
+
) : Object.keys(newCompacted).length ? /* @__PURE__ */ new Map([[defaultedOptions.key, newCompacted]]) : /* @__PURE__ */ new Map();
|
|
136
|
+
const result = [];
|
|
137
|
+
params.forEach((p) => {
|
|
138
|
+
const key = p.split("=")[0];
|
|
139
|
+
if (!managedKeys.has(key)) {
|
|
140
|
+
result.push(p);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (valuesToWrite.has(key)) {
|
|
144
|
+
const encoded = defaultedOptions.format.stringify(
|
|
145
|
+
valuesToWrite.get(key),
|
|
146
|
+
standalone
|
|
147
|
+
);
|
|
148
|
+
result.push(`${key}=${encoded}`);
|
|
149
|
+
valuesToWrite.delete(key);
|
|
126
150
|
}
|
|
127
|
-
return true;
|
|
128
151
|
});
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
url.search = otherParams.length ? "?" + otherParams.join("&") : "";
|
|
152
|
+
valuesToWrite.forEach((value, key) => {
|
|
153
|
+
const encoded = defaultedOptions.format.stringify(value, standalone);
|
|
154
|
+
result.push(`${key}=${encoded}`);
|
|
155
|
+
});
|
|
156
|
+
url.search = result.length ? "?" + result.join("&") : "";
|
|
135
157
|
if (url.search !== previous) {
|
|
136
158
|
history.replaceState(history.state, "", url);
|
|
137
159
|
}
|