@aws-amplify/ui
Version:
`@aws-amplify/ui` contains low-level logic & styles for stand-alone usage or re-use in framework-specific implementations.
134 lines (131 loc) • 5.87 kB
JavaScript
import '@aws-amplify/core/internals/utils';
import '../../utils/setUserAgent/constants.mjs';
import { cloneDeep, has } from '../../utils/utils.mjs';
import { getName, getPathFromName, resolveReference, usesReference } from '../../utils/references.mjs';
import { REFERENCE_REGEX } from './constants.mjs';
const DEFAULTS = {
ignoreKeys: ['original'],
};
function resolveObject(object) {
const foundCirc = {};
const clone = cloneDeep(object); // This object will be edited
const currentContext = []; // To maintain the context to be able to test for circular definitions
if (typeof object === 'object') {
return traverseObject({
slice: clone,
fullObj: clone,
currentContext,
foundCirc,
});
}
else {
throw new Error('Please pass an object in');
}
}
/**
* Recursively traverses an object (slice) to resolve and uses
* compileValue to replace any string references found within it
*/
function traverseObject({ slice, fullObj, currentContext, foundCirc, }) {
for (let key in slice) {
if (!has(slice, key)) {
continue;
}
const prop = slice[key];
// We want to check for ignoredKeys, this is to
// skip over attributes that should not be
// mutated, like a copy of the original property.
if (DEFAULTS.ignoreKeys && DEFAULTS.ignoreKeys.indexOf(key) !== -1) {
continue;
}
currentContext.push(key);
if (typeof prop === 'object') {
traverseObject({ currentContext, slice: prop, fullObj, foundCirc });
}
else {
if (typeof prop === 'string' && prop.indexOf('{') > -1) {
slice[key] = compileValue({
value: prop,
stack: [getName(currentContext)],
foundCirc,
fullObj,
});
}
}
currentContext.pop();
}
return fullObj;
}
/**
* Resolves references in a value, performing recursive lookups when references are nested.
* value: The string that may contain references (e.g., {color.border.light}) that need to be replaced
* stack: keeps track of the current chain of references to detect circular references
* foundCirc: stores any detected circular references
* fullObj: The full object where references are looked up, essentially the source of all values
*/
function compileValue({ value, stack, foundCirc, fullObj }) {
let toRet = value, ref;
const regex = new RegExp(REFERENCE_REGEX);
// Replace the reference inline, but don't replace the whole string because
// references can be part of the value such as "1px solid {color.border.light}"
value.replace(regex, function (match, variable) {
variable = variable.trim();
// Find what the value is referencing
const pathName = getPathFromName(variable);
const refHasValue = pathName[pathName.length - 1] === 'value';
stack.push(variable);
ref = resolveReference(pathName, fullObj);
// If the reference doesn't end in 'value'
// and
// the reference points to someplace that has a `value` attribute
// we should take the '.value' of the reference
// per the W3C draft spec where references do not have .value
// https://design-tokens.github.io/community-group/format/#aliases-references
if (!refHasValue && ref && has(ref, 'value')) {
ref = ref.value;
}
if (typeof ref !== 'undefined') {
if (typeof ref === 'string' || typeof ref === 'number') {
toRet = value.replace(match, ref);
// Recursive, therefore we can compute multi-layer variables like a = b, b = c, eventually a = c
if (usesReference(toRet)) {
var reference = toRet.slice(1, -1);
// Compare to found circular references
if (has(foundCirc, reference)) ;
else if (stack.indexOf(reference) !== -1) {
// If the current stack already contains the current reference, we found a new circular reference
// chop down only the circular part, save it to our circular reference info, and spit out an error
// Get the position of the existing reference in the stack
var stackIndexReference = stack.indexOf(reference);
// Get the portion of the stack that starts at the circular reference and brings you through until the end
var circStack = stack.slice(stackIndexReference);
// For all the references in this list, add them to the list of references that end up in a circular reference
circStack.forEach(function (key) {
foundCirc[key] = true;
});
// Add our found circular reference to the end of the cycle
circStack.push(reference);
}
else {
toRet = compileValue({ value: toRet, stack, foundCirc, fullObj });
}
}
// if evaluated value is a number and equal to the reference, we want to keep the type
if (typeof ref === 'number' && ref.toString() === toRet) {
toRet = ref;
}
}
else {
// if evaluated value is not a string or number, we want to keep the type
toRet = ref;
}
}
else {
toRet = ref;
}
stack.pop(variable);
return toRet;
});
return toRet;
}
export { compileValue, resolveObject, traverseObject };