Skip to content

Commit 45b487e

Browse files
committed
Watches window resize event
1 parent 6bbe4f4 commit 45b487e

File tree

4 files changed

+131
-91
lines changed

4 files changed

+131
-91
lines changed

demo/App.vue

Lines changed: 34 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<template>
2-
<div class="px-8 my-32 md:px-0">
2+
<div class="px-8 mt-16 mb-6 sm:mt-32 sm:mb-6 md:px-0">
33
<div class="flex items-center justify-between w-full mx-auto mt-8 max-w-prose">
4-
<h1 class="flex items-center text-4xl font-bold">
5-
<Logo class="text-purple-500 w-auto h-8 fill-current relative top-0.5" aria-hidden="true" />
6-
<span class="inline-block ml-4 text-gray-600">vue-input-autowidth</span>
4+
<h1 class="flex items-center text-lg font-bold sm:text-4xl">
5+
<Logo class="text-purple-500 w-auto h-6 sm:h-8 fill-current relative top-0.5" aria-hidden="true" />
6+
<span class="inline-block ml-2 text-gray-600 sm:ml-4">vue-input-autowidth</span>
77
</h1>
8-
<nav class="flex items-center">
8+
<nav class="absolute top-[20px] right-[20px] flex items-center sm:relative sm:top-0 sm:right-0">
99
<a
1010
href="https://github.com/syropian/vue-input-autowidth"
1111
target="_blank"
@@ -18,7 +18,13 @@
1818
</nav>
1919
</div>
2020
<div class="w-full mx-auto mt-8 max-w-prose">
21-
<input v-model="msg" v-autowidth type="text" class="input" placeholder="Watch me change size with my content!" />
21+
<input
22+
v-model="msg"
23+
v-autowidth
24+
type="text"
25+
class="block max-w-full input"
26+
placeholder="Watch me change size with my content!"
27+
/>
2228
</div>
2329
<div class="w-full mx-auto mt-8 prose max-w-prose">
2430
<h2>Install</h2>
@@ -35,50 +41,41 @@
3541
<h3>Configure</h3>
3642
<p>You can pass various options to the directive</p>
3743
<highlightjs language="xml" :code="showOptionsCode" />
38-
<input
39-
v-model="msg"
40-
v-autowidth="{
41-
minWidth: '75px',
42-
maxWidth: '75%',
43-
comfortZone: '1ch',
44-
}"
45-
type="text"
46-
class="input"
47-
placeholder="An example with custom options"
48-
/>
49-
50-
<footer>
51-
<p class="text-gray-500">&copy; {{ new Date().getFullYear() }} Collin Henderson</p>
52-
</footer>
5344
</div>
45+
<footer class="w-full pt-4 mx-auto mt-8 border-t border-gray-200 sm:mt-12 max-w-prose">
46+
<p class="text-gray-500">
47+
&copy; {{ new Date().getFullYear() }}
48+
<a
49+
href="https://syropia.net"
50+
target="blank"
51+
rel="noopener noreferrer"
52+
class="font-semibold text-purple-500 hover:underline"
53+
>Collin Henderson</a
54+
>
55+
</p>
56+
</footer>
5457
</div>
5558
</template>
5659

57-
<script lang="ts">
58-
import { defineComponent, ref } from 'vue'
60+
<script lang="ts" setup>
61+
import { ref } from 'vue'
5962
import Logo from './Logo.vue'
6063
import GitHubIcon from './GitHubIcon.vue'
6164
62-
export default defineComponent({
63-
components: {
64-
Logo,
65-
GitHubIcon,
66-
},
67-
setup() {
68-
const msg = ref('')
65+
const msg = ref('')
6966
70-
const installCode = `$ npm install vue-input-autowidth@next --save
67+
const installCode = `$ npm install vue-input-autowidth@next --save
7168
# or...
7269
$ yarn add vue-input-autowidth@next`
7370
74-
const addGlobalCode = `import { createApp } from 'vue'
71+
const addGlobalCode = `import { createApp } from 'vue'
7572
import App from './App.vue'
7673
import { plugin as VueInputAutowidth } from 'vue-input-autowidth'
7774
7875
createApp(App).use(VueInputAutowidth).mount("#app")
7976
`
8077
81-
const perComponentCode = `import { directive as VueInputAutowidth } from 'vue-input-autowidth'
78+
const perComponentCode = `import { directive as VueInputAutowidth } from 'vue-input-autowidth'
8279
8380
export default {
8481
directives: { autowidth: VueInputAutowidth },
@@ -87,33 +84,23 @@ export default {
8784
},
8885
}`
8986
90-
const useDirectiveCode = `<input
87+
const useDirectiveCode = `<input
9188
type="text"
9289
placeholder="Watch me change size with my content!"
9390
v-model="msg"
9491
v-autowidth
9592
/>`
9693
97-
const showOptionsCode = `<input
94+
const showOptionsCode = `<input
9895
type="text"
9996
placeholder="An example with custom options"
10097
v-model="msg"
10198
v-autowidth="{
10299
minWidth: '75px',
103-
maxWidth: '75%',
100+
maxWidth: '86%',
104101
comfortZone: '1ch',
105102
}"
106103
/>`
107-
return {
108-
msg,
109-
installCode,
110-
addGlobalCode,
111-
perComponentCode,
112-
useDirectiveCode,
113-
showOptionsCode,
114-
}
115-
},
116-
})
117104
</script>
118105

119106
<style>
@@ -123,6 +110,6 @@ export default {
123110
}
124111
125112
.input {
126-
@apply transition-colors min-w-0 px-3 py-2 rounded-md text-lg focus:outline-none border-2 border-gray-300 focus:border-purple-500;
113+
@apply transition-colors min-w-0 px-2 sm:px-3 py-1 sm:py-2 rounded-md text-base sm:text-lg focus:outline-none border-2 border-gray-300 focus:border-purple-500;
127114
}
128115
</style>

lib/directive.ts

Lines changed: 92 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,53 @@ type Complete<T> = {
44
[P in keyof Required<T>]: Pick<T, P> extends Required<Pick<T, P>> ? T[P] : T[P] | undefined
55
}
66

7-
interface InputWithMirror extends HTMLInputElement {
7+
interface AutowidthInput extends HTMLInputElement {
88
mirror: HTMLElement
9+
options: Complete<InputAutoWidthOptions>
10+
windowResizeHandler?: () => void
11+
sizerFunc?: (e?: Event) => () => void
912
}
1013

1114
export interface InputAutoWidthOptions {
1215
maxWidth?: string
1316
minWidth?: string
1417
comfortZone?: string
18+
watchWindowSize?: boolean
19+
windowResizeHandlerDebounceTime?: number
20+
disableNonInputWarning?: boolean
1521
}
1622

1723
const defaults: Complete<InputAutoWidthOptions> = {
18-
maxWidth: 'none',
19-
minWidth: 'none',
24+
maxWidth: undefined,
25+
minWidth: undefined,
2026
comfortZone: '0px',
27+
watchWindowSize: true,
28+
windowResizeHandlerDebounceTime: 150,
29+
disableNonInputWarning: false,
2130
} as const
2231

23-
const checkWidth = (el: InputWithMirror, options: Complete<InputAutoWidthOptions>) => {
24-
const mirror: HTMLElement = el.mirror
32+
export const debounce = <T extends (...args: any[]) => any>(callback: T, waitFor: number) => {
33+
let timeout: ReturnType<typeof setTimeout>
34+
return (...args: Parameters<T>): ReturnType<T> => {
35+
let result: any
36+
timeout && clearTimeout(timeout)
37+
timeout = setTimeout(() => {
38+
result = callback(...args)
39+
}, waitFor)
40+
return result
41+
}
42+
}
43+
44+
const checkWidth = (el: AutowidthInput) => {
45+
const { mirror, options } = el
2546

26-
el.style.maxWidth = options.maxWidth || 'none'
27-
el.style.minWidth = options.minWidth || 'none'
47+
if (options.maxWidth) {
48+
el.style.maxWidth = options.maxWidth
49+
}
50+
51+
if (options.minWidth) {
52+
el.style.minWidth = options.minWidth
53+
}
2854

2955
let val = el.value
3056

@@ -45,54 +71,81 @@ const checkWidth = (el: InputWithMirror, options: Complete<InputAutoWidthOptions
4571
}
4672
}
4773

48-
const mergeDefaultWithOptions = (options: InputAutoWidthOptions) => Object.assign({}, defaults, options)
74+
const copyStylesToMirror = (el: AutowidthInput) => {
75+
const styles = window.getComputedStyle(el)
76+
const { options } = el
77+
78+
Object.assign(el.mirror.style, {
79+
position: 'absolute',
80+
top: '0',
81+
left: '0',
82+
visibility: 'hidden',
83+
height: '0',
84+
overflow: 'hidden',
85+
whiteSpace: 'pre',
86+
fontSize: styles.fontSize,
87+
fontFamily: styles.fontFamily,
88+
fontWeight: styles.fontWeight,
89+
fontStyle: styles.fontStyle,
90+
letterSpacing: styles.letterSpacing,
91+
textTransform: styles.textTransform,
92+
paddingRight: `calc(${options.comfortZone} + ${styles.paddingRight} + ${styles.borderRightWidth})`,
93+
paddingLeft: `calc(${styles.paddingLeft} + ${styles.borderLeftWidth})`,
94+
})
95+
}
96+
97+
const copyStylesAndCheckWidth = (el: AutowidthInput) => {
98+
copyStylesToMirror(el)
99+
checkWidth(el)
100+
}
101+
102+
const mergeDefaultsWithOptions = (options: InputAutoWidthOptions): Complete<InputAutoWidthOptions> =>
103+
Object.assign({}, defaults, options)
49104

50105
export default {
51-
beforeMount: function (el: HTMLElement) {
52-
if (el.tagName.toLocaleUpperCase() !== 'INPUT') {
106+
beforeMount: function (el: AutowidthInput, binding: DirectiveBinding) {
107+
el.options = mergeDefaultsWithOptions(binding.value as InputAutoWidthOptions)
108+
109+
if (!el.options.disableNonInputWarning && el.tagName.toLocaleUpperCase() !== 'INPUT') {
53110
throw new Error('v-input-autowidth can only be used on input elements.')
54111
}
55112
},
56-
mounted: function (el: InputWithMirror, binding: DirectiveBinding, vnode: VNode) {
57-
const options: Complete<InputAutoWidthOptions> = mergeDefaultWithOptions(binding.value)
113+
mounted: function (el: AutowidthInput, binding: DirectiveBinding, vnode: VNode) {
58114
const hasVModel = Object.prototype.hasOwnProperty.call(vnode.props, '@onUpdate:modelValue')
59-
const styles = window.getComputedStyle(el)
60115

61-
el.mirror = document.createElement('div')
62-
63-
Object.assign(el.mirror.style, {
64-
position: 'absolute',
65-
top: '0',
66-
left: '0',
67-
visibility: 'hidden',
68-
height: '0',
69-
overflow: 'hidden',
70-
whiteSpace: 'pre',
71-
fontSize: styles.fontSize,
72-
fontFamily: styles.fontFamily,
73-
fontWeight: styles.fontWeight,
74-
fontStyle: styles.fontStyle,
75-
letterSpacing: styles.letterSpacing,
76-
textTransform: styles.textTransform,
77-
paddingRight: `calc(${options.comfortZone} + ${styles.paddingRight} + ${styles.borderRightWidth})`,
78-
paddingLeft: `calc(${styles.paddingLeft} + ${styles.borderLeftWidth})`,
79-
})
116+
el.sizerFunc = (_e?: Event) => checkWidth.bind(null, el)
80117

118+
el.mirror = document.createElement('div')
119+
copyStylesToMirror(el)
81120
el.mirror.setAttribute('aria-hidden', 'true')
82-
83121
document.body.appendChild(el.mirror)
84122

85-
checkWidth(el, options)
123+
copyStylesAndCheckWidth(el)
86124

87125
if (!hasVModel) {
88-
el.addEventListener('input', checkWidth.bind(null, el, options))
126+
el.addEventListener('input', el.sizerFunc)
127+
}
128+
129+
if (el.options.watchWindowSize && el.options.windowResizeHandlerDebounceTime !== undefined) {
130+
const windowResizeHandler = (_e?: Event) => copyStylesAndCheckWidth(el)
131+
el.windowResizeHandler = debounce(windowResizeHandler, el.options.windowResizeHandlerDebounceTime)
132+
window.addEventListener('resize', el.windowResizeHandler, { passive: true })
89133
}
90134
},
91-
updated: function (el: InputWithMirror, binding: DirectiveBinding) {
92-
checkWidth(el, mergeDefaultWithOptions(binding.value))
135+
updated: function (el: AutowidthInput) {
136+
if (el.sizerFunc) {
137+
el.sizerFunc()()
138+
}
93139
},
94-
unmounted: function (el: InputWithMirror, binding: DirectiveBinding) {
140+
unmounted: function (el: AutowidthInput) {
95141
document.body.removeChild(el.mirror)
96-
el.removeEventListener('input', checkWidth.bind(null, el, mergeDefaultWithOptions(binding.value)))
142+
143+
if (el.sizerFunc) {
144+
el.removeEventListener('input', el.sizerFunc)
145+
}
146+
147+
if (el.options.watchWindowSize && el.windowResizeHandler) {
148+
window.removeEventListener('resize', el.windowResizeHandler)
149+
}
97150
},
98151
} as ObjectDirective

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"@types/node": "^16.9.2",
3131
"@typescript-eslint/eslint-plugin": "^4.31.2",
3232
"@typescript-eslint/parser": "^4.31.2",
33-
"@vitejs/plugin-vue": "^1.9.1",
33+
"@vitejs/plugin-vue": "^1.9.2",
3434
"@vue/compiler-sfc": "^3.2.16",
3535
"@vue/eslint-config-prettier": "^6.0.0",
3636
"@vue/eslint-config-typescript": "^7.0.0",

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -305,10 +305,10 @@
305305
"@typescript-eslint/types" "4.31.2"
306306
eslint-visitor-keys "^2.0.0"
307307

308-
"@vitejs/plugin-vue@^1.9.1":
309-
version "1.9.1"
310-
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-1.9.1.tgz#69a92d066f7fabde656c5a9ed983bf8c066bcd8b"
311-
integrity sha512-9YuxaU2nLoSS/S1Ep4QTG/pEIh96LlauNM1g7LN/EOJ14Nj8HBeSy1OL26ydxb+MPhKn5XKGARh5wQF0UjHbLw==
308+
"@vitejs/plugin-vue@^1.9.2":
309+
version "1.9.2"
310+
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-1.9.2.tgz#7234efb8c3c3d60c7eac350a935074ab1820ae0e"
311+
integrity sha512-QnUQJvGmY+YT9xTidLcjr6NAjKWNdSuul1M+BZ6uwTQaO5vpAY9USBncXESATk742dYMZGJenegJgeJhG/HMNQ==
312312

313313
"@volar/code-gen@^0.27.24":
314314
version "0.27.24"

0 commit comments

Comments
 (0)