zentrixui
Version:
ZentrixUI - A modern, highly customizable and accessible React file upload component library with multiple variants, JSON-based configuration, and excellent developer experience.
345 lines (344 loc) • 10.3 kB
JavaScript
import { defaultConfig, validateConfig, loadConfigFromJSON, mergeConfig } from "./schema.js";
function deepMerge(target, source) {
const result = { ...target };
for (const key in source) {
if (source[key] !== void 0 && source[key] !== null) {
if (typeof source[key] === "object" && source[key] !== null && !Array.isArray(source[key])) {
result[key] = deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
}
return result;
}
function mergeConfigurations(fileConfig, propsConfig) {
const sources = {};
const warnings = [];
let config = { ...defaultConfig };
if (fileConfig) {
try {
config = deepMerge(config, fileConfig);
markSources(sources, fileConfig, "file");
} catch (error) {
warnings.push(`Failed to merge file configuration: ${error instanceof Error ? error.message : "Unknown error"}`);
}
}
if (propsConfig) {
try {
config = deepMerge(config, propsConfig);
markSources(sources, propsConfig, "props");
} catch (error) {
warnings.push(`Failed to merge props configuration: ${error instanceof Error ? error.message : "Unknown error"}`);
}
}
const validation = validateConfig(config);
if (!validation.isValid) {
warnings.push(`Merged configuration has validation errors: ${validation.errors.map((e) => e.message).join(", ")}`);
}
return { config, sources, warnings };
}
function markSources(sources, config, source, prefix = "") {
for (const [key, value] of Object.entries(config)) {
const fullKey = prefix ? `${prefix}.${key}` : key;
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
markSources(sources, value, source, fullKey);
} else {
sources[fullKey] = source;
}
}
}
async function loadConfiguration(configSource) {
if (!configSource) {
return { config: defaultConfig, errors: [] };
}
if (typeof configSource === "string") {
if (configSource.trim().startsWith("{")) {
return loadConfigFromJSON(configSource);
}
return await loadConfigFromFile(configSource);
}
try {
const mergedConfig = mergeConfig(configSource);
const validation = validateConfig(mergedConfig);
if (validation.isValid) {
return { config: mergedConfig, errors: [] };
} else {
return { config: defaultConfig, errors: validation.errors };
}
} catch (error) {
return {
config: defaultConfig,
errors: [{
path: "root",
message: `Failed to process configuration: ${error instanceof Error ? error.message : "Unknown error"}`
}]
};
}
}
async function loadConfigFromFile(filePath) {
try {
if (typeof window !== "undefined") {
const response = await fetch(filePath);
if (!response.ok) {
return {
config: defaultConfig,
errors: [{
path: "file",
message: `Failed to load configuration file: ${response.status} ${response.statusText}`
}]
};
}
const jsonString = await response.text();
return loadConfigFromJSON(jsonString);
} else {
const fs = await import("../_virtual/__vite-browser-external.js");
const path = await import("../_virtual/__vite-browser-external.js");
try {
const resolvedPath = path.resolve(filePath);
const jsonString = await fs.readFile(resolvedPath, "utf-8");
return loadConfigFromJSON(jsonString);
} catch (fsError) {
return {
config: defaultConfig,
errors: [{
path: "file",
message: `Failed to read configuration file: ${fsError instanceof Error ? fsError.message : "Unknown file system error"}`
}]
};
}
}
} catch (error) {
return {
config: defaultConfig,
errors: [{
path: "file",
message: `Failed to load configuration file: ${error instanceof Error ? error.message : "Unknown error"}`
}]
};
}
}
function createConfigPreset(preset) {
const base = { ...defaultConfig };
switch (preset) {
case "minimal":
return {
...base,
defaults: {
...base.defaults,
variant: "button"
},
features: {
...base.features,
dragAndDrop: false,
preview: false,
progress: false,
multipleFiles: false,
removeFiles: false,
retryFailed: false,
showFileSize: false,
showFileType: false
}
};
case "full-featured":
return {
...base,
defaults: {
...base.defaults,
variant: "multi-file",
multiple: true
},
features: {
...base.features,
dragAndDrop: true,
preview: true,
progress: true,
multipleFiles: true,
removeFiles: true,
retryFailed: true,
showFileSize: true,
showFileType: true,
chunkedUpload: true,
resumableUpload: true
}
};
case "image-only":
return {
...base,
defaults: {
...base.defaults,
variant: "image-only",
accept: "image/*"
},
validation: {
...base.validation,
allowedTypes: ["image/jpeg", "image/png", "image/gif", "image/webp"],
allowedExtensions: [".jpg", ".jpeg", ".png", ".gif", ".webp"],
validateDimensions: true,
maxWidth: 4096,
maxHeight: 4096
},
features: {
...base.features,
preview: true,
showFileSize: true,
showFileType: false
}
};
case "drag-drop":
return {
...base,
defaults: {
...base.defaults,
variant: "dropzone",
multiple: true
},
features: {
...base.features,
dragAndDrop: true,
preview: true,
progress: true,
multipleFiles: true,
removeFiles: true
}
};
default:
return base;
}
}
function getConfigurationErrors(config) {
const validation = validateConfig(config);
return validation.errors.map((error) => {
const path = error.path.replace(/\./g, " → ");
return `${path}: ${error.message}`;
});
}
function generateTypeDefinitions(config) {
return `
// Generated type definitions from configuration
export interface GeneratedFileUploadConfig {
defaults: {
variant: '${config.defaults.variant}'
size: '${config.defaults.size}'
radius: '${config.defaults.radius}'
theme: '${config.defaults.theme}'
multiple: ${config.defaults.multiple}
disabled: ${config.defaults.disabled}
accept: '${config.defaults.accept}'
maxSize: ${config.defaults.maxSize}
maxFiles: ${config.defaults.maxFiles}
}
// ... additional type definitions would be generated here
}
`;
}
function configToCSSProperties(config) {
const cssProps = {};
Object.entries(config.styling.colors).forEach(([key, value]) => {
cssProps[`--file-upload-color-${key}`] = value;
});
Object.entries(config.styling.spacing).forEach(([key, value]) => {
cssProps[`--file-upload-spacing-${key}`] = value;
});
Object.entries(config.styling.typography).forEach(([key, value]) => {
cssProps[`--file-upload-typography-${key}`] = value;
});
Object.entries(config.styling.borders).forEach(([key, value]) => {
cssProps[`--file-upload-border-${key}`] = value;
});
Object.entries(config.styling.shadows).forEach(([key, value]) => {
cssProps[`--file-upload-shadow-${key}`] = value;
});
cssProps["--file-upload-animation-duration"] = `${config.animations.duration}ms`;
cssProps["--file-upload-animation-easing"] = config.animations.easing;
return cssProps;
}
function diffConfigurations(config1, config2) {
const added = [];
const removed = [];
const changed = [];
function compareObjects(obj1, obj2, path = "") {
const keys1 = Object.keys(obj1 || {});
const keys2 = Object.keys(obj2 || {});
keys1.forEach((key) => {
const fullPath = path ? `${path}.${key}` : key;
if (!(key in obj2)) {
removed.push(fullPath);
} else if (typeof obj1[key] === "object" && typeof obj2[key] === "object") {
compareObjects(obj1[key], obj2[key], fullPath);
} else if (obj1[key] !== obj2[key]) {
changed.push(fullPath);
}
});
keys2.forEach((key) => {
const fullPath = path ? `${path}.${key}` : key;
if (!(key in obj1)) {
added.push(fullPath);
}
});
}
compareObjects(config1, config2);
return { added, removed, changed };
}
class ConfigurationMigrator {
migrations = [];
addMigration(migration) {
this.migrations.push(migration);
this.migrations.sort((a, b) => a.version.localeCompare(b.version));
return this;
}
migrate(config, fromVersion, toVersion) {
const appliedMigrations = [];
let currentConfig = { ...config };
for (const migration of this.migrations) {
if (migration.version > fromVersion && migration.version <= toVersion) {
currentConfig = migration.migrate(currentConfig);
appliedMigrations.push(migration.version);
}
}
return { config: currentConfig, appliedMigrations };
}
getAvailableMigrations() {
return [...this.migrations];
}
}
new ConfigurationMigrator().addMigration({
version: "1.1.0",
description: "Add accessibility configuration",
migrate: (config) => ({
...config,
accessibility: {
announceFileSelection: true,
announceProgress: true,
announceErrors: true,
keyboardNavigation: true,
focusManagement: true,
...config.accessibility
}
})
}).addMigration({
version: "1.2.0",
description: "Add animation configuration",
migrate: (config) => ({
...config,
animations: {
enabled: true,
duration: 200,
easing: "ease-in-out",
...config.animations
}
})
});
export {
ConfigurationMigrator,
configToCSSProperties,
createConfigPreset,
deepMerge,
diffConfigurations,
generateTypeDefinitions,
getConfigurationErrors,
loadConfigFromFile,
loadConfiguration,
mergeConfigurations
};
//# sourceMappingURL=utils.js.map