@instructure/ui-truncate-text 10.26.1 → 11.0.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/CHANGELOG.md CHANGED
@@ -3,9 +3,30 @@
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
- ## [10.26.1](https://github.com/instructure/instructure-ui/compare/v10.26.0...v10.26.1) (2025-10-06)
6
+ # [11.0.0](https://github.com/instructure/instructure-ui/compare/v10.26.0...v11.0.0) (2025-10-06)
7
7
 
8
- **Note:** Version bump only for package @instructure/ui-truncate-text
8
+
9
+ ### Features
10
+
11
+ * **many:** instUI v11 release ([36f5438](https://github.com/instructure/instructure-ui/commit/36f54382669186227ba24798bbf7201ef2f5cd4c))
12
+
13
+
14
+ ### BREAKING CHANGES
15
+
16
+ * **many:** InstUI v11 contains the following breaking changes:
17
+ - React 16 and 17 are no longer supported
18
+ - remove `PropTypes` from all packages
19
+ - remove `CodeEditor` component
20
+ - remove `@instui/theme-registry` package
21
+ - remove `@testable`, `@experimental`, `@hack` decorators
22
+ - InstUISettingsProvider's `as` prop is removed
23
+ - `canvas.use()`, `canvasHighContrast.use()` functions are removed
24
+ - `canvasThemeLocal`, `canvasHighContrastThemeLocal` are removed
25
+ - `variables` field on theme objects are removed
26
+ - remove deprecated props from Table: Row's `isStacked`, Body's
27
+ `isStacked`, `hover`, and `headers`
28
+ - `Table`'s `caption` prop is now required
29
+ - `ui-dom-utils`'s `getComputedStyle` can now return `undefined`
9
30
 
10
31
 
11
32
 
@@ -1,4 +1,4 @@
1
- var _dec, _dec2, _dec3, _class, _TruncateText;
1
+ var _dec, _class, _TruncateText;
2
2
  /*
3
3
  * The MIT License (MIT)
4
4
  *
@@ -26,21 +26,20 @@ var _dec, _dec2, _dec3, _class, _TruncateText;
26
26
  import { Children, Component } from 'react';
27
27
  import { debounce } from '@instructure/debounce';
28
28
  import { canUseDOM, getBoundingClientRect } from '@instructure/ui-dom-utils';
29
- import { safeCloneElement, ensureSingleChild, hack } from '@instructure/ui-react-utils';
29
+ import { safeCloneElement, ensureSingleChild } from '@instructure/ui-react-utils';
30
30
  import { logError as error } from '@instructure/console';
31
- import { testable } from '@instructure/ui-testable';
32
31
  import { withStyle } from '@instructure/emotion';
33
32
  import generateStyle from './styles';
34
33
  import generateComponentTheme from './theme';
35
34
  import truncate from './utils/truncate';
36
- import { propTypes, allowedProps } from './props';
35
+ import { allowedProps } from './props';
37
36
  import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
38
37
  /**
39
38
  ---
40
39
  category: components
41
40
  ---
42
41
  **/
43
- let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _dec2 = testable(), _dec3 = hack(['shouldTruncateWhenInvisible']), _dec(_class = _dec2(_class = _dec3(_class = (_TruncateText = class TruncateText extends Component {
42
+ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _dec(_class = (_TruncateText = class TruncateText extends Component {
44
43
  constructor(props) {
45
44
  super(props);
46
45
  this.ref = null;
@@ -109,6 +108,19 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
109
108
  this._debounced.cancel();
110
109
  }
111
110
  }
111
+ shallowCompare(obj1, obj2) {
112
+ const keys1 = Object.keys(obj1);
113
+ const keys2 = Object.keys(obj2);
114
+ if (keys1.length !== keys2.length) {
115
+ return false;
116
+ }
117
+ for (const key of keys1) {
118
+ if (obj1[key] !== obj2[key]) {
119
+ return false;
120
+ }
121
+ }
122
+ return true;
123
+ }
112
124
  componentDidUpdate(prevProps) {
113
125
  const _this$props2 = this.props,
114
126
  children = _this$props2.children,
@@ -120,7 +132,9 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
120
132
  needsSecondRender = _this$state.needsSecondRender,
121
133
  truncatedText = _this$state.truncatedText;
122
134
  if (children) {
123
- if (prevProps !== this.props) {
135
+ // for some reason in React 19 prevPros and this.props are a different
136
+ // object even if their contents are the same, so we cannot use !==
137
+ if (!this.shallowCompare(prevProps, this.props)) {
124
138
  if (prevProps.children !== this.props.children) {
125
139
  // reset internal text variable if children change
126
140
  this.checkChildren();
@@ -215,7 +229,7 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
215
229
  style: {
216
230
  width: width || void 0
217
231
  }
218
- }));
232
+ }, "spacer"));
219
233
  const children = Children.map(childElements, child => child);
220
234
  return this._text.props ? safeCloneElement(this._text, this._text.props, children) : children;
221
235
  }
@@ -224,6 +238,7 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
224
238
  const truncatedElement = this.state.truncatedElement;
225
239
  const children = this.props.children;
226
240
  return _jsxs("span", {
241
+ "data-cid": "TruncateText",
227
242
  css: (_this$props$styles3 = this.props.styles) === null || _this$props$styles3 === void 0 ? void 0 : _this$props$styles3.truncateText,
228
243
  ref: el => {
229
244
  this.ref = el;
@@ -236,13 +251,13 @@ let TruncateText = (_dec = withStyle(generateStyle, generateComponentTheme), _de
236
251
  })), truncatedElement]
237
252
  });
238
253
  }
239
- }, _TruncateText.displayName = "TruncateText", _TruncateText.componentId = 'TruncateText', _TruncateText.allowedProps = allowedProps, _TruncateText.propTypes = propTypes, _TruncateText.defaultProps = {
254
+ }, _TruncateText.displayName = "TruncateText", _TruncateText.componentId = 'TruncateText', _TruncateText.allowedProps = allowedProps, _TruncateText.defaultProps = {
240
255
  maxLines: 1,
241
256
  ellipsis: '\u2026',
242
257
  truncate: 'character',
243
258
  position: 'end',
244
259
  ignore: [' ', '.', ','],
245
260
  debounce: 0
246
- }, _TruncateText)) || _class) || _class) || _class);
261
+ }, _TruncateText)) || _class);
247
262
  export default TruncateText;
248
263
  export { TruncateText };
@@ -22,17 +22,5 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import PropTypes from 'prop-types';
26
- const propTypes = {
27
- children: PropTypes.node.isRequired,
28
- maxLines: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
29
- position: PropTypes.oneOf(['end', 'middle']),
30
- truncate: PropTypes.oneOf(['character', 'word']),
31
- ellipsis: PropTypes.string,
32
- ignore: PropTypes.arrayOf(PropTypes.string),
33
- debounce: PropTypes.number,
34
- onUpdate: PropTypes.func,
35
- shouldTruncateWhenInvisible: PropTypes.bool
36
- };
37
25
  const allowedProps = ['children', 'maxLines', 'position', 'truncate', 'ellipsis', 'ignore', 'debounce', 'onUpdate', 'shouldTruncateWhenInvisible'];
38
- export { propTypes, allowedProps };
26
+ export { allowedProps };
@@ -22,7 +22,7 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import { getComputedStyle } from '@instructure/ui-dom-utils';
25
+ import { getCSSStyleDeclaration } from '@instructure/ui-dom-utils';
26
26
 
27
27
  /**
28
28
  * ---
@@ -43,7 +43,7 @@ function measureText(nodes, parentNode) {
43
43
  return width;
44
44
  }
45
45
  function measure(string, domNode) {
46
- const style = getComputedStyle(domNode);
46
+ const style = getCSSStyleDeclaration(domNode);
47
47
  // we use a canvas in a doc fragment to prevent having to render the string full width in the DOM
48
48
  const canvas = document.createElement('canvas');
49
49
  document.createDocumentFragment().appendChild(canvas);
@@ -56,16 +56,16 @@ function measure(string, domNode) {
56
56
  let width = 0;
57
57
  // grab individual font styles
58
58
  // some browsers don't report a value for style['font']
59
- context.font = [style.fontWeight, style.fontStyle, style.fontSize, style.fontFamily].join(' ');
60
- if (style.textTransform === 'uppercase') {
59
+ context.font = [style === null || style === void 0 ? void 0 : style.fontWeight, style === null || style === void 0 ? void 0 : style.fontStyle, style === null || style === void 0 ? void 0 : style.fontSize, style === null || style === void 0 ? void 0 : style.fontFamily].join(' ');
60
+ if ((style === null || style === void 0 ? void 0 : style.textTransform) === 'uppercase') {
61
61
  text = string.toUpperCase();
62
- } else if (style.textTransform === 'lowercase') {
62
+ } else if ((style === null || style === void 0 ? void 0 : style.textTransform) === 'lowercase') {
63
63
  text = string.toLowerCase();
64
- } else if (style.textTransform === 'capitalize') {
64
+ } else if ((style === null || style === void 0 ? void 0 : style.textTransform) === 'capitalize') {
65
65
  text = string.replace(/\b\w/g, str => str.toUpperCase());
66
66
  }
67
- if (style.letterSpacing !== 'normal') {
68
- letterOffset = text.length * parseInt(style.letterSpacing);
67
+ if ((style === null || style === void 0 ? void 0 : style.letterSpacing) !== 'normal') {
68
+ letterOffset = text.length * parseInt(style === null || style === void 0 ? void 0 : style.letterSpacing);
69
69
  }
70
70
  width = context.measureText(text).width + letterOffset;
71
71
  // returns the max potential width of the text, assuming the text was on a single line
@@ -25,7 +25,7 @@
25
25
  import escapeHtml from 'escape-html';
26
26
  import { cloneArray } from '@instructure/ui-utils';
27
27
  import { logError as error } from '@instructure/console';
28
- import { getComputedStyle, getBoundingClientRect, isVisible } from '@instructure/ui-dom-utils';
28
+ import { getCSSStyleDeclaration, getBoundingClientRect, isVisible } from '@instructure/ui-dom-utils';
29
29
  import measureText from './measureText';
30
30
  import cleanString from './cleanString';
31
31
  import cleanData from './cleanData';
@@ -93,11 +93,14 @@ class Truncator {
93
93
  if (!this._stage) {
94
94
  return;
95
95
  }
96
+ const style = getCSSStyleDeclaration(this._parent);
97
+ if (!style) {
98
+ return;
99
+ }
96
100
  const _this$_options = this._options,
97
101
  maxLines = _this$_options.maxLines,
98
102
  truncate = _this$_options.truncate,
99
103
  lineHeight = _this$_options.lineHeight;
100
- const style = getComputedStyle(this._parent);
101
104
  // if no explicit lineHeight is inherited, use lineHeight multiplier for calculations
102
105
  const actualLineHeight = style.lineHeight === 'normal' ? lineHeight * parseFloat(style.fontSize) : parseFloat(style.lineHeight);
103
106
  const node = this._stage.firstChild.children ? this._stage.firstChild : this._stage;
@@ -11,16 +11,14 @@ var _canUseDOM = require("@instructure/ui-dom-utils/lib/canUseDOM.js");
11
11
  var _getBoundingClientRect = require("@instructure/ui-dom-utils/lib/getBoundingClientRect.js");
12
12
  var _safeCloneElement = require("@instructure/ui-react-utils/lib/safeCloneElement.js");
13
13
  var _ensureSingleChild = require("@instructure/ui-react-utils/lib/ensureSingleChild.js");
14
- var _hack = require("@instructure/ui-react-utils/lib/hack.js");
15
14
  var _console = require("@instructure/console");
16
- var _testable = require("@instructure/ui-testable/lib/testable.js");
17
15
  var _emotion = require("@instructure/emotion");
18
16
  var _styles = _interopRequireDefault(require("./styles"));
19
17
  var _theme = _interopRequireDefault(require("./theme"));
20
18
  var _truncate = _interopRequireDefault(require("./utils/truncate"));
21
19
  var _props = require("./props");
22
20
  var _jsxRuntime = require("@emotion/react/jsx-runtime");
23
- var _dec, _dec2, _dec3, _class, _TruncateText;
21
+ var _dec, _class, _TruncateText;
24
22
  /*
25
23
  * The MIT License (MIT)
26
24
  *
@@ -49,7 +47,7 @@ var _dec, _dec2, _dec3, _class, _TruncateText;
49
47
  category: components
50
48
  ---
51
49
  **/
52
- let TruncateText = exports.TruncateText = (_dec = (0, _emotion.withStyle)(_styles.default, _theme.default), _dec2 = (0, _testable.testable)(), _dec3 = (0, _hack.hack)(['shouldTruncateWhenInvisible']), _dec(_class = _dec2(_class = _dec3(_class = (_TruncateText = class TruncateText extends _react.Component {
50
+ let TruncateText = exports.TruncateText = (_dec = (0, _emotion.withStyle)(_styles.default, _theme.default), _dec(_class = (_TruncateText = class TruncateText extends _react.Component {
53
51
  constructor(props) {
54
52
  super(props);
55
53
  this.ref = null;
@@ -118,6 +116,19 @@ let TruncateText = exports.TruncateText = (_dec = (0, _emotion.withStyle)(_style
118
116
  this._debounced.cancel();
119
117
  }
120
118
  }
119
+ shallowCompare(obj1, obj2) {
120
+ const keys1 = Object.keys(obj1);
121
+ const keys2 = Object.keys(obj2);
122
+ if (keys1.length !== keys2.length) {
123
+ return false;
124
+ }
125
+ for (const key of keys1) {
126
+ if (obj1[key] !== obj2[key]) {
127
+ return false;
128
+ }
129
+ }
130
+ return true;
131
+ }
121
132
  componentDidUpdate(prevProps) {
122
133
  const _this$props2 = this.props,
123
134
  children = _this$props2.children,
@@ -129,7 +140,9 @@ let TruncateText = exports.TruncateText = (_dec = (0, _emotion.withStyle)(_style
129
140
  needsSecondRender = _this$state.needsSecondRender,
130
141
  truncatedText = _this$state.truncatedText;
131
142
  if (children) {
132
- if (prevProps !== this.props) {
143
+ // for some reason in React 19 prevPros and this.props are a different
144
+ // object even if their contents are the same, so we cannot use !==
145
+ if (!this.shallowCompare(prevProps, this.props)) {
133
146
  if (prevProps.children !== this.props.children) {
134
147
  // reset internal text variable if children change
135
148
  this.checkChildren();
@@ -224,7 +237,7 @@ let TruncateText = exports.TruncateText = (_dec = (0, _emotion.withStyle)(_style
224
237
  style: {
225
238
  width: width || void 0
226
239
  }
227
- }));
240
+ }, "spacer"));
228
241
  const children = _react.Children.map(childElements, child => child);
229
242
  return this._text.props ? (0, _safeCloneElement.safeCloneElement)(this._text, this._text.props, children) : children;
230
243
  }
@@ -233,6 +246,7 @@ let TruncateText = exports.TruncateText = (_dec = (0, _emotion.withStyle)(_style
233
246
  const truncatedElement = this.state.truncatedElement;
234
247
  const children = this.props.children;
235
248
  return (0, _jsxRuntime.jsxs)("span", {
249
+ "data-cid": "TruncateText",
236
250
  css: (_this$props$styles3 = this.props.styles) === null || _this$props$styles3 === void 0 ? void 0 : _this$props$styles3.truncateText,
237
251
  ref: el => {
238
252
  this.ref = el;
@@ -245,12 +259,12 @@ let TruncateText = exports.TruncateText = (_dec = (0, _emotion.withStyle)(_style
245
259
  })), truncatedElement]
246
260
  });
247
261
  }
248
- }, _TruncateText.displayName = "TruncateText", _TruncateText.componentId = 'TruncateText', _TruncateText.allowedProps = _props.allowedProps, _TruncateText.propTypes = _props.propTypes, _TruncateText.defaultProps = {
262
+ }, _TruncateText.displayName = "TruncateText", _TruncateText.componentId = 'TruncateText', _TruncateText.allowedProps = _props.allowedProps, _TruncateText.defaultProps = {
249
263
  maxLines: 1,
250
264
  ellipsis: '\u2026',
251
265
  truncate: 'character',
252
266
  position: 'end',
253
267
  ignore: [' ', '.', ','],
254
268
  debounce: 0
255
- }, _TruncateText)) || _class) || _class) || _class);
269
+ }, _TruncateText)) || _class);
256
270
  var _default = exports.default = TruncateText;
@@ -1,11 +1,9 @@
1
1
  "use strict";
2
2
 
3
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
3
  Object.defineProperty(exports, "__esModule", {
5
4
  value: true
6
5
  });
7
- exports.propTypes = exports.allowedProps = void 0;
8
- var _propTypes = _interopRequireDefault(require("prop-types"));
6
+ exports.allowedProps = void 0;
9
7
  /*
10
8
  * The MIT License (MIT)
11
9
  *
@@ -30,15 +28,4 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
30
28
  * SOFTWARE.
31
29
  */
32
30
 
33
- const propTypes = exports.propTypes = {
34
- children: _propTypes.default.node.isRequired,
35
- maxLines: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]),
36
- position: _propTypes.default.oneOf(['end', 'middle']),
37
- truncate: _propTypes.default.oneOf(['character', 'word']),
38
- ellipsis: _propTypes.default.string,
39
- ignore: _propTypes.default.arrayOf(_propTypes.default.string),
40
- debounce: _propTypes.default.number,
41
- onUpdate: _propTypes.default.func,
42
- shouldTruncateWhenInvisible: _propTypes.default.bool
43
- };
44
31
  const allowedProps = exports.allowedProps = ['children', 'maxLines', 'position', 'truncate', 'ellipsis', 'ignore', 'debounce', 'onUpdate', 'shouldTruncateWhenInvisible'];
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _getComputedStyle = require("@instructure/ui-dom-utils/lib/getComputedStyle.js");
7
+ var _getCSSStyleDeclaration = require("@instructure/ui-dom-utils/lib/getCSSStyleDeclaration.js");
8
8
  /*
9
9
  * The MIT License (MIT)
10
10
  *
@@ -48,7 +48,7 @@ function measureText(nodes, parentNode) {
48
48
  return width;
49
49
  }
50
50
  function measure(string, domNode) {
51
- const style = (0, _getComputedStyle.getComputedStyle)(domNode);
51
+ const style = (0, _getCSSStyleDeclaration.getCSSStyleDeclaration)(domNode);
52
52
  // we use a canvas in a doc fragment to prevent having to render the string full width in the DOM
53
53
  const canvas = document.createElement('canvas');
54
54
  document.createDocumentFragment().appendChild(canvas);
@@ -61,16 +61,16 @@ function measure(string, domNode) {
61
61
  let width = 0;
62
62
  // grab individual font styles
63
63
  // some browsers don't report a value for style['font']
64
- context.font = [style.fontWeight, style.fontStyle, style.fontSize, style.fontFamily].join(' ');
65
- if (style.textTransform === 'uppercase') {
64
+ context.font = [style === null || style === void 0 ? void 0 : style.fontWeight, style === null || style === void 0 ? void 0 : style.fontStyle, style === null || style === void 0 ? void 0 : style.fontSize, style === null || style === void 0 ? void 0 : style.fontFamily].join(' ');
65
+ if ((style === null || style === void 0 ? void 0 : style.textTransform) === 'uppercase') {
66
66
  text = string.toUpperCase();
67
- } else if (style.textTransform === 'lowercase') {
67
+ } else if ((style === null || style === void 0 ? void 0 : style.textTransform) === 'lowercase') {
68
68
  text = string.toLowerCase();
69
- } else if (style.textTransform === 'capitalize') {
69
+ } else if ((style === null || style === void 0 ? void 0 : style.textTransform) === 'capitalize') {
70
70
  text = string.replace(/\b\w/g, str => str.toUpperCase());
71
71
  }
72
- if (style.letterSpacing !== 'normal') {
73
- letterOffset = text.length * parseInt(style.letterSpacing);
72
+ if ((style === null || style === void 0 ? void 0 : style.letterSpacing) !== 'normal') {
73
+ letterOffset = text.length * parseInt(style === null || style === void 0 ? void 0 : style.letterSpacing);
74
74
  }
75
75
  width = context.measureText(text).width + letterOffset;
76
76
  // returns the max potential width of the text, assuming the text was on a single line
@@ -8,7 +8,7 @@ exports.default = void 0;
8
8
  var _escapeHtml = _interopRequireDefault(require("escape-html"));
9
9
  var _cloneArray = require("@instructure/ui-utils/lib/cloneArray.js");
10
10
  var _console = require("@instructure/console");
11
- var _getComputedStyle = require("@instructure/ui-dom-utils/lib/getComputedStyle.js");
11
+ var _getCSSStyleDeclaration = require("@instructure/ui-dom-utils/lib/getCSSStyleDeclaration.js");
12
12
  var _getBoundingClientRect = require("@instructure/ui-dom-utils/lib/getBoundingClientRect.js");
13
13
  var _isVisible = require("@instructure/ui-dom-utils/lib/isVisible.js");
14
14
  var _measureText = _interopRequireDefault(require("./measureText"));
@@ -102,11 +102,14 @@ class Truncator {
102
102
  if (!this._stage) {
103
103
  return;
104
104
  }
105
+ const style = (0, _getCSSStyleDeclaration.getCSSStyleDeclaration)(this._parent);
106
+ if (!style) {
107
+ return;
108
+ }
105
109
  const _this$_options = this._options,
106
110
  maxLines = _this$_options.maxLines,
107
111
  truncate = _this$_options.truncate,
108
112
  lineHeight = _this$_options.lineHeight;
109
- const style = (0, _getComputedStyle.getComputedStyle)(this._parent);
110
113
  // if no explicit lineHeight is inherited, use lineHeight multiplier for calculations
111
114
  const actualLineHeight = style.lineHeight === 'normal' ? lineHeight * parseFloat(style.fontSize) : parseFloat(style.lineHeight);
112
115
  const node = this._stage.firstChild.children ? this._stage.firstChild : this._stage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-truncate-text",
3
- "version": "10.26.1",
3
+ "version": "11.0.0",
4
4
  "description": "A TruncateText component made by Instructure Inc.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -24,30 +24,28 @@
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "@babel/runtime": "^7.27.6",
27
- "@instructure/console": "10.26.1",
28
- "@instructure/debounce": "10.26.1",
29
- "@instructure/emotion": "10.26.1",
30
- "@instructure/shared-types": "10.26.1",
31
- "@instructure/ui-dom-utils": "10.26.1",
32
- "@instructure/ui-react-utils": "10.26.1",
33
- "@instructure/ui-testable": "10.26.1",
34
- "@instructure/ui-utils": "10.26.1",
35
- "escape-html": "^1.0.3",
36
- "prop-types": "^15.8.1"
27
+ "@instructure/console": "11.0.0",
28
+ "@instructure/debounce": "11.0.0",
29
+ "@instructure/emotion": "11.0.0",
30
+ "@instructure/shared-types": "11.0.0",
31
+ "@instructure/ui-dom-utils": "11.0.0",
32
+ "@instructure/ui-react-utils": "11.0.0",
33
+ "@instructure/ui-utils": "11.0.0",
34
+ "escape-html": "^1.0.3"
37
35
  },
38
36
  "devDependencies": {
39
- "@instructure/ui-axe-check": "10.26.1",
40
- "@instructure/ui-babel-preset": "10.26.1",
41
- "@instructure/ui-color-utils": "10.26.1",
42
- "@instructure/ui-text": "10.26.1",
43
- "@instructure/ui-themes": "10.26.1",
37
+ "@instructure/ui-axe-check": "11.0.0",
38
+ "@instructure/ui-babel-preset": "11.0.0",
39
+ "@instructure/ui-color-utils": "11.0.0",
40
+ "@instructure/ui-text": "11.0.0",
41
+ "@instructure/ui-themes": "11.0.0",
44
42
  "@testing-library/jest-dom": "^6.6.3",
45
- "@testing-library/react": "^16.0.1",
43
+ "@testing-library/react": "15.0.7",
46
44
  "@types/escape-html": "^1.0.4",
47
45
  "vitest": "^3.2.2"
48
46
  },
49
47
  "peerDependencies": {
50
- "react": ">=16.14 <=18"
48
+ "react": ">=18 <=19"
51
49
  },
52
50
  "publishConfig": {
53
51
  "access": "public"
@@ -22,25 +22,23 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import { Children, Component } from 'react'
25
+ import { Children, Component, type JSX } from 'react'
26
26
 
27
27
  import { debounce } from '@instructure/debounce'
28
28
  import type { Debounced } from '@instructure/debounce'
29
29
  import { canUseDOM, getBoundingClientRect } from '@instructure/ui-dom-utils'
30
30
  import {
31
31
  safeCloneElement,
32
- ensureSingleChild,
33
- hack
32
+ ensureSingleChild
34
33
  } from '@instructure/ui-react-utils'
35
34
  import { logError as error } from '@instructure/console'
36
- import { testable } from '@instructure/ui-testable'
37
35
  import { withStyle } from '@instructure/emotion'
38
36
 
39
37
  import generateStyle from './styles'
40
38
  import generateComponentTheme from './theme'
41
39
 
42
40
  import truncate from './utils/truncate'
43
- import { propTypes, allowedProps, TruncateTextState } from './props'
41
+ import { allowedProps, TruncateTextState } from './props'
44
42
  import type { TruncateTextProps } from './props'
45
43
 
46
44
  /**
@@ -49,13 +47,10 @@ category: components
49
47
  ---
50
48
  **/
51
49
  @withStyle(generateStyle, generateComponentTheme)
52
- @testable()
53
- @hack(['shouldTruncateWhenInvisible'])
54
50
  class TruncateText extends Component<TruncateTextProps, TruncateTextState> {
55
51
  static readonly componentId = 'TruncateText'
56
52
 
57
53
  static allowedProps = allowedProps
58
- static propTypes = propTypes
59
54
 
60
55
  static defaultProps = {
61
56
  maxLines: 1,
@@ -142,15 +137,29 @@ class TruncateText extends Component<TruncateTextProps, TruncateTextState> {
142
137
  }
143
138
  }
144
139
 
140
+ shallowCompare(obj1: any, obj2: any) {
141
+ const keys1 = Object.keys(obj1)
142
+ const keys2 = Object.keys(obj2)
143
+ if (keys1.length !== keys2.length) {
144
+ return false
145
+ }
146
+ for (const key of keys1) {
147
+ if (obj1[key] !== obj2[key]) {
148
+ return false
149
+ }
150
+ }
151
+ return true
152
+ }
153
+
145
154
  componentDidUpdate(prevProps: TruncateTextProps) {
146
155
  const { children, onUpdate, makeStyles } = this.props
147
-
148
156
  makeStyles?.()
149
-
150
157
  const { isTruncated, needsSecondRender, truncatedText } = this.state
151
158
 
152
159
  if (children) {
153
- if (prevProps !== this.props) {
160
+ // for some reason in React 19 prevPros and this.props are a different
161
+ // object even if their contents are the same, so we cannot use !==
162
+ if (!this.shallowCompare(prevProps, this.props)) {
154
163
  if (prevProps.children !== this.props.children) {
155
164
  // reset internal text variable if children change
156
165
  this.checkChildren()
@@ -263,6 +272,7 @@ class TruncateText extends Component<TruncateTextProps, TruncateTextState> {
263
272
  <span
264
273
  css={this.props.styles?.spacer}
265
274
  style={{ width: width || undefined }}
275
+ key="spacer" // TODO this is a temp solution so tests on the CI pass... for v11_rc
266
276
  />
267
277
  )
268
278
 
@@ -277,6 +287,7 @@ class TruncateText extends Component<TruncateTextProps, TruncateTextState> {
277
287
  const { children } = this.props
278
288
  return (
279
289
  <span
290
+ data-cid="TruncateText"
280
291
  css={this.props.styles?.truncateText}
281
292
  ref={(el) => {
282
293
  this.ref = el
@@ -23,12 +23,8 @@
23
23
  */
24
24
 
25
25
  import { ReactNode } from 'react'
26
- import PropTypes from 'prop-types'
27
26
 
28
- import type {
29
- PropValidators,
30
- TruncateTextTheme
31
- } from '@instructure/shared-types'
27
+ import type { TruncateTextTheme } from '@instructure/shared-types'
32
28
  import type { WithStyleProps, ComponentStyle } from '@instructure/emotion'
33
29
 
34
30
  type CleanDataOptions = {
@@ -95,19 +91,6 @@ type TruncateTextState = {
95
91
  truncatedElement?: ReactNode
96
92
  truncatedText?: string
97
93
  }
98
-
99
- const propTypes: PropValidators<PropKeys> = {
100
- children: PropTypes.node.isRequired,
101
- maxLines: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
102
- position: PropTypes.oneOf(['end', 'middle']),
103
- truncate: PropTypes.oneOf(['character', 'word']),
104
- ellipsis: PropTypes.string,
105
- ignore: PropTypes.arrayOf(PropTypes.string),
106
- debounce: PropTypes.number,
107
- onUpdate: PropTypes.func,
108
- shouldTruncateWhenInvisible: PropTypes.bool
109
- }
110
-
111
94
  const allowedProps: AllowedPropKeys = [
112
95
  'children',
113
96
  'maxLines',
@@ -127,4 +110,4 @@ export type {
127
110
  TruncateTextState,
128
111
  TruncateTextStyle
129
112
  }
130
- export { propTypes, allowedProps }
113
+ export { allowedProps }
@@ -22,7 +22,7 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import { getComputedStyle } from '@instructure/ui-dom-utils'
25
+ import { getCSSStyleDeclaration } from '@instructure/ui-dom-utils'
26
26
 
27
27
  /**
28
28
  * ---
@@ -44,7 +44,7 @@ function measureText(nodes: Node[], parentNode?: Node) {
44
44
  }
45
45
 
46
46
  function measure(string: string | null, domNode?: Node) {
47
- const style = getComputedStyle(domNode)
47
+ const style = getCSSStyleDeclaration(domNode)
48
48
  // we use a canvas in a doc fragment to prevent having to render the string full width in the DOM
49
49
  const canvas = document.createElement('canvas')
50
50
  document.createDocumentFragment().appendChild(canvas)
@@ -60,22 +60,22 @@ function measure(string: string | null, domNode?: Node) {
60
60
  // grab individual font styles
61
61
  // some browsers don't report a value for style['font']
62
62
  context.font = [
63
- style.fontWeight,
64
- style.fontStyle,
65
- style.fontSize,
66
- style.fontFamily
63
+ style?.fontWeight,
64
+ style?.fontStyle,
65
+ style?.fontSize,
66
+ style?.fontFamily
67
67
  ].join(' ')
68
68
 
69
- if (style.textTransform === 'uppercase') {
69
+ if (style?.textTransform === 'uppercase') {
70
70
  text = string.toUpperCase()
71
- } else if (style.textTransform === 'lowercase') {
71
+ } else if (style?.textTransform === 'lowercase') {
72
72
  text = string.toLowerCase()
73
- } else if (style.textTransform === 'capitalize') {
73
+ } else if (style?.textTransform === 'capitalize') {
74
74
  text = string.replace(/\b\w/g, (str: string) => str.toUpperCase())
75
75
  }
76
76
 
77
- if (style.letterSpacing !== 'normal') {
78
- letterOffset = text.length * parseInt(style.letterSpacing as string)
77
+ if (style?.letterSpacing !== 'normal') {
78
+ letterOffset = text.length * parseInt(style?.letterSpacing as string)
79
79
  }
80
80
 
81
81
  width = context.measureText(text).width + letterOffset
@@ -27,7 +27,7 @@ import escapeHtml from 'escape-html'
27
27
  import { cloneArray } from '@instructure/ui-utils'
28
28
  import { logError as error } from '@instructure/console'
29
29
  import {
30
- getComputedStyle,
30
+ getCSSStyleDeclaration,
31
31
  getBoundingClientRect,
32
32
  isVisible
33
33
  } from '@instructure/ui-dom-utils'
@@ -122,8 +122,13 @@ class Truncator {
122
122
  if (!this._stage) {
123
123
  return
124
124
  }
125
+
126
+ const style = getCSSStyleDeclaration(this._parent)
127
+ if (!style) {
128
+ return
129
+ }
130
+
125
131
  const { maxLines, truncate, lineHeight } = this._options
126
- const style = getComputedStyle(this._parent)
127
132
  // if no explicit lineHeight is inherited, use lineHeight multiplier for calculations
128
133
  const actualLineHeight =
129
134
  style.lineHeight === 'normal'