@samuelbradshaw/highlight-helper-js 1.0.0 → 1.0.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/README.md +1 -1
- package/highlight-helper.js +45 -11
- package/package.json +1 -1
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
|
```
|
package/highlight-helper.js
CHANGED
|
@@ -272,6 +272,9 @@
|
|
|
272
272
|
this._dragAnchorOffset = this._activeHandle.dataset.hhPosition === 'start' ? selectionRange.endOffset : selectionRange.startOffset;
|
|
273
273
|
this._container.addEventListener('pointermove', respondToHandleDrag, { signal: this._controller.signal });
|
|
274
274
|
this._updateSelectionState();
|
|
275
|
+
|
|
276
|
+
// Check for tap targets so hh:tap can fire if the area around a handle is tapped without dragging (tap result will reset to null if the user starts dragging)
|
|
277
|
+
this._tapResult = this._checkForTapTargets(event);
|
|
275
278
|
}
|
|
276
279
|
// Prevent default drag interaction (which would show a thumbnail and drag selected text)
|
|
277
280
|
return event.preventDefault();
|
|
@@ -409,7 +412,11 @@
|
|
|
409
412
|
const height = Math.round(entry.contentBoxSize[0].blockSize);
|
|
410
413
|
// Respond to width changes (resize window, device rotation) and height changes (lazy-loaded images, dynamically-inserted elements)
|
|
411
414
|
if (width !== previousWidth || height !== previousHeight) {
|
|
412
|
-
if (options.drawingMode === 'svg')
|
|
415
|
+
if (options.drawingMode === 'svg') {
|
|
416
|
+
this.drawHighlights();
|
|
417
|
+
} else {
|
|
418
|
+
this._updateHighlightRects();
|
|
419
|
+
}
|
|
413
420
|
if (this._previousSelectionRange) this._updateSelectionState();
|
|
414
421
|
previousWidth = width;
|
|
415
422
|
previousHeight = height;
|
|
@@ -531,13 +538,23 @@
|
|
|
531
538
|
startParagraphOffset = Number.parseInt(properties.startParagraphOffset ?? oldHighlightInfo?.startParagraphOffset);
|
|
532
539
|
endParagraphId = properties.endParagraphId ?? oldHighlightInfo?.endParagraphId;
|
|
533
540
|
endParagraphOffset = Number.parseInt(properties.endParagraphOffset ?? oldHighlightInfo?.endParagraphOffset);
|
|
541
|
+
if (!this._paragraphIds.includes(startParagraphId) || !this._paragraphIds.includes(endParagraphId)) return;
|
|
534
542
|
([ startNode, startOffset ] = this._getTextNodeAndOffset(document.getElementById(startParagraphId), startParagraphOffset, 'start'));
|
|
535
543
|
([ endNode, endOffset ] = this._getTextNodeAndOffset(document.getElementById(endParagraphId), endParagraphOffset, 'end'));
|
|
536
544
|
} else if (adjustedSelectionRange) {
|
|
537
545
|
startNode = adjustedSelectionRange.startContainer;
|
|
538
546
|
startOffset = adjustedSelectionRange.startOffset;
|
|
547
|
+
if (this._shouldSkipTextNode(startNode)) {
|
|
548
|
+
startNode = this._getNextValidTextNode(startNode);
|
|
549
|
+
startOffset = 0;
|
|
550
|
+
}
|
|
539
551
|
endNode = adjustedSelectionRange.endContainer;
|
|
540
552
|
endOffset = adjustedSelectionRange.endOffset;
|
|
553
|
+
if (this._shouldSkipTextNode(endNode)) {
|
|
554
|
+
endNode = this._getPreviousValidTextNode(endNode);
|
|
555
|
+
endOffset = endNode?.length;
|
|
556
|
+
}
|
|
557
|
+
if (!startNode || !endNode) return;
|
|
541
558
|
([ startParagraphId, startParagraphOffset ] = this._getParagraphOffset(startNode, startOffset));
|
|
542
559
|
([ endParagraphId, endParagraphOffset ] = this._getParagraphOffset(endNode, endOffset));
|
|
543
560
|
}
|
|
@@ -952,9 +969,9 @@
|
|
|
952
969
|
this._additionsRect = this._additionsDiv.getBoundingClientRect();
|
|
953
970
|
const x = clientX - this._additionsRect.left;
|
|
954
971
|
const y = clientY - this._additionsRect.top;
|
|
955
|
-
for (const highlightId of Object.
|
|
972
|
+
for (const [highlightId, highlightInfo] of Object.entries(this._highlightsById)) {
|
|
956
973
|
if (highlightIds.has(highlightId)) continue;
|
|
957
|
-
const rangeLineRects =
|
|
974
|
+
const rangeLineRects = highlightInfo.rangeLineRects;
|
|
958
975
|
if (!rangeLineRects) continue;
|
|
959
976
|
for (const rect of rangeLineRects) {
|
|
960
977
|
const padding = rect.height / 8;
|
|
@@ -1131,8 +1148,12 @@
|
|
|
1131
1148
|
const styleString = this._getStyleString(highlightInfo.style, 'css', false, highlightInfo.variables, highlightInfo.rangeRect);
|
|
1132
1149
|
if (resolvedMode === 'highlight-api') {
|
|
1133
1150
|
const colorString = this._options.colorDefs[highlightInfo.color];
|
|
1134
|
-
cssText +=
|
|
1135
|
-
|
|
1151
|
+
cssText += `
|
|
1152
|
+
${this._containerSelector} ::highlight(${highlightInfo.escapedHighlightId}) { all: unset; }
|
|
1153
|
+
${this._containerSelector} :is(${this._paragraphSelector})::highlight(${highlightInfo.escapedHighlightId}),
|
|
1154
|
+
${this._containerSelector} :is(${this._paragraphSelector}) *::highlight(${highlightInfo.escapedHighlightId}) { --hh-color: ${colorString}; ${styleString} }
|
|
1155
|
+
${this._containerSelector} :is(rt, img)::highlight(${highlightInfo.escapedHighlightId}) { color: inherit; background-color: transparent; }
|
|
1156
|
+
`;
|
|
1136
1157
|
} else {
|
|
1137
1158
|
cssText += `mark[data-hh-highlight-id=${highlightInfo.escapedHighlightId}][data-hh-style] { ${styleString} }\n`;
|
|
1138
1159
|
}
|
|
@@ -1250,8 +1271,10 @@
|
|
|
1250
1271
|
${this._containerSelector} ::highlight(${highlightInfo.escapedHighlightId}) { all: unset; }
|
|
1251
1272
|
${this._containerSelector} mark[data-hh-highlight-id="${activeHighlightId}"][data-hh-style] { all: unset; }
|
|
1252
1273
|
${this._containerSelector} [data-hh-wrapper][data-hh-highlight-id="${activeHighlightId}"] { visibility: hidden; }
|
|
1253
|
-
${this._containerSelector} ::selection {
|
|
1254
|
-
${this._containerSelector}
|
|
1274
|
+
${this._containerSelector} ::selection { background-color: transparent; }
|
|
1275
|
+
${this._containerSelector} :is(${this._paragraphSelector})::selection,
|
|
1276
|
+
${this._containerSelector} :is(${this._paragraphSelector}) *::selection { --hh-color: ${colorString}; ${styleString} }
|
|
1277
|
+
${this._containerSelector} :is(rt, img)::selection { background-color: transparent; }
|
|
1255
1278
|
`);
|
|
1256
1279
|
|
|
1257
1280
|
// No active highlight (show the regular selection UI)
|
|
@@ -1268,7 +1291,7 @@
|
|
|
1268
1291
|
(options.showDragHandles.includes('touch') || pointerType === 'mouse')
|
|
1269
1292
|
&& (options.showDragHandles.includes('highlights') && activeHighlightId
|
|
1270
1293
|
|| options.showDragHandles.includes('selection') && !activeHighlightId);
|
|
1271
|
-
if (selection.type === 'Range' && showDragHandles) {
|
|
1294
|
+
if (selection.type === 'Range' && showDragHandles && rangeLineRects.length > 0) {
|
|
1272
1295
|
const startNodeIsRtl = globalThis.getComputedStyle(selectionRange.startContainer.parentElement).direction === 'rtl';
|
|
1273
1296
|
const endNodeIsRtl = globalThis.getComputedStyle(selectionRange.endContainer.parentElement).direction === 'rtl';
|
|
1274
1297
|
for (const handle of this._dragHandles) {
|
|
@@ -1356,7 +1379,7 @@
|
|
|
1356
1379
|
}
|
|
1357
1380
|
if (skipEnd) {
|
|
1358
1381
|
endNode = this._getPreviousValidTextNode(endNode);
|
|
1359
|
-
endOffset = endNode
|
|
1382
|
+
endOffset = endNode?.length;
|
|
1360
1383
|
}
|
|
1361
1384
|
if (!startNode || !endNode || (startNode === endNode && startOffset === endOffset)) {
|
|
1362
1385
|
return range.cloneRange().collapse(true);
|
|
@@ -1459,7 +1482,7 @@
|
|
|
1459
1482
|
// Determine if text node should be skipped when snapping to word or calculating character offsets
|
|
1460
1483
|
Highlighter.prototype._shouldSkipTextNode = function (textNode) {
|
|
1461
1484
|
const parentParagraph = this._closestValidParagraph(textNode.parentNode);
|
|
1462
|
-
const ignoreParent = textNode.parentNode.closest(
|
|
1485
|
+
const ignoreParent = textNode.parentNode.closest(`${this._containerSelector} [data-hh-ignore]`);
|
|
1463
1486
|
if (!parentParagraph || ignoreParent || textNode.textContent === '') return true;
|
|
1464
1487
|
return false;
|
|
1465
1488
|
}
|
|
@@ -1675,7 +1698,7 @@
|
|
|
1675
1698
|
range = document.caretRangeFromPoint(clientX, clientY);
|
|
1676
1699
|
}
|
|
1677
1700
|
if (!range) return;
|
|
1678
|
-
if (range.startContainer.parentElement?.closest(
|
|
1701
|
+
if (range.startContainer.parentElement?.closest(`${this._containerSelector} [data-hh-ignore]`)) return;
|
|
1679
1702
|
if (checkXDistance || checkYDistance) {
|
|
1680
1703
|
const maxDistance = 30;
|
|
1681
1704
|
const caretClientRect = range.getBoundingClientRect();
|
|
@@ -1737,6 +1760,16 @@
|
|
|
1737
1760
|
return { lines, snapTolerance, pseudoElemRects };
|
|
1738
1761
|
}
|
|
1739
1762
|
|
|
1763
|
+
Highlighter.prototype._updateHighlightRects = function (highlightIds = Object.keys(this._highlightsById), paragraphLineRectsCache = new Map(), columnGeometryCache = new Map()) {
|
|
1764
|
+
for (const highlightId of highlightIds) {
|
|
1765
|
+
const highlightInfo = this._highlightsById[highlightId];
|
|
1766
|
+
const rangeParagraphs = highlightInfo.rangeParagraphIds.map(id => document.getElementById(id));
|
|
1767
|
+
const [rangeRect, rangeLineRects] = this._getRangeRects(highlightInfo.rangeObj, rangeParagraphs, paragraphLineRectsCache, columnGeometryCache);
|
|
1768
|
+
highlightInfo.rangeRect = rangeRect;
|
|
1769
|
+
highlightInfo.rangeLineRects = rangeLineRects;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1740
1773
|
// Get merged client rects from the highlight range
|
|
1741
1774
|
Highlighter.prototype._getRangeRects = function (range, paragraphs, paragraphLineRectsCache = new Map(), columnGeometryCache = new Map()) {
|
|
1742
1775
|
let rangeRectCoordinates = { top: Number.POSITIVE_INFINITY, bottom: Number.NEGATIVE_INFINITY, left: Number.POSITIVE_INFINITY, right: Number.NEGATIVE_INFINITY };
|
|
@@ -1752,6 +1785,7 @@
|
|
|
1752
1785
|
}
|
|
1753
1786
|
const { lines, snapTolerance, pseudoElemRects } = paragraphLineRects;
|
|
1754
1787
|
// If the previous paragraph is below the current paragraph (such as in multi-column layouts), reset prevParagraphBottom
|
|
1788
|
+
if (lines.length === 0) continue;
|
|
1755
1789
|
if (prevParagraphBottom > lines[0].bottom) prevParagraphBottom = Number.NEGATIVE_INFINITY;
|
|
1756
1790
|
const lineStates = lines.map((lineRect, i) => ({
|
|
1757
1791
|
topY: i === 0 ? prevParagraphBottom : lines[i - 1].bottom,
|
package/package.json
CHANGED