use-auto-width-input 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -11
- package/dist/index.cjs +40 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +41 -17
- package/dist/index.js.map +1 -1
- package/package.json +23 -7
package/README.md
CHANGED
|
@@ -17,6 +17,31 @@ bun add use-auto-width-input
|
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
|
20
|
+
There are two simple overloads for this hook: one that receives only an optional parameter for configuration, and the other that receives the actual reference of the input **and** the the configuration option.
|
|
21
|
+
|
|
22
|
+
#### `useAutoWidthInput(options?: AutoWidthInput)`
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { useRef } from 'react';
|
|
26
|
+
import { useAutoWidthInput } from 'use-auto-width-input';
|
|
27
|
+
|
|
28
|
+
function App() {
|
|
29
|
+
const { callbackRef } = useAutoWidthInput();
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<input
|
|
33
|
+
ref={callbackRef}
|
|
34
|
+
type="text"
|
|
35
|
+
placeholder="Type something..."
|
|
36
|
+
style={{ minWidth: "50px", maxWidth: "480px" }}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
#### `useAutoWidthInput(inputRef: RefObject<HTMLElement>, options?: AutoWidthInput)`
|
|
44
|
+
|
|
20
45
|
```tsx
|
|
21
46
|
import { useRef } from 'react';
|
|
22
47
|
import { useAutoWidthInput } from 'use-auto-width-input';
|
|
@@ -38,18 +63,22 @@ function App() {
|
|
|
38
63
|
|
|
39
64
|
## API
|
|
40
65
|
|
|
41
|
-
### `useAutoWidthInput(inputRef
|
|
66
|
+
### `useAutoWidthInput(inputRef?, options?)`
|
|
42
67
|
|
|
43
68
|
#### Parameters
|
|
44
69
|
|
|
45
|
-
- `inputRef
|
|
70
|
+
- `inputRef` (optional): A React ref object for the input element
|
|
46
71
|
- `options` (optional):
|
|
47
|
-
- `minWidth?: string` - Minimum width for the input
|
|
72
|
+
- `minWidth?: string` - Minimum width for the input (can be overwritten via CSS using `!important`)
|
|
73
|
+
- `maxWidth?: string` - Maximum width for the input (can be overwritten via CSS using `!important`)
|
|
74
|
+
- `ghostElement?: object` - Object with the configuration for the ghost paragraph element
|
|
75
|
+
- `className?: string` - Classes to be added to the ghost element
|
|
76
|
+
- `id?: string` - ID to be given to the ghost element
|
|
77
|
+
- `styles?: Partial<CSSStyleDeclaration>` - Styles to be applied to the ghost element (can be overwritten via CSS using `!important`)
|
|
48
78
|
|
|
49
79
|
#### Returns
|
|
50
80
|
|
|
51
81
|
- `callbackRef`: Callback ref to attach to the input element
|
|
52
|
-
- `width`: Current width value
|
|
53
82
|
- `ref`: The original input ref
|
|
54
83
|
|
|
55
84
|
## How It Works
|
|
@@ -61,13 +90,31 @@ The hook creates an invisible "ghost" element that mirrors the input's text and
|
|
|
61
90
|
You can easily control the minimum and maximum width by applying standard CSS styles directly to your input element:
|
|
62
91
|
|
|
63
92
|
```tsx
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
93
|
+
...
|
|
94
|
+
const { callbackRef } = useAutoWidthInput();
|
|
95
|
+
|
|
96
|
+
return
|
|
97
|
+
<input
|
|
98
|
+
ref={callbackRef}
|
|
99
|
+
style={{
|
|
100
|
+
minWidth: "100px", // Won't shrink below this
|
|
101
|
+
maxWidth: "500px" // Won't grow beyond this
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
...
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Or via the `AutoWidthInputOptions` parameter:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
...
|
|
111
|
+
const { callbackRef } = useAutoWidthInput({
|
|
112
|
+
minWidth: '100px',
|
|
113
|
+
maxWidth: '250px'
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return <input ref={callbackRef} />;
|
|
117
|
+
...
|
|
71
118
|
```
|
|
72
119
|
|
|
73
120
|
This gives you full control over the input's width boundaries without needing additional hook configuration.
|
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,15 @@ var applyFontStyles = (styles, target) => {
|
|
|
34
34
|
target.style.letterSpacing = styles.letterSpacing;
|
|
35
35
|
target.style.fontWeight = styles.fontWeight;
|
|
36
36
|
};
|
|
37
|
+
var applyStyles = (styles, target) => {
|
|
38
|
+
Object.keys(styles).forEach((styleAttr) => {
|
|
39
|
+
const key = styleAttr;
|
|
40
|
+
const value = styles[key];
|
|
41
|
+
if (typeof value === "string" && typeof key === "string") {
|
|
42
|
+
target.style[key] = value;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
};
|
|
37
46
|
var createGhostElement = (options) => {
|
|
38
47
|
const tempElement = document.createElement("p");
|
|
39
48
|
if (options?.minWidth) {
|
|
@@ -57,14 +66,20 @@ var createGhostElement = (options) => {
|
|
|
57
66
|
return document.body.appendChild(tempElement);
|
|
58
67
|
};
|
|
59
68
|
var syncWidth = (inputRef, ghostElement) => {
|
|
60
|
-
if (inputRef.current)
|
|
69
|
+
if (inputRef.current) {
|
|
61
70
|
inputRef.current.style.width = `${ghostElement.current?.getBoundingClientRect()?.width ?? 0}px`;
|
|
71
|
+
return inputRef.current.style.width;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
62
74
|
};
|
|
63
75
|
|
|
64
76
|
// src/use-auto-width-input.ts
|
|
65
|
-
function useAutoWidthInput(
|
|
66
|
-
const
|
|
77
|
+
function useAutoWidthInput(inputRefOrOptions, opts) {
|
|
78
|
+
const isRefObject = typeof inputRefOrOptions === "object" && "current" in inputRefOrOptions;
|
|
67
79
|
const ghostElement = (0, import_react.useRef)(null);
|
|
80
|
+
const internalInpuRef = (0, import_react.useRef)(null);
|
|
81
|
+
const inputRef = isRefObject ? inputRefOrOptions : internalInpuRef;
|
|
82
|
+
const options = isRefObject ? opts : inputRefOrOptions;
|
|
68
83
|
const input = inputRef.current;
|
|
69
84
|
const handleInput = (event) => {
|
|
70
85
|
const target = event.target;
|
|
@@ -78,8 +93,28 @@ function useAutoWidthInput(inputRef, options) {
|
|
|
78
93
|
ghostElement.current?.remove();
|
|
79
94
|
ghostElement.current = null;
|
|
80
95
|
};
|
|
96
|
+
const mountBehaviour = () => {
|
|
97
|
+
if (!inputRef.current) return;
|
|
98
|
+
if (ghostElement.current) return;
|
|
99
|
+
const styles = window.getComputedStyle(inputRef.current);
|
|
100
|
+
ghostElement.current = createGhostElement(options);
|
|
101
|
+
if (options?.ghostElement?.className)
|
|
102
|
+
ghostElement.current.classList.add(options?.ghostElement?.className);
|
|
103
|
+
ghostElement.current.id = options?.ghostElement?.id || "";
|
|
104
|
+
ghostElement.current.innerText = inputRef.current.value ?? "";
|
|
105
|
+
if (options?.minWidth) inputRef.current.style.minWidth = options?.minWidth;
|
|
106
|
+
if (options?.maxWidth) inputRef.current.style.maxWidth = options?.maxWidth;
|
|
107
|
+
applyFontStyles(styles, ghostElement.current);
|
|
108
|
+
if (options?.ghostElement?.styles)
|
|
109
|
+
applyStyles(options?.ghostElement?.styles, ghostElement.current);
|
|
110
|
+
syncWidth(inputRef, ghostElement);
|
|
111
|
+
inputRef.current.addEventListener("input", handleInput);
|
|
112
|
+
};
|
|
81
113
|
(0, import_react.useEffect)(
|
|
82
|
-
() =>
|
|
114
|
+
() => {
|
|
115
|
+
mountBehaviour();
|
|
116
|
+
return () => destroy();
|
|
117
|
+
},
|
|
83
118
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
84
119
|
[]
|
|
85
120
|
);
|
|
@@ -88,19 +123,13 @@ function useAutoWidthInput(inputRef, options) {
|
|
|
88
123
|
inputRef.current = element;
|
|
89
124
|
if (!element) destroy();
|
|
90
125
|
else if (element && inputRef.current) {
|
|
91
|
-
|
|
92
|
-
ghostElement.current = createGhostElement(options);
|
|
93
|
-
ghostElement.current.innerText = inputRef.current.value;
|
|
94
|
-
applyFontStyles(styles, ghostElement.current);
|
|
95
|
-
syncWidth(inputRef, ghostElement);
|
|
96
|
-
inputRef.current.addEventListener("input", handleInput);
|
|
126
|
+
mountBehaviour();
|
|
97
127
|
}
|
|
98
128
|
},
|
|
99
129
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
100
130
|
[inputRef]
|
|
101
131
|
);
|
|
102
132
|
return {
|
|
103
|
-
width,
|
|
104
133
|
callbackRef,
|
|
105
134
|
ref: inputRef
|
|
106
135
|
};
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/use-auto-width-input.ts","../src/helpers.ts"],"sourcesContent":["export * from \"./use-auto-width-input\";\n\nexport * from \"./types\";\n","import {
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/use-auto-width-input.ts","../src/helpers.ts"],"sourcesContent":["export * from \"./use-auto-width-input\";\n\nexport * from \"./types\";\n","import { useCallback, useEffect, useRef, type RefObject } from \"react\";\nimport type { AutWidthInputOptions, UseAutoWidthInputReturn } from \"./types\";\nimport {\n applyFontStyles,\n applyStyles,\n createGhostElement,\n syncWidth,\n} from \"./helpers\";\n\nexport function useAutoWidthInput(\n inputRef?: RefObject<HTMLInputElement | null>,\n options?: AutWidthInputOptions\n): UseAutoWidthInputReturn;\n\nexport function useAutoWidthInput(\n options?: AutWidthInputOptions\n): UseAutoWidthInputReturn;\n\nexport function useAutoWidthInput(\n inputRefOrOptions?: RefObject<HTMLInputElement | null> | AutWidthInputOptions,\n opts?: AutWidthInputOptions\n): UseAutoWidthInputReturn {\n // NOTE: Removed the width state because it inserts a race condition in chrome with the onChange event and setText\n // const [width, setWidth] = useState(options?.minWidth ?? 0);\n const isRefObject =\n typeof inputRefOrOptions === \"object\" && \"current\" in inputRefOrOptions;\n\n const ghostElement = useRef<HTMLElement>(null);\n const internalInpuRef = useRef<HTMLInputElement>(null);\n\n const inputRef = isRefObject ? inputRefOrOptions : internalInpuRef;\n const options: AutWidthInputOptions | undefined = isRefObject\n ? opts\n : inputRefOrOptions;\n\n const input = inputRef.current;\n\n const handleInput = (event: Event) => {\n const target = event.target as HTMLInputElement;\n\n if (ghostElement.current) {\n ghostElement.current.innerText = target.value;\n\n syncWidth(inputRef, ghostElement);\n }\n };\n\n const destroy = () => {\n input?.removeEventListener(\"input\", handleInput);\n ghostElement.current?.remove();\n ghostElement.current = null;\n };\n\n const mountBehaviour = () => {\n if (!inputRef.current) return;\n\n // Doesnt make sense to mount an element if the element already exist\n if (ghostElement.current) return;\n\n const styles = window.getComputedStyle(inputRef.current);\n ghostElement.current = createGhostElement(options);\n\n if (options?.ghostElement?.className)\n ghostElement.current.classList.add(options?.ghostElement?.className);\n\n ghostElement.current.id = options?.ghostElement?.id || \"\";\n // Ensure we capture the current value at mount time\n ghostElement.current.innerText = inputRef.current.value ?? \"\";\n\n if (options?.minWidth) inputRef.current.style.minWidth = options?.minWidth;\n if (options?.maxWidth) inputRef.current.style.maxWidth = options?.maxWidth;\n\n applyFontStyles(styles, ghostElement.current);\n\n // Here I allow you to mess up the styles of the hidden element\n if (options?.ghostElement?.styles)\n // NOTE: this will overwrite anything from the previus `applyFontStyles`\n applyStyles(options?.ghostElement?.styles, ghostElement.current);\n\n syncWidth(inputRef, ghostElement);\n\n inputRef.current.addEventListener(\"input\", handleInput);\n };\n\n useEffect(\n () => {\n mountBehaviour();\n return () => destroy();\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n []\n );\n\n const callbackRef = useCallback(\n (element: HTMLInputElement | null) => {\n inputRef.current = element;\n\n if (!element) destroy();\n else if (element && inputRef.current) {\n mountBehaviour();\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [inputRef]\n );\n\n return {\n callbackRef,\n ref: inputRef,\n };\n}\n","import type { RefObject } from \"react\";\nimport type { AutWidthInputOptions } from \"./types\";\n\nexport const applyFontStyles = (\n styles: CSSStyleDeclaration,\n target: HTMLElement\n) => {\n target.style.fontFamily = styles.fontFamily;\n target.style.fontSize = styles.fontSize;\n target.style.letterSpacing = styles.letterSpacing;\n target.style.fontWeight = styles.fontWeight;\n};\n\nexport const applyStyles = (\n styles: Partial<CSSStyleDeclaration>,\n target: HTMLElement\n) => {\n Object.keys(styles).forEach((styleAttr: string) => {\n const key = styleAttr as keyof CSSStyleDeclaration;\n\n const value = styles[key];\n\n if (typeof value === \"string\" && typeof key === \"string\") {\n // target.style.setProperty(key, value);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target.style as any)[key] = value;\n }\n });\n};\n\nexport const createGhostElement = (options?: AutWidthInputOptions) => {\n const tempElement = document.createElement(\"p\");\n\n if (options?.minWidth) {\n tempElement.style.minWidth = options?.minWidth;\n }\n\n Object.assign(tempElement.style, {\n position: \"absolute\",\n whiteSpace: \"pre\",\n top: \"0\",\n left: \"0\",\n visibility: \"hidden\",\n height: \"0\",\n overflow: \"hidden\",\n pointerEvents: \"none\",\n zIndex: -1000,\n margin: \"0\", // Reset margin to avoid layout interference\n });\n\n tempElement.innerText = \"\";\n tempElement.setAttribute(\"class\", \"ghost-paragraph-element\");\n\n return document.body.appendChild(tempElement);\n};\n\nexport const syncWidth = (\n inputRef: RefObject<HTMLInputElement | null>,\n ghostElement: RefObject<HTMLElement | null>\n) => {\n if (inputRef.current) {\n inputRef.current.style.width = `${\n ghostElement.current?.getBoundingClientRect()?.width ?? 0\n }px`;\n return inputRef.current.style.width;\n }\n\n return null;\n};\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -2,12 +2,19 @@ import { RefObject } from 'react';
|
|
|
2
2
|
|
|
3
3
|
type AutWidthInputOptions = {
|
|
4
4
|
minWidth?: string;
|
|
5
|
+
maxWidth?: string;
|
|
6
|
+
ghostElement?: {
|
|
7
|
+
className?: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
styles?: Partial<CSSStyleDeclaration>;
|
|
10
|
+
};
|
|
5
11
|
};
|
|
6
|
-
|
|
7
|
-
declare function useAutoWidthInput(inputRef: RefObject<HTMLInputElement | null>, options?: AutWidthInputOptions): {
|
|
8
|
-
width: string | number;
|
|
12
|
+
type UseAutoWidthInputReturn = {
|
|
9
13
|
callbackRef: (element: HTMLInputElement | null) => void;
|
|
10
14
|
ref: RefObject<HTMLInputElement | null>;
|
|
11
15
|
};
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
declare function useAutoWidthInput(inputRef?: RefObject<HTMLInputElement | null>, options?: AutWidthInputOptions): UseAutoWidthInputReturn;
|
|
18
|
+
declare function useAutoWidthInput(options?: AutWidthInputOptions): UseAutoWidthInputReturn;
|
|
19
|
+
|
|
20
|
+
export { type AutWidthInputOptions, type UseAutoWidthInputReturn, useAutoWidthInput };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,12 +2,19 @@ import { RefObject } from 'react';
|
|
|
2
2
|
|
|
3
3
|
type AutWidthInputOptions = {
|
|
4
4
|
minWidth?: string;
|
|
5
|
+
maxWidth?: string;
|
|
6
|
+
ghostElement?: {
|
|
7
|
+
className?: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
styles?: Partial<CSSStyleDeclaration>;
|
|
10
|
+
};
|
|
5
11
|
};
|
|
6
|
-
|
|
7
|
-
declare function useAutoWidthInput(inputRef: RefObject<HTMLInputElement | null>, options?: AutWidthInputOptions): {
|
|
8
|
-
width: string | number;
|
|
12
|
+
type UseAutoWidthInputReturn = {
|
|
9
13
|
callbackRef: (element: HTMLInputElement | null) => void;
|
|
10
14
|
ref: RefObject<HTMLInputElement | null>;
|
|
11
15
|
};
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
declare function useAutoWidthInput(inputRef?: RefObject<HTMLInputElement | null>, options?: AutWidthInputOptions): UseAutoWidthInputReturn;
|
|
18
|
+
declare function useAutoWidthInput(options?: AutWidthInputOptions): UseAutoWidthInputReturn;
|
|
19
|
+
|
|
20
|
+
export { type AutWidthInputOptions, type UseAutoWidthInputReturn, useAutoWidthInput };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
// src/use-auto-width-input.ts
|
|
2
|
-
import {
|
|
3
|
-
useCallback,
|
|
4
|
-
useEffect,
|
|
5
|
-
useRef,
|
|
6
|
-
useState
|
|
7
|
-
} from "react";
|
|
2
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
8
3
|
|
|
9
4
|
// src/helpers.ts
|
|
10
5
|
var applyFontStyles = (styles, target) => {
|
|
@@ -13,6 +8,15 @@ var applyFontStyles = (styles, target) => {
|
|
|
13
8
|
target.style.letterSpacing = styles.letterSpacing;
|
|
14
9
|
target.style.fontWeight = styles.fontWeight;
|
|
15
10
|
};
|
|
11
|
+
var applyStyles = (styles, target) => {
|
|
12
|
+
Object.keys(styles).forEach((styleAttr) => {
|
|
13
|
+
const key = styleAttr;
|
|
14
|
+
const value = styles[key];
|
|
15
|
+
if (typeof value === "string" && typeof key === "string") {
|
|
16
|
+
target.style[key] = value;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
};
|
|
16
20
|
var createGhostElement = (options) => {
|
|
17
21
|
const tempElement = document.createElement("p");
|
|
18
22
|
if (options?.minWidth) {
|
|
@@ -36,14 +40,20 @@ var createGhostElement = (options) => {
|
|
|
36
40
|
return document.body.appendChild(tempElement);
|
|
37
41
|
};
|
|
38
42
|
var syncWidth = (inputRef, ghostElement) => {
|
|
39
|
-
if (inputRef.current)
|
|
43
|
+
if (inputRef.current) {
|
|
40
44
|
inputRef.current.style.width = `${ghostElement.current?.getBoundingClientRect()?.width ?? 0}px`;
|
|
45
|
+
return inputRef.current.style.width;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
41
48
|
};
|
|
42
49
|
|
|
43
50
|
// src/use-auto-width-input.ts
|
|
44
|
-
function useAutoWidthInput(
|
|
45
|
-
const
|
|
51
|
+
function useAutoWidthInput(inputRefOrOptions, opts) {
|
|
52
|
+
const isRefObject = typeof inputRefOrOptions === "object" && "current" in inputRefOrOptions;
|
|
46
53
|
const ghostElement = useRef(null);
|
|
54
|
+
const internalInpuRef = useRef(null);
|
|
55
|
+
const inputRef = isRefObject ? inputRefOrOptions : internalInpuRef;
|
|
56
|
+
const options = isRefObject ? opts : inputRefOrOptions;
|
|
47
57
|
const input = inputRef.current;
|
|
48
58
|
const handleInput = (event) => {
|
|
49
59
|
const target = event.target;
|
|
@@ -57,8 +67,28 @@ function useAutoWidthInput(inputRef, options) {
|
|
|
57
67
|
ghostElement.current?.remove();
|
|
58
68
|
ghostElement.current = null;
|
|
59
69
|
};
|
|
70
|
+
const mountBehaviour = () => {
|
|
71
|
+
if (!inputRef.current) return;
|
|
72
|
+
if (ghostElement.current) return;
|
|
73
|
+
const styles = window.getComputedStyle(inputRef.current);
|
|
74
|
+
ghostElement.current = createGhostElement(options);
|
|
75
|
+
if (options?.ghostElement?.className)
|
|
76
|
+
ghostElement.current.classList.add(options?.ghostElement?.className);
|
|
77
|
+
ghostElement.current.id = options?.ghostElement?.id || "";
|
|
78
|
+
ghostElement.current.innerText = inputRef.current.value ?? "";
|
|
79
|
+
if (options?.minWidth) inputRef.current.style.minWidth = options?.minWidth;
|
|
80
|
+
if (options?.maxWidth) inputRef.current.style.maxWidth = options?.maxWidth;
|
|
81
|
+
applyFontStyles(styles, ghostElement.current);
|
|
82
|
+
if (options?.ghostElement?.styles)
|
|
83
|
+
applyStyles(options?.ghostElement?.styles, ghostElement.current);
|
|
84
|
+
syncWidth(inputRef, ghostElement);
|
|
85
|
+
inputRef.current.addEventListener("input", handleInput);
|
|
86
|
+
};
|
|
60
87
|
useEffect(
|
|
61
|
-
() =>
|
|
88
|
+
() => {
|
|
89
|
+
mountBehaviour();
|
|
90
|
+
return () => destroy();
|
|
91
|
+
},
|
|
62
92
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
63
93
|
[]
|
|
64
94
|
);
|
|
@@ -67,19 +97,13 @@ function useAutoWidthInput(inputRef, options) {
|
|
|
67
97
|
inputRef.current = element;
|
|
68
98
|
if (!element) destroy();
|
|
69
99
|
else if (element && inputRef.current) {
|
|
70
|
-
|
|
71
|
-
ghostElement.current = createGhostElement(options);
|
|
72
|
-
ghostElement.current.innerText = inputRef.current.value;
|
|
73
|
-
applyFontStyles(styles, ghostElement.current);
|
|
74
|
-
syncWidth(inputRef, ghostElement);
|
|
75
|
-
inputRef.current.addEventListener("input", handleInput);
|
|
100
|
+
mountBehaviour();
|
|
76
101
|
}
|
|
77
102
|
},
|
|
78
103
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
79
104
|
[inputRef]
|
|
80
105
|
);
|
|
81
106
|
return {
|
|
82
|
-
width,
|
|
83
107
|
callbackRef,
|
|
84
108
|
ref: inputRef
|
|
85
109
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/use-auto-width-input.ts","../src/helpers.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../src/use-auto-width-input.ts","../src/helpers.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, type RefObject } from \"react\";\nimport type { AutWidthInputOptions, UseAutoWidthInputReturn } from \"./types\";\nimport {\n applyFontStyles,\n applyStyles,\n createGhostElement,\n syncWidth,\n} from \"./helpers\";\n\nexport function useAutoWidthInput(\n inputRef?: RefObject<HTMLInputElement | null>,\n options?: AutWidthInputOptions\n): UseAutoWidthInputReturn;\n\nexport function useAutoWidthInput(\n options?: AutWidthInputOptions\n): UseAutoWidthInputReturn;\n\nexport function useAutoWidthInput(\n inputRefOrOptions?: RefObject<HTMLInputElement | null> | AutWidthInputOptions,\n opts?: AutWidthInputOptions\n): UseAutoWidthInputReturn {\n // NOTE: Removed the width state because it inserts a race condition in chrome with the onChange event and setText\n // const [width, setWidth] = useState(options?.minWidth ?? 0);\n const isRefObject =\n typeof inputRefOrOptions === \"object\" && \"current\" in inputRefOrOptions;\n\n const ghostElement = useRef<HTMLElement>(null);\n const internalInpuRef = useRef<HTMLInputElement>(null);\n\n const inputRef = isRefObject ? inputRefOrOptions : internalInpuRef;\n const options: AutWidthInputOptions | undefined = isRefObject\n ? opts\n : inputRefOrOptions;\n\n const input = inputRef.current;\n\n const handleInput = (event: Event) => {\n const target = event.target as HTMLInputElement;\n\n if (ghostElement.current) {\n ghostElement.current.innerText = target.value;\n\n syncWidth(inputRef, ghostElement);\n }\n };\n\n const destroy = () => {\n input?.removeEventListener(\"input\", handleInput);\n ghostElement.current?.remove();\n ghostElement.current = null;\n };\n\n const mountBehaviour = () => {\n if (!inputRef.current) return;\n\n // Doesnt make sense to mount an element if the element already exist\n if (ghostElement.current) return;\n\n const styles = window.getComputedStyle(inputRef.current);\n ghostElement.current = createGhostElement(options);\n\n if (options?.ghostElement?.className)\n ghostElement.current.classList.add(options?.ghostElement?.className);\n\n ghostElement.current.id = options?.ghostElement?.id || \"\";\n // Ensure we capture the current value at mount time\n ghostElement.current.innerText = inputRef.current.value ?? \"\";\n\n if (options?.minWidth) inputRef.current.style.minWidth = options?.minWidth;\n if (options?.maxWidth) inputRef.current.style.maxWidth = options?.maxWidth;\n\n applyFontStyles(styles, ghostElement.current);\n\n // Here I allow you to mess up the styles of the hidden element\n if (options?.ghostElement?.styles)\n // NOTE: this will overwrite anything from the previus `applyFontStyles`\n applyStyles(options?.ghostElement?.styles, ghostElement.current);\n\n syncWidth(inputRef, ghostElement);\n\n inputRef.current.addEventListener(\"input\", handleInput);\n };\n\n useEffect(\n () => {\n mountBehaviour();\n return () => destroy();\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n []\n );\n\n const callbackRef = useCallback(\n (element: HTMLInputElement | null) => {\n inputRef.current = element;\n\n if (!element) destroy();\n else if (element && inputRef.current) {\n mountBehaviour();\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [inputRef]\n );\n\n return {\n callbackRef,\n ref: inputRef,\n };\n}\n","import type { RefObject } from \"react\";\nimport type { AutWidthInputOptions } from \"./types\";\n\nexport const applyFontStyles = (\n styles: CSSStyleDeclaration,\n target: HTMLElement\n) => {\n target.style.fontFamily = styles.fontFamily;\n target.style.fontSize = styles.fontSize;\n target.style.letterSpacing = styles.letterSpacing;\n target.style.fontWeight = styles.fontWeight;\n};\n\nexport const applyStyles = (\n styles: Partial<CSSStyleDeclaration>,\n target: HTMLElement\n) => {\n Object.keys(styles).forEach((styleAttr: string) => {\n const key = styleAttr as keyof CSSStyleDeclaration;\n\n const value = styles[key];\n\n if (typeof value === \"string\" && typeof key === \"string\") {\n // target.style.setProperty(key, value);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target.style as any)[key] = value;\n }\n });\n};\n\nexport const createGhostElement = (options?: AutWidthInputOptions) => {\n const tempElement = document.createElement(\"p\");\n\n if (options?.minWidth) {\n tempElement.style.minWidth = options?.minWidth;\n }\n\n Object.assign(tempElement.style, {\n position: \"absolute\",\n whiteSpace: \"pre\",\n top: \"0\",\n left: \"0\",\n visibility: \"hidden\",\n height: \"0\",\n overflow: \"hidden\",\n pointerEvents: \"none\",\n zIndex: -1000,\n margin: \"0\", // Reset margin to avoid layout interference\n });\n\n tempElement.innerText = \"\";\n tempElement.setAttribute(\"class\", \"ghost-paragraph-element\");\n\n return document.body.appendChild(tempElement);\n};\n\nexport const syncWidth = (\n inputRef: RefObject<HTMLInputElement | null>,\n ghostElement: RefObject<HTMLElement | null>\n) => {\n if (inputRef.current) {\n inputRef.current.style.width = `${\n ghostElement.current?.getBoundingClientRect()?.width ?? 0\n }px`;\n return inputRef.current.style.width;\n }\n\n return null;\n};\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "use-auto-width-input",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "A React hook to automatically resize an input based on its content",
|
|
5
5
|
"author": "Luis Vanin",
|
|
6
6
|
"module": "./dist/index.js",
|
|
7
7
|
"main": "./dist/index.cjs",
|
|
8
8
|
"types": "/dist/index.d.ts",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/LuigiVanin/use-auto-width-input"
|
|
13
|
+
},
|
|
9
14
|
"files": [
|
|
10
15
|
"dist"
|
|
11
16
|
],
|
|
@@ -15,25 +20,36 @@
|
|
|
15
20
|
"lint": "bun eslint",
|
|
16
21
|
"build": "tsup",
|
|
17
22
|
"prepare": "husky",
|
|
23
|
+
"test": "vitest --config ./vite.config.ts",
|
|
18
24
|
"prepublishOnly": "bun run build"
|
|
19
25
|
},
|
|
26
|
+
"dependencies": {},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"typescript": "^5.9.3",
|
|
29
|
+
"react": "^19.2.3",
|
|
30
|
+
"react-dom": "^19.2.3"
|
|
31
|
+
},
|
|
20
32
|
"devDependencies": {
|
|
33
|
+
"@testing-library/dom": "^10.4.1",
|
|
34
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
35
|
+
"@testing-library/react": "^16.3.1",
|
|
36
|
+
"@testing-library/user-event": "^14.6.1",
|
|
21
37
|
"@types/bun": "latest",
|
|
22
38
|
"@types/react": "^19.2.7",
|
|
23
39
|
"@types/react-dom": "^19.2.3",
|
|
40
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
24
41
|
"eslint": "^9.39.2",
|
|
25
42
|
"eslint-plugin-react": "^7.37.5",
|
|
26
43
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
27
44
|
"globals": "^16.5.0",
|
|
28
45
|
"husky": "^9.1.7",
|
|
46
|
+
"jsdom": "^27.3.0",
|
|
29
47
|
"react": "^19.2.3",
|
|
30
48
|
"react-dom": "^19.2.3",
|
|
31
49
|
"tsup": "^8.5.1",
|
|
32
|
-
"typescript-eslint": "^8.49.0"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"react": "^19.2.3",
|
|
37
|
-
"react-dom": "^19.2.3"
|
|
50
|
+
"typescript-eslint": "^8.49.0",
|
|
51
|
+
"unplugin-auto-import": "^20.3.0",
|
|
52
|
+
"vite": "^7.3.0",
|
|
53
|
+
"vitest": "^4.0.16"
|
|
38
54
|
}
|
|
39
55
|
}
|