UNPKG

@muban/muban

Version:

Writing components for server-rendered HTML

144 lines (143 loc) 6.8 kB
import { isUndefined } from 'isntnt'; import typedObjectEntries from '../type-utils/typedObjectEntries'; function convertToInternalPropInfo(element, propName, propDefinition, refs) { var _a; const { type, default: defaultValue, isOptional, validator } = propDefinition; const getPropInfo = (sourceOption) => { var _a; let target; // resolve refs into elements if (sourceOption === null || sourceOption === void 0 ? void 0 : sourceOption.target) { const targetRef = sourceOption.target ? refs[sourceOption.target] : undefined; if (!targetRef) { // eslint-disable-next-line no-console console.error(`Property "${propName}" would like to use target "${sourceOption === null || sourceOption === void 0 ? void 0 : sourceOption.target}", but is not found in available refs.`, Object.keys(refs)); } else { // TODO: support collections... but how? if (targetRef.type === 'element' && targetRef.element) { target = targetRef.element; } if (targetRef.type === 'component' && ((_a = targetRef.component) === null || _a === void 0 ? void 0 : _a.element)) { target = targetRef.component.element; } if (targetRef.type === 'collection' || targetRef.type === 'componentCollection') { // eslint-disable-next-line no-console console.warn(`Property "${propName}" would like to use the collection "${sourceOption === null || sourceOption === void 0 ? void 0 : sourceOption.target}" as target, using collections as target is not supported yet, please use a single element/component as target.`); } } } else { target = element; } return { name: propName, type, default: defaultValue, isOptional, validator, source: { name: (sourceOption && 'name' in sourceOption && sourceOption.name) || propName, target, type: sourceOption === null || sourceOption === void 0 ? void 0 : sourceOption.type, options: (sourceOption && 'options' in sourceOption && sourceOption.options) || undefined, }, }; }; return propDefinition.sourceOptions instanceof Array ? (_a = propDefinition.sourceOptions) === null || _a === void 0 ? void 0 : _a.map(getPropInfo) : [getPropInfo(propDefinition.sourceOptions)]; } function errorUnlessOptional(propInfo, message, ...logParameters) { if (!propInfo.isOptional) { // eslint-disable-next-line no-console console.error(message, ...logParameters); throw new Error('Exit'); } return undefined; } export function getValueFromMultipleSources(propInfo, sources) { for (const info of propInfo) { try { return getValueFromSource(info, sources); } catch (_a) { } } } export function getValueFromSource(propInfo, sources) { // if target cannot be found, just return undefined if (!propInfo.source.target) { return errorUnlessOptional(propInfo, `Property "${propInfo.name}" is marked as required, but the source target was not found.`); } // if source type is explicitly provided if (propInfo.source.type) { const source = sources.find((s) => propInfo.source.type === s.sourceName); if (!(source === null || source === void 0 ? void 0 : source.hasProp(propInfo))) { return errorUnlessOptional(propInfo, `Property "${propInfo.name}" is not available in source "${propInfo.source.type}".`); } return source === null || source === void 0 ? void 0 : source.getProp(propInfo); } // otherwise, use the default sources const defaultSources = sources.filter((s) => ['data', 'json', 'css'].includes(s.sourceName)); // find available sources from those that are registered const availableSources = defaultSources.filter((s) => (propInfo.source.type === undefined || propInfo.source.type === s.sourceName) && s.hasProp(propInfo)); if (availableSources.length === 0) { return errorUnlessOptional(propInfo, `Property "${propInfo.name}" is marked as required, but not found at target.`, propInfo.source.target); } if (propInfo.type === Boolean) { return availableSources .map((source) => source.getProp(propInfo)) .reduce((accumulator, value) => { if (accumulator === true || value === true) return true; if (value !== undefined) return value; return accumulator; }, undefined); } // if more than 1 sources, pick the first one (except for booleans) const usedSource = availableSources[0]; if (availableSources.length > 1) { // eslint-disable-next-line no-console console.warn(`Property "${propInfo.name}" is defined in more than one property source: ${availableSources .map((s) => s.sourceName) .join(', ')}. We'll use the first from the list: "${usedSource.sourceName}"`); } return usedSource.getProp(propInfo); } export function getComponentProps(props, element, propertySources, refs) { var _a; const sources = propertySources.map((s) => s(element)); const p = (_a = typedObjectEntries(props !== null && props !== void 0 ? props : {}).reduce((accumulator, [propName, propType]) => { const propInfo = convertToInternalPropInfo(element, propName, propType, refs); // TODO: ignore function prop types for some sources? let extractedValue; try { extractedValue = getValueFromMultipleSources(propInfo, sources); } catch (_a) { return accumulator; } // set default value if (!isUndefined(propType.default) && isUndefined(extractedValue)) { extractedValue = propType.default; } // validate value if (propType.validator && !isUndefined(extractedValue)) { if (!propType.validator(extractedValue)) { // TODO: should we indeed throw errors here, or resolve the value to undefined? throw new Error(`Validation Error: This prop value ("${extractedValue}") is not valid for:`); } } accumulator[propName] = extractedValue; return accumulator; }, {})) !== null && _a !== void 0 ? _a : {}; return p; } // const sources = [ // createDataAttributePropertySource(), // createJsonScriptPropertySource(), // createReactivePropertySource(), // ]; // getComponentProps({}, element, sources);