@qualweb/act-rules 0.7.4 → 0.7.6

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.
@@ -1,23 +1,21 @@
1
1
  import type { QWElement } from '@qualweb/qw-element';
2
- import { Test } from '@qualweb/core/evaluation';
3
2
  import { AtomicRule } from '../lib/AtomicRule.object';
4
3
  declare class QW_ACT_R37 extends AtomicRule {
5
4
  execute(element: QWElement): void;
6
- getBackground(element: QWElement): string;
7
- isImage(color: string): boolean;
8
- evaluateGradient(test: Test, element: QWElement, parsedGradientString: any, fgColor: any, opacity: number, fontSize: string, fontWeight: string, fontStyle: string, fontFamily: string, elementText: string): boolean;
9
- isHumanLanguage(text: string): boolean;
10
- equals(color1: any, color2: any): boolean;
11
- getGradientDirection(gradient: string): string | undefined;
12
- parseGradientString(gradient: string, opacity: number): any;
13
- parseRGBString(colorString: string, opacity: number): any;
14
- getRelativeLuminance(red: number, green: number, blue: number): number;
15
- flattenColors(fgColor: any, bgColor: any): any;
5
+ private isHumanLanguage;
6
+ private evaluateGradient;
7
+ private parseGradientString;
8
+ private getColorInGradient;
9
+ private getTextSize;
10
+ private getBackground;
11
+ private isImage;
12
+ private parseRGBString;
13
+ private flattenColors;
14
+ private getContrast;
15
+ private getLuminance;
16
+ private hasValidContrastRatio;
16
17
  private isBold;
17
- getContrast(bgColor: any, fgColor: any): number;
18
- hasValidContrastRatio(contrast: number, fontSize: string, isBold: boolean): boolean;
19
- getTextSize(font: string, fontSize: number, bold: boolean, italic: boolean, text: string): number;
20
- getColorInGradient(fromColor: any, toColor: any, ratio: number): any;
18
+ private equals;
21
19
  }
22
20
  export { QW_ACT_R37 };
23
21
  //# sourceMappingURL=QW-ACT-R37.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"QW-ACT-R37.d.ts","sourceRoot":"","sources":["../../src/rules/QW-ACT-R37.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQrD,OAAO,EAAE,IAAI,EAAW,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,cAAM,UAAW,SAAQ,UAAU;IAMjC,OAAO,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI;IAuOjC,aAAa,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM;IAczC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAS/B,gBAAgB,CACd,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,SAAS,EAClB,oBAAoB,EAAE,GAAG,EACzB,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO;IAoDV,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAItC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO;IASzC,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAY1D,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG;IAW3D,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG;IAiCzD,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAYtE,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,GAAG;IAU9C,OAAO,CAAC,MAAM;IAId,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM;IAW/C,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO;IAWnF,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAIjG,kBAAkB,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG;CAOrE;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"QW-ACT-R37.d.ts","sourceRoot":"","sources":["../../src/rules/QW-ACT-R37.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQrD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAUtD,cAAM,UAAW,SAAQ,UAAU;IAKjC,OAAO,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI;IAqIjC,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,gBAAgB;IA+BxB,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,cAAc;IAkBtB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,MAAM;CAGf;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -8,42 +8,44 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.QW_ACT_R37 = void 0;
13
16
  const applicability_1 = require("@qualweb/util/applicability");
14
17
  const evaluation_1 = require("@qualweb/core/evaluation");
15
18
  const AtomicRule_object_1 = require("../lib/AtomicRule.object");
19
+ const colorjs_io_1 = __importDefault(require("colorjs.io"));
16
20
  class QW_ACT_R37 extends AtomicRule_object_1.AtomicRule {
17
21
  execute(element) {
22
+ var _a;
18
23
  const visible = window.DomUtils.isElementVisible(element);
19
- if (!visible) {
24
+ if (!visible)
20
25
  return;
21
- }
22
- const hasTextNode = element.hasTextNode();
23
- const elementText = element.getElementOwnText();
24
- if (!hasTextNode && elementText.trim() === '') {
26
+ const nodeName = element.getElementTagName();
27
+ const isInputField = ['input', 'select', 'textarea'].includes(nodeName);
28
+ const elementText = element.getElementOwnText().trim();
29
+ const placeholder = (_a = element.getElementAttribute('placeholder')) === null || _a === void 0 ? void 0 : _a.trim();
30
+ if (elementText === '' && !isInputField && !placeholder) {
25
31
  return;
26
32
  }
27
- const isHTML = element.isElementHTMLElement();
28
- if (!isHTML) {
33
+ if (!element.isElementHTMLElement())
29
34
  return;
30
- }
31
35
  const disabledWidgets = window.disabledWidgets;
32
36
  const elementSelectors = element.getElementSelector();
33
37
  for (const disableWidget of disabledWidgets || []) {
34
- const selectors = window.AccessibilityUtils.getAccessibleNameSelector(disableWidget);
35
- if (disableWidget && selectors && selectors.includes(elementSelectors)) {
38
+ const selectorsResult = window.AccessibilityUtils.getAccessibleNameSelector(disableWidget);
39
+ const selectors = typeof selectorsResult === 'string' ? [selectorsResult] : selectorsResult;
40
+ if (disableWidget && selectors && selectors.includes(elementSelectors))
36
41
  return;
37
- }
38
- if (disableWidget.getElementSelector() === elementSelectors) {
42
+ if (disableWidget.getElementSelector() === elementSelectors)
39
43
  return;
40
- }
41
44
  const children = disableWidget.getElementChildren();
42
45
  if (children) {
43
46
  for (const child of children) {
44
- if (child.getElementSelector() === elementSelectors) {
47
+ if (child.getElementSelector() === elementSelectors)
45
48
  return;
46
- }
47
49
  }
48
50
  }
49
51
  }
@@ -51,27 +53,25 @@ class QW_ACT_R37 extends AtomicRule_object_1.AtomicRule {
51
53
  if (role === 'group') {
52
54
  const disable = element.getElementAttribute('disabled') !== null;
53
55
  const ariaDisable = element.getElementAttribute('aria-disabled') !== null;
54
- if (disable || ariaDisable) {
56
+ if (disable || ariaDisable)
55
57
  return;
56
- }
57
58
  }
58
59
  const test = new evaluation_1.Test();
59
60
  const fgColor = element.getElementStyleProperty('color', null);
60
61
  let bgColor = this.getBackground(element);
61
- const opacity = parseFloat(element.getElementStyleProperty('opacity', null));
62
+ const opacity = parseFloat(element.getElementStyleProperty('opacity', null) || '1');
62
63
  const fontSize = element.getElementStyleProperty('font-size', null);
63
64
  const fontWeight = element.getElementStyleProperty('font-weight', null);
64
65
  const fontFamily = element.getElementStyleProperty('font-family', null);
65
66
  const fontStyle = element.getElementStyleProperty('font-style', null);
66
67
  const textShadow = element.getElementStyleProperty('text-shadow', null);
67
- if (textShadow.trim() !== 'none') {
68
- const properties = textShadow.trim().split(' ');
69
- if (properties.length === 6) {
70
- const vs = parseInt(properties[3], 0);
71
- const hs = parseInt(properties[4], 0);
72
- const blur = parseInt(properties[5], 0);
73
- const validateTextShadow = vs === 0 && hs === 0 && blur > 0 && blur <= 15;
74
- if (validateTextShadow) {
68
+ if (textShadow && textShadow.trim() !== 'none' && textShadow.trim() !== '') {
69
+ const pixelValues = textShadow.match(/(-?\d+px)/g);
70
+ if (pixelValues && pixelValues.length >= 3) {
71
+ const hs = Math.abs(parseInt(pixelValues[0].replace('px', ''), 10));
72
+ const vs = Math.abs(parseInt(pixelValues[1].replace('px', ''), 10));
73
+ const blur = parseInt(pixelValues[2].replace('px', ''), 10);
74
+ if (blur > 0 || hs > 1 || vs > 1) {
75
75
  test.verdict = evaluation_1.Verdict.WARNING;
76
76
  test.resultCode = 'W1';
77
77
  test.addElement(element);
@@ -90,9 +90,8 @@ class QW_ACT_R37 extends AtomicRule_object_1.AtomicRule {
90
90
  const regexGradient = /((\w-?)*gradient.*)/gm;
91
91
  let regexGradientMatches = bgColor.match(regexGradient);
92
92
  if (regexGradientMatches) {
93
- if (this.isHumanLanguage(elementText)) {
94
- const parsedGradientString = regexGradientMatches[0];
95
- this.evaluateGradient(test, element, parsedGradientString, fgColor, opacity, fontSize, fontWeight, fontStyle, fontFamily, elementText);
93
+ if (this.isHumanLanguage(elementText || placeholder || "")) {
94
+ this.evaluateGradient(test, element, regexGradientMatches[0], fgColor, opacity, fontSize, fontWeight, fontStyle, fontFamily, elementText || placeholder || "");
96
95
  }
97
96
  else {
98
97
  test.verdict = evaluation_1.Verdict.PASSED;
@@ -100,85 +99,40 @@ class QW_ACT_R37 extends AtomicRule_object_1.AtomicRule {
100
99
  test.addElement(element);
101
100
  this.addTestResult(test);
102
101
  }
102
+ return;
103
103
  }
104
- else {
105
- let parsedBG = this.parseRGBString(bgColor, opacity);
106
- let elementAux = element;
107
- let opacityAUX;
108
- while (parsedBG === undefined ||
109
- (parsedBG.red === 0 && parsedBG.green === 0 && parsedBG.blue === 0 && parsedBG.alpha === 0)) {
110
- const parent = elementAux.getElementParent();
111
- if (parent) {
112
- bgColor = this.getBackground(parent);
113
- if (this.isImage(bgColor)) {
114
- test.verdict = evaluation_1.Verdict.WARNING;
115
- test.resultCode = 'W2';
116
- test.addElement(element);
117
- this.addTestResult(test);
118
- return;
119
- }
120
- else {
121
- regexGradientMatches = bgColor.match(regexGradient);
122
- if (regexGradientMatches) {
123
- const parsedGradientString = regexGradientMatches[0];
124
- if (this.evaluateGradient(test, element, parsedGradientString, fgColor, opacity, fontSize, fontWeight, fontStyle, fontFamily, elementText)) {
125
- return;
126
- }
127
- }
128
- else {
129
- opacityAUX = parseFloat(parent.getElementStyleProperty('opacity', null));
130
- parsedBG = this.parseRGBString(parent.getElementStyleProperty('background', null), opacityAUX);
131
- elementAux = parent;
132
- }
133
- }
134
- }
135
- else {
136
- break;
137
- }
138
- }
139
- if (parsedBG === undefined ||
140
- (parsedBG.red === 0 && parsedBG.green === 0 && parsedBG.blue === 0 && parsedBG.alpha === 0)) {
141
- parsedBG = { red: 255, green: 255, blue: 255, alpha: 1 };
142
- }
143
- let secondElement = elementAux;
144
- if (parsedBG.alpha !== 1) {
145
- let secondOpacity = opacityAUX;
146
- let parsedSecondBG = parsedBG;
147
- while (parsedSecondBG && parsedSecondBG.alpha !== 1) {
148
- const parent = secondElement.getElementParent();
149
- if (!parent) {
150
- break;
151
- }
152
- else {
153
- secondOpacity = parseFloat(parent.getElementStyleProperty('opacity', null));
154
- parsedSecondBG = this.parseRGBString(parent.getElementStyleProperty('background', null), secondOpacity);
155
- secondElement = parent;
156
- }
157
- }
158
- if (parsedSecondBG && parsedSecondBG.alpha === 1) {
159
- const outputRed = Math.round(parsedBG.red * parsedBG.alpha + parsedSecondBG.red * (1.0 - parsedBG.alpha));
160
- const outputGreen = Math.round(parsedBG.green * parsedBG.alpha + parsedSecondBG.green * (1.0 - parsedBG.alpha));
161
- const outputBlue = Math.round(parsedBG.blue * parsedBG.alpha + parsedSecondBG.blue * (1.0 - parsedBG.alpha));
162
- parsedBG = { red: outputRed, green: outputGreen, blue: outputBlue, alpha: 1 };
163
- }
164
- }
165
- const parsedFG = this.parseRGBString(fgColor, opacity);
104
+ let parsedBG = this.parseRGBString(bgColor);
105
+ if (parsedBG)
106
+ parsedBG.alpha *= opacity;
107
+ let elementAux = element;
108
+ while (!parsedBG || (parsedBG.red === 0 && parsedBG.green === 0 && parsedBG.blue === 0 && parsedBG.alpha === 0)) {
109
+ const parent = elementAux.getElementParent();
110
+ if (!parent)
111
+ break;
112
+ const parentOpacity = parseFloat(parent.getElementStyleProperty('opacity', null) || '1');
113
+ parsedBG = this.parseRGBString(this.getBackground(parent));
114
+ if (parsedBG)
115
+ parsedBG.alpha *= parentOpacity;
116
+ elementAux = parent;
117
+ }
118
+ if (!parsedBG || parsedBG.alpha === 0) {
119
+ parsedBG = { red: 255, green: 255, blue: 255, alpha: 1 };
120
+ }
121
+ if (parsedBG.alpha < 1) {
122
+ parsedBG = this.flattenColors(parsedBG, { red: 255, green: 255, blue: 255, alpha: 1 });
123
+ }
124
+ const parsedFG = this.parseRGBString(fgColor);
125
+ if (parsedFG) {
126
+ parsedFG.alpha *= opacity;
166
127
  if (!this.equals(parsedBG, parsedFG)) {
167
- if (this.isHumanLanguage(elementText)) {
128
+ const textToVerify = elementText || placeholder || "";
129
+ if (this.isHumanLanguage(textToVerify)) {
168
130
  const contrastRatio = this.getContrast(parsedBG, parsedFG);
169
131
  const isValid = this.hasValidContrastRatio(contrastRatio, fontSize, this.isBold(fontWeight));
170
- if (isValid) {
171
- test.verdict = evaluation_1.Verdict.PASSED;
172
- test.resultCode = 'P1';
173
- test.addElement(element);
174
- this.addTestResult(test);
175
- }
176
- else {
177
- test.verdict = evaluation_1.Verdict.FAILED;
178
- test.resultCode = 'F1';
179
- test.addElement(element);
180
- this.addTestResult(test);
181
- }
132
+ test.verdict = isValid ? evaluation_1.Verdict.PASSED : evaluation_1.Verdict.FAILED;
133
+ test.resultCode = isValid ? 'P1' : 'F1';
134
+ test.addElement(element);
135
+ this.addTestResult(test);
182
136
  }
183
137
  else {
184
138
  test.verdict = evaluation_1.Verdict.PASSED;
@@ -189,168 +143,130 @@ class QW_ACT_R37 extends AtomicRule_object_1.AtomicRule {
189
143
  }
190
144
  }
191
145
  }
192
- getBackground(element) {
193
- const backgroundImage = element.getElementStyleProperty('background-image', null);
194
- if (backgroundImage === 'none') {
195
- let bg = element.getElementStyleProperty('background', null);
196
- if (bg === '') {
197
- bg = element.getElementStyleProperty('background-color', null);
198
- }
199
- return bg;
200
- }
201
- else {
202
- return backgroundImage;
203
- }
204
- }
205
- isImage(color) {
206
- return (color.toLowerCase().includes('.jpeg') ||
207
- color.toLowerCase().includes('.jpg') ||
208
- color.toLowerCase().includes('.png') ||
209
- color.toLowerCase().includes('.svg'));
146
+ isHumanLanguage(text) {
147
+ return window.DomUtils.isHumanLanguage(text);
210
148
  }
211
149
  evaluateGradient(test, element, parsedGradientString, fgColor, opacity, fontSize, fontWeight, fontStyle, fontFamily, elementText) {
212
150
  if (parsedGradientString.startsWith('linear-gradient')) {
213
- const gradientDirection = this.getGradientDirection(parsedGradientString);
214
- if (gradientDirection === 'to do') {
215
- const colors = this.parseGradientString(parsedGradientString, opacity);
216
- let isValid = true;
217
- let contrastRatio;
218
- const textSize = this.getTextSize(fontFamily.toLowerCase().replace(/['"]+/g, ''), parseInt(fontSize.replace('px', '')), this.isBold(fontWeight), fontStyle.toLowerCase().includes('italic'), elementText);
219
- if (textSize !== -1) {
220
- const elementWidth = element.getElementStyleProperty('width', null);
221
- const lastCharRatio = textSize / parseInt(elementWidth.replace('px', ''));
222
- const lastCharBgColor = this.getColorInGradient(colors[0], colors[colors.length - 1], lastCharRatio);
223
- contrastRatio = this.getContrast(colors[0], this.parseRGBString(fgColor, opacity));
224
- isValid = isValid && this.hasValidContrastRatio(contrastRatio, fontSize, this.isBold(fontWeight));
225
- contrastRatio = this.getContrast(lastCharBgColor, this.parseRGBString(fgColor, opacity));
226
- isValid = isValid && this.hasValidContrastRatio(contrastRatio, fontSize, this.isBold(fontWeight));
227
- }
228
- else {
229
- for (const color of colors) {
230
- contrastRatio = this.getContrast(color, this.parseRGBString(fgColor, opacity));
231
- isValid = isValid && this.hasValidContrastRatio(contrastRatio, fontSize, this.isBold(fontWeight));
232
- }
233
- }
234
- if (isValid) {
235
- test.verdict = evaluation_1.Verdict.PASSED;
236
- test.resultCode = 'P3';
237
- }
238
- else {
239
- test.verdict = evaluation_1.Verdict.FAILED;
240
- test.resultCode = 'F2';
241
- }
242
- }
243
- else if (gradientDirection === 'to left' || gradientDirection === 'to right') {
244
- test.verdict = evaluation_1.Verdict.WARNING;
245
- test.resultCode = 'W3';
151
+ const colors = this.parseGradientString(parsedGradientString);
152
+ let isValid = true;
153
+ const parsedFG = this.parseRGBString(fgColor);
154
+ if (!parsedFG)
155
+ return false;
156
+ parsedFG.alpha *= opacity;
157
+ const textSize = this.getTextSize(fontFamily.toLowerCase().replace(/['"]+/g, ''), parseInt(fontSize.replace('px', '')), this.isBold(fontWeight), fontStyle.toLowerCase().includes('italic'), elementText);
158
+ if (textSize !== -1) {
159
+ const elementWidth = element.getElementStyleProperty('width', null);
160
+ const lastCharRatio = textSize / parseInt(elementWidth.replace('px', ''));
161
+ const lastCharBgColor = this.getColorInGradient(colors[0], colors[colors.length - 1], lastCharRatio);
162
+ isValid = isValid && this.hasValidContrastRatio(this.getContrast(colors[0], parsedFG), fontSize, this.isBold(fontWeight));
163
+ isValid = isValid && this.hasValidContrastRatio(this.getContrast(lastCharBgColor, parsedFG), fontSize, this.isBold(fontWeight));
246
164
  }
247
165
  else {
248
- test.verdict = evaluation_1.Verdict.WARNING;
249
- test.resultCode = 'W3';
166
+ for (const color of colors) {
167
+ isValid = isValid && this.hasValidContrastRatio(this.getContrast(color, parsedFG), fontSize, this.isBold(fontWeight));
168
+ }
250
169
  }
170
+ test.verdict = isValid ? evaluation_1.Verdict.PASSED : evaluation_1.Verdict.FAILED;
171
+ test.resultCode = isValid ? 'P3' : 'F2';
251
172
  }
252
173
  else {
174
+ test.verdict = evaluation_1.Verdict.WARNING;
253
175
  test.resultCode = 'W3';
254
176
  }
255
177
  test.addElement(element);
256
178
  this.addTestResult(test);
257
179
  return true;
258
180
  }
259
- isHumanLanguage(text) {
260
- return window.DomUtils.isHumanLanguage(text);
261
- }
262
- equals(color1, color2) {
263
- return (color1.red === color2.red &&
264
- color1.green === color2.green &&
265
- color1.blue === color2.blue &&
266
- color1.alpha === color2.alpha);
267
- }
268
- getGradientDirection(gradient) {
269
- const direction = gradient.replace('linear-gradient(', '').split(',')[0];
270
- if (direction) {
271
- if (direction === '90deg')
272
- return 'to right';
273
- if (direction === '-90deg')
274
- return 'to left';
275
- return direction;
276
- }
277
- return undefined;
278
- }
279
- parseGradientString(gradient, opacity) {
181
+ parseGradientString(gradient) {
280
182
  const regex = /rgb(a?)\((\d+), (\d+), (\d+)+(, +(\d)+)?\)/gm;
281
183
  const colorsMatch = gradient.match(regex);
282
184
  const colors = [];
283
185
  for (const stringColor of colorsMatch || []) {
284
- colors.push(this.parseRGBString(stringColor, opacity));
186
+ const parsed = this.parseRGBString(stringColor);
187
+ if (parsed)
188
+ colors.push(parsed);
285
189
  }
286
190
  return colors;
287
191
  }
288
- parseRGBString(colorString, opacity) {
289
- const rgbRegex = /^rgb\((\d+), (\d+), (\d+)\)/;
290
- const rgbaRegex = /^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/;
291
- if (colorString === 'transparent') {
192
+ getColorInGradient(fromColor, toColor, ratio) {
193
+ return {
194
+ red: fromColor.red + (toColor.red - fromColor.red) * ratio,
195
+ green: fromColor.green + (toColor.green - fromColor.green) * ratio,
196
+ blue: fromColor.blue + (toColor.blue - fromColor.blue) * ratio,
197
+ alpha: 1
198
+ };
199
+ }
200
+ getTextSize(font, fontSize, bold, italic, text) {
201
+ return window.DomUtils.getTextSize(font, fontSize, bold, italic, text);
202
+ }
203
+ getBackground(element) {
204
+ const bgImg = element.getElementStyleProperty('background-image', null);
205
+ if (bgImg && bgImg !== 'none' && bgImg !== '')
206
+ return bgImg;
207
+ const bgColor = element.getElementStyleProperty('background-color', null);
208
+ return (bgColor && bgColor !== '' && bgColor !== 'transparent') ? bgColor : element.getElementStyleProperty('background', null);
209
+ }
210
+ isImage(s) {
211
+ const lower = s.toLowerCase();
212
+ return lower.includes('.jpg') || lower.includes('.png') || lower.includes('.svg') || lower.includes('url(');
213
+ }
214
+ parseRGBString(colorString) {
215
+ var _a;
216
+ if (!colorString || colorString === 'transparent' || colorString === 'none')
292
217
  return { red: 0, green: 0, blue: 0, alpha: 0 };
293
- }
294
- let match = colorString.match(rgbRegex);
295
- if (match) {
218
+ const rgb = colorString.match(/^rgb\((\d+), (\d+), (\d+)\)/);
219
+ if (rgb)
220
+ return { red: parseInt(rgb[1]), green: parseInt(rgb[2]), blue: parseInt(rgb[3]), alpha: 1.0 };
221
+ const rgba = colorString.match(/^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/);
222
+ if (rgba)
223
+ return { red: parseInt(rgba[1]), green: parseInt(rgba[2]), blue: parseInt(rgba[3]), alpha: Math.round(parseFloat(rgba[4]) * 100) / 100 };
224
+ try {
225
+ const color = new colorjs_io_1.default(colorString);
226
+ const srgb = color.to('srgb');
296
227
  return {
297
- red: parseInt(match[1], 10),
298
- green: parseInt(match[2], 10),
299
- blue: parseInt(match[3], 10),
300
- alpha: opacity
228
+ red: Math.round(srgb.coords[0] * 255),
229
+ green: Math.round(srgb.coords[1] * 255),
230
+ blue: Math.round(srgb.coords[2] * 255),
231
+ alpha: (_a = color.alpha) !== null && _a !== void 0 ? _a : 1
301
232
  };
302
233
  }
303
- match = colorString.match(rgbaRegex);
304
- if (match) {
305
- return {
306
- red: parseInt(match[1], 10),
307
- green: parseInt(match[2], 10),
308
- blue: parseInt(match[3], 10),
309
- alpha: Math.round(parseFloat(match[4]) * 100) / 100
310
- };
234
+ catch (e) {
235
+ return undefined;
311
236
  }
312
237
  }
313
- getRelativeLuminance(red, green, blue) {
314
- const rSRGB = red / 255;
315
- const gSRGB = green / 255;
316
- const bSRGB = blue / 255;
317
- const r = rSRGB <= 0.03928 ? rSRGB / 12.92 : Math.pow((rSRGB + 0.055) / 1.055, 2.4);
318
- const g = gSRGB <= 0.03928 ? gSRGB / 12.92 : Math.pow((gSRGB + 0.055) / 1.055, 2.4);
319
- const b = bSRGB <= 0.03928 ? bSRGB / 12.92 : Math.pow((bSRGB + 0.055) / 1.055, 2.4);
320
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
321
- }
322
- flattenColors(fgColor, bgColor) {
323
- const fgAlpha = fgColor['alpha'];
324
- const red = (1 - fgAlpha) * bgColor['red'] + fgAlpha * fgColor['red'];
325
- const green = (1 - fgAlpha) * bgColor['green'] + fgAlpha * fgColor['green'];
326
- const blue = (1 - fgAlpha) * bgColor['blue'] + fgAlpha * fgColor['blue'];
327
- const alpha = fgColor['alpha'] + bgColor['alpha'] * (1 - fgColor['alpha']);
328
- return { red: red, green: green, blue: blue, alpha: alpha };
238
+ flattenColors(fg, bg) {
239
+ const alpha = fg.alpha;
240
+ return {
241
+ red: Math.round((1 - alpha) * bg.red + alpha * fg.red),
242
+ green: Math.round((1 - alpha) * bg.green + alpha * fg.green),
243
+ blue: Math.round((1 - alpha) * bg.blue + alpha * fg.blue),
244
+ alpha: fg.alpha + bg.alpha * (1 - fg.alpha)
245
+ };
329
246
  }
330
- isBold(fontWeight) {
331
- return !!fontWeight && ['bold', 'bolder', '700', '800', '900'].includes(fontWeight);
247
+ getContrast(bg, fg) {
248
+ const finalFG = fg.alpha < 1 ? this.flattenColors(fg, bg) : fg;
249
+ const L1 = this.getLuminance(bg);
250
+ const L2 = this.getLuminance(finalFG);
251
+ return (Math.max(L1, L2) + 0.05) / (Math.min(L1, L2) + 0.05);
332
252
  }
333
- getContrast(bgColor, fgColor) {
334
- if (fgColor.alpha < 1) {
335
- fgColor = this.flattenColors(fgColor, bgColor);
336
- }
337
- const bL = this.getRelativeLuminance(bgColor['red'], bgColor['green'], bgColor['blue']);
338
- const fL = this.getRelativeLuminance(fgColor['red'], fgColor['green'], fgColor['blue']);
339
- return (Math.max(fL, bL) + 0.05) / (Math.min(fL, bL) + 0.05);
253
+ getLuminance(c) {
254
+ const a = [c.red, c.green, c.blue].map(v => {
255
+ v /= 255;
256
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
257
+ });
258
+ return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
340
259
  }
341
260
  hasValidContrastRatio(contrast, fontSize, isBold) {
342
- const isSmallFont = (isBold && parseFloat(fontSize) < 18.6667) || (!isBold && parseFloat(fontSize) < 24);
343
- const expectedContrastRatio = isSmallFont ? 4.5 : 3;
344
- return contrast >= expectedContrastRatio;
261
+ const size = parseFloat(fontSize);
262
+ const threshold = (isBold && size >= 18.66) || size >= 24 ? 3 : 4.5;
263
+ return (contrast + 0.02) >= threshold;
345
264
  }
346
- getTextSize(font, fontSize, bold, italic, text) {
347
- return window.DomUtils.getTextSize(font, fontSize, bold, italic, text);
265
+ isBold(fontWeight) {
266
+ return !!fontWeight && ['bold', 'bolder', '700', '800', '900'].includes(fontWeight);
348
267
  }
349
- getColorInGradient(fromColor, toColor, ratio) {
350
- const red = fromColor['red'] + (toColor['red'] - fromColor['red']) * ratio;
351
- const green = fromColor['green'] + (toColor['green'] - fromColor['green']) * ratio;
352
- const blue = fromColor['blue'] + (toColor['blue'] - fromColor['blue']) * ratio;
353
- return { red: red, green: green, blue: blue, alpha: 1 };
268
+ equals(c1, c2) {
269
+ return c1.red === c2.red && c1.green === c2.green && c1.blue === c2.blue && c1.alpha === c2.alpha;
354
270
  }
355
271
  }
356
272
  exports.QW_ACT_R37 = QW_ACT_R37;
@@ -359,7 +275,6 @@ __decorate([
359
275
  applicability_1.ElementIsHTMLElement,
360
276
  (0, applicability_1.ElementIsNot)(['html', 'head', 'body', 'script', 'style', 'meta']),
361
277
  applicability_1.ElementIsVisible,
362
- applicability_1.ElementHasText,
363
278
  __metadata("design:type", Function),
364
279
  __metadata("design:paramtypes", [Function]),
365
280
  __metadata("design:returntype", void 0)
@@ -1 +1 @@
1
- {"version":3,"file":"QW-ACT-R76.d.ts","sourceRoot":"","sources":["../../src/rules/QW-ACT-R76.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQrD,OAAO,EAAE,IAAI,EAAW,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,cAAM,UAAW,SAAQ,UAAU;IAMjC,OAAO,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI;IAwMjC,aAAa,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM;IAczC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAS/B,gBAAgB,CACd,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,SAAS,EAClB,oBAAoB,EAAE,GAAG,EACzB,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO;IAoDV,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAItC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO;IASzC,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAY1D,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG;IAW3D,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG;IAiCzD,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAYtE,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,GAAG;IAU9C,OAAO,CAAC,MAAM;IAId,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM;IAW/C,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO;IAWnF,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAIjG,kBAAkB,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG;CAOrE;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"QW-ACT-R76.d.ts","sourceRoot":"","sources":["../../src/rules/QW-ACT-R76.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQrD,OAAO,EAAE,IAAI,EAAW,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAGtD,cAAM,UAAW,SAAQ,UAAU;IAMjC,OAAO,CAAC,OAAO,EAAE,SAAS,GAAG,IAAI;IAwMjC,aAAa,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM;IAczC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAS/B,gBAAgB,CACd,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,SAAS,EAClB,oBAAoB,EAAE,GAAG,EACzB,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO;IAoDV,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAItC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO;IASzC,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAY1D,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG;IAW3D,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG;IAyDzD,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAYtE,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,GAAG;IAU9C,OAAO,CAAC,MAAM;IAId,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM;IAW/C,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO;IAWnF,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAIjG,kBAAkB,CAAC,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG;CAOrE;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -8,11 +8,15 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
8
8
  var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.QW_ACT_R76 = void 0;
13
16
  const applicability_1 = require("@qualweb/util/applicability");
14
17
  const evaluation_1 = require("@qualweb/core/evaluation");
15
18
  const AtomicRule_object_1 = require("../lib/AtomicRule.object");
19
+ const colorjs_io_1 = __importDefault(require("colorjs.io"));
16
20
  class QW_ACT_R76 extends AtomicRule_object_1.AtomicRule {
17
21
  execute(element) {
18
22
  const disabledWidgets = window.disabledWidgets;
@@ -258,6 +262,8 @@ class QW_ACT_R76 extends AtomicRule_object_1.AtomicRule {
258
262
  parseRGBString(colorString, opacity) {
259
263
  const rgbRegex = /^rgb\((\d+), (\d+), (\d+)\)/;
260
264
  const rgbaRegex = /^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/;
265
+ const oklchRegex = /^oklch\((\d*(\.\d+)?) (\d*(\.\d+)?) (\d*(\.\d+)?)\)/;
266
+ const oklch2Regex = /^oklch\((\d*(\.\d+)?) (\d*(\.\d+)?) (\d*(\.\d+)?) \/ (\d*(\.\d+)?)\)/;
261
267
  if (colorString === 'transparent') {
262
268
  return { red: 0, green: 0, blue: 0, alpha: 0 };
263
269
  }
@@ -279,6 +285,28 @@ class QW_ACT_R76 extends AtomicRule_object_1.AtomicRule {
279
285
  alpha: Math.round(parseFloat(match[4]) * 100) / 100
280
286
  };
281
287
  }
288
+ match = colorString.match(oklch2Regex);
289
+ if (match) {
290
+ const oklchColor = new colorjs_io_1.default("oklch", [parseFloat(match[1]), parseFloat(match[2]), parseFloat(match[3])]);
291
+ const rgba = oklchColor.to("srgb");
292
+ return {
293
+ red: rgba.srgb.red,
294
+ green: rgba.srgb.green,
295
+ blue: rgba.srgb.blue,
296
+ alpha: parseFloat(match[4])
297
+ };
298
+ }
299
+ match = colorString.match(oklchRegex);
300
+ if (match) {
301
+ const oklchColor = new colorjs_io_1.default("oklch", [parseFloat(match[1]), parseFloat(match[2]), parseFloat(match[3])]);
302
+ const rgba = oklchColor.to("srgb");
303
+ return {
304
+ red: rgba.srgb.red,
305
+ green: rgba.srgb.green,
306
+ blue: rgba.srgb.blue,
307
+ alpha: rgba.alpha
308
+ };
309
+ }
282
310
  }
283
311
  getRelativeLuminance(red, green, blue) {
284
312
  const rSRGB = red / 255;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qualweb/act-rules",
3
- "version": "0.7.4",
3
+ "version": "0.7.6",
4
4
  "description": "ACT rules module for qualweb web accessibility evaluator",
5
5
  "files": [
6
6
  "dist/*"
@@ -45,16 +45,17 @@
45
45
  "author": "João Vicente",
46
46
  "license": "ISC",
47
47
  "devDependencies": {
48
- "@qualweb/core": "^0.8.3",
48
+ "@qualweb/core": "^0.8.6",
49
49
  "@qualweb/locale": "0.2.2",
50
50
  "@qualweb/qw-page": "0.3.2",
51
- "@qualweb/util": "0.6.3",
51
+ "@qualweb/util": "0.6.5",
52
52
  "@tsconfig/recommended": "^1.0.3",
53
53
  "@types/mocha": "^10.0.6",
54
54
  "@types/node": "^16.11.11",
55
55
  "@types/node-fetch": "^2.6.11",
56
56
  "ajv": "^6.12.6",
57
57
  "chai": "4.5.0",
58
+ "colorjs.io": "^0.5.2",
58
59
  "mocha": "^10.2.0",
59
60
  "node-fetch": "^2.7.0",
60
61
  "prettier": "^3.1.1",