@cmtlyt/lingshu-toolkit 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/607.js +446 -20
- package/dist/707.js +7 -2
- package/dist/react/index.js +1 -3
- package/dist/react/use-ref-state/index.js +1 -3
- package/dist/shared/allx/__test__/allsettled.test.d.ts +1 -0
- package/dist/shared/allx/__test__/basic.test.d.ts +1 -0
- package/dist/shared/allx/__test__/circular-dependency.test.d.ts +1 -0
- package/dist/shared/allx/__test__/dependency.test.d.ts +1 -0
- package/dist/shared/allx/__test__/edge-cases.test.d.ts +1 -0
- package/dist/shared/allx/__test__/error-handling.test.d.ts +1 -0
- package/dist/shared/allx/__test__/execution-order.test.d.ts +1 -0
- package/dist/shared/allx/__test__/falsy-values.test.d.ts +1 -0
- package/dist/shared/allx/__test__/performance.test.d.ts +1 -0
- package/dist/shared/allx/__test__/type-checking.test.d.ts +1 -0
- package/dist/shared/allx/__test__/use-cases.test.d.ts +1 -0
- package/dist/shared/allx/index.d.ts +13 -0
- package/dist/shared/allx/index.js +44 -0
- package/dist/shared/allx/types.d.ts +13 -0
- package/dist/shared/allx/types.js +0 -0
- package/dist/shared/allx/utils.d.ts +9 -0
- package/dist/shared/allx/utils.js +94 -0
- package/dist/shared/animation/index.d.ts +2 -2
- package/dist/shared/animation/index.js +19 -13
- package/dist/shared/animation/types.d.ts +6 -4
- package/dist/shared/animation/utils.d.ts +3 -5
- package/dist/shared/animation/utils.js +2 -6
- package/dist/shared/data-handler/tools.js +1 -3
- package/dist/shared/data-mixed-manager/__test__/basic.test.d.ts +1 -0
- package/dist/shared/data-mixed-manager/__test__/build-options.test.d.ts +1 -0
- package/dist/shared/data-mixed-manager/__test__/constructor-options.test.d.ts +1 -0
- package/dist/shared/data-mixed-manager/__test__/data-management.test.d.ts +1 -0
- package/dist/shared/data-mixed-manager/__test__/edge-cases.test.d.ts +1 -0
- package/dist/shared/data-mixed-manager/__test__/events.browser.test.d.ts +1 -0
- package/dist/shared/data-mixed-manager/__test__/events.test.d.ts +1 -0
- package/dist/shared/data-mixed-manager/__test__/fixed-slots.test.d.ts +1 -0
- package/dist/shared/data-mixed-manager/__test__/insert-mode.test.d.ts +1 -0
- package/dist/shared/data-mixed-manager/constants.d.ts +8 -0
- package/dist/shared/data-mixed-manager/constants.js +9 -0
- package/dist/shared/data-mixed-manager/index.d.ts +128 -0
- package/dist/shared/data-mixed-manager/index.js +226 -0
- package/dist/shared/data-mixed-manager/types.d.ts +90 -0
- package/dist/shared/data-mixed-manager/types.js +0 -0
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +2 -2
- package/dist/shared/throw-error/index.d.ts +1 -0
- package/dist/shared/throw-error/index.js +5 -2
- package/dist/shared/types/base.d.ts +3 -0
- package/dist/shared/utils/__test__/base.test.d.ts +1 -0
- package/dist/shared/utils/__test__/verify.test.d.ts +1 -0
- package/dist/shared/utils/base.d.ts +3 -0
- package/dist/shared/utils/base.js +6 -0
- package/dist/shared/utils/index.d.ts +2 -0
- package/dist/shared/utils/index.js +2 -0
- package/dist/shared/utils/verify.d.ts +53 -0
- package/dist/shared/utils/verify.js +67 -0
- package/package.json +6 -6
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createError } from "../throw-error/index.js";
|
|
2
|
+
import { withResolvers } from "../with-resolvers/index.js";
|
|
3
|
+
function detectCycle(from, to, waitingForGraph) {
|
|
4
|
+
const visited = new Set();
|
|
5
|
+
const queue = [
|
|
6
|
+
to
|
|
7
|
+
];
|
|
8
|
+
let head = 0;
|
|
9
|
+
while(head < queue.length){
|
|
10
|
+
const node = queue[head++];
|
|
11
|
+
if (node === from) return true;
|
|
12
|
+
if (visited.has(node)) continue;
|
|
13
|
+
visited.add(node);
|
|
14
|
+
const deps = waitingForGraph.get(node);
|
|
15
|
+
if (deps) {
|
|
16
|
+
const depsIter = deps.values();
|
|
17
|
+
for(let dep = depsIter.next(); !dep.done; dep = depsIter.next())queue.push(dep.value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
function getCached(results, depName, allSettled) {
|
|
23
|
+
if (Reflect.getOwnPropertyDescriptor(results, depName)) {
|
|
24
|
+
const cached = results[depName];
|
|
25
|
+
if (allSettled) return 'rejected' === cached.status ? Promise.reject(cached.reason) : Promise.resolve(cached.value);
|
|
26
|
+
return Promise.resolve(cached);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function createDepResolver(waitingFor, resolverMap, currentTask, depName) {
|
|
30
|
+
waitingFor.set(currentTask, (waitingFor.get(currentTask) || new Set()).add(depName));
|
|
31
|
+
const depResolvers = resolverMap.get(depName) || withResolvers();
|
|
32
|
+
resolverMap.set(depName, depResolvers);
|
|
33
|
+
return depResolvers.promise.then((value)=>{
|
|
34
|
+
waitingFor.get(currentTask)?.delete(depName);
|
|
35
|
+
return value;
|
|
36
|
+
}, (error)=>{
|
|
37
|
+
waitingFor.get(currentTask)?.delete(depName);
|
|
38
|
+
throw error;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function cleanWaitingForGraph(waitingForGraph, depName) {
|
|
42
|
+
waitingForGraph.forEach((deps, task)=>{
|
|
43
|
+
deps.delete(depName);
|
|
44
|
+
if (0 === deps.size) waitingForGraph.delete(task);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function createDepProxy(tasks, results, options) {
|
|
48
|
+
const resolverMap = new Map();
|
|
49
|
+
const taskNameSet = new Set(Reflect.ownKeys(tasks));
|
|
50
|
+
const waitingForGraph = new Map();
|
|
51
|
+
const resolveDepFor = (depName, value)=>{
|
|
52
|
+
const resolver = resolverMap.get(depName);
|
|
53
|
+
if (resolver) {
|
|
54
|
+
resolver.resolve(value);
|
|
55
|
+
resolverMap.delete(depName);
|
|
56
|
+
}
|
|
57
|
+
cleanWaitingForGraph(waitingForGraph, depName);
|
|
58
|
+
};
|
|
59
|
+
const rejectDepFor = (depName, error)=>{
|
|
60
|
+
const resolver = resolverMap.get(depName);
|
|
61
|
+
if (resolver) {
|
|
62
|
+
resolver.reject(error);
|
|
63
|
+
resolverMap.delete(depName);
|
|
64
|
+
}
|
|
65
|
+
cleanWaitingForGraph(waitingForGraph, depName);
|
|
66
|
+
};
|
|
67
|
+
const createContextFor = (currentTask)=>new Proxy({}, {
|
|
68
|
+
get (_, depName) {
|
|
69
|
+
if (!taskNameSet.has(depName)) return Promise.reject(createError('allx', `Unknown task "${String(depName)}"`));
|
|
70
|
+
const cached = getCached(results, depName, options.allSettled);
|
|
71
|
+
if (cached) return cached;
|
|
72
|
+
if (detectCycle(currentTask, depName, waitingForGraph)) return Promise.reject(createError('allx', `Circular dependency detected: "${String(currentTask)}" -> "${String(depName)}"`));
|
|
73
|
+
return createDepResolver(waitingForGraph, resolverMap, currentTask, depName);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
taskNameSet,
|
|
78
|
+
createContextFor,
|
|
79
|
+
resolveDepFor,
|
|
80
|
+
rejectDepFor
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function getValueFormatFunc(options) {
|
|
84
|
+
if (!options) return (value, _type = 'fulfilled')=>value;
|
|
85
|
+
if (options.allSettled) return (value, status = 'fulfilled')=>'fulfilled' === status ? {
|
|
86
|
+
status,
|
|
87
|
+
value
|
|
88
|
+
} : {
|
|
89
|
+
status,
|
|
90
|
+
reason: value
|
|
91
|
+
};
|
|
92
|
+
return (value, _type = 'fulfilled')=>value;
|
|
93
|
+
}
|
|
94
|
+
export { createDepProxy, getValueFormatFunc };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { AnimationBaseOptions, AnimationOptions, AnimationResult } from './types';
|
|
2
|
-
export declare function stepAnimation<T>(from: T, to: T, step: number, options?: AnimationBaseOptions): Generator<
|
|
3
|
-
export declare function animation<T>(from: T, to: T, duration: number, options?: AnimationOptions): AnimationResult;
|
|
2
|
+
export declare function stepAnimation<T>(from: T, to: T, step: number, options?: AnimationBaseOptions<T>): Generator<any, void, unknown>;
|
|
3
|
+
export declare function animation<T>(from: T, to: T, duration: number, options?: AnimationOptions<T>): AnimationResult;
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import { $dt, $t, dataHandler } from "../data-handler/index.js";
|
|
2
2
|
import { throwError } from "../throw-error/index.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
if (!Number.isInteger(step) || step <= 0) throwError('stepAnimation', 'step must be a positive integer', RangeError);
|
|
6
|
-
const { parser: valueParser = identity, formatter: valueFormatter = identity } = options;
|
|
7
|
-
const [validFrom, validTo] = matchValid(from, to, valueParser);
|
|
8
|
-
const getNextValue = getNextValueHandler(validFrom, validTo, valueFormatter);
|
|
9
|
-
for(let i = 0; i <= step; i++){
|
|
10
|
-
const value = getNextValue(i / step);
|
|
11
|
-
yield value;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
3
|
+
import { identity, noop } from "../utils/base.js";
|
|
4
|
+
import { createNextTick, createRunningControllerSignal, getNextValueHandler, matchValid } from "./utils.js";
|
|
14
5
|
const validInfo = $dt({
|
|
15
6
|
autoStart: $t.boolean(true),
|
|
16
7
|
easing: $t["function"](()=>identity),
|
|
@@ -19,17 +10,32 @@ const validInfo = $dt({
|
|
|
19
10
|
onClear: $t["function"](()=>noop),
|
|
20
11
|
onUpdate: $t["function"](()=>noop),
|
|
21
12
|
onComplete: $t["function"](()=>noop),
|
|
13
|
+
formatterValue: $t["function"](()=>identity),
|
|
22
14
|
formatter: $t["function"](()=>identity),
|
|
23
15
|
parser: $t["function"](()=>identity)
|
|
24
16
|
});
|
|
17
|
+
function* stepAnimation(from, to, step, options = {}) {
|
|
18
|
+
if (!Number.isInteger(step) || step <= 0) throwError('stepAnimation', 'step must be a positive integer', RangeError);
|
|
19
|
+
const validOptions = dataHandler(options, validInfo, {
|
|
20
|
+
unwrap: true
|
|
21
|
+
});
|
|
22
|
+
const { parser: valueParser = identity, formatterValue = identity, formatter } = validOptions;
|
|
23
|
+
const [validFrom, validTo] = matchValid(from, to, valueParser);
|
|
24
|
+
const getNextValue = getNextValueHandler(validFrom, validTo, formatterValue);
|
|
25
|
+
for(let i = 0; i <= step; i++){
|
|
26
|
+
const value = formatter(getNextValue(i / step));
|
|
27
|
+
yield value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
25
30
|
function animation(from, to, duration, options = {}) {
|
|
26
31
|
if (duration <= 0 || !Number.isInteger(duration)) throwError('animation', 'duration must be a positive integer', RangeError);
|
|
27
32
|
const validOptions = dataHandler(options, validInfo, {
|
|
28
33
|
unwrap: true
|
|
29
34
|
});
|
|
30
35
|
const [validFrom, validTo] = matchValid(from, to, validOptions.parser);
|
|
31
|
-
const { autoStart, easing, onComplete, onUpdate, formatter
|
|
32
|
-
const
|
|
36
|
+
const { autoStart, easing, onComplete, onUpdate, formatterValue, formatter } = validOptions;
|
|
37
|
+
const _getNextValue = getNextValueHandler(validFrom, validTo, formatterValue);
|
|
38
|
+
const getNextValue = (...args)=>formatter(_getNextValue(...args));
|
|
33
39
|
let startTime = 0;
|
|
34
40
|
let hasStarted = false;
|
|
35
41
|
const rcSignal = createRunningControllerSignal(()=>{
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
export type
|
|
2
|
-
export
|
|
1
|
+
export type FormatterValue = (value: number) => any;
|
|
2
|
+
export type Formatter<T> = (value: T) => any;
|
|
3
|
+
export interface AnimationBaseOptions<T> {
|
|
3
4
|
parser?: (value: any) => number;
|
|
4
|
-
|
|
5
|
+
formatterValue?: FormatterValue;
|
|
6
|
+
formatter?: Formatter<T>;
|
|
5
7
|
}
|
|
6
|
-
export interface AnimationOptions extends AnimationBaseOptions {
|
|
8
|
+
export interface AnimationOptions<T> extends AnimationBaseOptions<T> {
|
|
7
9
|
autoStart?: boolean;
|
|
8
10
|
easing?: (time: number) => number;
|
|
9
11
|
onStart?: () => void;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { type Resolver } from '../with-resolvers';
|
|
2
|
-
import type { AnimationOptions,
|
|
3
|
-
export declare
|
|
4
|
-
export declare const identity: <T>(_v: T) => T;
|
|
5
|
-
export declare function getNextValueHandler(from: any, to: any, valueFormatter: Formatter): (progress: number) => any;
|
|
2
|
+
import type { AnimationOptions, FormatterValue } from './types';
|
|
3
|
+
export declare function getNextValueHandler<T>(from: T, to: T, valueFormatter: FormatterValue): (progress: number) => any;
|
|
6
4
|
export declare function matchValid(from: any, to: any, valueParser: (value: any) => number): number[] | unknown[][] | [Record<PropertyKey, any>, Record<PropertyKey, any>];
|
|
7
5
|
export declare function createNextTick(resolvers: Resolver<any>, rcSignal: RCSignal): (callback: (...args: any[]) => void) => boolean;
|
|
8
6
|
export declare function tryRun(callback: () => any, resolvers: Resolver<any>, customErrorHandler?: (err: any) => void): Promise<void>;
|
|
9
|
-
export declare function createRunningControllerSignal(startFn: () => void, options: Required<AnimationOptions
|
|
7
|
+
export declare function createRunningControllerSignal(startFn: () => void, options: Required<AnimationOptions<any>>): {
|
|
10
8
|
stopSignal: boolean;
|
|
11
9
|
resolvers: Resolver<boolean>;
|
|
12
10
|
stop: () => void;
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { throwType } from "../throw-error/index.js";
|
|
2
|
+
import { getType } from "../utils/base.js";
|
|
2
3
|
import { withResolvers } from "../with-resolvers/index.js";
|
|
3
|
-
const noop = ()=>void 0;
|
|
4
|
-
const identity = (_v)=>_v;
|
|
5
|
-
function getType(_v) {
|
|
6
|
-
return Object.prototype.toString.call(_v).slice(8, -1).toLowerCase();
|
|
7
|
-
}
|
|
8
4
|
function getNextValueHandler(from, to, valueFormatter) {
|
|
9
5
|
const type = getType(from);
|
|
10
6
|
const context = {
|
|
@@ -135,4 +131,4 @@ function createRunningControllerSignal(startFn, options) {
|
|
|
135
131
|
};
|
|
136
132
|
return ctrl;
|
|
137
133
|
}
|
|
138
|
-
export { createNextTick, createRunningControllerSignal, getNextValueHandler,
|
|
134
|
+
export { createNextTick, createRunningControllerSignal, getNextValueHandler, matchValid, tryRun };
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { logger } from "../logger/index.js";
|
|
2
2
|
import { throwType } from "../throw-error/index.js";
|
|
3
|
-
|
|
4
|
-
return Object.prototype.toString.call(_v).slice(8, -1).toLowerCase();
|
|
5
|
-
}
|
|
3
|
+
import { getType } from "../utils/base.js";
|
|
6
4
|
function typeHandler(type, verifyFn) {
|
|
7
5
|
return (fullback)=>(_v, actions)=>{
|
|
8
6
|
if (verifyFn ? verifyFn(_v) : getType(_v) === type) return true;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { PickRequired } from '../types/base';
|
|
2
|
+
import type { BuildOptions, DataMixedManagerOptions, DMMEventHandler, EventDetailMap, InputSlotConfig, MixedDataItem } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* 通用数据管理器类
|
|
5
|
+
* 支持定坑逻辑处理,不包含具体业务逻辑
|
|
6
|
+
*
|
|
7
|
+
* @template T - 数据类型
|
|
8
|
+
*/
|
|
9
|
+
declare class DataMixedManager<T> extends EventTarget {
|
|
10
|
+
addEventListener<E extends keyof EventDetailMap<T>>(type: E, listener: DMMEventHandler<T, E> | null, options?: AddEventListenerOptions | boolean): void;
|
|
11
|
+
removeEventListener<E extends keyof EventDetailMap<T>>(type: E, listener: DMMEventHandler<T, E> | null, options?: EventListenerOptions | boolean): void;
|
|
12
|
+
/** 实例配置 */
|
|
13
|
+
private readonly options;
|
|
14
|
+
/** 位置到定坑配置的映射 */
|
|
15
|
+
private readonly fixedSlots;
|
|
16
|
+
/** 普通数据列表 */
|
|
17
|
+
private readonly dataList;
|
|
18
|
+
/** 混合后的数据 */
|
|
19
|
+
private readonly mixedData;
|
|
20
|
+
/** 最后混合的定坑位置 */
|
|
21
|
+
private lastMixedSlotIdx;
|
|
22
|
+
/** 上次处理的数据长度 */
|
|
23
|
+
private prevDataLength;
|
|
24
|
+
/** 是否正在批量更新 */
|
|
25
|
+
private isBatching;
|
|
26
|
+
constructor(options?: DataMixedManagerOptions<T>);
|
|
27
|
+
/**
|
|
28
|
+
* 初始化事件监听
|
|
29
|
+
* @param listener - 事件监听
|
|
30
|
+
*/
|
|
31
|
+
private initListener;
|
|
32
|
+
private getTypeText;
|
|
33
|
+
private buildSlotConfig;
|
|
34
|
+
/**
|
|
35
|
+
* 添加定坑配置
|
|
36
|
+
* @param config - 定坑配置
|
|
37
|
+
* @param buildOptions - 构建选项
|
|
38
|
+
* @returns 返回添加后的定坑位置
|
|
39
|
+
*/
|
|
40
|
+
addFixedSlot(config: InputSlotConfig<T>, buildOptions?: BuildOptions): number;
|
|
41
|
+
/**
|
|
42
|
+
* 重新排序定坑位置
|
|
43
|
+
* 当插入模式为 before 或 after 时,需要调整后续定坑的位置
|
|
44
|
+
* @param config - 定坑配置
|
|
45
|
+
* @returns 返回调整后的定坑配置
|
|
46
|
+
*/
|
|
47
|
+
private reorderFixedSlots;
|
|
48
|
+
/**
|
|
49
|
+
* 批量添加定坑配置
|
|
50
|
+
* @param configs - 定坑配置数组
|
|
51
|
+
* @param buildOptions - 构建选项
|
|
52
|
+
* @returns 返回添加后的定坑位置数组
|
|
53
|
+
*/
|
|
54
|
+
addFixedSlots(configs: InputSlotConfig<T>[], buildOptions?: BuildOptions): number[];
|
|
55
|
+
/**
|
|
56
|
+
* 移除指定位置的定坑配置
|
|
57
|
+
* @param position - 要移除的定坑位置
|
|
58
|
+
* @param buildOptions - 构建选项
|
|
59
|
+
*/
|
|
60
|
+
deleteFixedSlot(position: number, buildOptions?: BuildOptions): void;
|
|
61
|
+
/**
|
|
62
|
+
* 批量移除定坑配置
|
|
63
|
+
* @param positions - 要移除的定坑位置数组
|
|
64
|
+
* @param buildOptions - 构建选项
|
|
65
|
+
*/
|
|
66
|
+
deleteFixedSlots(positions: number[], buildOptions?: BuildOptions): void;
|
|
67
|
+
/**
|
|
68
|
+
* 批量更新
|
|
69
|
+
* @param callback - 更新回调
|
|
70
|
+
*/
|
|
71
|
+
private batchUpdate;
|
|
72
|
+
/**
|
|
73
|
+
* 清除所有定坑配置
|
|
74
|
+
* @param buildOptions - 构建选项
|
|
75
|
+
*/
|
|
76
|
+
clearFixedSlots(buildOptions?: BuildOptions): void;
|
|
77
|
+
/**
|
|
78
|
+
* 追加新的数据列表到普通数据列表末尾
|
|
79
|
+
* @param list - 要追加的数据数组
|
|
80
|
+
* @param buildOptions - 构建选项,lazy 为 true 时延迟构建
|
|
81
|
+
*/
|
|
82
|
+
appendList(list: T[], buildOptions?: BuildOptions): void;
|
|
83
|
+
/**
|
|
84
|
+
* 清空普通数据列表
|
|
85
|
+
*/
|
|
86
|
+
clearList(): void;
|
|
87
|
+
/**
|
|
88
|
+
* 获取混合后的数据
|
|
89
|
+
* @param buildOptions - 构建选项
|
|
90
|
+
* @returns 返回混合后的数据项数组
|
|
91
|
+
*/
|
|
92
|
+
getMixedData(buildOptions?: Omit<BuildOptions, 'lazy'>): MixedDataItem<T>[];
|
|
93
|
+
/**
|
|
94
|
+
* 分发事件
|
|
95
|
+
* 同时在实例和 window 对象上触发事件
|
|
96
|
+
* @param name - 事件名称
|
|
97
|
+
* @param data - 事件数据
|
|
98
|
+
*/
|
|
99
|
+
private dispatch;
|
|
100
|
+
/**
|
|
101
|
+
* 重新构建混合数据
|
|
102
|
+
* 全局定坑:定坑位置始终固定在全局位置,普通数据在定坑位置之外填充
|
|
103
|
+
* 定坑位置不会因为普通数据的变化而改变
|
|
104
|
+
*/
|
|
105
|
+
private buildMixedData;
|
|
106
|
+
/**
|
|
107
|
+
* 切片获取指定范围内的定坑位置
|
|
108
|
+
* @param startIdx - 起始位置
|
|
109
|
+
* @param endIdx - 结束位置,默认为正无穷
|
|
110
|
+
* @returns 返回过滤后的定坑位置数组
|
|
111
|
+
*/
|
|
112
|
+
private sliceSlots;
|
|
113
|
+
/**
|
|
114
|
+
* 插入数据(插卡模式)
|
|
115
|
+
* 在指定位置插入数据,会触发全量重建
|
|
116
|
+
* @param config - 定坑配置,必须指定插入模式
|
|
117
|
+
* @returns 返回插入后的实际位置
|
|
118
|
+
*/
|
|
119
|
+
insertSlot(config: PickRequired<InputSlotConfig<T>, 'insertMode'>): number;
|
|
120
|
+
insertSlots(configs: PickRequired<InputSlotConfig<T>, 'insertMode'>[]): number[];
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 创建数据管理器实例的工厂函数
|
|
124
|
+
* @template T - 数据类型
|
|
125
|
+
* @returns 返回一个新的 DataMixedManager 实例
|
|
126
|
+
*/
|
|
127
|
+
export declare function dataMixedManager<T>(options?: DataMixedManagerOptions<T>): DataMixedManager<T>;
|
|
128
|
+
export {};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { $dt, $t, dataHandler } from "../data-handler/index.js";
|
|
2
|
+
import { throwError } from "../throw-error/index.js";
|
|
3
|
+
import { SLOT_TYPE } from "./constants.js";
|
|
4
|
+
const validInfo = $dt({
|
|
5
|
+
name: $t.string('default'),
|
|
6
|
+
fixedSlots: $t.array([]),
|
|
7
|
+
dataList: $t.array([]),
|
|
8
|
+
listener: $t.object({})
|
|
9
|
+
});
|
|
10
|
+
class DataMixedManager extends EventTarget {
|
|
11
|
+
addEventListener(...args) {
|
|
12
|
+
return super.addEventListener.apply(this, args);
|
|
13
|
+
}
|
|
14
|
+
removeEventListener(...args) {
|
|
15
|
+
return super.removeEventListener.apply(this, args);
|
|
16
|
+
}
|
|
17
|
+
options;
|
|
18
|
+
fixedSlots = new Map();
|
|
19
|
+
dataList = [];
|
|
20
|
+
mixedData = [];
|
|
21
|
+
lastMixedSlotIdx = -1;
|
|
22
|
+
prevDataLength = 0;
|
|
23
|
+
isBatching = false;
|
|
24
|
+
constructor(options){
|
|
25
|
+
super();
|
|
26
|
+
const validOptions = dataHandler(options || {}, validInfo, {
|
|
27
|
+
unwrap: true
|
|
28
|
+
});
|
|
29
|
+
const { fixedSlots, dataList, listener } = validOptions;
|
|
30
|
+
this.options = validOptions;
|
|
31
|
+
this.addFixedSlots(fixedSlots, {
|
|
32
|
+
lazy: true
|
|
33
|
+
});
|
|
34
|
+
this.appendList(dataList);
|
|
35
|
+
try {
|
|
36
|
+
this.initListener(listener);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throwError('dataMixedManager', error.message, error.constructor);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
initListener(listener) {
|
|
42
|
+
const listenerNames = Object.keys(listener);
|
|
43
|
+
for(let i = 0, name = listenerNames[i], handler = listener[name]; i < listenerNames.length; name = listenerNames[++i], handler = listener[name])this.addEventListener(name, handler);
|
|
44
|
+
}
|
|
45
|
+
getTypeText(_type) {
|
|
46
|
+
switch(_type){
|
|
47
|
+
case SLOT_TYPE.fixed:
|
|
48
|
+
return 'fixed';
|
|
49
|
+
case SLOT_TYPE.insert:
|
|
50
|
+
return 'insert';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
buildSlotConfig(config, type) {
|
|
54
|
+
const typeText = this.getTypeText(config.type || type);
|
|
55
|
+
return {
|
|
56
|
+
...config,
|
|
57
|
+
type: typeText,
|
|
58
|
+
inputPosition: config.position,
|
|
59
|
+
insertMode: config.insertMode || 'cover'
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
addFixedSlot(config, buildOptions) {
|
|
63
|
+
const realConfig = this.reorderFixedSlots(this.buildSlotConfig(config, SLOT_TYPE.fixed));
|
|
64
|
+
this.fixedSlots.set(realConfig.position, realConfig);
|
|
65
|
+
this.buildMixedData(buildOptions);
|
|
66
|
+
return realConfig.position;
|
|
67
|
+
}
|
|
68
|
+
reorderFixedSlots(config) {
|
|
69
|
+
const { position: oldPosition, insertMode } = config;
|
|
70
|
+
if (null == insertMode || 'cover' === insertMode || !this.fixedSlots.has(oldPosition)) return {
|
|
71
|
+
...config,
|
|
72
|
+
inputPosition: oldPosition
|
|
73
|
+
};
|
|
74
|
+
const position = 'after' === insertMode ? oldPosition + 1 : oldPosition;
|
|
75
|
+
for(let i = position + 1, preItem = this.fixedSlots.get(i - 1), currItem = this.fixedSlots.get(i); preItem; ++i, preItem = currItem, currItem = this.fixedSlots.get(i)){
|
|
76
|
+
preItem.position = i;
|
|
77
|
+
this.fixedSlots.set(i, preItem);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
...config,
|
|
81
|
+
position,
|
|
82
|
+
inputPosition: oldPosition
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
addFixedSlots(configs, buildOptions) {
|
|
86
|
+
if (!configs.length) return [];
|
|
87
|
+
const positions = this.batchUpdate(()=>configs.map((config)=>this.addFixedSlot(config)));
|
|
88
|
+
this.buildMixedData(buildOptions);
|
|
89
|
+
return positions;
|
|
90
|
+
}
|
|
91
|
+
deleteFixedSlot(position, buildOptions) {
|
|
92
|
+
this.fixedSlots.delete(position);
|
|
93
|
+
this.buildMixedData(buildOptions);
|
|
94
|
+
}
|
|
95
|
+
deleteFixedSlots(positions, buildOptions) {
|
|
96
|
+
if (!positions.length) return;
|
|
97
|
+
this.batchUpdate(()=>positions.forEach((position)=>void this.deleteFixedSlot(position)));
|
|
98
|
+
this.buildMixedData(buildOptions);
|
|
99
|
+
}
|
|
100
|
+
batchUpdate(callback) {
|
|
101
|
+
try {
|
|
102
|
+
this.isBatching = true;
|
|
103
|
+
const result = callback();
|
|
104
|
+
return result;
|
|
105
|
+
} finally{
|
|
106
|
+
this.isBatching = false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
clearFixedSlots(buildOptions) {
|
|
110
|
+
this.fixedSlots.clear();
|
|
111
|
+
this.buildMixedData(buildOptions);
|
|
112
|
+
}
|
|
113
|
+
appendList(list, buildOptions) {
|
|
114
|
+
if (!list.length) return;
|
|
115
|
+
this.dataList.push(...list);
|
|
116
|
+
this.buildMixedData({
|
|
117
|
+
...buildOptions,
|
|
118
|
+
lazy: true === (buildOptions || {}).lazy
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
clearList() {
|
|
122
|
+
this.dataList.length = 0;
|
|
123
|
+
this.mixedData.length = 0;
|
|
124
|
+
this.dispatch('change', {
|
|
125
|
+
mode: 'clear',
|
|
126
|
+
mixedData: this.getMixedData({
|
|
127
|
+
mode: 'rebuild'
|
|
128
|
+
})
|
|
129
|
+
});
|
|
130
|
+
this.dispatch('clear');
|
|
131
|
+
}
|
|
132
|
+
getMixedData(buildOptions) {
|
|
133
|
+
this.buildMixedData({
|
|
134
|
+
...buildOptions,
|
|
135
|
+
lazy: false
|
|
136
|
+
});
|
|
137
|
+
return this.mixedData.slice();
|
|
138
|
+
}
|
|
139
|
+
dispatch(name, data) {
|
|
140
|
+
const detail = {
|
|
141
|
+
name: this.options.name,
|
|
142
|
+
...data
|
|
143
|
+
};
|
|
144
|
+
this.dispatchEvent(new CustomEvent(name, {
|
|
145
|
+
detail
|
|
146
|
+
}));
|
|
147
|
+
if ("u" > typeof window) window.dispatchEvent(new CustomEvent(`[DMM]:${name}`, {
|
|
148
|
+
detail
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
buildMixedData(buildOptions) {
|
|
152
|
+
if (this.isBatching) return;
|
|
153
|
+
const { lazy, mode } = buildOptions || {};
|
|
154
|
+
if ('rebuild' === mode) {
|
|
155
|
+
this.prevDataLength = 0;
|
|
156
|
+
this.lastMixedSlotIdx = -1;
|
|
157
|
+
}
|
|
158
|
+
if (false !== lazy || this.dataList.length <= this.prevDataLength) return;
|
|
159
|
+
let dataStartIdx = this.prevDataLength;
|
|
160
|
+
const dataEndIdx = this.dataList.length;
|
|
161
|
+
const newItemCount = dataEndIdx - dataStartIdx;
|
|
162
|
+
this.prevDataLength = dataEndIdx;
|
|
163
|
+
const isPatchMode = dataStartIdx > 0;
|
|
164
|
+
const filteredSlots = this.sliceSlots(isPatchMode ? this.mixedData.length : this.lastMixedSlotIdx, this.mixedData.length + newItemCount);
|
|
165
|
+
this.lastMixedSlotIdx = filteredSlots.at(-1) ?? this.lastMixedSlotIdx;
|
|
166
|
+
let mixedStartIdx = isPatchMode ? this.mixedData.length : 0;
|
|
167
|
+
this.mixedData.length = (isPatchMode ? newItemCount + mixedStartIdx : dataEndIdx) + filteredSlots.length;
|
|
168
|
+
for(let fpIdx = 0, fpItem = filteredSlots[fpIdx]; dataStartIdx < dataEndIdx; ++mixedStartIdx)if (mixedStartIdx === fpItem) {
|
|
169
|
+
const fixedSlot = this.fixedSlots.get(fpItem);
|
|
170
|
+
this.mixedData[mixedStartIdx] = {
|
|
171
|
+
isFixed: true,
|
|
172
|
+
type: fixedSlot.type,
|
|
173
|
+
data: fixedSlot.data
|
|
174
|
+
};
|
|
175
|
+
fpItem = filteredSlots[++fpIdx];
|
|
176
|
+
} else {
|
|
177
|
+
this.mixedData[mixedStartIdx] = {
|
|
178
|
+
isFixed: false,
|
|
179
|
+
type: 'plain',
|
|
180
|
+
data: this.dataList[dataStartIdx]
|
|
181
|
+
};
|
|
182
|
+
++dataStartIdx;
|
|
183
|
+
}
|
|
184
|
+
this.dispatch('change', {
|
|
185
|
+
mode: isPatchMode ? 'patch' : 'rebuild',
|
|
186
|
+
mixedData: this.mixedData.slice()
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
sliceSlots(startIdx, endIdx = 1 / 0) {
|
|
190
|
+
let prevItem = -2;
|
|
191
|
+
let count = 0;
|
|
192
|
+
return Array.from(this.fixedSlots.keys()).sort((ai, bi)=>ai - bi).filter((item)=>{
|
|
193
|
+
if (item >= startIdx && item < endIdx + count || item - 1 === prevItem) {
|
|
194
|
+
++count;
|
|
195
|
+
prevItem = item;
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
insertSlot(config) {
|
|
202
|
+
const realPosition = this.addFixedSlot({
|
|
203
|
+
...config,
|
|
204
|
+
type: SLOT_TYPE.insert
|
|
205
|
+
});
|
|
206
|
+
if (realPosition > this.mixedData.length) return realPosition;
|
|
207
|
+
this.buildMixedData({
|
|
208
|
+
lazy: false,
|
|
209
|
+
mode: 'rebuild'
|
|
210
|
+
});
|
|
211
|
+
return realPosition;
|
|
212
|
+
}
|
|
213
|
+
insertSlots(configs) {
|
|
214
|
+
if (!configs.length) return [];
|
|
215
|
+
const positions = this.batchUpdate(()=>configs.map((config)=>this.insertSlot(config)));
|
|
216
|
+
this.buildMixedData({
|
|
217
|
+
lazy: false,
|
|
218
|
+
mode: 'rebuild'
|
|
219
|
+
});
|
|
220
|
+
return positions;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function dataMixedManager(options) {
|
|
224
|
+
return new DataMixedManager(options);
|
|
225
|
+
}
|
|
226
|
+
export { dataMixedManager };
|