@readium/navigator-html-injectables 2.2.1 → 2.2.3
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/dist/index.js +856 -784
- package/dist/index.umd.cjs +12 -11
- package/package.json +3 -3
- package/src/helpers/color.ts +190 -99
- package/src/modules/Decorator.ts +71 -9
- package/src/types/protection.ts +16 -0
- package/types/src/helpers/color.d.ts +16 -6
- package/types/src/modules/Decorator.d.ts +3 -0
- package/types/src/types/protection.d.ts +7 -0
package/src/helpers/color.ts
CHANGED
|
@@ -1,114 +1,205 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
1
|
+
// Lazy canvas/offscreen canvas initialization for color conversion
|
|
2
|
+
let canvas: HTMLCanvasElement | OffscreenCanvas | null = null;
|
|
3
|
+
let ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null = null;
|
|
4
|
+
|
|
5
|
+
// Track which pixel to use next for color sampling (0-24 for 5x5 grid)
|
|
6
|
+
let currentPixelIndex = 0;
|
|
7
|
+
|
|
8
|
+
// Default color for failed conversions
|
|
9
|
+
const DEFAULT_COLOR = { r: 255, g: 255, b: 255, a: 1 };
|
|
10
|
+
|
|
11
|
+
// Cache for computed color conversions
|
|
12
|
+
const colorCache = new Map<string, { r: number; g: number; b: number; a: number; } | null>();
|
|
13
|
+
|
|
14
|
+
const getCanvasContext = (): CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null => {
|
|
15
|
+
if (!canvas) {
|
|
16
|
+
// Try to use OffscreenCanvas if available
|
|
17
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
18
|
+
canvas = new OffscreenCanvas(5, 5);
|
|
19
|
+
ctx = canvas.getContext("2d", {
|
|
20
|
+
willReadFrequently: true,
|
|
21
|
+
desynchronized: true
|
|
22
|
+
});
|
|
23
|
+
} else {
|
|
24
|
+
// Fall back to regular canvas
|
|
25
|
+
const htmlCanvas = document.createElement("canvas");
|
|
26
|
+
htmlCanvas.width = 5;
|
|
27
|
+
htmlCanvas.height = 5;
|
|
28
|
+
canvas = htmlCanvas;
|
|
29
|
+
ctx = htmlCanvas.getContext("2d", {
|
|
30
|
+
willReadFrequently: true,
|
|
31
|
+
desynchronized: true
|
|
32
|
+
});
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
|
-
return
|
|
35
|
+
return ctx;
|
|
31
36
|
};
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const isSpecialColorValue = (color: string): boolean => {
|
|
39
|
+
if (!color) return true;
|
|
40
|
+
|
|
41
|
+
const normalizedColor = color.trim().toLowerCase();
|
|
42
|
+
|
|
43
|
+
// Check for CSS variables
|
|
44
|
+
if (normalizedColor.startsWith("var(")) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check for CSS color keywords
|
|
49
|
+
const cssKeywords = [
|
|
50
|
+
"transparent",
|
|
51
|
+
"currentcolor",
|
|
52
|
+
"inherit",
|
|
53
|
+
"initial",
|
|
54
|
+
"revert",
|
|
55
|
+
"unset",
|
|
56
|
+
"revert-layer"
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
if (cssKeywords.includes(normalizedColor)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check for gradients
|
|
64
|
+
const gradientTypes = [
|
|
65
|
+
"linear-gradient",
|
|
66
|
+
"radial-gradient",
|
|
67
|
+
"conic-gradient",
|
|
68
|
+
"repeating-linear-gradient",
|
|
69
|
+
"repeating-radial-gradient",
|
|
70
|
+
"repeating-conic-gradient"
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
return gradientTypes.some(grad => normalizedColor.includes(grad));
|
|
41
74
|
};
|
|
42
75
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const rgba2 = colorToRgba(color2);
|
|
48
|
-
const lum1 = getLuminance(rgba1);
|
|
49
|
-
const lum2 = getLuminance(rgba2);
|
|
50
|
-
const brightest = Math.max(lum1, lum2);
|
|
51
|
-
const darkest = Math.min(lum1, lum2);
|
|
52
|
-
return (brightest + 0.05) / (darkest + 0.05);
|
|
76
|
+
const warnAboutInvalidColor = (color: string, reason: string): void => {
|
|
77
|
+
console.warn(
|
|
78
|
+
`[Decorator] Could not parse color: "${color}". ${reason} Falling back to ${JSON.stringify(DEFAULT_COLOR)} to compute contrast. Please use a CSS color value that can be computed to RGB(A).`
|
|
79
|
+
);
|
|
53
80
|
};
|
|
54
81
|
|
|
55
|
-
export const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (contrastRatio >= contrast) {
|
|
65
|
-
return [
|
|
66
|
-
`rgba(${c1.r}, ${c1.g}, ${c1.b}, ${c1.a})`,
|
|
67
|
-
`rgba(${c2.r}, ${c2.g}, ${c2.b}, ${c2.a})`
|
|
68
|
-
];
|
|
82
|
+
export const colorToRgba = (
|
|
83
|
+
color: string,
|
|
84
|
+
backgroundColor: string | null = null
|
|
85
|
+
): { r: number; g: number; b: number; a: number } => {
|
|
86
|
+
// Check cache with background key if provided
|
|
87
|
+
const cacheKey = backgroundColor ? `${color}|${backgroundColor}` : color;
|
|
88
|
+
const cached = colorCache.get(cacheKey);
|
|
89
|
+
if (cached !== undefined) {
|
|
90
|
+
return cached ?? DEFAULT_COLOR;
|
|
69
91
|
}
|
|
70
92
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
const delta = ((contrast - contrastRatio) * 255) / (contrastRatio + 0.05);
|
|
79
|
-
let correctedColor: { r: number; g: number; b: number; a: number };
|
|
80
|
-
let otherColor: { r: number; g: number; b: number; a: number };
|
|
81
|
-
if (lum1 < lum2) {
|
|
82
|
-
correctedColor = c1;
|
|
83
|
-
otherColor = c2;
|
|
84
|
-
} else {
|
|
85
|
-
correctedColor = c2;
|
|
86
|
-
otherColor = c1;
|
|
93
|
+
// Check for special color values
|
|
94
|
+
if (isSpecialColorValue(color)) {
|
|
95
|
+
warnAboutInvalidColor(color, "Unsupported color format or special value.");
|
|
96
|
+
colorCache.set(cacheKey, null);
|
|
97
|
+
return DEFAULT_COLOR;
|
|
87
98
|
}
|
|
88
99
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const updatedDelta = ((contrast - newContrastRatio) * 255) / (newContrastRatio + 0.05);
|
|
95
|
-
const otherColorAdjusted = adjustColor(otherColor, updatedDelta);
|
|
96
|
-
return [
|
|
97
|
-
lum1 < lum2
|
|
98
|
-
? `rgba(${correctedColorAdjusted.r}, ${correctedColorAdjusted.g}, ${correctedColorAdjusted.b}, ${correctedColorAdjusted.a})`
|
|
99
|
-
: `rgba(${otherColorAdjusted.r}, ${otherColorAdjusted.g}, ${otherColorAdjusted.b}, ${otherColorAdjusted.a})`,
|
|
100
|
-
lum1 < lum2
|
|
101
|
-
? `rgba(${otherColorAdjusted.r}, ${otherColorAdjusted.g}, ${otherColorAdjusted.b}, ${otherColorAdjusted.a})`
|
|
102
|
-
: `rgba(${correctedColorAdjusted.r}, ${correctedColorAdjusted.g}, ${correctedColorAdjusted.b}, ${correctedColorAdjusted.a})`,
|
|
103
|
-
];
|
|
100
|
+
const context = getCanvasContext();
|
|
101
|
+
if (!context) {
|
|
102
|
+
warnAboutInvalidColor(color, "Could not get canvas context.");
|
|
103
|
+
colorCache.set(cacheKey, null);
|
|
104
|
+
return DEFAULT_COLOR;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
try {
|
|
108
|
+
// Clear and initialize canvas at the start of each cycle
|
|
109
|
+
if (currentPixelIndex === 0) {
|
|
110
|
+
context.clearRect(0, 0, 5, 5);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Calculate which pixel to use for this operation
|
|
114
|
+
const x = currentPixelIndex % 5;
|
|
115
|
+
const y = Math.floor(currentPixelIndex / 5);
|
|
116
|
+
|
|
117
|
+
// Clear just this pixel to ensure clean state
|
|
118
|
+
context.clearRect(x, y, 1, 1);
|
|
119
|
+
|
|
120
|
+
// Fill background color if provided
|
|
121
|
+
if (backgroundColor) {
|
|
122
|
+
context.fillStyle = backgroundColor;
|
|
123
|
+
context.fillRect(x, y, 1, 1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Draw the color at the current pixel
|
|
127
|
+
context.fillStyle = color;
|
|
128
|
+
context.fillRect(x, y, 1, 1);
|
|
129
|
+
|
|
130
|
+
// Get the pixel data for this specific pixel
|
|
131
|
+
const imageData = context.getImageData(x, y, 1, 1);
|
|
132
|
+
|
|
133
|
+
// Move to next pixel for next call
|
|
134
|
+
currentPixelIndex = (currentPixelIndex + 1) % 25;
|
|
135
|
+
|
|
136
|
+
// Get the pixel data for the pixel we just sampled
|
|
137
|
+
const [r, g, b, a] = imageData.data;
|
|
138
|
+
|
|
139
|
+
// If the color is completely transparent, return default
|
|
140
|
+
if (a === 0) {
|
|
141
|
+
warnAboutInvalidColor(color, "Fully transparent color.");
|
|
142
|
+
colorCache.set(cacheKey, null);
|
|
143
|
+
return DEFAULT_COLOR;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const result = { r, g, b, a: a / 255 };
|
|
147
|
+
colorCache.set(cacheKey, result);
|
|
148
|
+
return result;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
warnAboutInvalidColor(color, `Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
151
|
+
colorCache.set(cacheKey, null);
|
|
152
|
+
return DEFAULT_COLOR;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const toLinear = (c: number): number => {
|
|
157
|
+
const normalized = c / 255;
|
|
158
|
+
return normalized <= 0.03928
|
|
159
|
+
? normalized / 12.92
|
|
160
|
+
: Math.pow((normalized + 0.055) / 1.055, 2.4);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const getLuminance = (color: { r: number; g: number; b: number; a?: number }): number => {
|
|
164
|
+
// Convert sRGB to linear RGB and apply WCAG 2.2 formula
|
|
165
|
+
const r = toLinear(color.r);
|
|
166
|
+
const g = toLinear(color.g);
|
|
167
|
+
const b = toLinear(color.b);
|
|
168
|
+
|
|
169
|
+
// WCAG 2.2 relative luminance formula (returns 0-1)
|
|
170
|
+
// Note: Alpha is ignored for contrast calculations. WCAG 2.2 only defines contrast for opaque colors,
|
|
171
|
+
// and semi-transparent colors have a range of possible contrast ratios depending on background.
|
|
172
|
+
// For text readability decisions, we use the base color as the most conservative approach.
|
|
173
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
174
|
+
return luminance;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const checkContrast = (
|
|
178
|
+
color1: string | { r: number; g: number; b: number; a?: number },
|
|
179
|
+
color2: string | { r: number; g: number; b: number; a?: number }
|
|
180
|
+
): number => {
|
|
181
|
+
const rgba1 = typeof color1 === "string" ? colorToRgba(color1) : color1;
|
|
182
|
+
const rgba2 = typeof color2 === "string" ? colorToRgba(color2) : color2;
|
|
183
|
+
|
|
184
|
+
const l1 = getLuminance(rgba1);
|
|
185
|
+
const l2 = getLuminance(rgba2);
|
|
186
|
+
|
|
187
|
+
const lighter = Math.max(l1, l2);
|
|
188
|
+
const darker = Math.min(l1, l2);
|
|
189
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
114
190
|
};
|
|
191
|
+
|
|
192
|
+
export const isDarkColor = (color: string, blendedWith: string | null = null): boolean => {
|
|
193
|
+
const blended = colorToRgba(color, blendedWith);
|
|
194
|
+
const contrastWithWhite = checkContrast(blended, { r: 255, g: 255, b: 255, a: 1 });
|
|
195
|
+
const contrastWithBlack = checkContrast(blended, { r: 0, g: 0, b: 0, a: 1 });
|
|
196
|
+
return contrastWithWhite > contrastWithBlack;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export const isLightColor = (color: string, blendedWith: string | null = null): boolean => {
|
|
200
|
+
return !isDarkColor(color, blendedWith);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export const getContrastingTextColor = (color: string, blendedWith: string | null = null): "black" | "white" => {
|
|
204
|
+
return isDarkColor(color, blendedWith) ? "white" : "black";
|
|
205
|
+
};
|
package/src/modules/Decorator.ts
CHANGED
|
@@ -6,7 +6,9 @@ import { ModuleName } from "./ModuleLibrary";
|
|
|
6
6
|
import { Rect, getClientRectsNoOverlap } from "../helpers/rect";
|
|
7
7
|
import { getProperty } from "../helpers/css";
|
|
8
8
|
import { ReadiumWindow } from "../helpers/dom";
|
|
9
|
-
import { isDarkColor } from "../helpers/color";
|
|
9
|
+
import { isDarkColor, getContrastingTextColor } from "../helpers/color";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_HIGHLIGHT_COLOR = "#FFFF00"; // Yellow in HEX
|
|
10
12
|
|
|
11
13
|
export enum Width {
|
|
12
14
|
Wrap = "wrap", // Smallest width fitting the CSS border box.
|
|
@@ -103,8 +105,10 @@ class DecorationGroup {
|
|
|
103
105
|
// The common ancestor is an element that definitely cannot be highlighted
|
|
104
106
|
this.notTextFlag?.set(id, true);
|
|
105
107
|
}
|
|
106
|
-
if
|
|
107
|
-
|
|
108
|
+
// Check if the range itself contains elements that cannot be highlighted
|
|
109
|
+
const rangeFragment = range.cloneContents();
|
|
110
|
+
if(rangeFragment.querySelector(cannotNativeHighlight.join(", ").toLowerCase())) {
|
|
111
|
+
// Range contains elements that definitely cannot be highlighted
|
|
108
112
|
this.notTextFlag?.set(id, true);
|
|
109
113
|
}
|
|
110
114
|
if((ancestor.textContent?.trim() || "").length === 0) {
|
|
@@ -180,11 +184,15 @@ class DecorationGroup {
|
|
|
180
184
|
const [stylesheet, highlighter]: [HTMLStyleElement, any] = this.requireContainer(true) as [HTMLStyleElement, unknown];
|
|
181
185
|
highlighter.add(item.range);
|
|
182
186
|
|
|
187
|
+
const backgroundColor = getProperty(this.wnd, "--USER__backgroundColor") ||
|
|
188
|
+
this.wnd.getComputedStyle(this.wnd.document.documentElement).getPropertyValue("background-color");
|
|
189
|
+
const tint = item.decoration?.style?.tint ?? DEFAULT_HIGHLIGHT_COLOR;
|
|
190
|
+
|
|
183
191
|
// TODO add caching layer ("vdom") to this so we aren't completely replacing the CSS every time
|
|
184
192
|
stylesheet.innerHTML = `
|
|
185
193
|
::highlight(${this.id}) {
|
|
186
|
-
color:
|
|
187
|
-
background-color: ${
|
|
194
|
+
color: ${getContrastingTextColor(tint, backgroundColor)};
|
|
195
|
+
background-color: ${tint};
|
|
188
196
|
}`;
|
|
189
197
|
}
|
|
190
198
|
|
|
@@ -201,6 +209,7 @@ class DecorationGroup {
|
|
|
201
209
|
|
|
202
210
|
const itemContainer = this.wnd.document.createElement("div");
|
|
203
211
|
itemContainer.setAttribute("id", item.id);
|
|
212
|
+
itemContainer.dataset.highlightId = item.decoration.id;
|
|
204
213
|
// itemContainer.dataset.style = item.decoration.style; // TODO style
|
|
205
214
|
itemContainer.style.setProperty("pointer-events", "none");
|
|
206
215
|
|
|
@@ -251,14 +260,14 @@ class DecorationGroup {
|
|
|
251
260
|
// template.innerHTML = item.decoration.element.trim();
|
|
252
261
|
// TODO more styles logic
|
|
253
262
|
|
|
254
|
-
const isDarkMode =
|
|
255
|
-
isDarkColor(getProperty(this.wnd, "--USER__backgroundColor"));
|
|
263
|
+
const isDarkMode = this.getCurrentDarkMode();
|
|
256
264
|
|
|
257
265
|
template.innerHTML = `
|
|
258
266
|
<div
|
|
259
|
-
|
|
267
|
+
data-readium="true"
|
|
268
|
+
class="readium-highlight"
|
|
260
269
|
style="${[
|
|
261
|
-
`background-color: ${item.decoration?.style?.tint ??
|
|
270
|
+
`background-color: ${item.decoration?.style?.tint ?? DEFAULT_HIGHLIGHT_COLOR} !important`,
|
|
262
271
|
//"opacity: 0.3 !important",
|
|
263
272
|
`mix-blend-mode: ${isDarkMode ? "exclusion" : "multiply"} !important`,
|
|
264
273
|
"opacity: 1 !important",
|
|
@@ -359,6 +368,12 @@ class DecorationGroup {
|
|
|
359
368
|
return this.container;
|
|
360
369
|
}
|
|
361
370
|
|
|
371
|
+
getCurrentDarkMode(): boolean {
|
|
372
|
+
return getProperty(this.wnd, "--USER__appearance") === "readium-night-on" ||
|
|
373
|
+
isDarkColor(getProperty(this.wnd, "--USER__backgroundColor")) ||
|
|
374
|
+
isDarkColor(this.wnd.getComputedStyle(this.wnd.document.documentElement).getPropertyValue("background-color"));
|
|
375
|
+
}
|
|
376
|
+
|
|
362
377
|
/**
|
|
363
378
|
* Removes the group container.
|
|
364
379
|
*/
|
|
@@ -376,6 +391,7 @@ class DecorationGroup {
|
|
|
376
391
|
export class Decorator extends Module {
|
|
377
392
|
static readonly moduleName: ModuleName = "decorator";
|
|
378
393
|
private resizeObserver!: ResizeObserver;
|
|
394
|
+
private backgroundObserver!: MutationObserver;
|
|
379
395
|
private wnd!: ReadiumWindow;
|
|
380
396
|
/*private readonly lastSize = {
|
|
381
397
|
width: 0,
|
|
@@ -392,6 +408,19 @@ export class Decorator extends Module {
|
|
|
392
408
|
this.groups.clear();
|
|
393
409
|
}
|
|
394
410
|
|
|
411
|
+
private updateHighlightStyles() {
|
|
412
|
+
this.groups.forEach(group => {
|
|
413
|
+
group.requestLayout();
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private extractCustomProperty(style: string | null, propertyName: string): string | null {
|
|
418
|
+
if (!style) return null;
|
|
419
|
+
|
|
420
|
+
const match = style.match(new RegExp(`${propertyName}:\\s*([^;]+)`));
|
|
421
|
+
return match ? match[1].trim() : null;
|
|
422
|
+
}
|
|
423
|
+
|
|
395
424
|
private handleResize() {
|
|
396
425
|
this.wnd.clearTimeout(this.resizeFrame);
|
|
397
426
|
this.resizeFrame = this.wnd.setTimeout(() => {
|
|
@@ -442,6 +471,38 @@ export class Decorator extends Module {
|
|
|
442
471
|
wnd.addEventListener("orientationchange", this.handleResizer);
|
|
443
472
|
wnd.addEventListener("resize", this.handleResizer);
|
|
444
473
|
|
|
474
|
+
// Set up MutationObserver to watch for CSS custom property changes
|
|
475
|
+
this.backgroundObserver = new MutationObserver((mutations) => {
|
|
476
|
+
const shouldUpdate = mutations.some(mutation => {
|
|
477
|
+
if (mutation.type === "attributes" && mutation.attributeName === "style") {
|
|
478
|
+
const element = mutation.target as Element;
|
|
479
|
+
const oldStyle = mutation.oldValue;
|
|
480
|
+
const newStyle = element.getAttribute("style");
|
|
481
|
+
|
|
482
|
+
// Check if the relevant CSS custom properties actually changed
|
|
483
|
+
const oldAppearance = this.extractCustomProperty(oldStyle, "--USER__appearance");
|
|
484
|
+
const newAppearance = this.extractCustomProperty(newStyle, "--USER__appearance");
|
|
485
|
+
const oldBgColor = this.extractCustomProperty(oldStyle, "--USER__backgroundColor");
|
|
486
|
+
const newBgColor = this.extractCustomProperty(newStyle, "--USER__backgroundColor");
|
|
487
|
+
|
|
488
|
+
return oldAppearance !== newAppearance ||
|
|
489
|
+
oldBgColor !== newBgColor;
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
if (shouldUpdate) {
|
|
495
|
+
this.updateHighlightStyles();
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
this.backgroundObserver.observe(wnd.document.documentElement, {
|
|
500
|
+
attributes: true,
|
|
501
|
+
attributeFilter: ["style"],
|
|
502
|
+
attributeOldValue: true,
|
|
503
|
+
subtree: true
|
|
504
|
+
});
|
|
505
|
+
|
|
445
506
|
comms.log("Decorator Mounted");
|
|
446
507
|
return true;
|
|
447
508
|
}
|
|
@@ -452,6 +513,7 @@ export class Decorator extends Module {
|
|
|
452
513
|
|
|
453
514
|
comms.unregisterAll(Decorator.moduleName);
|
|
454
515
|
this.resizeObserver.disconnect();
|
|
516
|
+
this.backgroundObserver.disconnect();
|
|
455
517
|
this.cleanup();
|
|
456
518
|
|
|
457
519
|
comms.log("Decorator Unmounted");
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type SuspiciousActivityType =
|
|
2
|
+
| "developer_tools_attempt"
|
|
3
|
+
| "select_all_attempt"
|
|
4
|
+
| "suspicious_selection"
|
|
5
|
+
| "suspicious_copy"
|
|
6
|
+
| "suspicious_scrolling"
|
|
7
|
+
| "suspicious_snapping"
|
|
8
|
+
| "drag_detected"
|
|
9
|
+
| "drop_detected"
|
|
10
|
+
| "context_menu";
|
|
11
|
+
|
|
12
|
+
export interface SuspiciousActivityEvent {
|
|
13
|
+
timestamp: number;
|
|
14
|
+
type: SuspiciousActivityType;
|
|
15
|
+
event?: Event;
|
|
16
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const colorToRgba: (color: string) => {
|
|
1
|
+
export declare const colorToRgba: (color: string, backgroundColor?: string | null) => {
|
|
2
2
|
r: number;
|
|
3
3
|
g: number;
|
|
4
4
|
b: number;
|
|
@@ -8,9 +8,19 @@ export declare const getLuminance: (color: {
|
|
|
8
8
|
r: number;
|
|
9
9
|
g: number;
|
|
10
10
|
b: number;
|
|
11
|
-
a
|
|
11
|
+
a?: number;
|
|
12
|
+
}) => number;
|
|
13
|
+
export declare const checkContrast: (color1: string | {
|
|
14
|
+
r: number;
|
|
15
|
+
g: number;
|
|
16
|
+
b: number;
|
|
17
|
+
a?: number;
|
|
18
|
+
}, color2: string | {
|
|
19
|
+
r: number;
|
|
20
|
+
g: number;
|
|
21
|
+
b: number;
|
|
22
|
+
a?: number;
|
|
12
23
|
}) => number;
|
|
13
|
-
export declare const isDarkColor: (color: string) => boolean;
|
|
14
|
-
export declare const isLightColor: (color: string) => boolean;
|
|
15
|
-
export declare const
|
|
16
|
-
export declare const ensureContrast: (color1: string, color2: string, contrast?: number) => string[];
|
|
24
|
+
export declare const isDarkColor: (color: string, blendedWith?: string | null) => boolean;
|
|
25
|
+
export declare const isLightColor: (color: string, blendedWith?: string | null) => boolean;
|
|
26
|
+
export declare const getContrastingTextColor: (color: string, blendedWith?: string | null) => "black" | "white";
|
|
@@ -31,11 +31,14 @@ export interface DecoratorRequest {
|
|
|
31
31
|
export declare class Decorator extends Module {
|
|
32
32
|
static readonly moduleName: ModuleName;
|
|
33
33
|
private resizeObserver;
|
|
34
|
+
private backgroundObserver;
|
|
34
35
|
private wnd;
|
|
35
36
|
private resizeFrame;
|
|
36
37
|
private lastGroupId;
|
|
37
38
|
private groups;
|
|
38
39
|
private cleanup;
|
|
40
|
+
private updateHighlightStyles;
|
|
41
|
+
private extractCustomProperty;
|
|
39
42
|
private handleResize;
|
|
40
43
|
private readonly handleResizer;
|
|
41
44
|
mount(wnd: ReadiumWindow, comms: Comms): boolean;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type SuspiciousActivityType = "developer_tools_attempt" | "select_all_attempt" | "suspicious_selection" | "suspicious_copy" | "suspicious_scrolling" | "suspicious_snapping" | "drag_detected" | "drop_detected" | "context_menu";
|
|
2
|
+
export interface SuspiciousActivityEvent {
|
|
3
|
+
timestamp: number;
|
|
4
|
+
type: SuspiciousActivityType;
|
|
5
|
+
event?: Event;
|
|
6
|
+
}
|
|
7
|
+
export {};
|