@samuelbradshaw/highlight-helper-js 1.0.3 → 1.1.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/README.md +15 -12
- package/highlight-helper.js +76 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ You can also install it using [npm](https://www.npmjs.com/package/@samuelbradsha
|
|
|
122
122
|
- **loadHighlights(highlights)** – Load a set of highlights. If highlights are already loaded, this method will diff for changes and replace the loaded highlights. Parameters:
|
|
123
123
|
- **highlights** – Array of [highlight objects](#highlight-objects). Each highlight should have a `highlightId`, and one or more other attributes. Omitted or invalid properties will fall back to previous or default values.
|
|
124
124
|
- **createOrUpdateHighlight(properties, draw = true, activate = true)** – Create and load a new highlight, or update an existing highlight. Return value: updated highlight object. Parameters:
|
|
125
|
-
- **properties** – Object with one or more [highlight object](#highlight-objects) properties. When updating an existing highlight, only the `highlightId` and properties that changed need to be provided. If a highlight ID isn't provided, HighlightHelper.js will update the currently-active highlight or create a new highlight with a default ID.
|
|
125
|
+
- **properties** – Object with one or more [highlight object](#highlight-objects) properties. When updating an existing highlight, only the `highlightId` and properties that changed need to be provided. If a highlight ID isn't provided, HighlightHelper.js will update the currently-active highlight or create a new highlight with a default ID. New highlights and the active highlight use the text selection for their range, unless a range is provided. Other highlights keep their range.
|
|
126
126
|
- **draw** – Boolean. Indicates if the highlight should be drawn after loading. Default: `true`.
|
|
127
127
|
- **activate** – Boolean. Indicates if the highlight should be activated after loading. Default: `true`.
|
|
128
128
|
- **drawHighlights(highlightIds = all)** – Draw or redraw highlights. In most cases, HighlightHelper.js will draw highlights automatically, but a manual redraw can be triggered if needed. Parameters:
|
|
@@ -165,6 +165,7 @@ HighlightHelper.js sends [custom events](https://developer.mozilla.org/en-US/doc
|
|
|
165
165
|
- **hh:highlightactivate** – Sent when a highlight is activated.
|
|
166
166
|
- **hh:highlightdeactivate** – Sent when a highlight is deactivated.
|
|
167
167
|
- **hh:highlightremove** – Sent when a highlight is removed.
|
|
168
|
+
- **hh:layoutchange** – Sent when highlights redraw because of container size changes.
|
|
168
169
|
|
|
169
170
|
Each event has a `detail` property that provides additional information. For example, the `hh:tap` event could be used to activate a highlight:
|
|
170
171
|
|
|
@@ -221,9 +222,9 @@ The following options can be set using the `setOptions()` method. Options that a
|
|
|
221
222
|
- **right** – HTML string for the right selection handle.
|
|
222
223
|
- Template variables (optional): `var(--hh-color)` can be used to reference the highlight color.
|
|
223
224
|
- **showDragHandles** – Array. Indicates when custom drag handles should be shown. Default: `['highlights']`. Possible values:
|
|
224
|
-
- **highlights** – Show custom handles for active highlights.
|
|
225
|
-
- **selection** – Show custom handles for text selection.
|
|
226
|
-
- **touch** – Show custom handles on touch devices (may
|
|
225
|
+
- **highlights** – Show custom handles for active highlights.
|
|
226
|
+
- **selection** – Show custom handles for text selection.
|
|
227
|
+
- **touch** – Show custom handles on touch devices (may conflict with built-in selection handles).
|
|
227
228
|
- **colorDefs** – Object. Defines highlight colors. Keys are color names, and values are [CSS color values](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). Default keys: `red`, `orange`, `yellow`, `green`, `blue`.
|
|
228
229
|
- **styleDefs** – Object. Defines highlight style templates. Keys are style names. Default keys: `fill`, `underline`. Each style has four available properties:
|
|
229
230
|
- **css** (required) – CSS string used in `highlight-api` and `mark-elements` drawing modes, for active text selection, and as a fallback in `svg` drawing mode.
|
|
@@ -279,19 +280,21 @@ Other:
|
|
|
279
280
|
### <a name="known-issues"></a>Known issues
|
|
280
281
|
|
|
281
282
|
- **Chrome on Android** doesn't show text selection handles when text is selected programmatically (such as from a disambiguation panel, or with snap-to-word). Text selection UI must be initiated by a user gesture.
|
|
282
|
-
- Workaround: Tap the highlight again to show the handles.
|
|
283
|
-
- **Safari on iOS and macOS** doesn't allow
|
|
283
|
+
- Workaround: Tap the highlight again to show the handles, or enable custom drag handles.
|
|
284
|
+
- **Safari on iOS and macOS** doesn't allow full control of the underline style on a `::highlight()` pseudo-element, so underline highlights drawn by the CSS Custom Highlight API may look incorrect (see [StackOverflow](https://stackoverflow.com/q/79060854/1349044) and [WebKit Bugzilla](https://bugs.webkit.org/show_bug.cgi?id=282027)).
|
|
284
285
|
- Workaround: Use a different drawing mode.
|
|
286
|
+
- **Safari on iOS and macOS** ignores text decoration styles on `::selection`, so active underline highlights can't be styled with CSS alone.
|
|
287
|
+
- Workaround: Set `svg` and `svgActive` for underline styles in styleDefs. On Safari, if a style has an `svg` template, HighlightHelper.js draws the active highlight with `svgActive` (or `svg` if `svgActive` isn't set), in any drawing mode.
|
|
288
|
+
- **Safari on iOS** ignores all `::selection` styles, so active highlights show the native blue selection overlay instead of the highlight style.
|
|
289
|
+
- Workaround: Set `svg` and `svgActive` for all styles in styleDefs. Alternatively, in a native app with a webview, you may be able to set colors natively. See [tintColor](https://stackoverflow.com/a/60510743/1349044), [highlightView](https://developer.apple.com/documentation/uikit/uitextselectiondisplayinteraction/4195471-highlightview), and/or [handleViews](https://developer.apple.com/documentation/uikit/uitextselectiondisplayinteraction/4195470-handleviews).
|
|
285
290
|
- **Safari on iOS and macOS** expand text selection to include the previous word when selecting a word in an inline element at the beginning of the line of text. Code to reproduce: `<p style="font-family: Times; font-size: 16px; width: 250px">This is an example of <i>text selection</i> <b>jumping</b> to include the previous word when text wraps at the beginning of an inline element. Double-click (macOS) or long-press (iOS) the word "jumping."</p>`
|
|
286
291
|
- Workaround: Manually adjust highlight after it's created.
|
|
287
|
-
- **Safari on iOS** doesn't respect text selection colors set via CSS, so when a highlight is active for editing it has a blue overlay.
|
|
288
|
-
- Workaround: In a native app with a webview, you may be able to set colors natively. See [tintColor](https://stackoverflow.com/a/60510743/1349044), [highlightView](https://developer.apple.com/documentation/uikit/uitextselectiondisplayinteraction/4195471-highlightview), and/or [handleViews](https://developer.apple.com/documentation/uikit/uitextselectiondisplayinteraction/4195470-handleviews).
|
|
289
292
|
- **Safari on iOS 26** doesn't create a text selection when using an Apple Pencil (or other compatible stylus), until the stylus is lifted.
|
|
290
293
|
- Workaround: Double-tap then drag the stylus.
|
|
291
294
|
- **Safari on macOS** – SVG highlights are slightly shifted when zoomed to the maximum zoom size.
|
|
292
295
|
- Workaround: Reduce text size.
|
|
293
|
-
- **Amazon Fire webview** doesn’t show selection handles, making it difficult to resize an existing highlight
|
|
294
|
-
- Workaround: Long-press and drag to select everything you need before lifting your finger.
|
|
296
|
+
- **Amazon Fire webview** doesn’t show selection handles, making it difficult to resize an existing highlight.
|
|
297
|
+
- Workaround: Long-press and drag to select everything you need before lifting your finger, or enable custom drag handles.
|
|
295
298
|
|
|
296
299
|
|
|
297
300
|
### <a name="choosing-a-drawing-mode"></a>Choosing a drawing mode
|
|
@@ -449,7 +452,7 @@ mark[data-hh-highlight-id][data-hh-style="fill"][data-hh-position~="end"] {
|
|
|
449
452
|
border-bottom-right-radius: 0.25em;
|
|
450
453
|
margin-right: -0.13em; padding-right: 0.13em;
|
|
451
454
|
}
|
|
452
|
-
mark[data-hh-highlight-id][data-hh-style="fill"] + mark[hh-highlight-id][data-hh-style="fill"] {
|
|
455
|
+
mark[data-hh-highlight-id][data-hh-style="fill"] + mark[data-hh-highlight-id][data-hh-style="fill"] {
|
|
453
456
|
margin-left: 0; padding-left: 0;
|
|
454
457
|
}
|
|
455
458
|
```
|
|
@@ -610,7 +613,7 @@ document.getElementById('copy-html').addEventListener('click', (event) => {
|
|
|
610
613
|
@media print {
|
|
611
614
|
[data-hh-additions] { display: none; } /* Hide SVG highlights and custom drag handles */
|
|
612
615
|
[data-hh-wrapper] { display: none; } /* Hide wrappers */
|
|
613
|
-
mark[data-hh-highlight-id] { all: unset; } /* Hide mark-element highlights */
|
|
616
|
+
mark[data-hh-highlight-id][data-hh-style] { all: unset; } /* Hide mark-element highlights */
|
|
614
617
|
}
|
|
615
618
|
```
|
|
616
619
|
|
package/highlight-helper.js
CHANGED
|
@@ -124,6 +124,7 @@
|
|
|
124
124
|
position: absolute;
|
|
125
125
|
width: 0px;
|
|
126
126
|
visibility: hidden;
|
|
127
|
+
touch-action: none;
|
|
127
128
|
}
|
|
128
129
|
[data-hh-pointer-down="true"] [data-hh-handle] {
|
|
129
130
|
/* Hiding selection handle reduces jumpiness when dragging (and makes it easier to see the selection) */
|
|
@@ -136,13 +137,13 @@
|
|
|
136
137
|
[data-hh-handle] [draggable] {
|
|
137
138
|
position: absolute;
|
|
138
139
|
top: -0.3em;
|
|
139
|
-
width:
|
|
140
|
-
height: calc(100% + 1.
|
|
140
|
+
width: 2.2em;
|
|
141
|
+
height: calc(100% + 1.8em);
|
|
141
142
|
background-color: transparent;
|
|
142
143
|
z-index: 1;
|
|
143
144
|
}
|
|
144
|
-
[data-hh-handle][data-hh-side="left"] [draggable] { right: -
|
|
145
|
-
[data-hh-handle][data-hh-side="right"] [draggable] { left: -
|
|
145
|
+
[data-hh-handle][data-hh-side="left"] [draggable] { right: -0.4em; }
|
|
146
|
+
[data-hh-handle][data-hh-side="right"] [draggable] { left: -0.4em; }
|
|
146
147
|
[data-hh-default-handle] {
|
|
147
148
|
position: absolute;
|
|
148
149
|
width: 0.8em;
|
|
@@ -177,7 +178,8 @@
|
|
|
177
178
|
fill: transparent;
|
|
178
179
|
stroke: none;
|
|
179
180
|
}
|
|
180
|
-
mark[data-hh-highlight-id] {
|
|
181
|
+
mark[data-hh-highlight-id]:where(${this._containerSelector} *) {
|
|
182
|
+
/* Scoped to the container, so another Highlighter instance's base rule can't override this instance's highlight styles */
|
|
181
183
|
print-color-adjust: exact;
|
|
182
184
|
background-color: transparent;
|
|
183
185
|
color: inherit;
|
|
@@ -397,6 +399,7 @@
|
|
|
397
399
|
// Hyperlink click (for each hyperlink in container)
|
|
398
400
|
for (const hyperlinkElement of this._hyperlinkElements) {
|
|
399
401
|
hyperlinkElement.addEventListener('click', (event) => {
|
|
402
|
+
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
400
403
|
this.deactivateHighlights();
|
|
401
404
|
if (!this._allowHyperlinkClick) event.preventDefault();
|
|
402
405
|
}, { signal: this._controller.signal });
|
|
@@ -420,6 +423,7 @@
|
|
|
420
423
|
if (this._previousSelectionRange) this._updateSelectionState();
|
|
421
424
|
previousWidth = width;
|
|
422
425
|
previousHeight = height;
|
|
426
|
+
this._container.dispatchEvent(new CustomEvent('hh:layoutchange', { detail: { width, height } }));
|
|
423
427
|
}
|
|
424
428
|
}
|
|
425
429
|
}, Math.floor(Object.keys(this._highlightsById).length / 20)));
|
|
@@ -428,6 +432,7 @@
|
|
|
428
432
|
// Set drawing mode to mark elements temporarily when printing (highlights get offset in SVG drawing mode)
|
|
429
433
|
let previousDrawingMode = null;
|
|
430
434
|
globalThis.addEventListener('beforeprint', () => {
|
|
435
|
+
if (this._activeHighlightId) this.deactivateHighlights();
|
|
431
436
|
previousDrawingMode = this._options.drawingMode;
|
|
432
437
|
if (previousDrawingMode === 'mark-elements') return;
|
|
433
438
|
this.setOptions({ drawingMode: 'mark-elements' });
|
|
@@ -533,9 +538,15 @@
|
|
|
533
538
|
let startParagraphId, startParagraphOffset, endParagraphId, endParagraphOffset;
|
|
534
539
|
const selection = this._getRestoredSelectionOrCaret(globalThis.getSelection());
|
|
535
540
|
if (selection.type === 'Range') adjustedSelectionRange = this._snapRangeToBoundaries(selection.getRangeAt(0));
|
|
536
|
-
|
|
541
|
+
const hasBoundsProperties = ['startParagraphId', 'startParagraphOffset', 'endParagraphId', 'endParagraphOffset'].some(key => properties[key] != null);
|
|
542
|
+
// New highlights and the active highlight take their bounds from the text selection. Other highlights keep their saved bounds, unless new bounds are provided (otherwise, updating an inactive highlight would move it to the text selection, or do nothing if there isn't one).
|
|
543
|
+
const useSelectionBounds = !hasBoundsProperties && adjustedSelectionRange && !adjustedSelectionRange.collapsed && (isNewHighlight || highlightId === this._activeHighlightId);
|
|
544
|
+
if (!isNewHighlight && !hasBoundsProperties && !useSelectionBounds) {
|
|
545
|
+
// Only appearance properties changed, so reuse the saved range instead of recalculating it
|
|
546
|
+
highlightRange = this._getCorrectedRangeObj(highlightId);
|
|
547
|
+
} else if (hasBoundsProperties || useSelectionBounds) {
|
|
537
548
|
let startNode, startOffset, endNode, endOffset;
|
|
538
|
-
if (
|
|
549
|
+
if (!useSelectionBounds) {
|
|
539
550
|
startParagraphId = properties.startParagraphId ?? oldHighlightInfo?.startParagraphId;
|
|
540
551
|
startParagraphOffset = Number.parseInt(properties.startParagraphOffset ?? oldHighlightInfo?.startParagraphOffset);
|
|
541
552
|
endParagraphId = properties.endParagraphId ?? oldHighlightInfo?.endParagraphId;
|
|
@@ -543,7 +554,7 @@
|
|
|
543
554
|
if (!this._paragraphIds.includes(startParagraphId) || !this._paragraphIds.includes(endParagraphId)) return;
|
|
544
555
|
([ startNode, startOffset ] = this._getTextNodeAndOffset(document.getElementById(startParagraphId), startParagraphOffset, 'start'));
|
|
545
556
|
([ endNode, endOffset ] = this._getTextNodeAndOffset(document.getElementById(endParagraphId), endParagraphOffset, 'end'));
|
|
546
|
-
} else
|
|
557
|
+
} else {
|
|
547
558
|
startNode = adjustedSelectionRange.startContainer;
|
|
548
559
|
startOffset = adjustedSelectionRange.startOffset;
|
|
549
560
|
if (this._shouldSkipTextNode(startNode)) {
|
|
@@ -883,9 +894,19 @@
|
|
|
883
894
|
return;
|
|
884
895
|
}
|
|
885
896
|
const selection = globalThis.getSelection();
|
|
886
|
-
|
|
897
|
+
|
|
898
|
+
// Switch the active highlight before redrawing the previously active one, so there's no in-between state with no active highlight (an hh:selectionchange event in that state could lead an app to create a new highlight over the previous one)
|
|
899
|
+
const previousHighlight = this._highlightsById[this._activeHighlightId];
|
|
887
900
|
this._activeHighlightId = highlightId;
|
|
888
901
|
|
|
902
|
+
// Redraw the previously active highlight. In mark-elements mode, this changes the DOM, so get the new highlight's range afterward.
|
|
903
|
+
if (previousHighlight) this.drawHighlights([previousHighlight.highlightId]);
|
|
904
|
+
const highlightRange = this._getCorrectedRangeObj(highlightId);
|
|
905
|
+
|
|
906
|
+
// Save the highlight's range as the previous selection range. Otherwise, a pending selectionchange event (such as one from the tap that led to this highlight being activated) is compared against a stale range, which can deactivate the highlight or restore a different selection range.
|
|
907
|
+
this._previousSelectionRange = highlightRange.cloneRange();
|
|
908
|
+
this._previousSelectionDirection = 'forward';
|
|
909
|
+
|
|
889
910
|
// Update the selection range if needed
|
|
890
911
|
// In Android Chrome, sometimes selection handles don't show when a selection is updated programmatically, so it's best to only update the selection if needed.
|
|
891
912
|
let selectionRange = selection.type === 'Range' ? selection.getRangeAt(0) : null;
|
|
@@ -895,6 +916,7 @@
|
|
|
895
916
|
}
|
|
896
917
|
|
|
897
918
|
this._updateSelectionState();
|
|
919
|
+
if (previousHighlight) this._container.dispatchEvent(new CustomEvent('hh:highlightdeactivate', { detail: { highlight: previousHighlight } }));
|
|
898
920
|
this._container.dispatchEvent(new CustomEvent('hh:highlightactivate', { detail: { highlight: highlightToActivate } }));
|
|
899
921
|
}
|
|
900
922
|
|
|
@@ -1151,13 +1173,14 @@
|
|
|
1151
1173
|
if (resolvedMode === 'highlight-api') {
|
|
1152
1174
|
const colorString = this._options.colorDefs[highlightInfo.color];
|
|
1153
1175
|
cssText += `
|
|
1154
|
-
${this._containerSelector} ::highlight(${highlightInfo.escapedHighlightId}) { all: unset; }
|
|
1155
|
-
${this._containerSelector} :is(${this._paragraphSelector})::highlight(${highlightInfo.escapedHighlightId}),
|
|
1156
|
-
${this._containerSelector} :is(${this._paragraphSelector})
|
|
1157
|
-
${this._containerSelector} :is(rt, img)::highlight(${highlightInfo.escapedHighlightId})
|
|
1176
|
+
:where(${this._containerSelector} *)::highlight(${highlightInfo.escapedHighlightId}) { all: unset; }
|
|
1177
|
+
:where(${this._containerSelector} :is(${this._paragraphSelector}))::highlight(${highlightInfo.escapedHighlightId}),
|
|
1178
|
+
:where(${this._containerSelector} :is(${this._paragraphSelector}) *)::highlight(${highlightInfo.escapedHighlightId}) { --hh-color: ${colorString}; ${styleString} }
|
|
1179
|
+
:where(${this._containerSelector} :is(rt, img))::highlight(${highlightInfo.escapedHighlightId}),
|
|
1180
|
+
:where(${this._containerSelector} :is(${this._paragraphSelector}) :is(rt, img))::highlight(${highlightInfo.escapedHighlightId}) { color: inherit; background-color: transparent; }
|
|
1158
1181
|
`;
|
|
1159
1182
|
} else {
|
|
1160
|
-
cssText += `mark[data-hh-highlight-id=${highlightInfo.escapedHighlightId}]
|
|
1183
|
+
cssText += `mark[data-hh-style]:where([data-hh-highlight-id=${highlightInfo.escapedHighlightId}]) { ${styleString} }\n`;
|
|
1161
1184
|
}
|
|
1162
1185
|
}
|
|
1163
1186
|
}
|
|
@@ -1190,7 +1213,6 @@
|
|
|
1190
1213
|
// Get active highlight properties
|
|
1191
1214
|
const activeHighlightId = this._activeHighlightId;
|
|
1192
1215
|
const highlightInfo = this._highlightsById[activeHighlightId] ?? null;
|
|
1193
|
-
const isSvgActive = activeHighlightId && highlightInfo?.resolvedDrawingMode === 'svg';
|
|
1194
1216
|
const color = highlightInfo?.color ?? null;
|
|
1195
1217
|
const style = highlightInfo?.style ?? null;
|
|
1196
1218
|
const variables = highlightInfo?.variables ?? {};
|
|
@@ -1209,6 +1231,9 @@
|
|
|
1209
1231
|
if (boundsHash != this._previousBoundsHash) changes.push('selection');
|
|
1210
1232
|
if (changes.length === 0) return;
|
|
1211
1233
|
|
|
1234
|
+
// WebKit doesn't draw text decorations on ::selection, and iOS ignores ::selection styles entirely. If the style has an SVG template, use it for the active state.
|
|
1235
|
+
const useSvgSelection = activeHighlightId && (highlightInfo?.resolvedDrawingMode === 'svg' || isWebKit && this._getStyleDef(style)?.svg);
|
|
1236
|
+
|
|
1212
1237
|
// Get range metadata
|
|
1213
1238
|
let rangeText = null, rangeHtml = null, rangeParagraphIds = null, rangeObj = null;
|
|
1214
1239
|
let rangeRect = null, rangeParagraphs = [], rangeLineRects = [];
|
|
@@ -1234,9 +1259,9 @@
|
|
|
1234
1259
|
this._selectionState = { activeHighlightId, color, style, variables, isResizing, pointerType, selection, changes, rangeText, rangeHtml, rangeRect: rangeRect, rangeLineRects, rangeParagraphIds, rangeObj };
|
|
1235
1260
|
this._previousBoundsHash = boundsHash;
|
|
1236
1261
|
|
|
1237
|
-
// Draw SVG selection rects (SVG drawing mode
|
|
1262
|
+
// Draw SVG selection rects (SVG drawing mode, or WebKit selection style fallback)
|
|
1238
1263
|
this._svgActiveOverlay.innerHTML = '';
|
|
1239
|
-
if (
|
|
1264
|
+
if (useSvgSelection) {
|
|
1240
1265
|
this._svgActiveOverlay.dataset.hhColor = color;
|
|
1241
1266
|
this._svgActiveOverlay.dataset.hhStyle = style;
|
|
1242
1267
|
let svgContent = '';
|
|
@@ -1256,33 +1281,33 @@
|
|
|
1256
1281
|
const colorString = options.colorDefs[color] ?? (supportsAccentColor ? 'AccentColor' : 'dodgerblue');
|
|
1257
1282
|
this._container.style.setProperty('--hh-color', colorString);
|
|
1258
1283
|
|
|
1259
|
-
// Hide the active highlight (and wrappers), and set a selection style that mimics
|
|
1260
|
-
if (
|
|
1261
|
-
|
|
1262
|
-
${this._containerSelector}
|
|
1263
|
-
${this._containerSelector}
|
|
1264
|
-
${this._containerSelector}
|
|
1265
|
-
`);
|
|
1266
|
-
} else if (activeHighlightId) {
|
|
1267
|
-
// Remove stale SVG highlight if a highlight switches from SVG to a different resolvedDrawingMode
|
|
1268
|
-
for (const svgHighlight of this._svgBackground.querySelectorAll(`g[data-hh-highlight-id="${activeHighlightId}"]`)) {
|
|
1269
|
-
svgHighlight.remove();
|
|
1270
|
-
}
|
|
1271
|
-
const styleString = this._getStyleString(style, 'css', true, highlightInfo.variables, highlightInfo.rangeRect);
|
|
1272
|
-
this._selectionStylesheet.replaceSync(`
|
|
1273
|
-
${this._containerSelector} ::highlight(${highlightInfo.escapedHighlightId}) { all: unset; }
|
|
1284
|
+
// Hide the active highlight (and wrappers), and either draw it with the SVG overlay or set a selection style that mimics it. This avoids the need to redraw the highlight while actively editing it (especially important for <mark> highlights, because DOM manipulation around the selection can make the selection UI unstable).
|
|
1285
|
+
if (activeHighlightId) {
|
|
1286
|
+
let cssText = `
|
|
1287
|
+
${this._containerSelector} ::highlight(${highlightInfo.escapedHighlightId}),
|
|
1288
|
+
${this._containerSelector} :is(${this._paragraphSelector})::highlight(${highlightInfo.escapedHighlightId}),
|
|
1289
|
+
${this._containerSelector} :is(${this._paragraphSelector}) *::highlight(${highlightInfo.escapedHighlightId}) { all: unset; }
|
|
1274
1290
|
${this._containerSelector} mark[data-hh-highlight-id="${activeHighlightId}"][data-hh-style] { all: unset; }
|
|
1275
1291
|
${this._containerSelector} [data-hh-wrapper][data-hh-highlight-id="${activeHighlightId}"] { visibility: hidden; }
|
|
1292
|
+
${this._containerSelector} g[data-hh-highlight-id="${activeHighlightId}"][data-hh-style] { display: none; }
|
|
1276
1293
|
${this._containerSelector} ::selection { background-color: transparent; }
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1294
|
+
`;
|
|
1295
|
+
// If not drawn by the SVG overlay, style the selection to mimic the highlight
|
|
1296
|
+
if (!useSvgSelection) {
|
|
1297
|
+
const styleString = this._getStyleString(style, 'css', true, highlightInfo.variables, highlightInfo.rangeRect);
|
|
1298
|
+
cssText += `
|
|
1299
|
+
${this._containerSelector} :is(${this._paragraphSelector})::selection,
|
|
1300
|
+
${this._containerSelector} :is(${this._paragraphSelector}) *::selection { --hh-color: ${colorString}; ${styleString} }
|
|
1301
|
+
${this._containerSelector} :is(rt, img)::selection,
|
|
1302
|
+
${this._containerSelector} :is(${this._paragraphSelector}) :is(rt, img)::selection { background-color: transparent; }
|
|
1303
|
+
`;
|
|
1304
|
+
}
|
|
1305
|
+
this._selectionStylesheet.replaceSync(cssText);
|
|
1281
1306
|
|
|
1282
1307
|
// No active highlight (show the regular selection UI)
|
|
1283
1308
|
} else {
|
|
1284
1309
|
this._selectionStylesheet.replaceSync(`
|
|
1285
|
-
${this._containerSelector} ::selection { background-color: Highlight; color: inherit; }
|
|
1310
|
+
:where(${this._containerSelector} *)::selection { background-color: Highlight; color: inherit; }
|
|
1286
1311
|
${this._containerSelector} rt::selection, ${this._containerSelector} img::selection { background-color: transparent; }
|
|
1287
1312
|
`);
|
|
1288
1313
|
}
|
|
@@ -1506,7 +1531,7 @@
|
|
|
1506
1531
|
const options = this._options;
|
|
1507
1532
|
let css = '';
|
|
1508
1533
|
for (const color of Object.keys(options.colorDefs)) {
|
|
1509
|
-
css +=
|
|
1534
|
+
css += `:where(${this._containerSelector} [data-hh-color="${color}"]) { --hh-color: ${options.colorDefs[color]}; }\n`;
|
|
1510
1535
|
}
|
|
1511
1536
|
this._colorStylesheet.replaceSync(css);
|
|
1512
1537
|
}
|
|
@@ -1515,8 +1540,7 @@
|
|
|
1515
1540
|
Highlighter.prototype._getResolvedDrawingMode = function (highlightInfo) {
|
|
1516
1541
|
const options = this._options;
|
|
1517
1542
|
if (options.drawingMode === 'mark-elements') return 'mark-elements';
|
|
1518
|
-
|
|
1519
|
-
if ((options.drawingMode === 'svg' && !options.styleDefs[resolvedStyle]?.svg)
|
|
1543
|
+
if ((options.drawingMode === 'svg' && !this._getStyleDef(highlightInfo.style)?.svg)
|
|
1520
1544
|
|| (options.drawingMode === 'highlight-api' && !supportsHighlightApi)) {
|
|
1521
1545
|
return 'mark-elements';
|
|
1522
1546
|
}
|
|
@@ -1589,13 +1613,18 @@
|
|
|
1589
1613
|
return str.replace(/\{[^}]+\}/g, match => map[match] ?? match);
|
|
1590
1614
|
}
|
|
1591
1615
|
|
|
1616
|
+
// Get the style definition for a highlight style, falling back to the default style
|
|
1617
|
+
Highlighter.prototype._getStyleDef = function (style) {
|
|
1618
|
+
const styleDefs = this._options.styleDefs;
|
|
1619
|
+
return styleDefs[Object.hasOwn(styleDefs, style) ? style : _defaultStyle];
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1592
1622
|
// Get style string for a given highlight style
|
|
1593
1623
|
Highlighter.prototype._getStyleString = function (style, type, active = false, variables = {}, rangeRect = null, lineRect = null) {
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
let styleString = options.styleDefs[style]?.[type] ?? '';
|
|
1624
|
+
const styleDef = this._getStyleDef(style);
|
|
1625
|
+
let styleString = styleDef?.[type] ?? '';
|
|
1597
1626
|
if (active) {
|
|
1598
|
-
styleString =
|
|
1627
|
+
styleString = styleDef?.[`${type}Active`] ?? styleString;
|
|
1599
1628
|
}
|
|
1600
1629
|
if (styleString) {
|
|
1601
1630
|
styleString = this._applySubstitutions(styleString, variables, rangeRect, lineRect);
|
|
@@ -1777,6 +1806,8 @@
|
|
|
1777
1806
|
}
|
|
1778
1807
|
|
|
1779
1808
|
Highlighter.prototype._updateHighlightRects = function (highlightIds = Object.keys(this._highlightsById), paragraphLineRectsCache = new Map(), columnGeometryCache = new Map()) {
|
|
1809
|
+
// Refresh _additionsRect in case it has stale data
|
|
1810
|
+
this._additionsRect = this._additionsDiv.getBoundingClientRect();
|
|
1780
1811
|
for (const highlightId of highlightIds) {
|
|
1781
1812
|
const highlightInfo = this._highlightsById[highlightId];
|
|
1782
1813
|
const rangeParagraphs = highlightInfo.rangeParagraphIds.map(id => document.getElementById(id));
|
|
@@ -2064,7 +2095,7 @@
|
|
|
2064
2095
|
|
|
2065
2096
|
// Check browser type
|
|
2066
2097
|
const isTouchDevice = navigator.maxTouchPoints > 0;
|
|
2067
|
-
const isWebKit = /^((
|
|
2098
|
+
const isWebKit = /^(?!.*(Chrome|Firefox|Android|Samsung)).*AppleWebKit/i.test(navigator.userAgent);
|
|
2068
2099
|
const isWKWebView = isWebKit && globalThis.webkit?.messageHandlers;
|
|
2069
2100
|
const supportsCaretPositionFromPoint = document.caretPositionFromPoint;
|
|
2070
2101
|
const supportsCaretRangeFromPoint = document.caretRangeFromPoint;
|
package/package.json
CHANGED