@1money/hooks 0.1.1 → 0.1.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/es/index.d.ts CHANGED
@@ -9,3 +9,5 @@ export { default as useSyncState } from './useSyncState';
9
9
  export { default as useUpdateEffect } from './useUpdateEffect';
10
10
  export { default as useLayoutState, useTimeoutLock } from './useLayoutState';
11
11
  export type { Updater } from './useLayoutState';
12
+ export { default as useQueryState, queryString, queryNumber, queryBoolean, queryJson, } from './useQueryState';
13
+ export type { Parser, Serializer, QueryStateOptions, } from './useQueryState';
package/es/index.js CHANGED
@@ -8,4 +8,5 @@ export { default as usePrevious } from "./usePrevious";
8
8
  export { default as useSafeState } from "./useSafeState";
9
9
  export { default as useSyncState } from "./useSyncState";
10
10
  export { default as useUpdateEffect } from "./useUpdateEffect";
11
- export { default as useLayoutState, useTimeoutLock } from "./useLayoutState";
11
+ export { default as useLayoutState, useTimeoutLock } from "./useLayoutState";
12
+ export { default as useQueryState, queryString, queryNumber, queryBoolean, queryJson } from "./useQueryState";
@@ -0,0 +1,64 @@
1
+ /** Turn a raw query string into a typed value. */
2
+ export type Parser<T> = (value: string) => T;
3
+ /** Turn a typed value back into a query string. */
4
+ export type Serializer<T> = (value: T) => string;
5
+ export interface QueryStateOptions<T> {
6
+ /**
7
+ * Parse the raw `string` read from the URL into `T`.
8
+ * Defaults to identity (the raw string).
9
+ */
10
+ parser?: Parser<T>;
11
+ /**
12
+ * Serialize `T` back into a `string` for the URL.
13
+ * Defaults to `String(value)`.
14
+ */
15
+ serializer?: Serializer<T>;
16
+ /**
17
+ * Value returned when the param is absent from
18
+ * the URL. When set, the hook never returns
19
+ * `null`.
20
+ */
21
+ defaultValue?: T;
22
+ /**
23
+ * How the URL is mutated:
24
+ * - `'replace'` (default) — `history.replaceState`,
25
+ * does not add a history entry.
26
+ * - `'push'` — `history.pushState`, back/forward
27
+ * navigates between values.
28
+ */
29
+ history?: 'push' | 'replace';
30
+ }
31
+ type SetQueryState<T> = (value: T | null | ((prev: T | null) => T | null)) => void;
32
+ /** Identity parser used when none is supplied. */
33
+ export declare const queryString: Parser<string>;
34
+ /** Parse a numeric query param. `NaN` for non-numbers. */
35
+ export declare const queryNumber: Parser<number>;
36
+ /** Parse a boolean query param. Only `'true'` is true. */
37
+ export declare const queryBoolean: Parser<boolean>;
38
+ /** Parse a JSON-encoded query param. */
39
+ export declare const queryJson: Parser<unknown>;
40
+ /**
41
+ * Sync a piece of React state with a URL query
42
+ * parameter, so it survives refreshes and is
43
+ * shareable via the link.
44
+ *
45
+ * Reads/writes go through native
46
+ * `history.pushState/replaceState`, which keeps it
47
+ * compatible with the Next.js App Router (it does
48
+ * not trigger a route re-mount). Because the value
49
+ * is reactive, using it as a SWR/React Query key
50
+ * makes dependent requests refetch automatically.
51
+ *
52
+ * @param key The query parameter name
53
+ * @param options Parsing, serialization and history
54
+ * behaviour
55
+ * @returns A `[value, setValue]` tuple. `setValue`
56
+ * accepts a value, an updater function, or `null`
57
+ * to remove the param.
58
+ */
59
+ declare function useQueryState(key: string): [string | null, SetQueryState<string>];
60
+ declare function useQueryState<T>(key: string, options: QueryStateOptions<T> & {
61
+ defaultValue: T;
62
+ }): [T, SetQueryState<T>];
63
+ declare function useQueryState<T>(key: string, options: QueryStateOptions<T>): [T | null, SetQueryState<T>];
64
+ export default useQueryState;
@@ -0,0 +1,133 @@
1
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
2
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
4
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
5
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
+ import { useState, useEffect } from 'react';
8
+ import useMemoizedFn from "../useMemoizedFn";
9
+
10
+ /** Turn a raw query string into a typed value. */
11
+
12
+ /** Turn a typed value back into a query string. */
13
+
14
+ /** Identity parser used when none is supplied. */
15
+ export var queryString = function queryString(value) {
16
+ return value;
17
+ };
18
+
19
+ /** Parse a numeric query param. `NaN` for non-numbers. */
20
+ export var queryNumber = function queryNumber(value) {
21
+ return Number(value);
22
+ };
23
+
24
+ /** Parse a boolean query param. Only `'true'` is true. */
25
+ export var queryBoolean = function queryBoolean(value) {
26
+ return value === 'true';
27
+ };
28
+
29
+ /** Parse a JSON-encoded query param. */
30
+ export var queryJson = function queryJson(value) {
31
+ return JSON.parse(value);
32
+ };
33
+ var isBrowser = typeof window !== 'undefined';
34
+
35
+ /**
36
+ * Subscribers re-read the URL when any instance
37
+ * mutates it. `history.pushState/replaceState` do
38
+ * not emit `popstate`, so we notify manually.
39
+ */
40
+ var listeners = new Set();
41
+ var emit = function emit() {
42
+ listeners.forEach(function (fn) {
43
+ return fn();
44
+ });
45
+ };
46
+
47
+ /**
48
+ * Sync a piece of React state with a URL query
49
+ * parameter, so it survives refreshes and is
50
+ * shareable via the link.
51
+ *
52
+ * Reads/writes go through native
53
+ * `history.pushState/replaceState`, which keeps it
54
+ * compatible with the Next.js App Router (it does
55
+ * not trigger a route re-mount). Because the value
56
+ * is reactive, using it as a SWR/React Query key
57
+ * makes dependent requests refetch automatically.
58
+ *
59
+ * @param key The query parameter name
60
+ * @param options Parsing, serialization and history
61
+ * behaviour
62
+ * @returns A `[value, setValue]` tuple. `setValue`
63
+ * accepts a value, an updater function, or `null`
64
+ * to remove the param.
65
+ */
66
+
67
+ // eslint-disable-next-line no-redeclare
68
+
69
+ // eslint-disable-next-line no-redeclare
70
+
71
+ // eslint-disable-next-line no-redeclare
72
+ function useQueryState(key) {
73
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
74
+ var parser = options.parser,
75
+ _options$serializer = options.serializer,
76
+ serializer = _options$serializer === void 0 ? String : _options$serializer,
77
+ defaultValue = options.defaultValue,
78
+ _options$history = options.history,
79
+ history = _options$history === void 0 ? 'replace' : _options$history;
80
+ var read = useMemoizedFn(function () {
81
+ if (!isBrowser) {
82
+ return defaultValue !== null && defaultValue !== void 0 ? defaultValue : null;
83
+ }
84
+ var params = new URLSearchParams(window.location.search);
85
+ var raw = params.get(key);
86
+ if (raw === null) {
87
+ return defaultValue !== null && defaultValue !== void 0 ? defaultValue : null;
88
+ }
89
+ return parser ? parser(raw) : raw;
90
+ });
91
+ var _useState = useState(read),
92
+ _useState2 = _slicedToArray(_useState, 2),
93
+ value = _useState2[0],
94
+ setValue = _useState2[1];
95
+ useEffect(function () {
96
+ var handler = function handler() {
97
+ return setValue(read());
98
+ };
99
+ listeners.add(handler);
100
+ window.addEventListener('popstate', handler);
101
+ // Resync in case the URL changed between the
102
+ // initial render and this effect running.
103
+ handler();
104
+ return function () {
105
+ listeners.delete(handler);
106
+ window.removeEventListener('popstate', handler);
107
+ };
108
+ // `read` is stable via useMemoizedFn.
109
+ }, [key, read]);
110
+ var set = useMemoizedFn(function (next) {
111
+ if (!isBrowser) {
112
+ return;
113
+ }
114
+ var prev = read();
115
+ var resolved = typeof next === 'function' ? next(prev) : next;
116
+ var params = new URLSearchParams(window.location.search);
117
+ if (resolved === null || resolved === undefined) {
118
+ params.delete(key);
119
+ } else {
120
+ params.set(key, serializer(resolved));
121
+ }
122
+ var search = params.toString();
123
+ var url = window.location.pathname + (search ? "?".concat(search) : '') + window.location.hash;
124
+ if (history === 'push') {
125
+ window.history.pushState(null, '', url);
126
+ } else {
127
+ window.history.replaceState(null, '', url);
128
+ }
129
+ emit();
130
+ });
131
+ return [value, set];
132
+ }
133
+ export default useQueryState;
package/lib/index.d.ts CHANGED
@@ -9,3 +9,5 @@ export { default as useSyncState } from './useSyncState';
9
9
  export { default as useUpdateEffect } from './useUpdateEffect';
10
10
  export { default as useLayoutState, useTimeoutLock } from './useLayoutState';
11
11
  export type { Updater } from './useLayoutState';
12
+ export { default as useQueryState, queryString, queryNumber, queryBoolean, queryJson, } from './useQueryState';
13
+ export type { Parser, Serializer, QueryStateOptions, } from './useQueryState';
package/lib/index.js CHANGED
@@ -29,6 +29,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  // src/index.ts
30
30
  var index_exports = {};
31
31
  __export(index_exports, {
32
+ queryBoolean: () => import_useQueryState.queryBoolean,
33
+ queryJson: () => import_useQueryState.queryJson,
34
+ queryNumber: () => import_useQueryState.queryNumber,
35
+ queryString: () => import_useQueryState.queryString,
32
36
  useControlledState: () => import_useControlledState.default,
33
37
  useEventCallback: () => import_useEventCallback.default,
34
38
  useLatest: () => import_useLatest.default,
@@ -36,6 +40,7 @@ __export(index_exports, {
36
40
  useLayoutState: () => import_useLayoutState.default,
37
41
  useMemoizedFn: () => import_useMemoizedFn.default,
38
42
  usePrevious: () => import_usePrevious.default,
43
+ useQueryState: () => import_useQueryState.default,
39
44
  useSafeState: () => import_useSafeState.default,
40
45
  useSyncState: () => import_useSyncState.default,
41
46
  useTimeoutLock: () => import_useLayoutState.useTimeoutLock,
@@ -52,8 +57,13 @@ var import_useSafeState = __toESM(require("./useSafeState"));
52
57
  var import_useSyncState = __toESM(require("./useSyncState"));
53
58
  var import_useUpdateEffect = __toESM(require("./useUpdateEffect"));
54
59
  var import_useLayoutState = __toESM(require("./useLayoutState"));
60
+ var import_useQueryState = __toESM(require("./useQueryState"));
55
61
  // Annotate the CommonJS export names for ESM import in node:
56
62
  0 && (module.exports = {
63
+ queryBoolean,
64
+ queryJson,
65
+ queryNumber,
66
+ queryString,
57
67
  useControlledState,
58
68
  useEventCallback,
59
69
  useLatest,
@@ -61,6 +71,7 @@ var import_useLayoutState = __toESM(require("./useLayoutState"));
61
71
  useLayoutState,
62
72
  useMemoizedFn,
63
73
  usePrevious,
74
+ useQueryState,
64
75
  useSafeState,
65
76
  useSyncState,
66
77
  useTimeoutLock,
@@ -0,0 +1,64 @@
1
+ /** Turn a raw query string into a typed value. */
2
+ export type Parser<T> = (value: string) => T;
3
+ /** Turn a typed value back into a query string. */
4
+ export type Serializer<T> = (value: T) => string;
5
+ export interface QueryStateOptions<T> {
6
+ /**
7
+ * Parse the raw `string` read from the URL into `T`.
8
+ * Defaults to identity (the raw string).
9
+ */
10
+ parser?: Parser<T>;
11
+ /**
12
+ * Serialize `T` back into a `string` for the URL.
13
+ * Defaults to `String(value)`.
14
+ */
15
+ serializer?: Serializer<T>;
16
+ /**
17
+ * Value returned when the param is absent from
18
+ * the URL. When set, the hook never returns
19
+ * `null`.
20
+ */
21
+ defaultValue?: T;
22
+ /**
23
+ * How the URL is mutated:
24
+ * - `'replace'` (default) — `history.replaceState`,
25
+ * does not add a history entry.
26
+ * - `'push'` — `history.pushState`, back/forward
27
+ * navigates between values.
28
+ */
29
+ history?: 'push' | 'replace';
30
+ }
31
+ type SetQueryState<T> = (value: T | null | ((prev: T | null) => T | null)) => void;
32
+ /** Identity parser used when none is supplied. */
33
+ export declare const queryString: Parser<string>;
34
+ /** Parse a numeric query param. `NaN` for non-numbers. */
35
+ export declare const queryNumber: Parser<number>;
36
+ /** Parse a boolean query param. Only `'true'` is true. */
37
+ export declare const queryBoolean: Parser<boolean>;
38
+ /** Parse a JSON-encoded query param. */
39
+ export declare const queryJson: Parser<unknown>;
40
+ /**
41
+ * Sync a piece of React state with a URL query
42
+ * parameter, so it survives refreshes and is
43
+ * shareable via the link.
44
+ *
45
+ * Reads/writes go through native
46
+ * `history.pushState/replaceState`, which keeps it
47
+ * compatible with the Next.js App Router (it does
48
+ * not trigger a route re-mount). Because the value
49
+ * is reactive, using it as a SWR/React Query key
50
+ * makes dependent requests refetch automatically.
51
+ *
52
+ * @param key The query parameter name
53
+ * @param options Parsing, serialization and history
54
+ * behaviour
55
+ * @returns A `[value, setValue]` tuple. `setValue`
56
+ * accepts a value, an updater function, or `null`
57
+ * to remove the param.
58
+ */
59
+ declare function useQueryState(key: string): [string | null, SetQueryState<string>];
60
+ declare function useQueryState<T>(key: string, options: QueryStateOptions<T> & {
61
+ defaultValue: T;
62
+ }): [T, SetQueryState<T>];
63
+ declare function useQueryState<T>(key: string, options: QueryStateOptions<T>): [T | null, SetQueryState<T>];
64
+ export default useQueryState;
@@ -0,0 +1,116 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/useQueryState/index.ts
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
+ default: () => index_default,
33
+ queryBoolean: () => queryBoolean,
34
+ queryJson: () => queryJson,
35
+ queryNumber: () => queryNumber,
36
+ queryString: () => queryString
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+ var import_react = require("react");
40
+ var import_useMemoizedFn = __toESM(require("../useMemoizedFn"));
41
+ var queryString = (value) => value;
42
+ var queryNumber = (value) => Number(value);
43
+ var queryBoolean = (value) => value === "true";
44
+ var queryJson = (value) => JSON.parse(value);
45
+ var isBrowser = typeof window !== "undefined";
46
+ var listeners = /* @__PURE__ */ new Set();
47
+ var emit = () => {
48
+ listeners.forEach((fn) => fn());
49
+ };
50
+ function useQueryState(key, options = {}) {
51
+ const {
52
+ parser,
53
+ serializer = String,
54
+ defaultValue,
55
+ history = "replace"
56
+ } = options;
57
+ const read = (0, import_useMemoizedFn.default)(() => {
58
+ if (!isBrowser) {
59
+ return defaultValue ?? null;
60
+ }
61
+ const params = new URLSearchParams(
62
+ window.location.search
63
+ );
64
+ const raw = params.get(key);
65
+ if (raw === null) {
66
+ return defaultValue ?? null;
67
+ }
68
+ return parser ? parser(raw) : raw;
69
+ });
70
+ const [value, setValue] = (0, import_react.useState)(read);
71
+ (0, import_react.useEffect)(() => {
72
+ const handler = () => setValue(read());
73
+ listeners.add(handler);
74
+ window.addEventListener("popstate", handler);
75
+ handler();
76
+ return () => {
77
+ listeners.delete(handler);
78
+ window.removeEventListener(
79
+ "popstate",
80
+ handler
81
+ );
82
+ };
83
+ }, [key, read]);
84
+ const set = (0, import_useMemoizedFn.default)((next) => {
85
+ if (!isBrowser) {
86
+ return;
87
+ }
88
+ const prev = read();
89
+ const resolved = typeof next === "function" ? next(prev) : next;
90
+ const params = new URLSearchParams(
91
+ window.location.search
92
+ );
93
+ if (resolved === null || resolved === void 0) {
94
+ params.delete(key);
95
+ } else {
96
+ params.set(key, serializer(resolved));
97
+ }
98
+ const search = params.toString();
99
+ const url = window.location.pathname + (search ? `?${search}` : "") + window.location.hash;
100
+ if (history === "push") {
101
+ window.history.pushState(null, "", url);
102
+ } else {
103
+ window.history.replaceState(null, "", url);
104
+ }
105
+ emit();
106
+ });
107
+ return [value, set];
108
+ }
109
+ var index_default = useQueryState;
110
+ // Annotate the CommonJS export names for ESM import in node:
111
+ 0 && (module.exports = {
112
+ queryBoolean,
113
+ queryJson,
114
+ queryNumber,
115
+ queryString
116
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1money/hooks",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "React hooks for 1money front-end projects",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -41,6 +41,11 @@
41
41
  "import": "./es/useLayoutState/index.js",
42
42
  "require": "./lib/useLayoutState/index.js"
43
43
  },
44
+ "./useQueryState": {
45
+ "types": "./es/useQueryState/index.d.ts",
46
+ "import": "./es/useQueryState/index.js",
47
+ "require": "./lib/useQueryState/index.js"
48
+ },
44
49
  "./useMemoizedFn": {
45
50
  "types": "./es/useMemoizedFn/index.d.ts",
46
51
  "import": "./es/useMemoizedFn/index.js",