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/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/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..5d980530b
--- /dev/null
+++ b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.scss
@@ -0,0 +1,40 @@
+app-slide-contrast {
+ position: absolute;
+ top: 16px;
+ left: 16px;
+
+ transition: opacity 0.5s;
+
+ visibility: initial;
+ opacity: 1;
+
+ &:not(.warning) {
+ 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
new file mode 100644
index 000000000..2f47f2fa1
--- /dev/null
+++ b/studio/src/app/components/editor/app-slide-contrast/app-slide-contrast.tsx
@@ -0,0 +1,133 @@
+import {Component, h, Host, Listen, State} from '@stencil/core';
+
+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',
+})
+export class AppSlideContrast {
+ private readonly lowestAACompliantLevel: number = 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('deckDidChange', {target: 'document'})
+ async onDeckDidChange() {
+ 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),[slot="author"]:not(:empty),deckgo-drr > section:not(:empty)'
+ );
+
+ if (!slots || slots.length <= 0) {
+ return false;
+ }
+
+ // Slots with direct text children
+ const slotsWithText: HTMLElement[] = await NodeUtils.childrenTextNode(slots);
+
+ // All children () of the slots
+ const children: HTMLElement[] = await NodeUtils.children(slots);
+
+ const elements: HTMLElement[] =
+ children && children.length > 0
+ ? slotsWithText && slotsWithText.length > 0
+ ? [...Array.from(slotsWithText), ...children]
+ : [...children]
+ : slotsWithText && slotsWithText.length > 0
+ ? [...slotsWithText]
+ : null;
+
+ if (!elements) {
+ return false;
+ }
+
+ const promises: Promise[] = Array.from(elements).map((element: HTMLElement) => this.calculateRatio(element, deck, slide));
+
+ 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);
+
+ 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);
+
+ console.log('yo', bgColor, color);
+
+ return ContrastUtils.calculateContrastRatio(bgColor, color);
+ }
+
+ 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 (
+
+
+
+ );
+ }
+}
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/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/app/utils/editor/contrast.utils.tsx b/studio/src/app/utils/editor/contrast.utils.tsx
new file mode 100644
index 000000000..cd9de1568
--- /dev/null
+++ b/studio/src/app/utils/editor/contrast.utils.tsx
@@ -0,0 +1,110 @@
+import {extractRgb, extractRgba} from '@deckdeckgo/utils';
+
+export class ContrastUtils {
+ 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(bgColorWithDefault);
+
+ if (!bgRgba || bgRgba.length < 4 || bgRgba[3] >= 1) {
+ return this.calculateContrastRatioOpaque(bgColorWithDefault, colorWithDefault);
+ }
+
+ return this.calculateContrastRatioAlpha(bgColorWithDefault, colorWithDefault);
+ }
+
+ 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 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}> {
+ 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 {
+ 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 {
+ 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);
+ }
+}
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..1be09e516
--- /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[styleAttr];
+ }
+
+ return await this.findColors(node.parentElement, color, slide, deck);
+ }
+}
diff --git a/studio/src/components.d.ts b/studio/src/components.d.ts
index 833c205e8..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 {
@@ -367,6 +369,8 @@ export namespace Components {
"redirect": string;
"redirectId": string;
}
+ interface AppSlideContrast {
+ }
interface AppSlideNavigate {
}
interface AppSlotType {
@@ -508,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: {
@@ -1000,6 +1010,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: {
@@ -1080,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;
@@ -1162,6 +1179,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;
@@ -1285,6 +1303,8 @@ declare namespace LocalJSX {
}
interface AppContactForm {
}
+ interface AppContrastInfo {
+ }
interface AppCreateSlide {
"onSignIn"?: (event: CustomEvent) => void;
}
@@ -1568,6 +1588,8 @@ declare namespace LocalJSX {
"redirect"?: string;
"redirectId"?: string;
}
+ interface AppSlideContrast {
+ }
interface AppSlideNavigate {
"onReorder"?: (event: CustomEvent) => void;
}
@@ -1615,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;
@@ -1697,6 +1720,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;
@@ -1732,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;
@@ -1814,6 +1839,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;
}
}
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",
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));
}