-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port use-listener package to aacc (#53)
- Loading branch information
1 parent
a011584
commit c6c61b0
Showing
13 changed files
with
475 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'use-listener': minor | ||
--- | ||
|
||
Port to aacc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"presets": [ | ||
[ | ||
"@aacc/babel-preset", | ||
{ | ||
"typescript": true, | ||
"react": true | ||
} | ||
] | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* @type {import('eslint').Linter.Config} | ||
*/ | ||
module.exports = { | ||
root: true, | ||
extends: ['@aacc/eslint-config/typescript'], | ||
parserOptions: { | ||
tsconfigRootDir: __dirname, | ||
project: 'tsconfig.eslint.json', | ||
}, | ||
ignorePatterns: ['node_modules', 'dist'], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# use-listener | ||
|
||
The **most** type safe React hook for adding and removing event listeners. | ||
|
||
Supports the following event targets: | ||
|
||
- `Window` | ||
- `Document` | ||
- `HTMLElement` | ||
- `React.RefObject<HTMLElement>` | ||
|
||
## Installation | ||
|
||
```sh | ||
npm install use-listener | ||
``` | ||
|
||
## Usage | ||
|
||
### With default target (`Window`) | ||
|
||
```tsx | ||
import { useListener } from 'use-listener' | ||
|
||
function App() { | ||
useListener('click', (e) => console.log(e)) | ||
|
||
return <div>Use listener</div> | ||
} | ||
``` | ||
|
||
> The `click` event and default `Window` target are used to resolve the event | ||
> object type (`MouseEvent`). | ||
### With explicit target (`Document`) | ||
|
||
```tsx | ||
import { useListener } from 'use-listener' | ||
|
||
function App() { | ||
useListener('copy', (e) => console.log(e), window.document) | ||
|
||
return <div>Use listener</div> | ||
} | ||
``` | ||
|
||
> Note: The `copy` event and explicit `Document` target are used to resolve the | ||
> event object type (`ClipboardEvent`). | ||
### With React `ref` (`HTMLDivElement`) | ||
|
||
```tsx | ||
import { useListener } from 'use-listener' | ||
|
||
function App() { | ||
const ref = React.useRef<HTMLDivElement>(null) | ||
|
||
useListener('mouseover', (e) => console.log(e), ref) | ||
|
||
return <div ref={ref}>Use listener</div> | ||
} | ||
``` | ||
|
||
> Note: The `mouseover` event and inferred `HTMLDivElement` target are used to | ||
> resolve the event object type (`MouseEvent`) | ||
### Dynamic React `ref` (`HTMLParagraphElement`) | ||
|
||
```jsx | ||
import React from 'react' | ||
import { useListener } from 'use-listener' | ||
|
||
function App() { | ||
const [element, ref] = useElementRef<HTMLParagraphElement>() | ||
const [on, setOn] = React.useState(false) | ||
|
||
useListener('mouseover', (e) => console.log(e), element) | ||
|
||
return ( | ||
<div> | ||
{on && <p ref={ref}>Hover me</p>} | ||
<button onClick={() => setOn((prevOn) => !prevOn)}> | ||
Toggle element | ||
</button> | ||
</div> | ||
) | ||
} | ||
|
||
function useElementRef<T extends HTMLElement>() { | ||
const [node, setNode] = React.useState<T | null>(null) | ||
|
||
const ref: React.RefCallback<T> = React.useCallback((node) => { | ||
if (node !== null) setNode(node) | ||
}, []) | ||
|
||
return [node, ref] as const | ||
} | ||
``` | ||
|
||
> Note: The `mouseover` event and dynamically set `HTMLParagraphElement` target | ||
> are used to resolve the event object type (`MouseEvent`) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
{ | ||
"name": "use-listener", | ||
"version": "0.1.4", | ||
"description": "", | ||
"author": "Aaron Casanova <aaronccasanova@gmail.com>", | ||
"license": "MIT", | ||
"main": "dist/cjs/index.js", | ||
"module": "dist/esm/index.mjs", | ||
"browser": "dist/browser/index.js", | ||
"types": "dist/types/index.d.ts", | ||
"exports": { | ||
".": { | ||
"types": "./dist/types/index.d.ts", | ||
"import": "./dist/esm/index.mjs", | ||
"require": "./dist/cjs/index.js" | ||
} | ||
}, | ||
"scripts": { | ||
"dev": "npm-run-all --parallel 'build:* -- --watch'", | ||
"build": "npm-run-all --parallel build:*", | ||
"build:js": "rollup -c", | ||
"build:types": "tsc --emitDeclarationOnly", | ||
"type-check": "tsc --noEmit", | ||
"type-check:watch": "npm run type-check -- --watch", | ||
"lint": "TIMING=1 eslint . --ext .js,.ts --cache", | ||
"prepublishOnly": "npm run build" | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"peerDependencies": { | ||
"react": "^17.0.0" | ||
}, | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"@aacc/babel-preset": "*", | ||
"@aacc/browserslist-config": "*", | ||
"@aacc/eslint-config": "*", | ||
"@aacc/tsconfigs": "*", | ||
"@rollup/plugin-babel": "^5.3.1", | ||
"@rollup/plugin-commonjs": "^21.1.0", | ||
"@rollup/plugin-node-resolve": "^13.2.1", | ||
"@types/react": "^18.0.21", | ||
"react": "^17.0.0", | ||
"rollup": "^2.70.2", | ||
"typescript": "^4.7.3" | ||
}, | ||
"browserslist": [ | ||
"extends @aacc/browserslist-config" | ||
], | ||
"publishConfig": { | ||
"access": "public", | ||
"@aacc:registry": "https://registry.npmjs.org" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/aaronccasanova/aacc.git", | ||
"directory": "hooks/use-listener" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/aaronccasanova/aacc/issues" | ||
}, | ||
"homepage": "https://github.com/aaronccasanova/aacc/blob/main/hooks/use-listener/README.md" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import path from 'path' | ||
|
||
import nodeResolve from '@rollup/plugin-node-resolve' | ||
import commonjs from '@rollup/plugin-commonjs' | ||
import babel from '@rollup/plugin-babel' | ||
|
||
import pkg from './package.json' | ||
|
||
const name = 'useListener' | ||
|
||
const extensions = ['.js', '.jsx', '.ts', '.tsx'] | ||
|
||
/** | ||
* @type {import('rollup').RollupOptions} | ||
*/ | ||
export default { | ||
input: 'src/index.ts', | ||
output: [ | ||
{ | ||
format: /** @type {const} */ ('cjs'), | ||
entryFileNames: '[name][assetExtname].js', | ||
dir: path.dirname(pkg.main), | ||
preserveModules: true, | ||
}, | ||
{ | ||
format: /** @type {const} */ ('es'), | ||
entryFileNames: '[name][assetExtname].mjs', | ||
dir: path.dirname(pkg.module), | ||
preserveModules: true, | ||
}, | ||
{ | ||
format: /** @type {const} */ ('iife'), | ||
file: pkg.browser, | ||
name, | ||
|
||
// https://rollupjs.org/guide/en/#outputglobals | ||
// globals: {}, | ||
}, | ||
], | ||
plugins: [ | ||
// Allows node_modules resolution | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call | ||
nodeResolve({ extensions }), | ||
// Allow bundling cjs modules. Rollup doesn't understand cjs | ||
commonjs(), | ||
// Compile TypeScript/JavaScript files | ||
babel({ | ||
extensions, | ||
babelHelpers: 'bundled', | ||
include: ['src/**/*'], | ||
}), | ||
], | ||
external: [ | ||
...Object.keys(pkg.dependencies ?? {}), | ||
...Object.keys(pkg.peerDependencies ?? {}), | ||
], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './use-listener' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import React from 'react' | ||
|
||
function isServer() { | ||
const isDOM = | ||
typeof window !== 'undefined' && | ||
window.document && | ||
window.document.documentElement | ||
|
||
return !isDOM | ||
} | ||
|
||
export const useIsomorphicLayoutEffect = isServer() | ||
? React.useEffect | ||
: React.useLayoutEffect |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import React from 'react' | ||
|
||
import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect' | ||
|
||
/** | ||
* Acceptable target elements for `useListener`. | ||
*/ | ||
type UseListenerTarget = | ||
| Window | ||
| Document | ||
| HTMLElement | ||
| React.RefObject<HTMLElement> | ||
|
||
/** | ||
* Extracts the target element from a React `RefObject` or returns the input element. | ||
*/ | ||
type ExtractTargetElement<Target> = Target extends React.RefObject< | ||
infer Element | ||
> | ||
? Element | ||
: Target | ||
|
||
/** | ||
* Extracts a (lib.dom.ts) EventMap for a given target element. | ||
*/ | ||
type ExtractEventMap<Target> = ExtractTargetElement<Target> extends Window | ||
? WindowEventMap | ||
: ExtractTargetElement<Target> extends Document | ||
? DocumentEventMap | ||
: HTMLElementEventMap | ||
|
||
/** | ||
* Extracts all event names for a given target element. | ||
*/ | ||
type ExtractEventName<Target> = keyof ExtractEventMap< | ||
ExtractTargetElement<Target> | ||
> | ||
|
||
/** | ||
* Extracts the `event` object for a given event type. | ||
*/ | ||
type ExtractEvent< | ||
Target, | ||
EventName extends ExtractEventName<Target>, | ||
> = ExtractEventMap<ExtractTargetElement<Target>>[EventName] | ||
|
||
/** | ||
* React hook encapsulating the boilerplate logic for adding and removing event listeners. | ||
*/ | ||
export function useListener< | ||
TargetEventName extends ExtractEventName<Target>, | ||
TargetEvent extends ExtractEvent<Target, TargetEventName>, | ||
Target extends UseListenerTarget = Window, | ||
>( | ||
eventName: TargetEventName, | ||
handler: (event: TargetEvent) => void, | ||
target?: undefined | null | Target, | ||
options?: undefined | AddEventListenerOptions, | ||
): void { | ||
const handlerRef = React.useRef(handler) | ||
const optionsRef = React.useRef(options) | ||
|
||
useIsomorphicLayoutEffect(() => { | ||
handlerRef.current = handler | ||
}, [handler]) | ||
|
||
useIsomorphicLayoutEffect(() => { | ||
optionsRef.current = options | ||
}, [options]) | ||
|
||
React.useEffect(() => { | ||
if (!(typeof eventName === 'string' && target !== null)) return | ||
|
||
let targetElement: Exclude<UseListenerTarget, React.RefObject<HTMLElement>> | ||
|
||
if (typeof target === 'undefined') { | ||
targetElement = window | ||
} else if ('current' in target) { | ||
if (target.current === null) return | ||
|
||
targetElement = target.current | ||
} else { | ||
targetElement = target | ||
} | ||
|
||
const eventOptions = optionsRef.current | ||
|
||
const eventListener = (event: Event) => | ||
handlerRef.current(event as unknown as TargetEvent) | ||
|
||
targetElement.addEventListener(eventName, eventListener, eventOptions) | ||
|
||
// eslint-disable-next-line consistent-return | ||
return () => { | ||
targetElement.removeEventListener(eventName, eventListener, eventOptions) | ||
} | ||
}, [eventName, target]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
// https://typescript-eslint.io/docs/linting/monorepo#one-root-tsconfigjson | ||
"extends": "./tsconfig.json", | ||
"compilerOptions": { | ||
// Ensures this config is not used for a build | ||
"noEmit": true | ||
}, | ||
"include": [ | ||
// Paths to lint | ||
"src", | ||
".eslintrc.js", | ||
"rollup.config.js" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"extends": "@aacc/tsconfigs/node-library.json", | ||
"compilerOptions": { | ||
"outDir": "dist", | ||
"declarationDir": "dist/types", | ||
"lib": ["DOM"] | ||
}, | ||
"include": ["src"], | ||
"exclude": ["node_modules", "dist"] | ||
} |
Oops, something went wrong.
c6c61b0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
aacc-next-ts – ./recipes/next-ts
aacc-next-ts-aaronccasanova-gmailcom.vercel.app
aacc-next-ts.vercel.app
aacc-next-ts-git-main-aaronccasanova-gmailcom.vercel.app
c6c61b0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
aacc-scales – ./apps/scales
aacc-scales.vercel.app
aacc-scales-aaronccasanova-gmailcom.vercel.app
aacc-scales-git-main-aaronccasanova-gmailcom.vercel.app
c6c61b0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
aacc-next-ts-styled-comps – ./recipes/next-ts-styled-comps
aacc-next-ts-styled-comps.vercel.app
aacc-next-ts-styled-comps-git-main-aaronccasanova-gmailcom.vercel.app
aacc-next-ts-styled-comps-aaronccasanova-gmailcom.vercel.app