UNPKG

vscroll

Version:
1,703 lines (1,680 loc) 171 kB
/** * vscroll (https://github.com/dhilt/vscroll) FESM2015 * Version: 1.8.0 (2025-11-28T14:24:43.799Z) * Author: Denis Hilt * License: MIT */ class Reactive { constructor(value, options) { this.id = 0; if (value !== void 0) { this.value = value; this.initialValue = value; } this.options = options || {}; this.subscriptions = new Map(); } set(value) { if (this.value === value && !this.options.emitEqual) { return; } this.value = value; for (const [, sub] of this.subscriptions) { sub.emit(value); if (this.value !== value) { break; } } } get() { return this.value; } on(func) { const id = this.id++; const subscription = { emit: func, off: () => { subscription.emit = () => null; this.subscriptions.delete(id); } }; this.subscriptions.set(id, subscription); if (this.options.emitOnSubscribe) { subscription.emit(this.value); } return () => subscription.off(); } once(func) { const off = this.on(v => { off(); func(v); }); return off; } reset() { this.set(this.initialValue); } dispose() { this.subscriptions.forEach(sub => sub.off()); } } var AdapterPropName; (function (AdapterPropName) { AdapterPropName["id"] = "id"; AdapterPropName["mock"] = "mock"; AdapterPropName["augmented"] = "augmented"; AdapterPropName["version"] = "version"; AdapterPropName["init"] = "init"; AdapterPropName["init$"] = "init$"; AdapterPropName["packageInfo"] = "packageInfo"; AdapterPropName["itemsCount"] = "itemsCount"; AdapterPropName["bufferInfo"] = "bufferInfo"; AdapterPropName["isLoading"] = "isLoading"; AdapterPropName["isLoading$"] = "isLoading$"; AdapterPropName["loopPending"] = "loopPending"; AdapterPropName["loopPending$"] = "loopPending$"; AdapterPropName["firstVisible"] = "firstVisible"; AdapterPropName["firstVisible$"] = "firstVisible$"; AdapterPropName["lastVisible"] = "lastVisible"; AdapterPropName["lastVisible$"] = "lastVisible$"; AdapterPropName["bof"] = "bof"; AdapterPropName["bof$"] = "bof$"; AdapterPropName["eof"] = "eof"; AdapterPropName["eof$"] = "eof$"; AdapterPropName["paused"] = "paused"; AdapterPropName["paused$"] = "paused$"; AdapterPropName["reset"] = "reset"; AdapterPropName["reload"] = "reload"; AdapterPropName["append"] = "append"; AdapterPropName["prepend"] = "prepend"; AdapterPropName["check"] = "check"; AdapterPropName["remove"] = "remove"; AdapterPropName["clip"] = "clip"; AdapterPropName["insert"] = "insert"; AdapterPropName["replace"] = "replace"; AdapterPropName["update"] = "update"; AdapterPropName["pause"] = "pause"; AdapterPropName["resume"] = "resume"; AdapterPropName["fix"] = "fix"; AdapterPropName["relax"] = "relax"; AdapterPropName["showLog"] = "showLog"; })(AdapterPropName || (AdapterPropName = {})); var AdapterPropType; (function (AdapterPropType) { AdapterPropType[AdapterPropType["Scalar"] = 0] = "Scalar"; AdapterPropType[AdapterPropType["Reactive"] = 1] = "Reactive"; AdapterPropType[AdapterPropType["WorkflowRunner"] = 2] = "WorkflowRunner"; AdapterPropType[AdapterPropType["Function"] = 3] = "Function"; })(AdapterPropType || (AdapterPropType = {})); const Name = AdapterPropName; const Type = AdapterPropType; const noop = () => null; const methodPreResult = { immediate: true, success: true, details: 'Adapter is not initialized' }; const methodPausedResult = { immediate: true, success: true, details: 'Scroller is paused' }; const noopWF = () => Promise.resolve(methodPreResult); const emptyPackageInfo = { core: { name: '', version: '' }, consumer: { name: '', version: '' } }; const bufferInfoDefault = { firstIndex: NaN, lastIndex: NaN, minIndex: NaN, maxIndex: NaN, absMinIndex: -Infinity, absMaxIndex: +Infinity, defaultSize: NaN }; const EMPTY_ITEM = { data: {}, element: {} }; const getDefaultAdapterProps = () => [ { type: Type.Scalar, name: Name.id, value: 0, permanent: true }, { type: Type.Scalar, name: Name.mock, value: true, permanent: true }, { type: Type.Scalar, name: Name.augmented, value: false, permanent: true }, { type: Type.Scalar, name: Name.version, value: '', permanent: true }, { type: Type.Scalar, name: Name.init, value: false, reactive: Name.init$ }, { type: Type.Scalar, name: Name.packageInfo, value: emptyPackageInfo, onDemand: true }, { type: Type.Scalar, name: Name.itemsCount, value: 0, onDemand: true }, { type: Type.Scalar, name: Name.bufferInfo, value: bufferInfoDefault, onDemand: true }, { type: Type.Scalar, name: Name.isLoading, value: false, reactive: Name.isLoading$ }, { type: Type.Scalar, name: Name.loopPending, value: false, reactive: Name.loopPending$ }, { type: Type.Scalar, name: Name.firstVisible, value: EMPTY_ITEM, reactive: Name.firstVisible$, wanted: true }, { type: Type.Scalar, name: Name.lastVisible, value: EMPTY_ITEM, reactive: Name.lastVisible$, wanted: true }, { type: Type.Scalar, name: Name.bof, value: false, reactive: Name.bof$ }, { type: Type.Scalar, name: Name.eof, value: false, reactive: Name.eof$ }, { type: Type.Scalar, name: Name.paused, value: false, reactive: Name.paused$ }, { type: Type.WorkflowRunner, name: Name.reset, value: noopWF, allowedWhenPaused: true }, { type: Type.WorkflowRunner, name: Name.reload, value: noopWF }, { type: Type.WorkflowRunner, name: Name.append, value: noopWF }, { type: Type.WorkflowRunner, name: Name.prepend, value: noopWF }, { type: Type.WorkflowRunner, name: Name.check, value: noopWF }, { type: Type.WorkflowRunner, name: Name.remove, value: noopWF }, { type: Type.WorkflowRunner, name: Name.clip, value: noopWF }, { type: Type.WorkflowRunner, name: Name.insert, value: noopWF }, { type: Type.WorkflowRunner, name: Name.replace, value: noopWF }, { type: Type.WorkflowRunner, name: Name.update, value: noopWF }, { type: Type.WorkflowRunner, name: Name.pause, value: noopWF }, { type: Type.WorkflowRunner, name: Name.resume, value: noopWF, allowedWhenPaused: true }, { type: Type.WorkflowRunner, name: Name.fix, value: noopWF }, { type: Type.Function, name: Name.relax, value: noop }, { type: Type.Function, name: Name.showLog, value: noop }, { type: Type.Reactive, name: Name.init$, value: new Reactive() }, { type: Type.Reactive, name: Name.isLoading$, value: new Reactive() }, { type: Type.Reactive, name: Name.loopPending$, value: new Reactive() }, { type: Type.Reactive, name: Name.firstVisible$, value: new Reactive(EMPTY_ITEM, { emitOnSubscribe: true }), wanted: true }, { type: Type.Reactive, name: Name.lastVisible$, value: new Reactive(EMPTY_ITEM, { emitOnSubscribe: true }), wanted: true }, { type: Type.Reactive, name: Name.bof$, value: new Reactive() }, { type: Type.Reactive, name: Name.eof$, value: new Reactive() }, { type: Type.Reactive, name: Name.paused$, value: new Reactive() } ]; const reactiveConfigStorage = new Map(); var core = { name: 'vscroll', version: '1.8.0' }; const getBox = (id) => { var _a; return (_a = wantedStorage.get(id || -1)) === null || _a === void 0 ? void 0 : _a.box; }; const setBox = ({ name, wanted }, id) => { const Wanted = wantedStorage.get(id || -1); if (wanted && Wanted && !Wanted.box[name] && !Wanted.block) { const { firstVisible: a, firstVisible$: a$ } = AdapterPropName; const { lastVisible: b, lastVisible$: b$ } = AdapterPropName; Wanted.box[a] = Wanted.box[a$] = [a, a$].some(n => n === name) || Wanted.box[a]; Wanted.box[b] = Wanted.box[b$] = [b, b$].some(n => n === name) || Wanted.box[b]; return true; } return false; }; const setBlock = (value, id) => { const Wanted = wantedStorage.get(id || -1); if (Wanted) { Wanted.block = value; } }; const wantedUtils = { getBox, setBox, setBlock }; const wantedStorage = new Map(); let instanceCount$1 = 0; class AdapterContext { constructor(config) { const { mock, reactive } = config; const id = ++instanceCount$1; const conf = { configurable: true }; const reactivePropsStore = {}; wantedStorage.set(id, { box: {}, block: false }); // set up permanent props Object.defineProperty(this, AdapterPropName.id, Object.assign({ get: () => id }, conf)); Object.defineProperty(this, AdapterPropName.mock, Object.assign({ get: () => mock }, conf)); Object.defineProperty(this, AdapterPropName.augmented, Object.assign({ get: () => false }, conf)); Object.defineProperty(this, AdapterPropName.version, Object.assign({ get: () => core.version }, conf)); // set up default props, they will be reassigned during the Adapter instantiation getDefaultAdapterProps() .filter(({ permanent }) => !permanent) .forEach(prop => { let { value } = prop; // reactive props might be reconfigured by the vscroll consumer if (reactive && prop.type === AdapterPropType.Reactive) { const react = reactive[prop.name]; if (react) { // here we have a configured reactive property that came from the outer config // this prop must be exposed via Adapter, but at the same time we need to // persist the original default value as it will be used by the Adapter internally reactivePropsStore[prop.name] = Object.assign(Object.assign({}, react), { default: value // persisting the default native Reactive prop }); value = react.source; // exposing the configured prop instead of the default one } } Object.defineProperty(this, prop.name, Object.assign({ get: () => { wantedUtils.setBox(prop, id); return value; } }, conf)); }); if (reactive) { // save both configured and default reactive props in the store reactiveConfigStorage.set(id, reactivePropsStore); } } } const getDefaultAdapterConfig = () => { const reactive = getDefaultAdapterProps() .filter(({ type }) => type === AdapterPropType.Reactive) .reduce((acc, { name, value }) => { acc[name] = { source: value, emit: (source, val) => source.set(val) }; return acc; }, {}); return { mock: false, reactive }; }; class DatasourceGeneric { constructor(datasource, config) { this.get = datasource.get; this.settings = datasource.settings; this.devSettings = datasource.devSettings; const adapterContext = new AdapterContext(config || { mock: false }); this.adapter = adapterContext; } // todo: should it be published? dispose() { reactiveConfigStorage.delete(this.adapter.id); wantedStorage.delete(this.adapter.id); } } const makeDatasource = (getAdapterConfig) => class extends DatasourceGeneric { constructor(datasource) { const config = typeof getAdapterConfig === 'function' ? getAdapterConfig() : getDefaultAdapterConfig(); super(datasource, config); } }; const Datasource = makeDatasource(); var Direction; (function (Direction) { Direction["forward"] = "forward"; Direction["backward"] = "backward"; })(Direction || (Direction = {})); var SizeStrategy; (function (SizeStrategy) { SizeStrategy["Average"] = "average"; SizeStrategy["Constant"] = "constant"; SizeStrategy["Frequent"] = "frequent"; })(SizeStrategy || (SizeStrategy = {})); var ValidatorType; (function (ValidatorType) { ValidatorType["number"] = "must be a number"; ValidatorType["integer"] = "must be an integer"; ValidatorType["integerUnlimited"] = "must be an integer or infinity"; ValidatorType["moreOrEqual"] = "must be a number greater than (or equal to) {arg1}"; ValidatorType["itemList"] = "must be an array of items {arg1}"; ValidatorType["boolean"] = "must be a boolean"; ValidatorType["object"] = "must be an object"; ValidatorType["element"] = "must be an html element"; ValidatorType["function"] = "must be a function"; ValidatorType["funcOfxArguments"] = "must have {arg1} argument(s)"; ValidatorType["funcOfxAndMoreArguments"] = "must have at least {arg1} argument(s)"; ValidatorType["funcOfXToYArguments"] = "must have {arg1} to {arg2} arguments"; ValidatorType["oneOfCan"] = "can be present as only one item of {arg1} list"; ValidatorType["oneOfMust"] = "must be present as only one item of {arg1} list"; ValidatorType["or"] = "must satisfy at least 1 validator from {arg1} list"; ValidatorType["enum"] = "must belong to {arg1} list"; })(ValidatorType || (ValidatorType = {})); const getError = (msg, args) => (args || ['']).reduce((acc, arg, index) => acc.replace(`{arg${index + 1}}`, arg), msg); const getNumber = (value) => typeof value === 'number' || (typeof value === 'string' && value !== '') ? Number(value) : NaN; const onNumber = (value) => { const parsedValue = getNumber(value); const errors = []; if (Number.isNaN(parsedValue)) { errors.push(ValidatorType.number); } return { value: parsedValue, isSet: true, isValid: !errors.length, errors }; }; const onInteger = (value) => { const errors = []; value = getNumber(value); const parsedValue = parseInt(String(value), 10); if (value !== parsedValue) { errors.push(ValidatorType.integer); } return { value: parsedValue, isSet: true, isValid: !errors.length, errors }; }; const onIntegerUnlimited = (value) => { let parsedValue = value; const errors = []; value = getNumber(value); if (!Number.isFinite(value)) { parsedValue = value; } else { parsedValue = parseInt(String(value), 10); } if (value !== parsedValue) { errors.push(ValidatorType.integerUnlimited); } return { value: parsedValue, isSet: true, isValid: !errors.length, errors }; }; const onMoreOrEqual = (limit, fallback) => (value) => { const result = onNumber(value); if (!result.isValid) { return result; } let parsedValue = result.value; const errors = []; if (parsedValue < limit) { if (!fallback) { errors.push(getError(ValidatorType.moreOrEqual, [String(limit)])); } else { parsedValue = limit; } } return { value: parsedValue, isSet: true, isValid: !errors.length, errors }; }; const onBoolean = (value) => { const errors = []; let parsedValue = value; if (value === 'true') { parsedValue = true; } else if (value === 'false') { parsedValue = false; } if (typeof parsedValue !== 'boolean') { errors.push(ValidatorType.boolean); } return { value: parsedValue, isSet: true, isValid: !errors.length, errors }; }; const onObject = (value) => { const errors = []; if (!value || Object.prototype.toString.call(value) !== '[object Object]') { errors.push(ValidatorType.object); } return { value, isSet: true, isValid: !errors.length, errors }; }; const onHtmlElement = (value) => { const errors = []; if (!(value instanceof Element) && !(value instanceof Document)) { errors.push(ValidatorType.element); } return { value, isSet: true, isValid: !errors.length, errors }; }; const onItemList = (value) => { let parsedValue = value; const errors = []; if (!Array.isArray(value)) { errors.push(ValidatorType.itemList); parsedValue = []; } else if (!value.length) { errors.push(getError(ValidatorType.itemList, ['with at least 1 item'])); } else if (value.length > 1) { const type = typeof value[0]; for (let i = value.length - 1; i >= 0; i--) { if (typeof value[i] !== type) { errors.push(getError(ValidatorType.itemList, ['of items of the same type'])); break; } } } return { value: parsedValue, isSet: true, isValid: !errors.length, errors }; }; const onFunction = (value) => { const errors = []; if (typeof value !== 'function') { errors.push(ValidatorType.function); } return { value: value, isSet: true, isValid: !errors.length, errors }; }; const onFunctionWithXArguments = (argsCount) => (value) => { const result = onFunction(value); if (!result.isValid) { return result; } value = result.value; const errors = []; if (value.length !== argsCount) { errors.push(getError(ValidatorType.funcOfxArguments, [String(argsCount)])); } return { value: value, isSet: true, isValid: !errors.length, errors }; }; const onFunctionWithXAndMoreArguments = (argsCount) => (value) => { const result = onFunction(value); if (!result.isValid) { return result; } value = result.value; const errors = []; if (value.length < argsCount) { errors.push(getError(ValidatorType.funcOfxArguments, [String(argsCount)])); } return { value: value, isSet: true, isValid: !errors.length, errors }; }; const onFunctionWithXToYArguments = (from, to) => (value) => { const result = onFunction(value); if (!result.isValid) { return result; } value = result.value; const errors = []; if (value.length < from || value.length > to) { errors.push(getError(ValidatorType.funcOfXToYArguments, [String(from), String(to)])); } return { value: value, isSet: true, isValid: !errors.length, errors }; }; const onOneOf = (tokens, must) => (value, context) => { const errors = []; const isSet = value !== void 0; let noOneIsPresent = !isSet; const err = must ? ValidatorType.oneOfMust : ValidatorType.oneOfCan; if (!Array.isArray(tokens) || !tokens.length) { errors.push(getError(err, ['undefined'])); } else { for (let i = tokens.length - 1; i >= 0; i--) { const token = tokens[i]; if (typeof token !== 'string') { errors.push(getError(err, [tokens.join('", "')]) + ' (non-string token)'); break; } const isAnotherPresent = context && Object.prototype.hasOwnProperty.call(context, token); if (isSet && isAnotherPresent) { errors.push(getError(err, [tokens.join('", "')]) + ` (${token} is present)`); break; } if (noOneIsPresent && isAnotherPresent) { noOneIsPresent = false; } } if (must && noOneIsPresent) { errors.push(getError(err, [tokens.join('", "')])); } } return { value, isSet, isValid: !errors.length, errors }; }; const onOr = (validators) => (value) => { const errors = []; if (validators.every(validator => !validator.method(value).isValid)) { errors.push(validators.map(v => v.type).join(' OR ')); } return { value, isSet: true, isValid: !errors.length, errors }; }; const onEnum = (list) => (value) => { const errors = []; const values = Object.keys(list) .filter(k => isNaN(Number(k))) .map(k => list[k]); if (!values.some(item => item === value)) { errors.push(getError(ValidatorType.enum, ['[' + values.join(',') + ']'])); } return { value, isSet: true, isValid: !errors.length, errors }; }; const VALIDATORS = { NUMBER: { type: ValidatorType.number, method: onNumber }, INTEGER: { type: ValidatorType.integer, method: onInteger }, INTEGER_UNLIMITED: { type: ValidatorType.integerUnlimited, method: onIntegerUnlimited }, MORE_OR_EQUAL: (limit, fallback) => ({ type: ValidatorType.moreOrEqual, method: onMoreOrEqual(limit, fallback) }), BOOLEAN: { type: ValidatorType.boolean, method: onBoolean }, OBJECT: { type: ValidatorType.object, method: onObject }, ITEM_LIST: { type: ValidatorType.itemList, method: onItemList }, ELEMENT: { type: ValidatorType.element, method: onHtmlElement }, FUNC: { type: ValidatorType.function, method: onFunction }, FUNC_WITH_X_ARGUMENTS: (count) => ({ type: ValidatorType.funcOfxArguments, method: onFunctionWithXArguments(count) }), FUNC_WITH_X_AND_MORE_ARGUMENTS: (count) => ({ type: ValidatorType.funcOfxAndMoreArguments, method: onFunctionWithXAndMoreArguments(count) }), FUNC_WITH_X_TO_Y_ARGUMENTS: (from, to) => ({ type: ValidatorType.funcOfXToYArguments, method: onFunctionWithXToYArguments(from, to) }), ONE_OF_CAN: (list) => ({ type: ValidatorType.oneOfCan, method: onOneOf(list, false) }), ONE_OF_MUST: (list) => ({ type: ValidatorType.oneOfMust, method: onOneOf(list, true) }), OR: (list) => ({ type: ValidatorType.or, method: onOr(list) }), ENUM: (list) => ({ type: ValidatorType.enum, method: onEnum(list) }) }; class ValidatedData { constructor(context) { this.params = {}; this.contextErrors = []; this.errors = []; this.isValid = true; this.setContext(context); } setContext(context) { if (!context || Object.prototype.toString.call(context) !== '[object Object]') { this.setCommonError('context is not an object'); this.isValidContext = false; } else { this.isValidContext = true; } this.context = context; } setValidity() { this.errors = Object.keys(this.params).reduce((acc, key) => [...acc, ...this.params[key].errors], []); this.isValid = !this.errors.length; } setCommonError(error) { this.contextErrors.push(error); this.errors.push(error); this.isValid = false; } setParam(token, value) { if (!value.isValid) { value.errors = !value.isSet ? [`"${token}" must be set`] : value.errors.map((err) => `"${token}" ${err}`); } this.params[token] = value; this.setValidity(); } showErrors() { return this.errors.length ? 'validation failed: ' + this.errors.join(', ') : ''; } } const runValidator = (current, validator, context) => { const { value, errors } = current; const result = validator.method(value, context); const _errors = [...errors, ...result.errors]; return { value: result.value, isSet: result.isSet, isValid: !_errors.length, errors: _errors }; }; const getDefault = (value, prop) => { const empty = value === void 0; const auto = !prop.mandatory && prop.defaultValue !== void 0; return { value: !empty ? value : auto ? prop.defaultValue : void 0, isSet: !empty || auto, isValid: !empty || !prop.mandatory, errors: [] }; }; const validateOne = (context, name, prop) => { const result = getDefault(context[name], prop); if (!result.isSet) { const oneOfMust = prop.validators.find(v => v.type === ValidatorType.oneOfMust); if (oneOfMust) { return runValidator(result, oneOfMust, context); } } else { for (const validator of Object.values(prop.validators)) { const current = runValidator(result, validator, context); if (!current.isValid && prop.defaultValue !== void 0) { return { value: prop.defaultValue, isSet: true, isValid: true, errors: [] }; } Object.assign(result, current); } } return result; }; const validate = (context, params) => { const data = new ValidatedData(context); Object.entries(params).forEach(([key, prop]) => data.setParam(key, data.isValidContext ? validateOne(data.context, key, prop) : getDefault(void 0, prop))); return data; }; const { OBJECT: OBJECT$2, FUNC_WITH_X_AND_MORE_ARGUMENTS: FUNC_WITH_X_AND_MORE_ARGUMENTS$1 } = VALIDATORS; var DatasourceProps; (function (DatasourceProps) { DatasourceProps["get"] = "get"; DatasourceProps["settings"] = "settings"; DatasourceProps["devSettings"] = "devSettings"; })(DatasourceProps || (DatasourceProps = {})); const DATASOURCE = { [DatasourceProps.get]: { validators: [FUNC_WITH_X_AND_MORE_ARGUMENTS$1(2)], mandatory: true }, [DatasourceProps.settings]: { validators: [OBJECT$2] }, [DatasourceProps.devSettings]: { validators: [OBJECT$2] } }; const { NUMBER, INTEGER: INTEGER$1, INTEGER_UNLIMITED: INTEGER_UNLIMITED$1, MORE_OR_EQUAL, BOOLEAN: BOOLEAN$1, ELEMENT: ELEMENT$1, FUNC: FUNC$1, OR: OR$1, ENUM } = VALIDATORS; var Settings$1; (function (Settings) { Settings["adapter"] = "adapter"; Settings["startIndex"] = "startIndex"; Settings["minIndex"] = "minIndex"; Settings["maxIndex"] = "maxIndex"; Settings["itemSize"] = "itemSize"; Settings["bufferSize"] = "bufferSize"; Settings["padding"] = "padding"; Settings["infinite"] = "infinite"; Settings["horizontal"] = "horizontal"; Settings["windowViewport"] = "windowViewport"; Settings["viewportElement"] = "viewportElement"; Settings["inverse"] = "inverse"; Settings["onBeforeClip"] = "onBeforeClip"; Settings["sizeStrategy"] = "sizeStrategy"; })(Settings$1 || (Settings$1 = {})); var DevSettings; (function (DevSettings) { DevSettings["debug"] = "debug"; DevSettings["immediateLog"] = "immediateLog"; DevSettings["logProcessRun"] = "logProcessRun"; DevSettings["logTime"] = "logTime"; DevSettings["logColor"] = "logColor"; DevSettings["throttle"] = "throttle"; DevSettings["initDelay"] = "initDelay"; DevSettings["initWindowDelay"] = "initWindowDelay"; DevSettings["cacheData"] = "cacheData"; DevSettings["cacheOnReload"] = "cacheOnReload"; DevSettings["dismissOverflowAnchor"] = "dismissOverflowAnchor"; DevSettings["directionPriority"] = "directionPriority"; })(DevSettings || (DevSettings = {})); const MIN = { [Settings$1.itemSize]: 1, [Settings$1.bufferSize]: 1, [Settings$1.padding]: 0.01, [DevSettings.throttle]: 0, [DevSettings.initDelay]: 0, [DevSettings.initWindowDelay]: 0 }; const SETTINGS = { [Settings$1.adapter]: { validators: [BOOLEAN$1], defaultValue: false }, [Settings$1.startIndex]: { validators: [INTEGER$1], defaultValue: 1 }, [Settings$1.minIndex]: { validators: [INTEGER_UNLIMITED$1], defaultValue: -Infinity }, [Settings$1.maxIndex]: { validators: [INTEGER_UNLIMITED$1], defaultValue: Infinity }, [Settings$1.itemSize]: { validators: [INTEGER$1, MORE_OR_EQUAL(MIN[Settings$1.itemSize], true)], defaultValue: NaN }, [Settings$1.bufferSize]: { validators: [INTEGER$1, MORE_OR_EQUAL(MIN[Settings$1.bufferSize], true)], defaultValue: 5 }, [Settings$1.padding]: { validators: [NUMBER, MORE_OR_EQUAL(MIN[Settings$1.padding], true)], defaultValue: 0.5 }, [Settings$1.infinite]: { validators: [BOOLEAN$1], defaultValue: false }, [Settings$1.horizontal]: { validators: [BOOLEAN$1], defaultValue: false }, [Settings$1.windowViewport]: { validators: [BOOLEAN$1], defaultValue: false }, [Settings$1.viewportElement]: { validators: [OR$1([ELEMENT$1, FUNC$1])], defaultValue: null }, [Settings$1.inverse]: { validators: [BOOLEAN$1], defaultValue: false }, [Settings$1.onBeforeClip]: { validators: [FUNC$1], defaultValue: null }, [Settings$1.sizeStrategy]: { validators: [ENUM(SizeStrategy)], defaultValue: SizeStrategy.Average } }; const DEV_SETTINGS = { [DevSettings.debug]: { validators: [BOOLEAN$1], defaultValue: false }, [DevSettings.immediateLog]: { validators: [BOOLEAN$1], defaultValue: true }, [DevSettings.logProcessRun]: { validators: [BOOLEAN$1], defaultValue: false }, [DevSettings.logTime]: { validators: [BOOLEAN$1], defaultValue: false }, [DevSettings.logColor]: { validators: [BOOLEAN$1], defaultValue: true }, [DevSettings.throttle]: { validators: [INTEGER$1, MORE_OR_EQUAL(MIN[DevSettings.throttle], true)], defaultValue: 40 }, [DevSettings.initDelay]: { validators: [INTEGER$1, MORE_OR_EQUAL(MIN[DevSettings.initDelay], true)], defaultValue: 1 }, [DevSettings.initWindowDelay]: { validators: [INTEGER$1, MORE_OR_EQUAL(MIN[DevSettings.initWindowDelay], true)], defaultValue: 40 }, [DevSettings.cacheData]: { validators: [BOOLEAN$1], defaultValue: false }, [DevSettings.cacheOnReload]: { validators: [BOOLEAN$1], defaultValue: false }, [DevSettings.dismissOverflowAnchor]: { validators: [BOOLEAN$1], defaultValue: true }, [DevSettings.directionPriority]: { validators: [ENUM(Direction)], defaultValue: Direction.backward } }; var CommonProcess; (function (CommonProcess) { CommonProcess["init"] = "init"; CommonProcess["scroll"] = "scroll"; CommonProcess["start"] = "start"; CommonProcess["preFetch"] = "preFetch"; CommonProcess["fetch"] = "fetch"; CommonProcess["postFetch"] = "postFetch"; CommonProcess["render"] = "render"; CommonProcess["preClip"] = "preClip"; CommonProcess["clip"] = "clip"; CommonProcess["adjust"] = "adjust"; CommonProcess["end"] = "end"; })(CommonProcess || (CommonProcess = {})); var AdapterProcess; (function (AdapterProcess) { AdapterProcess["reset"] = "adapter.reset"; AdapterProcess["reload"] = "adapter.reload"; AdapterProcess["append"] = "adapter.append"; AdapterProcess["prepend"] = "adapter.prepend"; AdapterProcess["check"] = "adapter.check"; AdapterProcess["remove"] = "adapter.remove"; AdapterProcess["replace"] = "adapter.replace"; AdapterProcess["update"] = "adapter.update"; AdapterProcess["clip"] = "adapter.clip"; AdapterProcess["insert"] = "adapter.insert"; AdapterProcess["pause"] = "adapter.pause"; AdapterProcess["fix"] = "adapter.fix"; })(AdapterProcess || (AdapterProcess = {})); var ProcessStatus; (function (ProcessStatus) { ProcessStatus["start"] = "start"; ProcessStatus["next"] = "next"; ProcessStatus["done"] = "done"; ProcessStatus["error"] = "error"; })(ProcessStatus || (ProcessStatus = {})); const { INTEGER, INTEGER_UNLIMITED, BOOLEAN, OBJECT: OBJECT$1, ITEM_LIST, FUNC_WITH_X_ARGUMENTS: FUNC_WITH_X_ARGUMENTS$1, FUNC_WITH_X_AND_MORE_ARGUMENTS, FUNC_WITH_X_TO_Y_ARGUMENTS, ONE_OF_MUST, ONE_OF_CAN, OR } = VALIDATORS; var AdapterNoParams; (function (AdapterNoParams) { })(AdapterNoParams || (AdapterNoParams = {})); const NO_METHOD_PARAMS = {}; const RESET_METHOD_PARAMS = { [DatasourceProps.get]: { validators: [FUNC_WITH_X_AND_MORE_ARGUMENTS(2)] }, [DatasourceProps.settings]: { validators: [OBJECT$1] }, [DatasourceProps.devSettings]: { validators: [OBJECT$1] } }; var AdapterReloadParams; (function (AdapterReloadParams) { AdapterReloadParams["reloadIndex"] = "reloadIndex"; })(AdapterReloadParams || (AdapterReloadParams = {})); const RELOAD_METHOD_PARAMS = { [AdapterReloadParams.reloadIndex]: { validators: [INTEGER] } }; var AdapterPrependParams; (function (AdapterPrependParams) { AdapterPrependParams["items"] = "items"; AdapterPrependParams["bof"] = "bof"; AdapterPrependParams["increase"] = "increase"; AdapterPrependParams["virtualize"] = "virtualize"; })(AdapterPrependParams || (AdapterPrependParams = {})); const PREPEND_METHOD_PARAMS = { [AdapterPrependParams.items]: { validators: [ITEM_LIST], mandatory: true }, [AdapterPrependParams.bof]: { validators: [BOOLEAN, ONE_OF_CAN([AdapterPrependParams.virtualize])], defaultValue: false }, [AdapterPrependParams.increase]: { validators: [BOOLEAN], defaultValue: false }, [AdapterPrependParams.virtualize]: { validators: [BOOLEAN, ONE_OF_CAN([AdapterPrependParams.bof])], defaultValue: false } }; var AdapterAppendParams; (function (AdapterAppendParams) { AdapterAppendParams["items"] = "items"; AdapterAppendParams["eof"] = "eof"; AdapterAppendParams["decrease"] = "decrease"; AdapterAppendParams["virtualize"] = "virtualize"; })(AdapterAppendParams || (AdapterAppendParams = {})); const APPEND_METHOD_PARAMS = { [AdapterAppendParams.items]: { validators: [ITEM_LIST], mandatory: true }, [AdapterAppendParams.eof]: { validators: [BOOLEAN, ONE_OF_CAN([AdapterAppendParams.virtualize])], defaultValue: false }, [AdapterAppendParams.decrease]: { validators: [BOOLEAN], defaultValue: false }, [AdapterPrependParams.virtualize]: { validators: [BOOLEAN, ONE_OF_CAN([AdapterAppendParams.eof])], defaultValue: false } }; var AdapterRemoveParams; (function (AdapterRemoveParams) { AdapterRemoveParams["predicate"] = "predicate"; AdapterRemoveParams["indexes"] = "indexes"; AdapterRemoveParams["increase"] = "increase"; })(AdapterRemoveParams || (AdapterRemoveParams = {})); const REMOVE_METHOD_PARAMS = { [AdapterRemoveParams.predicate]: { validators: [FUNC_WITH_X_ARGUMENTS$1(1), ONE_OF_MUST([AdapterRemoveParams.indexes])] }, [AdapterRemoveParams.indexes]: { validators: [ITEM_LIST, ONE_OF_MUST([AdapterRemoveParams.predicate])] }, [AdapterRemoveParams.increase]: { validators: [BOOLEAN], defaultValue: false } }; var AdapterClipParams; (function (AdapterClipParams) { AdapterClipParams["backwardOnly"] = "backwardOnly"; AdapterClipParams["forwardOnly"] = "forwardOnly"; })(AdapterClipParams || (AdapterClipParams = {})); const CLIP_METHOD_PARAMS = { [AdapterClipParams.backwardOnly]: { validators: [BOOLEAN, ONE_OF_CAN([AdapterClipParams.forwardOnly])], defaultValue: false }, [AdapterClipParams.forwardOnly]: { validators: [BOOLEAN, ONE_OF_CAN([AdapterClipParams.backwardOnly])], defaultValue: false } }; var AdapterInsertParams; (function (AdapterInsertParams) { AdapterInsertParams["items"] = "items"; AdapterInsertParams["before"] = "before"; AdapterInsertParams["after"] = "after"; AdapterInsertParams["beforeIndex"] = "beforeIndex"; AdapterInsertParams["afterIndex"] = "afterIndex"; AdapterInsertParams["decrease"] = "decrease"; })(AdapterInsertParams || (AdapterInsertParams = {})); const INSERT_METHOD_PARAMS = { [AdapterInsertParams.items]: { validators: [ITEM_LIST], mandatory: true }, [AdapterInsertParams.before]: { validators: [ FUNC_WITH_X_ARGUMENTS$1(1), ONE_OF_MUST([ AdapterInsertParams.after, AdapterInsertParams.beforeIndex, AdapterInsertParams.afterIndex ]) ] }, [AdapterInsertParams.after]: { validators: [ FUNC_WITH_X_ARGUMENTS$1(1), ONE_OF_MUST([ AdapterInsertParams.before, AdapterInsertParams.beforeIndex, AdapterInsertParams.afterIndex ]) ] }, [AdapterInsertParams.beforeIndex]: { validators: [ INTEGER, ONE_OF_MUST([ AdapterInsertParams.before, AdapterInsertParams.after, AdapterInsertParams.afterIndex ]) ] }, [AdapterInsertParams.afterIndex]: { validators: [ INTEGER, ONE_OF_MUST([ AdapterInsertParams.before, AdapterInsertParams.after, AdapterInsertParams.beforeIndex ]) ] }, [AdapterInsertParams.decrease]: { validators: [BOOLEAN], defaultValue: false } }; var AdapterReplaceParams; (function (AdapterReplaceParams) { AdapterReplaceParams["items"] = "items"; AdapterReplaceParams["predicate"] = "predicate"; AdapterReplaceParams["fixRight"] = "fixRight"; })(AdapterReplaceParams || (AdapterReplaceParams = {})); const REPLACE_METHOD_PARAMS = { [AdapterInsertParams.items]: { validators: [ITEM_LIST], mandatory: true }, [AdapterReplaceParams.predicate]: { validators: [FUNC_WITH_X_ARGUMENTS$1(1)], mandatory: true }, [AdapterReplaceParams.fixRight]: { validators: [BOOLEAN], defaultValue: false } }; var AdapterUpdateParams; (function (AdapterUpdateParams) { AdapterUpdateParams["predicate"] = "predicate"; AdapterUpdateParams["fixRight"] = "fixRight"; })(AdapterUpdateParams || (AdapterUpdateParams = {})); const UPDATE_METHOD_PARAMS = { [AdapterUpdateParams.predicate]: { validators: [FUNC_WITH_X_ARGUMENTS$1(1)], mandatory: true }, [AdapterUpdateParams.fixRight]: { validators: [BOOLEAN], defaultValue: false } }; var AdapterFixParams; (function (AdapterFixParams) { AdapterFixParams["scrollPosition"] = "scrollPosition"; AdapterFixParams["minIndex"] = "minIndex"; AdapterFixParams["maxIndex"] = "maxIndex"; AdapterFixParams["updater"] = "updater"; AdapterFixParams["scrollToItem"] = "scrollToItem"; AdapterFixParams["scrollToItemOpt"] = "scrollToItemOpt"; })(AdapterFixParams || (AdapterFixParams = {})); const FIX_METHOD_PARAMS = { [AdapterFixParams.scrollPosition]: { validators: [INTEGER_UNLIMITED] }, [AdapterFixParams.minIndex]: { validators: [INTEGER_UNLIMITED] }, [AdapterFixParams.maxIndex]: { validators: [INTEGER_UNLIMITED] }, [AdapterFixParams.updater]: { validators: [FUNC_WITH_X_TO_Y_ARGUMENTS(1, 2)] }, [AdapterFixParams.scrollToItem]: { validators: [FUNC_WITH_X_ARGUMENTS$1(1)] }, [AdapterFixParams.scrollToItemOpt]: { validators: [OR([BOOLEAN, OBJECT$1])] } }; const AdapterMethods = { [AdapterProcess.reset]: DatasourceProps, [AdapterProcess.reload]: AdapterReloadParams, [AdapterProcess.prepend]: AdapterPrependParams, [AdapterProcess.append]: AdapterAppendParams, [AdapterProcess.check]: AdapterNoParams, [AdapterProcess.remove]: AdapterRemoveParams, [AdapterProcess.clip]: AdapterClipParams, [AdapterProcess.insert]: AdapterInsertParams, [AdapterProcess.replace]: AdapterReplaceParams, [AdapterProcess.update]: AdapterUpdateParams, [AdapterProcess.pause]: AdapterNoParams, [AdapterProcess.fix]: AdapterFixParams }; const ADAPTER_METHODS = { [AdapterProcess.reset]: RESET_METHOD_PARAMS, [AdapterProcess.reload]: RELOAD_METHOD_PARAMS, [AdapterProcess.prepend]: PREPEND_METHOD_PARAMS, [AdapterProcess.append]: APPEND_METHOD_PARAMS, [AdapterProcess.check]: NO_METHOD_PARAMS, [AdapterProcess.remove]: REMOVE_METHOD_PARAMS, [AdapterProcess.clip]: CLIP_METHOD_PARAMS, [AdapterProcess.insert]: INSERT_METHOD_PARAMS, [AdapterProcess.replace]: REPLACE_METHOD_PARAMS, [AdapterProcess.update]: UPDATE_METHOD_PARAMS, [AdapterProcess.pause]: NO_METHOD_PARAMS, [AdapterProcess.fix]: FIX_METHOD_PARAMS }; const { ELEMENT, OBJECT, FUNC, FUNC_WITH_X_ARGUMENTS } = VALIDATORS; var WorkflowProps; (function (WorkflowProps) { WorkflowProps["consumer"] = "consumer"; WorkflowProps["element"] = "element"; WorkflowProps["datasource"] = "datasource"; WorkflowProps["run"] = "run"; WorkflowProps["Routines"] = "Routines"; })(WorkflowProps || (WorkflowProps = {})); const WORKFLOW = { [WorkflowProps.consumer]: { validators: [OBJECT] }, [WorkflowProps.element]: { validators: [ELEMENT], mandatory: true }, [WorkflowProps.datasource]: { validators: [OBJECT], mandatory: true }, [WorkflowProps.run]: { validators: [FUNC_WITH_X_ARGUMENTS(1)], mandatory: true }, [WorkflowProps.Routines]: { validators: [FUNC] } }; class Settings { constructor(settings, devSettings, instanceIndex) { this.parseInput(settings, SETTINGS); this.parseInput(devSettings, DEV_SETTINGS); this.instanceIndex = instanceIndex; this.initializeDelay = this.getInitializeDelay(); this.viewport = this.getViewport(); // todo: min/max indexes must be ignored if infinite mode is enabled ?? } parseInput(input, props) { const result = validate(input, props); if (!result.isValid) { throw new Error('Invalid settings'); } Object.entries(result.params).forEach(([key, par]) => Object.assign(this, { [key]: par.value })); } getInitializeDelay() { let result = 0; if (this.windowViewport && this.initWindowDelay && !('scrollRestoration' in history)) { result = this.initWindowDelay; } if (this.initDelay > 0) { result = Math.max(result, this.initDelay); } return result; } getViewport() { if (typeof this.viewportElement !== 'function') { return this.viewportElement; } const value = this.viewportElement(); const result = validateOne({ value }, 'value', { validators: [VALIDATORS.ELEMENT] }); if (!result.isValid) { return null; // fallback to default (null) if Function didn't return HTML element synchronously } return result.value; } } const BaseProcessFactory = (process) => { var _a; return _a = class BaseProcess { }, _a.process = process, _a; }; const BaseAdapterProcessFactory = (process) => { var _a; return _a = class BaseAdapterProcess extends BaseProcessFactory(process) { static parseInput(scroller, options, ignoreErrors = false, _process) { const result = { data: validate(options, ADAPTER_METHODS[_process || process]) }; if (result.data.isValid) { result.params = Object.entries(result.data.params).reduce((acc, [key, { value }]) => (Object.assign(Object.assign({}, acc), { [key]: value })), {}); } else { scroller.logger.log(() => result.data.showErrors()); if (!ignoreErrors) { scroller.workflow.call({ process, status: ProcessStatus.error, payload: { error: `Wrong argument of the "${process}" method call` } }); } } return result; } }, _a.process = process, _a; }; const initProcesses = [CommonProcess.init, AdapterProcess.reset, AdapterProcess.reload]; class Init extends BaseProcessFactory(CommonProcess.init) { static run(scroller, process) { const { state, workflow } = scroller; const isInitial = initProcesses.includes(process); scroller.logger.logCycle(true); state.startWorkflowCycle(isInitial, process); workflow.call({ process: Init.process, status: ProcessStatus.next }); } } class Scroll extends BaseProcessFactory(CommonProcess.scroll) { static run(scroller, _payload) { const { workflow, viewport } = scroller; const position = viewport.scrollPosition; if (Scroll.onSynthetic(scroller, position)) { return; } Scroll.onThrottle(scroller, position, () => Scroll.onScroll(scroller, workflow)); } static onSynthetic(scroller, position) { const { scroll } = scroller.state; const synthPos = scroll.syntheticPosition; if (synthPos !== null) { if (scroll.syntheticFulfill) { scroll.syntheticPosition = null; } if (!scroll.syntheticFulfill || synthPos === position) { scroller.logger.log(() => [ 'skipping scroll', position, `[${scroll.syntheticFulfill ? '' : 'pre-'}synthetic]` ]); return true; } scroller.logger.log(() => [ 'synthetic scroll has been fulfilled:', position, position < synthPos ? '<' : '>', synthPos ]); } return false; } static onThrottle(scroller, position, done) { const { state, settings, logger } = scroller; const { scroll } = state; scroll.current = Scroll.getScrollEvent(position, scroll.previous); const { direction, time } = scroll.current; const timeDiff = scroll.previous ? time - scroll.previous.time : Infinity; const delta = settings.throttle - timeDiff; const shouldDelay = isFinite(delta) && delta > 0; const alreadyDelayed = !!scroll.scrollTimer; logger.log(() => [ direction === Direction.backward ? '\u2934' : '\u2935', position, shouldDelay ? timeDiff + 'ms' : '0ms', shouldDelay ? (alreadyDelayed ? 'delayed' : `/ ${delta}ms delay`) : '' ]); if (!shouldDelay) { if (scroll.scrollTimer) { clearTimeout(scroll.scrollTimer); scroll.scrollTimer = null; } done(); return; } if (!alreadyDelayed) { scroll.scrollTimer = setTimeout(() => { logger.log(() => { const curr = Scroll.getScrollEvent(scroller.viewport.scrollPosition, scroll.current); return [ curr.direction === Direction.backward ? '\u2934' : '\u2935', curr.position, curr.time - time + 'ms', 'triggered by timer set on', position ]; }); scroll.scrollTimer = null; done(); }, delta); } } static getScrollEvent(position, previous) { const time = Number(new Date()); let direction = Direction.forward; if (previous) { if (position === previous.position) { direction = previous.direction; } else if (position < previous.position) { direction = Direction.backward; } } return { position, direction, time }; } static onScroll(scroller, workflow) { const { scroll, cycle } = scroller.state; scroll.previous = Object.assign({}, scroll.current); scroll.current = null; if (cycle.busy.get()) { scroller.logger.log(() => [ 'skipping scroll', scroll.previous.position, '[pending]' ]); return; } workflow.call({ process: Scroll.process, status: ProcessStatus.next }); } } class Reset extends BaseAdapterProcessFactory(AdapterProcess.reset) { static run(scroller, options) { const { datasource, buffer, viewport, state } = scroller; if (options) { const { data } = Reset.parseInput(scroller, options); if (!data.isValid) { return; } const constructed = options instanceof Datasource; Object.keys(DatasourceProps).forEach(key => { const param = data.params[key]; const ds = datasource; if (param.isSet || (constructed && ds[key])) { ds[key] = param.value; } }); } buffer.reset(true); viewport.paddings.backward.reset(); viewport.paddings.forward.reset(); const payload = { datasource }; if (state.cycle.busy.get()) { payload.finalize = true; state.cycle.interrupter = Reset.process; } scroller.workflow.call({ process: Reset.process, status: ProcessStatus.next, payload }); } } class Reload extends BaseAdapterProcessFactory(AdapterProcess.reload) { static run(scroller, reloadIndex) { const { viewport, state, buffer } = scroller; const { params } = Reload.parseInput(scroller, { reloadIndex }, true); buffer.reset(false, params ? params.reloadIndex : void 0); viewport.reset(buffer.startIndex); const payload = {}; if (state.cycle.busy.get()) { payload.finalize = true; state.cycle.interrupter = Reload.process; } scroller.workflow.call({ process: Reload.process, status: ProcessStatus.next, payload }); } } class Item { get $index() { return this.container.$index; } set $index(value) { this.container.$index = value; } get data() { return this.container.data; } set data(value) { this.container.data = value; } get element() { return this.container.element; } set element(value) { this.container.element = value; } constructor($index, data, routines) { this.container = { $index, data }; this.nodeId = String($index); this.routines = routines; this.invisible = true; this.toRemove = false; this.toInsert = false; } dispose() { delete this.container.element; } setSize(preSize = 0) { this.preSize = preSize; if (this.element) { this.size = this.routines.getSize(this.element); } } makeVisible() { this.routines.makeElementVisible(this.element); this.invisible = false; } hide() { if (this.element) { this.routines.hideElement(this.element); } } scrollTo(argument) { if (this.element) { this.routines.scrollTo(this.element, argument); } } updateIndex(index) { this.$index = index; this.nodeId = String(index); } get() { return this.container; } } class Update extends BaseAdapterProcessFactory(AdapterProcess.update) { static run(scroller, options) { const { params } = Update.parseInput(scroller, options); if (!params) { return; } const shouldUpdate = Update.doUpdate(scroller, params); scroller.workflow.call({ process: Update.process, status: shouldUpdate ? ProcessStatus.next : ProcessStatus.done }); } static doUpdate(scroller, params) { const { buffer, viewport, state, routines, logger } = scroller; if (!buffer.items) { logger.log(() => 'no items in Buffer'); return false; } const { item: firstItem, index: firstIndex, diff: firstItemDiff } = viewport.getEdgeVisibleItem(buffer.items, Direction.backward); const { trackedIndex, toRemove } = buffer.updateItems(params.predicate, (index, data) => new Item(index, data, routines), firstIndex, !!params.fixRight); let delta = 0; const trackedItem = buffer.get(trackedIndex); if (firstItem && firstItem === trackedItem) { delta = -buffer.getSizeByIndex(trackedIndex) + firstItemDiff; } toRemove.forEach(item => item.hide()); logger.log(() => toRemove.length ? 'items to remove: [' + toRemove.map(({ $index }) => $index).join(',') + ']' : 'no items to remove'); if (toRemove.length) { // insertions will be processed on render buffer.checkDefaultSize(); } const toRender = buffer.items.filter(({ toInsert }) => toInsert); logger.log(() => toRender.length ? 'items to render: [' + toRender.map(({ $index }) => $index).join(',') + ']' : 'no items to render'); state.fetch.update(trackedIndex, delta, toRender, toRemove); return !!toRemove.length || !!toRender.length; } } class Insert extends BaseAdapterProcessFactory(AdapterProcess.insert) { static run(scroller, options) { const { params } = Insert.parseInput(scroller, options); if (!params) { return; } const shouldInsert = Insert.doInsert(scroller, params); scroller.workflow.call({ process: Insert.process, status: shouldInsert ? ProcessStatus.next : ProcessStatus.done }); } static doInsert(scroller, params) { if (!Insert.insertEmpty(scroller, params)) {