single-file-core 1.6.21 → 1.6.23

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.
@@ -25,79 +25,79 @@ import * as cssTree from "../vendor/css-tree.js";
25
25
  const CANONICAL_PSEUDO_ELEMENT_NAMES = new Set(["after", "before", "first-letter", "first-line", "placeholder", "selection", "part", "marker"]);
26
26
 
27
27
  export {
28
- parsePrelude
28
+ parsePrelude
29
29
  };
30
30
 
31
31
  function parsePrelude(prelude) {
32
- if (!prelude) {
33
- return { include: [], exclude: [] };
34
- }
32
+ if (!prelude) {
33
+ return { include: [], exclude: [] };
34
+ }
35
35
 
36
- const scopeNode = findScopeNode(prelude);
37
- if (!scopeNode) {
38
- return { include: [], exclude: [] };
39
- }
36
+ const scopeNode = findScopeNode(prelude);
37
+ if (!scopeNode) {
38
+ return { include: [], exclude: [] };
39
+ }
40
40
 
41
- const include = extractSelectorList(scopeNode.root);
42
- const exclude = extractSelectorList(scopeNode.limit);
41
+ const include = extractSelectorList(scopeNode.root);
42
+ const exclude = extractSelectorList(scopeNode.limit);
43
43
 
44
- // Validate: pseudo-elements are not allowed in scope start/end selectors
45
- function containsPseudoElement(selectorAst) {
46
- let found = false;
47
- cssTree.walk(selectorAst, {
48
- visit: "PseudoElementSelector",
49
- enter() { found = true; }
50
- });
51
- if (!found) {
52
- // also check for pseudo-class names that are treated as pseudo-elements by some authors
53
- cssTree.walk(selectorAst, {
54
- visit: "PseudoClassSelector",
55
- enter(node) {
56
- const name = (node.name || "").toLowerCase();
57
- // keep this conservative: disallow known pseudo-element names if used as pseudo-class
58
- if (CANONICAL_PSEUDO_ELEMENT_NAMES.has(name)) {
59
- found = true;
60
- }
61
- }
62
- });
63
- }
64
- return found;
65
- }
44
+ // Validate: pseudo-elements are not allowed in scope start/end selectors
45
+ function containsPseudoElement(selectorAst) {
46
+ let found = false;
47
+ cssTree.walk(selectorAst, {
48
+ visit: "PseudoElementSelector",
49
+ enter() { found = true; }
50
+ });
51
+ if (!found) {
52
+ // also check for pseudo-class names that are treated as pseudo-elements by some authors
53
+ cssTree.walk(selectorAst, {
54
+ visit: "PseudoClassSelector",
55
+ enter(node) {
56
+ const name = (node.name || "").toLowerCase();
57
+ // keep this conservative: disallow known pseudo-element names if used as pseudo-class
58
+ if (CANONICAL_PSEUDO_ELEMENT_NAMES.has(name)) {
59
+ found = true;
60
+ }
61
+ }
62
+ });
63
+ }
64
+ return found;
65
+ }
66
66
 
67
- for (const s of include) {
68
- if (containsPseudoElement(s.data)) {
69
- throw new Error("Pseudo-elements are not allowed in @scope prelude (scope-start)");
70
- }
71
- }
72
- for (const s of exclude) {
73
- if (containsPseudoElement(s.data)) {
74
- throw new Error("Pseudo-elements are not allowed in @scope prelude (scope-end)");
75
- }
76
- }
67
+ for (const s of include) {
68
+ if (containsPseudoElement(s.data)) {
69
+ throw new Error("Pseudo-elements are not allowed in @scope prelude (scope-start)");
70
+ }
71
+ }
72
+ for (const s of exclude) {
73
+ if (containsPseudoElement(s.data)) {
74
+ throw new Error("Pseudo-elements are not allowed in @scope prelude (scope-end)");
75
+ }
76
+ }
77
77
 
78
- return { include, exclude };
78
+ return { include, exclude };
79
79
  }
80
80
 
81
81
  function findScopeNode(prelude) {
82
- if (!prelude || !prelude.children) {
83
- return null;
84
- }
85
- for (let node = prelude.children.head; node; node = node.next) {
86
- if (node.data && node.data.type === "Scope") {
87
- return node.data;
88
- }
89
- }
90
- return null;
82
+ if (!prelude || !prelude.children) {
83
+ return null;
84
+ }
85
+ for (let node = prelude.children.head; node; node = node.next) {
86
+ if (node.data && node.data.type === "Scope") {
87
+ return node.data;
88
+ }
89
+ }
90
+ return null;
91
91
  }
92
92
 
93
93
  function extractSelectorList(selectorList) {
94
- if (!selectorList || !selectorList.children) {
95
- return [];
96
- }
97
- const selectors = [];
98
- for (let node = selectorList.children.head; node; node = node.next) {
99
- const selector = node.data;
100
- selectors.push({ data: selector, text: cssTree.generate(selector) });
101
- }
102
- return selectors;
94
+ if (!selectorList || !selectorList.children) {
95
+ return [];
96
+ }
97
+ const selectors = [];
98
+ for (let node = selectorList.children.head; node; node = node.next) {
99
+ const selector = node.data;
100
+ selectors.push({ data: selector, text: cssTree.generate(selector) });
101
+ }
102
+ return selectors;
103
103
  }
@@ -26,112 +26,112 @@ import * as cssTree from "./../vendor/css-tree.js";
26
26
  import { decodeName } from "./css-identifier.js";
27
27
 
28
28
  const TREE_STRUCTURAL_PSEUDO_CLASSES = [
29
- "root",
30
- "scope",
31
- "empty",
32
- "first-child",
33
- "last-child",
34
- "only-child",
35
- "first-of-type",
36
- "last-of-type",
37
- "only-of-type"
29
+ "root",
30
+ "scope",
31
+ "empty",
32
+ "first-child",
33
+ "last-child",
34
+ "only-child",
35
+ "first-of-type",
36
+ "last-of-type",
37
+ "only-of-type"
38
38
  ];
39
39
  const TREE_STRUCTURAL_FUNCTIONAL_PSEUDO_CLASSES = [
40
- "nth-child",
41
- "nth-last-child",
42
- "heading",
43
- "nth-of-type",
44
- "nth-last-of-type"
40
+ "nth-child",
41
+ "nth-last-child",
42
+ "heading",
43
+ "nth-of-type",
44
+ "nth-last-of-type"
45
45
  ];
46
46
  const FUNCTIONAL_PSEUDO_CLASSES = [
47
- "not",
48
- "is",
49
- "where",
50
- "has"
47
+ "not",
48
+ "is",
49
+ "where",
50
+ "has"
51
51
  ];
52
52
  const MATCHING_FUNCTIONAL_PSEUDO_CLASSES = [
53
- "is",
54
- "where"
53
+ "is",
54
+ "where"
55
55
  ];
56
56
  const STATE_ATTRIBUTE_NAMES = [
57
- "open"
57
+ "open"
58
58
  ];
59
59
  export {
60
- matchUnqueryableAttributeSelector,
61
- matchUnqueryablePseudoClass,
62
- sanitizeSelector,
60
+ matchUnqueryableAttributeSelector,
61
+ matchUnqueryablePseudoClass,
62
+ sanitizeSelector,
63
63
  };
64
64
 
65
65
  function sanitizeSelector(selector, docContext) {
66
- if (!docContext.normalizedSelectorText) {
67
- docContext.normalizedSelectorText = new WeakMap();
68
- }
69
- if (docContext.normalizedSelectorText.has(selector)) {
70
- return docContext.normalizedSelectorText.get(selector);
71
- }
72
- const ast = copySelectorNode(selector.data);
73
- normalizeSelectorNode(ast);
74
- let normalized = cssTree.generate(ast);
75
- if (!normalized || !normalized.trim()) {
76
- normalized = "*";
77
- }
78
- docContext.normalizedSelectorText.set(selector, normalized);
79
- return normalized;
66
+ if (!docContext.normalizedSelectorText) {
67
+ docContext.normalizedSelectorText = new WeakMap();
68
+ }
69
+ if (docContext.normalizedSelectorText.has(selector)) {
70
+ return docContext.normalizedSelectorText.get(selector);
71
+ }
72
+ const ast = copySelectorNode(selector.data);
73
+ normalizeSelectorNode(ast);
74
+ let normalized = cssTree.generate(ast);
75
+ if (!normalized || !normalized.trim()) {
76
+ normalized = "*";
77
+ }
78
+ docContext.normalizedSelectorText.set(selector, normalized);
79
+ return normalized;
80
80
  }
81
81
 
82
82
  function copySelectorNode(node) {
83
- // A node with no children is a Raw one, which clone would hand back raw where the round-trip
84
- // parses it into the selector list the rest of this module walks.
85
- return node.children ? cssTree.clone(node) : cssTree.parse(cssTree.generate(node), { context: "selectorList" });
83
+ // A node with no children is a Raw one, which clone would hand back raw where the round-trip
84
+ // parses it into the selector list the rest of this module walks.
85
+ return node.children ? cssTree.clone(node) : cssTree.parse(cssTree.generate(node), { context: "selectorList" });
86
86
  }
87
87
 
88
88
  function normalizeSelectorNode(selector) {
89
- let current = selector.children.head;
90
- while (current) {
91
- const next = current.next;
92
- const childNode = current.data;
93
- if (childNode.type === "NestingSelector") {
94
- selector.children.replace(current, cssTree.parse(":scope", { context: "selector" }).children.head);
95
- } else if (childNode.type === "TypeSelector" && typeof childNode.name === "string" && childNode.name.includes("|")) {
96
- childNode.name = childNode.name.substring(childNode.name.lastIndexOf("|") + 1);
97
- } else if (childNode.type === "PseudoElementSelector") {
98
- selector.children.remove(current);
99
- } else if (childNode.type === "PseudoClassSelector") {
100
- if (matchUnqueryablePseudoClass(childNode)) {
101
- removeNode(selector.children, current);
102
- } else if (childNode.children && MATCHING_FUNCTIONAL_PSEUDO_CLASSES.includes(childNode.name.toLowerCase())) {
103
- normalizeSelectorNode(childNode.children.head.data);
104
- }
105
- } else if (childNode.type === "AttributeSelector") {
106
- if (matchUnqueryableAttributeSelector(childNode)) {
107
- removeNode(selector.children, current);
108
- }
109
- } else if (childNode.type === "Selector") {
110
- normalizeSelectorNode(childNode);
111
- }
112
- current = next;
113
- }
89
+ let current = selector.children.head;
90
+ while (current) {
91
+ const next = current.next;
92
+ const childNode = current.data;
93
+ if (childNode.type === "NestingSelector") {
94
+ selector.children.replace(current, cssTree.parse(":scope", { context: "selector" }).children.head);
95
+ } else if (childNode.type === "TypeSelector" && typeof childNode.name === "string" && childNode.name.includes("|")) {
96
+ childNode.name = childNode.name.substring(childNode.name.lastIndexOf("|") + 1);
97
+ } else if (childNode.type === "PseudoElementSelector") {
98
+ selector.children.remove(current);
99
+ } else if (childNode.type === "PseudoClassSelector") {
100
+ if (matchUnqueryablePseudoClass(childNode)) {
101
+ removeNode(selector.children, current);
102
+ } else if (childNode.children && MATCHING_FUNCTIONAL_PSEUDO_CLASSES.includes(childNode.name.toLowerCase())) {
103
+ normalizeSelectorNode(childNode.children.head.data);
104
+ }
105
+ } else if (childNode.type === "AttributeSelector") {
106
+ if (matchUnqueryableAttributeSelector(childNode)) {
107
+ removeNode(selector.children, current);
108
+ }
109
+ } else if (childNode.type === "Selector") {
110
+ normalizeSelectorNode(childNode);
111
+ }
112
+ current = next;
113
+ }
114
114
  }
115
115
 
116
116
  function removeNode(list, item) {
117
- if (item.prev == null || item.prev.data.type == "Combinator" || item.prev.data.type == "WhiteSpace") {
118
- list.replace(item, cssTree.parse("*", { context: "selector" }).children.head);
119
- } else {
120
- list.remove(item);
121
- }
117
+ if (item.prev == null || item.prev.data.type == "Combinator" || item.prev.data.type == "WhiteSpace") {
118
+ list.replace(item, cssTree.parse("*", { context: "selector" }).children.head);
119
+ } else {
120
+ list.remove(item);
121
+ }
122
122
  }
123
123
 
124
124
  function matchUnqueryableAttributeSelector(attributeSelector) {
125
- return Boolean(attributeSelector.name) && STATE_ATTRIBUTE_NAMES.includes(decodeName(attributeSelector.name.name));
125
+ return Boolean(attributeSelector.name) && STATE_ATTRIBUTE_NAMES.includes(decodeName(attributeSelector.name.name));
126
126
  }
127
127
 
128
128
  function matchUnqueryablePseudoClass(pseudoClass) {
129
- // A functional pseudo-class is compared by spelling on purpose: css-tree recognises the known
130
- // names by spelling too, so it parses the argument of an escaped one as Raw rather than as a
131
- // selector list, and reading `:i\73 (…)` as `:is(…)` would hand the rest of the pass an argument
132
- // it cannot walk. Leaving it unrecognised keeps it unqueryable, which is the safe answer.
133
- return pseudoClass.children ? (
134
- !TREE_STRUCTURAL_FUNCTIONAL_PSEUDO_CLASSES.includes(pseudoClass.name.toLowerCase()) &&
129
+ // A functional pseudo-class is compared by spelling on purpose: css-tree recognises the known
130
+ // names by spelling too, so it parses the argument of an escaped one as Raw rather than as a
131
+ // selector list, and reading `:i\73 (…)` as `:is(…)` would hand the rest of the pass an argument
132
+ // it cannot walk. Leaving it unrecognised keeps it unqueryable, which is the safe answer.
133
+ return pseudoClass.children ? (
134
+ !TREE_STRUCTURAL_FUNCTIONAL_PSEUDO_CLASSES.includes(pseudoClass.name.toLowerCase()) &&
135
135
  !FUNCTIONAL_PSEUDO_CLASSES.includes(pseudoClass.name.toLowerCase())
136
- ) : !TREE_STRUCTURAL_PSEUDO_CLASSES.includes(decodeName(pseudoClass.name));
136
+ ) : !TREE_STRUCTURAL_PSEUDO_CLASSES.includes(decodeName(pseudoClass.name));
137
137
  }
@@ -22,138 +22,166 @@
22
22
  */
23
23
 
24
24
  import * as cssTree from "./../vendor/css-tree.js";
25
+ import { decodeName } from "./css-identifier.js";
26
+
27
+ const PSEUDO_ELEMENT_SYNONYMS = new Set(["after", "before", "first-letter", "first-line"]);
28
+ const SELECTOR_ARGUMENT_PSEUDO_CLASSES = new Set(["host", "host-context"]);
29
+ const SELECTOR_ARGUMENT_PSEUDO_ELEMENTS = new Set(["slotted"]);
25
30
 
26
31
  export {
27
- computeSpecificity,
28
- computeMaxSpecificity
32
+ computeSpecificity,
33
+ computeMaxSpecificity,
34
+ PSEUDO_ELEMENT_SYNONYMS
29
35
  };
30
36
 
31
37
  function computeSpecificity(selector, specificity = { a: 0, b: 0, c: 0 }) {
32
- if (!selector || !selector.type) {
33
- return specificity;
34
- }
35
- switch (selector.type) {
36
- case "Selector":
37
- traverseChildren(selector.children, (child) => computeSpecificity(child, specificity));
38
- break;
39
-
40
- case "IdSelector":
41
- specificity.a++;
42
- break;
43
-
44
- case "ClassSelector":
45
- specificity.b++;
46
- break;
47
-
48
- case "AttributeSelector":
49
- specificity.b++;
50
- break;
51
-
52
- case "TypeSelector":
53
- if (selector.name !== "*") {
54
- specificity.c++;
55
- }
56
- break;
57
-
58
- case "PseudoElementSelector":
59
- specificity.c++;
60
- break;
61
-
62
- case "PseudoClassSelector": {
63
- const pseudoName = selector.name.toLowerCase();
64
-
65
- if (pseudoName === "where") {
66
- // :where() has zero specificity - do nothing
67
- break;
68
- }
69
-
70
- if (pseudoName === "is" || pseudoName === "not" || pseudoName === "has") {
71
- // :is(), :not(), :has() - use the max specificity from their selector list
72
- traverseChildren(selector.children, (child) => {
73
- if (child.type === "SelectorList") {
74
- addMaxSpecificity(specificity, getMaxSpecificityFromList(child));
75
- }
76
- });
77
- break;
78
- }
79
-
80
- if (pseudoName === "nth-child" || pseudoName === "nth-last-child") {
81
- // :nth-child() and :nth-last-child() count as one pseudo-class
82
- specificity.b++;
83
-
84
- // Plus the max specificity from their selector list (if any)
85
- traverseChildren(selector.children, (child) => {
86
- if (child.type === "Nth" && child.selector) {
87
- addMaxSpecificity(specificity, getMaxSpecificityFromList(child.selector));
88
- }
89
- });
90
- break;
91
- }
92
-
93
- // Regular pseudo-classes contribute to 'b'
94
- specificity.b++;
95
- break;
96
- }
97
-
98
- case "Combinator":
99
- case "Raw":
100
- break;
101
- }
102
-
103
- return specificity;
38
+ if (!selector || !selector.type) {
39
+ return specificity;
40
+ }
41
+ switch (selector.type) {
42
+ case "Selector":
43
+ traverseChildren(selector.children, (child) => computeSpecificity(child, specificity));
44
+ break;
45
+
46
+ case "IdSelector":
47
+ specificity.a++;
48
+ break;
49
+
50
+ case "ClassSelector":
51
+ specificity.b++;
52
+ break;
53
+
54
+ case "AttributeSelector":
55
+ specificity.b++;
56
+ break;
57
+
58
+ case "TypeSelector":
59
+ if (selector.name !== "*") {
60
+ specificity.c++;
61
+ }
62
+ break;
63
+
64
+ case "PseudoElementSelector":
65
+ specificity.c++;
66
+ if (SELECTOR_ARGUMENT_PSEUDO_ELEMENTS.has(decodeName(selector.name))) {
67
+ addArgumentSpecificity(specificity, selector);
68
+ }
69
+ break;
70
+
71
+ case "PseudoClassSelector": {
72
+ const pseudoName = decodeName(selector.name);
73
+
74
+ if (pseudoName === "where") {
75
+ // :where() has zero specificity - do nothing
76
+ break;
77
+ }
78
+
79
+ if (PSEUDO_ELEMENT_SYNONYMS.has(pseudoName)) {
80
+ specificity.c++;
81
+ break;
82
+ }
83
+
84
+ if (SELECTOR_ARGUMENT_PSEUDO_CLASSES.has(pseudoName)) {
85
+ specificity.b++;
86
+ addArgumentSpecificity(specificity, selector);
87
+ break;
88
+ }
89
+
90
+ if (pseudoName === "is" || pseudoName === "not" || pseudoName === "has") {
91
+ // :is(), :not(), :has() - use the max specificity from their selector list
92
+ traverseChildren(selector.children, (child) => {
93
+ if (child.type === "SelectorList") {
94
+ addMaxSpecificity(specificity, getMaxSpecificityFromList(child));
95
+ }
96
+ });
97
+ break;
98
+ }
99
+
100
+ if (pseudoName === "nth-child" || pseudoName === "nth-last-child") {
101
+ // :nth-child() and :nth-last-child() count as one pseudo-class
102
+ specificity.b++;
103
+
104
+ // Plus the max specificity from their selector list (if any)
105
+ traverseChildren(selector.children, (child) => {
106
+ if (child.type === "Nth" && child.selector) {
107
+ addMaxSpecificity(specificity, getMaxSpecificityFromList(child.selector));
108
+ }
109
+ });
110
+ break;
111
+ }
112
+
113
+ // Regular pseudo-classes contribute to 'b'
114
+ specificity.b++;
115
+ break;
116
+ }
117
+
118
+ case "Combinator":
119
+ case "Raw":
120
+ break;
121
+ }
122
+
123
+ return specificity;
124
+ }
125
+
126
+ function addArgumentSpecificity(specificity, selector) {
127
+ traverseChildren(selector.children, (child) => {
128
+ if (child.type === "Selector") {
129
+ addMaxSpecificity(specificity, computeSpecificity(child, { a: 0, b: 0, c: 0 }));
130
+ }
131
+ });
104
132
  }
105
133
 
106
134
  function addMaxSpecificity(specificity, maxSpec) {
107
- specificity.a += maxSpec.a;
108
- specificity.b += maxSpec.b;
109
- specificity.c += maxSpec.c;
135
+ specificity.a += maxSpec.a;
136
+ specificity.b += maxSpec.b;
137
+ specificity.c += maxSpec.c;
110
138
  }
111
139
 
112
140
  function traverseChildren(children, callback) {
113
- if (!children) return;
141
+ if (!children) return;
114
142
 
115
- let current = children.head;
116
- while (current) {
117
- callback(current.data);
118
- current = current.next;
119
- }
143
+ let current = children.head;
144
+ while (current) {
145
+ callback(current.data);
146
+ current = current.next;
147
+ }
120
148
  }
121
149
 
122
150
  function getMaxSpecificityFromList(selectorList) {
123
- let maxSpec = { a: 0, b: 0, c: 0 };
151
+ let maxSpec = { a: 0, b: 0, c: 0 };
124
152
 
125
- traverseChildren(selectorList.children, (selector) => {
126
- const spec = computeSpecificity(selector, { a: 0, b: 0, c: 0 });
127
- if (spec.a > maxSpec.a ||
153
+ traverseChildren(selectorList.children, (selector) => {
154
+ const spec = computeSpecificity(selector, { a: 0, b: 0, c: 0 });
155
+ if (spec.a > maxSpec.a ||
128
156
  (spec.a === maxSpec.a && spec.b > maxSpec.b) ||
129
157
  (spec.a === maxSpec.a && spec.b === maxSpec.b && spec.c > maxSpec.c)) {
130
- maxSpec = spec;
131
- }
132
- });
158
+ maxSpec = spec;
159
+ }
160
+ });
133
161
 
134
- return maxSpec;
162
+ return maxSpec;
135
163
  }
136
164
 
137
165
  function computeMaxSpecificity(selector) {
138
- let maxSpecificity = { a: 0, b: 0, c: 0 };
139
- const stack = [];
140
- cssTree.walk(selector, {
141
- enter(node) {
142
- stack.push(node);
143
- if (node.type === "Selector") {
144
- const insideWhere = stack.some(n => n.type === "PseudoClassSelector" && n.name.toLowerCase() === "where");
145
- if (insideWhere) return;
146
- const specificity = computeSpecificity(node);
147
- if (specificity.a > maxSpecificity.a ||
166
+ let maxSpecificity = { a: 0, b: 0, c: 0 };
167
+ const stack = [];
168
+ cssTree.walk(selector, {
169
+ enter(node) {
170
+ stack.push(node);
171
+ if (node.type === "Selector") {
172
+ const insideWhere = stack.some(n => n.type === "PseudoClassSelector" && n.name.toLowerCase() === "where");
173
+ if (insideWhere) return;
174
+ const specificity = computeSpecificity(node);
175
+ if (specificity.a > maxSpecificity.a ||
148
176
  (specificity.a === maxSpecificity.a && specificity.b > maxSpecificity.b) ||
149
177
  (specificity.a === maxSpecificity.a && specificity.b === maxSpecificity.b && specificity.c > maxSpecificity.c)) {
150
- maxSpecificity = specificity;
151
- }
152
- }
153
- },
154
- leave() {
155
- stack.pop();
156
- }
157
- });
158
- return maxSpecificity;
178
+ maxSpecificity = specificity;
179
+ }
180
+ }
181
+ },
182
+ leave() {
183
+ stack.pop();
184
+ }
185
+ });
186
+ return maxSpecificity;
159
187
  }
@@ -250,7 +250,7 @@ function compressJSONLD(node) {
250
250
 
251
251
  function removeEmptyInlineElements(doc) {
252
252
  doc.querySelectorAll("style, script:not([src])").forEach(element => {
253
- if (!element.textContent.trim()) {
253
+ if (!element.textContent.trim() && element.parentElement == doc.head) {
254
254
  element.remove();
255
255
  }
256
256
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.6.21",
3
+ "version": "1.6.23",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",