├── .editorconfig
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── pull_request_template.md
├── .gitignore
├── .prettierrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── UtilityJs-wide.png
├── package.json
├── scripts
└── build-npm-module.js
├── src
├── data-structure
│ ├── Comparator
│ │ ├── Comparator.ts
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── DisjointSet
│ │ ├── DisjointSet.ts
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── Graph
│ │ ├── Graph.ts
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── Heap
│ │ ├── Heap.ts
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── LinkedList
│ │ ├── LinkedList.ts
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── MaxHeap
│ │ ├── MaxHeap.ts
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── MinHeap
│ │ ├── MinHeap.ts
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── PriorityQueue
│ │ ├── PriorityQueue.ts
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── Queue
│ │ ├── Queue.ts
│ │ ├── README.md
│ │ ├── index.ts
│ │ └── package.json
│ ├── Stack
│ │ ├── README.md
│ │ ├── Stack.ts
│ │ ├── index.ts
│ │ └── package.json
│ └── Vector
│ │ ├── README.md
│ │ ├── Vector.ts
│ │ ├── index.ts
│ │ └── package.json
├── function
│ └── createStoreContext
│ │ ├── README.md
│ │ ├── createStoreContext.tsx
│ │ ├── index.ts
│ │ └── package.json
├── hook
│ ├── useControlledProp
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useControlledProp.ts
│ ├── useCopyToClipboard
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useCopyToClipboard.ts
│ ├── useDarkMode
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useDarkMode.ts
│ ├── useDeterministicId
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useDeterministicId.ts
│ ├── useEventListener
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useEventListener.ts
│ ├── useForceRerender
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useForceRerender.ts
│ ├── useForkedRefs
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useForkedRefs.ts
│ ├── useGetLatest
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useGetLatest.ts
│ ├── useGetScrollbarWidth
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useGetScrollbarWidth.ts
│ ├── useHash
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useHash.ts
│ ├── useHover
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useHover.ts
│ ├── useImmutableArray
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useImmutableArray.ts
│ ├── useIsInViewport
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useIsInViewport.ts
│ ├── useIsMounted
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useIsMounted.ts
│ ├── useIsServerHandoffComplete
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useIsServerHandoffComplete.ts
│ ├── useIsomorphicLayoutEffect
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useIsomorphicLayoutEffect.ts
│ ├── useKeybind
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useKeybind.ts
│ ├── useLazyInitializedValue
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useLazyInitializedValue.ts
│ ├── useLongPress
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useLongPress.ts
│ ├── useMediaQuery
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useMediaQuery.ts
│ ├── useMementoState
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ ├── use-memento-state-img.png
│ │ └── useMementoState.ts
│ ├── useOnChange
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useOnChange.ts
│ ├── useOnOutsideClick
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useOnOutsideClick.ts
│ ├── usePersistedState
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── usePersistedState.ts
│ ├── usePreviousValue
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── usePreviousValue.ts
│ ├── usePubSub
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── usePubSub.ts
│ ├── useRegisterNodeRef
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useRegisterNodeRef.ts
│ ├── useResizeSensor
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useResizeSensor.ts
│ ├── useScrollGuard
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useScrollGuard.ts
│ └── useSyncEffect
│ │ ├── README.md
│ │ ├── index.ts
│ │ ├── package.json
│ │ └── useSyncEffect.ts
└── typedef
│ └── index.d.ts
├── tsconfig.cjs.json
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = false
12 | insert_final_newline = false
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const peerDeps = Object.keys(require("./package.json").peerDependencies);
2 |
3 | module.exports = {
4 | extends: [
5 | "eslint:recommended",
6 | "plugin:react/recommended",
7 | "plugin:react-hooks/recommended",
8 | "plugin:import/recommended",
9 | "plugin:import/typescript",
10 | "prettier"
11 | ],
12 | env: {
13 | browser: true,
14 | es6: true,
15 | node: true,
16 | commonjs: true
17 | },
18 | globals: {
19 | Atomics: "readonly",
20 | SharedArrayBuffer: "readonly",
21 | JSX: true
22 | },
23 | plugins: [
24 | "eslint-plugin-import",
25 | "eslint-plugin-react",
26 | "eslint-plugin-react-hooks",
27 | "@typescript-eslint/eslint-plugin"
28 | ],
29 | parser: "@typescript-eslint/parser",
30 | parserOptions: {
31 | sourceType: "module"
32 | },
33 | rules: {
34 | "no-alert": "error",
35 | "no-console": "warn",
36 | "prefer-const": "error",
37 | "default-case": "warn",
38 | "react/react-in-jsx-scope": "off",
39 | "@typescript-eslint/no-unused-vars": [
40 | "warn",
41 | { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }
42 | ],
43 | "import/no-unresolved": ["error", { ignore: peerDeps }]
44 | },
45 | overrides: [
46 | {
47 | files: ["*.ts", "*.tsx", "*.d.ts"],
48 | extends: [
49 | "plugin:@typescript-eslint/recommended",
50 | "plugin:@typescript-eslint/recommended-requiring-type-checking"
51 | ],
52 | parserOptions: {
53 | sourceType: "module",
54 | project: ["./tsconfig.json"]
55 | }
56 | }
57 | ],
58 | settings: {
59 | react: { version: "detect" },
60 | "import/resolver": {
61 | alias: {
62 | map: [
63 | [
64 | "@utilityjs/use-get-latest",
65 | "./src/hook/useGetLatest/useGetLatest.ts"
66 | ],
67 | [
68 | "@utilityjs/use-event-listener",
69 | "./src/hook/useEventListener/useEventListener.ts"
70 | ],
71 | [
72 | "@utilityjs/use-previous-value",
73 | "./src/hook/usePreviousValue/usePreviousValue.ts"
74 | ],
75 | [
76 | "@utilityjs/use-get-scrollbar-width",
77 | "./src/hook/useGetScrollbarWidth/useGetScrollbarWidth.ts"
78 | ],
79 | [
80 | "@utilityjs/use-register-node-ref",
81 | "./src/hook/useRegisterNodeRef/useRegisterNodeRef.ts"
82 | ],
83 | [
84 | "@utilityjs/use-persisted-state",
85 | "./src/hook/usePersistedState/usePersistedState.ts"
86 | ],
87 | [
88 | "@utilityjs/use-media-query",
89 | "./src/hook/useMediaQuery/useMediaQuery.ts"
90 | ],
91 | [
92 | "@utilityjs/use-lazy-initialized-value",
93 | "./src/hook/useLazyInitializedValue/useLazyInitializedValue.ts"
94 | ],
95 | [
96 | "@utilityjs/comparator",
97 | "./src/data-structure/Comparator/Comparator.ts"
98 | ],
99 | ["@utilityjs/heap", "./src/data-structure/Heap/Heap.ts"],
100 | ["@utilityjs/min-heap", "./src/data-structure/MinHeap/MinHeap.ts"],
101 | [
102 | "@utilityjs/linked-list",
103 | "./src/data-structure/LinkedList/LinkedList.ts"
104 | ]
105 | ]
106 | }
107 | }
108 | }
109 | };
110 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
5 |
6 | - [ ] Related issues linked using `fixes #number`
7 | - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
8 | - [ ] Documentation added
9 | - [ ] The linting passes
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | .DS_STORE
107 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": true,
3 | "packageManager": "yarn",
4 | "printWidth": 80,
5 | "semi": true,
6 | "singleQuote": false,
7 | "tabWidth": 2,
8 | "useEditorConfig": true,
9 | "trailingComma": "none",
10 | "arrowParens": "avoid"
11 | }
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Mostafa Shamsitabar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## Contributing
6 |
7 | Please see our [CONTRIBUTING.md](https://github.com/mimshins/utilityjs/blob/main/CONTRIBUTING.md).
8 |
9 | ## Community
10 |
11 | The UtilityJS community can be found on [Github Discussions](https://github.com/mimshins/utilityjs/discussions), where you can ask questions, voice ideas, and share your projects.
12 |
13 | Our [Code of Conduct](https://github.com/mimshins/utilityjs/blob/main/CODE_OF_CONDUCT.md) applies to all UtilityJS community channels.
14 |
15 | ## Authors
16 |
17 | - Mostafa Shamsitabar ([Twitter](https://twitter.com/mimshins) | [Website](https://mimsh.in))
18 |
--------------------------------------------------------------------------------
/UtilityJs-wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mimshins/utilityjs/310ad1d404711faf578f6ab4a0cfd2fd96a77b83/UtilityJs-wide.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "utilityjs",
3 | "version": "0.1.0",
4 | "main": "./cjs/index.js",
5 | "module": "./esm/index.js",
6 | "description": "A collection of useful utility Classes, Functions, React Hooks and Components.",
7 | "repository": "git@github.com:mimshins/utilityjs.git",
8 | "author": "mimshins ",
9 | "license": "MIT",
10 | "sideEffects": false,
11 | "keywords": [
12 | "utils",
13 | "utility",
14 | "functions",
15 | "functional programming",
16 | "react"
17 | ],
18 | "scripts": {
19 | "clear": "rimraf dist",
20 | "build:npm": "node ./scripts/build-npm-module.js",
21 | "build:ts:esm": "tsc -p tsconfig.json",
22 | "build:ts:cjs": "tsc -p tsconfig.cjs.json",
23 | "build:ts": "npm-run-all build:ts:esm build:ts:cjs",
24 | "prebuild:ts": "npm run clear"
25 | },
26 | "peerDependencies": {
27 | "react": ">=16.8",
28 | "react-dom": ">=16.8"
29 | },
30 | "devDependencies": {
31 | "@types/fs-extra": "^9.0.12",
32 | "@types/lodash.debounce": "^4.0.6",
33 | "@types/lodash.throttle": "^4.1.6",
34 | "@types/node": "^16.7.13",
35 | "@types/react": "^18.0.21",
36 | "@types/react-dom": "^18.0.6",
37 | "@types/rimraf": "^3.0.2",
38 | "@typescript-eslint/eslint-plugin": "^4.31.0",
39 | "@typescript-eslint/parser": "^4.31.0",
40 | "eslint": "^7.32.0",
41 | "eslint-config-prettier": "^8.3.0",
42 | "eslint-import-resolver-alias": "^1.1.2",
43 | "eslint-plugin-import": "^2.24.2",
44 | "eslint-plugin-react": "^7.25.1",
45 | "eslint-plugin-react-hooks": "^4.2.0",
46 | "fast-glob": "^3.2.7",
47 | "fs-extra": "^10.0.0",
48 | "npm-run-all": "^4.1.5",
49 | "prettier": "^2.3.2",
50 | "rimraf": "^3.0.2",
51 | "typescript": "^4.4.2"
52 | },
53 | "dependencies": {
54 | "lodash.debounce": "^4.0.8",
55 | "lodash.throttle": "^4.1.1"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/scripts/build-npm-module.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const fse = require("fs-extra");
3 |
4 | const moduleType = process.argv[2];
5 |
6 | const args = process.argv.slice(3);
7 |
8 | const packagePath = process.cwd();
9 |
10 | const buildPath = path.join(packagePath, "dist");
11 | const srcPath = path.join(packagePath, "src");
12 |
13 | const esmPath = path.join(buildPath, "esm", moduleType);
14 | const cjsPath = path.join(buildPath, "cjs", moduleType);
15 | const npmPath = path.join(buildPath, "npm");
16 |
17 | const modules = args
18 | .map(arg => path.join(srcPath, moduleType, arg))
19 | .filter(path => fse.existsSync(path))
20 | .map(path => [path, path.replace(`${srcPath}/${moduleType}/`, "")]);
21 |
22 | const ensureOutDirs = async () => {
23 | await fse.ensureDir(npmPath);
24 | await Promise.all(
25 | modules.map(module => fse.ensureDir(path.join(npmPath, module[1])))
26 | );
27 | };
28 |
29 | const createModules = async () => {
30 | await Promise.all(
31 | modules.map(async module => {
32 | await fse.copyFile(
33 | path.join(`${cjsPath}/${module[1]}`, `${module[1]}.js`),
34 | path.join(npmPath, module[1], `${module[1]}.js`)
35 | );
36 | await fse.copyFile(
37 | path.join(`${cjsPath}/${module[1]}`, `index.js`),
38 | path.join(npmPath, module[1], `index.js`)
39 | );
40 | await fse.copyFile(
41 | path.join(`${cjsPath}/${module[1]}`, `${module[1]}.d.ts`),
42 | path.join(npmPath, module[1], `${module[1]}.d.ts`)
43 | );
44 | await fse.copyFile(
45 | path.join(`${cjsPath}/${module[1]}`, `index.d.ts`),
46 | path.join(npmPath, module[1], `index.d.ts`)
47 | );
48 | await fse.ensureDir(`${npmPath}/${module[1]}/esm`);
49 | await fse.copyFile(
50 | path.join(`${esmPath}/${module[1]}`, `${module[1]}.js`),
51 | path.join(npmPath, module[1], "esm", `${module[1]}.js`)
52 | );
53 | await fse.copyFile(
54 | path.join(`${esmPath}/${module[1]}`, `index.js`),
55 | path.join(npmPath, module[1], "esm", `index.js`)
56 | );
57 | })
58 | );
59 | };
60 |
61 | const copyFromModules = async fileName => {
62 | await Promise.all(
63 | modules.map(module =>
64 | fse.copyFile(
65 | path.join(module[0], fileName),
66 | path.join(npmPath, module[1], fileName)
67 | )
68 | )
69 | );
70 | };
71 |
72 | const copyPackageJson = async () => {
73 | await copyFromModules("package.json");
74 | };
75 |
76 | const copyREADME = async () => {
77 | await copyFromModules("README.md");
78 | };
79 |
80 | (async () => {
81 | try {
82 | await ensureOutDirs();
83 | await createModules();
84 | await copyREADME();
85 | await copyPackageJson();
86 | } catch (err) {
87 | // eslint-disable-next-line no-console
88 | console.error(err);
89 | process.exit(1);
90 | }
91 | })();
92 |
--------------------------------------------------------------------------------
/src/data-structure/Comparator/Comparator.ts:
--------------------------------------------------------------------------------
1 | export type CompareFunction = (a: T, b: T) => -1 | 0 | 1;
2 |
3 | export default class Comparator {
4 | private compare: CompareFunction;
5 |
6 | static defaultComparatorFunction = (a: T, b: T): -1 | 0 | 1 => {
7 | if (a === b) return 0;
8 | return a < b ? -1 : 1;
9 | };
10 |
11 | constructor(compareFunction?: CompareFunction) {
12 | this.compare = compareFunction || Comparator.defaultComparatorFunction;
13 | }
14 |
15 | public isEqual(a: T, b: T): boolean {
16 | return this.compare(a, b) === 0;
17 | }
18 |
19 | public isLessThan(a: T, b: T): boolean {
20 | return this.compare(a, b) === -1;
21 | }
22 |
23 | public isLessThanOrEqual(a: T, b: T): boolean {
24 | return this.isLessThan(a, b) || this.isEqual(a, b);
25 | }
26 |
27 | public isGreaterThan(a: T, b: T): boolean {
28 | return this.compare(a, b) === 1;
29 | }
30 |
31 | public isGreaterThanOrEqual(a: T, b: T): boolean {
32 | return this.isGreaterThan(a, b) || this.isEqual(a, b);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/data-structure/Comparator/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Comparator
4 |
5 |
6 |
7 |
8 |
9 | A utility class that compares its comparable arguments.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/comparator)
13 | [](https://www.npmjs.com/package/@utilityjs/comparator)
14 | [](https://www.npmjs.com/package/@utilityjs/comparator)
15 |
16 | ```bash
17 | npm i @utilityjs/comparator | yarn add @utilityjs/comparator
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```ts
27 | interface Item {
28 | type: string;
29 | value: number;
30 | }
31 |
32 | const comparator = new Comparator((a, b) => {
33 | if (a.value === b.value) return 0;
34 | return a.value < b.value ? -1 : 1;
35 | });
36 |
37 | // TRUE
38 | comparator.isEqual({ type: "a", value: 0}, { type: "b", value: 0});
39 | ```
40 |
41 | ## API
42 |
43 | ### `Comparator(compareFunction?)`
44 |
45 | ```ts
46 | declare type CompareFunction = (a: T, b: T) => -1 | 0 | 1;
47 | declare class Comparator {
48 | private compare;
49 | static defaultComparatorFunction: (a: U, b: U) => -1 | 0 | 1;
50 | constructor(compareFunction?: CompareFunction);
51 | isEqual(a: T, b: T): boolean;
52 | isLessThan(a: T, b: T): boolean;
53 | isLessThanOrEqual(a: T, b: T): boolean;
54 | isGreaterThan(a: T, b: T): boolean;
55 | isGreaterThanOrEqual(a: T, b: T): boolean;
56 | }
57 | ```
--------------------------------------------------------------------------------
/src/data-structure/Comparator/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Comparator";
2 | export * from "./Comparator";
3 |
--------------------------------------------------------------------------------
/src/data-structure/Comparator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/comparator",
3 | "version": "1.0.1",
4 | "description": "A utility class that compares its comparable arguments.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/data-structure/Comparator"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/data-structure/Comparator",
16 | "keywords": [
17 | "javascript",
18 | "typescript",
19 | "compare function",
20 | "comparator",
21 | "compare class",
22 | "class",
23 | "comparator class"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/src/data-structure/DisjointSet/DisjointSet.ts:
--------------------------------------------------------------------------------
1 | type KeyGenerator = (value: T) => string;
2 |
3 | const _defaultKeyGenerator = (value: T) => JSON.stringify(value);
4 |
5 | export class Item {
6 | private _value: T;
7 |
8 | private _parent: Item | null = null;
9 | private _children: Record> = {};
10 |
11 | private _keyGenerator: KeyGenerator;
12 |
13 | constructor(value: T, keyGenerator: KeyGenerator = _defaultKeyGenerator) {
14 | this._value = value;
15 | this._keyGenerator = keyGenerator;
16 | }
17 |
18 | public getKey(): string {
19 | return this._keyGenerator(this._value);
20 | }
21 |
22 | public getRoot(): Item {
23 | return this.isRoot() ? this : (this._parent as Item).getRoot();
24 | }
25 |
26 | public isRoot(): boolean {
27 | return this._parent === null;
28 | }
29 |
30 | public getRank(): number {
31 | return this.getChildren().reduce(
32 | (result, child) => result + 1 + child.getRank(),
33 | 0
34 | );
35 | }
36 |
37 | public getChildren(): Item[] {
38 | return Object.keys(this._children).map(key => this._children[key]);
39 | }
40 |
41 | public setParent(parent: Item, alsoAsParentChild = false): void {
42 | this._parent = parent;
43 | if (alsoAsParentChild) parent.addChild(this);
44 | }
45 |
46 | public getParent(): Item | null {
47 | return this._parent;
48 | }
49 |
50 | public addChild(child: Item): void {
51 | this._children[child.getKey()] = child;
52 | child.setParent(this);
53 | }
54 | }
55 |
56 | export default class DisjointSet {
57 | private _items: Record> = {};
58 |
59 | private _keyGenerator: KeyGenerator;
60 |
61 | constructor(keyGenerator: KeyGenerator = _defaultKeyGenerator) {
62 | this._keyGenerator = keyGenerator;
63 | }
64 |
65 | public makeSet(value: T): void {
66 | const key = this._keyGenerator(value);
67 | const item = this._items[key];
68 |
69 | if (!item) this._items[key] = new Item(value, this._keyGenerator);
70 | }
71 |
72 | public find(value: T): string | null {
73 | const item = this._items[this._keyGenerator(value)];
74 |
75 | if (!item) return null;
76 | return item.getRoot().getKey();
77 | }
78 |
79 | public union(valueA: T, valueB: T): DisjointSet {
80 | const rootKeyA = this.find(valueA);
81 | const rootKeyB = this.find(valueB);
82 |
83 | if (rootKeyA === null)
84 | throw new Error(`${String(valueA)} isn't in any set.`);
85 | if (rootKeyB === null)
86 | throw new Error(`${String(valueB)} isn't in any set.`);
87 |
88 | if (rootKeyA === rootKeyB) return this;
89 |
90 | const rootA = this._items[rootKeyA];
91 | const rootB = this._items[rootKeyB];
92 |
93 | if (rootA.getRank() < rootB.getRank()) {
94 | rootB.addChild(rootA);
95 | return this;
96 | }
97 |
98 | rootA.addChild(rootB);
99 | return this;
100 | }
101 |
102 | public inSameSet(valueA: T, valueB: T): boolean {
103 | const rootKeyA = this.find(valueA);
104 | const rootKeyB = this.find(valueB);
105 |
106 | if (rootKeyA === null)
107 | throw new Error(`${String(valueA)} isn't in any set.`);
108 | if (rootKeyB === null)
109 | throw new Error(`${String(valueB)} isn't in any set.`);
110 |
111 | return rootKeyA === rootKeyB;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/data-structure/DisjointSet/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | DisjointSet
4 |
5 |
6 |
7 |
8 |
9 | An implementation of DisjointSet data structure.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/disjoint-set)
13 | [](https://www.npmjs.com/package/@utilityjs/disjoint-set)
14 | [](https://www.npmjs.com/package/@utilityjs/disjoint-set)
15 |
16 | ```bash
17 | npm i @utilityjs/disjoint-set | yarn add @utilityjs/disjoint-set
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ```ts
25 | declare type KeyGenerator = (value: T) => string;
26 |
27 | export declare class Item {
28 | constructor(value: T, keyGenerator?: KeyGenerator);
29 | getKey(): string;
30 | getRoot(): Item;
31 | isRoot(): boolean;
32 | getRank(): number;
33 | getChildren(): Item[];
34 | setParent(parent: Item, alsoAsParentChild?: boolean): void;
35 | getParent(): Item | null;
36 | addChild(child: Item): void;
37 | }
38 |
39 | export default class DisjointSet {
40 | constructor(keyGenerator?: KeyGenerator);
41 | makeSet(value: T): void;
42 | find(value: T): string | null;
43 | union(valueA: T, valueB: T): DisjointSet;
44 | inSameSet(valueA: T, valueB: T): boolean;
45 | }
46 | ```
--------------------------------------------------------------------------------
/src/data-structure/DisjointSet/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./DisjointSet";
2 | export * from "./DisjointSet";
3 |
--------------------------------------------------------------------------------
/src/data-structure/DisjointSet/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/disjoint-set",
3 | "version": "1.0.0",
4 | "description": "An implementation of DisjointSet data structure.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/data-structure/DisjointSet"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/data-structure/DisjointSet",
16 | "keywords": [
17 | "javascript",
18 | "typescript",
19 | "data structure",
20 | "algorithm",
21 | "disjoint set",
22 | "union-find",
23 | "merge-find"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/src/data-structure/Graph/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Graph
4 |
5 |
6 |
7 |
8 |
9 | An implementation of Graph data structure.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/graph)
13 | [](https://www.npmjs.com/package/@utilityjs/graph)
14 | [](https://www.npmjs.com/package/@utilityjs/graph)
15 |
16 | ```bash
17 | npm i @utilityjs/graph | yarn add @utilityjs/graph
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ### `Graph(isDirected?)`
25 |
26 | ```ts
27 | interface SearchCallbacks {
28 | onEnter: (previous: Vertex | null, current: Vertex) => void;
29 | onLeave: (previous: Vertex | null, current: Vertex) => void;
30 | shouldTraverse: (
31 | previous: Vertex | null,
32 | current: Vertex,
33 | next: Vertex
34 | ) => boolean;
35 | }
36 |
37 | export declare class Vertex {
38 | constructor(value: T, key?: string | null);
39 | getValue(): T;
40 | setValue(value: T): void;
41 | addEdge(edge: Edge): void;
42 | deleteEdge(edge: Edge): void;
43 | getKey(): string;
44 | getEdges(): Edge[];
45 | getDegree(): number;
46 | getNeighborEdge(vertex: Vertex): Edge | null;
47 | hasEdge(edge: Edge): boolean;
48 | getSelfLoop(): Edge | null;
49 | hasSelfLoop(): boolean;
50 | hasNeighbor(vertex: Vertex): boolean;
51 | getNeighbors(): Vertex[];
52 | clearEdges(): void;
53 | }
54 |
55 | export declare class Edge {
56 | constructor(
57 | vA: Vertex,
58 | vB: Vertex,
59 | weight?: number,
60 | key?: string | null
61 | );
62 | setVA(vA: Vertex): void;
63 | setVB(vB: Vertex): void;
64 | getVA(): Vertex;
65 | getVB(): Vertex;
66 | isSelfLoop(): boolean;
67 | setWeight(weight: number): void;
68 | getWeight(): number;
69 | getKey(): string;
70 | reverse(): void;
71 | }
72 |
73 | export default class Graph {
74 | constructor(isDirected?: boolean);
75 | isDirected(): boolean;
76 | getVertex(key: string): Vertex | null;
77 | addVertex(vertex: Vertex): void;
78 | getVertices(): Vertex[];
79 | getEdges(): Edge[];
80 | getWeight(): number;
81 | getVerticesIndexMap(): Record;
82 | reverse(): void;
83 | addEdge(edge: Edge): void;
84 | findEdge(edge: Edge): Edge | null;
85 | findEdge(vA: Vertex, vB: Vertex): Edge | null;
86 | deleteEdge(edge: Edge): void;
87 | getAdjacencyMatrix(unweighted?: boolean): number[][];
88 | breadthFirstSearch(
89 | startVertex: Vertex,
90 | callbacks?: SearchCallbacks
91 | ): void;
92 | depthFirstSearch(
93 | startVertex: Vertex,
94 | callbacks?: SearchCallbacks
95 | ): void;
96 | }
97 | ```
--------------------------------------------------------------------------------
/src/data-structure/Graph/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Graph";
2 | export * from "./Graph";
3 |
--------------------------------------------------------------------------------
/src/data-structure/Graph/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/graph",
3 | "version": "1.2.1",
4 | "description": "An implementation of Graph data structure.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/data-structure/Graph"
14 | },
15 | "dependencies": {
16 | "@utilityjs/linked-list": "^1.0.2"
17 | },
18 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/data-structure/Graph",
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "data structure",
23 | "algorithm",
24 | "graph",
25 | "weighted graph",
26 | "unweighted graph",
27 | "directed graph",
28 | "undirected graph"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/src/data-structure/Heap/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Heap
4 |
5 |
6 |
7 |
8 |
9 | An implementation of abstract Heap class.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/heap)
13 | [](https://www.npmjs.com/package/@utilityjs/heap)
14 | [](https://www.npmjs.com/package/@utilityjs/heap)
15 |
16 | ```bash
17 | npm i @utilityjs/heap | yarn add @utilityjs/heap
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | This is an abstract class with a single abstract method (`pairIsInCorrectOrder`).\
27 | This method checks if pair of heap elements is in correct order.
28 |
29 | ## API
30 |
31 | ### `Heap(compareFunction?)`
32 |
33 | ```ts
34 | abstract class Heap {
35 | protected comparator: Comparator;
36 | constructor(compareFunction?: CompareFunction);
37 | abstract pairIsInCorrectOrder(
38 | firstItem: T | null,
39 | secondItem: T | null
40 | ): boolean;
41 | getLeftChildIndex(parentIndex: number): number;
42 | getRightChildIndex(parentIndex: number): number;
43 | getParentIndex(childIndex: number): number;
44 | hasParent(childIndex: number): boolean;
45 | hasLeftChild(parentIndex: number): boolean;
46 | hasRightChild(parentIndex: number): boolean;
47 | getLeftChild(parentIndex: number): T | null;
48 | getRightChild(parentIndex: number): T | null;
49 | getParent(childIndex: number): T | null;
50 | isEmpty(): boolean;
51 | toString(): string;
52 | peek(): T | null;
53 | poll(): T | null;
54 | add(item: T): void;
55 | remove(item: T): void;
56 | find(item: T): number[];
57 | swap(index1: number, index2: number): void;
58 | heapifyDown(startIndex?: number): void;
59 | heapifyUp(startIndex?: number): void;
60 | }
61 | ```
--------------------------------------------------------------------------------
/src/data-structure/Heap/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Heap";
2 |
--------------------------------------------------------------------------------
/src/data-structure/Heap/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/heap",
3 | "version": "1.0.1",
4 | "description": "An implementation of abstract Heap class.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/data-structure/Heap"
14 | },
15 | "dependencies": {
16 | "@utilityjs/comparator": "^1.0.1"
17 | },
18 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/data-structure/Heap",
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "data structure",
23 | "algorithm",
24 | "heap"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/data-structure/LinkedList/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | LinkedList
4 |
5 |
6 |
7 |
8 |
9 | An implementation of LinkedList data structure.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/linked-list)
13 | [](https://www.npmjs.com/package/@utilityjs/linked-list)
14 | [](https://www.npmjs.com/package/@utilityjs/linked-list)
15 |
16 | ```bash
17 | npm i @utilityjs/linked-list | yarn add @utilityjs/linked-list
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```ts
27 | interface Item {
28 | type: string;
29 | value: number;
30 | }
31 |
32 | const compareListItems = (a: Item, b: Item) => {
33 | if (a.value === b.value) return 0;
34 | return a.value < b.value ? -1 : 1;
35 | };
36 |
37 | const list = new LinkedList- (compareListItems);
38 |
39 | list.append({ type: "b", value: 1 }); // b -> null
40 | list.prepend({ type: "a", value: 0 }); // a -> b -> null
41 | list.fromArray([
42 | { type: "c", value: 2 },
43 | { type: "d", value: 3 },
44 | { type: "f", value: 4 },
45 | ]); // a -> b -> c -> d -> f -> null
46 | ```
47 |
48 | ## API
49 |
50 | ### `LinkedList(compareFunction?)`
51 |
52 | ```ts
53 | export declare class Node
{
54 | constructor(value: T, next?: Node | null);
55 | getValue(): T;
56 | setValue(value: T): void;
57 | getNext(): Node | null;
58 | setNext(next: Node | null): void;
59 | hasNext(): boolean;
60 | }
61 |
62 | export default class LinkedList {
63 | constructor(compareFunction?: CompareFunction);
64 | getHead(): Node | null;
65 | getTail(): Node | null;
66 | isEmpty(): boolean;
67 | getLength(): number;
68 | append(value: T): void;
69 | prepend(value: T): void;
70 | traverse(callback: (node: Node, index: number) => void | boolean): void;
71 | deleteHead(): void;
72 | deleteTail(): void;
73 | delete(value: T): void;
74 | reverse(): void;
75 | fromArray(array: T[]): void;
76 | toArray(): T[];
77 | }
78 | ```
--------------------------------------------------------------------------------
/src/data-structure/LinkedList/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./LinkedList";
2 | export * from "./LinkedList";
3 |
--------------------------------------------------------------------------------
/src/data-structure/LinkedList/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/linked-list",
3 | "version": "1.0.2",
4 | "description": "An implementation of LinkedList data structure.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/data-structure/LinkedList"
14 | },
15 | "dependencies": {
16 | "@utilityjs/comparator": "^1.0.1"
17 | },
18 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/data-structure/LinkedList",
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "data structure",
23 | "algorithm",
24 | "linked list",
25 | "list"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/src/data-structure/MaxHeap/MaxHeap.ts:
--------------------------------------------------------------------------------
1 | import Heap from "@utilityjs/heap";
2 |
3 | export default class MaxHeap extends Heap {
4 | pairIsInCorrectOrder(firstItem: T | null, secondItem: T | null): boolean {
5 | if (firstItem == null || secondItem == null) return false;
6 |
7 | return this.comparator.isGreaterThanOrEqual(firstItem, secondItem);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/data-structure/MaxHeap/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | MaxHeap
4 |
5 |
6 |
7 |
8 |
9 | An implementation of MaxHeap data structure.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/max-heap)
13 | [](https://www.npmjs.com/package/@utilityjs/max-heap)
14 | [](https://www.npmjs.com/package/@utilityjs/max-heap)
15 |
16 | ```bash
17 | npm i @utilityjs/max-heap | yarn add @utilityjs/max-heap
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ### `MaxHeap(compareFunction?)`
25 |
26 | ```ts
27 | class MaxHeap {
28 | constructor(compareFunction?: CompareFunction);
29 | pairIsInCorrectOrder(firstItem: T | null, secondItem: T | null): boolean;
30 | getLeftChildIndex(parentIndex: number): number;
31 | getRightChildIndex(parentIndex: number): number;
32 | getParentIndex(childIndex: number): number;
33 | hasParent(childIndex: number): boolean;
34 | hasLeftChild(parentIndex: number): boolean;
35 | hasRightChild(parentIndex: number): boolean;
36 | getLeftChild(parentIndex: number): T | null;
37 | getRightChild(parentIndex: number): T | null;
38 | getParent(childIndex: number): T | null;
39 | isEmpty(): boolean;
40 | toString(): string;
41 | peek(): T | null;
42 | poll(): T | null;
43 | add(item: T): void;
44 | remove(item: T): void;
45 | find(item: T): number[];
46 | swap(index1: number, index2: number): void;
47 | heapifyDown(startIndex?: number): void;
48 | heapifyUp(startIndex?: number): void;
49 | }
50 | ```
--------------------------------------------------------------------------------
/src/data-structure/MaxHeap/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./MaxHeap";
2 |
--------------------------------------------------------------------------------
/src/data-structure/MaxHeap/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/max-heap",
3 | "version": "1.0.1",
4 | "description": "An implementation of MaxHeap data structure.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/data-structure/MaxHeap"
14 | },
15 | "dependencies": {
16 | "@utilityjs/heap": "^1.0.1"
17 | },
18 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/data-structure/MaxHeap",
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "data structure",
23 | "algorithm",
24 | "heap",
25 | "max heap",
26 | "maximum heap"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/src/data-structure/MinHeap/MinHeap.ts:
--------------------------------------------------------------------------------
1 | import Heap from "@utilityjs/heap";
2 |
3 | export default class MinHeap extends Heap {
4 | pairIsInCorrectOrder(firstItem: T | null, secondItem: T | null): boolean {
5 | if (firstItem == null || secondItem == null) return false;
6 |
7 | return this.comparator.isLessThanOrEqual(firstItem, secondItem);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/data-structure/MinHeap/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | MinHeap
4 |
5 |
6 |
7 |
8 |
9 | An implementation of MinHeap data structure.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/min-heap)
13 | [](https://www.npmjs.com/package/@utilityjs/min-heap)
14 | [](https://www.npmjs.com/package/@utilityjs/min-heap)
15 |
16 | ```bash
17 | npm i @utilityjs/min-heap | yarn add @utilityjs/min-heap
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ### `MinHeap(compareFunction?)`
25 |
26 | ```ts
27 | class MinHeap {
28 | constructor(compareFunction?: CompareFunction);
29 | pairIsInCorrectOrder(firstItem: T | null, secondItem: T | null): boolean;
30 | getLeftChildIndex(parentIndex: number): number;
31 | getRightChildIndex(parentIndex: number): number;
32 | getParentIndex(childIndex: number): number;
33 | hasParent(childIndex: number): boolean;
34 | hasLeftChild(parentIndex: number): boolean;
35 | hasRightChild(parentIndex: number): boolean;
36 | getLeftChild(parentIndex: number): T | null;
37 | getRightChild(parentIndex: number): T | null;
38 | getParent(childIndex: number): T | null;
39 | isEmpty(): boolean;
40 | toString(): string;
41 | peek(): T | null;
42 | poll(): T | null;
43 | add(item: T): void;
44 | remove(item: T): void;
45 | find(item: T): number[];
46 | swap(index1: number, index2: number): void;
47 | heapifyDown(startIndex?: number): void;
48 | heapifyUp(startIndex?: number): void;
49 | }
50 | ```
--------------------------------------------------------------------------------
/src/data-structure/MinHeap/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./MinHeap";
2 |
--------------------------------------------------------------------------------
/src/data-structure/MinHeap/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/min-heap",
3 | "version": "1.0.1",
4 | "description": "An implementation of MinHeap data structure.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/data-structure/MinHeap"
14 | },
15 | "dependencies": {
16 | "@utilityjs/heap": "^1.0.1"
17 | },
18 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/data-structure/MinHeap",
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "data structure",
23 | "algorithm",
24 | "heap",
25 | "min heap",
26 | "minimum heap"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/src/data-structure/PriorityQueue/PriorityQueue.ts:
--------------------------------------------------------------------------------
1 | import Comparator, { CompareFunction } from "@utilityjs/comparator";
2 | import MinHeap from "@utilityjs/min-heap";
3 |
4 | export default class PriorityQueue extends MinHeap {
5 | private priorities: Map;
6 |
7 | private valueComparator: Comparator;
8 |
9 | constructor(compareFunction?: CompareFunction) {
10 | // eslint-disable-next-line
11 | super();
12 |
13 | this.priorities = new Map();
14 | this.valueComparator = new Comparator(compareFunction);
15 |
16 | this.comparator = new Comparator((a: T, b: T) => {
17 | const pA = this.priorities.get(a);
18 | const pB = this.priorities.get(b);
19 |
20 | if (typeof pA !== "number" || typeof pB !== "number")
21 | throw new ReferenceError();
22 |
23 | if (pA === pB) return 0;
24 | return pA < pB ? -1 : 1;
25 | });
26 | }
27 |
28 | override add(item: T, priority = 0): void {
29 | this.priorities.set(item, priority);
30 | // eslint-disable-next-line
31 | super.add(item);
32 | }
33 |
34 | override remove(item: T, comparator?: Comparator): void {
35 | // eslint-disable-next-line
36 | super.remove(item, comparator ?? this.valueComparator);
37 | this.priorities.delete(item);
38 | }
39 |
40 | changePriority(item: T, priority: number): void {
41 | this.remove(item);
42 | this.add(item, priority);
43 | }
44 |
45 | findByValue(item: T): number[] {
46 | // eslint-disable-next-line
47 | return this.find(item, this.valueComparator);
48 | }
49 |
50 | hasValue(item: T): boolean {
51 | return this.findByValue(item).length > 0;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/data-structure/PriorityQueue/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | PriorityQueue
4 |
5 |
6 |
7 |
8 |
9 | An implementation of PriorityQueue data structure.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/priority-queue)
13 | [](https://www.npmjs.com/package/@utilityjs/priority-queue)
14 | [](https://www.npmjs.com/package/@utilityjs/priority-queue)
15 |
16 | ```bash
17 | npm i @utilityjs/priority-queue | yarn add @utilityjs/priority-queue
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ### `PriorityQueue(compareFunction?)`
25 |
26 | ```ts
27 | export default class Vector {
28 | constructor(x: number, y: number);
29 | constructor(x: number, y: number, z: number);
30 | setX(x: number): Vector;
31 | getX(): number;
32 | setY(y: number): Vector;
33 | getY(): number;
34 | setZ(z: number): Vector;
35 | getZ(): number;
36 | setAxes(x: number, y: number): Vector;
37 | setAxes(x: number, y: number, z: number): Vector;
38 | add(vector: Vector): Vector;
39 | subtract(vector: Vector): Vector;
40 | multiply(vector: Vector): Vector;
41 | multiply(scalar: number): Vector;
42 | dotProduct(vector: Vector): number;
43 | crossProduct(vector: Vector): Vector;
44 | distance(vector: Vector): number;
45 | angleBetween(vector: Vector): number;
46 | lerp(vector: Vector, t: number): Vector;
47 | normalize(): Vector;
48 | reflect(surfaceNormal: Vector): Vector;
49 | reverse(): Vector;
50 | setMagnitude(magnitude: number): Vector;
51 | magnitude(): number;
52 | squaredMagnitude(): number;
53 | equalsTo(vector: Vector): boolean;
54 | clone(): Vector;
55 | toString(): string;
56 | toArray(): [number, number, number];
57 | toObject(): { x: number; y: number; z: number; };
58 | static lerp(vector1: Vector, vector2: Vector, t: number): Vector;
59 | static add(vector1: Vector, vector2: Vector): Vector;
60 | static subtract(vector1: Vector, vector2: Vector): Vector;
61 | static multiply(vector1: Vector, vector2: Vector): Vector;
62 | static multiply(vector1: Vector, scalar: number): Vector;
63 | static dotProduce(vector1: Vector, vector2: Vector): number;
64 | static crossProduct(vector1: Vector, vector2: Vector): Vector;
65 | static distance(vector1: Vector, vector2: Vector): number;
66 | static fromAngle(angleInRadians: number, magnitude?: number): Vector;
67 | static fromArray(
68 | arrayOfComponents: [number, number, number] | [number, number]
69 | ): Vector;
70 | }
71 | ```
--------------------------------------------------------------------------------
/src/data-structure/PriorityQueue/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./PriorityQueue";
2 |
--------------------------------------------------------------------------------
/src/data-structure/PriorityQueue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/priority-queue",
3 | "version": "1.0.0",
4 | "description": "An implementation of PriorityQueue data structure.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/data-structure/PriorityQueue"
14 | },
15 | "dependencies": {
16 | "@utilityjs/comparator": "^1.0.1",
17 | "@utilityjs/min-heap": "^1.0.1"
18 | },
19 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/data-structure/PriorityQueue",
20 | "keywords": [
21 | "javascript",
22 | "typescript",
23 | "data structure",
24 | "algorithm",
25 | "queue",
26 | "priority queue",
27 | "PriorityQueue"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/src/data-structure/Queue/Queue.ts:
--------------------------------------------------------------------------------
1 | import LinkedList from "@utilityjs/linked-list";
2 |
3 | export default class Queue {
4 | private list: LinkedList;
5 |
6 | constructor() {
7 | this.list = new LinkedList();
8 | }
9 |
10 | isEmpty(): boolean {
11 | return this.list.isEmpty();
12 | }
13 |
14 | enqueue(value: T): void {
15 | this.list.append(value);
16 | }
17 |
18 | dequeue(): T | null {
19 | const dequeued = this.peek();
20 |
21 | this.list.deleteHead();
22 |
23 | return dequeued;
24 | }
25 |
26 | peek(): T | null {
27 | const _head = this.list.getHead();
28 |
29 | if (!_head) return null;
30 |
31 | return _head.getValue();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/data-structure/Queue/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Queue
4 |
5 |
6 |
7 |
8 |
9 | An implementation of Queue data structure.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/queue)
13 | [](https://www.npmjs.com/package/@utilityjs/queue)
14 | [](https://www.npmjs.com/package/@utilityjs/queue)
15 |
16 | ```bash
17 | npm i @utilityjs/queue | yarn add @utilityjs/queue
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ### `Queue()`
25 |
26 | ```ts
27 | export default class Queue {
28 | constructor();
29 | isEmpty(): boolean;
30 | enqueue(value: T): void;
31 | dequeue(): T | null;
32 | peek(): T | null;
33 | }
34 | ```
--------------------------------------------------------------------------------
/src/data-structure/Queue/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Queue";
2 |
--------------------------------------------------------------------------------
/src/data-structure/Queue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/queue",
3 | "version": "1.0.1",
4 | "description": "An implementation of Queue data structure.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/data-structure/Queue"
14 | },
15 | "dependencies": {
16 | "@utilityjs/linked-list": "^1.0.1"
17 | },
18 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/data-structure/Queue",
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "data structure",
23 | "algorithm",
24 | "queue"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/data-structure/Stack/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Stack
4 |
5 |
6 |
7 |
8 |
9 | An implementation of Stack data structure.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/stack)
13 | [](https://www.npmjs.com/package/@utilityjs/stack)
14 | [](https://www.npmjs.com/package/@utilityjs/stack)
15 |
16 | ```bash
17 | npm i @utilityjs/stack | yarn add @utilityjs/stack
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ### `Stack()`
25 |
26 | ```ts
27 | export default class Stack {
28 | constructor();
29 | isEmpty(): boolean;
30 | push(value: T): void;
31 | pop(): T | null;
32 | peek(): T | null;
33 | toArray(): T[];
34 | }
35 | ```
--------------------------------------------------------------------------------
/src/data-structure/Stack/Stack.ts:
--------------------------------------------------------------------------------
1 | import LinkedList from "@utilityjs/linked-list";
2 |
3 | export default class Stack {
4 | private list: LinkedList;
5 |
6 | constructor() {
7 | this.list = new LinkedList();
8 | }
9 |
10 | isEmpty(): boolean {
11 | return this.list.isEmpty();
12 | }
13 |
14 | push(value: T): void {
15 | this.list.prepend(value);
16 | }
17 |
18 | pop(): T | null {
19 | const poppedValue = this.peek();
20 |
21 | this.list.deleteHead();
22 |
23 | return poppedValue;
24 | }
25 |
26 | peek(): T | null {
27 | const _head = this.list.getHead();
28 |
29 | if (!_head) return null;
30 |
31 | return _head.getValue();
32 | }
33 |
34 | toArray(): T[] {
35 | return this.list.toArray();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/data-structure/Stack/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Stack";
2 |
--------------------------------------------------------------------------------
/src/data-structure/Stack/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/stack",
3 | "version": "1.0.1",
4 | "description": "An implementation of Stack data structure.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/data-structure/Stack"
14 | },
15 | "dependencies": {
16 | "@utilityjs/linked-list": "^1.0.1"
17 | },
18 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/data-structure/Stack",
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "data structure",
23 | "algorithm",
24 | "stack"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/data-structure/Vector/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Vector
4 |
5 |
6 |
7 |
8 |
9 | An implementation of a two or three-dimensional Vector.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/vector)
13 | [](https://www.npmjs.com/package/@utilityjs/vector)
14 | [](https://www.npmjs.com/package/@utilityjs/vector)
15 |
16 | ```bash
17 | npm i @utilityjs/vector | yarn add @utilityjs/vector
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ### `Vector(x, y, z?)`
25 |
26 | ```ts
27 | export default class Vector {
28 | constructor(x: number, y: number);
29 | constructor(x: number, y: number, z: number);
30 | setX(x: number): Vector;
31 | getX(): number;
32 | setY(y: number): Vector;
33 | getY(): number;
34 | setZ(z: number): Vector;
35 | getZ(): number;
36 | setAxes(x: number, y: number): Vector;
37 | setAxes(x: number, y: number, z: number): Vector;
38 | add(vector: Vector): Vector;
39 | subtract(vector: Vector): Vector;
40 | multiply(vector: Vector): Vector;
41 | multiply(scalar: number): Vector;
42 | dotProduct(vector: Vector): number;
43 | crossProduct(vector: Vector): Vector;
44 | distance(vector: Vector): number;
45 | angleBetween(vector: Vector): number;
46 | lerp(vector: Vector, t: number): Vector;
47 | normalize(): Vector;
48 | getNormalizedVector(): Vector;
49 | reflect(surfaceNormal: Vector): Vector;
50 | reverse(): Vector;
51 | setMagnitude(magnitude: number): Vector;
52 | magnitude(): number;
53 | squaredMagnitude(): number;
54 | equalsTo(vector: Vector): boolean;
55 | clone(): Vector;
56 | toString(): string;
57 | toArray(): [number, number, number];
58 | toObject(): {
59 | x: number;
60 | y: number;
61 | z: number;
62 | };
63 | static fromAngle(angleInRadians: number, magnitude?: number): Vector;
64 | static fromArray(
65 | arrayOfComponents: [number, number, number] | [number, number]
66 | ): Vector;
67 | }
68 | ```
--------------------------------------------------------------------------------
/src/data-structure/Vector/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Vector";
2 |
--------------------------------------------------------------------------------
/src/data-structure/Vector/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/vector",
3 | "version": "1.0.0",
4 | "description": "An implementation of a two or three-dimensional Vector.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/data-structure/Vector"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/data-structure/Vector",
16 | "keywords": [
17 | "javascript",
18 | "typescript",
19 | "data structure",
20 | "algorithm",
21 | "math",
22 | "direction",
23 | "position",
24 | "vector"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/function/createStoreContext/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | createStoreContext
4 |
5 |
6 |
7 |
8 |
9 | A React store-context that manages states of a tree where unnecessary re-renders have been omitted thanks to the Pub/Sub design pattern.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/create-store-context)
13 | [](https://www.npmjs.com/package/@utilityjs/create-store-context)
14 | [](https://www.npmjs.com/package/@utilityjs/create-store-context)
15 |
16 | ```bash
17 | npm i @utilityjs/create-store-context | yarn add @utilityjs/create-store-context
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## How does it work?
25 |
26 | The `` works as a Publisher and each `useStore` is a Subscriber. So when the state changes, only necessary Subscribers will be re-rendered. Since we are using `useRef` instead of `useState` the Publisher itself won't re-render.
27 |
28 | This way we guarantee that only the necessary components need to be re-rendered.
29 |
30 |
31 |
32 | ## Usage
33 |
34 | ```jsx
35 | import createStoreContext from "@utilityjs/create-store-context";
36 |
37 | interface StoreState {
38 | count: number;
39 | log: () => void;
40 | increase: (amount: number) => void;
41 | }
42 |
43 | const { useStore, StoreProvider } = createStoreContext(
44 | (setState, getState) => ({
45 | count: 0,
46 | log: () => void getState().count,
47 | increase: amount =>
48 | setState(state => ({ ...state, count: state.count + amount }))
49 | })
50 | );
51 |
52 | const Controls = () => {
53 | const increase = useStore(state => state.increase);
54 |
55 | return (
56 |
57 | increase(1)}>Increase by 1
58 | increase(5)}>Increase by 5
59 | increase(10)}>Increase by 10
60 |
61 | );
62 | };
63 |
64 | const Display = () => {
65 | const { count, log } = useStore(state => ({
66 | count: state.count,
67 | log: state.log
68 | }));
69 |
70 | log();
71 |
72 | return Count: {count}
;
73 | };
74 |
75 | const Container = () => (
76 |
77 |
Container
78 |
79 |
80 |
81 | );
82 |
83 | const App = () => {
84 | return (
85 |
86 |
87 |
88 | App
89 |
90 |
91 |
92 |
93 | );
94 | };
95 | ```
96 |
97 | ## API
98 |
99 | ### `createStoreContext(stateFactory)`
100 |
101 | ```ts
102 | type StateSelector = (
103 | store: State
104 | ) => PartialState;
105 |
106 | type UseStoreHook = (
107 | selector: StateSelector
108 | ) => PartialState;
109 |
110 | type StateFactory = (
111 | setState: (setter: (prevState: S) => S) => void,
112 | getState: () => S
113 | ) => S;
114 |
115 | declare const createStoreContext: (stateFactory: StateFactory) => {
116 | StoreProvider: (props: { children: React.ReactNode }) => JSX.Element;
117 | useStore: UseStoreHook;
118 | };
119 | ```
120 |
121 | #### `stateFactory`
122 |
123 | An initialization function to initialize the states.
124 |
--------------------------------------------------------------------------------
/src/function/createStoreContext/createStoreContext.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | type SubscribeCallback = () => void;
4 |
5 | type StateSelector = (store: State) => PartialState;
6 |
7 | type UseStoreHook = (
8 | selector: StateSelector
9 | ) => PartialState;
10 |
11 | type StateFactory = (
12 | setState: (setter: (prevState: S) => S) => void,
13 | getState: () => S
14 | ) => S;
15 |
16 | const useSyncStore = (
17 | subscribe: (onStoreChange: SubscribeCallback) => () => void,
18 | getSnapshot: () => State,
19 | getServerSnapshot?: () => State
20 | ): State => {
21 | const getIsomorphicSnapshot =
22 | typeof document !== "undefined"
23 | ? getSnapshot
24 | : getServerSnapshot ?? getSnapshot;
25 |
26 | const [state, setState] = React.useState(getIsomorphicSnapshot);
27 |
28 | const onStoreChange = React.useCallback(
29 | () => setState(() => getIsomorphicSnapshot()),
30 | [getIsomorphicSnapshot]
31 | );
32 |
33 | // eslint-disable-next-line react-hooks/exhaustive-deps
34 | React.useEffect(() => subscribe(onStoreChange), [onStoreChange]);
35 |
36 | return state;
37 | };
38 |
39 | const __STORE_SENTINEL__ = {};
40 |
41 | const createStoreContext = (
42 | stateFactory: StateFactory
43 | ): {
44 | StoreProvider: (props: { children: React.ReactNode }) => JSX.Element;
45 | useStore: UseStoreHook;
46 | } => {
47 | const StoreContext = React.createContext<{
48 | getState: () => S;
49 | subscribe: (onStoreChange: SubscribeCallback) => () => void;
50 | } | null>(null);
51 |
52 | if (process.env.NODE_ENV !== "production")
53 | StoreContext.displayName = "StoreContext";
54 |
55 | const StoreProvider = (props: { children: React.ReactNode }) => {
56 | const store = React.useRef(__STORE_SENTINEL__ as S);
57 | const subscribers = React.useRef(new Set());
58 |
59 | const getState = React.useCallback(() => store.current, []);
60 |
61 | const setState = React.useCallback((setter: (prevState: S) => S) => {
62 | const newState = setter(store.current);
63 |
64 | store.current = newState;
65 | subscribers.current.forEach(cb => cb());
66 | }, []);
67 |
68 | // Lazy initialization
69 | if (store.current === __STORE_SENTINEL__)
70 | store.current = stateFactory(setState, getState);
71 |
72 | const subscribe = React.useCallback((onStoreChange: SubscribeCallback) => {
73 | subscribers.current.add(onStoreChange);
74 |
75 | return () => {
76 | subscribers.current.delete(onStoreChange);
77 | };
78 | }, []);
79 |
80 | const context = React.useMemo(
81 | () => ({ getState, subscribe }),
82 | [getState, subscribe]
83 | );
84 |
85 | return (
86 |
87 | {props.children}
88 |
89 | );
90 | };
91 |
92 | const useStore: UseStoreHook = selector => {
93 | const storeContext = React.useContext(StoreContext);
94 |
95 | if (!storeContext) {
96 | throw new Error(
97 | "[@utilityjs/create-store-context]: You can only use `useStore` in a subtree of ``."
98 | );
99 | }
100 |
101 | const { getState, subscribe } = storeContext;
102 |
103 | const getSnapshot = React.useCallback(
104 | () => selector(getState()),
105 | // eslint-disable-next-line react-hooks/exhaustive-deps
106 | [selector]
107 | );
108 |
109 | return useSyncStore(subscribe, getSnapshot, getSnapshot);
110 | };
111 |
112 | return { useStore, StoreProvider };
113 | };
114 |
115 | export default createStoreContext;
116 |
--------------------------------------------------------------------------------
/src/function/createStoreContext/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./createStoreContext";
2 |
--------------------------------------------------------------------------------
/src/function/createStoreContext/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/create-store-context",
3 | "version": "1.1.0",
4 | "description": "A React store-context that manages states of a tree where unnecessary re-renders have been omitted thanks to the Pub/Sub design pattern.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/function/createStoreContext"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/function/createStoreContext",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "context",
24 | "store",
25 | "state store",
26 | "state management",
27 | "context state"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/src/hook/useControlledProp/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useControlledProp
4 |
5 |
6 |
7 |
8 |
9 | A React hook that handles controllable props.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-controlled-prop)
13 | [](https://www.npmjs.com/package/@utilityjs/use-controlled-prop)
14 | [](https://www.npmjs.com/package/@utilityjs/use-controlled-prop)
15 |
16 | ```bash
17 | npm i @utilityjs/use-controlled-prop | yarn add @utilityjs/use-controlled-prop
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```jsx
27 | import * as React from "react";
28 | import useControlledProp from "@utilityjs/use-controlled-prop";
29 |
30 | const MyComponent = (props, ref) => {
31 | const {
32 | onChange,
33 | value: valueProp,
34 | defaultValue: defaultValueProp, s
35 | ...otherProps
36 | } = props;
37 |
38 | const [value, setUncontrolledValue] = useControlledProp(
39 | valueProp,
40 | defaultValueProp,
41 | "" // Fallback value (if both values were `undefined`)
42 | );
43 |
44 | return (
45 | {
49 | if (onChange) onChange(e);
50 | // This line only works when the `valueProp` is not controlled
51 | setUncontrolledValue(e.target.value);
52 | }}
53 | {...otherProps}
54 | />
55 | );
56 | };
57 | ```
58 |
59 | ## API
60 |
61 | ### `useControlledProp(controlledValue, defaultValue, fallbackValue)`
62 |
63 | ```ts
64 | declare const useControlledProp: (
65 | controlledValueProp: T | undefined,
66 | defaultValueProp: T | undefined,
67 | fallbackValue: T
68 | ) => [
69 | value: T,
70 | setUncontrolledValue: React.Dispatch>,
71 | isControlled: boolean
72 | ];
73 | ```
74 |
75 | #### `controlledValueProp`
76 |
77 | The value to be controlled.
78 |
79 | #### `defaultValueProp`
80 |
81 | The default value.
82 |
83 | #### `fallbackValue`
84 |
85 | The value to fallback to when `controlledValue` and `defaultValueProp` were `undefined`.
86 |
--------------------------------------------------------------------------------
/src/hook/useControlledProp/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useControlledProp";
2 |
--------------------------------------------------------------------------------
/src/hook/useControlledProp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-controlled-prop",
3 | "version": "1.1.2",
4 | "description": "A React hook that handles controllable props.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/hook/useControlledProp"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useControlledProp",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "react hook",
24 | "controlled",
25 | "uncontrolled",
26 | "prop",
27 | "use controlled",
28 | "use uncontrolled"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/src/hook/useControlledProp/useControlledProp.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const isEqual = (v1: T, v2: T): boolean => {
4 | if (typeof v1 !== typeof v2) return false;
5 | if (typeof v1 === "object") {
6 | if (!Array.isArray(v1)) return false;
7 | else if (v1.length !== (v2).length) return false;
8 | else {
9 | for (let i = 0; i < v1.length; i++) {
10 | if (v1[i] !== (v2)[i]) return false;
11 | }
12 | }
13 | }
14 |
15 | return true;
16 | };
17 |
18 | const isUndef = (value: T): boolean => typeof value === "undefined";
19 |
20 | const useControlledProp = (
21 | controlledValueProp: T | undefined,
22 | defaultValueProp: T | undefined,
23 | fallbackValue: T
24 | ): [
25 | value: T,
26 | setUncontrolledValue: React.Dispatch>,
27 | isControlled: boolean
28 | ] => {
29 | const { current: isControlled } = React.useRef(!isUndef(controlledValueProp));
30 | const { current: defaultValue } = React.useRef(defaultValueProp);
31 |
32 | const { current: fallback } = React.useRef(
33 | isUndef(controlledValueProp)
34 | ? isUndef(defaultValueProp)
35 | ? fallbackValue
36 | : defaultValueProp
37 | : undefined
38 | );
39 |
40 | if (process.env.NODE_ENV !== "production") {
41 | // eslint-disable-next-line react-hooks/rules-of-hooks
42 | React.useEffect(() => {
43 | if (
44 | !isControlled &&
45 | defaultValue !== defaultValueProp &&
46 | !isEqual(defaultValue, defaultValueProp)
47 | ) {
48 | // eslint-disable-next-line no-console
49 | console.error(
50 | [
51 | `[@utilityjs/use-controlled-prop]: A component is changing the defaultValue state of an uncontrolled prop after being initialized.`,
52 | `To suppress this warning use a controlled prop.`
53 | ].join(" ")
54 | );
55 | }
56 | // eslint-disable-next-line react-hooks/exhaustive-deps
57 | }, [defaultValueProp]);
58 |
59 | // eslint-disable-next-line react-hooks/rules-of-hooks
60 | React.useEffect(() => {
61 | if (isControlled !== !isUndef(controlledValueProp)) {
62 | // eslint-disable-next-line no-console
63 | console.error(
64 | [
65 | `[@utilityjs/use-controlled-prop]: A component is changing the ${
66 | isControlled ? "" : "un"
67 | }controlled state of a prop to be ${
68 | isControlled ? "un" : ""
69 | }controlled.`,
70 | "Decide between using a controlled or uncontrolled prop " +
71 | "for the lifetime of the component.",
72 | "The nature of the prop's state is determined during the first render, it's considered controlled if the prop is not `undefined`."
73 | ].join("\n")
74 | );
75 | }
76 | // eslint-disable-next-line react-hooks/exhaustive-deps
77 | }, [controlledValueProp]);
78 |
79 | if (
80 | isUndef(controlledValueProp) &&
81 | isUndef(defaultValueProp) &&
82 | isUndef(fallbackValue)
83 | ) {
84 | // eslint-disable-next-line no-console
85 | console.error(
86 | [
87 | "[@utilityjs/use-controlled-prop]: The values you provide are `undefined`!",
88 | "To suppress this warning use a valid non-undefined controlled, default or fallback value."
89 | ].join(" ")
90 | );
91 | }
92 | }
93 |
94 | const [uncontrolledValue, setUncontrolledValue] = React.useState(fallback);
95 | const value = isControlled ? controlledValueProp : uncontrolledValue;
96 |
97 | return [
98 | value,
99 | React.useCallback((newValue: React.SetStateAction) => {
100 | if (!isControlled) setUncontrolledValue(newValue);
101 | // eslint-disable-next-line react-hooks/exhaustive-deps
102 | }, []),
103 | isControlled
104 | ];
105 | };
106 |
107 | export default useControlledProp;
108 |
--------------------------------------------------------------------------------
/src/hook/useCopyToClipboard/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useCopyToClipboard
4 |
5 |
6 |
7 |
8 |
9 | A React hook for copying text to the clipboard.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-copy-to-clipboard)
13 | [](https://www.npmjs.com/package/@utilityjs/use-copy-to-clipboard)
14 | [](https://www.npmjs.com/package/@utilityjs/use-copy-to-clipboard)
15 |
16 | ```bash
17 | npm i @utilityjs/use-copy-to-clipboard | yarn add @utilityjs/use-copy-to-clipboard
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```tsx
27 | const App: React.FC = () => {
28 | const [isCopied, setIsCopied] = React.useState(false);
29 |
30 | const copy = useCopyToClipboard();
31 |
32 | return (
33 |
34 | void setIsCopied(await copy("Hello, World!"))}
37 | >
38 | {isCopied ? "Copied!" : "Copy"}
39 |
40 |
41 | );
42 | };
43 | ```
44 |
45 | ## API
46 |
47 | ### `useCopyToClipboard()`
48 |
49 | ```ts
50 | declare const useCopyToClipboard: () => (text: string) => Promise;
51 | ```
52 |
--------------------------------------------------------------------------------
/src/hook/useCopyToClipboard/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useCopyToClipboard";
2 |
--------------------------------------------------------------------------------
/src/hook/useCopyToClipboard/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-copy-to-clipboard",
3 | "version": "1.0.1",
4 | "description": "A React hook for copying text to the clipboard.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/useCopyToClipboard"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useCopyToClipboard",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "react hook",
24 | "clipboard",
25 | "copy text",
26 | "copy to clipboard"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/src/hook/useCopyToClipboard/useCopyToClipboard.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const copyText = (text: string): Promise => {
4 | // Uses the Async Clipboard API when available
5 | if (navigator.clipboard) return navigator.clipboard.writeText(text);
6 |
7 | // Fallback to old depricated API
8 | return (() => {
9 | const dummy = document.createElement("span");
10 | dummy.textContent = text;
11 | dummy.style.whiteSpace = "pre";
12 |
13 | document.body.appendChild(dummy);
14 |
15 | const selection = window.getSelection();
16 |
17 | if (!selection) return Promise.reject();
18 |
19 | const range = window.document.createRange();
20 |
21 | selection.removeAllRanges();
22 | range.selectNode(dummy);
23 | selection.addRange(range);
24 |
25 | try {
26 | window.document.execCommand("copy");
27 | } catch (err) {
28 | return Promise.reject();
29 | }
30 |
31 | selection.removeAllRanges();
32 | document.body.removeChild(dummy);
33 |
34 | return Promise.resolve();
35 | })();
36 | };
37 |
38 | const useCopyToClipboard = (): ((text: string) => Promise) => {
39 | return React.useCallback(async (text: string) => {
40 | try {
41 | await copyText(text);
42 | return true;
43 | } catch {
44 | if (navigator.clipboard) {
45 | // eslint-disable-next-line no-console
46 | console.error(
47 | "[@utilityjs/use-copy-to-clipboard]: The caller does not have permission to write to the clipboard!"
48 | );
49 | }
50 | return false;
51 | }
52 | }, []);
53 | };
54 |
55 | export default useCopyToClipboard;
56 |
--------------------------------------------------------------------------------
/src/hook/useDarkMode/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useDarkMode
4 |
5 |
6 |
7 |
8 |
9 | A React hook that enables a SSR-friendly multi-tab persistent dark mode behaviour.\
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-dark-mode)
13 | [](https://www.npmjs.com/package/@utilityjs/use-dark-mode)
14 | [](https://www.npmjs.com/package/@utilityjs/use-dark-mode)
15 |
16 | ```bash
17 | npm i @utilityjs/use-dark-mode | yarn add @utilityjs/use-dark-mode
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```tsx
27 | const App: React.FC = () => {
28 | const { isDarkMode, toggle } = useDarkMode();
29 |
30 | return (
31 |
32 |
void toggle()}>Toggle
33 |
{isDarkMode}
34 |
35 | );
36 | };
37 | ```
38 |
39 | ## API
40 |
41 | ### `useDarkMode(options?)`
42 |
43 | ```ts
44 | interface Options {
45 | /**
46 | * The initial state of the dark mode.\
47 | * If left unset, it will be set based on `(prefers-color-scheme: dark)` query.
48 | */
49 | initialState?: boolean;
50 | /**
51 | * The key is used to persist the state.
52 | *
53 | * @default "utilityjs-dark-mode"
54 | */
55 | storageKey?: string;
56 | /**
57 | * The class to toggle when state changes.\
58 | * The specified class will be applied on dark mode.
59 | *
60 | * @default "dark-mode"
61 | */
62 | toggleClassName?: string;
63 | /**
64 | * A function returning a storage.\
65 | * The storage must fit `window.localStorage`'s api.
66 | *
67 | * @default () => localStorage
68 | */
69 | getStorage?: () => Storage | null;
70 | }
71 |
72 | declare const useDarkMode: (options?: Options | undefined) => {
73 | isDarkMode: boolean;
74 | enable: () => void;
75 | disable: () => void;
76 | toggle: () => void;
77 | };
78 | ```
79 |
80 | #### `options`
81 |
82 | The options to adjust the hook.
83 |
84 | ##### `options.storageKey` - (`default: "utilityjs-dark-mode"`)
85 |
86 | The key is used to persist the state.
87 |
88 | ##### `options.initialState`
89 |
90 | The initial state of the dark mode.\
91 | If left unset, it will be set based on `(prefers-color-scheme: dark)` query.
92 |
93 | ##### `options.getStorage` - (`default: () => localStorage`)
94 |
95 | A function returning a storage.\
96 | The storage must fit `window.localStorage`'s api.
97 |
98 | ##### `options.toggleClassName` - (`default: "dark-mode"`)
99 |
100 | The class to toggle when state changes.\
101 | The specified class will be applied on dark mode.
--------------------------------------------------------------------------------
/src/hook/useDarkMode/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useDarkMode";
2 |
--------------------------------------------------------------------------------
/src/hook/useDarkMode/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-dark-mode",
3 | "version": "1.0.1",
4 | "description": "A React hook that enables a SSR-friendly multi-tab persistent dark mode behaviour.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/hook/useDarkMode"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useDarkMode",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "dependencies": {
20 | "@utilityjs/use-media-query": "^1.0.1",
21 | "@utilityjs/use-persisted-state": "^1.1.3"
22 | },
23 | "keywords": [
24 | "javascript",
25 | "typescript",
26 | "react",
27 | "react hook",
28 | "dark mode",
29 | "use dark mode",
30 | "use persistent dark mode"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/src/hook/useDarkMode/useDarkMode.ts:
--------------------------------------------------------------------------------
1 | import useMediaQuery from "@utilityjs/use-media-query";
2 | import usePersistedState from "@utilityjs/use-persisted-state";
3 | import * as React from "react";
4 |
5 | interface Options {
6 | /**
7 | * The initial state of the dark mode.\
8 | * If left unset, it will be set based on `(prefers-color-scheme: dark)` query.
9 | */
10 | initialState?: boolean;
11 | /**
12 | * The key is used to persist the state.
13 | *
14 | * @default "utilityjs-dark-mode"
15 | */
16 | storageKey?: string;
17 | /**
18 | * The class to toggle when state changes.\
19 | * The specified class will be applied on dark mode.
20 | *
21 | * @default "dark-mode"
22 | */
23 | toggleClassName?: string;
24 | /**
25 | * A function returning a storage.
26 | * The storage must fit `window.localStorage`'s api.
27 | *
28 | * @default () => localStorage
29 | */
30 | getStorage?: () => Storage | null;
31 | }
32 |
33 | const addClassName = (element: HTMLElement, className: string) =>
34 | void (element.className = (element.className + ` ${className}`).trim());
35 |
36 | const removeClassName = (element: HTMLElement, className: string) =>
37 | void (element.className = element.className
38 | .split(" ")
39 | .filter(cls => cls !== className)
40 | .join(" "));
41 |
42 | const useIsomorphicLayoutEffect =
43 | typeof window === "undefined" ? React.useEffect : React.useLayoutEffect;
44 |
45 | const useDarkMode = (
46 | options?: Options
47 | ): {
48 | isDarkMode: boolean;
49 | enable: () => void;
50 | disable: () => void;
51 | toggle: () => void;
52 | } => {
53 | const {
54 | getStorage,
55 | initialState,
56 | storageKey = "utilityjs-dark-mode",
57 | toggleClassName = "dark-mode"
58 | } = options || {};
59 |
60 | const [prefersDark] = useMediaQuery("(prefers-color-scheme: dark)");
61 |
62 | const [state, setState] = usePersistedState(initialState, {
63 | name: storageKey,
64 | getStorage
65 | });
66 |
67 | const darkMode = typeof state === "undefined" ? prefersDark : state;
68 |
69 | useIsomorphicLayoutEffect(() => {
70 | if (darkMode) addClassName(document.body, toggleClassName);
71 | else removeClassName(document.body, toggleClassName);
72 | }, [darkMode]);
73 |
74 | return {
75 | isDarkMode: darkMode,
76 | /* eslint-disable react-hooks/exhaustive-deps */
77 | enable: React.useCallback(() => setState(true), []),
78 | disable: React.useCallback(() => setState(false), []),
79 | toggle: () => setState(!darkMode)
80 | };
81 | };
82 |
83 | export default useDarkMode;
84 |
--------------------------------------------------------------------------------
/src/hook/useDeterministicId/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useDeterministicId
4 |
5 |
6 |
7 |
8 |
9 | A React hook that generates a deterministic unique ID once per component.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-deterministic-id)
13 | [](https://www.npmjs.com/package/@utilityjs/use-deterministic-id)
14 | [](https://www.npmjs.com/package/@utilityjs/use-deterministic-id)
15 |
16 | ```bash
17 | npm i @utilityjs/use-deterministic-id | yarn add @utilityjs/use-deterministic-id
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | This hook in React versions less than 18 relies on hydration to avoid server/client mismatch errors.\
25 | If you'd like to generate ids server-side, we suggest upgrading to React 18.
26 |
27 |
28 |
29 | ## Usage
30 |
31 | ```tsx
32 | const App = (props) => {
33 | const id = useDeterministicId();
34 | // or useDeterministicId(props.id)
35 |
36 | return (
37 |
38 | Label
39 |
40 |
41 | );
42 | };
43 | ```
44 |
45 | ## API
46 |
47 | ### `useDeterministicId(idOverride?, prefix?)`
48 |
49 | ```ts
50 | declare const useDeterministicId: (idOverride?: string, prefix?: string) => string;
51 | ```
52 |
53 | #### `idOverride`
54 |
55 | Allows you to override the generated id with your own id.
56 |
57 | #### `prefix`
58 |
59 | Allows you to prefix the generated id.
60 |
--------------------------------------------------------------------------------
/src/hook/useDeterministicId/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useDeterministicId";
2 |
--------------------------------------------------------------------------------
/src/hook/useDeterministicId/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-deterministic-id",
3 | "version": "1.2.0",
4 | "description": "A React hook that generates a deterministic unique ID once per component.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/hook/useDeterministicId"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useDeterministicId",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "react hook",
24 | "id",
25 | "deterministic id",
26 | "use id",
27 | "id hook",
28 | "unique id hook",
29 | "unique id"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/src/hook/useDeterministicId/useDeterministicId.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const DEFAULT_PREFIX = "UTILITYJS-GEN-ID";
4 | let globalId = 0;
5 |
6 | /* eslint-disable */
7 | const useReactId: () => string | undefined =
8 | // We use `toString()` to prevent bundlers from trying to `import { useId } from "react"`
9 | string)>(React)["useId".toString()] ??
10 | (() => void 0);
11 | /* eslint-enable */
12 |
13 | const prefixStr = (str: string, prefix: string) =>
14 | `${prefix.length ? prefix : DEFAULT_PREFIX}-${str}`;
15 |
16 | /**
17 | * In `React < 18`: This hook relies on hydration to avoid server/client mismatch errors.
18 | */
19 | const useDeterministicId = (
20 | idOverride?: string,
21 | prefix = DEFAULT_PREFIX
22 | ): string => {
23 | const reactId = useReactId();
24 | const inputId =
25 | idOverride ?? (reactId ? prefixStr(reactId, prefix) : undefined);
26 |
27 | const [defaultId, setDefaultId] = React.useState(inputId);
28 | const id = inputId ?? defaultId;
29 |
30 | React.useEffect(() => {
31 | if (id != null) return;
32 | // Fallback to this default id when possible.
33 | // Use the incrementing value for client-side rendering only.
34 | // We can't use it server-side.
35 | globalId += 1;
36 | setDefaultId(prefixStr(String(globalId), prefix));
37 | }, [id, prefix]);
38 |
39 | return id ?? "";
40 | };
41 |
42 | export default useDeterministicId;
43 |
--------------------------------------------------------------------------------
/src/hook/useEventListener/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useEventListener
4 |
5 |
6 |
7 |
8 |
9 | A React hook that handles binding/unbinding event listeners in a smart way.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-event-listener)
13 | [](https://www.npmjs.com/package/@utilityjs/use-event-listener)
14 | [](https://www.npmjs.com/package/@utilityjs/use-event-listener)
15 |
16 | ```bash
17 | npm i @utilityjs/use-event-listener | yarn add @utilityjs/use-event-listener
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```ts
27 | useEventListener({
28 | target: document,
29 | eventType: "click",
30 | handler: event => console.log(event)
31 | });
32 |
33 | useEventListener({
34 | target: window,
35 | eventType: "click",
36 | handler: event => console.log(event)
37 | });
38 |
39 | useEventListener({
40 | target: document.getElementById("target"),
41 | eventType: "click",
42 | handler: event => console.log(event)
43 | });
44 | ```
45 |
46 | ## API
47 |
48 | ### `useEventListener(config, shouldAttach?)`
49 |
50 | ```ts
51 | type UseEventListener = {
52 | (
53 | config: {
54 | target: T | null;
55 | eventType: K;
56 | handler: DocumentEventListener;
57 | options?: Options;
58 | },
59 | shouldAttach?: boolean
60 | ): void;
61 | (
62 | config: {
63 | target: T | null;
64 | eventType: K;
65 | handler: WindowEventListener;
66 | options?: Options;
67 | },
68 | shouldAttach?: boolean
69 | ): void;
70 | (
71 | config: {
72 | target: React.RefObject | T | null;
73 | eventType: K;
74 | handler: ElementEventListener;
75 | options?: Options;
76 | },
77 | shouldAttach?: boolean
78 | ): void;
79 | };
80 |
81 | declare const useEventListener: UseEventListener;
82 | ```
83 |
84 | #### `config`
85 |
86 | ##### `config.target`
87 |
88 | The target to which the listener will be attached.
89 |
90 | ##### `config.eventType`
91 |
92 | A case-sensitive string representing the event type to listen for.
93 |
94 | ##### `config.handler`
95 |
96 | See [The event listener callback](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#The_event_listener_callback) for details on the callback itself.
97 |
98 | ##### `config.options`
99 |
100 | See [The event listener callback](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters) for details on the third parameter.
101 |
102 | #### `shouldAttach`
103 |
104 | If set to `false`, the listener won't be attached. (default = true)
105 |
--------------------------------------------------------------------------------
/src/hook/useEventListener/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useEventListener";
2 |
--------------------------------------------------------------------------------
/src/hook/useEventListener/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-event-listener",
3 | "version": "1.0.6",
4 | "description": "A React hook that handles binding/unbinding event listeners in a smart way.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/hook/useEventListener"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useEventListener",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "dependencies": {
20 | "@utilityjs/use-get-latest": "^1.0.2"
21 | },
22 | "keywords": [
23 | "javascript",
24 | "typescript",
25 | "react",
26 | "react hook",
27 | "event listener",
28 | "use event listener"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/src/hook/useEventListener/useEventListener.ts:
--------------------------------------------------------------------------------
1 | import useLatest from "@utilityjs/use-get-latest";
2 | import * as React from "react";
3 |
4 | type ElementEventListener = (
5 | this: HTMLElement,
6 | ev: HTMLElementEventMap[K]
7 | ) => void;
8 |
9 | type DocumentEventListener = (
10 | this: Document,
11 | ev: DocumentEventMap[K]
12 | ) => void;
13 |
14 | type WindowEventListener = (
15 | this: Window,
16 | ev: WindowEventMap[K]
17 | ) => void;
18 |
19 | type Options = boolean | AddEventListenerOptions;
20 |
21 | type UseEventListener = {
22 | (
23 | config: {
24 | target: React.RefObject | T | null;
25 | eventType: K;
26 | handler: ElementEventListener;
27 | options?: Options;
28 | },
29 | shouldAttach?: boolean
30 | ): void;
31 | (
32 | config: {
33 | target: T | null;
34 | eventType: K;
35 | handler: DocumentEventListener;
36 | options?: Options;
37 | },
38 | shouldAttach?: boolean
39 | ): void;
40 | (
41 | config: {
42 | target: T | null;
43 | eventType: K;
44 | handler: WindowEventListener;
45 | options?: Options;
46 | },
47 | shouldAttach?: boolean
48 | ): void;
49 | };
50 |
51 | const isOptionParamSupported = (): boolean => {
52 | let optionSupported = false;
53 | // eslint-disable-next-line @typescript-eslint/no-empty-function
54 | const fn = () => {};
55 |
56 | try {
57 | const opt = Object.defineProperty({}, "passive", {
58 | get: () => {
59 | optionSupported = true;
60 | return null;
61 | }
62 | });
63 |
64 | window.addEventListener("test", fn, opt);
65 | window.removeEventListener("test", fn, opt);
66 | } catch (e) {
67 | return false;
68 | }
69 |
70 | return optionSupported;
71 | };
72 |
73 | const useEventListener: UseEventListener = (
74 | config: {
75 | target:
76 | | React.RefObject
77 | | HTMLElement
78 | | Window
79 | | Document
80 | | null;
81 | eventType: string;
82 | handler: unknown;
83 | options?: Options;
84 | },
85 | shouldAttach = true
86 | ): void => {
87 | const { target = null, eventType, handler, options } = config;
88 |
89 | const cachedOptions = useLatest(options);
90 | const cachedHandler = useLatest(handler);
91 |
92 | React.useEffect(() => {
93 | const element = target && "current" in target ? target.current : target;
94 |
95 | if (!element) return;
96 |
97 | let unsubscribed = false;
98 | const listener = (event: Event) => {
99 | if (unsubscribed) return;
100 | (cachedHandler.current as (ev: Event) => void)(event);
101 | };
102 |
103 | let thirdParam = cachedOptions.current;
104 |
105 | if (typeof cachedOptions.current !== "boolean") {
106 | if (isOptionParamSupported()) thirdParam = cachedOptions.current;
107 | else thirdParam = cachedOptions.current?.capture;
108 | }
109 |
110 | shouldAttach && element.addEventListener(eventType, listener, thirdParam);
111 |
112 | return () => {
113 | unsubscribed = true;
114 | element.removeEventListener(eventType, listener, thirdParam);
115 | };
116 | // eslint-disable-next-line react-hooks/exhaustive-deps
117 | }, [target, eventType, shouldAttach]);
118 | };
119 |
120 | export default useEventListener;
121 |
--------------------------------------------------------------------------------
/src/hook/useForceRerender/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useForceRerender
4 |
5 |
6 |
7 |
8 |
9 | A React hook that returns a function that will re-render your component when called.\
10 | Useful when logic relies on state not represented in "React state".
11 |
12 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
13 | [](https://www.npmjs.com/package/@utilityjs/use-force-rerender)
14 | [](https://www.npmjs.com/package/@utilityjs/use-force-rerender)
15 | [](https://www.npmjs.com/package/@utilityjs/use-force-rerender)
16 |
17 | ```bash
18 | npm i @utilityjs/use-force-rerender | yarn add @utilityjs/use-force-rerender
19 | ```
20 |
21 |
22 |
23 |
24 |
25 | ## Usage
26 |
27 | ```ts
28 | const Component = () => {
29 | const forceRerender = useForceRerender();
30 |
31 | React.useEffect(() => void DataStore.subscribe(() => void forceRerender()), []);
32 |
33 | return <>{DataStore.data}>;
34 | }
35 | ```
36 |
37 | ## API
38 |
39 | ### `useForceRerender()`
40 |
41 | ```ts
42 | declare const useForceRerender: () => (() => void);
43 | ```
44 |
--------------------------------------------------------------------------------
/src/hook/useForceRerender/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useForceRerender";
2 |
--------------------------------------------------------------------------------
/src/hook/useForceRerender/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-force-rerender",
3 | "version": "1.0.0",
4 | "description": "A React hook that returns a function that will re-render your component when called.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/hook/useForceRerender"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useForceRerender",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "react hook",
24 | "force update",
25 | "force update hook",
26 | "use force update",
27 | "force rerender",
28 | "force rerender hook",
29 | "use force rerender"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/src/hook/useForceRerender/useForceRerender.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const useForceRerender = (): (() => void) => {
4 | const [, setState] = React.useState({});
5 | return React.useCallback(() => void setState({}), []);
6 | };
7 |
8 | export default useForceRerender;
9 |
--------------------------------------------------------------------------------
/src/hook/useForkedRefs/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useForkedRefs
4 |
5 |
6 |
7 |
8 |
9 | A React hook for forking/merging multiple refs into a single one.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-forked-refs)
13 | [](https://www.npmjs.com/package/@utilityjs/use-forked-refs)
14 | [](https://www.npmjs.com/package/@utilityjs/use-forked-refs)
15 |
16 | ```bash
17 | npm i @utilityjs/use-forked-refs | yarn add @utilityjs/use-forked-refs
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```jsx
27 | import * as React from "react";
28 | import useForkedRefs from "@utilityjs/use-forked-refs";
29 |
30 | const MyComponent = React.forwardRef((props, ref) => {
31 | const rootRef = React.useRef(null);
32 | const handleRef = useForkedRefs(ref, rootRef);
33 |
34 | return
;
35 | });
36 | ```
37 |
38 | ## API
39 |
40 | ### `useForkedRefs(...refs)`
41 |
42 | ```ts
43 | declare const useForkedRefs: (...refs: React.Ref[]) => (instance: T | null) => void;
44 | ```
45 |
46 | #### `refs`
47 |
48 | React callback refs or refs created with `useRef()` or `createRef()`.
49 |
--------------------------------------------------------------------------------
/src/hook/useForkedRefs/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useForkedRefs";
2 |
--------------------------------------------------------------------------------
/src/hook/useForkedRefs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-forked-refs",
3 | "version": "1.0.2",
4 | "description": "A React hook for forking/merging multiple refs into a single one.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/useForkedRefs"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useForkedRefs",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "utility",
23 | "fork refs",
24 | "merge ref",
25 | "merge two refs",
26 | "combine refs"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/src/hook/useForkedRefs/useForkedRefs.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const setRef = (ref: React.Ref, value: T) => {
4 | if (typeof ref === "function") ref(value);
5 | else if (ref && typeof ref === "object" && "current" in ref)
6 | (ref as React.MutableRefObject).current = value;
7 | };
8 |
9 | const useForkedRefs = (...refs: React.Ref[]): React.RefCallback =>
10 | React.useCallback(
11 | (instance: T) => void refs.forEach(ref => void setRef(ref, instance)),
12 | [refs]
13 | );
14 |
15 | export default useForkedRefs;
16 |
--------------------------------------------------------------------------------
/src/hook/useGetLatest/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useGetLatest
4 |
5 |
6 |
7 |
8 |
9 | A React hook that stores & updates `ref.current` with the most recent value.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-get-latest)
13 | [](https://www.npmjs.com/package/@utilityjs/use-get-latest)
14 | [](https://www.npmjs.com/package/@utilityjs/use-get-latest)
15 |
16 | ```bash
17 | npm i @utilityjs/use-get-latest | yarn add @utilityjs/use-get-latest
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```jsx
27 | import useGetLatest from "@utilityjs/use-get-latest";
28 | import * as React from "react";
29 |
30 | const useAttachDomClick = (callback) => {
31 | const cachedCallback = useGetLatest(callback);
32 |
33 | React.useEffect(() => {
34 | document.addEventListener("click", cachedCallback.current);
35 | return () => {
36 | document.removeEventListener("click", cachedCallback.current);
37 | }
38 | }, [])
39 | };
40 | ```
41 |
42 | ## API
43 |
44 | ### `useGetLatest(value)`
45 |
46 | ```ts
47 | declare const useGetLatest: (value: T) => MutableRefObject;
48 | ```
49 |
50 | #### `value`
51 |
52 | The value to be stored.
53 |
--------------------------------------------------------------------------------
/src/hook/useGetLatest/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useGetLatest";
2 |
--------------------------------------------------------------------------------
/src/hook/useGetLatest/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-get-latest",
3 | "version": "1.0.2",
4 | "description": "A React hook that stores & updates `ref.current` with the most recent value.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/useGetLatest"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useGetLatest",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "react hook",
24 | "ref",
25 | "useref",
26 | "update ref",
27 | "latest ref value"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/src/hook/useGetLatest/useGetLatest.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const useGetLatest = (value: T): React.MutableRefObject => {
4 | const ref = React.useRef(value);
5 |
6 | React.useEffect(() => void (ref.current = value));
7 |
8 | return ref;
9 | };
10 |
11 | export default useGetLatest;
12 |
--------------------------------------------------------------------------------
/src/hook/useGetScrollbarWidth/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useGetScrollbarWidth
4 |
5 |
6 |
7 |
8 |
9 | A React hook that calculates the width of the user agent's scrollbar.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-get-scrollbar-width)
13 | [](https://www.npmjs.com/package/@utilityjs/use-get-scrollbar-width)
14 | [](https://www.npmjs.com/package/@utilityjs/use-get-scrollbar-width)
15 |
16 | ```bash
17 | npm i @utilityjs/use-get-scrollbar-width | yarn add @utilityjs/use-get-scrollbar-width
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```tsx
27 | const App: React.FC = () => {
28 | const getScrollbarWidth = useGetScrollbarWidth();
29 |
30 | return (
31 |
32 | void console.log(getScrollbarWidth())}>Log Scrollbar Width
33 |
34 | );
35 | };
36 | ```
37 |
38 | ## API
39 |
40 | ### `useGetScrollbarWidth()`
41 |
42 | ```ts
43 | declare const useGetScrollbarWidth: (): (() => number);
44 | ```
45 |
--------------------------------------------------------------------------------
/src/hook/useGetScrollbarWidth/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useGetScrollbarWidth";
2 |
--------------------------------------------------------------------------------
/src/hook/useGetScrollbarWidth/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-get-scrollbar-width",
3 | "version": "1.0.1",
4 | "description": "A React hook that calculates the width of the user agent's scrollbar.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/useGetScrollbarWidth"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useGetScrollbarWidth",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "react hook",
24 | "scrollbar width",
25 | "scrollbar size",
26 | "use scrollbar width",
27 | "use scrollbar size"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/src/hook/useGetScrollbarWidth/useGetScrollbarWidth.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const useIsomorphicLayoutEffect =
4 | typeof window === "undefined" ? React.useEffect : React.useLayoutEffect;
5 |
6 | const useGetScrollbarWidth = (): (() => number) => {
7 | const scrollbarWidth = React.useRef(0);
8 |
9 | useIsomorphicLayoutEffect(() => {
10 | const dummy = document.createElement("div");
11 |
12 | dummy.style.width = "100px";
13 | dummy.style.height = "100px";
14 | dummy.style.overflow = "scroll";
15 | dummy.style.position = "absolute";
16 | dummy.style.top = "-9999px";
17 | dummy.setAttribute("aria-hidden", "true");
18 | dummy.setAttribute("role", "presentation");
19 |
20 | document.body.appendChild(dummy);
21 | scrollbarWidth.current = dummy.offsetWidth - dummy.clientWidth;
22 | document.body.removeChild(dummy);
23 | }, []);
24 |
25 | const getScrollBarWidth = React.useCallback(() => scrollbarWidth.current, []);
26 |
27 | return getScrollBarWidth;
28 | };
29 |
30 | export default useGetScrollbarWidth;
31 |
--------------------------------------------------------------------------------
/src/hook/useHash/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useHash
4 |
5 |
6 |
7 |
8 |
9 | A React hook that helps to sync and modify browser's location hash.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-hash)
13 | [](https://www.npmjs.com/package/@utilityjs/use-hash)
14 | [](https://www.npmjs.com/package/@utilityjs/use-hash)
15 |
16 | ```bash
17 | npm i @utilityjs/use-hash | yarn add @utilityjs/use-hash
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```tsx
27 | import * as React from "react";
28 | import useHash from "@utilityjs/use-hash";
29 |
30 | const App: React.FC = () => {
31 | const hashConsumer = useHash();
32 |
33 | const [key, setKey] = React.useState("");
34 | const [value, setValue] = React.useState("");
35 |
36 | const addParam = () => {
37 | hashConsumer.addParam(key, value);
38 | setKey("");
39 | setValue("");
40 | };
41 |
42 | const deleteParam = () => {
43 | hashConsumer.deleteParam(key);
44 | setKey("");
45 | setValue("");
46 | };
47 |
48 | const deleteValue = () => {
49 | hashConsumer.deleteParamValue(key, value);
50 | setKey("");
51 | setValue("");
52 | };
53 |
54 | return
55 |
Hash: {hashConsumer.hash}
56 |
Params: {JSON.stringify(hashConsumer.getParams(), null, 2)}
57 |
58 |
59 | Key {" "}
60 | setKey(e.target.value)}
66 | />
67 |
68 | Value {" "}
69 | setValue(e.target.value)}
75 | />
76 |
77 |
78 | void addParam()}>Add Param
79 | void deleteParam()}>Delete Param
80 | void deleteValue()}>Delete Value
81 | void hashConsumer.setHash(value)}>
82 | Set Hash State
83 |
84 |
85 |
;
86 | };
87 |
88 | export default App;
89 | ```
90 |
91 | ## API
92 |
93 | ### `useHash()`
94 |
95 | ```ts
96 | interface HashConsumer {
97 | /** The hash state. */
98 | hash: string;
99 |
100 | /** The hash state updater function. */
101 | setHash: React.Dispatch>;
102 |
103 | /** Returns a boolean value indicating if such a given parameter exists. */
104 | hasParam: (key: string) => boolean;
105 |
106 | /** Returns all parameters as key/value pairs. */
107 | getParams: () => Record;
108 |
109 | /** Adds a specified key/value pair as a new parameter. */
110 | addParam: (key: string, value: string) => void;
111 |
112 | /**
113 | * Sets the value associated with a given parameter to the given value.
114 | * If there are several values, the others are deleted.
115 | */
116 | setParam: (key: string, value: string) => void;
117 |
118 | /** Deletes the given parameter, and its associated value. */
119 | deleteParam: (key: string) => void;
120 |
121 | /** Returns the values associated with a given parameter. */
122 | getParamValue: (key: string) => string | string[] | null;
123 |
124 | /**
125 | * Deletes the value associated with a given parameter.
126 | * If there aren't several values, the parameter is deleted.
127 | */
128 | deleteParamValue: (key: string, value: string) => void;
129 | }
130 |
131 | declare const useHash: () => HashConsumer;
132 | ```
133 |
--------------------------------------------------------------------------------
/src/hook/useHash/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useHash";
2 |
--------------------------------------------------------------------------------
/src/hook/useHash/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-hash",
3 | "version": "1.0.0",
4 | "description": "A React hook that helps to sync and modify browser's location hash.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/hook/useHash"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useHash",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "react hook",
24 | "hash",
25 | "use hash",
26 | "hash state",
27 | "use hash state"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/src/hook/useHover/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useHover
4 |
5 |
6 |
7 |
8 |
9 | A React hook that determines if the mouse is hovering an element.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-hover)
13 | [](https://www.npmjs.com/package/@utilityjs/use-hover)
14 | [](https://www.npmjs.com/package/@utilityjs/use-hover)
15 |
16 | ```bash
17 | npm i @utilityjs/use-hover | yarn add @utilityjs/use-hover
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```tsx
27 | const App: React.FC = () => {
28 | const { isHovered, registerRef } = useHover();
29 |
30 | return (
31 |
32 |
33 | {`The current div is ${isHovered ? `hovered` : `unhovered`}`}
34 |
35 |
36 | );
37 | };
38 | ```
39 |
40 | ## API
41 |
42 | ### `useHover()`
43 |
44 | ```ts
45 | declare const useHover: () => {
46 | isHovered: boolean;
47 | setIsHovered: React.Dispatch>;
48 | registerRef: (node: T | null) => void;
49 | };
50 | ```
--------------------------------------------------------------------------------
/src/hook/useHover/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useHover";
2 |
--------------------------------------------------------------------------------
/src/hook/useHover/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-hover",
3 | "version": "1.0.2",
4 | "description": "A React hook that determines if the mouse is hovering an element.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/hook/useHover"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useHover",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "dependencies": {
20 | "@utilityjs/use-register-node-ref": "latest"
21 | },
22 | "keywords": [
23 | "javascript",
24 | "typescript",
25 | "react",
26 | "react hook",
27 | "ref callback",
28 | "use hover hook",
29 | "use mouse enter",
30 | "use mouse leave"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/src/hook/useHover/useHover.ts:
--------------------------------------------------------------------------------
1 | import useRegisterNodeRef from "@utilityjs/use-register-node-ref";
2 | import * as React from "react";
3 |
4 | const useHover = (): {
5 | isHovered: boolean;
6 | setIsHovered: React.Dispatch>;
7 | registerRef: (node: T | null) => void;
8 | } => {
9 | const [isHovered, setIsHovered] = React.useState(false);
10 |
11 | const handleMouseEnter = () => setIsHovered(true);
12 | const handleMouseLeave = () => setIsHovered(false);
13 |
14 | const registerRef = useRegisterNodeRef(node => {
15 | node.addEventListener("mouseenter", handleMouseEnter);
16 | node.addEventListener("mouseleave", handleMouseLeave);
17 |
18 | return () => {
19 | node.removeEventListener("mouseenter", handleMouseEnter);
20 | node.removeEventListener("mouseleave", handleMouseLeave);
21 | };
22 | });
23 |
24 | return { isHovered, setIsHovered, registerRef };
25 | };
26 |
27 | export default useHover;
28 |
--------------------------------------------------------------------------------
/src/hook/useImmutableArray/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useImmutableArray
4 |
5 |
6 |
7 |
8 |
9 | A React hook that creates an array with immutable operations.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-immutable-array)
13 | [](https://www.npmjs.com/package/@utilityjs/use-immutable-array)
14 | [](https://www.npmjs.com/package/@utilityjs/use-immutable-array)
15 |
16 | ```bash
17 | npm i @utilityjs/use-immutable-array | yarn add @utilityjs/use-immutable-array
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```ts
27 | const array = useImmutableArray([1, 2, 3, 4, 5]);
28 |
29 | array.push(6) // [1, 2, 3, 4, 5, 6]
30 | array.pop() // [1, 2, 3, 4, 5]
31 | array.shift() // [2, 3, 4, 5]
32 | array.shift(10) // [10, 2, 3, 4, 5]
33 | array.reverse() // [5, 4, 3, 2, 10]
34 | array.removeByIndex(1) // [5, 3, 2, 10]
35 | array.removeByValue(10) // [5, 3, 2]
36 | array.filter(item => item % 2 !== 0) // [5, 3]
37 | array.insertItem(1, 7) // [5, 7, 3]
38 | array.moveItem(0, 1) // [7, 5, 3]
39 | array.values // [7, 5, 3]
40 | array.setValues([1, 2, 3, 4]) // [1, 2, 3, 4]
41 | ```
42 |
43 | ## API
44 |
45 | ### `useImmutableArray(array)`
46 |
47 | ```ts
48 | interface Return {
49 | pop: () => void;
50 | push: (value: T) => void;
51 | shift: () => void;
52 | unshift: (value: T) => void;
53 | reverse: () => void;
54 | removeByIndex: (index: number) => void;
55 | removeByValue: (value: T) => void;
56 | filter: (
57 | predicate: (value: T, index: number, array: T[]) => value is T,
58 | thisArg?: any
59 | ) => void;
60 | insertItem: (index: number, value: T) => void;
61 | moveItem: (fromIndex: number, toIndex: number) => void;
62 | values: T[];
63 | setValues: SetArrayValues;
64 | }
65 |
66 | declare const useImmutableArray: (array: T[]) => Return;
67 | ```
68 |
69 | #### `array`
70 |
71 | The initial value of the immutable-array.
72 |
--------------------------------------------------------------------------------
/src/hook/useImmutableArray/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useImmutableArray";
2 |
--------------------------------------------------------------------------------
/src/hook/useImmutableArray/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-immutable-array",
3 | "version": "1.0.1",
4 | "description": "A React hook that creates an array with immutable operations.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/useImmutableArray"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useImmutableArray",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "react hook",
24 | "immutable",
25 | "array",
26 | "immutable array"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/src/hook/useImmutableArray/useImmutableArray.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | type SetArrayValues = React.Dispatch>;
4 |
5 | type Pop = () => void;
6 | type Push = (value: T) => void;
7 | type Shift = () => void;
8 | type Unshift = (value: T) => void;
9 | type Reverse = () => void;
10 | type RemoveByIndex = (index: number) => void;
11 | type RemoveByValue = (value: T) => void;
12 | type Filter = (
13 | predicate: (value: T, index: number, array: T[]) => value is T,
14 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
15 | thisArg?: any
16 | ) => void;
17 | type InsertItem = (index: number, value: T) => void;
18 | type MoveItem = (fromIndex: number, toIndex: number) => void;
19 |
20 | const makePop =
21 | (array: T[], setValues: SetArrayValues): Pop =>
22 | () =>
23 | setValues(array.slice(0, -1));
24 |
25 | const makePush =
26 | (array: T[], setValues: SetArrayValues): Push =>
27 | (value: T) =>
28 | setValues([...array, value]);
29 |
30 | const makeShift =
31 | (array: T[], setValues: SetArrayValues): Shift =>
32 | () =>
33 | setValues(array.slice(1));
34 |
35 | const makeUnshift =
36 | (array: T[], setValues: SetArrayValues): Unshift =>
37 | (value: T) =>
38 | setValues([value, ...array]);
39 |
40 | const makeReverse =
41 | (array: T[], setValues: SetArrayValues): Reverse =>
42 | () =>
43 | setValues([...array].reverse());
44 |
45 | const makeRemoveByIndex =
46 | (array: T[], setValues: SetArrayValues): RemoveByIndex =>
47 | (index: number) =>
48 | setValues([...array.slice(0, index), ...array.slice(index + 1)]);
49 |
50 | const makeRemoveByValue =
51 | (array: T[], setValues: SetArrayValues): RemoveByValue =>
52 | (value: T) =>
53 | setValues(array.filter(item => item !== value));
54 |
55 | const makeFilter =
56 | (array: T[], setValues: SetArrayValues): Filter =>
57 | (predicate, thisArg) =>
58 | setValues(array.filter(predicate, thisArg));
59 |
60 | const makeInsertItem =
61 | (array: T[], setValues: SetArrayValues): InsertItem =>
62 | (index: number, value: T) =>
63 | setValues([...array.slice(0, index), value, ...array.slice(index)]);
64 |
65 | const makeMoveItem =
66 | (array: T[], setValues: SetArrayValues): MoveItem =>
67 | (fromIndex: number, toIndex: number) => {
68 | if (fromIndex > toIndex) {
69 | setValues([
70 | ...array.slice(0, toIndex),
71 | array[fromIndex],
72 | ...array.slice(toIndex + 1, fromIndex),
73 | array[toIndex],
74 | ...array.slice(fromIndex + 1)
75 | ]);
76 | } else makeMoveItem(array, setValues)(toIndex, fromIndex);
77 | };
78 |
79 | interface Return {
80 | pop: Pop;
81 | push: Push;
82 | shift: Shift;
83 | unshift: Unshift;
84 | reverse: Reverse;
85 | removeByIndex: RemoveByIndex;
86 | removeByValue: RemoveByValue;
87 | filter: Filter;
88 | insertItem: InsertItem;
89 | moveItem: MoveItem;
90 | values: T[];
91 | setValues: SetArrayValues;
92 | }
93 |
94 | const useImmutableArray = (array: T[]): Return => {
95 | const [values, setValues] = React.useState(array);
96 |
97 | return React.useMemo(
98 | () => ({
99 | pop: makePop(values, setValues),
100 | push: makePush(values, setValues),
101 | shift: makeShift(values, setValues),
102 | unshift: makeUnshift(values, setValues),
103 | reverse: makeReverse(values, setValues),
104 | removeByIndex: makeRemoveByIndex(values, setValues),
105 | removeByValue: makeRemoveByValue(values, setValues),
106 | filter: makeFilter(values, setValues),
107 | insertItem: makeInsertItem(values, setValues),
108 | moveItem: makeMoveItem(values, setValues),
109 | values,
110 | setValues
111 | }),
112 | [values]
113 | );
114 | };
115 |
116 | export default useImmutableArray;
117 |
--------------------------------------------------------------------------------
/src/hook/useIsInViewport/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useIsInViewport
4 |
5 |
6 |
7 |
8 |
9 | A React hook that tells you when an element enters or leaves the viewport.\
10 | (Reuses observer instances where possible)
11 |
12 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
13 | [](https://www.npmjs.com/package/@utilityjs/use-is-in-viewport)
14 | [](https://www.npmjs.com/package/@utilityjs/use-is-in-viewport)
15 | [](https://www.npmjs.com/package/@utilityjs/use-is-in-viewport)
16 |
17 | ```bash
18 | npm i @utilityjs/use-is-in-viewport | yarn add @utilityjs/use-is-in-viewport
19 | ```
20 |
21 |
22 |
23 |
24 |
25 | ## Usage
26 |
27 | ```tsx
28 | const App: React.FC = () => {
29 | const { registerNode, isInViewport } = useIsInViewport();
30 |
31 | console.log(`is red box in the viewport? ${isInViewport}`);
32 |
33 | return (
34 |
35 |
36 |
37 |
38 |
45 |
46 |
47 |
48 | );
49 | };
50 | ```
51 |
52 | ## API
53 |
54 | ### `useIsInViewport( options?)`
55 |
56 | ```ts
57 | interface HookOptions {
58 | once?: boolean;
59 | disabled?: boolean;
60 | }
61 |
62 | interface HookConsumer {
63 | registerNode: (node: T | null) => void;
64 | isInViewport: boolean;
65 | }
66 |
67 | declare const useIsInViewport: (
68 | options?: (IntersectionObserverInit & HookOptions) | undefined
69 | ) => HookConsumer;
70 | ```
71 |
72 | #### `options.once` - (default: `false`)
73 |
74 | Only trigger the callback once. (unless you have toggled `disabled` option.)
75 |
76 | #### `options.disabled` - (default: `false`)
77 |
78 | Skip creating the observer.\
79 | You can use this to enable and disable the observer as needed.
80 |
81 | #### `options.threshold` - (default: `[0, 1]`)
82 |
83 | Read the [MDN Web Doc](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#intersection_observer_options)
84 |
85 | #### `options.root` - (default: `null`)
86 |
87 | Read the [MDN Web Doc](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#intersection_observer_options)
88 |
89 | #### `options.rootMargin` - (default: `0px`)
90 |
91 | Read the [MDN Web Doc](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#intersection_observer_options)
--------------------------------------------------------------------------------
/src/hook/useIsInViewport/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useIsInViewport";
2 |
--------------------------------------------------------------------------------
/src/hook/useIsInViewport/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-is-in-viewport",
3 | "version": "1.0.0",
4 | "description": "A React hook that tells you when an element enters or leaves the viewport.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/hook/useIsInViewport"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useIsInViewport",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "dependencies": {
20 | "@utilityjs/use-register-node-ref": "^1.1.0"
21 | },
22 | "keywords": [
23 | "javascript",
24 | "typescript",
25 | "react",
26 | "react hook",
27 | "viewport",
28 | "lazy-loading",
29 | "intersection",
30 | "observer",
31 | "intersection observer",
32 | "isInViewport"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/src/hook/useIsMounted/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useIsMounted
4 |
5 |
6 |
7 |
8 |
9 | A React hook that returns `true` if the component is mounted.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-is-mounted)
13 | [](https://www.npmjs.com/package/@utilityjs/use-is-mounted)
14 | [](https://www.npmjs.com/package/@utilityjs/use-is-mounted)
15 |
16 | ```bash
17 | npm i @utilityjs/use-is-mounted | yarn add @utilityjs/use-is-mounted
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```ts
27 | import useGetLatest from "@utilityjs/use-get-latest";
28 | import useIsMounted from "@utilityjs/use-is-mounted";
29 | import * as React from "react";
30 |
31 | const useHook = (callback: () => void) => {
32 | const isMounted = useIsMounted();
33 |
34 | const cachedCallback = useGetLatest(callback);
35 |
36 | React.useEffect(() => {
37 | const cb = cachedCallback.current();
38 | if (isMounted()) cb();
39 | }, []);
40 | };
41 | ```
42 |
43 | ## API
44 |
45 | ### `useIsMounted()`
46 |
47 | ```ts
48 | declare const useIsMounted: () => (() => boolean);
49 | ```
50 |
--------------------------------------------------------------------------------
/src/hook/useIsMounted/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useIsMounted";
2 |
--------------------------------------------------------------------------------
/src/hook/useIsMounted/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-is-mounted",
3 | "version": "1.0.2",
4 | "description": "A React hook that returns `true` if the component is mounted.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/useIsMounted"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useIsMounted",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "react hook",
24 | "mounted",
25 | "is mounted",
26 | "use is mounted"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/src/hook/useIsMounted/useIsMounted.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const useIsMounted = (): (() => boolean) => {
4 | const isMountedRef = React.useRef(false);
5 |
6 | React.useEffect(() => {
7 | isMountedRef.current = true;
8 | return () => void (isMountedRef.current = false);
9 | }, []);
10 |
11 | return React.useCallback(() => isMountedRef.current, []);
12 | };
13 |
14 | export default useIsMounted;
15 |
--------------------------------------------------------------------------------
/src/hook/useIsServerHandoffComplete/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useIsServerHandoffComplete
4 |
5 |
6 |
7 |
8 |
9 | A React hook that returns `true` if the SSR handoff completes.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-is-server-handoff-complete)
13 | [](https://www.npmjs.com/package/@utilityjs/use-is-server-handoff-complete)
14 | [](https://www.npmjs.com/package/@utilityjs/use-is-server-handoff-complete)
15 |
16 | ```bash
17 | npm i @utilityjs/use-is-server-handoff-complete | yarn add @utilityjs/use-is-server-handoff-complete
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```ts
27 | import useIsServerHandoffComplete from "@utilityjs/use-is-server-handoff-complete";
28 | import * as React from "react";
29 |
30 | let __ID__ = 0;
31 |
32 | const useDeterministicId = (inputId?: string) => {
33 | const handoffCompletes = useIsServerHandoffComplete();
34 |
35 | const [id, setId] = React.useState(
36 | inputId ?? handoffCompletes ? String(__ID__++) : null
37 | );
38 |
39 | React.useEffect(() => {
40 | if (id != null) return;
41 | setId(__ID__++);
42 | }, [id]);
43 |
44 | return id;
45 | };
46 | ```
47 |
48 | ## API
49 |
50 | ### `useIsServerHandoffComplete()`
51 |
52 | ```ts
53 | declare const useIsServerHandoffComplete: () => boolean;
54 | ```
55 |
--------------------------------------------------------------------------------
/src/hook/useIsServerHandoffComplete/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useIsServerHandoffComplete";
2 |
--------------------------------------------------------------------------------
/src/hook/useIsServerHandoffComplete/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-is-server-handoff-complete",
3 | "version": "1.0.0",
4 | "description": "A React hook that returns `true` if the SSR handoff completes.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/hook/useIsServerHandoffComplete"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useIsServerHandoffComplete",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "react hook",
24 | "ssr",
25 | "server handoff",
26 | "ssr handoff"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/src/hook/useIsServerHandoffComplete/useIsServerHandoffComplete.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const handoff = { isComplete: false };
4 |
5 | const useIsServerHandoffComplete = (): boolean => {
6 | const [isComplete, setIsComplete] = React.useState(handoff.isComplete);
7 |
8 | React.useEffect(
9 | () => void (!isComplete && setIsComplete(true)),
10 | [isComplete]
11 | );
12 |
13 | React.useEffect(() => {
14 | if (!handoff.isComplete) handoff.isComplete = true;
15 | }, []);
16 |
17 | return isComplete;
18 | };
19 |
20 | export default useIsServerHandoffComplete;
21 |
--------------------------------------------------------------------------------
/src/hook/useIsomorphicLayoutEffect/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useIsomorphicLayoutEffect
4 |
5 |
6 |
7 |
8 |
9 | A React hook that schedules a `React.useLayoutEffect` with a fallback to a `React.useEffect` for environments where layout effects should not be used (such as server-side rendering).
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-isomorphic-layout-effect)
13 | [](https://www.npmjs.com/package/@utilityjs/use-isomorphic-layout-effect)
14 | [](https://www.npmjs.com/package/@utilityjs/use-isomorphic-layout-effect)
15 |
16 | ```bash
17 | npm i @utilityjs/use-isomorphic-layout-effect | yarn add @utilityjs/use-isomorphic-layout-effect
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## API
25 |
26 | ### `useIsomorphicLayoutEffect(effect, deps?)`
27 |
28 | ```ts
29 | declare const useIsomorphicLayoutEffect: typeof React.useLayoutEffect;
30 | ```
31 |
--------------------------------------------------------------------------------
/src/hook/useIsomorphicLayoutEffect/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useIsomorphicLayoutEffect";
2 |
--------------------------------------------------------------------------------
/src/hook/useIsomorphicLayoutEffect/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-isomorphic-layout-effect",
3 | "version": "1.0.2",
4 | "description": "A React hook that schedules a `React.useLayoutEffect` with a fallback to a `React.useEffect` for environments where layout effects should not be used (such as server-side rendering).",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/useIsomorphicLayoutEffect"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useIsomorphicLayoutEffect",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "react hook",
24 | "layout effect",
25 | "isomorphic",
26 | "isomorphic layout effect"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/src/hook/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const useIsomorphicLayoutEffect =
4 | typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
5 |
6 | export default useIsomorphicLayoutEffect;
7 |
--------------------------------------------------------------------------------
/src/hook/useKeybind/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./useKeybind";
2 | export { default } from "./useKeybind";
3 |
--------------------------------------------------------------------------------
/src/hook/useKeybind/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-keybind",
3 | "version": "1.0.4",
4 | "description": "A React hook for invoking a callback when hotkeys are pressed.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/hook/useKeybind"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useKeybind",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "dependencies": {
20 | "@utilityjs/use-event-listener": "^1.0.6"
21 | },
22 | "keywords": [
23 | "javascript",
24 | "typescript",
25 | "react",
26 | "react hook",
27 | "keybind",
28 | "hotkey",
29 | "use hotkey",
30 | "use keybind",
31 | "shortcut",
32 | "use shortcut",
33 | "shortkey",
34 | "use shortkey"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/src/hook/useLazyInitializedValue/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useLazyInitializedValue
4 |
5 |
6 |
7 |
8 |
9 | A React hook that holds a lazy-initialized value for a component's lifecycle.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-lazy-initialized-value)
13 | [](https://www.npmjs.com/package/@utilityjs/use-lazy-initialized-value)
14 | [](https://www.npmjs.com/package/@utilityjs/use-lazy-initialized-value)
15 |
16 | ```bash
17 | npm i @utilityjs/use-lazy-initialized-value | yarn add @utilityjs/use-lazy-initialized-value
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | This hook calls the provided `initFactory` on mount and returns the factory value for the duration of the component's lifecycle. See React docs on [creating expensive objects lazily](https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily).
25 |
26 | #### **Comparison to `useMemo`**
27 | You may rely on `useMemo` only as a performance optimization, not as a [semantic guarantee](https://reactjs.org/docs/hooks-reference.html#usememo).
28 | React may throw away the cached value and recall your factory even if deps did not change.
29 |
30 | #### **Comparison to `useState`**
31 | You can get the same result using `useState(factory)[0]`, but it's a little more expensive supporting unused update functionality.
32 |
33 | The right way is to implement it using `useRef` as described in React doc's [how to create expensive objects lazily](https://reactjs.org/docs/hooks-faq.html#how-to-create-expensive-objects-lazily). However, `useLazyInitializedValue` is likely more convenient and hides the `ref.current` implementation detail.
34 |
35 |
36 |
37 | ## Usage
38 |
39 | ```tsx
40 | const Component = () => {
41 | // Creating expensive object lazily
42 | // Can guarantee that `obj` is always same instance
43 | const obj = useLazyInitializedValue(() => {
44 | return createExpensiveObject();
45 | });
46 |
47 | // ...
48 | }
49 | ```
50 |
51 | ## API
52 |
53 | ### `useLazyInitializedValue(initFactory)`
54 |
55 | ```ts
56 | declare const useLazyInitializedValue: (initFactory: () => T) => T;
57 | ```
--------------------------------------------------------------------------------
/src/hook/useLazyInitializedValue/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useLazyInitializedValue";
2 |
--------------------------------------------------------------------------------
/src/hook/useLazyInitializedValue/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-lazy-initialized-value",
3 | "version": "1.0.0",
4 | "description": "A React hook that holds a lazy-initialized value for a component's lifecycle.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/hook/useLazyInitializedValue"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useLazyInitializedValue",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "keywords": [
20 | "javascript",
21 | "typescript",
22 | "react",
23 | "react hook",
24 | "lazy initialization",
25 | "lazy initialization hook",
26 | "lazy initialized value",
27 | "lazy initialized value hook",
28 | "use lazy initialized value",
29 | "lazy value",
30 | "lazy value hook",
31 | "use lazy value"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/src/hook/useLazyInitializedValue/useLazyInitializedValue.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const __SENTINEL__ = {};
4 |
5 | const useLazyInitializedValue = (initFactory: () => T): T => {
6 | const lazyValue = React.useRef(__SENTINEL__);
7 |
8 | if (lazyValue.current === __SENTINEL__) lazyValue.current = initFactory();
9 | return lazyValue.current;
10 | };
11 |
12 | export default useLazyInitializedValue;
13 |
--------------------------------------------------------------------------------
/src/hook/useLongPress/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | useLongPress
4 |
5 |
6 |
7 |
8 |
9 | A React hook that detects long clicks/taps.
10 |
11 | [](https://github.com/mimshins/utilityjs/blob/main/LICENSE)
12 | [](https://www.npmjs.com/package/@utilityjs/use-long-press)
13 | [](https://www.npmjs.com/package/@utilityjs/use-long-press)
14 | [](https://www.npmjs.com/package/@utilityjs/use-long-press)
15 |
16 | ```bash
17 | npm i @utilityjs/use-long-press | yarn add @utilityjs/use-long-press
18 | ```
19 |
20 |
21 |
22 |
23 |
24 | ## Usage
25 |
26 | ```tsx
27 | const App: React.FC = () => {
28 | const { registerNode } = useLongPress(
29 | () => {
30 | console.log("long pressed");
31 | },
32 | { preventLongPressOnMove: true, preventContextMenuOnLongPress: true }
33 | );
34 |
35 | return (
36 |
42 | );
43 | };
44 | ```
45 |
46 | ## API
47 |
48 | ### `useLongPress(callback, options?)`
49 |
50 | ```ts
51 | interface Options {
52 | pressDelay?: number;
53 | moveThreshold?: number;
54 | preventContextMenuOnLongPress?: boolean;
55 | preventLongPressOnMove?: boolean;
56 | }
57 |
58 | interface HookReturn {
59 | registerNode: (node: T | null) => void;
60 | }
61 |
62 | declare const useLongPress: (callback: () => void, options?: Options) => HookReturn;
63 |
64 | ```
65 |
66 | #### `callback`
67 |
68 | The callback is called when the long press happened.
69 |
70 | #### `options.pressDelay` - default: 500
71 |
72 | The time (in miliseconds) user need to hold click or tap before long press callback is triggered.
73 |
74 | #### `options.moveThreshold` - default: 25
75 |
76 | The move tolerance in pixels.
77 |
78 | #### `options.preventContextMenuOnLongPress` - default: false
79 |
80 | Determines whether default context menu should be cancelled when long press happened.
81 |
82 | #### `options.preventLongPressOnMove` - default: false
83 |
84 | Determines whether long press should be cancelled when detected movement while pressing.
--------------------------------------------------------------------------------
/src/hook/useLongPress/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./useLongPress";
2 |
--------------------------------------------------------------------------------
/src/hook/useLongPress/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@utilityjs/use-long-press",
3 | "version": "1.0.2",
4 | "description": "A React hook that detects long clicks/taps.",
5 | "sideEffects": false,
6 | "module": "./esm/index.js",
7 | "main": "./index.js",
8 | "types": "./index.d.ts",
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:mimshins/utilityjs.git",
13 | "directory": "src/hook/useLongPress"
14 | },
15 | "homepage": "https://github.com/mimshins/utilityjs/tree/main/src/hook/useLongPress",
16 | "peerDependencies": {
17 | "react": ">=16.8"
18 | },
19 | "dependencies": {
20 | "@utilityjs/use-get-latest": "^1.0.2",
21 | "@utilityjs/use-register-node-ref": "^1.1.0"
22 | },
23 | "keywords": [
24 | "javascript",
25 | "typescript",
26 | "react",
27 | "react hook",
28 | "long press",
29 | "long touch",
30 | "long tap",
31 | "use long press",
32 | "use long touch",
33 | "use long tap"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/src/hook/useLongPress/useLongPress.ts:
--------------------------------------------------------------------------------
1 | import useGetLatest from "@utilityjs/use-get-latest";
2 | import useRegisterNodeRef from "@utilityjs/use-register-node-ref";
3 | import * as React from "react";
4 |
5 | type TargetEvent = MouseEvent | TouchEvent;
6 |
7 | type Coordinates = { x: number; y: number };
8 |
9 | interface Options {
10 | pressDelay?: number;
11 | moveThreshold?: number;
12 | preventContextMenuOnLongPress?: boolean;
13 | preventLongPressOnMove?: boolean;
14 | }
15 |
16 | interface HookReturn {
17 | registerNode: (node: T | null) => void;
18 | }
19 |
20 | const isTouch = (event: Event): event is TouchEvent =>
21 | TouchEvent ? event instanceof TouchEvent : "touches" in event;
22 |
23 | const isMouse = (event: Event): event is MouseEvent =>
24 | event instanceof MouseEvent;
25 |
26 | const calcPosition = (event: TargetEvent): Coordinates | null => {
27 | return isTouch(event)
28 | ? { x: event.touches[0].pageX, y: event.touches[0].pageY }
29 | : isMouse(event)
30 | ? { x: event.pageX, y: event.pageY }
31 | : null;
32 | };
33 |
34 | const useLongPress = (
35 | callback: () => void,
36 | options: Options = {}
37 | ): HookReturn => {
38 | const {
39 | pressDelay = 500,
40 | moveThreshold = 25,
41 | preventContextMenuOnLongPress = false,
42 | preventLongPressOnMove = false
43 | } = options;
44 |
45 | const callbackRef = useGetLatest(callback);
46 |
47 | const timeoutRef = React.useRef>();
48 | const startPositionsRef = React.useRef(null);
49 |
50 | const isPressedRef = React.useRef(false);
51 |
52 | const startLongPress = (event: TargetEvent) => {
53 | // Ignore events other than mouse and touch
54 | if (!isMouse(event) && !isTouch(event)) return;
55 |
56 | if (isPressedRef.current) return;
57 |
58 | // Main button pressed
59 | isPressedRef.current = isMouse(event) ? event.button === 0 : true;
60 | startPositionsRef.current = calcPosition(event);
61 |
62 | timeoutRef.current = setTimeout(
63 | () => void callbackRef.current?.(),
64 | pressDelay
65 | );
66 | };
67 |
68 | const stopLongPress = (event: TargetEvent) => {
69 | // Ignore events other than mouse and touch
70 | if (!isMouse(event) && !isTouch(event)) return;
71 |
72 | isPressedRef.current = false;
73 | startPositionsRef.current = null;
74 |
75 | if (timeoutRef.current) clearTimeout(timeoutRef.current);
76 | };
77 |
78 | const preventLongPress = (event: TargetEvent) => {
79 | if (!preventLongPressOnMove) return;
80 |
81 | // Ignore events other than mouse and touch
82 | if (!isMouse(event) && !isTouch(event)) return;
83 |
84 | if (!startPositionsRef.current) return;
85 |
86 | const position = calcPosition(event);
87 | const initialPosition = startPositionsRef.current;
88 |
89 | if (!position) return;
90 |
91 | const dx = Math.abs(position.x - initialPosition.x);
92 | const dy = Math.abs(position.y - initialPosition.y);
93 |
94 | if (dx > moveThreshold || dy > moveThreshold) stopLongPress(event);
95 | };
96 |
97 | const preventContextMenu = (event: TargetEvent) => {
98 | if (!isPressedRef.current) return;
99 | if (preventContextMenuOnLongPress) event.preventDefault?.();
100 | };
101 |
102 | const handleEvents = (node: HTMLElement, unsubscribe = false) => {
103 | node.oncontextmenu = unsubscribe ? null : preventContextMenu;
104 |
105 | const fn = unsubscribe
106 | ? node.removeEventListener.bind(node)
107 | : node.addEventListener.bind(node);
108 |
109 | fn("mousedown", startLongPress);
110 | fn("touchstart", startLongPress);
111 |
112 | fn("mousemove", preventLongPress);
113 | fn("touchmove", preventLongPress);
114 |
115 | fn("mouseup", stopLongPress);
116 | fn("mouseleave", stopLongPress);
117 | fn("touchend", stopLongPress);
118 | };
119 |
120 | const subscriber = (node: T) => {
121 | handleEvents(node);
122 | return () => void handleEvents(node, true);
123 | };
124 |
125 | const registerNode = useRegisterNodeRef(subscriber);
126 |
127 | React.useEffect(() => {
128 | return () => {
129 | if (timeoutRef.current) clearTimeout(timeoutRef.current);
130 | };
131 | }, []);
132 |
133 | return { registerNode };
134 | };
135 |
136 | export default useLongPress;
137 |
--------------------------------------------------------------------------------
/src/hook/useMediaQuery/README.md:
--------------------------------------------------------------------------------
1 |