@instructure/ui-truncate-text 8.33.1 → 8.33.2
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/CHANGELOG.md +4 -0
- package/es/TruncateText/index.js +19 -51
- package/es/TruncateText/props.js +1 -0
- package/es/TruncateText/styles.js +0 -1
- package/es/TruncateText/theme.js +2 -2
- package/es/TruncateText/utils/cleanData.js +3 -31
- package/es/TruncateText/utils/cleanString.js +0 -5
- package/es/TruncateText/utils/measureText.js +8 -16
- package/es/TruncateText/utils/truncate.js +30 -82
- package/lib/TruncateText/index.js +19 -70
- package/lib/TruncateText/props.js +1 -3
- package/lib/TruncateText/styles.js +0 -2
- package/lib/TruncateText/theme.js +2 -3
- package/lib/TruncateText/utils/cleanData.js +2 -32
- package/lib/TruncateText/utils/cleanString.js +0 -6
- package/lib/TruncateText/utils/measureText.js +6 -17
- package/lib/TruncateText/utils/truncate.js +29 -92
- package/lib/index.js +0 -1
- package/package.json +14 -14
- package/tsconfig.build.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [8.33.2](https://github.com/instructure/instructure-ui/compare/v8.33.1...v8.33.2) (2023-01-25)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @instructure/ui-truncate-text
|
|
9
|
+
|
|
6
10
|
## [8.33.1](https://github.com/instructure/instructure-ui/compare/v8.33.0...v8.33.1) (2023-01-06)
|
|
7
11
|
|
|
8
12
|
**Note:** Version bump only for package @instructure/ui-truncate-text
|
package/es/TruncateText/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
var _dec, _dec2, _dec3, _class, _class2;
|
|
2
|
-
|
|
3
2
|
/*
|
|
4
3
|
* The MIT License (MIT)
|
|
5
4
|
*
|
|
@@ -23,7 +22,6 @@ var _dec, _dec2, _dec3, _class, _class2;
|
|
|
23
22
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
23
|
* SOFTWARE.
|
|
25
24
|
*/
|
|
26
|
-
|
|
27
25
|
/** @jsx jsx */
|
|
28
26
|
import React, { Component } from 'react';
|
|
29
27
|
import { debounce } from '@instructure/debounce';
|
|
@@ -36,7 +34,6 @@ import generateStyle from './styles';
|
|
|
36
34
|
import generateComponentTheme from './theme';
|
|
37
35
|
import truncate from './utils/truncate';
|
|
38
36
|
import { propTypes, allowedProps } from './props';
|
|
39
|
-
|
|
40
37
|
/**
|
|
41
38
|
---
|
|
42
39
|
category: components
|
|
@@ -52,20 +49,16 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
|
|
|
52
49
|
this._stage = null;
|
|
53
50
|
this._wasTruncated = void 0;
|
|
54
51
|
this._resizeListener = void 0;
|
|
55
|
-
|
|
56
52
|
this.update = () => {
|
|
57
53
|
if (this.ref) {
|
|
58
54
|
this.setState(this.initialState);
|
|
59
55
|
}
|
|
60
56
|
};
|
|
61
|
-
|
|
62
57
|
this.state = this.initialState;
|
|
63
58
|
}
|
|
64
|
-
|
|
65
59
|
get _ref() {
|
|
66
60
|
return this.ref;
|
|
67
61
|
}
|
|
68
|
-
|
|
69
62
|
get initialState() {
|
|
70
63
|
return {
|
|
71
64
|
isTruncated: false,
|
|
@@ -74,13 +67,11 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
|
|
|
74
67
|
truncatedText: void 0
|
|
75
68
|
};
|
|
76
69
|
}
|
|
77
|
-
|
|
78
70
|
componentDidMount() {
|
|
79
71
|
const _this$props = this.props,
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
children = _this$props.children,
|
|
73
|
+
makeStyles = _this$props.makeStyles;
|
|
82
74
|
makeStyles === null || makeStyles === void 0 ? void 0 : makeStyles();
|
|
83
|
-
|
|
84
75
|
if (children) {
|
|
85
76
|
this.checkChildren();
|
|
86
77
|
const txt = ensureSingleChild(children);
|
|
@@ -90,10 +81,8 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
|
|
|
90
81
|
leading: true,
|
|
91
82
|
trailing: true
|
|
92
83
|
});
|
|
93
|
-
|
|
94
84
|
const _getBoundingClientRec = getBoundingClientRect(this.ref),
|
|
95
|
-
|
|
96
|
-
|
|
85
|
+
origWidth = _getBoundingClientRec.width;
|
|
97
86
|
this._resizeListener = new ResizeObserver(entries => {
|
|
98
87
|
// requestAnimationFrame call is needed becuase some truncatetext test cases
|
|
99
88
|
// failed due to ResizeObserver was not able to deliver all observations within a single animation frame
|
|
@@ -101,39 +90,33 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
|
|
|
101
90
|
requestAnimationFrame(() => {
|
|
102
91
|
for (const entry of entries) {
|
|
103
92
|
const width = entry.contentRect.width;
|
|
104
|
-
|
|
105
93
|
if (origWidth !== width) {
|
|
106
94
|
this.props.debounce === 0 ? this.update() : this._debounced();
|
|
107
95
|
}
|
|
108
96
|
}
|
|
109
97
|
});
|
|
110
98
|
});
|
|
111
|
-
|
|
112
99
|
this._resizeListener.observe(this.ref);
|
|
113
100
|
}
|
|
114
101
|
}
|
|
115
|
-
|
|
116
102
|
componentWillUnmount() {
|
|
117
103
|
if (this._resizeListener) {
|
|
118
104
|
this._resizeListener.disconnect();
|
|
119
105
|
}
|
|
120
|
-
|
|
121
106
|
if (this._debounced) {
|
|
122
107
|
this._debounced.cancel();
|
|
123
108
|
}
|
|
124
109
|
}
|
|
125
|
-
|
|
126
110
|
componentDidUpdate(prevProps) {
|
|
127
111
|
const _this$props2 = this.props,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
112
|
+
children = _this$props2.children,
|
|
113
|
+
onUpdate = _this$props2.onUpdate,
|
|
114
|
+
makeStyles = _this$props2.makeStyles;
|
|
131
115
|
makeStyles === null || makeStyles === void 0 ? void 0 : makeStyles();
|
|
132
116
|
const _this$state = this.state,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
117
|
+
isTruncated = _this$state.isTruncated,
|
|
118
|
+
needsSecondRender = _this$state.needsSecondRender,
|
|
119
|
+
truncatedText = _this$state.truncatedText;
|
|
137
120
|
if (children) {
|
|
138
121
|
if (prevProps !== this.props) {
|
|
139
122
|
if (prevProps.children !== this.props.children) {
|
|
@@ -141,13 +124,11 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
|
|
|
141
124
|
this.checkChildren();
|
|
142
125
|
const txt = ensureSingleChild(children);
|
|
143
126
|
this._text = txt ? txt : void 0;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
127
|
+
}
|
|
128
|
+
// require the double render whenever props change
|
|
147
129
|
this.setState(this.initialState);
|
|
148
130
|
return;
|
|
149
131
|
}
|
|
150
|
-
|
|
151
132
|
if (!needsSecondRender && (isTruncated || this._wasTruncated)) {
|
|
152
133
|
onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate(isTruncated, truncatedText);
|
|
153
134
|
this._wasTruncated = isTruncated;
|
|
@@ -156,7 +137,6 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
|
|
|
156
137
|
}
|
|
157
138
|
}
|
|
158
139
|
}
|
|
159
|
-
|
|
160
140
|
checkChildren() {
|
|
161
141
|
error(!(() => {
|
|
162
142
|
let isTooDeep = false;
|
|
@@ -175,20 +155,17 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
|
|
|
175
155
|
return isTooDeep;
|
|
176
156
|
})(), `[TruncateText] Some children are too deep in the node tree and will not render.`);
|
|
177
157
|
}
|
|
178
|
-
|
|
179
158
|
truncate() {
|
|
180
159
|
if (!this.state.needsSecondRender) {
|
|
181
160
|
return;
|
|
182
161
|
}
|
|
183
|
-
|
|
184
162
|
if (canUseDOM) {
|
|
185
163
|
var _this$props$styles;
|
|
186
|
-
|
|
187
|
-
|
|
164
|
+
const result = truncate(this._stage, {
|
|
165
|
+
...this.props,
|
|
188
166
|
parent: this.ref ? this.ref : void 0,
|
|
189
167
|
lineHeight: (_this$props$styles = this.props.styles) === null || _this$props$styles === void 0 ? void 0 : _this$props$styles.lineHeight
|
|
190
168
|
});
|
|
191
|
-
|
|
192
169
|
if (result) {
|
|
193
170
|
const element = this.renderChildren(result.isTruncated, result.data, result.constraints.width);
|
|
194
171
|
this.setState({
|
|
@@ -200,9 +177,8 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
|
|
|
200
177
|
}
|
|
201
178
|
} else {
|
|
202
179
|
var _this$ref, _this$ref2;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
180
|
+
const textContent = (_this$ref = this.ref) !== null && _this$ref !== void 0 && _this$ref.textContent ? (_this$ref2 = this.ref) === null || _this$ref2 === void 0 ? void 0 : _this$ref2.textContent : void 0;
|
|
181
|
+
// if dom isn't available, use original children
|
|
206
182
|
this.setState({
|
|
207
183
|
needsSecondRender: false,
|
|
208
184
|
isTruncated: false,
|
|
@@ -211,32 +187,27 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
|
|
|
211
187
|
});
|
|
212
188
|
}
|
|
213
189
|
}
|
|
214
|
-
|
|
215
190
|
renderChildren(truncated, data, width) {
|
|
216
191
|
var _this$props$styles2;
|
|
217
|
-
|
|
218
192
|
if (!truncated) {
|
|
219
193
|
return this._text;
|
|
220
194
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
195
|
+
const childElements = [];
|
|
196
|
+
// iterate over each node used in the truncated string
|
|
224
197
|
for (let i = 0; i < data.length; i++) {
|
|
225
198
|
const item = data[i];
|
|
226
199
|
const element = this._text.props.children[i];
|
|
227
200
|
const nodeText = item.join('');
|
|
228
|
-
|
|
229
201
|
if (element && element.props) {
|
|
230
202
|
// if node is an html element and not just a string
|
|
231
203
|
childElements.push(safeCloneElement(element, element.props, nodeText));
|
|
232
204
|
} else {
|
|
233
205
|
childElements.push(nodeText);
|
|
234
206
|
}
|
|
235
|
-
}
|
|
207
|
+
}
|
|
208
|
+
// this spacer element is set to the max width the full text could
|
|
236
209
|
// potentially be without this, text in `width: auto` elements won't expand
|
|
237
210
|
// to accommodate more text, once truncated
|
|
238
|
-
|
|
239
|
-
|
|
240
211
|
childElements.push(jsx("span", {
|
|
241
212
|
css: (_this$props$styles2 = this.props.styles) === null || _this$props$styles2 === void 0 ? void 0 : _this$props$styles2.spacer,
|
|
242
213
|
style: {
|
|
@@ -246,10 +217,8 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
|
|
|
246
217
|
const children = React.Children.map(childElements, child => child);
|
|
247
218
|
return this._text.props ? safeCloneElement(this._text, this._text.props, children) : children;
|
|
248
219
|
}
|
|
249
|
-
|
|
250
220
|
render() {
|
|
251
221
|
var _this$props$styles3;
|
|
252
|
-
|
|
253
222
|
const truncatedElement = this.state.truncatedElement;
|
|
254
223
|
const children = this.props.children;
|
|
255
224
|
return jsx("span", {
|
|
@@ -263,7 +232,6 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
|
|
|
263
232
|
}
|
|
264
233
|
}, ensureSingleChild(children))), truncatedElement);
|
|
265
234
|
}
|
|
266
|
-
|
|
267
235
|
}, _class2.displayName = "TruncateText", _class2.componentId = 'TruncateText', _class2.allowedProps = allowedProps, _class2.propTypes = propTypes, _class2.defaultProps = {
|
|
268
236
|
maxLines: 1,
|
|
269
237
|
ellipsis: '\u2026',
|
package/es/TruncateText/props.js
CHANGED
package/es/TruncateText/theme.js
CHANGED
|
@@ -33,8 +33,8 @@ const generateComponentTheme = theme => {
|
|
|
33
33
|
fontFamily: typography === null || typography === void 0 ? void 0 : typography.fontFamily,
|
|
34
34
|
lineHeight: typography === null || typography === void 0 ? void 0 : typography.lineHeight
|
|
35
35
|
};
|
|
36
|
-
return {
|
|
36
|
+
return {
|
|
37
|
+
...componentVariables
|
|
37
38
|
};
|
|
38
39
|
};
|
|
39
|
-
|
|
40
40
|
export default generateComponentTheme;
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
* SOFTWARE.
|
|
23
23
|
*/
|
|
24
|
-
import { cloneArray } from '@instructure/ui-utils';
|
|
25
24
|
|
|
25
|
+
import { cloneArray } from '@instructure/ui-utils';
|
|
26
26
|
/**
|
|
27
27
|
* ---
|
|
28
28
|
* parent: TruncateText
|
|
@@ -41,91 +41,73 @@ import { cloneArray } from '@instructure/ui-utils';
|
|
|
41
41
|
function cleanData(stringData, options) {
|
|
42
42
|
let repeat = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : false;
|
|
43
43
|
const truncate = options.truncate,
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
ignore = options.ignore,
|
|
45
|
+
ellipsis = options.ellipsis;
|
|
46
46
|
let newData = cloneArray(stringData);
|
|
47
47
|
let ellipsisNode = -1;
|
|
48
48
|
let ellipsisIndex = -1;
|
|
49
|
-
|
|
50
49
|
const findEllipsis = () => {
|
|
51
50
|
for (let i = 0; i < newData.length; i++) {
|
|
52
51
|
const nodeData = newData[i];
|
|
53
|
-
|
|
54
52
|
if (nodeData.indexOf(ellipsis) !== -1) {
|
|
55
53
|
ellipsisNode = i;
|
|
56
54
|
ellipsisIndex = nodeData.indexOf(ellipsis);
|
|
57
55
|
}
|
|
58
56
|
}
|
|
59
57
|
};
|
|
60
|
-
|
|
61
58
|
if (truncate === 'character') {
|
|
62
59
|
findEllipsis();
|
|
63
60
|
let node = newData[ellipsisNode];
|
|
64
|
-
|
|
65
61
|
if (node) {
|
|
66
62
|
const before = node[ellipsisIndex - 1];
|
|
67
|
-
|
|
68
63
|
if (before && ignore.indexOf(before) !== -1) {
|
|
69
64
|
// remove character immediately BEFORE the ellipsis in the same node
|
|
70
65
|
newData[ellipsisNode].splice(ellipsisIndex - 1, 1);
|
|
71
66
|
}
|
|
72
|
-
|
|
73
67
|
if (!before) {
|
|
74
68
|
// character before the ellipsis is part of a different node
|
|
75
69
|
// find the next node with data and remove last datum
|
|
76
70
|
let prevNode = null;
|
|
77
71
|
let prevNodeIndex = ellipsisNode - 1;
|
|
78
|
-
|
|
79
72
|
while (prevNodeIndex >= 0) {
|
|
80
73
|
prevNode = newData[prevNodeIndex];
|
|
81
|
-
|
|
82
74
|
if (prevNode.length > 0) {
|
|
83
75
|
break;
|
|
84
76
|
} else {
|
|
85
77
|
prevNodeIndex--;
|
|
86
78
|
}
|
|
87
79
|
}
|
|
88
|
-
|
|
89
80
|
if (prevNode) {
|
|
90
81
|
const lastChar = String(prevNode.slice(-1));
|
|
91
|
-
|
|
92
82
|
if (ignore.indexOf(lastChar) !== -1) {
|
|
93
83
|
newData[prevNodeIndex].length -= 1;
|
|
94
84
|
}
|
|
95
85
|
}
|
|
96
86
|
}
|
|
97
87
|
}
|
|
98
|
-
|
|
99
88
|
findEllipsis();
|
|
100
89
|
node = newData[ellipsisNode];
|
|
101
|
-
|
|
102
90
|
if (node) {
|
|
103
91
|
const after = node[ellipsisIndex + 1];
|
|
104
|
-
|
|
105
92
|
if (after && ignore.indexOf(after) !== -1) {
|
|
106
93
|
// remove character immediately AFTER the ellipsis in the same node
|
|
107
94
|
newData[ellipsisNode].splice(ellipsisIndex + 1, 1);
|
|
108
95
|
}
|
|
109
|
-
|
|
110
96
|
if (!after) {
|
|
111
97
|
// character after the ellipsis is part of a different node
|
|
112
98
|
// find the next node with data and remove first datum
|
|
113
99
|
let nextNode = null;
|
|
114
100
|
let nextNodeIndex = ellipsisNode + 1;
|
|
115
|
-
|
|
116
101
|
while (nextNodeIndex < newData.length) {
|
|
117
102
|
nextNode = newData[nextNodeIndex];
|
|
118
|
-
|
|
119
103
|
if (nextNode.length > 0) {
|
|
120
104
|
break;
|
|
121
105
|
} else {
|
|
122
106
|
nextNodeIndex++;
|
|
123
107
|
}
|
|
124
108
|
}
|
|
125
|
-
|
|
126
109
|
if (nextNode) {
|
|
127
110
|
const firstChar = String(nextNode[0]);
|
|
128
|
-
|
|
129
111
|
if (ignore.indexOf(firstChar) !== -1) {
|
|
130
112
|
newData[nextNodeIndex].shift();
|
|
131
113
|
}
|
|
@@ -135,10 +117,8 @@ function cleanData(stringData, options) {
|
|
|
135
117
|
} else {
|
|
136
118
|
findEllipsis();
|
|
137
119
|
const node = newData[ellipsisNode];
|
|
138
|
-
|
|
139
120
|
if (node) {
|
|
140
121
|
const before = node[ellipsisIndex - 1];
|
|
141
|
-
|
|
142
122
|
if (before && ignore.indexOf(before.slice(-1)) !== -1) {
|
|
143
123
|
if (before.length === 1) {
|
|
144
124
|
// remove entire word datum
|
|
@@ -148,26 +128,21 @@ function cleanData(stringData, options) {
|
|
|
148
128
|
newData[ellipsisNode][ellipsisIndex - 1] = before.slice(0, -1);
|
|
149
129
|
}
|
|
150
130
|
}
|
|
151
|
-
|
|
152
131
|
if (!before) {
|
|
153
132
|
// word before the ellipsis is part of a different node
|
|
154
133
|
// find the next node with data and remove last datum
|
|
155
134
|
let prevNode = null;
|
|
156
135
|
let prevNodeIndex = ellipsisNode - 1;
|
|
157
|
-
|
|
158
136
|
while (prevNodeIndex >= 0) {
|
|
159
137
|
prevNode = newData[prevNodeIndex];
|
|
160
|
-
|
|
161
138
|
if (prevNode.length > 0) {
|
|
162
139
|
break;
|
|
163
140
|
} else {
|
|
164
141
|
prevNodeIndex--;
|
|
165
142
|
}
|
|
166
143
|
}
|
|
167
|
-
|
|
168
144
|
if (prevNode) {
|
|
169
145
|
const lastChar = String(prevNode.slice(-1)).slice(-1);
|
|
170
|
-
|
|
171
146
|
if (ignore.indexOf(lastChar) !== -1) {
|
|
172
147
|
const lastItem = prevNode.length - 1;
|
|
173
148
|
newData[prevNodeIndex][lastItem] = prevNode[lastItem].slice(0, -1);
|
|
@@ -176,12 +151,9 @@ function cleanData(stringData, options) {
|
|
|
176
151
|
}
|
|
177
152
|
}
|
|
178
153
|
}
|
|
179
|
-
|
|
180
154
|
if (repeat) {
|
|
181
155
|
newData = cleanData(newData, options, false);
|
|
182
156
|
}
|
|
183
|
-
|
|
184
157
|
return newData;
|
|
185
158
|
}
|
|
186
|
-
|
|
187
159
|
export default cleanData;
|
|
@@ -42,20 +42,15 @@ function cleanString(string, ignore) {
|
|
|
42
42
|
let text = string;
|
|
43
43
|
const firstChar = text.charAt(0);
|
|
44
44
|
const lastChar = text.slice(-1);
|
|
45
|
-
|
|
46
45
|
if (start && ignore.indexOf(firstChar) !== -1) {
|
|
47
46
|
text = text.slice(1);
|
|
48
47
|
}
|
|
49
|
-
|
|
50
48
|
if (end && ignore.indexOf(lastChar) !== -1) {
|
|
51
49
|
text = text.slice(0, -1);
|
|
52
50
|
}
|
|
53
|
-
|
|
54
51
|
if (repeat) {
|
|
55
52
|
text = cleanString(text, ignore, start, end, false);
|
|
56
53
|
}
|
|
57
|
-
|
|
58
54
|
return text;
|
|
59
55
|
}
|
|
60
|
-
|
|
61
56
|
export default cleanString;
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
22
|
* SOFTWARE.
|
|
23
23
|
*/
|
|
24
|
+
|
|
24
25
|
import { getComputedStyle } from '@instructure/ui-dom-utils';
|
|
26
|
+
|
|
25
27
|
/**
|
|
26
28
|
* ---
|
|
27
29
|
* parent: TruncateText
|
|
@@ -32,36 +34,29 @@ import { getComputedStyle } from '@instructure/ui-dom-utils';
|
|
|
32
34
|
* @param {DOMNode[]} nodes Array of DOM nodes.
|
|
33
35
|
* @param {DOMNode} parentNode The node to inherit default styles from.
|
|
34
36
|
*/
|
|
35
|
-
|
|
36
37
|
function measureText(nodes, parentNode) {
|
|
37
38
|
let width = 0;
|
|
38
|
-
|
|
39
39
|
for (let i = 0; i < nodes.length; i++) {
|
|
40
40
|
const node = nodes[i];
|
|
41
41
|
width += measure(node.textContent, node.nodeType === 1 ? node : parentNode);
|
|
42
42
|
}
|
|
43
|
-
|
|
44
43
|
return width;
|
|
45
44
|
}
|
|
46
|
-
|
|
47
45
|
function measure(string, domNode) {
|
|
48
|
-
const style = getComputedStyle(domNode);
|
|
49
|
-
|
|
46
|
+
const style = getComputedStyle(domNode);
|
|
47
|
+
// we use a canvas in a doc fragment to prevent having to render the string full width in the DOM
|
|
50
48
|
const canvas = document.createElement('canvas');
|
|
51
49
|
document.createDocumentFragment().appendChild(canvas);
|
|
52
50
|
const context = canvas.getContext('2d');
|
|
53
|
-
|
|
54
51
|
if (!context || !string) {
|
|
55
52
|
return 0;
|
|
56
53
|
}
|
|
57
|
-
|
|
58
54
|
let text = string;
|
|
59
55
|
let letterOffset = 0;
|
|
60
|
-
let width = 0;
|
|
56
|
+
let width = 0;
|
|
57
|
+
// grab individual font styles
|
|
61
58
|
// some browsers don't report a value for style['font']
|
|
62
|
-
|
|
63
59
|
context.font = [style.fontWeight, style.fontStyle, style.fontSize, style.fontFamily].join(' ');
|
|
64
|
-
|
|
65
60
|
if (style.textTransform === 'uppercase') {
|
|
66
61
|
text = string.toUpperCase();
|
|
67
62
|
} else if (style.textTransform === 'lowercase') {
|
|
@@ -69,14 +64,11 @@ function measure(string, domNode) {
|
|
|
69
64
|
} else if (style.textTransform === 'capitalize') {
|
|
70
65
|
text = string.replace(/\b\w/g, str => str.toUpperCase());
|
|
71
66
|
}
|
|
72
|
-
|
|
73
67
|
if (style.letterSpacing !== 'normal') {
|
|
74
68
|
letterOffset = text.length * parseInt(style.letterSpacing);
|
|
75
69
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
width = context.measureText(text).width + letterOffset;
|
|
71
|
+
// returns the max potential width of the text, assuming the text was on a single line
|
|
79
72
|
return width;
|
|
80
73
|
}
|
|
81
|
-
|
|
82
74
|
export default measureText;
|