Skip to content

Commit c899d5d

Browse files
Allow more flexibility in styling active highlights
1 parent 600a451 commit c899d5d

File tree

2 files changed

+40
-14
lines changed

2 files changed

+40
-14
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Options can be provided when Highlight Helper is initialized. They can also be s
7070
- **containerSelector** – CSS selector for the section of the page that should be annotatable. Default: `body`.
7171
- **paragraphSelector** – CSS selector for the paragraphs (or other block elements) on the page that should be annotatable. Each paragraph is expected to have an ID attribute in the HTML, which is used to keep track of where a highlight starts and ends. Default: `h1[id], h2[id], h3[id], h4[id], h5[id], h6[id], p[id], ol[id], ul[id], dl[id], tr[id]`.
7272
- **colors** – Object that describes available highlight colors. Keys are color names, and values are [CSS color values](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). Default: red, orange, yellow, green, blue (see full default values at the bottom of highlight-helper.js).
73-
- **styles** – Object that describes available highlight styles. Keys are style names, and there are two properties for each style: `css` and `svg`. `css` is a CSS string used for styling highlights in `highlight-api` and `mark-elements` drawing modes, as well as for read-only highlights. `svg` is an SVG string used in `svg` drawing mode. The CSS custom property `var(--hh-color)` can be used to reference the highlight color value. For SVG highlights, the following variables (if present) will be replaced with relevant values from the highlight’s DOMRect: `{x}`, `{y}`, `{width}`, `{height}`, `{top}`, `{bottom}`, `{left}`, `{right}`. Default: fill, single-underline, double-underline, colored-text, redacted (see full default values at the bottom of highlight-helper.js).
73+
- **styles** – Object that describes available highlight styles. Keys are style names, and there are four properties for each style: `css`, `svg`, `css-active` (optional), and `svg-active` (optional). `css` is a CSS string used for styling highlights in `highlight-api` and `mark-elements` drawing modes, as well as for read-only highlights. `svg` is an SVG string (one or more SVG shapes) to represent the highlight in `svg` drawing mode. `css-active` and `svg-active` are alternative styles to be used when a highlight is active. The CSS custom property `var(--hh-color)` can be used to reference the highlight color value. For SVG highlights, the following variables (if present) will be replaced with relevant values from the highlight’s DOMRect: `{x}`, `{y}`, `{width}`, `{height}`, `{top}`, `{bottom}`, `{left}`, `{right}`. Default: fill, single-underline, double-underline, colored-text, redacted (see full default values at the bottom of highlight-helper.js).
7474
- **wrappers** – Object that describes available highlight wrappers. Keys are wrapper names, and values are objects with two optional properties: `start` (HTML string to be inserted at the beginning of the highlight), and `end` (HTML string to be inserted at the end of the highlight). Only read-only highlights support wrappers. To avoid problems when calculating ranges and offsets, all [text nodes](https://developer.mozilla.org/en-US/docs/Web/API/Text) in the start and end wrappers will be removed (if text is needed, it should be rendered with CSS). `var(--hh-color)` can be used to reference the highlight color value. Variables surrounded by curly brackets will be replaced with variables stored in the `wrapperVariables` attribute of the highlight, if applicable. See default values at the bottom of highlight-helper.js.
7575
- **selectionHandles** – Object that describes custom selection handles that a user can drag to resize a selection or highlight. Because most touch devices have built-in selection handles, custom selection handles will only show when using a mouse or trackpad. There are two properties: `left` (HTML string to be used for the left selection handle) and `right` (HTML string to be used for the right selection handle). `var(--hh-color)` can be used to reference the highlight color value. See default values at the bottom of highlight-helper.js.
7676
- **rememberStyle** – Whether the most recent color, style, and wrapper should be remembered and used by default the next time the user creates a highlight. Boolean. Default: `true`.

highlight-helper.js

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -448,10 +448,7 @@ function Highlighter(options = hhDefaultOptions) {
448448
changes: appearanceChanges.concat(boundsChanges),
449449
}
450450

451-
if (highlightId !== activeHighlightId || options.drawingMode === 'svg') {
452-
this.drawHighlights([highlightId]);
453-
}
454-
451+
if (highlightId !== activeHighlightId) this.drawHighlights([highlightId]);
455452
if (highlightId === activeHighlightId && appearanceChanges.length > 0) {
456453
updateSelectionUi('appearance');
457454
} else if (triggeredByUserAction && highlightId !== activeHighlightId) {
@@ -514,9 +511,7 @@ function Highlighter(options = hhDefaultOptions) {
514511
}
515512
updateSelectionUi('appearance');
516513
if (deactivatedHighlight) {
517-
if (options.drawingMode !== 'svg') {
518-
this.drawHighlights([deactivatedHighlight.highlightId]);
519-
}
514+
this.drawHighlights([deactivatedHighlight.highlightId]);
520515
this.annotatableContainer.dispatchEvent(new CustomEvent('hh:highlightdeactivate', { detail: {
521516
highlight: deactivatedHighlight,
522517
}}));
@@ -898,13 +893,22 @@ function Highlighter(options = hhDefaultOptions) {
898893
const style = highlightInfo?.style ?? null;
899894
const colorString = options.colors[color] ?? 'AccentColor';
900895

901-
// Update SVG shapes for the active highlight (bring shape group to front, and duplicate it to make the highlight darker)
896+
// Draw SVG selection rects (SVG drawing mode only)
902897
svgActiveOverlay.innerHTML = '';
903898
if (activeHighlightId && options.drawingMode === 'svg') {
904-
const svgHighlight = svgBackground.querySelector(`g[data-highlight-id="${activeHighlightId}"]`);
899+
let range = getCorrectedRangeObj(activeHighlightId);
900+
const rangeParagraphs = this.annotatableContainer.querySelectorAll(`#${highlightInfo.rangeParagraphIds.join(', #')}`);
901+
const clientRects = getMergedClientRects(range, rangeParagraphs);
905902
svgActiveOverlay.dataset.color = color;
906903
svgActiveOverlay.dataset.style = style;
907-
svgActiveOverlay.innerHTML = svgHighlight.innerHTML;
904+
let svgContent = '';
905+
for (const clientRect of clientRects) {
906+
svgContent += getStyleTemplate(highlightInfo.style, 'svg', clientRect, true);
907+
}
908+
svgActiveOverlay.innerHTML = svgContent;
909+
910+
// Bring active highlight to the front
911+
const svgHighlight = svgBackground.querySelector(`g[data-highlight-id="${activeHighlightId}"]`);
908912
svgBackground.appendChild(svgHighlight);
909913
svgBackground.appendChild(svgActiveOverlay);
910914
}
@@ -914,9 +918,13 @@ function Highlighter(options = hhDefaultOptions) {
914918

915919
// Update selection background
916920
if (activeHighlightId && options.drawingMode === 'svg') {
917-
selectionStylesheet.replaceSync(`${options.containerSelector} ::selection { background-color: transparent; }`);
921+
selectionStylesheet.replaceSync(`
922+
${options.containerSelector} g[data-highlight-id="${activeHighlightId}"][data-style] { display: none; }
923+
${options.containerSelector} .hh-wrapper-start[data-highlight-id="${activeHighlightId}"], .hh-wrapper-end[data-highlight-id="${activeHighlightId}"] { display: none; }
924+
${options.containerSelector} ::selection { background-color: transparent; }
925+
`);
918926
} else if (activeHighlightId) {
919-
const styleTemplate = getStyleTemplate(style, 'css', null);
927+
const styleTemplate = getStyleTemplate(style, 'css', null, true);
920928
// Hide the active highlight (and wrappers), and set a selection style that mimics the highlight. 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).
921929
selectionStylesheet.replaceSync(`
922930
${options.containerSelector} ::highlight(${highlightInfo.escapedHighlightId}) { all: unset; }
@@ -1104,9 +1112,12 @@ function Highlighter(options = hhDefaultOptions) {
11041112
}
11051113

11061114
// Get style template for a given highlight style
1107-
const getStyleTemplate = (style, type, clientRect) => {
1115+
const getStyleTemplate = (style, type, clientRect = null, active = false) => {
11081116
style = options.styles.hasOwnProperty(style) ? style : options.defaultStyle;
11091117
let styleTemplate = options.styles[style]?.[type] ?? '';
1118+
if (active) {
1119+
styleTemplate = options.styles[style]?.[`${type}-active`] ?? styleTemplate;
1120+
}
11101121
if (!styleTemplate) {
11111122
console.warn(`Highlight style "${style}" in options does not have a defined "${type}" value.`);
11121123
return;
@@ -1384,14 +1395,29 @@ let hhDefaultOptions = {
13841395
'fill': {
13851396
'css': 'background-color: hsl(from var(--hh-color) h s l / 50%);',
13861397
'svg': '<rect x="{x}" y="{y}" rx="4" style="fill: hsl(from var(--hh-color) h s l / 50%); width: calc({width}px + ({height}px / 6)); height: calc({height}px * 0.85); transform: translateX(calc({height}px / -12)) translateY(calc({height}px * 0.14));" />',
1398+
'css-active': 'background-color: hsl(from var(--hh-color) h s l / 80%);',
1399+
'svg-active': `
1400+
<rect x="{x}" y="{y}" rx="4" style="fill: hsl(from var(--hh-color) h s l / 80%); width: calc({width}px + ({height}px / 6)); height: calc({height}px * 0.85); transform: translateX(calc({height}px / -12)) translateY(calc({height}px * 0.14));" />
1401+
`,
13871402
},
13881403
'single-underline': {
13891404
'css': 'text-decoration: underline; text-decoration-color: var(--hh-color); text-decoration-thickness: 0.15em; text-underline-offset: 0.15em; text-decoration-skip-ink: none;',
13901405
'svg': '<rect x="{x}" y="{y}" style="fill: var(--hh-color); width: {width}px; height: calc({height}px / 12); transform: translateY(calc({height}px * 0.9));" />',
1406+
'css-active': 'background-color: hsl(from var(--hh-color) h s l / 25%); text-decoration: underline; text-decoration-color: var(--hh-color); text-decoration-thickness: 0.15em; text-underline-offset: 0.15em; text-decoration-skip-ink: none;',
1407+
'svg-active': `
1408+
<rect x="{x}" y="{y}" rx="4" style="fill: hsl(from var(--hh-color) h s l / 25%); width: calc({width}px + ({height}px / 6)); height: calc({height}px * 0.85); transform: translateX(calc({height}px / -12)) translateY(calc({height}px * 0.14));" />
1409+
<rect x="{x}" y="{y}" style="fill: var(--hh-color); width: {width}px; height: calc({height}px / 12); transform: translateY(calc({height}px * 0.9));" />
1410+
`,
13911411
},
13921412
'double-underline': {
13931413
'css': 'text-decoration: underline; text-decoration-color: var(--hh-color); text-decoration-style: double; text-decoration-skip-ink: none;',
13941414
'svg': '<rect x="{x}" y="{y}" style="fill: var(--hh-color); width: {width}px; height: calc({height}px / 15); transform: translateY(calc({height}px * 0.9));" /><rect x="{x}" y="{y}" style="fill: var(--hh-color); width: {width}px; height: calc({height}px / 15); transform: translateY(calc({height}px * 1.05));" />',
1415+
'css-active': 'background-color: hsl(from var(--hh-color) h s l / 25%); text-decoration: underline; text-decoration-color: var(--hh-color); text-decoration-style: double; text-decoration-skip-ink: none;',
1416+
'svg-active': `
1417+
<rect x="{x}" y="{y}" rx="4" style="fill: hsl(from var(--hh-color) h s l / 25%); width: calc({width}px + ({height}px / 6)); height: calc({height}px * 0.85); transform: translateX(calc({height}px / -12)) translateY(calc({height}px * 0.14));" />
1418+
<rect x="{x}" y="{y}" style="fill: var(--hh-color); width: {width}px; height: calc({height}px / 15); transform: translateY(calc({height}px * 0.9));" />
1419+
<rect x="{x}" y="{y}" style="fill: var(--hh-color); width: {width}px; height: calc({height}px / 15); transform: translateY(calc({height}px * 1.05));" />
1420+
`,
13951421
},
13961422
'colored-text': {
13971423
'css': 'color: var(--hh-color);',

0 commit comments

Comments
 (0)