@arcblock/react-hooks 2.10.7 → 2.10.9

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.
@@ -0,0 +1,25 @@
1
+ /* eslint-disable import/no-unresolved */
2
+ import { defineBuildConfig, BuildEntry } from 'unbuild';
3
+
4
+ const pattern = ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx', '**/*.png', '!**/*.stories.js', '!**/demo', '**/*.svg'];
5
+
6
+ const shared: BuildEntry = {
7
+ builder: 'mkdist',
8
+ input: './src',
9
+ pattern,
10
+ ext: 'js',
11
+ esbuild: {
12
+ jsx: 'automatic',
13
+ },
14
+ declaration: true,
15
+ };
16
+ export default defineBuildConfig({
17
+ failOnWarn: false,
18
+ entries: [
19
+ {
20
+ ...shared,
21
+ outDir: './lib',
22
+ format: 'esm',
23
+ },
24
+ ],
25
+ });
package/lib/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { default as useInterval } from './useInterval';
2
+ export { default as useStorage } from './useStorage';
3
+ export { default as useBrowser } from './useBrowser';
4
+ export { default as useLineClamp } from './useLineClamp';
package/lib/index.js CHANGED
@@ -1,27 +1,4 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- Object.defineProperty(exports, "useBrowser", {
7
- enumerable: true,
8
- get: function get() {
9
- return _useBrowser.default;
10
- }
11
- });
12
- Object.defineProperty(exports, "useInterval", {
13
- enumerable: true,
14
- get: function get() {
15
- return _useInterval.default;
16
- }
17
- });
18
- Object.defineProperty(exports, "useStorage", {
19
- enumerable: true,
20
- get: function get() {
21
- return _useStorage.default;
22
- }
23
- });
24
- var _useInterval = _interopRequireDefault(require("./useInterval"));
25
- var _useStorage = _interopRequireDefault(require("./useStorage"));
26
- var _useBrowser = _interopRequireDefault(require("./useBrowser"));
27
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
1
+ export { default as useInterval } from "./useInterval.js";
2
+ export { default as useStorage } from "./useStorage.js";
3
+ export { default as useBrowser } from "./useBrowser.js";
4
+ export { default as useLineClamp } from "./useLineClamp.js";
@@ -0,0 +1,40 @@
1
+ export default function useBrowser(): {
2
+ wallet: boolean;
3
+ walletVersion: string | undefined;
4
+ wechat: boolean;
5
+ mobile: {
6
+ apple: {
7
+ phone: boolean;
8
+ ipod: boolean;
9
+ tablet: boolean;
10
+ universal: boolean;
11
+ device: boolean;
12
+ };
13
+ amazon: {
14
+ phone: boolean;
15
+ tablet: boolean;
16
+ device: boolean;
17
+ };
18
+ android: {
19
+ phone: boolean;
20
+ tablet: boolean;
21
+ device: boolean;
22
+ };
23
+ windows: {
24
+ phone: boolean;
25
+ tablet: boolean;
26
+ device: boolean;
27
+ };
28
+ other: {
29
+ blackberry: boolean;
30
+ blackberry10: boolean;
31
+ opera: boolean;
32
+ firefox: boolean;
33
+ chrome: boolean;
34
+ device: boolean;
35
+ };
36
+ phone: boolean;
37
+ tablet: boolean;
38
+ any: boolean;
39
+ };
40
+ };
package/lib/useBrowser.js CHANGED
@@ -1,54 +1,43 @@
1
- "use strict";
1
+ import { useState } from 'react';
2
+ import isMobile from 'ismobilejs';
2
3
 
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = useBrowser;
7
- var _react = require("react");
8
- var _ismobilejs = _interopRequireDefault(require("ismobilejs"));
9
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
- function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
11
- function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
12
- function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
13
- function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
14
- function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
15
4
  const DID_WALLET_TAG = 'ABTWallet';
16
5
  const ARC_SPHERE_TAG = 'ArcSphere';
6
+
17
7
  function getDIDWalletVersion(userAgent) {
18
- var _result$groups;
19
- const reg = new RegExp("".concat(DID_WALLET_TAG, "/(?<version>[0-9.]+)"), 'g');
8
+ const reg = new RegExp(`${DID_WALLET_TAG}/(?<version>[0-9.]+)`, 'g');
20
9
  const result = reg.exec(userAgent);
21
- const version = result === null || result === void 0 ? void 0 : (_result$groups = result.groups) === null || _result$groups === void 0 ? void 0 : _result$groups.version;
10
+ const version = result?.groups?.version;
22
11
  return version;
23
12
  }
24
- function useBrowser() {
25
- var _window, _window$navigator;
26
- const userAgent = (_window = window) === null || _window === void 0 ? void 0 : (_window$navigator = _window.navigator) === null || _window$navigator === void 0 ? void 0 : _window$navigator.userAgent;
27
- const [browser] = (0, _react.useState)({
13
+
14
+ export default function useBrowser() {
15
+ const userAgent = window?.navigator?.userAgent;
16
+ const [browser] = useState({
28
17
  wallet: userAgent.indexOf(DID_WALLET_TAG) > -1 || userAgent.indexOf(ARC_SPHERE_TAG) > -1,
29
18
  walletVersion: getDIDWalletVersion(userAgent),
30
19
  wechat: /MicroMessenger/i.test(userAgent),
31
- mobile: _objectSpread({
20
+ mobile: {
32
21
  apple: {
33
22
  phone: false,
34
23
  ipod: false,
35
24
  tablet: false,
36
- device: false
25
+ device: false,
37
26
  },
38
27
  amazon: {
39
28
  phone: false,
40
29
  tablet: false,
41
- device: false
30
+ device: false,
42
31
  },
43
32
  android: {
44
33
  phone: false,
45
34
  tablet: false,
46
- device: false
35
+ device: false,
47
36
  },
48
37
  windows: {
49
38
  phone: false,
50
39
  tablet: false,
51
- device: false
40
+ device: false,
52
41
  },
53
42
  other: {
54
43
  blackberry: false,
@@ -56,12 +45,14 @@ function useBrowser() {
56
45
  opera: false,
57
46
  firefox: false,
58
47
  chrome: false,
59
- device: false
48
+ device: false,
60
49
  },
61
50
  phone: false,
62
51
  tablet: false,
63
- any: false
64
- }, (0, _ismobilejs.default)(userAgent))
52
+ any: false,
53
+ ...isMobile(userAgent),
54
+ },
65
55
  });
56
+
66
57
  return browser;
67
- }
58
+ }
@@ -0,0 +1 @@
1
+ export default function useInterval(callback: any, delay: any): void;
@@ -1,22 +1,16 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = useInterval;
7
- var _react = require("react");
8
1
  /* eslint-disable consistent-return */
2
+ import { useEffect, useRef } from 'react';
9
3
 
10
- function useInterval(callback, delay) {
11
- const savedCallback = (0, _react.useRef)();
4
+ export default function useInterval(callback, delay) {
5
+ const savedCallback = useRef();
12
6
 
13
7
  // Remember the latest callback.
14
- (0, _react.useEffect)(() => {
8
+ useEffect(() => {
15
9
  savedCallback.current = callback;
16
10
  }, [callback]);
17
11
 
18
12
  // Set up the interval.
19
- (0, _react.useEffect)(() => {
13
+ useEffect(() => {
20
14
  function tick() {
21
15
  savedCallback.current();
22
16
  }
@@ -25,4 +19,4 @@ function useInterval(callback, delay) {
25
19
  return () => clearInterval(id);
26
20
  }
27
21
  }, [delay]);
28
- }
22
+ }
@@ -0,0 +1,17 @@
1
+ export default function useLineClamp({ text, ellipsis, lines, expanded, debounceTime, charWidth, offset, }: {
2
+ text: string;
3
+ ellipsis?: string;
4
+ lines?: number;
5
+ expanded?: boolean;
6
+ debounceTime?: number;
7
+ charWidth?: number;
8
+ offset?: number;
9
+ }): {
10
+ textRef: import("react").MutableRefObject<HTMLElement | null | undefined>;
11
+ buttonRef: import("react").MutableRefObject<HTMLElement | null | undefined>;
12
+ needClamp: boolean;
13
+ noClamp: boolean;
14
+ clampedText: string;
15
+ key: string;
16
+ run: () => void;
17
+ };
@@ -0,0 +1,127 @@
1
+ import { useDebounceFn, useMemoizedFn, useMount, useReactive, useUpdateEffect } from "ahooks";
2
+ import { useRef } from "react";
3
+ const defaultEllipsis = "\u2026";
4
+ let keyCount = 0;
5
+ const getNewKey = () => `__clamp_text_key__${keyCount++}`;
6
+ export default function useLineClamp({
7
+ text,
8
+ ellipsis = defaultEllipsis,
9
+ lines = 3,
10
+ expanded = false,
11
+ debounceTime = 300,
12
+ charWidth = 1.2,
13
+ offset = 1
14
+ }) {
15
+ const currentState = useReactive({
16
+ needClamp: true,
17
+ noClamp: false,
18
+ clampedText: "\u2026",
19
+ key: getNewKey()
20
+ });
21
+ const textRef = useRef();
22
+ const buttonRef = useRef();
23
+ const lineHeightRef = useRef(0);
24
+ const clampLines = useMemoizedFn(({ lineHeight, originalText, expanded: expanded2, ellipsis: ellipsis2, lines: lines2 }) => {
25
+ if (!textRef.current) {
26
+ return;
27
+ }
28
+ const node = textRef.current;
29
+ const button = buttonRef.current;
30
+ if (!originalText || expanded2) {
31
+ currentState.noClamp = true;
32
+ currentState.clampedText = originalText;
33
+ currentState.key = getNewKey();
34
+ return;
35
+ }
36
+ const maxHeight = lineHeight * lines2 + 1;
37
+ let start = 0;
38
+ let middle = 0;
39
+ let end = originalText.length;
40
+ if (!node.clientHeight) {
41
+ return;
42
+ }
43
+ function moveMarkers() {
44
+ const clientHeight = node?.getBoundingClientRect().height ?? 1;
45
+ if (clientHeight <= maxHeight) {
46
+ start = middle + 1;
47
+ } else {
48
+ end = middle - 1;
49
+ }
50
+ }
51
+ while (start <= end) {
52
+ middle = Math.floor((start + end) / 2);
53
+ node.innerText = originalText.slice(0, middle) + ellipsis2;
54
+ if (button) {
55
+ node.appendChild(button.cloneNode(true));
56
+ }
57
+ if (middle === originalText.length) {
58
+ currentState.needClamp = false;
59
+ currentState.noClamp = true;
60
+ currentState.clampedText = originalText;
61
+ currentState.key = getNewKey();
62
+ return;
63
+ }
64
+ moveMarkers();
65
+ }
66
+ const clampedText = originalText.slice(0, Math.max(middle - offset, 0)).trim() + (typeof ellipsis2 === "string" ? ellipsis2 : "");
67
+ node.innerText = clampedText;
68
+ if (button)
69
+ node.appendChild(button.cloneNode(true));
70
+ currentState.needClamp = true;
71
+ currentState.noClamp = false;
72
+ currentState.clampedText = clampedText;
73
+ currentState.key = getNewKey();
74
+ });
75
+ const { run: debouncedClampLines } = useDebounceFn(clampLines, {
76
+ wait: debounceTime
77
+ });
78
+ const resizeListener = useMemoizedFn(() => {
79
+ debouncedClampLines({
80
+ lineHeight: lineHeightRef.current,
81
+ originalText: text,
82
+ expanded,
83
+ ellipsis,
84
+ lines,
85
+ charWidth
86
+ });
87
+ });
88
+ useMount(() => {
89
+ if (text && !lineHeightRef.current) {
90
+ const lineHeight = (textRef.current?.clientHeight ?? 1) + 1;
91
+ lineHeightRef.current = lineHeight;
92
+ setTimeout(() => {
93
+ clampLines({
94
+ lineHeight,
95
+ originalText: text,
96
+ expanded,
97
+ ellipsis,
98
+ lines,
99
+ charWidth
100
+ });
101
+ });
102
+ }
103
+ window.addEventListener("resize", resizeListener);
104
+ return () => {
105
+ window.removeEventListener("resize", resizeListener);
106
+ };
107
+ });
108
+ useUpdateEffect(() => {
109
+ clampLines({
110
+ lineHeight: lineHeightRef.current,
111
+ originalText: text,
112
+ expanded,
113
+ ellipsis,
114
+ lines,
115
+ charWidth
116
+ });
117
+ }, [expanded, text, charWidth, ellipsis, lines]);
118
+ return {
119
+ textRef,
120
+ buttonRef,
121
+ needClamp: currentState.needClamp,
122
+ noClamp: currentState.noClamp,
123
+ clampedText: currentState.clampedText,
124
+ key: currentState.key,
125
+ run: resizeListener
126
+ };
127
+ }
@@ -0,0 +1 @@
1
+ export default function useStorage(storage: any, keyPrefix: any): (key: any, defaultValue: any) => any[];
package/lib/useStorage.js CHANGED
@@ -1,46 +1,39 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = useStorage;
7
- var _react = require("react");
8
- var _eventTargetShim = require("event-target-shim");
9
1
  /* eslint-disable react-hooks/rules-of-hooks */
2
+ import { useState, useEffect } from 'react';
3
+ import { EventTarget } from 'event-target-shim';
10
4
 
11
- const evtTarget = new _eventTargetShim.EventTarget();
12
- function useStorage(storage, keyPrefix) {
5
+ const evtTarget = new EventTarget();
6
+ export default function useStorage(storage, keyPrefix) {
13
7
  return (key, defaultValue) => {
14
- const storeKey = "".concat(keyPrefix, ".").concat(key);
8
+ const storeKey = `${keyPrefix}.${key}`;
15
9
  const raw = storage.getItem(storeKey);
16
- const [value, setValue] = (0, _react.useState)(raw ? JSON.parse(raw) : defaultValue);
17
- const updater = updatedValue => {
10
+ const [value, setValue] = useState(raw ? JSON.parse(raw) : defaultValue);
11
+
12
+ const updater = (updatedValue) => {
18
13
  setValue(updatedValue);
19
14
  storage.setItem(storeKey, JSON.stringify(updatedValue));
20
- evtTarget.dispatchEvent(new CustomEvent('storage_change', {
21
- detail: {
22
- key
23
- }
24
- }));
15
+ evtTarget.dispatchEvent(new CustomEvent('storage_change', { detail: { key } }));
25
16
  };
17
+
26
18
  if (defaultValue != null && !raw) {
27
19
  updater(defaultValue);
28
20
  }
29
- (0, _react.useEffect)(() => {
30
- const listener = _ref => {
31
- let {
32
- detail
33
- } = _ref;
21
+
22
+ useEffect(() => {
23
+ const listener = ({ detail }) => {
34
24
  if (detail.key === key) {
35
25
  const _raw = storage.getItem(storeKey);
26
+
36
27
  if (_raw !== raw) {
37
28
  setValue(JSON.parse(_raw));
38
29
  }
39
30
  }
40
31
  };
32
+
41
33
  evtTarget.addEventListener('storage_change', listener);
42
34
  return () => evtTarget.removeEventListener('storage_change', listener);
43
35
  });
36
+
44
37
  return [value, updater];
45
38
  };
46
- }
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/react-hooks",
3
- "version": "2.10.7",
3
+ "version": "2.10.9",
4
4
  "description": "React hooks used by arcblock products",
5
5
  "keywords": [
6
6
  "react",
@@ -18,8 +18,8 @@
18
18
  "scripts": {
19
19
  "lint": "eslint src tests --ext js --ext jsx",
20
20
  "lint:fix": "npm run lint -- --fix",
21
- "build": "babel src --out-dir lib --copy-files",
22
- "watch": "babel src --out-dir lib -w --copy-files",
21
+ "build": "unbuild",
22
+ "watch": "CONSOLA_LEVEL=1 nodemon -e .jsx,.js,.ts,.tsx -w src -x 'unbuild'",
23
23
  "precommit": "CI=1 npm run lint",
24
24
  "prepush": "CI=1 npm run lint",
25
25
  "prepublish": "npm run build",
@@ -43,7 +43,8 @@
43
43
  "@babel/preset-env": "^7.19.3",
44
44
  "@babel/preset-react": "^7.18.6",
45
45
  "eslint-plugin-react-hooks": "^4.6.0",
46
- "jest": "^28.1.3"
46
+ "jest": "^28.1.3",
47
+ "unbuild": "^2.0.0"
47
48
  },
48
- "gitHead": "865d3f490c7148b8ef706768add621409198a3c1"
49
+ "gitHead": "9eccd40a20ce65bf32982abbf82f38d7e177b525"
49
50
  }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ // @ts-expect-error
2
+ export { default as useInterval } from './useInterval';
3
+ // @ts-expect-error
4
+ export { default as useStorage } from './useStorage';
5
+ // @ts-expect-error
6
+ export { default as useBrowser } from './useBrowser';
7
+
8
+ export { default as useLineClamp } from './useLineClamp';
@@ -0,0 +1,156 @@
1
+ // fork from https://github.com/drenther/use-clamp-text
2
+
3
+ /* eslint-disable no-shadow */
4
+ /* eslint-disable import/prefer-default-export */
5
+ import { useDebounceFn, useMemoizedFn, useMount, useReactive, useUpdateEffect } from 'ahooks';
6
+ import { useRef } from 'react';
7
+
8
+ const defaultEllipsis = '…';
9
+
10
+ let keyCount = 0;
11
+ const getNewKey = () => `__clamp_text_key__${keyCount++}`;
12
+
13
+ export default function useLineClamp({
14
+ text,
15
+ ellipsis = defaultEllipsis,
16
+ lines = 3,
17
+ expanded = false,
18
+ debounceTime = 300,
19
+ charWidth = 1.2,
20
+ offset = 1,
21
+ }: {
22
+ text: string;
23
+ ellipsis?: string;
24
+ lines?: number;
25
+ expanded?: boolean;
26
+ debounceTime?: number;
27
+ charWidth?: number;
28
+ offset?: number;
29
+ }) {
30
+ const currentState = useReactive({
31
+ needClamp: true,
32
+ noClamp: false,
33
+ clampedText: '…',
34
+ key: getNewKey(),
35
+ });
36
+
37
+ const textRef = useRef<HTMLElement | null>();
38
+ const buttonRef = useRef<HTMLElement | null>();
39
+ const lineHeightRef = useRef(0);
40
+
41
+ const clampLines = useMemoizedFn(({ lineHeight, originalText, expanded, ellipsis, lines }) => {
42
+ if (!textRef.current) {
43
+ return;
44
+ }
45
+ const node = textRef.current;
46
+ const button = buttonRef.current;
47
+ if (!originalText || expanded) {
48
+ currentState.noClamp = true;
49
+ currentState.clampedText = originalText;
50
+ currentState.key = getNewKey();
51
+ return;
52
+ }
53
+
54
+ const maxHeight = lineHeight * lines + 1;
55
+
56
+ let start = 0;
57
+ let middle = 0;
58
+ let end = originalText.length;
59
+
60
+ if (!node.clientHeight) {
61
+ return;
62
+ }
63
+
64
+ function moveMarkers() {
65
+ const clientHeight = node?.getBoundingClientRect().height ?? 1;
66
+ if (clientHeight <= maxHeight) {
67
+ start = middle + 1;
68
+ } else {
69
+ end = middle - 1;
70
+ }
71
+ }
72
+
73
+ while (start <= end) {
74
+ middle = Math.floor((start + end) / 2);
75
+ node.innerText = originalText.slice(0, middle) + ellipsis;
76
+ if (button) {
77
+ node.appendChild(button.cloneNode(true));
78
+ }
79
+
80
+ if (middle === originalText.length) {
81
+ currentState.needClamp = false;
82
+ currentState.noClamp = true;
83
+ currentState.clampedText = originalText;
84
+ currentState.key = getNewKey();
85
+ return;
86
+ }
87
+ moveMarkers();
88
+ }
89
+
90
+ const clampedText =
91
+ originalText.slice(0, Math.max(middle - offset, 0)).trim() + (typeof ellipsis === 'string' ? ellipsis : '');
92
+
93
+ node.innerText = clampedText;
94
+ if (button) node.appendChild(button.cloneNode(true));
95
+
96
+ currentState.needClamp = true;
97
+ currentState.noClamp = false;
98
+ currentState.clampedText = clampedText;
99
+ currentState.key = getNewKey();
100
+ });
101
+ const { run: debouncedClampLines } = useDebounceFn(clampLines, {
102
+ wait: debounceTime,
103
+ });
104
+
105
+ const resizeListener = useMemoizedFn(() => {
106
+ debouncedClampLines({
107
+ lineHeight: lineHeightRef.current,
108
+ originalText: text,
109
+ expanded,
110
+ ellipsis,
111
+ lines,
112
+ charWidth,
113
+ });
114
+ });
115
+
116
+ useMount(() => {
117
+ if (text && !lineHeightRef.current) {
118
+ const lineHeight = (textRef.current?.clientHeight ?? 1) + 1;
119
+ lineHeightRef.current = lineHeight;
120
+ setTimeout(() => {
121
+ clampLines({
122
+ lineHeight,
123
+ originalText: text,
124
+ expanded,
125
+ ellipsis,
126
+ lines,
127
+ charWidth,
128
+ });
129
+ });
130
+ }
131
+ window.addEventListener('resize', resizeListener);
132
+ return () => {
133
+ window.removeEventListener('resize', resizeListener);
134
+ };
135
+ });
136
+ useUpdateEffect(() => {
137
+ clampLines({
138
+ lineHeight: lineHeightRef.current,
139
+ originalText: text,
140
+ expanded,
141
+ ellipsis,
142
+ lines,
143
+ charWidth,
144
+ });
145
+ }, [expanded, text, charWidth, ellipsis, lines]);
146
+
147
+ return {
148
+ textRef,
149
+ buttonRef,
150
+ needClamp: currentState.needClamp,
151
+ noClamp: currentState.noClamp,
152
+ clampedText: currentState.clampedText,
153
+ key: currentState.key,
154
+ run: resizeListener,
155
+ };
156
+ }
package/src/index.js DELETED
@@ -1,5 +0,0 @@
1
- import useInterval from './useInterval';
2
- import useStorage from './useStorage';
3
- import useBrowser from './useBrowser';
4
-
5
- export { useInterval, useStorage, useBrowser };