UNPKG

@browser.style/data-entry

Version:

Dynamic data entry form component with JSON schema validation and internationalization support

441 lines (392 loc) 12.2 kB
import { dynamicFunctions } from "./dynamic.js"; /* === attrs === */ export function attrs( attributes, path = '', additionalAttributes = [], attributesToRemove = [], attributesToInclude = [] ) { const merged = {}; // Merge all attributes, respecting attributesToRemove and attributesToInclude attributes.concat(additionalAttributes).forEach(attr => { Object.entries(attr).forEach(([key, value]) => { // Include the attribute if it's in the include list (if provided) and not in the remove list const shouldInclude = (attributesToInclude.length === 0 || attributesToInclude.includes(key)) && !attributesToRemove.includes(key); if (shouldInclude) { if (merged[key]) { merged[key] = `${merged[key]} ${value}`.trim(); } else { merged[key] = value; } } }); }); // If a path is provided, set the name attribute to it if (path) { merged['name'] = path; } // Convert the merged attributes object into a string of HTML attributes return Object.entries(merged) .map(([key, value]) => { // Handle the case where the key and value are both "name" if (key === 'name' && value === 'name') { return `${key}="${value}"`; } return key === value ? `${key}` : `${key}="${value}"`; }).join(' '); } /* === buttonAttrs === */ export function buttonAttrs(entry) { const commonAttributes = Object.keys(entry) .filter(key => key !== 'label' && key !== 'class') .map(key => `data-${key}="${entry[key]}"`) .join(' '); const classAttribute = entry.class ? ` class="${entry.class}"` : ''; return `${commonAttributes}${classAttribute}`; } /* === convertValue === */ export function convertValue(value, dataType, inputType, checked) { switch (dataType) { case 'number': return Number(value); case 'boolean': if (inputType === 'checkbox') { return checked; } return value === 'true' || value === true; case 'object': try { return JSON.parse(value); } catch { return value; } default: return value; } } /* === deepMerge === */ export function deepMerge(target, source, key = null) { if (Array.isArray(target) && Array.isArray(source)) { // If both are arrays, merge them based on a unique identifier (key) const mergedArray = new Map(target.map(item => [getKey(item, key), item])); source.forEach(item => { const itemKey = getKey(item, key); if (mergedArray.has(itemKey)) { mergedArray.set(itemKey, deepMerge(mergedArray.get(itemKey), item, key)); } else { mergedArray.set(itemKey, item); } }); return Array.from(mergedArray.values()); } else if (isObject(target) && isObject(source)) { // If both are objects, merge them recursively const result = { ...target }; for (const key in source) { if (isObject(source[key]) && key in target) { result[key] = deepMerge(target[key], source[key], key); } else { result[key] = source[key]; } } return result; } return source; } /* === fetchOptions === */ export function fetchOptions(config, instance) { const optionsKey = config?.render?.options; let options = []; if (Array.isArray(optionsKey)) { options = optionsKey; } else if (typeof optionsKey === 'string') { // First check instance.lookup if (instance.lookup && Array.isArray(instance.lookup[optionsKey])) { options = instance.lookup[optionsKey]; } // Then check instance.data else if (instance.data && getObjectByPath(instance.data, optionsKey)) { const dataOptions = getObjectByPath(instance.data, optionsKey); // Handle both array and single object cases if (Array.isArray(dataOptions)) { options = dataOptions; } else if (typeof dataOptions === 'object') { // Convert single object to array with one item options = [dataOptions]; } } // Finally check localStorage else { const storedOptions = localStorage.getItem(optionsKey); if (storedOptions) { try { const parsed = JSON.parse(storedOptions); options = Array.isArray(parsed) ? parsed : [parsed]; } catch { options = []; } } } } return options; } /* === getKey === */ export function getKey(item, key) { if (key === null) { return JSON.stringify(item); } else if (Array.isArray(key)) { return key.map(k => item[k]).join('_'); } return item[key]; } /* === getObjectByPath === */ export function getObjectByPath(obj, path) { return path.split('.').reduce((acc, key) => { if (acc === null || acc === undefined) { return undefined; } const match = key.match(/([^\[]+)\[?(\d*)\]?/); const prop = match[1]; const idx = match[2]; if (idx === '') { return acc[prop]; } if (idx !== undefined) { return acc[prop] && Array.isArray(acc[prop]) ? acc[prop][idx] : undefined; } return acc[prop]; }, obj); } /* === getValueWithFallback === */ export function getValueWithFallback(data, key, config) { const mainValue = data[key]; if (mainValue !== undefined) { return mainValue; } const fallbackData = config?.render?.data; return fallbackData?.[key]; } /* === isEmpty === */ export function isEmpty(obj) { if (obj === null || obj === undefined) { return true; } return typeof obj === 'object' && Object.keys(obj).length === 0; } /* === isObject === */ export function isObject(item) { return item && typeof item === 'object' && !Array.isArray(item); } /* === itemExists === */ export function itemExists(array, newItem, properties) { return array.some(item => properties.every(prop => item[prop] !== undefined && newItem[prop] !== undefined && item[prop] === newItem[prop] ) ); } /* === mapObject === */ export function mapObject(sourceObj, mapping, basePath = '') { const result = {}; Object.entries(mapping).forEach(([field, config]) => { const fullPath = basePath ? `${basePath}.${field}` : field; let mappedValue; // Handle string mapping (direct path) if (typeof config === 'string') { mappedValue = getObjectByPath(sourceObj, config); } // Handle object mapping configuration else if (typeof config === 'object') { if ('value' in config) { mappedValue = config.value; } else if ('path' in config) { // Handle template strings in path if (/\$\{.+?\}/.test(config.path)) { mappedValue = config.path.replace(/\$\{(.+?)\}/g, (_match, p1) => { const value = getObjectByPath(sourceObj, p1.trim()); return value !== undefined ? value : ''; }); } else { mappedValue = getObjectByPath(sourceObj, config.path); } } // Apply type conversion if specified if (config.type) { try { switch (config.type) { case 'number': mappedValue = Number(mappedValue); break; case 'boolean': mappedValue = Boolean(mappedValue); break; case 'date': mappedValue = new Date(mappedValue).toISOString(); break; case 'string': default: mappedValue = String(mappedValue); } } catch (error) { console.error(`Type conversion failed for ${field}:`, error); } } } setObjectByPath(result, fullPath, mappedValue); }); return result; } /* === processAttributes === */ export function processAttributes(attributes, data, instance) { try { return attributes.map(attr => { const processed = {}; Object.entries(attr).forEach(([key, value]) => { processed[key] = resolveTemplateString(value, data, instance.lang, instance.i18n, instance.constants); }); return processed; }); } catch (error) { console.warn('Error processing attributes:', error); return attributes; } } /* === processConfigValue === */ function processConfigValue(value, data, instance) { if (typeof value === 'string') { return resolveTemplateString(value, data, instance.lang, instance.i18n, instance.constants); } if (Array.isArray(value)) { return value.map(item => typeof item === 'string' ? resolveTemplateString(item, data, instance.lang, instance.i18n, instance.constants) : item ); } if (typeof value === 'object' && value !== null) { return Object.entries(value).reduce((acc, [k, v]) => { acc[k] = typeof v === 'string' ? resolveTemplateString(v, data, instance.lang, instance.i18n, instance.constants) : v; return acc; }, {}); } return value; } /* === processRenderConfig === */ export function processRenderConfig(config, data, instance) { if (!config?.render) return config; try { const processed = { ...config }; Object.entries(config.render).forEach(([key, value]) => { processed.render[key] = processConfigValue(value, data, instance); }); return processed; } catch (error) { console.warn('Error processing render config:', error); return config; } } /* === resolveTemplateString === */ export function resolveTemplateString(template, data, instance = {}, fallback = '') { try { const { lang = 'en', i18n = {}, constants = {} } = instance; if (!template) return ''; if (typeof template !== 'string') return template; if (!template.includes('${')) return template; return template.replace(/\$\{([^}]+)\}/g, (_, key) => { const trimmedKey = key.trim(); // Handle dynamic functions if (trimmedKey.startsWith('d:')) { const parts = trimmedKey.slice(2).trim().split(/\s+/); const fn = dynamicFunctions[parts[0]]; if (fn) { const params = parts.slice(1).map(param => { // Check for global references [prefix:name] const globalMatch = param.match(/\[(.*?)\]/); if (globalMatch) { const [prefix, name] = globalMatch[1].split(':'); switch(prefix) { case 'o': return instance.lookup?.[name] || []; case 'v': return constants[name]; default: return param; } } return param.startsWith('${') && param.endsWith('}') ? getObjectByPath(data, param.slice(2, -1)) : getObjectByPath(data, param) ?? param; }); return fn(...params); } } if (trimmedKey.startsWith('t:')) { return t(trimmedKey.slice(2).trim(), lang, i18n) || ''; } if (trimmedKey.startsWith('v:')) { const value = constants[trimmedKey.slice(2).trim()]; return value !== undefined ? value : ''; } return getObjectByPath(data, trimmedKey) || ''; }); } catch (error) { console.warn('Template resolution failed:', error); return fallback || template; } } /* === safeRender === */ export function safeRender(renderMethod, params) { try { return renderMethod(params); } catch { return ''; } } /* === setObjectByPath === */ export function setObjectByPath(obj, path, value) { path.split('.').reduce((acc, key, index, array) => { const match = key.match(/([^\[]+)\[?(\d*)\]?/); const prop = match[1]; const idx = match[2]; if (!acc[prop]) { acc[prop] = idx ? [] : {}; } if (idx) { if (index === array.length - 1) { acc[prop][idx] = value; } else { acc[prop][idx] = acc[prop][idx] || {}; } return acc[prop][idx]; } if (index === array.length - 1) { acc[prop] = value; } return acc[prop]; }, obj); } /* === t === */ export function t(key, lang, i18n) { return i18n[lang]?.[key] || key; } /* === toCamelCase === */ export function toCamelCase(str) { return str .toLowerCase() .split('-') .map((word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)) .join(''); } /* === toPascalCase === */ export function toPascalCase(str) { return str .split('-') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(''); } /* === uuid === */ export function uuid() { return crypto.getRandomValues(new Uint32Array(1))[0] || Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); }