@silexlabs/grapesjs-symbols
Version:
Symbols for GrapesJS
149 lines • 5.76 kB
JavaScript
/**
* Attributes that can contain references to element IDs.
* These should be updated when IDs are made unique.
*/
const ID_REFERENCE_ATTRIBUTES = [
'for', // <label for="...">
'aria-labelledby', // ARIA: labels
'aria-describedby', // ARIA: descriptions
'aria-controls', // ARIA: controls
'aria-owns', // ARIA: ownership
'aria-activedescendant', // ARIA: active descendant
'aria-flowto', // ARIA: flow
'aria-errormessage', // ARIA: error message
'list', // <input list="..."> for datalist
'form', // <input form="..."> for form association
'headers', // <td headers="..."> for table headers
];
/**
* Attributes that contain ID references prefixed with #
*/
const HASH_ID_ATTRIBUTES = [
'href', // <a href="#id">
'data-target', // Bootstrap 4: data-target="#id"
'data-bs-target', // Bootstrap 5: data-bs-target="#id"
];
/**
* Helper to get all components under a root component in depth-first order.
*/
function flattenComponents(root) {
const components = [];
root.onAll((c) => components.push(c));
return components;
}
/**
* Build a map from main symbol component IDs to instance component IDs
* Uses component order to correlate since both have the same structure
*/
function buildIdMap(mainSymbol, instance) {
const idMap = new Map();
const mainComponents = flattenComponents(mainSymbol);
const instanceComponents = flattenComponents(instance);
const n = Math.min(mainComponents.length, instanceComponents.length);
for (let i = 0; i < n; i++) {
const mainId = mainComponents[i].getAttributes().id;
const instanceId = instanceComponents[i].getAttributes().id;
if (mainId && instanceId) {
idMap.set(mainId, instanceId);
}
}
return idMap;
}
/**
* Collect all ID reference values from the main symbol.
* Returns a map of: referenced ID -> list of {component index, attribute name, isHash}
*/
function collectIdReferences(mainSymbol) {
const refs = new Map();
flattenComponents(mainSymbol).forEach((component, index) => {
const attrs = component.getAttributes();
// Check direct ID references
for (const attr of ID_REFERENCE_ATTRIBUTES) {
const value = attrs[attr];
if (typeof value === 'string' && value) {
const ids = value.split(/\s+/);
ids.forEach(id => {
if (!refs.has(id))
refs.set(id, []);
refs.get(id).push({ index, attr, isHash: false });
});
}
}
// Check hash-prefixed ID references
for (const attr of HASH_ID_ATTRIBUTES) {
const value = attrs[attr];
if (typeof value === 'string' && value.startsWith('#')) {
const id = value.substring(1);
if (!refs.has(id))
refs.set(id, []);
refs.get(id).push({ index, attr, isHash: true });
}
}
});
return refs;
}
/**
* Update ID references in a symbol instance to match the new unique IDs.
*
* This function:
* 1. Gets the main symbol for this instance
* 2. Builds a map of main symbol IDs -> instance IDs
* 3. Finds all ID references in the main symbol (for, aria-*, href="#...", etc.)
* 4. Updates those references in the instance to use the new IDs
*
* @param editor - The GrapesJS editor instance
* @param instance - The root component of the symbol instance
*/
export function makeInstanceIdsUnique(editor, instance) {
const symbolInfo = editor.Components.getSymbolInfo(instance);
if (!(symbolInfo === null || symbolInfo === void 0 ? void 0 : symbolInfo.main))
return;
const mainSymbol = symbolInfo.main;
const idMap = buildIdMap(mainSymbol, instance);
if (idMap.size === 0)
return;
const refs = collectIdReferences(mainSymbol);
const instanceComponents = flattenComponents(instance);
refs.forEach((refList, referencedId) => {
// Prefer exact match, fallback to prefix match (e.g., for GrapesJS suffixes)
let newId;
idMap.forEach((instanceId, mainId) => {
if (mainId === referencedId || mainId.startsWith(referencedId + '-')) {
newId = instanceId;
}
});
if (!newId)
return;
refList.forEach(ref => {
const component = instanceComponents[ref.index];
if (!component)
return;
const attrs = component.getAttributes();
const currentValue = attrs[ref.attr];
let newValue;
if (ref.isHash) {
newValue = `#${newId}`;
}
else if (typeof currentValue === 'string' && currentValue.includes(' ')) {
// Handle space-separated list of IDs
newValue = currentValue
.split(/\s+/)
.map(id => id === referencedId ? newId : id)
.join(' ');
}
else {
newValue = newId;
}
// Temporarily add symbol override for attributes, then set attribute, then restore overrides
const overrides = component.getSymbolOverride() || [];
const overridesArray = Array.isArray(overrides) ? overrides : [];
const hasAttrOverride = overridesArray.includes('attributes');
if (!hasAttrOverride)
component.setSymbolOverride([...overridesArray, 'attributes']);
component.setAttributes({ [ref.attr]: newValue });
if (!hasAttrOverride)
component.setSymbolOverride(overrides);
});
});
}
//# sourceMappingURL=id-utils.js.map