Skip to content
This repository was archived by the owner on Feb 6, 2024. It is now read-only.

Commit 8237953

Browse files
feat: recursively iterate through children to find correct colors and handle text node
1 parent f16a3d7 commit 8237953

File tree

3 files changed

+89
-47
lines changed

3 files changed

+89
-47
lines changed

studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import {Component, h, Host, Listen, State} from '@stencil/core';
22

3-
import {ContrastUtils, ParentsColors} from '../../../utils/editor/contrast.utils';
43
import {popoverController} from '@ionic/core';
54

5+
import {ContrastUtils} from '../../../utils/editor/contrast.utils';
6+
import {NodeUtils} from '../../../utils/editor/node.utils';
7+
68
@Component({
79
tag: 'app-slide-contrast',
810
styleUrl: 'app-slide-contrast.scss',
@@ -65,26 +67,26 @@ export class AppSlideContrast {
6567
return false;
6668
}
6769

68-
const slotsChildren = Array.from(slots).reduce((acc: HTMLElement[], slot: HTMLElement) => {
69-
const children: NodeListOf<HTMLElement> = slot.querySelectorAll('*');
70-
71-
if (children && children.length > 0) {
72-
acc.push(...Array.from(children));
73-
}
70+
// Slots with direct text children
71+
const slotsWithText: HTMLElement[] = await NodeUtils.childrenTextNode(slots);
7472

75-
return acc;
76-
}, []);
73+
// All children (<span/>) of the slots
74+
const children: HTMLElement[] = await NodeUtils.children(slots);
7775

78-
const elements: HTMLElement[] = slotsChildren && slotsChildren.length > 0 ? [...Array.from(slots), ...slotsChildren] : Array.from(slots);
76+
const elements: HTMLElement[] =
77+
children && children.length > 0
78+
? slotsWithText && slotsWithText.length > 0
79+
? [...Array.from(slotsWithText), ...children]
80+
: [...children]
81+
: slotsWithText && slotsWithText.length > 0
82+
? [...slotsWithText]
83+
: null;
7984

80-
const parentsColors: ParentsColors = {
81-
slideBgColor: slide.style.background,
82-
slideColor: slide.style.color,
83-
deckBgColor: deck.style.getPropertyValue('--background'),
84-
deckColor: deck.style.getPropertyValue('--color'),
85-
};
85+
if (!elements) {
86+
return false;
87+
}
8688

87-
const promises: Promise<number>[] = Array.from(elements).map((slot: HTMLElement) => ContrastUtils.calculateContrastRatio(slot, parentsColors));
89+
const promises: Promise<number>[] = Array.from(elements).map((element: HTMLElement) => this.calculateRatio(element, deck, slide));
8890

8991
const contrasts: number[] = await Promise.all(promises);
9092

@@ -97,6 +99,13 @@ export class AppSlideContrast {
9799
return lowContrast !== undefined;
98100
}
99101

102+
private async calculateRatio(element: HTMLElement, deck: HTMLElement, slide: HTMLElement) {
103+
const bgColor = await NodeUtils.findColors(element, 'background', deck, slide);
104+
const color = await NodeUtils.findColors(element, 'color', deck, slide);
105+
106+
return ContrastUtils.calculateContrastRatio(bgColor, color);
107+
}
108+
100109
private async openInformation($event: UIEvent) {
101110
const popover: HTMLIonPopoverElement = await popoverController.create({
102111
component: 'app-contrast-info',

studio/src/app/utils/editor/contrast.utils.tsx

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,18 @@
11
import {extractRgb, extractRgba} from '@deckdeckgo/utils';
22

3-
export interface ParentsColors {
4-
slideBgColor: string | undefined;
5-
slideColor: string | undefined;
6-
deckBgColor: string | undefined;
7-
deckColor: string | undefined;
8-
}
9-
103
export class ContrastUtils {
11-
static async calculateContrastRatio(element: HTMLElement, parentsColors: ParentsColors): Promise<number> {
12-
const style: CSSStyleDeclaration = window.getComputedStyle(element);
13-
14-
const bgColor: string =
15-
element.style.background !== '' && element.style.background !== 'initial'
16-
? style.backgroundColor
17-
: parentsColors.slideBgColor !== ''
18-
? parentsColors.slideBgColor
19-
: parentsColors.deckBgColor !== ''
20-
? parentsColors.deckBgColor
21-
: style.backgroundColor;
22-
23-
const color: string =
24-
element.style.color !== '' && element.style.color !== 'initial'
25-
? style.color
26-
: parentsColors.slideColor !== ''
27-
? parentsColors.slideColor
28-
: parentsColors.deckColor !== ''
29-
? parentsColors.deckColor
30-
: style.color;
4+
static async calculateContrastRatio(bgColor: string | undefined, color: string | undefined): Promise<number> {
5+
const bgColorWithDefault: string = bgColor === undefined || bgColor === '' ? `rgb(255, 255, 255)` : bgColor;
6+
const colorWithDefault: string = color === undefined || color === '' ? `rgb(0, 0, 0)` : color;
317

328
// The text color may or may not be semi-transparent, but that doesn't matter
33-
const bgRgba: number[] | undefined = extractRgba(bgColor);
9+
const bgRgba: number[] | undefined = extractRgba(bgColorWithDefault);
3410

3511
if (!bgRgba || bgRgba.length < 4 || bgRgba[3] >= 1) {
36-
return this.calculateContrastRatioOpaque(bgColor, color);
12+
return this.calculateContrastRatioOpaque(bgColorWithDefault, colorWithDefault);
3713
}
3814

39-
return this.calculateContrastRatioAlpha(bgColor, color);
15+
return this.calculateContrastRatioAlpha(bgColorWithDefault, colorWithDefault);
4016
}
4117

4218
private static calculateLuminance(rgb: number[]): number {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
export interface NodeColors {
2+
bgColor: string | undefined;
3+
color: string | undefined;
4+
}
5+
6+
export class NodeUtils {
7+
static async childrenTextNode(elements: NodeListOf<HTMLElement>): Promise<HTMLElement[]> {
8+
return Array.from(elements).reduce((acc: HTMLElement[], slot: HTMLElement) => {
9+
const text = Array.from(slot.childNodes).find((child) => child.nodeType === Node.TEXT_NODE);
10+
11+
if (text !== null && text !== undefined && text.textContent.replace(/(?:\r\n|\r|\n|\s)/g, '') !== '') {
12+
acc.push(slot);
13+
}
14+
15+
return acc;
16+
}, []);
17+
}
18+
19+
static async children(elements: NodeListOf<HTMLElement>): Promise<HTMLElement[]> {
20+
return Array.from(elements).reduce((acc: HTMLElement[], slot: HTMLElement) => {
21+
const children: NodeListOf<HTMLElement> = slot.querySelectorAll('*');
22+
23+
if (children && children.length > 0) {
24+
acc.push(...Array.from(children));
25+
}
26+
27+
return acc;
28+
}, []);
29+
}
30+
31+
static async findColors(node: HTMLElement, color: 'color' | 'background', slide: HTMLElement, deck: HTMLElement): Promise<string | undefined> {
32+
// Just in case
33+
if (node.nodeName.toUpperCase() === 'HTML' || node.nodeName.toUpperCase() === 'BODY') {
34+
return undefined;
35+
}
36+
37+
if (!node.parentNode) {
38+
return undefined;
39+
}
40+
41+
if (node.isEqualNode(deck)) {
42+
return deck.style.getPropertyValue(`--${color}`);
43+
}
44+
45+
if (node.isEqualNode(slide) && slide.style[color] !== '') {
46+
return slide.style[color];
47+
}
48+
49+
const styleAttr: string = color === 'background' ? 'background-color' : 'color';
50+
51+
if (node.style[styleAttr] !== '' && node.style[styleAttr] !== 'initial') {
52+
return node.style[color];
53+
}
54+
55+
return await this.findColors(node.parentElement, color, slide, deck);
56+
}
57+
}

0 commit comments

Comments
 (0)