@instructure/ui-truncate-text 11.7.4-snapshot-136 → 11.7.4-snapshot-142

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,11 +3,13 @@
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
- ## [11.7.4-snapshot-136](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-136) (2026-07-24)
6
+ ## [11.7.4-snapshot-142](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-142) (2026-07-24)
7
7
 
8
8
 
9
9
  ### Bug Fixes
10
10
 
11
+ * **many:** do not allow javascript: and data: hrefs ([4a0ac03](https://github.com/instructure/instructure-ui/commit/4a0ac038c6a4102646e04d27c1c3251f97367822))
12
+ * **ui-truncate-text,ui-scripts:** replace innerHTML with manual DOM construction ([434d381](https://github.com/instructure/instructure-ui/commit/434d3810892129e9abcff94acd3efea3397e268c))
11
13
  * **ui-truncate-text:** remove lineHeight as a theme input and use the 1.2 as the default ([d4f938f](https://github.com/instructure/instructure-ui/commit/d4f938f2fd3a8618e716987c054f17060a5009e5))
12
14
 
13
15
 
@@ -22,7 +22,6 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import escapeHtml from 'escape-html';
26
25
  import { cloneArray } from '@instructure/ui-utils';
27
26
  import { logError as error } from '@instructure/console';
28
27
  import { getCSSStyleDeclaration, getBoundingClientRect, isVisible } from '@instructure/ui-dom-utils';
@@ -157,32 +156,31 @@ class Truncator {
157
156
  }
158
157
  return nodeDataIndexes;
159
158
  }
160
- domString(data) {
161
- let html = '';
159
+ checkFit(data) {
160
+ const node = this._options.maxLines === 'auto' ? this._stage : this._parent;
161
+ let fits = true;
162
+ while (this._stage.firstChild) {
163
+ this._stage.removeChild(this._stage.firstChild);
164
+ }
162
165
  for (let i = 0; i < data.length; i++) {
163
- const mapItem = this._nodeMap[i];
166
+ const {
167
+ node: mapNode
168
+ } = this._nodeMap[i];
164
169
  const text = data[i].join('');
165
- const safeText = escapeHtml(text);
166
- if (mapItem.node.nodeType === Node.ELEMENT_NODE) {
167
- const name = mapItem.node.nodeName;
168
- const attr = mapItem.node.attributes;
169
- let attributes = '';
170
+ if (mapNode.nodeType === Node.ELEMENT_NODE) {
171
+ const el = document.createElement(mapNode.nodeName);
172
+ const attr = mapNode.attributes;
170
173
  for (let j = 0; j < attr.length; j++) {
171
174
  const att = attr[j];
172
- attributes += ` ${att.nodeName}="${att.nodeValue}"`;
175
+ el.setAttribute(att.nodeName, att.nodeValue ?? '');
173
176
  }
174
- html += `<${name}${attributes}>${safeText}</${name}>`;
175
- } else if (mapItem.node.nodeType === 3) {
176
- html += safeText;
177
+ el.textContent = text;
178
+ this._stage.appendChild(el);
179
+ } else if (mapNode.nodeType === Node.TEXT_NODE) {
180
+ this._stage.appendChild(document.createTextNode(text));
177
181
  }
178
182
  }
179
- return html;
180
- }
181
- checkFit(data) {
182
- const html = this.domString(data);
183
- const node = this._options.maxLines === 'auto' ? this._stage : this._parent;
184
- let fits = true;
185
- this._stage.innerHTML = html;
183
+
186
184
  // allow a 0.5 px margin of error for browser calculation discrepancies
187
185
  if (getBoundingClientRect(node).height - this._maxHeight > 0.5) {
188
186
  fits = false;
@@ -22,7 +22,6 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import escapeHtml from 'escape-html';
26
25
  import { cloneArray } from '@instructure/ui-utils';
27
26
  import { logError as error } from '@instructure/console';
28
27
  import { getCSSStyleDeclaration, getBoundingClientRect, isVisible } from '@instructure/ui-dom-utils';
@@ -157,32 +156,31 @@ class Truncator {
157
156
  }
158
157
  return nodeDataIndexes;
159
158
  }
160
- domString(data) {
161
- let html = '';
159
+ checkFit(data) {
160
+ const node = this._options.maxLines === 'auto' ? this._stage : this._parent;
161
+ let fits = true;
162
+ while (this._stage.firstChild) {
163
+ this._stage.removeChild(this._stage.firstChild);
164
+ }
162
165
  for (let i = 0; i < data.length; i++) {
163
- const mapItem = this._nodeMap[i];
166
+ const {
167
+ node: mapNode
168
+ } = this._nodeMap[i];
164
169
  const text = data[i].join('');
165
- const safeText = escapeHtml(text);
166
- if (mapItem.node.nodeType === Node.ELEMENT_NODE) {
167
- const name = mapItem.node.nodeName;
168
- const attr = mapItem.node.attributes;
169
- let attributes = '';
170
+ if (mapNode.nodeType === Node.ELEMENT_NODE) {
171
+ const el = document.createElement(mapNode.nodeName);
172
+ const attr = mapNode.attributes;
170
173
  for (let j = 0; j < attr.length; j++) {
171
174
  const att = attr[j];
172
- attributes += ` ${att.nodeName}="${att.nodeValue}"`;
175
+ el.setAttribute(att.nodeName, att.nodeValue ?? '');
173
176
  }
174
- html += `<${name}${attributes}>${safeText}</${name}>`;
175
- } else if (mapItem.node.nodeType === 3) {
176
- html += safeText;
177
+ el.textContent = text;
178
+ this._stage.appendChild(el);
179
+ } else if (mapNode.nodeType === Node.TEXT_NODE) {
180
+ this._stage.appendChild(document.createTextNode(text));
177
181
  }
178
182
  }
179
- return html;
180
- }
181
- checkFit(data) {
182
- const html = this.domString(data);
183
- const node = this._options.maxLines === 'auto' ? this._stage : this._parent;
184
- let fits = true;
185
- this._stage.innerHTML = html;
183
+
186
184
  // allow a 0.5 px margin of error for browser calculation discrepancies
187
185
  if (getBoundingClientRect(node).height - this._maxHeight > 0.5) {
188
186
  fits = false;
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.default = void 0;
8
- var _escapeHtml = _interopRequireDefault(require("escape-html"));
9
8
  var _cloneArray = require("@instructure/ui-utils/lib/cloneArray.js");
10
9
  var _console = require("@instructure/console");
11
10
  var _getCSSStyleDeclaration = require("@instructure/ui-dom-utils/lib/getCSSStyleDeclaration.js");
@@ -166,32 +165,31 @@ class Truncator {
166
165
  }
167
166
  return nodeDataIndexes;
168
167
  }
169
- domString(data) {
170
- let html = '';
168
+ checkFit(data) {
169
+ const node = this._options.maxLines === 'auto' ? this._stage : this._parent;
170
+ let fits = true;
171
+ while (this._stage.firstChild) {
172
+ this._stage.removeChild(this._stage.firstChild);
173
+ }
171
174
  for (let i = 0; i < data.length; i++) {
172
- const mapItem = this._nodeMap[i];
175
+ const {
176
+ node: mapNode
177
+ } = this._nodeMap[i];
173
178
  const text = data[i].join('');
174
- const safeText = (0, _escapeHtml.default)(text);
175
- if (mapItem.node.nodeType === Node.ELEMENT_NODE) {
176
- const name = mapItem.node.nodeName;
177
- const attr = mapItem.node.attributes;
178
- let attributes = '';
179
+ if (mapNode.nodeType === Node.ELEMENT_NODE) {
180
+ const el = document.createElement(mapNode.nodeName);
181
+ const attr = mapNode.attributes;
179
182
  for (let j = 0; j < attr.length; j++) {
180
183
  const att = attr[j];
181
- attributes += ` ${att.nodeName}="${att.nodeValue}"`;
184
+ el.setAttribute(att.nodeName, att.nodeValue ?? '');
182
185
  }
183
- html += `<${name}${attributes}>${safeText}</${name}>`;
184
- } else if (mapItem.node.nodeType === 3) {
185
- html += safeText;
186
+ el.textContent = text;
187
+ this._stage.appendChild(el);
188
+ } else if (mapNode.nodeType === Node.TEXT_NODE) {
189
+ this._stage.appendChild(document.createTextNode(text));
186
190
  }
187
191
  }
188
- return html;
189
- }
190
- checkFit(data) {
191
- const html = this.domString(data);
192
- const node = this._options.maxLines === 'auto' ? this._stage : this._parent;
193
- let fits = true;
194
- this._stage.innerHTML = html;
192
+
195
193
  // allow a 0.5 px margin of error for browser calculation discrepancies
196
194
  if ((0, _getBoundingClientRect.getBoundingClientRect)(node).height - this._maxHeight > 0.5) {
197
195
  fits = false;
@@ -5,7 +5,6 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.default = void 0;
8
- var _escapeHtml = _interopRequireDefault(require("escape-html"));
9
8
  var _cloneArray = require("@instructure/ui-utils/lib/cloneArray.js");
10
9
  var _console = require("@instructure/console");
11
10
  var _getCSSStyleDeclaration = require("@instructure/ui-dom-utils/lib/getCSSStyleDeclaration.js");
@@ -166,32 +165,31 @@ class Truncator {
166
165
  }
167
166
  return nodeDataIndexes;
168
167
  }
169
- domString(data) {
170
- let html = '';
168
+ checkFit(data) {
169
+ const node = this._options.maxLines === 'auto' ? this._stage : this._parent;
170
+ let fits = true;
171
+ while (this._stage.firstChild) {
172
+ this._stage.removeChild(this._stage.firstChild);
173
+ }
171
174
  for (let i = 0; i < data.length; i++) {
172
- const mapItem = this._nodeMap[i];
175
+ const {
176
+ node: mapNode
177
+ } = this._nodeMap[i];
173
178
  const text = data[i].join('');
174
- const safeText = (0, _escapeHtml.default)(text);
175
- if (mapItem.node.nodeType === Node.ELEMENT_NODE) {
176
- const name = mapItem.node.nodeName;
177
- const attr = mapItem.node.attributes;
178
- let attributes = '';
179
+ if (mapNode.nodeType === Node.ELEMENT_NODE) {
180
+ const el = document.createElement(mapNode.nodeName);
181
+ const attr = mapNode.attributes;
179
182
  for (let j = 0; j < attr.length; j++) {
180
183
  const att = attr[j];
181
- attributes += ` ${att.nodeName}="${att.nodeValue}"`;
184
+ el.setAttribute(att.nodeName, att.nodeValue ?? '');
182
185
  }
183
- html += `<${name}${attributes}>${safeText}</${name}>`;
184
- } else if (mapItem.node.nodeType === 3) {
185
- html += safeText;
186
+ el.textContent = text;
187
+ this._stage.appendChild(el);
188
+ } else if (mapNode.nodeType === Node.TEXT_NODE) {
189
+ this._stage.appendChild(document.createTextNode(text));
186
190
  }
187
191
  }
188
- return html;
189
- }
190
- checkFit(data) {
191
- const html = this.domString(data);
192
- const node = this._options.maxLines === 'auto' ? this._stage : this._parent;
193
- let fits = true;
194
- this._stage.innerHTML = html;
192
+
195
193
  // allow a 0.5 px margin of error for browser calculation discrepancies
196
194
  if ((0, _getBoundingClientRect.getBoundingClientRect)(node).height - this._maxHeight > 0.5) {
197
195
  fits = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-truncate-text",
3
- "version": "11.7.4-snapshot-136",
3
+ "version": "11.7.4-snapshot-142",
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",
@@ -15,25 +15,23 @@
15
15
  "license": "MIT",
16
16
  "dependencies": {
17
17
  "@babel/runtime": "^7.29.7",
18
- "escape-html": "^1.0.3",
19
- "@instructure/console": "11.7.4-snapshot-136",
20
- "@instructure/shared-types": "11.7.4-snapshot-136",
21
- "@instructure/debounce": "11.7.4-snapshot-136",
22
- "@instructure/emotion": "11.7.4-snapshot-136",
23
- "@instructure/ui-dom-utils": "11.7.4-snapshot-136",
24
- "@instructure/ui-react-utils": "11.7.4-snapshot-136",
25
- "@instructure/ui-themes": "11.7.4-snapshot-136",
26
- "@instructure/ui-utils": "11.7.4-snapshot-136"
18
+ "@instructure/debounce": "11.7.4-snapshot-142",
19
+ "@instructure/console": "11.7.4-snapshot-142",
20
+ "@instructure/emotion": "11.7.4-snapshot-142",
21
+ "@instructure/shared-types": "11.7.4-snapshot-142",
22
+ "@instructure/ui-dom-utils": "11.7.4-snapshot-142",
23
+ "@instructure/ui-react-utils": "11.7.4-snapshot-142",
24
+ "@instructure/ui-themes": "11.7.4-snapshot-142",
25
+ "@instructure/ui-utils": "11.7.4-snapshot-142"
27
26
  },
28
27
  "devDependencies": {
29
28
  "@testing-library/jest-dom": "^6.9.1",
30
29
  "@testing-library/react": "16.3.2",
31
- "@types/escape-html": "^1.0.4",
32
30
  "vitest": "^4.1.9",
33
- "@instructure/ui-babel-preset": "11.7.4-snapshot-136",
34
- "@instructure/ui-axe-check": "11.7.4-snapshot-136",
35
- "@instructure/ui-text": "11.7.4-snapshot-136",
36
- "@instructure/ui-color-utils": "11.7.4-snapshot-136"
31
+ "@instructure/ui-axe-check": "11.7.4-snapshot-142",
32
+ "@instructure/ui-color-utils": "11.7.4-snapshot-142",
33
+ "@instructure/ui-babel-preset": "11.7.4-snapshot-142",
34
+ "@instructure/ui-text": "11.7.4-snapshot-142"
37
35
  },
38
36
  "peerDependencies": {
39
37
  "react": ">=18 <=19"
@@ -22,8 +22,6 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import escapeHtml from 'escape-html'
26
-
27
25
  import { cloneArray } from '@instructure/ui-utils'
28
26
  import { logError as error } from '@instructure/console'
29
27
  import {
@@ -210,35 +208,32 @@ class Truncator {
210
208
  return nodeDataIndexes
211
209
  }
212
210
 
213
- domString(data: string[][]) {
214
- let html = ''
211
+ checkFit(data: string[][]) {
212
+ const node = this._options.maxLines === 'auto' ? this._stage : this._parent
213
+ let fits = true
214
+
215
+ while (this._stage.firstChild) {
216
+ this._stage.removeChild(this._stage.firstChild)
217
+ }
215
218
 
216
219
  for (let i = 0; i < data.length; i++) {
217
- const mapItem = this._nodeMap[i]
220
+ const { node: mapNode } = this._nodeMap[i]
218
221
  const text = data[i].join('')
219
- const safeText = escapeHtml(text)
220
222
 
221
- if (mapItem.node.nodeType === Node.ELEMENT_NODE) {
222
- const name = mapItem.node.nodeName
223
- const attr = (mapItem.node as Element).attributes
224
- let attributes = ''
223
+ if (mapNode.nodeType === Node.ELEMENT_NODE) {
224
+ const el = document.createElement(mapNode.nodeName)
225
+ const attr = (mapNode as Element).attributes
225
226
  for (let j = 0; j < attr.length; j++) {
226
227
  const att = attr[j]
227
- attributes += ` ${att.nodeName}="${att.nodeValue}"`
228
+ el.setAttribute(att.nodeName, att.nodeValue ?? '')
228
229
  }
229
- html += `<${name}${attributes}>${safeText}</${name}>`
230
- } else if (mapItem.node.nodeType === 3) {
231
- html += safeText
230
+ el.textContent = text
231
+ this._stage.appendChild(el)
232
+ } else if (mapNode.nodeType === Node.TEXT_NODE) {
233
+ this._stage.appendChild(document.createTextNode(text))
232
234
  }
233
235
  }
234
- return html
235
- }
236
236
 
237
- checkFit(data: string[][]) {
238
- const html = this.domString(data)
239
- const node = this._options.maxLines === 'auto' ? this._stage : this._parent
240
- let fits = true
241
- this._stage.innerHTML = html
242
237
  // allow a 0.5 px margin of error for browser calculation discrepancies
243
238
  if (getBoundingClientRect(node).height - this._maxHeight > 0.5) {
244
239
  fits = false
@@ -22,8 +22,6 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import escapeHtml from 'escape-html'
26
-
27
25
  import { cloneArray } from '@instructure/ui-utils'
28
26
  import { logError as error } from '@instructure/console'
29
27
  import {
@@ -210,35 +208,32 @@ class Truncator {
210
208
  return nodeDataIndexes
211
209
  }
212
210
 
213
- domString(data: string[][]) {
214
- let html = ''
211
+ checkFit(data: string[][]) {
212
+ const node = this._options.maxLines === 'auto' ? this._stage : this._parent
213
+ let fits = true
214
+
215
+ while (this._stage.firstChild) {
216
+ this._stage.removeChild(this._stage.firstChild)
217
+ }
215
218
 
216
219
  for (let i = 0; i < data.length; i++) {
217
- const mapItem = this._nodeMap[i]
220
+ const { node: mapNode } = this._nodeMap[i]
218
221
  const text = data[i].join('')
219
- const safeText = escapeHtml(text)
220
222
 
221
- if (mapItem.node.nodeType === Node.ELEMENT_NODE) {
222
- const name = mapItem.node.nodeName
223
- const attr = (mapItem.node as Element).attributes
224
- let attributes = ''
223
+ if (mapNode.nodeType === Node.ELEMENT_NODE) {
224
+ const el = document.createElement(mapNode.nodeName)
225
+ const attr = (mapNode as Element).attributes
225
226
  for (let j = 0; j < attr.length; j++) {
226
227
  const att = attr[j]
227
- attributes += ` ${att.nodeName}="${att.nodeValue}"`
228
+ el.setAttribute(att.nodeName, att.nodeValue ?? '')
228
229
  }
229
- html += `<${name}${attributes}>${safeText}</${name}>`
230
- } else if (mapItem.node.nodeType === 3) {
231
- html += safeText
230
+ el.textContent = text
231
+ this._stage.appendChild(el)
232
+ } else if (mapNode.nodeType === Node.TEXT_NODE) {
233
+ this._stage.appendChild(document.createTextNode(text))
232
234
  }
233
235
  }
234
- return html
235
- }
236
236
 
237
- checkFit(data: string[][]) {
238
- const html = this.domString(data)
239
- const node = this._options.maxLines === 'auto' ? this._stage : this._parent
240
- let fits = true
241
- this._stage.innerHTML = html
242
237
  // allow a 0.5 px margin of error for browser calculation discrepancies
243
238
  if (getBoundingClientRect(node).height - this._maxHeight > 0.5) {
244
239
  fits = false