xuxi
Version:
Dynamically utility for combining different types of values into a single value.
317 lines • 11.7 kB
JavaScript
(function(g,f){typeof exports==='object'&&typeof module!=='undefined'?f(exports):typeof define==='function'&&define.amd?define(['exports'],f):(g=typeof globalThis!=='undefined'?globalThis:g||self,f(g.Xuxi={}));})(this,(function(exports){'use strict';/**
* A utility function for managing values based on variant configurations.
*
* This function simplifies the handling of value generation with support for variants, default values, and dynamic overrides.
* @template T - The type of variant keys and their possible values.
* @param {VariantRecord<T>} keys - The configuration object containing:
* - `assign` (optional): A base value to always include.
* - `variants`: An object defining variant keys and their possible values as classes.
* - `defaultVariants` (optional): Default variant values for each variant key.
* @returns {(result?: VariantResult<T>) => string} - A function that takes a `result` object to override default variants
* and generates a class name string.
* @example
* @see {@link https://ilkhoeri.github.io/xuxi/variant Docs}
*/
function variant(keys) {
return (result = {}) => {
const mergedVariant = { ...keys.defaultVariants, ...result };
const variants = Object.keys(keys.variants)
.map(key => {
var _a;
const variantKey = mergedVariant[key] || ((_a = keys.defaultVariants) === null || _a === undefined ? undefined : _a[key]);
return variantKey ? keys.variants[key][variantKey] : undefined;
})
.filter(Boolean)
.join(' ')
.trim();
return keys.assign ? [keys.assign, variants].join(' ').trim() : variants;
};
}/**
* Serializes a given value into a space-separated string.
* @param v - The value to be processed.
* @returns A space-separated string representation of the value.
*/
function sv(v) {
let k, y, s = '';
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'bigint' || v === null) {
s += v;
}
else if (typeof v === 'object') {
if (Array.isArray(v)) {
let o = v.length;
for (k = 0; k < o; k++) {
if (v[k]) {
if ((y = sv(v[k]))) {
s && (s += ' ');
s += y;
}
}
}
}
else {
for (y in v) {
if (v[y]) {
s && (s += ' ');
s += y;
}
}
}
}
else if (typeof v === 'function') {
s += sv(v(s));
}
return s;
}
/**
* Recursively serializes objects into a key-value string format.
* @param v - The value to be processed.
* @returns A string representation of the object.
*/
function rv(v) {
let k, y, s = '';
if (typeof v === 'object' && v !== null) {
if (Array.isArray(v)) {
let o = v.length;
for (k = 0; k < o; k++) {
if (v[k]) {
if ((y = rv(v[k]))) {
s && (s += ' ');
s += y;
}
}
}
}
else {
for (y in v) {
if (v[y]) {
s && (s += ' ');
s += `${y}:${typeof v[y] === 'object' ? rv(v[y]) : v[y]}`;
}
}
for (const sym of Object.getOwnPropertySymbols(v)) {
if (v[sym]) {
s && (s += ' ');
s += `${String(sym)}:${v[sym]}`;
}
}
}
}
return s;
}
/**
* Serializes instances of Date, Map, and Set objects into a string format.
* @param v - The value to be processed.
* @returns A string representation of the instance.
*/
function iv(v) {
let k, y, s = '';
if (v instanceof Date) {
s += v.toISOString();
}
else if (v instanceof Map) {
for (const [q, u] of v.entries()) {
if (u) {
s && (s += ' '); // concatenation
s += `${q}:${u}`;
}
}
}
else if (v instanceof Set) {
v.forEach(e => {
if (e) {
s && (s += ' ');
s += e;
}
});
}
else if (typeof v === 'object') {
if (Array.isArray(v)) {
var o = v.length;
for (k = 0; k < o; k++) {
if (v[k]) {
if ((y = iv(v[k]))) {
s && (s += ' ');
s += y;
}
}
}
}
}
else if (typeof v === 'function') {
s += iv(v(s));
}
return s;
}
/**
* Applies recursive transformation to the input values.
* @param args - Input values.
* @returns Transformed string.
*/
function recursiveFn(...args) {
return rv(args);
}
/**
* Applies instance-based transformation to the input values.
* @param args - Input values.
* @returns Transformed string.
*/
function instanceofFn(...args) {
return iv(args);
}
/**
* Converts input values into a space-separated string.
* @param args - Input values.
* @returns The formatted string.
*/
const string = (...args) => {
let i = 0, t, x, s = '', o = args.length;
for (; i < o; i++) {
if ((t = args[i])) {
if ((x = sv(t))) {
s && (s += ' ');
s += x;
}
}
}
return s;
};
string.recursive = recursiveFn;
string.instanceof = instanceofFn;
string.variant = variant;/**
* Checks if a given value is a plain object (i.e., not an array or null).
* @param value - The value to check.
* @returns True if the value is a plain object, otherwise false.
*/
function isPlainObject(value) {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
/**
* Merges multiple objects deeply, handling arrays and functions gracefully.
* @template T - The base object type.
* @param obj - One or more objects to merge.
* @returns The deeply merged object.
*/
function baseObject(...obj) {
const seen = new WeakMap(); // Use WeakMap to store processed objects
function merge(acc, input) {
if (!input)
return acc;
if (isPlainObject(input)) {
if (seen.has(input))
return seen.get(input); // If there is one, use previous result.
const newAcc = { ...acc }; // Copy acc so as not to change direct references
seen.set(input, newAcc); // Mark objects as processed
acc = newAcc; // Use copied version
}
if (Array.isArray(input))
return { ...acc, ...baseObject(...input) };
if (typeof input === 'function') {
const result = input(acc);
return isPlainObject(result) ? merge(acc, result) : { ...acc, ...baseObject(result) };
}
if (isPlainObject(input)) {
Reflect.ownKeys(input).forEach(key => {
const value = input[key];
if (isPlainObject(value) && isPlainObject(acc[key])) {
acc[key] = merge(acc[key], value);
}
else {
acc[key] = value;
}
});
return acc;
}
return acc;
}
return obj.reduce((acc, input) => merge(acc, input), {});
}
/**
* Merges multiple objects deeply, handling arrays and functions gracefully **without overwriting**.
* @template T - The base object type.
* @param obj - One or more objects to merge.
* @returns The deeply merged object **without overwriting** the value at the first key, only change the value if it does not exist.
*/
function preserveRoot(...obj) {
const seen = new WeakMap();
function merge(acc, input) {
if (!input)
return acc;
if (isPlainObject(input)) {
if (seen.has(input))
return seen.get(input);
const newAcc = { ...acc };
seen.set(input, newAcc);
acc = newAcc;
}
if (Array.isArray(input))
return { ...acc, ...preserveRoot(...input) };
if (typeof input === 'function') {
const result = input(acc);
return isPlainObject(result) ? merge(acc, result) : { ...acc, ...preserveRoot(result) };
}
if (isPlainObject(input)) {
Reflect.ownKeys(input).forEach(key => {
const value = input[key];
if (acc[key] === undefined) {
acc[key] = value; // Only change the value if it does not exist
}
else if (isPlainObject(value) && isPlainObject(acc[key])) {
acc[key] = merge(acc[key], value);
}
});
return acc;
}
return acc;
}
return obj.reduce((acc, input) => merge(acc, input), {});
}
/**
* Recursively removes falsy values from an object, except those specified in `exclude`.
* @template T - The object type.
* @param obj - The object to clean.
* @param exclude - An array of values to be preserved even if they are falsy (default: `[]`).
* @param seen - To detect cyclic references (default: `new WeakSet<object>()`).
* @returns A new object without the falsy values.
* @example
* @see {@link https://ilkhoeri.github.io/xuxi/clean Docs}
*/
function clean(obj, exclude = [], seen = new WeakSet()) {
const excludeSet = new Set(exclude);
if (seen.has(obj))
return obj; // Avoid infinite loops
seen.add(obj); // Mark object as visited
return Reflect.ownKeys(obj).reduce((acc, key) => {
const value = obj[key];
if (isPlainObject(value)) {
const cleanedObject = clean(value, exclude, seen); // Clean objects recursively
// Ensure the object is not empty before inserting
if (Object.keys(cleanedObject).length > 0 || typeof key === 'symbol')
acc[key] = cleanedObject;
}
else if (Array.isArray(value)) {
// Clear every element in the array, remove empty objects
const cleanedArray = value
.map(item => (isPlainObject(item) ? clean(item, exclude, seen) : item))
.filter(item => (item && !(isPlainObject(item) && Object.keys(item).length === 0)) || excludeSet.has(item));
if (cleanedArray.length > 0)
acc[key] = cleanedArray;
}
else if (value || excludeSet.has(value) || typeof key === 'symbol') {
// Save the value if it is not falsy or belongs to `excludeSet`
acc[key] = value;
}
return acc;
}, {});
}
/**
* Recursively merge objects with support for arrays, dynamic functions, and non falsy properties into a single object.
*
* Provides a chaining:
* - {@link baseObject raw} method to **get falsy values** from the result.
* - {@link preserveRoot preserve} method to join **without overwriting** first value.
* @example
* @see {@link https://ilkhoeri.github.io/xuxi/?id=object Docs}
*/
const object = (...obj) => clean(baseObject(...obj), [0]);
object.raw = baseObject;
object.preserve = preserveRoot;var x=/*#__PURE__*/Object.freeze({__proto__:null,clean:clean,instanceof:instanceofFn,isPlainObject:isPlainObject,object:object,recursive:recursiveFn,string:string,variant:variant});exports.clean=clean;exports.default=x;exports.instanceof=instanceofFn;exports.isPlainObject=isPlainObject;exports.object=object;exports.recursive=recursiveFn;exports.string=string;exports.variant=variant;exports.x=x;Object.defineProperty(exports,'__esModule',{value:true});}));