govuk_publishing_components 21.22.0 → 21.22.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/govuk_publishing_components/components/govspeak/_button.scss +2 -1
- data/app/views/govuk_publishing_components/components/_character_count.html.erb +14 -13
- data/app/views/govuk_publishing_components/components/docs/character_count.yml +8 -0
- data/app/views/govuk_publishing_components/components/docs/govspeak.yml +17 -4
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/node_modules/axe-core/CHANGELOG.md +37 -0
- data/node_modules/axe-core/README.md +2 -2
- data/node_modules/axe-core/axe.d.ts +1 -1
- data/node_modules/axe-core/axe.js +2430 -2242
- data/node_modules/axe-core/axe.min.js +3 -3
- data/node_modules/axe-core/bower.json +1 -1
- data/node_modules/axe-core/doc/API.md +10 -0
- data/node_modules/axe-core/doc/check-message-template.md +124 -0
- data/node_modules/axe-core/doc/examples/jasmine/package-lock.json +1489 -0
- data/node_modules/axe-core/doc/examples/jest_react/package-lock.json +7525 -0
- data/node_modules/axe-core/doc/examples/mocha/package-lock.json +1671 -0
- data/node_modules/axe-core/doc/examples/phantomjs/package-lock.json +862 -0
- data/node_modules/axe-core/doc/examples/qunit/package-lock.json +2951 -0
- data/node_modules/axe-core/doc/rule-descriptions.md +2 -2
- data/node_modules/axe-core/lib/checks/aria/allowed-attr.json +4 -1
- data/node_modules/axe-core/lib/checks/aria/aria-allowed-role.json +8 -2
- data/node_modules/axe-core/lib/checks/aria/errormessage.json +4 -1
- data/node_modules/axe-core/lib/checks/aria/implicit-role-fallback.json +1 -0
- data/node_modules/axe-core/lib/checks/aria/no-implicit-explicit-label.json +1 -1
- data/node_modules/axe-core/lib/checks/aria/required-attr.json +4 -1
- data/node_modules/axe-core/lib/checks/aria/required-children.json +8 -2
- data/node_modules/axe-core/lib/checks/aria/required-parent.json +4 -1
- data/node_modules/axe-core/lib/checks/aria/unsupportedattr.json +1 -1
- data/node_modules/axe-core/lib/checks/aria/unsupportedrole.json +1 -1
- data/node_modules/axe-core/lib/checks/aria/valid-attr-value.json +8 -2
- data/node_modules/axe-core/lib/checks/aria/valid-attr.json +5 -2
- data/node_modules/axe-core/lib/checks/color/color-contrast.js +1 -1
- data/node_modules/axe-core/lib/checks/color/color-contrast.json +4 -4
- data/node_modules/axe-core/lib/checks/color/link-in-text-block.js +2 -2
- data/node_modules/axe-core/lib/checks/color/link-in-text-block.json +2 -2
- data/node_modules/axe-core/lib/checks/forms/fieldset.js +1 -1
- data/node_modules/axe-core/lib/checks/forms/fieldset.json +8 -1
- data/node_modules/axe-core/lib/checks/forms/group-labelledby.js +2 -2
- data/node_modules/axe-core/lib/checks/forms/group-labelledby.json +6 -2
- data/node_modules/axe-core/lib/checks/keyboard/landmark-is-top-level.json +2 -2
- data/node_modules/axe-core/lib/checks/lists/listitem.js +3 -1
- data/node_modules/axe-core/lib/checks/lists/listitem.json +4 -1
- data/node_modules/axe-core/lib/checks/mobile/css-orientation-lock.js +216 -99
- data/node_modules/axe-core/lib/checks/mobile/css-orientation-lock.json +3 -0
- data/node_modules/axe-core/lib/checks/mobile/meta-viewport.json +1 -1
- data/node_modules/axe-core/lib/checks/parsing/duplicate-id-active.json +1 -1
- data/node_modules/axe-core/lib/checks/parsing/duplicate-id-aria.json +1 -1
- data/node_modules/axe-core/lib/checks/shared/aria-label.js +1 -1
- data/node_modules/axe-core/lib/checks/shared/avoid-inline-spacing.json +4 -1
- data/node_modules/axe-core/lib/checks/shared/non-empty-if-present.js +5 -1
- data/node_modules/axe-core/lib/checks/shared/non-empty-if-present.json +4 -1
- data/node_modules/axe-core/lib/checks/tables/has-caption.json +1 -0
- data/node_modules/axe-core/lib/checks/tables/has-summary.json +1 -0
- data/node_modules/axe-core/lib/checks/tables/has-th.json +1 -0
- data/node_modules/axe-core/lib/commons/aria/arialabel-text.js +7 -4
- data/node_modules/axe-core/lib/commons/aria/get-element-unallowed-roles.js +21 -0
- data/node_modules/axe-core/lib/commons/dom/get-element-stack.js +465 -0
- data/node_modules/axe-core/lib/commons/dom/shadow-elements-from-point.js +2 -1
- data/node_modules/axe-core/lib/core/base/audit.js +39 -17
- data/node_modules/axe-core/lib/core/base/virtual-node/serial-virtual-node.js +2 -0
- data/node_modules/axe-core/lib/core/base/virtual-node/virtual-node.js +43 -0
- data/node_modules/axe-core/lib/core/reporters/helpers/incomplete-fallback-msg.js +3 -1
- data/node_modules/axe-core/lib/core/utils/parse-crossorigin-stylesheet.js +1 -0
- data/node_modules/axe-core/lib/core/utils/preload-cssom.js +4 -3
- data/node_modules/axe-core/lib/core/utils/process-message.js +72 -0
- data/node_modules/axe-core/lib/core/utils/publish-metadata.js +20 -7
- data/node_modules/axe-core/lib/rules/aria-dpub-role-fallback.json +2 -1
- data/node_modules/axe-core/lib/rules/label-content-name-mismatch-matches.js +1 -1
- data/node_modules/axe-core/lib/rules/layout-table.json +2 -1
- data/node_modules/axe-core/locales/ja.json +1 -1
- data/node_modules/axe-core/package.json +16 -16
- data/node_modules/axe-core/sri-history.json +4 -0
- data/node_modules/axe-core/typings/axe-core/axe-core-tests.js +151 -0
- data/node_modules/axe-core/typings/axe-core/axe-core-tests.ts +18 -0
- metadata +11 -2
@@ -4,7 +4,7 @@
|
|
4
4
|
| area-alt | Ensures <area> elements of image maps have alternate text | Critical | cat.text-alternatives, wcag2a, wcag111, section508, section508.22.a | true | true | false |
|
5
5
|
| aria-allowed-attr | Ensures ARIA attributes are allowed for an element's role | Critical | cat.aria, wcag2a, wcag412 | true | true | false |
|
6
6
|
| aria-allowed-role | Ensures role attribute has an appropriate value for the element | Minor | cat.aria, best-practice | true | true | true |
|
7
|
-
| aria-dpub-role-fallback | Ensures unsupported DPUB roles are only used on elements with implicit fallback roles | Moderate | cat.aria, wcag2a, wcag131
|
7
|
+
| aria-dpub-role-fallback | Ensures unsupported DPUB roles are only used on elements with implicit fallback roles | Moderate | cat.aria, wcag2a, wcag131, deprecated | false | true | false |
|
8
8
|
| aria-hidden-body | Ensures aria-hidden='true' is not present on the document body. | Critical | cat.aria, wcag2a, wcag412 | true | true | false |
|
9
9
|
| aria-hidden-focus | Ensures aria-hidden elements do not contain focusable elements | Serious | cat.name-role-value, wcag2a, wcag412, wcag131 | true | true | false |
|
10
10
|
| aria-input-field-name | Ensures every ARIA input field has an accessible name | Moderate, Serious | wcag2a, wcag412 | true | true | true |
|
@@ -57,7 +57,7 @@
|
|
57
57
|
| landmark-no-duplicate-contentinfo | Ensures the document has at most one contentinfo landmark | Moderate | cat.semantics, best-practice | true | true | false |
|
58
58
|
| landmark-one-main | Ensures the document has only one main landmark and each iframe in the page has at most one main landmark | Moderate | cat.semantics, best-practice | true | true | false |
|
59
59
|
| landmark-unique | Landmarks must have a unique role or role/label/title (i.e. accessible name) combination | Moderate | cat.semantics, best-practice | true | true | false |
|
60
|
-
| layout-table | Ensures presentational <table> elements do not use <th>, <caption> elements or the summary attribute | Serious | cat.semantics, wcag2a, wcag131
|
60
|
+
| layout-table | Ensures presentational <table> elements do not use <th>, <caption> elements or the summary attribute | Serious | cat.semantics, wcag2a, wcag131, deprecated | false | true | false |
|
61
61
|
| link-in-text-block | Links can be distinguished without relying on color | Serious | cat.color, experimental, wcag2a, wcag141 | true | true | true |
|
62
62
|
| link-name | Ensures links have discernible text | Serious | cat.name-role-value, wcag2a, wcag412, wcag244, section508, section508.22.a | true | true | false |
|
63
63
|
| list | Ensures that lists are structured correctly | Serious | cat.structure, wcag2a, wcag131 | true | true | false |
|
@@ -5,7 +5,10 @@
|
|
5
5
|
"impact": "critical",
|
6
6
|
"messages": {
|
7
7
|
"pass": "ARIA attributes are used correctly for the defined role",
|
8
|
-
"fail":
|
8
|
+
"fail": {
|
9
|
+
"singular": "ARIA attribute is not allowed: ${data.values}",
|
10
|
+
"plural": "ARIA attributes are not allowed: ${data.values}"
|
11
|
+
}
|
9
12
|
}
|
10
13
|
}
|
11
14
|
}
|
@@ -9,8 +9,14 @@
|
|
9
9
|
"impact": "minor",
|
10
10
|
"messages": {
|
11
11
|
"pass": "ARIA role is allowed for given element",
|
12
|
-
"fail":
|
13
|
-
|
12
|
+
"fail": {
|
13
|
+
"singular": "ARIA role ${data.values} is not allowed for given element",
|
14
|
+
"plural": "ARIA roles ${data.values} are not allowed for given element"
|
15
|
+
},
|
16
|
+
"incomplete": {
|
17
|
+
"singular": "ARIA role ${data.values} must be removed when the element is made visible, as it is not allowed for the element",
|
18
|
+
"plural": "ARIA roles ${data.values} must be removed when the element is made visible, as they are not allowed for the element"
|
19
|
+
}
|
14
20
|
}
|
15
21
|
}
|
16
22
|
}
|
@@ -5,7 +5,10 @@
|
|
5
5
|
"impact": "critical",
|
6
6
|
"messages": {
|
7
7
|
"pass": "Uses a supported aria-errormessage technique",
|
8
|
-
"fail":
|
8
|
+
"fail": {
|
9
|
+
"singular": "aria-errormessage value `${data.values}` must use a technique to announce the message (e.g., aria-live, aria-describedby, role=alert, etc.)",
|
10
|
+
"plural": "aria-errormessage values `${data.values}` must use a technique to announce the message (e.g., aria-live, aria-describedby, role=alert, etc.)"
|
11
|
+
}
|
9
12
|
}
|
10
13
|
}
|
11
14
|
}
|
@@ -5,7 +5,7 @@
|
|
5
5
|
"impact": "moderate",
|
6
6
|
"messages": {
|
7
7
|
"pass": "There is no mismatch between a <label> and accessible name",
|
8
|
-
"incomplete": "Check that the <label> does not need be part of the ARIA {
|
8
|
+
"incomplete": "Check that the <label> does not need be part of the ARIA ${data} field's name"
|
9
9
|
}
|
10
10
|
}
|
11
11
|
}
|
@@ -5,7 +5,10 @@
|
|
5
5
|
"impact": "critical",
|
6
6
|
"messages": {
|
7
7
|
"pass": "All required ARIA attributes are present",
|
8
|
-
"fail":
|
8
|
+
"fail": {
|
9
|
+
"singular": "Required ARIA attribute not present: ${data.values}",
|
10
|
+
"plural": "Required ARIA attributes not present: ${data.values}"
|
11
|
+
}
|
9
12
|
}
|
10
13
|
}
|
11
14
|
}
|
@@ -19,8 +19,14 @@
|
|
19
19
|
"impact": "critical",
|
20
20
|
"messages": {
|
21
21
|
"pass": "Required ARIA children are present",
|
22
|
-
"fail":
|
23
|
-
|
22
|
+
"fail": {
|
23
|
+
"singular": "Required ARIA child role not present: ${data.values}",
|
24
|
+
"plural": "Required ARIA children role not present: ${data.values}"
|
25
|
+
},
|
26
|
+
"incomplete": {
|
27
|
+
"singular": "Expecting ARIA child role to be added: ${data.values}",
|
28
|
+
"plural": "Expecting ARIA children role to be added: ${data.values}"
|
29
|
+
}
|
24
30
|
}
|
25
31
|
}
|
26
32
|
}
|
@@ -5,7 +5,10 @@
|
|
5
5
|
"impact": "critical",
|
6
6
|
"messages": {
|
7
7
|
"pass": "Required ARIA parent role present",
|
8
|
-
"fail":
|
8
|
+
"fail": {
|
9
|
+
"singular": "Required ARIA parent role not present: ${data.values}",
|
10
|
+
"plural": "Required ARIA parents role not present: ${data.values}"
|
11
|
+
}
|
9
12
|
}
|
10
13
|
}
|
11
14
|
}
|
@@ -5,7 +5,7 @@
|
|
5
5
|
"impact": "critical",
|
6
6
|
"messages": {
|
7
7
|
"pass": "ARIA attribute is supported",
|
8
|
-
"fail": "ARIA attribute is not widely supported in screen readers and assistive technologies: {
|
8
|
+
"fail": "ARIA attribute is not widely supported in screen readers and assistive technologies: ${data.values}"
|
9
9
|
}
|
10
10
|
}
|
11
11
|
}
|
@@ -5,7 +5,7 @@
|
|
5
5
|
"impact": "critical",
|
6
6
|
"messages": {
|
7
7
|
"pass": "ARIA role is supported",
|
8
|
-
"fail": "The role used is not widely supported in screen readers and assistive technologies: {
|
8
|
+
"fail": "The role used is not widely supported in screen readers and assistive technologies: ${data.values}"
|
9
9
|
}
|
10
10
|
}
|
11
11
|
}
|
@@ -6,8 +6,14 @@
|
|
6
6
|
"impact": "critical",
|
7
7
|
"messages": {
|
8
8
|
"pass": "ARIA attribute values are valid",
|
9
|
-
"fail":
|
10
|
-
|
9
|
+
"fail": {
|
10
|
+
"singular": "Invalid ARIA attribute value: ${data.values}",
|
11
|
+
"plural": "Invalid ARIA attribute values: ${data.values}"
|
12
|
+
},
|
13
|
+
"incomplete": {
|
14
|
+
"singular": "ARIA attribute element ID does not exist on the page: ${data.values}",
|
15
|
+
"plural": "ARIA attributes element ID does not exist on the page: ${data.values}"
|
16
|
+
}
|
11
17
|
}
|
12
18
|
}
|
13
19
|
}
|
@@ -5,8 +5,11 @@
|
|
5
5
|
"metadata": {
|
6
6
|
"impact": "critical",
|
7
7
|
"messages": {
|
8
|
-
"pass": "ARIA attribute name
|
9
|
-
"fail":
|
8
|
+
"pass": "ARIA attribute name is valid",
|
9
|
+
"fail": {
|
10
|
+
"singular": "Invalid ARIA attribute name: ${data.values}",
|
11
|
+
"plural": "Invalid ARIA attribute names: ${data.values}"
|
12
|
+
}
|
10
13
|
}
|
11
14
|
}
|
12
15
|
}
|
@@ -44,7 +44,7 @@ const data = {
|
|
44
44
|
contrastRatio: cr ? truncatedResult : undefined,
|
45
45
|
fontSize: `${((fontSize * 72) / 96).toFixed(1)}pt (${fontSize}px)`,
|
46
46
|
fontWeight: bold ? 'bold' : 'normal',
|
47
|
-
|
47
|
+
messageKey: missing,
|
48
48
|
expectedContrastRatio: cr.expectedContrastRatio + ':1'
|
49
49
|
};
|
50
50
|
|
@@ -4,9 +4,10 @@
|
|
4
4
|
"metadata": {
|
5
5
|
"impact": "serious",
|
6
6
|
"messages": {
|
7
|
-
"pass": "Element has sufficient color contrast of {
|
8
|
-
"fail": "Element has insufficient color contrast of {
|
7
|
+
"pass": "Element has sufficient color contrast of ${data.contrastRatio}",
|
8
|
+
"fail": "Element has insufficient color contrast of ${data.contrastRatio} (foreground color: ${data.fgColor}, background color: ${data.bgColor}, font size: ${data.fontSize}, font weight: ${data.fontWeight}). Expected contrast ratio of ${data.expectedContrastRatio}",
|
9
9
|
"incomplete": {
|
10
|
+
"default": "Unable to determine contrast ratio",
|
10
11
|
"bgImage": "Element's background color could not be determined due to a background image",
|
11
12
|
"bgGradient": "Element's background color could not be determined due to a background gradient",
|
12
13
|
"imgNode": "Element's background color could not be determined because element contains an image node",
|
@@ -16,8 +17,7 @@
|
|
16
17
|
"elmPartiallyObscuring": "Element's background color could not be determined because it partially overlaps other elements",
|
17
18
|
"outsideViewport": "Element's background color could not be determined because it's outside the viewport",
|
18
19
|
"equalRatio": "Element has a 1:1 contrast ratio with the background",
|
19
|
-
"shortTextContent": "Element content is too short to determine if it is actual text content"
|
20
|
-
"default": "Unable to determine contrast ratio"
|
20
|
+
"shortTextContent": "Element content is too short to determine if it is actual text content"
|
21
21
|
}
|
22
22
|
}
|
23
23
|
}
|
@@ -50,7 +50,7 @@ if (color.elementIsDistinct(node, parentBlock)) {
|
|
50
50
|
} else if (contrast >= 3.0) {
|
51
51
|
axe.commons.color.incompleteData.set('fgColor', 'bgContrast');
|
52
52
|
this.data({
|
53
|
-
|
53
|
+
messageKey: axe.commons.color.incompleteData.get('fgColor')
|
54
54
|
});
|
55
55
|
axe.commons.color.incompleteData.clear();
|
56
56
|
// User needs to check whether there is a hover and a focus style
|
@@ -74,7 +74,7 @@ if (color.elementIsDistinct(node, parentBlock)) {
|
|
74
74
|
}
|
75
75
|
axe.commons.color.incompleteData.set('fgColor', reason);
|
76
76
|
this.data({
|
77
|
-
|
77
|
+
messageKey: axe.commons.color.incompleteData.get('fgColor')
|
78
78
|
});
|
79
79
|
axe.commons.color.incompleteData.clear();
|
80
80
|
return undefined;
|
@@ -7,12 +7,12 @@
|
|
7
7
|
"pass": "Links can be distinguished from surrounding text in some way other than by color",
|
8
8
|
"fail": "Links need to be distinguished from surrounding text in some way other than by color",
|
9
9
|
"incomplete": {
|
10
|
+
"default": "Unable to determine contrast ratio",
|
10
11
|
"bgContrast": "Element's contrast ratio could not be determined. Check for a distinct hover/focus style",
|
11
12
|
"bgImage": "Element's contrast ratio could not be determined due to a background image",
|
12
13
|
"bgGradient": "Element's contrast ratio could not be determined due to a background gradient",
|
13
14
|
"imgNode": "Element's contrast ratio could not be determined because element contains an image node",
|
14
|
-
"bgOverlap": "Element's contrast ratio could not be determined because of element overlap"
|
15
|
-
"default": "Unable to determine contrast ratio"
|
15
|
+
"bgOverlap": "Element's contrast ratio could not be determined because of element overlap"
|
16
16
|
}
|
17
17
|
}
|
18
18
|
}
|
@@ -7,7 +7,14 @@
|
|
7
7
|
"impact": "critical",
|
8
8
|
"messages": {
|
9
9
|
"pass": "Element is contained in a fieldset",
|
10
|
-
"fail":
|
10
|
+
"fail": {
|
11
|
+
"default": "Element does not have a containing fieldset or ARIA group",
|
12
|
+
"no-legend": "Fieldset does not have a legend as its first child",
|
13
|
+
"empty-legend": "Legend does not have text that is visible to screen readers",
|
14
|
+
"mixed-inputs": "Fieldset contains unrelated inputs",
|
15
|
+
"no-group-label": "ARIA group does not have aria-label or aria-labelledby",
|
16
|
+
"group-mixed-inputs": "ARIA group contains unrelated inputs"
|
17
|
+
}
|
11
18
|
}
|
12
19
|
}
|
13
20
|
}
|
@@ -57,9 +57,9 @@ if (uniqueLabels.length > 0 && sharedLabels.length > 0) {
|
|
57
57
|
}
|
58
58
|
|
59
59
|
if (uniqueLabels.length > 0 && sharedLabels.length === 0) {
|
60
|
-
data.
|
60
|
+
data.messageKey = 'no-shared-label';
|
61
61
|
} else if (uniqueLabels.length === 0 && sharedLabels.length > 0) {
|
62
|
-
data.
|
62
|
+
data.messageKey = 'no-unique-label';
|
63
63
|
}
|
64
64
|
|
65
65
|
this.data(data);
|
@@ -6,8 +6,12 @@
|
|
6
6
|
"metadata": {
|
7
7
|
"impact": "critical",
|
8
8
|
"messages": {
|
9
|
-
"pass": "Elements with the name \"{
|
10
|
-
"fail":
|
9
|
+
"pass": "Elements with the name \"${data.name}\" have both a shared label, and a unique label, referenced through aria-labelledby",
|
10
|
+
"fail": {
|
11
|
+
"default": "Elements with the name \"${data.name}\" do not all have both a shared label, and a unique label referenced through aria-labelledby",
|
12
|
+
"no-shared-label": "Elements with the name \"${data.name}\" do not all have a shared label referenced through aria-labelledby",
|
13
|
+
"no-unique-label": "Elements with the name \"${data.name}\" do not all have a unique label referenced through aria-labelledby"
|
14
|
+
}
|
11
15
|
}
|
12
16
|
}
|
13
17
|
}
|
@@ -4,8 +4,8 @@
|
|
4
4
|
"metadata": {
|
5
5
|
"impact": "moderate",
|
6
6
|
"messages": {
|
7
|
-
"pass": "The {
|
8
|
-
"fail": "The {
|
7
|
+
"pass": "The ${data.role} landmark is at the top level.",
|
8
|
+
"fail": "The ${data.role} landmark is contained in another landmark."
|
9
9
|
}
|
10
10
|
}
|
11
11
|
}
|
@@ -5,7 +5,10 @@
|
|
5
5
|
"impact": "serious",
|
6
6
|
"messages": {
|
7
7
|
"pass": "List item has a <ul>, <ol> or role=\"list\" parent element",
|
8
|
-
"fail":
|
8
|
+
"fail": {
|
9
|
+
"default": "List item does not have a <ul>, <ol> parent element",
|
10
|
+
"roleNotValid": "List item does not have a <ul>, <ol> parent element without a role, or a role=\"list\""
|
11
|
+
}
|
9
12
|
}
|
10
13
|
}
|
11
14
|
}
|
@@ -1,134 +1,251 @@
|
|
1
1
|
/* global context */
|
2
|
-
|
3
|
-
// extract asset of type `cssom` from context
|
4
2
|
const { cssom = undefined } = context || {};
|
5
|
-
|
6
|
-
// if there is no cssom <- return incomplete
|
3
|
+
const { degreeThreshold = 0 } = options || {};
|
7
4
|
if (!cssom || !cssom.length) {
|
8
5
|
return undefined;
|
9
6
|
}
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
8
|
+
let isLocked = false;
|
9
|
+
let relatedElements = [];
|
10
|
+
const rulesGroupByDocumentFragment = groupCssomByDocument(cssom);
|
11
|
+
|
12
|
+
for (const key of Object.keys(rulesGroupByDocumentFragment)) {
|
13
|
+
const { root, rules } = rulesGroupByDocumentFragment[key];
|
14
|
+
const orientationRules = rules.filter(isMediaRuleWithOrientation);
|
15
|
+
if (!orientationRules.length) {
|
16
|
+
continue;
|
17
|
+
}
|
18
|
+
|
19
|
+
orientationRules.forEach(({ cssRules }) => {
|
20
|
+
Array.from(cssRules).forEach(cssRule => {
|
21
|
+
const locked = getIsOrientationLocked(cssRule);
|
22
|
+
|
23
|
+
// if locked and not root HTML, preserve as relatedNodes
|
24
|
+
if (locked && cssRule.selectorText.toUpperCase() !== 'HTML') {
|
25
|
+
const elms =
|
26
|
+
Array.from(root.querySelectorAll(cssRule.selectorText)) || [];
|
27
|
+
relatedElements = relatedElements.concat(elms);
|
28
|
+
}
|
29
|
+
|
30
|
+
isLocked = isLocked || locked;
|
31
|
+
});
|
32
|
+
});
|
33
|
+
}
|
34
|
+
|
35
|
+
if (!isLocked) {
|
36
|
+
return true;
|
37
|
+
}
|
38
|
+
if (relatedElements.length) {
|
39
|
+
this.relatedNodes(relatedElements);
|
40
|
+
}
|
41
|
+
return false;
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Group given cssom by document/ document fragment
|
45
|
+
* @param {Array<Object>} allCssom cssom
|
46
|
+
* @return {Object}
|
47
|
+
*/
|
48
|
+
function groupCssomByDocument(cssObjectModel) {
|
49
|
+
return cssObjectModel.reduce((out, { sheet, root, shadowId }) => {
|
15
50
|
const key = shadowId ? shadowId : 'topDocument';
|
16
|
-
|
51
|
+
|
17
52
|
if (!out[key]) {
|
18
|
-
out[key] = {
|
19
|
-
root,
|
20
|
-
rules: []
|
21
|
-
};
|
53
|
+
out[key] = { root, rules: [] };
|
22
54
|
}
|
23
|
-
|
55
|
+
|
24
56
|
if (!sheet || !sheet.cssRules) {
|
25
|
-
//return
|
26
57
|
return out;
|
27
58
|
}
|
59
|
+
|
28
60
|
const rules = Array.from(sheet.cssRules);
|
29
|
-
// add rules into same document fragment
|
30
61
|
out[key].rules = out[key].rules.concat(rules);
|
31
62
|
|
32
|
-
//return
|
33
63
|
return out;
|
34
|
-
},
|
35
|
-
|
36
|
-
);
|
64
|
+
}, {});
|
65
|
+
}
|
37
66
|
|
38
|
-
|
39
|
-
|
67
|
+
/**
|
68
|
+
* Filter CSS Rules that target Orientation CSS Media Features
|
69
|
+
* @param {Array<Object>} cssRules
|
70
|
+
* @returns {Array<Object>}
|
71
|
+
*/
|
72
|
+
function isMediaRuleWithOrientation({ type, cssText }) {
|
73
|
+
/**
|
74
|
+
* Filter:
|
75
|
+
* CSSRule.MEDIA_Rule
|
76
|
+
* -> https://developer.mozilla.org/en-US/docs/Web/API/CSSMediaRule
|
77
|
+
*/
|
78
|
+
if (type !== 4) {
|
79
|
+
return false;
|
80
|
+
}
|
40
81
|
|
41
|
-
|
42
|
-
|
43
|
-
|
82
|
+
/**
|
83
|
+
* Filter:
|
84
|
+
* CSSRule with conditionText of `orientation`
|
85
|
+
*/
|
86
|
+
return (
|
87
|
+
/orientation:\s*landscape/i.test(cssText) ||
|
88
|
+
/orientation:\s*portrait/i.test(cssText)
|
89
|
+
);
|
90
|
+
}
|
44
91
|
|
45
|
-
|
46
|
-
|
92
|
+
/**
|
93
|
+
* Interpolate a given CSS Rule to ascertain if orientation is locked by use of any transformation functions that affect rotation along the Z Axis
|
94
|
+
* @param {Object} cssRule given CSS Rule
|
95
|
+
* @property {String} cssRule.selectorText selector text targetted by given cssRule
|
96
|
+
* @property {Object} cssRule.style style
|
97
|
+
* @return {Boolean}
|
98
|
+
*/
|
99
|
+
function getIsOrientationLocked({ selectorText, style }) {
|
100
|
+
if (!selectorText || style.length <= 0) {
|
101
|
+
return false;
|
102
|
+
}
|
47
103
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
return r.type === 4;
|
53
|
-
});
|
54
|
-
if (!mediaRules || !mediaRules.length) {
|
55
|
-
return;
|
104
|
+
const transformStyle =
|
105
|
+
style.transform || style.webkitTransform || style.msTransform || false;
|
106
|
+
if (!transformStyle) {
|
107
|
+
return false;
|
56
108
|
}
|
57
109
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
);
|
67
|
-
});
|
68
|
-
if (!orientationRules || !orientationRules.length) {
|
69
|
-
return;
|
110
|
+
/**
|
111
|
+
* get last match/occurence of a transformation function that can affect rotation along Z axis
|
112
|
+
*/
|
113
|
+
const matches = transformStyle.match(
|
114
|
+
/(rotate|rotateZ|rotate3d|matrix|matrix3d)\(([^)]+)\)(?!.*(rotate|rotateZ|rotate3d|matrix|matrix3d))/
|
115
|
+
);
|
116
|
+
if (!matches) {
|
117
|
+
return false;
|
70
118
|
}
|
71
119
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
}
|
120
|
+
const [, transformFn, transformFnValue] = matches;
|
121
|
+
let degrees = getRotationInDegrees(transformFn, transformFnValue);
|
122
|
+
if (!degrees) {
|
123
|
+
return false;
|
124
|
+
}
|
125
|
+
degrees = Math.abs(degrees);
|
126
|
+
|
127
|
+
/**
|
128
|
+
* When degree is a multiple of 180, it is not considered an orientation lock
|
129
|
+
*/
|
130
|
+
if (Math.abs(degrees - 180) % 180 <= degreeThreshold) {
|
131
|
+
return false;
|
132
|
+
}
|
133
|
+
|
134
|
+
return Math.abs(degrees - 90) % 90 <= degreeThreshold;
|
135
|
+
}
|
89
136
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
137
|
+
/**
|
138
|
+
* Interpolate rotation along the z axis from a given value to a transform function
|
139
|
+
* @param {String} transformFunction CSS transformation function
|
140
|
+
* @param {String} transformFnValue value applied to a transform function (contains a unit)
|
141
|
+
* @returns {Number}
|
142
|
+
*/
|
143
|
+
function getRotationInDegrees(transformFunction, transformFnValue) {
|
144
|
+
switch (transformFunction) {
|
145
|
+
case 'rotate':
|
146
|
+
case 'rotateZ':
|
147
|
+
return getAngleInDegrees(transformFnValue);
|
148
|
+
case 'rotate3d':
|
149
|
+
const [, , z, angleWithUnit] = transformFnValue
|
150
|
+
.split(',')
|
151
|
+
.map(value => value.trim());
|
152
|
+
if (parseInt(z) === 0) {
|
153
|
+
// no transform is applied along z axis -> ignore
|
99
154
|
return;
|
100
155
|
}
|
156
|
+
return getAngleInDegrees(angleWithUnit);
|
157
|
+
case 'matrix':
|
158
|
+
case 'matrix3d':
|
159
|
+
return getAngleInDegreesFromMatrixTransform(transformFnValue);
|
160
|
+
default:
|
161
|
+
return;
|
162
|
+
}
|
163
|
+
}
|
101
164
|
|
102
|
-
|
103
|
-
|
104
|
-
|
165
|
+
/**
|
166
|
+
* Get angle in degrees from a transform value by interpolating the unit of measure
|
167
|
+
* @param {String} angleWithUnit value applied to a transform function (Eg: 1turn)
|
168
|
+
* @returns{Number|undefined}
|
169
|
+
*/
|
170
|
+
function getAngleInDegrees(angleWithUnit) {
|
171
|
+
const [unit] = angleWithUnit.match(/(deg|grad|rad|turn)/) || [];
|
172
|
+
if (!unit) {
|
173
|
+
return;
|
174
|
+
}
|
105
175
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
176
|
+
const angle = parseFloat(angleWithUnit.replace(unit, ``));
|
177
|
+
switch (unit) {
|
178
|
+
case 'rad':
|
179
|
+
return convertRadToDeg(angle);
|
180
|
+
case 'grad':
|
181
|
+
return convertGradToDeg(angle);
|
182
|
+
case 'turn':
|
183
|
+
return convertTurnToDeg(angle);
|
184
|
+
case 'deg':
|
185
|
+
default:
|
186
|
+
return parseInt(angle);
|
187
|
+
}
|
188
|
+
}
|
116
189
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
190
|
+
/**
|
191
|
+
* Get angle in degrees from a transform value applied to `matrix` or `matrix3d` transform functions
|
192
|
+
* @param {String} transformFnValue value applied to a transform function (contains a unit)
|
193
|
+
* @returns {Number}
|
194
|
+
*/
|
195
|
+
function getAngleInDegreesFromMatrixTransform(transformFnValue) {
|
196
|
+
const values = transformFnValue.split(',');
|
197
|
+
|
198
|
+
/**
|
199
|
+
* Matrix 2D
|
200
|
+
* Notes: https://css-tricks.com/get-value-of-css-rotation-through-javascript/
|
201
|
+
*/
|
202
|
+
if (values.length <= 6) {
|
203
|
+
const [a, b] = values;
|
204
|
+
const radians = Math.atan2(parseFloat(b), parseFloat(a));
|
205
|
+
return convertRadToDeg(radians);
|
206
|
+
}
|
122
207
|
|
123
|
-
|
124
|
-
|
125
|
-
|
208
|
+
/**
|
209
|
+
* Matrix 3D
|
210
|
+
* Notes: https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix
|
211
|
+
*/
|
212
|
+
const sinB = parseFloat(values[8]);
|
213
|
+
const b = Math.asin(sinB);
|
214
|
+
const cosB = Math.cos(b);
|
215
|
+
const rotateZRadians = Math.acos(parseFloat(values[0]) / cosB);
|
216
|
+
return convertRadToDeg(rotateZRadians);
|
126
217
|
}
|
127
218
|
|
128
|
-
|
129
|
-
|
130
|
-
|
219
|
+
/**
|
220
|
+
* Convert angle specified in unit radians to degrees
|
221
|
+
* See - https://drafts.csswg.org/css-values-3/#rad
|
222
|
+
* @param {Number} radians radians
|
223
|
+
* @return {Number}
|
224
|
+
*/
|
225
|
+
function convertRadToDeg(radians) {
|
226
|
+
return Math.round(radians * (180 / Math.PI));
|
131
227
|
}
|
132
228
|
|
133
|
-
|
134
|
-
|
229
|
+
/**
|
230
|
+
* Convert angle specified in unit grad to degrees
|
231
|
+
* See - https://drafts.csswg.org/css-values-3/#grad
|
232
|
+
* @param {Number} grad grad
|
233
|
+
* @return {Number}
|
234
|
+
*/
|
235
|
+
function convertGradToDeg(grad) {
|
236
|
+
grad = grad % 400;
|
237
|
+
if (grad < 0) {
|
238
|
+
grad += 400;
|
239
|
+
}
|
240
|
+
return Math.round((grad / 400) * 360);
|
241
|
+
}
|
242
|
+
|
243
|
+
/**
|
244
|
+
* Convert angle specifed in unit turn to degrees
|
245
|
+
* See - https://drafts.csswg.org/css-values-3/#turn
|
246
|
+
* @param {Number} turn
|
247
|
+
* @returns {Number}
|
248
|
+
*/
|
249
|
+
function convertTurnToDeg(turn) {
|
250
|
+
return Math.round(360 / (1 / turn));
|
251
|
+
}
|