%k25u25%fgd5n!
/**
* Utility functions for patching variant classes in HTML.
* Handles class patterns like "is-style-outline--N" and "is-style-ext-preset--...--N"
* that WordPress may reorder when parsing blocks.
*/
// escape for regex building
export const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
export const getVariantNumbersInTree = (rootEl, base) => {
if (!rootEl) return [];
// Match classes ending with --{number}
const re = new RegExp(`^${esc(base)}.*--(\\d+)$`, 'i');
const out = [];
const seen = new Set();
// include root + descendants
const all = [rootEl, ...rootEl.querySelectorAll(`[class*="${base}"]`)];
for (const el of all) {
for (const cls of el.classList) {
const m = cls.match(re);
if (!m) continue;
const n = Number(m[1]);
if (seen.has(n)) continue;
seen.add(n);
out.push(n);
}
}
return out;
};
/**
* Extracts variant class info as { prefix, number } pairs.
* prefix = everything before the final --N (used for matching element types)
*/
export const getVariantClassInfo = (rootEl, base) => {
if (!rootEl) return [];
const re = new RegExp(`^(${esc(base)}.*)--(\\d+)$`, 'i');
const out = [];
const seenNumbers = new Set();
const all = [rootEl, ...rootEl.querySelectorAll(`[class*="${base}"]`)];
for (const el of all) {
for (const cls of el.classList) {
const m = cls.match(re);
if (!m) continue;
const prefix = m[1];
const number = Number(m[2]);
// Skip duplicate numbers (same variant on multiple elements)
if (seenNumbers.has(number)) continue;
seenNumbers.add(number);
out.push({ prefix, number });
}
}
return out;
};
export const getVariantClassInfoFromHtml = (html, base) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = html;
return getVariantClassInfo(wrapper, base);
};
export const getVariantNumbersInHtml = (html, base) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = html;
return getVariantNumbersInTree(wrapper, base);
};
export const applyVariantNumberMapToHtml = (html, base, numberMap) => {
if (!numberMap || !numberMap.size) return html;
const wrapper = document.createElement('div');
wrapper.innerHTML = html;
// Match classes ending with --{number}
const reToken = new RegExp(`^${esc(base)}.*--(\\d+)$`, 'i');
// target every element that *could* contain the class
const els = wrapper.querySelectorAll(`[class*="${base}"]`);
for (const el of els) {
const classes = Array.from(el.classList);
let changed = false;
for (const [i, cls] of classes.entries()) {
const m = cls.match(reToken);
if (!m) continue;
const oldN = Number(m[1]);
if (!numberMap.has(oldN)) continue;
const newN = numberMap.get(oldN);
// Replace the variant number at the end
const nextCls = cls.replace(/--(\d+)$/, `--${newN}`);
if (nextCls === cls) continue;
classes[i] = nextCls;
changed = true;
}
// keep the base style class too
if (!classes.includes(base)) {
classes.push(base);
changed = true;
}
if (changed) el.className = classes.join(' ');
}
return wrapper.innerHTML;
};
/**
* Patches variant classes in HTML to match the DOM element's numbering.
* Handles patterns like "is-style-outline--N" and "is-style-ext-preset--...--N"
* Note that that variant numbers are globally unique in each page and are autogenerated by WP.
*/
export const patchVariantClasses = (html, el, bases) => {
let patchedHtml = html;
for (const base of bases) {
// Get variant info with prefixes for matching by element type
const targetInfo = getVariantClassInfo(el, base);
const currentInfo = getVariantClassInfoFromHtml(patchedHtml, base);
if (!targetInfo.length || !currentInfo.length) continue;
// Group by prefix - allows matching by type, with position-based fallback
// for multiple elements of the same type
const targetByPrefix = new Map();
for (const { prefix, number } of targetInfo) {
if (!targetByPrefix.has(prefix)) targetByPrefix.set(prefix, []);
targetByPrefix.get(prefix).push(number);
}
const currentByPrefix = new Map();
for (const { prefix, number } of currentInfo) {
if (!currentByPrefix.has(prefix)) currentByPrefix.set(prefix, []);
currentByPrefix.get(prefix).push(number);
}
// Map by matching prefixes, then by position within each prefix group
const map = new Map();
for (const [prefix, currentNums] of currentByPrefix) {
const targetNums = targetByPrefix.get(prefix);
if (!targetNums) continue;
const count = Math.min(currentNums.length, targetNums.length);
for (const [i, cur] of currentNums.entries()) {
if (i >= count) break;
if (cur === targetNums[i]) continue;
map.set(cur, targetNums[i]);
}
}
if (!map.size) continue;
patchedHtml = applyVariantNumberMapToHtml(patchedHtml, base, map);
}
return patchedHtml;
};