PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / QuickEdit / lib / over-media.js

over-media.js in Extendify 3.2.1, at src/QuickEdit/lib/over-media.js

84 lines 2.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // A partner-coloured outline disappears against a background close to it.
2
3 const MIN_CONTRAST = 3;
4
5 export const MEDIA_RING = '0 0 0 4px rgba(255, 255, 255, 0.2)';
6
7 const parseColor = (value = '') => {
8 const rgb = value.match(/rgba?\(([^)]+)\)/);
9 if (rgb) {
10 const parts = rgb[1]
11 .split(/[\s,/]+/)
12 .filter(Boolean)
13 .map(Number);
14 const [r, g, b, a = 1] = parts;
15 return a === 0 ? null : [r, g, b];
16 }
17 const hex = value.trim().match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
18 if (!hex) return null;
19 const h =
20 hex[1].length === 3
21 ? hex[1]
22 .split('')
23 .map((c) => c + c)
24 .join('')
25 : hex[1];
26 return [0, 2, 4].map((i) => Number.parseInt(h.slice(i, i + 2), 16));
27 };
28
29 const luminance = ([r, g, b]) => {
30 const channel = (v) => {
31 const s = v / 255;
32 return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
33 };
34 return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
35 };
36
37 const contrast = (a, b) => {
38 const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
39 return (hi + 0.05) / (lo + 0.05);
40 };
41
42 // A photo leaves every ancestor transparent, so there is no colour to judge.
43 const backdropOf = (el) => {
44 let node = el;
45 while (node?.nodeType === 1) {
46 // A cover paints its image and dim into a child, not itself.
47 if (node.classList?.contains('wp-block-cover')) {
48 const dim = node.querySelector(':scope > .wp-block-cover__background');
49 return dim ? parseColor(getComputedStyle(dim).backgroundColor) : null;
50 }
51 const color = parseColor(getComputedStyle(node).backgroundColor);
52 if (color) return color;
53 node = node.parentElement;
54 }
55 return null;
56 };
57
58 const overMedia = (el) =>
59 Boolean(
60 el?.closest?.('.wp-block-cover') ||
61 el?.matches?.('.wp-block-image, .wp-block-media-text') ||
62 el?.querySelector?.('img'),
63 );
64
65 const decide = (el) => {
66 const backdrop = backdropOf(el);
67 // A staged section nearly always contains an image somewhere.
68 if (!backdrop) return overMedia(el);
69 const partner = parseColor(
70 getComputedStyle(el).getPropertyValue('--wp--preset--color--primary'),
71 );
72 if (!partner) return false;
73 return contrast(partner, backdrop) < MIN_CONTRAST;
74 };
75
76 // Callers run on scroll; the walk above reads styles up the whole chain.
77 const decided = new WeakMap();
78
79 export const needsContrastRing = (el) => {
80 if (!el) return false;
81 if (!decided.has(el)) decided.set(el, decide(el));
82 return decided.get(el);
83 };
84