Framework updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-03-04 23:20:19 +00:00
parent a89daf3d43
commit 3ed8517b2a
891 changed files with 11126 additions and 9600 deletions

View File

@@ -106,7 +106,7 @@ export const elemsGroups = {
* Elements where adding or removing whitespace may affect rendering, metadata,
* or semantic meaning.
*
* @see https://developer.mozilla.org/docs/Web/HTML/Element/pre
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre
* @type {Readonly<Set<string>>}
*/
export const textElems = new Set([...elemsGroups.textContent, 'pre', 'title']);
@@ -2395,7 +2395,7 @@ export const colorsProps = new Set([
/**
* @type {Readonly<Record<string, Set<string>>>}
* @see https://developer.mozilla.org/docs/Web/CSS/Pseudo-classes
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes
*/
export const pseudoClasses = {
displayState: new Set(['fullscreen', 'modal', 'picture-in-picture']),

View File

@@ -44,8 +44,10 @@ export const fn = (_root, params) => {
element: {
enter: (node) => {
if (node.attributes.viewBox != null) {
const nums = node.attributes.viewBox.trim().split(/(?:\s,?|,)\s*/g);
node.attributes.viewBox = nums
const numbers = node.attributes.viewBox
.trim()
.split(/(?:\s,?|,)\s*/g);
node.attributes.viewBox = numbers
.map((value) => {
const num = Number(value);
return Number.isNaN(num)

View File

@@ -123,7 +123,7 @@ export const fn = (_root, params) => {
if (rgb2hex) {
const match = val.match(regRGB);
if (match != null) {
const nums = match.slice(1, 4).map((m) => {
const numbers = match.slice(1, 4).map((m) => {
let n;
if (m.indexOf('%') > -1) {
n = Math.round(parseFloat(m) * 2.55);
@@ -132,7 +132,7 @@ export const fn = (_root, params) => {
}
return Math.max(0, Math.min(n, 255));
});
val = convertRgbToHex(nums);
val = convertRgbToHex(numbers);
}
}

View File

@@ -15,8 +15,8 @@ export const description =
*
* @author Seth Falco <seth@falco.fun>
* @type {import('../lib/types.js').Plugin}
* @see https://developer.mozilla.org/docs/Web/SVG/Element/linearGradient
* @see https://developer.mozilla.org/docs/Web/SVG/Element/radialGradient
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient
*/
export const fn = (root) => {
const stylesheet = collectStylesheet(root);

View File

@@ -230,8 +230,7 @@ const convertToRelative = (pathData) => {
cursor[1] += args[1];
start[0] = cursor[0];
start[1] = cursor[1];
}
if (command === 'M') {
} else if (command === 'M') {
// M → m
// skip first moveto
if (i !== 0) {
@@ -247,11 +246,10 @@ const convertToRelative = (pathData) => {
}
// lineto (x y)
if (command === 'l') {
else if (command === 'l') {
cursor[0] += args[0];
cursor[1] += args[1];
}
if (command === 'L') {
} else if (command === 'L') {
// L → l
command = 'l';
args[0] -= cursor[0];
@@ -261,10 +259,9 @@ const convertToRelative = (pathData) => {
}
// horizontal lineto (x)
if (command === 'h') {
else if (command === 'h') {
cursor[0] += args[0];
}
if (command === 'H') {
} else if (command === 'H') {
// H → h
command = 'h';
args[0] -= cursor[0];
@@ -272,10 +269,9 @@ const convertToRelative = (pathData) => {
}
// vertical lineto (y)
if (command === 'v') {
else if (command === 'v') {
cursor[1] += args[0];
}
if (command === 'V') {
} else if (command === 'V') {
// V → v
command = 'v';
args[0] -= cursor[1];
@@ -283,11 +279,10 @@ const convertToRelative = (pathData) => {
}
// curveto (x1 y1 x2 y2 x y)
if (command === 'c') {
else if (command === 'c') {
cursor[0] += args[4];
cursor[1] += args[5];
}
if (command === 'C') {
} else if (command === 'C') {
// C → c
command = 'c';
args[0] -= cursor[0];
@@ -301,11 +296,10 @@ const convertToRelative = (pathData) => {
}
// smooth curveto (x2 y2 x y)
if (command === 's') {
else if (command === 's') {
cursor[0] += args[2];
cursor[1] += args[3];
}
if (command === 'S') {
} else if (command === 'S') {
// S → s
command = 's';
args[0] -= cursor[0];
@@ -317,11 +311,10 @@ const convertToRelative = (pathData) => {
}
// quadratic Bézier curveto (x1 y1 x y)
if (command === 'q') {
else if (command === 'q') {
cursor[0] += args[2];
cursor[1] += args[3];
}
if (command === 'Q') {
} else if (command === 'Q') {
// Q → q
command = 'q';
args[0] -= cursor[0];
@@ -333,11 +326,10 @@ const convertToRelative = (pathData) => {
}
// smooth quadratic Bézier curveto (x y)
if (command === 't') {
else if (command === 't') {
cursor[0] += args[0];
cursor[1] += args[1];
}
if (command === 'T') {
} else if (command === 'T') {
// T → t
command = 't';
args[0] -= cursor[0];
@@ -347,11 +339,10 @@ const convertToRelative = (pathData) => {
}
// elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
if (command === 'a') {
else if (command === 'a') {
cursor[0] += args[5];
cursor[1] += args[6];
}
if (command === 'A') {
} else if (command === 'A') {
// A → a
command = 'a';
args[5] -= cursor[0];
@@ -361,7 +352,7 @@ const convertToRelative = (pathData) => {
}
// closepath
if (command === 'Z' || command === 'z') {
else if (command === 'Z' || command === 'z') {
// reset cursor
cursor[0] = start[0];
cursor[1] = start[1];

View File

@@ -29,7 +29,7 @@ export const description = 'inline styles (additional options)';
* Pseudo-classes that we can evaluate during optimization, and shouldn't be
* toggled conditionally through the `usePseudos` parameter.
*
* @see https://developer.mozilla.org/docs/Web/CSS/Pseudo-classes
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes
*/
const preservedPseudos = [
...pseudoClasses.functional,

View File

@@ -67,7 +67,7 @@ export const description =
* ↓
* <rect x="0" y="0" width="100" height="100"/>
*
* @link https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors|MDN CSS Selectors
* @link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|MDN CSS Selectors
*
* @author Bradley Mease
*

View File

@@ -17,7 +17,7 @@ const standardDescs = /^(Created with|Created using)/;
* description.
*
* @author Daniel Wabyick
* @see https://developer.mozilla.org/docs/Web/SVG/Element/desc
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc
*
* @type {import('../lib/types.js').Plugin<RemoveDescParams>}
*/

View File

@@ -7,8 +7,7 @@ import { detachNodeFromParent } from '../lib/xast.js';
*/
export const name = 'removeElementsByAttr';
export const description =
'removes arbitrary elements by ID or className (disabled by default)';
export const description = 'removes arbitrary elements by ID or className';
/**
* Remove arbitrary SVG elements by ID or className.

View File

@@ -1,6 +1,7 @@
import { elemsGroups } from './_collections.js';
import { detachNodeFromParent } from '../lib/xast.js';
import { collectStylesheet, computeStyle } from '../lib/style.js';
import { findReferences } from '../lib/svgo/tools.js';
export const name = 'removeEmptyContainers';
export const description = 'removes empty container elements';
@@ -22,9 +23,33 @@ export const description = 'removes empty container elements';
*/
export const fn = (root) => {
const stylesheet = collectStylesheet(root);
const removedIds = new Set();
/**
* @type {Map<string, {
* node: import('../lib/types.js').XastElement,
* parent: import('../lib/types.js').XastParent,
* }[]>}
*/
const usesById = new Map();
return {
element: {
enter: (node, parentNode) => {
if (node.name === 'use') {
// Record uses so those referencing empty containers can be removed.
for (const [name, value] of Object.entries(node.attributes)) {
const ids = findReferences(name, value);
for (const id of ids) {
let references = usesById.get(id);
if (references === undefined) {
references = [];
usesById.set(id, references);
}
references.push({ node: node, parent: parentNode });
}
}
}
},
exit: (node, parentNode) => {
// remove only empty non-svg containers
if (
@@ -61,6 +86,22 @@ export const fn = (root) => {
}
detachNodeFromParent(node, parentNode);
if (node.attributes.id) {
removedIds.add(node.attributes.id);
}
},
},
root: {
exit: () => {
// Remove any <use> elements that referenced an empty container.
for (const id of removedIds) {
const uses = usesById.get(id);
if (uses) {
for (const use of uses) {
detachNodeFromParent(use.node, use.parent);
}
}
}
},
},
};

View File

@@ -190,38 +190,6 @@ export const fn = (root, params) => {
}
}
// Removes hidden elements
// https://www.w3schools.com/cssref/pr_class_visibility.asp
const computedStyle = computeStyle(stylesheet, node);
if (
isHidden &&
computedStyle.visibility &&
computedStyle.visibility.type === 'static' &&
computedStyle.visibility.value === 'hidden' &&
// keep if any descendant enables visibility
querySelector(node, '[visibility=visible]') == null
) {
removeElement(node, parentNode);
return;
}
// display="none"
//
// https://www.w3.org/TR/SVG11/painting.html#DisplayProperty
// "A value of display: none indicates that the given element
// and its children shall not be rendered directly"
if (
displayNone &&
computedStyle.display &&
computedStyle.display.type === 'static' &&
computedStyle.display.value === 'none' &&
// markers with display: none still rendered
node.name !== 'marker'
) {
removeElement(node, parentNode);
return;
}
// Circles with zero radius
//
// https://www.w3.org/TR/SVG11/shapes.html#CircleElementRAttribute
@@ -363,32 +331,6 @@ export const fn = (root, params) => {
return;
}
// Path with empty data
//
// https://www.w3.org/TR/SVG11/paths.html#DAttribute
//
// <path d=""/>
if (pathEmptyD && node.name === 'path') {
if (node.attributes.d == null) {
removeElement(node, parentNode);
return;
}
const pathData = parsePathData(node.attributes.d);
if (pathData.length === 0) {
removeElement(node, parentNode);
return;
}
// keep single point paths for markers
if (
pathData.length === 1 &&
computedStyle['marker-start'] == null &&
computedStyle['marker-end'] == null
) {
removeElement(node, parentNode);
return;
}
}
// Polyline with empty points
//
// https://www.w3.org/TR/SVG11/shapes.html#PolylineElementPointsAttribute
@@ -417,6 +359,64 @@ export const fn = (root, params) => {
return;
}
// Removes hidden elements
// https://www.w3schools.com/cssref/pr_class_visibility.asp
const computedStyle = computeStyle(stylesheet, node);
if (
isHidden &&
computedStyle.visibility &&
computedStyle.visibility.type === 'static' &&
computedStyle.visibility.value === 'hidden' &&
// keep if any descendant enables visibility
querySelector(node, '[visibility=visible]') == null
) {
removeElement(node, parentNode);
return;
}
// display="none"
//
// https://www.w3.org/TR/SVG11/painting.html#DisplayProperty
// "A value of display: none indicates that the given element
// and its children shall not be rendered directly"
if (
displayNone &&
computedStyle.display &&
computedStyle.display.type === 'static' &&
computedStyle.display.value === 'none' &&
// markers with display: none still rendered
node.name !== 'marker'
) {
removeElement(node, parentNode);
return;
}
// Path with empty data
//
// https://www.w3.org/TR/SVG11/paths.html#DAttribute
//
// <path d=""/>
if (pathEmptyD && node.name === 'path') {
if (node.attributes.d == null) {
removeElement(node, parentNode);
return;
}
const pathData = parsePathData(node.attributes.d);
if (pathData.length === 0) {
removeElement(node, parentNode);
return;
}
// keep single point paths for markers
if (
pathData.length === 1 &&
computedStyle['marker-start'] == null &&
computedStyle['marker-end'] == null
) {
removeElement(node, parentNode);
return;
}
}
for (const [name, value] of Object.entries(node.attributes)) {
const ids = findReferences(name, value);

View File

@@ -5,7 +5,7 @@ import { intersects } from './_path.js';
export const name = 'removeOffCanvasPaths';
export const description =
'removes elements that are drawn outside of the viewBox (disabled by default)';
'removes elements that are drawn outside of the viewBox';
/**
* Remove elements that are drawn outside of the viewBox.

View File

@@ -1,7 +1,7 @@
import { detachNodeFromParent } from '../lib/xast.js';
export const name = 'removeRasterImages';
export const description = 'removes raster images (disabled by default)';
export const description = 'removes raster images';
/**
* Remove raster images references in <image>.

View File

@@ -2,7 +2,7 @@ import { attrsGroups } from './_collections.js';
import { detachNodeFromParent } from '../lib/xast.js';
export const name = 'removeScripts';
export const description = 'removes scripts (disabled by default)';
export const description = 'removes scripts';
/** Union of all event attributes. */
const eventAttrs = [

View File

@@ -1,7 +1,7 @@
import { detachNodeFromParent } from '../lib/xast.js';
export const name = 'removeStyleElement';
export const description = 'removes <style> element (disabled by default)';
export const description = 'removes <style> element';
/**
* Remove <style>.

View File

@@ -6,7 +6,7 @@ export const description = 'removes <title>';
/**
* Remove <title>.
*
* https://developer.mozilla.org/docs/Web/SVG/Element/title
* https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title
*
* @author Igor Kalashnikov
*

View File

@@ -7,7 +7,11 @@ import {
} from './_collections.js';
import { detachNodeFromParent } from '../lib/xast.js';
import { visitSkip } from '../lib/util/visit.js';
import { collectStylesheet, computeStyle } from '../lib/style.js';
import {
collectStylesheet,
computeStyle,
includesAttrSelector,
} from '../lib/style.js';
/**
* @typedef RemoveUnknownsAndDefaultsParams
@@ -192,7 +196,12 @@ export const fn = (root, params) => {
attributesDefaults.get(name) === value
) {
// keep defaults if parent has own or inherited style
if (computedParentStyle?.[name] == null) {
if (
computedParentStyle?.[name] == null &&
!stylesheet.rules.some((rule) =>
includesAttrSelector(rule.selector, name),
)
) {
delete node.attributes[name];
}
}

View File

@@ -31,12 +31,12 @@ export const fn = () => {
if (node.name === 'svg' && parentNode.type !== 'root') {
return;
}
const nums = node.attributes.viewBox.split(/[ ,]+/g);
const numbers = node.attributes.viewBox.split(/[ ,]+/g);
if (
nums[0] === '0' &&
nums[1] === '0' &&
node.attributes.width.replace(/px$/, '') === nums[2] && // could use parseFloat too
node.attributes.height.replace(/px$/, '') === nums[3]
numbers[0] === '0' &&
numbers[1] === '0' &&
node.attributes.width.replace(/px$/, '') === numbers[2] && // could use parseFloat too
node.attributes.height.replace(/px$/, '') === numbers[3]
) {
delete node.attributes.viewBox;
}

View File

@@ -1,6 +1,5 @@
export const name = 'removeXMLNS';
export const description =
'removes xmlns attribute (for inline svg, disabled by default)';
export const description = 'removes xmlns attribute (for inline svg)';
/**
* Remove the xmlns attribute when present.

View File

@@ -18,7 +18,7 @@ const XLINK_NAMESPACE = 'http://www.w3.org/1999/xlink';
* Map of `xlink:show` values to the SVG 2 `target` attribute values.
*
* @type {Record<string, string>}
* @see https://developer.mozilla.org/docs/Web/SVG/Attribute/xlink:show#usage_notes
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:show#usage_notes
*/
const SHOW_TO_TARGET = {
new: '_blank',
@@ -30,8 +30,8 @@ const SHOW_TO_TARGET = {
* don't support the SVG 2 href attribute.
*
* @type {Set<string>}
* @see https://developer.mozilla.org/docs/Web/SVG/Attribute/xlink:href
* @see https://developer.mozilla.org/docs/Web/SVG/Attribute/href
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/href
*/
const LEGACY_ELEMENTS = new Set([
'cursor',
@@ -60,7 +60,7 @@ const findPrefixedAttrs = (node, prefixes, attr) => {
* XLink namespace is deprecated in SVG 2.
*
* @type {import('../lib/types.js').Plugin<RemoveXlinkParams>}
* @see https://developer.mozilla.org/docs/Web/SVG/Attribute/xlink:href
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
*/
export const fn = (_, params) => {
const { includeLegacy } = params;

View File

@@ -26,7 +26,7 @@ export const fn = (root) => {
* element if one exists.
*
* @type {import('../lib/types.js').XastElement}
* @see https://developer.mozilla.org/docs/Web/SVG/Element/defs
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs
*/
let svgDefs;