-
Notifications
You must be signed in to change notification settings - Fork 0
/
textarea-max-rows.js
131 lines (104 loc) · 3.36 KB
/
textarea-max-rows.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
function createElement(tagName, attributes) {
const element = document.createElement(tagName)
for (const [key, value] of Object.entries(attributes)) {
if (value) {
element.setAttribute(key, value)
}
}
return element
}
function shapeElement(element, attributes) {
for (const [key, value] of Object.entries(attributes)) {
if (value) {
element.setAttribute(key, value)
}
}
return element
}
function moveElementOffscreen(element) {
const move = () => {
element.style.position = 'absolute'
element.style.left = `-${document.body.clientWidth * 2}px`
}
move()
window.addEventListener('resize', move)
return element
}
function syncWidthFrom(newElement, existingElement) {
const resizeObserver = new ResizeObserver(([entry], _observer) => {
const width = entry.target.offsetWidth
newElement.style.width = `${width}px`
})
resizeObserver.observe(existingElement)
return resizeObserver
}
function insertAfter(newNode, existingNode) {
existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling)
}
function patchTextareaMaxRowsSupport(element, { shadowElement = null } = {}) {
const attrClass = element.getAttribute('class')
const attrStyle = element.getAttribute('style')
const attrRows = element.getAttribute('rows') || 1
const attrMaxRows = element.getAttribute('max-rows')
const minRows = Number.parseInt(attrRows)
const maxRows = Number.parseInt(attrMaxRows)
const shouldInsertShadowElement = !shadowElement
const attributes = {
class: attrClass,
style: `box-sizing: border-box !important; ${attrStyle}`,
rows: attrRows,
'max-rows': attrMaxRows,
}
if (shadowElement) {
shadowElement = shapeElement(shadowElement, attributes)
} else {
shadowElement = createElement('textarea', attributes)
}
moveElementOffscreen(shadowElement)
syncWidthFrom(shadowElement, element)
if (shouldInsertShadowElement) {
insertAfter(shadowElement, element)
}
function syncRows() {
// copy the content from the real textarea
shadowElement.value = element.value
// get the height of content
shadowElement.setAttribute('rows', 1)
const contentHeight = shadowElement.scrollHeight
// increase the number of rows until finding a proper number.
for (let rows = minRows; rows <= maxRows; rows++) {
shadowElement.setAttribute('rows', rows)
if (shadowElement.clientHeight >= contentHeight) {
break
}
}
const oldRows = element.getAttribute('rows')
const newRows = shadowElement.getAttribute('rows')
element.setAttribute('rows', newRows)
if (oldRows !== newRows) {
const event = new CustomEvent('rows-change', {
detail: { rows: Number.parseInt(newRows) },
})
element.dispatchEvent(event)
}
}
element.addEventListener('input', () => {
syncRows()
})
// override original getter and setter in order to call syncRows() when the value is set by JavaScript.
const { get, set } = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')
Object.defineProperty(element, 'value', {
get() {
return get.call(this)
},
set(value) {
set.call(this, value)
const event = new CustomEvent('value-change', {
detail: { value: value },
})
element.dispatchEvent(event)
syncRows()
},
})
}
export default patchTextareaMaxRowsSupport