mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-19 00:50:37 +03:00
introduce plugin system and split option validation into a plugin
This commit is contained in:
+148
-162
@@ -102,7 +102,7 @@ function each(source, callback) {
|
||||
|
||||
return source;
|
||||
}
|
||||
const indexOf = (arr, item, fromIndex) => isArray(arr) ? arr.indexOf(item, fromIndex) : -1;
|
||||
const indexOf = (arr, item, fromIndex) => arr.indexOf(item, fromIndex);
|
||||
const push = (array, items, arrayIsSingleItem) => {
|
||||
!arrayIsSingleItem && !isString(items) && isArrayLike(items) ? Array.prototype.push.apply(array, items) : array.push(items);
|
||||
return array;
|
||||
@@ -606,6 +606,28 @@ const absoluteCoordinates = elm => {
|
||||
} : zeroObj;
|
||||
};
|
||||
|
||||
const classNameEnvironment = 'os-environment';
|
||||
const classNameEnvironmentFlexboxGlue = `${classNameEnvironment}-flexbox-glue`;
|
||||
const classNameEnvironmentFlexboxGlueMax = `${classNameEnvironmentFlexboxGlue}-max`;
|
||||
const classNameHost = 'os-host';
|
||||
const classNamePadding = 'os-padding';
|
||||
const classNameViewport = 'os-viewport';
|
||||
const classNameViewportArrange = `${classNameViewport}-arrange`;
|
||||
const classNameContent = 'os-content';
|
||||
const classNameViewportScrollbarStyling = `${classNameViewport}-scrollbar-styled`;
|
||||
const classNameSizeObserver = 'os-size-observer';
|
||||
const classNameSizeObserverAppear = `${classNameSizeObserver}-appear`;
|
||||
const classNameSizeObserverListener = `${classNameSizeObserver}-listener`;
|
||||
const classNameSizeObserverListenerScroll = `${classNameSizeObserverListener}-scroll`;
|
||||
const classNameSizeObserverListenerItem = `${classNameSizeObserverListener}-item`;
|
||||
const classNameSizeObserverListenerItemFinal = `${classNameSizeObserverListenerItem}-final`;
|
||||
const classNameTrinsicObserver = 'os-trinsic-observer';
|
||||
const classNameScrollbar = 'os-scrollbar';
|
||||
const classNameScrollbarHorizontal = `${classNameScrollbar}-horizontal`;
|
||||
const classNameScrollbarVertical = `${classNameScrollbar}-vertical`;
|
||||
const classNameScrollbarTrack = 'os-scrollbar-track';
|
||||
const classNameScrollbarHandle = 'os-scrollbar-handle';
|
||||
|
||||
function getDefaultExportFromCjs (x) {
|
||||
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
||||
}
|
||||
@@ -635,174 +657,74 @@ var _extends$1 = {exports: {}};
|
||||
|
||||
const _extends = getDefaultExportFromCjs(_extends$1.exports);
|
||||
|
||||
const {
|
||||
stringify
|
||||
} = JSON;
|
||||
const templateTypePrefixSuffix = ['__TPL_', '_TYPE__'];
|
||||
const optionsTemplateTypes = ['boolean', 'number', 'string', 'array', 'object', 'function', 'null'].reduce((result, item) => {
|
||||
result[item] = templateTypePrefixSuffix[0] + item + templateTypePrefixSuffix[1];
|
||||
return result;
|
||||
}, {});
|
||||
const stringify = value => JSON.stringify(value, (_, val) => {
|
||||
if (isFunction(val)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const validateRecursive = (options, template, optionsDiff, doWriteErrors, propPath) => {
|
||||
const validatedOptions = {};
|
||||
return val;
|
||||
});
|
||||
|
||||
const optionsCopy = _extends({}, options);
|
||||
|
||||
const props = keys(template).filter(prop => hasOwnProperty(options, prop));
|
||||
each(props, prop => {
|
||||
const optionsDiffValue = isUndefined(optionsDiff[prop]) ? {} : optionsDiff[prop];
|
||||
const optionsValue = options[prop];
|
||||
const templateValue = template[prop];
|
||||
const templateIsComplex = isPlainObject(templateValue);
|
||||
const propPrefix = propPath ? `${propPath}.` : '';
|
||||
|
||||
if (templateIsComplex && isPlainObject(optionsValue)) {
|
||||
const validatedResult = validateRecursive(optionsValue, templateValue, optionsDiffValue, doWriteErrors, propPrefix + prop);
|
||||
validatedOptions[prop] = validatedResult._validated;
|
||||
optionsCopy[prop] = validatedResult._foreign;
|
||||
each([optionsCopy, validatedOptions], value => {
|
||||
if (isEmptyObject(value[prop])) {
|
||||
delete value[prop];
|
||||
}
|
||||
});
|
||||
} else if (!templateIsComplex) {
|
||||
let isValid = false;
|
||||
const errorEnumStrings = [];
|
||||
const errorPossibleTypes = [];
|
||||
const optionsValueType = type(optionsValue);
|
||||
const templateValueArr = !isArray(templateValue) ? [templateValue] : templateValue;
|
||||
each(templateValueArr, currTemplateType => {
|
||||
let typeString;
|
||||
each(optionsTemplateTypes, (value, key) => {
|
||||
if (value === currTemplateType) {
|
||||
typeString = key;
|
||||
}
|
||||
});
|
||||
const isEnumString = isUndefined(typeString);
|
||||
|
||||
if (isEnumString && isString(optionsValue)) {
|
||||
const enumStringSplit = currTemplateType.split(' ');
|
||||
isValid = !!enumStringSplit.find(possibility => possibility === optionsValue);
|
||||
push(errorEnumStrings, enumStringSplit);
|
||||
} else {
|
||||
isValid = optionsTemplateTypes[optionsValueType] === currTemplateType;
|
||||
}
|
||||
|
||||
push(errorPossibleTypes, isEnumString ? optionsTemplateTypes.string : typeString);
|
||||
return !isValid;
|
||||
});
|
||||
|
||||
if (isValid) {
|
||||
const isPrimitiveArr = isArray(optionsValue) && !optionsValue.some(val => !isNumber(val) && !isString(val) && !isBoolean(val));
|
||||
const doStringifyComparison = isPrimitiveArr || isPlainObject(optionsValue);
|
||||
|
||||
if (doStringifyComparison ? stringify(optionsValue) !== stringify(optionsDiffValue) : optionsValue !== optionsDiffValue) {
|
||||
validatedOptions[prop] = optionsValue;
|
||||
}
|
||||
} else if (doWriteErrors) {
|
||||
console.warn(`${`The option "${propPrefix}${prop}" wasn't set, because it doesn't accept the type [ ${optionsValueType.toUpperCase()} ] with the value of "${optionsValue}".\r\n` + `Accepted types are: [ ${errorPossibleTypes.join(', ').toUpperCase()} ].\r\n`}${errorEnumStrings.length > 0 ? `\r\nValid strings are: [ ${errorEnumStrings.join(', ')} ].` : ''}`);
|
||||
}
|
||||
|
||||
delete optionsCopy[prop];
|
||||
}
|
||||
});
|
||||
return {
|
||||
_foreign: optionsCopy,
|
||||
_validated: validatedOptions
|
||||
};
|
||||
};
|
||||
|
||||
const validateOptions = (options, template, optionsDiff, doWriteErrors) => validateRecursive(options, template, optionsDiff || {}, doWriteErrors || false);
|
||||
|
||||
const transformOptions = optionsWithOptionsTemplate => {
|
||||
const result = {
|
||||
_template: {},
|
||||
_options: {}
|
||||
};
|
||||
each(keys(optionsWithOptionsTemplate), key => {
|
||||
const val = optionsWithOptionsTemplate[key];
|
||||
|
||||
if (isArray(val)) {
|
||||
result._template[key] = val[1];
|
||||
result._options[key] = val[0];
|
||||
} else {
|
||||
const tmpResult = transformOptions(val);
|
||||
result._template[key] = tmpResult._template;
|
||||
result._options[key] = tmpResult._options;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
const classNameEnvironment = 'os-environment';
|
||||
const classNameEnvironmentFlexboxGlue = `${classNameEnvironment}-flexbox-glue`;
|
||||
const classNameEnvironmentFlexboxGlueMax = `${classNameEnvironmentFlexboxGlue}-max`;
|
||||
const classNameHost = 'os-host';
|
||||
const classNamePadding = 'os-padding';
|
||||
const classNameViewport = 'os-viewport';
|
||||
const classNameViewportArrange = `${classNameViewport}-arrange`;
|
||||
const classNameContent = 'os-content';
|
||||
const classNameViewportScrollbarStyling = `${classNameViewport}-scrollbar-styled`;
|
||||
const classNameSizeObserver = 'os-size-observer';
|
||||
const classNameSizeObserverAppear = `${classNameSizeObserver}-appear`;
|
||||
const classNameSizeObserverListener = `${classNameSizeObserver}-listener`;
|
||||
const classNameSizeObserverListenerScroll = `${classNameSizeObserverListener}-scroll`;
|
||||
const classNameSizeObserverListenerItem = `${classNameSizeObserverListener}-item`;
|
||||
const classNameSizeObserverListenerItemFinal = `${classNameSizeObserverListenerItem}-final`;
|
||||
const classNameTrinsicObserver = 'os-trinsic-observer';
|
||||
const classNameScrollbar = 'os-scrollbar';
|
||||
const classNameScrollbarHorizontal = `${classNameScrollbar}-horizontal`;
|
||||
const classNameScrollbarVertical = `${classNameScrollbar}-vertical`;
|
||||
const classNameScrollbarTrack = 'os-scrollbar-track';
|
||||
const classNameScrollbarHandle = 'os-scrollbar-handle';
|
||||
|
||||
const numberAllowedValues = optionsTemplateTypes.number;
|
||||
const arrayNullValues = [optionsTemplateTypes.array, optionsTemplateTypes.null];
|
||||
const stringArrayNullAllowedValues = [optionsTemplateTypes.string, optionsTemplateTypes.array, optionsTemplateTypes.null];
|
||||
const booleanTrueTemplate = [true, optionsTemplateTypes.boolean];
|
||||
const booleanFalseTemplate = [false, optionsTemplateTypes.boolean];
|
||||
const resizeAllowedValues = 'none both horizontal vertical';
|
||||
const overflowAllowedValues = 'hidden scroll visible visible-hidden';
|
||||
const scrollbarsVisibilityAllowedValues = 'visible hidden auto';
|
||||
const scrollbarsAutoHideAllowedValues = 'never scroll leavemove';
|
||||
const defaultOptionsWithTemplate = {
|
||||
resize: ['none', resizeAllowedValues],
|
||||
paddingAbsolute: booleanFalseTemplate,
|
||||
const defaultOptions = {
|
||||
resize: 'none',
|
||||
paddingAbsolute: false,
|
||||
updating: {
|
||||
elementEvents: [[['img', 'load']], arrayNullValues],
|
||||
attributes: [null, arrayNullValues],
|
||||
debounce: [[0, 33], [optionsTemplateTypes.number, optionsTemplateTypes.array, optionsTemplateTypes.null]]
|
||||
elementEvents: [['img', 'load']],
|
||||
attributes: null,
|
||||
debounce: [0, 33]
|
||||
},
|
||||
overflow: {
|
||||
x: ['scroll', overflowAllowedValues],
|
||||
y: ['scroll', overflowAllowedValues]
|
||||
x: 'scroll',
|
||||
y: 'scroll'
|
||||
},
|
||||
scrollbars: {
|
||||
visibility: ['auto', scrollbarsVisibilityAllowedValues],
|
||||
autoHide: ['never', scrollbarsAutoHideAllowedValues],
|
||||
autoHideDelay: [800, numberAllowedValues],
|
||||
dragScroll: booleanTrueTemplate,
|
||||
clickScroll: booleanFalseTemplate,
|
||||
touch: booleanTrueTemplate
|
||||
visibility: 'auto',
|
||||
autoHide: 'never',
|
||||
autoHideDelay: 800,
|
||||
dragScroll: true,
|
||||
clickScroll: false,
|
||||
touch: true
|
||||
},
|
||||
textarea: {
|
||||
dynWidth: booleanFalseTemplate,
|
||||
dynHeight: booleanFalseTemplate,
|
||||
inheritedAttrs: [['style', 'class'], stringArrayNullAllowedValues]
|
||||
dynWidth: false,
|
||||
dynHeight: false,
|
||||
inheritedAttrs: ['style', 'class']
|
||||
},
|
||||
nativeScrollbarsOverlaid: {
|
||||
show: booleanFalseTemplate,
|
||||
initialize: booleanFalseTemplate
|
||||
show: false,
|
||||
initialize: false
|
||||
},
|
||||
callbacks: {
|
||||
onUpdated: [null, [optionsTemplateTypes.function, optionsTemplateTypes.null]]
|
||||
onUpdated: null
|
||||
}
|
||||
};
|
||||
const {
|
||||
_template: optionsTemplate,
|
||||
_options: defaultOptions
|
||||
} = transformOptions(defaultOptionsWithTemplate);
|
||||
const getOptionsDiff = (currOptions, newOptions) => {
|
||||
const diff = {};
|
||||
const optionsKeys = keys(newOptions).concat(keys(currOptions));
|
||||
each(optionsKeys, optionKey => {
|
||||
const currOptionValue = currOptions[optionKey];
|
||||
const newOptionValue = newOptions[optionKey];
|
||||
|
||||
if (isObject(currOptionValue) && isObject(newOptionValue)) {
|
||||
assignDeep(diff[optionKey] = {}, getOptionsDiff(currOptionValue, newOptionValue));
|
||||
} else if (hasOwnProperty(newOptions, optionKey) && newOptionValue !== currOptionValue) {
|
||||
let isDiff = true;
|
||||
|
||||
if (isArray(currOptionValue) || isArray(newOptionValue)) {
|
||||
try {
|
||||
if (stringify(currOptionValue) === stringify(newOptionValue)) {
|
||||
isDiff = false;
|
||||
}
|
||||
} catch (_unused) {}
|
||||
}
|
||||
|
||||
if (isDiff) {
|
||||
diff[optionKey] = newOptionValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
return diff;
|
||||
};
|
||||
|
||||
let environmentInstance;
|
||||
const {
|
||||
@@ -2408,25 +2330,81 @@ const createLifecycleHub = (options, structureSetup, scrollbarsSetup) => {
|
||||
};
|
||||
};
|
||||
|
||||
const OverlayScrollbars = (target, options, extensions) => {
|
||||
const pluginRegistry = {};
|
||||
const getPlugins = () => assignDeep({}, pluginRegistry);
|
||||
const addPlugin = addedPlugin => each(isArray(addedPlugin) ? addedPlugin : [addedPlugin], plugin => {
|
||||
pluginRegistry[plugin[0]] = plugin[1];
|
||||
});
|
||||
|
||||
const templateTypePrefixSuffix = ['__TPL_', '_TYPE__'];
|
||||
const optionsTemplateTypes = ['boolean', 'number', 'string', 'array', 'object', 'function', 'null'].reduce((result, item) => {
|
||||
result[item] = templateTypePrefixSuffix[0] + item + templateTypePrefixSuffix[1];
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
const numberAllowedValues = optionsTemplateTypes.number;
|
||||
const booleanAllowedValues = optionsTemplateTypes.boolean;
|
||||
const arrayNullValues = [optionsTemplateTypes.array, optionsTemplateTypes.null];
|
||||
const stringArrayNullAllowedValues = [optionsTemplateTypes.string, optionsTemplateTypes.array, optionsTemplateTypes.null];
|
||||
const resizeAllowedValues = 'none both horizontal vertical';
|
||||
const overflowAllowedValues = 'hidden scroll visible visible-hidden';
|
||||
const scrollbarsVisibilityAllowedValues = 'visible hidden auto';
|
||||
const scrollbarsAutoHideAllowedValues = 'never scroll leavemove';
|
||||
({
|
||||
resize: resizeAllowedValues,
|
||||
paddingAbsolute: booleanAllowedValues,
|
||||
updating: {
|
||||
elementEvents: arrayNullValues,
|
||||
attributes: arrayNullValues,
|
||||
debounce: [optionsTemplateTypes.number, optionsTemplateTypes.array, optionsTemplateTypes.null]
|
||||
},
|
||||
overflow: {
|
||||
x: overflowAllowedValues,
|
||||
y: overflowAllowedValues
|
||||
},
|
||||
scrollbars: {
|
||||
visibility: scrollbarsVisibilityAllowedValues,
|
||||
autoHide: scrollbarsAutoHideAllowedValues,
|
||||
autoHideDelay: numberAllowedValues,
|
||||
dragScroll: booleanAllowedValues,
|
||||
clickScroll: booleanAllowedValues,
|
||||
touch: booleanAllowedValues
|
||||
},
|
||||
textarea: {
|
||||
dynWidth: booleanAllowedValues,
|
||||
dynHeight: booleanAllowedValues,
|
||||
inheritedAttrs: stringArrayNullAllowedValues
|
||||
},
|
||||
nativeScrollbarsOverlaid: {
|
||||
show: booleanAllowedValues,
|
||||
initialize: booleanAllowedValues
|
||||
},
|
||||
callbacks: {
|
||||
onUpdated: [optionsTemplateTypes.function, optionsTemplateTypes.null]
|
||||
}
|
||||
});
|
||||
const optionsValidationPluginName = '__osOptionsValidationPlugin';
|
||||
|
||||
const OverlayScrollbars = (target, options) => {
|
||||
const {
|
||||
_getDefaultOptions
|
||||
} = getEnvironment();
|
||||
const currentOptions = assignDeep({}, _getDefaultOptions(), validateOptions(options || {}, optionsTemplate, null, true)._validated);
|
||||
const plugins = getPlugins();
|
||||
const optionsValidationPlugin = plugins[optionsValidationPluginName];
|
||||
const validateOptions = optionsValidationPlugin && optionsValidationPlugin._;
|
||||
const currentOptions = assignDeep({}, _getDefaultOptions(), validateOptions ? validateOptions(options || {}, true) : options);
|
||||
const structureSetup = createStructureSetup(target);
|
||||
const scrollbarsSetup = createScrollbarsSetup(target, structureSetup);
|
||||
const lifecycleHub = createLifecycleHub(currentOptions, structureSetup, scrollbarsSetup);
|
||||
const instance = {
|
||||
options(newOptions) {
|
||||
if (newOptions) {
|
||||
const {
|
||||
_validated: _changedOptions
|
||||
} = validateOptions(newOptions, optionsTemplate, currentOptions, true);
|
||||
const changedOptions = getOptionsDiff(currentOptions, validateOptions ? validateOptions(newOptions, true) : newOptions);
|
||||
|
||||
if (!isEmptyObject(_changedOptions)) {
|
||||
assignDeep(currentOptions, _changedOptions);
|
||||
if (!isEmptyObject(changedOptions)) {
|
||||
assignDeep(currentOptions, changedOptions);
|
||||
|
||||
lifecycleHub._update(_changedOptions);
|
||||
lifecycleHub._update(changedOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2441,9 +2419,17 @@ const OverlayScrollbars = (target, options, extensions) => {
|
||||
|
||||
destroy: () => lifecycleHub._destroy()
|
||||
};
|
||||
each(keys(plugins), pluginName => {
|
||||
const pluginInstance = plugins[pluginName];
|
||||
|
||||
if (isFunction(pluginInstance)) {
|
||||
pluginInstance(OverlayScrollbars, instance);
|
||||
}
|
||||
});
|
||||
instance.update(true);
|
||||
return instance;
|
||||
};
|
||||
OverlayScrollbars.extend = addPlugin;
|
||||
|
||||
export { OverlayScrollbars as default };
|
||||
//# sourceMappingURL=overlayscrollbars.esm.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+151
-164
@@ -114,7 +114,7 @@
|
||||
return source;
|
||||
}
|
||||
var indexOf = function indexOf(arr, item, fromIndex) {
|
||||
return isArray(arr) ? arr.indexOf(item, fromIndex) : -1;
|
||||
return arr.indexOf(item, fromIndex);
|
||||
};
|
||||
var push = function push(array, items, arrayIsSingleItem) {
|
||||
!arrayIsSingleItem && !isString(items) && isArrayLike(items) ? Array.prototype.push.apply(array, items) : array.push(items);
|
||||
@@ -680,6 +680,28 @@
|
||||
} : zeroObj;
|
||||
};
|
||||
|
||||
var classNameEnvironment = 'os-environment';
|
||||
var classNameEnvironmentFlexboxGlue = classNameEnvironment + "-flexbox-glue";
|
||||
var classNameEnvironmentFlexboxGlueMax = classNameEnvironmentFlexboxGlue + "-max";
|
||||
var classNameHost = 'os-host';
|
||||
var classNamePadding = 'os-padding';
|
||||
var classNameViewport = 'os-viewport';
|
||||
var classNameViewportArrange = classNameViewport + "-arrange";
|
||||
var classNameContent = 'os-content';
|
||||
var classNameViewportScrollbarStyling = classNameViewport + "-scrollbar-styled";
|
||||
var classNameSizeObserver = 'os-size-observer';
|
||||
var classNameSizeObserverAppear = classNameSizeObserver + "-appear";
|
||||
var classNameSizeObserverListener = classNameSizeObserver + "-listener";
|
||||
var classNameSizeObserverListenerScroll = classNameSizeObserverListener + "-scroll";
|
||||
var classNameSizeObserverListenerItem = classNameSizeObserverListener + "-item";
|
||||
var classNameSizeObserverListenerItemFinal = classNameSizeObserverListenerItem + "-final";
|
||||
var classNameTrinsicObserver = 'os-trinsic-observer';
|
||||
var classNameScrollbar = 'os-scrollbar';
|
||||
var classNameScrollbarHorizontal = classNameScrollbar + "-horizontal";
|
||||
var classNameScrollbarVertical = classNameScrollbar + "-vertical";
|
||||
var classNameScrollbarTrack = 'os-scrollbar-track';
|
||||
var classNameScrollbarHandle = 'os-scrollbar-handle';
|
||||
|
||||
function getDefaultExportFromCjs (x) {
|
||||
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
||||
}
|
||||
@@ -709,180 +731,76 @@
|
||||
|
||||
var _extends = getDefaultExportFromCjs(_extends$1.exports);
|
||||
|
||||
var stringify = JSON.stringify;
|
||||
var templateTypePrefixSuffix = ['__TPL_', '_TYPE__'];
|
||||
var optionsTemplateTypes = ['boolean', 'number', 'string', 'array', 'object', 'function', 'null'].reduce(function (result, item) {
|
||||
result[item] = templateTypePrefixSuffix[0] + item + templateTypePrefixSuffix[1];
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
var validateRecursive = function validateRecursive(options, template, optionsDiff, doWriteErrors, propPath) {
|
||||
var validatedOptions = {};
|
||||
|
||||
var optionsCopy = _extends({}, options);
|
||||
|
||||
var props = keys(template).filter(function (prop) {
|
||||
return hasOwnProperty(options, prop);
|
||||
});
|
||||
each(props, function (prop) {
|
||||
var optionsDiffValue = isUndefined(optionsDiff[prop]) ? {} : optionsDiff[prop];
|
||||
var optionsValue = options[prop];
|
||||
var templateValue = template[prop];
|
||||
var templateIsComplex = isPlainObject(templateValue);
|
||||
var propPrefix = propPath ? propPath + "." : '';
|
||||
|
||||
if (templateIsComplex && isPlainObject(optionsValue)) {
|
||||
var validatedResult = validateRecursive(optionsValue, templateValue, optionsDiffValue, doWriteErrors, propPrefix + prop);
|
||||
validatedOptions[prop] = validatedResult._validated;
|
||||
optionsCopy[prop] = validatedResult._foreign;
|
||||
each([optionsCopy, validatedOptions], function (value) {
|
||||
if (isEmptyObject(value[prop])) {
|
||||
delete value[prop];
|
||||
}
|
||||
});
|
||||
} else if (!templateIsComplex) {
|
||||
var isValid = false;
|
||||
var errorEnumStrings = [];
|
||||
var errorPossibleTypes = [];
|
||||
var optionsValueType = type(optionsValue);
|
||||
var templateValueArr = !isArray(templateValue) ? [templateValue] : templateValue;
|
||||
each(templateValueArr, function (currTemplateType) {
|
||||
var typeString;
|
||||
each(optionsTemplateTypes, function (value, key) {
|
||||
if (value === currTemplateType) {
|
||||
typeString = key;
|
||||
}
|
||||
});
|
||||
var isEnumString = isUndefined(typeString);
|
||||
|
||||
if (isEnumString && isString(optionsValue)) {
|
||||
var enumStringSplit = currTemplateType.split(' ');
|
||||
isValid = !!enumStringSplit.find(function (possibility) {
|
||||
return possibility === optionsValue;
|
||||
});
|
||||
push(errorEnumStrings, enumStringSplit);
|
||||
} else {
|
||||
isValid = optionsTemplateTypes[optionsValueType] === currTemplateType;
|
||||
}
|
||||
|
||||
push(errorPossibleTypes, isEnumString ? optionsTemplateTypes.string : typeString);
|
||||
return !isValid;
|
||||
});
|
||||
|
||||
if (isValid) {
|
||||
var isPrimitiveArr = isArray(optionsValue) && !optionsValue.some(function (val) {
|
||||
return !isNumber(val) && !isString(val) && !isBoolean(val);
|
||||
});
|
||||
var doStringifyComparison = isPrimitiveArr || isPlainObject(optionsValue);
|
||||
|
||||
if (doStringifyComparison ? stringify(optionsValue) !== stringify(optionsDiffValue) : optionsValue !== optionsDiffValue) {
|
||||
validatedOptions[prop] = optionsValue;
|
||||
}
|
||||
} else if (doWriteErrors) {
|
||||
console.warn("" + ("The option \"" + propPrefix + prop + "\" wasn't set, because it doesn't accept the type [ " + optionsValueType.toUpperCase() + " ] with the value of \"" + optionsValue + "\".\r\n" + ("Accepted types are: [ " + errorPossibleTypes.join(', ').toUpperCase() + " ].\r\n")) + (errorEnumStrings.length > 0 ? "\r\nValid strings are: [ " + errorEnumStrings.join(', ') + " ]." : ''));
|
||||
}
|
||||
|
||||
delete optionsCopy[prop];
|
||||
var stringify = function stringify(value) {
|
||||
return JSON.stringify(value, function (_, val) {
|
||||
if (isFunction(val)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return val;
|
||||
});
|
||||
return {
|
||||
_foreign: optionsCopy,
|
||||
_validated: validatedOptions
|
||||
};
|
||||
};
|
||||
|
||||
var validateOptions = function validateOptions(options, template, optionsDiff, doWriteErrors) {
|
||||
return validateRecursive(options, template, optionsDiff || {}, doWriteErrors || false);
|
||||
};
|
||||
|
||||
var transformOptions = function transformOptions(optionsWithOptionsTemplate) {
|
||||
var result = {
|
||||
_template: {},
|
||||
_options: {}
|
||||
};
|
||||
each(keys(optionsWithOptionsTemplate), function (key) {
|
||||
var val = optionsWithOptionsTemplate[key];
|
||||
|
||||
if (isArray(val)) {
|
||||
result._template[key] = val[1];
|
||||
result._options[key] = val[0];
|
||||
} else {
|
||||
var tmpResult = transformOptions(val);
|
||||
result._template[key] = tmpResult._template;
|
||||
result._options[key] = tmpResult._options;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
var classNameEnvironment = 'os-environment';
|
||||
var classNameEnvironmentFlexboxGlue = classNameEnvironment + "-flexbox-glue";
|
||||
var classNameEnvironmentFlexboxGlueMax = classNameEnvironmentFlexboxGlue + "-max";
|
||||
var classNameHost = 'os-host';
|
||||
var classNamePadding = 'os-padding';
|
||||
var classNameViewport = 'os-viewport';
|
||||
var classNameViewportArrange = classNameViewport + "-arrange";
|
||||
var classNameContent = 'os-content';
|
||||
var classNameViewportScrollbarStyling = classNameViewport + "-scrollbar-styled";
|
||||
var classNameSizeObserver = 'os-size-observer';
|
||||
var classNameSizeObserverAppear = classNameSizeObserver + "-appear";
|
||||
var classNameSizeObserverListener = classNameSizeObserver + "-listener";
|
||||
var classNameSizeObserverListenerScroll = classNameSizeObserverListener + "-scroll";
|
||||
var classNameSizeObserverListenerItem = classNameSizeObserverListener + "-item";
|
||||
var classNameSizeObserverListenerItemFinal = classNameSizeObserverListenerItem + "-final";
|
||||
var classNameTrinsicObserver = 'os-trinsic-observer';
|
||||
var classNameScrollbar = 'os-scrollbar';
|
||||
var classNameScrollbarHorizontal = classNameScrollbar + "-horizontal";
|
||||
var classNameScrollbarVertical = classNameScrollbar + "-vertical";
|
||||
var classNameScrollbarTrack = 'os-scrollbar-track';
|
||||
var classNameScrollbarHandle = 'os-scrollbar-handle';
|
||||
|
||||
var numberAllowedValues = optionsTemplateTypes.number;
|
||||
var arrayNullValues = [optionsTemplateTypes.array, optionsTemplateTypes.null];
|
||||
var stringArrayNullAllowedValues = [optionsTemplateTypes.string, optionsTemplateTypes.array, optionsTemplateTypes.null];
|
||||
var booleanTrueTemplate = [true, optionsTemplateTypes.boolean];
|
||||
var booleanFalseTemplate = [false, optionsTemplateTypes.boolean];
|
||||
var resizeAllowedValues = 'none both horizontal vertical';
|
||||
var overflowAllowedValues = 'hidden scroll visible visible-hidden';
|
||||
var scrollbarsVisibilityAllowedValues = 'visible hidden auto';
|
||||
var scrollbarsAutoHideAllowedValues = 'never scroll leavemove';
|
||||
var defaultOptionsWithTemplate = {
|
||||
resize: ['none', resizeAllowedValues],
|
||||
paddingAbsolute: booleanFalseTemplate,
|
||||
var defaultOptions = {
|
||||
resize: 'none',
|
||||
paddingAbsolute: false,
|
||||
updating: {
|
||||
elementEvents: [[['img', 'load']], arrayNullValues],
|
||||
attributes: [null, arrayNullValues],
|
||||
debounce: [[0, 33], [optionsTemplateTypes.number, optionsTemplateTypes.array, optionsTemplateTypes.null]]
|
||||
elementEvents: [['img', 'load']],
|
||||
attributes: null,
|
||||
debounce: [0, 33]
|
||||
},
|
||||
overflow: {
|
||||
x: ['scroll', overflowAllowedValues],
|
||||
y: ['scroll', overflowAllowedValues]
|
||||
x: 'scroll',
|
||||
y: 'scroll'
|
||||
},
|
||||
scrollbars: {
|
||||
visibility: ['auto', scrollbarsVisibilityAllowedValues],
|
||||
autoHide: ['never', scrollbarsAutoHideAllowedValues],
|
||||
autoHideDelay: [800, numberAllowedValues],
|
||||
dragScroll: booleanTrueTemplate,
|
||||
clickScroll: booleanFalseTemplate,
|
||||
touch: booleanTrueTemplate
|
||||
visibility: 'auto',
|
||||
autoHide: 'never',
|
||||
autoHideDelay: 800,
|
||||
dragScroll: true,
|
||||
clickScroll: false,
|
||||
touch: true
|
||||
},
|
||||
textarea: {
|
||||
dynWidth: booleanFalseTemplate,
|
||||
dynHeight: booleanFalseTemplate,
|
||||
inheritedAttrs: [['style', 'class'], stringArrayNullAllowedValues]
|
||||
dynWidth: false,
|
||||
dynHeight: false,
|
||||
inheritedAttrs: ['style', 'class']
|
||||
},
|
||||
nativeScrollbarsOverlaid: {
|
||||
show: booleanFalseTemplate,
|
||||
initialize: booleanFalseTemplate
|
||||
show: false,
|
||||
initialize: false
|
||||
},
|
||||
callbacks: {
|
||||
onUpdated: [null, [optionsTemplateTypes.function, optionsTemplateTypes.null]]
|
||||
onUpdated: null
|
||||
}
|
||||
};
|
||||
var getOptionsDiff = function getOptionsDiff(currOptions, newOptions) {
|
||||
var diff = {};
|
||||
var optionsKeys = keys(newOptions).concat(keys(currOptions));
|
||||
each(optionsKeys, function (optionKey) {
|
||||
var currOptionValue = currOptions[optionKey];
|
||||
var newOptionValue = newOptions[optionKey];
|
||||
|
||||
var _transformOptions = transformOptions(defaultOptionsWithTemplate),
|
||||
optionsTemplate = _transformOptions._template,
|
||||
defaultOptions = _transformOptions._options;
|
||||
if (isObject(currOptionValue) && isObject(newOptionValue)) {
|
||||
assignDeep(diff[optionKey] = {}, getOptionsDiff(currOptionValue, newOptionValue));
|
||||
} else if (hasOwnProperty(newOptions, optionKey) && newOptionValue !== currOptionValue) {
|
||||
var isDiff = true;
|
||||
|
||||
if (isArray(currOptionValue) || isArray(newOptionValue)) {
|
||||
try {
|
||||
if (stringify(currOptionValue) === stringify(newOptionValue)) {
|
||||
isDiff = false;
|
||||
}
|
||||
} catch (_unused) {}
|
||||
}
|
||||
|
||||
if (isDiff) {
|
||||
diff[optionKey] = newOptionValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
return diff;
|
||||
};
|
||||
|
||||
var environmentInstance;
|
||||
var abs$1 = Math.abs,
|
||||
@@ -2510,24 +2428,85 @@
|
||||
};
|
||||
};
|
||||
|
||||
var OverlayScrollbars = function OverlayScrollbars(target, options, extensions) {
|
||||
var pluginRegistry = {};
|
||||
var getPlugins = function getPlugins() {
|
||||
return assignDeep({}, pluginRegistry);
|
||||
};
|
||||
var addPlugin = function addPlugin(addedPlugin) {
|
||||
return each(isArray(addedPlugin) ? addedPlugin : [addedPlugin], function (plugin) {
|
||||
pluginRegistry[plugin[0]] = plugin[1];
|
||||
});
|
||||
};
|
||||
|
||||
var templateTypePrefixSuffix = ['__TPL_', '_TYPE__'];
|
||||
var optionsTemplateTypes = ['boolean', 'number', 'string', 'array', 'object', 'function', 'null'].reduce(function (result, item) {
|
||||
result[item] = templateTypePrefixSuffix[0] + item + templateTypePrefixSuffix[1];
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
var numberAllowedValues = optionsTemplateTypes.number;
|
||||
var booleanAllowedValues = optionsTemplateTypes.boolean;
|
||||
var arrayNullValues = [optionsTemplateTypes.array, optionsTemplateTypes.null];
|
||||
var stringArrayNullAllowedValues = [optionsTemplateTypes.string, optionsTemplateTypes.array, optionsTemplateTypes.null];
|
||||
var resizeAllowedValues = 'none both horizontal vertical';
|
||||
var overflowAllowedValues = 'hidden scroll visible visible-hidden';
|
||||
var scrollbarsVisibilityAllowedValues = 'visible hidden auto';
|
||||
var scrollbarsAutoHideAllowedValues = 'never scroll leavemove';
|
||||
({
|
||||
resize: resizeAllowedValues,
|
||||
paddingAbsolute: booleanAllowedValues,
|
||||
updating: {
|
||||
elementEvents: arrayNullValues,
|
||||
attributes: arrayNullValues,
|
||||
debounce: [optionsTemplateTypes.number, optionsTemplateTypes.array, optionsTemplateTypes.null]
|
||||
},
|
||||
overflow: {
|
||||
x: overflowAllowedValues,
|
||||
y: overflowAllowedValues
|
||||
},
|
||||
scrollbars: {
|
||||
visibility: scrollbarsVisibilityAllowedValues,
|
||||
autoHide: scrollbarsAutoHideAllowedValues,
|
||||
autoHideDelay: numberAllowedValues,
|
||||
dragScroll: booleanAllowedValues,
|
||||
clickScroll: booleanAllowedValues,
|
||||
touch: booleanAllowedValues
|
||||
},
|
||||
textarea: {
|
||||
dynWidth: booleanAllowedValues,
|
||||
dynHeight: booleanAllowedValues,
|
||||
inheritedAttrs: stringArrayNullAllowedValues
|
||||
},
|
||||
nativeScrollbarsOverlaid: {
|
||||
show: booleanAllowedValues,
|
||||
initialize: booleanAllowedValues
|
||||
},
|
||||
callbacks: {
|
||||
onUpdated: [optionsTemplateTypes.function, optionsTemplateTypes.null]
|
||||
}
|
||||
});
|
||||
var optionsValidationPluginName = '__osOptionsValidationPlugin';
|
||||
|
||||
var OverlayScrollbars = function OverlayScrollbars(target, options) {
|
||||
var _getEnvironment = getEnvironment(),
|
||||
_getDefaultOptions = _getEnvironment._getDefaultOptions;
|
||||
|
||||
var currentOptions = assignDeep({}, _getDefaultOptions(), validateOptions(options || {}, optionsTemplate, null, true)._validated);
|
||||
var plugins = getPlugins();
|
||||
var optionsValidationPlugin = plugins[optionsValidationPluginName];
|
||||
var validateOptions = optionsValidationPlugin && optionsValidationPlugin._;
|
||||
var currentOptions = assignDeep({}, _getDefaultOptions(), validateOptions ? validateOptions(options || {}, true) : options);
|
||||
var structureSetup = createStructureSetup(target);
|
||||
var scrollbarsSetup = createScrollbarsSetup(target, structureSetup);
|
||||
var lifecycleHub = createLifecycleHub(currentOptions, structureSetup, scrollbarsSetup);
|
||||
var instance = {
|
||||
options: function options(newOptions) {
|
||||
if (newOptions) {
|
||||
var _validateOptions = validateOptions(newOptions, optionsTemplate, currentOptions, true),
|
||||
_changedOptions = _validateOptions._validated;
|
||||
var changedOptions = getOptionsDiff(currentOptions, validateOptions ? validateOptions(newOptions, true) : newOptions);
|
||||
|
||||
if (!isEmptyObject(_changedOptions)) {
|
||||
assignDeep(currentOptions, _changedOptions);
|
||||
if (!isEmptyObject(changedOptions)) {
|
||||
assignDeep(currentOptions, changedOptions);
|
||||
|
||||
lifecycleHub._update(_changedOptions);
|
||||
lifecycleHub._update(changedOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2543,9 +2522,17 @@
|
||||
return lifecycleHub._destroy();
|
||||
}
|
||||
};
|
||||
each(keys(plugins), function (pluginName) {
|
||||
var pluginInstance = plugins[pluginName];
|
||||
|
||||
if (isFunction(pluginInstance)) {
|
||||
pluginInstance(OverlayScrollbars, instance);
|
||||
}
|
||||
});
|
||||
instance.update(true);
|
||||
return instance;
|
||||
};
|
||||
OverlayScrollbars.extend = addPlugin;
|
||||
|
||||
return OverlayScrollbars;
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -16,7 +16,6 @@ import {
|
||||
getBoundingClientRect,
|
||||
assignDeep,
|
||||
cssProperty,
|
||||
PartialOptions,
|
||||
} from 'support';
|
||||
import {
|
||||
classNameEnvironment,
|
||||
@@ -25,7 +24,7 @@ import {
|
||||
classNameViewportScrollbarStyling,
|
||||
} from 'classnames';
|
||||
import { OSOptions, defaultOptions } from 'options';
|
||||
import { OSTargetElement } from 'typings';
|
||||
import { OSTargetElement, PartialOptions } from 'typings';
|
||||
|
||||
type StructureInitializationElementFn<T> = ((target: OSTargetElement) => HTMLElement | T) | T;
|
||||
|
||||
@@ -255,10 +254,8 @@ const createEnvironment = (): Environment => {
|
||||
const isZoom = deltaIsBigger && difference && dprChanged;
|
||||
|
||||
if (isZoom) {
|
||||
const newScrollbarSize = (environmentInstance._nativeScrollbarSize = getNativeScrollbarSize(
|
||||
body,
|
||||
envElm
|
||||
));
|
||||
const newScrollbarSize = (environmentInstance._nativeScrollbarSize =
|
||||
getNativeScrollbarSize(body, envElm));
|
||||
removeElements(envElm);
|
||||
|
||||
if (scrollbarSize.x !== newScrollbarSize.x || scrollbarSize.y !== newScrollbarSize.y) {
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
WH,
|
||||
TRBL,
|
||||
CacheValues,
|
||||
PartialOptions,
|
||||
each,
|
||||
hasOwnProperty,
|
||||
isNumber,
|
||||
@@ -18,7 +17,7 @@ import { lifecycleHubOservers } from 'lifecycles/lifecycleHubObservers';
|
||||
import { createTrinsicLifecycle } from 'lifecycles/trinsicLifecycle';
|
||||
import { createPaddingLifecycle } from 'lifecycles/paddingLifecycle';
|
||||
import { createOverflowLifecycle } from 'lifecycles/overflowLifecycle';
|
||||
import { StyleObject } from 'typings';
|
||||
import { StyleObject, PartialOptions } from 'typings';
|
||||
import { ScrollbarsSetup } from 'setups/scrollbarsSetup';
|
||||
|
||||
export type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import {
|
||||
optionsTemplateTypes as oTypes,
|
||||
transformOptions,
|
||||
OptionsTemplateValue,
|
||||
OptionsWithOptionsTemplateValue,
|
||||
OptionsWithOptionsTemplate,
|
||||
} from 'support/options';
|
||||
import { assignDeep, each, isObject, keys, isArray, hasOwnProperty, isFunction } from 'support';
|
||||
import { PartialOptions } from 'typings';
|
||||
|
||||
const stringify = (value: any) =>
|
||||
JSON.stringify(value, (_, val) => {
|
||||
if (isFunction(val)) {
|
||||
throw new Error();
|
||||
}
|
||||
return val;
|
||||
});
|
||||
|
||||
export type ResizeBehavior = 'none' | 'both' | 'horizontal' | 'vertical';
|
||||
|
||||
@@ -107,83 +110,69 @@ export interface UpdatedArgs {
|
||||
forced: boolean;
|
||||
}
|
||||
|
||||
const numberAllowedValues: OptionsTemplateValue<number> = oTypes.number;
|
||||
const arrayNullValues: OptionsTemplateValue<Array<unknown> | null> = [oTypes.array, oTypes.null];
|
||||
const stringArrayNullAllowedValues: OptionsTemplateValue<string | ReadonlyArray<string> | null> = [oTypes.string, oTypes.array, oTypes.null];
|
||||
const booleanTrueTemplate: OptionsWithOptionsTemplateValue<boolean> = [true, oTypes.boolean];
|
||||
const booleanFalseTemplate: OptionsWithOptionsTemplateValue<boolean> = [false, oTypes.boolean];
|
||||
// const callbackTemplate: OptionsWithOptionsTemplateValue<Func | null> = [null, [oTypes.function, oTypes.null]];
|
||||
const resizeAllowedValues: OptionsTemplateValue<ResizeBehavior> = 'none both horizontal vertical';
|
||||
const overflowAllowedValues: OptionsTemplateValue<OverflowBehavior> = 'hidden scroll visible visible-hidden';
|
||||
const scrollbarsVisibilityAllowedValues: OptionsTemplateValue<VisibilityBehavior> = 'visible hidden auto';
|
||||
const scrollbarsAutoHideAllowedValues: OptionsTemplateValue<AutoHideBehavior> = 'never scroll leavemove';
|
||||
|
||||
/**
|
||||
* A object which serves as "default options object" and "options template object".
|
||||
* I combined these two into one object so that I don't have to define two separate big objects, instead I define one big object.
|
||||
*
|
||||
* The property value is a tuple:
|
||||
* the first value is the default value
|
||||
* the second value is the template value
|
||||
* Example:
|
||||
* {
|
||||
* a: ['default', [Type.string, Type.null]],
|
||||
* b: [250, Type.number]
|
||||
* }
|
||||
* Property "a" has a default value of 'default' and it can be a string or null
|
||||
* Property "b" has a default value of 250 and it can be number
|
||||
*/
|
||||
const defaultOptionsWithTemplate: OptionsWithOptionsTemplate<OSOptions> = {
|
||||
resize: ['none', resizeAllowedValues], // none || both || horizontal || vertical || n || b || h || v
|
||||
paddingAbsolute: booleanFalseTemplate, // true || false
|
||||
export const defaultOptions: OSOptions = {
|
||||
resize: 'none', // none || both || horizontal || vertical || n || b || h || v
|
||||
paddingAbsolute: false, // true || false
|
||||
updating: {
|
||||
elementEvents: [[['img', 'load']], arrayNullValues], // array of tuples || null
|
||||
attributes: [null, arrayNullValues],
|
||||
debounce: [
|
||||
[0, 33],
|
||||
[oTypes.number, oTypes.array, oTypes.null],
|
||||
], // number || number array || null
|
||||
elementEvents: [['img', 'load']], // array of tuples || null
|
||||
attributes: null,
|
||||
debounce: [0, 33], // number || number array || null
|
||||
},
|
||||
overflow: {
|
||||
x: ['scroll', overflowAllowedValues], // visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s
|
||||
y: ['scroll', overflowAllowedValues], // visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s
|
||||
x: 'scroll', // visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s
|
||||
y: 'scroll', // visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s
|
||||
},
|
||||
scrollbars: {
|
||||
visibility: ['auto', scrollbarsVisibilityAllowedValues], // visible || hidden || auto || v || h || a
|
||||
autoHide: ['never', scrollbarsAutoHideAllowedValues], // never || scroll || leave || move || n || s || l || m
|
||||
autoHideDelay: [800, numberAllowedValues], // number
|
||||
dragScroll: booleanTrueTemplate, // true || false
|
||||
clickScroll: booleanFalseTemplate, // true || false
|
||||
touch: booleanTrueTemplate, // true || false
|
||||
visibility: 'auto', // visible || hidden || auto || v || h || a
|
||||
autoHide: 'never', // never || scroll || leave || move || n || s || l || m
|
||||
autoHideDelay: 800, // number
|
||||
dragScroll: true, // true || false
|
||||
clickScroll: false, // true || false
|
||||
touch: true, // true || false
|
||||
},
|
||||
textarea: {
|
||||
dynWidth: booleanFalseTemplate, // true || false
|
||||
dynHeight: booleanFalseTemplate, // true || false
|
||||
inheritedAttrs: [['style', 'class'], stringArrayNullAllowedValues], // string || array || null
|
||||
dynWidth: false, // true || false
|
||||
dynHeight: false, // true || false
|
||||
inheritedAttrs: ['style', 'class'], // string || array || null
|
||||
},
|
||||
nativeScrollbarsOverlaid: {
|
||||
show: booleanFalseTemplate, // true || false
|
||||
initialize: booleanFalseTemplate, // true || false
|
||||
show: false, // true || false
|
||||
initialize: false, // true || false
|
||||
},
|
||||
callbacks: {
|
||||
onUpdated: [null, [oTypes.function, oTypes.null]],
|
||||
onUpdated: null,
|
||||
},
|
||||
/*
|
||||
callbacks: {
|
||||
onInitialized: callbackTemplate, // null || function
|
||||
onInitializationWithdrawn: callbackTemplate, // null || function
|
||||
onDestroyed: callbackTemplate, // null || function
|
||||
onScrollStart: callbackTemplate, // null || function
|
||||
onScroll: callbackTemplate, // null || function
|
||||
onScrollStop: callbackTemplate, // null || function
|
||||
onOverflowChanged: callbackTemplate, // null || function
|
||||
onOverflowAmountChanged: callbackTemplate, // null || function
|
||||
onDirectionChanged: callbackTemplate, // null || function
|
||||
onContentSizeChanged: callbackTemplate, // null || function
|
||||
onHostSizeChanged: callbackTemplate, // null || function
|
||||
onUpdated: callbackTemplate, // null || function
|
||||
},
|
||||
*/
|
||||
};
|
||||
|
||||
export const { _template: optionsTemplate, _options: defaultOptions } = transformOptions(defaultOptionsWithTemplate);
|
||||
export const getOptionsDiff = <T>(
|
||||
currOptions: T,
|
||||
newOptions: PartialOptions<T>
|
||||
): PartialOptions<T> => {
|
||||
const diff: PartialOptions<T> = {};
|
||||
const optionsKeys = keys(newOptions).concat(keys(currOptions));
|
||||
|
||||
each(optionsKeys, (optionKey) => {
|
||||
const currOptionValue = currOptions[optionKey];
|
||||
const newOptionValue = newOptions[optionKey];
|
||||
|
||||
if (isObject(currOptionValue) && isObject(newOptionValue)) {
|
||||
assignDeep((diff[optionKey] = {}), getOptionsDiff(currOptionValue, newOptionValue));
|
||||
} else if (hasOwnProperty(newOptions, optionKey) && newOptionValue !== currOptionValue) {
|
||||
let isDiff = true;
|
||||
|
||||
if (isArray(currOptionValue) || isArray(newOptionValue)) {
|
||||
try {
|
||||
if (stringify(currOptionValue) === stringify(newOptionValue)) {
|
||||
isDiff = false;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (isDiff) {
|
||||
diff[optionKey] = newOptionValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return diff;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { OSTarget, OSInitializationObject } from 'typings';
|
||||
import { PartialOptions, validateOptions, assignDeep, isEmptyObject } from 'support';
|
||||
import { OSTarget, OSInitializationObject, PartialOptions } from 'typings';
|
||||
import { assignDeep, isEmptyObject, each, isFunction, keys, isHTMLElement } from 'support';
|
||||
import { createStructureSetup, StructureSetup } from 'setups/structureSetup';
|
||||
import { createScrollbarsSetup, ScrollbarsSetup } from 'setups/scrollbarsSetup';
|
||||
import { createLifecycleHub } from 'lifecycles/lifecycleHub';
|
||||
import { OSOptions, optionsTemplate } from 'options';
|
||||
import { getOptionsDiff, OSOptions } from 'options';
|
||||
import { getEnvironment } from 'environment';
|
||||
import {
|
||||
getPlugins,
|
||||
addPlugin,
|
||||
optionsValidationPluginName,
|
||||
OSPlugin,
|
||||
OptionsValidationPluginInstance,
|
||||
} from 'plugins';
|
||||
import { addInstance, getInstance } from 'instances';
|
||||
|
||||
export interface OverlayScrollbarsStatic {
|
||||
(
|
||||
@@ -12,6 +20,8 @@ export interface OverlayScrollbarsStatic {
|
||||
options?: PartialOptions<OSOptions>,
|
||||
extensions?: any
|
||||
): OverlayScrollbars;
|
||||
|
||||
extend(osPlugin: OSPlugin | OSPlugin[]): void;
|
||||
}
|
||||
|
||||
export interface OverlayScrollbars {
|
||||
@@ -26,16 +36,24 @@ export interface OverlayScrollbars {
|
||||
|
||||
export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
target: OSTarget | OSInitializationObject,
|
||||
options?: PartialOptions<OSOptions>,
|
||||
extensions?: any
|
||||
options?: PartialOptions<OSOptions>
|
||||
): OverlayScrollbars => {
|
||||
const potentialInstance = getInstance(isHTMLElement(target) ? target : target.target);
|
||||
if (potentialInstance) {
|
||||
return potentialInstance;
|
||||
}
|
||||
|
||||
const { _getDefaultOptions } = getEnvironment();
|
||||
const currentOptions: OSOptions = assignDeep(
|
||||
{},
|
||||
_getDefaultOptions(),
|
||||
validateOptions(options || ({} as PartialOptions<OSOptions>), optionsTemplate, null, true)
|
||||
._validated
|
||||
);
|
||||
const plugins = getPlugins();
|
||||
const optionsValidationPlugin = plugins[
|
||||
optionsValidationPluginName
|
||||
] as OptionsValidationPluginInstance;
|
||||
const validateOptions = (newOptions?: PartialOptions<OSOptions>) => {
|
||||
const opts = newOptions || {};
|
||||
const validate = optionsValidationPlugin && optionsValidationPlugin._;
|
||||
return validate ? validate(opts, true) : opts;
|
||||
};
|
||||
const currentOptions: OSOptions = assignDeep({}, _getDefaultOptions(), validateOptions(options));
|
||||
const structureSetup: StructureSetup = createStructureSetup(target);
|
||||
const scrollbarsSetup: ScrollbarsSetup = createScrollbarsSetup(target, structureSetup);
|
||||
const lifecycleHub = createLifecycleHub(currentOptions, structureSetup, scrollbarsSetup);
|
||||
@@ -43,16 +61,11 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
const instance: OverlayScrollbars = {
|
||||
options(newOptions?: PartialOptions<OSOptions>) {
|
||||
if (newOptions) {
|
||||
const { _validated: _changedOptions } = validateOptions(
|
||||
newOptions,
|
||||
optionsTemplate,
|
||||
currentOptions,
|
||||
true
|
||||
);
|
||||
const changedOptions = getOptionsDiff(currentOptions, validateOptions(newOptions));
|
||||
|
||||
if (!isEmptyObject(_changedOptions)) {
|
||||
assignDeep(currentOptions, _changedOptions);
|
||||
lifecycleHub._update(_changedOptions);
|
||||
if (!isEmptyObject(changedOptions)) {
|
||||
assignDeep(currentOptions, changedOptions);
|
||||
lifecycleHub._update(changedOptions);
|
||||
}
|
||||
}
|
||||
return currentOptions;
|
||||
@@ -64,7 +77,18 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
destroy: () => lifecycleHub._destroy(),
|
||||
};
|
||||
|
||||
each(keys(plugins), (pluginName) => {
|
||||
const pluginInstance = plugins[pluginName];
|
||||
if (isFunction(pluginInstance)) {
|
||||
pluginInstance(OverlayScrollbars, instance);
|
||||
}
|
||||
});
|
||||
|
||||
instance.update(true);
|
||||
|
||||
addInstance(structureSetup._targetObj._target, instance);
|
||||
|
||||
return instance;
|
||||
};
|
||||
|
||||
OverlayScrollbars.extend = addPlugin;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { optionsValidationPlugin } from 'plugins/optionsValidation/optionsValidation';
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './plugins';
|
||||
export * from './optionsValidation';
|
||||
@@ -0,0 +1 @@
|
||||
export * from 'plugins/optionsValidation/optionsValidation';
|
||||
@@ -0,0 +1,81 @@
|
||||
import { OSPlugin } from 'plugins';
|
||||
import {
|
||||
OSOptions,
|
||||
ResizeBehavior,
|
||||
OverflowBehavior,
|
||||
VisibilityBehavior,
|
||||
AutoHideBehavior,
|
||||
} from 'options';
|
||||
import {
|
||||
validateOptions,
|
||||
OptionsTemplate,
|
||||
OptionsTemplateValue,
|
||||
optionsTemplateTypes as oTypes,
|
||||
} from 'plugins/optionsValidation/validation';
|
||||
import { PartialOptions } from 'typings';
|
||||
|
||||
const numberAllowedValues: OptionsTemplateValue<number> = oTypes.number;
|
||||
const booleanAllowedValues: OptionsTemplateValue<boolean> = oTypes.boolean;
|
||||
const arrayNullValues: OptionsTemplateValue<Array<unknown> | null> = [oTypes.array, oTypes.null];
|
||||
const stringArrayNullAllowedValues: OptionsTemplateValue<string | ReadonlyArray<string> | null> = [
|
||||
oTypes.string,
|
||||
oTypes.array,
|
||||
oTypes.null,
|
||||
];
|
||||
const resizeAllowedValues: OptionsTemplateValue<ResizeBehavior> = 'none both horizontal vertical';
|
||||
const overflowAllowedValues: OptionsTemplateValue<OverflowBehavior> =
|
||||
'hidden scroll visible visible-hidden';
|
||||
const scrollbarsVisibilityAllowedValues: OptionsTemplateValue<VisibilityBehavior> =
|
||||
'visible hidden auto';
|
||||
const scrollbarsAutoHideAllowedValues: OptionsTemplateValue<AutoHideBehavior> =
|
||||
'never scroll leavemove';
|
||||
|
||||
const optionsTemplate: OptionsTemplate<OSOptions> = {
|
||||
resize: resizeAllowedValues, // none || both || horizontal || vertical || n || b ||
|
||||
paddingAbsolute: booleanAllowedValues, // true || false
|
||||
updating: {
|
||||
elementEvents: arrayNullValues, // array of tuples || null
|
||||
attributes: arrayNullValues,
|
||||
debounce: [oTypes.number, oTypes.array, oTypes.null], // number || number array || null
|
||||
},
|
||||
overflow: {
|
||||
x: overflowAllowedValues, // visible-hidden || visible-scroll || hidden || scrol
|
||||
y: overflowAllowedValues, // visible-hidden || visible-scroll || hidden || scrol
|
||||
},
|
||||
scrollbars: {
|
||||
visibility: scrollbarsVisibilityAllowedValues, // visible || hidden || auto || v ||
|
||||
autoHide: scrollbarsAutoHideAllowedValues, // never || scroll || leave || move ||
|
||||
autoHideDelay: numberAllowedValues, // number
|
||||
dragScroll: booleanAllowedValues, // true || false
|
||||
clickScroll: booleanAllowedValues, // true || false
|
||||
touch: booleanAllowedValues, // true || false
|
||||
},
|
||||
textarea: {
|
||||
dynWidth: booleanAllowedValues, // true || false
|
||||
dynHeight: booleanAllowedValues, // true || false
|
||||
inheritedAttrs: stringArrayNullAllowedValues, // string || array || nul
|
||||
},
|
||||
nativeScrollbarsOverlaid: {
|
||||
show: booleanAllowedValues, // true || false
|
||||
initialize: booleanAllowedValues, // true || false
|
||||
},
|
||||
callbacks: {
|
||||
onUpdated: [oTypes.function, oTypes.null],
|
||||
},
|
||||
};
|
||||
|
||||
export type OptionsValidationPluginInstance = {
|
||||
_: (options: PartialOptions<OSOptions>, doWriteErrors?: boolean) => PartialOptions<OSOptions>;
|
||||
};
|
||||
|
||||
export const optionsValidationPluginName = '__osOptionsValidationPlugin';
|
||||
|
||||
export const optionsValidationPlugin: OSPlugin<OptionsValidationPluginInstance> = [
|
||||
optionsValidationPluginName,
|
||||
{
|
||||
_: (options: PartialOptions<OSOptions>, doWriteErrors?: boolean) => {
|
||||
const [validated, foreign] = validateOptions(optionsTemplate, options, doWriteErrors);
|
||||
return { ...foreign, ...validated };
|
||||
},
|
||||
},
|
||||
];
|
||||
+2
-2
@@ -4,7 +4,7 @@ import {
|
||||
OptionsTemplateNativeTypes,
|
||||
OptionsTemplateTypes,
|
||||
OptionsTemplateValue,
|
||||
} from 'support/options/validation';
|
||||
} from 'plugins/optionsValidation/validation';
|
||||
import { PlainObject } from 'typings';
|
||||
import { isArray } from 'support/utils/types';
|
||||
import { each, keys } from 'support/utils';
|
||||
@@ -45,8 +45,8 @@ export const transformOptions = <T>(
|
||||
optionsWithOptionsTemplate[key];
|
||||
|
||||
if (isArray(val)) {
|
||||
result._template[key] = val[1];
|
||||
result._options[key] = val[0];
|
||||
result._template[key] = val[1];
|
||||
} else {
|
||||
// if (isObject(val))
|
||||
const tmpResult = transformOptions(val as OptionsWithOptionsTemplate<typeof val>);
|
||||
+21
-69
@@ -1,14 +1,6 @@
|
||||
import { each, hasOwnProperty, keys, push, isEmptyObject } from 'support/utils';
|
||||
import {
|
||||
type,
|
||||
isArray,
|
||||
isUndefined,
|
||||
isPlainObject,
|
||||
isString,
|
||||
isNumber,
|
||||
isBoolean,
|
||||
} from 'support/utils/types';
|
||||
import { PlainObject } from 'typings';
|
||||
import { type, isArray, isUndefined, isPlainObject, isString } from 'support/utils/types';
|
||||
import { PlainObject, PartialOptions } from 'typings';
|
||||
|
||||
export type OptionsObjectType = Record<string, unknown>;
|
||||
export type OptionsFunctionType = (this: unknown, ...args: unknown[]) => unknown;
|
||||
@@ -33,14 +25,10 @@ export type OptionsTemplate<T> = {
|
||||
: never;
|
||||
};
|
||||
|
||||
export type OptionsValidationResult<T> = {
|
||||
readonly _foreign: Record<string, unknown>;
|
||||
readonly _validated: PartialOptions<T>;
|
||||
};
|
||||
|
||||
export type PartialOptions<T> = {
|
||||
[P in keyof T]?: T[P] extends OptionsObjectType ? PartialOptions<T[P]> : T[P];
|
||||
};
|
||||
export type OptionsValidationResult<T> = [
|
||||
PartialOptions<T>, // validated
|
||||
Record<string, unknown> // foreign
|
||||
];
|
||||
|
||||
type OptionsTemplateTypeMap = {
|
||||
__TPL_boolean_TYPE__: boolean;
|
||||
@@ -70,8 +58,6 @@ type OptionsTemplateTypesDictionary = {
|
||||
readonly null: OptionsTemplateType<null>;
|
||||
};
|
||||
|
||||
const { stringify } = JSON;
|
||||
|
||||
/**
|
||||
* A prefix and suffix tuple which serves as recognition pattern for template types.
|
||||
*/
|
||||
@@ -101,8 +87,8 @@ const optionsTemplateTypes: OptionsTemplateTypesDictionary = [
|
||||
* foreign : a object which consists of properties which aren't defined inside the template. (foreign properties)
|
||||
* validated : a object which consists only of valid properties. (property name is inside the template and value has a correct type)
|
||||
* }
|
||||
* @param options The options object which shall be validated.
|
||||
* @param template The template according to which the options object shall be validated.
|
||||
* @param options The options object which shall be validated.
|
||||
* @param optionsDiff When provided the returned validated object will only have properties which are different to this objects properties.
|
||||
* Example (assume all properties are valid to the template):
|
||||
* Options object : { a: 'a', b: 'b', c: 'c' }
|
||||
@@ -114,9 +100,8 @@ const optionsTemplateTypes: OptionsTemplateTypesDictionary = [
|
||||
* @param propPath The propertyPath which lead to this object. (used for error logging)
|
||||
*/
|
||||
const validateRecursive = <T extends PlainObject>(
|
||||
options: PartialOptions<T>,
|
||||
template: OptionsTemplate<T>,
|
||||
optionsDiff: T,
|
||||
options: PartialOptions<T>,
|
||||
doWriteErrors?: boolean,
|
||||
propPath?: string
|
||||
): OptionsValidationResult<T> => {
|
||||
@@ -125,7 +110,6 @@ const validateRecursive = <T extends PlainObject>(
|
||||
const props = keys(template).filter((prop) => hasOwnProperty(options, prop));
|
||||
|
||||
each(props, (prop: Extract<keyof T, string>) => {
|
||||
const optionsDiffValue: any = isUndefined(optionsDiff[prop]) ? {} : optionsDiff[prop];
|
||||
const optionsValue: any = options[prop];
|
||||
const templateValue: PlainObject | string | OptionsTemplateTypes | Array<OptionsTemplateTypes> =
|
||||
template[prop];
|
||||
@@ -134,15 +118,14 @@ const validateRecursive = <T extends PlainObject>(
|
||||
|
||||
// if the template has a object as value, it means that the options are complex (verschachtelt)
|
||||
if (templateIsComplex && isPlainObject(optionsValue)) {
|
||||
const validatedResult = validateRecursive(
|
||||
optionsValue,
|
||||
const [validated, foreign] = validateRecursive(
|
||||
templateValue as T,
|
||||
optionsDiffValue,
|
||||
optionsValue,
|
||||
doWriteErrors,
|
||||
propPrefix + prop
|
||||
);
|
||||
validatedOptions[prop] = validatedResult._validated as any;
|
||||
optionsCopy[prop] = validatedResult._foreign as any;
|
||||
validatedOptions[prop] = validated as any;
|
||||
optionsCopy[prop] = foreign as any;
|
||||
|
||||
each([optionsCopy, validatedOptions], (value) => {
|
||||
if (isEmptyObject(value[prop])) {
|
||||
@@ -186,17 +169,7 @@ const validateRecursive = <T extends PlainObject>(
|
||||
});
|
||||
|
||||
if (isValid) {
|
||||
const isPrimitiveArr =
|
||||
isArray(optionsValue) &&
|
||||
!optionsValue.some((val) => !isNumber(val) && !isString(val) && !isBoolean(val));
|
||||
const doStringifyComparison = isPrimitiveArr || isPlainObject(optionsValue);
|
||||
if (
|
||||
doStringifyComparison
|
||||
? stringify(optionsValue) !== stringify(optionsDiffValue)
|
||||
: optionsValue !== optionsDiffValue
|
||||
) {
|
||||
validatedOptions[prop] = optionsValue;
|
||||
}
|
||||
validatedOptions[prop] = optionsValue;
|
||||
} else if (doWriteErrors) {
|
||||
console.warn(
|
||||
`${
|
||||
@@ -214,44 +187,23 @@ const validateRecursive = <T extends PlainObject>(
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
_foreign: optionsCopy,
|
||||
_validated: validatedOptions,
|
||||
};
|
||||
return [validatedOptions, optionsCopy]; // optionsCopy equals now to foreign options
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the given options object according to the given template object and returns a object which looks like:
|
||||
* {
|
||||
* foreign : a object which consists of properties which aren't defined inside the template. (foreign properties)
|
||||
* Validates the given options object according to the given template object and returns a tuple which looks like:
|
||||
* [
|
||||
* validated : a object which consists only of valid properties. (property name is inside the template and value has a correct type)
|
||||
* }
|
||||
* @param options The options object which shall be validated.
|
||||
* foreign : a object which consists of properties which aren't defined inside the template. (foreign properties)
|
||||
* ]
|
||||
* @param template The template according to which the options object shall be validated.
|
||||
* @param optionsDiff When provided the returned validated object will only have properties which are different to this objects properties.
|
||||
* Example (assume all properties are valid to the template):
|
||||
* Options object : { a: 'a', b: 'b', c: 'c' }
|
||||
* optionsDiff object : { a: 'a', b: 'b', c: undefined }
|
||||
* Returned validated object : { c: 'c' }
|
||||
* Because the value of the properties a and b didn't change, they aren't included in the returned object.
|
||||
* Without the optionsDiff object the returned validated object would be: { a: 'a', b: 'b', c: 'c' }
|
||||
* @param options The options object which shall be validated.
|
||||
* @param doWriteErrors True if errors shall be logged into the console, false otherwise.
|
||||
*/
|
||||
const validateOptions = <T extends PlainObject>(
|
||||
options: PartialOptions<T>,
|
||||
template: OptionsTemplate<T>,
|
||||
optionsDiff?: T | null,
|
||||
options: PartialOptions<T>,
|
||||
doWriteErrors?: boolean
|
||||
): OptionsValidationResult<T> =>
|
||||
/*
|
||||
if (!isEmptyObject(foreign) && doWriteErrors)
|
||||
console.warn(`The following options are discarded due to invalidity:\r\n ${window.JSON.stringify(foreign, null, 2)}`);
|
||||
|
||||
//add values, which aren't specified in the template, to the finished validated object to prevent them from being discarded
|
||||
if (keepForeignProps) {
|
||||
Object.assign(result.validated, foreign);
|
||||
}
|
||||
*/
|
||||
validateRecursive<T>(options, template, optionsDiff || ({} as T), doWriteErrors || false);
|
||||
): OptionsValidationResult<T> => validateRecursive<T>(template, options, doWriteErrors);
|
||||
|
||||
export { validateOptions, optionsTemplateTypes };
|
||||
@@ -0,0 +1,16 @@
|
||||
import { assignDeep, each, isArray } from 'support';
|
||||
import { OverlayScrollbars, OverlayScrollbarsStatic } from 'overlayscrollbars';
|
||||
|
||||
export type OSPluginInstance =
|
||||
| Record<string, unknown>
|
||||
| ((staticObj: OverlayScrollbarsStatic, instanceObj: OverlayScrollbars) => void);
|
||||
export type OSPlugin<T extends OSPluginInstance = OSPluginInstance> = [string, T];
|
||||
|
||||
const pluginRegistry: Record<string, OSPluginInstance> = {};
|
||||
|
||||
export const getPlugins = () => assignDeep({}, pluginRegistry);
|
||||
|
||||
export const addPlugin = (addedPlugin: OSPlugin | OSPlugin[]) =>
|
||||
each((isArray(addedPlugin) ? addedPlugin : [addedPlugin]) as OSPlugin[], (plugin) => {
|
||||
pluginRegistry[plugin[0]] = plugin[1];
|
||||
});
|
||||
@@ -67,9 +67,13 @@ const unwrap = (elm: HTMLElement | false | null | undefined) => {
|
||||
};
|
||||
|
||||
const createUniqueViewportArrangeElement = (): HTMLStyleElement | false => {
|
||||
const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid, _cssCustomProperties } = getEnvironment();
|
||||
const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid, _cssCustomProperties } =
|
||||
getEnvironment();
|
||||
/* istanbul ignore next */
|
||||
const create = !_cssCustomProperties && !_nativeScrollbarStyling && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y);
|
||||
const create =
|
||||
!_cssCustomProperties &&
|
||||
!_nativeScrollbarStyling &&
|
||||
(_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y);
|
||||
const result = create ? document.createElement('style') : false;
|
||||
|
||||
if (result) {
|
||||
@@ -86,8 +90,9 @@ const staticCreationFromStrategy = (
|
||||
strategy: StructureInitializationStaticElement,
|
||||
elementClass: string
|
||||
): HTMLElement => {
|
||||
const result = initializationValue ? initializationValue : isFunction(strategy) ? strategy(target) : (strategy as null);
|
||||
return result ? result : createDiv(elementClass);
|
||||
const result =
|
||||
initializationValue || (isFunction(strategy) ? strategy(target) : (strategy as null));
|
||||
return result || createDiv(elementClass);
|
||||
};
|
||||
|
||||
const dynamicCreationFromStrategy = (
|
||||
@@ -98,7 +103,11 @@ const dynamicCreationFromStrategy = (
|
||||
defaultValue: boolean
|
||||
): HTMLElement | false => {
|
||||
const takeInitializationValue = isBoolean(initializationValue) || initializationValue;
|
||||
const result = takeInitializationValue ? (initializationValue as boolean | HTMLElement) : isFunction(strategy) ? strategy(target) : strategy;
|
||||
const result = takeInitializationValue
|
||||
? (initializationValue as boolean | HTMLElement)
|
||||
: isFunction(strategy)
|
||||
? strategy(target)
|
||||
: strategy;
|
||||
|
||||
if (result === null) {
|
||||
return defaultValue ? createDiv(elementClass) : false;
|
||||
@@ -107,7 +116,9 @@ const dynamicCreationFromStrategy = (
|
||||
return result === true ? createDiv(elementClass) : result;
|
||||
};
|
||||
|
||||
export const createStructureSetup = (target: OSTarget | StructureInitialization): StructureSetup => {
|
||||
export const createStructureSetup = (
|
||||
target: OSTarget | StructureInitialization
|
||||
): StructureSetup => {
|
||||
const { _getInitializationStrategy, _nativeScrollbarStyling } = getEnvironment();
|
||||
const {
|
||||
_host: hostInitializationStrategy,
|
||||
@@ -117,7 +128,9 @@ export const createStructureSetup = (target: OSTarget | StructureInitialization)
|
||||
} = _getInitializationStrategy() as StructureInitializationStrategy;
|
||||
const targetIsElm = isHTMLElement(target);
|
||||
const targetStructureInitialization = target as StructureInitialization;
|
||||
const targetElement = targetIsElm ? (target as OSTargetElement) : targetStructureInitialization.target;
|
||||
const targetElement = targetIsElm
|
||||
? (target as OSTargetElement)
|
||||
: targetStructureInitialization.target;
|
||||
const isTextarea = is(targetElement, 'textarea');
|
||||
const isBody = !isTextarea && is(targetElement, 'body');
|
||||
const ownerDocument: HTMLDocument = targetElement!.ownerDocument;
|
||||
@@ -126,9 +139,19 @@ export const createStructureSetup = (target: OSTarget | StructureInitialization)
|
||||
const evaluatedTargetObj: PreparedOSTargetObject = {
|
||||
_target: targetElement,
|
||||
_host: isTextarea
|
||||
? staticCreationFromStrategy(targetElement, targetStructureInitialization.host, hostInitializationStrategy, classNameHost)
|
||||
? staticCreationFromStrategy(
|
||||
targetElement,
|
||||
targetStructureInitialization.host,
|
||||
hostInitializationStrategy,
|
||||
classNameHost
|
||||
)
|
||||
: (targetElement as HTMLElement),
|
||||
_viewport: staticCreationFromStrategy(targetElement, targetStructureInitialization.viewport, viewportInitializationStrategy, classNameViewport),
|
||||
_viewport: staticCreationFromStrategy(
|
||||
targetElement,
|
||||
targetStructureInitialization.viewport,
|
||||
viewportInitializationStrategy,
|
||||
classNameViewport
|
||||
),
|
||||
_padding: dynamicCreationFromStrategy(
|
||||
targetElement,
|
||||
targetStructureInitialization.padding,
|
||||
@@ -158,13 +181,18 @@ export const createStructureSetup = (target: OSTarget | StructureInitialization)
|
||||
const value = evaluatedTargetObj[key];
|
||||
return push(arr, value && !parent(value) ? value : false);
|
||||
}, [] as HTMLElement[]);
|
||||
const elementIsGenerated = (elm: HTMLElement | false) => (elm ? indexOf(generatedElements, elm) > -1 : null);
|
||||
const elementIsGenerated = (elm: HTMLElement | false) =>
|
||||
elm ? indexOf(generatedElements, elm) > -1 : null;
|
||||
const { _target, _host, _padding, _viewport, _content, _viewportArrange } = evaluatedTargetObj;
|
||||
const destroyFns: (() => any)[] = [];
|
||||
const isTextareaHostGenerated = isTextarea && elementIsGenerated(_host);
|
||||
const targetContents = isTextarea
|
||||
? _target
|
||||
: contents([_content, _viewport, _padding, _host, _target].find((elm) => elementIsGenerated(elm) === false));
|
||||
: contents(
|
||||
[_content, _viewport, _padding, _host, _target].find(
|
||||
(elm) => elementIsGenerated(elm) === false
|
||||
)
|
||||
);
|
||||
const contentSlot = _content || _viewport;
|
||||
|
||||
// only insert host for textarea after target if it was generated
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from 'support/cache';
|
||||
export * from 'support/compatibility';
|
||||
export * from 'support/dom';
|
||||
export * from 'support/options';
|
||||
export * from 'support/utils';
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from 'support/options/validation';
|
||||
export * from 'support/options/transformation';
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isArrayLike, isString, isArray } from 'support/utils/types';
|
||||
import { isArrayLike, isString } from 'support/utils/types';
|
||||
import { PlainObject } from 'typings';
|
||||
|
||||
type RunEachItem = ((...args: any) => any | any[]) | null | undefined;
|
||||
@@ -57,7 +57,7 @@ export function each<T>(
|
||||
* @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at index 0.
|
||||
*/
|
||||
export const indexOf = <T = any>(arr: T[], item: T, fromIndex?: number): number =>
|
||||
isArray(arr) ? arr.indexOf(item, fromIndex) : -1;
|
||||
arr.indexOf(item, fromIndex);
|
||||
|
||||
/**
|
||||
* Pushesh all given items into the given array and returns it.
|
||||
@@ -75,7 +75,7 @@ export const push = <T>(array: T[], items: T | ArrayLike<T>, arrayIsSingleItem?:
|
||||
* Creates a shallow-copied Array instance from an array-like or iterable object.
|
||||
* @param arr The object from which the array instance shall be created.
|
||||
*/
|
||||
export const from = <T = any>(arr: ArrayLike<T>) => {
|
||||
export const from = <T = any>(arr: ArrayLike<T> | Set<T>) => {
|
||||
if (Array.from) {
|
||||
return Array.from(arr);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
|
||||
export type PartialOptions<T> = {
|
||||
[P in keyof T]?: T[P] extends Record<string, unknown> ? PartialOptions<T[P]> : T[P];
|
||||
};
|
||||
|
||||
export type PlainObject<T = any> = { [name: string]: T };
|
||||
|
||||
export type StyleObject<CustomCssProps = ''> = {
|
||||
[Key in (keyof CSSStyleDeclaration | (CustomCssProps extends string ? CustomCssProps : ''))]?: string | number;
|
||||
}
|
||||
[Key in keyof CSSStyleDeclaration | (CustomCssProps extends string ? CustomCssProps : '')]?:
|
||||
| string
|
||||
| number;
|
||||
};
|
||||
|
||||
export type InternalVersionOf<T> = {
|
||||
[K in keyof T as `_${Uncapitalize<string & K>}`]: T[K];
|
||||
@@ -27,15 +31,15 @@ type StructureInitializationDynamicElement = HTMLElement | boolean;
|
||||
|
||||
/**
|
||||
* Object for special initialization.
|
||||
*
|
||||
*
|
||||
* Target is always required, if element is not provided or undefined it will be generated.
|
||||
*
|
||||
* If element is provided, the provided element takes all its responsibilities.
|
||||
*
|
||||
* If element is provided, the provided element takes all its responsibilities.
|
||||
* DOM hierarchy isn't checked in this case, its assumed that hieararchy is correct in such a case.
|
||||
*
|
||||
*
|
||||
* Undefined means that the environment initialization strategy for the respective element is used.
|
||||
*/
|
||||
export interface StructureInitialization {
|
||||
export interface StructureInitialization {
|
||||
target: OSTargetElement;
|
||||
host?: StructureInitializationStaticElement; // only relevant for textarea
|
||||
viewport?: StructureInitializationStaticElement;
|
||||
@@ -45,15 +49,17 @@ type StructureInitializationDynamicElement = HTMLElement | boolean;
|
||||
|
||||
/**
|
||||
* Object for special initialization.
|
||||
*
|
||||
*
|
||||
* scrollbarsSlot is the element to which the scrollbars are applied to. If null or undefined the plugin decides by itself whats the scrollbars slot.
|
||||
*/
|
||||
export interface ScrollbarsInitialization {
|
||||
scrollbarsSlot?: null | HTMLElement | ((target: OSTargetElement, host: HTMLElement, viewport: HTMLElement) => null | HTMLElement);
|
||||
scrollbarsSlot?:
|
||||
| null
|
||||
| HTMLElement
|
||||
| ((target: OSTargetElement, host: HTMLElement, viewport: HTMLElement) => null | HTMLElement);
|
||||
}
|
||||
|
||||
export interface OSInitializationObject extends StructureInitialization, ScrollbarsInitialization {
|
||||
}
|
||||
export interface OSInitializationObject extends StructureInitialization, ScrollbarsInitialization {}
|
||||
|
||||
export type OSTarget = OSTargetElement | OSInitializationObject;
|
||||
|
||||
|
||||
@@ -1,9 +1,252 @@
|
||||
import { validateOptions } from 'support/options';
|
||||
import { defaultOptions, optionsTemplate } from 'options';
|
||||
import { defaultOptions, getOptionsDiff } from 'options';
|
||||
|
||||
describe('options', () => {
|
||||
test('default options matching the options template', () => {
|
||||
const { _validated } = validateOptions(defaultOptions, optionsTemplate);
|
||||
expect(_validated).toEqual(defaultOptions);
|
||||
test('defaultOptions', () => {
|
||||
expect(defaultOptions).toEqual({
|
||||
resize: 'none',
|
||||
paddingAbsolute: false,
|
||||
updating: {
|
||||
elementEvents: [['img', 'load']],
|
||||
attributes: null,
|
||||
debounce: [0, 33],
|
||||
},
|
||||
overflow: {
|
||||
x: 'scroll',
|
||||
y: 'scroll',
|
||||
},
|
||||
scrollbars: {
|
||||
visibility: 'auto',
|
||||
autoHide: 'never',
|
||||
autoHideDelay: 800,
|
||||
dragScroll: true,
|
||||
clickScroll: false,
|
||||
touch: true,
|
||||
},
|
||||
textarea: {
|
||||
dynWidth: false,
|
||||
dynHeight: false,
|
||||
inheritedAttrs: ['style', 'class'],
|
||||
},
|
||||
nativeScrollbarsOverlaid: {
|
||||
show: false,
|
||||
initialize: false,
|
||||
},
|
||||
callbacks: {
|
||||
onUpdated: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOptionsDiff', () => {
|
||||
test('diff simple options', () => {
|
||||
const options = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
d: 4,
|
||||
e: 5,
|
||||
};
|
||||
const changed = {
|
||||
a: 0,
|
||||
b: 0,
|
||||
};
|
||||
|
||||
expect(getOptionsDiff(options, changed)).toEqual(changed);
|
||||
|
||||
expect(
|
||||
getOptionsDiff(options, {
|
||||
...options,
|
||||
...changed,
|
||||
})
|
||||
).toEqual(changed);
|
||||
});
|
||||
|
||||
test('diff nested options', () => {
|
||||
const options = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
d: 4,
|
||||
e: 5,
|
||||
deep: {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
d: 4,
|
||||
},
|
||||
};
|
||||
const changed = {
|
||||
a: 0,
|
||||
b: 0,
|
||||
deep: {
|
||||
a: 0,
|
||||
b: 0,
|
||||
},
|
||||
};
|
||||
|
||||
expect(getOptionsDiff(options, changed)).toEqual(changed);
|
||||
|
||||
expect(
|
||||
getOptionsDiff(options, {
|
||||
...options,
|
||||
...changed,
|
||||
})
|
||||
).toEqual(changed);
|
||||
|
||||
expect(
|
||||
getOptionsDiff(options, {
|
||||
...options,
|
||||
...changed,
|
||||
deep: {
|
||||
...options.deep,
|
||||
...changed.deep,
|
||||
},
|
||||
})
|
||||
).toEqual(changed);
|
||||
});
|
||||
|
||||
test('diff foreign options', () => {
|
||||
const options = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
c: 3,
|
||||
};
|
||||
const changed = {
|
||||
a: 0,
|
||||
d: 4,
|
||||
e: 5,
|
||||
};
|
||||
|
||||
expect(getOptionsDiff(options, changed)).toEqual(changed);
|
||||
|
||||
expect(
|
||||
getOptionsDiff(options, {
|
||||
...options,
|
||||
...changed,
|
||||
})
|
||||
).toEqual(changed);
|
||||
});
|
||||
|
||||
describe('diff arrays', () => {
|
||||
test('dont diff same looking arrays with primitve values', () => {
|
||||
const options = {
|
||||
a: [],
|
||||
b: [1, 2, 3],
|
||||
c: ['1', '2', '3'],
|
||||
d: [true, false, false],
|
||||
};
|
||||
const changed = {
|
||||
a: [],
|
||||
b: [1, 2, 3],
|
||||
c: ['1', '2', '3'],
|
||||
d: [true, false, false],
|
||||
};
|
||||
|
||||
expect(
|
||||
getOptionsDiff(options, {
|
||||
...options,
|
||||
...changed,
|
||||
})
|
||||
).toEqual({});
|
||||
|
||||
expect(getOptionsDiff(options, changed)).toEqual({});
|
||||
});
|
||||
|
||||
test('dont diff same looking arrays with non-primitve values', () => {
|
||||
const options = {
|
||||
a: [],
|
||||
b: [{ a: 1 }, { b: 2 }, { c: 3 }, ['tuple1', 'tuple2'], []],
|
||||
c: [
|
||||
['tuple1', 'tuple2'],
|
||||
['tuple1', 'tuple2'],
|
||||
['tuple1', 'tuple2'],
|
||||
],
|
||||
};
|
||||
const changed = {
|
||||
a: [],
|
||||
b: [{ a: 1 }, { b: 2 }, { c: 3 }, ['tuple1', 'tuple2'], []],
|
||||
c: [
|
||||
['tuple1', 'tuple2'],
|
||||
['tuple1', 'tuple2'],
|
||||
['tuple1', 'tuple2'],
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
getOptionsDiff(options, {
|
||||
...options,
|
||||
...changed,
|
||||
})
|
||||
).toEqual({});
|
||||
|
||||
expect(getOptionsDiff(options, changed)).toEqual({});
|
||||
});
|
||||
|
||||
test('diff arrays with primitve values', () => {
|
||||
const options = {
|
||||
a: [] as string[],
|
||||
b: [1, 2, 3],
|
||||
c: ['1', '2', '3'],
|
||||
d: [true, false, false],
|
||||
};
|
||||
const changed = {
|
||||
a: ['hello'],
|
||||
b: [1, 2, 3, 4],
|
||||
c: ['1', '2', '3', '4'],
|
||||
d: [true, false, false, true],
|
||||
};
|
||||
|
||||
expect(
|
||||
getOptionsDiff(options, {
|
||||
...options,
|
||||
...changed,
|
||||
})
|
||||
).toEqual(changed);
|
||||
|
||||
expect(getOptionsDiff(options, changed)).toEqual(changed);
|
||||
});
|
||||
|
||||
test('diff arrays with mixed values', () => {
|
||||
const options = {
|
||||
a: [] as Record<string, unknown>[],
|
||||
b: [1, 2, 3] as Array<number | (() => void)>,
|
||||
c: ['1', '2', '3'] as Array<string | Record<string, unknown>>,
|
||||
d: [true, false, false],
|
||||
};
|
||||
const changed = {
|
||||
a: [{}],
|
||||
b: [1, 2, 3, () => {}],
|
||||
c: ['1', '2', '3', {}],
|
||||
};
|
||||
|
||||
expect(getOptionsDiff(options, changed)).toEqual(changed);
|
||||
|
||||
expect(
|
||||
getOptionsDiff(options, {
|
||||
...options,
|
||||
...changed,
|
||||
})
|
||||
).toEqual(changed);
|
||||
});
|
||||
|
||||
test('diff same looking arrays with not serializable values', () => {
|
||||
const fn = () => {};
|
||||
const options = {
|
||||
c: [fn, fn, () => {}],
|
||||
};
|
||||
const changed = {
|
||||
c: [fn, fn, () => {}],
|
||||
};
|
||||
|
||||
expect(
|
||||
getOptionsDiff(options, {
|
||||
...options,
|
||||
...changed,
|
||||
})
|
||||
).toEqual(changed);
|
||||
|
||||
expect(getOptionsDiff(options, changed)).toEqual(changed);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { defaultOptions } from 'options';
|
||||
import { optionsValidationPlugin, optionsValidationPluginName } from 'plugins/optionsValidation';
|
||||
|
||||
const getValidationFn = () => {
|
||||
const [name, instance] = optionsValidationPlugin;
|
||||
const validationFn = instance._;
|
||||
|
||||
expect(name).toBe(optionsValidationPluginName);
|
||||
expect(typeof validationFn).toBe('function');
|
||||
return validationFn;
|
||||
};
|
||||
|
||||
describe('optionsValidationPlugin', () => {
|
||||
test('default options matching the options template', () => {
|
||||
const validationFn = getValidationFn();
|
||||
|
||||
expect(validationFn(defaultOptions)).toEqual(defaultOptions);
|
||||
});
|
||||
|
||||
test('foreign options are in the result', () => {
|
||||
const validationFn = getValidationFn();
|
||||
const foreignOps = {
|
||||
someOption: true,
|
||||
someDeepOption: {
|
||||
someDeepOption: false,
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
expect(validationFn(foreignOps)).toEqual(foreignOps);
|
||||
});
|
||||
});
|
||||
+10
-3
@@ -1,5 +1,12 @@
|
||||
import { PlainObject } from 'typings';
|
||||
import { optionsTemplateTypes as oTypes, transformOptions, OptionsTemplate, OptionsWithOptionsTemplate } from 'support/options';
|
||||
import {
|
||||
optionsTemplateTypes as oTypes,
|
||||
OptionsTemplate,
|
||||
} from 'plugins/optionsValidation/validation';
|
||||
import {
|
||||
transformOptions,
|
||||
OptionsWithOptionsTemplate,
|
||||
} from 'plugins/optionsValidation/transformation';
|
||||
|
||||
type TestOptionsObj = { propA: 'propA'; null: null };
|
||||
type TestOptionsEnum = 'A' | 'B' | 'C';
|
||||
@@ -36,7 +43,7 @@ const options: DeepRequired<TestOptions> = {
|
||||
func: () => {},
|
||||
};
|
||||
|
||||
const optionsTemplate: OptionsTemplate<Required<TestOptions>> = {
|
||||
const optionsTemplate: OptionsTemplate<DeepRequired<TestOptions>> = {
|
||||
str: oTypes.string,
|
||||
strArrNull: [oTypes.string, oTypes.array, oTypes.null],
|
||||
nullbool: [oTypes.boolean, oTypes.null],
|
||||
@@ -51,7 +58,7 @@ const optionsTemplate: OptionsTemplate<Required<TestOptions>> = {
|
||||
func: oTypes.function,
|
||||
};
|
||||
|
||||
const TestOptionsWithOptionsTemplate: OptionsWithOptionsTemplate<Required<TestOptions>> = {
|
||||
const TestOptionsWithOptionsTemplate: OptionsWithOptionsTemplate<DeepRequired<TestOptions>> = {
|
||||
str: [options.str, optionsTemplate.str],
|
||||
strArrNull: [options.strArrNull, optionsTemplate.strArrNull],
|
||||
nullbool: [options.nullbool, optionsTemplate.nullbool],
|
||||
@@ -0,0 +1,303 @@
|
||||
import {
|
||||
validateOptions,
|
||||
optionsTemplateTypes as oTypes,
|
||||
OptionsTemplate,
|
||||
} from 'plugins/optionsValidation/validation';
|
||||
import { assignDeep } from 'support/utils';
|
||||
|
||||
type TestOptionsObj = { propA: 'propA'; null: null };
|
||||
type TestOptionsEnum = 'A' | 'B' | 'C';
|
||||
type TestOptions = {
|
||||
str: string;
|
||||
strArrNull: string | Array<string> | null;
|
||||
nullbool: boolean | null;
|
||||
nested: {
|
||||
num: number;
|
||||
switch: boolean;
|
||||
abc: TestOptionsEnum;
|
||||
};
|
||||
obj: TestOptionsObj | null;
|
||||
abc: TestOptionsEnum;
|
||||
arr: Array<any>;
|
||||
func: () => void;
|
||||
};
|
||||
|
||||
const options: TestOptions = {
|
||||
str: 'hi',
|
||||
strArrNull: null,
|
||||
nullbool: true,
|
||||
nested: {
|
||||
num: 1,
|
||||
switch: false,
|
||||
abc: 'B',
|
||||
},
|
||||
obj: { propA: 'propA', null: null },
|
||||
abc: 'A',
|
||||
arr: [1, 2, 3],
|
||||
func: () => {},
|
||||
};
|
||||
|
||||
const template: OptionsTemplate<TestOptions> = {
|
||||
str: oTypes.string,
|
||||
strArrNull: [oTypes.string, oTypes.array, oTypes.null],
|
||||
nullbool: [oTypes.boolean, oTypes.null],
|
||||
nested: {
|
||||
num: oTypes.number,
|
||||
switch: oTypes.boolean,
|
||||
abc: 'A B C',
|
||||
},
|
||||
obj: [oTypes.object, oTypes.null],
|
||||
abc: 'A B C',
|
||||
arr: oTypes.array,
|
||||
func: oTypes.function,
|
||||
};
|
||||
|
||||
describe('options validation', () => {
|
||||
describe('object return & mutation', () => {
|
||||
test('foreign properties wont affect validated object', () => {
|
||||
const foreignObj = {
|
||||
foreignProp: 'foreign',
|
||||
foreignDeep: { a: 'A', b: 'B' },
|
||||
};
|
||||
const modifiedOptions = assignDeep({}, options, { nested: foreignObj }, foreignObj);
|
||||
const [validated, foreign] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated).toEqual(options);
|
||||
expect(foreign).toEqual({ ...foreignObj, nested: foreignObj });
|
||||
});
|
||||
|
||||
test('passed object isnt returned object', () => {
|
||||
const clonedOptions = assignDeep({}, options);
|
||||
const [validated, foreign] = validateOptions(template, clonedOptions);
|
||||
|
||||
expect(validated).not.toBe(clonedOptions);
|
||||
expect(foreign).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('foreign property return', () => {
|
||||
test('return no foreign property', () => {
|
||||
const [, foreign] = validateOptions(template, options);
|
||||
|
||||
expect(foreign).toEqual({});
|
||||
});
|
||||
|
||||
test('return signle non-object foreign property', () => {
|
||||
const foreignObj = { foreignProp: 'foreign' };
|
||||
const modifiedOptions = assignDeep({}, options, foreignObj);
|
||||
const [, foreign] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(foreign).toEqual(foreignObj);
|
||||
});
|
||||
|
||||
test('return complex foreign properties', () => {
|
||||
const foreignObj = {
|
||||
foreignProp: 'foreign',
|
||||
foreignDeep: { a: 'A', b: 'B' },
|
||||
};
|
||||
const modifiedOptions = assignDeep({}, options, foreignObj);
|
||||
const [, foreign] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(foreign).toEqual(foreignObj);
|
||||
});
|
||||
|
||||
test('return nested complex foreign properties', () => {
|
||||
const foreignObj = {
|
||||
foreignProp: 'foreign',
|
||||
foreignDeep: { a: 'A', b: 'B' },
|
||||
};
|
||||
const modifiedOptions = assignDeep({}, options, { nested: foreignObj }, foreignObj);
|
||||
const [, foreign] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(foreign.nested).toEqual(foreignObj);
|
||||
delete foreign.nested;
|
||||
expect(foreign).toEqual(foreignObj);
|
||||
});
|
||||
});
|
||||
|
||||
describe('value validity', () => {
|
||||
test('single value doesnt match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, { str: 1 });
|
||||
const [validated] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated).not.toHaveProperty('str');
|
||||
});
|
||||
|
||||
test('single enum value doesnt match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, { abc: 'testval' });
|
||||
const [validated] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated).not.toHaveProperty('abc');
|
||||
});
|
||||
|
||||
test('multiple values dont match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
str: 1,
|
||||
abc: 'testval',
|
||||
nullbool: 'string',
|
||||
});
|
||||
const [validated] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated).not.toHaveProperty('str');
|
||||
expect(validated).not.toHaveProperty('abc');
|
||||
expect(validated).not.toHaveProperty('nullbool');
|
||||
});
|
||||
|
||||
test('single nested value dont match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, { nested: { num: 'hi' } });
|
||||
const [validated] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated.nested).not.toHaveProperty('num');
|
||||
});
|
||||
|
||||
test('single nested enum value dont match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
nested: { abc: 'testabc' },
|
||||
});
|
||||
const [validated] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated.nested).not.toHaveProperty('abc');
|
||||
});
|
||||
|
||||
test('multiple nested values dont match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
nested: { num: 'hi', abc: 'testabc' },
|
||||
});
|
||||
const [validated] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated.nested).not.toHaveProperty('num');
|
||||
expect(validated.nested).not.toHaveProperty('abc');
|
||||
});
|
||||
|
||||
test('all nested values dont match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
nested: { num: 'hi', abc: 'testabc', switch: 1 },
|
||||
});
|
||||
const [validated] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated).not.toHaveProperty('nested');
|
||||
});
|
||||
|
||||
test('all nested values dont match template with foreign property', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
nested: {
|
||||
foreign: 'foreign',
|
||||
num: 'hi',
|
||||
abc: 'testabc',
|
||||
switch: 1,
|
||||
},
|
||||
});
|
||||
const [validated] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated).not.toHaveProperty('nested');
|
||||
});
|
||||
|
||||
test('various values dont match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
nested: { switch: null },
|
||||
obj: 1,
|
||||
abc: 'testest',
|
||||
func: {},
|
||||
});
|
||||
const [validated] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated.nested).not.toHaveProperty('switch');
|
||||
expect(validated).not.toHaveProperty('obj');
|
||||
expect(validated).not.toHaveProperty('abc');
|
||||
expect(validated).not.toHaveProperty('func');
|
||||
});
|
||||
|
||||
test('various values dont match template with foreign properties', () => {
|
||||
const foreignObj = {
|
||||
foreignProp: 'foreign',
|
||||
foreignDeep: { a: 'A', b: 'B' },
|
||||
};
|
||||
const modifiedOptions = assignDeep(
|
||||
{},
|
||||
options,
|
||||
{
|
||||
nested: { switch: null },
|
||||
obj: 1,
|
||||
abc: 'testest',
|
||||
func: {},
|
||||
},
|
||||
foreignObj,
|
||||
{ nested: foreignObj }
|
||||
);
|
||||
const [validated, foreign] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(foreign.nested).toEqual(foreignObj);
|
||||
delete foreign.nested;
|
||||
expect(foreign).toEqual(foreignObj);
|
||||
|
||||
expect(validated.nested).not.toHaveProperty('switch');
|
||||
expect(validated).not.toHaveProperty('obj');
|
||||
expect(validated).not.toHaveProperty('abc');
|
||||
expect(validated).not.toHaveProperty('func');
|
||||
});
|
||||
|
||||
test('nested object is string', () => {
|
||||
const modifiedOptions = assignDeep({}, options, { nested: 'string' });
|
||||
const [validated] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated).not.toHaveProperty('nested');
|
||||
});
|
||||
|
||||
test('nested object is null', () => {
|
||||
const modifiedOptions = assignDeep({}, options, { nested: null });
|
||||
const [validated] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated).not.toHaveProperty('nested');
|
||||
});
|
||||
|
||||
test('nested object is undefined', () => {
|
||||
const modifiedOptions: Partial<TestOptions> = assignDeep({}, options);
|
||||
modifiedOptions.nested = undefined;
|
||||
const [validated] = validateOptions(template, modifiedOptions);
|
||||
|
||||
expect(validated).not.toHaveProperty('nested');
|
||||
});
|
||||
});
|
||||
|
||||
describe('error logging', () => {
|
||||
test('dont log error if nothing is wrong', () => {
|
||||
const { warn } = console;
|
||||
console.warn = jest.fn();
|
||||
|
||||
validateOptions(template, options, true);
|
||||
expect(console.warn).not.toBeCalled();
|
||||
|
||||
console.warn = warn;
|
||||
});
|
||||
|
||||
test('dont log error if something is wrong and flag is false', () => {
|
||||
const { warn } = console;
|
||||
console.warn = jest.fn();
|
||||
|
||||
const modifiedOptions = assignDeep({}, options, { str: 1 });
|
||||
validateOptions(template, modifiedOptions, false);
|
||||
expect(console.warn).not.toBeCalled();
|
||||
|
||||
console.warn = warn;
|
||||
});
|
||||
|
||||
test('log error if something is wrong and flag is true', () => {
|
||||
const { warn } = console;
|
||||
console.warn = jest.fn();
|
||||
|
||||
// str must be string
|
||||
validateOptions(template, assignDeep({}, options, { str: 1 }), true);
|
||||
expect(console.warn).toBeCalledTimes(1);
|
||||
|
||||
// abc must be A | B | C
|
||||
validateOptions(template, assignDeep({}, options, { abc: 'some string' }), true);
|
||||
expect(console.warn).toBeCalledTimes(2);
|
||||
|
||||
// everthing OK
|
||||
validateOptions(template, assignDeep({}, options, { abc: 'C' }), true);
|
||||
expect(console.warn).toBeCalledTimes(2);
|
||||
|
||||
console.warn = warn;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,11 +8,9 @@ import { createStructureSetup, StructureSetup } from 'setups/structureSetup';
|
||||
import { isHTMLElement } from 'support';
|
||||
|
||||
const mockGetEnvironment = jest.fn();
|
||||
jest.mock('environment', () => {
|
||||
return {
|
||||
getEnvironment: jest.fn().mockImplementation(() => mockGetEnvironment()),
|
||||
};
|
||||
});
|
||||
jest.mock('environment', () => ({
|
||||
getEnvironment: jest.fn().mockImplementation(() => mockGetEnvironment()),
|
||||
}));
|
||||
|
||||
interface StructureSetupProxy {
|
||||
input: OSTarget | StructureInitialization;
|
||||
@@ -377,9 +375,11 @@ describe('structureSetup', () => {
|
||||
describe('complex', () => {
|
||||
describe('single assigned', () => {
|
||||
test('padding', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="padding">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -394,9 +394,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -411,9 +413,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="content">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -430,9 +434,11 @@ describe('structureSetup', () => {
|
||||
|
||||
describe('multiple assigned', () => {
|
||||
test('padding viewport content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="viewport"><div id="content">${content}</div></div></div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="padding"><div id="viewport"><div id="content">${content}</div></div></div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -449,9 +455,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('padding viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -467,9 +475,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('padding content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="content">${content}</div></div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="padding"><div id="content">${content}</div></div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -485,9 +495,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('viewport content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -599,9 +611,11 @@ describe('structureSetup', () => {
|
||||
|
||||
describe('mixed', () => {
|
||||
test('false: padding & content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -618,9 +632,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('true: padding & content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -637,9 +653,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('true: content | false: padding | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -656,9 +674,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('true: padding | false: content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -675,9 +695,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('false: padding | assigned: content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="content">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -693,9 +715,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('true: padding | assigned: content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="content">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -711,9 +735,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('false: padding | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -729,9 +755,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('true: padding | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -747,9 +775,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('false: padding | assigned: viewport & content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -766,9 +796,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('true: padding | assigned: viewport & content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -785,9 +817,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('false: content | assigned: padding', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="padding">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -803,9 +837,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('true: content | assigned: padding', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="padding">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -821,9 +857,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('false: content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -839,9 +877,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('true: content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -857,9 +897,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('false: content | assigned: padding & viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
@@ -876,9 +918,11 @@ describe('structureSetup', () => {
|
||||
});
|
||||
|
||||
test('true: content | assigned: padding & viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
|
||||
});
|
||||
const snapshot = fillBody(
|
||||
isTextarea,
|
||||
(content, hostId) =>
|
||||
`<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`
|
||||
);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
import { validateOptions, optionsTemplateTypes as oTypes, OptionsTemplate } from 'support/options';
|
||||
import { assignDeep, isEmptyObject } from 'support/utils';
|
||||
|
||||
type TestOptionsObj = { propA: 'propA'; null: null };
|
||||
type TestOptionsEnum = 'A' | 'B' | 'C';
|
||||
type TestOptions = {
|
||||
str: string;
|
||||
strArrNull: string | Array<string> | null;
|
||||
nullbool: boolean | null;
|
||||
nested: {
|
||||
num: number;
|
||||
switch: boolean;
|
||||
abc: TestOptionsEnum;
|
||||
};
|
||||
obj: TestOptionsObj | null;
|
||||
abc: TestOptionsEnum;
|
||||
arr: Array<any>;
|
||||
func: () => void;
|
||||
};
|
||||
|
||||
const options: TestOptions = {
|
||||
str: 'hi',
|
||||
strArrNull: null,
|
||||
nullbool: true,
|
||||
nested: {
|
||||
num: 1,
|
||||
switch: false,
|
||||
abc: 'B',
|
||||
},
|
||||
obj: { propA: 'propA', null: null },
|
||||
abc: 'A',
|
||||
arr: [1, 2, 3],
|
||||
func: () => {},
|
||||
};
|
||||
|
||||
const template: OptionsTemplate<TestOptions> = {
|
||||
str: oTypes.string,
|
||||
strArrNull: [oTypes.string, oTypes.array, oTypes.null],
|
||||
nullbool: [oTypes.boolean, oTypes.null],
|
||||
nested: {
|
||||
num: oTypes.number,
|
||||
switch: oTypes.boolean,
|
||||
abc: 'A B C',
|
||||
},
|
||||
obj: [oTypes.object, oTypes.null],
|
||||
abc: 'A B C',
|
||||
arr: oTypes.array,
|
||||
func: oTypes.function,
|
||||
};
|
||||
|
||||
describe('options validation', () => {
|
||||
describe('object return & mutation', () => {
|
||||
test('foreign properties wont affect validated object', () => {
|
||||
const foreignObj = {
|
||||
foreignProp: 'foreign',
|
||||
foreignDeep: { a: 'A', b: 'B' },
|
||||
};
|
||||
const modifiedOptions = assignDeep({}, options, { nested: foreignObj }, foreignObj);
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated).toEqual(options);
|
||||
});
|
||||
|
||||
test('passed objects arent mutated', () => {
|
||||
const clonedOptions = assignDeep({}, options);
|
||||
validateOptions(clonedOptions, template, clonedOptions);
|
||||
|
||||
expect(clonedOptions).toEqual(options);
|
||||
});
|
||||
|
||||
test('passed object isnt returned object', () => {
|
||||
const clonedOptions = assignDeep({}, options);
|
||||
const result = validateOptions(clonedOptions, template);
|
||||
|
||||
expect(result._validated).not.toBe(clonedOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('foreign property return', () => {
|
||||
test('return no foreign property', () => {
|
||||
const result = validateOptions(options, template);
|
||||
|
||||
expect(isEmptyObject(result._foreign)).toBe(true);
|
||||
});
|
||||
|
||||
test('return signle non-object foreign property', () => {
|
||||
const foreignObj = { foreignProp: 'foreign' };
|
||||
const modifiedOptions = assignDeep({}, options, foreignObj);
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _foreign } = result;
|
||||
|
||||
expect(_foreign).toEqual(foreignObj);
|
||||
});
|
||||
|
||||
test('return complex foreign properties', () => {
|
||||
const foreignObj = {
|
||||
foreignProp: 'foreign',
|
||||
foreignDeep: { a: 'A', b: 'B' },
|
||||
};
|
||||
const modifiedOptions = assignDeep({}, options, foreignObj);
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _foreign } = result;
|
||||
|
||||
expect(_foreign).toEqual(foreignObj);
|
||||
});
|
||||
|
||||
test('return nested complex foreign properties', () => {
|
||||
const foreignObj = {
|
||||
foreignProp: 'foreign',
|
||||
foreignDeep: { a: 'A', b: 'B' },
|
||||
};
|
||||
const modifiedOptions = assignDeep({}, options, { nested: foreignObj }, foreignObj);
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _foreign } = result;
|
||||
|
||||
expect(_foreign.nested).toEqual(foreignObj);
|
||||
delete _foreign.nested;
|
||||
expect(_foreign).toEqual(foreignObj);
|
||||
});
|
||||
});
|
||||
|
||||
describe('diff property return', () => {
|
||||
test('one value changed', () => {
|
||||
const modifiedOptions = assignDeep({}, options, { str: 'newvaluetest' });
|
||||
const result = validateOptions(modifiedOptions, template, options);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated.str).toBe('newvaluetest');
|
||||
delete _validated.str;
|
||||
expect(isEmptyObject(_validated)).toBe(true);
|
||||
});
|
||||
|
||||
test('multiple values changed', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
str: 'newvaluetest',
|
||||
nullbool: null,
|
||||
});
|
||||
const result = validateOptions(modifiedOptions, template, options);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated.str).toBe('newvaluetest');
|
||||
expect(_validated.nullbool).toBe(null);
|
||||
delete _validated.str;
|
||||
delete _validated.nullbool;
|
||||
expect(isEmptyObject(_validated)).toBe(true);
|
||||
});
|
||||
|
||||
test('one nested value changed', () => {
|
||||
const modifiedOptions = assignDeep({}, options, { nested: { num: -1293 } });
|
||||
const result = validateOptions(modifiedOptions, template, options);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated.nested?.num).toBe(-1293);
|
||||
delete _validated.nested?.num;
|
||||
expect(isEmptyObject(_validated.nested)).toBe(true);
|
||||
delete _validated.nested;
|
||||
expect(isEmptyObject(_validated)).toBe(true);
|
||||
});
|
||||
|
||||
test('multiple nested values changed', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
nested: { num: -1293, abc: 'C' },
|
||||
});
|
||||
const result = validateOptions(modifiedOptions, template, options);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated.nested?.num).toBe(-1293);
|
||||
expect(_validated.nested?.abc).toBe('C');
|
||||
delete _validated.nested?.num;
|
||||
delete _validated.nested?.abc;
|
||||
expect(isEmptyObject(_validated.nested)).toBe(true);
|
||||
delete _validated.nested;
|
||||
expect(isEmptyObject(_validated)).toBe(true);
|
||||
});
|
||||
|
||||
test('various values changed', () => {
|
||||
const newFunc = () => {};
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
str: 'newstrvalue',
|
||||
func: newFunc,
|
||||
abc: 'C',
|
||||
nested: { num: -1293, abc: 'C' },
|
||||
});
|
||||
const result = validateOptions(modifiedOptions, template, options);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated.str).toBe('newstrvalue');
|
||||
expect(_validated.func).toBe(newFunc);
|
||||
expect(_validated.abc).toBe('C');
|
||||
delete _validated.str;
|
||||
delete _validated.func;
|
||||
delete _validated.abc;
|
||||
expect(_validated.nested?.num).toBe(-1293);
|
||||
expect(_validated.nested?.abc).toBe('C');
|
||||
delete _validated.nested?.num;
|
||||
delete _validated.nested?.abc;
|
||||
expect(isEmptyObject(_validated.nested)).toBe(true);
|
||||
delete _validated.nested;
|
||||
expect(isEmptyObject(_validated)).toBe(true);
|
||||
});
|
||||
|
||||
test('various values changed with foreign properties', () => {
|
||||
const foreignObj = {
|
||||
foreignProp: 'foreign',
|
||||
foreignDeep: { a: 'A', b: 'B' },
|
||||
};
|
||||
const newFunc = () => {};
|
||||
const modifiedOptions = assignDeep(
|
||||
{},
|
||||
options,
|
||||
{
|
||||
str: 'newstrvalue',
|
||||
func: newFunc,
|
||||
abc: 'C',
|
||||
nested: { num: -1293, abc: 'C' },
|
||||
},
|
||||
foreignObj,
|
||||
{ nested: foreignObj }
|
||||
);
|
||||
const result = validateOptions(modifiedOptions, template, options);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated.str).toBe('newstrvalue');
|
||||
expect(_validated.func).toBe(newFunc);
|
||||
expect(_validated.abc).toBe('C');
|
||||
delete _validated.str;
|
||||
delete _validated.func;
|
||||
delete _validated.abc;
|
||||
expect(_validated.nested?.num).toBe(-1293);
|
||||
expect(_validated.nested?.abc).toBe('C');
|
||||
delete _validated.nested?.num;
|
||||
delete _validated.nested?.abc;
|
||||
expect(isEmptyObject(_validated.nested)).toBe(true);
|
||||
delete _validated.nested;
|
||||
expect(isEmptyObject(_validated)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('value validity', () => {
|
||||
test('single value doesnt match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, { str: 1 });
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated).not.toHaveProperty('str');
|
||||
});
|
||||
|
||||
test('single enum value doesnt match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, { abc: 'testval' });
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated).not.toHaveProperty('abc');
|
||||
});
|
||||
|
||||
test('multiple values dont match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
str: 1,
|
||||
abc: 'testval',
|
||||
nullbool: 'string',
|
||||
});
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated).not.toHaveProperty('str');
|
||||
expect(_validated).not.toHaveProperty('abc');
|
||||
expect(_validated).not.toHaveProperty('nullbool');
|
||||
});
|
||||
|
||||
test('single nested value dont match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, { nested: { num: 'hi' } });
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated.nested).not.toHaveProperty('num');
|
||||
});
|
||||
|
||||
test('single nested enum value dont match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
nested: { abc: 'testabc' },
|
||||
});
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated.nested).not.toHaveProperty('abc');
|
||||
});
|
||||
|
||||
test('multiple nested values dont match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
nested: { num: 'hi', abc: 'testabc' },
|
||||
});
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated.nested).not.toHaveProperty('num');
|
||||
expect(_validated.nested).not.toHaveProperty('abc');
|
||||
});
|
||||
|
||||
test('all nested values dont match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
nested: { num: 'hi', abc: 'testabc', switch: 1 },
|
||||
});
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated).not.toHaveProperty('nested');
|
||||
});
|
||||
|
||||
test('all nested values dont match template with foreign property', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
nested: {
|
||||
foreign: 'foreign',
|
||||
num: 'hi',
|
||||
abc: 'testabc',
|
||||
switch: 1,
|
||||
},
|
||||
});
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated).not.toHaveProperty('nested');
|
||||
});
|
||||
|
||||
test('various values dont match template', () => {
|
||||
const modifiedOptions = assignDeep({}, options, {
|
||||
nested: { switch: null },
|
||||
obj: 1,
|
||||
abc: 'testest',
|
||||
func: {},
|
||||
});
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated.nested).not.toHaveProperty('switch');
|
||||
expect(_validated).not.toHaveProperty('obj');
|
||||
expect(_validated).not.toHaveProperty('abc');
|
||||
expect(_validated).not.toHaveProperty('func');
|
||||
});
|
||||
|
||||
test('various values dont match template with foreign properties', () => {
|
||||
const foreignObj = {
|
||||
foreignProp: 'foreign',
|
||||
foreignDeep: { a: 'A', b: 'B' },
|
||||
};
|
||||
const modifiedOptions = assignDeep(
|
||||
{},
|
||||
options,
|
||||
{
|
||||
nested: { switch: null },
|
||||
obj: 1,
|
||||
abc: 'testest',
|
||||
func: {},
|
||||
},
|
||||
foreignObj,
|
||||
{ nested: foreignObj }
|
||||
);
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated, _foreign } = result;
|
||||
|
||||
expect(_foreign.nested).toEqual(foreignObj);
|
||||
delete _foreign.nested;
|
||||
expect(_foreign).toEqual(foreignObj);
|
||||
|
||||
expect(_validated.nested).not.toHaveProperty('switch');
|
||||
expect(_validated).not.toHaveProperty('obj');
|
||||
expect(_validated).not.toHaveProperty('abc');
|
||||
expect(_validated).not.toHaveProperty('func');
|
||||
});
|
||||
|
||||
test('nested object is string', () => {
|
||||
const modifiedOptions = assignDeep({}, options, { nested: 'string' });
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated).not.toHaveProperty('nested');
|
||||
});
|
||||
|
||||
test('nested object is null', () => {
|
||||
const modifiedOptions = assignDeep({}, options, { nested: null });
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated).not.toHaveProperty('nested');
|
||||
});
|
||||
|
||||
test('nested object is undefined', () => {
|
||||
const modifiedOptions: Partial<TestOptions> = assignDeep({}, options);
|
||||
modifiedOptions.nested = undefined;
|
||||
const result = validateOptions(modifiedOptions, template);
|
||||
const { _validated } = result;
|
||||
|
||||
expect(_validated).not.toHaveProperty('nested');
|
||||
});
|
||||
});
|
||||
|
||||
describe('error logging', () => {
|
||||
test('dont log error if nothing is wrong', () => {
|
||||
const { warn } = console;
|
||||
console.warn = jest.fn();
|
||||
|
||||
validateOptions(options, template, null, true);
|
||||
expect(console.warn).not.toBeCalled();
|
||||
|
||||
console.warn = warn;
|
||||
});
|
||||
|
||||
test('dont log error if something is wrong and flag is false', () => {
|
||||
const { warn } = console;
|
||||
console.warn = jest.fn();
|
||||
|
||||
const modifiedOptions = assignDeep({}, options, { str: 1 });
|
||||
validateOptions(modifiedOptions, template, null, false);
|
||||
expect(console.warn).not.toBeCalled();
|
||||
|
||||
console.warn = warn;
|
||||
});
|
||||
|
||||
test('log error if something is wrong and flag is true', () => {
|
||||
const { warn } = console;
|
||||
console.warn = jest.fn();
|
||||
|
||||
// str must be string
|
||||
validateOptions(assignDeep({}, options, { str: 1 }), template, null, true);
|
||||
expect(console.warn).toBeCalledTimes(1);
|
||||
|
||||
// abc must be A | B | C
|
||||
validateOptions(assignDeep({}, options, { abc: 'some string' }), template, null, true);
|
||||
expect(console.warn).toBeCalledTimes(2);
|
||||
|
||||
// everthing OK
|
||||
validateOptions(assignDeep({}, options, { abc: 'C' }), template, null, true);
|
||||
expect(console.warn).toBeCalledTimes(2);
|
||||
|
||||
console.warn = warn;
|
||||
});
|
||||
});
|
||||
});
|
||||
+9
-4
@@ -1,3 +1,6 @@
|
||||
type PartialOptions<T> = {
|
||||
[P in keyof T]?: T[P] extends Record<string, unknown> ? PartialOptions<T[P]> : T[P];
|
||||
};
|
||||
type OSTargetElement = HTMLElement | HTMLTextAreaElement;
|
||||
/**
|
||||
* Static elements MUST be present.
|
||||
@@ -38,10 +41,6 @@ interface ScrollbarsInitialization {
|
||||
interface OSInitializationObject extends StructureInitialization, ScrollbarsInitialization {
|
||||
}
|
||||
type OSTarget = OSTargetElement | OSInitializationObject;
|
||||
type OptionsObjectType = Record<string, unknown>;
|
||||
type PartialOptions<T> = {
|
||||
[P in keyof T]?: T[P] extends OptionsObjectType ? PartialOptions<T[P]> : T[P];
|
||||
};
|
||||
type ResizeBehavior = "none" | "both" | "horizontal" | "vertical";
|
||||
type OverflowBehavior = "hidden" | "scroll" | "visible" | "visible-hidden";
|
||||
type VisibilityBehavior = "visible" | "hidden" | "auto";
|
||||
@@ -85,8 +84,14 @@ interface OSOptions {
|
||||
onUpdated: (() => any) | null;
|
||||
};
|
||||
}
|
||||
type OSPluginInstance = Record<string, unknown> | ((staticObj: OverlayScrollbarsStatic, instanceObj: OverlayScrollbars) => void);
|
||||
type OSPlugin<T extends OSPluginInstance = OSPluginInstance> = [
|
||||
string,
|
||||
T
|
||||
];
|
||||
interface OverlayScrollbarsStatic {
|
||||
(target: OSTarget | OSInitializationObject, options?: PartialOptions<OSOptions>, extensions?: any): OverlayScrollbars;
|
||||
extend(osPlugin: OSPlugin | OSPlugin[]): void;
|
||||
}
|
||||
interface OverlayScrollbars {
|
||||
options(): OSOptions;
|
||||
|
||||
Reference in New Issue
Block a user