timered-counter
Version:
Make the value change more vivid and natural
195 lines • 8.79 kB
JavaScript
import { __classPrivateFieldGet, __classPrivateFieldSet, __decorate } from "tslib";
import { zip } from 'd3-array';
import { property } from 'lit/decorators.js';
import { isArray, isObjectType } from 'remeda';
import { transitionDigit } from '../utils/transition-digit.js';
import { preprocessPartData, } from '../utils/preprocess-part-data.js';
import { parseJsonString } from '../utils/parse-json-string.js';
function toChar(value) {
return String.fromCodePoint(value + 48);
}
function preprocessPartsOptions(options) {
const digitToChar = {};
if (isArray(options.digitToChar)) {
options.digitToChar.forEach((char, index) => {
digitToChar[toChar(index)] = char;
});
}
else if (isObjectType(options.digitToChar)) {
Object.entries(options.digitToChar).forEach(([key, value]) => {
digitToChar[key] = value;
});
}
return {
...options,
digitToChar,
};
}
export const CounterPartsMixin = (superClass) => {
var _a, _CounterPartsMixinClass___partsOptions;
class CounterPartsMixinClass extends superClass {
constructor() {
super(...arguments);
_CounterPartsMixinClass___partsOptions.set(this, {
..._a.DEFAULT_PARTS_OPTIONS,
});
// partsOptions: InnerPartsOptions =
// CounterPartsMixinClass.DEFAULT_PARTS_OPTIONS;
this.parts = [];
this.oldParts = [];
this.partPreprocessDataList = [];
}
/**
* 这是 `usePartData` 的配置项. `usePartData` 被用于从数值的变化中生成用于滚动的数据.
* 这里不会有太多解释, 因为它是一个底层的配置项. 你可以查看 `CounterPartsMixinClass` 的源码了解更多信息.
*/
get partsOptions() {
return __classPrivateFieldGet(this, _CounterPartsMixinClass___partsOptions, "f");
}
set partsOptions(value) {
const old = __classPrivateFieldGet(this, _CounterPartsMixinClass___partsOptions, "f");
__classPrivateFieldSet(this, _CounterPartsMixinClass___partsOptions, preprocessPartsOptions({
..._a.DEFAULT_PARTS_OPTIONS,
...value,
}), "f");
this.requestUpdate('partsOptions', old);
}
sampling(from, to) {
return transitionDigit(this.numberAdapter, this.numberAdapter.max(from, to), this.numberAdapter.min(from, to), this.partsOptions.sampleCount);
}
// eslint-disable-next-line class-methods-use-this
sampleSplit(samples) {
return [samples.slice()];
}
sampleToString(value) {
return this.numberAdapter.toString(value);
}
/**
* 判断是否需要重新生成 parts 数据.
*
* {@link processPartData} 依赖 {@link partsOptions} 和 {@link value} 生成数据. 因此, 仅当依赖项变化时, 需要重新生成数据.
*
* @param changedProperties
* @protected
*/
// eslint-disable-next-line class-methods-use-this
shouldRebuildParts(changedProperties) {
return (changedProperties.has('value') || changedProperties.has('partsOptions'));
}
willUpdate(changedProperties) {
super.willUpdate(changedProperties);
if (changedProperties.has('value')) {
this.oldParts = this.parts;
}
if (this.shouldRebuildParts(changedProperties)) {
this.parts = this.processPartData();
}
if (changedProperties.has('value')) {
this.partPreprocessDataList = preprocessPartData(this.direction, this.parts, this.oldDirection, this.oldParts);
}
/**
* `value` 没变但 `direction` 变化时, 也要重新生成 `partPreprocessDataList`.
*/
// todo test events 和 animationOptions 冲突
// if (
// changedProperties.has('value') ||
// changedProperties.has('direction')
// ) {
// }
}
/**
* process:
* 1. 采样
* 2. 转换
* 3. 构造
*/
processPartData() {
const { decimalSeparator, digitToChar, minPlaces, fillChar, type } = this.partsOptions;
const from = this.value;
const to = this.oldValue;
let _sampleToString = value => this.sampleToString(value).split(decimalSeparator);
if (type === 'string') {
_sampleToString = value => [this.sampleToString(value), ''];
}
const result = [];
/**
* 对 {@link from} 到 {@link to} 的范围采样.
*/
const tempParts = this.sampleSplit(this.sampling(from, to));
/**
* 将时间部分的数字转换为用于滚动的字符串数组
*
* headNumber: 最先显示的数字. 向下滚动时, 为滚动列表的最后一个数字, 向上滚动时相反.
* tailNumber: 最后显示的数字. 向下滚动时, 为滚动列表的第一个数字, 向上滚动时相反.
*/
{
const directionValue = this.numberAdapter.gt(from, to) ? 'down' : 'up';
for (let i = 0; i < tempParts.length; i++) {
const partData = tempParts[i];
// const headNumber =
// partData[directionValue === "down" ? partData.length - 1 : 0];
const tailNumber = partData[directionValue === 'down' ? 0 : partData.length - 1];
const [minIntegerPlaces = 1, minDecimalPlaces = 0] = minPlaces;
const numberParts = _sampleToString(tailNumber);
const integerPlaces = Math.max(
// numberParts[0].length,
this.stringAdapter.stringToChars(numberParts[0]).length, minIntegerPlaces);
const decimalPlaces = Math.max(
// numberParts[1]?.length ?? 0,
this.stringAdapter.stringToChars(numberParts[1] ?? '').length, minDecimalPlaces);
/**
* 使用 zip 将二维矩阵, 旋转90度
*/
const data = zip(...partData
/**
* 保证位数一致, 向前补零
*/
.map(digitData => {
const [integer = '', decimal = ''] = _sampleToString(digitData);
const integerChars = this.stringAdapter.stringToChars(integer);
const decimalChars = this.stringAdapter.stringToChars(decimal);
const filledIntegerPlaces = Math.max(integerPlaces - integerChars.length, 0);
const filledDecimalPlaces = Math.max(decimalPlaces - decimalChars.length, 0);
let filledChars = [].concat(new Array(filledIntegerPlaces).fill(fillChar), integerChars);
if (decimalPlaces > 0) {
filledChars = filledChars.concat([decimalSeparator], decimalChars, new Array(filledDecimalPlaces).fill(fillChar));
}
return filledChars;
}))
/**
* 删除连续的重复项
*/
.map((digitList, index, array) => ({
data: digitList
.filter((digit, digitIndex, digitArray) => digitIndex === 0 || digit !== digitArray[digitIndex - 1])
.map(digit => digitToChar[digit] ?? digit),
place: array.length - index,
}));
result.push({
digits: data,
});
}
}
return result;
}
}
_a = CounterPartsMixinClass, _CounterPartsMixinClass___partsOptions = new WeakMap();
CounterPartsMixinClass.DEFAULT_PARTS_OPTIONS = {
sampleCount: 16,
decimalSeparator: '.',
fillChar: '0',
minPlaces: [1, 0],
digitToChar: {},
type: 'number',
};
__decorate([
property({
type: Object,
attribute: 'parts-options',
converter: value => parseJsonString(value ?? '') ?? {},
noAccessor: true,
})
], CounterPartsMixinClass.prototype, "partsOptions", null);
return CounterPartsMixinClass;
};
//# sourceMappingURL=counter-parts.js.map