From c306290ba9e46c57e82272af51b6c6b35bc5b49c Mon Sep 17 00:00:00 2001
From: peterpeterparker
Date: Thu, 13 Aug 2020 13:36:27 +0200
Subject: [PATCH 01/12] feat: display an a11y warning when slide's colors do
not meet contrast ratio
---
.../app-slide-contrast.scss | 31 ++++++
.../app-slide-contrast/app-slide-contrast.tsx | 95 +++++++++++++++++++
.../pages/editor/app-editor/app-editor.tsx | 1 +
.../src/app/utils/editor/contrast.utils.tsx | 67 +++++++++++++
studio/src/components.d.ts | 13 +++
.../theme/editor/editor-fullscreen.scss | 9 ++
6 files changed, 216 insertions(+)
create mode 100644 studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.scss
create mode 100644 studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
create mode 100644 studio/src/app/utils/editor/contrast.utils.tsx
diff --git a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.scss b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.scss
new file mode 100644
index 000000000..4b7a6838f
--- /dev/null
+++ b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.scss
@@ -0,0 +1,31 @@
+app-slide-contrast {
+ position: absolute;
+ top: 16px;
+ left: 16px;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ background: var(--ion-color-warning);
+ color: var(--ion-color-warning-contrast);
+
+ padding: 8px 16px;
+ border-radius: 16px;
+
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
+
+ ion-label {
+ margin-right: 4px;
+ }
+
+ transition: opacity 0.5s;
+
+ visibility: initial;
+ opacity: 1;
+
+ &:not(.warning) {
+ visibility: hidden;
+ opacity: 0;
+ }
+}
diff --git a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
new file mode 100644
index 000000000..b64a92ae0
--- /dev/null
+++ b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
@@ -0,0 +1,95 @@
+import {Component, h, Host, Listen, State} from '@stencil/core';
+
+import {ContrastUtils, ParentsColors} from '../../../utils/editor/contrast.utils';
+
+@Component({
+ tag: 'app-slide-contrast',
+ styleUrl: 'app-slide-contrast.scss',
+})
+export class AppSlideContrast {
+ private readonly lowestAACompliantLevel: number = 1 / 3;
+
+ @State()
+ private warning: boolean = false;
+
+ @Listen('slidesDidLoad', {target: 'document'})
+ async onSlidesDidLoad() {
+ await this.analyzeContrast();
+ }
+
+ @Listen('slideDidUpdate', {target: 'document'})
+ async onSlideDidUpdate() {
+ await this.analyzeContrast();
+ }
+
+ @Listen('slideNextDidChange', {target: 'document'})
+ @Listen('slidePrevDidChange', {target: 'document'})
+ @Listen('slideToChange', {target: 'document'})
+ async onSlideNavigate() {
+ await this.analyzeContrast();
+ }
+
+ private async analyzeContrast() {
+ this.warning = await this.hasLowContrast();
+ }
+
+ private async hasLowContrast(): Promise {
+ const deck: HTMLElement = document.querySelector('main > deckgo-deck');
+
+ if (!deck) {
+ return false;
+ }
+
+ const index = await (deck as any).getActiveIndex();
+
+ const slide: HTMLElement = deck.querySelector('.deckgo-slide-container:nth-child(' + (index + 1) + ')');
+
+ if (!slide) {
+ return false;
+ }
+
+ const slots: NodeListOf = slide.querySelectorAll(
+ '[slot="title"]:not(:empty),[slot="content"]:not(:empty),[slot="start"]:not(:empty),[slot="end"]:not(:empty),[slot="header"]:not(:empty),[slot="footer"]:not(:empty)'
+ );
+
+ // TODO drr
+ // const slots: NodeListOf = slide.querySelectorAll('[slot="title"],[slot="content"],[slot="start"],[slot="end"],[slot="header"],[slot="footer"],deckgo-drr');
+
+ if (!slots || slots.length <= 0) {
+ return false;
+ }
+
+ const parentsColors: ParentsColors = {
+ slideBgColor: slide.style.background,
+ slideColor: slide.style.color,
+ deckBgColor: deck.style.getPropertyValue('--background'),
+ deckColor: deck.style.getPropertyValue('--color'),
+ };
+
+ const promises: Promise[] = Array.from(slots).map((slot: HTMLElement) => ContrastUtils.calculateContrastRatio(slot, parentsColors));
+
+ const contrasts: number[] = await Promise.all(promises);
+
+ if (!contrasts || contrasts.length <= 0) {
+ return false;
+ }
+
+ const lowContrast: number | undefined = contrasts.find((contrast: number) => contrast > this.lowestAACompliantLevel);
+
+ console.log(lowContrast, contrasts);
+
+ return lowContrast !== undefined;
+ }
+
+ render() {
+ return (
+
+ Low contrast
+
+
+ );
+ }
+}
diff --git a/studio/src/app/pages/editor/app-editor/app-editor.tsx b/studio/src/app/pages/editor/app-editor/app-editor.tsx
index 7df7243ba..c96b5dc61 100644
--- a/studio/src/app/pages/editor/app-editor/app-editor.tsx
+++ b/studio/src/app/pages/editor/app-editor/app-editor.tsx
@@ -652,6 +652,7 @@ export class AppEditor {
{this.footer}
+
,
diff --git a/studio/src/app/utils/editor/contrast.utils.tsx b/studio/src/app/utils/editor/contrast.utils.tsx
new file mode 100644
index 000000000..e54c1bb0c
--- /dev/null
+++ b/studio/src/app/utils/editor/contrast.utils.tsx
@@ -0,0 +1,67 @@
+export interface ParentsColors {
+ slideBgColor: string | undefined;
+ slideColor: string | undefined;
+ deckBgColor: string | undefined;
+ deckColor: string | undefined;
+}
+
+export class ContrastUtils {
+ static async calculateContrastRatio(element: HTMLElement, parentsColors: ParentsColors): Promise {
+ const style: CSSStyleDeclaration = window.getComputedStyle(element);
+
+ // TODO: how to handle alpha? both in custom color as in default backgroundColor rba(0,0,0,0)
+
+ const bgColor: string =
+ element.style.background !== ''
+ ? style.backgroundColor
+ : parentsColors.slideBgColor !== ''
+ ? parentsColors.slideBgColor
+ : parentsColors.deckBgColor !== ''
+ ? parentsColors.deckBgColor
+ : style.backgroundColor;
+ const color: string =
+ element.style.color !== ''
+ ? style.color
+ : parentsColors.slideColor !== ''
+ ? parentsColors.slideColor
+ : parentsColors.deckColor !== ''
+ ? parentsColors.deckColor
+ : style.color;
+
+ // TODO utils
+ const extractRgb = (rgb: string): number[] | undefined => {
+ const match: RegExpMatchArray | null = rgb.match(/(\d+),\s*(\d+),\s*(\d+)/);
+
+ if (!match) {
+ return undefined;
+ }
+
+ return match.splice(1, 3).map((v) => Number(v));
+ };
+
+ const bgRgb: number[] | undefined = extractRgb(bgColor);
+ const colorRgb: number[] | undefined = extractRgb(color);
+
+ if (bgColor === undefined || colorRgb === undefined) {
+ // 0 being AA and AAA level friendly. We assume that if for some reason we can't extract color, we better not display a warning about it.
+ return 0;
+ }
+
+ const lumBg: number = this.calculateLuminance(bgRgb);
+ const lumColor: number = this.calculateLuminance(colorRgb);
+
+ return this.calculateColorContrastRatio(lumBg, lumColor);
+ }
+
+ private static calculateLuminance(rgb: number[]): number {
+ const a = rgb.map((v) => {
+ v /= 255;
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
+ });
+ return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
+ }
+
+ private static calculateColorContrastRatio(firstColor: number, secondColor: number): number {
+ return firstColor > secondColor ? (secondColor + 0.05) / (firstColor + 0.05) : (firstColor + 0.05) / (secondColor + 0.05);
+ }
+}
diff --git a/studio/src/components.d.ts b/studio/src/components.d.ts
index 833c205e8..024458398 100644
--- a/studio/src/components.d.ts
+++ b/studio/src/components.d.ts
@@ -367,6 +367,8 @@ export namespace Components {
"redirect": string;
"redirectId": string;
}
+ interface AppSlideContrast {
+ }
interface AppSlideNavigate {
}
interface AppSlotType {
@@ -1000,6 +1002,12 @@ declare global {
prototype: HTMLAppSigninElement;
new (): HTMLAppSigninElement;
};
+ interface HTMLAppSlideContrastElement extends Components.AppSlideContrast, HTMLStencilElement {
+ }
+ var HTMLAppSlideContrastElement: {
+ prototype: HTMLAppSlideContrastElement;
+ new (): HTMLAppSlideContrastElement;
+ };
interface HTMLAppSlideNavigateElement extends Components.AppSlideNavigate, HTMLStencilElement {
}
var HTMLAppSlideNavigateElement: {
@@ -1162,6 +1170,7 @@ declare global {
"app-share-deck": HTMLAppShareDeckElement;
"app-share-options": HTMLAppShareOptionsElement;
"app-signin": HTMLAppSigninElement;
+ "app-slide-contrast": HTMLAppSlideContrastElement;
"app-slide-navigate": HTMLAppSlideNavigateElement;
"app-slot-type": HTMLAppSlotTypeElement;
"app-team": HTMLAppTeamElement;
@@ -1568,6 +1577,8 @@ declare namespace LocalJSX {
"redirect"?: string;
"redirectId"?: string;
}
+ interface AppSlideContrast {
+ }
interface AppSlideNavigate {
"onReorder"?: (event: CustomEvent) => void;
}
@@ -1697,6 +1708,7 @@ declare namespace LocalJSX {
"app-share-deck": AppShareDeck;
"app-share-options": AppShareOptions;
"app-signin": AppSignin;
+ "app-slide-contrast": AppSlideContrast;
"app-slide-navigate": AppSlideNavigate;
"app-slot-type": AppSlotType;
"app-team": AppTeam;
@@ -1814,6 +1826,7 @@ declare module "@stencil/core" {
"app-share-deck": LocalJSX.AppShareDeck & JSXBase.HTMLAttributes;
"app-share-options": LocalJSX.AppShareOptions & JSXBase.HTMLAttributes;
"app-signin": LocalJSX.AppSignin & JSXBase.HTMLAttributes;
+ "app-slide-contrast": LocalJSX.AppSlideContrast & JSXBase.HTMLAttributes;
"app-slide-navigate": LocalJSX.AppSlideNavigate & JSXBase.HTMLAttributes;
"app-slot-type": LocalJSX.AppSlotType & JSXBase.HTMLAttributes;
"app-team": LocalJSX.AppTeam & JSXBase.HTMLAttributes;
diff --git a/studio/src/global/theme/editor/editor-fullscreen.scss b/studio/src/global/theme/editor/editor-fullscreen.scss
index 1a5047142..aa3832122 100644
--- a/studio/src/global/theme/editor/editor-fullscreen.scss
+++ b/studio/src/global/theme/editor/editor-fullscreen.scss
@@ -60,6 +60,15 @@
box-shadow: none;
}
}
+
+ app-slide-contrast {
+ display: none;
+ }
+ }
+
+ app-slide-contrast {
+ top: 32px;
+ left: 32px;
}
}
From b8c2143ad4eac101b0039035c474c1142fd2c95f Mon Sep 17 00:00:00 2001
From: peterpeterparker
Date: Thu, 13 Aug 2020 14:03:27 +0200
Subject: [PATCH 02/12] feat: iterate through slots children
---
.../app-slide-contrast/app-slide-contrast.tsx | 21 ++++++++++++++++---
.../src/app/utils/editor/contrast.utils.tsx | 4 ++--
2 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
index b64a92ae0..ebc31b1fe 100644
--- a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
+++ b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
@@ -22,6 +22,11 @@ export class AppSlideContrast {
await this.analyzeContrast();
}
+ @Listen('deckDidChange', {target: 'document'})
+ async onDeckDidChange() {
+ await this.analyzeContrast();
+ }
+
@Listen('slideNextDidChange', {target: 'document'})
@Listen('slidePrevDidChange', {target: 'document'})
@Listen('slideToChange', {target: 'document'})
@@ -59,6 +64,18 @@ export class AppSlideContrast {
return false;
}
+ const slotsChildren = Array.from(slots).reduce((acc: HTMLElement[], slot: HTMLElement) => {
+ const children: NodeListOf = slot.querySelectorAll('*');
+
+ if (children && children.length > 0) {
+ acc.push(...Array.from(children));
+ }
+
+ return acc;
+ }, []);
+
+ const elements: HTMLElement[] = slotsChildren && slotsChildren.length > 0 ? [...Array.from(slots), ...slotsChildren] : Array.from(slots);
+
const parentsColors: ParentsColors = {
slideBgColor: slide.style.background,
slideColor: slide.style.color,
@@ -66,7 +83,7 @@ export class AppSlideContrast {
deckColor: deck.style.getPropertyValue('--color'),
};
- const promises: Promise[] = Array.from(slots).map((slot: HTMLElement) => ContrastUtils.calculateContrastRatio(slot, parentsColors));
+ const promises: Promise[] = Array.from(elements).map((slot: HTMLElement) => ContrastUtils.calculateContrastRatio(slot, parentsColors));
const contrasts: number[] = await Promise.all(promises);
@@ -76,8 +93,6 @@ export class AppSlideContrast {
const lowContrast: number | undefined = contrasts.find((contrast: number) => contrast > this.lowestAACompliantLevel);
- console.log(lowContrast, contrasts);
-
return lowContrast !== undefined;
}
diff --git a/studio/src/app/utils/editor/contrast.utils.tsx b/studio/src/app/utils/editor/contrast.utils.tsx
index e54c1bb0c..c3a4e3319 100644
--- a/studio/src/app/utils/editor/contrast.utils.tsx
+++ b/studio/src/app/utils/editor/contrast.utils.tsx
@@ -12,7 +12,7 @@ export class ContrastUtils {
// TODO: how to handle alpha? both in custom color as in default backgroundColor rba(0,0,0,0)
const bgColor: string =
- element.style.background !== ''
+ element.style.background !== '' && element.style.background !== 'initial'
? style.backgroundColor
: parentsColors.slideBgColor !== ''
? parentsColors.slideBgColor
@@ -20,7 +20,7 @@ export class ContrastUtils {
? parentsColors.deckBgColor
: style.backgroundColor;
const color: string =
- element.style.color !== ''
+ element.style.color !== '' && element.style.color !== 'initial'
? style.color
: parentsColors.slideColor !== ''
? parentsColors.slideColor
From 4f8b4a060ab728eb530762cf0b20f44bb575450c Mon Sep 17 00:00:00 2001
From: peterpeterparker
Date: Sat, 15 Aug 2020 09:14:47 +0200
Subject: [PATCH 03/12] feat: handle opacity in contrast warning
---
.../app-slide-contrast/app-slide-contrast.tsx | 4 +-
.../src/app/utils/editor/contrast.utils.tsx | 150 ++++++++++++++++--
2 files changed, 136 insertions(+), 18 deletions(-)
diff --git a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
index ebc31b1fe..043394188 100644
--- a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
+++ b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
@@ -7,7 +7,7 @@ import {ContrastUtils, ParentsColors} from '../../../utils/editor/contrast.utils
styleUrl: 'app-slide-contrast.scss',
})
export class AppSlideContrast {
- private readonly lowestAACompliantLevel: number = 1 / 3;
+ private readonly lowestAACompliantLevel: number = 3;
@State()
private warning: boolean = false;
@@ -91,7 +91,7 @@ export class AppSlideContrast {
return false;
}
- const lowContrast: number | undefined = contrasts.find((contrast: number) => contrast > this.lowestAACompliantLevel);
+ const lowContrast: number | undefined = contrasts.find((contrast: number) => contrast < this.lowestAACompliantLevel);
return lowContrast !== undefined;
}
diff --git a/studio/src/app/utils/editor/contrast.utils.tsx b/studio/src/app/utils/editor/contrast.utils.tsx
index c3a4e3319..80ff77b7c 100644
--- a/studio/src/app/utils/editor/contrast.utils.tsx
+++ b/studio/src/app/utils/editor/contrast.utils.tsx
@@ -9,8 +9,6 @@ export class ContrastUtils {
static async calculateContrastRatio(element: HTMLElement, parentsColors: ParentsColors): Promise {
const style: CSSStyleDeclaration = window.getComputedStyle(element);
- // TODO: how to handle alpha? both in custom color as in default backgroundColor rba(0,0,0,0)
-
const bgColor: string =
element.style.background !== '' && element.style.background !== 'initial'
? style.backgroundColor
@@ -19,6 +17,7 @@ export class ContrastUtils {
: parentsColors.deckBgColor !== ''
? parentsColors.deckBgColor
: style.backgroundColor;
+
const color: string =
element.style.color !== '' && element.style.color !== 'initial'
? style.color
@@ -28,9 +27,140 @@ export class ContrastUtils {
? parentsColors.deckColor
: style.color;
- // TODO utils
+ // The text color may or may not be semi-transparent, but that doesn't matter
+ const extractRgba = (rgb: string): number[] | undefined => {
+ const match: RegExpMatchArray | null = rgb.match(/([.\d]+),\s*([.\d]+),\s*([.\d]+),\s*([.\d]+)/);
+
+ if (!match) {
+ return undefined;
+ }
+
+ return match.splice(1, 4).map((v) => Number(v));
+ };
+
+ const bgRgba = extractRgba(bgColor);
+
+ if (!bgRgba || bgRgba.length < 4 || bgRgba[3] >= 1) {
+ return this.calculateContrastRatioOpaque(bgColor, color);
+ }
+
+ return this.calculateContrastRatioAlpha(bgColor, color);
+ }
+
+ private static calculateLuminance(rgb: number[]): number {
+ const a = rgb.map((v) => {
+ v /= 255;
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
+ });
+ return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
+ }
+
+ private static calculateColorContrastRatio(firstColorLum: number, secondColorLum: number): number {
+ // return firstColorLum > secondColorLum ? (secondColorLum + 0.05) / (firstColorLum + 0.05) : (firstColorLum + 0.05) / (secondColorLum + 0.05);
+
+ const l1 = firstColorLum + 0.05;
+ const l2 = secondColorLum + 0.05;
+
+ let ratio = l1 / l2;
+
+ if (l2 > l1) {
+ ratio = 1 / ratio;
+ }
+
+ return ratio;
+ }
+
+ // Source: https://github.com/LeaVerou/contrast-ratio/blob/eb7fe8f16206869f8d36d517d7eb0962830d0e81/color.js#L86
+ private static async convertAlphaRgba(color: string, base: number[]): Promise {
+ const extractRgba = (rgb: string): number[] | undefined => {
+ const match: RegExpMatchArray | null = rgb.match(/([.\d]+),\s*([.\d]+),\s*([.\d]+),\s*([.\d]+)/);
+
+ if (!match) {
+ return undefined;
+ }
+
+ return match.splice(1, 4).map((v) => Number(v));
+ };
+
+ const rgba: number[] | undefined = extractRgba(color);
+
+ if (!rgba || rgba.length < 4) {
+ return color;
+ }
+
+ const alpha: number = rgba[3];
+
+ const rgb: number[] = [];
+
+ for (let i = 0; i < 3; i++) {
+ rgb.push(rgba[i] * alpha + base[i] * base[3] * (1 - alpha));
+ }
+
+ // Not used here
+ // rgb[3] = alpha + base[3] * (1 - alpha);
+
+ return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
+ }
+
+ private static async calculateColorContrastRatioWithBase(
+ bgColor: string,
+ lumColor: number,
+ base: number[]
+ ): Promise<{luminanceOverlay: number; ratio: number}> {
+ // TODO extract utils
+ const extractRgb = (rgb: string): number[] | undefined => {
+ const match: RegExpMatchArray | null = rgb.match(/([.\d]+),\s*([.\d]+),\s*([.\d]+)/);
+
+ if (!match) {
+ return undefined;
+ }
+
+ return match.splice(1, 3).map((v) => Number(v));
+ };
+
+ const overlay = extractRgb(await this.convertAlphaRgba(bgColor, base));
+
+ const lumOverlay: number = this.calculateLuminance(overlay);
+
+ return {
+ luminanceOverlay: lumOverlay,
+ ratio: this.calculateColorContrastRatio(lumOverlay, lumColor),
+ };
+ }
+
+ private static async calculateContrastRatioAlpha(bgColor: string, color: string): Promise {
+ // TODO extract utils
const extractRgb = (rgb: string): number[] | undefined => {
- const match: RegExpMatchArray | null = rgb.match(/(\d+),\s*(\d+),\s*(\d+)/);
+ const match: RegExpMatchArray | null = rgb.match(/([.\d]+),\s*([.\d]+),\s*([.\d]+)/);
+
+ if (!match) {
+ return undefined;
+ }
+
+ return match.splice(1, 3).map((v) => Number(v));
+ };
+
+ const lumColor: number = this.calculateLuminance(extractRgb(color));
+
+ const onBlack: {luminanceOverlay: number; ratio: number} = await this.calculateColorContrastRatioWithBase(bgColor, lumColor, [0, 0, 0, 1]);
+ const onWhite: {luminanceOverlay: number; ratio: number} = await this.calculateColorContrastRatioWithBase(bgColor, lumColor, [255, 255, 255, 1]);
+
+ const max = Math.max(onBlack.ratio, onWhite.ratio);
+
+ let min = 1;
+ if (onBlack.luminanceOverlay > lumColor) {
+ min = onBlack.ratio;
+ } else if (onWhite.luminanceOverlay < lumColor) {
+ min = onWhite.ratio;
+ }
+
+ return (min + max) / 2;
+ }
+
+ private static async calculateContrastRatioOpaque(bgColor: string, color: string): Promise {
+ // TODO extract utils
+ const extractRgb = (rgb: string): number[] | undefined => {
+ const match: RegExpMatchArray | null = rgb.match(/([.\d]+),\s*([.\d]+),\s*([.\d]+)/);
if (!match) {
return undefined;
@@ -52,16 +182,4 @@ export class ContrastUtils {
return this.calculateColorContrastRatio(lumBg, lumColor);
}
-
- private static calculateLuminance(rgb: number[]): number {
- const a = rgb.map((v) => {
- v /= 255;
- return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
- });
- return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
- }
-
- private static calculateColorContrastRatio(firstColor: number, secondColor: number): number {
- return firstColor > secondColor ? (secondColor + 0.05) / (firstColor + 0.05) : (firstColor + 0.05) / (secondColor + 0.05);
- }
}
From bc79b6a88736d603cb64101c6daba91b83bc975e Mon Sep 17 00:00:00 2001
From: peterpeterparker
Date: Sat, 15 Aug 2020 09:19:36 +0200
Subject: [PATCH 04/12] feat: extractRGB with decimals and expose functions
---
utils/utils/src/utils/color-utils.ts | 26 ++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)
diff --git a/utils/utils/src/utils/color-utils.ts b/utils/utils/src/utils/color-utils.ts
index 7667e8e23..8bae4b254 100644
--- a/utils/utils/src/utils/color-utils.ts
+++ b/utils/utils/src/utils/color-utils.ts
@@ -23,15 +23,25 @@ export async function rgbToHex(rgb: string | undefined): Promise v.toString(16).padStart(2, '0')).join('')}`;
};
- const extractRgb = (rgb: string): number[] | undefined => {
- const match: RegExpMatchArray | null = rgb.match(/(\d+),\s*(\d+),\s*(\d+)/);
+ return toHex(extractRgb(rgb));
+}
- if (!match) {
- return undefined;
- }
+export function extractRgb(rgb: string): number[] | undefined {
+ const match: RegExpMatchArray | null = rgb.match(/([.\d]+),\s*([.\d]+),\s*([.\d]+)/);
- return match.splice(1, 3).map((v) => Number(v));
- };
+ if (!match) {
+ return undefined;
+ }
- return toHex(extractRgb(rgb));
+ return match.splice(1, 3).map((v) => Number(v));
+}
+
+export function extractRgba(rgb: string): number[] | undefined {
+ const match: RegExpMatchArray | null = rgb.match(/([.\d]+),\s*([.\d]+),\s*([.\d]+),\s*([.\d]+)/);
+
+ if (!match) {
+ return undefined;
+ }
+
+ return match.splice(1, 4).map((v) => Number(v));
}
From d120e040f9a13e44e3c04bda9829e1f3d8d07231 Mon Sep 17 00:00:00 2001
From: peterpeterparker
Date: Sat, 15 Aug 2020 09:22:17 +0200
Subject: [PATCH 05/12] release: utils v1.3.0
---
CHANGELOG.md | 2 +-
utils/utils/CHANGELOG.md | 8 ++++++++
utils/utils/package-lock.json | 2 +-
utils/utils/package.json | 2 +-
4 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b6257ce44..36e57f2a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,7 +18,7 @@
### Others
- deck-utils: v2.4.0 ([CHANGELOG](https://github.com/deckgo/deckdeckgo/blob/master/utils/deck/CHANGELOG.md))
-- utils: v1.2.0 ([CHANGELOG](https://github.com/deckgo/deckdeckgo/blob/master/utils/utils/CHANGELOG.md))
+- utils: v1.3.0 ([CHANGELOG](https://github.com/deckgo/deckdeckgo/blob/master/utils/utils/CHANGELOG.md))
- starter kit: v2.6.4 ([CHANGELOG](https://github.com/deckgo/deckdeckgo-starter/blob/master/CHANGELOG.md))
diff --git a/utils/utils/CHANGELOG.md b/utils/utils/CHANGELOG.md
index 906e26a20..9d9bf6438 100644
--- a/utils/utils/CHANGELOG.md
+++ b/utils/utils/CHANGELOG.md
@@ -1,3 +1,11 @@
+
+
+# 1.3.0 (2020-08-15)
+
+- improve `extractRgb` to support decimals value (as for example `rgb(5.5, 4.7, 4)`)
+- expose function `extractRgb`
+- add and expose function `extractRgba`
+
# 1.2.0 (2020-07-31)
diff --git a/utils/utils/package-lock.json b/utils/utils/package-lock.json
index 7df9c7b0d..c2e6f5f07 100644
--- a/utils/utils/package-lock.json
+++ b/utils/utils/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "@deckdeckgo/utils",
- "version": "1.2.0",
+ "version": "1.3.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/utils/utils/package.json b/utils/utils/package.json
index 821dffdf1..72e158016 100644
--- a/utils/utils/package.json
+++ b/utils/utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@deckdeckgo/utils",
- "version": "1.2.0",
+ "version": "1.3.0",
"author": "David Dal Busco",
"description": "A collection of utils methods and functions developed for DeckDeckGo",
"license": "MIT",
From 54f6f8e8a685a1bb13628dc5a455f2217e980a75 Mon Sep 17 00:00:00 2001
From: peterpeterparker
Date: Sat, 15 Aug 2020 09:26:21 +0200
Subject: [PATCH 06/12] feat: use extracted functions
---
studio/package-lock.json | 13 ++++--
studio/package.json | 2 +-
.../src/app/utils/editor/contrast.utils.tsx | 45 +------------------
3 files changed, 13 insertions(+), 47 deletions(-)
diff --git a/studio/package-lock.json b/studio/package-lock.json
index 89a54bce7..e79b3ab9b 100644
--- a/studio/package-lock.json
+++ b/studio/package-lock.json
@@ -102,6 +102,13 @@
"requires": {
"@deckdeckgo/utils": "1.2.0",
"prismjs": "^1.21.0"
+ },
+ "dependencies": {
+ "@deckdeckgo/utils": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@deckdeckgo/utils/-/utils-1.2.0.tgz",
+ "integrity": "sha512-VAZAOeHdMKITOMawcur5Ih71F7VouAZyihSZda9s24q6dqf3K+3nHjtLa+v8cOzTIUkFkVzWGggvTSrcKjQMkw=="
+ }
}
},
"@deckdeckgo/inline-editor": {
@@ -263,9 +270,9 @@
"dev": true
},
"@deckdeckgo/utils": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@deckdeckgo/utils/-/utils-1.2.0.tgz",
- "integrity": "sha512-VAZAOeHdMKITOMawcur5Ih71F7VouAZyihSZda9s24q6dqf3K+3nHjtLa+v8cOzTIUkFkVzWGggvTSrcKjQMkw=="
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@deckdeckgo/utils/-/utils-1.3.0.tgz",
+ "integrity": "sha512-QrV3jyqIlL+u1NUqSyMsnIsfORLqBuzEhK2+rGYCT89TLVhnPpOP0b11aP/XvHyJMzG9mZvjadLN/MQ9ls9UGw=="
},
"@deckdeckgo/youtube": {
"version": "1.1.2",
diff --git a/studio/package.json b/studio/package.json
index 3270c94ff..66235f4be 100644
--- a/studio/package.json
+++ b/studio/package.json
@@ -40,7 +40,7 @@
"@deckdeckgo/slide-title": "^1.1.3",
"@deckdeckgo/slide-youtube": "^1.1.2",
"@deckdeckgo/social": "^2.0.0",
- "@deckdeckgo/utils": "^1.2.0",
+ "@deckdeckgo/utils": "^1.3.0",
"@deckdeckgo/youtube": "^1.1.2",
"@ionic/core": "^5.3.1",
"firebase": "^7.17.2",
diff --git a/studio/src/app/utils/editor/contrast.utils.tsx b/studio/src/app/utils/editor/contrast.utils.tsx
index 80ff77b7c..bf77b6602 100644
--- a/studio/src/app/utils/editor/contrast.utils.tsx
+++ b/studio/src/app/utils/editor/contrast.utils.tsx
@@ -1,3 +1,5 @@
+import {extractRgb, extractRgba} from '@deckdeckgo/utils';
+
export interface ParentsColors {
slideBgColor: string | undefined;
slideColor: string | undefined;
@@ -72,16 +74,6 @@ export class ContrastUtils {
// Source: https://github.com/LeaVerou/contrast-ratio/blob/eb7fe8f16206869f8d36d517d7eb0962830d0e81/color.js#L86
private static async convertAlphaRgba(color: string, base: number[]): Promise {
- const extractRgba = (rgb: string): number[] | undefined => {
- const match: RegExpMatchArray | null = rgb.match(/([.\d]+),\s*([.\d]+),\s*([.\d]+),\s*([.\d]+)/);
-
- if (!match) {
- return undefined;
- }
-
- return match.splice(1, 4).map((v) => Number(v));
- };
-
const rgba: number[] | undefined = extractRgba(color);
if (!rgba || rgba.length < 4) {
@@ -107,17 +99,6 @@ export class ContrastUtils {
lumColor: number,
base: number[]
): Promise<{luminanceOverlay: number; ratio: number}> {
- // TODO extract utils
- const extractRgb = (rgb: string): number[] | undefined => {
- const match: RegExpMatchArray | null = rgb.match(/([.\d]+),\s*([.\d]+),\s*([.\d]+)/);
-
- if (!match) {
- return undefined;
- }
-
- return match.splice(1, 3).map((v) => Number(v));
- };
-
const overlay = extractRgb(await this.convertAlphaRgba(bgColor, base));
const lumOverlay: number = this.calculateLuminance(overlay);
@@ -129,17 +110,6 @@ export class ContrastUtils {
}
private static async calculateContrastRatioAlpha(bgColor: string, color: string): Promise {
- // TODO extract utils
- const extractRgb = (rgb: string): number[] | undefined => {
- const match: RegExpMatchArray | null = rgb.match(/([.\d]+),\s*([.\d]+),\s*([.\d]+)/);
-
- if (!match) {
- return undefined;
- }
-
- return match.splice(1, 3).map((v) => Number(v));
- };
-
const lumColor: number = this.calculateLuminance(extractRgb(color));
const onBlack: {luminanceOverlay: number; ratio: number} = await this.calculateColorContrastRatioWithBase(bgColor, lumColor, [0, 0, 0, 1]);
@@ -158,17 +128,6 @@ export class ContrastUtils {
}
private static async calculateContrastRatioOpaque(bgColor: string, color: string): Promise {
- // TODO extract utils
- const extractRgb = (rgb: string): number[] | undefined => {
- const match: RegExpMatchArray | null = rgb.match(/([.\d]+),\s*([.\d]+),\s*([.\d]+)/);
-
- if (!match) {
- return undefined;
- }
-
- return match.splice(1, 3).map((v) => Number(v));
- };
-
const bgRgb: number[] | undefined = extractRgb(bgColor);
const colorRgb: number[] | undefined = extractRgb(color);
From 2d941ed17d48891ce2f2e6a7e5cfe8c64f89d699 Mon Sep 17 00:00:00 2001
From: peterpeterparker
Date: Sat, 15 Aug 2020 10:09:29 +0200
Subject: [PATCH 07/12] feat: contrast explanations
---
.../app-slide-contrast.scss | 41 +++++++++++--------
.../app-slide-contrast/app-slide-contrast.tsx | 19 ++++++++-
.../app-contrast-info/app-contrast-info.tsx | 35 ++++++++++++++++
.../editor/app-get-help/app-get-help.tsx | 2 +-
studio/src/components.d.ts | 13 ++++++
5 files changed, 91 insertions(+), 19 deletions(-)
create mode 100644 studio/src/app/popovers/editor/app-contrast-info/app-contrast-info.tsx
diff --git a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.scss b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.scss
index 4b7a6838f..5d980530b 100644
--- a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.scss
+++ b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.scss
@@ -3,22 +3,6 @@ app-slide-contrast {
top: 16px;
left: 16px;
- display: flex;
- justify-content: center;
- align-items: center;
-
- background: var(--ion-color-warning);
- color: var(--ion-color-warning-contrast);
-
- padding: 8px 16px;
- border-radius: 16px;
-
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
-
- ion-label {
- margin-right: 4px;
- }
-
transition: opacity 0.5s;
visibility: initial;
@@ -28,4 +12,29 @@ app-slide-contrast {
visibility: hidden;
opacity: 0;
}
+
+ button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ background: var(--ion-color-warning);
+ color: var(--ion-color-warning-contrast);
+
+ padding: 6px 12px;
+ border-radius: 64px;
+
+ position: relative;
+ overflow: hidden;
+
+ outline: 0;
+
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
+
+ font-size: var(--font-size-small);
+
+ ion-label {
+ margin-right: 4px;
+ }
+ }
}
diff --git a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
index 043394188..d8c255d6b 100644
--- a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
+++ b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
@@ -1,6 +1,7 @@
import {Component, h, Host, Listen, State} from '@stencil/core';
import {ContrastUtils, ParentsColors} from '../../../utils/editor/contrast.utils';
+import {popoverController} from '@ionic/core';
@Component({
tag: 'app-slide-contrast',
@@ -96,14 +97,28 @@ export class AppSlideContrast {
return lowContrast !== undefined;
}
+ private async openInformation($event: UIEvent) {
+ const popover: HTMLIonPopoverElement = await popoverController.create({
+ component: 'app-contrast-info',
+ event: $event,
+ mode: 'ios',
+ cssClass: 'info',
+ });
+
+ await popover.present();
+ }
+
render() {
return (
- Low contrast
-
+
);
}
diff --git a/studio/src/app/popovers/editor/app-contrast-info/app-contrast-info.tsx b/studio/src/app/popovers/editor/app-contrast-info/app-contrast-info.tsx
new file mode 100644
index 000000000..e9f21471d
--- /dev/null
+++ b/studio/src/app/popovers/editor/app-contrast-info/app-contrast-info.tsx
@@ -0,0 +1,35 @@
+import {Component, Element, h} from '@stencil/core';
+
+@Component({
+ tag: 'app-contrast-info',
+})
+export class AppContrastInfo {
+ @Element() el: HTMLElement;
+
+ private async closePopover() {
+ await (this.el.closest('ion-popover') as HTMLIonPopoverElement).dismiss();
+ }
+
+ render() {
+ return (
+
+
Low contrast
+
We noticed that (a part of) the text color of this slide does not meet contrast ratio standards.
+
+ Elements are compared according{' '}
+
+ WCAG
+ {' '}
+ Level AA.
+
+
+
Note that if you are using semi-transparent background, the contrast ratio cannot be precise.
+
+ this.closePopover()}>
+ Got it
+
+
+
+ );
+ }
+}
diff --git a/studio/src/app/popovers/editor/app-get-help/app-get-help.tsx b/studio/src/app/popovers/editor/app-get-help/app-get-help.tsx
index cc8e7bd84..4494fa8f6 100644
--- a/studio/src/app/popovers/editor/app-get-help/app-get-help.tsx
+++ b/studio/src/app/popovers/editor/app-get-help/app-get-help.tsx
@@ -29,7 +29,7 @@ export class AppGetHelp {
.
-
+
this.closePopover()}>
Got it
diff --git a/studio/src/components.d.ts b/studio/src/components.d.ts
index 024458398..c105e527f 100644
--- a/studio/src/components.d.ts
+++ b/studio/src/components.d.ts
@@ -109,6 +109,8 @@ export namespace Components {
}
interface AppContactForm {
}
+ interface AppContrastInfo {
+ }
interface AppCreateSlide {
}
interface AppCustomData {
@@ -510,6 +512,12 @@ declare global {
prototype: HTMLAppContactFormElement;
new (): HTMLAppContactFormElement;
};
+ interface HTMLAppContrastInfoElement extends Components.AppContrastInfo, HTMLStencilElement {
+ }
+ var HTMLAppContrastInfoElement: {
+ prototype: HTMLAppContrastInfoElement;
+ new (): HTMLAppContrastInfoElement;
+ };
interface HTMLAppCreateSlideElement extends Components.AppCreateSlide, HTMLStencilElement {
}
var HTMLAppCreateSlideElement: {
@@ -1088,6 +1096,7 @@ declare global {
"app-color-text-background": HTMLAppColorTextBackgroundElement;
"app-contact": HTMLAppContactElement;
"app-contact-form": HTMLAppContactFormElement;
+ "app-contrast-info": HTMLAppContrastInfoElement;
"app-create-slide": HTMLAppCreateSlideElement;
"app-custom-data": HTMLAppCustomDataElement;
"app-custom-images": HTMLAppCustomImagesElement;
@@ -1294,6 +1303,8 @@ declare namespace LocalJSX {
}
interface AppContactForm {
}
+ interface AppContrastInfo {
+ }
interface AppCreateSlide {
"onSignIn"?: (event: CustomEvent) => void;
}
@@ -1626,6 +1637,7 @@ declare namespace LocalJSX {
"app-color-text-background": AppColorTextBackground;
"app-contact": AppContact;
"app-contact-form": AppContactForm;
+ "app-contrast-info": AppContrastInfo;
"app-create-slide": AppCreateSlide;
"app-custom-data": AppCustomData;
"app-custom-images": AppCustomImages;
@@ -1744,6 +1756,7 @@ declare module "@stencil/core" {
"app-color-text-background": LocalJSX.AppColorTextBackground & JSXBase.HTMLAttributes;
"app-contact": LocalJSX.AppContact & JSXBase.HTMLAttributes;
"app-contact-form": LocalJSX.AppContactForm & JSXBase.HTMLAttributes;
+ "app-contrast-info": LocalJSX.AppContrastInfo & JSXBase.HTMLAttributes;
"app-create-slide": LocalJSX.AppCreateSlide & JSXBase.HTMLAttributes;
"app-custom-data": LocalJSX.AppCustomData & JSXBase.HTMLAttributes;
"app-custom-images": LocalJSX.AppCustomImages & JSXBase.HTMLAttributes;
From f16a3d76c3e2936e297a5b254b0f0e4fe57b09f1 Mon Sep 17 00:00:00 2001
From: peterpeterparker
Date: Sun, 16 Aug 2020 09:59:39 +0200
Subject: [PATCH 08/12] refactor: use utils
---
studio/src/app/utils/editor/contrast.utils.tsx | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/studio/src/app/utils/editor/contrast.utils.tsx b/studio/src/app/utils/editor/contrast.utils.tsx
index bf77b6602..d405f398d 100644
--- a/studio/src/app/utils/editor/contrast.utils.tsx
+++ b/studio/src/app/utils/editor/contrast.utils.tsx
@@ -30,17 +30,7 @@ export class ContrastUtils {
: style.color;
// The text color may or may not be semi-transparent, but that doesn't matter
- const extractRgba = (rgb: string): number[] | undefined => {
- const match: RegExpMatchArray | null = rgb.match(/([.\d]+),\s*([.\d]+),\s*([.\d]+),\s*([.\d]+)/);
-
- if (!match) {
- return undefined;
- }
-
- return match.splice(1, 4).map((v) => Number(v));
- };
-
- const bgRgba = extractRgba(bgColor);
+ const bgRgba: number[] | undefined = extractRgba(bgColor);
if (!bgRgba || bgRgba.length < 4 || bgRgba[3] >= 1) {
return this.calculateContrastRatioOpaque(bgColor, color);
From 82379534c9fa36a4de40bb0cbae1a311e6c016ad Mon Sep 17 00:00:00 2001
From: peterpeterparker
Date: Mon, 17 Aug 2020 16:42:11 +0200
Subject: [PATCH 09/12] feat: recursively iterate through children to find
correct colors and handle text node
---
.../app-slide-contrast/app-slide-contrast.tsx | 43 ++++++++------
.../src/app/utils/editor/contrast.utils.tsx | 36 ++----------
studio/src/app/utils/editor/node.utils.tsx | 57 +++++++++++++++++++
3 files changed, 89 insertions(+), 47 deletions(-)
create mode 100644 studio/src/app/utils/editor/node.utils.tsx
diff --git a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
index d8c255d6b..baa3d034b 100644
--- a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
+++ b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
@@ -1,8 +1,10 @@
import {Component, h, Host, Listen, State} from '@stencil/core';
-import {ContrastUtils, ParentsColors} from '../../../utils/editor/contrast.utils';
import {popoverController} from '@ionic/core';
+import {ContrastUtils} from '../../../utils/editor/contrast.utils';
+import {NodeUtils} from '../../../utils/editor/node.utils';
+
@Component({
tag: 'app-slide-contrast',
styleUrl: 'app-slide-contrast.scss',
@@ -65,26 +67,26 @@ export class AppSlideContrast {
return false;
}
- const slotsChildren = Array.from(slots).reduce((acc: HTMLElement[], slot: HTMLElement) => {
- const children: NodeListOf = slot.querySelectorAll('*');
-
- if (children && children.length > 0) {
- acc.push(...Array.from(children));
- }
+ // Slots with direct text children
+ const slotsWithText: HTMLElement[] = await NodeUtils.childrenTextNode(slots);
- return acc;
- }, []);
+ // All children () of the slots
+ const children: HTMLElement[] = await NodeUtils.children(slots);
- const elements: HTMLElement[] = slotsChildren && slotsChildren.length > 0 ? [...Array.from(slots), ...slotsChildren] : Array.from(slots);
+ const elements: HTMLElement[] =
+ children && children.length > 0
+ ? slotsWithText && slotsWithText.length > 0
+ ? [...Array.from(slotsWithText), ...children]
+ : [...children]
+ : slotsWithText && slotsWithText.length > 0
+ ? [...slotsWithText]
+ : null;
- const parentsColors: ParentsColors = {
- slideBgColor: slide.style.background,
- slideColor: slide.style.color,
- deckBgColor: deck.style.getPropertyValue('--background'),
- deckColor: deck.style.getPropertyValue('--color'),
- };
+ if (!elements) {
+ return false;
+ }
- const promises: Promise[] = Array.from(elements).map((slot: HTMLElement) => ContrastUtils.calculateContrastRatio(slot, parentsColors));
+ const promises: Promise[] = Array.from(elements).map((element: HTMLElement) => this.calculateRatio(element, deck, slide));
const contrasts: number[] = await Promise.all(promises);
@@ -97,6 +99,13 @@ export class AppSlideContrast {
return lowContrast !== undefined;
}
+ private async calculateRatio(element: HTMLElement, deck: HTMLElement, slide: HTMLElement) {
+ const bgColor = await NodeUtils.findColors(element, 'background', deck, slide);
+ const color = await NodeUtils.findColors(element, 'color', deck, slide);
+
+ return ContrastUtils.calculateContrastRatio(bgColor, color);
+ }
+
private async openInformation($event: UIEvent) {
const popover: HTMLIonPopoverElement = await popoverController.create({
component: 'app-contrast-info',
diff --git a/studio/src/app/utils/editor/contrast.utils.tsx b/studio/src/app/utils/editor/contrast.utils.tsx
index d405f398d..cd9de1568 100644
--- a/studio/src/app/utils/editor/contrast.utils.tsx
+++ b/studio/src/app/utils/editor/contrast.utils.tsx
@@ -1,42 +1,18 @@
import {extractRgb, extractRgba} from '@deckdeckgo/utils';
-export interface ParentsColors {
- slideBgColor: string | undefined;
- slideColor: string | undefined;
- deckBgColor: string | undefined;
- deckColor: string | undefined;
-}
-
export class ContrastUtils {
- static async calculateContrastRatio(element: HTMLElement, parentsColors: ParentsColors): Promise {
- const style: CSSStyleDeclaration = window.getComputedStyle(element);
-
- const bgColor: string =
- element.style.background !== '' && element.style.background !== 'initial'
- ? style.backgroundColor
- : parentsColors.slideBgColor !== ''
- ? parentsColors.slideBgColor
- : parentsColors.deckBgColor !== ''
- ? parentsColors.deckBgColor
- : style.backgroundColor;
-
- const color: string =
- element.style.color !== '' && element.style.color !== 'initial'
- ? style.color
- : parentsColors.slideColor !== ''
- ? parentsColors.slideColor
- : parentsColors.deckColor !== ''
- ? parentsColors.deckColor
- : style.color;
+ static async calculateContrastRatio(bgColor: string | undefined, color: string | undefined): Promise {
+ const bgColorWithDefault: string = bgColor === undefined || bgColor === '' ? `rgb(255, 255, 255)` : bgColor;
+ const colorWithDefault: string = color === undefined || color === '' ? `rgb(0, 0, 0)` : color;
// The text color may or may not be semi-transparent, but that doesn't matter
- const bgRgba: number[] | undefined = extractRgba(bgColor);
+ const bgRgba: number[] | undefined = extractRgba(bgColorWithDefault);
if (!bgRgba || bgRgba.length < 4 || bgRgba[3] >= 1) {
- return this.calculateContrastRatioOpaque(bgColor, color);
+ return this.calculateContrastRatioOpaque(bgColorWithDefault, colorWithDefault);
}
- return this.calculateContrastRatioAlpha(bgColor, color);
+ return this.calculateContrastRatioAlpha(bgColorWithDefault, colorWithDefault);
}
private static calculateLuminance(rgb: number[]): number {
diff --git a/studio/src/app/utils/editor/node.utils.tsx b/studio/src/app/utils/editor/node.utils.tsx
new file mode 100644
index 000000000..553cd4803
--- /dev/null
+++ b/studio/src/app/utils/editor/node.utils.tsx
@@ -0,0 +1,57 @@
+export interface NodeColors {
+ bgColor: string | undefined;
+ color: string | undefined;
+}
+
+export class NodeUtils {
+ static async childrenTextNode(elements: NodeListOf): Promise {
+ return Array.from(elements).reduce((acc: HTMLElement[], slot: HTMLElement) => {
+ const text = Array.from(slot.childNodes).find((child) => child.nodeType === Node.TEXT_NODE);
+
+ if (text !== null && text !== undefined && text.textContent.replace(/(?:\r\n|\r|\n|\s)/g, '') !== '') {
+ acc.push(slot);
+ }
+
+ return acc;
+ }, []);
+ }
+
+ static async children(elements: NodeListOf): Promise {
+ return Array.from(elements).reduce((acc: HTMLElement[], slot: HTMLElement) => {
+ const children: NodeListOf = slot.querySelectorAll('*');
+
+ if (children && children.length > 0) {
+ acc.push(...Array.from(children));
+ }
+
+ return acc;
+ }, []);
+ }
+
+ static async findColors(node: HTMLElement, color: 'color' | 'background', slide: HTMLElement, deck: HTMLElement): Promise {
+ // Just in case
+ if (node.nodeName.toUpperCase() === 'HTML' || node.nodeName.toUpperCase() === 'BODY') {
+ return undefined;
+ }
+
+ if (!node.parentNode) {
+ return undefined;
+ }
+
+ if (node.isEqualNode(deck)) {
+ return deck.style.getPropertyValue(`--${color}`);
+ }
+
+ if (node.isEqualNode(slide) && slide.style[color] !== '') {
+ return slide.style[color];
+ }
+
+ const styleAttr: string = color === 'background' ? 'background-color' : 'color';
+
+ if (node.style[styleAttr] !== '' && node.style[styleAttr] !== 'initial') {
+ return node.style[color];
+ }
+
+ return await this.findColors(node.parentElement, color, slide, deck);
+ }
+}
From 20ed7db58e90aae0d07a8a3f2585e06d35ca55b7 Mon Sep 17 00:00:00 2001
From: peterpeterparker
Date: Mon, 17 Aug 2020 16:47:03 +0200
Subject: [PATCH 10/12] feat: check contrast author slot
---
.../components/editor/app-slide-contrast/app-slide-contrast.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
index baa3d034b..1a8970ea1 100644
--- a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
+++ b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
@@ -57,7 +57,7 @@ export class AppSlideContrast {
}
const slots: NodeListOf = slide.querySelectorAll(
- '[slot="title"]:not(:empty),[slot="content"]:not(:empty),[slot="start"]:not(:empty),[slot="end"]:not(:empty),[slot="header"]:not(:empty),[slot="footer"]:not(:empty)'
+ '[slot="title"]:not(:empty),[slot="content"]:not(:empty),[slot="start"]:not(:empty),[slot="end"]:not(:empty),[slot="header"]:not(:empty),[slot="footer"]:not(:empty),[slot="author"]:not(:empty)'
);
// TODO drr
From dd442e4584f66b03d48b43252e519add7f7e3ec6 Mon Sep 17 00:00:00 2001
From: peterpeterparker
Date: Mon, 17 Aug 2020 17:02:34 +0200
Subject: [PATCH 11/12] fix: color attribute
---
studio/src/app/utils/editor/node.utils.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/studio/src/app/utils/editor/node.utils.tsx b/studio/src/app/utils/editor/node.utils.tsx
index 553cd4803..1be09e516 100644
--- a/studio/src/app/utils/editor/node.utils.tsx
+++ b/studio/src/app/utils/editor/node.utils.tsx
@@ -49,7 +49,7 @@ export class NodeUtils {
const styleAttr: string = color === 'background' ? 'background-color' : 'color';
if (node.style[styleAttr] !== '' && node.style[styleAttr] !== 'initial') {
- return node.style[color];
+ return node.style[styleAttr];
}
return await this.findColors(node.parentElement, color, slide, deck);
From 8206cfdaee41c6ee84f456bc372ec9cd60582c9e Mon Sep 17 00:00:00 2001
From: peterpeterparker
Date: Mon, 17 Aug 2020 17:06:46 +0200
Subject: [PATCH 12/12] feat: add drr to check contrast
---
.../editor/app-slide-contrast/app-slide-contrast.tsx | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
index 1a8970ea1..2f47f2fa1 100644
--- a/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
+++ b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
@@ -57,12 +57,9 @@ export class AppSlideContrast {
}
const slots: NodeListOf = slide.querySelectorAll(
- '[slot="title"]:not(:empty),[slot="content"]:not(:empty),[slot="start"]:not(:empty),[slot="end"]:not(:empty),[slot="header"]:not(:empty),[slot="footer"]:not(:empty),[slot="author"]:not(:empty)'
+ '[slot="title"]:not(:empty),[slot="content"]:not(:empty),[slot="start"]:not(:empty),[slot="end"]:not(:empty),[slot="header"]:not(:empty),[slot="footer"]:not(:empty),[slot="author"]:not(:empty),deckgo-drr > section:not(:empty)'
);
- // TODO drr
- // const slots: NodeListOf = slide.querySelectorAll('[slot="title"],[slot="content"],[slot="start"],[slot="end"],[slot="header"],[slot="footer"],deckgo-drr');
-
if (!slots || slots.length <= 0) {
return false;
}
@@ -103,6 +100,8 @@ export class AppSlideContrast {
const bgColor = await NodeUtils.findColors(element, 'background', deck, slide);
const color = await NodeUtils.findColors(element, 'color', deck, slide);
+ console.log('yo', bgColor, color);
+
return ContrastUtils.calculateContrastRatio(bgColor, color);
}