zustand-querystring 0.0.9 → 0.0.11

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.
@@ -5,6 +5,7 @@ type DeepSelect<T> = T extends object ? {
5
5
  export interface QueryStringOptions<T> {
6
6
  url?: string;
7
7
  select?: (pathname: string) => DeepSelect<T>;
8
+ key?: string;
8
9
  }
9
10
  type QueryString = <T, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = []>(initializer: StateCreator<T, Mps, Mcs>, options?: QueryStringOptions<T>) => StateCreator<T, Mps, Mcs>;
10
11
  export declare const querystring: QueryString;
package/lib/middleware.js CHANGED
@@ -36,10 +36,30 @@ const translateSelectionToState = (selection, state) => Object.keys(selection).r
36
36
  }
37
37
  return acc;
38
38
  }, {});
39
+ const escapeStringRegexp = string => {
40
+ if (typeof string !== 'string') {
41
+ throw new TypeError('Expected a string');
42
+ }
43
+ return string.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
44
+ };
39
45
  const queryStringImpl = (fn, options) => (set, get, api) => {
40
46
  const defaultedOptions = {
47
+ key: '$',
41
48
  ...options,
42
49
  };
50
+ const stateMatcher = new RegExp(`${escapeStringRegexp(defaultedOptions.key)}=(.*);;`);
51
+ const matcher = new RegExp(`[&\?]?${escapeStringRegexp(defaultedOptions.key)}=(.*);;&?`);
52
+ const parseQueryString = querystring => {
53
+ const match = querystring.match(stateMatcher);
54
+ if (match) {
55
+ let m = match[1];
56
+ if (!m.startsWith('$')) {
57
+ m = '$' + m;
58
+ }
59
+ return parse(m);
60
+ }
61
+ return null;
62
+ };
43
63
  const url = defaultedOptions.url;
44
64
  const initialState = get() ?? fn(set, get, api);
45
65
  const getSelectedState = (state, pathname) => {
@@ -54,19 +74,15 @@ const queryStringImpl = (fn, options) => (set, get, api) => {
54
74
  const initialize = (url, _set = set) => {
55
75
  const fallback = () => fn(_set, get, api);
56
76
  try {
57
- const splitUrl = url.split('?');
58
- let queryString = splitUrl[1];
59
- const pathname = splitUrl[0];
77
+ const queryString = url.search.substring(1);
78
+ const pathname = url.pathname;
60
79
  if (!queryString) {
61
80
  return fallback();
62
81
  }
63
- if (!queryString) {
82
+ const parsed = parseQueryString(queryString);
83
+ if (!parsed) {
64
84
  return fallback();
65
85
  }
66
- if (!queryString.startsWith('$')) {
67
- queryString = '$' + queryString;
68
- }
69
- const parsed = parse(queryString);
70
86
  const currentValue = get() ?? fn(_set, get, api);
71
87
  const merged = mergeWith(currentValue, getSelectedState(parsed, pathname));
72
88
  set(merged, true);
@@ -79,49 +95,60 @@ const queryStringImpl = (fn, options) => (set, get, api) => {
79
95
  };
80
96
  if (typeof window !== 'undefined') {
81
97
  const setQuery = () => {
82
- const selectedState = getSelectedState(get(), window.location.pathname);
83
- if (!selectedState || Object.keys(selectedState).length === 0) {
84
- return;
85
- }
86
- let currentQueryString = window.location.search.slice(1);
87
- if (!currentQueryString.startsWith('$')) {
88
- currentQueryString = '$' + currentQueryString;
89
- }
90
- const currentParsed = parse(currentQueryString);
91
- const newMerged = mergeWith(currentParsed, selectedState, (objValue, srcValue) => {
92
- if (Array.isArray(objValue)) {
93
- return srcValue;
94
- }
95
- });
98
+ const selectedState = getSelectedState(get(), location.pathname);
99
+ const currentQueryString = location.search.slice(1);
100
+ const currentParsed = parseQueryString(currentQueryString);
101
+ const newMerged = {
102
+ ...currentParsed,
103
+ ...selectedState,
104
+ };
105
+ const ignored = currentQueryString.replace(matcher, '');
96
106
  const newCompacted = compact(newMerged, initialState);
97
107
  if (Object.keys(newCompacted).length) {
98
108
  const stringified = stringify(newCompacted).substring(1);
99
- window.history.replaceState(null, '', `?${stringified}`);
109
+ const newQueryState = `${defaultedOptions.key}=${stringified};;`;
110
+ let newQueryString = '';
111
+ if (currentParsed) {
112
+ newQueryString = currentQueryString.replace(stateMatcher, newQueryState);
113
+ }
114
+ else if (ignored) {
115
+ newQueryString = ignored + '&' + newQueryState;
116
+ }
117
+ else {
118
+ newQueryString = newQueryState;
119
+ }
120
+ history.replaceState(history.state, '', location.pathname + (newQueryString ? '?' + newQueryString : ''));
100
121
  }
101
122
  else {
102
- window.history.replaceState(null, '', window.location.pathname);
123
+ history.replaceState(history.state, '', location.pathname + (ignored ? '?' + ignored : ''));
103
124
  }
104
125
  };
105
- //TODO: find a better way to do this
106
- let previousPathname = '';
107
- setInterval(() => {
108
- if (window.location.pathname !== previousPathname) {
109
- previousPathname = window.location.pathname;
110
- setQuery();
111
- }
112
- }, 50);
126
+ // @ts-ignore
127
+ if (!api.__ZUSTAND_QUERYSTRING_INIT__) {
128
+ // @ts-ignore
129
+ api.__ZUSTAND_QUERYSTRING_INIT__ = true;
130
+ let previousPathname = '';
131
+ const cb = () => {
132
+ if (location.pathname !== previousPathname) {
133
+ previousPathname = location.pathname;
134
+ setQuery();
135
+ }
136
+ requestAnimationFrame(cb);
137
+ };
138
+ requestAnimationFrame(cb);
139
+ }
113
140
  const originalSetState = api.setState;
114
141
  api.setState = (...args) => {
115
142
  originalSetState(...args);
116
143
  setQuery();
117
144
  };
118
- return initialize(window.location.pathname + window.location.search, (...args) => {
145
+ return initialize(new URL(location.href), (...args) => {
119
146
  set(...args);
120
147
  setQuery();
121
148
  });
122
149
  }
123
- if (url) {
124
- return initialize(url);
150
+ else if (url) {
151
+ return initialize(new URL(decodeURIComponent(url), 'http://localhost'));
125
152
  }
126
153
  return fn(set, get, api);
127
154
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zustand-querystring",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -35,7 +35,7 @@
35
35
  "@types/lodash-es": "^4.17.6",
36
36
  "rimraf": "^3.0.2",
37
37
  "typescript": "^4.9.3",
38
- "zustand": "^4.1.4"
38
+ "zustand": "^4.3.2"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "rimraf lib && tsc -b"