@samuelbradshaw/highlight-helper-js 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -609,6 +609,7 @@ document.getElementById('copy-html').addEventListener('click', (event) => {
609
609
  ```css
610
610
  @media print {
611
611
  [data-hh-additions] { display: none; } /* Hide SVG highlights and custom drag handles */
612
+ [data-hh-wrapper] { display: none; } /* Hide wrappers */
612
613
  mark[data-hh-highlight-id] { all: unset; } /* Hide mark-element highlights */
613
614
  }
614
615
  ```
@@ -618,5 +619,4 @@ document.getElementById('copy-html').addEventListener('click', (event) => {
618
619
  ```javascript
619
620
  // After a period of inactivity with a locked screen, certain versions of iOS Safari may get into a state where system selection handles don't show when the app tries to select text programmatically (such as when a user taps an existing highlight). Sending a visibilitychange event triggers HighlightHelper.js to run code that enables programmatic text selection again.
620
621
  document.dispatchEvent(new Event('visibilitychange'));
621
- });
622
622
  ```
@@ -409,7 +409,11 @@
409
409
  const height = Math.round(entry.contentBoxSize[0].blockSize);
410
410
  // Respond to width changes (resize window, device rotation) and height changes (lazy-loaded images, dynamically-inserted elements)
411
411
  if (width !== previousWidth || height !== previousHeight) {
412
- if (options.drawingMode === 'svg') this.drawHighlights();
412
+ if (options.drawingMode === 'svg') {
413
+ this.drawHighlights();
414
+ } else {
415
+ this._updateHighlightRects();
416
+ }
413
417
  if (this._previousSelectionRange) this._updateSelectionState();
414
418
  previousWidth = width;
415
419
  previousHeight = height;
@@ -531,13 +535,23 @@
531
535
  startParagraphOffset = Number.parseInt(properties.startParagraphOffset ?? oldHighlightInfo?.startParagraphOffset);
532
536
  endParagraphId = properties.endParagraphId ?? oldHighlightInfo?.endParagraphId;
533
537
  endParagraphOffset = Number.parseInt(properties.endParagraphOffset ?? oldHighlightInfo?.endParagraphOffset);
538
+ if (!this._paragraphIds.includes(startParagraphId) || !this._paragraphIds.includes(endParagraphId)) return;
534
539
  ([ startNode, startOffset ] = this._getTextNodeAndOffset(document.getElementById(startParagraphId), startParagraphOffset, 'start'));
535
540
  ([ endNode, endOffset ] = this._getTextNodeAndOffset(document.getElementById(endParagraphId), endParagraphOffset, 'end'));
536
541
  } else if (adjustedSelectionRange) {
537
542
  startNode = adjustedSelectionRange.startContainer;
538
543
  startOffset = adjustedSelectionRange.startOffset;
544
+ if (this._shouldSkipTextNode(startNode)) {
545
+ startNode = this._getNextValidTextNode(startNode);
546
+ startOffset = 0;
547
+ }
539
548
  endNode = adjustedSelectionRange.endContainer;
540
549
  endOffset = adjustedSelectionRange.endOffset;
550
+ if (this._shouldSkipTextNode(endNode)) {
551
+ endNode = this._getPreviousValidTextNode(endNode);
552
+ endOffset = endNode?.length;
553
+ }
554
+ if (!startNode || !endNode) return;
541
555
  ([ startParagraphId, startParagraphOffset ] = this._getParagraphOffset(startNode, startOffset));
542
556
  ([ endParagraphId, endParagraphOffset ] = this._getParagraphOffset(endNode, endOffset));
543
557
  }
@@ -952,9 +966,9 @@
952
966
  this._additionsRect = this._additionsDiv.getBoundingClientRect();
953
967
  const x = clientX - this._additionsRect.left;
954
968
  const y = clientY - this._additionsRect.top;
955
- for (const highlightId of Object.keys(this._highlightsById)) {
969
+ for (const [highlightId, highlightInfo] of Object.entries(this._highlightsById)) {
956
970
  if (highlightIds.has(highlightId)) continue;
957
- const rangeLineRects = this._highlightsById[highlightId].rangeLineRects;
971
+ const rangeLineRects = highlightInfo.rangeLineRects;
958
972
  if (!rangeLineRects) continue;
959
973
  for (const rect of rangeLineRects) {
960
974
  const padding = rect.height / 8;
@@ -1131,8 +1145,12 @@
1131
1145
  const styleString = this._getStyleString(highlightInfo.style, 'css', false, highlightInfo.variables, highlightInfo.rangeRect);
1132
1146
  if (resolvedMode === 'highlight-api') {
1133
1147
  const colorString = this._options.colorDefs[highlightInfo.color];
1134
- cssText += `${this._containerSelector} ::highlight(${highlightInfo.escapedHighlightId}) { --hh-color: ${colorString}; ${styleString} }\n`;
1135
- cssText += `${this._containerSelector} :is(rt, img)::highlight(${highlightInfo.escapedHighlightId}) { color: inherit; background-color: transparent; }\n`;
1148
+ cssText += `
1149
+ ${this._containerSelector} ::highlight(${highlightInfo.escapedHighlightId}) { all: unset; }
1150
+ ${this._containerSelector} :is(${this._paragraphSelector})::highlight(${highlightInfo.escapedHighlightId}),
1151
+ ${this._containerSelector} :is(${this._paragraphSelector}) *::highlight(${highlightInfo.escapedHighlightId}) { --hh-color: ${colorString}; ${styleString} }
1152
+ ${this._containerSelector} :is(rt, img)::highlight(${highlightInfo.escapedHighlightId}) { color: inherit; background-color: transparent; }
1153
+ `;
1136
1154
  } else {
1137
1155
  cssText += `mark[data-hh-highlight-id=${highlightInfo.escapedHighlightId}][data-hh-style] { ${styleString} }\n`;
1138
1156
  }
@@ -1250,8 +1268,10 @@
1250
1268
  ${this._containerSelector} ::highlight(${highlightInfo.escapedHighlightId}) { all: unset; }
1251
1269
  ${this._containerSelector} mark[data-hh-highlight-id="${activeHighlightId}"][data-hh-style] { all: unset; }
1252
1270
  ${this._containerSelector} [data-hh-wrapper][data-hh-highlight-id="${activeHighlightId}"] { visibility: hidden; }
1253
- ${this._containerSelector} ::selection { --hh-color: ${colorString}; ${styleString} }
1254
- ${this._containerSelector} rt::selection, ${this._containerSelector} img::selection { background-color: transparent; }
1271
+ ${this._containerSelector} ::selection { background-color: transparent; }
1272
+ ${this._containerSelector} :is(${this._paragraphSelector})::selection,
1273
+ ${this._containerSelector} :is(${this._paragraphSelector}) *::selection { --hh-color: ${colorString}; ${styleString} }
1274
+ ${this._containerSelector} :is(rt, img)::selection { background-color: transparent; }
1255
1275
  `);
1256
1276
 
1257
1277
  // No active highlight (show the regular selection UI)
@@ -1268,7 +1288,7 @@
1268
1288
  (options.showDragHandles.includes('touch') || pointerType === 'mouse')
1269
1289
  && (options.showDragHandles.includes('highlights') && activeHighlightId
1270
1290
  || options.showDragHandles.includes('selection') && !activeHighlightId);
1271
- if (selection.type === 'Range' && showDragHandles) {
1291
+ if (selection.type === 'Range' && showDragHandles && rangeLineRects.length > 0) {
1272
1292
  const startNodeIsRtl = globalThis.getComputedStyle(selectionRange.startContainer.parentElement).direction === 'rtl';
1273
1293
  const endNodeIsRtl = globalThis.getComputedStyle(selectionRange.endContainer.parentElement).direction === 'rtl';
1274
1294
  for (const handle of this._dragHandles) {
@@ -1356,7 +1376,7 @@
1356
1376
  }
1357
1377
  if (skipEnd) {
1358
1378
  endNode = this._getPreviousValidTextNode(endNode);
1359
- endOffset = endNode.length;
1379
+ endOffset = endNode?.length;
1360
1380
  }
1361
1381
  if (!startNode || !endNode || (startNode === endNode && startOffset === endOffset)) {
1362
1382
  return range.cloneRange().collapse(true);
@@ -1459,7 +1479,7 @@
1459
1479
  // Determine if text node should be skipped when snapping to word or calculating character offsets
1460
1480
  Highlighter.prototype._shouldSkipTextNode = function (textNode) {
1461
1481
  const parentParagraph = this._closestValidParagraph(textNode.parentNode);
1462
- const ignoreParent = textNode.parentNode.closest('[data-hh-ignore]');
1482
+ const ignoreParent = textNode.parentNode.closest(`${this._containerSelector} [data-hh-ignore]`);
1463
1483
  if (!parentParagraph || ignoreParent || textNode.textContent === '') return true;
1464
1484
  return false;
1465
1485
  }
@@ -1675,7 +1695,7 @@
1675
1695
  range = document.caretRangeFromPoint(clientX, clientY);
1676
1696
  }
1677
1697
  if (!range) return;
1678
- if (range.startContainer.parentElement?.closest('[data-hh-ignore]')) return;
1698
+ if (range.startContainer.parentElement?.closest(`${this._containerSelector} [data-hh-ignore]`)) return;
1679
1699
  if (checkXDistance || checkYDistance) {
1680
1700
  const maxDistance = 30;
1681
1701
  const caretClientRect = range.getBoundingClientRect();
@@ -1737,6 +1757,16 @@
1737
1757
  return { lines, snapTolerance, pseudoElemRects };
1738
1758
  }
1739
1759
 
1760
+ Highlighter.prototype._updateHighlightRects = function (highlightIds = Object.keys(this._highlightsById), paragraphLineRectsCache = new Map(), columnGeometryCache = new Map()) {
1761
+ for (const highlightId of highlightIds) {
1762
+ const highlightInfo = this._highlightsById[highlightId];
1763
+ const rangeParagraphs = highlightInfo.rangeParagraphIds.map(id => document.getElementById(id));
1764
+ const [rangeRect, rangeLineRects] = this._getRangeRects(highlightInfo.rangeObj, rangeParagraphs, paragraphLineRectsCache, columnGeometryCache);
1765
+ highlightInfo.rangeRect = rangeRect;
1766
+ highlightInfo.rangeLineRects = rangeLineRects;
1767
+ }
1768
+ }
1769
+
1740
1770
  // Get merged client rects from the highlight range
1741
1771
  Highlighter.prototype._getRangeRects = function (range, paragraphs, paragraphLineRectsCache = new Map(), columnGeometryCache = new Map()) {
1742
1772
  let rangeRectCoordinates = { top: Number.POSITIVE_INFINITY, bottom: Number.NEGATIVE_INFINITY, left: Number.POSITIVE_INFINITY, right: Number.NEGATIVE_INFINITY };
@@ -1752,6 +1782,7 @@
1752
1782
  }
1753
1783
  const { lines, snapTolerance, pseudoElemRects } = paragraphLineRects;
1754
1784
  // If the previous paragraph is below the current paragraph (such as in multi-column layouts), reset prevParagraphBottom
1785
+ if (lines.length === 0) continue;
1755
1786
  if (prevParagraphBottom > lines[0].bottom) prevParagraphBottom = Number.NEGATIVE_INFINITY;
1756
1787
  const lineStates = lines.map((lineRect, i) => ({
1757
1788
  topY: i === 0 ? prevParagraphBottom : lines[i - 1].bottom,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@samuelbradshaw/highlight-helper-js",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "JavaScript tool that enables highlighting or underlining text in an HTML page.",
5
5
  "type": "module",
6
6
  "main": "highlight-helper.mjs",