diff --git a/.vscode/settings.json b/.vscode/settings.json index 1ca5f45911c3..0c9451d41917 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,6 +29,7 @@ "endindex", "esbuild", "evented", + "excalidraw", "execa", "EXTRALIGHT", "FONTFACE", @@ -70,6 +71,7 @@ "numfmt", "opencollective", "opentype", + "OOXML", "Overlines", "Pacifico", "Plass", diff --git a/examples/package.json b/examples/package.json index df9aecbd5f10..6e168694e8ad 100644 --- a/examples/package.json +++ b/examples/package.json @@ -28,6 +28,8 @@ "@univerjs/sheets-conditional-formatting": "workspace:*", "@univerjs/sheets-conditional-formatting-ui": "workspace:*", "@univerjs/sheets-data-validation": "workspace:*", + "@univerjs/sheets-filter": "workspace:*", + "@univerjs/sheets-filter-ui": "workspace:*", "@univerjs/sheets-find-replace": "workspace:*", "@univerjs/sheets-formula": "workspace:*", "@univerjs/sheets-numfmt": "workspace:*", diff --git a/examples/src/data/sheets/demo/default-workbook-data-demo.ts b/examples/src/data/sheets/demo/default-workbook-data-demo.ts index cd5748d5033d..01167a99edfe 100644 --- a/examples/src/data/sheets/demo/default-workbook-data-demo.ts +++ b/examples/src/data/sheets/demo/default-workbook-data-demo.ts @@ -13976,6 +13976,7 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = { 5: { 5: { s: 'uJSelZ11', + v: 'sadf', }, 6: { s: 'uJSelZ11', @@ -13993,6 +13994,7 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = { 6: { 5: { s: 'uJSelZ22', + v: '123123', }, 6: { s: 'uJSelZ22', @@ -14005,8 +14007,100 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = { }, }, 10: {}, - 11: {}, + 11: { + 4: { + v: 123, + t: 2, + }, + }, + 12: { + 4: { + v: '123tu', + t: 1, + }, + 5: { + v: 'sdfj', + t: 1, + }, + }, + 13: { + 4: { + v: 'ghj', + t: 1, + }, + 5: { + v: 'ghk', + t: 1, + }, + }, + 17: { + 4: { + v: 'fh', + t: 1, + }, + 5: { + v: 'jk', + t: 1, + }, + }, + 18: { + 4: { + v: 'dfg', + t: 1, + }, + 5: { + v: 'l', + t: 1, + }, + }, + 19: { + 4: { + v: 'sdfg', + t: 1, + }, + 5: { + v: 'h', + t: 1, + }, + }, + 20: { + 4: { + v: 'fdgh', + t: 1, + }, + 5: { + v: 345, + t: 2, + }, + }, + 21: { + 4: { + v: 'sgh', + t: 1, + }, + 5: { + v: 'fgs', + t: 1, + }, + }, + 22: { + 4: { + v: 'sdfh', + t: 1, + }, + 5: { + v: 'gth', + t: 1, + }, + }, + 23: { + 5: { + v: 'iop', + t: 1, + }, + }, }, + freeze: { xSplit: 0, ySplit: 0, @@ -14019,6 +14113,21 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = { defaultRowHeight: 19, mergeData: [], rowData: { + 11: { + hd: 0, + h: 19, + ah: 19, + }, + 12: { + hd: 0, + h: 19, + ah: 19, + }, + 13: { + hd: 0, + h: 19, + ah: 19, + }, 14: { hd: 1, }, @@ -14028,6 +14137,41 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = { 16: { hd: 1, }, + 17: { + hd: 0, + h: 19, + ah: 19, + }, + 18: { + hd: 0, + h: 19, + ah: 19, + }, + 19: { + hd: 0, + h: 19, + ah: 19, + }, + 20: { + hd: 0, + h: 19, + ah: 19, + }, + 21: { + hd: 0, + h: 19, + ah: 19, + }, + 22: { + hd: 0, + h: 19, + ah: 19, + }, + 23: { + hd: 0, + h: 19, + ah: 19, + }, }, columnData: {}, showGridlines: 1, @@ -14039,7 +14183,9 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = { height: 20, hidden: 0, }, - selections: ['A1'], + selections: [ + 'A1', + ], rightToLeft: 0, }, 'sheet-0010': { @@ -23400,6 +23546,19 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = { 'sheet-0011': dataValidation, }), }, + { + name: 'SHEET_AUTO_FILTER', + data: JSON.stringify({ + 'sheet-0011': { + ref: { + startRow: 11, + endRow: 23, + startColumn: 4, + endColumn: 6, + }, + }, + }), + }, ], // namedRanges: [ // { diff --git a/examples/src/sheets/lazy.ts b/examples/src/sheets/lazy.ts index 5f69d2cda454..f642604d63cd 100644 --- a/examples/src/sheets/lazy.ts +++ b/examples/src/sheets/lazy.ts @@ -15,6 +15,7 @@ */ import type { Plugin, PluginCtor } from '@univerjs/core'; +import { UniverSheetsFilterUIPlugin } from '@univerjs/sheets-filter-ui'; import { UniverUniscriptPlugin } from '@univerjs/uniscript'; export default function getLazyPlugins(): Array<[PluginCtor] | [PluginCtor, unknown]> { @@ -31,5 +32,6 @@ export default function getLazyPlugins(): Array<[PluginCtor] | [PluginCt }, }, ], + [UniverSheetsFilterUIPlugin], ]; } diff --git a/examples/src/sheets/locales.ts b/examples/src/sheets/locales.ts index 8a98f07d1d1d..1db04fc93e0c 100644 --- a/examples/src/sheets/locales.ts +++ b/examples/src/sheets/locales.ts @@ -24,7 +24,8 @@ import { enUS as UniverSheetsFormulaEnUS } from '@univerjs/sheets-formula'; import { enUS as UniverSheetsDataValidationEnUS } from '@univerjs/sheets-data-validation'; import { enUS as UniverSheetsConditionalFormattingUIEnUS } from '@univerjs/sheets-conditional-formatting-ui'; import { enUS as UniverSheetsZenEditorEnUS } from '@univerjs/sheets-zen-editor'; -import { enUS as UniverUiEnUS } from '@univerjs/ui'; +import { enUS as UniverUIEnUS } from '@univerjs/ui'; +import { enUS as UniverSheetsFilterUIEnUS } from '@univerjs/sheets-filter-ui'; export const locales = { [LocaleType.EN_US]: Tools.deepMerge( @@ -36,7 +37,8 @@ export const locales = { UniverSheetsDataValidationEnUS, UniverSheetsConditionalFormattingUIEnUS, UniverSheetsZenEditorEnUS, - UniverUiEnUS, - UniverDesignEnUS + UniverUIEnUS, + UniverDesignEnUS, + UniverSheetsFilterUIEnUS ), }; diff --git a/examples/src/sheets/main.ts b/examples/src/sheets/main.ts index 4e5436cd9cef..9ef0241389a2 100644 --- a/examples/src/sheets/main.ts +++ b/examples/src/sheets/main.ts @@ -20,6 +20,8 @@ import { UniverDocsPlugin } from '@univerjs/docs'; import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; +import { UniverFindReplacePlugin } from '@univerjs/find-replace'; +import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; import type { IUniverRPCMainThreadConfig } from '@univerjs/rpc'; import { UniverRPCMainThreadPlugin } from '@univerjs/rpc'; import { UniverSheetsPlugin } from '@univerjs/sheets'; @@ -33,8 +35,8 @@ import { UniverDataValidationPlugin } from '@univerjs/data-validation'; import { UniverSheetsDataValidationPlugin } from '@univerjs/sheets-data-validation'; import { UniverSheetsConditionalFormattingUIPlugin } from '@univerjs/sheets-conditional-formatting-ui'; -import { DebuggerPlugin } from '../plugins/debugger'; import { DEFAULT_WORKBOOK_DATA_DEMO } from '../data/sheets/demo/default-workbook-data-demo'; +import { DebuggerPlugin } from '../plugins/debugger'; import { locales } from './locales'; /* eslint-disable-next-line node/prefer-global/process */ @@ -69,7 +71,6 @@ univer.registerPlugin(UniverSheetsUIPlugin); // sheet feature plugins univer.registerPlugin(UniverSheetsNumfmtPlugin); -univer.registerPlugin(DebuggerPlugin); univer.registerPlugin(UniverSheetsZenEditorPlugin); univer.registerPlugin(UniverFormulaEnginePlugin, { notExecuteFormula: false, @@ -79,10 +80,16 @@ univer.registerPlugin(UniverRPCMainThreadPlugin, { workerURL: './worker.js', } as IUniverRPCMainThreadConfig); +// find replace +univer.registerPlugin(UniverFindReplacePlugin); +univer.registerPlugin(UniverSheetsFindReplacePlugin); + // data validation univer.registerPlugin(UniverDataValidationPlugin); univer.registerPlugin(UniverSheetsDataValidationPlugin); -univer.registerPlugin(UniverSheetsFindReplacePlugin); + +// filter +univer.registerPlugin(UniverSheetsFilterPlugin); // sheet condition formatting univer.registerPlugin(UniverSheetsConditionalFormattingUIPlugin); @@ -100,6 +107,10 @@ if (!IS_E2E) { // univer.createUnit(UniverInstanceType.SHEET, DEFAULT_WORKBOOK_DATA_DEMO); // }, 7000); + +// debugger plugin +univer.registerPlugin(DebuggerPlugin); + declare global { interface Window { univer?: Univer; diff --git a/examples/src/sheets/worker.ts b/examples/src/sheets/worker.ts index 7c3d25aeec5c..c4f1b80c06e2 100644 --- a/examples/src/sheets/worker.ts +++ b/examples/src/sheets/worker.ts @@ -18,6 +18,7 @@ import { LocaleType, Univer } from '@univerjs/core'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverRPCWorkerThreadPlugin } from '@univerjs/rpc'; import { UniverSheetsPlugin } from '@univerjs/sheets'; +import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; // Univer web worker is also a univer application. const univer = new Univer({ @@ -27,6 +28,7 @@ const univer = new Univer({ univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverFormulaEnginePlugin); univer.registerPlugin(UniverRPCWorkerThreadPlugin); +univer.registerPlugin(UniverSheetsFilterPlugin); declare let self: WorkerGlobalScope & typeof globalThis & { univer: Univer }; self.univer = univer; diff --git a/packages/core/src/common/boolean.ts b/packages/core/src/common/boolean.ts new file mode 100644 index 000000000000..295daeef4811 --- /dev/null +++ b/packages/core/src/common/boolean.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function isBooleanString(str: string): boolean { + return ['true', 'false'].includes(str.toLowerCase()); +} diff --git a/packages/core/src/common/interceptor.ts b/packages/core/src/common/interceptor.ts index 5abd6c8463dc..977377005099 100644 --- a/packages/core/src/common/interceptor.ts +++ b/packages/core/src/common/interceptor.ts @@ -28,9 +28,9 @@ export interface IInterceptor { handler: InterceptorHandler; } -export const createInterceptorKey = (key: string) => { +export function createInterceptorKey(key: string): IInterceptor { const symbol = `sheet_interceptor_${key}`; - return symbol as unknown as IInterceptor; + return symbol as unknown as IInterceptor; // FIXME: priority and handler is completely missing? }; export type IComposeInterceptors = ( diff --git a/packages/core/src/common/number.ts b/packages/core/src/common/number.ts new file mode 100644 index 000000000000..53424aca06a0 --- /dev/null +++ b/packages/core/src/common/number.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function isNumeric(str: string): boolean { + return /^-?\d+(\.\d+)?$/.test(str); +} + +export function isSafeNumeric(str: string): boolean { + const numeric = isNumeric(str); + if (!numeric) { + return false; + } + + return Number(str) <= Number.MAX_SAFE_INTEGER; +} diff --git a/packages/core/src/common/set.ts b/packages/core/src/common/set.ts new file mode 100644 index 000000000000..8cc37d0cfcb9 --- /dev/null +++ b/packages/core/src/common/set.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Merge the second set to the first set. + * @param s1 the first set + * @param s2 the second set + * @returns the merged set + */ +export function mergeSets(s1: Set, s2: Set): Set { + s2.forEach((s) => s1.add(s)); + return s1; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b93cdda5cdb5..7335aec1a83e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -21,7 +21,10 @@ export { Registry, RegistryAsMap } from './common/registry'; export { Univer } from './univer'; export { PluginHolder } from './services/plugin/plugin-holder'; export { shallowEqual, isRangesEqual, isUnitRangesEqual } from './common/equal'; +export { isNumeric, isSafeNumeric } from './common/number'; +export { isBooleanString } from './common/boolean'; export { dedupe, remove, rotate, groupBy } from './common/array'; +export { mergeSets } from './common/set'; export { DEFAULT_EMPTY_DOCUMENT_VALUE, DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY, @@ -113,11 +116,21 @@ export * from './shared'; export { fromCallback } from './shared/rxjs'; // #region sheet + export type { IComposeInterceptors, IInterceptor, InterceptorHandler } from './common/interceptor'; export { composeInterceptors, createInterceptorKey, InterceptorManager } from './common/interceptor'; export { normalizeTextRuns } from './docs/data-model/apply-utils/common'; export type { PluginCtor } from './services/plugin/plugin'; export { type DependencyOverride, mergeOverrideWithDependencies } from './services/plugin/plugin-override'; +export * from './types/const'; +export * from './types/enum'; +export * from './types/interfaces'; +export { UniverInstanceService } from './services/instance/instance.service'; +export { LifecycleInitializerService } from './services/lifecycle/lifecycle.service'; +export { ConfigService } from './services/config/config.service'; + +// #region sheet + export { Range } from './sheets/range'; export { Styles } from './sheets/styles'; export { @@ -142,9 +155,6 @@ export { SlideDataModel } from './slides/slide-model'; export * from './types/const'; export * from './types/enum'; export * from './types/interfaces'; -export { UniverInstanceService } from './services/instance/instance.service'; -export { LifecycleInitializerService } from './services/lifecycle/lifecycle.service'; -export { ConfigService } from './services/config/config.service'; export { ISnapshotServerService } from './services/snapshot/snapshot-server.service'; export { transformSnapshotToWorkbookData, @@ -162,6 +172,8 @@ export { getSheetBlocksFromSnapshot } from './services/snapshot/snapshot-transfo export { isBlackColor, isWhiteColor } from './shared/color/color-kit'; export { cellToRange } from './shared/common'; +// #endregion + export type { IDataValidationRule, IDataValidationRuleBase, IDataValidationRuleInfo, IDataValidationRuleOptions, ISheetDataValidationRule } from './types/interfaces/i-data-validation'; export type { ICellCustomRender, ICellRenderContext } from './types/interfaces/i-cell-custom-render'; diff --git a/packages/core/src/services/command/command.service.ts b/packages/core/src/services/command/command.service.ts index 839a6dd633a9..0354789fcedd 100644 --- a/packages/core/src/services/command/command.service.ts +++ b/packages/core/src/services/command/command.service.ts @@ -148,6 +148,8 @@ export interface ICommandService { options?: IExecutionOptions ): Promise; + hasCommand(id: string): boolean; + syncExecuteCommand

(id: string, params?: P, options?: IExecutionOptions): R; /** diff --git a/packages/core/src/services/instance/instance.service.ts b/packages/core/src/services/instance/instance.service.ts index 66bbd73b94ec..2ade3ab22634 100644 --- a/packages/core/src/services/instance/instance.service.ts +++ b/packages/core/src/services/instance/instance.service.ts @@ -166,10 +166,6 @@ export class UniverInstanceService extends Disposable implements IUniverInstance return unit; } - getCurrentUniverSheetInstance(): Nullable { - return this.getCurrentUnitForType(UniverInstanceType.SHEET) as Nullable; - } - getCurrentUniverDocInstance(): Nullable { return this.getCurrentUnitForType(UniverInstanceType.DOC) as Nullable; } diff --git a/packages/core/src/services/resource-loader/resource-loader.service.ts b/packages/core/src/services/resource-loader/resource-loader.service.ts index 51ad31850364..809d2cef4beb 100644 --- a/packages/core/src/services/resource-loader/resource-loader.service.ts +++ b/packages/core/src/services/resource-loader/resource-loader.service.ts @@ -30,8 +30,6 @@ export class ResourceLoaderService extends Disposable implements IResourceLoader constructor( @Inject(IResourceManagerService) private readonly _resourceManagerService: IResourceManagerService, @Inject(IUniverInstanceService) private readonly _univerInstanceService: IUniverInstanceService - - ) { super(); this._init(); diff --git a/packages/core/src/shared/lifecycle.ts b/packages/core/src/shared/lifecycle.ts index f365ccd01752..311626d2b320 100644 --- a/packages/core/src/shared/lifecycle.ts +++ b/packages/core/src/shared/lifecycle.ts @@ -23,22 +23,23 @@ import type { Nullable } from '../common/type-util'; import type { Observer } from '../observer/observable'; import { isObserver } from '../observer/observable'; +type DisposableLike = IDisposable | Nullable> | SubscriptionLike | (() => void); + +export function toDisposable(disposable: IDisposable): IDisposable; export function toDisposable(observer: Nullable>): IDisposable; export function toDisposable(subscription: SubscriptionLike): IDisposable; export function toDisposable(callback: () => void): IDisposable; -export function toDisposable(v: SubscriptionLike | (() => void) | Nullable>): IDisposable { +export function toDisposable(v: DisposableLike): IDisposable; +export function toDisposable(v: DisposableLike): IDisposable { let disposed = false; + if (!v) { + return toDisposable(() => { }); + } + if (isSubscription(v)) { return { - dispose: () => { - if (disposed) { - return; - } - - disposed = true; - v.unsubscribe(); - }, + dispose: () => v.unsubscribe(), }; } @@ -61,16 +62,20 @@ export function toDisposable(v: SubscriptionLike | (() => void) | Nullable { - if (disposed) { - return; - } + if (typeof v === 'function') { + return { + dispose: () => { + if (disposed) { + return; + } + + disposed = true; + (v as () => void)(); + }, + }; + } - disposed = true; - (v as () => void)(); - }, - }; + return v as IDisposable; } /** @@ -85,12 +90,14 @@ export function fromObservable(subscription: Subscription) { export class DisposableCollection implements IDisposable { private readonly _disposables = new Set(); - add(disposable: IDisposable): IDisposable { - this._disposables.add(disposable); + add(disposable: DisposableLike): IDisposable { + const d = toDisposable(disposable); + this._disposables.add(d); + return { dispose: () => { - disposable.dispose(); - this._disposables.delete(disposable); + d.dispose(); + this._disposables.delete(d); }, }; } @@ -108,9 +115,8 @@ export class Disposable implements IDisposable { protected _disposed = false; private readonly _collection = new DisposableCollection(); - protected disposeWithMe(disposable: IDisposable | SubscriptionLike): IDisposable { - const d = isSubscription(disposable) ? toDisposable(disposable) : (disposable as IDisposable); - return this._collection.add(d); + protected disposeWithMe(disposable: DisposableLike): IDisposable { + return this._collection.add(disposable); } protected ensureNotDisposed(): void { diff --git a/packages/core/src/shared/object-matrix.ts b/packages/core/src/shared/object-matrix.ts index 7c46dd8f926f..b42ad0ff74ba 100644 --- a/packages/core/src/shared/object-matrix.ts +++ b/packages/core/src/shared/object-matrix.ts @@ -607,6 +607,10 @@ export class ObjectMatrix { startRow = rowIndex; } + if (row == null) { + return; + } + const rowSize = getArrayLength(row) - 1; if (rowSize > endColumn) { endColumn = rowSize; diff --git a/packages/core/src/sheets/__tests__/create-core-test-bed.ts b/packages/core/src/sheets/__tests__/create-core-test-bed.ts index 4485fc1634e5..382a2485fee3 100644 --- a/packages/core/src/sheets/__tests__/create-core-test-bed.ts +++ b/packages/core/src/sheets/__tests__/create-core-test-bed.ts @@ -20,7 +20,7 @@ import { ILogService, LogLevel } from '../../services/log/log.service'; import { LocaleType } from '../../types/enum/locale-type'; import type { IWorkbookData } from '../../types/interfaces/i-workbook-data'; -const TEST_WORKBOOK_DATA: IWorkbookData = { +const testWorkbookDataFactory: () => IWorkbookData = () => ({ id: 'test', appVersion: '3.0.0-alpha', sheets: { @@ -43,16 +43,17 @@ const TEST_WORKBOOK_DATA: IWorkbookData = { name: '', sheetOrder: [], styles: {}, -}; +}); -export function createCoreTestBed(workbookConfig?: IWorkbookData) { +export function createCoreTestBed(workbookData?: IWorkbookData) { const univer = new Univer(); const injector = univer.__getInjector(); const get = injector.get.bind(injector); - const sheet = univer.createUniverSheet(workbookConfig || TEST_WORKBOOK_DATA); + const sheet = univer.createUniverSheet(workbookData || testWorkbookDataFactory()); + const univerInstanceService = get(IUniverInstanceService); - univerInstanceService.focusUnit(workbookConfig?.id ?? 'test'); + univerInstanceService.focusUnit(workbookData?.id ?? 'test'); const logService = get(ILogService); logService.setLogLevel(LogLevel.SILENT); diff --git a/packages/core/src/sheets/__tests__/worksheet.spec.ts b/packages/core/src/sheets/__tests__/worksheet.spec.ts index e4ab3b44546c..c26175296f28 100644 --- a/packages/core/src/sheets/__tests__/worksheet.spec.ts +++ b/packages/core/src/sheets/__tests__/worksheet.spec.ts @@ -99,7 +99,7 @@ describe('test worksheet', () => { }); const range: IRange = { startRow: 0, startColumn: 0, endRow: 1, endColumn: 2, rangeType: RANGE_TYPE.NORMAL }; - const iterator1 = worksheet.iterateByRow(range); + const iterator1 = worksheet.iterateByRow(range)[Symbol.iterator](); const value1 = iterator1.next(); expect(value1.done).toBeFalsy(); @@ -183,7 +183,7 @@ describe('test worksheet', () => { }); const range: IRange = { startRow: 0, startColumn: 0, endRow: 2, endColumn: 2, rangeType: RANGE_TYPE.NORMAL }; - const iterator1 = worksheet.iterateByColumn(range); + const iterator1 = worksheet.iterateByColumn(range)[Symbol.iterator](); const value1 = iterator1.next(); expect(value1.done).toBeFalsy(); diff --git a/packages/core/src/sheets/row-manager.ts b/packages/core/src/sheets/row-manager.ts index b69e636ba937..15edc2783020 100644 --- a/packages/core/src/sheets/row-manager.ts +++ b/packages/core/src/sheets/row-manager.ts @@ -19,6 +19,7 @@ import type { Nullable } from '../shared/types'; import { BooleanNumber } from '../types/enum'; import type { IRange, IRowData, IWorksheetData } from '../types/interfaces'; import { RANGE_TYPE } from '../types/interfaces'; +import type { SheetViewModel } from './view-model'; /** * Manage configuration information of all rows, get row height, row length, set row height, etc. @@ -28,6 +29,7 @@ export class RowManager { constructor( private readonly _config: IWorksheetData, + private readonly _viewModel: SheetViewModel, data: IObjectArrayPrimitiveType> ) { this._rowData = data; @@ -112,7 +114,7 @@ export class RowManager { let startRow = -1; for (let i = start; i <= end; i++) { - const visible = this.getRowVisible(i); + const visible = this.getRowRawVisible(i); if (inHiddenRange && visible) { inHiddenRange = false; hiddenRows.push({ @@ -148,7 +150,7 @@ export class RowManager { let startRow = -1; for (let i = start; i <= end; i++) { - const visible = this.getRowVisible(i); + const visible = this.getRowRawVisible(i); if (inVisibleRange && !visible) { inVisibleRange = false; visibleRows.push({ @@ -171,13 +173,13 @@ export class RowManager { return visibleRows; } - getRowVisible(rowPos: number): boolean { - const row = this.getRow(rowPos); - if (!row) { + getRowRawVisible(row: number): boolean { + const rowData = this.getRow(row); + if (!rowData) { return true; } - return row.hd !== BooleanNumber.TRUE; + return rowData.hd !== BooleanNumber.TRUE; } /** diff --git a/packages/core/src/sheets/view-model.ts b/packages/core/src/sheets/view-model.ts index f5b0cd2312e4..a84cdca60fe5 100644 --- a/packages/core/src/sheets/view-model.ts +++ b/packages/core/src/sheets/view-model.ts @@ -16,11 +16,13 @@ import type { IDisposable } from '@wendellhu/redi'; -import { remove } from '../common/array'; import type { Nullable } from '../common/type-util'; import { Disposable, toDisposable } from '../shared/lifecycle'; -import type { ICellDataForSheetInterceptor } from '../types/interfaces/i-cell-data'; +import type { ICellData, ICellDataForSheetInterceptor } from '../types/interfaces/i-cell-data'; +/** + * @internal + */ export interface ICellContentInterceptor { getCell: (row: number, col: number) => Nullable; } @@ -41,66 +43,59 @@ export interface ISheetViewModel { } /** - * SheetViewModel + * @internal */ -export class SheetViewModel extends Disposable implements ISheetViewModel { - private readonly _cellContentInterceptors: ICellContentInterceptor[] = []; - private readonly _rowFilteredInterceptors: IRowFilteredInterceptor[] = []; - private readonly _rowVisibleInterceptors: IRowVisibleInterceptor[] = []; - private readonly _colVisibleInterceptors: IColVisibleInterceptor[] = []; +export interface IRowFilteredInterceptor { + getRowFiltered(row: number): boolean; +} + +/** + * @internal + */ +export class SheetViewModel extends Disposable { + private _cellContentInterceptor: Nullable = null; + private _rowFilteredInterceptor: Nullable = null; + + constructor( + private readonly getRawCell: (row: number, col: number) => Nullable + ) { + super(); + } override dispose(): void { super.dispose(); - this._cellContentInterceptors.length = 0; - this._rowFilteredInterceptors.length = 0; - this._rowVisibleInterceptors.length = 0; - this._colVisibleInterceptors.length = 0; + this._cellContentInterceptor = null; + this._rowFilteredInterceptor = null; } getCell(row: number, col: number): Nullable { - for (const interceptor of this._cellContentInterceptors) { - const result = interceptor.getCell(row, col); - if (typeof result !== 'undefined') { - return result; - } + if (this._cellContentInterceptor) { + return this._cellContentInterceptor.getCell(row, col); } - return null; - } - - registerCellContentInterceptor(interceptor: ICellContentInterceptor): IDisposable { - if (this._cellContentInterceptors.includes(interceptor)) { - throw new Error('[SheetViewModel]: Interceptor already registered.'); - } - this._cellContentInterceptors.push(interceptor); - return toDisposable(() => remove(this._cellContentInterceptors, interceptor)); + return this.getRawCell(row, col); } - registerRowFilteredInterceptor(interceptor: IRowFilteredInterceptor): IDisposable { - if (this._rowFilteredInterceptors.includes(interceptor)) { - throw new Error('[SheetViewModel]: Interceptor already registered.'); - } - - this._rowFilteredInterceptors.push(interceptor); - return toDisposable(() => remove(this._rowFilteredInterceptors, interceptor)); + getRowFiltered(row: number): boolean { + return this._rowFilteredInterceptor?.getRowFiltered(row) ?? false; } - registerRowVisibleInterceptor(interceptor: IRowVisibleInterceptor): IDisposable { - if (this._rowVisibleInterceptors.includes(interceptor)) { + registerCellContentInterceptor(interceptor: ICellContentInterceptor): IDisposable { + if (this._cellContentInterceptor) { throw new Error('[SheetViewModel]: Interceptor already registered.'); } - this._rowVisibleInterceptors.push(interceptor); - return toDisposable(() => remove(this._rowVisibleInterceptors, interceptor)); + this._cellContentInterceptor = interceptor; + return toDisposable(() => this._cellContentInterceptor = null); } - registerColVisibleInterceptor(interceptor: IColVisibleInterceptor): IDisposable { - if (this._colVisibleInterceptors.includes(interceptor)) { + registerRowFilteredInterceptor(interceptor: IRowFilteredInterceptor): IDisposable { + if (this._rowFilteredInterceptor) { throw new Error('[SheetViewModel]: Interceptor already registered.'); } - this._colVisibleInterceptors.push(interceptor); - return toDisposable(() => remove(this._colVisibleInterceptors, interceptor)); + this._rowFilteredInterceptor = interceptor; + return toDisposable(() => this._rowFilteredInterceptor = null); } } diff --git a/packages/core/src/sheets/workbook.ts b/packages/core/src/sheets/workbook.ts index 45026fde73a9..9b5a272fba49 100644 --- a/packages/core/src/sheets/workbook.ts +++ b/packages/core/src/sheets/workbook.ts @@ -166,7 +166,7 @@ export class Workbook extends UnitModel sheets[id] = worksheetSnapshot; sheetOrder.splice(index, 0, id); - const worksheet = new Worksheet(worksheetSnapshot, this._styles); + const worksheet = new Worksheet(this._unitId, worksheetSnapshot, this._styles); this._worksheets.set(id, worksheet); this._sheetCreated$.next(worksheet); @@ -538,7 +538,7 @@ export class Workbook extends UnitModel this._logService.debug('[Workbook]', `The worksheet name ${name} is duplicated, we changed it to ${worksheetSnapshot.name}. Please fix the problem in your snapshot.`); } - const worksheet = new Worksheet(worksheetSnapshot, this._styles); + const worksheet = new Worksheet(this._unitId, worksheetSnapshot, this._styles); _worksheets.set(sheetId, worksheet); if (!sheetOrder.includes(sheetId)) { diff --git a/packages/core/src/sheets/worksheet.ts b/packages/core/src/sheets/worksheet.ts index edc8f32f02b2..5d3fc6756a9d 100644 --- a/packages/core/src/sheets/worksheet.ts +++ b/packages/core/src/sheets/worksheet.ts @@ -42,6 +42,7 @@ export class Worksheet { protected readonly _viewModel: SheetViewModel; constructor( + public readonly unitId: string, snapshot: Partial, private readonly _styles: Styles ) { @@ -50,11 +51,11 @@ export class Worksheet { const { columnData, rowData, cellData } = this._snapshot; this._sheetId = this._snapshot.id ?? Tools.generateRandomId(6); this._cellData = new ObjectMatrix(cellData); - this._rowManager = new RowManager(this._snapshot, rowData); - this._columnManager = new ColumnManager(this._snapshot, columnData); // This view model will immediately injected with hooks from SheetViewModel service as Worksheet is constructed. - this._viewModel = new SheetViewModel(); + this._viewModel = new SheetViewModel((row, col) => this.getCellRaw(row, col)); + this._rowManager = new RowManager(this._snapshot, this._viewModel, rowData); + this._columnManager = new ColumnManager(this._snapshot, columnData); } /** @@ -170,6 +171,13 @@ export class Worksheet { return this._rowManager; } + /** + * Returns the ID of its parent unit. + */ + getUnitId(): string { + return this.unitId; + } + /** * Returns the ID of the sheet represented by this object. * @returns ID of the sheet @@ -203,7 +211,7 @@ export class Worksheet { const { _snapshot: _config } = this; const copy = Tools.deepClone(_config); - return new Worksheet(copy, this._styles); + return new Worksheet(this.unitId, copy, this._styles); } getMergeData(): IRange[] { @@ -244,6 +252,10 @@ export class Worksheet { return this.getCellMatrix().getValue(row, col); } + getRowFiltered(row: number): boolean { + return this._viewModel.getRowFiltered(row); + } + /** * Get cell matrix from a given range and pick out non-first cells of merged cells. * @@ -254,7 +266,8 @@ export class Worksheet { row: number, col: number, endRow: number, - endCol: number + endCol: number, + isRaw = false ): ObjectMatrix { const matrix = this.getCellMatrix(); @@ -263,13 +276,12 @@ export class Worksheet { Rectangle.intersects({ startRow: row, startColumn: col, endRow, endColumn: endCol }, rect) ); - const ret = new ObjectMatrix(); - // iterate all cells in the range + const returnCellMatrix = new ObjectMatrix(); createRowColIter(row, endRow, col, endCol).forEach((row, col) => { - const v = matrix.getValue(row, col); + const v = isRaw ? this.getCellRaw(row, col) : this.getCell(row, col); if (v) { - ret.setValue(row, col, v); + returnCellMatrix.setValue(row, col, v); } }); @@ -279,7 +291,7 @@ export class Worksheet { const { startColumn, startRow, endColumn, endRow } = mergedCell; createRowColIter(startRow, endRow, startColumn, endColumn).forEach((row, col) => { if (row === startRow && col === startColumn) { - ret.setValue(row, col, { + returnCellMatrix.setValue(row, col, { ...matrix.getValue(row, col), rowSpan: endRow - startRow + 1, colSpan: endColumn - startColumn + 1, @@ -287,12 +299,12 @@ export class Worksheet { } if (row !== startRow || col !== startColumn) { - ret.realDeleteValue(row, col); + returnCellMatrix.realDeleteValue(row, col); } }); }); - return ret; + return returnCellMatrix; } getRange(range: IRange): Range; @@ -425,15 +437,35 @@ export class Worksheet { /** * Gets the height in pixels of the given row. - * @param rowPosition row index + * @param row row index * @returns Gets the height in pixels of the given row. */ - getRowHeight(rowPosition: number): number { - return this.getRowManager().getRowHeight(rowPosition); + getRowHeight(row: number): number { + const filtered = this._viewModel.getRowFiltered(row); + if (filtered) return 0; + + return this.getRowManager().getRowHeight(row); } + /** + * Get if the row in visible. It may be affected by features like filter and view. + * @param row the row index + * @returns if the row in visible to the user + */ getRowVisible(row: number): boolean { - return this.getRowManager().getRowVisible(row); + const filtered = this._viewModel.getRowFiltered(row); + if (filtered) return false; + + return this.getRowRawVisible(row); + } + + /** + * Get if the row does not have `hidden` property. + * @param row the row index + * @returns if the row does not have `hidden` property + */ + getRowRawVisible(row: number): boolean { + return this.getRowManager().getRowRawVisible(row); } getHiddenRows(start?: number, end?: number): IRange[] { @@ -513,52 +545,67 @@ export class Worksheet { * Iterate a range row by row. * * Performance intensive. + * + * @param range the iterate range + * @param skipEmpty whether to skip empty cells, default to be `true` */ - iterateByRow(range: IRange): Iterator, Readonly> { + iterateByRow(range: IRange, skipEmpty = true): Iterable> { const { startRow, startColumn, endRow, endColumn } = range; // eslint-disable-next-line ts/no-this-alias const worksheet = this; - let rowIndex = startRow; - let columnIndex = startColumn; - return { - next(): IteratorResult> { - while (true) { - if (columnIndex > endColumn) { - rowIndex += 1; - columnIndex = startColumn; - } - - if (rowIndex > endRow) { - return { done: true, value: undefined }; - } - - // search for the next cell that is not non-top-left cell of a merged cell - const cellValue = worksheet.getCell(rowIndex, columnIndex); - const mergedCell = worksheet.getMergedCell(rowIndex, columnIndex); - if ( - mergedCell && - (!cellValue || rowIndex !== mergedCell.startRow || columnIndex !== mergedCell.startColumn) - ) { - columnIndex = mergedCell.endColumn + 1; - // continue searching - } else if (!cellValue) { - columnIndex += 1; - // continue searching - } else { - const value: ICell = { row: rowIndex, col: columnIndex, value: cellValue }; - if (mergedCell) { - value.colSpan = mergedCell.endColumn - mergedCell.startColumn + 1; - value.rowSpan = mergedCell.endRow - mergedCell.startRow + 1; + [Symbol.iterator]: () => { + let rowIndex = startRow; + let columnIndex = startColumn; + + return { + next(): IteratorResult> { + while (true) { + if (columnIndex > endColumn) { + rowIndex += 1; + columnIndex = startColumn; + } + + if (rowIndex > endRow) { + return { done: true, value: undefined }; + } + + // search for the next cell that is not non-top-left cell of a merged cell + const cellValue = worksheet.getCell(rowIndex, columnIndex); + const isEmptyCell = !cellValue; + const mergedCell = worksheet.getMergedCell(rowIndex, columnIndex); + + if (mergedCell) { + const isNotTopLeft = rowIndex !== mergedCell.startRow || columnIndex !== mergedCell.startColumn; + if (isNotTopLeft) { + columnIndex = mergedCell.endColumn + 1; + continue; + } + + if (isEmptyCell && skipEmpty) { + columnIndex = mergedCell.endColumn + 1; + continue; + } + + const value: ICell = { row: rowIndex, col: columnIndex, value: cellValue }; + value.colSpan = mergedCell.endColumn - mergedCell.startColumn + 1; + value.rowSpan = mergedCell.endRow - mergedCell.startRow + 1; + columnIndex = mergedCell.endColumn + 1; + return { done: false, value }; + } + + if (isEmptyCell && skipEmpty) { + columnIndex += 1; + } else { + const value: ICell = { row: rowIndex, col: columnIndex, value: cellValue }; + columnIndex += 1; + return { done: false, value }; + } } - - // we still need to move to the next position by leave searching to the next time `next` get called - columnIndex += 1; - return { done: false, value }; - } - } + }, + }; }, }; } @@ -567,57 +614,77 @@ export class Worksheet { * Iterate a range column by column. This is pretty similar to `iterateByRow` but with different order. * * Performance intensive. + * + * @param range The iterate range. + * @param skipEmpty Whether to skip empty cells, default to be `true`. + * @param skipNonTopLeft Whether to skip non-top-left cells of merged cells, default to be `true`. If the + * parameter is set to `false`, the iterator will return cells in the top row. */ - iterateByColumn(range: IRange): Iterator, Readonly> { + iterateByColumn(range: IRange, skipEmpty = true, skipNonTopLeft = true): Iterable> { const { startRow, startColumn, endRow, endColumn } = range; // eslint-disable-next-line ts/no-this-alias const worksheet = this; - let rowIndex = startRow; - let columnIndex = startColumn; - return { - next(): IteratorResult> { - while (true) { - if (rowIndex > endRow) { - columnIndex += 1; - rowIndex = startRow; - } - - if (columnIndex > endColumn) { - return { done: true, value: undefined }; - } - - // search for the next cell that is not non-top-left cell of a merged cell - const cellValue = worksheet.getCell(rowIndex, columnIndex); - const mergedCell = worksheet.getMergedCell(rowIndex, columnIndex); - if ( - mergedCell && - (!cellValue || rowIndex !== mergedCell.startRow || columnIndex !== mergedCell.startColumn) - ) { - rowIndex = mergedCell.endRow + 1; - // continue searching - } else if (!cellValue) { - rowIndex += 1; - // continue searching - } else { - const value: ICell = { row: rowIndex, col: columnIndex, value: cellValue }; - if (mergedCell) { - value.colSpan = mergedCell.endColumn - mergedCell.startColumn + 1; - value.rowSpan = mergedCell.endRow - mergedCell.startRow + 1; + [Symbol.iterator]: () => { + let rowIndex = startRow; + let columnIndex = startColumn; + + return { + next(): IteratorResult> { + while (true) { + if (rowIndex > endRow) { + columnIndex += 1; + rowIndex = startRow; + } + + if (columnIndex > endColumn) { + return { done: true, value: undefined }; + } + + // search for the next cell that is not non-top-left cell of a merged cell + const mergedCell = worksheet.getMergedCell(rowIndex, columnIndex); + + if (mergedCell) { + const isNotTop = rowIndex !== mergedCell.startRow; + const isNotTopLeft = isNotTop || columnIndex !== mergedCell.startColumn; + if ((skipNonTopLeft && isNotTopLeft) || (!skipNonTopLeft && isNotTop)) { + rowIndex = mergedCell.endRow + 1; + continue; + } + + const cellValue = worksheet.getCell(mergedCell.startRow, mergedCell.startColumn); + const isEmptyCell = !cellValue; + if (isEmptyCell && skipEmpty) { + rowIndex = mergedCell.endRow + 1; + continue; + } + + const value: ICell = { row: rowIndex, col: mergedCell.startColumn, value: cellValue }; + value.colSpan = mergedCell.endColumn - mergedCell.startColumn + 1; + value.rowSpan = mergedCell.endRow - mergedCell.startRow + 1; + rowIndex = mergedCell.endRow + 1; + return { done: false, value }; + } + + const cellValue = worksheet.getCell(rowIndex, columnIndex); + const isEmptyCell = !cellValue; + if (isEmptyCell && skipEmpty) { + rowIndex += 1; + } else { + const value: ICell = { row: rowIndex, col: columnIndex, value: cellValue }; + rowIndex += 1; + return { done: false, value }; + } } - - // we still need to move to the next position by leave searching to the next time `next` get called - rowIndex += 1; - return { done: false, value }; - } - } + }, + }; }, }; - } - // #endregion + // #endregion + } } /** @@ -628,7 +695,7 @@ export interface ICell { col: number; rowSpan?: number; colSpan?: number; - value: ICellData; + value: Nullable; } /** @@ -636,7 +703,9 @@ export interface ICell { * @param cell * @returns pure text in this cell */ -export function extractPureTextFromCell(cell: ICellData): string { +export function extractPureTextFromCell(cell: Nullable): string { + if (!cell) return ''; + const richTextValue = cell.p?.body?.dataStream; if (richTextValue) return richTextValue; diff --git a/packages/core/src/types/enum/condition-type.ts b/packages/core/src/types/enum/condition-type.ts deleted file mode 100644 index 3a5784d6c66d..000000000000 --- a/packages/core/src/types/enum/condition-type.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2023-present DreamNum Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export enum ConditionType { - CONDITION_TYPE_UNSPECIFIED, // The default value, do not use. - NUMNUMBER_BETWEENR_GREATER, // The cell's value must be greater than the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue - NUMBER_GREATER_THAN_EQ, // The cell's value must be greater than or equal to the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue . - NUMBER_LESS, // The cell's value must be less than the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue - NUMBER_LESS_THAN_EQ, // The cell's value must be less than or equal to the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue . - NUMBER_EQ, // The cell's value must be equal to the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue for data validation, conditional formatting, and filters on non-data source objects and at least one ConditionValue for filters on data source objects. - NUMBER_NOT_EQ, // The cell's value must be not equal to the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue for data validation, conditional formatting, and filters on non-data source objects and at least one ConditionValue for filters on data source objects. - NUMBER_BETWEEN, // The cell's value must be between the two condition values. Supported by data validation, conditional formatting and filters. Requires exactly two ConditionValues . - NUMBER_NOT_BETWEEN, // The cell's value must not be between the two condition values. Supported by data validation, conditional formatting and filters. Requires exactly two ConditionValues . - TEXT_CONTAINS, // The cell's value must contain the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue . - TEXT_NOT_CONTAINS, // The cell's value must not contain the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue - TEXT_STARTS_WITH, // The cell's value must start with the condition's value. Supported by conditional formatting and filters. Requires a single ConditionValue . - TEXT_ENDS_WITH, // The cell's value must end with the condition's value. Supported by conditional formatting and filters. Requires a single ConditionValue . - TEXT_EQ, // The cell's value must be exactly the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue for data validation, conditional formatting, and filters on non-data source objects and at least one ConditionValue for filters on data source objects. - TEXT_IS_EMAIL, // The cell's value must be a valid email address. Supported by data validation. Requires no ConditionValues . - TEXT_IS_URL, // The cell's value must be a valid URL. Supported by data validation. Requires no ConditionValues . - DATE_EQ, // The cell's value must be the same date as the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue for data validation, conditional formatting, and filters on non-data source objects and at least one ConditionValue for filters on data source objects. - DATE_BEFORE, // The cell's value must be before the date of the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue that may be a relative date . - DATE_AFTER, // The cell's value must be after the date of the condition's value. Supported by data validation, conditional formatting and filters. Requires a single ConditionValue that may be a relative date . - DATE_ON_OR_BEFORE, // The cell's value must be on or before the date of the condition's value. Supported by data validation. Requires a single ConditionValue that may be a relative date . - DATE_ON_OR_AFTER, // The cell's value must be on or after the date of the condition's value. Supported by data validation. Requires a single ConditionValue that may be a relative date . - DATE_BETWEEN, // The cell's value must be between the dates of the two condition values. Supported by data validation. Requires exactly two ConditionValues . - DATE_NOT_BETWEEN, // The cell's value must be outside the dates of the two condition values. Supported by data validation. Requires exactly two ConditionValues . - DATE_IS_VALID, // The cell's value must be a date. Supported by data validation. Requires no ConditionValues . - ONE_OF_RANGE, // The cell's value must be listed in the grid in condition value's range. Supported by data validation. Requires a single ConditionValue , and the value must be a valid range in A1 notation. - ONE_OF_LIST, // The cell's value must be in the list of condition values. Supported by data validation. Supports any number of condition values , one per item in the list. Formulas are not supported in the values. - BLANK, // The cell's value must be empty. Supported by conditional formatting and filters. Requires no ConditionValues . - NOT_BLANK, // The cell's value must not be empty. Supported by conditional formatting and filters. Requires no ConditionValues . - CUSTOM_FORMULA, // The condition's formula must evaluate to true. Supported by data validation, conditional formatting and filters. Not supported by data source sheet filters. Requires a single ConditionValue . - BOOLEAN, // The cell's value must be TRUE/FALSE or in the list of condition values. Supported by data validation. Renders as a cell checkbox. Supports zero, one or two ConditionValues . No values indicates the cell must be TRUE or FALSE, where TRUE renders as checked and FALSE renders as unchecked. One value indicates the cell will render as checked when it contains that value and unchecked when it is blank. Two values indicate that the cell will render as checked when it contains the first value and unchecked when it contains the second value. For example, ["Yes","No"] indicates that the cell will render a checked box when it has the value "Yes" and an unchecked box when it has the value "No". - TEXT_NOT_EQ, // The cell's value must be exactly not the condition's value. Supported by filters on data source objects. Requires at least one ConditionValue . - DATE_NOT_EQ, // The cell's value must be exactly not the condition's value. Supported by filters on data source objects. Requires at least one ConditionValue . -} diff --git a/packages/core/src/types/enum/index.ts b/packages/core/src/types/enum/index.ts index 35ad9877cf00..eebd4f3dc2f9 100644 --- a/packages/core/src/types/enum/index.ts +++ b/packages/core/src/types/enum/index.ts @@ -18,7 +18,6 @@ export * from './auto-fill-series'; export * from './border-style-types'; export * from './color-type'; export * from './common-hide-types'; -export * from './condition-type'; export * from './copy-paste-type'; export * from './developer-metadata-visibility'; export * from './dimension'; diff --git a/packages/core/src/types/enum/text-style.ts b/packages/core/src/types/enum/text-style.ts index 6bb68c25ba47..cbe0f8be45fd 100644 --- a/packages/core/src/types/enum/text-style.ts +++ b/packages/core/src/types/enum/text-style.ts @@ -123,8 +123,8 @@ export enum BaselineOffset { * General Boolean Enum */ export enum BooleanNumber { - FALSE, - TRUE, + FALSE = 0, + TRUE = 1, } /** diff --git a/packages/core/src/types/interfaces/i-cell-custom-render.ts b/packages/core/src/types/interfaces/i-cell-custom-render.ts index 59ade66714ab..7ff17e7cadf3 100644 --- a/packages/core/src/types/interfaces/i-cell-custom-render.ts +++ b/packages/core/src/types/interfaces/i-cell-custom-render.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +/* eslint-disable ts/no-explicit-any */ + import type { Nullable } from '../../shared'; import type { ISelectionCellWithCoord } from './i-selection-data'; import type { IStyleData } from './i-style-data'; @@ -31,6 +33,8 @@ export interface ICellRenderContext { /** * @debt This shouldn't exist in core package. + * + * @deprecated This interface is subject to change in the future. */ export interface ICellCustomRender { drawWith(ctx: CanvasRenderingContext2D, info: ICellRenderContext, skeleton: any, spreadsheets: any): void; diff --git a/packages/core/src/types/interfaces/i-row-data.ts b/packages/core/src/types/interfaces/i-row-data.ts index 5099dac1b3d5..362438102e5e 100644 --- a/packages/core/src/types/interfaces/i-row-data.ts +++ b/packages/core/src/types/interfaces/i-row-data.ts @@ -24,14 +24,17 @@ export interface IRowData { * height in pixel */ h?: number; + /** * is current row self-adaptive to its content, use `ah` to set row height when true, else use `h`. */ ia?: BooleanNumber; // pre name `isAutoHeight` + /** * auto height */ ah?: number; + /** * hidden */ diff --git a/packages/core/src/types/interfaces/i-workbook-data.ts b/packages/core/src/types/interfaces/i-workbook-data.ts index 46e20a62d813..92758099da10 100644 --- a/packages/core/src/types/interfaces/i-workbook-data.ts +++ b/packages/core/src/types/interfaces/i-workbook-data.ts @@ -28,22 +28,27 @@ export interface IWorkbookData extends IExtraModelData { * unit id */ id: string; + /** * Revision of this spreadsheet. Would be used in collaborated editing. Starts from one. */ rev?: number; + /** * Name of the spreadsheet. */ name: string; + /** * Version of Univer model definition. */ appVersion: string; + locale: LocaleType; /** Style reference. */ styles: IKeyType>; + sheetOrder: string[]; // sheet id order list ['xxxx-sheet3', 'xxxx-sheet1','xxxx-sheet2'] sheets: { [sheetId: string]: Partial }; diff --git a/packages/core/src/types/interfaces/i-worksheet-data.ts b/packages/core/src/types/interfaces/i-worksheet-data.ts index a62ec0d89b6a..08f753251c6d 100644 --- a/packages/core/src/types/interfaces/i-worksheet-data.ts +++ b/packages/core/src/types/interfaces/i-worksheet-data.ts @@ -48,7 +48,9 @@ export interface IWorksheetData { * @defaultValue `BooleanNumber.FALSE` */ hidden: BooleanNumber; + freeze: IFreeze; + rowCount: number; columnCount: number; zoomRatio: number; diff --git a/packages/design/src/components/menu/index.module.less b/packages/design/src/components/menu/index.module.less index b44ce2b80777..cee9cd09a50f 100644 --- a/packages/design/src/components/menu/index.module.less +++ b/packages/design/src/components/menu/index.module.less @@ -94,6 +94,7 @@ &.menu-item-disabled, &.menu-submenu-disabled { color: rgb(var(--grey-200)) !important; + cursor: not-allowed; } } diff --git a/packages/design/src/components/popup/RectPopup.tsx b/packages/design/src/components/popup/RectPopup.tsx index fcde3c4419ce..41a285522712 100644 --- a/packages/design/src/components/popup/RectPopup.tsx +++ b/packages/design/src/components/popup/RectPopup.tsx @@ -37,6 +37,7 @@ export interface IRectPopupProps { direction?: 'horizontal' | 'vertical'; + closeOnSelfTarget?: boolean; onClickOutside?: (e: MouseEvent) => void; excludeOutside?: HTMLElement[]; @@ -77,7 +78,7 @@ const calcPopupPosition = (layout: IPopupLayoutInfo) => { }; function RectPopup(props: IRectPopupProps) { - const { children, anchorRect, direction = 'vertical', onClickOutside, excludeOutside } = props; + const { children, anchorRect, direction = 'vertical', onClickOutside, excludeOutside, closeOnSelfTarget } = props; const nodeRef = useRef(null); const clickOtherFn = useEvent(onClickOutside ?? (() => { })); diff --git a/packages/design/src/components/popup/index.module.less b/packages/design/src/components/popup/index.module.less index 1c3de9d16c3a..14c5edad5c41 100644 --- a/packages/design/src/components/popup/index.module.less +++ b/packages/design/src/components/popup/index.module.less @@ -10,6 +10,10 @@ z-index: 1070; top: -9999px; left: -9999px; + background-color: rgb(var(--bg-color-secondary)); + border-radius: 6px; + overflow: hidden; + box-shadow: var(--box-shadow-base); &-enter { .effect(); diff --git a/packages/design/src/components/segmented/index.module.less b/packages/design/src/components/segmented/index.module.less index c6ebe63819da..82d82d0f4d91 100644 --- a/packages/design/src/components/segmented/index.module.less +++ b/packages/design/src/components/segmented/index.module.less @@ -21,6 +21,7 @@ width: 100%; border-radius: 6px; background-color: rgb(var(--grey-50)); + box-sizing: border-box; &-group { position: relative; @@ -34,7 +35,7 @@ &-item { position: relative; - min-height: 28px; + height: 100%; padding: 4px 10px; border-radius: 4px; height: 100%; @@ -66,7 +67,7 @@ &-label { z-index: 2; - line-height: 24px; + line-height: 20px; } &-input { diff --git a/packages/design/src/components/select/index.module.less b/packages/design/src/components/select/index.module.less index 0b88f2c41463..88eb84dedf30 100644 --- a/packages/design/src/components/select/index.module.less +++ b/packages/design/src/components/select/index.module.less @@ -229,7 +229,7 @@ &-dropdown { position: absolute; - z-index: 1060; + z-index: 1070; background: rgb(var(--bg-color-secondary)); border: 1px solid rgb(var(--border-color)); diff --git a/packages/docs/src/commands/commands/__tests__/create-command-test-bed.ts b/packages/docs/src/commands/commands/__tests__/create-command-test-bed.ts index 48901b5acde8..dd2e2da68be7 100644 --- a/packages/docs/src/commands/commands/__tests__/create-command-test-bed.ts +++ b/packages/docs/src/commands/commands/__tests__/create-command-test-bed.ts @@ -88,7 +88,7 @@ const TEST_DOCUMENT_DATA_EN: IDocumentData = { }, }; -export function createCommandTestBed(workbookConfig?: IDocumentData, dependencies?: Dependency[]) { +export function createCommandTestBed(workbookData?: IDocumentData, dependencies?: Dependency[]) { const univer = new Univer(); const injector = univer.__getInjector(); const get = injector.get.bind(injector); @@ -122,7 +122,7 @@ export function createCommandTestBed(workbookConfig?: IDocumentData, dependencie univer.registerPlugin(TestPlugin); - const doc = univer.createUniverDoc(workbookConfig || TEST_DOCUMENT_DATA_EN); + const doc = univer.createUniverDoc(workbookData || TEST_DOCUMENT_DATA_EN); const univerInstanceService = get(IUniverInstanceService); univerInstanceService.focusUnit('test-doc'); diff --git a/packages/docs/src/commands/operations/select-all.operation.ts b/packages/docs/src/commands/operations/select-all.operation.ts index e79c3d35f32e..1e815333563b 100644 --- a/packages/docs/src/commands/operations/select-all.operation.ts +++ b/packages/docs/src/commands/operations/select-all.operation.ts @@ -23,9 +23,7 @@ interface ISelectAllOperationParams { } export const SelectAllOperation: ICommand = { id: 'doc.operation.select-all', - type: CommandType.COMMAND, - handler: async (accessor) => { const univerInstanceService = accessor.get(IUniverInstanceService); const textSelectionManagerService = accessor.get(TextSelectionManagerService); @@ -34,10 +32,7 @@ export const SelectAllOperation: ICommand = { if (!currentDoc) return false; const prevBody = currentDoc.getSnapshot().body; - - if (prevBody == null) { - return false; - } + if (prevBody == null) return false; const textRanges = [ { @@ -47,7 +42,6 @@ export const SelectAllOperation: ICommand = { ]; textSelectionManagerService.replaceTextRanges(textRanges, false); - return true; }, }; diff --git a/packages/engine-formula/src/engine/analysis/__tests__/create-command-test-bed.ts b/packages/engine-formula/src/engine/analysis/__tests__/create-command-test-bed.ts index fef83e52719d..c8abbe874aa1 100644 --- a/packages/engine-formula/src/engine/analysis/__tests__/create-command-test-bed.ts +++ b/packages/engine-formula/src/engine/analysis/__tests__/create-command-test-bed.ts @@ -224,7 +224,7 @@ const TEST_WORKBOOK_DATA: IWorkbookData = { sheetOrder: [], styles: {}, }; -export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]) { +export function createCommandTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]) { const univer = new Univer(); const injector = univer.__getInjector(); const get = injector.get.bind(injector); @@ -256,7 +256,7 @@ export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencie } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || TEST_WORKBOOK_DATA); + const sheet = univer.createUniverSheet(workbookData || TEST_WORKBOOK_DATA); const univerInstanceService = get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/engine-formula/src/functions/__tests__/create-function-test-bed.ts b/packages/engine-formula/src/functions/__tests__/create-function-test-bed.ts index c3ceada10193..c98326bee4c2 100644 --- a/packages/engine-formula/src/functions/__tests__/create-function-test-bed.ts +++ b/packages/engine-formula/src/functions/__tests__/create-function-test-bed.ts @@ -147,7 +147,7 @@ const getTestWorkbookData = (): IWorkbookData => { styles: {}, }; }; -export function createFunctionTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]) { +export function createFunctionTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]) { const univer = new Univer(); const injector = univer.__getInjector(); const get = injector.get.bind(injector); @@ -204,7 +204,7 @@ export function createFunctionTestBed(workbookConfig?: IWorkbookData, dependenci } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || getTestWorkbookData()); + const sheet = univer.createUniverSheet(workbookData || getTestWorkbookData()); const univerInstanceService = get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/engine-formula/src/models/__tests__/create-command-test-bed.ts b/packages/engine-formula/src/models/__tests__/create-command-test-bed.ts index af0078d158b9..87185e99568b 100644 --- a/packages/engine-formula/src/models/__tests__/create-command-test-bed.ts +++ b/packages/engine-formula/src/models/__tests__/create-command-test-bed.ts @@ -50,7 +50,7 @@ const TEST_WORKBOOK_DATA: IWorkbookData = { sheetOrder: [], styles: {}, }; -export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]) { +export function createCommandTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]) { const univer = new Univer(); const injector = univer.__getInjector(); const get = injector.get.bind(injector); @@ -83,7 +83,7 @@ export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencie } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || TEST_WORKBOOK_DATA); + const sheet = univer.createUniverSheet(workbookData || TEST_WORKBOOK_DATA); const univerInstanceService = get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/engine-render/src/components/sheets/extensions/custom.ts b/packages/engine-render/src/components/sheets/extensions/custom.ts index d239da7634cc..486f41c0fb5a 100644 --- a/packages/engine-render/src/components/sheets/extensions/custom.ts +++ b/packages/engine-render/src/components/sheets/extensions/custom.ts @@ -88,7 +88,7 @@ export class Custom extends SheetExtension { }; // current cell is hidden - if (!worksheet.getColVisible(col) || !worksheet.getRowVisible(row)) { + if (!worksheet.getColVisible(col) || !worksheet.getRowRawVisible(row)) { return; } diff --git a/packages/engine-render/src/components/sheets/extensions/marker.ts b/packages/engine-render/src/components/sheets/extensions/marker.ts index f2a9af213408..69701e43914b 100644 --- a/packages/engine-render/src/components/sheets/extensions/marker.ts +++ b/packages/engine-render/src/components/sheets/extensions/marker.ts @@ -84,7 +84,7 @@ export class Marker extends SheetExtension { } // current cell is hidden - if (!worksheet.getColVisible(col) || !worksheet.getRowVisible(row)) { + if (!worksheet.getColVisible(col) || !worksheet.getRowRawVisible(row)) { return; } diff --git a/packages/engine-render/src/components/sheets/sheet-skeleton.ts b/packages/engine-render/src/components/sheets/sheet-skeleton.ts index 395cfc69986f..77395ed6fdc1 100644 --- a/packages/engine-render/src/components/sheets/sheet-skeleton.ts +++ b/packages/engine-render/src/components/sheets/sheet-skeleton.ts @@ -16,6 +16,25 @@ /* eslint-disable max-lines-per-function */ +import { + BooleanNumber, + CellValueType, + DEFAULT_EMPTY_DOCUMENT_VALUE, + DocumentDataModel, + extractPureTextFromCell, + getColorStyle, + HorizontalAlign, + IContextService, + isEmptyCell, + isNullCell, + isWhiteColor, + LocaleService, + ObjectMatrix, + searchArray, + Tools, + VerticalAlign, + WrapStrategy, +} from '@univerjs/core'; import type { BorderStyleTypes, IBorderStyleData, @@ -38,25 +57,6 @@ import type { TextDirection, Worksheet, } from '@univerjs/core'; -import { - BooleanNumber, - CellValueType, - DEFAULT_EMPTY_DOCUMENT_VALUE, - DocumentDataModel, - extractPureTextFromCell, - getColorStyle, - HorizontalAlign, - IContextService, - isEmptyCell, - isNullCell, - isWhiteColor, - LocaleService, - ObjectMatrix, - searchArray, - Tools, - VerticalAlign, - WrapStrategy, -} from '@univerjs/core'; import { Inject } from '@wendellhu/redi'; import { distinctUntilChanged, startWith } from 'rxjs'; @@ -246,7 +246,11 @@ export class SpreadsheetSkeleton extends Skeleton { constructor( private _worksheet: Worksheet | undefined, - private _config: IWorksheetData, + /** + * @deprecated avoid use `IWorksheetData` directly, use API provided by `Worksheet`, otherwise + * `ViewModel` will be not working. + */ + private _worksheetData: IWorksheetData, private _cellData: ObjectMatrix>, private _styles: Styles, @Inject(LocaleService) _localeService: LocaleService, @@ -304,7 +308,7 @@ export class SpreadsheetSkeleton extends Skeleton { } get mergeData() { - return this._config.mergeData; + return this._worksheetData.mergeData; } get rowHeaderWidthAndMarginLeft() { @@ -377,7 +381,7 @@ export class SpreadsheetSkeleton extends Skeleton { } calculateSegment(bounds?: IViewportBound) { - if (!this._config) { + if (!this._worksheetData) { return; } @@ -399,7 +403,7 @@ export class SpreadsheetSkeleton extends Skeleton { return; } - const { mergeData } = this._config; + const { mergeData } = this._worksheetData; this._dataMergeCache = mergeData && this._getMergeCells(mergeData, this._rowColumnSegment); @@ -422,7 +426,7 @@ export class SpreadsheetSkeleton extends Skeleton { } const results: IRowAutoHeightInfo[] = []; - const { mergeData, rowData } = this._config; + const { mergeData, rowData } = this._worksheetData; const rowObjectArray = rowData; for (const range of ranges) { @@ -457,7 +461,7 @@ export class SpreadsheetSkeleton extends Skeleton { // TODO: auto height private _calculateRowAutoHeight(rowNum: number): number { - const { columnCount, columnData, mergeData, defaultRowHeight, defaultColumnWidth } = this._config; + const { columnCount, columnData, mergeData, defaultRowHeight, defaultColumnWidth } = this._worksheetData; let height = defaultRowHeight; const worksheet = this._worksheet; @@ -544,7 +548,7 @@ export class SpreadsheetSkeleton extends Skeleton { rowHeader, columnHeader, showGridlines, - } = this._config; + } = this._worksheetData; const { rowTotalHeight, rowHeightAccumulation } = this._generateRowMatrixCache( rowCount, rowData, @@ -585,7 +589,7 @@ export class SpreadsheetSkeleton extends Skeleton { * @returns */ getWorksheetConfig() { - return this._config; + return this._worksheetData; } getRowColumnSegmentByViewBound(bound?: IBoundRectNoAngle) { @@ -593,7 +597,7 @@ export class SpreadsheetSkeleton extends Skeleton { } getMergeBounding(startRow: number, startColumn: number, endRow: number, endColumn: number) { - const mergeData = this._config.mergeData; + const mergeData = this._worksheetData.mergeData; if (!mergeData) { return { startRow, @@ -947,7 +951,7 @@ export class SpreadsheetSkeleton extends Skeleton { column, rowHeightAccumulation, columnWidthAccumulation, - this._config.mergeData + this._worksheetData.mergeData ); const { isMerged, isMergedMainCell } = primary; let { startY, endY, startX, endX, mergeInfo } = primary; @@ -980,7 +984,7 @@ export class SpreadsheetSkeleton extends Skeleton { column, rowHeightAccumulation, columnWidthAccumulation, - this._config.mergeData + this._worksheetData.mergeData ); const { isMerged, isMergedMainCell } = primary; const { startY, endY, startX, endX, mergeInfo } = primary; @@ -1394,12 +1398,14 @@ export class SpreadsheetSkeleton extends Skeleton { for (let r = 0; r < rowCount; r++) { let rowHeight = defaultRowHeight; - if (data[r] != null) { + if (this._worksheet?.getRowFiltered(r)) { + rowHeight = 0; + } else if (data[r] != null) { const rowDataItem = data[r]; - if (!rowDataItem) { continue; } + const { h = defaultRowHeight, ah, ia } = rowDataItem; if ((ia == null || ia === BooleanNumber.TRUE) && typeof ah === 'number') { rowHeight = ah; diff --git a/packages/engine-render/src/components/sheets/spreadsheet.ts b/packages/engine-render/src/components/sheets/spreadsheet.ts index a687351684ff..6fb33e5e3e58 100644 --- a/packages/engine-render/src/components/sheets/spreadsheet.ts +++ b/packages/engine-render/src/components/sheets/spreadsheet.ts @@ -219,10 +219,7 @@ export class Spreadsheet extends SheetComponent { } const spreadsheetSkeleton = this.getSkeleton(); - - if (!spreadsheetSkeleton) { - return; - } + if (!spreadsheetSkeleton) return; spreadsheetSkeleton.calculateWithoutClearingCache(bounds); diff --git a/packages/engine-render/src/render-manager/render-manager.service.ts b/packages/engine-render/src/render-manager/render-manager.service.ts index 810a49be5b89..31ecd7814472 100644 --- a/packages/engine-render/src/render-manager/render-manager.service.ts +++ b/packages/engine-render/src/render-manager/render-manager.service.ts @@ -194,7 +194,9 @@ export class RenderManagerService extends Disposable implements IRenderManagerSe const item = this._renderMap.get(unitId); if (item != null) { this._disposeItem(item); + (item as RenderUnit).dispose(); } + this._renderMap.delete(unitId); } diff --git a/packages/facade/src/apis/__tests__/create-test-bed.ts b/packages/facade/src/apis/__tests__/create-test-bed.ts index 42f12e24d143..be958b35e6bd 100644 --- a/packages/facade/src/apis/__tests__/create-test-bed.ts +++ b/packages/facade/src/apis/__tests__/create-test-bed.ts @@ -28,7 +28,7 @@ import { } from '@univerjs/core'; import { FunctionService, IFunctionService } from '@univerjs/engine-formula'; import { ISocketService, WebSocketService } from '@univerjs/network'; -import { SelectionManagerService, SheetInterceptorService } from '@univerjs/sheets'; +import { SelectionManagerService, SheetInterceptorService, SheetPermissionService } from '@univerjs/sheets'; import { DescriptionService, enUS, @@ -81,7 +81,7 @@ export interface ITestBed { univerAPI: FUniver; } -export function createTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]): ITestBed { +export function createTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]): ITestBed { const univer = new Univer(); const injector = univer.__getInjector(); @@ -99,6 +99,7 @@ export function createTestBed(workbookConfig?: IWorkbookData, dependencies?: Dep override onStarting(injector: Injector): void { injector.add([SelectionManagerService]); injector.add([SheetInterceptorService]); + injector.add([SheetPermissionService]); injector.add([IRegisterFunctionService, { useClass: RegisterFunctionService }]); injector.add([ IDescriptionService, @@ -131,7 +132,7 @@ export function createTestBed(workbookConfig?: IWorkbookData, dependencies?: Dep }); univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || getTestWorkbookDataDemo()); + const sheet = univer.createUniverSheet(workbookData || getTestWorkbookDataDemo()); const univerInstanceService = injector.get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/facade/src/apis/__tests__/facade.spec.ts b/packages/facade/src/apis/__tests__/facade.spec.ts index c190350cbc51..54dcd01ba691 100644 --- a/packages/facade/src/apis/__tests__/facade.spec.ts +++ b/packages/facade/src/apis/__tests__/facade.spec.ts @@ -37,6 +37,7 @@ describe('Test FUniver', () => { endRow: number, endColumn: number ) => Nullable; + let getStyleByPosition: ( startRow: number, startColumn: number, diff --git a/packages/facade/src/apis/sheets/__tests__/create-test-bed.ts b/packages/facade/src/apis/sheets/__tests__/create-test-bed.ts index 03251b7d2c48..29e489bf8910 100644 --- a/packages/facade/src/apis/sheets/__tests__/create-test-bed.ts +++ b/packages/facade/src/apis/sheets/__tests__/create-test-bed.ts @@ -68,7 +68,7 @@ export interface ITestBed { univerAPI: FUniver; } -export function createTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]): ITestBed { +export function createTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]): ITestBed { const univer = new Univer(); const injector = univer.__getInjector(); @@ -106,7 +106,7 @@ export function createTestBed(workbookConfig?: IWorkbookData, dependencies?: Dep injector.get(LocaleService).load({ zhCN, enUS }); univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || getTestWorkbookDataDemo()); + const sheet = univer.createUniverSheet(workbookData || getTestWorkbookDataDemo()); const univerInstanceService = injector.get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/rpc/src/controllers/data-sync/data-sync-primary.controller.ts b/packages/rpc/src/controllers/data-sync/data-sync-primary.controller.ts index df95d04880e9..54584e47cf0a 100644 --- a/packages/rpc/src/controllers/data-sync/data-sync-primary.controller.ts +++ b/packages/rpc/src/controllers/data-sync/data-sync-primary.controller.ts @@ -97,9 +97,7 @@ export class DataSyncPrimaryController extends RxDisposable { // Mutations executed on the main thread should be synced to the worker thread. this._commandService.onCommandExecuted((commandInfo, options) => { const { type, params } = commandInfo; - // TODO@wzhudev: use a universal way to get unitId const unitID = (params as any)?.unitId || ''; - if ( // only sync mutations to the worker thread type === CommandType.MUTATION && diff --git a/packages/rpc/src/services/rpc/rpc.service.ts b/packages/rpc/src/services/rpc/rpc.service.ts index 8511e50b8343..a06ecbd02e20 100644 --- a/packages/rpc/src/services/rpc/rpc.service.ts +++ b/packages/rpc/src/services/rpc/rpc.service.ts @@ -121,7 +121,7 @@ export interface IChannelServer { registerChannel(channelName: string, channel: T): void; } -const enum RequestType { +enum RequestType { /** A simple remote calling wrapper in a Promise. */ CALL = 100, @@ -140,7 +140,7 @@ interface IRPCRequest { args?: any; } -const enum ResponseType { +enum ResponseType { /** * When underlying protocol is established. The server should send the * client an `INITIALIZE` response to indicate that the server is up diff --git a/packages/sheets-conditional-formatting-ui/src/controllers/cf.auto-fill.controller.ts b/packages/sheets-conditional-formatting-ui/src/controllers/cf.auto-fill.controller.ts index 531aa363157d..83319dd0ef75 100644 --- a/packages/sheets-conditional-formatting-ui/src/controllers/cf.auto-fill.controller.ts +++ b/packages/sheets-conditional-formatting-ui/src/controllers/cf.auto-fill.controller.ts @@ -18,8 +18,8 @@ import type { IMutationInfo, IRange, Workbook } from '@univerjs/core'; import { Disposable, IUniverInstanceService, LifecycleStages, ObjectMatrix, OnLifecycle, Range, Rectangle, UniverInstanceType } from '@univerjs/core'; import { createTopMatrixFromMatrix, findAllRectangle } from '@univerjs/sheets'; -import type { ISheetAutoFillHook } from '@univerjs/sheets-ui'; -import { APPLY_TYPE, getAutoFillRepeatRange, IAutoFillService } from '@univerjs/sheets-ui'; +import type { IDiscreteRange, ISheetAutoFillHook } from '@univerjs/sheets-ui'; +import { APPLY_TYPE, getAutoFillRepeatRange, IAutoFillService, virtualizeDiscreteRanges } from '@univerjs/sheets-ui'; import { Inject, Injector } from '@wendellhu/redi'; import { ConditionalFormattingRuleModel, ConditionalFormattingViewModel, SetConditionalRuleMutation, setConditionalRuleMutationUndoFactory, SHEET_CONDITIONAL_FORMATTING_PLUGIN } from '@univerjs/sheets-conditional-formatting'; import type { ISetConditionalRuleMutationParams } from '@univerjs/sheets-conditional-formatting'; @@ -40,13 +40,16 @@ export class ConditionalFormattingAutoFillController extends Disposable { this._initAutoFill(); } + // eslint-disable-next-line max-lines-per-function private _initAutoFill() { const noopReturnFunc = () => ({ redos: [], undos: [] }); + // eslint-disable-next-line max-lines-per-function const loopFunc = ( sourceStartCell: { row: number; col: number }, targetStartCell: { row: number; col: number }, relativeRange: IRange, - matrixMap: Map> + matrixMap: Map>, + mapFunc: (row: number, col: number) => ({ row: number; col: number }) ) => { const unitId = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)!.getUnitId(); const subUnitId = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)!.getActiveSheet().getSheetId(); @@ -82,18 +85,19 @@ export class ConditionalFormattingAutoFillController extends Disposable { }, targetRange ); + const { row: sourceRow, col: sourceCol } = mapFunc(sourcePositionRange.startRow, sourcePositionRange.startColumn); const sourceCellCf = this._conditionalFormattingViewModel.getCellCf( unitId, subUnitId, - sourcePositionRange.startRow, - sourcePositionRange.startColumn + sourceRow, + sourceCol ); - + const { row: targetRow, col: targetCol } = mapFunc(targetPositionRange.startRow, targetPositionRange.startColumn); const targetCellCf = this._conditionalFormattingViewModel.getCellCf( unitId, subUnitId, - targetPositionRange.startRow, - targetPositionRange.startColumn + targetRow, + targetCol ); if (targetCellCf) { targetCellCf.cfList.forEach((cf) => { @@ -111,7 +115,7 @@ export class ConditionalFormattingAutoFillController extends Disposable { }); matrixMap.set(cf.cfId, matrix); } - matrix!.realDeleteValue(targetPositionRange.startRow, targetPositionRange.startColumn); + matrix!.realDeleteValue(targetRow, targetCol); }); } @@ -131,26 +135,35 @@ export class ConditionalFormattingAutoFillController extends Disposable { }); matrixMap.set(cf.cfId, matrix); } - matrix!.setValue(targetPositionRange.startRow, targetPositionRange.startColumn, 1); + matrix!.setValue(targetRow, targetCol, 1); }); } }); }; - const generalApplyFunc = (sourceRange: IRange, targetRange: IRange) => { - const unitId = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)!.getUnitId(); - const subUnitId = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)!.getActiveSheet().getSheetId(); + + const generalApplyFunc = (sourceRange: IDiscreteRange, targetRange: IDiscreteRange) => { + const unitId = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)?.getUnitId(); + const subUnitId = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)?.getActiveSheet().getSheetId(); const matrixMap: Map> = new Map(); const redos: IMutationInfo[] = []; const undos: IMutationInfo[] = []; + if (!unitId || !subUnitId) { + return noopReturnFunc(); + } + + const virtualRange = virtualizeDiscreteRanges([sourceRange, targetRange]); + const [vSourceRange, vTargetRange] = virtualRange.ranges; + const { mapFunc } = virtualRange; const sourceStartCell = { - row: sourceRange.startRow, - col: sourceRange.startColumn, + row: vSourceRange.startRow, + col: vSourceRange.startColumn, }; - const repeats = getAutoFillRepeatRange(sourceRange, targetRange); + + const repeats = getAutoFillRepeatRange(vSourceRange, vTargetRange); repeats.forEach((repeat) => { - loopFunc(sourceStartCell, repeat.repeatStartCell, repeat.relativeRange, matrixMap); + loopFunc(sourceStartCell, repeat.repeatStartCell, repeat.relativeRange, matrixMap, mapFunc); }); matrixMap.forEach((item, cfId) => { const rule = this._conditionalFormattingRuleModel.getRule(unitId, subUnitId, cfId); @@ -169,20 +182,19 @@ export class ConditionalFormattingAutoFillController extends Disposable { redos, }; }; + const hook: ISheetAutoFillHook = { id: SHEET_CONDITIONAL_FORMATTING_PLUGIN, onFillData: (location, direction, applyType) => { - if ( - applyType === APPLY_TYPE.COPY || - applyType === APPLY_TYPE.ONLY_FORMAT || - applyType === APPLY_TYPE.SERIES - ) { + if (applyType === APPLY_TYPE.COPY || applyType === APPLY_TYPE.ONLY_FORMAT || applyType === APPLY_TYPE.SERIES) { const { source, target } = location; return generalApplyFunc(source, target); } + return noopReturnFunc(); }, }; + this.disposeWithMe(this._autoFillService.addHook(hook)); } } diff --git a/packages/sheets-conditional-formatting-ui/src/controllers/cf.copy-paste.controller.ts b/packages/sheets-conditional-formatting-ui/src/controllers/cf.copy-paste.controller.ts index a96d91e5c4ba..e82860ec810f 100644 --- a/packages/sheets-conditional-formatting-ui/src/controllers/cf.copy-paste.controller.ts +++ b/packages/sheets-conditional-formatting-ui/src/controllers/cf.copy-paste.controller.ts @@ -29,7 +29,8 @@ import { import { createTopMatrixFromMatrix, findAllRectangle, } from '@univerjs/sheets'; -import { COPY_TYPE, getRepeatRange, ISheetClipboardService, PREDEFINED_HOOK_NAME } from '@univerjs/sheets-ui'; +import type { IDiscreteRange } from '@univerjs/sheets-ui'; +import { COPY_TYPE, getRepeatRange, ISheetClipboardService, PREDEFINED_HOOK_NAME, rangeToDiscreteRange, virtualizeDiscreteRanges } from '@univerjs/sheets-ui'; import { Inject, Injector } from '@wendellhu/redi'; import { AddConditionalRuleMutation, AddConditionalRuleMutationUndoFactory, ConditionalFormattingRuleModel, ConditionalFormattingViewModel, DeleteConditionalRuleMutation, DeleteConditionalRuleMutationUndoFactory, SetConditionalRuleMutation, setConditionalRuleMutationUndoFactory, SHEET_CONDITIONAL_FORMATTING_PLUGIN } from '@univerjs/sheets-conditional-formatting'; import type { IAddConditionalRuleMutationParams, IConditionalFormattingRuleConfig, IConditionFormattingRule, IDeleteConditionalRuleMutationParams, ISetConditionalRuleMutationParams } from '@univerjs/sheets-conditional-formatting'; @@ -87,23 +88,24 @@ export class ConditionalFormattingCopyPasteController extends Disposable { if (!model) { return; } + const accessor = { + get: this._injector.get.bind(this._injector), + }; + const discreteRange = rangeToDiscreteRange(range, accessor, unitId, subUnitId); + if (!discreteRange) { + return; + } + const { rows, cols } = discreteRange; const cfIdSet: Set = new Set(); - Range.foreach(range, (row, col) => { - const cellCfList = this._conditionalFormattingViewModel.getCellCf(unitId, subUnitId, row, col, model); - if (!cellCfList) { - return; - } - const relativeRange = Rectangle.getRelativeRange( - { - startRow: row, - endRow: row, - startColumn: col, - endColumn: col, - }, - range - ); - cellCfList.cfList.forEach((item) => cfIdSet.add(item.cfId)); - matrix.setValue(relativeRange.startRow, relativeRange.startColumn, cellCfList.cfList.map((item) => item.cfId)); + rows.forEach((row, rowIndex) => { + cols.forEach((col, colIndex) => { + const cellCfList = this._conditionalFormattingViewModel.getCellCf(unitId, subUnitId, row, col, model); + if (!cellCfList) { + return; + } + cellCfList.cfList.forEach((item) => cfIdSet.add(item.cfId)); + matrix.setValue(rowIndex, colIndex, cellCfList.cfList.map((item) => item.cfId)); + }); }); cfIdSet.forEach((cfId) => { const rule = this._conditionalFormattingRuleModel.getRule(unitId, subUnitId, cfId); @@ -114,10 +116,10 @@ export class ConditionalFormattingCopyPasteController extends Disposable { } private _generateConditionalFormattingMutations( - pastedRange: IRange, + pastedRange: IDiscreteRange, copyInfo: { copyType: COPY_TYPE; - copyRange?: IRange; + copyRange?: IDiscreteRange; pasteType: string; } ) { @@ -143,11 +145,14 @@ export class ConditionalFormattingCopyPasteController extends Disposable { ) { return { redos: [], undos: [] }; } - const repeatRange = getRepeatRange(copyInfo.copyRange, pastedRange, true); + + const { ranges: [vCopyRange, vPastedRange], mapFunc } = virtualizeDiscreteRanges([copyInfo.copyRange, pastedRange]); + const repeatRange = getRepeatRange(vCopyRange, vPastedRange, true); const model = this._conditionalFormattingViewModel.getMatrix(unitId, subUnitId); const effectedConditionalFormattingRuleMatrix: Record> = {}; - Range.foreach(pastedRange, (row, col) => { - const cellCfList = this._conditionalFormattingViewModel.getCellCf(unitId, subUnitId, row, col, model!); + Range.foreach(vPastedRange, (row, col) => { + const { row: realRow, col: realCol } = mapFunc(row, col); + const cellCfList = this._conditionalFormattingViewModel.getCellCf(unitId, subUnitId, realRow, realCol, model!); if (cellCfList) { cellCfList.cfList.forEach((item) => { if (!effectedConditionalFormattingRuleMatrix[item.cfId]) { @@ -160,7 +165,7 @@ export class ConditionalFormattingCopyPasteController extends Disposable { }); }); } - effectedConditionalFormattingRuleMatrix[item.cfId].realDeleteValue(row, col); + effectedConditionalFormattingRuleMatrix[item.cfId].realDeleteValue(realRow, realCol); }); } }); @@ -212,8 +217,8 @@ export class ConditionalFormattingCopyPasteController extends Disposable { item.startRange ); - const _row = range.startRow; - const _col = range.startColumn; + const { row: _row, col: _col } = mapFunc(range.startRow, range.startColumn); + copyRangeCfIdList.forEach((cfId) => { if (!effectedConditionalFormattingRuleMatrix[cfId]) { const rule = getCurrentSheetCfRule(cfId); diff --git a/packages/sheets-conditional-formatting/src/render/data-bar.render.ts b/packages/sheets-conditional-formatting/src/render/data-bar.render.ts index c0a41a366a0a..68d30dd4ec12 100644 --- a/packages/sheets-conditional-formatting/src/render/data-bar.render.ts +++ b/packages/sheets-conditional-formatting/src/render/data-bar.render.ts @@ -46,7 +46,7 @@ export class DataBar extends SheetExtension { Range.foreach(spreadsheetSkeleton.rowColumnSegment, (row, col) => { const cellData = worksheet.getCell(row, col) as IDataBarCellData; if (cellData && cellData.dataBar) { - if (!worksheet.getColVisible(col) || !worksheet.getRowVisible(row)) { + if (!worksheet.getColVisible(col) || !worksheet.getRowRawVisible(row)) { return; } diff --git a/packages/sheets-conditional-formatting/src/render/icon.render.ts b/packages/sheets-conditional-formatting/src/render/icon.render.ts index 47c542b80c6b..6776aed1ac21 100644 --- a/packages/sheets-conditional-formatting/src/render/icon.render.ts +++ b/packages/sheets-conditional-formatting/src/render/icon.render.ts @@ -58,7 +58,7 @@ export class ConditionalFormattingIcon extends SheetExtension { Range.foreach(spreadsheetSkeleton.rowColumnSegment, (row, col) => { const cellData = worksheet.getCell(row, col) as IIconSetCellData; if (cellData?.iconSet) { - if (!worksheet.getColVisible(col) || !worksheet.getRowVisible(row)) { + if (!worksheet.getColVisible(col) || !worksheet.getRowRawVisible(row)) { return; } diff --git a/packages/sheets-conditional-formatting/src/services/conditional-formatting.service.ts b/packages/sheets-conditional-formatting/src/services/conditional-formatting.service.ts index e13d3fb91d69..1ac228405c10 100644 --- a/packages/sheets-conditional-formatting/src/services/conditional-formatting.service.ts +++ b/packages/sheets-conditional-formatting/src/services/conditional-formatting.service.ts @@ -40,7 +40,7 @@ type ComputeStatus = 'computing' | 'end' | 'error'; interface IComputeCache { status: ComputeStatus }; -const beforeUpdateRuleResult = createInterceptorKey<{ subUnitId: string; unitId: string; cfId: string }>('conditional-formatting-before-update-rule-result'); +const beforeUpdateRuleResult = createInterceptorKey<{ subUnitId: string; unitId: string; cfId: string }, undefined>('conditional-formatting-before-update-rule-result'); @OnLifecycle(LifecycleStages.Starting, ConditionalFormattingService) export class ConditionalFormattingService extends Disposable { // >> diff --git a/packages/sheets-data-validation/src/controllers/dv-auto-fill.controller.ts b/packages/sheets-data-validation/src/controllers/dv-auto-fill.controller.ts index 7c4f708f8128..1a0e5a66613c 100644 --- a/packages/sheets-data-validation/src/controllers/dv-auto-fill.controller.ts +++ b/packages/sheets-data-validation/src/controllers/dv-auto-fill.controller.ts @@ -16,7 +16,7 @@ import { Disposable, LifecycleStages, OnLifecycle, Range, Rectangle } from '@univerjs/core'; import type { IAutoFillLocation, ISheetAutoFillHook } from '@univerjs/sheets-ui'; -import { APPLY_TYPE, getAutoFillRepeatRange, IAutoFillService } from '@univerjs/sheets-ui'; +import { APPLY_TYPE, getAutoFillRepeatRange, IAutoFillService, virtualizeDiscreteRanges } from '@univerjs/sheets-ui'; import { Inject } from '@wendellhu/redi'; import { DataValidationModel } from '@univerjs/data-validation'; import { DATA_VALIDATION_PLUGIN_NAME } from '../common/const'; @@ -33,6 +33,7 @@ export class DataValidationAutoFillController extends Disposable { this._initAutoFill(); } + // eslint-disable-next-line max-lines-per-function private _initAutoFill() { const noopReturnFunc = () => ({ redos: [], undos: [] }); @@ -40,12 +41,15 @@ export class DataValidationAutoFillController extends Disposable { const { source: sourceRange, target: targetRange, unitId, subUnitId } = location; const manager = this._dataValidationModel.ensureManager(unitId, subUnitId) as SheetDataValidationManager; const ruleMatrixCopy = manager.getRuleObjectMatrix().clone(); + + const virtualRange = virtualizeDiscreteRanges([sourceRange, targetRange]); + const [vSourceRange, vTargetRange] = virtualRange.ranges; + const { mapFunc } = virtualRange; const sourceStartCell = { - row: sourceRange.startRow, - col: sourceRange.startColumn, + row: vSourceRange.startRow, + col: vSourceRange.startColumn, }; - - const repeats = getAutoFillRepeatRange(sourceRange, targetRange); + const repeats = getAutoFillRepeatRange(vSourceRange, vTargetRange); repeats.forEach((repeat) => { const targetStartCell = repeat.repeatStartCell; const relativeRange = repeat.relativeRange; @@ -71,7 +75,8 @@ export class DataValidationAutoFillController extends Disposable { }, sourceRange ); - const ruleId = manager.getRuleIdByLocation(sourcePositionRange.startRow, sourcePositionRange.startColumn); + const { row: sourceRow, col: sourceCol } = mapFunc(sourcePositionRange.startRow, sourcePositionRange.startColumn); + const ruleId = manager.getRuleIdByLocation(sourceRow, sourceCol); if (ruleId) { const targetPositionRange = Rectangle.getPositionRange( { @@ -82,7 +87,9 @@ export class DataValidationAutoFillController extends Disposable { }, targetRange ); - ruleMatrixCopy.setValue(targetPositionRange.startRow, targetPositionRange.startColumn, ruleId); + const { row: targetRow, col: targetCol } = mapFunc(targetPositionRange.startRow, targetPositionRange.startColumn); + + ruleMatrixCopy.setValue(targetRow, targetCol, ruleId); } }); }); diff --git a/packages/sheets-data-validation/src/controllers/dv-copy-paste.controller.ts b/packages/sheets-data-validation/src/controllers/dv-copy-paste.controller.ts index 5af02cf0906e..d25df24d4d5c 100644 --- a/packages/sheets-data-validation/src/controllers/dv-copy-paste.controller.ts +++ b/packages/sheets-data-validation/src/controllers/dv-copy-paste.controller.ts @@ -15,9 +15,10 @@ */ import type { IRange, ISheetDataValidationRule, Nullable } from '@univerjs/core'; -import { Disposable, LifecycleStages, ObjectMatrix, OnLifecycle, Range, Rectangle } from '@univerjs/core'; -import { COPY_TYPE, getRepeatRange, ISheetClipboardService, PREDEFINED_HOOK_NAME } from '@univerjs/sheets-ui'; -import { Inject } from '@wendellhu/redi'; +import { Disposable, LifecycleStages, ObjectMatrix, OnLifecycle, Rectangle } from '@univerjs/core'; +import type { IDiscreteRange } from '@univerjs/sheets-ui'; +import { COPY_TYPE, getRepeatRange, ISheetClipboardService, PREDEFINED_HOOK_NAME, rangeToDiscreteRange, virtualizeDiscreteRanges } from '@univerjs/sheets-ui'; +import { Inject, Injector } from '@wendellhu/redi'; import { DataValidationModel } from '@univerjs/data-validation'; import { SPECIAL_PASTE_FORMULA } from '@univerjs/sheets-formula'; import type { SheetDataValidationManager } from '../models/sheet-data-validation-manager'; @@ -34,7 +35,8 @@ export class DataValidationCopyPasteController extends Disposable { constructor( @ISheetClipboardService private _sheetClipboardService: ISheetClipboardService, - @Inject(DataValidationModel) private _dataValidationModel: DataValidationModel + @Inject(DataValidationModel) private _dataValidationModel: DataValidationModel, + @Inject(Injector) private _injector: Injector ) { super(); this._initCopyPaste(); @@ -62,28 +64,28 @@ export class DataValidationCopyPasteController extends Disposable { }; const manager = this._dataValidationModel.ensureManager(unitId, subUnitId) as SheetDataValidationManager; + const accessor = { + get: this._injector.get.bind(this._injector), + }; + const discreteRange = rangeToDiscreteRange(range, accessor, unitId, subUnitId); + if (!discreteRange) { + return; + } + const { rows, cols } = discreteRange; + rows.forEach((row, rowIndex) => { + cols.forEach((col, colIndex) => { + const ruleId = manager.getRuleIdByLocation(row, col); - Range.foreach(range, (row, col) => { - const ruleId = manager.getRuleIdByLocation(row, col); - - const relativeRange = Rectangle.getRelativeRange( - { - startRow: row, - endRow: row, - startColumn: col, - endColumn: col, - }, - range - ); - matrix.setValue(relativeRange.startRow, relativeRange.startColumn, ruleId ?? ''); + matrix.setValue(rowIndex, colIndex, ruleId ?? ''); + }); }); } private _generateMutations( - pastedRange: IRange, + pastedRange: IDiscreteRange, copyInfo: { copyType: COPY_TYPE; - copyRange?: IRange; + copyRange?: IDiscreteRange; pasteType: string; unitId: string; subUnitId: string; @@ -119,7 +121,10 @@ export class DataValidationCopyPasteController extends Disposable { const originManager = this._dataValidationModel.ensureManager(unitId, subUnitId) as SheetDataValidationManager; const manager = this._dataValidationModel.ensureManager(copyInfo.unitId, copyInfo.subUnitId) as SheetDataValidationManager; const ruleMatrix = manager.getRuleObjectMatrix().clone(); - const repeatRange = getRepeatRange(copyInfo.copyRange, pastedRange, true); + + const { ranges: [vCopyRange, vPastedRange], mapFunc } = virtualizeDiscreteRanges([copyInfo.copyRange, pastedRange]); + + const repeatRange = getRepeatRange(vCopyRange, vPastedRange, true); const additionRules: Map = new Map(); repeatRange.forEach(({ startRange }) => { @@ -140,7 +145,8 @@ export class DataValidationCopyPasteController extends Disposable { additionRules.set(transformedRuleId, { ...oldRule, uid: transformedRuleId }); } - ruleMatrix.setValue(range.startRow, range.startColumn, transformedRuleId); + const { row: startRow, col: startColumn } = mapFunc(range.startRow, range.startColumn); + ruleMatrix.setValue(startRow, startColumn, transformedRuleId); }); }); @@ -157,7 +163,10 @@ export class DataValidationCopyPasteController extends Disposable { } else { const manager = this._dataValidationModel.ensureManager(unitId, subUnitId) as SheetDataValidationManager; const ruleMatrix = manager.getRuleObjectMatrix().clone(); - const repeatRange = getRepeatRange(copyInfo.copyRange, pastedRange, true); + + const { ranges: [vCopyRange, vPastedRange], mapFunc } = virtualizeDiscreteRanges([copyInfo.copyRange, pastedRange]); + + const repeatRange = getRepeatRange(vCopyRange, vPastedRange, true); repeatRange.forEach(({ startRange }) => { this._copyInfo?.matrix.forValue((row, col, ruleId) => { @@ -170,7 +179,8 @@ export class DataValidationCopyPasteController extends Disposable { }, startRange ); - ruleMatrix.setValue(range.startRow, range.startColumn, ruleId); + const { row: startRow, col: startColumn } = mapFunc(range.startRow, range.startColumn); + ruleMatrix.setValue(startRow, startColumn, ruleId); }); }); diff --git a/packages/sheets-data-validation/src/widgets/dropdown-widget.ts b/packages/sheets-data-validation/src/widgets/dropdown-widget.ts index 466ba8374b97..856d2fceea9d 100644 --- a/packages/sheets-data-validation/src/widgets/dropdown-widget.ts +++ b/packages/sheets-data-validation/src/widgets/dropdown-widget.ts @@ -16,7 +16,7 @@ import { BooleanNumber, DataValidationRenderMode, DEFAULT_EMPTY_DOCUMENT_VALUE, DocumentDataModel, HorizontalAlign, ICommandService, LocaleService, Tools, VerticalAlign, WrapStrategy } from '@univerjs/core'; import type { ICellRenderContext, IDocumentData, IPaddingData, IStyleData, Nullable } from '@univerjs/core'; -import { Documents, DocumentSkeleton, DocumentViewModel, getDocsSkeletonPageSize, type IMouseEvent, type IPointerEvent, Rect, type Spreadsheet, type SpreadsheetSkeleton, type UniverRenderingContext2D } from '@univerjs/engine-render'; +import { Documents, DocumentSkeleton, DocumentViewModel, getDocsSkeletonPageSize, type IMouseEvent, type IPointerEvent, Rect, type SpreadsheetSkeleton, type UniverRenderingContext2D } from '@univerjs/engine-render'; import { Inject } from '@wendellhu/redi'; import { DataValidationModel, DataValidatorRegistryService, type IBaseDataValidationWidget } from '@univerjs/data-validation'; import { getCellValueOrigin } from '../utils/get-cell-data-origin'; @@ -126,6 +126,7 @@ export class DropdownWidget implements IBaseDataValidationWidget { }; private _dropdownInfoMap: Map> = new Map(); + constructor( @Inject(LocaleService) private readonly _localeService: LocaleService, @ICommandService private readonly _commandService: ICommandService, @@ -173,7 +174,7 @@ export class DropdownWidget implements IBaseDataValidationWidget { ctx.restore(); } - drawWith(ctx: UniverRenderingContext2D, info: ICellRenderContext, skeleton: SpreadsheetSkeleton, spreadsheets: Spreadsheet): void { + drawWith(ctx: UniverRenderingContext2D, info: ICellRenderContext, skeleton: SpreadsheetSkeleton): void { const { primaryWithCoord, row, col, style, data, subUnitId } = info; const cellBounding = primaryWithCoord.isMergedMainCell ? primaryWithCoord.mergeInfo : primaryWithCoord; const cellWidth = cellBounding.endX - cellBounding.startX; diff --git a/packages/sheets-filter-ui/README.md b/packages/sheets-filter-ui/README.md new file mode 100644 index 000000000000..66a9d924af62 --- /dev/null +++ b/packages/sheets-filter-ui/README.md @@ -0,0 +1,16 @@ +# @univerjs/sheets-filter-ui + +[![npm version](https://img.shields.io/npm/v/@univerjs/0.1.1)](https://npmjs.org/packages/@univerjs/0.1.1) +[![license](https://img.shields.io/npm/l/@univerjs/0.1.1)](https://img.shields.io/npm/l/@univerjs/0.1.1) + +## Introduction + +> TODO: Introduction + +## Usage + +### Installation + +```shell +npm i @univerjs/0.1.1 +``` diff --git a/packages/sheets-filter-ui/package.json b/packages/sheets-filter-ui/package.json new file mode 100644 index 000000000000..9a84ca07acbd --- /dev/null +++ b/packages/sheets-filter-ui/package.json @@ -0,0 +1,97 @@ +{ + "name": "@univerjs/sheets-filter-ui", + "version": "0.0.1", + "private": true, + "description": "", + "author": "DreamNum ", + "license": "Apache-2.0", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/univer" + }, + "homepage": "https://univer.ai", + "repository": { + "type": "git", + "url": "https://github.com/dream-num/univer" + }, + "bugs": { + "url": "https://github.com/dream-num/univer/issues" + }, + "keywords": [], + "sideEffects": [ + "**/*.css" + ], + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "main": "./lib/cjs/index.js", + "module": "./lib/es/index.js", + "types": "./lib/types/index.d.ts", + "publishConfig": { + "access": "public", + "main": "./lib/cjs/index.js", + "module": "./lib/es/index.js", + "exports": { + ".": { + "import": "./lib/es/index.js", + "require": "./lib/cjs/index.js", + "types": "./lib/types/index.d.ts" + }, + "./*": { + "import": "./lib/es/*", + "require": "./lib/cjs/*", + "types": "./lib/types/index.d.ts" + }, + "./lib/*": "./lib/*" + } + }, + "directories": { + "lib": "lib" + }, + "files": [ + "lib" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "coverage": "vitest run --coverage", + "lint:types": "tsc --noEmit", + "build": "tsc && vite build" + }, + "peerDependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/design": "workspace:*", + "@univerjs/engine-render": "workspace:*", + "@univerjs/sheets": "workspace:*", + "@univerjs/sheets-filter": "workspace:*", + "@univerjs/sheets-ui": "workspace:*", + "@univerjs/ui": "workspace:*", + "@wendellhu/redi": ">=0.12.13", + "clsx": ">=2.0.0", + "react": ">=16.9.0", + "rxjs": ">=7.0.0" + }, + "dependencies": { + "@univerjs/icons": "^0.1.42", + "rc-virtual-list": "^3.11.4" + }, + "devDependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/design": "workspace:*", + "@univerjs/engine-render": "workspace:*", + "@univerjs/shared": "workspace:*", + "@univerjs/sheets": "workspace:*", + "@univerjs/sheets-filter": "workspace:*", + "@univerjs/sheets-ui": "workspace:*", + "@univerjs/ui": "workspace:*", + "@wendellhu/redi": "^0.13.1", + "clsx": "^2.1.0", + "less": "^4.2.0", + "react": "^18.2.0", + "rxjs": "^7.8.1", + "typescript": "^5.3.3", + "vite": "^5.1.4", + "vitest": "^1.3.1" + } +} diff --git a/packages/sheets-filter-ui/src/__testing__/data.ts b/packages/sheets-filter-ui/src/__testing__/data.ts new file mode 100644 index 000000000000..fc77001cace9 --- /dev/null +++ b/packages/sheets-filter-ui/src/__testing__/data.ts @@ -0,0 +1,433 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IWorkbookData } from '@univerjs/core'; +import { LocaleType } from '@univerjs/core'; +import { CustomFilterOperator, SHEET_FILTER_SNAPSHOT_ID } from '@univerjs/sheets-filter'; + +/// This file contains mock up snapshots for testing purposes. + +export function WithCustomFilterModelFactory(): IWorkbookData { + return { + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + cellData: { + 0: { 0: { v: 'Header' } }, + 1: { 0: { v: 1 } }, + 2: { 0: { v: 2 } }, + 3: { 0: { v: 3 } }, + 4: { 0: { v: 4 } }, + 5: { 0: { v: 5 } }, + 6: { 0: { v: 6 } }, + 7: { 0: { v: 7 } }, + 8: { 0: { v: 8 } }, + 9: { 0: { v: 9 } }, + }, + }, + }, + resources: [ + { + name: SHEET_FILTER_SNAPSHOT_ID, + data: JSON.stringify({ + sheet1: { + ref: { startColumn: 0, startRow: 0, endColumn: 5, endRow: 5 }, + filterColumns: [ + { + colId: 0, + customFilters: { + customFilters: [{ val: 123, operator: CustomFilterOperator.GREATER_THAN }], + }, + }, + ], + }, + }), + }, + ], + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [ + 'sheet1', + ], + styles: {}, + }; +} + +export function WithValuesFilterModelFactory(): IWorkbookData { + return { + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + cellData: { + 0: { 0: { v: 'Column Header' } }, // this row should not appear in filter panel + 1: { 0: { v: '1' } }, + 2: { 0: { v: '2' } }, + 3: { 0: { v: 'Michael Jackson King of Pop' } }, + 4: { 0: { v: 'Michael' } }, + 5: { 0: { v: 'Jackson' } }, + 6: { 0: { v: 'Evan Wallace' } }, + 7: { 0: { v: 'Evan' } }, + 8: { 0: { v: 'Wallace' } }, + 9: { 0: { v: 'Steve' } }, + 10: { 0: { v: 'Minecraft' } }, + }, + }, + }, + resources: [ + { + name: SHEET_FILTER_SNAPSHOT_ID, + data: JSON.stringify({ + sheet1: { + ref: { startColumn: 0, startRow: 0, endColumn: 10, endRow: 10 }, + filterColumns: [ + { + colId: 0, + filters: { filters: ['1'] }, + }, + ], + }, + }), + }, + ], + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [ + 'sheet1', + ], + styles: {}, + }; +} + +export function WithValuesAndEmptyFilterModelFactory(): IWorkbookData { + return { + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + cellData: { + 0: { 0: { v: 'Column Header' } }, // this row should not appear in filter panel + 1: { 0: { v: '1' } }, + 2: { 0: { v: '2' } }, + 3: { 0: { v: 'Michael Jackson King of Pop' } }, + 4: { 0: { v: 'Michael' } }, + 5: { 0: { v: 'Jackson' } }, + 6: { 0: { v: 'Evan Wallace' } }, + 7: { 0: { v: 'Evan' } }, + 8: { 0: { v: 'Wallace' } }, + 9: { 0: { v: 'Steve' } }, + 10: { 0: { v: 'Minecraft' } }, + 11: { 0: { v: '' } }, + }, + }, + }, + resources: [ + { + name: SHEET_FILTER_SNAPSHOT_ID, + data: JSON.stringify({ + sheet1: { + ref: { startColumn: 0, startRow: 0, endColumn: 11, endRow: 11 }, + filterColumns: [ + { + colId: 0, + filters: { blank: true, filters: ['1'] }, + }, + ], + + }, + }), + }, + ], + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [ + 'sheet1', + ], + styles: {}, + }; +} + +export function WithMultiEmptyCellsModelFactory(): IWorkbookData { + return { + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + cellData: { + 0: { 0: { v: 'Column Header' } }, // this row should not appear in filter panel + 1: { 0: { v: '1' } }, + 2: { 0: { v: '2' } }, + 3: { 0: { v: 'Michael Jackson King of Pop' } }, + 4: { 0: { v: 'Michael' } }, + 5: { 0: { v: 'Jackson' } }, + 6: { 0: { v: '' } }, + 7: { 0: { v: '' } }, + 8: { 0: { v: '' } }, + 9: { 0: { v: 'Steve' } }, + 10: { 0: { v: 'Minecraft' } }, + 11: { 0: { v: '' } }, + }, + }, + }, + resources: [ + { + name: SHEET_FILTER_SNAPSHOT_ID, + data: JSON.stringify({ + sheet1: { + ref: { startColumn: 0, startRow: 0, endColumn: 11, endRow: 11 }, + filterColumns: [ + { + colId: 0, + filters: { blank: true, filters: ['1'] }, + }, + ], + + }, + }), + }, + ], + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [ + 'sheet1', + ], + styles: {}, + }; +} + +export function WithTwoFilterColumnsFactory(): IWorkbookData { + return { + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + cellData: { + 0: { 0: { v: 'Column Header' }, 1: { v: 'Header 2' } }, // this row should not appear in filter panel + 1: { 0: { v: '1' }, 1: { v: 'a' } }, + 2: { 0: { v: '2' }, 1: { v: 'b' } }, + 3: { 0: { v: '3' }, 1: { v: 'c' } }, + 4: { 0: { v: '4' }, 1: { v: 'd' } }, + 5: { 0: { v: '5' }, 1: { v: 'e' } }, + 6: { 0: { v: '6' }, 1: { v: 'f' } }, + 7: { 0: { v: '7' }, 1: { v: 'g' } }, + 8: { 0: { v: '8' }, 1: { v: 'h' } }, + }, + }, + }, + resources: [ + { + name: SHEET_FILTER_SNAPSHOT_ID, + data: JSON.stringify({ + sheet1: { + ref: { startColumn: 0, startRow: 0, endColumn: 11, endRow: 11 }, + filterColumns: [ + { + colId: 0, + filters: { blank: true, filters: ['1', '2', '3'] }, + }, + ], + }, + }), + }, + ], + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [ + 'sheet1', + ], + styles: {}, + }; +} + +export function WithMergedCellFilterFactory(): IWorkbookData { + return { + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + cellData: { + 0: { 0: { v: 'Column Header' }, 1: { v: 'Header 2' } }, // this row should not appear in filter panel + 1: { 0: { v: '1' }, 1: { v: 'a' } }, + 2: { 0: { v: '2' }, 1: { v: 'b' } }, + 3: { 0: { v: '3' }, 1: { v: 'c' } }, + 4: { 0: { v: '' }, 1: { v: '' } }, + 5: { 0: { v: '5' }, 1: { v: 'e' } }, + 6: { 0: { v: '6' }, 1: { v: 'f' } }, + 7: { 0: { v: '7' }, 1: { v: 'g' } }, + 8: { 0: { v: '8' }, 1: { v: 'h' } }, + }, + mergeData: [ + { startRow: 3, endRow: 4, startColumn: 0, endColumn: 1 }, + ], + }, + }, + resources: [ + { + name: SHEET_FILTER_SNAPSHOT_ID, + data: JSON.stringify({ + sheet1: { + ref: { startColumn: 0, startRow: 0, endColumn: 11, endRow: 11 }, + filterColumns: [ + { + colId: 0, + filters: { blank: true, filters: ['3', '4', '5'] }, + }, + ], + + }, + }), + }, + ], + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [ + 'sheet1', + ], + styles: {}, + }; +} + +export const ITEMS = [ + { + checked: true, + count: 1, + index: 0, + isEmpty: false, + value: '1', + }, + { + checked: false, + count: 1, + index: 1, + isEmpty: false, + value: '2', + }, + { + checked: false, + count: 1, + index: 2, + isEmpty: false, + value: 'Michael Jackson King of Pop', + }, + { + checked: false, + count: 1, + index: 3, + isEmpty: false, + value: 'Michael', + }, + { + checked: false, + count: 1, + index: 4, + isEmpty: false, + value: 'Jackson', + }, + { + checked: false, + count: 1, + index: 5, + isEmpty: false, + value: 'Evan Wallace', + }, + { + checked: false, + count: 1, + index: 6, + isEmpty: false, + value: 'Evan', + }, + { + checked: false, + count: 1, + index: 7, + isEmpty: false, + value: 'Wallace', + }, + { + checked: false, + count: 1, + index: 8, + isEmpty: false, + value: 'Steve', + }, + { + checked: false, + count: 1, + index: 9, + isEmpty: false, + value: 'Minecraft', + }, +]; + +export const E_ITEMS = [ + { + checked: false, + count: 1, + index: 2, + isEmpty: false, + value: 'Michael Jackson King of Pop', + }, + { + checked: false, + count: 1, + index: 3, + isEmpty: false, + value: 'Michael', + }, + { + checked: false, + count: 1, + index: 5, + isEmpty: false, + value: 'Evan Wallace', + }, + { + checked: false, + count: 1, + index: 6, + isEmpty: false, + value: 'Evan', + }, + { + checked: false, + count: 1, + index: 7, + isEmpty: false, + value: 'Wallace', + }, + { + checked: false, + count: 1, + index: 8, + isEmpty: false, + value: 'Steve', + }, + { + checked: false, + count: 1, + index: 9, + isEmpty: false, + value: 'Minecraft', + }, +]; diff --git a/packages/sheets-filter-ui/src/commands/__tests__/sheets-filter.command.spec.ts b/packages/sheets-filter-ui/src/commands/__tests__/sheets-filter.command.spec.ts new file mode 100644 index 000000000000..116ff4a0c6d8 --- /dev/null +++ b/packages/sheets-filter-ui/src/commands/__tests__/sheets-filter.command.spec.ts @@ -0,0 +1,342 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IRange, IWorkbookData } from '@univerjs/core'; +import { ICommandService, IUniverInstanceService, LocaleType, Plugin, RANGE_TYPE, RedoCommand, UndoCommand, Univer, UniverInstanceType } from '@univerjs/core'; +import type { ISetRangeValuesCommandParams } from '@univerjs/sheets'; +import { NORMAL_SELECTION_PLUGIN_NAME, RefRangeService, SelectionManagerService, SetRangeValuesCommand, SetRangeValuesMutation, SheetInterceptorService, SheetPermissionService } from '@univerjs/sheets'; +import type { FilterModel, ISetSheetsFilterRangeMutationParams } from '@univerjs/sheets-filter'; +import { SetSheetsFilterRangeMutation, SheetsFilterService, UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; +import type { Dependency } from '@wendellhu/redi'; +import { Inject, Injector } from '@wendellhu/redi'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { IMessageService } from '@univerjs/ui'; +import { MockMessageService } from '@univerjs/ui/services/message/__testing__/mock-message.service.js'; + +import type { ISetSheetsFilterCriteriaCommandParams } from '../sheets-filter.command'; +import { ClearSheetsFilterCriteriaCommand, ReCalcSheetsFilterCommand, SetSheetsFilterCriteriaCommand, SmartToggleSheetsFilterCommand } from '../sheets-filter.command'; + +function testWorkbookDataFactory(): IWorkbookData { + return { + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + cellData: { + 0: { + 0: { + v: 'A1', + }, + }, + 1: { + 0: { + v: '1', + }, + 1: { + v: 'a', + }, + }, + 2: { + 0: { + v: '2', + }, + 1: { + v: 'b', + }, + }, + 3: { + 0: { + v: '3', + }, + 1: { + v: 'c', + }, + }, + }, + name: 'Sheet-001', + }, + }, + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [], + styles: {}, + }; +}; + +function createFilterCommandTestBed() { + const univer = new Univer(); + const injector = univer.__getInjector(); + const get = injector.get.bind(injector); + + class SheetsFilterCommandTestPlugin extends Plugin { + static override pluginName = 'sheets-filter-command-test'; + static override type = UniverInstanceType.SHEET; + constructor(_config: unknown, @Inject(Injector) protected readonly _injector: Injector) { + super(); + } + + override onStarting(injector: Injector): void { + ([ + [RefRangeService], + [SheetInterceptorService], + [SelectionManagerService], + [SheetPermissionService], + [IMessageService, { useClass: MockMessageService }], + ] as Dependency[]).forEach((d) => injector.add(d)); + } + } + + univer.registerPlugin(UniverSheetsFilterPlugin); + univer.registerPlugin(SheetsFilterCommandTestPlugin); + + univer.createUniverSheet(testWorkbookDataFactory()); + + const commandService = get(ICommandService); + [ + SetRangeValuesCommand, + SetRangeValuesMutation, + SmartToggleSheetsFilterCommand, + SetSheetsFilterCriteriaCommand, + ClearSheetsFilterCriteriaCommand, + ReCalcSheetsFilterCommand, + ].forEach((command) => commandService.registerCommand(command)); + + const univerInstanceService = get(IUniverInstanceService); + univerInstanceService.focusUnit('test'); + + return { univer, get }; +} + +describe('test sheets filter commands', () => { + let univer: Univer; + let get: Injector['get']; + let commandService: ICommandService; + let sheetsFilterService: SheetsFilterService; + let selectionManagerService: SelectionManagerService; + + beforeEach(() => { + const testBed = createFilterCommandTestBed(); + univer = testBed.univer; + get = testBed.get; + + commandService = get(ICommandService); + sheetsFilterService = get(SheetsFilterService); + selectionManagerService = get(SelectionManagerService); + }); + + afterEach(() => { + univer.dispose(); + }); + + function select(range: IRange) { + selectionManagerService.setCurrentSelection({ + pluginName: NORMAL_SELECTION_PLUGIN_NAME, + unitId: 'test', + sheetId: 'sheet1', + }); + + const { startColumn, startRow, endColumn, endRow } = range; + selectionManagerService.add([ + { + range: { startRow, startColumn, endColumn, endRow, rangeType: RANGE_TYPE.NORMAL }, + primary: { + startRow, + startColumn, + endColumn, + endRow, + actualRow: startRow, + actualColumn: startColumn, + isMerged: false, + isMergedMainCell: false, + }, + style: null, + }, + ]); + } + + function getFilterModel(): FilterModel { + return sheetsFilterService.getFilterModel('test', 'sheet1')!; + } + + describe('test "SmartToggleSheetsFilterCommand"', () => { + it('should remove filter when there is one', async () => { + expect(commandService.syncExecuteCommand(SetSheetsFilterRangeMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + range: { startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }, + } as ISetSheetsFilterRangeMutationParams)); + expect(getFilterModel()).toBeTruthy(); + + expect(await commandService.executeCommand(SmartToggleSheetsFilterCommand.id)).toBeTruthy(); + expect(getFilterModel()).toBeNull(); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + const filterModel = getFilterModel(); + expect(filterModel).toBeTruthy(); + expect(filterModel!.getRange()).toEqual({ startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + expect(getFilterModel()).toBeNull(); + }); + + it('should add filter when there is no filter', async () => { + expect(getFilterModel()).toBeNull(); + + // since there is no selection, it should return false + expect(await commandService.executeCommand(SmartToggleSheetsFilterCommand.id)).toBeFalsy(); + + select({ startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }); + expect(await commandService.executeCommand(SmartToggleSheetsFilterCommand.id)).toBeTruthy(); + expect(getFilterModel()!.getRange()).toEqual({ + startRow: 0, + startColumn: 0, + endRow: 5, + endColumn: 5, + rangeType: RANGE_TYPE.NORMAL, + }); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + expect(getFilterModel()).toBeNull(); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + expect(getFilterModel()).toBeTruthy(); + }); + }); + + describe('test "SetSheetsFilterCriteriaCommand"', () => { + it('should return false when there is not filter', async () => { + expect(await commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + criteria: null, + } as ISetSheetsFilterCriteriaCommandParams)).toBeFalsy(); + }); + + it('should set filter criteria works', async () => { + select({ startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }); + expect(await commandService.executeCommand(SmartToggleSheetsFilterCommand.id)).toBeTruthy(); + + // set filter criteria + expect(await commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + criteria: { + filters: { filters: ['1'] }, + }, + } as ISetSheetsFilterCriteriaCommandParams)).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set([2, 3, 4, 5])); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set()); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set([2, 3, 4, 5])); + + // manually set criteria to null + expect(await commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + criteria: null, + } as ISetSheetsFilterCriteriaCommandParams)).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set()); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set([2, 3, 4, 5])); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set()); + }); + }); + + describe('test "ClearSheetsFilterConditionsCommand"', () => { + it('should clear all filter criteria', async () => { + select({ startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }); + expect(await commandService.executeCommand(SmartToggleSheetsFilterCommand.id)).toBeTruthy(); + + expect(await commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + criteria: { + filters: { filters: ['1'] }, + }, + } as ISetSheetsFilterCriteriaCommandParams)).toBeTruthy(); + expect(await commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 1, + criteria: { + filters: { filters: ['b'] }, + }, + } as ISetSheetsFilterCriteriaCommandParams)).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set([1, 2, 3, 4, 5])); + + expect(await commandService.executeCommand(ClearSheetsFilterCriteriaCommand.id, { + unitId: 'test', + subUnitId: 'sheet1', + })).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set()); + + expect(await commandService.executeCommand(UndoCommand.id)).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set([1, 2, 3, 4, 5])); + + expect(await commandService.executeCommand(RedoCommand.id)).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set()); + }); + }); + + describe('test "ReCalcSheetsFilterConditionsCommand"', () => { + it('should reCalc after set range values and get correct result', async () => { + select({ startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }); + expect(await commandService.executeCommand(SmartToggleSheetsFilterCommand.id)).toBeTruthy(); + + expect(await commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + criteria: { + filters: { filters: ['1'] }, + }, + } as ISetSheetsFilterCriteriaCommandParams)).toBeTruthy(); + expect(await commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 1, + criteria: { + filters: { filters: ['b'] }, + }, + } as ISetSheetsFilterCriteriaCommandParams)).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set([1, 2, 3, 4, 5])); + + expect(await commandService.executeCommand(SetRangeValuesCommand.id, { + unitId: 'test', + subUnitId: 'sheet1', + value: { 1: { 1: { v: 'b' } } }, + } as ISetRangeValuesCommandParams)).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set([1, 2, 3, 4, 5])); + + expect(await commandService.executeCommand(ReCalcSheetsFilterCommand.id, { + unitId: 'test', + subUnitId: 'sheet1', + })).toBeTruthy(); + expect(getFilterModel()!.filteredOutRows).toEqual(new Set([2, 3, 4, 5])); + }); + }); +}); diff --git a/packages/sheets-filter-ui/src/commands/__tests__/sheets-filter.operation.spec.ts b/packages/sheets-filter-ui/src/commands/__tests__/sheets-filter.operation.spec.ts new file mode 100644 index 000000000000..76bd56bbd4b9 --- /dev/null +++ b/packages/sheets-filter-ui/src/commands/__tests__/sheets-filter.operation.spec.ts @@ -0,0 +1,170 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IOperation, IWorkbookData } from '@univerjs/core'; +import { CommandType, ICommandService, IContextService, LocaleService, LocaleType, Plugin, Univer, UniverInstanceType } from '@univerjs/core'; +import type { ISetSheetsFilterRangeMutationParams } from '@univerjs/sheets-filter'; +import { SetSheetsFilterRangeMutation, UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; +import type { Dependency } from '@wendellhu/redi'; +import { Inject, Injector } from '@wendellhu/redi'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { RefRangeService, SelectionManagerService, SheetInterceptorService } from '@univerjs/sheets'; +import type { IEditorBridgeServiceVisibleParam } from '@univerjs/sheets-ui'; +import { CloseFilterPanelOperation, FILTER_PANEL_OPENED_KEY, OpenFilterPanelOperation } from '../sheets-filter.operation'; +import { SheetsFilterPanelService } from '../../services/sheets-filter-panel.service'; + +const SetCellEditVisibleOperation: IOperation = { + id: 'sheet.operation.set-cell-edit-visible', + type: CommandType.OPERATION, + handler: () => { + return true; + }, +}; + + +function testWorkbookDataFactory(): IWorkbookData { + return { + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + cellData: { + 0: { + 0: { + v: 'A1', + }, + 1: { + v: 'B1', + }, + }, + }, + name: 'Sheet-001', + }, + }, + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [], + styles: {}, + }; +}; + +function createFilterOperationTestBed() { + const univer = new Univer(); + const injector = univer.__getInjector(); + const get = injector.get.bind(injector); + + class SheetsFilterOperationTestPlugin extends Plugin { + static override type = UniverInstanceType.SHEET; + static override pluginName = 'sheets-filter-operation-test'; + + constructor(_config: unknown, @Inject(Injector) protected readonly _injector: Injector) { + super(); + } + + override onStarting(injector: Injector): void { + ([ + [SheetInterceptorService], + [SheetsFilterPanelService], + [RefRangeService], + [SelectionManagerService], + ] as Dependency[]).forEach((d) => injector.add(d)); + } + } + + univer.registerPlugin(UniverSheetsFilterPlugin); + univer.registerPlugin(SheetsFilterOperationTestPlugin); + + univer.createUniverSheet(testWorkbookDataFactory()); + + get(LocaleService).load({}); + + const commandService = get(ICommandService); + + [ + OpenFilterPanelOperation, + CloseFilterPanelOperation, + SetCellEditVisibleOperation, + ].forEach((command) => commandService.registerCommand(command)); + + return { univer, get }; +} + +describe('test sheets filter ui operations', () => { + let univer: Univer; + let get: Injector['get']; + let contextService: IContextService; + let commandService: ICommandService; + let sheetsFilterPanelService: SheetsFilterPanelService; + + beforeEach(() => { + const testBed = createFilterOperationTestBed(); + univer = testBed.univer; + get = testBed.get; + + contextService = get(IContextService); + commandService = get(ICommandService); + sheetsFilterPanelService = get(SheetsFilterPanelService); + }); + + afterEach(() => { + univer.dispose(); + }); + + describe('test "OpenFilerPanelOperation"', () => { + it('should return false when the filter model is not found', () => { + const spy = vi.spyOn(sheetsFilterPanelService, 'setupCol'); + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + })).toBeFalsy(); + expect(spy).not.toHaveBeenCalled(); + }); + + it('should open the filter panel and setup the filter model on the specific column', () => { + const spy = vi.spyOn(sheetsFilterPanelService, 'setupCol'); + expect(commandService.syncExecuteCommand(SetSheetsFilterRangeMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + range: { startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }, + } as ISetSheetsFilterRangeMutationParams)).toBeTruthy(); + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + })).toBeTruthy(); + expect(spy).toHaveBeenCalledTimes(1); + }); + }); + + describe('test "CloseFilterPanelOperation"', () => { + it('should return false when the filter panel is not activated', () => { + const spy = vi.spyOn(sheetsFilterPanelService, 'terminate'); + contextService.setContextValue(FILTER_PANEL_OPENED_KEY, false); + expect(commandService.syncExecuteCommand(CloseFilterPanelOperation.id)).toBeFalsy(); + expect(spy).not.toHaveBeenCalled(); + }); + + it('should call sheets filter panel service to close the panel is activated', () => { + const spy = vi.spyOn(sheetsFilterPanelService, 'terminate'); + contextService.setContextValue(FILTER_PANEL_OPENED_KEY, true); + expect(commandService.syncExecuteCommand(CloseFilterPanelOperation.id)).toBeTruthy(); + expect(contextService.getContextValue(FILTER_PANEL_OPENED_KEY)).toBeFalsy(); + expect(spy).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/packages/sheets-filter-ui/src/commands/sheets-filter.command.ts b/packages/sheets-filter-ui/src/commands/sheets-filter.command.ts new file mode 100644 index 000000000000..9812212f3ca9 --- /dev/null +++ b/packages/sheets-filter-ui/src/commands/sheets-filter.command.ts @@ -0,0 +1,317 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ICommand, IMutationInfo, Nullable, Workbook } from '@univerjs/core'; +import { CommandType, ICommandService, IUndoRedoService, IUniverInstanceService, LocaleService, sequenceExecute, UniverInstanceType } from '@univerjs/core'; +import { MessageType } from '@univerjs/design'; +import type { ISheetCommandSharedParams } from '@univerjs/sheets'; +import { isSingleCellSelection, SelectionManagerService } from '@univerjs/sheets'; +import type { FilterColumn, IAutoFilter, IFilterColumn, IReCalcSheetsFilterMutationParams, ISetSheetsFilterCriteriaMutationParams, ISetSheetsFilterRangeMutationParams } from '@univerjs/sheets-filter'; +import { ReCalcSheetsFilterMutation, RemoveSheetsFilterMutation, SetSheetsFilterCriteriaMutation, SetSheetsFilterRangeMutation, SheetsFilterService } from '@univerjs/sheets-filter'; +import { expandToContinuousRange } from '@univerjs/sheets-ui'; +import { IMessageService } from '@univerjs/ui'; +import { type IAccessor, Quantity } from '@wendellhu/redi'; + +/** + * This command is for toggling filter in the currently active Worksheet. + */ +export const SmartToggleSheetsFilterCommand: ICommand = { + id: 'sheet.command.smart-toggle-filter', + type: CommandType.COMMAND, + handler: async (accessor: IAccessor) => { + const univerInstanceService = accessor.get(IUniverInstanceService); + const sheetsFilterService = accessor.get(SheetsFilterService); + const commandService = accessor.get(ICommandService); + const undoRedoService = accessor.get(IUndoRedoService); + + const currentWorkbook = univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET); + const currentWorksheet = currentWorkbook?.getActiveSheet(); + if (!currentWorksheet || !currentWorkbook) return false; + + const unitId = currentWorkbook.getUnitId(); + const subUnitId = currentWorksheet.getSheetId(); + + // If there is a filter model, we should remove it and prepare undo redo. + const filterModel = sheetsFilterService.getFilterModel(unitId, subUnitId); + if (filterModel) { + const autoFilter = filterModel?.serialize(); + const undoMutations = destructFilterModel(unitId, subUnitId, autoFilter); + const result = commandService.syncExecuteCommand(RemoveSheetsFilterMutation.id, { unitId, subUnitId }); + if (result) { + undoRedoService.pushUndoRedo({ + unitID: unitId, + undoMutations, + redoMutations: [{ id: RemoveSheetsFilterMutation.id, params: { unitId, subUnitId } }], + }); + } + + return result; + } + + const selectionManager = accessor.get(SelectionManagerService); + const lastSelection = selectionManager.getLast(); + if (!lastSelection) return false; + + const startRange = lastSelection.range; + const targetFilterRange = isSingleCellSelection(lastSelection) + ? expandToContinuousRange(startRange, { left: true, right: true, up: true, down: true }, currentWorksheet) + : startRange; + + if (targetFilterRange.endRow === targetFilterRange.startRow) { + const messageService = accessor.get(IMessageService, Quantity.OPTIONAL); + const localeService = accessor.get(LocaleService); + messageService?.show({ type: MessageType.Warning, content: localeService.t('sheets-filter.command.not-valid-filter-range') }); + return false; + } + + // Execute the command to set filter range and prepare undo redo. + const redoMutation = { id: SetSheetsFilterRangeMutation.id, params: { unitId, subUnitId, range: targetFilterRange } }; + const result = commandService.syncExecuteCommand(redoMutation.id, redoMutation.params); + if (result) { + undoRedoService.pushUndoRedo({ + unitID: unitId, + undoMutations: [{ id: RemoveSheetsFilterMutation.id, params: { unitId, subUnitId } }], + redoMutations: [redoMutation], + }); + } + + return result; + }, +}; + +export interface ISetSheetsFilterCriteriaCommandParams extends ISheetCommandSharedParams { + col: number; + criteria: Nullable; +} +/** + * This command is for setting filter criteria to a column in the targeting `FilterModel`. + */ +export const SetSheetsFilterCriteriaCommand: ICommand = { + id: 'sheet.command.set-filter-criteria', + type: CommandType.COMMAND, + handler: async (accessor: IAccessor, params: ISetSheetsFilterCriteriaCommandParams) => { + const sheetsFilterService = accessor.get(SheetsFilterService); + const commandService = accessor.get(ICommandService); + const undoRedoService = accessor.get(IUndoRedoService); + + const { unitId, subUnitId, col, criteria } = params; + const filterModel = sheetsFilterService.getFilterModel(unitId, subUnitId); + if (!filterModel) { + return false; + } + + const range = filterModel.getRange(); + if (!range || col < range.startColumn || col > range.endColumn) { + return false; + } + + const filterColumn = filterModel.getFilterColumn(col); + const undoMutation = destructFilterColumn(unitId, subUnitId, col, filterColumn); + const redoMutation: IMutationInfo = { + id: SetSheetsFilterCriteriaMutation.id, + params: { + unitId, + subUnitId, + col, + criteria, + }, + }; + + const result = commandService.syncExecuteCommand(redoMutation.id, redoMutation.params); + if (result) { + undoRedoService.pushUndoRedo({ + unitID: unitId, + undoMutations: [undoMutation], + redoMutations: [redoMutation], + }); + } + + return result; + }, +}; + +/** + * This command is for clearing all filter criteria in the currently active `FilterModel`. + */ +export const ClearSheetsFilterCriteriaCommand: ICommand = { + id: 'sheet.command.clear-filter-criteria', + type: CommandType.COMMAND, + handler: (accessor: IAccessor) => { + const sheetsFilterService = accessor.get(SheetsFilterService); + const undoRedoService = accessor.get(IUndoRedoService); + const commandService = accessor.get(ICommandService); + + const currentFilterModel = sheetsFilterService.activeFilterModel; + if (!currentFilterModel) { + return false; + } + + // TODO@wzhudev: should also return when no filter criteria is set + + const { unitId, subUnitId } = currentFilterModel; + const autoFilter = currentFilterModel.serialize(); + const undoMutations = destructFilterCriteria(unitId, subUnitId, autoFilter); + const redoMutations = generateRemoveCriteriaMutations(unitId, subUnitId, autoFilter); + + const result = sequenceExecute(redoMutations, commandService); + if (result) { + undoRedoService.pushUndoRedo({ + unitID: unitId, + undoMutations, + redoMutations, + }); + } + + return true; + }, +}; + +/** + * This command force the currently active `FilterModel` to re-calculate all filter criteria. + */ +export const ReCalcSheetsFilterCommand: ICommand = { + id: 'sheet.command.re-calc-filter', + type: CommandType.COMMAND, + handler: (accessor: IAccessor) => { + const sheetsFilterService = accessor.get(SheetsFilterService); + const commandService = accessor.get(ICommandService); + + const currentFilterModel = sheetsFilterService.activeFilterModel; + if (!currentFilterModel) { + return false; + } + + // No need to handle undo redo for this command. + const { unitId, subUnitId } = currentFilterModel; + return commandService.executeCommand(ReCalcSheetsFilterMutation.id, { unitId, subUnitId } as IReCalcSheetsFilterMutationParams); + }, +}; + +/** + * Destruct a `FilterModel` to a list of mutations. + * @param unitId the unit id of the Workbook + * @param subUnitId the sub unit id of the Worksheet + * @param autoFilter the to be destructed FilterModel + * @returns a list of mutations those can be used to reconstruct the FilterModel + */ +function destructFilterModel( + unitId: string, + subUnitId: string, + autoFilter: IAutoFilter +): IMutationInfo[] { + const mutations: IMutationInfo[] = []; + + const setFilterMutation: IMutationInfo = { + id: SetSheetsFilterRangeMutation.id, + params: { + unitId, + subUnitId, + range: autoFilter.ref, + }, + }; + mutations.push(setFilterMutation); + + const criteriaMutations = destructFilterCriteria(unitId, subUnitId, autoFilter); + criteriaMutations.forEach((m) => mutations.push(m)); + + return mutations; +} + +export function destructFilterCriteria( + unitId: string, + subUnitId: string, + autoFilter: IAutoFilter +): IMutationInfo[] { + const mutations: IMutationInfo[] = []; + + autoFilter.filterColumns?.forEach((filterColumn) => { + const setFilterCriteriaMutation: IMutationInfo = { + id: SetSheetsFilterCriteriaMutation.id, + params: { + unitId, + subUnitId, + col: filterColumn.colId, + criteria: filterColumn, + }, + }; + mutations.push(setFilterCriteriaMutation); + }); + + return mutations; +} + +/** Generate mutations to remove all criteria on a `FilterModel` */ +function generateRemoveCriteriaMutations( + unitId: string, + subUnitId: string, + autoFilter: IAutoFilter +): IMutationInfo[] { + const mutations: IMutationInfo[] = []; + + autoFilter.filterColumns?.forEach((filterColumn) => { + const removeFilterCriteriaMutation: IMutationInfo = { + id: SetSheetsFilterCriteriaMutation.id, + params: { + unitId, + subUnitId, + col: filterColumn.colId, + criteria: null, + }, + }; + mutations.push(removeFilterCriteriaMutation); + }); + + return mutations; +} + +/** + * Prepare the undo mutation, it should rollback to the old criteria if there's already a `FilterColumn`, + * or remove the filter criteria when there is no `FilterColumn`. + * @param unitId + * @param subUnitId + * @param colId + * @param filterColumn + * @returns the undo mutation + */ +function destructFilterColumn( + unitId: string, + subUnitId: string, + colId: number, + filterColumn: Nullable +): IMutationInfo { + if (!filterColumn) { + return { + id: SetSheetsFilterCriteriaMutation.id, + params: { + unitId, + subUnitId, + col: colId, + criteria: null, + }, + }; + } + + const serialize = filterColumn.serialize(); + return { + id: SetSheetsFilterCriteriaMutation.id, + params: { + unitId, + subUnitId, + col: colId, + criteria: serialize, + }, + }; +} diff --git a/packages/sheets-filter-ui/src/commands/sheets-filter.operation.ts b/packages/sheets-filter-ui/src/commands/sheets-filter.operation.ts new file mode 100644 index 000000000000..370386209e56 --- /dev/null +++ b/packages/sheets-filter-ui/src/commands/sheets-filter.operation.ts @@ -0,0 +1,95 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CommandType, ICommandService, IContextService, type IOperation } from '@univerjs/core'; +import { SheetsFilterService } from '@univerjs/sheets-filter'; +import { ILayoutService } from '@univerjs/ui'; +import { Quantity } from '@wendellhu/redi'; +import { SetCellEditVisibleOperation } from '@univerjs/sheets-ui'; +import type { FilterBy } from '../services/sheets-filter-panel.service'; +import { SheetsFilterPanelService } from '../services/sheets-filter-panel.service'; + +export const FILTER_PANEL_OPENED_KEY = 'FILTER_PANEL_OPENED'; + +export interface IOpenFilterPanelOperationParams { + unitId: string; + subUnitId: string; + + col: number; +} + +/** + * The operation to open the filter panel and prepare for changing the filter conditions on a given column. + */ +export const OpenFilterPanelOperation: IOperation = { + id: 'sheet.operation.open-filter-panel', + type: CommandType.OPERATION, + handler: (accessor, params) => { + const contextService = accessor.get(IContextService); + const sheetsFilterService = accessor.get(SheetsFilterService); + const sheetsFilterPanelService = accessor.get(SheetsFilterPanelService); + const commandService = accessor.get(ICommandService); + + // Close the cell edit if it is opened. + commandService.syncExecuteCommand(SetCellEditVisibleOperation.id, { visible: false }); + + const { unitId, subUnitId, col } = params; + const filterModel = sheetsFilterService.getFilterModel(unitId, subUnitId); + if (!filterModel) return false; + + const result = sheetsFilterPanelService.setupCol(filterModel, col); + if (!result) return false; + + if (!contextService.getContextValue(FILTER_PANEL_OPENED_KEY)) { + contextService.setContextValue(FILTER_PANEL_OPENED_KEY, true); + } + + // Set the filter condition to the filter ui service on the specific column. + return true; + }, +}; + +export const CloseFilterPanelOperation: IOperation = { + id: 'sheet.operation.close-filter-panel', + type: CommandType.OPERATION, + handler: (accessor) => { + const contextService = accessor.get(IContextService); + const sheetsFilterPanelService = accessor.get(SheetsFilterPanelService); + const layoutService = accessor.get(ILayoutService, Quantity.OPTIONAL); + + if (contextService.getContextValue(FILTER_PANEL_OPENED_KEY)) { + contextService.setContextValue(FILTER_PANEL_OPENED_KEY, false); + layoutService?.focus(); + + return sheetsFilterPanelService.terminate(); + } + + return false; + }, +}; + +export interface IChangeFilterByOperationParams { + filterBy: FilterBy; +} +export const ChangeFilterByOperation: IOperation = { + id: 'sheet.operation.apply-filter', + type: CommandType.OPERATION, + handler: (accessor, params) => { + const { filterBy } = params!; + const sheetsFilterPanelService = accessor.get(SheetsFilterPanelService); + return sheetsFilterPanelService.changeFilterBy(filterBy); + }, +}; diff --git a/packages/sheets-filter-ui/src/controllers/__tests__/sheets-filter.menu.spec.ts b/packages/sheets-filter-ui/src/controllers/__tests__/sheets-filter.menu.spec.ts new file mode 100644 index 000000000000..f9bf2535babb --- /dev/null +++ b/packages/sheets-filter-ui/src/controllers/__tests__/sheets-filter.menu.spec.ts @@ -0,0 +1,230 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IRange, IWorkbookData } from '@univerjs/core'; +import { DisposableCollection, ICommandService, LocaleType, Plugin, RANGE_TYPE, Univer, UniverInstanceType } from '@univerjs/core'; +import { NORMAL_SELECTION_PLUGIN_NAME, RefRangeService, SelectionManagerService, SheetInterceptorService, SheetPermissionService } from '@univerjs/sheets'; +import type { ISetSheetsFilterCriteriaMutationParams, ISetSheetsFilterRangeMutationParams } from '@univerjs/sheets-filter'; +import { RemoveSheetsFilterMutation, SetSheetsFilterCriteriaMutation, SetSheetsFilterRangeMutation, UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; +import { DesktopMenuService, DesktopShortcutService, IMenuService, IShortcutService } from '@univerjs/ui'; +import { Injector } from '@wendellhu/redi'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { ClearSheetsFilterCriteriaCommand, ReCalcSheetsFilterCommand, SmartToggleSheetsFilterCommand } from '../../commands/sheets-filter.command'; +import { CloseFilterPanelOperation, OpenFilterPanelOperation } from '../../commands/sheets-filter.operation'; +import { ClearFilterCriteriaMenuItemFactory, ReCalcFilterMenuItemFactory, SmartToggleFilterMenuItemFactory } from '../sheets-filter.menu'; + +const TEST_WORKBOOK_DATA_DEMO: IWorkbookData = { + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + cellData: { + 0: { + 0: { + v: 'A1', + }, + }, + }, + }, + }, + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [], + styles: {}, +}; + +function createSheetsFilterMenuTestBed() { + const univer = new Univer(); + const injector = univer.__getInjector(); + const get = injector.get.bind(injector); + + class TestPlugin extends Plugin { + static override type = UniverInstanceType.SHEET; + static override pluginName = 'test-plugin'; + + constructor(_config: unknown, override readonly _injector: Injector) { + super(); + } + + override onStarting(injector: Injector): void { + injector.add([RefRangeService]); + injector.add([SelectionManagerService]); + injector.add([IShortcutService, { useClass: DesktopShortcutService }]); + injector.add([IMenuService, { useClass: DesktopMenuService }]); + injector.add([SheetPermissionService]); + injector.add([SheetInterceptorService]); + + const commandService = injector.get(ICommandService); + [ + SmartToggleSheetsFilterCommand, + ClearSheetsFilterCriteriaCommand, + ReCalcSheetsFilterCommand, + OpenFilterPanelOperation, + CloseFilterPanelOperation, + ].forEach((command) => commandService.registerCommand(command)); + } + } + + univer.registerPlugin(TestPlugin); + univer.registerPlugin(UniverSheetsFilterPlugin); + + const sheet = univer.createUniverSheet(TEST_WORKBOOK_DATA_DEMO); + + return { univer, get, sheet }; +} + +describe('test sheet filter menu items', () => { + let univer: Univer; + let get: Injector['get']; + + let disposableCollection: DisposableCollection; + + let commandService: ICommandService; + + beforeEach(() => { + const testBed = createSheetsFilterMenuTestBed(); + + univer = testBed.univer; + get = testBed.get; + + disposableCollection = new DisposableCollection(); + + commandService = get(ICommandService); + }); + + afterEach(() => { + univer.dispose(); + + disposableCollection.dispose(); + }); + + function select(range: IRange) { + const selectionManager = get(SelectionManagerService); + selectionManager.setCurrentSelection({ + pluginName: NORMAL_SELECTION_PLUGIN_NAME, + unitId: 'test', + sheetId: 'sheet1', + }); + + const { startColumn, startRow, endColumn, endRow } = range; + selectionManager.add([ + { + range: { startRow, startColumn, endColumn, endRow, rangeType: RANGE_TYPE.NORMAL }, + primary: { + startRow, + startColumn, + endColumn, + endRow, + actualRow: startRow, + actualColumn: startColumn, + isMerged: false, + isMergedMainCell: false, + }, + style: null, + }, + ]); + } + + it('should "SmartToggleSheetsFilterMenu" change status correctly', () => { + let activated = false; + + const menuItem = get(Injector).invoke(SmartToggleFilterMenuItemFactory); + disposableCollection.add(menuItem.activated$!.subscribe((value) => (activated = value))); + expect(activated).toBeFalsy(); + + expect(commandService.syncExecuteCommand(SetSheetsFilterRangeMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + range: { startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }, + } as ISetSheetsFilterRangeMutationParams)).toBeTruthy(); + expect(activated).toBeTruthy(); + + expect(commandService.syncExecuteCommand(RemoveSheetsFilterMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + })).toBeTruthy(); + expect(activated).toBeFalsy(); + }); + + it('should "ClearSheetsFilterCriteriaCommand" be enabled when there is filter criteria', () => { + let disabled = false; + + const menuItem = get(Injector).invoke(ClearFilterCriteriaMenuItemFactory); + disposableCollection.add(menuItem.disabled$!.subscribe((value) => (disabled = value))); + expect(disabled).toBeTruthy(); + + expect(commandService.syncExecuteCommand(SetSheetsFilterRangeMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + range: { startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }, + } as ISetSheetsFilterRangeMutationParams)).toBeTruthy(); + expect(disabled).toBeTruthy(); + + expect(commandService.syncExecuteCommand(SetSheetsFilterCriteriaMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + criteria: { + colId: 0, + filters: ['123'], + }, + } as ISetSheetsFilterCriteriaMutationParams)).toBeTruthy(); + expect(disabled).toBeFalsy(); + + expect(commandService.syncExecuteCommand(SetSheetsFilterCriteriaMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + criteria: null, + } as ISetSheetsFilterCriteriaMutationParams)).toBeTruthy(); + expect(disabled).toBeTruthy(); + }); + + it('should "ReCalcFilterMenuItemFactory" be enabled when there is filter criteria', () => { + let disabled = false; + + const menuItem = get(Injector).invoke(ReCalcFilterMenuItemFactory); + disposableCollection.add(menuItem.disabled$!.subscribe((value) => (disabled = value))); + expect(disabled).toBeTruthy(); + + expect(commandService.syncExecuteCommand(SetSheetsFilterRangeMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + range: { startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }, + } as ISetSheetsFilterRangeMutationParams)).toBeTruthy(); + expect(disabled).toBeTruthy(); + + expect(commandService.syncExecuteCommand(SetSheetsFilterCriteriaMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + criteria: { + colId: 0, + filters: ['123'], + }, + } as ISetSheetsFilterCriteriaMutationParams)).toBeTruthy(); + expect(disabled).toBeFalsy(); + + expect(commandService.syncExecuteCommand(SetSheetsFilterCriteriaMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + criteria: null, + } as ISetSheetsFilterCriteriaMutationParams)).toBeTruthy(); + expect(disabled).toBeTruthy(); + }); +}); diff --git a/packages/sheets-filter-ui/src/controllers/sheets-filter-render.controller.ts b/packages/sheets-filter-ui/src/controllers/sheets-filter-render.controller.ts new file mode 100644 index 000000000000..a57b548e2793 --- /dev/null +++ b/packages/sheets-filter-ui/src/controllers/sheets-filter-render.controller.ts @@ -0,0 +1,231 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IRange } from '@univerjs/core'; +import { CommandType, fromCallback, ICommandService, IUniverInstanceService, LifecycleStages, OnLifecycle, RxDisposable, ThemeService } from '@univerjs/core'; +import type { SpreadsheetSkeleton } from '@univerjs/engine-render'; +import { IRenderManagerService } from '@univerjs/engine-render'; +import type { ISelectionStyle, ISheetCommandSharedParams } from '@univerjs/sheets'; +import { INTERCEPTOR_POINT, SheetInterceptorService } from '@univerjs/sheets'; +import type { FilterModel } from '@univerjs/sheets-filter'; +import { FILTER_MUTATIONS, ReCalcSheetsFilterMutation, RemoveSheetsFilterMutation, SetSheetsFilterCriteriaMutation, SetSheetsFilterRangeMutation, SheetsFilterService } from '@univerjs/sheets-filter'; +import { getCoordByCell, ISelectionRenderService, SelectionShape, SheetRenderController, SheetSkeletonManagerService } from '@univerjs/sheets-ui'; +import type { IDisposable } from '@wendellhu/redi'; +import { Inject, Injector } from '@wendellhu/redi'; + +import { filter, map, of, startWith, switchMap, takeUntil, throttleTime } from 'rxjs'; +import type { ISheetsFilterButtonShapeProps } from '../views/widgets/filter-button.shape'; +import { FILTER_ICON_PADDING, FILTER_ICON_SIZE, SheetsFilterButtonShape } from '../views/widgets/filter-button.shape'; + +const DEFAULT_Z_INDEX = 1000; + +const SHEETS_FILTER_BUTTON_Z_INDEX = 5000; + +interface ISheetsFilterRenderParams { + unitId: string; + worksheetId: string; + filterModel?: FilterModel; + range?: IRange; + skeleton: SpreadsheetSkeleton; +} + +@OnLifecycle(LifecycleStages.Ready, SheetsFilterRenderController) +export class SheetsFilterRenderController extends RxDisposable { + private _filterRangeShape: SelectionShape | null = null; + private _buttonRenderDisposable: IDisposable | null = null; + private _filterButtonShapes: SheetsFilterButtonShape[] = []; + + constructor( + @Inject(Injector) private readonly _injector: Injector, + @Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService, + @Inject(SheetsFilterService) private readonly _sheetsFilterService: SheetsFilterService, + @Inject(ThemeService) private readonly _themeService: ThemeService, + @Inject(SheetInterceptorService) private readonly _sheetInterceptorService: SheetInterceptorService, + @Inject(SheetRenderController) private _sheetRenderController: SheetRenderController, + @ICommandService private readonly _commandService: ICommandService, + @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, + @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, + @ISelectionRenderService private readonly _selectionRenderService: ISelectionRenderService + ) { + super(); + + [ + SetSheetsFilterRangeMutation, + SetSheetsFilterCriteriaMutation, + RemoveSheetsFilterMutation, + ReCalcSheetsFilterMutation, + ].forEach((m) => this.disposeWithMe(this._sheetRenderController.registerSkeletonChangingMutations(m.id))); + + this._initRenderer(); + } + + private _initRenderer(): void { + // Subscribe to skeleton change and filter model change. + this._sheetSkeletonManagerService.currentSkeleton$ + .pipe( + switchMap((skeletonParams) => { + if (!skeletonParams) { + return of(null); + } + + const { unitId } = skeletonParams; + const workbook = this._univerInstanceService.getUniverSheetInstance(unitId); + if (!workbook) { + return of(null); + } + + const worksheetId = workbook.getActiveSheet().getSheetId(); + const filterModel = this._sheetsFilterService.getFilterModel(unitId, worksheetId) ?? undefined; + const getParams = (): ISheetsFilterRenderParams => ({ + unitId, + worksheetId, + filterModel, + range: filterModel?.getRange(), + skeleton: skeletonParams.skeleton, + }); + + return fromCallback(this._commandService.onCommandExecuted) + .pipe( + filter(([command]) => + command.type === CommandType.MUTATION + && (command.params as ISheetCommandSharedParams).unitId === workbook.getUnitId() + && FILTER_MUTATIONS.has(command.id) + ), + throttleTime(20, undefined, { leading: false, trailing: true }), + map(getParams), + startWith(getParams()) // must trigger once + ); + }), + takeUntil(this.dispose$) + ) + .subscribe((renderParams) => { + this._disposeRendering(); + + // If there's no filter range, we don't need to render anything. + if (!renderParams || !renderParams.range) { + return; + } + + this._renderRange(renderParams.unitId, renderParams.range, renderParams.skeleton); + this._renderButtons(renderParams as Required); + }); + } + + private _renderRange(unitId: string, range: IRange, skeleton: SpreadsheetSkeleton): void { + const renderer = this._renderManagerService.getRenderById(unitId); + if (!renderer) { + return; + } + + const { scene } = renderer; + const { rangeWithCoord, style } = this._selectionRenderService.convertSelectionRangeToData({ + range, + primary: null, + style: null, + }); + + const { rowHeaderWidth, columnHeaderHeight } = skeleton; + const filterRangeShape = this._filterRangeShape = new SelectionShape(scene, DEFAULT_Z_INDEX, true, this._themeService); + filterRangeShape.update(rangeWithCoord, rowHeaderWidth, columnHeaderHeight, { + hasAutoFill: false, + fill: 'rgba(0, 0, 0, 0.0)', + ...style, + } as ISelectionStyle); + filterRangeShape.setEvent(false); + + scene.makeDirty(true); + } + + private _renderButtons(params: Required): void { + const { range, filterModel, unitId, skeleton, worksheetId } = params; + const currentRenderer = this._renderManagerService.getRenderById(unitId); + if (!currentRenderer) { + return; + } + + const { scene } = currentRenderer; + + // Push cell contents to leave space for the filter buttons. + this._interceptCellContent(params.range); + + // Create filter button shapes. + const { startColumn, endColumn, startRow } = range; + for (let col = startColumn; col <= endColumn; col++) { + const key = `sheets-filter-button-${col}`; + const startPosition = getCoordByCell(startRow, col, scene, skeleton); + const { startX, startY, endX, endY } = startPosition; + + // Too little space to draw the button, just ignore it. + const cellWidth = endX - startX; + const cellHeight = endY - startY; + if (cellHeight <= FILTER_ICON_PADDING || cellWidth <= FILTER_ICON_PADDING) { + continue; + } + + // In other cases we need to draw the button, and we need to take care of the position and clipping. + const hasCriteria = !!filterModel.getFilterColumn(col); + const iconStartX = endX - FILTER_ICON_SIZE - FILTER_ICON_PADDING; + const iconStartY = endY - FILTER_ICON_SIZE - FILTER_ICON_PADDING; + const props: ISheetsFilterButtonShapeProps = { + left: iconStartX, + top: iconStartY, + height: FILTER_ICON_SIZE, + width: FILTER_ICON_SIZE, + cellHeight, + cellWidth, + filterParams: { unitId, subUnitId: worksheetId, col, hasCriteria }, + }; + + const buttonShape = this._injector.createInstance(SheetsFilterButtonShape, key, props); + this._filterButtonShapes.push(buttonShape); + } + + scene.addObjects(this._filterButtonShapes, SHEETS_FILTER_BUTTON_Z_INDEX); + scene.makeDirty(); + } + + private _interceptCellContent(range: IRange): void { + const { startRow, startColumn, endColumn } = range; + this._buttonRenderDisposable = this._sheetInterceptorService.intercept(INTERCEPTOR_POINT.CELL_CONTENT, { + handler: (cell, pos, next) => { + const { row, col } = pos; + if (row !== startRow || col < startColumn || col > endColumn) { + return next(cell); + } + + return next({ + ...cell, + // @ts-ignore + fontRenderExtension: { + // @ts-ignore + ...cell?.fontRenderExtension, + rightOffset: FILTER_ICON_SIZE, + }, + }); + }, + }); + } + + private _disposeRendering(): void { + this._filterRangeShape?.dispose(); + this._filterButtonShapes.forEach((s) => s.dispose()); + this._buttonRenderDisposable?.dispose(); + + this._filterRangeShape = null; + this._buttonRenderDisposable = null; + this._filterButtonShapes = []; + } +} diff --git a/packages/sheets-filter-ui/src/controllers/sheets-filter-ui.controller.ts b/packages/sheets-filter-ui/src/controllers/sheets-filter-ui.controller.ts new file mode 100644 index 000000000000..fed6616efa8f --- /dev/null +++ b/packages/sheets-filter-ui/src/controllers/sheets-filter-ui.controller.ts @@ -0,0 +1,134 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Nullable } from '@univerjs/core'; +import { ICommandService, IContextService, LifecycleStages, OnLifecycle, RxDisposable } from '@univerjs/core'; +import type { IMenuItemFactory } from '@univerjs/ui'; +import { ComponentManager, IMenuService, IShortcutService } from '@univerjs/ui'; +import type { IDisposable } from '@wendellhu/redi'; +import { Inject, Injector } from '@wendellhu/redi'; + +import { distinctUntilChanged } from 'rxjs'; +import { SheetCanvasPopManagerService } from '@univerjs/sheets-ui'; +import { FilterSingle } from '@univerjs/icons'; + +import { ClearSheetsFilterCriteriaCommand, ReCalcSheetsFilterCommand, SetSheetsFilterCriteriaCommand, SmartToggleSheetsFilterCommand } from '../commands/sheets-filter.command'; +import { FilterPanel } from '../views/components/SheetsFilterPanel'; +import { ChangeFilterByOperation, CloseFilterPanelOperation, FILTER_PANEL_OPENED_KEY, OpenFilterPanelOperation } from '../commands/sheets-filter.operation'; +import { SheetsFilterPanelService } from '../services/sheets-filter-panel.service'; +import { SmartToggleFilterShortcut } from './sheets-filter.shortcut'; +import { ClearFilterCriteriaMenuItemFactory, ReCalcFilterMenuItemFactory, SmartToggleFilterMenuItemFactory } from './sheets-filter.menu'; + +export const FILTER_PANEL_POPUP_KEY = 'FILTER_PANEL_POPUP'; + +/** + * This controller controls the UI of "filter" features. Menus, commands and filter panel etc. Except for the rendering. + */ +@OnLifecycle(LifecycleStages.Ready, SheetsFilterUIController) +export class SheetsFilterUIController extends RxDisposable { + constructor( + @Inject(Injector) private readonly _injector: Injector, + @Inject(ComponentManager) private readonly _componentManager: ComponentManager, + @Inject(SheetsFilterPanelService) private readonly _sheetsFilterPanelService: SheetsFilterPanelService, + @Inject(SheetCanvasPopManagerService) private _sheetCanvasPopupService: SheetCanvasPopManagerService, + @IShortcutService private readonly _shortcutService: IShortcutService, + @ICommandService private readonly _commandService: ICommandService, + @IMenuService private readonly _menuService: IMenuService, + @IContextService private readonly _contextService: IContextService + ) { + super(); + + this._initCommands(); + this._initShortcuts(); + this._initMenuItems(); + this._initUI(); + } + + override dispose(): void { + super.dispose(); + + this._closeFilterPopup(); + } + + private _initShortcuts(): void { + [ + SmartToggleFilterShortcut, + ].forEach((shortcut) => { + this.disposeWithMe(this._shortcutService.registerShortcut(shortcut)); + }); + } + + private _initCommands(): void { + [ + SmartToggleSheetsFilterCommand, + SetSheetsFilterCriteriaCommand, + ClearSheetsFilterCriteriaCommand, + ReCalcSheetsFilterCommand, + ChangeFilterByOperation, + OpenFilterPanelOperation, + CloseFilterPanelOperation, + ].forEach((c) => { + this.disposeWithMe(this._commandService.registerCommand(c)); + }); + } + + private _initMenuItems(): void { + ([ + SmartToggleFilterMenuItemFactory, + ClearFilterCriteriaMenuItemFactory, + ReCalcFilterMenuItemFactory, + ] as IMenuItemFactory[]).forEach((factory) => { + this.disposeWithMe(this._menuService.addMenuItem(this._injector.invoke(factory))); + }); + } + + private _initUI(): void { + this.disposeWithMe(this._componentManager.register(FILTER_PANEL_POPUP_KEY, FilterPanel)); + this.disposeWithMe(this._componentManager.register('FilterSingle', FilterSingle)); + this.disposeWithMe(this._contextService.subscribeContextValue$(FILTER_PANEL_OPENED_KEY) + .pipe(distinctUntilChanged()) + .subscribe((open) => { + if (open) { + this._openFilterPopup(); + } else { + this._closeFilterPopup(); + } + })); + } + + private _popupDisposable?: Nullable; + private _openFilterPopup(): void { + const currentFilterModel = this._sheetsFilterPanelService.filterModel; + if (!currentFilterModel) { + throw new Error('[SheetsFilterUIController]: no filter model when opening filter popup!'); + } + + const range = currentFilterModel.getRange(); + const col = this._sheetsFilterPanelService.col; + const { startRow } = range; + this._popupDisposable = this._sheetCanvasPopupService.attachPopupToCell(startRow, col, { + componentKey: FILTER_PANEL_POPUP_KEY, + direction: 'horizontal', + closeOnSelfTarget: true, + onClickOutside: () => this._commandService.syncExecuteCommand(CloseFilterPanelOperation.id), + }); + } + + private _closeFilterPopup(): void { + this._popupDisposable?.dispose(); + this._popupDisposable = null; + } +} diff --git a/packages/sheets-filter-ui/src/controllers/sheets-filter.menu.ts b/packages/sheets-filter-ui/src/controllers/sheets-filter.menu.ts new file mode 100644 index 000000000000..f51a0ddecb1a --- /dev/null +++ b/packages/sheets-filter-ui/src/controllers/sheets-filter.menu.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getMenuHiddenObservable, MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui'; +import type { IAccessor } from '@wendellhu/redi'; +import { SheetsFilterService } from '@univerjs/sheets-filter'; +import { UniverInstanceType } from '@univerjs/core'; + +import { map, of, switchMap } from 'rxjs'; +import { ClearSheetsFilterCriteriaCommand, ReCalcSheetsFilterCommand, SmartToggleSheetsFilterCommand } from '../commands/sheets-filter.command'; + +export function SmartToggleFilterMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + const sheetsFilterService = accessor.get(SheetsFilterService); + + return { + id: SmartToggleSheetsFilterCommand.id, + group: MenuGroup.TOOLBAR_FORMULAS_INSERT, + type: MenuItemType.BUTTON_SELECTOR, + icon: 'FilterSingle', + tooltip: 'sheets-filter.toolbar.smart-toggle-filter-tooltip', + positions: [MenuPosition.TOOLBAR_START], + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.SHEET), + activated$: sheetsFilterService.activeFilterModel$.pipe(map((model) => !!model)), + }; +} + +export function ClearFilterCriteriaMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const sheetsFilterService = accessor.get(SheetsFilterService); + + return { + id: ClearSheetsFilterCriteriaCommand.id, + group: MenuGroup.TOOLBAR_OTHERS, + type: MenuItemType.BUTTON, + title: 'sheets-filter.toolbar.clear-filter-criteria', + positions: [SmartToggleSheetsFilterCommand.id], + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.SHEET), + disabled$: sheetsFilterService.activeFilterModel$.pipe(switchMap((model) => model?.hasCriteria$.pipe(map((m) => !m)) ?? of(true))), + }; +} + +export function ReCalcFilterMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const sheetsFilterService = accessor.get(SheetsFilterService); + + return { + id: ReCalcSheetsFilterCommand.id, + group: MenuGroup.TOOLBAR_OTHERS, + type: MenuItemType.BUTTON, + title: 'sheets-filter.toolbar.re-calc-filter-conditions', + positions: [SmartToggleSheetsFilterCommand.id], + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.SHEET), + disabled$: sheetsFilterService.activeFilterModel$.pipe(switchMap((model) => model?.hasCriteria$.pipe(map((m) => !m)) ?? of(true))), + }; +} diff --git a/packages/sheets-filter-ui/src/controllers/sheets-filter.shortcut.ts b/packages/sheets-filter-ui/src/controllers/sheets-filter.shortcut.ts new file mode 100644 index 000000000000..bbd736a28b66 --- /dev/null +++ b/packages/sheets-filter-ui/src/controllers/sheets-filter.shortcut.ts @@ -0,0 +1,27 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { type IShortcutItem, KeyCode, MetaKeys } from '@univerjs/ui'; +import { whenSheetEditorFocused } from '@univerjs/sheets-ui'; +import { SmartToggleSheetsFilterCommand } from '../commands/sheets-filter.command'; + +export const SmartToggleFilterShortcut: IShortcutItem = { + id: SmartToggleSheetsFilterCommand.id, + binding: KeyCode.L | MetaKeys.CTRL_COMMAND | MetaKeys.SHIFT, + description: 'sheets-filter.shortcut.smart-toggle-filter', + preconditions: whenSheetEditorFocused, + group: '4_sheet-edit', +}; diff --git a/packages/sheets-filter-ui/src/index.ts b/packages/sheets-filter-ui/src/index.ts new file mode 100644 index 000000000000..a42eed979d7c --- /dev/null +++ b/packages/sheets-filter-ui/src/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { UniverSheetsFilterUIPlugin } from './plugin'; +export { default as enUS } from './locale/en-US'; +export { default as zhCN } from './locale/zh-CN'; diff --git a/packages/sheets-filter-ui/src/locale/en-US.ts b/packages/sheets-filter-ui/src/locale/en-US.ts new file mode 100644 index 000000000000..6c07fa341c48 --- /dev/null +++ b/packages/sheets-filter-ui/src/locale/en-US.ts @@ -0,0 +1,66 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export default { + 'sheets-filter': { + toolbar: { + 'smart-toggle-filter-tooltip': 'Toggle Filter', + 'clear-filter-criteria': 'Clear Filter Conditions', + 're-calc-filter-conditions': 'Re-calc Filter Conditions', + }, + command: { + 'not-valid-filter-range': 'The selected range only has one row and not valid for filter.', + }, + shortcut: { + 'smart-toggle-filter': 'Toggle Filter', + }, + panel: { + 'clear-filter': 'Clear Filter', + cancel: 'Cancel', + confirm: 'Confirm', + 'by-values': 'By Values', + 'by-conditions': 'By Conditions', + 'filter-only': 'Filter Only', + 'search-placeholder': 'Use space to separate keywords', + 'select-all': 'Select All', + 'input-values-placeholder': 'Input Values', + and: 'AND', + or: 'OR', + empty: '(empty)', + '?': 'Use “?” to represent a single character.', + '*': 'Use “*” to represent multiple characters.', + }, + conditions: { + none: 'None', + empty: 'Is Empty', + 'not-empty': 'Is Not Empty', + 'text-contains': 'Text Contains', + 'does-not-contain': 'Text Does Not Contain', + 'starts-with': 'Text Starts With', + 'ends-with': 'Text Ends With', + equals: 'Text Equals', + 'greater-than': 'Greater Than', + 'greater-than-or-equal': 'Greater Than Or Equal To', + 'less-than': 'Less Than', + 'less-than-or-equal': 'Less Than Or Equal To', + equal: 'Equal', + 'not-equal': 'Not Equal', + between: 'Between', + 'not-between': 'Not Between', + custom: 'Custom', + }, + }, +}; diff --git a/packages/sheets-filter-ui/src/locale/index.ts b/packages/sheets-filter-ui/src/locale/index.ts new file mode 100644 index 000000000000..1eff5fc204b1 --- /dev/null +++ b/packages/sheets-filter-ui/src/locale/index.ts @@ -0,0 +1,18 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { default as enUS } from './en-US'; +export { default as zhCN } from './zh-CN'; diff --git a/packages/sheets-filter-ui/src/locale/zh-CN.ts b/packages/sheets-filter-ui/src/locale/zh-CN.ts new file mode 100644 index 000000000000..db2492d6e006 --- /dev/null +++ b/packages/sheets-filter-ui/src/locale/zh-CN.ts @@ -0,0 +1,70 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type enUS from './en-US'; + +const zLocale: typeof enUS = { + 'sheets-filter': { + toolbar: { + 'smart-toggle-filter-tooltip': '筛选', + 'clear-filter-criteria': '清除筛选条件', + 're-calc-filter-conditions': '重新计算', + }, + command: { + 'not-valid-filter-range': '选中的区域只有一行,无法进行筛选', + }, + shortcut: { + 'smart-toggle-filter': '切换筛选', + }, + panel: { + 'clear-filter': '清除筛选', + cancel: '取消', + confirm: '确认', + 'by-values': '按值', + 'by-conditions': '按条件', + 'filter-only': '仅筛选', + 'search-placeholder': '使用空格分隔关键字', + 'select-all': '全选', + 'input-values-placeholder': '请输入', + or: '或', + and: '和', + empty: '(空白)', + '?': '可用 ? 代表单个字符', + '*': '可用 * 代表任意多个字符', + }, + conditions: { + none: '无', + empty: '为空', + 'not-empty': '不为空', + 'text-contains': '文本包含', + 'does-not-contain': '文本不包含', + 'starts-with': '文本开头', + 'ends-with': '文本结尾', + equals: '文本相符', + 'greater-than': '大于', + 'greater-than-or-equal': '大于等于', + 'less-than': '小于', + 'less-than-or-equal': '小于等于', + equal: '等于', + 'not-equal': '不等于', + between: '介于', + 'not-between': '不介于', + custom: '自定义', + }, + }, +}; + +export default zLocale; diff --git a/packages/sheets-filter-ui/src/models/__tests__/conditions.spec.ts b/packages/sheets-filter-ui/src/models/__tests__/conditions.spec.ts new file mode 100644 index 000000000000..94d8d022a9d0 --- /dev/null +++ b/packages/sheets-filter-ui/src/models/__tests__/conditions.spec.ts @@ -0,0 +1,599 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; +import { BooleanNumber } from '@univerjs/core'; +import { CustomFilterOperator } from '@univerjs/sheets-filter'; + +import { FilterConditionItems } from '../conditions'; +import { ExtendCustomFilterOperator } from '../extended-operators'; + +describe('test "FilterConditionItems"', () => { + describe('test utils work as expected in different condition format situations', () => { + describe('test NONE condition', () => { + it('should load NONE condition from empty filter column', () => { + expect(FilterConditionItems.testMappingFilterColumn()).toEqual([FilterConditionItems.NONE, {}]); + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: undefined })).toEqual([FilterConditionItems.NONE, {}]); + }); + + it('should throw error when trying to get default params', () => { + expect(() => FilterConditionItems.NONE.getDefaultFormParams()).toThrowError(); + }); + + it('should mapped to its own operator', () => { + expect(FilterConditionItems.NONE.testMappingParams({ operator1: ExtendCustomFilterOperator.NONE })).toBeTruthy(); + expect(FilterConditionItems.NONE.testMappingParams({ operator1: ExtendCustomFilterOperator.NOT_EQUALS })).toBeFalsy(); + }); + + it('should map to empty filter column', () => { + // clear filter column + expect(FilterConditionItems.NONE.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.NONE })).toEqual(null); + }); + }); + + describe('test EMPTY condition', () => { + it('should load EMPTY condition from custom filter with empty string', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: '' }] } })) + .toEqual([FilterConditionItems.EMPTY, { operator1: ExtendCustomFilterOperator.EMPTY }]); + }); + + it('should throw error when trying to get default params', () => { + expect(() => FilterConditionItems.EMPTY.getDefaultFormParams()).toThrowError(); + }); + + it('should map to its own operator', () => { + expect(FilterConditionItems.EMPTY.testMappingParams({ operator1: ExtendCustomFilterOperator.EMPTY })).toBeTruthy(); + expect(FilterConditionItems.EMPTY.testMappingParams({ operator1: ExtendCustomFilterOperator.NONE })).toBeFalsy(); + }); + + it('should map to filter column with empty string', () => { + expect(FilterConditionItems.EMPTY.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.EMPTY })) + .toEqual({ customFilters: { customFilters: [{ val: '' }] } }); + }); + }); + + describe('test NOT_EMPTY condition', () => { + it('should load NOT_EMPTY condition from custom filter with space string', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ operator: CustomFilterOperator.NOT_EQUALS, val: ' ' }] } })) + .toEqual([FilterConditionItems.NOT_EMPTY, { operator1: ExtendCustomFilterOperator.NOT_EMPTY }]); + + // when the val is not a space char, it would map to NOT_EQUAL + expect(FilterConditionItems.testMappingFilterColumn({ + customFilters: { customFilters: [{ operator: CustomFilterOperator.NOT_EQUALS, val: '123' }] }, + })).toEqual([FilterConditionItems.NOT_EQUAL, { operator1: CustomFilterOperator.NOT_EQUALS, val1: '123' }]); + }); + + it('should throw error when trying to get default params', () => { + expect(() => FilterConditionItems.NOT_EMPTY.getDefaultFormParams()).toThrowError(); + }); + + it('should map to its own operator', () => { + expect(FilterConditionItems.NOT_EMPTY.testMappingParams({ operator1: ExtendCustomFilterOperator.NOT_EMPTY })).toBeTruthy(); + expect(FilterConditionItems.NOT_EMPTY.testMappingParams({ operator1: ExtendCustomFilterOperator.NONE })).toBeFalsy(); + }); + + it('should map to filter column with space string', () => { + expect(FilterConditionItems.NOT_EMPTY.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.NOT_EMPTY })) + .toEqual({ customFilters: { customFilters: [{ operator: CustomFilterOperator.NOT_EQUALS, val: ' ' }] } }); + }); + }); + + describe('test TEXT_CONTAINS condition', () => { + it('should load TEXT_CONTAINS condition from char set "*xxx*"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: '*xxx*' }] } })) + .toEqual([FilterConditionItems.TEXT_CONTAINS, { operator1: ExtendCustomFilterOperator.CONTAINS, val1: 'xxx' }]); + + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: '*xxx' }] } })) + .not.toEqual([FilterConditionItems.TEXT_CONTAINS, { operator1: ExtendCustomFilterOperator.CONTAINS, val1: 'xxx' }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.TEXT_CONTAINS.getDefaultFormParams()).toEqual({ operator1: ExtendCustomFilterOperator.CONTAINS, val1: '' }); + }); + + it('should mapped params with only one operator CONTAINS', () => { + expect(FilterConditionItems.TEXT_CONTAINS.testMappingParams({ operator1: ExtendCustomFilterOperator.CONTAINS })).toBeTruthy(); + expect(FilterConditionItems.TEXT_CONTAINS.testMappingParams({ operator1: ExtendCustomFilterOperator.DOES_NOT_CONTAIN })).toBeFalsy(); + expect(() => FilterConditionItems.TEXT_CONTAINS.testMappingParams({ operator1: ExtendCustomFilterOperator.CONTAINS, operator2: ExtendCustomFilterOperator.CONTAINS })) + .toThrowError(); + + expect(FilterConditionItems.TEXT_CONTAINS.testMappingParams({ operator2: ExtendCustomFilterOperator.CONTAINS })).toBeTruthy(); + }); + + it('should map to filter column with char set "*xxx*"', () => { + expect(FilterConditionItems.TEXT_CONTAINS.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.CONTAINS, val1: 'univer' })) + .toEqual({ customFilters: { customFilters: [{ val: '*univer*' }] } }); + + expect(FilterConditionItems.TEXT_CONTAINS.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.CONTAINS, val1: ' ' })) + .toEqual({ customFilters: { customFilters: [{ val: '* *' }] } }); + }); + }); + + describe('test DOES_NOT_CONTAIN condition', () => { + it('should load DOES_NOT_CONTAIN condition from char set "*xxx*"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: '*xxx*', operator: CustomFilterOperator.NOT_EQUALS }] } })) + .toEqual([FilterConditionItems.DOES_NOT_CONTAIN, { operator1: ExtendCustomFilterOperator.DOES_NOT_CONTAIN, val1: 'xxx' }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.DOES_NOT_CONTAIN.getDefaultFormParams()).toEqual({ operator1: ExtendCustomFilterOperator.DOES_NOT_CONTAIN, val1: '' }); + }); + + it('should mapped params with only one operator DOES_NOT_CONTAIN', () => { + expect(FilterConditionItems.DOES_NOT_CONTAIN.testMappingParams({ operator1: ExtendCustomFilterOperator.DOES_NOT_CONTAIN })).toBeTruthy(); + expect(FilterConditionItems.DOES_NOT_CONTAIN.testMappingParams({ operator1: ExtendCustomFilterOperator.CONTAINS })).toBeFalsy(); + expect(() => FilterConditionItems.DOES_NOT_CONTAIN.testMappingParams({ operator1: ExtendCustomFilterOperator.DOES_NOT_CONTAIN, operator2: ExtendCustomFilterOperator.DOES_NOT_CONTAIN })) + .toThrowError(); + + expect(FilterConditionItems.DOES_NOT_CONTAIN.testMappingParams({ operator2: ExtendCustomFilterOperator.DOES_NOT_CONTAIN })).toBeTruthy(); + }); + + it('should map to filter column with char set "*xxx*"', () => { + expect(FilterConditionItems.DOES_NOT_CONTAIN.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.DOES_NOT_CONTAIN, val1: 'univer' })) + .toEqual({ customFilters: { customFilters: [{ val: '*univer*', operator: CustomFilterOperator.NOT_EQUALS }] } }); + + expect(FilterConditionItems.DOES_NOT_CONTAIN.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.DOES_NOT_CONTAIN, val1: ' ' })) + .toEqual({ customFilters: { customFilters: [{ val: '* *', operator: CustomFilterOperator.NOT_EQUALS }] } }); + }); + }); + + describe('test STARTS_WITH', () => { + it('should load STARTS_WITH condition from char set "xxx*"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: 'xxx*' }] } })) + .toEqual([FilterConditionItems.STARTS_WITH, { operator1: ExtendCustomFilterOperator.STARTS_WITH, val1: 'xxx' }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.STARTS_WITH.getDefaultFormParams()).toEqual({ operator1: ExtendCustomFilterOperator.STARTS_WITH, val1: '' }); + }); + + it('should mapped params with only one operator STARTS_WITH', () => { + expect(FilterConditionItems.STARTS_WITH.testMappingParams({ operator1: ExtendCustomFilterOperator.STARTS_WITH })).toBeTruthy(); + expect(FilterConditionItems.STARTS_WITH.testMappingParams({ operator1: ExtendCustomFilterOperator.DOES_NOT_START_WITH })).toBeFalsy(); + expect(() => FilterConditionItems.STARTS_WITH.testMappingParams({ operator1: ExtendCustomFilterOperator.STARTS_WITH, operator2: ExtendCustomFilterOperator.STARTS_WITH })) + .toThrowError(); + + expect(FilterConditionItems.STARTS_WITH.testMappingParams({ operator2: ExtendCustomFilterOperator.STARTS_WITH })).toBeTruthy(); + }); + + it('should map to filter column with char set "xxx*"', () => { + expect(FilterConditionItems.STARTS_WITH.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.STARTS_WITH, val1: 'univer' })) + .toEqual({ customFilters: { customFilters: [{ val: 'univer*' }] } }); + + expect(FilterConditionItems.STARTS_WITH.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.STARTS_WITH, val1: ' ' })) + .toEqual({ customFilters: { customFilters: [{ val: ' *' }] } }); + }); + }); + + describe('test ENDS_WITH', () => { + it('should load ENDS_WITH condition from char set "*xxx"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: '*xxx' }] } })) + .toEqual([FilterConditionItems.ENDS_WITH, { operator1: ExtendCustomFilterOperator.ENDS_WITH, val1: 'xxx' }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.ENDS_WITH.getDefaultFormParams()).toEqual({ operator1: ExtendCustomFilterOperator.ENDS_WITH, val1: '' }); + }); + + it('should mapped params with only one operator ENDS_WITH', () => { + expect(FilterConditionItems.ENDS_WITH.testMappingParams({ operator1: ExtendCustomFilterOperator.ENDS_WITH })).toBeTruthy(); + expect(FilterConditionItems.ENDS_WITH.testMappingParams({ operator1: ExtendCustomFilterOperator.DOES_NOT_END_WITH })).toBeFalsy(); + expect(() => FilterConditionItems.ENDS_WITH.testMappingParams({ operator1: ExtendCustomFilterOperator.ENDS_WITH, operator2: ExtendCustomFilterOperator.ENDS_WITH })) + .toThrowError(); + + expect(FilterConditionItems.ENDS_WITH.testMappingParams({ operator2: ExtendCustomFilterOperator.ENDS_WITH })).toBeTruthy(); + }); + + it('should map to filter column with char set "*xxx"', () => { + expect(FilterConditionItems.ENDS_WITH.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.ENDS_WITH, val1: 'univer' })) + .toEqual({ customFilters: { customFilters: [{ val: '*univer' }] } }); + + expect(FilterConditionItems.ENDS_WITH.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.ENDS_WITH, val1: ' ' })) + .toEqual({ customFilters: { customFilters: [{ val: '* ' }] } }); + }); + }); + + describe('test EQUALS', () => { + it('should load EQUALS condition from char set "xxx"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: 'xxx' }] } })) + .toEqual([FilterConditionItems.EQUALS, { operator1: ExtendCustomFilterOperator.EQUALS, val1: 'xxx' }]); + + expect(FilterConditionItems.testMappingFilterColumn({ filters: { filters: [''] } })) + .toEqual([FilterConditionItems.EQUALS, { operator1: ExtendCustomFilterOperator.EQUALS, val1: '' }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.EQUALS.getDefaultFormParams()).toEqual({ operator1: ExtendCustomFilterOperator.EQUALS, val1: '' }); + }); + + it('should mapped params with only one operator EQUALS', () => { + expect(FilterConditionItems.EQUALS.testMappingParams({ operator1: ExtendCustomFilterOperator.EQUALS })).toBeTruthy(); + expect(FilterConditionItems.EQUALS.testMappingParams({ operator1: ExtendCustomFilterOperator.NOT_EQUALS })).toBeFalsy(); + expect(() => FilterConditionItems.EQUALS.testMappingParams({ operator1: ExtendCustomFilterOperator.EQUALS, operator2: ExtendCustomFilterOperator.EQUALS })) + .toThrowError(); + + expect(FilterConditionItems.EQUALS.testMappingParams({ operator2: ExtendCustomFilterOperator.EQUALS })).toBeTruthy(); + }); + + it('should map to filter column with char set "xxx"', () => { + expect(FilterConditionItems.EQUALS.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.EQUALS, val1: 'univer' })) + .toEqual({ customFilters: { customFilters: [{ val: 'univer' }] } }); + + expect(FilterConditionItems.EQUALS.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.EQUALS, val1: ' ' })) + .toEqual({ customFilters: { customFilters: [{ val: ' ' }] } }); + }); + }); + + describe('test GREATER_THAN', () => { + it('should load GREATER_THAN condition from operator"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: '123', operator: CustomFilterOperator.GREATER_THAN }] } })) + .toEqual([FilterConditionItems.GREATER_THAN, { operator1: CustomFilterOperator.GREATER_THAN, val1: '123' }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.GREATER_THAN.getDefaultFormParams()).toEqual({ operator1: CustomFilterOperator.GREATER_THAN, val1: '' }); + }); + + it('should mapped params with only one operator GREATER_THAN', () => { + expect(FilterConditionItems.GREATER_THAN.testMappingParams({ operator1: CustomFilterOperator.GREATER_THAN })).toBeTruthy(); + expect(FilterConditionItems.GREATER_THAN.testMappingParams({ operator1: CustomFilterOperator.LESS_THAN })).toBeFalsy(); + expect(() => FilterConditionItems.GREATER_THAN.testMappingParams({ operator1: CustomFilterOperator.GREATER_THAN, operator2: CustomFilterOperator.GREATER_THAN })) + .toThrowError(); + + expect(FilterConditionItems.GREATER_THAN.testMappingParams({ operator2: CustomFilterOperator.GREATER_THAN })).toBeTruthy(); + }); + + it('should map to filter column with operator GREATER_THAN', () => { + expect(FilterConditionItems.GREATER_THAN.mapToFilterColumn({ operator1: CustomFilterOperator.GREATER_THAN, val1: '123' })) + .toEqual({ customFilters: { customFilters: [{ val: '123', operator: CustomFilterOperator.GREATER_THAN }] } }); + }); + }); + + describe('test GREATER_THAN_OR_EQUAL', () => { + it('should load GREATER_THAN_OR_EQUAL condition from operator"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: '123', operator: CustomFilterOperator.GREATER_THAN_OR_EQUAL }] } })) + .toEqual([FilterConditionItems.GREATER_THAN_OR_EQUAL, { operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, val1: '123' }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.GREATER_THAN_OR_EQUAL.getDefaultFormParams()).toEqual({ operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, val1: '' }); + }); + + it('should mapped params with only one operator GREATER_THAN_OR_EQUAL', () => { + expect(FilterConditionItems.GREATER_THAN_OR_EQUAL.testMappingParams({ operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL })).toBeTruthy(); + expect(FilterConditionItems.GREATER_THAN_OR_EQUAL.testMappingParams({ operator1: CustomFilterOperator.LESS_THAN_OR_EQUAL })).toBeFalsy(); + expect(() => FilterConditionItems.GREATER_THAN_OR_EQUAL.testMappingParams({ operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, operator2: CustomFilterOperator.GREATER_THAN_OR_EQUAL })) + .toThrowError(); + + expect(FilterConditionItems.GREATER_THAN_OR_EQUAL.testMappingParams({ operator2: CustomFilterOperator.GREATER_THAN_OR_EQUAL })).toBeTruthy(); + }); + + it('should map to filter column with operator GREATER_THAN_OR_EQUAL', () => { + expect(FilterConditionItems.GREATER_THAN_OR_EQUAL.mapToFilterColumn({ operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, val1: '123' })) + .toEqual({ customFilters: { customFilters: [{ val: '123', operator: CustomFilterOperator.GREATER_THAN_OR_EQUAL }] } }); + }); + }); + + describe('test LESS_THAN', () => { + it('should load LESS_THAN condition from operator"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: '123', operator: CustomFilterOperator.LESS_THAN }] } })) + .toEqual([FilterConditionItems.LESS_THAN, { operator1: CustomFilterOperator.LESS_THAN, val1: '123' }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.LESS_THAN.getDefaultFormParams()).toEqual({ operator1: CustomFilterOperator.LESS_THAN, val1: '' }); + }); + + it('should mapped params with only one operator LESS_THAN', () => { + expect(FilterConditionItems.LESS_THAN.testMappingParams({ operator1: CustomFilterOperator.LESS_THAN })).toBeTruthy(); + expect(FilterConditionItems.LESS_THAN.testMappingParams({ operator1: CustomFilterOperator.GREATER_THAN })).toBeFalsy(); + expect(() => FilterConditionItems.LESS_THAN.testMappingParams({ operator1: CustomFilterOperator.LESS_THAN, operator2: CustomFilterOperator.LESS_THAN })) + .toThrowError(); + + expect(FilterConditionItems.LESS_THAN.testMappingParams({ operator2: CustomFilterOperator.LESS_THAN })).toBeTruthy(); + }); + + it('should map to filter column with operator LESS_THAN', () => { + expect(FilterConditionItems.LESS_THAN.mapToFilterColumn({ operator1: CustomFilterOperator.LESS_THAN, val1: '123' })) + .toEqual({ customFilters: { customFilters: [{ val: '123', operator: CustomFilterOperator.LESS_THAN }] } }); + }); + }); + + describe('test LESS_THAN_OR_EQUAL', () => { + it('should load LESS_THAN_OR_EQUAL condition from operator"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: '123', operator: CustomFilterOperator.LESS_THAN_OR_EQUAL }] } })) + .toEqual([FilterConditionItems.LESS_THAN_OR_EQUAL, { operator1: CustomFilterOperator.LESS_THAN_OR_EQUAL, val1: '123' }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.LESS_THAN_OR_EQUAL.getDefaultFormParams()).toEqual({ operator1: CustomFilterOperator.LESS_THAN_OR_EQUAL, val1: '' }); + }); + + it('should mapped params with only one operator LESS_THAN_OR_EQUAL', () => { + expect(FilterConditionItems.LESS_THAN_OR_EQUAL.testMappingParams({ operator1: CustomFilterOperator.LESS_THAN_OR_EQUAL })).toBeTruthy(); + expect(FilterConditionItems.LESS_THAN_OR_EQUAL.testMappingParams({ operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL })).toBeFalsy(); + expect(() => FilterConditionItems.LESS_THAN_OR_EQUAL.testMappingParams({ operator1: CustomFilterOperator.LESS_THAN_OR_EQUAL, operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL })) + .toThrowError(); + + expect(FilterConditionItems.LESS_THAN_OR_EQUAL.testMappingParams({ operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL })).toBeTruthy(); + }); + + it('should map to filter column with operator LESS_THAN_OR_EQUAL', () => { + expect(FilterConditionItems.LESS_THAN_OR_EQUAL.mapToFilterColumn({ operator1: CustomFilterOperator.LESS_THAN_OR_EQUAL, val1: '123' })) + .toEqual({ customFilters: { customFilters: [{ val: '123', operator: CustomFilterOperator.LESS_THAN_OR_EQUAL }] } }); + }); + }); + + describe('test EQUAL condition', () => { + it('should load EQUAL condition from operator"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: '123', operator: CustomFilterOperator.EQUAL }] } })) + .toEqual([FilterConditionItems.EQUAL, { operator1: CustomFilterOperator.EQUAL, val1: '123' }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.EQUAL.getDefaultFormParams()).toEqual({ operator1: CustomFilterOperator.EQUAL, val1: '' }); + }); + + it('should mapped params with only one operator EQUAL', () => { + expect(FilterConditionItems.EQUAL.testMappingParams({ operator1: CustomFilterOperator.EQUAL })).toBeTruthy(); + expect(FilterConditionItems.EQUAL.testMappingParams({ operator1: CustomFilterOperator.NOT_EQUALS })).toBeFalsy(); + expect(() => FilterConditionItems.EQUAL.testMappingParams({ operator1: CustomFilterOperator.EQUAL, operator2: CustomFilterOperator.EQUAL })) + .toThrowError(); + + expect(FilterConditionItems.EQUAL.testMappingParams({ operator2: CustomFilterOperator.EQUAL })).toBeTruthy(); + }); + + it('should map to filter column with operator EQUAL', () => { + expect(FilterConditionItems.EQUAL.mapToFilterColumn({ operator1: CustomFilterOperator.EQUAL, val1: '123' })) + .toEqual({ customFilters: { customFilters: [{ val: '123', operator: CustomFilterOperator.EQUAL }] } }); + }); + }); + + describe('test NOT_EQUAL', () => { + it('should load NOT_EQUAL condition from operator"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { customFilters: [{ val: '123', operator: CustomFilterOperator.NOT_EQUALS }] } })) + .toEqual([FilterConditionItems.NOT_EQUAL, { operator1: CustomFilterOperator.NOT_EQUALS, val1: '123' }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.NOT_EQUAL.getDefaultFormParams()).toEqual({ operator1: CustomFilterOperator.NOT_EQUALS, val1: '' }); + }); + + it('should mapped params with only one operator NOT_EQUAL', () => { + expect(FilterConditionItems.NOT_EQUAL.testMappingParams({ operator1: CustomFilterOperator.NOT_EQUALS })).toBeTruthy(); + expect(FilterConditionItems.NOT_EQUAL.testMappingParams({ operator1: CustomFilterOperator.EQUAL })).toBeFalsy(); + expect(() => FilterConditionItems.NOT_EQUAL.testMappingParams({ operator1: CustomFilterOperator.NOT_EQUALS, operator2: CustomFilterOperator.NOT_EQUALS })) + .toThrowError(); + + expect(FilterConditionItems.NOT_EQUAL.testMappingParams({ operator2: CustomFilterOperator.NOT_EQUALS })).toBeTruthy(); + }); + + it('should map to filter column with operator NOT_EQUAL', () => { + expect(FilterConditionItems.NOT_EQUAL.mapToFilterColumn({ operator1: CustomFilterOperator.NOT_EQUALS, val1: '123' })) + .toEqual({ customFilters: { customFilters: [{ val: '123', operator: CustomFilterOperator.NOT_EQUALS }] } }); + }); + }); + + describe('test BETWEEN', () => { + it('should load BETWEEN condition from correct combination of two operators"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ + customFilters: { + and: BooleanNumber.TRUE, + customFilters: [ + { val: '123', operator: CustomFilterOperator.GREATER_THAN_OR_EQUAL }, + { val: '456', operator: CustomFilterOperator.LESS_THAN_OR_EQUAL }, + ], + }, + })).toEqual([FilterConditionItems.BETWEEN, { + and: true, + operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, val1: '123', + operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL, val2: '456', + }]); + + expect(FilterConditionItems.testMappingFilterColumn({ + customFilters: { + and: BooleanNumber.TRUE, + customFilters: [ + { val: '456', operator: CustomFilterOperator.LESS_THAN_OR_EQUAL }, + { val: '123', operator: CustomFilterOperator.GREATER_THAN_OR_EQUAL }, + ], + }, + })).toEqual([FilterConditionItems.BETWEEN, { + and: true, + operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, val1: '123', + operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL, val2: '456', + }]); + + expect(FilterConditionItems.testMappingFilterColumn({ + customFilters: { + customFilters: [ + { val: '456', operator: CustomFilterOperator.LESS_THAN_OR_EQUAL }, + { val: '123', operator: CustomFilterOperator.GREATER_THAN_OR_EQUAL }, + ], + }, + })).not.toEqual([FilterConditionItems.BETWEEN, { + and: true, + operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, val1: '123', + operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL, val2: '456', + }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.BETWEEN.getDefaultFormParams()).toEqual({ + and: true, + operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, val1: '', + operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL, val2: '', + }); + }); + + it('should mapped params with only two operators GREATER_THAN_OR_EQUAL and LESS_THAN_OR_EQUAL', () => { + expect(FilterConditionItems.BETWEEN.testMappingParams({ + and: true, + operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, + operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL, + })).toBeTruthy(); + + expect(FilterConditionItems.BETWEEN.testMappingParams({ + and: true, + operator1: CustomFilterOperator.LESS_THAN_OR_EQUAL, + operator2: CustomFilterOperator.GREATER_THAN_OR_EQUAL, + })).toBeTruthy(); + + expect(FilterConditionItems.BETWEEN.testMappingParams({ + and: true, + operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, + })).toBeFalsy(); + + expect(FilterConditionItems.BETWEEN.testMappingParams({ + and: true, + operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL, + })).toBeFalsy(); + + expect(FilterConditionItems.BETWEEN.testMappingParams({ + and: true, + operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, + operator2: CustomFilterOperator.GREATER_THAN_OR_EQUAL, + })).toBeFalsy(); + }); + + it('should map to filter column with correct combination of two operators', () => { + expect(FilterConditionItems.BETWEEN.mapToFilterColumn({ + and: true, + operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, val1: '123', + operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL, val2: '456', + })).toEqual({ + customFilters: { + and: BooleanNumber.TRUE, + customFilters: [ + { val: '123', operator: CustomFilterOperator.GREATER_THAN_OR_EQUAL }, + { val: '456', operator: CustomFilterOperator.LESS_THAN_OR_EQUAL }, + ], + }, + }); + }); + }); + + describe('test NOT_BETWEEN', () => { + it('should load NOT_BETWEEN condition from correct combination of two operators"', () => { + expect(FilterConditionItems.testMappingFilterColumn({ + customFilters: { + customFilters: [ + { val: '123', operator: CustomFilterOperator.LESS_THAN }, + { val: '456', operator: CustomFilterOperator.GREATER_THAN }, + ], + }, + })).toEqual([FilterConditionItems.NOT_BETWEEN, { + operator1: CustomFilterOperator.LESS_THAN, val1: '123', + operator2: CustomFilterOperator.GREATER_THAN, val2: '456', + }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.NOT_BETWEEN.getDefaultFormParams()).toEqual({ + operator1: CustomFilterOperator.LESS_THAN, val1: '', + operator2: CustomFilterOperator.GREATER_THAN, val2: '', + }); + }); + + it('should mapped params with only two operators GREATER_THAN and LESS_THAN', () => { + expect(FilterConditionItems.NOT_BETWEEN.testMappingParams({ + operator1: CustomFilterOperator.GREATER_THAN, + operator2: CustomFilterOperator.LESS_THAN, + })).toBeTruthy(); + + expect(FilterConditionItems.NOT_BETWEEN.testMappingParams({ + operator1: CustomFilterOperator.LESS_THAN, + operator2: CustomFilterOperator.GREATER_THAN, + })).toBeTruthy(); + + expect(FilterConditionItems.NOT_BETWEEN.testMappingParams({ + operator1: CustomFilterOperator.GREATER_THAN, + })).toBeFalsy(); + + expect(FilterConditionItems.NOT_BETWEEN.testMappingParams({ + operator2: CustomFilterOperator.LESS_THAN, + })).toBeFalsy(); + + expect(FilterConditionItems.NOT_BETWEEN.testMappingParams({ + operator1: CustomFilterOperator.GREATER_THAN, + operator2: CustomFilterOperator.GREATER_THAN, + })).toBeFalsy(); + }); + + it('should map to filter column with correct combination of two operators', () => { + expect(FilterConditionItems.NOT_BETWEEN.mapToFilterColumn({ + operator1: CustomFilterOperator.GREATER_THAN, val1: '123', + operator2: CustomFilterOperator.LESS_THAN, val2: '456', + })).toEqual({ + customFilters: { + customFilters: [ + { val: '123', operator: CustomFilterOperator.GREATER_THAN }, + { val: '456', operator: CustomFilterOperator.LESS_THAN }, + ], + }, + }); + }); + }); + + describe('test CUSTOM', () => { + it('should load CUSTOM condition from two custom filters that cannot map to previous', () => { + expect(FilterConditionItems.testMappingFilterColumn({ customFilters: { and: BooleanNumber.TRUE, customFilters: [ + { val: '*123' }, + { val: '123*' }, + ] } })).toEqual([FilterConditionItems.CUSTOM, { + and: true, + operator1: ExtendCustomFilterOperator.ENDS_WITH, val1: '123', + operator2: ExtendCustomFilterOperator.STARTS_WITH, val2: '123', + }]); + }); + + it('should support default params with empty string', () => { + expect(FilterConditionItems.CUSTOM.getDefaultFormParams()).toEqual({ + operator1: ExtendCustomFilterOperator.NONE, val1: '', + operator2: ExtendCustomFilterOperator.NONE, val2: '', + }); + }); + + it('should mapped by any params change', () => { + expect(FilterConditionItems.CUSTOM.testMappingParams({ operator1: ExtendCustomFilterOperator.CUSTOM })).toBeTruthy(); + }); + + it('should map to filter column with empty string', () => { + expect(FilterConditionItems.CUSTOM.mapToFilterColumn({ + and: true, + operator1: ExtendCustomFilterOperator.ENDS_WITH, val1: '123', + operator2: CustomFilterOperator.GREATER_THAN, val2: '456', + })) + .toEqual({ customFilters: { + and: BooleanNumber.TRUE, + customFilters: [{ val: '*123' }, { val: '456', operator: CustomFilterOperator.GREATER_THAN }], + } }); + }); + + it('should fallback to NONE when no custom filter is provided', () => { + expect(FilterConditionItems.CUSTOM.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.NONE })).toEqual(null); + }); + + it('should fallback to one conditional filter when only one is provided', () => { + expect(FilterConditionItems.CUSTOM.mapToFilterColumn({ operator1: ExtendCustomFilterOperator.ENDS_WITH, val1: '123' })) + .toEqual({ customFilters: { customFilters: [{ val: '*123' }] } }); + + expect(FilterConditionItems.CUSTOM.mapToFilterColumn({ operator2: ExtendCustomFilterOperator.STARTS_WITH, val2: '123' })) + .toEqual({ customFilters: { customFilters: [{ val: '123*' }] } }); + }); + }); + }); +}); diff --git a/packages/sheets-filter-ui/src/models/conditions.ts b/packages/sheets-filter-ui/src/models/conditions.ts new file mode 100644 index 000000000000..b537a1582229 --- /dev/null +++ b/packages/sheets-filter-ui/src/models/conditions.ts @@ -0,0 +1,819 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Nullable } from '@univerjs/core'; +import { BooleanNumber } from '@univerjs/core'; +import type { ICustomFilters, IFilterColumn } from '@univerjs/sheets-filter'; +import { CustomFilterOperator } from '@univerjs/sheets-filter'; +import { ExtendCustomFilterOperator, OperatorOrder } from './extended-operators'; + +// This file implements a simple form schema system for condition filters (and only for it!) + +export type FilterOperator = ExtendCustomFilterOperator | CustomFilterOperator; + +export interface IFilterConditionFormParams { + and?: true; + + operator1?: FilterOperator; + val1?: string; + + operator2?: FilterOperator; + val2?: string; +} + +export interface IFilterConditionItem { + operator: FilterOperator; + numOfParameters: number; + order: OperatorOrder; + + /** + * Name of the filter condition. Should be an i18n key. + */ + label: string; + + and?: true; + + /** + * Get the initial form parameters for this condition item. This should only be called when `numOfParameters` is not `0`. + */ + getDefaultFormParams(): IFilterConditionFormParams; + /** + * Test if the form params can be mapped to this condition item. This should be called when the + * condition form changes and `numOfParameters` is not `0`. + * @param params + */ + testMappingParams(params: IFilterConditionFormParams): boolean; + + /** + * When user confirm changing filter condition, this method will be called to map the form params + * to the filter column data. + * @param mapParams + */ + mapToFilterColumn(mapParams: IFilterConditionFormParams): Nullable>; + /** + * Test if the filter column data can be mapped to this condition item. + * It should return the mapping parameters if it can be mapped, otherwise `false`. + * This should be called when the filter panel opens. + * + * @param filterColumn + * @returns the mapping parameters if it can be mapped, otherwise `false` + */ + testMappingFilterColumn(filterColumn: Omit): IFilterConditionFormParams | false; +} + +// eslint-disable-next-line ts/no-namespace +export namespace FilterConditionItems { + export const NONE: IFilterConditionItem = { + label: 'sheets-filter.conditions.none', + + operator: ExtendCustomFilterOperator.NONE, + order: OperatorOrder.SECOND, + numOfParameters: 0, + + getDefaultFormParams: () => { throw new Error('[FilterConditionItems.NONE]: should not have initial form params!'); }, + testMappingParams: (params) => { + return params.operator1 === ExtendCustomFilterOperator.NONE; + }, + + mapToFilterColumn: () => null, + testMappingFilterColumn: (filterColumn) => { + if (!filterColumn.customFilters && !filterColumn.filters) return {}; + return false; + }, + }; + + // ------------------------------ + + export const EMPTY: IFilterConditionItem = { + label: 'sheets-filter.conditions.empty', + + operator: ExtendCustomFilterOperator.EMPTY, + order: OperatorOrder.SECOND, + numOfParameters: 0, + + getDefaultFormParams: () => { throw new Error('[FilterConditionItems.EMPTY]: should not have initial form params!'); }, + testMappingParams: ({ operator1 }) => operator1 === ExtendCustomFilterOperator.EMPTY, + + mapToFilterColumn: () => ({ customFilters: { customFilters: [{ val: '' }] } }), + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 1) { + return false; + } + + const firstCustomFilter = filterColumn.customFilters.customFilters[0]; + const mapped = firstCustomFilter.val === '' && firstCustomFilter.operator === undefined; + if (!mapped) { + return false; + } + + return { operator1: ExtendCustomFilterOperator.EMPTY }; + }, + }; + + export const NOT_EMPTY: IFilterConditionItem = { + label: 'sheets-filter.conditions.not-empty', + + operator: ExtendCustomFilterOperator.NOT_EMPTY, + order: OperatorOrder.SECOND, + numOfParameters: 0, + + getDefaultFormParams: () => { throw new Error('[FilterConditionItems.NOT_EMPTY]: should not have initial form params!'); }, + testMappingParams: ({ operator1 }) => operator1 === ExtendCustomFilterOperator.NOT_EMPTY, + + mapToFilterColumn: () => ({ customFilters: { customFilters: [{ val: ' ', operator: CustomFilterOperator.NOT_EQUALS }] } }), + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 1) { + return false; + } + + const firstCustomFilter = filterColumn.customFilters.customFilters[0]; + const canMap = firstCustomFilter.val === ' ' && firstCustomFilter.operator === CustomFilterOperator.NOT_EQUALS; + if (!canMap) { + return false; + } + + return { operator1: ExtendCustomFilterOperator.NOT_EMPTY }; + }, + }; + + // ------------------------------ + + export const TEXT_CONTAINS: IFilterConditionItem = { + label: 'sheets-filter.conditions.text-contains', + + operator: ExtendCustomFilterOperator.CONTAINS, + order: OperatorOrder.FIRST, + numOfParameters: 1, + + getDefaultFormParams: () => ({ operator1: ExtendCustomFilterOperator.CONTAINS, val1: '' }), + testMappingParams: (params) => { + const [op] = getOnlyOperatorAndVal(params); + return op === ExtendCustomFilterOperator.CONTAINS; + }, + + mapToFilterColumn: (mapParams) => { + const { val1 } = mapParams; + if (val1 === '') return null; + + return { + customFilters: { customFilters: [{ val: `*${val1}*` }] }, + }; + }, + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 1) { + return false; + } + + const firstCustomFilter = filterColumn.customFilters.customFilters[0]; + const valAsString = firstCustomFilter.val.toString(); + if (!firstCustomFilter.operator && valAsString.startsWith('*') && valAsString.endsWith('*')) { + return { operator1: ExtendCustomFilterOperator.CONTAINS, val1: valAsString.slice(1, -1) }; + } + + return false; + }, + }; + + export const DOES_NOT_CONTAIN: IFilterConditionItem = { + label: 'sheets-filter.conditions.does-not-contain', + + operator: ExtendCustomFilterOperator.DOES_NOT_CONTAIN, + order: OperatorOrder.FIRST, + numOfParameters: 1, + + getDefaultFormParams: () => ({ operator1: ExtendCustomFilterOperator.DOES_NOT_CONTAIN, val1: '' }), + mapToFilterColumn: (mapParams) => ({ + customFilters: { customFilters: [{ val: `*${mapParams.val1}*`, operator: CustomFilterOperator.NOT_EQUALS }] }, + }), + testMappingParams: (params) => { + const [op] = getOnlyOperatorAndVal(params); + return op === ExtendCustomFilterOperator.DOES_NOT_CONTAIN; + }, + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 1) { + return false; + } + + const firstCustomFilter = filterColumn.customFilters.customFilters[0]; + const valAsString = firstCustomFilter.val.toString(); + if ( + firstCustomFilter.operator === CustomFilterOperator.NOT_EQUALS + && valAsString.startsWith('*') + && valAsString.endsWith('*') + ) { + return { operator1: ExtendCustomFilterOperator.DOES_NOT_CONTAIN, val1: valAsString.slice(1, -1) }; + } + + return false; + }, + }; + + export const STARTS_WITH: IFilterConditionItem = { + label: 'sheets-filter.conditions.starts-with', + + operator: ExtendCustomFilterOperator.STARTS_WITH, + order: OperatorOrder.FIRST, + numOfParameters: 1, + + getDefaultFormParams: () => ({ operator1: ExtendCustomFilterOperator.STARTS_WITH, val1: '' }), + mapToFilterColumn: (mapParams) => ({ + customFilters: { customFilters: [{ val: `${mapParams.val1}*` }] }, + }), + testMappingParams: (params) => { + const [op] = getOnlyOperatorAndVal(params); + return op === ExtendCustomFilterOperator.STARTS_WITH; + }, + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 1) { + return false; + } + + const firstCustomFilter = filterColumn.customFilters.customFilters[0]; + const valAsString = firstCustomFilter.val.toString(); + if (!firstCustomFilter.operator && valAsString.endsWith('*') && !valAsString.startsWith('*')) { + return { operator1: ExtendCustomFilterOperator.STARTS_WITH, val1: valAsString.slice(0, -1) }; + } + + return false; + }, + }; + + export const ENDS_WITH: IFilterConditionItem = { + label: 'sheets-filter.conditions.ends-with', + + operator: ExtendCustomFilterOperator.ENDS_WITH, + order: OperatorOrder.FIRST, + numOfParameters: 1, + + getDefaultFormParams: () => ({ operator1: ExtendCustomFilterOperator.ENDS_WITH, val1: '' }), + mapToFilterColumn: (mapParams) => ({ + customFilters: { customFilters: [{ val: `*${mapParams.val1}` }] }, + }), + testMappingParams: (params) => { + const [op] = getOnlyOperatorAndVal(params); + return op === ExtendCustomFilterOperator.ENDS_WITH; + }, + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 1) { + return false; + } + + const firstCustomFilter = filterColumn.customFilters.customFilters[0]; + const valAsString = firstCustomFilter.val.toString(); + if (!firstCustomFilter.operator && valAsString.startsWith('*') && !valAsString.endsWith('*')) { + return { operator1: ExtendCustomFilterOperator.ENDS_WITH, val1: valAsString.slice(1) }; + } + + return false; + }, + }; + + export const EQUALS: IFilterConditionItem = { + label: 'sheets-filter.conditions.equals', + + operator: ExtendCustomFilterOperator.EQUALS, + order: OperatorOrder.FIRST, + numOfParameters: 1, + + getDefaultFormParams: () => ({ operator1: ExtendCustomFilterOperator.EQUALS, val1: '' }), + testMappingParams: (params) => { + const [op] = getOnlyOperatorAndVal(params); + return op === ExtendCustomFilterOperator.EQUALS; + }, + + mapToFilterColumn: (mapParams) => { + const { val1 } = mapParams; + if (val1 === '') return null; + + return { + customFilters: { customFilters: [{ val: val1! }] }, + }; + }, + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.filters?.filters?.length === 1) { + return { operator1: ExtendCustomFilterOperator.EQUALS, val1: '' }; + } + + if (filterColumn.customFilters?.customFilters.length === 1 && !filterColumn.customFilters.customFilters[0].operator) { + return { operator1: ExtendCustomFilterOperator.EQUALS, val1: filterColumn.customFilters.customFilters[0].val.toString() }; + } + + return false; + }, + }; + + // #region number conditions + + export const GREATER_THAN: IFilterConditionItem = { + label: 'sheets-filter.conditions.greater-than', + + operator: CustomFilterOperator.GREATER_THAN, + numOfParameters: 1, + order: OperatorOrder.FIRST, + + getDefaultFormParams: () => ({ operator1: CustomFilterOperator.GREATER_THAN, val1: '' }), + mapToFilterColumn: (mapParams) => ({ + customFilters: { customFilters: [{ val: mapParams.val1!, operator: CustomFilterOperator.GREATER_THAN }] }, + }), + testMappingParams: (params) => { + const [op] = getOnlyOperatorAndVal(params); + return op === CustomFilterOperator.GREATER_THAN; + }, + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 1) { + return false; + } + + const firstCustomFilter = filterColumn.customFilters.customFilters[0]; + if (firstCustomFilter.operator !== CustomFilterOperator.GREATER_THAN) { + return false; + } + + return { operator1: CustomFilterOperator.GREATER_THAN, val1: firstCustomFilter.val.toString() }; + }, + }; + + export const GREATER_THAN_OR_EQUAL: IFilterConditionItem = { + label: 'sheets-filter.conditions.greater-than-or-equal', + + operator: CustomFilterOperator.GREATER_THAN_OR_EQUAL, + numOfParameters: 1, + order: OperatorOrder.FIRST, + + getDefaultFormParams: () => ({ operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, val1: '' }), + testMappingParams: (params) => { + const [op] = getOnlyOperatorAndVal(params); + return op === CustomFilterOperator.GREATER_THAN_OR_EQUAL; + }, + + mapToFilterColumn: (mapParams) => ({ + customFilters: { customFilters: [{ val: mapParams.val1!, operator: CustomFilterOperator.GREATER_THAN_OR_EQUAL }] }, + }), + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 1) { + return false; + } + + const firstCustomFilter = filterColumn.customFilters.customFilters[0]; + if (firstCustomFilter.operator !== CustomFilterOperator.GREATER_THAN_OR_EQUAL) { + return false; + } + + return { operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, val1: firstCustomFilter.val.toString() }; + }, + }; + + export const LESS_THAN: IFilterConditionItem = { + label: 'sheets-filter.conditions.less-than', + + operator: CustomFilterOperator.LESS_THAN, + numOfParameters: 1, + order: OperatorOrder.FIRST, + + getDefaultFormParams: () => ({ operator1: CustomFilterOperator.LESS_THAN, val1: '' }), + testMappingParams: (params) => { + const [op] = getOnlyOperatorAndVal(params); + return op === CustomFilterOperator.LESS_THAN; + }, + + mapToFilterColumn: (mapParams) => ({ + customFilters: { customFilters: [{ val: mapParams.val1!, operator: CustomFilterOperator.LESS_THAN }] }, + }), + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 1) { + return false; + } + + const firstCustomFilter = filterColumn.customFilters.customFilters[0]; + if (firstCustomFilter.operator !== CustomFilterOperator.LESS_THAN) { + return false; + } + + return { operator1: CustomFilterOperator.LESS_THAN, val1: firstCustomFilter.val.toString() }; + }, + }; + + export const LESS_THAN_OR_EQUAL: IFilterConditionItem = { + label: 'sheets-filter.conditions.less-than-or-equal', + + operator: CustomFilterOperator.LESS_THAN_OR_EQUAL, + numOfParameters: 1, + order: OperatorOrder.FIRST, + + getDefaultFormParams: () => ({ operator1: CustomFilterOperator.LESS_THAN_OR_EQUAL, val1: '' }), + testMappingParams: (params) => { + const [op] = getOnlyOperatorAndVal(params); + return op === CustomFilterOperator.LESS_THAN_OR_EQUAL; + }, + + mapToFilterColumn: (mapParams) => ({ + customFilters: { customFilters: [{ val: mapParams.val1!, operator: CustomFilterOperator.LESS_THAN_OR_EQUAL }] }, + }), + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 1) { + return false; + } + + const firstCustomFilter = filterColumn.customFilters.customFilters[0]; + if (firstCustomFilter.operator !== CustomFilterOperator.LESS_THAN_OR_EQUAL) { + return false; + } + + return { operator1: CustomFilterOperator.LESS_THAN_OR_EQUAL, val1: firstCustomFilter.val.toString() }; + }, + }; + + export const EQUAL: IFilterConditionItem = { + label: 'sheets-filter.conditions.equal', + + operator: CustomFilterOperator.EQUAL, + numOfParameters: 1, + order: OperatorOrder.FIRST, + + getDefaultFormParams: () => ({ operator1: CustomFilterOperator.EQUAL, val1: '' }), + testMappingParams: (params) => { + const [op] = getOnlyOperatorAndVal(params); + return op === CustomFilterOperator.EQUAL; + }, + + mapToFilterColumn: (mapParams) => ({ + customFilters: { customFilters: [{ val: mapParams.val1!, operator: CustomFilterOperator.EQUAL }] }, + }), + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 1) { + return false; + } + + const firstCustomFilter = filterColumn.customFilters.customFilters[0]; + if (firstCustomFilter.operator !== CustomFilterOperator.EQUAL) { + return false; + } + + return { operator1: CustomFilterOperator.EQUAL, val1: firstCustomFilter.val.toString() }; + }, + }; + + export const NOT_EQUAL: IFilterConditionItem = { + label: 'sheets-filter.conditions.not-equal', + + operator: CustomFilterOperator.NOT_EQUALS, + numOfParameters: 1, + order: OperatorOrder.FIRST, + + getDefaultFormParams: () => ({ operator1: CustomFilterOperator.NOT_EQUALS, val1: '' }), + testMappingParams: (params) => { + const [op] = getOnlyOperatorAndVal(params); + return op === CustomFilterOperator.NOT_EQUALS; + }, + + mapToFilterColumn: (mapParams) => ({ + customFilters: { customFilters: [{ val: mapParams.val1!, operator: CustomFilterOperator.NOT_EQUALS }] }, + }), + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 1) { + return false; + } + + const firstCustomFilter = filterColumn.customFilters.customFilters[0]; + if (firstCustomFilter.operator !== CustomFilterOperator.NOT_EQUALS) { + return false; + } + + return { operator1: CustomFilterOperator.NOT_EQUALS, val1: firstCustomFilter.val.toString() }; + }, + }; + + // #endregion + + export const BETWEEN: IFilterConditionItem = { + label: 'sheets-filter.conditions.between', + + operator: ExtendCustomFilterOperator.BETWEEN, + order: OperatorOrder.SECOND, + numOfParameters: 2, + + getDefaultFormParams: () => ({ + and: true, + operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, + val1: '', + operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL, + val2: '', + }), + testMappingParams: (params) => { + const { and, operator1, operator2 } = params; + if (!and) return false; + + const operators = [operator1, operator2]; + return operators.includes(CustomFilterOperator.GREATER_THAN_OR_EQUAL) + && operators.includes(CustomFilterOperator.LESS_THAN_OR_EQUAL); + }, + + mapToFilterColumn: (mapParams) => { + const { val1, val2, operator1 } = mapParams; + const operator1IsGreater = operator1 === CustomFilterOperator.GREATER_THAN_OR_EQUAL; + return { + customFilters: { + and: BooleanNumber.TRUE, + customFilters: [ + { val: operator1IsGreater ? val1! : val2!, operator: CustomFilterOperator.GREATER_THAN_OR_EQUAL }, + { val: operator1IsGreater ? val2! : val1!, operator: CustomFilterOperator.LESS_THAN_OR_EQUAL }, + ], + }, + }; + }, + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 2) { + return false; + } + + const [firstCustomFilter, secondCustomFilter] = filterColumn.customFilters.customFilters; + if ( + firstCustomFilter.operator === CustomFilterOperator.GREATER_THAN_OR_EQUAL + && secondCustomFilter.operator === CustomFilterOperator.LESS_THAN_OR_EQUAL + && filterColumn.customFilters.and + ) { + return { + and: true, + operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, + val1: firstCustomFilter.val.toString(), + operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL, + val2: secondCustomFilter.val.toString(), + }; + } + + if ( + secondCustomFilter.operator === CustomFilterOperator.GREATER_THAN_OR_EQUAL + && firstCustomFilter.operator === CustomFilterOperator.LESS_THAN_OR_EQUAL + && filterColumn.customFilters.and + ) { + return { + and: true, + operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, + val1: secondCustomFilter.val.toString(), + operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL, + val2: firstCustomFilter.val.toLocaleString(), + }; + } + + return false; + }, + }; + + export const NOT_BETWEEN: IFilterConditionItem = { + label: 'sheets-filter.conditions.not-between', + + operator: ExtendCustomFilterOperator.NOT_BETWEEN, + order: OperatorOrder.SECOND, + numOfParameters: 2, + + getDefaultFormParams: () => ({ + operator1: CustomFilterOperator.LESS_THAN, val1: '', + operator2: CustomFilterOperator.GREATER_THAN, val2: '', + }), + testMappingParams: (params) => { + const { and, operator1, operator2 } = params; + if (and) return false; + + const operators = [operator1, operator2]; + return operators.includes(CustomFilterOperator.GREATER_THAN) && operators.includes(CustomFilterOperator.LESS_THAN); + }, + + mapToFilterColumn: (mapParams) => { + const { val1, val2, operator1 } = mapParams; + const operator1IsGreater = operator1 === CustomFilterOperator.GREATER_THAN; + return { + customFilters: { + customFilters: [ + { val: operator1IsGreater ? val1! : val2!, operator: CustomFilterOperator.GREATER_THAN }, + { val: operator1IsGreater ? val2! : val1!, operator: CustomFilterOperator.LESS_THAN }, + ], + }, + }; + }, + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 2) { + return false; + } + + const [firstCustomFilter, secondCustomFilter] = filterColumn.customFilters.customFilters; + if ( + firstCustomFilter.operator === CustomFilterOperator.LESS_THAN + && secondCustomFilter.operator === CustomFilterOperator.GREATER_THAN + && !filterColumn.customFilters.and + ) { + return { + operator1: CustomFilterOperator.LESS_THAN, + val1: firstCustomFilter.val.toString(), + operator2: CustomFilterOperator.GREATER_THAN, + val2: secondCustomFilter.val.toString(), + }; + } + + if ( + secondCustomFilter.operator === CustomFilterOperator.LESS_THAN + && firstCustomFilter.operator === CustomFilterOperator.GREATER_THAN + && !filterColumn.customFilters.and + ) { + return { + operator1: CustomFilterOperator.GREATER_THAN, + val1: secondCustomFilter.val.toString(), + operator2: CustomFilterOperator.LESS_THAN, + val2: firstCustomFilter.val.toLocaleString(), + }; + } + + return false; + }, + }; + + /** + * This should be test last. If no other condition item can be mapped, then it should be mapped. + */ + export const CUSTOM: IFilterConditionItem = { + label: 'sheets-filter.conditions.custom', + + operator: ExtendCustomFilterOperator.CUSTOM, + order: OperatorOrder.SECOND, + numOfParameters: 2, + + getDefaultFormParams: () => { + return { + operator1: ExtendCustomFilterOperator.NONE, + val1: '', + operator2: ExtendCustomFilterOperator.NONE, + val2: '', + }; + }, + testMappingParams: () => true, + + mapToFilterColumn: (mapParams) => { + const { and, val1, val2, operator1, operator2 } = mapParams; + + function mapOperator(operator: FilterOperator | undefined, val: string) { + // eslint-disable-next-line ts/no-use-before-define + for (const condition of ALL_CONDITIONS) { + if (condition.operator === operator) { + return condition.mapToFilterColumn({ val1: val, operator1: operator }); + } + } + } + + const operator1IsNone = !operator1 || operator1 === FilterConditionItems.NONE.operator; + const operator2IsNone = !operator2 || operator2 === FilterConditionItems.NONE.operator; + + if (operator1IsNone && operator2IsNone) { + return NONE.mapToFilterColumn({}); + } + + if (operator1IsNone) { + return mapOperator(operator2!, val2!)!; + } + + if (operator2IsNone) { + return mapOperator(operator1!, val1!)!; + } + + const mappedCustomFilter1 = mapOperator(operator1!, val1!)!; + const mappedCustomFilter2 = mapOperator(operator2!, val2!)!; + const customFilters: ICustomFilters = { + customFilters: [ + mappedCustomFilter1.customFilters!.customFilters[0], + mappedCustomFilter2.customFilters!.customFilters[0], + ], + }; + + if (and) customFilters.and = BooleanNumber.TRUE; + return { customFilters }; + }, + testMappingFilterColumn: (filterColumn) => { + if (filterColumn.customFilters?.customFilters.length !== 2) { + return false; + } + + const params = filterColumn.customFilters.customFilters.map((customFilter) => { + return testMappingFilterColumn({ customFilters: { customFilters: [customFilter] } }); + }); + + const result: IFilterConditionFormParams = { + operator1: params[0][0].operator, + val1: params[0][1].val1, + operator2: params[1][0].operator, + val2: params[1][1].val1, + }; + + if (filterColumn.customFilters.and) { + result.and = true; + } + + return result; + }, + }; + + export const ALL_CONDITIONS: IFilterConditionItem[] = [ + // ------------------------------ + NONE, + // ------------------------------ + EMPTY, + NOT_EMPTY, + // ------------------------------ + TEXT_CONTAINS, + DOES_NOT_CONTAIN, + STARTS_WITH, + ENDS_WITH, + EQUALS, + // ------------------------------ + GREATER_THAN, + GREATER_THAN_OR_EQUAL, + LESS_THAN, + LESS_THAN_OR_EQUAL, + EQUAL, + NOT_EQUAL, + BETWEEN, + NOT_BETWEEN, + // ------------------------------ + CUSTOM, + ]; + + export function getItemByOperator(operator: FilterOperator): IFilterConditionItem { + const item = ALL_CONDITIONS.find((condition) => condition.operator === operator); + if (!item) { + throw new Error(`[SheetsFilter]: no condition item found for operator: ${operator}`); + } + + return item; + } + + export function testMappingParams(mapParams: IFilterConditionFormParams, numOfParameters: number): IFilterConditionItem { + // TODO@wzhudev: iteration here can be optimized + // We match operators with same count of parameters first. + for (const condition of ALL_CONDITIONS.filter((condition) => condition.numOfParameters === numOfParameters)) { + if (condition.numOfParameters !== 0 && condition.testMappingParams(mapParams)) { + return condition; + } + } + + // And fallback to others. + for (const condition of ALL_CONDITIONS) { + if (condition.testMappingParams(mapParams)) { + return condition; + } + } + + throw new Error('[SheetsFilter]: no condition item can be mapped from the filter map params!'); + } + + export function getInitialFormParams(operator: FilterOperator): IFilterConditionFormParams { + const condition = ALL_CONDITIONS.find((condition) => condition.operator === operator)!; + if (condition?.numOfParameters === 0) { + return { operator1: condition.operator }; + } + + return condition.getDefaultFormParams(); + } + + export function mapToFilterColumn(condition: IFilterConditionItem, mapParams: IFilterConditionFormParams): Nullable> { + return condition.mapToFilterColumn(mapParams); + } + + export function testMappingFilterColumn(filterColumn: Nullable>): [IFilterConditionItem, IFilterConditionFormParams] { + if (!filterColumn) { + return [NONE, {}]; + } + + for (const condition of ALL_CONDITIONS) { + const mapParams = condition.testMappingFilterColumn(filterColumn); + if (mapParams) { + return [condition, mapParams]; + } + } + + // Return NONE by default. + return [NONE, {}]; + } +} + +function getOnlyOperatorAndVal(mapParams: IFilterConditionFormParams): [FilterOperator, string | undefined] { + const { operator1, operator2, val1, val2 } = mapParams; + if (operator1 && operator2) { + throw new Error('Both operator1 and operator2 are set!'); + } + + if (!operator1 && !operator2) { + throw new Error('Neither operator1 and operator2 and both not set!'); + } + + return operator1 ? [operator1, val1] : [operator2!, val2]; +} diff --git a/packages/sheets-filter-ui/src/models/extended-operators.ts b/packages/sheets-filter-ui/src/models/extended-operators.ts new file mode 100644 index 000000000000..694b4bd5f885 --- /dev/null +++ b/packages/sheets-filter-ui/src/models/extended-operators.ts @@ -0,0 +1,101 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum OperatorOrder { + FIRST, + SECOND, +} + +/** + * Extended custom filter operators. + * + * These operators are not defined in OOXML, + * so when exporting these operators we need to fallback to `CustomFilterOperator` and + * other possible filter types. + */ +export enum ExtendCustomFilterOperator { + NONE = 'none', + + /** Fallback to `` */ + STARTS_WITH = 'startsWith', + /** Fallback to `` */ + DOES_NOT_START_WITH = 'doesNotStartWith', + + /** Fallback to `` */ + ENDS_WITH = 'endsWith', + /** Fallback to `` */ + DOES_NOT_END_WITH = 'doesNotEndWith', + + /** Fallback to `` */ + CONTAINS = 'contains', + /** Fallback to `` */ + DOES_NOT_CONTAIN = 'doesNotContain', + + /** + * Text equals. It is not same as CustomFilterOperator.EQUAL. + * + * When equals empty, it will be mapped to. + * + * + * + * + * + * + */ + EQUALS = 'equals', + + /** Fallback to `` */ + NOT_EQUALS = 'notEquals', + + /** + * Fallback to ``. + * + * It can also fallback to ``, when with another custom filter. + */ + EMPTY = 'empty', + + /** Fallback to `` */ + NOT_EMPTY = 'notEmpty', + + /** + * Falls back to the following XML: + * + * ```xml + * + * + * + * + * ``` + * + * Actually in Microsoft Excel, `NOT_BETWEEN` is still `BETWEEN`, as long as the two operators + * are `greaterThanOrEqual` and `lessThanOrEqual`. + */ + BETWEEN = 'between', + + /** + * Falls back to the following XML: + * + * ```xml + * + * + * + * + * ``` + */ + NOT_BETWEEN = 'notBetween', + + CUSTOM = 'custom', +} diff --git a/packages/sheets-filter-ui/src/models/utils.ts b/packages/sheets-filter-ui/src/models/utils.ts new file mode 100644 index 000000000000..c0856adee128 --- /dev/null +++ b/packages/sheets-filter-ui/src/models/utils.ts @@ -0,0 +1,42 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IFilterByValueItem } from '../services/sheets-filter-panel.service'; + +export function statisticFilterByValueItems(items: IFilterByValueItem[]): { + checked: number; + unchecked: number; + checkedItems: IFilterByValueItem[]; + uncheckedItems: IFilterByValueItem[]; +} { + const checkedItems: IFilterByValueItem[] = []; + const uncheckedItems: IFilterByValueItem[] = []; + + for (const item of items) { + if (item.checked) { + checkedItems.push(item); + } else { + uncheckedItems.push(item); + } + } + + return { + checkedItems, + uncheckedItems, + checked: checkedItems.length, + unchecked: uncheckedItems.length, + }; +} diff --git a/packages/sheets-filter-ui/src/plugin.ts b/packages/sheets-filter-ui/src/plugin.ts new file mode 100644 index 000000000000..b697e9c7eb2b --- /dev/null +++ b/packages/sheets-filter-ui/src/plugin.ts @@ -0,0 +1,51 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LocaleService, Plugin, UniverInstanceType } from '@univerjs/core'; +import type { Dependency } from '@wendellhu/redi'; +import { Inject, Injector } from '@wendellhu/redi'; + +import { zhCN } from './locale'; +import { SheetsFilterUIController } from './controllers/sheets-filter-ui.controller'; +import { SheetsFilterPanelService } from './services/sheets-filter-panel.service'; +import { SheetsFilterRenderController } from './controllers/sheets-filter-render.controller'; + +const NAME = 'UNIVER_SHEETS_FILTER_UI_PLUGIN'; + +export class UniverSheetsFilterUIPlugin extends Plugin { + static override type = UniverInstanceType.SHEET; + static override pluginName = NAME; + + constructor( + _config: unknown, + @Inject(Injector) protected readonly _injector: Injector, + @Inject(LocaleService) private readonly _localeService: LocaleService + ) { + super(); + + this._localeService.load({ + zhCN, + }); + } + + override onStarting(injector: Injector): void { + ([ + [SheetsFilterPanelService], + [SheetsFilterUIController], + [SheetsFilterRenderController], + ] as Dependency[]).forEach((d) => injector.add(d)); + } +} diff --git a/packages/sheets-filter-ui/src/services/__tests__/sheets-filter-panel.service.spec.ts b/packages/sheets-filter-ui/src/services/__tests__/sheets-filter-panel.service.spec.ts new file mode 100644 index 000000000000..bf62ccbf231f --- /dev/null +++ b/packages/sheets-filter-ui/src/services/__tests__/sheets-filter-panel.service.spec.ts @@ -0,0 +1,469 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IOperation, IWorkbookData } from '@univerjs/core'; +import { CommandType, ICommandService, LocaleService, Plugin, Univer, UniverInstanceType } from '@univerjs/core'; +import { RefRangeService, SelectionManagerService, SheetInterceptorService } from '@univerjs/sheets'; +import type { Dependency } from '@wendellhu/redi'; +import { Inject, Injector } from '@wendellhu/redi'; +import { afterEach, beforeEach, describe, expect, it, vitest } from 'vitest'; +import { CustomFilterOperator, SheetsFilterService, UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; +import type { IEditorBridgeServiceVisibleParam } from '@univerjs/sheets-ui'; +import { ByConditionsModel, ByValuesModel, FilterBy, SheetsFilterPanelService } from '../sheets-filter-panel.service'; +import type { IOpenFilterPanelOperationParams } from '../../commands/sheets-filter.operation'; +import { CloseFilterPanelOperation, OpenFilterPanelOperation } from '../../commands/sheets-filter.operation'; +import { E_ITEMS, ITEMS, WithCustomFilterModelFactory, WithMergedCellFilterFactory, WithMultiEmptyCellsModelFactory, WithTwoFilterColumnsFactory, WithValuesAndEmptyFilterModelFactory, WithValuesFilterModelFactory } from '../../__testing__/data'; +import type { IFilterConditionFormParams } from '../../models/conditions'; +import { FilterConditionItems } from '../../models/conditions'; +import { ExtendCustomFilterOperator } from '../../models/extended-operators'; +import { SetSheetsFilterCriteriaCommand } from '../../commands/sheets-filter.command'; + + +const SetCellEditVisibleOperation: IOperation = { + id: 'sheet.operation.set-cell-edit-visible', + type: CommandType.OPERATION, + handler: () => { + return true; + }, +}; + + +function createSheetsFilterPanelServiceTestBed(workbookData: IWorkbookData) { + const univer = new Univer(); + const injector = univer.__getInjector(); + const get = injector.get.bind(injector); + + class SheetsFilterPanelTestPlugin extends Plugin { + static override type = UniverInstanceType.SHEET; + static override pluginName = 'sheets-filter-panel-test'; + + constructor(_config: unknown, @Inject(Injector) protected readonly _injector: Injector) { + super(); + } + + override onStarting(injector: Injector): void { + ([ + [RefRangeService], + [SelectionManagerService], + [SheetInterceptorService], + [SheetsFilterPanelService], + ] as Dependency[]).forEach((d) => injector.add(d)); + } + } + + get(LocaleService).load({}); + + univer.registerPlugin(UniverSheetsFilterPlugin); + univer.registerPlugin(SheetsFilterPanelTestPlugin); + + univer.createUniverSheet(workbookData); + + // It should be registered later in avoid of time sequence problem. + // injector.add([ISnapshotPersistenceService, { useClass: LocalSnapshotService }]); + // injector.get(ISnapshotPersistenceService); + + const commandService = get(ICommandService); + + [ + OpenFilterPanelOperation, + CloseFilterPanelOperation, + SetSheetsFilterCriteriaCommand, + SetCellEditVisibleOperation, + ].forEach((command) => commandService.registerCommand(command)); + + return { univer, get }; +} + +describe('test "SheetsFilterPanelService"', () => { + let univer: Univer; + let get: Injector['get']; + let commandService: ICommandService; + let sheetsFilterService: SheetsFilterService; + let sheetsFilterPanelService: SheetsFilterPanelService; + + function prepare(workbookData: IWorkbookData) { + const testBed = createSheetsFilterPanelServiceTestBed(workbookData); + + univer = testBed.univer; + get = testBed.get; + + commandService = get(ICommandService); + sheetsFilterService = get(SheetsFilterService); + sheetsFilterPanelService = get(SheetsFilterPanelService); + } + + afterEach(() => { + univer.dispose(); + }); + + describe('test filter by conditions', () => { + describe('test initialization behavior', () => { + it('should initialize by conditional model when the filter column is by custom filters', () => { + prepare(WithCustomFilterModelFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams)).toBe(true); + + expect(sheetsFilterPanelService.filterBy).toBe(FilterBy.CONDITIONS); + const filterByModel = sheetsFilterPanelService.filterByModel as ByConditionsModel; + expect(filterByModel instanceof ByConditionsModel).toBeTruthy(); + expect(filterByModel.conditionItem.operator).toBe(FilterConditionItems.GREATER_THAN.operator); + expect(filterByModel.filterConditionFormParams).toEqual({ operator1: CustomFilterOperator.GREATER_THAN, val1: '123' }); + }); + }); + + describe('test behavior when changing the primary operator', () => { + it('should update the filter condition item when the primary operator changes', () => { + prepare(WithCustomFilterModelFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams)).toBe(true); + + const filterByModel = sheetsFilterPanelService.filterByModel as ByConditionsModel; + + filterByModel.onPrimaryConditionChange(ExtendCustomFilterOperator.BETWEEN); + expect(filterByModel.conditionItem.operator).toBe(FilterConditionItems.BETWEEN.operator); + expect(filterByModel.filterConditionFormParams).toEqual({ + and: true, + operator1: CustomFilterOperator.GREATER_THAN_OR_EQUAL, val1: '', + operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL, val2: '', + } as IFilterConditionFormParams); + + filterByModel.onPrimaryConditionChange(ExtendCustomFilterOperator.ENDS_WITH); + expect(filterByModel.conditionItem.operator).toBe(FilterConditionItems.ENDS_WITH.operator); + expect(filterByModel.filterConditionFormParams).toEqual({ + operator1: ExtendCustomFilterOperator.ENDS_WITH, + val1: '', + } as IFilterConditionFormParams); + }); + }); + + describe('test behavior when changing the secondary operators', () => { + it('should update primary operator when the secondary operator changes (in some conditions)', () => { + prepare(WithCustomFilterModelFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams)).toBe(true); + + const filterByModel = sheetsFilterPanelService.filterByModel as ByConditionsModel; + + filterByModel.onConditionFormChange({ operator1: ExtendCustomFilterOperator.DOES_NOT_CONTAIN }); + expect(filterByModel.conditionItem.operator).toBe(FilterConditionItems.DOES_NOT_CONTAIN.operator); + expect(filterByModel.filterConditionFormParams).toEqual({ + operator1: ExtendCustomFilterOperator.DOES_NOT_CONTAIN, + val1: '123', + } as IFilterConditionFormParams); + + filterByModel.onPrimaryConditionChange(ExtendCustomFilterOperator.BETWEEN); + filterByModel.onConditionFormChange({ operator1: ExtendCustomFilterOperator.ENDS_WITH }); + expect(filterByModel.conditionItem.operator).toBe(FilterConditionItems.CUSTOM.operator); + expect(filterByModel.filterConditionFormParams).toEqual({ + and: true, + operator1: ExtendCustomFilterOperator.ENDS_WITH, val1: '', + operator2: CustomFilterOperator.LESS_THAN_OR_EQUAL, val2: '', + } as IFilterConditionFormParams); + }); + }); + + it('should execute command when "apply" is called', async () => { + prepare(WithCustomFilterModelFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams)).toBe(true); + + const filterByModel = sheetsFilterPanelService.filterByModel as ByConditionsModel; + filterByModel.onConditionFormChange({ val1: '5' }); + expect(await filterByModel.apply()); + + const filterModel = sheetsFilterService.activeFilterModel; + expect(filterModel!.filteredOutRows).toEqual(new Set([1, 2, 3, 4, 5])); + }); + }); + + describe('test filter by values', () => { + it('should initialize by filters when the filter column holds "filters"', () => { + prepare(WithValuesFilterModelFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams)).toBeTruthy(); + + expect(sheetsFilterPanelService.filterBy).toBe(FilterBy.VALUES); + const filterByModel = sheetsFilterPanelService.filterByModel as ByValuesModel; + expect(filterByModel instanceof ByValuesModel).toBeTruthy(); + expect(filterByModel.filterItems).toEqual(ITEMS); + }); + + it('should initialize with blank content', () => { + prepare(WithValuesAndEmptyFilterModelFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams)).toBeTruthy(); + + expect(sheetsFilterPanelService.filterBy).toBe(FilterBy.VALUES); + const filterByModel = sheetsFilterPanelService.filterByModel as ByValuesModel; + expect(filterByModel instanceof ByValuesModel).toBeTruthy(); + expect(filterByModel.filterItems).toEqual([...ITEMS, { + checked: true, + count: 1, + index: 11, + isEmpty: true, + value: 'sheets-filter.panel.empty', + }]); + }); + + it('should count empty cells', () => { + prepare(WithMultiEmptyCellsModelFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams)).toBeTruthy(); + + expect(sheetsFilterPanelService.filterBy).toBe(FilterBy.VALUES); + const filterByModel = sheetsFilterPanelService.filterByModel as ByValuesModel; + expect(filterByModel instanceof ByValuesModel).toBeTruthy(); + const filterItems = filterByModel.filterItems; + expect(filterItems[filterItems.length - 1]).toEqual({ + checked: true, + count: 4, + index: 11, + isEmpty: true, + value: 'sheets-filter.panel.empty', + }); + }); + + it('should filter out the items from the panel if its row is already filtered out by other column', () => { + prepare(WithTwoFilterColumnsFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 1, + } as IOpenFilterPanelOperationParams)).toBeTruthy(); + + expect(sheetsFilterPanelService.filterBy).toBe(FilterBy.VALUES); + const filterByModel = sheetsFilterPanelService.filterByModel as ByValuesModel; + expect(filterByModel instanceof ByValuesModel).toBeTruthy(); + const filterItems = filterByModel.filterItems; + expect(filterItems).toEqual([ + { + checked: true, + count: 1, + index: 0, + isEmpty: false, + value: 'a', + }, + { + checked: true, + count: 1, + index: 1, + isEmpty: false, + value: 'b', + }, + { + checked: true, + count: 1, + index: 2, + isEmpty: false, + value: 'c', + }, + { + checked: true, + count: 3, + index: 11, + isEmpty: true, + value: 'sheets-filter.panel.empty', + }, + ]); + }); + + it('merged cell should use value of the top left corner', () => { + prepare(WithMergedCellFilterFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 1, + } as IOpenFilterPanelOperationParams)).toBeTruthy(); + + expect(sheetsFilterPanelService.filterBy).toBe(FilterBy.VALUES); + const filterByModel = sheetsFilterPanelService.filterByModel as ByValuesModel; + expect(filterByModel instanceof ByValuesModel).toBeTruthy(); + const filterItems = filterByModel.filterItems; + expect(filterItems).toEqual([ + { + checked: true, + count: 2, // show be the same with rowSpan + index: 2, + isEmpty: false, + value: '3', + }, + { + checked: true, + count: 1, + index: 3, + isEmpty: false, + value: 'e', + }, + { + checked: true, + count: 3, + index: 10, + isEmpty: true, + value: 'sheets-filter.panel.empty', + }, + ]); + }); + + it('should update the filter items when toggle checked status', () => { + prepare(WithValuesFilterModelFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams)).toBe(true); + + const filterByModel = sheetsFilterPanelService.filterByModel as ByValuesModel; + filterByModel.onFilterCheckToggled(ITEMS[0], true); + expect(filterByModel.filterItems[0].checked).toBeTruthy(); + + filterByModel.onFilterOnly(ITEMS[5]); + expect(filterByModel.filterItems[0].checked).toBeFalsy(); + expect(filterByModel.filterItems[1].checked).toBeFalsy(); + expect(filterByModel.filterItems[5].checked).toBeTruthy(); + + filterByModel.onCheckAllToggled(true); + expect(filterByModel.filterItems.filter((i) => i.checked).length).toBe(10); + + filterByModel.onCheckAllToggled(false); + expect(filterByModel.filterItems.filter((i) => i.checked).length).toBe(0); + }); + + it('should execute command when "apply" is called', async () => { + prepare(WithValuesFilterModelFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams)).toBe(true); + + const filterByModel = sheetsFilterPanelService.filterByModel as ByValuesModel; + filterByModel.onFilterCheckToggled(ITEMS[0], true); + expect(filterByModel.filterItems[0].checked).toBeTruthy(); + + filterByModel.onFilterOnly(ITEMS[5]); + expect(await filterByModel.apply()).toBeTruthy(); + + const filterModel = sheetsFilterService.activeFilterModel; + expect(filterModel!.filteredOutRows).toEqual(new Set([1, 2, 3, 4, 5, 7, 8, 9, 10])); + }); + + describe('with searching', () => { + // use fake timers + // for performance reasons, search string are throttled to change the search results + beforeEach(() => { + vitest.useFakeTimers(); + }); + + afterEach(() => { + vitest.useRealTimers(); + }); + + it('should update the filter items when searching', () => { + prepare(WithValuesFilterModelFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams)).toBe(true); + + const filterByModel = sheetsFilterPanelService.filterByModel as ByValuesModel; + + filterByModel.setSearchString('e'); + vitest.advanceTimersByTime(600); + expect(filterByModel.filterItems).toEqual(E_ITEMS); + }); + + it('should toggle all applied to searched results', () => { + prepare(WithValuesFilterModelFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams)).toBe(true); + + const filterByModel = sheetsFilterPanelService.filterByModel as ByValuesModel; + + filterByModel.setSearchString('e'); + vitest.advanceTimersByTime(600); + expect(filterByModel.filterItems.length).toBe(7); + filterByModel.onCheckAllToggled(true); + expect(filterByModel.rawFilterItems.filter((i) => i.checked).length).toBe(8); // the original "1" should be checked as well + }); + + it('should filter only applied to searched results', () => { + prepare(WithValuesFilterModelFactory()); + + expect(commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams)).toBe(true); + + const filterByModel = sheetsFilterPanelService.filterByModel as ByValuesModel; + + filterByModel.setSearchString('e'); + vitest.advanceTimersByTime(600); + expect(filterByModel.filterItems.length).toBe(7); + filterByModel.onFilterOnly(filterByModel.filterItems[5]); + expect(filterByModel.rawFilterItems.filter((i) => i.checked).length).toBe(2); // the original "1" should be checked as well + }); + }); + }); + + describe('test behavior when filter range or filter column changes', () => { + it('should close the panel when the filter removed or the column in not in the filter range', () => { + // TODO@wzhudev + }); + + it('should close the panel when the active sheet changes', () => { + // TODO@wzhudev + }); + }); +}); diff --git a/packages/sheets-filter-ui/src/services/sheets-filter-panel.service.ts b/packages/sheets-filter-ui/src/services/sheets-filter-panel.service.ts new file mode 100644 index 000000000000..e05d5f3d4816 --- /dev/null +++ b/packages/sheets-filter-ui/src/services/sheets-filter-panel.service.ts @@ -0,0 +1,645 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IRange, Nullable } from '@univerjs/core'; +import { Disposable, extractPureTextFromCell, ICommandService, IUniverInstanceService, LocaleService } from '@univerjs/core'; +import { SheetsFilterService } from '@univerjs/sheets-filter'; +import type { FilterColumn, FilterModel, IFilterColumn } from '@univerjs/sheets-filter'; +import type { IDisposable } from '@wendellhu/redi'; +import { createIdentifier, Inject, Injector } from '@wendellhu/redi'; +import type { Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest, map, merge, of, ReplaySubject, shareReplay, startWith, Subject, throttleTime } from 'rxjs'; +import { RefRangeService } from '@univerjs/sheets'; + +import type { FilterOperator, IFilterConditionFormParams, IFilterConditionItem } from '../models/conditions'; +import { FilterConditionItems } from '../models/conditions'; +import type { ISetSheetsFilterCriteriaCommandParams } from '../commands/sheets-filter.command'; +import { SetSheetsFilterCriteriaCommand } from '../commands/sheets-filter.command'; +import { statisticFilterByValueItems } from '../models/utils'; + +export enum FilterBy { + VALUES, + CONDITIONS, +} + +export interface IFilterByValueItem { + value: string; + checked: boolean; + count: number; + index: number; + + /** + * This property indicates that this is a special item which maps to empty strings or empty cells. + */ + isEmpty: boolean; +} + +export interface ISheetsFilterPanelService { + /** + * Set up the panel to change the filter condition on a specific column. + * @param filterModel the filter model we will be working on + * @param col + * @returns if the filter condition is set up successfully + */ + setUpFilterConditionOfCol(filterModel: FilterModel, col: number): boolean; + + /** + * Terminate the filter panel without applying changes. + */ + terminate(): boolean; +} +export const ISheetsFilterPanelService = createIdentifier('sheets-filter-ui.sheets-filter-panel.service'); + +export interface IFilterByModel extends IDisposable { + canApply$: Observable; + + deltaCol(offset: number): void; + + clear(): Promise; + apply(): Promise; +} + +/** + * This service controls the state of the filter panel. There should be only one instance of the filter panel + * at one time. + */ +export class SheetsFilterPanelService extends Disposable { + private readonly _filterBy$ = new BehaviorSubject(FilterBy.VALUES); + readonly filterBy$ = this._filterBy$.asObservable(); + get filterBy(): FilterBy { return this._filterBy$.getValue(); } + + private readonly _filterByModel$ = new ReplaySubject>(1); + readonly filterByModel$ = this._filterByModel$.asObservable(); + private _filterByModel: Nullable = null; + get filterByModel(): Nullable { return this._filterByModel; } + private set filterByModel(model: Nullable) { + this._filterByModel = model; + this._filterByModel$.next(model); + } + + private readonly _hasCriteria$ = new BehaviorSubject(false); + readonly hasCriteria$ = this._hasCriteria$.asObservable(); + + private _filterModel: Nullable = null; + get filterModel() { return this._filterModel; } + + private readonly _col$ = new BehaviorSubject(-1); + readonly col$ = this._col$.asObservable(); + get col(): number { return this._col$.getValue(); } + + constructor( + @Inject(Injector) private readonly _injector: Injector, + @Inject(SheetsFilterService) private _sheetsFilterService: SheetsFilterService, + @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, + @Inject(RefRangeService) private readonly _refRangeService: RefRangeService + ) { + super(); + } + + override dispose(): void { + this._filterBy$.complete(); + this._filterByModel$.complete(); + this._hasCriteria$.complete(); + } + + setupCol(filterModel: FilterModel, col: number): boolean { + this.terminate(); + + this._filterModel = filterModel; + this._col$.next(col); + + // We use filter type that (if) has been set on the column as the default filter type. + const filterColumn = filterModel.getFilterColumn(col); + if (filterColumn) { + const info = filterColumn.getColumnData(); + if (info.customFilters) { + this._hasCriteria$.next(true); + return this._setupByConditions(filterModel, col); + } + + if (info.filters) { + this._hasCriteria$.next(true); + return this._setupByValues(filterModel, col); + } + + // Use value values by default. + this._hasCriteria$.next(false); + return this._setupByValues(filterModel, col); + } + + // By default we filter by values. + this._hasCriteria$.next(false); + return this._setupByValues(filterModel, col); + }; + + changeFilterBy(filterBy: FilterBy): boolean { + if (!this._filterModel || this.col === -1) { + return false; + } + + if (filterBy === FilterBy.VALUES) { + this._setupByValues(this._filterModel, this.col); + } else { + this._setupByConditions(this._filterModel, this.col); + } + + return true; + } + + terminate(): boolean { + this._filterModel = null; + this._col$.next(-1); + + this._disposeFilterHeaderChangeListener(); + return true; + } + + private _filterHeaderListener: Nullable = null; + private _disposeFilterHeaderChangeListener(): void { + this._filterHeaderListener?.dispose(); + this._filterHeaderListener = null; + } + + private _listenToFilterHeaderChange(filterModel: FilterModel, col: number): void { + this._disposeFilterHeaderChangeListener(); + + const unitId = filterModel.unitId; + const subUnitId = filterModel.subUnitId; + const filterRange = filterModel.getRange(); + const columnHeaderRange: IRange = { + startColumn: col, + startRow: filterRange.startRow, + endRow: filterRange.startRow, + endColumn: col, + }; + + this._filterHeaderListener = this._refRangeService.watchRange(unitId, subUnitId, columnHeaderRange, (before, after) => { + if (!after) { + // If the range collapsed, the column header must be deleted. + this.terminate(); + } else { + const offset = after.startColumn - before.startColumn; + if (offset !== 0) { + this._filterByModel!.deltaCol(offset); + } + } + }); + } + + private _setupByValues(filterModel: FilterModel, col: number): boolean { + this._disposePreviousModel(); + + const range = filterModel.getRange(); + if (range.startRow === range.endRow) return false; + + const model = ByValuesModel.fromFilterColumn( + this._injector, + filterModel, + col + ); + + this.filterByModel = model; + this._filterBy$.next(FilterBy.VALUES); + + this._listenToFilterHeaderChange(filterModel, col); + return true; + } + + private _setupByConditions(filterModel: FilterModel, col: number): boolean { + this._disposePreviousModel(); + + const range = filterModel.getRange(); + if (range.startRow === range.endRow) return false; + + const model = ByConditionsModel.fromFilterColumn( + this._injector, + filterModel, + col, + filterModel.getFilterColumn(col) + ); + + this.filterByModel = model; + this._filterBy$.next(FilterBy.CONDITIONS); + + this._listenToFilterHeaderChange(filterModel, col); + + return true; + } + + private _disposePreviousModel(): void { + this._filterByModel?.dispose(); + this.filterByModel = null; + } +} + +// #region ByConditionsModel + +/** + * This model would be used to control the "Filter By Conditions" panel. It should be reconstructed in the following + * situations: + * + * 1. The target `FilterColumn` object is changed + * 2. User toggles "Filter By" + */ +export class ByConditionsModel extends Disposable implements IFilterByModel { + /** + * Create a model with targeting filter column. If there is not a filter column, the model would be created with + * default values. + * + * @param injector + * @param filterModel + * @param col + * @param filterColumn + * + * @returns the model to control the panel's state + */ + static fromFilterColumn(injector: Injector, filterModel: FilterModel, col: number, filterColumn?: Nullable): ByConditionsModel { + const [conditionItem, conditionParams] = FilterConditionItems.testMappingFilterColumn(filterColumn?.getColumnData()); + const model = injector.createInstance(ByConditionsModel, filterModel, col, conditionItem, conditionParams); + return model; + } + + canApply$: Observable = of(true); + + private readonly _conditionItem$: BehaviorSubject; + readonly conditionItem$: Observable; + get conditionItem(): IFilterConditionItem { return this._conditionItem$.getValue(); } + + private readonly _filterConditionFormParams$: BehaviorSubject; + readonly filterConditionFormParams$: Observable; + get filterConditionFormParams(): IFilterConditionFormParams { return this._filterConditionFormParams$.getValue(); } + + constructor( + private readonly _filterModel: FilterModel, + public col: number, + conditionItem: IFilterConditionItem, + conditionParams: IFilterConditionFormParams, + @ICommandService private readonly _commandService: ICommandService + ) { + super(); + + this._conditionItem$ = new BehaviorSubject(conditionItem); + this.conditionItem$ = this._conditionItem$.asObservable(); + + this._filterConditionFormParams$ = new BehaviorSubject(conditionParams); + this.filterConditionFormParams$ = this._filterConditionFormParams$.asObservable(); + } + + override dispose(): void { + super.dispose(); + + this._conditionItem$.complete(); + this._filterConditionFormParams$.complete(); + } + + deltaCol(offset: number): void { + this.col += offset; + } + + clear(): Promise { + if (this._disposed) return Promise.resolve(false); + + return this._commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { + unitId: this._filterModel.unitId, + subUnitId: this._filterModel.subUnitId, + col: this.col, + criteria: null, + } as ISetSheetsFilterCriteriaCommandParams); + } + + /** + * Apply the filter condition to the target filter column. + */ + async apply(): Promise { + if (this._disposed) return false; + + const filterColumn = FilterConditionItems.mapToFilterColumn(this.conditionItem, this.filterConditionFormParams); + return this._commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { + unitId: this._filterModel.unitId, + subUnitId: this._filterModel.subUnitId, + col: this.col, + criteria: filterColumn, + } as ISetSheetsFilterCriteriaCommandParams); + } + + /** + * This method would be called when user changes the primary condition. The model would load the corresponding + * `IFilterConditionFormParams` and load default condition form params. + */ + onPrimaryConditionChange(operator: FilterOperator): void { + const conditionItem = FilterConditionItems.ALL_CONDITIONS.find((item) => item.operator === operator); + if (!conditionItem) { + throw new Error(`[ByConditionsModel]: condition item not found for operator: ${operator}!`); + } + + this._conditionItem$.next(conditionItem); + this._filterConditionFormParams$.next(FilterConditionItems.getInitialFormParams(operator)); + } + + /** + * This method would be called when user changes the primary conditions, the input values or "AND" "OR" ratio. + * If the primary conditions or the ratio is changed, the method would load the corresponding `IFilterCondition`. + * + * When the panel call this method, it only has to pass the changed keys. + * + * @param params + */ + onConditionFormChange(params: Partial & { and: boolean }>): void { + const newParams = { ...this.filterConditionFormParams, ...params }; + if (newParams.and !== true) { + delete newParams.and; + } + + if (typeof params.and !== 'undefined' || typeof params.operator1 !== 'undefined' || typeof params.operator2 !== 'undefined') { + const conditionItem = FilterConditionItems.testMappingParams(newParams as IFilterConditionFormParams, this.conditionItem.numOfParameters); + this._conditionItem$.next(conditionItem); + } + + this._filterConditionFormParams$.next(newParams as IFilterConditionFormParams); + } +} + +// #endregion + +// #region - ByValuesModel + +/** + * This model would be used to control the "Filter By Values" panel. It should be reconstructed in the following + * situations: + * + * 1. The target `FilterColumn` object is changed + * 2. User toggles "Filter By" + */ +export class ByValuesModel extends Disposable implements IFilterByModel { + /** + * Create a model with targeting filter column. If there is not a filter column, the model would be created with + * default values. + * + * @param injector + * @param filterModel + * @param col + * + * @returns the model to control the panel's state + */ + static fromFilterColumn(injector: Injector, filterModel: FilterModel, col: number): ByValuesModel { + const univerInstanceService = injector.get(IUniverInstanceService); + const localeService = injector.get(LocaleService); + + const { unitId, subUnitId } = filterModel; + const workbook = univerInstanceService.getUniverSheetInstance(unitId); + if (!workbook) throw new Error(`[ByValuesModel]: Workbook not found for filter model with unitId: ${unitId}!`); + + const worksheet = workbook?.getSheetBySheetId(subUnitId); + if (!worksheet) throw new Error(`[ByValuesModel]: Worksheet not found for filter model with unitId: ${unitId} and subUnitId: ${subUnitId}!`); + + const range = filterModel.getRange(); + const column = col; + const filters = filterModel.getFilterColumn(col)?.getColumnData().filters; + const blankChecked = !!(filters && filters.blank); + + // the first row is filter header and should be added to options + const iterateRange: IRange = { ...range, startRow: range.startRow + 1, startColumn: column, endColumn: column }; + const items: IFilterByValueItem[] = []; + const itemsByKey: Record = {}; + const alreadyChecked = new Set(filters?.filters); + const filteredOutRowsByOtherColumns = filterModel.getFilteredOutRowsExceptCol(col); + + let index = 0; + let emptyCount = 0; + for (const cell of worksheet.iterateByColumn(iterateRange, false, false)) { // iterate and do not skip empty cells + const { row, rowSpan = 1 } = cell; + + let rowIndex = 0; + while (rowIndex < rowSpan) { + const targetRow = row + rowIndex; + + if (filteredOutRowsByOtherColumns.has(targetRow)) { + rowIndex++; + continue; + } + + const value = cell?.value ? extractPureTextFromCell(cell.value) : ''; + if (!value) { + emptyCount += 1; + rowIndex += rowSpan; + continue; + } + + if (!itemsByKey[value]) { + const item: IFilterByValueItem = { + value, + checked: alreadyChecked.size ? alreadyChecked.has(value) : !blankChecked, + count: 1, + index, + isEmpty: false, + }; + + itemsByKey[value] = item; + items.push(item); + } else { + itemsByKey[value].count++; + } + rowIndex++; + } + + index++; + } + + const initialBlankChecked = filters ? blankChecked : true; + if (emptyCount > 0) { + const item: IFilterByValueItem = { + value: localeService.t('sheets-filter.panel.empty'), + checked: initialBlankChecked, + count: emptyCount, + index, + isEmpty: true, + }; + + items.push(item); + } + + return injector.createInstance(ByValuesModel, filterModel, col, items); + } + + private readonly _rawFilterItems$: BehaviorSubject; + readonly rawFilterItems$: Observable; + get rawFilterItems(): IFilterByValueItem[] { return this._rawFilterItems$.getValue(); } + + readonly filterItems$: Observable; + private _filterItems: IFilterByValueItem[] = []; + get filterItems() { return this._filterItems; } + + readonly canApply$: Observable; + + private readonly _manuallyUpdateFilterItems$: Subject; + + private readonly _searchString$: BehaviorSubject; + readonly searchString$: Observable; + + constructor( + private readonly _filterModel: FilterModel, + public col: number, + /** + * Filter items would remain unchanged after we create them, + * though data may change after. + */ + items: IFilterByValueItem[], + @ICommandService private readonly _commandService: ICommandService + ) { + super(); + + this._searchString$ = new BehaviorSubject(''); + this.searchString$ = this._searchString$.asObservable(); + + this._rawFilterItems$ = new BehaviorSubject(items); + this.rawFilterItems$ = this._rawFilterItems$.asObservable(); + + this._manuallyUpdateFilterItems$ = new Subject(); + + this.filterItems$ = merge( + combineLatest([ + this._searchString$.pipe( + throttleTime(500, undefined, { leading: true, trailing: true }), + startWith(void 0) + ), + this._rawFilterItems$, + ]).pipe( + map(([searchString, items]) => { + if (!searchString) return items; + + const lowerSearchString = searchString.toLowerCase(); + const searchKeyWords = lowerSearchString.split(/\s+/).filter((s) => !!s); + return items.filter((item) => { + const loweredItemValue = item.value.toLowerCase(); + return searchKeyWords.some((keyword) => loweredItemValue.includes(keyword)); + }); + }) + ), + this._manuallyUpdateFilterItems$ + ).pipe(shareReplay(1)); + + this.canApply$ = this.filterItems$.pipe(map((items) => { + const stat = statisticFilterByValueItems(items); + return stat.checked > 0; + })); + + this.disposeWithMe(this.filterItems$.subscribe((items) => this._filterItems = items)); + } + + override dispose(): void { + this._rawFilterItems$.complete(); + this._searchString$.complete(); + } + + deltaCol(offset: number): void { + this.col += offset; + } + + setSearchString(str: string): void { + this._searchString$.next(str); + } + + /** + * Toggle a filter item. + */ + onFilterCheckToggled(item: IFilterByValueItem, checked: boolean): void { + const items = this._filterItems.slice(); + const changedItem = items.find((i) => i.index === item.index); + changedItem!.checked = checked; + this._manuallyUpdateFilterItems(items); + } + + onFilterOnly(item: IFilterByValueItem) { + const items = this._filterItems.slice(); + items.forEach((i) => i.checked = i.index === item.index); + this._manuallyUpdateFilterItems(items); + } + + onCheckAllToggled(checked: boolean): void { + const items = this._filterItems.slice(); + items.forEach((i) => i.checked = checked); + this._manuallyUpdateFilterItems(items); + } + + private _manuallyUpdateFilterItems(items: IFilterByValueItem[]): void { + this._manuallyUpdateFilterItems$.next(items); + } + + // expose method here to let the panel change filter items + + // #region ByValuesModel apply methods + clear(): Promise { + if (this._disposed) return Promise.resolve(false); + + return this._commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { + unitId: this._filterModel.unitId, + subUnitId: this._filterModel.subUnitId, + col: this.col, + criteria: null, + } as ISetSheetsFilterCriteriaCommandParams); + } + + /** + * Apply the filter condition to the target filter column. + */ + async apply(): Promise { + if (this._disposed) { + return false; + } + + const statistics = statisticFilterByValueItems(this._filterItems); + const { checked, checkedItems } = statistics; + const rawFilterItems = this.rawFilterItems; + + const noChecked = checked === 0; + const allChecked = statistics.checked === rawFilterItems.length; + + const criteria: IFilterColumn = { colId: this.col }; + if (noChecked) { + throw new Error('[ByValuesModel]: no checked items!'); + } else if (allChecked) { + return this._commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { + unitId: this._filterModel.unitId, + subUnitId: this._filterModel.subUnitId, + col: this.col, + criteria: null, + } as ISetSheetsFilterCriteriaCommandParams); + } else { + criteria.filters = {}; + + const nonEmptyItems = checkedItems.filter((item) => !item.isEmpty); + if (nonEmptyItems.length > 0) { + criteria.filters = { filters: nonEmptyItems.map((item) => item.value) }; + } + + const hasEmpty = nonEmptyItems.length !== checkedItems.length; + if (hasEmpty) { + criteria.filters.blank = true; + } + } + + return this._commandService.executeCommand(SetSheetsFilterCriteriaCommand.id, { + unitId: this._filterModel.unitId, + subUnitId: this._filterModel.subUnitId, + col: this.col, + criteria, + } as ISetSheetsFilterCriteriaCommandParams); + } + + // #endregion +} + +// #endregion diff --git a/packages/sheets-filter-ui/src/views/components/SheetsFilterByConditionsPanel.tsx b/packages/sheets-filter-ui/src/views/components/SheetsFilterByConditionsPanel.tsx new file mode 100644 index 000000000000..b39997666800 --- /dev/null +++ b/packages/sheets-filter-ui/src/views/components/SheetsFilterByConditionsPanel.tsx @@ -0,0 +1,153 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LocaleService } from '@univerjs/core'; +import type { ISelectProps } from '@univerjs/design'; +import { Input, Radio, RadioGroup, Select } from '@univerjs/design'; +import { useObservable } from '@univerjs/ui'; +import { useDependency } from '@wendellhu/redi/react-bindings'; +import React, { useCallback, useMemo } from 'react'; + +import type { ByConditionsModel } from '../../services/sheets-filter-panel.service'; +import type { FilterOperator, IFilterConditionFormParams } from '../../models/conditions'; +import { FilterConditionItems } from '../../models/conditions'; + +import styles from './index.module.less'; + +/** + * Filter by conditions. + */ +export function FilterByCondition(props: { model: ByConditionsModel }) { + const { model } = props; + + const localeService = useDependency(LocaleService); + + // form state is from the model + const condition = useObservable(model.conditionItem$, undefined, true); + const formParams = useObservable(model.filterConditionFormParams$, undefined, true); + const { operator, numOfParameters } = condition; + const { operator1, operator2, val1, val2, and } = formParams; + + const radioValue = and ? 'AND' : 'OR'; + const onRadioChange = useCallback((key: string | number | boolean) => { + model.onConditionFormChange({ and: key === 'AND' }); + }, [model]); + + const primaryOptions = usePrimaryOptions(localeService); + const onPrimaryConditionChange = useCallback((value: string) => { + model.onPrimaryConditionChange(value as FilterOperator); + }, [model]); + + const secondaryOptions = useSecondaryOptions(localeService); + const onFormParamsChange = useCallback((diffParams: Partial) => { + model.onConditionFormChange(diffParams); + }, [model]); + + const placeholder = localeService.t('sheets-filter.panel.input-values-placeholder'); + function renderSecondaryCondition(operator: FilterOperator, val: string, name: 'operator1' | 'operator2') { + const shouldRenderInput = FilterConditionItems.getItemByOperator(operator).numOfParameters === 1; + return ( + <> + { name === 'operator2' && ( + + {localeService.t('sheets-filter.panel.and')} + {localeService.t('sheets-filter.panel.or')} + + )} + onFormParamsChange({ [name === 'operator1' ? 'val1' : 'val2']: value })} /> + )} + + ); + } + + return ( +

+ {/* primary condition */} + +
+ {/* The on-top select all button */} +
+
+ + + {`${localeService.t('sheets-filter.panel.select-all')}`} + {`(${stat.checked}/${stat.checked + stat.unchecked})`} +
+
+
+ `${item.value}----${item.checked}`}> + {(item) => ( +
+
+ onFilterCheckToggled(item, !item.checked)}> + {item.value} + {`(${item.count})`} + +
+
+ )} +
+
+
+
+ ); +} diff --git a/packages/sheets-filter-ui/src/views/components/SheetsFilterPanel.stories.tsx b/packages/sheets-filter-ui/src/views/components/SheetsFilterPanel.stories.tsx new file mode 100644 index 000000000000..8f2d541b7711 --- /dev/null +++ b/packages/sheets-filter-ui/src/views/components/SheetsFilterPanel.stories.tsx @@ -0,0 +1,133 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useState } from 'react'; +import type { Meta } from '@storybook/react'; +import { RediContext } from '@wendellhu/redi/react-bindings'; +import type { Injector } from '@wendellhu/redi'; +import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; +import type { IWorkbookData } from '@univerjs/core'; +import { ICommandService, ILogService, LocaleService, LocaleType, LogLevel, Plugin, Univer, UniverInstanceType } from '@univerjs/core'; +import { RefRangeService, SelectionManagerService, SheetInterceptorService, SheetPermissionService } from '@univerjs/sheets'; +import { DesktopMenuService, DesktopShortcutService, IMenuService, IShortcutService } from '@univerjs/ui'; +import { SheetsFilterPanelService } from '../../services/sheets-filter-panel.service'; +import { ClearSheetsFilterCriteriaCommand, ReCalcSheetsFilterCommand, SetSheetsFilterCriteriaCommand, SmartToggleSheetsFilterCommand } from '../../commands/sheets-filter.command'; +import type { IOpenFilterPanelOperationParams } from '../../commands/sheets-filter.operation'; +import { ChangeFilterByOperation, CloseFilterPanelOperation, OpenFilterPanelOperation } from '../../commands/sheets-filter.operation'; +import zhCN from '../../locale/zh-CN'; +import enUS from '../../locale/en-US'; +import { WithCustomFilterModelFactory, WithValuesFilterModelFactory } from '../../__testing__/data'; +import { FilterPanel } from './SheetsFilterPanel'; + +const meta: Meta = { + title: 'Components / FilterPanel', + component: FilterPanel, + tags: ['autodocs'], +}; + +export default meta; + +function createFilterStorybookBed(workbookData: IWorkbookData, locale: LocaleType = LocaleType.EN_US) { + const univer = new Univer(); + const injector = univer.__getInjector(); + const get = injector.get.bind(injector); + + const commandService = get(ICommandService); + + class TestPlugin extends Plugin { + static override type = UniverInstanceType.SHEET; + static override pluginName = 'test-plugin'; + + constructor(_config: unknown, override readonly _injector: Injector) { + super(); + } + + override onStarting(injector: Injector): void { + injector.add([SelectionManagerService]); + injector.add([IShortcutService, { useClass: DesktopShortcutService }]); + injector.add([IMenuService, { useClass: DesktopMenuService }]); + injector.add([SheetPermissionService]); + injector.add([SheetInterceptorService]); + injector.add([SheetsFilterPanelService]); + injector.add([RefRangeService]); + + [ + SmartToggleSheetsFilterCommand, + SetSheetsFilterCriteriaCommand, + ClearSheetsFilterCriteriaCommand, + ReCalcSheetsFilterCommand, + OpenFilterPanelOperation, + CloseFilterPanelOperation, + ChangeFilterByOperation, + ].forEach((command) => commandService.registerCommand(command)); + } + } + + univer.registerPlugin(TestPlugin); + univer.registerPlugin(UniverSheetsFilterPlugin); + + injector.get(LocaleService).setLocale(locale); + injector.get(LocaleService).load({ enUS, zhCN }); + injector.get(ILogService).setLogLevel(LogLevel.VERBOSE); + injector.get(ILogService).setLogLevel(LogLevel.VERBOSE); + + const sheet = univer.createUniverSheet(workbookData); + + commandService.syncExecuteCommand(OpenFilterPanelOperation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + } as IOpenFilterPanelOperationParams); + + return { univer, injector, sheet }; +} + +export const FilterWithConditions = { + render() { + const [bed] = useState(() => createFilterStorybookBed(WithCustomFilterModelFactory())); + const { injector } = bed; + + return ( + + + + ); + }, +}; + +export const FilterWithValues = { + render() { + const [bed] = useState(() => createFilterStorybookBed(WithValuesFilterModelFactory())); + + return ( + + + + ); + }, +}; + +export const FilterWithChinese = { + render() { + const [bed] = useState(() => createFilterStorybookBed(WithValuesFilterModelFactory(), LocaleType.ZH_CN)); + + return ( + + + + ); + }, +}; diff --git a/packages/sheets-filter-ui/src/views/components/SheetsFilterPanel.tsx b/packages/sheets-filter-ui/src/views/components/SheetsFilterPanel.tsx new file mode 100644 index 000000000000..1a75440d41b9 --- /dev/null +++ b/packages/sheets-filter-ui/src/views/components/SheetsFilterPanel.tsx @@ -0,0 +1,100 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useCallback, useMemo } from 'react'; +import { Button, type ISegmentedProps, Segmented } from '@univerjs/design'; +import { useDependency } from '@wendellhu/redi/react-bindings'; +import { useObservable } from '@univerjs/ui'; +import { ICommandService, LocaleService } from '@univerjs/core'; + +import { of } from 'rxjs'; +import type { ByConditionsModel, ByValuesModel } from '../../services/sheets-filter-panel.service'; +import { FilterBy, SheetsFilterPanelService } from '../../services/sheets-filter-panel.service'; +import { ChangeFilterByOperation, CloseFilterPanelOperation } from '../../commands/sheets-filter.operation'; +import styles from './index.module.less'; +import { FilterByCondition } from './SheetsFilterByConditionsPanel'; +import { FilterByValue } from './SheetsFilterByValuesPanel'; + +/** + * This Filter Panel component is used to filter the data in the sheet. + * + * @returns React element + */ +export function FilterPanel() { + const sheetsFilterPanelService = useDependency(SheetsFilterPanelService); + const localeService = useDependency(LocaleService); + const commandService = useDependency(ICommandService); + + const filterBy = useObservable(sheetsFilterPanelService.filterBy$, undefined, true); + const filterByModel = useObservable(sheetsFilterPanelService.filterByModel$, undefined, false); + const canApply = useObservable(() => filterByModel?.canApply$ || of(false), undefined, false, [filterByModel]); + const options = useFilterByOptions(localeService); + + // only can disable clear when there is no criteria + const clearFilterDisabled = !useObservable(sheetsFilterPanelService.hasCriteria$); + + const onFilterByTypeChange = useCallback((value: FilterBy) => { + commandService.executeCommand(ChangeFilterByOperation.id, { filterBy: value }); + }, [commandService]); + + const onClearCriteria = useCallback(async () => { + await filterByModel?.clear(); + commandService.executeCommand(CloseFilterPanelOperation.id); + }, [filterByModel, commandService]); + + const onCancel = useCallback(() => { + commandService.executeCommand(CloseFilterPanelOperation.id); + }, [commandService]); + + const onApply = useCallback(async () => { + await filterByModel?.apply(); + commandService.executeCommand(CloseFilterPanelOperation.id); + }, [filterByModel, commandService]); + + return ( +
+
+ onFilterByTypeChange(value as FilterBy)}> +
+ { filterByModel + ? ( +
+ {filterBy === FilterBy.VALUES + ? + : } +
+ ) + : null } +
+ + + + + +
+
+ ); +} + +function useFilterByOptions(localeService: LocaleService): ISegmentedProps['options'] { + const locale = localeService.getCurrentLocale(); + return useMemo(() => [ + { label: localeService.t('sheets-filter.panel.by-values'), value: FilterBy.VALUES }, + { label: localeService.t('sheets-filter.panel.by-conditions'), value: FilterBy.CONDITIONS }, + ] + // eslint-disable-next-line react-hooks/exhaustive-deps + , [locale, localeService]); +} diff --git a/packages/sheets-filter-ui/src/views/components/index.module.less b/packages/sheets-filter-ui/src/views/components/index.module.less new file mode 100644 index 000000000000..ca59a8beb7c2 --- /dev/null +++ b/packages/sheets-filter-ui/src/views/components/index.module.less @@ -0,0 +1,199 @@ +.sheets-filter-panel { + display: flex; + width: 312px; + height: 400px; + flex-direction: column; + background: rgb(var(--bg-color-secondary)); + border-radius: 10px; + box-sizing: border-box; + box-shadow: var(--box-shadow-lg); + padding: 16px; + overflow: hidden; + + &-header { + flex-grow: 0; + flex-shrink: 0; + margin-bottom: 4px; + } + + &-content { + flex-grow: 1; + flex-shrink: 1; + overflow: hidden; + padding-top: 8px; + } + + &-select-all { + font-size: 13px; + margin-right: 4px; + + &-count { + font-size: 13px; + color: rgb(var(--grey-400)); + } + } + + &-values { + &-container { + display: flex; + height: 100%; + flex-direction: column; + + > .input-affix-wrapper { + flex: 0 0 32px; + } + + .select, + .input-affix-wrapper, + .radio-group { + width: 100%; + } + } + + &-find { + height: 32px; + margin-bottom: 8px; + } + + &-list { + flex-grow: 1; + margin-top: 8px; + border: 1px solid rgb(var(--grey-200)); + box-sizing: border-box; + border-radius: 6px; + overflow: hidden; + display: flex; + flex-direction: column; + + padding: 6px 0 6px 8px; + + &-inner-container { + padding-right: 8px; + } + } + + &-virtual { + flex-grow: 1; + } + + &-item { + box-sizing: border-box; + height: 32px; + padding: 2px 0; + width: 100%; + + &-inner { + align-items: center; + padding: 0 2px 0 24px; + line-height: 28px; + box-sizing: border-box; + border-radius: 6px; + height: 28px; + display: flex; + font-size: 13px; + + &:hover { + background-color: rgb(var(--grey-50)); + + .sheets-filter-panel-values-item-count { + margin-right: 2px; + } + + .sheets-filter-panel-values-item-exclude-button { + box-sizing: border-box; + margin-left: auto; + display: inline-block; + height: 24px; + font-size: 12px; + line-height: 12px; + + &:hover { + background-color: rgb(var(--hyacinth-50)); + } + + &:active { + background-color: rgb(var(--hyacinth-100)); + } + } + } + } + + &-text { + display: inline-block; + height: 100%; + margin: 0 4px; + flex-shrink: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &-count { + color: rgb(var(--grey-400)); + } + + &-exclude-button { + display: none; + } + } + } + + &-conditions { + &-container { + display: flex; + flex-direction: column; + height: 100%; + + .select, + .input-affix-wrapper, + .radio-group { + width: 100%; + margin-bottom: 8px; + } + + &-inner { + flex-grow: 1; + border-radius: 6px; + border: 1px solid rgb(var(--grey-200)); + overflow: hidden; + padding: 8px; + } + } + + &-desc { + margin-top: -6px; + color: rgb(var(--grey-500)); + font-size: 12px; + line-height: 18px; + } + } + + &-footer { + margin-top: 16px; + flex-grow: 0; + flex-shrink: 0; + display: inline-flex; + flex-wrap: nowrap; + overflow: hidden; + + justify-content: space-between; + + &-primary-buttons { + > .button { + margin-left: 12px; + } + } + } + + .select { + width: 100%; + } + + .input { + width: 100%; + } + + .form-dual-column-layout { + margin-bottom: 8px; + } +} diff --git a/packages/sheets-filter-ui/src/views/widgets/drawings.ts b/packages/sheets-filter-ui/src/views/widgets/drawings.ts new file mode 100644 index 000000000000..90e6131f8e7c --- /dev/null +++ b/packages/sheets-filter-ui/src/views/widgets/drawings.ts @@ -0,0 +1,68 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { UniverRenderingContext2D } from '@univerjs/engine-render'; +import { Rect } from '@univerjs/engine-render'; + +const BUTTON_VIEWPORT = 16; + +// This path is deprecated. We need to get rounded edge of the stroked line. +// export const FILTER_BUTTON_HAS_CRITERIA = new Path2D('M3 4H13 M4.5 8H11.5 M6 12H10'); +export const FILTER_BUTTON_EMPTY = new Path2D('M3.30363 3C2.79117 3 2.51457 3.60097 2.84788 3.99024L6.8 8.60593V12.5662C6.8 12.7184 6.8864 12.8575 7.02289 12.9249L8.76717 13.7863C8.96655 13.8847 9.2 13.7396 9.2 13.5173V8.60593L13.1521 3.99024C13.4854 3.60097 13.2088 3 12.6964 3H3.30363Z'); + +export class FilterButton { + static drawNoCriteria(ctx: UniverRenderingContext2D, size: number, fgColor: string, bgColor: string): void { + ctx.save(); + + Rect.drawWith(ctx, { + radius: 2, + width: BUTTON_VIEWPORT, + height: BUTTON_VIEWPORT, + fill: bgColor, + }); + + ctx.lineCap = 'square'; + ctx.strokeStyle = fgColor; + ctx.scale(size / BUTTON_VIEWPORT, size / BUTTON_VIEWPORT); + ctx.beginPath(); + ctx.lineWidth = 1; + ctx.lineCap = 'round'; + ctx.moveTo(3, 4); + ctx.lineTo(13, 4); + ctx.moveTo(4.5, 8); + ctx.lineTo(11.5, 8); + ctx.moveTo(6, 12); + ctx.lineTo(10, 12); + ctx.stroke(); + ctx.restore(); + } + + static drawHasCriteria(ctx: UniverRenderingContext2D, size: number, fgColor: string, bgColor: string): void { + ctx.save(); + + Rect.drawWith(ctx, { + radius: 2, + width: BUTTON_VIEWPORT, + height: BUTTON_VIEWPORT, + fill: bgColor, + }); + + ctx.scale(size / BUTTON_VIEWPORT, size / BUTTON_VIEWPORT); + ctx.fillStyle = fgColor; + ctx.fill(FILTER_BUTTON_EMPTY); + ctx.restore(); + } +} diff --git a/packages/sheets-filter-ui/src/views/widgets/filter-button.shape.ts b/packages/sheets-filter-ui/src/views/widgets/filter-button.shape.ts new file mode 100644 index 000000000000..ce4f525afcf8 --- /dev/null +++ b/packages/sheets-filter-ui/src/views/widgets/filter-button.shape.ts @@ -0,0 +1,137 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ICommandService, IContextService, ThemeService } from '@univerjs/core'; +import { type IMouseEvent, type IPointerEvent, type IShapeProps, Shape, type UniverRenderingContext2D } from '@univerjs/engine-render'; + +import { Inject } from '@wendellhu/redi'; +import type { IOpenFilterPanelOperationParams } from '../../commands/sheets-filter.operation'; +import { FILTER_PANEL_OPENED_KEY, OpenFilterPanelOperation } from '../../commands/sheets-filter.operation'; +import { FilterButton } from './drawings'; + +export const FILTER_ICON_SIZE = 16; +export const FILTER_ICON_PADDING = 1; + +export interface ISheetsFilterButtonShapeProps extends IShapeProps { + cellWidth: number; + cellHeight: number; + filterParams: { col: number; unitId: string; subUnitId: string; hasCriteria: boolean }; +} + +/** + * The widget to render a filter button on canvas. + */ +export class SheetsFilterButtonShape extends Shape { + private _cellWidth: number = 0; + private _cellHeight: number = 0; + + private _filterParams?: { col: number; unitId: string; subUnitId: string; hasCriteria: boolean }; + + private _hovered = false; + + constructor( + key: string, + props: ISheetsFilterButtonShapeProps, + @IContextService private readonly _contextService: IContextService, + @ICommandService private readonly _commandService: ICommandService, + @Inject(ThemeService) private readonly _themeService: ThemeService + ) { + super(key, props); + + this.setShapeProps(props); + + // Here we need to make sure that the event is on the rectangle range. + this.onPointerDownObserver.add((evt) => this.onPointerDown(evt)); + this.onPointerEnterObserver.add(() => this.onPointerEnter()); + this.onPointerLeaveObserver.add(() => this.onPointerLeave()); + } + + setShapeProps(props: Partial): void { + if (typeof props.cellHeight !== 'undefined') { + this._cellHeight = props.cellHeight; + } + + if (typeof props.cellWidth !== 'undefined') { + this._cellWidth = props.cellWidth; + } + + if (typeof props.filterParams !== 'undefined') { + this._filterParams = props.filterParams; + } + + this.transformByState({ + width: props.width!, + height: props.height!, + }); + } + + protected override _draw(ctx: UniverRenderingContext2D): void { + const cellHeight = this._cellHeight; + const cellWidth = this._cellWidth; + + const left = FILTER_ICON_SIZE - cellWidth; + const top = FILTER_ICON_SIZE - cellHeight; + + ctx.save(); + + const cellRegion = new Path2D(); + cellRegion.rect(left, top, cellWidth, cellHeight); + ctx.clip(cellRegion); + + const { hasCriteria } = this._filterParams!; + const fgColor = this._themeService.getCurrentTheme().primaryColor; + const bgColor = this._hovered + ? this._themeService.getCurrentTheme().grey50 + : 'rgba(255, 255, 255, 1.0)'; + + if (hasCriteria) { + FilterButton.drawHasCriteria(ctx, FILTER_ICON_SIZE, fgColor, bgColor); + } else { + FilterButton.drawNoCriteria(ctx, FILTER_ICON_SIZE, fgColor, bgColor); + } + + ctx.restore(); + } + + onPointerDown(evt: IPointerEvent | IMouseEvent): void { + // Right click not trigger this event. + if (evt.button === 2) { + return; + } + + const { col, unitId, subUnitId } = this._filterParams!; + const opened = this._contextService.getContextValue(FILTER_PANEL_OPENED_KEY); + if (opened) return; + + setTimeout(() => { + this._commandService.executeCommand(OpenFilterPanelOperation.id, { + unitId, + subUnitId, + col, + } as IOpenFilterPanelOperationParams); + }, 200); + } + + onPointerEnter(): void { + this._hovered = true; + this.makeDirty(true); + } + + onPointerLeave(): void { + this._hovered = false; + this.makeDirty(true); + } +} diff --git a/packages/sheets-filter-ui/src/vite-env.d.ts b/packages/sheets-filter-ui/src/vite-env.d.ts new file mode 100644 index 000000000000..11f02fe2a006 --- /dev/null +++ b/packages/sheets-filter-ui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/sheets-filter-ui/tsconfig.json b/packages/sheets-filter-ui/tsconfig.json new file mode 100644 index 000000000000..d676ad2a20dc --- /dev/null +++ b/packages/sheets-filter-ui/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@univerjs/shared/tsconfigs/base", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "references": [{ "path": "./tsconfig.node.json" }], + "include": ["src"] +} diff --git a/packages/sheets-filter-ui/tsconfig.node.json b/packages/sheets-filter-ui/tsconfig.node.json new file mode 100644 index 000000000000..e53dac88688c --- /dev/null +++ b/packages/sheets-filter-ui/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": "@univerjs/shared/tsconfigs/node", + "include": ["vite.config.ts"] +} diff --git a/packages/sheets-filter-ui/vite.config.ts b/packages/sheets-filter-ui/vite.config.ts new file mode 100644 index 000000000000..925b530b4d51 --- /dev/null +++ b/packages/sheets-filter-ui/vite.config.ts @@ -0,0 +1,12 @@ +import createViteConfig from '@univerjs/shared/vite'; +import pkg from './package.json'; + +export default ({ mode }) => createViteConfig({}, { + mode, + pkg, + features: { + react: false, + css: true, + dom: true, + }, +}); diff --git a/packages/sheets-filter/README.md b/packages/sheets-filter/README.md new file mode 100644 index 000000000000..b3c2d518c65c --- /dev/null +++ b/packages/sheets-filter/README.md @@ -0,0 +1,16 @@ +# @univerjs/sheets-filter + +[![npm version](https://img.shields.io/npm/v/@univerjs/0.0.1)](https://npmjs.org/packages/@univerjs/0.0.1) +[![license](https://img.shields.io/npm/l/@univerjs/0.0.1)](https://img.shields.io/npm/l/@univerjs/0.0.1) + +## Introduction + +> TODO: Introduction + +## Usage + +### Installation + +```shell +npm i @univerjs/0.0.1 +``` diff --git a/packages/sheets-filter/docs/README.md b/packages/sheets-filter/docs/README.md new file mode 100644 index 000000000000..b68e8076d042 --- /dev/null +++ b/packages/sheets-filter/docs/README.md @@ -0,0 +1,26 @@ +# Sheets Filter + +> There are a lot of implementation details that is not explicitly documented in the OOXML document or just simply hard to be noticed. This documents entails some interesting and important details. + +## Architecture Design + +Please refer to the excalidraw file in the same folder. + +## Details of Microsoft Excel + +The first detail that is interesting is + +## History Tickets + +- [\[Feature Ticket\] Filter for Sheet](https://github.com/dream-num/univer/issues/1450) + +## Custom Filters + +### OOXML Compatibility + +In OOXML, there are a limited number of custom filter operators (please refer to `CustomFilterOperator`), and other operators including `startsWith` and `endsWith` (for a full list of this kind of operators see `ExtendCustomFilterOperator`) are actually supported by the combinations of OOXML operators, `*?` regex-like fuzzy matching, filter-by-values, dynamic filter, and so on. + +We take the same method instead of making `ExtendCustomFilterOperator` to be first-class citizens, because it is more OOXML-compatible and makes importing/exporting Excel files easier to implement. + +The cost is to implement the transformation between `ExtendCustomFilterOperator` and combinations. But the overall cost seems to be acceptable. + diff --git a/packages/sheets-filter/docs/architecture.excalidraw b/packages/sheets-filter/docs/architecture.excalidraw new file mode 100644 index 000000000000..7b0e59d90c4f --- /dev/null +++ b/packages/sheets-filter/docs/architecture.excalidraw @@ -0,0 +1,2000 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", + "elements": [ + { + "type": "rectangle", + "version": 535, + "versionNonce": 268109660, + "isDeleted": false, + "id": "Wo0_9IeyNh2nP4LXReZmo", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 568.5682799151973, + "y": 373.02730145116107, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 846.505841664141, + "height": 155.31822765624224, + "seed": 1231215192, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "skjWeSInzEmEn3uiRPei3" + }, + { + "id": "1LQZ-4OhRIcHkjiftPB_k", + "type": "arrow" + }, + { + "id": "MAt44M2i9bMujXxTybVyG", + "type": "arrow" + }, + { + "id": "OabY2dpulZvjMUBYLDb-c", + "type": "arrow" + } + ], + "updated": 1709529868650, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 255, + "versionNonce": 1055532764, + "isDeleted": false, + "id": "skjWeSInzEmEn3uiRPei3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 911.2412828395529, + "y": 378.02730145116107, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 161.1598358154297, + "height": 25, + "seed": 931504984, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529868651, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "SheetsFilterModel", + "textAlign": "center", + "verticalAlign": "top", + "containerId": "Wo0_9IeyNh2nP4LXReZmo", + "originalText": "SheetsFilterModel", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 190, + "versionNonce": 317222372, + "isDeleted": false, + "id": "2GiJPBSXYl-3UZCZzA7T8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 608.0703206278608, + "y": 436.5304323371829, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 240.14453125, + "height": 57.953125, + "seed": 817986600, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "qsnmD5AWaAtQ1HxZZ3QoZ" + } + ], + "updated": 1709529491331, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 156, + "versionNonce": 1082708572, + "isDeleted": false, + "id": "qsnmD5AWaAtQ1HxZZ3QoZ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 643.2626729227827, + "y": 453.0069948371829, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 169.75982666015625, + "height": 25, + "seed": 1713720104, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529491331, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "SheetsFilterColumn", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "2GiJPBSXYl-3UZCZzA7T8", + "originalText": "SheetsFilterColumn", + "lineHeight": 1.25, + "baseline": 19 + }, + { + "type": "rectangle", + "version": 236, + "versionNonce": 115937636, + "isDeleted": false, + "id": "ESHr4MN4x6js5bqTvZL8A", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 869.8769612528608, + "y": 436.5304323371829, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 240.14453125, + "height": 57.953125, + "seed": 257578024, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "U_mt7qDKOAdCDcxG1mIY-" + } + ], + "updated": 1709529491331, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 202, + "versionNonce": 83240668, + "isDeleted": false, + "id": "U_mt7qDKOAdCDcxG1mIY-", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 905.0693135477827, + "y": 453.0069948371829, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 169.75982666015625, + "height": 25, + "seed": 206409512, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529491331, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "SheetsFilterColumn", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ESHr4MN4x6js5bqTvZL8A", + "originalText": "SheetsFilterColumn", + "lineHeight": 1.25, + "baseline": 19 + }, + { + "type": "rectangle", + "version": 283, + "versionNonce": 168379620, + "isDeleted": false, + "id": "rLpLoR7dI717hG_0fDIOz", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1129.6816487528608, + "y": 436.5304323371829, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 240.14453125, + "height": 57.953125, + "seed": 1229315624, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "4LsyRziximpvCQkTygNH2" + } + ], + "updated": 1709529491331, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 249, + "versionNonce": 1774512988, + "isDeleted": false, + "id": "4LsyRziximpvCQkTygNH2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1164.8740010477827, + "y": 453.0069948371829, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 169.75982666015625, + "height": 25, + "seed": 1412946216, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529491331, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "SheetsFilterColumn", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "rLpLoR7dI717hG_0fDIOz", + "originalText": "SheetsFilterColumn", + "lineHeight": 1.25, + "baseline": 19 + }, + { + "id": "eD7q3TO5HwewuHD1ehJ5A", + "type": "rectangle", + "x": 1117.6464725834735, + "y": -40.00893620465888, + "width": 428.0325042529553, + "height": 113.36248554036945, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1450039516, + "version": 465, + "versionNonce": 160904676, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "wMsEnyH_fm8arkwE8qiOF" + }, + { + "id": "1LQZ-4OhRIcHkjiftPB_k", + "type": "arrow" + }, + { + "id": "rNw6wTRniT8wZrtOFMT3f", + "type": "arrow" + }, + { + "id": "GvT8eNMzbir52Ncwk_ieq", + "type": "arrow" + }, + { + "id": "2OyzL49hpoQ1cSagBLOW3", + "type": "arrow" + }, + { + "id": "frIZ04lSYhsp0eiNkKcnd", + "type": "arrow" + }, + { + "id": "_wfgP2BWS8519rai8Nc_K", + "type": "arrow" + } + ], + "updated": 1709529850348, + "link": null, + "locked": false + }, + { + "id": "wMsEnyH_fm8arkwE8qiOF", + "type": "text", + "x": 1244.3128177885644, + "y": 4.172306565525844, + "width": 174.69981384277344, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 92494300, + "version": 337, + "versionNonce": 1283381604, + "isDeleted": false, + "boundElements": null, + "updated": 1709529850348, + "link": null, + "locked": false, + "text": "SheetsFilterService", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "eD7q3TO5HwewuHD1ehJ5A", + "originalText": "SheetsFilterService", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 411, + "versionNonce": 1841221860, + "isDeleted": false, + "id": "k8VRsAR3ODxOqJPd38VTV", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 494.76701314929346, + "y": -55.98039124876334, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 356.2740549869553, + "height": 113.36248554036945, + "seed": 752224996, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "jy4KeNqOaNUrKegClzv70" + }, + { + "id": "MAt44M2i9bMujXxTybVyG", + "type": "arrow" + }, + { + "id": "Bduw1dViUvutS_TpDV07Q", + "type": "arrow" + } + ], + "updated": 1709529845615, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 307, + "versionNonce": 1315684964, + "isDeleted": false, + "id": "jy4KeNqOaNUrKegClzv70", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 561.6641267023415, + "y": -11.799148478578616, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 222.47982788085938, + "height": 25, + "seed": 723193444, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529845616, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Commands / Mutations", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "k8VRsAR3ODxOqJPd38VTV", + "originalText": "Commands / Mutations", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "id": "1LQZ-4OhRIcHkjiftPB_k", + "type": "arrow", + "x": 1247.3575471004908, + "y": 93.94614810485214, + "width": 296.4683248639353, + "height": 261.0458056224046, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1721263716, + "version": 740, + "versionNonce": 1877860444, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "l2-xMVe93eyzttz1sAmfv" + } + ], + "updated": 1709529868650, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -296.4683248639353, + 261.0458056224046 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "eD7q3TO5HwewuHD1ehJ5A", + "gap": 20.592598769141546, + "focus": -0.012253391746855843 + }, + "endBinding": { + "elementId": "Wo0_9IeyNh2nP4LXReZmo", + "gap": 18.03534772390435, + "focus": -0.29252454387057053 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "l2-xMVe93eyzttz1sAmfv", + "type": "text", + "x": 981.4028668194715, + "y": 201.74257841264455, + "width": 210.83984375, + "height": 75, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1055404508, + "version": 68, + "versionNonce": 1209153508, + "isDeleted": false, + "boundElements": null, + "updated": 1709529541346, + "link": null, + "locked": false, + "text": "manage instances of \nSheetsFilterModel for \neach Worksheet", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 68, + "containerId": "1LQZ-4OhRIcHkjiftPB_k", + "originalText": "manage instances of SheetsFilterModel for each Worksheet", + "lineHeight": 1.25 + }, + { + "id": "MAt44M2i9bMujXxTybVyG", + "type": "arrow", + "x": 679.1095310252703, + "y": 73.22931012970312, + "width": 100.80883902524477, + "height": 274.8777351366351, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1917390436, + "version": 477, + "versionNonce": 1371437404, + "isDeleted": false, + "boundElements": null, + "updated": 1709529868651, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 100.80883902524477, + 274.8777351366351 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "k8VRsAR3ODxOqJPd38VTV", + "gap": 15.847215838096986, + "focus": 0.10265597141057622 + }, + "endBinding": { + "elementId": "Wo0_9IeyNh2nP4LXReZmo", + "gap": 24.920256184822847, + "focus": -0.3858087535356203 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "version": 655, + "versionNonce": 1947851100, + "isDeleted": false, + "id": "B0d_SJgkv3tx8QXxBpD9m", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1526.8694539885219, + "y": 241.90796895450381, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 303.9694634049088, + "height": 113.36248554036945, + "seed": 1196723676, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "fiE_jcJmbb2Cr0JOrjshe" + }, + { + "id": "rNw6wTRniT8wZrtOFMT3f", + "type": "arrow" + }, + { + "id": "pJZRckWJwVwymCILKT7Ul", + "type": "arrow" + }, + { + "id": "4sD4NnxsLDc3_sGQDhM5k", + "type": "arrow" + } + ], + "updated": 1709529860383, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 552, + "versionNonce": 1803190236, + "isDeleted": false, + "id": "fiE_jcJmbb2Cr0JOrjshe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1561.0343080664645, + "y": 286.08921172468854, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 235.63975524902344, + "height": 25, + "seed": 1103000156, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529860384, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "SheetInterceptorService", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "B0d_SJgkv3tx8QXxBpD9m", + "originalText": "SheetInterceptorService", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "id": "rNw6wTRniT8wZrtOFMT3f", + "type": "arrow", + "x": 1366.6752688239949, + "y": 89.92142690031405, + "width": 255.16430494480664, + "height": 134.54569627549859, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1509428572, + "version": 579, + "versionNonce": 1953416036, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "jbZpkBJ8GDIY0_FtQclm5" + } + ], + "updated": 1709530103486, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 255.16430494480664, + 134.54569627549859 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "eD7q3TO5HwewuHD1ehJ5A", + "focus": 0.3231720186558348, + "gap": 16.567877564603478 + }, + "endBinding": { + "elementId": "B0d_SJgkv3tx8QXxBpD9m", + "focus": 0.32201723866591586, + "gap": 17.440845778691198 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "jbZpkBJ8GDIY0_FtQclm5", + "type": "text", + "x": 1385.667531770031, + "y": 132.19427503806332, + "width": 217.17977905273438, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1245867364, + "version": 50, + "versionNonce": 1735711708, + "isDeleted": false, + "boundElements": null, + "updated": 1709530102404, + "link": null, + "locked": false, + "text": "RegisterFilterIntecept\nor", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 43, + "containerId": "rNw6wTRniT8wZrtOFMT3f", + "originalText": "RegisterFilterInteceptor", + "lineHeight": 1.25 + }, + { + "id": "pJZRckWJwVwymCILKT7Ul", + "type": "arrow", + "x": 1651.9372616908367, + "y": 373.67859969501, + "width": 34.726079206353006, + "height": 161.156845679997, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 122126684, + "version": 534, + "versionNonce": 786713436, + "isDeleted": false, + "boundElements": null, + "updated": 1709529860383, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 34.726079206353006, + 161.156845679997 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "B0d_SJgkv3tx8QXxBpD9m", + "gap": 18.408145200136744, + "focus": 0.2613015510363769 + }, + "endBinding": { + "elementId": "LN9hzLXZEu1nq-GHD2QOt", + "gap": 16.325585576558012, + "focus": -0.2476381428592879 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "version": 626, + "versionNonce": 1632026212, + "isDeleted": false, + "id": "LN9hzLXZEu1nq-GHD2QOt", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1631.5767909976685, + "y": 551.1610309515651, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 196.295528007121, + "height": 113.36248554036945, + "seed": 1950489564, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "xqc2JD9eCY5yXVm2z1Rl9" + }, + { + "id": "pJZRckWJwVwymCILKT7Ul", + "type": "arrow" + }, + { + "id": "OabY2dpulZvjMUBYLDb-c", + "type": "arrow" + }, + { + "id": "4sD4NnxsLDc3_sGQDhM5k", + "type": "arrow" + } + ], + "updated": 1709529743523, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 547, + "versionNonce": 175385820, + "isDeleted": false, + "id": "xqc2JD9eCY5yXVm2z1Rl9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1665.5946035241782, + "y": 582.8422737217497, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 128.25990295410156, + "height": 50, + "seed": 1439318108, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529737734, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Worksheet / \nViewModel", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "LN9hzLXZEu1nq-GHD2QOt", + "originalText": "Worksheet / ViewModel", + "lineHeight": 1.25, + "baseline": 43 + }, + { + "type": "rectangle", + "version": 680, + "versionNonce": 1370018276, + "isDeleted": false, + "id": "Y5dNLERl6k8elExZjb3YX", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1866.565342872022, + "y": 550.3083653407415, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 196.295528007121, + "height": 113.36248554036945, + "seed": 181482460, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "h7YAuHDVNfWfLKJP6FVwX" + } + ], + "updated": 1709529605299, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 591, + "versionNonce": 362695132, + "isDeleted": false, + "id": "h7YAuHDVNfWfLKJP6FVwX", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1918.8031489898403, + "y": 594.4896081109262, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 91.81991577148438, + "height": 25, + "seed": 2058770524, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529605299, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "ViewModel", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Y5dNLERl6k8elExZjb3YX", + "originalText": "ViewModel", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 740, + "versionNonce": 1949914460, + "isDeleted": false, + "id": "UqRuy0eTjDjLvl8lETWrA", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2100.2870772674373, + "y": 550.3083653407415, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 196.295528007121, + "height": 113.36248554036945, + "seed": 1611470948, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "zzCU14ylPnJnMo0ZDoMF7" + } + ], + "updated": 1709529604310, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 652, + "versionNonce": 672611044, + "isDeleted": false, + "id": "zzCU14ylPnJnMo0ZDoMF7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2152.5248833852556, + "y": 594.4896081109262, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 91.81991577148438, + "height": 25, + "seed": 1326738404, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529604310, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "ViewModel", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "UqRuy0eTjDjLvl8lETWrA", + "originalText": "ViewModel", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "id": "OabY2dpulZvjMUBYLDb-c", + "type": "arrow", + "x": 967.9945166944887, + "y": 547.5557698021926, + "width": 648.6239450309803, + "height": 74.98118527864256, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1459661028, + "version": 438, + "versionNonce": 687657564, + "isDeleted": false, + "boundElements": null, + "updated": 1709529868651, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 648.6239450309803, + 74.98118527864256 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "Wo0_9IeyNh2nP4LXReZmo", + "gap": 19.210240694789263, + "focus": 0.7869977756164281 + }, + "endBinding": { + "elementId": "LN9hzLXZEu1nq-GHD2QOt", + "gap": 14.958329272199535, + "focus": -0.4082162078529981 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "version": 456, + "versionNonce": 584183140, + "isDeleted": false, + "id": "a1dvkJycQ8OE6bnv7yVWt", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1149.6562013586015, + "y": -441.6559200531219, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 357.49347058833655, + "height": 113.36248554036945, + "seed": 1924214500, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "EVgWjLRArw7lqe1TBhP_z" + }, + { + "id": "GvT8eNMzbir52Ncwk_ieq", + "type": "arrow" + }, + { + "id": "SuJYIRe0j0ZqodTUuZkDy", + "type": "arrow" + }, + { + "id": "IcurtrVGFRXj0JtMBjhKz", + "type": "arrow" + } + ], + "updated": 1709529853517, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 373, + "versionNonce": 1819713764, + "isDeleted": false, + "id": "EVgWjLRArw7lqe1TBhP_z", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1215.053044990172, + "y": -397.4746772829372, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 226.6997833251953, + "height": 25, + "seed": 1374794340, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529853517, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "SheetsFilterUIController", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "a1dvkJycQ8OE6bnv7yVWt", + "originalText": "SheetsFilterUIController", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "id": "GvT8eNMzbir52Ncwk_ieq", + "type": "arrow", + "x": 1329.4428737142298, + "y": -309.9949177482932, + "width": 4.716473982616208, + "height": 249.03863056135498, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1710778468, + "version": 485, + "versionNonce": 1709619172, + "isDeleted": false, + "boundElements": null, + "updated": 1709529853518, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4.716473982616208, + 249.03863056135498 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "a1dvkJycQ8OE6bnv7yVWt", + "gap": 18.298516764459293, + "focus": 0.002275587583412344 + }, + "endBinding": { + "elementId": "eD7q3TO5HwewuHD1ehJ5A", + "gap": 20.947350982279318, + "focus": 0.01844258052068849 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "version": 689, + "versionNonce": 2061519716, + "isDeleted": false, + "id": "PwHD7KKAVA21vpjUo86nG", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2183.873414560562, + "y": 94.46289389612701, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.0607693170814, + "height": 113.36248554036945, + "seed": 192491492, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "rbhwmrMcDcq58sgtGAyLI" + }, + { + "id": "2OyzL49hpoQ1cSagBLOW3", + "type": "arrow" + }, + { + "id": "4sD4NnxsLDc3_sGQDhM5k", + "type": "arrow" + }, + { + "id": "8Hyck_68cfbdi9aXxGGwj", + "type": "arrow" + } + ], + "updated": 1709529858212, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 598, + "versionNonce": 789135076, + "isDeleted": false, + "id": "rbhwmrMcDcq58sgtGAyLI", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2269.913870019884, + "y": 138.64413666631174, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 135.9798583984375, + "height": 25, + "seed": 1947308900, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529858212, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "SheetSkeleton", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "PwHD7KKAVA21vpjUo86nG", + "originalText": "SheetSkeleton", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "id": "2OyzL49hpoQ1cSagBLOW3", + "type": "arrow", + "x": 1559.5761785884113, + "y": 43.195770053308934, + "width": 609.1530838648735, + "height": 45.94795527255235, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1731732828, + "version": 539, + "versionNonce": 2129253860, + "isDeleted": false, + "boundElements": null, + "updated": 1709529858212, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 609.1530838648735, + 45.94795527255235 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "eD7q3TO5HwewuHD1ehJ5A", + "gap": 13.897201751982493, + "focus": 0.13299354697571075 + }, + "endBinding": { + "elementId": "PwHD7KKAVA21vpjUo86nG", + "gap": 16.05113383307406, + "focus": 0.7209365350584219 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "4sD4NnxsLDc3_sGQDhM5k", + "type": "arrow", + "x": 2261.8835599320473, + "y": 216.33042261378108, + "width": 514.9892872668315, + "height": 313.80888195363497, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 907773028, + "version": 403, + "versionNonce": 584101092, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "T4yDQDrr0C2H3j3dN9QBY" + } + ], + "updated": 1709529858212, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -514.9892872668315, + 313.80888195363497 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "PwHD7KKAVA21vpjUo86nG", + "gap": 8.50504317728462, + "focus": -0.12541303746538945 + }, + "endBinding": { + "elementId": "LN9hzLXZEu1nq-GHD2QOt", + "gap": 21.021726384148963, + "focus": -0.5772338750446874 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "T4yDQDrr0C2H3j3dN9QBY", + "type": "text", + "x": 2299.625583944765, + "y": 265.49771787758834, + "width": 134.71986389160156, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1504563036, + "version": 15, + "versionNonce": 1048303844, + "isDeleted": false, + "boundElements": null, + "updated": 1709529729061, + "link": null, + "locked": false, + "text": "getRowRawVisible?", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18, + "containerId": "4sD4NnxsLDc3_sGQDhM5k", + "originalText": "getRowRawVisible?", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 523, + "versionNonce": 1885015260, + "isDeleted": false, + "id": "nIWLYf7WUUkh8GE7kwhA5", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1803.5703518545354, + "y": -259.25569944420255, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 338.69837634786813, + "height": 113.36248554036945, + "seed": 787423964, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "n83LDv51wxkTbyRskZP1R" + }, + { + "id": "SuJYIRe0j0ZqodTUuZkDy", + "type": "arrow" + }, + { + "id": "frIZ04lSYhsp0eiNkKcnd", + "type": "arrow" + }, + { + "id": "8Hyck_68cfbdi9aXxGGwj", + "type": "arrow" + } + ], + "updated": 1709529855934, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 464, + "versionNonce": 597496156, + "isDeleted": false, + "id": "n83LDv51wxkTbyRskZP1R", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1867.3896404313014, + "y": -215.07445667401782, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 211.05979919433594, + "height": 25, + "seed": 1665034076, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529855934, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "FilterRenderController", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "nIWLYf7WUUkh8GE7kwhA5", + "originalText": "FilterRenderController", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "id": "SuJYIRe0j0ZqodTUuZkDy", + "type": "arrow", + "x": 1517.8855370706126, + "y": -315.3155955610826, + "width": 295.03740343972436, + "height": 39.48498198001437, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1762341724, + "version": 481, + "versionNonce": 1444244060, + "isDeleted": false, + "boundElements": null, + "updated": 1709529855934, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 295.03740343972436, + 39.48498198001437 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "a1dvkJycQ8OE6bnv7yVWt", + "gap": 16.842894757410647, + "focus": 0.5530295635592469 + }, + "endBinding": { + "elementId": "nIWLYf7WUUkh8GE7kwhA5", + "gap": 16.574914136865686, + "focus": 0.6533938662679707 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "frIZ04lSYhsp0eiNkKcnd", + "type": "arrow", + "x": 1849.1746779414852, + "y": -137.01056167032658, + "width": 310.3974663357285, + "height": 81.13981580508693, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 775177060, + "version": 370, + "versionNonce": 1191936860, + "isDeleted": false, + "boundElements": null, + "updated": 1709529855934, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -310.3974663357285, + 81.13981580508693 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "nIWLYf7WUUkh8GE7kwhA5", + "gap": 8.88265223350652, + "focus": -0.3295665054401579 + }, + "endBinding": { + "elementId": "eD7q3TO5HwewuHD1ehJ5A", + "gap": 15.861809660580775, + "focus": -0.1633901418219207 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "8Hyck_68cfbdi9aXxGGwj", + "type": "arrow", + "x": 2008.9455103294063, + "y": -129.66545647994565, + "width": 315.93064511498915, + "height": 208.4061018500305, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1076104548, + "version": 256, + "versionNonce": 2116928484, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "pEKCJZjnA8AbYkXCKtgt2" + } + ], + "updated": 1709529858213, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 315.93064511498915, + 208.4061018500305 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "nIWLYf7WUUkh8GE7kwhA5", + "gap": 16.227757423887454, + "focus": 0.2938513747663335 + }, + "endBinding": { + "elementId": "PwHD7KKAVA21vpjUo86nG", + "gap": 15.722248526042165, + "focus": 0.40312260319074367 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "pEKCJZjnA8AbYkXCKtgt2", + "type": "text", + "x": 2093.7695549049927, + "y": -43.84258835904927, + "width": 203.09982299804688, + "height": 75, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1087199452, + "version": 127, + "versionNonce": 1219283172, + "isDeleted": false, + "boundElements": null, + "updated": 1709529857568, + "link": null, + "locked": false, + "text": "Register how to \nrender filter ranges \nand buttons", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 68, + "containerId": "8Hyck_68cfbdi9aXxGGwj", + "originalText": "Register how to render filter ranges and buttons", + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 470, + "versionNonce": 1503245532, + "isDeleted": false, + "id": "SBWl3n8p3gP3n8e4_IbLf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 530.7956377478577, + "y": -357.60046070703413, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 308.02416074769457, + "height": 113.36248554036945, + "seed": 1741493852, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "tNDmZu6EwkcqVbRXhF7n3" + }, + { + "id": "IcurtrVGFRXj0JtMBjhKz", + "type": "arrow" + }, + { + "id": "_wfgP2BWS8519rai8Nc_K", + "type": "arrow" + }, + { + "id": "Bduw1dViUvutS_TpDV07Q", + "type": "arrow" + } + ], + "updated": 1709529847744, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 405, + "versionNonce": 1809515612, + "isDeleted": false, + "id": "tNDmZu6EwkcqVbRXhF7n3", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 632.7777651187753, + "y": -313.4192179368494, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 104.05990600585938, + "height": 25, + "seed": 1125247708, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1709529847744, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "FitlerPanel", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "SBWl3n8p3gP3n8e4_IbLf", + "originalText": "FitlerPanel", + "lineHeight": 1.25, + "baseline": 18 + }, + { + "id": "IcurtrVGFRXj0JtMBjhKz", + "type": "arrow", + "x": 1138.5683649126772, + "y": -399.40204325330967, + "width": 285.83475251818516, + "height": 119.13489801594665, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1195241948, + "version": 216, + "versionNonce": 917087716, + "isDeleted": false, + "boundElements": null, + "updated": 1709529853518, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -285.83475251818516, + 119.13489801594665 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "a1dvkJycQ8OE6bnv7yVWt", + "gap": 11.087836445924381, + "focus": 0.7131737445655809 + }, + "endBinding": { + "elementId": "SBWl3n8p3gP3n8e4_IbLf", + "gap": 13.913813898939793, + "focus": 0.7499031108699328 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "_wfgP2BWS8519rai8Nc_K", + "type": "arrow", + "x": 855.69358015778, + "y": -270.1375969875737, + "width": 375.54295362397295, + "height": 210.6985978027534, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 1737375076, + "version": 182, + "versionNonce": 1344037732, + "isDeleted": false, + "boundElements": null, + "updated": 1709529850349, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 375.54295362397295, + 210.6985978027534 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "SBWl3n8p3gP3n8e4_IbLf", + "gap": 16.87378166222777, + "focus": -0.45780850450901195 + }, + "endBinding": { + "elementId": "eD7q3TO5HwewuHD1ehJ5A", + "gap": 19.430062980161438, + "focus": 0.11183338766534877 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "Bduw1dViUvutS_TpDV07Q", + "type": "arrow", + "x": 683.228875163786, + "y": -225.71373040791127, + "width": 9.090132629715072, + "height": 145.99462397895786, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "seed": 668128228, + "version": 179, + "versionNonce": 236675036, + "isDeleted": false, + "boundElements": null, + "updated": 1709529847744, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -9.090132629715072, + 145.99462397895786 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "SBWl3n8p3gP3n8e4_IbLf", + "gap": 18.52424475875341, + "focus": -0.01990776613516245 + }, + "endBinding": { + "elementId": "k8VRsAR3ODxOqJPd38VTV", + "gap": 23.73871518019007, + "focus": -0.020766241510610742 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/packages/sheets-filter/package.json b/packages/sheets-filter/package.json new file mode 100644 index 000000000000..dbb6755f5c40 --- /dev/null +++ b/packages/sheets-filter/package.json @@ -0,0 +1,78 @@ +{ + "name": "@univerjs/sheets-filter", + "version": "0.1.1", + "private": true, + "description": "", + "author": "DreamNum ", + "license": "Apache-2.0", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/univer" + }, + "homepage": "https://univer.ai", + "repository": { + "type": "git", + "url": "https://github.com/dream-num/univer" + }, + "bugs": { + "url": "https://github.com/dream-num/univer/issues" + }, + "keywords": [], + "exports": { + ".": "./src/index.ts", + "./*": "./src/*" + }, + "main": "./lib/cjs/index.js", + "module": "./lib/es/index.js", + "types": "./lib/types/index.d.ts", + "publishConfig": { + "access": "public", + "main": "./lib/cjs/index.js", + "module": "./lib/es/index.js", + "exports": { + ".": { + "import": "./lib/es/index.js", + "require": "./lib/cjs/index.js", + "types": "./lib/types/index.d.ts" + }, + "./*": { + "import": "./lib/es/*", + "require": "./lib/cjs/*", + "types": "./lib/types/index.d.ts" + }, + "./lib/*": "./lib/*" + } + }, + "directories": { + "lib": "lib" + }, + "files": [ + "lib" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "coverage": "vitest run --coverage", + "lint:types": "tsc --noEmit", + "build": "tsc && vite build" + }, + "peerDependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/shared": "workspace:*", + "@univerjs/sheets": "workspace:*", + "@wendellhu/redi": ">=0.12.13", + "rxjs": ">=7.0.0" + }, + "dependencies": { + }, + "devDependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/shared": "workspace:*", + "@univerjs/sheets": "workspace:*", + "@wendellhu/redi": "^0.13.1", + "rxjs": "^7.8.1", + "typescript": "^5.3.3", + "vite": "^5.1.4", + "vitest": "^1.3.1" + } +} diff --git a/packages/sheets-filter/src/commands/__tests__/sheets-filter.mutation.spec.ts b/packages/sheets-filter/src/commands/__tests__/sheets-filter.mutation.spec.ts new file mode 100644 index 000000000000..c451067fb370 --- /dev/null +++ b/packages/sheets-filter/src/commands/__tests__/sheets-filter.mutation.spec.ts @@ -0,0 +1,273 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IWorkbookData } from '@univerjs/core'; +import { ICommandService, LocaleType, Plugin, Univer, UniverInstanceType } from '@univerjs/core'; +import { Inject, Injector } from '@wendellhu/redi'; +import { afterEach, describe, expect, it } from 'vitest'; +import { SHEET_FILTER_SNAPSHOT_ID, SheetsFilterService } from '../../services/sheet-filter.service'; +import type { ISetSheetsFilterCriteriaMutationParams, ISetSheetsFilterRangeMutationParams } from '../sheets-filter.mutation'; +import { ReCalcSheetsFilterMutation, RemoveSheetsFilterMutation, SetSheetsFilterCriteriaMutation, SetSheetsFilterRangeMutation } from '../sheets-filter.mutation'; + +function testWorkbookDataFactory(): IWorkbookData { + return { + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + cellData: { + 0: { + 0: { + v: 'A1', + }, + 1: { + v: 'B1', + }, + }, + }, + name: 'Sheet-001', + }, + }, + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [], + styles: {}, + }; +}; + +function testWorkbookDataWithFilterFactory(): IWorkbookData { + return { + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + cellData: { + 0: { + 0: { + v: 'header', + }, + 1: { + v: 'A', + }, + 2: { + v: 'B', + }, + 3: { + v: 'C', + }, + }, + }, + name: 'Sheet-001', + }, + }, + resources: [ + { + name: SHEET_FILTER_SNAPSHOT_ID, + data: JSON.stringify({ + sheet1: { + ref: { startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }, + }, + }), + }, + ], + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [], + styles: {}, + }; +} + +function createFilterMutationTestBed(workbookData?: IWorkbookData) { + const univer = new Univer(); + const injector = univer.__getInjector(); + const get = injector.get.bind(injector); + + class SheetsFilterTestPlugin extends Plugin { + static override type = UniverInstanceType.SHEET; + static override pluginName = 'SheetsFilterTestPlugin'; + + constructor(_config: unknown, @Inject(Injector) protected readonly _injector: Injector) { + super(); + } + + override onStarting(injector: Injector): void { + injector.add([SheetsFilterService]); + } + } + + univer.registerPlugin(SheetsFilterTestPlugin); + + const sheet = univer.createUniverSheet(workbookData || testWorkbookDataFactory()); + + const sheetsFilterService = get(SheetsFilterService); + const commandService = get(ICommandService); + + // It should be registered later in avoid of time sequence problem. + // injector.add([ISnapshotPersistenceService, { useClass: LocalSnapshotService }]); + // injector.get(ISnapshotPersistenceService); + + ([ + SetSheetsFilterRangeMutation, + SetSheetsFilterCriteriaMutation, + RemoveSheetsFilterMutation, + ReCalcSheetsFilterMutation] + ).forEach((command) => commandService.registerCommand(command)); + + return { + univer, + get, + sheet, + sheetsFilterService, + commandService, + }; +} + +describe('test mutations of sheets filter', () => { + let univer: Univer; + + afterEach(() => { + univer.dispose(); + }); + + describe('test set sheets filter range mutations', () => { + it('should set filter range work when there is no filter range', () => { + const testBed = createFilterMutationTestBed(testWorkbookDataFactory()); + const { commandService, sheetsFilterService } = testBed; + univer = testBed.univer; + + expect(sheetsFilterService.getFilterModel('test', 'sheet1')).toBeNull(); + + expect(commandService.syncExecuteCommand(SetSheetsFilterRangeMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + range: { startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }, + } as ISetSheetsFilterRangeMutationParams)).toBeTruthy(); + expect(sheetsFilterService.getFilterModel('test', 'sheet1')!.getRange()) + .toEqual({ startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }); + }); + + it('should load filter info to the service and support changing its range', () => { + const testBed = createFilterMutationTestBed(testWorkbookDataWithFilterFactory()); + const { commandService, sheetsFilterService } = testBed; + univer = testBed.univer; + + const filterModel = sheetsFilterService.getFilterModel('test', 'sheet1'); + expect(filterModel).toBeTruthy(); + expect(filterModel!.getRange()).toEqual({ startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }); + + expect(commandService.syncExecuteCommand(SetSheetsFilterRangeMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + range: { startRow: 5, startColumn: 5, endRow: 8, endColumn: 8 }, + } as ISetSheetsFilterRangeMutationParams)).toBeTruthy(); + expect(sheetsFilterService.getFilterModel('test', 'sheet1')!.getRange()) + .toEqual({ startRow: 5, startColumn: 5, endRow: 8, endColumn: 8 }); + }); + }); + + describe('test set sheets filter criteria mutations', () => { + it('should re calc when set filter criteria', () => { + const testBed = createFilterMutationTestBed(testWorkbookDataWithFilterFactory()); + const { commandService, sheetsFilterService } = testBed; + univer = testBed.univer; + + const filterModel = sheetsFilterService.getFilterModel('test', 'sheet1'); + expect(filterModel).toBeTruthy(); + expect(filterModel!.getRange()).toEqual({ startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }); + expect(filterModel!.filteredOutRows.size).toBe(0); + + expect(commandService.syncExecuteCommand(SetSheetsFilterCriteriaMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + col: 0, + criteria: { + colId: 0, + filters: ['A'], + }, + } as ISetSheetsFilterCriteriaMutationParams)).toBeTruthy(); + expect(filterModel!.filteredOutRows.size).toBe(5); + }); + + it('should return false when set filter criteria on a non-existing filter model', () => { + const testBed = createFilterMutationTestBed(testWorkbookDataWithFilterFactory()); + const { commandService, sheetsFilterService } = testBed; + univer = testBed.univer; + + const filterModel = sheetsFilterService.getFilterModel('test', 'sheet1'); + expect(filterModel).toBeTruthy(); + expect(filterModel!.getRange()).toEqual({ startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }); + expect(filterModel!.filteredOutRows.size).toBe(0); + + expect(commandService.syncExecuteCommand(SetSheetsFilterCriteriaMutation.id, { + unitId: 'test', + subUnitId: 'sheet2', + colId: 0, + criteria: { + colId: 0, + filters: ['A'], + }, + })).toBeFalsy(); + }); + + it('should throw error when set filter criteria outside of filter range', () => { + const testBed = createFilterMutationTestBed(testWorkbookDataWithFilterFactory()); + const { commandService, sheetsFilterService } = testBed; + univer = testBed.univer; + + const filterModel = sheetsFilterService.getFilterModel('test', 'sheet1'); + expect(filterModel).toBeTruthy(); + expect(filterModel!.getRange()).toEqual({ startRow: 0, startColumn: 0, endRow: 5, endColumn: 5 }); + expect(filterModel!.filteredOutRows.size).toBe(0); + + expect(() => commandService.syncExecuteCommand(SetSheetsFilterCriteriaMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + colId: 10, + criteria: { + colId: 10, + filters: ['A'], + }, + })).toThrowError(); + }); + }); + + describe('test remove sheets filter mutations', () => { + it('should return true when there is a filter', () => { + const testBed = createFilterMutationTestBed(testWorkbookDataWithFilterFactory()); + const { commandService, sheetsFilterService } = testBed; + univer = testBed.univer; + + expect(commandService.syncExecuteCommand(RemoveSheetsFilterMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + })).toBeTruthy(); + expect(sheetsFilterService.getFilterModel('test', 'sheet1')).toBeNull(); + }); + + it('should return false when there is no filter', () => { + const testBed = createFilterMutationTestBed(testWorkbookDataFactory()); + const { commandService } = testBed; + univer = testBed.univer; + + expect(commandService.syncExecuteCommand(RemoveSheetsFilterMutation.id, { + unitId: 'test', + subUnitId: 'sheet1', + })).toBeFalsy(); + }); + }); +}); diff --git a/packages/sheets-filter/src/commands/sheets-filter.mutation.ts b/packages/sheets-filter/src/commands/sheets-filter.mutation.ts new file mode 100644 index 000000000000..f247c51e68d8 --- /dev/null +++ b/packages/sheets-filter/src/commands/sheets-filter.mutation.ts @@ -0,0 +1,115 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This file provides a ton of mutations to manipulate `FilterModel`. +// These models would be held on `SheetsFilterService`. + +import { CommandType } from '@univerjs/core'; +import type { IMutation, IRange, Nullable } from '@univerjs/core'; +import type { ISheetCommandSharedParams } from '@univerjs/sheets'; + +import { SheetsFilterService } from '../services/sheet-filter.service'; +import type { IFilterColumn } from '../models/types'; + +export interface ISetSheetsFilterRangeMutationParams extends ISheetCommandSharedParams { + range: IRange; +} + +/** + * Set filter range in a Worksheet. If the `FilterModel` does not exist, it will be created. + * + * Since there could only be a filter on a worksheet, when you want to update the range, you + * don't necessarily need to remove the filter first, you can just execute this mutation. + */ +export const SetSheetsFilterRangeMutation: IMutation = { + id: 'sheet.mutation.set-filter-range', + type: CommandType.MUTATION, + handler: (accessor, params) => { + const { subUnitId, unitId, range } = params; + const sheetsFilterService = accessor.get(SheetsFilterService); + + // check if the range is in bound? + const filterModel = sheetsFilterService.ensureFilterModel(unitId, subUnitId); + filterModel.setRange(range); + + return true; + }, +}; + +export interface ISetSheetsFilterCriteriaMutationParams extends ISheetCommandSharedParams { + col: number; + + /** + * Filter criteria to set. If it is `null`, the criteria will be removed. + */ + criteria: Nullable; + + /** + * If it should trigger calculation on this `FilterColumn`. + * + * @default true + */ + reCalc?: boolean; +} +/** + * Set filter criteria of a Worksheet. + */ +export const SetSheetsFilterCriteriaMutation: IMutation = { + id: 'sheet.mutation.set-filter-criteria', + type: CommandType.MUTATION, + handler: (accessor, params) => { + const { subUnitId, unitId, criteria, col, reCalc = true } = params; + const sheetsFilterService = accessor.get(SheetsFilterService); + + const filterModel = sheetsFilterService.getFilterModel(unitId, subUnitId); + if (!filterModel) { + return false; + } + + // TODO@wzhudev: check criteria out of bound. + + filterModel.setCriteria(col, criteria, reCalc); + return true; + }, +}; + +export interface IRemoveSheetsFilterMutationParams extends ISheetCommandSharedParams { } +export const RemoveSheetsFilterMutation: IMutation = { + id: 'sheet.mutation.remove-filter', + type: CommandType.MUTATION, + handler: (accessor, params) => { + const { unitId, subUnitId } = params; + const sheetsFilterService = accessor.get(SheetsFilterService); + return sheetsFilterService.removeFilterModel(unitId, subUnitId); + }, +}; + +export interface IReCalcSheetsFilterMutationParams extends ISheetCommandSharedParams { } +export const ReCalcSheetsFilterMutation: IMutation = { + id: 'sheet.mutation.re-calc-filter', + type: CommandType.MUTATION, + handler: (accessor, params) => { + const { unitId, subUnitId } = params; + const sheetsFilterService = accessor.get(SheetsFilterService); + const filterModel = sheetsFilterService.getFilterModel(unitId, subUnitId); + if (!filterModel) { + return false; + } + + filterModel.reCalc(); + return true; + }, +}; diff --git a/packages/sheets-filter/src/controllers/sheets-fiter.controller.ts b/packages/sheets-filter/src/controllers/sheets-fiter.controller.ts new file mode 100644 index 000000000000..d5db0490bc8e --- /dev/null +++ b/packages/sheets-filter/src/controllers/sheets-fiter.controller.ts @@ -0,0 +1,762 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ICommandInfo, IMutationInfo, IObjectArrayPrimitiveType, Nullable } from '@univerjs/core'; +import { Disposable, DisposableCollection, ICommandService, IUniverInstanceService, LifecycleStages, moveMatrixArray, OnLifecycle, Rectangle } from '@univerjs/core'; +import type { EffectRefRangeParams, IAddWorksheetMergeMutationParams, IInsertColCommandParams, IInsertRowCommandParams, IInsertRowMutationParams, IMoveColsCommandParams, IMoveRangeCommandParams, IMoveRowsCommandParams, IRemoveColMutationParams, IRemoveRowsMutationParams, IRemoveSheetCommandParams, ISetWorksheetActivateCommandParams, ISheetCommandSharedParams } from '@univerjs/sheets'; +import { EffectRefRangId, InsertColCommand, InsertRowCommand, InsertRowMutation, INTERCEPTOR_POINT, MoveRangeCommand, RefRangeService, RemoveColCommand, RemoveRowCommand, RemoveRowMutation, RemoveSheetCommand, SetWorksheetActivateCommand, SheetInterceptorService } from '@univerjs/sheets'; +import { Inject } from '@wendellhu/redi'; + +import { SheetsFilterService } from '../services/sheet-filter.service'; +import type { IRemoveSheetsFilterMutationParams, ISetSheetsFilterCriteriaMutationParams, ISetSheetsFilterRangeMutationParams } from '../commands/sheets-filter.mutation'; +import { ReCalcSheetsFilterMutation, RemoveSheetsFilterMutation, SetSheetsFilterCriteriaMutation, SetSheetsFilterRangeMutation } from '../commands/sheets-filter.mutation'; +import type { FilterColumn } from '../models/filter-model'; +import { mergeSetFilterCriteria } from '../utils'; + + +@OnLifecycle(LifecycleStages.Ready, SheetsFilterController) +export class SheetsFilterController extends Disposable { + constructor( + @ICommandService private readonly _commandService: ICommandService, + @Inject(SheetInterceptorService) private readonly _sheetInterceptorService: SheetInterceptorService, + @Inject(SheetsFilterService) private readonly _sheetsFilterService: SheetsFilterService, + @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, + @Inject(RefRangeService) private readonly _refRangeService: RefRangeService + ) { + super(); + + this._initCommands(); + this._initRowFilteredInterceptor(); + this._initInterceptors(); + this._commandExecutedListener(); + } + + private _initCommands(): void { + [ + SetSheetsFilterCriteriaMutation, + SetSheetsFilterRangeMutation, + ReCalcSheetsFilterMutation, + RemoveSheetsFilterMutation, + ].forEach((command) => this.disposeWithMe(this._commandService.registerCommand(command))); + } + + private _initInterceptors(): void { + this.disposeWithMe(this._sheetInterceptorService.interceptCommand({ + getMutations: (command) => this._getUpdateFilter(command), + })); + + const disposableCollection = new DisposableCollection(); + const registerRefRange = (unitId: string, subUnitId: string) => { + const workbook = this._univerInstanceService.getUniverSheetInstance(unitId); + if (!workbook) return; + + const workSheet = workbook?.getSheetBySheetId(subUnitId); + if (!workSheet) return; + + disposableCollection.dispose(); + const range = this._sheetsFilterService.getFilterModel(unitId, subUnitId)?.getRange(); + const handler = (config: EffectRefRangeParams) => { + switch (config.id) { + case InsertRowCommand.id: { + const params = config.params as IInsertRowCommandParams; + const _unitId = params.unitId || unitId; + const _subUnitId = params.subUnitId || subUnitId; + return this._handleInsertRowCommand(params, _unitId, _subUnitId); + } + case InsertColCommand.id: { + const params = config.params as IInsertColCommandParams; + const _unitId = params.unitId || unitId; + const _subUnitId = params.subUnitId || subUnitId; + return this._handleInsertColCommand(params, _unitId, _subUnitId); + } + case RemoveColCommand.id: { + const params = config.params as IRemoveColMutationParams; + return this._handleRemoveColCommand(params, unitId, subUnitId); + } + case RemoveRowCommand.id: { + const params = config.params as IRemoveRowsMutationParams; + return this._handleRemoveRowCommand(params, unitId, subUnitId); + } + case EffectRefRangId.MoveColsCommandId: { + const params = config.params as IMoveColsCommandParams; + return this._handleMoveColsCommand(params, unitId, subUnitId); + } + case EffectRefRangId.MoveRowsCommandId: { + const params = config.params as IMoveRowsCommandParams; + return this._handleMoveRowsCommand(params, unitId, subUnitId); + } + case MoveRangeCommand.id: { + const params = config.params as IMoveRangeCommandParams; + return this._handleMoveRangeCommand(params, unitId, subUnitId); + } + } + return { redos: [], undos: [] }; + }; + + if (range) { + disposableCollection.add(this._refRangeService.registerRefRange(range, handler, unitId, subUnitId)); + } + }; + this.disposeWithMe(this._commandService.onCommandExecuted((commandInfo) => { + if (commandInfo.id === SetWorksheetActivateCommand.id) { + const params = commandInfo.params as ISetWorksheetActivateCommandParams; + const sheetId = params.subUnitId; + const unitId = params.unitId; + if (!sheetId || !unitId) { + return; + } + registerRefRange(unitId, sheetId); + } + if (commandInfo.id === SetSheetsFilterRangeMutation.id) { + const params = commandInfo.params as IAddWorksheetMergeMutationParams; + const sheetId = params.subUnitId; + const unitId = params.unitId; + if (!sheetId || !unitId) { + return; + } + registerRefRange(params.unitId, params.subUnitId); + } + })); + + this.disposeWithMe(this._sheetsFilterService.loadedUnitId$.subscribe((unitId) => { + if (unitId) { + const workbook = this._univerInstanceService.getUniverSheetInstance(unitId); + const sheet = workbook?.getActiveSheet(); + if (sheet) { + registerRefRange(unitId, sheet.getSheetId()); + } + } + })); + } + + private _getUpdateFilter(command: ICommandInfo) { + const { id } = command; + switch (id) { + case RemoveSheetCommand.id: { + const params = command.params as ISheetCommandSharedParams; + return this._handleRemoveSheetCommand(params, params.unitId, params.subUnitId); + } + } + return { + redos: [], + undos: [], + }; + } + + private _handleInsertColCommand(config: IInsertColCommandParams, unitId: string, subUnitId: string) { + const filterModel = this._sheetsFilterService.getFilterModel(unitId, subUnitId); + const filterRange = filterModel?.getRange() ?? null; + if (!filterModel || !filterRange) { + return this._handleNull(); + } + const { startColumn, endColumn } = filterRange; + const { startColumn: insertStartColumn, endColumn: insertEndColumn } = config.range; + const count = insertEndColumn - insertStartColumn + 1; + + if (insertEndColumn > endColumn) { + return this._handleNull(); + } + + const redos: IMutationInfo[] = []; + const undos: IMutationInfo[] = []; + + const anchor = insertStartColumn; + const setFilterRangeMutationParams: ISetSheetsFilterRangeMutationParams = { + unitId, + subUnitId, + range: { + ...filterRange, + startColumn: insertStartColumn <= startColumn ? startColumn + count : startColumn, + endColumn: endColumn + count, + }, + }; + + const undoSetFilterRangeMutationParams: ISetSheetsFilterRangeMutationParams = { + unitId, + subUnitId, + range: filterRange, + }; + + redos.push({ id: SetSheetsFilterRangeMutation.id, params: setFilterRangeMutationParams }); + undos.push({ id: SetSheetsFilterRangeMutation.id, params: undoSetFilterRangeMutationParams }); + + const filterColumn = filterModel.getAllFilterColumns(); + const effected = filterColumn.filter((column) => column[0] >= anchor); + if (effected.length !== 0) { + const { undos: moveUndos, redos: moveRedos } = this.moveCriteria(unitId, subUnitId, effected, count); + redos.push(...moveRedos); + undos.push(...moveUndos); + } + + return { redos: mergeSetFilterCriteria(redos), undos: mergeSetFilterCriteria(undos) }; + } + + private _handleInsertRowCommand(config: IInsertRowCommandParams, unitId: string, subUnitId: string) { + const filterModel = this._sheetsFilterService.getFilterModel(unitId, subUnitId); + const filterRange = filterModel?.getRange() ?? null; + if (!filterModel || !filterRange) { + return this._handleNull(); + } + const { startRow, endRow } = filterRange; + const { startRow: insertStartRow, endRow: insertEndRow } = config.range; + const rowCount = insertEndRow - insertStartRow + 1; + if (insertEndRow > endRow) { + return this._handleNull(); + } + const redos: IMutationInfo[] = []; + const undos: IMutationInfo[] = []; + const setFilterRangeParams: ISetSheetsFilterRangeMutationParams = { + unitId, + subUnitId, + range: { + ...filterRange, + startRow: insertStartRow <= startRow ? startRow + rowCount : startRow, + endRow: endRow + rowCount, + }, + }; + const undoSetFilterRangeMutationParams: ISetSheetsFilterRangeMutationParams = { + unitId, + subUnitId, + range: filterRange, + }; + + redos.push({ id: SetSheetsFilterRangeMutation.id, params: setFilterRangeParams }); + undos.push({ id: SetSheetsFilterRangeMutation.id, params: undoSetFilterRangeMutationParams }); + return { + redos: mergeSetFilterCriteria(redos), undos: mergeSetFilterCriteria(undos), + }; + } + + private _handleRemoveColCommand(config: IRemoveColMutationParams, unitId: string, subUnitId: string) { + const filterModel = this._sheetsFilterService.getFilterModel(unitId, subUnitId); + const filterRange = filterModel?.getRange() ?? null; + if (!filterModel || !filterRange) { + return this._handleNull(); + } + const { startColumn, endColumn } = filterRange; + const { startColumn: removeStartColumn, endColumn: removeEndColumn } = config.range; + + if (removeStartColumn > endColumn) { + return this._handleNull(); + } + + const redos: IMutationInfo[] = []; + const undos: IMutationInfo[] = []; + + const rangeRemoveCount = + removeEndColumn < startColumn + ? 0 : + Math.min(removeEndColumn, endColumn) - Math.max(removeStartColumn, startColumn) + 1; + + const removeCount = removeEndColumn - removeStartColumn + 1; + + const filterColumn = filterModel.getAllFilterColumns(); + filterColumn.forEach((column) => { + const [col, filter] = column; + if (col <= removeEndColumn && col >= removeStartColumn) { + redos.push({ id: SetSheetsFilterCriteriaMutation.id, params: { unitId, subUnitId, col, criteria: null } }); + undos.push({ id: SetSheetsFilterCriteriaMutation.id, params: { unitId, subUnitId, col, criteria: { ...filter.serialize(), colId: col } } }); + } + }); + + const shifted = filterColumn.filter((column) => { + const [col, _] = column; + return col > removeEndColumn; + }); + if (shifted.length > 0) { + const { undos: moveUndos, redos: moveRedos } = this.moveCriteria(unitId, subUnitId, shifted, -removeCount); + redos.push(...moveRedos); + undos.push(...moveUndos); + } + + if (rangeRemoveCount === endColumn - startColumn + 1) { + const removeFilterRangeMutationParams: IRemoveSheetsFilterMutationParams = { + unitId, + subUnitId, + }; + redos.push({ id: RemoveSheetsFilterMutation.id, params: removeFilterRangeMutationParams }); + } else { + if (startColumn <= removeStartColumn) { + const finalEndColumn = endColumn - rangeRemoveCount; + const setFilterRangeMutationParams: ISetSheetsFilterRangeMutationParams = { + unitId, + subUnitId, + range: { + ...filterRange, + endColumn: finalEndColumn, + }, + }; + redos.push({ id: SetSheetsFilterRangeMutation.id, params: setFilterRangeMutationParams }); + } else { + const setFilterRangeMutationParams: ISetSheetsFilterRangeMutationParams = { + unitId, + subUnitId, + range: { + ...filterRange, + startColumn: removeStartColumn, + endColumn: endColumn - (removeEndColumn - removeStartColumn + 1), + }, + }; + redos.push({ id: SetSheetsFilterRangeMutation.id, params: setFilterRangeMutationParams }); + } + } + + undos.push({ id: SetSheetsFilterRangeMutation.id, params: { range: filterRange, unitId, subUnitId } }); + return { + undos: mergeSetFilterCriteria(undos), + redos: mergeSetFilterCriteria(redos), + }; + } + + private _handleRemoveRowCommand(config: IRemoveRowsMutationParams, unitId: string, subUnitId: string) { + const filterModel = this._sheetsFilterService.getFilterModel(unitId, subUnitId); + if (!filterModel) { + return this._handleNull(); + } + + const filterRange = filterModel.getRange(); + const { startRow, endRow } = filterRange; + const { startRow: removeStartRow, endRow: removeEndRow } = config.range; + if (removeStartRow > endRow) { + return this._handleNull(); + } + const redos: IMutationInfo[] = []; + const undos: IMutationInfo[] = []; + const filterColumn = filterModel.getAllFilterColumns(); + + const filterHeaderIsRemoved = startRow <= removeEndRow && startRow >= removeStartRow; + + undos.push({ id: SetSheetsFilterRangeMutation.id, params: { range: filterRange, unitId, subUnitId } }); + + const count = Math.min(removeEndRow, endRow) - Math.max(removeStartRow, startRow) + 1; + if (count === endRow - startRow + 1 || filterHeaderIsRemoved) { + const removeFilterRangeMutationParams: IRemoveSheetsFilterMutationParams = { + unitId, + subUnitId, + }; + redos.push({ id: RemoveSheetsFilterMutation.id, params: removeFilterRangeMutationParams }); + + filterColumn.forEach((column) => { + const [offset, filter] = column; + const setCriteriaMutationParams: ISetSheetsFilterCriteriaMutationParams = { + unitId, + subUnitId, + col: offset, + criteria: { ...filter.serialize(), colId: offset }, + }; + undos.push({ id: SetSheetsFilterCriteriaMutation.id, params: setCriteriaMutationParams }); + }); + } else { + const worksheet = this._univerInstanceService.getUniverSheetInstance(unitId)?.getSheetBySheetId(subUnitId); + if (!worksheet) { + return this._handleNull(); + } + const hiddenRows = []; + for (let r = removeStartRow; r <= removeEndRow; r++) { + if (worksheet.getRowFiltered(r)) { + hiddenRows.push(r); + } + } + const afterStartRow = Math.min(startRow, removeStartRow); + const afterEndRow = afterStartRow + (endRow - startRow) - count + hiddenRows.length; + const setFilterRangeMutationParams: ISetSheetsFilterRangeMutationParams = { + unitId, + subUnitId, + range: { + ...filterRange, + startRow: afterStartRow, + endRow: afterEndRow, + }, + }; + redos.push({ id: SetSheetsFilterRangeMutation.id, params: setFilterRangeMutationParams }); + } + + return { + undos: mergeSetFilterCriteria(undos), + redos: mergeSetFilterCriteria(redos), + }; + } + + private _handleMoveColsCommand(config: IMoveColsCommandParams, unitId: string, subUnitId: string) { + const filterModel = this._sheetsFilterService.getFilterModel(unitId, subUnitId); + const filterRange = filterModel?.getRange() ?? null; + if (!filterModel || !filterRange) { + return this._handleNull(); + } + const { startColumn, endColumn } = filterRange; + const { fromRange, toRange } = config; + if ((fromRange.endColumn < startColumn && toRange.startColumn <= startColumn) || ( + fromRange.startColumn > endColumn && toRange.endColumn > endColumn + )) { + return this._handleNull(); + } + const redos: IMutationInfo[] = []; + const undos: IMutationInfo[] = []; + const filterCol: IObjectArrayPrimitiveType<{ colIndex: number; filter: Nullable }> = {}; + for (let col = startColumn; col <= endColumn; col++) { + filterCol[col] = { + colIndex: col, + filter: filterModel.getFilterColumn(col), + }; + } + moveMatrixArray(fromRange.startColumn, fromRange.endColumn - fromRange.startColumn + 1, toRange.startColumn, filterCol); + + const numberCols = Object.keys(filterCol).map((col) => Number(col)) as number[]; + + const newEnd = Math.max(...numberCols); + const newStart = Math.min(...numberCols); + + numberCols.forEach((col) => { + const { colIndex: oldColIndex, filter } = filterCol[col]; + const newColIndex = col; + if (filter) { + const setCriteriaMutationParams: ISetSheetsFilterCriteriaMutationParams = { + unitId, + subUnitId, + col: newColIndex, + criteria: { ...filter.serialize(), colId: newColIndex }, + }; + redos.push({ id: SetSheetsFilterCriteriaMutation.id, params: setCriteriaMutationParams }); + undos.push({ id: RemoveSheetsFilterMutation.id, params: { unitId, subUnitId, col: newColIndex, criteria: { ...filterModel.getFilterColumn(newColIndex)?.serialize(), colId: newColIndex } } }); + + if (!filterCol[oldColIndex]?.filter) { + const setCriteriaMutationParams: ISetSheetsFilterCriteriaMutationParams = { + unitId, + subUnitId, + col: oldColIndex, + criteria: null, + }; + redos.push({ id: SetSheetsFilterCriteriaMutation.id, params: setCriteriaMutationParams }); + undos.push({ id: SetSheetsFilterCriteriaMutation.id, params: { unitId, subUnitId, col: oldColIndex, criteria: { ...filter.serialize(), colId: oldColIndex } } }); + } + } + }); + + if (startColumn !== newStart || endColumn !== newEnd) { + const setFilterRangeMutationParams: ISetSheetsFilterRangeMutationParams = { + unitId, + subUnitId, + range: { + ...filterRange, + startColumn: newStart, + endColumn: newEnd, + }, + }; + redos.unshift({ id: SetSheetsFilterRangeMutation.id, params: setFilterRangeMutationParams }); + undos.push({ id: SetSheetsFilterRangeMutation.id, params: { range: filterRange, unitId, subUnitId } }); + } + + return { + undos, + redos, + }; + } + + private _handleMoveRowsCommand(config: IMoveRowsCommandParams, unitId: string, subUnitId: string) { + const filterModel = this._sheetsFilterService.getFilterModel(unitId, subUnitId); + const filterRange = filterModel?.getRange() ?? null; + if (!filterModel || !filterRange) { + return this._handleNull(); + } + const { startRow, endRow } = filterRange; + const { fromRange, toRange } = config; + if ((fromRange.endRow < startRow && toRange.startRow <= startRow) || ( + fromRange.startRow > endRow && toRange.endRow > endRow + )) { + return this._handleNull(); + } + const redos: IMutationInfo[] = []; + const undos: IMutationInfo[] = []; + const filterRow: IObjectArrayPrimitiveType<{ offset: number }> = {}; + for (let row = startRow; row <= endRow; row++) { + filterRow[row] = { + offset: row - startRow, + }; + } + + moveMatrixArray(fromRange.startRow, fromRange.endRow - fromRange.startRow + 1, toRange.startRow, filterRow); + const numberRows = Object.keys(filterRow).map((row) => Number(row)); + + const newEnd = Math.max(...numberRows); + const newStart = Math.min(...numberRows); + if (startRow !== newStart || endRow !== newEnd) { + const setFilterRangeMutationParams: ISetSheetsFilterRangeMutationParams = { + unitId, + subUnitId, + range: { + ...filterRange, + startRow: newStart, + endRow: newEnd, + }, + }; + redos.unshift({ id: SetSheetsFilterRangeMutation.id, params: setFilterRangeMutationParams }); + undos.push({ id: SetSheetsFilterRangeMutation.id, params: { range: filterRange, unitId, subUnitId } }); + } + return { + redos, + undos, + }; + } + + private _handleMoveRangeCommand(config: IMoveRangeCommandParams, unitId: string, subUnitId: string) { + const { fromRange, toRange } = config; + const filterModel = this._sheetsFilterService.getFilterModel(unitId, subUnitId); + if (!filterModel) { + return this._handleNull(); + } + const filterRange = filterModel.getRange(); + if (!filterRange) { + return this._handleNull(); + } + + const redos: IMutationInfo[] = []; + const undos: IMutationInfo[] = []; + + if (Rectangle.contains(fromRange, filterRange)) { + const rowOffset = filterRange.startRow - fromRange.startRow; + const colOffset = filterRange.startColumn - fromRange.startColumn; + const newFilterRange = { + startRow: toRange.startRow + rowOffset, + startColumn: toRange.startColumn + colOffset, + endRow: toRange.startRow + rowOffset + (filterRange.endRow - filterRange.startRow), + endColumn: toRange.startColumn + colOffset + (filterRange.endColumn - filterRange.startColumn), + }; + const removeFilter = { + id: RemoveSheetsFilterMutation.id, + params: { + unitId, + subUnitId, + }, + }; + const setNewFilterRange = { id: SetSheetsFilterRangeMutation.id, params: { unitId, subUnitId, range: newFilterRange } as ISetSheetsFilterRangeMutationParams }; + const setOldFilterRange = { id: SetSheetsFilterRangeMutation.id, params: { unitId, subUnitId, range: filterRange } as ISetSheetsFilterRangeMutationParams }; + + + redos.push(removeFilter, setNewFilterRange); + + undos.push(removeFilter, setOldFilterRange); + + const filterColumn = filterModel.getAllFilterColumns(); + const moveColDelta = toRange.startColumn - fromRange.startColumn; + filterColumn.forEach((column) => { + const [col, criteria] = column; + if (criteria) { + redos.push({ id: SetSheetsFilterCriteriaMutation.id, params: { unitId, subUnitId, col: col + moveColDelta, criteria: { ...criteria.serialize(), colId: col + moveColDelta } } }); + undos.push({ id: SetSheetsFilterCriteriaMutation.id, params: { unitId, subUnitId, col, criteria: { ...criteria.serialize(), colId: col } } }); + } + }); + + // redos.push({ id: ReCalcSheetsFilterMutation.id, params: { unitId, subUnitId } }); + // undos.push({ id: ReCalcSheetsFilterMutation.id, params: { unitId, subUnitId } }); + } + return { + redos, + undos, + }; + } + + private _handleRemoveSheetCommand(config: IRemoveSheetCommandParams, unitId: string, subUnitId: string) { + const filterModel = this._sheetsFilterService.getFilterModel(unitId, subUnitId); + if (!filterModel) { + return this._handleNull(); + } + const filterRange = filterModel.getRange(); + if (!filterRange) { + return this._handleNull(); + } + const redos: IMutationInfo[] = []; + const undos: IMutationInfo[] = []; + const filterCols = filterModel.getAllFilterColumns(); + filterCols.forEach((col) => { + const [_, filter] = col; + undos.push({ id: SetSheetsFilterCriteriaMutation.id, params: { unitId, subUnitId, col, criteria: { ...filter.serialize(), colId: col } } }); + }); + redos.push({ id: RemoveSheetsFilterMutation.id, params: { unitId, subUnitId, range: filterRange } }); + undos.unshift({ id: SetSheetsFilterRangeMutation.id, params: { range: filterRange, unitId, subUnitId } }); + return { + undos, + redos, + }; + } + + private _handleNull() { + return { redos: [], undos: [] }; + } + + private _initRowFilteredInterceptor(): void { + this.disposeWithMe(this._sheetInterceptorService.intercept(INTERCEPTOR_POINT.ROW_FILTERED, { + handler: (filtered, rowLocation) => { + if (filtered) return true; + return this._sheetsFilterService.getFilterModel( + rowLocation.unitId, rowLocation.subUnitId)?.isRowFiltered(rowLocation.row) ?? false; + }, + })); + } + + private moveCriteria(unitId: string, subUnitId: string, target: [number, FilterColumn][], step: number) { + const defaultSetCriteriaMutationParams: ISetSheetsFilterCriteriaMutationParams = { + unitId, + subUnitId, + criteria: null, + col: -1, + }; + const undos: IMutationInfo[] = []; + const redos: IMutationInfo[] = []; + + target.forEach((column) => { + const [offset, filter] = column; + redos.push({ + id: SetSheetsFilterCriteriaMutation.id, + params: { + ...defaultSetCriteriaMutationParams, + col: offset, + }, + }); + undos.push({ + id: SetSheetsFilterCriteriaMutation.id, + params: { + ...defaultSetCriteriaMutationParams, + col: offset, + criteria: { ...filter.serialize(), colId: offset }, + }, + }); + }); + + target.forEach((column) => { + const [offset, filter] = column; + redos.push({ + id: SetSheetsFilterCriteriaMutation.id, + params: { + ...defaultSetCriteriaMutationParams, + col: offset + step, + criteria: { ...filter.serialize(), colId: offset + step }, + }, + }); + undos.push({ + id: SetSheetsFilterCriteriaMutation.id, + params: { + ...defaultSetCriteriaMutationParams, + col: offset + step, + criteria: null, + }, + }); + }); + + return { + redos, + undos, + }; + } + + private _commandExecutedListener() { + this.disposeWithMe(this._commandService.onCommandExecuted((command: ICommandInfo) => { + const { unitId, subUnitId } = command.params as unknown as ISheetCommandSharedParams || {}; + + const filterModel = this._sheetsFilterService.getFilterModel(unitId, subUnitId); + if (!filterModel) return; + const filteredOutRows = Array.from(filterModel.filteredOutRows).sort((a, b) => a - b); + const newFilteredOutRows: number[] = []; + let changed = false; + + if (command.id === RemoveRowMutation.id) { + const { startRow, endRow } = (command.params as IRemoveRowsMutationParams).range; + const filterOutInRemove = filteredOutRows.filter((row) => row >= startRow && row <= endRow); + filteredOutRows.forEach((row) => { + if (row < startRow) { + newFilteredOutRows.push(row); + } else { + changed = true; + if (row <= endRow) { + const newIndex = Math.max(startRow, newFilteredOutRows.length ? newFilteredOutRows[newFilteredOutRows.length - 1] + 1 : startRow); + newFilteredOutRows.push(newIndex); + } else { + newFilteredOutRows.push(row - (endRow - startRow + 1 - filterOutInRemove.length)); + } + } + }); + } + + if (command.id === InsertRowMutation.id) { + const { startRow, endRow } = (command.params as IInsertRowMutationParams).range; + filteredOutRows.forEach((row) => { + if (row >= startRow) { + changed = true; + newFilteredOutRows.push(row + (endRow - startRow + 1)); + } else { + newFilteredOutRows.push(row); + } + }); + } + + if (changed) { + filterModel.filteredOutRows = new Set(newFilteredOutRows); + } + + // if (command.id === DeleteRangeMoveLeftCommand.id || command.id === DeleteRangeMoveUpCommand.id || command.id === InsertRangeMoveRightCommand.id || command.id === InsertRangeMoveDownCommand.id) { + // const { range } = command.params as (IDeleteRangeMoveUpCommandParams | IDeleteRangeMoveLeftCommandParams | InsertRangeMoveDownCommandParams | InsertRangeMoveRightCommandParams); + // const { startRow } = range; + // const { endRow: filterEndRow } = filterModel.getRange(); + // if (startRow <= filterEndRow) { + // filterModel.reCalc(); + // } + // } + + // InsertRowsOrCols / RemoveRowsOrCols Mutations + // if (mutationIdByRowCol.includes(command.id)) { + // const params = command.params as IInsertRowCommandParams; + // if (!params) return; + // const { range } = params; + + // const isRowOperation = command.id.includes('row'); + // const isAddOperation = command.id.includes('insert'); + + // const operationStart = isRowOperation ? range.startRow : range.startColumn; + // const operationEnd = isRowOperation ? range.endRow : range.endColumn; + // const operationCount = operationEnd - operationStart + 1; + + // let { startRow, endRow, startColumn, endColumn } = filterModel.getRange(); + + // if (isAddOperation) { + // if (isRowOperation) { + // if (operationStart <= startRow) { + // startRow += operationCount; + // endRow += operationCount; + // } + // } else { + // if (operationStart <= startColumn) { + // startColumn += operationCount; + // endColumn += operationCount; + // } + // } + // } else { + // if (isRowOperation) { + // if (operationEnd < startRow) { + // startRow -= operationCount; + // endRow -= operationCount; + // } + // } else { + // if (operationEnd < startColumn) { + // startColumn -= operationCount; + // endColumn -= operationCount; + // } + // } + // } + // filterModel.setRange({ startRow, endRow, startColumn, endColumn }); + // } + })); + } +} + diff --git a/packages/sheets-filter/src/index.ts b/packages/sheets-filter/src/index.ts new file mode 100644 index 000000000000..07cbef0c3f07 --- /dev/null +++ b/packages/sheets-filter/src/index.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { UniverSheetsFilterPlugin } from './plugin'; +export { FilterColumn, FilterModel } from './models/filter-model'; +export { + equals, + notEquals, + getCustomFilterFn, + greaterThan, + greaterThanOrEqualTo, + lessThan, + lessThanOrEqualTo, +} from './models/custom-filters'; +export { SheetsFilterService, FILTER_MUTATIONS, SHEET_FILTER_SNAPSHOT_ID } from './services/sheet-filter.service'; +export { + type IReCalcSheetsFilterMutationParams, + type IRemoveSheetsFilterMutationParams, + type ISetSheetsFilterCriteriaMutationParams, + type ISetSheetsFilterRangeMutationParams, + SetSheetsFilterCriteriaMutation, + SetSheetsFilterRangeMutation, + ReCalcSheetsFilterMutation, + RemoveSheetsFilterMutation, +} from './commands/sheets-filter.mutation'; +export type { IAutoFilter, IFilterColumn, IFilters, ICustomFilters, ICustomFilter } from './models/types'; +export { CustomFilterOperator } from './models/types'; diff --git a/packages/sheets-filter/src/models/__tests__/custom-filter.spec.ts b/packages/sheets-filter/src/models/__tests__/custom-filter.spec.ts new file mode 100644 index 000000000000..7f5812a8fe80 --- /dev/null +++ b/packages/sheets-filter/src/models/__tests__/custom-filter.spec.ts @@ -0,0 +1,130 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; +import { equals, greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo, notEquals, textMatch } from '../custom-filters'; + +describe('Test custom filter functions', () => { + describe('Test "greaterThan" operator', () => { + it('filter out values if there are not of type number', () => { + expect(greaterThan.fn('1', 0)).toBeFalsy(); + }); + + it('should execute "greaterThan" correctly', () => { + expect(greaterThan.fn(1, 0)).toBeTruthy(); + expect(greaterThan.fn(0, 0)).toBeFalsy(); + expect(greaterThan.fn(-1, 0)).toBeFalsy(); + }); + }); + + describe('Test "greaterThanOrEqual" operator', () => { + it('filter out values if there are not of type number', () => { + expect(greaterThanOrEqualTo.fn('1', 0)).toBeFalsy(); + }); + + it('should execute "greaterThanOrEqual" correctly', () => { + expect(greaterThanOrEqualTo.fn(1, 0)).toBeTruthy(); + expect(greaterThanOrEqualTo.fn(0, 0)).toBeTruthy(); + expect(greaterThanOrEqualTo.fn(-1, 0)).toBeFalsy(); + }); + }); + + describe('Test "lessThan" operator', () => { + it('filter out values if there are not of type number', () => { + expect(lessThan.fn('1', 0)).toBeFalsy(); + }); + + it('should execute "lessThan" correctly', () => { + expect(lessThan.fn(1, 0)).toBeFalsy(); + expect(lessThan.fn(0, 0)).toBeFalsy(); + expect(lessThan.fn(-1, 0)).toBeTruthy(); + }); + }); + + describe('Test "lessThanOrEqual" operator', () => { + it('filter out values if there are not of type number', () => { + expect(lessThanOrEqualTo.fn('1', 0)).toBeFalsy(); + }); + + it('should execute "lessThanOrEqual" correctly', () => { + expect(lessThanOrEqualTo.fn(1, 0)).toBeFalsy(); + expect(lessThanOrEqualTo.fn(0, 0)).toBeTruthy(); + expect(lessThanOrEqualTo.fn(-1, 0)).toBeTruthy(); + }); + }); + + describe('Test "equals" operator', () => { + it('filter out values if there are not of type number', () => { + expect(equals.fn('0', 0)).toBeFalsy(); + }); + + it('should execute "equals" correctly', () => { + expect(equals.fn(1, 0)).toBeFalsy(); + expect(equals.fn(0, 0)).toBeTruthy(); + expect(equals.fn(-1, 0)).toBeFalsy(); + }); + }); + + describe('Test "notEquals" operator', () => { + it('should not filter out values if there are not of type number', () => { + expect(notEquals.fn('0', 0)).toBeTruthy(); + }); + + it('should execute "notEquals" correctly', () => { + expect(notEquals.fn(1, 0)).toBeTruthy(); + expect(notEquals.fn(0, 0)).toBeFalsy(); + expect(notEquals.fn(-1, 0)).toBeTruthy(); + }); + + it('should "notEquals" implement support matching strings if `compare` is a string', () => { + // equivalent to "notContains" + expect(notEquals.fn(123, '*123*')).toBeFalsy(); + expect(notEquals.fn('12', '*123*')).toBeTruthy(); + }); + }); + + describe('Test text match operator', () => { + it('should execute "textMatch" correctly', () => { + // equivalent to "startsWith" + expect(textMatch.fn('hello world', 'hello*')).toBeTruthy(); + expect(textMatch.fn('hello', 'hello*')).toBeTruthy(); + expect(textMatch.fn('hell', 'hello*')).toBeFalsy(); + + // equivalent to "endsWith" + expect(textMatch.fn('hello world', '*world')).toBeTruthy(); + expect(textMatch.fn('world', '*world')).toBeTruthy(); + expect(textMatch.fn('wrld', '*world')).toBeFalsy(); + + // equivalent to "contains" + expect(textMatch.fn('hello world', '*llo w*')).toBeTruthy(); + expect(textMatch.fn('hell world', '*llo w*')).toBeFalsy(); + }); + + it('should support "?" wild card', () => { + expect(textMatch.fn('hello', 'hell?')).toBeTruthy(); + expect(textMatch.fn('helloooo', 'hell?')).toBeFalsy(); + expect(textMatch.fn('helloooo', 'hell?ooo')).toBeTruthy(); + expect(textMatch.fn('hello world', 'hell?')).toBeFalsy(); + expect(textMatch.fn('hello', '?hell?')).toBeFalsy(); + expect(textMatch.fn('yhell', '?hell?')).toBeFalsy(); + + expect(textMatch.fn('hello111', '*hello?*')).toBeTruthy(); + expect(textMatch.fn('hello111\r\n', '*hello?*')).toBeFalsy(); + expect(textMatch.fn('hello111\r\n'.trimEnd(), '*hello?*')).toBeTruthy(); + expect(textMatch.fn('hello', '*hello?*')).toBeFalsy(); + }); + }); +}); diff --git a/packages/sheets-filter/src/models/__tests__/filter-interceptor.spec.ts b/packages/sheets-filter/src/models/__tests__/filter-interceptor.spec.ts new file mode 100644 index 000000000000..533a9319d82b --- /dev/null +++ b/packages/sheets-filter/src/models/__tests__/filter-interceptor.spec.ts @@ -0,0 +1,353 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import type { IWorkbookData, LocaleType } from '@univerjs/core'; +import { Direction, ICommandService, Plugin, RANGE_TYPE, Univer, UniverInstanceType } from '@univerjs/core'; +import type { Dependency } from '@wendellhu/redi'; +import { Inject, Injector } from '@wendellhu/redi'; + +import type { IInsertColCommandParams, IInsertRowCommandParams, IMoveColsCommandParams, IMoveRowsCommandParams, IRemoveRowColCommandParams, IRemoveSheetCommandParams, ISetSelectionsOperationParams } from '@univerjs/sheets'; +import { InsertColCommand, InsertColMutation, InsertRowCommand, InsertRowMutation, MoveColsCommand, MoveColsMutation, MoveRowsCommand, MoveRowsMutation, NORMAL_SELECTION_PLUGIN_NAME, RefRangeService, RemoveColCommand, RemoveColMutation, RemoveRowCommand, RemoveRowMutation, RemoveSheetCommand, RemoveSheetMutation, SelectionManagerService, SetSelectionsOperation, SheetInterceptorService } from '@univerjs/sheets'; +import { SHEET_FILTER_SNAPSHOT_ID, SheetsFilterService } from '../../services/sheet-filter.service'; +import { SheetsFilterController } from '../../controllers/sheets-fiter.controller'; + +describe('Test "Filter Interceptor"', () => { + let univer: Univer; + let get: Injector['get']; + let sheetsFilterService: SheetsFilterService; + let commandService: ICommandService; + + beforeEach(() => { + const testBed = createFilterTestUniver(); + univer = testBed.univer; + get = testBed.get; + sheetsFilterService = testBed.sheetsFilterService; + commandService = testBed.commandService; + }); + + afterEach(() => { + univer.dispose(); + }); + + describe('Test "insert Command"', () => { + it('insert col command, in filter range', async () => { + const insertColCommandParams = { unitId: 'workbookId', subUnitId: 'worksheetId', range: { startColumn: 2, endColumn: 2, startRow: 0, endRow: 4, type: RANGE_TYPE.COLUMN, + }, direction: Direction.RIGHT } as IInsertColCommandParams; + await commandService.executeCommand(InsertColCommand.id, insertColCommandParams); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getRange()).toStrictEqual({ startColumn: 1, endColumn: 3, startRow: 1, endRow: 2 }); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getAllFilterColumns().map((x) => x[0])).toStrictEqual([3]); + }); + + it('insert col command, before filter range', async () => { + const insertColCommandParams = { unitId: 'workbookId', subUnitId: 'worksheetId', range: { startColumn: 0, endColumn: 0, startRow: 0, endRow: 4, type: RANGE_TYPE.COLUMN, + }, direction: Direction.RIGHT } as IInsertColCommandParams; + await commandService.executeCommand(InsertColCommand.id, insertColCommandParams); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getRange()).toStrictEqual({ startColumn: 2, endColumn: 3, startRow: 1, endRow: 2 }); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getAllFilterColumns().map((x) => x[0])).toStrictEqual([3]); + }); + + it('insert row command, in filter range', async () => { + const insertRowCommandParams = { unitId: 'workbookId', subUnitId: 'worksheetId', range: { startColumn: 0, endColumn: 3, startRow: 2, endRow: 2, type: RANGE_TYPE.ROW, + }, direction: Direction.DOWN } as IInsertRowCommandParams; + await commandService.executeCommand(InsertRowCommand.id, insertRowCommandParams); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getRange()).toStrictEqual({ startColumn: 1, endColumn: 2, startRow: 1, endRow: 3 }); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getAllFilterColumns().map((x) => x[0])).toStrictEqual([2]); + }); + + it('insert row command, before filter range', async () => { + const insertRowCommandParams = { unitId: 'workbookId', subUnitId: 'worksheetId', range: { startColumn: 0, endColumn: 3, startRow: 0, endRow: 0, type: RANGE_TYPE.ROW, + }, direction: Direction.DOWN } as IInsertRowCommandParams; + await commandService.executeCommand(InsertRowCommand.id, insertRowCommandParams); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getRange()).toStrictEqual({ startColumn: 1, endColumn: 2, startRow: 2, endRow: 3 }); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getAllFilterColumns().map((x) => x[0])).toStrictEqual([2]); + }); + }); + + describe('Test "remove Command"', () => { + it('remove col command, in filter range', async () => { + const removeColCommandParams = { unitId: 'workbookId', subUnitId: 'worksheetId', range: { startColumn: 1, endColumn: 1, startRow: 0, endRow: 4, type: RANGE_TYPE.COLUMN, + }, direction: Direction.RIGHT } as IRemoveRowColCommandParams; + await commandService.executeCommand(RemoveColCommand.id, removeColCommandParams); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getRange()).toStrictEqual({ startColumn: 1, endColumn: 1, startRow: 1, endRow: 2 }); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getAllFilterColumns().map((x) => x[0])).toStrictEqual([1]); + }); + it('remove col command, before filter range', async () => { + const removeColCommandParams = { unitId: 'workbookId', subUnitId: 'worksheetId', range: { startColumn: 0, endColumn: 0, startRow: 0, endRow: 4, type: RANGE_TYPE.COLUMN, + }, direction: Direction.RIGHT } as IRemoveRowColCommandParams; + await commandService.executeCommand(RemoveColCommand.id, removeColCommandParams); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getRange()).toStrictEqual({ startColumn: 0, endColumn: 1, startRow: 1, endRow: 2 }); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getAllFilterColumns().map((x) => x[0])).toStrictEqual([1]); + }); + + it('remove row command, in filter range', async () => { + const removeRowCommandParams = { unitId: 'workbookId', subUnitId: 'worksheetId', range: { startColumn: 0, endColumn: 3, startRow: 1, endRow: 1, type: RANGE_TYPE.ROW, + }, direction: Direction.DOWN } as IRemoveRowColCommandParams; + await commandService.executeCommand(RemoveRowCommand.id, removeRowCommandParams); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')).toBe(null); + }); + + it('remove row command, before filter range', async () => { + const removeRowCommandParams = { unitId: 'workbookId', subUnitId: 'worksheetId', range: { startColumn: 0, endColumn: 3, startRow: 0, endRow: 0, type: RANGE_TYPE.ROW, + }, direction: Direction.DOWN } as IRemoveRowColCommandParams; + await commandService.executeCommand(RemoveRowCommand.id, removeRowCommandParams); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getRange()).toStrictEqual({ startColumn: 1, endColumn: 2, startRow: 0, endRow: 1 }); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getAllFilterColumns().map((x) => x[0])).toStrictEqual([2]); + }); + }); + + describe('Test "move Command"', () => { + it('move col command, filter column move to left', async () => { + const moveColCommandParams = { unitId: 'workbookId', subUnitId: 'worksheetId', fromRange: { startColumn: 1, endColumn: 1, startRow: 0, endRow: 4, type: RANGE_TYPE.COLUMN, + }, toRange: { startColumn: 3, endColumn: 3, startRow: 0, endRow: 4, type: RANGE_TYPE.COLUMN }, direction: Direction.RIGHT } as IMoveColsCommandParams; + const selectionManagerService = get(SelectionManagerService); + selectionManagerService.setCurrentSelection({ unitId: 'workbookId', sheetId: 'worksheetId', pluginName: NORMAL_SELECTION_PLUGIN_NAME }); + await commandService.executeCommand(SetSelectionsOperation.id, { unitId: 'workbookId', subUnitId: 'worksheetId', pluginName: NORMAL_SELECTION_PLUGIN_NAME, selections: [{ style: null, range: { startColumn: 1, endColumn: 1, startRow: 0, endRow: 4, rangeType: RANGE_TYPE.COLUMN }, primary: {} }] } as ISetSelectionsOperationParams); + await commandService.executeCommand(MoveColsCommand.id, moveColCommandParams); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getRange()).toStrictEqual({ startColumn: 1, endColumn: 2, startRow: 1, endRow: 2 }); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getAllFilterColumns().map((x) => x[0])).toStrictEqual([1]); + }); + it('move col command, filter column move to right', async () => { + const moveColCommandParams = { unitId: 'workbookId', subUnitId: 'worksheetId', fromRange: { startColumn: 2, endColumn: 2, startRow: 0, endRow: 4, type: RANGE_TYPE.COLUMN, + }, toRange: { startColumn: 4, endColumn: 4, startRow: 0, endRow: 4, type: RANGE_TYPE.COLUMN }, direction: Direction.RIGHT } as IMoveColsCommandParams; + const selectionManagerService = get(SelectionManagerService); + selectionManagerService.setCurrentSelection({ unitId: 'workbookId', sheetId: 'worksheetId', pluginName: NORMAL_SELECTION_PLUGIN_NAME }); + await commandService.executeCommand(SetSelectionsOperation.id, { unitId: 'workbookId', subUnitId: 'worksheetId', pluginName: NORMAL_SELECTION_PLUGIN_NAME, selections: [{ style: null, range: { startColumn: 2, endColumn: 2, startRow: 0, endRow: 4, rangeType: RANGE_TYPE.COLUMN }, primary: {} }] } as ISetSelectionsOperationParams); + await commandService.executeCommand(MoveColsCommand.id, moveColCommandParams); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getRange()).toStrictEqual({ startColumn: 1, endColumn: 3, startRow: 1, endRow: 2 }); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getAllFilterColumns().map((x) => x[0])).toStrictEqual([3]); + }); + + it('move row command', async () => { + const moveColCommandParams = { unitId: 'workbookId', subUnitId: 'worksheetId', fromRange: { startColumn: 0, endColumn: 3, startRow: 1, endRow: 1, type: RANGE_TYPE.ROW, + }, toRange: { startColumn: 0, endColumn: 3, startRow: 3, endRow: 3, type: RANGE_TYPE.ROW }, direction: Direction.RIGHT } as IMoveRowsCommandParams; + const selectionManagerService = get(SelectionManagerService); + selectionManagerService.setCurrentSelection({ unitId: 'workbookId', sheetId: 'worksheetId', pluginName: NORMAL_SELECTION_PLUGIN_NAME }); + await commandService.executeCommand(SetSelectionsOperation.id, { unitId: 'workbookId', subUnitId: 'worksheetId', pluginName: NORMAL_SELECTION_PLUGIN_NAME, selections: [{ style: null, range: { startColumn: 0, endColumn: 3, startRow: 1, endRow: 1, rangeType: RANGE_TYPE.ROW }, primary: {} }] } as ISetSelectionsOperationParams); + await commandService.executeCommand(MoveRowsCommand.id, moveColCommandParams); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getRange()).toStrictEqual({ startColumn: 1, endColumn: 2, startRow: 1, endRow: 2 }); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')!.getAllFilterColumns().map((x) => x[0])).toStrictEqual([2]); + }); + }); + + describe('Test "remove sheet Command"', () => { + it('remove sheet command', async () => { + const removeSheetCommandParams = { unitId: 'workbookId', subUnitId: 'worksheetId' } as IRemoveSheetCommandParams; + await commandService.executeCommand(RemoveSheetCommand.id, removeSheetCommandParams); + expect(sheetsFilterService.getFilterModel('workbookId', 'worksheetId')).toBe(null); + }); + }); +}); + +function createFilterTestUniver(dependencies?: Dependency[], workbookData?: IWorkbookData +) { + const univer = new Univer(); + const injector = univer.__getInjector(); + const get = injector.get.bind(injector); + + /** + * This plugin hooks into Sheet's DI system to expose API to test scripts + */ + class TestPlugin extends Plugin { + static override type = UniverInstanceType.SHEET; + static override pluginName = 'test-plugin'; + + constructor( + _config: undefined, + @Inject(Injector) override readonly _injector: Injector + ) { + super(); + } + + override onStarting(injector: Injector): void { + dependencies?.forEach((d) => injector.add(d)); + + injector.add([SheetInterceptorService]); + injector.add([SheetsFilterService]); + injector.add([SelectionManagerService]); + injector.add([RefRangeService]); + injector.add([SheetsFilterController]); + } + } + + univer.registerPlugin(TestPlugin); + + univer.createUniverSheet(workbookData || testWorkbookDataFactory()); + + const sheetsFilterService = injector.get(SheetsFilterService); + + // It should be registered later in avoid of time sequence problem. + // injector.add([ISnapshotPersistenceService, { useClass: LocalSnapshotService }]); + // injector.get(ISnapshotPersistenceService); + + const commandService = get(ICommandService); + [ + InsertColMutation, + RemoveColMutation, + InsertColCommand, + InsertRowMutation, + RemoveRowMutation, + InsertRowCommand, + RemoveColCommand, + RemoveRowCommand, + MoveColsMutation, + MoveColsCommand, + MoveRowsCommand, + MoveRowsMutation, + SetSelectionsOperation, + RemoveSheetCommand, + RemoveSheetMutation, + ].forEach((command) => { + commandService.registerCommand(command); + }); + + return { + univer, + get, + sheetsFilterService, + commandService, + }; +} + +function testWorkbookDataFactory(): IWorkbookData { + return { + id: 'workbookId', + sheetOrder: ['worksheetId'], + name: 'UniverSheet Demo', + appVersion: '3.0.0-alpha', + locale: 'zhCN' as LocaleType, + styles: {}, + sheets: { + worksheetId: { + name: '工作表1', + id: 'worksheetId', + tabColor: '', + hidden: 0, + rowCount: 1000, + columnCount: 20, + zoomRatio: 1, + scrollTop: 0, + scrollLeft: 0, + defaultColumnWidth: 73, + defaultRowHeight: 19, + mergeData: [], + cellData: { + 0: { + 0: { + v: 'A1', + }, + 1: { + v: 'B1', + }, + 2: { + v: 'C1', + }, + 3: { + v: 'D1', + }, + }, + 1: { + 0: { + v: 'A2', + }, + 1: { + v: 'B2', + }, + 2: { + v: 'C2', + }, + 3: { + v: 'D2', + }, + }, + 2: { + 0: { + v: 'A3', + }, + 1: { + v: 'B3', + }, + 2: { + v: 'C3', + }, + 3: { + v: 'D3', + }, + }, + 3: { + 0: { + v: 'A4', + }, + 1: { + v: 'B4', + }, + 2: { + v: 'C4', + }, + 3: { + v: 'D4', + }, + }, + 4: { + 0: { + v: 'A5', + }, + 1: { + v: 'B5', + }, + 2: { + v: 'C5', + }, + 3: { + v: 'D5', + }, + }, + }, + showGridlines: 1, + selections: ['A1'], + rightToLeft: 0, + }, + sheet1: { + name: 'sheet1', + id: 'sheet1', + cellData: {}, + }, + }, + resources: [ + { + name: SHEET_FILTER_SNAPSHOT_ID, + data: JSON.stringify({ + worksheetId: { + ref: { + startColumn: 1, + endColumn: 2, + startRow: 1, + endRow: 2, + }, + + filterColumns: [{ + colId: 2, + filters: { + filters: ['1'], + }, + }], + cachedFilteredOut: [], + }, + }), + }, + ], + }; +}; diff --git a/packages/sheets-filter/src/models/__tests__/filter-model.spec.ts b/packages/sheets-filter/src/models/__tests__/filter-model.spec.ts new file mode 100644 index 000000000000..032b5fc88f35 --- /dev/null +++ b/packages/sheets-filter/src/models/__tests__/filter-model.spec.ts @@ -0,0 +1,284 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import type { IWorkbookData, Workbook } from '@univerjs/core'; +import { ILogService, IUniverInstanceService, LocaleType, LogLevel, Univer, UniverInstanceType } from '@univerjs/core'; +import type { Injector } from '@wendellhu/redi'; + +import { FilterColumn, generateFilterFn } from '../filter-model'; +import { CustomFilterOperator } from '../types'; + +describe('Test filter model and related utils', () => { + describe('Test "FilterFn"s', () => { + it('should AND work', () => { + // equivalent to "between" + const fn = generateFilterFn({ + colId: 0, customFilters: { + and: 1, + customFilters: [ + { operator: CustomFilterOperator.LESS_THAN_OR_EQUAL, val: 456 }, + { operator: CustomFilterOperator.GREATER_THAN_OR_EQUAL, val: 123 }, + ], + }, + }); + + expect(fn(123)).toBeTruthy(); + expect(fn(456)).toBeTruthy(); + expect(fn(400)).toBeTruthy(); + expect(fn(100)).toBeFalsy(); + expect(fn(500)).toBeFalsy(); + }); + + it('should OR work', () => { + // equivalent to "notBetween" + const fn = generateFilterFn({ + colId: 0, customFilters: { + customFilters: [ + { operator: CustomFilterOperator.LESS_THAN, val: 123 }, + { operator: CustomFilterOperator.GREATER_THAN, val: 456 }, + ], + }, + }); + + expect(fn(123)).toBeFalsy(); + expect(fn(456)).toBeFalsy(); + expect(fn(400)).toBeFalsy(); + expect(fn(100)).toBeTruthy(); + expect(fn(500)).toBeTruthy(); + }); + + it('should throw error on not supported yet filter types', () => { + // eslint-disable-next-line ts/no-explicit-any + expect(() => generateFilterFn({ colId: 0, dynamicFilters: {} } as any)).toThrowError(); + }); + + it('should "filter by values" work', () => { + const fn = generateFilterFn({ colId: 0, filters: { filters: ['hello', 'univer'] } }); + + expect(fn('hello')).toBeTruthy(); + expect(fn('univer')).toBeTruthy(); + expect(fn('wzhudev')).toBeFalsy(); + expect(fn('')).toBeFalsy(); + expect(fn(undefined)).toBeFalsy(); + }); + + it('should "filter by values" work with blank values', () => { + const fn = generateFilterFn({ colId: 0, filters: { filters: ['hello', 'univer'], blank: true } }); + expect(fn('hello')).toBeTruthy(); + expect(fn('univer')).toBeTruthy(); + expect(fn('wzhudev')).toBeFalsy(); + expect(fn('')).toBeTruthy(); + expect(fn(undefined)).toBeTruthy(); + }); + }); + + describe('Test "FilterColumn"', () => { + let univer: Univer; + let filterColumn: FilterColumn; + let get: Injector['get']; + + beforeEach(() => { + const testBed = createFilterModelTestBed({ + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + name: 'sheet1', + cellData: { + 0: { + 1: { v: 'header' }, + }, + 1: { + 1: { v: 123 }, + }, + 2: { + 1: { v: 345 }, + }, + }, + }, + }, + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [], + styles: {}, + }); + + univer = testBed.univer; + get = testBed.get; + }); + + afterEach(() => { + univer.dispose(); + filterColumn.dispose(); + }); + + it('should not calc when filter fn or range is not set', () => { + filterColumn = new FilterColumn( + 'test', + 'sheet1', + get(IUniverInstanceService).getCurrentUnitForType(UniverInstanceType.SHEET)!.getActiveSheet()!, + { colId: 0, customFilters: { customFilters: [{ operator: CustomFilterOperator.LESS_THAN, val: 123 }] } }, + { getAlreadyFilteredOutRows() { return new Set(); } } + ); + + expect(filterColumn.hasCache()).toBeFalsy(); + + filterColumn.setRangeAndColumn({ startRow: 0, endRow: 100, startColumn: 0, endColumn: 0 }, 0); + expect(filterColumn.hasCache()).toBeFalsy(); + + expect(() => filterColumn.reCalc()).toThrowError(); + expect(filterColumn.hasCache()).toBeFalsy(); + + filterColumn.dispose(); + + // ------------------------------------------------------- + + filterColumn = new FilterColumn( + 'test', + 'sheet1', + get(IUniverInstanceService).getCurrentUnitForType(UniverInstanceType.SHEET)!.getActiveSheet()!, + { colId: 0, customFilters: { customFilters: [{ operator: CustomFilterOperator.LESS_THAN, val: 123 }] } }, + { getAlreadyFilteredOutRows() { return new Set(); } } + ); + + expect(filterColumn.hasCache()).toBeFalsy(); + + filterColumn.setCriteria({ colId: 0, customFilters: { customFilters: [{ operator: CustomFilterOperator.LESS_THAN, val: 123 }] } }); + expect(filterColumn.hasCache()).toBeFalsy(); + + expect(() => filterColumn.reCalc()).toThrowError(); + expect(filterColumn.hasCache()).toBeFalsy(); + }); + + it('should not auto calc when set criteria', () => { + filterColumn = new FilterColumn( + 'test', + 'sheet1', + get(IUniverInstanceService).getCurrentUnitForType(UniverInstanceType.SHEET)!.getActiveSheet()!, + { colId: 0, customFilters: { customFilters: [{ operator: CustomFilterOperator.LESS_THAN, val: 123 }] } }, + { getAlreadyFilteredOutRows() { return new Set(); } } + ); + + expect(filterColumn.hasCache()).toBeFalsy(); + + filterColumn.setRangeAndColumn({ startRow: 0, endRow: 100, startColumn: 0, endColumn: 0 }, 0); + filterColumn.setCriteria({ colId: 0, customFilters: { customFilters: [{ operator: CustomFilterOperator.LESS_THAN, val: 123 }] } }); + expect(filterColumn.hasCache()).toBeFalsy(); + expect(filterColumn.filteredOutRows).toBeNull(); + + filterColumn.reCalc(); + expect(filterColumn.hasCache()).toBeTruthy(); + expect(filterColumn.filteredOutRows!.size).toBe(100); + }); + + it('should filtered out rows correctly', () => { + filterColumn = new FilterColumn( + 'test', + 'sheet1', + get(IUniverInstanceService).getCurrentUnitForType(UniverInstanceType.SHEET)!.getActiveSheet()!, + { colId: 0, customFilters: { customFilters: [{ operator: CustomFilterOperator.LESS_THAN, val: 123 }] } }, + { getAlreadyFilteredOutRows() { return new Set(); } } + ); + + filterColumn.setRangeAndColumn({ startRow: 1, endRow: 3, startColumn: 1, endColumn: 1 }, 1); + filterColumn.setCriteria({ colId: 1, customFilters: { customFilters: [{ operator: CustomFilterOperator.GREATER_THAN, val: 200 }] } }); + filterColumn.reCalc(); + expect(filterColumn.filteredOutRows!.size).toBe(1); + }); + + it('should skip rows that already filtered out by other rows', () => { + filterColumn = new FilterColumn( + 'test', + 'sheet1', + get(IUniverInstanceService).getCurrentUnitForType(UniverInstanceType.SHEET)!.getActiveSheet()!, + { colId: 0, customFilters: { customFilters: [{ operator: CustomFilterOperator.LESS_THAN, val: 123 }] } }, + { getAlreadyFilteredOutRows() { return new Set([1]); } } + ); + + filterColumn.setRangeAndColumn({ startRow: 1, endRow: 3, startColumn: 1, endColumn: 1 }, 1); + filterColumn.setCriteria({ colId: 1, customFilters: { customFilters: [{ operator: CustomFilterOperator.GREATER_THAN, val: 200 }] } }); + filterColumn.reCalc(); + expect(filterColumn.filteredOutRows!.size).toBe(1); + }); + }); + + describe('Test "FilterModel"', () => { + let univer: Univer; + let filterColumn: FilterColumn; + let get: Injector['get']; + + beforeEach(() => { + const testBed = createFilterModelTestBed({ + id: 'test', + appVersion: '3.0.0-alpha', + sheets: { + sheet1: { + id: 'sheet1', + name: 'sheet1', + cellData: { + 0: { + 1: { v: 'header' }, + }, + 1: { + 1: { v: 123 }, + }, + 2: { + 1: { v: 345 }, + }, + }, + }, + }, + locale: LocaleType.ZH_CN, + name: '', + sheetOrder: [], + styles: {}, + }); + + univer = testBed.univer; + get = testBed.get; + }); + + // TODO@wzhudev: not implemented yet + + describe('Test serialization and deserialization', () => { + it('should serialize return a correct object', () => { }); + + it('should deserialize return a correct instance', () => { }); + }); + }); +}); + +function createFilterModelTestBed(workbookData: IWorkbookData) { + const univer = new Univer(); + const injector = univer.__getInjector(); + const get = injector.get.bind(injector); + + const sheet = univer.createUniverSheet(workbookData); + + const univerInstanceService = get(IUniverInstanceService); + univerInstanceService.focusUnit('test'); + + const logService = get(ILogService); + logService.setLogLevel(LogLevel.SILENT); + + return { + univer, + get, + sheet, + }; +} diff --git a/packages/sheets-filter/src/models/custom-filters.ts b/packages/sheets-filter/src/models/custom-filters.ts new file mode 100644 index 000000000000..c2a29e7f79d5 --- /dev/null +++ b/packages/sheets-filter/src/models/custom-filters.ts @@ -0,0 +1,198 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { CellValue, Nullable } from '@univerjs/core'; +import { isNumeric } from '@univerjs/core'; +import { CustomFilterOperator } from './types'; + +export interface IFilterFn

{ + label?: string; + + /** + * Description of this operator. Should be an i18n key. + */ + description?: string; + + fn: (...params: P) => boolean; + +} + +/** + * Custom filter functions normally used in "Filter by Conditions". + */ +export interface ICustomFilterFn

extends IFilterFn

{ + /** + * Operator of the custom filter function. + */ + operator?: CustomFilterOperator; + + /** + * Group of the custom filter belongs to. It would be rendered in the panel by groups. + */ + group?: string; + + fn: (...params: P) => boolean; +} + +type TwoParameters = [value: Nullable, compare: C]; + +export const greaterThan: ICustomFilterFn> = { + operator: CustomFilterOperator.GREATER_THAN, + fn: (value, compare): boolean => { + if (!ensureNumber(value)) { + return false; + } + + return value > compare; + }, +}; + +export const greaterThanOrEqualTo: ICustomFilterFn> = { + operator: CustomFilterOperator.GREATER_THAN_OR_EQUAL, + fn: (value, compare): boolean => { + if (!ensureNumber(value)) { + return false; + } + + return value >= compare; + }, +}; + +export const lessThan: ICustomFilterFn> = { + operator: CustomFilterOperator.LESS_THAN, + fn: (value, compare): boolean => { + if (!ensureNumber(value)) { + return false; + } + + return value < compare; + }, +}; + +export const lessThanOrEqualTo: ICustomFilterFn> = { + operator: CustomFilterOperator.LESS_THAN_OR_EQUAL, + fn: (value, compare): boolean => { + if (!ensureNumber(value)) { + return false; + } + + return value <= compare; + }, +}; + +export const equals: ICustomFilterFn> = { + operator: CustomFilterOperator.EQUAL, + fn: (value, compare): boolean => { + if (!ensureNumber(value)) { + return false; + } + + return value === compare; + }, +}; + +export const notEquals: ICustomFilterFn> = { + operator: CustomFilterOperator.NOT_EQUALS, + fn: (value, compare): boolean => { + // As text match. + if (typeof compare === 'string') { + if (compare === ' ') { + if (value !== undefined && value !== null) return true; + return false; + }; + + const ensuredString = ensureString(value); + if (ensuredString && isWildCardString(compare)) return !createREGEXFromWildChar(compare as string).test(ensuredString); + return ensuredString !== compare; + } + + // As numeric match. + if (!ensureNumber(value)) return true; + return value !== compare; + }, +}; + +// Register the custom filter functions to the registry, making it easier to get them. +export const CustomFilterFnRegistry = new Map>>([]); + +const ALL_CUSTOM_FILTER_FUNCTIONS = [greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo, equals, notEquals]; +ALL_CUSTOM_FILTER_FUNCTIONS.forEach((fn) => { + CustomFilterFnRegistry.set(fn.operator!, fn); +}); + +export function isNumericFilterFn(operator?: CustomFilterOperator): boolean { + return !!operator; +} + +/** This operators matches texts. */ +export const textMatch: ICustomFilterFn> = { + fn: (value, compare): boolean => { + const ensured = ensureString(value); + if (ensured === null) { + if (compare === '') return true; + return false; + } + + return createREGEXFromWildChar(compare).test(ensured); + }, +}; + +// eslint-disable-next-line ts/no-explicit-any +export function getCustomFilterFn(operator?: CustomFilterOperator): ICustomFilterFn> { + if (!operator) { + return textMatch; + } + + return CustomFilterFnRegistry.get(operator)!; +} + +function ensureNumber(value: Nullable): value is number { + return typeof value === 'number'; +} + +export function ensureNumeric(value: Nullable): boolean { + if (typeof value === 'number') { + return true; + }; + + if (typeof value === 'string' && isNumeric(value)) { + return true; + } + + return false; +} + +function ensureString(value: Nullable): string | null { + if (typeof value === 'boolean' || value == null) { + return null; + } + + return typeof value === 'string' ? value : value.toString(); +} + +function isWildCardString(str: string | number): boolean { + if (typeof str === 'number') { + return false; + } + + return str.indexOf('*') !== -1 || str.indexOf('?') !== -1; +} + +function createREGEXFromWildChar(wildChar: string): RegExp { + // only '*' and '?' is supported + const regexpStr = wildChar.replace(/[.+^${}()|[\]\\]/g, '\\$&').replaceAll('?', '.').replace(/[*]/g, '.$&'); + return new RegExp(`^${regexpStr}$`); +} diff --git a/packages/sheets-filter/src/models/filter-model.ts b/packages/sheets-filter/src/models/filter-model.ts new file mode 100644 index 000000000000..cb47a983a8e2 --- /dev/null +++ b/packages/sheets-filter/src/models/filter-model.ts @@ -0,0 +1,555 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CellValueType, Disposable, extractPureTextFromCell, mergeSets, Rectangle, Tools } from '@univerjs/core'; +import type { CellValue, ICellData, IRange, Nullable, Worksheet } from '@univerjs/core'; +import { BehaviorSubject } from 'rxjs'; +import type { Observable } from 'rxjs'; +import { ensureNumeric, getCustomFilterFn, isNumericFilterFn, notEquals } from './custom-filters'; +import { CustomFilterOperator, type IAutoFilter, type ICustomFilter, type ICustomFilters, type IFilterColumn, type IFilters } from './types'; + +const EMPTY = () => new Set(); + +/** + * This is the in-memory model of filter. + */ +export class FilterModel extends Disposable { + private readonly _filteredOutRows$ = new BehaviorSubject>>(EMPTY()); + /** An observable value. A set of filtered out rows. */ + readonly filteredOutRows$: Observable>> = this._filteredOutRows$.asObservable(); + get filteredOutRows() { return this._filteredOutRows$.getValue(); } + set filteredOutRows(rows: Set) { + this._alreadyFilteredOutRows = rows; + this._filteredOutRows$.next(rows); + } + // TODO: we may need to update which cols have criteria rather than simple boolean + + private readonly _hasCriteria$ = new BehaviorSubject(false); + readonly hasCriteria$: Observable = this._hasCriteria$.asObservable(); + + private _filterColumnByIndex = new Map(); + + private _alreadyFilteredOutRows = EMPTY(); + + private _range: Nullable; + + constructor( + public readonly unitId: string, + public readonly subUnitId: string, + private readonly _worksheet: Worksheet + ) { + super(); + } + + override dispose(): void { + super.dispose(); + + this._filteredOutRows$.complete(); + this._hasCriteria$.complete(); + } + + /** + * Serialize this filter model to the JSON format representation. + */ + serialize(): IAutoFilter { + const result: IAutoFilter = { + ref: Rectangle.clone(this._range!), + filterColumns: this._getAllFilterColumns(true) + .sort(([offset1], [offset2]) => offset1 - offset2) + .map(([_, filterColumn]) => filterColumn.serialize()), + }; + + if (this._alreadyFilteredOutRows) { + result.cachedFilteredOut = Array.from(this._alreadyFilteredOutRows).sort(); + } + + return result; + } + + /** + * Deserialize auto filter info to construct a `FilterModel` object. + * @param unitId workbook id + * @param subUnitId worksheet id + * @param worksheet the Worksheet object + * @param autoFilter auto filter data + */ + static deserialize( + unitId: string, + subUnitId: string, + worksheet: Worksheet, + autoFilter: IAutoFilter + ): FilterModel { + const filterModel = new FilterModel(unitId, subUnitId, worksheet); + filterModel._dump(autoFilter); + + return filterModel; + } + + private _dump(autoFilter: IAutoFilter) { + this.setRange(autoFilter.ref); + autoFilter.filterColumns?.forEach((filterColumn) => this._setCriteriaWithoutReCalc(filterColumn.colId, filterColumn)); + + if (autoFilter.cachedFilteredOut) { + this._alreadyFilteredOutRows = new Set(autoFilter.cachedFilteredOut); + this._emit(); + } + + this._emitHasCriteria(); + } + + isRowFiltered(row: number): boolean { + return this._alreadyFilteredOutRows.has(row); + } + + getRange(): IRange { + if (!this._range) { + throw new Error('[FilterModel] could not get range before a range is set!'); + } + + return this._range; + } + + /** + * Get filtered out rows except the specific column. This method is considered as "pure". In + * another word it would not change `filteredOutRows` on `FilterModel` nor `FilterColumn`. + * @param col + */ + getFilteredOutRowsExceptCol(col: number): Set { + return this._getAllFilterColumns(true) + .filter(([colOffset]) => colOffset !== col) + .reduce((acc, [, filterColumn]) => { + const newResult = filterColumn.calc({ getAlreadyFilteredOutRows: () => acc }); + if (newResult) return mergeSets(acc, newResult); + return acc; + }, new Set()); + } + + /** + * Set range of the filter model, this would remove some `IFilterColumn` + * if the new range not overlaps the old range. + */ + setRange(range: IRange): void { + this._range = range; + + // TODO@wzhudev: maybe we should remove the FilterColumn that is not in the new range! + // TODO@wzhudev: when a column in the range is deleted, we may need to change some FilterColumns' offset + + // set range for each FilterColumn + this._getAllFilterColumns(true) + .forEach(([col, filterColumn]) => { + filterColumn.setRangeAndColumn({ + startRow: range.startRow, + endRow: range.endRow, + startColumn: col, + endColumn: col, + }, col); + }); + } + + /** + * Set or remove filter criteria on a specific row. + */ + setCriteria(col: number, criteria: Nullable, reCalc = false): void { + if (!this._range) { + throw new Error('[FilterModel] could not set criteria before a range is set!'); + } + + if (!criteria) { + this._removeCriteria(col); + this._rebuildAlreadyFilteredOutRowsWithCache(); + if (reCalc) { + this._reCalcAllColumns(); + } + this._emit(); + this._emitHasCriteria(); + return; + } + + this._setCriteriaWithoutReCalc(col, criteria); + if (reCalc) { + this._rebuildAlreadyFilteredOutRowsWithCache(); + this._reCalcWithNoCacheColumns(); + this._emit(); + this._emitHasCriteria(); + } + } + + getAllFilterColumns(): [number, FilterColumn][] { + return this._getAllFilterColumns(true); + } + + getFilterColumn(index: number): Nullable { + return this._filterColumnByIndex.get(index) ?? null; + } + + reCalc(): void { + this._reCalcAllColumns(); + this._emit(); + } + + private _getAllFilterColumns(): FilterColumn[]; + private _getAllFilterColumns(withCol: true): [number, FilterColumn][]; + private _getAllFilterColumns(withCol = false): [number, FilterColumn][] | FilterColumn[] { + const columns = Array.from(this._filterColumnByIndex.entries()); + if (withCol) { + return columns; + } + + return columns.map(([_, filterColumn]) => filterColumn); + } + + private _reCalcAllColumns(): void { + this._alreadyFilteredOutRows = EMPTY(); + this._getAllFilterColumns().forEach((filterColumn) => filterColumn.__clearCache()); + this._reCalcWithNoCacheColumns(); + } + + private _setCriteriaWithoutReCalc(col: number, criteria: IFilterColumn): void { + const range = this._range; + if (!range) { + throw new Error('[FilterModel] could not set criteria before a range is set!'); + } + + const { startColumn, endColumn } = range; + if (col > endColumn || col < startColumn) { + throw new Error(`[FilterModel] could not set criteria on column ${col} which is out of range!`); + } + + let filterColumn: FilterColumn; + if (this._filterColumnByIndex.has(col)) { + filterColumn = this._filterColumnByIndex.get(col)!; + } else { + filterColumn = new FilterColumn( + this.unitId, + this.subUnitId, + this._worksheet, + criteria, + { getAlreadyFilteredOutRows: () => this._alreadyFilteredOutRows } + ); + filterColumn.setRangeAndColumn(range, col); + + this._filterColumnByIndex.set(col, filterColumn); + } + + filterColumn.setCriteria(criteria); + } + + private _removeCriteria(col: number): void { + const filterColumn = this._filterColumnByIndex.get(col); + if (filterColumn) { + filterColumn.dispose(); + this._filterColumnByIndex.delete(col); + } + } + + private _emit(): void { + this._filteredOutRows$.next(this._alreadyFilteredOutRows); + } + + private _emitHasCriteria(): void { + this._hasCriteria$.next(this._filterColumnByIndex.size > 0); + } + + private _rebuildAlreadyFilteredOutRowsWithCache(): void { + const newFilteredOutRows = this._getAllFilterColumns() + .filter((filterColumn) => filterColumn.hasCache()) + .reduce((acc, filterColumn) => { + return mergeSets(acc, filterColumn.filteredOutRows!); + }, new Set()); + + this._alreadyFilteredOutRows = newFilteredOutRows; + } + + private _reCalcWithNoCacheColumns(): void { + const noCacheFilteredOutRows = this._getAllFilterColumns().filter((filterColumn) => !filterColumn.hasCache()); + for (const filterColumn of noCacheFilteredOutRows) { + const filteredRows = filterColumn.reCalc(); + if (filteredRows) { + this._alreadyFilteredOutRows = mergeSets(this._alreadyFilteredOutRows, filteredRows); + } + } + } +} + +interface IFilterColumnContext { + getAlreadyFilteredOutRows(): Set; +} + +/** + * This is the filter criteria on a specific column. + */ +export class FilterColumn extends Disposable { + private _filteredOutRows: Nullable> = null; + get filteredOutRows(): Readonly>> { return this._filteredOutRows; } + + /** Cache the filter function. */ + private _filterFn: Nullable = null; + + private _range: Nullable = null; + private _column: number = 0; + + private _filterByValues = false; + + constructor( + public readonly unitId: string, + public readonly subUnitId: string, + private readonly _worksheet: Worksheet, + + /** + * A `FilterColumn` instance should not be created without a filter criteria. + */ + private _criteria: IFilterColumn, + private readonly _filterColumnContext: IFilterColumnContext + ) { + super(); + } + + override dispose(): void { + super.dispose(); + + this._filteredOutRows = null; + } + + /** + * @internal + */ + __clearCache(): void { + this._filteredOutRows = null; + } + + serialize(): IFilterColumn { + if (!this._criteria) { + throw new Error('[FilterColumn]: could not serialize without a filter column!'); + } + + return Tools.deepClone({ + ...this._criteria, + colId: this._column, + }); + } + + hasCache(): boolean { + return this._filteredOutRows !== null; + } + + // The first row should be omitted! + setRangeAndColumn(range: IRange, column: number): void { + this._range = range; + this._column = column; + } + + setCriteria(criteria: IFilterColumn): void { + this._criteria = criteria; + this._generateFilterFn(); + + // clear cache + this._filteredOutRows = null; + } + + getColumnData(): Readonly { + return Tools.deepClone(this._criteria); + } + + /** + * Trigger new calculation on this `FilterModel` instance. + * + * @external DO NOT EVER call this method from `FilterColumn` itself. The whole process heavily relies on + * `filteredOutByOthers`, and it is more comprehensible if we let `FilterModel` take full control over the process. + */ + reCalc(): Readonly>> { + this._filteredOutRows = this.calc(this._filterColumnContext); + return this._filteredOutRows; + } + + calc(context: IFilterColumnContext): Readonly>> { + if (!this._filterFn) { + throw new Error('[FilterColumn] cannot calculate without a filter fn!'); + } + + if (!this._range) { + throw new Error('[FilterColumn] cannot calculate without a range!'); + } + + if (typeof this._column !== 'number') { + throw new TypeError('[FilterColumn] cannot calculate without a column offset!'); + } + + const column = this._column; + const iterateRange: IRange = { startColumn: column, endColumn: column, startRow: this._range.startRow + 1, endRow: this._range.endRow }; + const filteredOutRows = new Set(); + const filteredOutByOthers = context.getAlreadyFilteredOutRows(); + + // Merged cells are take into consideration here. + for (const range of this._worksheet.iterateByColumn(iterateRange, false, false)) { + const { row, rowSpan, col } = range; + + // If this row is already filtered out by others, we don't need to check it again. + // But it only works for non-vertically-merged cells. + if (filteredOutByOthers.has(row) && (!rowSpan || rowSpan === 1)) { + continue; + } + + const value = this._filterByValues + ? extractPureTextFromCell(this._worksheet.getCell(row, col)) + : getFilterValueForConditionalFiltering(this._worksheet, row, col); + if (!this._filterFn(value)) { + filteredOutRows.add(row); + + // Add all rows into filtered out rows if the cell is a merged cell. + if (rowSpan) { + for (let i = 1; i < rowSpan; i++) { + filteredOutRows.add(row + i); + } + } + } + } + + return filteredOutRows; + } + + private _generateFilterFn(): void { + if (!this._criteria) { + return; + } + + this._filterFn = generateFilterFn(this._criteria); + this._filterByValues = !!this._criteria.filters; + } +} + +/** + * Filter function is a close function which received a cell's content and determine this value is considered as + * "matched" and the corresponding row would not be filtered out. + */ +export type FilterFn = (value: Nullable) => boolean; + +/** + * This functions take a `IFilterColumn` as input and return a function that can be used to filter rows. + * @param column + * @returns the filter function that takes the cell's value and return a boolean. + */ +export function generateFilterFn(column: IFilterColumn): FilterFn { + if (column.filters) { + return filterByValuesFnFactory(column.filters); + } + + if (column.customFilters) { + return customFilterFnFactory(column.customFilters); + } + + throw new Error('[FilterModel]: other types of filters are not supported yet.'); +} + +function filterByValuesFnFactory(values: IFilters): FilterFn { + const includeBlank = !!values.blank; + const valuesSet = new Set(values.filters); + + return (value) => { + if (value === undefined || value === '') return includeBlank; + return valuesSet.has(typeof value === 'string' ? value : `${value}`); + }; +} + +function customFilterFnFactory(customFilters: ICustomFilters): FilterFn { + const customFilterFns: FilterFn[] = customFilters.customFilters.map((filter) => generateCustomFilterFn(filter)); + if (isCompoundCustomFilter(customFilterFns)) { + if (customFilters.and) { + return AND(customFilterFns); + } + + return OR(customFilterFns); + } + + return customFilterFns[0]; +} + +function AND(filterFns: [FilterFn, FilterFn]): FilterFn { + const [fn1, fn2] = filterFns; + return (value) => fn1(value) && fn2(value); +} + +function OR(filterFns: [FilterFn, FilterFn]): FilterFn { + const [fn1, fn2] = filterFns; + return (value) => fn1(value) || fn2(value); +} + +function isCompoundCustomFilter(filter: FilterFn[]): filter is [FilterFn, FilterFn] { + return filter.length === 2; +} + +// TODO@wzhudev: this is not ideal +function generateCustomFilterFn(filter: ICustomFilter): FilterFn { + const compare = filter.val; + + // Not NOT_EQUALS, if the compare cannot be ensured as number, we should treat it like test not matching. + // Otherwise it goes to numeric match. + if (filter.operator === CustomFilterOperator.NOT_EQUALS) { + const ensured = ensureNumeric(compare); + if (!ensured) return (value) => notEquals.fn(value, compare); + } + + // numeric match + if (isNumericFilterFn(filter.operator)) { + const ensured = ensureNumeric(compare); + if (!ensured) return () => false; + + const customFilterFn = getCustomFilterFn(filter.operator); + const ensuredNumber = Number(compare); + return (value) => customFilterFn.fn(value, ensuredNumber); + } + + // text match + const customFilterFn = getCustomFilterFn(filter.operator); + return (value) => customFilterFn.fn(value, compare); +} + +function getFilterValueForConditionalFiltering(worksheet: Worksheet, row: number, col: number): Nullable { + const interceptedCell = worksheet.getCell(row, col); + if (!interceptedCell) return null; + + const rawCell = worksheet.getCellRaw(row, col); + + if (interceptedCell && !rawCell) return extractFilterValueFromCell(interceptedCell); + + if (!rawCell) return null; + + if (interceptedCell.t === CellValueType.NUMBER && typeof interceptedCell.v === 'string') { + return rawCell.v as number; + } + + return extractFilterValueFromCell(rawCell); +} + +function extractFilterValueFromCell(cell: ICellData): string | number { + const richTextValue = cell.p?.body?.dataStream; + if (richTextValue) return richTextValue.trimEnd(); + + const rawValue = cell.v; + + if (typeof rawValue === 'string') { + if (cell.t === CellValueType.BOOLEAN) return rawValue.toUpperCase(); + return rawValue; + }; + + if (typeof rawValue === 'number') { + if (cell.t === CellValueType.BOOLEAN) return rawValue ? 'TRUE' : 'FALSE'; + return rawValue; + }; + + if (typeof rawValue === 'boolean') return rawValue ? 'TRUE' : 'FALSE'; + + return ''; +} diff --git a/packages/sheets-filter/src/models/types.ts b/packages/sheets-filter/src/models/types.ts new file mode 100644 index 000000000000..5d7a04724767 --- /dev/null +++ b/packages/sheets-filter/src/models/types.ts @@ -0,0 +1,85 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { BooleanNumber, IRange } from '@univerjs/core'; + +// NOTE: Please refer to 18.3.2 AutoFilter Settings. Properties of this interface would be added in the future. +// Please make sure that it is backward compatible. + +export interface IAutoFilter { + ref: IRange; + + filterColumns?: IFilterColumn[]; + cachedFilteredOut?: number[]; +} + +export interface IFilterColumn { + colId: number; + + /** + * The filter value could be an empty string, which means . + */ + filters?: IFilters; + customFilters?: ICustomFilters; +}; + +export interface IFilters { + blank?: true; + + filters?: Array; +} + +export interface ICustomFilters { + and?: BooleanNumber.TRUE; + + /** Max 2 capacity. */ + customFilters: [ICustomFilter] | [ICustomFilter, ICustomFilter]; +} + +export interface IDynamicFilter { + val: string | number; + + type: DynamicFilterOperator; +} + +export interface ICustomFilter { + val: string | number; + + /** This field may be empty. */ + operator?: CustomFilterOperator; +} + +/** + * These basic operators are defined in 18.18.31. + * + * Some comparison such as startsWith, endsWith, contains, doesNotContain, isBlank, isNotBlank are not defined in OOXML. + * They are represented by regex-like values. + */ +export enum CustomFilterOperator { + EQUAL = 'equal', + GREATER_THAN = 'greaterThan', + GREATER_THAN_OR_EQUAL = 'greaterThanOrEqual', + LESS_THAN = 'lessThan', + LESS_THAN_OR_EQUAL = 'lessThanOrEqual', + NOT_EQUALS = 'notEqual', +} + +/** + * Not used now. Would be used in the future. + */ +export enum DynamicFilterOperator { + ABOVE_AVERAGE = 'aboveAverage', +} diff --git a/packages/sheets-filter/src/plugin.ts b/packages/sheets-filter/src/plugin.ts new file mode 100644 index 000000000000..c4a32c92072d --- /dev/null +++ b/packages/sheets-filter/src/plugin.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Plugin, UniverInstanceType } from '@univerjs/core'; +import type { Dependency } from '@wendellhu/redi'; +import { Inject, Injector } from '@wendellhu/redi'; + +import { SheetsFilterService } from './services/sheet-filter.service'; +import { SheetsFilterController } from './controllers/sheets-fiter.controller'; + +const NAME = 'UNIVER_SHEETS_FILTER_PLUGIN'; + +export class UniverSheetsFilterPlugin extends Plugin { + static override type = UniverInstanceType.SHEET; + static override pluginName = NAME; + + constructor(_config: unknown, @Inject(Injector) protected readonly _injector: Injector) { + super(); + } + + override onStarting(injector: Injector): void { + ([ + [SheetsFilterService], + [SheetsFilterController], + ] as Dependency[]).forEach((d) => injector.add(d)); + } +} diff --git a/packages/sheets-filter/src/services/sheet-filter.service.ts b/packages/sheets-filter/src/services/sheet-filter.service.ts new file mode 100644 index 000000000000..aa619cbcb0ee --- /dev/null +++ b/packages/sheets-filter/src/services/sheet-filter.service.ts @@ -0,0 +1,206 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Nullable, Workbook } from '@univerjs/core'; +import { CommandType, Disposable, + fromCallback, + ICommandService, + IResourceManagerService, + IUniverInstanceService, + LifecycleStages, + OnLifecycle, + UniverInstanceType, +} from '@univerjs/core'; +import { BehaviorSubject, filter, merge, of, switchMap } from 'rxjs'; + +import { FilterModel } from '../models/filter-model'; +import { + ReCalcSheetsFilterMutation, + RemoveSheetsFilterMutation, + SetSheetsFilterCriteriaMutation, + SetSheetsFilterRangeMutation, +} from '../commands/sheets-filter.mutation'; +import type { IAutoFilter } from '../models/types'; + +export const FILTER_MUTATIONS = new Set([ + SetSheetsFilterRangeMutation.id, + SetSheetsFilterCriteriaMutation.id, + RemoveSheetsFilterMutation.id, + ReCalcSheetsFilterMutation.id, +]); + +type WorksheetID = string; +export interface ISheetsFilterResource { + [key: WorksheetID]: IAutoFilter; +} + +export const SHEET_FILTER_SNAPSHOT_ID = 'SHEET_FILTER_PLUGIN'; + +/** + * This service is responsible for managing filter models, especially their lifecycle. + */ +@OnLifecycle(LifecycleStages.Ready, SheetsFilterService) +export class SheetsFilterService extends Disposable { + private readonly _filterModels = new Map>(); + + private readonly _loadedUnitId$ = new BehaviorSubject>(null); + readonly loadedUnitId$ = this._loadedUnitId$.asObservable(); + + private readonly _activeFilterModel$ = new BehaviorSubject>(null); + /** An observable value emitting the current Workbook's active Worksheet's filter model (if there is one). */ + readonly activeFilterModel$ = this._activeFilterModel$.asObservable(); + /** The current Workbook's active Worksheet's filter model (if there is one). */ + get activeFilterModel(): Nullable { return this._activeFilterModel$.getValue(); } + + constructor( + @IResourceManagerService private readonly _resourcesManagerService: IResourceManagerService, + @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, + @ICommandService private readonly _commandService: ICommandService) { + super(); + + this._initModel(); + this._initActiveFilterModel(); + } + + /** + * + * @param unitId + * @param subUnitId + */ + ensureFilterModel(unitId: string, subUnitId: string): FilterModel { + const already = this.getFilterModel(unitId, subUnitId); + if (already) { + return already; + } + + const workbook = this._univerInstanceService.getUniverSheetInstance(unitId); + if (!workbook) { + throw new Error(`[SheetsFilterService]: could not create "FilterModel" on a non-existing workbook ${unitId}!`); + } + + const worksheet = workbook.getSheetBySheetId(subUnitId); + if (!worksheet) { + throw new Error(`[SheetsFilterService]: could not create "FilterModel" on a non-existing worksheet ${subUnitId}!`); + } + + const filterModel = new FilterModel(unitId, subUnitId, worksheet); + this._cacheFilterModel(unitId, subUnitId, filterModel); + return filterModel; + } + + getFilterModel(unitId: string, subUnitId: string): Nullable { + return this._filterModels.get(unitId)?.get(subUnitId) ?? null; + } + + removeFilterModel(unitId: string, subUnitId: string): boolean { + const already = this.getFilterModel(unitId, subUnitId); + if (already) { + already.dispose(); + this._filterModels.get(unitId)!.delete(subUnitId); + return true; + } + + return false; + } + + private _updateActiveFilterModel() { + let workbook: Nullable; + try { + workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET); + if (!workbook) { + this._activeFilterModel$.next(null); + return; + } + } catch (err) { + return; + } + + const activeSheet = workbook.getActiveSheet(); + if (!activeSheet) { + this._activeFilterModel$.next(null); + return; + } + + const unitId = activeSheet.getUnitId(); + const subUnitId = activeSheet.getSheetId(); + const filterModel = this.getFilterModel(unitId, subUnitId); + this._activeFilterModel$.next(filterModel); + } + + private _initActiveFilterModel() { + this.disposeWithMe( + merge( + // source1: executing filter related mutations + fromCallback(this._commandService.onCommandExecuted) + .pipe(filter(([command]) => command.type === CommandType.MUTATION && FILTER_MUTATIONS.has(command.id))), + + // source2: activte sheet changes + this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.SHEET) + .pipe(switchMap((workbook) => workbook?.activeSheet$ ?? of(null))) + ).subscribe(() => this._updateActiveFilterModel())); + } + + private _serializeAutoFiltersForUnit(unitId: string): string { + const allFilterModels = this._filterModels.get(unitId); + if (!allFilterModels) { + return '{}'; + } + + const json: ISheetsFilterResource = {}; + allFilterModels.forEach((model, worksheetId) => { + json[worksheetId] = model.serialize(); + }); + + return JSON.stringify(json); + } + + private _deserializeAutoFiltersForUnit(unitId: string, json: ISheetsFilterResource) { + const workbook = this._univerInstanceService.getUniverSheetInstance(unitId)!; + Object.keys(json).forEach((worksheetId: WorksheetID) => { + const autoFilter = json[worksheetId]!; + const filterModel = FilterModel.deserialize(unitId, worksheetId, workbook.getSheetBySheetId(worksheetId)!, autoFilter); + this._cacheFilterModel(unitId, worksheetId, filterModel); + }); + } + + private _initModel() { + this._resourcesManagerService.registerPluginResource({ + pluginName: SHEET_FILTER_SNAPSHOT_ID, + businesses: [2], + toJson: (id) => this._serializeAutoFiltersForUnit(id), + parseJson: (json) => JSON.parse(json), + onLoad: (unitId, value) => { + this._deserializeAutoFiltersForUnit(unitId, value); + this._loadedUnitId$.next(unitId); + this._updateActiveFilterModel(); + }, + onUnLoad: (unitId: string) => { + const allFilterModels = this._filterModels.get(unitId); + if (allFilterModels) { + allFilterModels.forEach((model) => model.dispose()); + this._filterModels.delete(unitId); + } + }, + }); + } + + private _cacheFilterModel(unitId: string, subUnitId: string, filterModel: FilterModel) { + if (!this._filterModels.has(unitId)) { + this._filterModels.set(unitId, new Map()); + } + this._filterModels.get(unitId)!.set(subUnitId, filterModel); + } +} diff --git a/packages/sheets-filter/src/utils.ts b/packages/sheets-filter/src/utils.ts new file mode 100644 index 000000000000..ec427495d665 --- /dev/null +++ b/packages/sheets-filter/src/utils.ts @@ -0,0 +1,54 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IMutationInfo, Nullable } from '@univerjs/core'; +import type { ISetSheetsFilterCriteriaMutationParams } from './commands/sheets-filter.mutation'; +import { SetSheetsFilterCriteriaMutation } from './commands/sheets-filter.mutation'; + +interface ILine { + start: number; + end: number; +} +export function lineIntersect(line1: ILine, line2: ILine): boolean { + return line1.start <= line2.end && line1.end >= line2.start; +} + +export function lineContains(line1: ILine, line2: ILine): boolean { + return line1.start <= line2.start && line1.end >= line2.end; +} + +export function objectsShaker(target: Nullable[], isEqual: (o1: T, o2: T) => boolean) { + for (let i = 0; i < target.length; i++) { + let cur = i; + if (target[i]) { + for (let j = i + 1; j < target.length; j++) { + if (target[cur] && target[j] && isEqual(target[cur]!, target[j]!)) { + target[cur] = null; + cur = j; + } + } + } + } + return target.filter((o) => o !== null) as T[]; +}; + +export function mergeSetFilterCriteria(mutations: IMutationInfo[]) { + return objectsShaker(mutations, (o1, o2) => + o1.id === SetSheetsFilterCriteriaMutation.id && o2.id === SetSheetsFilterCriteriaMutation.id + && (o1.params as ISetSheetsFilterCriteriaMutationParams).unitId === (o2.params as ISetSheetsFilterCriteriaMutationParams).unitId + && (o1.params as ISetSheetsFilterCriteriaMutationParams).subUnitId === (o2.params as ISetSheetsFilterCriteriaMutationParams).subUnitId + && (o1.params as ISetSheetsFilterCriteriaMutationParams).col === (o2.params as ISetSheetsFilterCriteriaMutationParams).col); +} diff --git a/packages/sheets-filter/src/vite-env.d.ts b/packages/sheets-filter/src/vite-env.d.ts new file mode 100644 index 000000000000..11f02fe2a006 --- /dev/null +++ b/packages/sheets-filter/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/sheets-filter/tsconfig.json b/packages/sheets-filter/tsconfig.json new file mode 100644 index 000000000000..d676ad2a20dc --- /dev/null +++ b/packages/sheets-filter/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@univerjs/shared/tsconfigs/base", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "references": [{ "path": "./tsconfig.node.json" }], + "include": ["src"] +} diff --git a/packages/sheets-filter/tsconfig.node.json b/packages/sheets-filter/tsconfig.node.json new file mode 100644 index 000000000000..e53dac88688c --- /dev/null +++ b/packages/sheets-filter/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": "@univerjs/shared/tsconfigs/node", + "include": ["vite.config.ts"] +} diff --git a/packages/sheets-filter/vite.config.ts b/packages/sheets-filter/vite.config.ts new file mode 100644 index 000000000000..67b2fff8f299 --- /dev/null +++ b/packages/sheets-filter/vite.config.ts @@ -0,0 +1,7 @@ +import createViteConfig from '@univerjs/shared/vite'; +import pkg from './package.json'; + +export default ({ mode }) => createViteConfig({}, { + mode, + pkg, +}); diff --git a/packages/sheets-find-replace/src/controllers/sheet-find-replace.controller.ts b/packages/sheets-find-replace/src/controllers/sheet-find-replace.controller.ts index 47eacf438542..9d624add4cf2 100644 --- a/packages/sheets-find-replace/src/controllers/sheet-find-replace.controller.ts +++ b/packages/sheets-find-replace/src/controllers/sheet-find-replace.controller.ts @@ -358,13 +358,20 @@ export class SheetFindModel extends FindModel { const results: ISheetCellMatch[] = []; const subUnitId = worksheet.getSheetId(); - const iter = (query.findDirection === FindDirection.COLUMN ? worksheet.iterateByColumn : worksheet.iterateByRow).bind(worksheet)(range); - while (true) { - const { done, value } = iter.next(); - if (done) break; + const iter = (query.findDirection === FindDirection.COLUMN + ? worksheet.iterateByColumn + : worksheet.iterateByRow + ).bind(worksheet)(range); + for (const value of iter) { const { row, col, colSpan, rowSpan, value: cellData } = value; - if (dedupeFn?.(row, col)) continue; + if (dedupeFn?.(row, col) || !cellData) { + continue; + }; + + if (worksheet.getRowFiltered(row)) { + continue; + } const { hit, replaceable, isFormula } = hitCell(worksheet, row, col, query, cellData); if (hit) { @@ -475,7 +482,7 @@ export class SheetFindModel extends FindModel { const { startX, startY } = startPosition; const { endX, endY } = endPosition; - const rowHidden = !worksheet.getRowVisible(startRow); + const rowHidden = !worksheet.getRowRawVisible(startRow); const columnHidden = !worksheet.getColVisible(startColumn); const inHiddenRange = rowHidden || columnHidden; diff --git a/packages/sheets-find-replace/src/plugin.ts b/packages/sheets-find-replace/src/plugin.ts index 96bbc9a8c326..34a1825de35c 100644 --- a/packages/sheets-find-replace/src/plugin.ts +++ b/packages/sheets-find-replace/src/plugin.ts @@ -37,8 +37,6 @@ export class UniverSheetsFindReplacePlugin extends Plugin { } override onStarting(injector: Injector): void { - ([[SheetsFindReplaceController]] as Dependency[]).forEach((d) => { - injector.add(d); - }); + ([[SheetsFindReplaceController]] as Dependency[]).forEach((d) => injector.add(d)); } } diff --git a/packages/sheets-formula/src/commands/operations/__tests__/create-command-test-bed.ts b/packages/sheets-formula/src/commands/operations/__tests__/create-command-test-bed.ts index d68525b5f35c..07aa076b3f30 100644 --- a/packages/sheets-formula/src/commands/operations/__tests__/create-command-test-bed.ts +++ b/packages/sheets-formula/src/commands/operations/__tests__/create-command-test-bed.ts @@ -45,7 +45,7 @@ const TEST_WORKBOOK_DATA_DEMO: IWorkbookData = { styles: {}, }; -export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]) { +export function createCommandTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]) { const univer = new Univer(); const injector = univer.__getInjector(); const get = injector.get.bind(injector); @@ -78,7 +78,7 @@ export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencie } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || TEST_WORKBOOK_DATA_DEMO); + const sheet = univer.createUniverSheet(workbookData || TEST_WORKBOOK_DATA_DEMO); const univerInstanceService = get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets-formula/src/controllers/__tests__/create-command-test-bed.ts b/packages/sheets-formula/src/controllers/__tests__/create-command-test-bed.ts index a5cbeab5dcad..688bdbfdbed1 100644 --- a/packages/sheets-formula/src/controllers/__tests__/create-command-test-bed.ts +++ b/packages/sheets-formula/src/controllers/__tests__/create-command-test-bed.ts @@ -56,7 +56,7 @@ export interface ITestBed { sheet: Workbook; } -export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]): ITestBed { +export function createCommandTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]): ITestBed { const univer = new Univer(); const injector = univer.__getInjector(); const get = injector.get.bind(injector); @@ -94,7 +94,7 @@ export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencie } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || TEST_WORKBOOK_DATA_DEMO); + const sheet = univer.createUniverSheet(workbookData || TEST_WORKBOOK_DATA_DEMO); const univerInstanceService = injector.get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets-formula/src/controllers/__tests__/formula-clipboard.controller.spec.ts b/packages/sheets-formula/src/controllers/__tests__/formula-clipboard.controller.spec.ts index 9fa2104bc7fa..c3233415b92c 100644 --- a/packages/sheets-formula/src/controllers/__tests__/formula-clipboard.controller.spec.ts +++ b/packages/sheets-formula/src/controllers/__tests__/formula-clipboard.controller.spec.ts @@ -69,11 +69,8 @@ describe('Test paste with formula', () => { const unitId = 'test'; const subUnitId = 'sheet1'; const range = { - startRow: 12, - startColumn: 2, - endRow: 12, - endColumn: 3, - rangeType: 0, + rows: [12], + cols: [2, 3], }; const matrix = new ObjectMatrix({ 0: { @@ -94,11 +91,8 @@ describe('Test paste with formula', () => { const copyInfo = { copyRange: { - startRow: 0, - startColumn: 2, - endRow: 0, - endColumn: 3, - rangeType: 0, + rows: [0], + cols: [2, 3], }, copyType: COPY_TYPE.COPY, pasteType: PREDEFINED_HOOK_NAME.DEFAULT_PASTE, @@ -174,11 +168,8 @@ describe('Test paste with formula', () => { const unitId = 'test'; const subUnitId = 'sheet1'; const range = { - startRow: 5, - startColumn: 5, - endRow: 8, - endColumn: 8, - rangeType: 0, + rows: [5, 6, 7, 8], + cols: [5, 6, 7, 8], }; const matrix = new ObjectMatrix({ 0: { @@ -326,11 +317,8 @@ describe('Test paste with formula', () => { const copyInfo = { copyType: COPY_TYPE.COPY, copyRange: { - startRow: 0, - startColumn: 5, - endRow: 3, - endColumn: 8, - rangeType: 0, + rows: [0, 1, 2, 3], + cols: [5, 6, 7, 8], }, pasteType: PREDEFINED_HOOK_NAME.DEFAULT_PASTE, }; diff --git a/packages/sheets-formula/src/controllers/formula-clipboard.controller.ts b/packages/sheets-formula/src/controllers/formula-clipboard.controller.ts index 5d3794a1dd3a..8b155f87e179 100644 --- a/packages/sheets-formula/src/controllers/formula-clipboard.controller.ts +++ b/packages/sheets-formula/src/controllers/formula-clipboard.controller.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { ICellData, IMutationInfo, IRange, Workbook } from '@univerjs/core'; +import type { ICellData, IMutationInfo, Workbook } from '@univerjs/core'; import { Disposable, isFormulaId, @@ -29,8 +29,8 @@ import { import { LexerTreeBuilder } from '@univerjs/engine-formula'; import type { ISetRangeValuesMutationParams } from '@univerjs/sheets'; import { SetRangeValuesMutation, SetRangeValuesUndoMutationFactory } from '@univerjs/sheets'; -import type { ICellDataWithSpanInfo, ICopyPastePayload, ISheetClipboardHook, ISheetRangeLocation } from '@univerjs/sheets-ui'; import { COPY_TYPE, ISheetClipboardService, PREDEFINED_HOOK_NAME } from '@univerjs/sheets-ui'; +import type { ICellDataWithSpanInfo, ICopyPastePayload, IDiscreteRange, ISheetClipboardHook, ISheetDiscreteRangeLocation } from '@univerjs/sheets-ui'; import type { IAccessor } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; @@ -78,8 +78,8 @@ export class FormulaClipboardController extends Disposable { } private _onPasteCells( - pasteFrom: ISheetRangeLocation | null, - pasteTo: ISheetRangeLocation, + pasteFrom: ISheetDiscreteRangeLocation | null, + pasteTo: ISheetDiscreteRangeLocation, data: ObjectMatrix, payload: ICopyPastePayload, isSpecialPaste: boolean @@ -111,12 +111,12 @@ export class FormulaClipboardController extends Disposable { export function getSetCellFormulaMutations( unitId: string, subUnitId: string, - range: IRange, + range: IDiscreteRange, matrix: ObjectMatrix, accessor: IAccessor, copyInfo: { copyType: COPY_TYPE; - copyRange?: IRange; + copyRange?: IDiscreteRange; pasteType: string; }, lexerTreeBuilder: LexerTreeBuilder, @@ -124,7 +124,6 @@ export function getSetCellFormulaMutations( ) { const redoMutationsInfo: IMutationInfo[] = []; const undoMutationsInfo: IMutationInfo[] = []; - const { startColumn, startRow } = range; const valueMatrix = new ObjectMatrix(); const formulaIdMap = new Map(); @@ -132,16 +131,11 @@ export function getSetCellFormulaMutations( let copyRowLength = 0; let copyColumnLength = 0; - let copyRangeStartRow = 0; - let copyRangeStartColumn = 0; - - if (copyInfo) { + if (copyInfo && copyInfo.copyRange) { const { copyType, copyRange } = copyInfo; if (copyType === COPY_TYPE.COPY && copyRange) { - copyRangeStartRow = copyRange.startRow; - copyRangeStartColumn = copyRange.startColumn; - copyRowLength = copyRange.endRow - copyRangeStartRow + 1; - copyColumnLength = copyRange.endColumn - copyRangeStartColumn + 1; + copyRowLength = copyRange.rows.length; + copyColumnLength = copyRange.cols.length; } } @@ -176,8 +170,10 @@ export function getSetCellFormulaMutations( formulaId = Tools.generateRandomId(6); formulaIdMap.set(index, formulaId); - const offsetX = col + startColumn - (copyRangeStartColumn + colIndex); - const offsetY = row + startRow - (copyRangeStartRow + rowIndex); + const copyX = copyInfo?.copyRange ? copyInfo?.copyRange?.cols[colIndex] : colIndex; + const copyY = copyInfo?.copyRange ? copyInfo?.copyRange?.rows[rowIndex] : rowIndex; + const offsetX = range.cols[col] - copyX; + const offsetY = range.rows[row] - copyY; const shiftedFormula = lexerTreeBuilder.moveFormulaRefOffset(originalFormula, offsetX, offsetY); valueObject.si = formulaId; @@ -193,7 +189,7 @@ export function getSetCellFormulaMutations( } } - valueMatrix.setValue(row + startRow, col + startColumn, valueObject); + valueMatrix.setValue(range.rows[row], range.cols[col], valueObject); }); // set cell value and style const setValuesMutation: ISetRangeValuesMutationParams = { diff --git a/packages/sheets-numfmt/src/components/stories/Panel.stories.tsx b/packages/sheets-numfmt/src/components/stories/Panel.stories.tsx index f1c5ae0c1d6c..26fa12408ac8 100644 --- a/packages/sheets-numfmt/src/components/stories/Panel.stories.tsx +++ b/packages/sheets-numfmt/src/components/stories/Panel.stories.tsx @@ -15,28 +15,16 @@ */ import type { Meta, StoryObj } from '@storybook/react'; -import { LocaleService, LocaleType, ThemeService } from '@univerjs/core'; +import { LocaleService, LocaleType } from '@univerjs/core'; import type { Dependency } from '@wendellhu/redi'; -import { Injector } from '@wendellhu/redi'; -import { connectInjector, RediContext } from '@wendellhu/redi/react-bindings'; -import React, { useContext, useMemo, useState } from 'react'; +import { RediContext } from '@wendellhu/redi/react-bindings'; +import React, { useContext, useState } from 'react'; import { enUS, zhCN } from '../../locale'; import type { ISheetNumfmtPanelProps } from '../index'; import { SheetNumfmtPanel } from '../index'; import { UserHabitController } from '../../controllers/user-habit.controller'; -const Index = (props: any) => { - const inject = useMemo(() => new Injector([[LocaleService], [ThemeService]]), []); - const Wrap = useMemo(() => connectInjector(SheetNumfmtPanel, inject), []) as any; - - useMemo(() => { - const localeService = inject.get(LocaleService); - localeService.load({ zhCN }); - }, []); - - return ; -}; const meta: Meta = { title: 'numfmt', parameters: { @@ -70,8 +58,7 @@ export const Test: StoryObj = { { - }} + onChange={(_pattern) => {}} /> ); diff --git a/packages/sheets-ui/src/commands/commands/__tests__/create-command-test-bed.ts b/packages/sheets-ui/src/commands/commands/__tests__/create-command-test-bed.ts index ae22a5b42727..41c364c9ea71 100644 --- a/packages/sheets-ui/src/commands/commands/__tests__/create-command-test-bed.ts +++ b/packages/sheets-ui/src/commands/commands/__tests__/create-command-test-bed.ts @@ -26,7 +26,7 @@ import { Univer, UniverInstanceType, } from '@univerjs/core'; -import { BorderStyleManagerService, SelectionManagerService, SheetInterceptorService } from '@univerjs/sheets'; +import { BorderStyleManagerService, SelectionManagerService, SheetInterceptorService, SheetPermissionService } from '@univerjs/sheets'; import type { Dependency } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; @@ -76,7 +76,7 @@ export interface ITestBed { sheet: Workbook; } -export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]): ITestBed { +export function createCommandTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]): ITestBed { const univer = new Univer(); const injector = univer.__getInjector(); @@ -98,13 +98,14 @@ export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencie injector.add([BorderStyleManagerService]); injector.add([SheetInterceptorService]); injector.add([LexerTreeBuilder]); + injector.add([SheetPermissionService]); dependencies?.forEach((d) => injector.add(d)); } } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || getTestWorkbookDataDemo()); + const sheet = univer.createUniverSheet(workbookData || getTestWorkbookDataDemo()); const univerInstanceService = injector.get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets-ui/src/commands/commands/__tests__/create-selection-command-test-bed.ts b/packages/sheets-ui/src/commands/commands/__tests__/create-selection-command-test-bed.ts index d85fcb8932eb..c4a0f18db543 100644 --- a/packages/sheets-ui/src/commands/commands/__tests__/create-selection-command-test-bed.ts +++ b/packages/sheets-ui/src/commands/commands/__tests__/create-selection-command-test-bed.ts @@ -30,8 +30,8 @@ import { } from '../set-frozen.command'; import { ExpandSelectionCommand, MoveSelectionCommand, SelectAllCommand } from '../set-selection.command'; -export function createSelectionCommandTestBed(workbookConfig?: IWorkbookData) { - const { univer, get, sheet } = createCommandTestBed(workbookConfig || SIMPLE_SELECTION_WORKBOOK_DATA, [ +export function createSelectionCommandTestBed(workbookData?: IWorkbookData) { + const { univer, get, sheet } = createCommandTestBed(workbookData || SIMPLE_SELECTION_WORKBOOK_DATA, [ [ShortcutExperienceService], ]); @@ -47,8 +47,8 @@ export function createSelectionCommandTestBed(workbookConfig?: IWorkbookData) { }; } -export function createFrozenCommandTestBed(workbookConfig?: IWorkbookData) { - const { univer, get, sheet } = createCommandTestBed(workbookConfig || SIMPLE_SELECTION_WORKBOOK_DATA, [ +export function createFrozenCommandTestBed(workbookData?: IWorkbookData) { + const { univer, get, sheet } = createCommandTestBed(workbookData || SIMPLE_SELECTION_WORKBOOK_DATA, [ [ShortcutExperienceService], [ScrollManagerService], ]); diff --git a/packages/sheets-ui/src/commands/commands/__tests__/set-selections.command.spec.ts b/packages/sheets-ui/src/commands/commands/__tests__/set-selections.command.spec.ts index f0fa5f5e85f3..38243575c2b9 100644 --- a/packages/sheets-ui/src/commands/commands/__tests__/set-selections.command.spec.ts +++ b/packages/sheets-ui/src/commands/commands/__tests__/set-selections.command.spec.ts @@ -126,18 +126,6 @@ describe('Test commands used for change selections', () => { return worksheet.getColumnCount(); } - function getRowVisible(row: number): boolean { - const workbook = get(IUniverInstanceService).getCurrentUnitForType(UniverInstanceType.SHEET)!; - const worksheet = workbook.getActiveSheet(); - return worksheet.getRowVisible(row); - } - - function getColVisible(col: number): boolean { - const workbook = get(IUniverInstanceService).getCurrentUnitForType(UniverInstanceType.SHEET)!; - const worksheet = workbook.getActiveSheet(); - return worksheet.getColVisible(col); - } - function selectRow(rowStart: number, rowEnd: number): void { const selectionManagerService = get(SelectionManagerService); const endColumn = getColCount() - 1; diff --git a/packages/sheets-ui/src/commands/commands/delete-range-move-up-confirm.command.ts b/packages/sheets-ui/src/commands/commands/delete-range-move-up-confirm.command.ts index 42df3164b202..842786be3794 100644 --- a/packages/sheets-ui/src/commands/commands/delete-range-move-up-confirm.command.ts +++ b/packages/sheets-ui/src/commands/commands/delete-range-move-up-confirm.command.ts @@ -39,7 +39,21 @@ export const DeleteRangeMoveUpConfirmCommand: ICommand = { let range = selection[0].range; if (!range) return false; - range = { ...range, endRow: worksheet.getColumnCount() - 1 }; + range = { ...range, endRow: worksheet.getRowCount() - 1 }; + + for (let i = range.startRow; i <= range.endRow; i++) { + if (worksheet.getRowFiltered(i)) { + const result = await confirmService.confirm({ + id: DeleteRangeMoveUpConfirmCommand.id, + title: { title: localeService.t('filter.confirm.error') }, + children: { title: localeService.t('filter.confirm.notAllowedToInsertRange') }, + confirmText: localeService.t('button.confirm'), + }); + if (result) { + return false; + } + } + } const getColLength = (range: IRange) => range.endColumn - range.startColumn; const mergeData = worksheet.getMergeData().find((mergeRange) => { diff --git a/packages/sheets-ui/src/commands/commands/insert-range-move-down-confirm.command.ts b/packages/sheets-ui/src/commands/commands/insert-range-move-down-confirm.command.ts index 915ad816f6a4..c7bba16769a7 100644 --- a/packages/sheets-ui/src/commands/commands/insert-range-move-down-confirm.command.ts +++ b/packages/sheets-ui/src/commands/commands/insert-range-move-down-confirm.command.ts @@ -43,7 +43,21 @@ export const InsertRangeMoveDownConfirmCommand: ICommand = { if (!range) { return false; } - range = { ...range, endRow: worksheet.getColumnCount() - 1 }; + range = { ...range, endRow: worksheet.getRowCount() - 1 }; + + for (let i = range.startRow; i <= range.endRow; i++) { + if (worksheet.getRowFiltered(i)) { + const result = await confirmService.confirm({ + id: InsertRangeMoveDownConfirmCommand.id, + title: { title: localeService.t('filter.confirm.error') }, + children: { title: localeService.t('filter.confirm.notAllowedToInsertRange') }, + confirmText: localeService.t('button.confirm'), + }); + if (result) { + return false; + } + } + } const getColLength = (range: IRange) => range.endColumn - range.startColumn; const mergeData = worksheet.getMergeData().find((mergeRange) => { @@ -55,6 +69,7 @@ export const InsertRangeMoveDownConfirmCommand: ICommand = { return commandService.executeCommand(InsertRangeMoveDownCommand.id); } + const result = await confirmService.confirm({ id: InsertRangeMoveDownConfirmCommand.id, title: { title: localeService.t('merge.confirm.waring') }, diff --git a/packages/sheets-ui/src/commands/commands/set-format-painter.command.ts b/packages/sheets-ui/src/commands/commands/set-format-painter.command.ts index e1ca00e5b78d..e96a07a51587 100644 --- a/packages/sheets-ui/src/commands/commands/set-format-painter.command.ts +++ b/packages/sheets-ui/src/commands/commands/set-format-painter.command.ts @@ -35,7 +35,6 @@ import { AddWorksheetMergeMutation, getAddMergeMutationRangeByType, getSheetCommandTarget, - INTERCEPTOR_POINT, RemoveMergeUndoMutationFactory, RemoveWorksheetMergeMutation, SelectionManagerService, @@ -43,6 +42,7 @@ import { SetRangeValuesMutation, SetRangeValuesUndoMutationFactory, SheetInterceptorService, + SheetPermissionService, } from '@univerjs/sheets'; import type { IAccessor } from '@wendellhu/redi'; @@ -111,12 +111,18 @@ export const ApplyFormatPainterCommand: ICommand = { if (!target) return false; const { worksheet, unitId, subUnitId } = target; + const sheetPermissionService = accessor.get(SheetPermissionService); + const { styleValues: value, styleRange: range, mergeRanges, } = params; + if (!sheetPermissionService.getSheetEditable(unitId, subUnitId)) { + return false; + } + const currentSelections = range ? [range] : selectionManagerService.getSelectionRanges(); if (!currentSelections || !currentSelections.length) { return false; @@ -155,15 +161,6 @@ export const ApplyFormatPainterCommand: ICommand = { setRangeValuesMutationParams ); - if ( - !sheetInterceptorService.fetchThroughInterceptors(INTERCEPTOR_POINT.PERMISSION)(null, { - id: SetRangeValuesCommand.id, - params: setRangeValuesMutationParams, - }) - ) { - return false; - } - const setValueMutationResult = commandService.syncExecuteCommand( SetRangeValuesMutation.id, setRangeValuesMutationParams diff --git a/packages/sheets-ui/src/commands/commands/set-selection.command.ts b/packages/sheets-ui/src/commands/commands/set-selection.command.ts index e1f79c44a018..c40daddc9ae2 100644 --- a/packages/sheets-ui/src/commands/commands/set-selection.command.ts +++ b/packages/sheets-ui/src/commands/commands/set-selection.command.ts @@ -39,7 +39,6 @@ import { shrinkToNextGapRange, } from './utils/selection-utils'; -// TODO@wzhudev: we also need to handle when the current selection is the whole spreadsheet, whole rows or whole columns // TODO@DR-UNIVER: moveStepPage and moveStepEnd implement export enum JumpOver { @@ -92,7 +91,6 @@ export const MoveSelectionCommand: ICommand = { // If there are changes to the selection, clear the start position saved by the tab. // This function works in conjunction with the enter and tab shortcuts. - // TODO@wzhudev: this should be removed to sheets-ui, listening command execution accessor.get(ShortcutExperienceService).remove({ unitId: workbook.getUnitId(), sheetId: worksheet.getSheetId(), @@ -282,9 +280,7 @@ export const ExpandSelectionCommand: ICommand = { id: 'sheet.command.expand-selection', type: CommandType.COMMAND, handler: async (accessor, params) => { - if (!params) { - return false; - } + if (!params) return false; const target = getSheetCommandTarget(accessor.get(IUniverInstanceService)); if (!target) return false; @@ -292,16 +288,12 @@ export const ExpandSelectionCommand: ICommand = { const { worksheet, unitId, subUnitId } = target; const selection = accessor.get(SelectionManagerService).getLast(); - if (!selection) { - return false; - } - + if (!selection) return false; const { range: startRange, primary } = selection; const { jumpOver, direction } = params; const isShrink = checkIfShrink(selection, direction, worksheet); - const destRange = !isShrink ? jumpOver === JumpOver.moveGap ? expandToNextGapRange(startRange, direction, worksheet) @@ -309,7 +301,6 @@ export const ExpandSelectionCommand: ICommand = { : jumpOver === JumpOver.moveGap ? shrinkToNextGapRange( startRange, - // TODO: should fix on SelectionManagerService's side { ...Rectangle.clone(primary), rangeType: RANGE_TYPE.NORMAL }, direction, worksheet diff --git a/packages/sheets-ui/src/commands/commands/utils/selection-utils.ts b/packages/sheets-ui/src/commands/commands/utils/selection-utils.ts index 424aa4588d29..06ebb98198f1 100644 --- a/packages/sheets-ui/src/commands/commands/utils/selection-utils.ts +++ b/packages/sheets-ui/src/commands/commands/utils/selection-utils.ts @@ -31,6 +31,8 @@ export interface IExpandParams { down?: boolean; } +// TODO@wzhudev: methods in this file should use `worksheet.getCell()` instead of using raw data + export function findNextRange( startRange: IRange, direction: Direction, @@ -407,6 +409,7 @@ export function shrinkToNextCell(startRange: IRange, direction: Direction, works return alignToMergedCellsBorders(Rectangle.union(otherEdge, next), worksheet, false); } +// eslint-disable-next-line max-lines-per-function export function expandToContinuousRange(startRange: IRange, directions: IExpandParams, worksheet: Worksheet): IRange { const { left, right, up, down } = directions; const maxRow = worksheet.getMaxRows(); @@ -432,7 +435,7 @@ export function expandToContinuousRange(startRange: IRange, directions: IExpandP // we should check if there are value in the upper row of contents, if it does // we should update the `destRange` and set `changed` to true matrixFromLastRow.forValue((row, col, value) => { - if (value.v) { + if (cellHasValue(value)) { destRange.startRow = Math.min(row, destRange.startRow); destRange.startColumn = Math.min(col, destRange.startColumn); destRange.endColumn = Math.max(col, destRange.endColumn); @@ -451,7 +454,7 @@ export function expandToContinuousRange(startRange: IRange, directions: IExpandP ); matrixFromLastRow.forValue((row, col, value) => { - if (value.v) { + if (cellHasValue(value)) { destRange.endRow = Math.max( row + (value.rowSpan !== undefined ? value.rowSpan - 1 : 0), destRange.endRow @@ -473,7 +476,7 @@ export function expandToContinuousRange(startRange: IRange, directions: IExpandP ); matrixFromLastCol.forValue((row, col, value) => { - if (value.v) { + if (cellHasValue(value)) { destRange.startColumn = Math.min(col, destRange.startColumn); destRange.startRow = Math.min(row, destRange.startRow); destRange.endRow = Math.max(row, destRange.endRow); @@ -492,7 +495,7 @@ export function expandToContinuousRange(startRange: IRange, directions: IExpandP ); matrixFromLastCol.forValue((row, col, value) => { - if (value.v) { + if (cellHasValue(value)) { destRange.endColumn = Math.max( col + (value.colSpan !== undefined ? value.colSpan - 1 : 0), destRange.endColumn @@ -576,7 +579,7 @@ function rangeHasValue( let hasValue = false; const matrix = worksheet.getMatrixWithMergedCells(row, col, rowEnd, colEnd).forValue((_, __, value) => { - if (value.v) { + if (cellHasValue(value)) { hasValue = true; return false; // stop looping } @@ -742,3 +745,7 @@ export function getMergeableSelectionsByType(type: MergeType, selections: Nullab return selections; } + +function cellHasValue(cell: ICellData): boolean { + return (cell.v !== undefined && cell.v !== null && cell.v !== '') || cell.p !== undefined; +} diff --git a/packages/sheets-ui/src/controllers/auto-fill.controller.ts b/packages/sheets-ui/src/controllers/auto-fill.controller.ts index b4a65e74c5a0..651a0227ca48 100644 --- a/packages/sheets-ui/src/controllers/auto-fill.controller.ts +++ b/packages/sheets-ui/src/controllers/auto-fill.controller.ts @@ -56,7 +56,7 @@ import { Inject, Injector } from '@wendellhu/redi'; import { AutoClearContentCommand, AutoFillCommand } from '../commands/commands/auto-fill.command'; import { IAutoFillService } from '../services/auto-fill/auto-fill.service'; import { otherRule } from '../services/auto-fill/rules'; -import { fillCopy, fillCopyStyles, generateNullCellValue, getDataIndex, getLenS } from '../services/auto-fill/tools'; +import { fillCopy, fillCopyStyles, getDataIndex, getLenS } from '../services/auto-fill/tools'; import type { APPLY_FUNCTIONS, IAutoFillLocation, @@ -68,6 +68,8 @@ import type { import { APPLY_TYPE, AutoFillHookType, DATA_TYPE } from '../services/auto-fill/type'; import { IEditorBridgeService } from '../services/editor-bridge.service'; import { ISelectionRenderService } from '../services/selection/selection-render.service'; +import type { IDiscreteRange } from './utils/range-tools'; +import { discreteRangeToRange, generateNullCellValue, rangeToDiscreteRange } from './utils/range-tools'; @OnLifecycle(LifecycleStages.Steady, AutoFillController) export class AutoFillController extends Disposable { @@ -283,16 +285,25 @@ export class AutoFillController extends Disposable { const unitId = workbook.getUnitId(); const subUnitId = workbook.getActiveSheet().getSheetId(); this._autoFillService.direction = direction; + const accessor = { + get: this._injector.get.bind(this._injector), + }; + const autoFillSource = rangeToDiscreteRange(source, accessor); + const autoFillTarget = rangeToDiscreteRange(target, accessor); + + if (!autoFillSource || !autoFillTarget) { + return; + } this._autoFillService.autoFillLocation = { - source, - target, + source: autoFillSource, + target: autoFillTarget, unitId, subUnitId, }; const activeHooks = this._autoFillService.getActiveHooks(); activeHooks.forEach((hook) => { - hook?.onBeforeFillData?.({ source, target, unitId, subUnitId }, direction!); + hook?.onBeforeFillData?.({ source: autoFillSource, target: autoFillTarget, unitId, subUnitId }, direction!); }); // set apply type will trigger fillData @@ -367,7 +378,7 @@ export class AutoFillController extends Disposable { return; } - const selection = Rectangle.union(source, target); + const selection = Rectangle.union(discreteRangeToRange(source), discreteRangeToRange(target)); const applyType = this._autoFillService.applyType; const activeHooks = this._autoFillService.getActiveHooks(); @@ -521,43 +532,37 @@ export class AutoFillController extends Disposable { } } - private _getCopyData(source: IRange, direction: Direction) { - const { - startRow: copyStartRow, - startColumn: copyStartColumn, - endRow: copyEndRow, - endColumn: copyEndColumn, - } = source; + private _getCopyData(source: IDiscreteRange, direction: Direction) { + // const { + // startRow: copyStartRow, + // startColumn: copyStartColumn, + // endRow: copyEndRow, + // endColumn: copyEndColumn, + // } = source; const currentCellDatas = this._univerInstanceService .getCurrentUnitForType(UniverInstanceType.SHEET)! .getActiveSheet() .getCellMatrix(); const rules = this._autoFillService.getRules(); - const copyData = []; + const copyData: ICopyDataPiece[] = []; const isVertical = direction === Direction.DOWN || direction === Direction.UP; - let a1: number; - let a2: number; - let b1: number; - let b2: number; + let aArray: number[]; + let bArray: number[]; if (isVertical) { - a1 = copyStartColumn; - a2 = copyEndColumn; - b1 = copyStartRow; - b2 = copyEndRow; + aArray = source.cols; + bArray = source.rows; } else { - a1 = copyStartRow; - a2 = copyEndRow; - b1 = copyStartColumn; - b2 = copyEndColumn; + aArray = source.rows; + bArray = source.cols; } - for (let a = a1; a <= a2; a++) { + aArray.forEach((a) => { // a copyDataPiece is an array of original cells in same column or row, depending on direction (horizontal or vertical) const copyDataPiece = this._getEmptyCopyDataPiece(); const prevData: IRuleConfirmedData = { type: undefined, cellData: undefined, }; - for (let b = b1; b <= b2; b++) { + bArray.forEach((b) => { let data: Nullable; if (isVertical) { data = currentCellDatas.getValue(b, a); @@ -570,28 +575,28 @@ export class AutoFillController extends Disposable { const last = typeInfo![typeInfo!.length - 1]; last.data.push(data); - last.index.push(b - b1); + last.index.push(b - bArray[0]); } else { const typeInfo = copyDataPiece[type]; if (typeInfo) { typeInfo.push({ data: [data], - index: [b - b1], + index: [b - bArray[0]], }); } else { copyDataPiece[type] = [ { data: [data], - index: [b - b1], + index: [b - bArray[0]], }, ]; } } prevData.type = type; prevData.cellData = data; - } + }); copyData.push(copyDataPiece); - } + }); return copyData; } @@ -682,14 +687,14 @@ export class AutoFillController extends Disposable { .getActiveSheet() .getCellMatrix(); // cache the original data in currentCellDatas in apply range for later use / refill - const applyData = []; - for (let i = target.startRow; i <= target.endRow; i++) { - const row = []; - for (let j = target.startColumn; j <= target.endColumn; j++) { + const applyData: Nullable[][] = []; + target.rows.forEach((i) => { + const row: Nullable[] = []; + target.cols.forEach((j) => { row.push(Tools.deepClone(currentCellDatas.getValue(i, j))); - } + }); applyData.push(row); - } + }); this._beforeApplyData = applyData; this._copyData = this._getCopyData(source, direction); if (this._hasSeries(this._copyData)) { @@ -719,40 +724,32 @@ export class AutoFillController extends Disposable { }; } - const { - startRow: copyStartRow, - startColumn: copyStartColumn, - endRow: copyEndRow, - endColumn: copyEndColumn, - } = source; + const sourceRange = discreteRangeToRange(source); + const targetRange = discreteRangeToRange(target); - const { - startRow: applyStartRow, - startColumn: applyStartColumn, - endRow: applyEndRow, - endColumn: applyEndColumn, - } = target; + const { cols: targetCols, rows: targetRows } = target; + const { cols: sourceCols, rows: sourceRows } = source; const copyData = this._copyData; let csLen; if (direction === Direction.DOWN || direction === Direction.UP) { - csLen = copyEndRow - copyStartRow + 1; + csLen = sourceRows.length; } else { - csLen = copyEndColumn - copyStartColumn + 1; + csLen = sourceCols.length; } const applyDatas: Array>> = []; if (direction === Direction.DOWN || direction === Direction.UP) { - const asLen = applyEndRow - applyStartRow + 1; - const untransformedApplyDatas = []; - for (let i = applyStartColumn; i <= applyEndColumn; i++) { - const copyD = copyData[i - applyStartColumn]; + const asLen = targetRows.length; + const untransformedApplyDatas: Nullable[][] = []; + targetCols.forEach((_, i) => { + const copyD = copyData[i]; const applyData = this._getApplyData(copyD, csLen, asLen, direction, applyType, hasStyle); untransformedApplyDatas.push(applyData); - } + }); for (let i = 0; i < untransformedApplyDatas[0].length; i++) { const row: Array> = []; for (let j = 0; j < untransformedApplyDatas.length; j++) { @@ -764,9 +761,9 @@ export class AutoFillController extends Disposable { applyDatas.push(row); } } else { - const asLen = applyEndColumn - applyStartColumn + 1; - for (let i = applyStartRow; i <= applyEndRow; i++) { - const copyD = copyData[i - applyStartRow]; + const asLen = targetCols.length; + targetRows.forEach((_, i) => { + const copyD = copyData[i]; const applyData = this._getApplyData(copyD, csLen, asLen, direction, applyType, hasStyle); const row: Array> = []; for (let j = 0; j < applyData.length; j++) { @@ -776,14 +773,14 @@ export class AutoFillController extends Disposable { }); } applyDatas.push(row); - } + }); } // deal with styles let applyMergeRanges: IRange[] = []; const style = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)!.getStyles(); if (hasStyle) { - applyMergeRanges = this._getMergeApplyData(source, target, direction, csLen); + applyMergeRanges = this._getMergeApplyData(sourceRange, targetRange, direction, csLen); applyDatas.forEach((row) => { row.forEach((cellData) => { if (cellData && style) { @@ -828,7 +825,7 @@ export class AutoFillController extends Disposable { ?.getMergeData(); if (mergeData) { mergeData.forEach((merge) => { - if (Rectangle.intersects(merge, target)) { + if (Rectangle.intersects(merge, targetRange)) { deleteMergeRanges.push(merge); } }); @@ -863,15 +860,13 @@ export class AutoFillController extends Disposable { // set range value const cellValue = new ObjectMatrix(); - const { startRow, startColumn, endRow, endColumn } = target; - - for (let r = 0; r <= endRow - startRow; r++) { - for (let c = 0; c <= endColumn - startColumn; c++) { - if (applyDatas[r][c]) { - cellValue.setValue(r + startRow, c + startColumn, applyDatas[r][c]!); + targetRows.forEach((row, rowIndex) => { + targetCols.forEach((col, colIndex) => { + if (applyDatas[rowIndex][colIndex]) { + cellValue.setValue(row, col, applyDatas[rowIndex][colIndex]!); } - } - } + }); + }); const setRangeValuesMutationParams: ISetRangeValuesMutationParams = { subUnitId, diff --git a/packages/sheets-ui/src/controllers/clipboard/clipboard.controller.ts b/packages/sheets-ui/src/controllers/clipboard/clipboard.controller.ts index 638faa8344f6..11852ff9221f 100644 --- a/packages/sheets-ui/src/controllers/clipboard/clipboard.controller.ts +++ b/packages/sheets-ui/src/controllers/clipboard/clipboard.controller.ts @@ -83,7 +83,7 @@ import type { IClipboardPropertyItem, ICopyPastePayload, ISheetClipboardHook, - ISheetRangeLocation, + ISheetDiscreteRangeLocation, } from '../../services/clipboard/type'; import { SheetSkeletonManagerService } from '../../services/sheet-skeleton-manager.service'; import { whenSheetEditorFocused } from '../shortcuts/utils'; @@ -262,6 +262,20 @@ export class SheetClipboardController extends RxDisposable { onAfterCopy() { currentSheet = null; }, + getFilteredOutRows(range: IRange) { + const { startRow, endRow } = range; + const worksheet = self._currentUniverSheet.getCurrentUnitForType(UniverInstanceType.SHEET)?.getActiveSheet(); + const res: number[] = []; + if (!worksheet) { + return res; + } + for (let r = startRow; r <= endRow; r++) { + if (worksheet.getRowFiltered(r)) { + res.push(r); + } + } + return res; + }, }; } @@ -283,7 +297,8 @@ export class SheetClipboardController extends RxDisposable { // examine if pasting would cause number of cells to exceed the upper limit // this is not implemented yet const maxConfig = self._configService.getConfig(MAX_CELL_PER_SHEET_KEY); - const { endRow, endColumn } = range; + const endRow = range.rows[range.rows.length - 1]; + const endColumn = range.cols[range.cols.length - 1]; if (maxConfig && endRow * endColumn > maxConfig) { self._messageService.show({ type: MessageType.Error, @@ -302,7 +317,7 @@ export class SheetClipboardController extends RxDisposable { // if the range is outside ot the worksheet's boundary, we should add rows const maxRow = currentSheet!.getMaxRows(); - const addingRowsCount = range.endRow - maxRow + 1; + const addingRowsCount = range.rows[range.rows.length - 1] - maxRow; const existingRowsCount = rowProperties.length - addingRowsCount; const rowManager = currentSheet!.getRowManager(); @@ -344,7 +359,11 @@ export class SheetClipboardController extends RxDisposable { const addRowMutation: IInsertRowMutationParams = { unitId: unitId!, subUnitId: subUnitId!, - range: { ...range, startRow: maxRow }, + range: { + startColumn: range.cols[0], + endColumn: range.cols[range.cols.length - 1], + endRow: range.rows[range.rows.length - 1], + startRow: maxRow }, rowInfo, }; redoMutations.push({ @@ -378,20 +397,20 @@ export class SheetClipboardController extends RxDisposable { return false; }); - rowHeight[index + range.startRow] = height; + rowHeight[index + range.rows[0]] = height; } else if (propertyHeight) { - const rowConfigBeforePaste = rowManager.getRow(range.startRow + index); + const rowConfigBeforePaste = rowManager.getRow(range.rows[0] + index); const willSetHeight = Number.parseFloat(propertyHeight); if (rowConfigBeforePaste) { const { h = DEFAULT_WORKSHEET_ROW_HEIGHT, ah = 0 } = rowConfigBeforePaste; const nowRowHeight = Math.max(h, ah); if (willSetHeight > nowRowHeight) { - rowHeight[index + range.startRow] = willSetHeight; - originRowHeight[index + range.startRow] = nowRowHeight; + rowHeight[index + range.rows[0]] = willSetHeight; + originRowHeight[index + range.rows[0]] = nowRowHeight; } } else { - rowHeight[index + range.startRow] = willSetHeight; - originRowHeight[index + range.startRow] = rowManager.getRow(range.startRow + index)?.h ?? DEFAULT_WORKSHEET_ROW_HEIGHT; + rowHeight[index + range.rows[0]] = willSetHeight; + originRowHeight[index + range.rows[0]] = rowManager.getRow(range.rows[0] + index)?.h ?? DEFAULT_WORKSHEET_ROW_HEIGHT; } } }); @@ -400,7 +419,9 @@ export class SheetClipboardController extends RxDisposable { const setRowPropertyMutation: ISetWorksheetRowHeightMutationParams = { unitId: unitId!, subUnitId: subUnitId!, - ranges: [{ ...range, endRow: Math.min(range.endRow, maxRow) }], + ranges: [{ startRow: range.rows[0], endRow: Math.min(range.rows[range.rows.length - 1], maxRow), + startColumn: range.cols[0], endColumn: range.cols[range.cols.length - 1], + }], rowHeight, }; redoMutations.push({ @@ -429,7 +450,7 @@ export class SheetClipboardController extends RxDisposable { // if the range is outside ot the worksheet's boundary, we should add rows const maxColumn = currentSheet!.getMaxColumns(); - const addingColsCount = range.endColumn - maxColumn + 1; + const addingColsCount = range.cols[range.cols.length - 1] - maxColumn; const existingColsCount = colProperties.length - addingColsCount; const defaultColumnWidth = self._configService.getConfig(DEFAULT_WORKSHEET_COLUMN_WIDTH_KEY) ?? DEFAULT_WORKSHEET_COLUMN_WIDTH; @@ -438,7 +459,12 @@ export class SheetClipboardController extends RxDisposable { const addColMutation: IInsertColMutationParams = { unitId: unitId!, subUnitId: subUnitId!, - range: { ...range, startColumn: maxColumn }, + range: { + startRow: range.rows[0], + endRow: range.rows[range.rows.length - 1], + endColumn: range.cols[range.cols.length - 1], + startColumn: maxColumn, + }, colInfo: colProperties.slice(existingColsCount).map((property) => ({ w: property.width ? +property.width : defaultColumnWidth, hd: BooleanNumber.FALSE, @@ -453,7 +479,12 @@ export class SheetClipboardController extends RxDisposable { const setColPropertyMutation: ISetWorksheetColWidthMutationParams = { unitId: unitId!, subUnitId: subUnitId!, - ranges: [{ ...range, endRow: Math.min(range.endColumn, maxColumn) }], + ranges: [{ + startRow: range.rows[0], + endRow: range.rows[range.rows.length - 1], + + startColumn: range.cols[0], + endColumn: Math.min(range.cols[range.cols.length - 1], maxColumn) }], colWidth: colProperties .slice(0, existingColsCount) .map((property) => (property.width ? +property.width : defaultColumnWidth)), @@ -471,13 +502,13 @@ export class SheetClipboardController extends RxDisposable { }; }, - onPastePlainText(pasteTo: ISheetRangeLocation, text: string, payload: ICopyPastePayload) { + onPastePlainText(pasteTo: ISheetDiscreteRangeLocation, text: string, payload: ICopyPastePayload) { return self._onPastePlainText(pasteTo, text, payload); }, onPasteCells( - pasteFrom: ISheetRangeLocation, - pasteTo: ISheetRangeLocation, + pasteFrom: ISheetDiscreteRangeLocation, + pasteTo: ISheetDiscreteRangeLocation, data: ObjectMatrix, payload: ICopyPastePayload ) { @@ -503,15 +534,15 @@ export class SheetClipboardController extends RxDisposable { return documentModel?.getSnapshot(); } - private _onPastePlainText(pasteTo: ISheetRangeLocation, text: string, payload: ICopyPastePayload) { + private _onPastePlainText(pasteTo: ISheetDiscreteRangeLocation, text: string, payload: ICopyPastePayload) { const { range, unitId, subUnitId } = pasteTo; let cellValue: IObjectMatrixPrimitiveType; if (/\r|\n/.test(text)) { const body = generateBody(text); const p = this._generateDocumentDataModelSnapshot({ body }); cellValue = { - [range.startRow]: { - [range.startColumn]: { + [range.rows[0]]: { + [range.cols[0]]: { p, }, }, @@ -519,16 +550,16 @@ export class SheetClipboardController extends RxDisposable { } else { if (isFormulaString(text)) { cellValue = { - [range.startRow]: { - [range.startColumn]: { + [range.rows[0]]: { + [range.cols[0]]: { f: text, }, }, }; } else { cellValue = { - [range.startRow]: { - [range.startColumn]: { + [range.rows[0]]: { + [range.cols[0]]: { v: text, }, }, @@ -559,8 +590,8 @@ export class SheetClipboardController extends RxDisposable { } private _onPasteCells( - pasteFrom: ISheetRangeLocation, - pasteTo: ISheetRangeLocation, + pasteFrom: ISheetDiscreteRangeLocation, + pasteTo: ISheetDiscreteRangeLocation, data: ObjectMatrix, payload: ICopyPastePayload ): { @@ -648,14 +679,19 @@ export class SheetClipboardController extends RxDisposable { const { range } = pasteTo; // if the range is outside ot the worksheet's boundary, we should add rows const maxColumn = currentSheet!.getMaxColumns(); - const addingColsCount = range.endColumn - maxColumn; + const addingColsCount = range.cols[range.cols.length - 1] - maxColumn; const existingColsCount = colProperties.length - addingColsCount; const defaultColumnWidth = self._configService.getConfig(DEFAULT_WORKSHEET_COLUMN_WIDTH_KEY) ?? DEFAULT_WORKSHEET_COLUMN_WIDTH; const setColPropertyMutation: ISetWorksheetColWidthMutationParams = { unitId: unitId!, subUnitId: subUnitId!, - ranges: [{ ...range, endRow: Math.min(range.endColumn, maxColumn) }], + ranges: [{ + startRow: range.rows[0], + endRow: Math.min(range.cols[range.cols.length - 1], maxColumn), + startColumn: range.cols[0], + endColumn: range.cols[range.cols.length - 1], + }], colWidth: colProperties .slice(0, existingColsCount) .map((property) => (property.width ? +property.width : defaultColumnWidth)), @@ -689,14 +725,13 @@ export class SheetClipboardController extends RxDisposable { const redoMutationsInfo: IMutationInfo[] = []; const undoMutationsInfo: IMutationInfo[] = []; const { range, unitId, subUnitId } = pasteTo; - const { startColumn, startRow, endColumn, endRow } = range; const valueMatrix = new ObjectMatrix(); // TODO@Dushusir: undo selection matrix.forValue((row, col, value) => { const style = value.s; if (typeof style === 'object') { - valueMatrix.setValue(row + startRow, col + startColumn, { + valueMatrix.setValue(range.rows[row], range.cols[col], { s: { ...style, bd: undefined }, v: value.v, }); diff --git a/packages/sheets-ui/src/controllers/clipboard/utils.ts b/packages/sheets-ui/src/controllers/clipboard/utils.ts index 5bd473ac2c56..9cec2d6b96c3 100644 --- a/packages/sheets-ui/src/controllers/clipboard/utils.ts +++ b/packages/sheets-ui/src/controllers/clipboard/utils.ts @@ -40,13 +40,14 @@ import { import type { IAccessor } from '@wendellhu/redi'; import numfmt from '@univerjs/engine-numfmt'; -import type { ICellDataWithSpanInfo, ICopyPastePayload, ISheetRangeLocation } from '../../services/clipboard/type'; +import type { ICellDataWithSpanInfo, ICopyPastePayload, ISheetDiscreteRangeLocation } from '../../services/clipboard/type'; import { COPY_TYPE } from '../../services/clipboard/type'; +import { discreteRangeToRange, type IDiscreteRange, virtualizeDiscreteRanges } from '../utils/range-tools'; // if special paste need append mutations instead of replace the default, it can use this function to generate default mutations. export function getDefaultOnPasteCellMutations( - pasteFrom: ISheetRangeLocation, - pasteTo: ISheetRangeLocation, + pasteFrom: ISheetDiscreteRangeLocation, + pasteTo: ISheetDiscreteRangeLocation, data: ObjectMatrix, payload: ICopyPastePayload, accessor: IAccessor @@ -58,7 +59,6 @@ export function getDefaultOnPasteCellMutations( redoMutationsInfo.push(...redos); undoMutationsInfo.push(...undos); } else { - const { unitId, subUnitId, range } = pasteTo; // clear style const { undos: clearStyleUndos, redos: clearStyleRedos } = getClearCellStyleMutations(pasteTo, data, accessor); redoMutationsInfo.push(...clearStyleRedos); @@ -93,19 +93,21 @@ export function getMoveRangeMutations( from: { unitId: string; subUnitId: string; - range?: IRange; + range?: IDiscreteRange; }, to: { unitId: string; subUnitId: string; - range?: IRange; + range?: IDiscreteRange; }, accessor: IAccessor ) { let redos: IMutationInfo[] = []; let undos: IMutationInfo[] = []; - const { range: fromRange, subUnitId: fromSubUnitId, unitId } = from; - const { range: toRange, subUnitId: toSubUnitId } = to; + const { range: fromDiscreteRange, subUnitId: fromSubUnitId, unitId } = from; + const { range: toDiscreteRange, subUnitId: toSubUnitId } = to; + const toRange = toDiscreteRange ? discreteRangeToRange(toDiscreteRange) : null; + const fromRange = fromDiscreteRange ? discreteRangeToRange(fromDiscreteRange) : null; if (fromRange && toRange) { const univerInstanceService = accessor.get(IUniverInstanceService); @@ -254,6 +256,9 @@ export function getMoveRangeMutations( }, ]; undos = [ + { id: MoveRangeMutation.id, params: undoMoveRangeMutation }, + ...interceptorCommands.undos, + ...mergeUndos, { id: SetSelectionsOperation.id, params: { @@ -263,9 +268,6 @@ export function getMoveRangeMutations( selections: [{ range: fromRange }], }, }, - ...interceptorCommands.undos, - ...mergeUndos, - { id: MoveRangeMutation.id, params: undoMoveRangeMutation }, ]; } } @@ -277,15 +279,15 @@ export function getMoveRangeMutations( } export function getSetCellValueMutations( - pasteTo: ISheetRangeLocation, - pasteFrom: Nullable, + pasteTo: ISheetDiscreteRangeLocation, + pasteFrom: Nullable, matrix: ObjectMatrix, accessor: IAccessor ) { const { unitId, subUnitId, range } = pasteTo; const redoMutationsInfo: IMutationInfo[] = []; const undoMutationsInfo: IMutationInfo[] = []; - const { startColumn, startRow } = range; + const { mapFunc } = virtualizeDiscreteRanges([range]); const valueMatrix = new ObjectMatrix(); matrix.forValue((row, col, value) => { @@ -296,10 +298,12 @@ export function getSetCellValueMutations( value.v = numfmtValue.v; } } + const { row: realRow, col: realCol } = mapFunc(row, col); + if (value.p?.body) { - valueMatrix.setValue(row + startRow, col + startColumn, Tools.deepClone({ p: value.p, v: value.v })); + valueMatrix.setValue(realRow, realCol, Tools.deepClone({ p: value.p, v: value.v })); } else { - valueMatrix.setValue(row + startRow, col + startColumn, Tools.deepClone({ v: value.v })); + valueMatrix.setValue(realRow, realCol, Tools.deepClone({ v: value.v })); } }); // set cell value and style @@ -331,16 +335,17 @@ export function getSetCellValueMutations( } export function getSetCellStyleMutations( - pasteTo: ISheetRangeLocation, + pasteTo: ISheetDiscreteRangeLocation, matrix: ObjectMatrix, accessor: IAccessor ) { const redoMutationsInfo: IMutationInfo[] = []; const undoMutationsInfo: IMutationInfo[] = []; const { unitId, subUnitId, range } = pasteTo; - const { startColumn, startRow } = range; const valueMatrix = new ObjectMatrix(); + const { mapFunc } = virtualizeDiscreteRanges([range]); + matrix.forValue((row, col, value) => { const newValue: ICellData = { s: value.s, @@ -348,7 +353,8 @@ export function getSetCellStyleMutations( if (value.p?.body) { newValue.p = value.p; } - valueMatrix.setValue(row + startRow, col + startColumn, newValue); + const { row: actualRow, col: actualCol } = mapFunc(row, col); + valueMatrix.setValue(actualRow, actualCol, newValue); }); // set cell style const setValuesMutation: ISetRangeValuesMutationParams = { @@ -379,7 +385,7 @@ export function getSetCellStyleMutations( } export function getClearCellStyleMutations( - pasteTo: ISheetRangeLocation, + pasteTo: ISheetDiscreteRangeLocation, matrix: ObjectMatrix, accessor: IAccessor ) { @@ -387,14 +393,15 @@ export function getClearCellStyleMutations( const undoMutationsInfo: IMutationInfo[] = []; const clearStyleMatrix = new ObjectMatrix(); const { unitId, subUnitId, range } = pasteTo; - const { startColumn, startRow } = range; + const { mapFunc } = virtualizeDiscreteRanges([range]); matrix.forValue((row, col, value) => { // NOTE: When pasting, the original cell may contain a default style that is not explicitly carried, resulting in the failure to overwrite the style of the target cell. // If the original cell has a style (lack of other default styles) or is undefined (all default styles), we need to clear the existing styles in the target area // If the original cell style is "", it is to handle the situation where the target area contains merged cells. The style is not overwritten, only the value is overwritten. There is no need to clear the existing style of the target area. if (value.s) { - clearStyleMatrix.setValue(row + startRow, col + startColumn, { s: null }); + const { row: actualRow, col: actualCol } = mapFunc(row, col); + clearStyleMatrix.setValue(actualRow, actualCol, { s: null }); } }); // clear style @@ -425,14 +432,15 @@ export function getClearCellStyleMutations( } export function getClearAndSetMergeMutations( - pasteTo: ISheetRangeLocation, + pasteTo: ISheetDiscreteRangeLocation, matrix: ObjectMatrix, accessor: IAccessor ) { const redoMutationsInfo: IMutationInfo[] = []; const undoMutationsInfo: IMutationInfo[] = []; const { unitId, subUnitId, range } = pasteTo; - const { startColumn, startRow, endColumn, endRow } = range; + const { startColumn, startRow, endColumn, endRow } = discreteRangeToRange(range); + const hasMerge = false; const mergeRangeData: IRange[] = []; matrix.forValue((row, col, value) => { diff --git a/packages/sheets-ui/src/controllers/editor/__tests__/create-test-bed.ts b/packages/sheets-ui/src/controllers/editor/__tests__/create-test-bed.ts index d5f93906e10c..54af4c73a29b 100644 --- a/packages/sheets-ui/src/controllers/editor/__tests__/create-test-bed.ts +++ b/packages/sheets-ui/src/controllers/editor/__tests__/create-test-bed.ts @@ -43,7 +43,7 @@ export interface ITestBed { sheet: Workbook; } -export function createTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]): ITestBed { +export function createTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]): ITestBed { const univer = new Univer(); const injector = univer.__getInjector(); @@ -69,7 +69,7 @@ export function createTestBed(workbookConfig?: IWorkbookData, dependencies?: Dep } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || getTestWorkbookDataDemo()); + const sheet = univer.createUniverSheet(workbookData || getTestWorkbookDataDemo()); const univerInstanceService = injector.get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets-ui/src/controllers/menu/__tests__/create-menu-test-bed.ts b/packages/sheets-ui/src/controllers/menu/__tests__/create-menu-test-bed.ts index 84f821688f45..1bce6f6d8953 100644 --- a/packages/sheets-ui/src/controllers/menu/__tests__/create-menu-test-bed.ts +++ b/packages/sheets-ui/src/controllers/menu/__tests__/create-menu-test-bed.ts @@ -43,12 +43,9 @@ const TEST_WORKBOOK_DATA_DEMO: IWorkbookData = { export function createMenuTestBed() { const univer = new Univer(); + const injector = univer.__getInjector(); + const get = injector.get.bind(injector); - let get: Injector['get'] | null = null; - - /** - * This plugin hooks into Sheet's DI system to expose API to test scripts - */ class TestPlugin extends Plugin { static override pluginName = 'test-plugin'; protected override _injector: Injector; @@ -59,7 +56,7 @@ export function createMenuTestBed() { super(); this._injector = _injector; - get = this._injector.get.bind(this._injector); + // get = this._injector.get.bind(this._injector); } override onStarting(injector: Injector): void { @@ -74,10 +71,6 @@ export function createMenuTestBed() { univer.registerPlugin(TestPlugin); const sheet = univer.createUniverSheet(TEST_WORKBOOK_DATA_DEMO); - if (!get) { - throw new Error('[TestPlugin]: not hooked on!'); - } - return { univer, get, diff --git a/packages/sheets-ui/src/controllers/menu/__tests__/menu.spec.ts b/packages/sheets-ui/src/controllers/menu/__tests__/menu.spec.ts index e258ff204e63..c9375222a2f1 100644 --- a/packages/sheets-ui/src/controllers/menu/__tests__/menu.spec.ts +++ b/packages/sheets-ui/src/controllers/menu/__tests__/menu.spec.ts @@ -14,12 +14,11 @@ * limitations under the License. */ -import type { Univer } from '@univerjs/core'; +import type { IRange, Univer } from '@univerjs/core'; import { DisposableCollection, ICommandService, RANGE_TYPE, - toDisposable, } from '@univerjs/core'; import { NORMAL_SELECTION_PLUGIN_NAME, @@ -34,7 +33,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { BoldMenuItemFactory } from '../menu'; import { createMenuTestBed } from './create-menu-test-bed'; -describe('Test menu items', () => { +describe('test menu items', () => { let univer: Univer; let get: Injector['get']; let commandService: ICommandService; @@ -60,37 +59,43 @@ describe('Test menu items', () => { disposableCollection.dispose(); }); - it('Test bold menu item', async () => { - let activated = false; - let disabled = false; - const menuItem = get(Injector).invoke(BoldMenuItemFactory); - disposableCollection.add(toDisposable(menuItem.activated$!.subscribe((v: boolean) => (activated = v)))); - disposableCollection.add(toDisposable(menuItem.disabled$!.subscribe((v: boolean) => (disabled = v)))); - expect(activated).toBeFalsy(); - expect(disabled).toBeFalsy(); - + function select(range: IRange) { const selectionManager = get(SelectionManagerService); selectionManager.setCurrentSelection({ pluginName: NORMAL_SELECTION_PLUGIN_NAME, unitId: 'test', sheetId: 'sheet1', }); + + const { startColumn, startRow, endColumn, endRow } = range; selectionManager.add([ { - range: { startRow: 0, startColumn: 0, endColumn: 0, endRow: 0, rangeType: RANGE_TYPE.NORMAL }, + range: { startRow, startColumn, endColumn, endRow, rangeType: RANGE_TYPE.NORMAL }, primary: { - startRow: 0, - startColumn: 0, - endColumn: 0, - endRow: 0, - actualRow: 0, - actualColumn: 0, + startRow, + startColumn, + endColumn, + endRow, + actualRow: startRow, + actualColumn: startColumn, isMerged: false, isMergedMainCell: false, }, style: null, }, ]); + } + + it('should "BoldMenu" change status correctly', async () => { + let activated = false; + let disabled = false; + const menuItem = get(Injector).invoke(BoldMenuItemFactory); + disposableCollection.add(menuItem.activated$!.subscribe((v: boolean) => (activated = v))); + disposableCollection.add(menuItem.disabled$!.subscribe((v: boolean) => (disabled = v))); + expect(activated).toBeFalsy(); + expect(disabled).toBeFalsy(); + + select({ startRow: 0, startColumn: 0, endRow: 0, endColumn: 0 }); expect(await commandService.executeCommand(SetBoldCommand.id)).toBeTruthy(); expect(activated).toBe(true); diff --git a/packages/sheets-ui/src/controllers/menu/menu.ts b/packages/sheets-ui/src/controllers/menu/menu.ts index 887341d4e1c1..84d50576e0e8 100644 --- a/packages/sheets-ui/src/controllers/menu/menu.ts +++ b/packages/sheets-ui/src/controllers/menu/menu.ts @@ -1079,6 +1079,7 @@ export function HideColMenuItemFactory(): IMenuButtonItem { export function ShowRowMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const univerInstanceService = accessor.get(IUniverInstanceService); const selectionManagerService = accessor.get(SelectionManagerService); + const commandService = accessor.get(ICommandService); const affectedCommands = [SetSelectionsOperation, SetRowHiddenMutation, SetRowVisibleMutation].map((c) => c.id); @@ -1093,7 +1094,7 @@ export function ShowRowMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const rowRanges = selectionManagerService.getSelections()?.map((s) => s.range).filter((r) => r.rangeType === RANGE_TYPE.ROW); return !!rowRanges?.some((range) => { for (let r = range.startRow; r <= range.endRow; r++) { - if (!worksheet.getRowVisible(r)) return true; + if (!worksheet.getRowRawVisible(r)) return true; // should not take filtered out rows into account } return false; diff --git a/packages/sheets-ui/src/controllers/sheet-render.controller.ts b/packages/sheets-ui/src/controllers/sheet-render.controller.ts index a4d8839e7213..16c3e6d67c11 100644 --- a/packages/sheets-ui/src/controllers/sheet-render.controller.ts +++ b/packages/sheets-ui/src/controllers/sheet-render.controller.ts @@ -23,6 +23,7 @@ import { LifecycleStages, OnLifecycle, RxDisposable, + toDisposable, UniverInstanceType, } from '@univerjs/core'; import type { Rect, SpreadsheetColumnHeader, SpreadsheetRowHeader } from '@univerjs/engine-render'; @@ -32,6 +33,7 @@ import { COMMAND_LISTENER_VALUE_CHANGE, SetWorksheetActiveOperation, } from '@univerjs/sheets'; +import type { IDisposable } from '@wendellhu/redi'; import { Inject } from '@wendellhu/redi'; import { distinctUntilChanged, takeUntil } from 'rxjs'; @@ -45,6 +47,8 @@ interface ISetWorksheetMutationParams { @OnLifecycle(LifecycleStages.Ready, SheetRenderController) export class SheetRenderController extends RxDisposable { + private _skeletonChangeMutations = new Set(); + constructor( @Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService, @IContextService private readonly _contextService: IContextService, @@ -57,6 +61,21 @@ export class SheetRenderController extends RxDisposable { this._init(); } + /** + * Register a mutation id that will trigger the skeleton change. + * + * @param mutationId the id of the mutation + * @returns a disposable to unregister the mutation + */ + registerSkeletonChangingMutations(mutationId: string): IDisposable { + if (this._skeletonChangeMutations.has(mutationId)) { + throw new Error(`[SheetRenderController]: the mutationId ${mutationId} has already been registered!`); + } + + this._skeletonChangeMutations.add(mutationId); + return toDisposable(() => this._skeletonChangeMutations.delete(mutationId)); + } + private _init() { this._initialRenderRefresh(); this._initSheetDisposeListener(); @@ -112,7 +131,7 @@ export class SheetRenderController extends RxDisposable { if (!workbook) return; const unitId = workbook.getUnitId(); - if (COMMAND_LISTENER_SKELETON_CHANGE.includes(command.id)) { + if (COMMAND_LISTENER_SKELETON_CHANGE.includes(command.id) || this._skeletonChangeMutations.has(command.id)) { const worksheet = workbook.getActiveSheet(); const sheetId = worksheet.getSheetId(); const params = command.params; diff --git a/packages/sheets-ui/src/controllers/utils/range-tools.ts b/packages/sheets-ui/src/controllers/utils/range-tools.ts new file mode 100644 index 000000000000..e293a06af40a --- /dev/null +++ b/packages/sheets-ui/src/controllers/utils/range-tools.ts @@ -0,0 +1,116 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IAccessor } from '@wendellhu/redi'; +import type { ICellData, IObjectMatrixPrimitiveType, IRange, Workbook } from '@univerjs/core'; +import { IUniverInstanceService, ObjectMatrix, UniverInstanceType } from '@univerjs/core'; + +export interface IDiscreteRange { + rows: number[]; + cols: number[]; +} + +export function rangeToDiscreteRange(range: IRange, accessor: IAccessor, unitId?: string, subUnitId?: string): IDiscreteRange | null { + const univerInstanceService = accessor.get(IUniverInstanceService); + const workbook = unitId + ? univerInstanceService.getUnit(unitId, UniverInstanceType.SHEET) + : univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET); + const worksheet = subUnitId ? workbook?.getSheetBySheetId(subUnitId) : workbook?.getActiveSheet(); + if (!worksheet) { + return null; + } + const { startRow, endRow, startColumn, endColumn } = range; + + const rows = []; + const cols = []; + for (let r = startRow; r <= endRow; r++) { + if (!worksheet.getRowFiltered(r)) { + rows.push(r); + } + } + for (let c = startColumn; c <= endColumn; c++) { + cols.push(c); + } + return { + rows, + cols, + }; +} + +export function discreteRangeToRange(discreteRange: IDiscreteRange): IRange { + const { rows, cols } = discreteRange; + return { + startRow: rows[0], + endRow: rows[rows.length - 1], + startColumn: cols[0], + endColumn: cols[cols.length - 1], + }; +} + +export function virtualizeDiscreteRanges(ranges: IDiscreteRange[]): { + ranges: IRange[]; + mapFunc: (row: number, col: number) => { row: number; col: number }; +} { + let totalRows: number[] = []; + let totalCols: number[] = []; + const totalRanges: IRange[] = []; + + ranges.forEach((r) => { + totalRows.push(...r.rows); + totalCols.push(...r.cols); + }); + + totalRows = Array.from(new Set(totalRows)).sort((a, b) => a - b); + totalCols = Array.from(new Set(totalCols)).sort((a, b) => a - b); + + ranges.forEach((r) => { + totalRanges.push({ + startRow: totalRows.findIndex((row) => row === r.rows[0]), + endRow: totalRows.findIndex((row) => row === r.rows[r.rows.length - 1]), + startColumn: totalCols.findIndex((col) => col === r.cols[0]), + endColumn: totalCols.findIndex((col) => col === r.cols[r.cols.length - 1]), + }); + }); + + return { + ranges: totalRanges, + mapFunc: (row, col) => ( + { + row: totalRows[row], + col: totalCols[col], + } + ), + }; +} + +export function generateNullCellValue(range: IDiscreteRange[]): IObjectMatrixPrimitiveType { + const cellValue = new ObjectMatrix(); + range.forEach((r) => { + const { rows, cols } = r; + rows.forEach((i) => { + cols.forEach((j) => { + cellValue.setValue(i, j, { + v: null, + p: null, + f: null, + si: null, + }); + }); + }); + }); + + return cellValue.getData(); +} diff --git a/packages/sheets-ui/src/index.ts b/packages/sheets-ui/src/index.ts index 3bc514179800..b594c3e3038a 100644 --- a/packages/sheets-ui/src/index.ts +++ b/packages/sheets-ui/src/index.ts @@ -19,11 +19,13 @@ export { getEditorObject } from './basics/editor/get-editor-object'; export { AutoFillCommand } from './commands/commands/auto-fill.command'; export { SheetPasteCommand } from './commands/commands/clipboard.command'; export { SheetCopyCommand } from './commands/commands/clipboard.command'; +export { expandToContinuousRange } from './commands/commands/utils/selection-utils'; export { ExpandSelectionCommand, JumpOver, MoveSelectionCommand } from './commands/commands/set-selection.command'; export { SetCellEditVisibleArrowOperation, SetCellEditVisibleOperation } from './commands/operations/cell-edit.operation'; export { SetScrollOperation } from './commands/operations/scroll.operation'; export { ScrollController } from './controllers/scroll.controller'; export { deriveStateFromActiveSheet$ } from './controllers/menu/menu-util'; +export { SheetRenderController } from './controllers/sheet-render.controller'; export { SetZoomRatioOperation } from './commands/operations/set-zoom-ratio.operation'; export { ResetScrollCommand, @@ -58,7 +60,7 @@ export { PREDEFINED_HOOK_NAME, SheetClipboardService, } from './services/clipboard/clipboard.service'; -export type { ICellDataWithSpanInfo, ISheetClipboardHook, ISheetRangeLocation, ICopyPastePayload } from './services/clipboard/type'; +export type { ICellDataWithSpanInfo, ISheetClipboardHook, ISheetRangeLocation, ISheetDiscreteRangeLocation, ICopyPastePayload } from './services/clipboard/type'; export { COPY_TYPE } from './services/clipboard/type'; export { getRepeatRange } from './services/clipboard/utils'; export { CellEditorManagerService, ICellEditorManagerService } from './services/editor/cell-editor-manager.service'; @@ -84,3 +86,5 @@ export { SHEET_VIEW_KEY } from './common/keys'; export { SheetCanvasPopManagerService } from './services/canvas-pop-manager.service'; export { mergeSetRangeValues } from './services/clipboard/utils'; export type { IAutoFillLocation } from './services/auto-fill/type'; +export type { IDiscreteRange } from './controllers/utils/range-tools'; +export { virtualizeDiscreteRanges, rangeToDiscreteRange } from './controllers/utils/range-tools'; diff --git a/packages/sheets-ui/src/locale/en-US.ts b/packages/sheets-ui/src/locale/en-US.ts index 265fcf02d64e..e9d0055c6dcc 100644 --- a/packages/sheets-ui/src/locale/en-US.ts +++ b/packages/sheets-ui/src/locale/en-US.ts @@ -252,6 +252,12 @@ const locale: typeof zhCN = { dismantleMergeCellWaring: 'This will cause some merged cells to be split. Do you want to continue?', }, }, + filter: { + confirm: { + error: 'There was a problem', + notAllowedToInsertRange: 'Not allowed to move cells here until filter is cleared', + }, + }, textWrap: { overflow: 'Overflow', wrap: 'Wrap', diff --git a/packages/sheets-ui/src/locale/zh-CN.ts b/packages/sheets-ui/src/locale/zh-CN.ts index 7fc06859a60d..f5a3ce2a36dd 100644 --- a/packages/sheets-ui/src/locale/zh-CN.ts +++ b/packages/sheets-ui/src/locale/zh-CN.ts @@ -251,6 +251,12 @@ const locale = { dismantleMergeCellWaring: '此操作会导致一些合并单元格被拆散,是否继续?', }, }, + filter: { + confirm: { + error: '出现了一个问题', + notAllowedToInsertRange: '要移动这些单元格,请清除该区域的筛选器', + }, + }, textWrap: { overflow: '溢出', wrap: '自动换行', diff --git a/packages/sheets-ui/src/services/auto-fill/type.ts b/packages/sheets-ui/src/services/auto-fill/type.ts index cf807a88c699..bf253fe102d7 100644 --- a/packages/sheets-ui/src/services/auto-fill/type.ts +++ b/packages/sheets-ui/src/services/auto-fill/type.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import type { Direction, ICellData, IMutationInfo, IRange, Nullable } from '@univerjs/core'; +import type { Direction, ICellData, IMutationInfo, Nullable } from '@univerjs/core'; +import type { IDiscreteRange } from '../../controllers/utils/range-tools'; export enum AutoFillHookType { Append = 'APPEND', @@ -23,8 +24,8 @@ export enum AutoFillHookType { } export interface IAutoFillLocation { - source: IRange; - target: IRange; + source: IDiscreteRange; + target: IDiscreteRange; unitId: string; subUnitId: string; } diff --git a/packages/sheets-ui/src/services/canvas-pop-manager.service.ts b/packages/sheets-ui/src/services/canvas-pop-manager.service.ts index 2381a4acafea..6b535c76cf2e 100644 --- a/packages/sheets-ui/src/services/canvas-pop-manager.service.ts +++ b/packages/sheets-ui/src/services/canvas-pop-manager.service.ts @@ -36,6 +36,8 @@ export interface ICanvasPopup { direction?: 'vertical' | 'horizontal'; offset?: [number, number]; excludeOutside?: HTMLElement[]; + /** Close the popup even if the outside element clicked is its target. */ + closeOnSelfTarget?: boolean; extraProps?: Record; } diff --git a/packages/sheets-ui/src/services/clipboard/__tests__/clipboard-service.spec.ts b/packages/sheets-ui/src/services/clipboard/__tests__/clipboard-service.spec.ts index ab53f2741e70..9b8a1d3ce4ab 100644 --- a/packages/sheets-ui/src/services/clipboard/__tests__/clipboard-service.spec.ts +++ b/packages/sheets-ui/src/services/clipboard/__tests__/clipboard-service.spec.ts @@ -32,6 +32,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { ISheetClipboardService } from '../clipboard.service'; import { COPY_TYPE } from '../type'; +import { discreteRangeToRange } from '../../../controllers/utils/range-tools'; import { clipboardTestBed } from './clipboard-test-bed'; import type { IClipboardItem } from './mock-clipboard'; import { MockClipboard } from './mock-clipboard'; @@ -808,10 +809,10 @@ describe('Test clipboard', () => { it('cut value from A25 to B25', async () => { const unitId = 'test'; const subUnitId = 'sheet1'; - const fromRange = { startRow: 24, startColumn: 0, endRow: 24, endColumn: 0 }; + const fromRange = { rows: [24], cols: [0] }; const toRange = { startRow: 24, startColumn: 1, endRow: 24, endColumn: 1 }; const copyContentCache = sheetClipboardService.copyContentCache(); - const { matrixFragment, copyId } = (sheetClipboardService as any)._generateCopyContent(unitId, subUnitId, fromRange, []); + const { matrixFragment, copyId } = (sheetClipboardService as any)._generateCopyContent(unitId, subUnitId, discreteRangeToRange(fromRange), []); // cache the copy content for internal paste copyContentCache.set(copyId, { diff --git a/packages/sheets-ui/src/services/clipboard/__tests__/clipboard-test-bed.ts b/packages/sheets-ui/src/services/clipboard/__tests__/clipboard-test-bed.ts index 7af569c0c5b3..bda844d12baa 100644 --- a/packages/sheets-ui/src/services/clipboard/__tests__/clipboard-test-bed.ts +++ b/packages/sheets-ui/src/services/clipboard/__tests__/clipboard-test-bed.ts @@ -511,7 +511,7 @@ export class testPlatformService { isLinux: boolean = false; } -export function clipboardTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]) { +export function clipboardTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]) { const univer = new Univer(); const injector = univer.__getInjector(); const get = injector.get.bind(injector); @@ -565,7 +565,7 @@ export function clipboardTestBed(workbookConfig?: IWorkbookData, dependencies?: } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || TEST_WORKBOOK_DATA_DEMO); + const sheet = univer.createUniverSheet(workbookData || TEST_WORKBOOK_DATA_DEMO); const univerInstanceService = get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets-ui/src/services/clipboard/clipboard.service.ts b/packages/sheets-ui/src/services/clipboard/clipboard.service.ts index a453a88c8a5d..e9e704ba8d81 100644 --- a/packages/sheets-ui/src/services/clipboard/clipboard.service.ts +++ b/packages/sheets-ui/src/services/clipboard/clipboard.service.ts @@ -25,7 +25,6 @@ import { IUniverInstanceService, LocaleService, ObjectMatrix, - Rectangle, toDisposable, Tools, UniverInstanceType, @@ -39,25 +38,28 @@ import { } from '@univerjs/sheets'; import { HTML_CLIPBOARD_MIME_TYPE, IClipboardInterfaceService, INotificationService, IPlatformService, PLAIN_TEXT_CLIPBOARD_MIME_TYPE } from '@univerjs/ui'; import type { IDisposable } from '@wendellhu/redi'; -import { createIdentifier, Inject } from '@wendellhu/redi'; +import { createIdentifier, Inject, Injector } from '@wendellhu/redi'; import { BehaviorSubject } from 'rxjs'; import { PastePluginLark, PastePluginUniver, PastePluginWord } from '@univerjs/docs-ui'; import { IMarkSelectionService } from '../mark-selection/mark-selection.service'; import { SheetSkeletonManagerService } from '../sheet-skeleton-manager.service'; +import type { IDiscreteRange } from '../../controllers/utils/range-tools'; +import { rangeToDiscreteRange, virtualizeDiscreteRanges } from '../../controllers/utils/range-tools'; import { CopyContentCache, extractId, genId } from './copy-content-cache'; import { HtmlToUSMService } from './html-to-usm/converter'; import type { ICellDataWithSpanInfo, + IClipboardPropertyItem, IPasteTarget, ISheetClipboardHook, - ISheetRangeLocation, + ISheetDiscreteRangeLocation, IUniverSheetCopyDataModel, } from './type'; import { COPY_TYPE } from './type'; import { USMToHtmlService } from './usm-to-html/convertor'; -import { clipboardItemIsFromExcel, mergeSetRangeValues } from './utils'; +import { clipboardItemIsFromExcel, discreteRangeContainsRange, mergeSetRangeValues, rangeIntersectWithDiscreteRange } from './utils'; export const PREDEFINED_HOOK_NAME = { DEFAULT_COPY: 'default-copy', @@ -112,7 +114,8 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard @INotificationService private readonly _notificationService: INotificationService, @IPlatformService private readonly _platformService: IPlatformService, @Inject(LocaleService) private readonly _localeService: LocaleService, - @Inject(ErrorService) private readonly _errorService: ErrorService + @Inject(ErrorService) private readonly _errorService: ErrorService, + @Inject(Injector) private readonly _injector: Injector ) { super(); this._htmlToUSM = new HtmlToUSMService({ @@ -147,13 +150,13 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard } // 2. extract copy content for both internal and external - const { html, plain, matrixFragment, copyId } = copyContent; + const { html, plain, matrixFragment, copyId, discreteRange } = copyContent; // 3. cache the copy content for internal paste this._copyContentCache.set(copyId, { unitId: workbook.getUnitId(), subUnitId: worksheet.getSheetId(), - range: selection.range, + range: discreteRange, matrix: matrixFragment, copyType, }); @@ -259,7 +262,7 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard // get filtered out rows those are filtered out by plugins (e.g. filter feature) const filteredRows = hooks.reduce((acc, cur) => { - const rows = cur.getFilteredOutRows?.(); + const rows = cur.getFilteredOutRows?.(range); rows?.forEach((r) => acc.add(r)); return acc; }, new Set()); @@ -267,20 +270,31 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard // calculate selection matrix, span cells would only - maybe warn uses that cells are too may in the future const { startColumn, startRow, endColumn, endRow } = range; - const matrix = worksheet.getMatrixWithMergedCells(startRow, startColumn, endRow, endColumn); + const matrix = worksheet.getMatrixWithMergedCells(startRow, startColumn, endRow, endColumn, true); const matrixFragment = new ObjectMatrix(); + let rowIndex = startRow; + + const discreteRange: IDiscreteRange = { rows: [], cols: [] }; for (let r = startRow; r <= endRow; r++) { + if (filteredRows.has(r)) { + continue; + } + discreteRange.rows.push(r); for (let c = startColumn; c <= endColumn; c++) { const cellData = matrix.getValue(r, c); if (cellData) { - matrixFragment.setValue(r - startRow, c - startColumn, { + matrixFragment.setValue(rowIndex - startRow, c - startColumn, { ...getEmptyCell(), ...Tools.deepClone(cellData), }); } else { - matrixFragment.setValue(r - startRow, c - startColumn, getEmptyCell()); + matrixFragment.setValue(rowIndex - startRow, c - startColumn, getEmptyCell()); } } + rowIndex += 1; + } + for (let c = startColumn; c <= endColumn; c++) { + discreteRange.cols.push(c); } // convert matrix to html @@ -295,6 +309,7 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard plain, html, matrixFragment, + discreteRange, }; } @@ -307,8 +322,16 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard if (!target.selection) { return false; } + const accessor = { + get: this._injector.get.bind(this._injector), + }; + + const range = rangeToDiscreteRange(target.selection.range, accessor, target.unitId, target.subUnitId); + + if (!range) { + return false; + } - const range = target.selection.range; const { unitId, subUnitId } = target; const hooks = this._clipboardHooks; const enabledHooks: ISheetClipboardHook[] = []; @@ -367,28 +390,15 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard // this._logService.log('[SheetClipboardService]', 'pasting external content', html); // steps of pasting: - const target = this._getPastingTarget(); - const { selection, unitId, subUnitId } = target; - if (!selection) { - return false; - } // 1. get properties of the table by parsing raw html content, including col properties / row properties // cell properties and cell contents. const { rowProperties, colProperties, cellMatrix } = this._htmlToUSM.convert(html); - const { startColumn, endColumn, startRow, endRow } = cellMatrix.getDataRange(); - if (!cellMatrix || endColumn < startColumn || endRow < startRow) { - return false; - } - const worksheet = this._univerInstanceService - .getUniverSheetInstance(unitId) - ?.getSheetBySheetId(subUnitId); - if (!worksheet) { + if (!cellMatrix) { return false; } - const mergeData = worksheet?.getMergeData(); // 2. get filtered rows in the target pasting area and get the final pasting matrix // we also handle transpose pasting at this step @@ -398,18 +408,30 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard // 3. call hooks with cell position and properties and get mutations (both do mutations and undo mutations) // we also handle 'copy value only' or 'copy style only' as this step - const pastedRange = this._getPastedRange(cellMatrix, selection.range, mergeData); + const pasteTarget = this._getPastedRange(cellMatrix); // pastedRange.endColumn = pastedRange.startColumn + colCount; // pastedRange.endRow = pastedRange.startRow + rowCount; // If PastedRange is null, it means that the paste fails - if (!pastedRange) { + if (!pasteTarget) { + return false; + } + + + const worksheet = this._univerInstanceService + .getUniverSheetInstance(pasteTarget.unitId) + ?.getSheetBySheetId(pasteTarget.subUnitId); + if (!worksheet) { return false; } + const mergeData = worksheet?.getMergeData(); + if (mergeData) { - const pastedRangeLapWithMergedCell = mergeData.some((merge) => Rectangle.intersects(pastedRange, merge) && !Rectangle.contains(pastedRange, merge)); + const pastedRangeLapWithMergedCell = mergeData.some((m) => { + return rangeIntersectWithDiscreteRange(m, pasteTarget.pastedRange) && !discreteRangeContainsRange(pasteTarget.pastedRange, m); + }); if (pastedRangeLapWithMergedCell) { this._errorService.emit(this._localeService.t('clipboard.paste.overlappingMergedCells')); return false; @@ -423,25 +445,21 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard colProperties, cellMatrix, }, - { - unitId, - subUnitId, - pastedRange, - }, + pasteTarget, pasteType ); } private async _pasteInternal(copyId: string, pasteType: string): Promise { - const target = this._getPastingTarget(); - const { selection, unitId, subUnitId } = target; + // const target = this._getPastingTarget(); + // const { selection, unitId, subUnitId } = target; const cachedData = Tools.deepClone(this._copyContentCache.get(copyId)); const { range, matrix: cellMatrix, unitId: copyUnitId, subUnitId: copySubUnitId } = cachedData || {}; - if (!selection || !cellMatrix || !cachedData || !range || !copyUnitId || !copySubUnitId) { + if (!cellMatrix || !cachedData || !range || !copyUnitId || !copySubUnitId) { return false; } - if (!selection || !cellMatrix || !cachedData) { + if (!cellMatrix || !cachedData) { return false; } @@ -454,28 +472,28 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard } }); + + const pasteTarget = this._getPastedRange( + cellMatrix + ); + + if (!pasteTarget) { + return false; + } + const worksheet = this._univerInstanceService - .getUniverSheetInstance(copyUnitId) - ?.getSheetBySheetId(copySubUnitId); + .getUniverSheetInstance(pasteTarget.unitId) + ?.getSheetBySheetId(pasteTarget.subUnitId); if (!worksheet) { return false; } const mergeData = worksheet?.getMergeData(); - const { startColumn, endColumn, startRow, endRow } = range; - const pastedRange = this._getPastedRange( - cellMatrix, - selection.range, - mergeData - ); - - if (!pastedRange) { - return false; - } - if (mergeData) { - const pastedRangeLapWithMergedCell = mergeData.some((merge) => Rectangle.intersects(pastedRange, merge) && !Rectangle.contains(pastedRange, merge)); + const pastedRangeLapWithMergedCell = mergeData.some((m) => { + return rangeIntersectWithDiscreteRange(m, pasteTarget.pastedRange) && !discreteRangeContainsRange(pasteTarget.pastedRange, m); + }); if (pastedRangeLapWithMergedCell) { this._errorService.emit(this._localeService.t('clipboard.paste.overlappingMergedCells')); return false; @@ -487,17 +505,23 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard const defaultColumnWidth = worksheet.getConfig().defaultColumnWidth; const defaultRowHeight = worksheet.getConfig().defaultRowHeight; - const colProperties = []; - const rowProperties = []; + const colProperties: IClipboardPropertyItem[] = []; + const rowProperties: IClipboardPropertyItem[] = []; - for (let i = startColumn; i <= endColumn; i++) { + range.cols.forEach((i) => { const column = colManager.getColumnOrCreate(i); colProperties.push({ width: `${column.w || defaultColumnWidth}` }); - } + }); - for (let j = startRow; j <= endRow; j++) { + range.rows.forEach((j) => { const row = rowManager.getRowOrCreate(j); rowProperties.push({ height: `${row.ah || row.h || defaultRowHeight}` }); + }); + + if (cachedData.copyType === COPY_TYPE.CUT) { + const start = pasteTarget.pastedRange.rows[0]; + const end = range.rows[range.rows.length - 1] - range.rows[0] + start; + pasteTarget.pastedRange.rows = Array.from(new Array(end + 1).keys()).slice(start); } const pasteRes = this._pasteUSM( @@ -506,11 +530,7 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard colProperties, rowProperties, }, // paste data - { - unitId, // paste target - subUnitId, - pastedRange, - }, + pasteTarget, pasteType, { range, // paste source @@ -534,12 +554,11 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard data: IUniverSheetCopyDataModel, target: IPasteTarget, pasteType: string, - source?: ISheetRangeLocation & { copyId: string; copyType: COPY_TYPE } + source?: ISheetDiscreteRangeLocation & { copyId: string; copyType: COPY_TYPE } ): boolean { const { rowProperties, colProperties, cellMatrix } = data; const { unitId, subUnitId, pastedRange } = target; - const { startColumn, endColumn } = pastedRange; - const colCount = endColumn - startColumn + 1; + const colCount = pastedRange.cols.length; const hooks = this._clipboardHooks; const enabledHooks: ISheetClipboardHook[] = []; const disableCopying = hooks.some( @@ -636,14 +655,19 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard private _getSetSelectionOperation( unitId: string, subUnitId: string, - range: IRange, + range: IDiscreteRange, cellMatrix: ObjectMatrix ) { const worksheet = this._univerInstanceService.getUniverSheetInstance(unitId)?.getSheetBySheetId(subUnitId); if (!worksheet) { return null; } - const { startRow, startColumn } = range; + const { rows, cols } = range; + const startRow = rows[0]; + const startColumn = cols[0]; + const endRow = rows[rows.length - 1]; + const endColumn = cols[cols.length - 1]; + const primaryCell = { startRow, endRow: startRow, @@ -679,7 +703,12 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard unitId, subUnitId, pluginName: NORMAL_SELECTION_PLUGIN_NAME, - selections: [{ range, primary, style: null }], + selections: [{ range: { + startRow, + endRow, + startColumn, + endColumn, + }, primary, style: null }], }; return { id: SetSelectionsOperation.id, @@ -743,20 +772,38 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard private _transformPastedData( rowCount: number, colCount: number, - cellMatrix: ObjectMatrix, - range: IRange - ): IRange | null { - const { startRow, startColumn, endRow, endColumn } = range; + cellMatrix: ObjectMatrix + ): IPasteTarget | null { + const target = this._getPastingTarget(); + const { selection, unitId, subUnitId } = target; + if (!selection) { + return null; + } + const accessor = { + get: this._injector.get.bind(this._injector), + }; + const discreteRange = rangeToDiscreteRange(selection.range, accessor, unitId, subUnitId); + + if (!discreteRange) { + return null; + } + + const { ranges: [vRange], mapFunc } = virtualizeDiscreteRanges([discreteRange]); + const { startRow, startColumn, endRow, endColumn } = vRange; + const destinationRows = endRow - startRow + 1; const destinationColumns = endColumn - startColumn + 1; - const workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)!; - const worksheet = workbook.getActiveSheet(); - // const mergedRange = worksheet.getMergedCell(startRow, startColumn); + const workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET); + const worksheet = workbook?.getActiveSheet(); + if (!worksheet) { + return null; + } + const mergeData = worksheet.getMergeData(); // get all merged cells const mergedCellsInRange = mergeData.filter((rect) => - Rectangle.intersects({ startRow, startColumn, endRow, endColumn }, rect) + discreteRange.rows.includes(rect.startRow) && discreteRange.cols.includes(rect.startColumn) ); const mergedRange = mergedCellsInRange[0]; @@ -777,10 +824,10 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard if (mergedCellsInRange.length > 0 && (destinationRows !== rowCount || destinationColumns !== colCount)) { // Only merged cells, not other cells if ( - mergedRangeStartRow === startRow && - mergedRangeStartColumn === startColumn && - mergedRangeEndRow === endRow && - mergedRangeEndColumn === endColumn + mergedRangeStartRow === discreteRange.rows[0] && + mergedRangeStartColumn === discreteRange.cols[0] && + mergedRangeEndRow === discreteRange.rows[destinationRows - 1] && + mergedRangeEndColumn === discreteRange.cols[destinationColumns - 1] ) { const isMultiple = isMultipleCells(cellMatrix); if (isMultiple) { @@ -821,11 +868,17 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard } } } else if (mergedCellsInRange.length > 0) { - const isMatch = this._topLeftCellsMatch(rowCount, colCount, range); + const { row: topRow, col: leftCol } = mapFunc(startRow, startColumn); + const isMatch = this._topLeftCellsMatch(rowCount, colCount, { + topRow, + leftCol, + }); if (isMatch) { // Expand or shrink the destination to the same size as the original range - range.endRow = startRow + rowCount - 1; - range.endColumn = startColumn + colCount - 1; + const newDiscreteRange = this._expandOrShrinkRowsCols(unitId, subUnitId, discreteRange, colCount, rowCount); + + discreteRange.rows = newDiscreteRange.rows; + discreteRange.cols = newDiscreteRange.cols; } else if (endRow > mergedRange.endRow || endColumn > mergedRange.endColumn) { return null; } else { @@ -837,26 +890,51 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard } } else { // Expand or shrink the destination to the same size as the original range - range.endRow = startRow + rowCount - 1; - range.endColumn = startColumn + colCount - 1; + const newDiscreteRange = this._expandOrShrinkRowsCols(unitId, subUnitId, discreteRange, colCount, rowCount); + + discreteRange.rows = newDiscreteRange.rows; + discreteRange.cols = newDiscreteRange.cols; } - return range; + return { + pastedRange: discreteRange, + unitId, + subUnitId, + }; } - private _getPastedRange(cellMatrix: ObjectMatrix, pasteRange: IRange, mergedCell: IRange[]) { + private _getPastedRange(cellMatrix: ObjectMatrix) { + const target = this._getPastingTarget(); + const { selection, unitId, subUnitId } = target; + if (!selection) { + return null; + } + const accessor = { + get: this._injector.get.bind(this._injector), + }; + const discreteRange = rangeToDiscreteRange(selection.range, accessor, unitId, subUnitId); + + if (!discreteRange) { + return null; + } const { startColumn, endColumn, startRow, endRow } = cellMatrix.getDataRange(); const rowCount = endRow - startRow + 1; const colCount = endColumn - startColumn + 1; - const pasteStartRow = pasteRange.startRow; - const pasteStartColumn = pasteRange.startColumn; + const pasteSelectionRangeRowLen = discreteRange.rows.length; + const pasteSelectionRangeColLen = discreteRange.cols.length; - const pasteSelectionRangeRowLen = pasteRange.endRow - pasteRange.startRow + 1; - const pasteSelectionRangeColLen = pasteRange.endColumn - pasteRange.startColumn + 1; + const worksheet = this._univerInstanceService + .getUniverSheetInstance(unitId) + ?.getSheetBySheetId(subUnitId); + if (!worksheet) { + return null; + } + + const mergeData = worksheet?.getMergeData(); if (pasteSelectionRangeRowLen % rowCount === 0 && pasteSelectionRangeColLen % colCount === 0) { - const hasLapWithMerge = mergedCell?.some((merge) => Rectangle.intersects(pasteRange, merge)); + const hasLapWithMerge = mergeData?.some((merge) => rangeIntersectWithDiscreteRange(merge, discreteRange)); if (!hasLapWithMerge) { for (let r = 0; r < pasteSelectionRangeRowLen; r++) { for (let c = 0; c < pasteSelectionRangeColLen; c++) { @@ -864,39 +942,85 @@ export class SheetClipboardService extends Disposable implements ISheetClipboard cell && cellMatrix.setValue(r, c, cell); } } - return pasteRange; + return { + pastedRange: discreteRange, + unitId, + subUnitId, + }; } } + const newDiscreteRange = this._expandOrShrinkRowsCols(unitId, subUnitId, discreteRange, colCount, rowCount); + + discreteRange.rows = newDiscreteRange.rows; + discreteRange.cols = newDiscreteRange.cols; return { - ...pasteRange, - startRow: pasteStartRow, - startColumn: pasteStartColumn, - endRow: pasteStartRow + rowCount - 1, - endColumn: pasteStartColumn + colCount - 1, + pastedRange: discreteRange, + unitId, + subUnitId, }; } + private _expandOrShrinkRowsCols(unitId: string, subUnitId: string, range: IDiscreteRange, colCount: number, rowCount: number) { + const { rows, cols } = range; + const workbook = this._univerInstanceService.getUniverSheetInstance(unitId); + const worksheet = workbook?.getSheetBySheetId(subUnitId); + let newRows: number[]; + let newCols: number[]; + if (rows.length >= rowCount) { + newRows = rows.slice(0, rowCount); + } else { + newRows = rows.slice(0); + let rowIndex = rows[rows.length - 1] + 1; + while (newRows.length < rowCount) { + if (!worksheet!.getRowFiltered(rowIndex)) { + newRows.push(rowIndex); + } + rowIndex++; + } + } + + if (cols.length >= colCount) { + newCols = cols.slice(0, colCount); + } else { + newCols = cols.slice(0); + let colIndex = cols[cols.length - 1] + 1; + while (newCols.length < colCount) { + newCols.push(colIndex); + colIndex++; + } + } + + return { + rows: newRows, + cols: newCols, + }; + } + + /** * Determine whether the cells starting from the upper left corner of the range (merged or non-merged or combined) are consistent with the size of the original data * @param cellMatrix * @param range */ - private _topLeftCellsMatch(rowCount: number, colCount: number, range: IRange): boolean { + private _topLeftCellsMatch(rowCount: number, colCount: number, range: { topRow: number; leftCol: number }): boolean { const workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)!; - const worksheet = workbook.getActiveSheet(); - const { startRow, startColumn, endRow, endColumn } = range; + const worksheet = workbook?.getActiveSheet(); + if (!worksheet) { + return false; + } + const { topRow, leftCol } = range; const isRowAcross = rowAcrossMergedCell( - startRow + rowCount - 1, - startColumn, - startColumn + rowCount - 1, + topRow + rowCount - 1, + leftCol, + leftCol + colCount - 1, worksheet ); const isColAcross = columnAcrossMergedCell( - startColumn + colCount - 1, - startRow, - startRow + rowCount - 1, + leftCol + colCount - 1, + topRow, + topRow + rowCount - 1, worksheet ); diff --git a/packages/sheets-ui/src/services/clipboard/copy-content-cache.ts b/packages/sheets-ui/src/services/clipboard/copy-content-cache.ts index ac3252408ee7..2e71416ac4c4 100644 --- a/packages/sheets-ui/src/services/clipboard/copy-content-cache.ts +++ b/packages/sheets-ui/src/services/clipboard/copy-content-cache.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -import type { IRange, Nullable, ObjectMatrix } from '@univerjs/core'; +import type { Nullable, ObjectMatrix } from '@univerjs/core'; import { LRUMap, Tools } from '@univerjs/core'; +import type { IDiscreteRange } from '../../controllers/utils/range-tools'; import type { COPY_TYPE, ICellDataWithSpanInfo } from './type'; const COPY_CONTENT_CACHE_LIMIT = 10; @@ -25,7 +26,7 @@ const ID_LENGTH = 6; export interface ICopyContentCacheData { subUnitId: string; unitId: string; - range: IRange; + range: IDiscreteRange; copyType: COPY_TYPE; matrix: Nullable>; } diff --git a/packages/sheets-ui/src/services/clipboard/type.ts b/packages/sheets-ui/src/services/clipboard/type.ts index 0ae87ff9eefa..eced9ca8e13e 100644 --- a/packages/sheets-ui/src/services/clipboard/type.ts +++ b/packages/sheets-ui/src/services/clipboard/type.ts @@ -15,6 +15,7 @@ */ import type { ICellData, IDocumentData, IMutationInfo, IRange, ObjectMatrix } from '@univerjs/core'; +import type { IDiscreteRange } from '../../controllers/utils/range-tools'; export enum COPY_TYPE { COPY = 'COPY', @@ -53,7 +54,7 @@ export interface IUniverSheetCopyDataModel { } export interface IPasteTarget { - pastedRange: IRange; + pastedRange: IDiscreteRange; subUnitId: string; unitId: string; } @@ -69,6 +70,12 @@ export interface ISheetRangeLocation { subUnitId: string; unitId: string; } + +export interface ISheetDiscreteRangeLocation { + range: IDiscreteRange; + subUnitId: string; + unitId: string; +} export interface ISpecialPasteInfo { label: string; icon?: string; @@ -132,15 +139,15 @@ export interface ISheetClipboardHook { * * @returns if it block copying it should return false */ - onBeforePaste?(pasteTo: ISheetRangeLocation): boolean; + onBeforePaste?(pasteTo: ISheetDiscreteRangeLocation): boolean; /** * * @param row * @param col */ onPasteCells?( - pasteFrom: ISheetRangeLocation | null, - pasteTo: ISheetRangeLocation, + pasteFrom: ISheetDiscreteRangeLocation | null, + pasteTo: ISheetDiscreteRangeLocation, data: ObjectMatrix, payload: ICopyPastePayload ): { @@ -148,7 +155,7 @@ export interface ISheetClipboardHook { redos: IMutationInfo[]; }; onPasteRows?( - pasteTo: ISheetRangeLocation, + pasteTo: ISheetDiscreteRangeLocation, rowProperties: IClipboardPropertyItem[], payload: ICopyPastePayload ): { @@ -156,7 +163,7 @@ export interface ISheetClipboardHook { redos: IMutationInfo[]; }; onPasteColumns?( - pasteTo: ISheetRangeLocation, + pasteTo: ISheetDiscreteRangeLocation, colProperties: IClipboardPropertyItem[], payload: ICopyPastePayload ): { @@ -164,7 +171,7 @@ export interface ISheetClipboardHook { redos: IMutationInfo[]; }; onPastePlainText?( - pasteTo: ISheetRangeLocation, + pasteTo: ISheetDiscreteRangeLocation, text: string, payload: ICopyPastePayload ): { @@ -177,5 +184,5 @@ export interface ISheetClipboardHook { * The callback would be called before the clipboard service decides what region need to be copied from or pasted to. * It would jump over these filtered rows when copying or pasting. */ - getFilteredOutRows?(): number[]; + getFilteredOutRows?(range: IRange): number[]; } diff --git a/packages/sheets-ui/src/services/clipboard/utils.ts b/packages/sheets-ui/src/services/clipboard/utils.ts index 8e8e1187a0d1..487e62c77fe4 100644 --- a/packages/sheets-ui/src/services/clipboard/utils.ts +++ b/packages/sheets-ui/src/services/clipboard/utils.ts @@ -18,6 +18,7 @@ import { ObjectMatrix } from '@univerjs/core'; import type { ICellData, IMutationInfo, IObjectMatrixPrimitiveType, IRange, Nullable } from '@univerjs/core'; import type { ISetRangeValuesMutationParams } from '@univerjs/sheets'; import { SetRangeValuesMutation } from '@univerjs/sheets'; +import type { IDiscreteRange } from '../../controllers/utils/range-tools'; /** * @@ -151,3 +152,31 @@ export function mergeSetRangeValues(mutations: IMutationInfo[]) { } return newMutations; } + + +export function rangeIntersectWithDiscreteRange(range: IRange, discrete: IDiscreteRange) { + const { startRow, endRow, startColumn, endColumn } = range; + for (let i = startRow; i <= endRow; i++) { + for (let j = startColumn; j <= endColumn; j++) { + if (discrete.rows.includes(i) && discrete.cols.includes(j)) { + return true; + } + } + } +} + + +export function discreteRangeContainsRange(discrete: IDiscreteRange, range: IRange) { + const { startRow, endRow, startColumn, endColumn } = range; + for (let i = startRow; i <= endRow; i++) { + if (!discrete.rows.includes(i)) { + return false; + } + } + for (let j = startColumn; j <= endColumn; j++) { + if (!discrete.cols.includes(j)) { + return false; + } + } + return true; +} diff --git a/packages/sheets-ui/src/services/hover-manager.service.ts b/packages/sheets-ui/src/services/hover-manager.service.ts index 3f22733757ba..8b3c19b366d8 100644 --- a/packages/sheets-ui/src/services/hover-manager.service.ts +++ b/packages/sheets-ui/src/services/hover-manager.service.ts @@ -15,7 +15,7 @@ */ import type { IPosition, IRange, Nullable, Workbook } from '@univerjs/core'; -import { IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; +import { Disposable, IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; import type { ISheetLocation } from '@univerjs/sheets'; import { Inject } from '@wendellhu/redi'; import { distinctUntilChanged, Subject } from 'rxjs'; @@ -23,14 +23,13 @@ import { IRenderManagerService, Vector2 } from '@univerjs/engine-render'; import { getCellIndexByOffsetWithMerge } from '../common/utils'; import { ScrollManagerService } from './scroll-manager.service'; import { SheetSkeletonManagerService } from './sheet-skeleton-manager.service'; -import { ISelectionRenderService } from './selection/selection-render.service'; export interface IHoverCellPosition { position: IPosition; location: ISheetLocation; } -export class HoverManagerService { +export class HoverManagerService extends Disposable { private _currentCell$ = new Subject>(); currentCell$ = this._currentCell$.asObservable().pipe(distinctUntilChanged(( (pre, aft) => ( @@ -47,9 +46,19 @@ export class HoverManagerService { @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @Inject(ScrollManagerService) private readonly _scrollManagerService: ScrollManagerService, @Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService, - @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, - @ISelectionRenderService private readonly _selectionRenderService: ISelectionRenderService - ) { } + @IRenderManagerService private readonly _renderManagerService: IRenderManagerService + ) { + super(); + + // TODO@weird94: any better solution here? + this._initCellDisposableListener(); + } + + private _initCellDisposableListener(): void { + this.disposeWithMe(this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.SHEET).subscribe((workbook) => { + if (!workbook) this._currentCell$.next(null); + })); + } private _calcActiveCell() { if (!this._lastPosition) return; diff --git a/packages/sheets-ui/src/services/mark-selection/mark-selection.service.ts b/packages/sheets-ui/src/services/mark-selection/mark-selection.service.ts index 17991f15a3fd..d2f76f87a738 100644 --- a/packages/sheets-ui/src/services/mark-selection/mark-selection.service.ts +++ b/packages/sheets-ui/src/services/mark-selection/mark-selection.service.ts @@ -15,10 +15,9 @@ */ import type { Workbook } from '@univerjs/core'; -import { Disposable, ICommandService, IUniverInstanceService, ThemeService, Tools, UniverInstanceType } from '@univerjs/core'; +import { Disposable, IUniverInstanceService, ThemeService, Tools, UniverInstanceType } from '@univerjs/core'; import { IRenderManagerService } from '@univerjs/engine-render'; import type { ISelectionWithStyle } from '@univerjs/sheets'; -import { SelectionManagerService } from '@univerjs/sheets'; import { createIdentifier, Inject } from '@wendellhu/redi'; import { ISelectionRenderService } from '../selection/selection-render.service'; @@ -52,10 +51,8 @@ export class MarkSelectionService extends Disposable implements IMarkSelectionSe @IUniverInstanceService private readonly _currentService: IUniverInstanceService, @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, @ISelectionRenderService private readonly _selectionRenderService: ISelectionRenderService, - @ICommandService private readonly _commandService: ICommandService, @Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService, - @Inject(ThemeService) private readonly _themeService: ThemeService, - @Inject(SelectionManagerService) private readonly _selectionManagerService: SelectionManagerService + @Inject(ThemeService) private readonly _themeService: ThemeService ) { super(); } diff --git a/packages/sheets-ui/src/services/selection/__test__/create-service-test-bed.ts b/packages/sheets-ui/src/services/selection/__test__/create-service-test-bed.ts index 13d7d064e250..8bce8d588a3f 100644 --- a/packages/sheets-ui/src/services/selection/__test__/create-service-test-bed.ts +++ b/packages/sheets-ui/src/services/selection/__test__/create-service-test-bed.ts @@ -104,7 +104,7 @@ export interface ITestBed { sheet: Workbook; } -export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]): ITestBed { +export function createCommandTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]): ITestBed { const univer = new Univer(); const injector = univer.__getInjector(); @@ -126,7 +126,7 @@ export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencie } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || TEST_WORKBOOK_DATA_DEMO()); + const sheet = univer.createUniverSheet(workbookData || TEST_WORKBOOK_DATA_DEMO()); const univerInstanceService = injector.get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets-ui/src/views/header-unhide-shape.ts b/packages/sheets-ui/src/views/header-unhide-shape.ts index 00b762fd9ce2..1106ea4a6e82 100644 --- a/packages/sheets-ui/src/views/header-unhide-shape.ts +++ b/packages/sheets-ui/src/views/header-unhide-shape.ts @@ -19,7 +19,7 @@ import { Rect, RegularPolygon, Shape } from '@univerjs/engine-render'; import { HEADER_MENU_BACKGROUND_COLOR, HEADER_MENU_SHAPE_TRIANGLE_FILL } from './header-menu-shape'; -export const enum HeaderUnhideShapeType { +export enum HeaderUnhideShapeType { ROW, COLUMN, } diff --git a/packages/sheets-ui/src/views/operate-container/AutoFillPopupMenu.tsx b/packages/sheets-ui/src/views/operate-container/AutoFillPopupMenu.tsx index 357640fe7ca8..b3de2d2fd4c5 100644 --- a/packages/sheets-ui/src/views/operate-container/AutoFillPopupMenu.tsx +++ b/packages/sheets-ui/src/views/operate-container/AutoFillPopupMenu.tsx @@ -15,7 +15,7 @@ */ import type { ICommandInfo } from '@univerjs/core'; -import { ICommandService, IUniverInstanceService, LocaleService, Rectangle, toDisposable } from '@univerjs/core'; +import { ICommandService, IUniverInstanceService, LocaleService, toDisposable } from '@univerjs/core'; import { Dropdown } from '@univerjs/design'; import { IRenderManagerService } from '@univerjs/engine-render'; import { Autofill, CheckMarkSingle, MoreDownSingle } from '@univerjs/icons'; @@ -111,8 +111,9 @@ export const AutoFillPopupMenu: React.FC<{}> = () => { autoFillService.showMenu$.subscribe((show) => { const { source, target } = autoFillService.autoFillLocation || { source: null, target: null }; if (show && source && target) { - const selection = Rectangle.union(source, target); - setAnchor({ row: selection.endRow, col: selection.endColumn }); + const lastRow = Math.max(source.rows[source.rows.length - 1], target.rows[target.rows.length - 1]); + const lastCol = Math.max(source.cols[source.cols.length - 1], target.cols[target.cols.length - 1]); + setAnchor({ row: lastRow, col: lastCol }); } }) ); diff --git a/packages/sheets/src/basics/selection.ts b/packages/sheets/src/basics/selection.ts index fa7c29ac533f..e2e7fdc0db51 100644 --- a/packages/sheets/src/basics/selection.ts +++ b/packages/sheets/src/basics/selection.ts @@ -27,7 +27,7 @@ import { getCellInfoInMergeData } from '@univerjs/engine-render'; export const SELECTION_CONTROL_BORDER_BUFFER_WIDTH = 1.5; // The draggable range of the selection is too thin, making it easy for users to miss. Therefore, a buffer gap is provided to make it easier for users to select. -export const SELECTION_CONTROL_BORDER_BUFFER_COLOR = 'rgba(255,255,255, 0.01)'; +export const SELECTION_CONTROL_BORDER_BUFFER_COLOR = 'rgba(255, 255, 255, 0.01)'; /** * Whether to display the controller that modifies the selection, distributed in 8 locations diff --git a/packages/sheets/src/commands/commands/__tests__/create-command-test-bed.ts b/packages/sheets/src/commands/commands/__tests__/create-command-test-bed.ts index ce4a1fcd0992..c11b871c5359 100644 --- a/packages/sheets/src/commands/commands/__tests__/create-command-test-bed.ts +++ b/packages/sheets/src/commands/commands/__tests__/create-command-test-bed.ts @@ -34,6 +34,7 @@ import enUS from '../../../locale/en-US'; import { BorderStyleManagerService } from '../../../services/border-style-manager.service'; import { SelectionManagerService } from '../../../services/selection-manager.service'; import { SheetInterceptorService } from '../../../services/sheet-interceptor/sheet-interceptor.service'; +import { SheetPermissionService } from '../../../services/permission'; const TEST_WORKBOOK_DATA_DEMO: IWorkbookData = { id: 'test', @@ -76,7 +77,7 @@ export interface ITestBed { sheet: Workbook; } -export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]): ITestBed { +export function createCommandTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]): ITestBed { const univer = new Univer(); const injector = univer.__getInjector(); @@ -97,13 +98,14 @@ export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencie injector.add([SelectionManagerService]); injector.add([BorderStyleManagerService]); injector.add([SheetInterceptorService]); + injector.add([SheetPermissionService]); dependencies?.forEach((d) => injector.add(d)); } } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(Tools.deepClone(workbookConfig || TEST_WORKBOOK_DATA_DEMO)); + const sheet = univer.createUniverSheet(Tools.deepClone(workbookData || TEST_WORKBOOK_DATA_DEMO)); const univerInstanceService = injector.get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets/src/commands/commands/__tests__/set-range-values.command.spec.ts b/packages/sheets/src/commands/commands/__tests__/set-range-values.command.spec.ts index b440b38df5e1..f0a278d06363 100644 --- a/packages/sheets/src/commands/commands/__tests__/set-range-values.command.spec.ts +++ b/packages/sheets/src/commands/commands/__tests__/set-range-values.command.spec.ts @@ -101,6 +101,7 @@ describe('Test set range values commands', () => { afterEach(() => { univer.dispose(); }); + describe('set range values', () => { describe('correct situations', () => { it('will set range values when there is a selected range', async () => { diff --git a/packages/sheets/src/commands/commands/__tests__/set-row-col-visible.command.spec.ts b/packages/sheets/src/commands/commands/__tests__/set-row-col-visible.command.spec.ts index 67b899c994fb..157ab84dc87f 100644 --- a/packages/sheets/src/commands/commands/__tests__/set-row-col-visible.command.spec.ts +++ b/packages/sheets/src/commands/commands/__tests__/set-row-col-visible.command.spec.ts @@ -87,10 +87,10 @@ describe('Test row col hide/unhine commands', () => { return worksheet.getColumnCount(); } - function getRowVisible(row: number): boolean { + function getRowRawVisible(row: number): boolean { const workbook = get(IUniverInstanceService).getCurrentUnitForType(UniverInstanceType.SHEET)!; const worksheet = workbook.getActiveSheet(); - return worksheet.getRowVisible(row); + return worksheet.getRowRawVisible(row); } function getColVisible(col: number): boolean { @@ -149,32 +149,32 @@ describe('Test row col hide/unhine commands', () => { describe('hide / unhide rows', () => { it('should work', async () => { - expect(getRowVisible(0)).toBeTruthy(); + expect(getRowRawVisible(0)).toBeTruthy(); selectRow(0, 0); await commandService.executeCommand(SetRowHiddenCommand.id); - expect(getRowVisible(0)).toBeFalsy(); + expect(getRowRawVisible(0)).toBeFalsy(); await commandService.executeCommand(UndoCommand.id); - expect(getRowVisible(0)).toBeTruthy(); + expect(getRowRawVisible(0)).toBeTruthy(); await commandService.executeCommand(RedoCommand.id); - expect(getRowVisible(0)).toBeFalsy(); + expect(getRowRawVisible(0)).toBeFalsy(); selectRow(2, 2); await commandService.executeCommand(SetRowHiddenCommand.id); - expect(getRowVisible(2)).toBeFalsy(); + expect(getRowRawVisible(2)).toBeFalsy(); // select a range and invoke unhide command will unhide all // hidden rows in the selected range selectRow(0, 7); await commandService.executeCommand(SetSelectedRowsVisibleCommand.id); - expect(getRowVisible(0)).toBeTruthy(); - expect(getRowVisible(2)).toBeTruthy(); + expect(getRowRawVisible(0)).toBeTruthy(); + expect(getRowRawVisible(2)).toBeTruthy(); await commandService.executeCommand(UndoCommand.id); - expect(getRowVisible(0)).toBeFalsy(); - expect(getRowVisible(2)).toBeFalsy(); + expect(getRowRawVisible(0)).toBeFalsy(); + expect(getRowRawVisible(2)).toBeFalsy(); }); }); diff --git a/packages/sheets/src/commands/commands/copy-to-worksheet.command.ts b/packages/sheets/src/commands/commands/copy-to-worksheet.command.ts deleted file mode 100644 index 8ab231852f90..000000000000 --- a/packages/sheets/src/commands/commands/copy-to-worksheet.command.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright 2023-present DreamNum Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { ICommand } from '@univerjs/core'; -import { CommandType, ICommandService, IUndoRedoService, IUniverInstanceService, Tools } from '@univerjs/core'; -import type { IAccessor } from '@wendellhu/redi'; - -import type { ISetWorksheetConfigMutationParams } from '../mutations/set-worksheet-config.mutation'; -import { - SetWorksheetConfigMutation, - SetWorksheetConfigUndoMutationFactory, -} from '../mutations/set-worksheet-config.mutation'; -import { getSheetCommandTarget } from './utils/target-util'; - -export interface ICopySheetToCommandParams { - unitId?: string; - subUnitId?: string; - copyToUnitId?: string; - copyToSheetId?: string; -} - -export const CopySheetToCommand: ICommand = { - type: CommandType.COMMAND, - id: 'sheet.command.copy-sheet-to', - handler: async (accessor: IAccessor, params: ICopySheetToCommandParams) => { - const commandService = accessor.get(ICommandService); - const undoRedoService = accessor.get(IUndoRedoService); - const univerInstanceService = accessor.get(IUniverInstanceService); - - const fromTarget = getSheetCommandTarget(univerInstanceService, { unitId: params.unitId, subUnitId: params.subUnitId }); - const toTarget = getSheetCommandTarget(univerInstanceService, { unitId: params.copyToUnitId, subUnitId: params.copyToSheetId }); - if (!fromTarget || !toTarget) return false; - - const { subUnitId, unitId, worksheet } = fromTarget; - const { unitId: copyToUnitId, subUnitId: copyToSheetId } = toTarget; - if (unitId === copyToUnitId && subUnitId === copyToSheetId) return false; - - const config = Tools.deepClone(worksheet.getConfig()); - - const setWorksheetConfigMutationParams: ISetWorksheetConfigMutationParams = { - unitId, - subUnitId, - config, - }; - - const undoMutationParams: ISetWorksheetConfigMutationParams = SetWorksheetConfigUndoMutationFactory( - accessor, - setWorksheetConfigMutationParams - ); - - const result = commandService.syncExecuteCommand( - SetWorksheetConfigMutation.id, - setWorksheetConfigMutationParams - ); - - if (result) { - undoRedoService.pushUndoRedo({ - unitID: unitId, - undoMutations: [{ id: SetWorksheetConfigMutation.id, params: undoMutationParams }], - redoMutations: [{ id: SetWorksheetConfigMutation.id, params: setWorksheetConfigMutationParams }], - }); - return true; - } - return false; - }, -}; diff --git a/packages/sheets/src/commands/commands/insert-row-col.command.ts b/packages/sheets/src/commands/commands/insert-row-col.command.ts index f42cb5010fca..96fb8498c388 100644 --- a/packages/sheets/src/commands/commands/insert-row-col.command.ts +++ b/packages/sheets/src/commands/commands/insert-row-col.command.ts @@ -18,7 +18,6 @@ import type { ICellData, ICommand, IObjectMatrixPrimitiveType, IRange, Workbook import { BooleanNumber, CommandType, - Dimension, Direction, ICommandService, IUndoRedoService, @@ -31,7 +30,6 @@ import type { IAccessor } from '@wendellhu/redi'; import type { IInsertColMutationParams, - IInsertRangeMutationParams, IInsertRowMutationParams, IRemoveColMutationParams, IRemoveRowsMutationParams, @@ -64,10 +62,11 @@ export interface IInsertRowCommandParams { cellValue?: IObjectMatrixPrimitiveType; } export const InsertRowCommandId = 'sheet.command.insert-row'; + /** - * @internal - * * this command and its interface should not be exported from index.ts + * + * @internal */ export const InsertRowCommand: ICommand = { type: CommandType.COMMAND, @@ -252,7 +251,7 @@ export const InsertColCommand: ICommand = { const univerInstanceService = accessor.get(IUniverInstanceService); const sheetInterceptorService = accessor.get(SheetInterceptorService); - const { range, direction, subUnitId, unitId, cellValue } = params; + const { range, direction, subUnitId, unitId } = params; const { startColumn, endColumn } = params.range; const workbook = univerInstanceService.getUniverSheetInstance(params.unitId)!; const worksheet = workbook.getSheetBySheetId(params.subUnitId)!; @@ -274,13 +273,6 @@ export const InsertColCommand: ICommand = { insertColParams ); - const insertRangeMutationParams: IInsertRangeMutationParams = { - unitId: params.unitId, - subUnitId: params.subUnitId, - range: params.range, - shiftDimension: Dimension.COLUMNS, - cellValue, - }; const intercepted = sheetInterceptorService.onCommandExecute({ id: InsertColCommand.id, params, diff --git a/packages/sheets/src/commands/commands/remove-row-col.command.ts b/packages/sheets/src/commands/commands/remove-row-col.command.ts index 03c4d9d27a52..e13e237d1d67 100644 --- a/packages/sheets/src/commands/commands/remove-row-col.command.ts +++ b/packages/sheets/src/commands/commands/remove-row-col.command.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { ICommand, IRange } from '@univerjs/core'; +import type { ICommand, IMutationInfo, IRange } from '@univerjs/core'; import { CommandType, ICommandService, @@ -55,13 +55,14 @@ export const RemoveRowCommand: ICommand = { id: RemoveRowCommandId, + // eslint-disable-next-line max-lines-per-function handler: async (accessor: IAccessor, params?: IRemoveRowColCommandParams) => { const selectionManagerService = accessor.get(SelectionManagerService); const sheetInterceptorService = accessor.get(SheetInterceptorService); - let range = params?.range; - if (!range) range = selectionManagerService.getLast()?.range; - if (!range) return false; + let totalRange = params?.range; + if (!totalRange) totalRange = selectionManagerService.getLast()?.range; + if (!totalRange) return false; const univerInstanceService = accessor.get(IUniverInstanceService); const target = getSheetCommandTarget(univerInstanceService); @@ -69,42 +70,75 @@ export const RemoveRowCommand: ICommand = { const { workbook, worksheet, subUnitId, unitId } = target; - range = { - ...range, + totalRange = { + ...totalRange, startColumn: 0, endColumn: Math.max(worksheet.getMaxColumns() - 1, 0), }; - // row count - const removeRowsParams: IRemoveRowsMutationParams = { - unitId, - subUnitId, - range, - }; - const removedRows = worksheet.getCellMatrix().getSlice(range.startRow, range.endRow, 0, worksheet.getColumnCount() - 1); - const undoSetRangeValuesParams: ISetRangeValuesMutationParams = { - unitId, - subUnitId, - cellValue: removedRows.getMatrix(), - }; - const undoRemoveRowsParams: IInsertRowMutationParams = RemoveRowsUndoMutationFactory( - removeRowsParams, - worksheet - ); + const filterOutRowsInRemove: number[] = []; + for (let i = totalRange.startRow; i <= totalRange.endRow; i++) { + if (worksheet.getRowFiltered(i)) { + filterOutRowsInRemove.push(i); + } + } + + const ranges: IRange[] = []; + if (filterOutRowsInRemove.length) { + const starts = [totalRange.startRow, ...filterOutRowsInRemove.map((r) => r + 1)]; + const ends = [...filterOutRowsInRemove.map((r) => r - 1), totalRange.endRow]; + for (let i = starts.length - 1; i >= 0; i--) { + if (starts[i] <= ends[i]) { + ranges.push({ + startRow: starts[i], + endRow: ends[i], + startColumn: totalRange.startColumn, + endColumn: totalRange.endColumn, + }); + } + } + } else { + ranges.push(totalRange); + } + + const redos: IMutationInfo[] = []; + const undos: IMutationInfo[] = []; + + ranges.forEach((range) => { + // row count + const removeRowsParams: IRemoveRowsMutationParams = { + unitId, + subUnitId, + range, + }; + const removedRows = worksheet.getCellMatrix().getSlice(range.startRow, range.endRow, 0, worksheet.getColumnCount() - 1); + const undoSetRangeValuesParams: ISetRangeValuesMutationParams = { + unitId, + subUnitId, + cellValue: removedRows.getMatrix(), + }; + const undoRemoveRowsParams: IInsertRowMutationParams = RemoveRowsUndoMutationFactory( + removeRowsParams, + worksheet + ); + + redos.push({ id: RemoveRowMutation.id, params: removeRowsParams }); + undos.unshift({ id: InsertRowMutation.id, params: undoRemoveRowsParams }, { id: SetRangeValuesMutation.id, params: undoSetRangeValuesParams }); + }); const intercepted = sheetInterceptorService.onCommandExecute({ id: RemoveRowCommand.id, - params: { range } as IRemoveRowColCommandParams, + params: { range: totalRange } as IRemoveRowColCommandParams, }); const commandService = accessor.get(ICommandService); const result = sequenceExecute( [ ...(intercepted.preRedos ?? []), - { id: RemoveRowMutation.id, params: removeRowsParams }, + ...redos, ...intercepted.redos, - followSelectionOperation(range, workbook, worksheet), + followSelectionOperation(totalRange, workbook, worksheet), ], commandService ); @@ -112,8 +146,16 @@ export const RemoveRowCommand: ICommand = { if (result.result) { accessor.get(IUndoRedoService).pushUndoRedo({ unitID: unitId, - undoMutations: [...(intercepted.preUndos ?? []), { id: InsertRowMutation.id, params: undoRemoveRowsParams }, { id: SetRangeValuesMutation.id, params: undoSetRangeValuesParams }, ...intercepted.undos], - redoMutations: [...(intercepted.preRedos ?? []), { id: RemoveRowMutation.id, params: removeRowsParams }, ...intercepted.redos], + undoMutations: [ + ...(intercepted.preUndos ?? []), + ...undos, + ...intercepted.undos, + ], + redoMutations: [ + ...(intercepted.preRedos ?? []), + ...redos, + ...intercepted.redos, + ], }); return true; } diff --git a/packages/sheets/src/commands/commands/set-range-values.command.ts b/packages/sheets/src/commands/commands/set-range-values.command.ts index 1eae7cc491e3..513f73a4a20c 100644 --- a/packages/sheets/src/commands/commands/set-range-values.command.ts +++ b/packages/sheets/src/commands/commands/set-range-values.command.ts @@ -28,7 +28,6 @@ import { import type { IAccessor } from '@wendellhu/redi'; import { SelectionManagerService } from '../../services/selection-manager.service'; -import { INTERCEPTOR_POINT } from '../../services/sheet-interceptor/interceptor-const'; import { SheetInterceptorService } from '../../services/sheet-interceptor/sheet-interceptor.service'; import { SetRangeValuesMutation, SetRangeValuesUndoMutationFactory } from '../mutations/set-range-values.mutation'; import type { ISheetCommandSharedParams } from '../utils/interface'; @@ -102,14 +101,14 @@ export const SetRangeValuesCommand: ICommand = { }; const undoSetRangeValuesMutationParams = SetRangeValuesUndoMutationFactory(accessor, setRangeValuesMutationParams); - if ( - !sheetInterceptorService.fetchThroughInterceptors(INTERCEPTOR_POINT.PERMISSION)(null, { - id: SetRangeValuesCommand.id, - params: setRangeValuesMutationParams, - }) - ) { - return false; - } + // if ( + // !sheetInterceptorService.fetchThroughInterceptors(INTERCEPTOR_POINT.PERMISSION)(null, { + // id: SetRangeValuesCommand.id, + // params: setRangeValuesMutationParams, + // }) + // ) { + // return false; + // } const setValueMutationResult = commandService.syncExecuteCommand( SetRangeValuesMutation.id, diff --git a/packages/sheets/src/commands/commands/utils/selection-utils.ts b/packages/sheets/src/commands/commands/utils/selection-utils.ts index 96bf3339eb07..f4103af141f7 100644 --- a/packages/sheets/src/commands/commands/utils/selection-utils.ts +++ b/packages/sheets/src/commands/commands/utils/selection-utils.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import type { IRange, ISelectionCell, Workbook, Worksheet } from '@univerjs/core'; +import type { IRange, ISelectionCell, Nullable, Workbook, Worksheet } from '@univerjs/core'; import { RANGE_TYPE, Rectangle, selectionToArray } from '@univerjs/core'; import { NORMAL_SELECTION_PLUGIN_NAME } from '../../../services/selection-manager.service'; import type { ISetSelectionsOperationParams } from '../../operations/selection.operation'; import { SetSelectionsOperation } from '../../operations/selection.operation'; +import type { ISelectionWithStyle } from '../../../basics/selection'; export interface IExpandParams { left?: boolean; @@ -195,3 +196,17 @@ export const followSelectionOperation = (range: IRange, workbook: Workbook, work selections: [{ range, primary: getPrimaryForRange(range, worksheet) }], } as ISetSelectionsOperationParams, }); + +/** + * Examine if a selection only contains a single cell (a merged cell is considered as a single cell in this case). + * @param selection + * @returns `true` if the selection only contains a single cell. + */ +export function isSingleCellSelection(selection: Nullable): boolean { + if (!selection) { + return false; + } + + const { range, primary } = selection; + return Rectangle.equals(range, primary); +} diff --git a/packages/sheets/src/commands/mutations/__tests__/create-command-test-bed.ts b/packages/sheets/src/commands/mutations/__tests__/create-command-test-bed.ts index b5238cbf8b2f..9ed775050ca0 100644 --- a/packages/sheets/src/commands/mutations/__tests__/create-command-test-bed.ts +++ b/packages/sheets/src/commands/mutations/__tests__/create-command-test-bed.ts @@ -104,7 +104,7 @@ export interface ITestBed { sheet: Workbook; } -export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]): ITestBed { +export function createCommandTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]): ITestBed { const univer = new Univer(); const injector = univer.__getInjector(); @@ -127,7 +127,7 @@ export function createCommandTestBed(workbookConfig?: IWorkbookData, dependencie } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || TEST_WORKBOOK_DATA_DEMO()); + const sheet = univer.createUniverSheet(workbookData || TEST_WORKBOOK_DATA_DEMO()); const univerInstanceService = injector.get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets/src/commands/mutations/remove-row-col.mutation.ts b/packages/sheets/src/commands/mutations/remove-row-col.mutation.ts index 6a906e163617..961288c9fb3d 100644 --- a/packages/sheets/src/commands/mutations/remove-row-col.mutation.ts +++ b/packages/sheets/src/commands/mutations/remove-row-col.mutation.ts @@ -65,6 +65,14 @@ export const RemoveRowMutation: IMutation = { const manager = worksheet.getRowManager(); const rowPrimitive = manager.getRowData(); + const filterOutRows = []; + for (let i = range.startRow; i <= range.endRow; i++) { + if (worksheet.getRowFiltered(i)) { + filterOutRows.push(i); + } + } + + const rowCount = range.endRow - range.startRow + 1; spliceArray(range.startRow, rowCount, rowPrimitive); diff --git a/packages/sheets/src/commands/mutations/set-range-values.mutation.ts b/packages/sheets/src/commands/mutations/set-range-values.mutation.ts index 45572d1a4d65..678c22027bde 100644 --- a/packages/sheets/src/commands/mutations/set-range-values.mutation.ts +++ b/packages/sheets/src/commands/mutations/set-range-values.mutation.ts @@ -31,7 +31,7 @@ import type { ITextStyle, Nullable, } from '@univerjs/core'; -import { CellValueType, CommandType, IUniverInstanceService, normalizeTextRuns, ObjectMatrix, Tools } from '@univerjs/core'; +import { CellValueType, CommandType, isBooleanString, isSafeNumeric, IUniverInstanceService, normalizeTextRuns, ObjectMatrix, Tools } from '@univerjs/core'; import type { IAccessor } from '@wendellhu/redi'; /** Params of `SetRangeValuesMutation` */ @@ -527,20 +527,3 @@ export function mergeRichTextStyle(p: IDocumentData, newStyle: Nullable { - const workbook = accessor.get(IUniverInstanceService).getUniverSheetInstance(params.unitId); - const worksheet = workbook!.getSheetBySheetId(params.subUnitId)!; - const config = Tools.deepClone(worksheet.getConfig()); - - return { - unitId: params.unitId, - subUnitId: params.subUnitId, - config, - }; -}; - -/** @deprecated */ -export const SetWorksheetConfigMutation: IMutation = { - id: 'sheet.mutation.set-worksheet-config', - type: CommandType.MUTATION, - handler: (accessor, params) => { - const workbook = accessor.get(IUniverInstanceService).getUniverSheetInstance(params.unitId); - if (!workbook) return false; - const worksheet = workbook.getSheetBySheetId(params.subUnitId); - if (!worksheet) return false; - - // TODO: setConfig is going to be deprecated - // worksheet.setConfig(params.config); - - return true; - }, -}; diff --git a/packages/sheets/src/controllers/__tests__/util.ts b/packages/sheets/src/controllers/__tests__/util.ts index 028dea3d0aeb..e162d37d7abc 100644 --- a/packages/sheets/src/controllers/__tests__/util.ts +++ b/packages/sheets/src/controllers/__tests__/util.ts @@ -43,7 +43,7 @@ const TEST_WORKBOOK_DATA_DEMO: IWorkbookData = { styles: {}, }; -export function createTestBase(workbookConfig?: IWorkbookData, dependencies?: Dependency[]) { +export function createTestBase(workbookData?: IWorkbookData, dependencies?: Dependency[]) { const univer = new Univer(); const injector = univer.__getInjector(); const get = injector.get.bind(injector); @@ -68,7 +68,7 @@ export function createTestBase(workbookConfig?: IWorkbookData, dependencies?: De } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || TEST_WORKBOOK_DATA_DEMO); + const sheet = univer.createUniverSheet(workbookData || TEST_WORKBOOK_DATA_DEMO); const univerInstanceService = get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets/src/index.ts b/packages/sheets/src/index.ts index 917934ab49fe..c320f4754dac 100644 --- a/packages/sheets/src/index.ts +++ b/packages/sheets/src/index.ts @@ -43,7 +43,7 @@ export { transformCellDataToSelectionData, } from './basics/selection'; export { getSheetCommandTarget, getSheetCommandTargetWorkbook, getSheetMutationTarget } from './commands/commands/utils/target-util'; -export { alignToMergedCellsBorders, getCellAtRowCol, setEndForRange } from './commands/commands/utils/selection-utils'; +export { alignToMergedCellsBorders, getCellAtRowCol, setEndForRange, isSingleCellSelection } from './commands/commands/utils/selection-utils'; export { MAX_CELL_PER_SHEET_KEY } from './controllers/config/config'; export { BorderStyleManagerService, type IBorderInfo } from './services/border-style-manager.service'; export { getCurrentSheetDisabled$, SheetEditablePermission, SheetPermissionService } from './services/permission'; @@ -294,3 +294,4 @@ export { RemoveDefinedNameCommand } from './commands/commands/remove-defined-nam export { SetDefinedNameCommand, type ISetDefinedNameCommandParams } from './commands/commands/set-defined-name.command'; export { ScrollToCellOperation } from './commands/operations/scroll-to-cell.operation'; export type { FormatType } from './services/numfmt/type'; +export { getMoveRangeUndoRedoMutations } from './commands/commands/move-range.command'; diff --git a/packages/sheets/src/services/__tests__/util.ts b/packages/sheets/src/services/__tests__/util.ts index 31df6c8fe4d1..473065ae8c9f 100644 --- a/packages/sheets/src/services/__tests__/util.ts +++ b/packages/sheets/src/services/__tests__/util.ts @@ -56,7 +56,7 @@ export const TEST_WORKBOOK_DATA_DEMO: IWorkbookData = { styles: {}, }; -export function createTestBase(workbookConfig?: IWorkbookData, dependencies?: Dependency[]) { +export function createTestBase(workbookData?: IWorkbookData, dependencies?: Dependency[]) { const univer = new Univer(); const injector = univer.__getInjector(); @@ -82,7 +82,7 @@ export function createTestBase(workbookConfig?: IWorkbookData, dependencies?: De } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || TEST_WORKBOOK_DATA_DEMO); + const sheet = univer.createUniverSheet(workbookData || TEST_WORKBOOK_DATA_DEMO); const univerInstanceService = get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets/src/services/numfmt/numfmt.service.ts b/packages/sheets/src/services/numfmt/numfmt.service.ts index 276180909e8d..c4a8d728b45d 100644 --- a/packages/sheets/src/services/numfmt/numfmt.service.ts +++ b/packages/sheets/src/services/numfmt/numfmt.service.ts @@ -17,22 +17,19 @@ import type { IRange } from '@univerjs/core'; import { Disposable, - ICommandService, ILogService, IResourceManagerService, IUniverInstanceService, Range, } from '@univerjs/core'; -import { Inject } from '@wendellhu/redi'; import type { INumfmtService } from './type'; export class NumfmtService extends Disposable implements INumfmtService { constructor( - @Inject(ICommandService) private _commandService: ICommandService, - @Inject(IResourceManagerService) private _resourceManagerService: IResourceManagerService, - @Inject(IUniverInstanceService) private _univerInstanceService: IUniverInstanceService, - @Inject(ILogService) private _logService: ILogService + @IResourceManagerService private _resourceManagerService: IResourceManagerService, + @IUniverInstanceService private _univerInstanceService: IUniverInstanceService, + @ILogService private _logService: ILogService ) { super(); } diff --git a/packages/sheets/src/services/permission/sheet-permission.service.ts b/packages/sheets/src/services/permission/sheet-permission.service.ts index 6322e4a105f3..c55150ac5d66 100644 --- a/packages/sheets/src/services/permission/sheet-permission.service.ts +++ b/packages/sheets/src/services/permission/sheet-permission.service.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { PermissionService, Workbook } from '@univerjs/core'; +import type { Workbook } from '@univerjs/core'; import { DisposableCollection, IPermissionService, @@ -31,9 +31,6 @@ import { Inject } from '@wendellhu/redi'; import { takeUntil } from 'rxjs/operators'; import { of } from 'rxjs'; -import { SetRangeValuesCommand } from '../../commands/commands/set-range-values.command'; -import { INTERCEPTOR_POINT } from '../sheet-interceptor/interceptor-const'; -import { SheetInterceptorService } from '../sheet-interceptor/sheet-interceptor.service'; import { SheetEditablePermission } from './permission-point'; @OnLifecycle(LifecycleStages.Ready, SheetPermissionService) @@ -41,13 +38,12 @@ export class SheetPermissionService extends RxDisposable { private _disposableByUnit = new Map(); constructor( - @Inject(IPermissionService) private _permissionService: PermissionService, - @Inject(IUniverInstanceService) private _univerInstanceService: IUniverInstanceService, - @Inject(SheetInterceptorService) private _sheetInterceptorService: SheetInterceptorService + @Inject(IPermissionService) private _permissionService: IPermissionService, + @Inject(IUniverInstanceService) private _univerInstanceService: IUniverInstanceService ) { super(); + this._init(); - this._interceptCommandPermission(); } private _init() { @@ -55,17 +51,19 @@ export class SheetPermissionService extends RxDisposable { if (!workbook) return; this._interceptWorkbook(workbook); - this._univerInstanceService.getTypeOfUnitAdded$(UniverInstanceType.SHEET).pipe(takeUntil(this.dispose$)).subscribe((workbook) => this._interceptWorkbook(workbook)); - this._univerInstanceService.getTypeOfUnitDisposed$(UniverInstanceType.SHEET).pipe(takeUntil(this.dispose$)).subscribe((workbook) => { - const unitId = workbook.getUnitId(); - this._disposableByUnit.get(unitId)?.dispose(); - this._disposableByUnit.delete(unitId); - }); + this._univerInstanceService.getTypeOfUnitAdded$(UniverInstanceType.SHEET) + .pipe(takeUntil(this.dispose$)) + .subscribe((workbook) => this._interceptWorkbook(workbook)); + + this._univerInstanceService.getTypeOfUnitDisposed$(UniverInstanceType.SHEET) + .pipe(takeUntil(this.dispose$)) + .subscribe((workbook) => this._disposableByUnit.get(workbook.getUnitId())?.dispose()); } private _interceptWorkbook(workbook: Workbook): void { - const disposableCollection = new DisposableCollection(); const unitId = workbook.getUnitId(); + const disposableCollection = new DisposableCollection(); + workbook.getSheets().forEach((worksheet) => { const subUnitId = worksheet.getSheetId(); const sheetPermission = new SheetEditablePermission(unitId, subUnitId); @@ -88,33 +86,10 @@ export class SheetPermissionService extends RxDisposable { }) )); + disposableCollection.add(toDisposable(() => this._disposableByUnit.delete(unitId))); this._disposableByUnit.set(unitId, disposableCollection); } - private _interceptCommandPermission() { - this.disposeWithMe( - this._sheetInterceptorService.intercept(INTERCEPTOR_POINT.PERMISSION, { - priority: 99, - handler: (_value, commandInfo, next) => { - const workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)!; - const sheet = workbook?.getActiveSheet(); - const unitId = workbook?.getUnitId(); - const sheetId = sheet?.getSheetId(); - if (!unitId || !sheetId) { - return false; - } - switch (commandInfo.id) { - case SetRangeValuesCommand.id: { - return this.getSheetEditable(unitId, sheetId); - } - } - return next(); - }, - }) - ); - } - - // TODO@Gggpound: should get by unitId instead of the current one getEditable$(unitId?: string, sheetId?: string) { return of({ value: true, status: PermissionStatus.INIT }); diff --git a/packages/sheets/src/services/ref-range/__tests__/__testing__.ts b/packages/sheets/src/services/ref-range/__tests__/__testing__.ts index 31df6c8fe4d1..473065ae8c9f 100644 --- a/packages/sheets/src/services/ref-range/__tests__/__testing__.ts +++ b/packages/sheets/src/services/ref-range/__tests__/__testing__.ts @@ -56,7 +56,7 @@ export const TEST_WORKBOOK_DATA_DEMO: IWorkbookData = { styles: {}, }; -export function createTestBase(workbookConfig?: IWorkbookData, dependencies?: Dependency[]) { +export function createTestBase(workbookData?: IWorkbookData, dependencies?: Dependency[]) { const univer = new Univer(); const injector = univer.__getInjector(); @@ -82,7 +82,7 @@ export function createTestBase(workbookConfig?: IWorkbookData, dependencies?: De } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || TEST_WORKBOOK_DATA_DEMO); + const sheet = univer.createUniverSheet(workbookData || TEST_WORKBOOK_DATA_DEMO); const univerInstanceService = get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets/src/services/selection-manager.service.ts b/packages/sheets/src/services/selection-manager.service.ts index 1dd9ee93c51d..2ed4eb43822a 100644 --- a/packages/sheets/src/services/selection-manager.service.ts +++ b/packages/sheets/src/services/selection-manager.service.ts @@ -74,14 +74,7 @@ export class SelectionManagerService implements IDisposable { private _dirty: boolean = true; - // get isSelectionEnabled() { - // return this._isSelectionEnabled; - // } - - // get currentStyle() { - // return this._currentStyle; - // } - + // FIMXE: this dependency is not correct! constructor(@Inject(ThemeService) private readonly _themeService: ThemeService) {} getCurrent() { diff --git a/packages/sheets/src/services/sheet-interceptor/__tests__/create-core-test-bed.ts b/packages/sheets/src/services/sheet-interceptor/__tests__/create-core-test-bed.ts index 6da8913ec10e..a4e30935f6c2 100644 --- a/packages/sheets/src/services/sheet-interceptor/__tests__/create-core-test-bed.ts +++ b/packages/sheets/src/services/sheet-interceptor/__tests__/create-core-test-bed.ts @@ -43,7 +43,7 @@ const TEST_WORKBOOK_DATA: IWorkbookData = { styles: {}, }; -export function createCoreTestBed(workbookConfig?: IWorkbookData, dependencies?: Dependency[]) { +export function createCoreTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]) { const univer = new Univer(); const injector = univer.__getInjector(); const get = injector.get.bind(injector); @@ -65,7 +65,7 @@ export function createCoreTestBed(workbookConfig?: IWorkbookData, dependencies?: } univer.registerPlugin(TestPlugin); - const sheet = univer.createUniverSheet(workbookConfig || TEST_WORKBOOK_DATA); + const sheet = univer.createUniverSheet(workbookData || TEST_WORKBOOK_DATA); const univerInstanceService = get(IUniverInstanceService); univerInstanceService.focusUnit('test'); diff --git a/packages/sheets/src/services/sheet-interceptor/__tests__/sheet-interceptor.service.spec.ts b/packages/sheets/src/services/sheet-interceptor/__tests__/sheet-interceptor.service.spec.ts index 9c08b4d7aa01..89e4bfeb802a 100644 --- a/packages/sheets/src/services/sheet-interceptor/__tests__/sheet-interceptor.service.spec.ts +++ b/packages/sheets/src/services/sheet-interceptor/__tests__/sheet-interceptor.service.spec.ts @@ -44,7 +44,19 @@ describe('Test SheetInterceptorService', () => { return sheet.getCell(row, col); } - describe('Test intercept getting cell content', () => { + function getRowFiltered(row: number): boolean { + const cus = get(IUniverInstanceService); + const sheet = cus.getCurrentUnitForType(UniverInstanceType.SHEET)!.getActiveSheet()!; + return sheet.getRowFiltered(row); + } + + function getRowRawVisible(row: number): boolean { + const cus = get(IUniverInstanceService); + const sheet = cus.getCurrentUnitForType(UniverInstanceType.SHEET)!.getActiveSheet()!; + return sheet.getRowVisible(row); + } + + describe('Test intercepting getting cell content', () => { it('should intercept cells and merge result if next is called', () => { get(SheetInterceptorService).intercept(INTERCEPTOR_POINT.CELL_CONTENT, { priority: 100, @@ -78,6 +90,39 @@ describe('Test SheetInterceptorService', () => { }); }); + describe('Test intercepting getting row filtered', () => { + it('should return not filtered when no interceptor is registered', () => { + expect(getRowFiltered(1)).toBeFalsy(); + }); + + it('should return filtered according to the interceptor when it is registed', () => { + let realFiltered = false; + + get(SheetInterceptorService).intercept(INTERCEPTOR_POINT.ROW_FILTERED, { + handler(filtered, location, next) { + if (filtered) { + return true; + } + + if (realFiltered && location.row === 1) { + return true; + } + + return next(); + }, + }); + + expect(getRowFiltered(1)).toBeFalsy(); + expect(getRowRawVisible(1)).toBeTruthy(); + + realFiltered = true; + expect(getRowFiltered(1)).toBeTruthy(); + expect(getRowRawVisible(1)).toBeFalsy(); + expect(getRowFiltered(2)).toBeFalsy(); + expect(getRowRawVisible(2)).toBeTruthy(); + }); + }); + describe('Test intercept in general case', () => { it('should intercept BEFORE_CELL_EDIT and sum the values', () => { get(SheetInterceptorService).intercept(numberIntercept, { diff --git a/packages/sheets/src/services/sheet-interceptor/interceptor-const.ts b/packages/sheets/src/services/sheet-interceptor/interceptor-const.ts index b7f0b82f25a0..78cd2906678d 100644 --- a/packages/sheets/src/services/sheet-interceptor/interceptor-const.ts +++ b/packages/sheets/src/services/sheet-interceptor/interceptor-const.ts @@ -14,15 +14,15 @@ * limitations under the License. */ -import type { ICellDataForSheetInterceptor, ICommandInfo } from '@univerjs/core'; +import type { ICellDataForSheetInterceptor } from '@univerjs/core'; import { createInterceptorKey } from '@univerjs/core'; -import type { ISheetLocation } from './utils/interceptor'; +import type { ISheetLocation, ISheetRowLocation } from './utils/interceptor'; const CELL_CONTENT = createInterceptorKey('CELL_CONTENT'); -const PERMISSION = createInterceptorKey('PERMISSION'); +const ROW_FILTERED = createInterceptorKey('ROW_FILTERED'); export const INTERCEPTOR_POINT = { CELL_CONTENT, - PERMISSION, + ROW_FILTERED, }; diff --git a/packages/sheets/src/services/sheet-interceptor/sheet-interceptor.service.ts b/packages/sheets/src/services/sheet-interceptor/sheet-interceptor.service.ts index 8787e87e77d3..c07b7f4e384b 100644 --- a/packages/sheets/src/services/sheet-interceptor/sheet-interceptor.service.ts +++ b/packages/sheets/src/services/sheet-interceptor/sheet-interceptor.service.ts @@ -81,11 +81,6 @@ export class SheetInterceptorService extends Disposable { return rawData; }, }); - - this.intercept(INTERCEPTOR_POINT.PERMISSION, { - priority: -1, - handler: () => true, - }); } override dispose(): void { @@ -150,16 +145,16 @@ export class SheetInterceptorService extends Disposable { const unitId = workbook.getUnitId(); // eslint-disable-next-line ts/no-this-alias - const self = this; + const sheetInterceptorService = this; const interceptViewModel = (worksheet: Worksheet): void => { const subUnitId = worksheet.getSheetId(); worksheet.__interceptViewModel((viewModel) => { - const disposableId = getWorksheetDisposableID(unitId, worksheet); - const sheetDisposables = new DisposableCollection(); - const cellInterceptorDisposable = viewModel.registerCellContentInterceptor({ + sheetInterceptorService._worksheetDisposables.set(getWorksheetDisposableID(unitId, worksheet), sheetDisposables); + + sheetDisposables.add(viewModel.registerCellContentInterceptor({ getCell(row: number, col: number): Nullable { - return self.fetchThroughInterceptors(INTERCEPTOR_POINT.CELL_CONTENT)( + return sheetInterceptorService.fetchThroughInterceptors(INTERCEPTOR_POINT.CELL_CONTENT)( worksheet.getCellRaw(row, col), { unitId, @@ -171,12 +166,22 @@ export class SheetInterceptorService extends Disposable { } ); }, - }); - - sheetDisposables.add(cellInterceptorDisposable); - sheetDisposables.add(toDisposable(() => self._workbookDisposables.delete(disposableId))); + })); - self._worksheetDisposables.set(disposableId, sheetDisposables); + sheetDisposables.add(viewModel.registerRowFilteredInterceptor({ + getRowFiltered(row: number): boolean { + return !!sheetInterceptorService.fetchThroughInterceptors(INTERCEPTOR_POINT.ROW_FILTERED)( + false, + { + unitId, + subUnitId, + row, + workbook, + worksheet, + } + ); + }, + })); }); }; diff --git a/packages/sheets/src/services/sheet-interceptor/utils/interceptor.ts b/packages/sheets/src/services/sheet-interceptor/utils/interceptor.ts index 15697cd2aca3..88f86dc60951 100644 --- a/packages/sheets/src/services/sheet-interceptor/utils/interceptor.ts +++ b/packages/sheets/src/services/sheet-interceptor/utils/interceptor.ts @@ -24,3 +24,11 @@ export interface ISheetLocation { row: number; col: number; } + +export interface ISheetRowLocation { + workbook: Workbook; + worksheet: Worksheet; + unitId: string; + subUnitId: string; + row: number; +} diff --git a/packages/slides/src/views/render/adaptors/spreadsheet-adaptor.ts b/packages/slides/src/views/render/adaptors/spreadsheet-adaptor.ts index 45814bbc8b4d..48bfcc1d4a15 100644 --- a/packages/slides/src/views/render/adaptors/spreadsheet-adaptor.ts +++ b/packages/slides/src/views/render/adaptors/spreadsheet-adaptor.ts @@ -96,7 +96,7 @@ export class SpreadsheetAdaptor extends ObjectAdaptor { const cellDataMatrix = new ObjectMatrix(cellData); const styleModel = new Styles(styles); const spreadsheetSkeleton = new SpreadsheetSkeleton( - new Worksheet(worksheet, styleModel), // FIXME: worksheet in slide doesn't has a Worksheet object + new Worksheet(id, worksheet, styleModel), // FIXME: worksheet in slide doesn't has a Worksheet object worksheet, cellDataMatrix, styleModel, diff --git a/packages/ui/src/components/menu/Menu.tsx b/packages/ui/src/components/menu/Menu.tsx index 23fa03e4c471..2d17aca70efb 100644 --- a/packages/ui/src/components/menu/Menu.tsx +++ b/packages/ui/src/components/menu/Menu.tsx @@ -181,6 +181,7 @@ function MenuItem({ menuItem, onClick }: IMenuItemProps) { const menuItems = menuItem.id ? menuService.getMenuItems(menuItem.id) : []; const disabled = useObservable(menuItem.disabled$, false); + const activated = useObservable(menuItem.activated$, false); const hidden = useObservable(menuItem.hidden$, false); const value = useObservable(menuItem.value$); const [inputValue, setInputValue] = useState(value); @@ -203,6 +204,9 @@ function MenuItem({ menuItem, onClick }: IMenuItemProps) { key={item.id} eventKey={item.id} disabled={disabled} + className={clsx({ + [styles.menuItemActivated]: activated, + })} onClick={() => { onClick({ value: inputValue, id: item.id }); // merge cell }} diff --git a/packages/ui/src/components/menu/index.module.less b/packages/ui/src/components/menu/index.module.less index ef16b1960cc8..8e3152135fa9 100644 --- a/packages/ui/src/components/menu/index.module.less +++ b/packages/ui/src/components/menu/index.module.less @@ -4,6 +4,10 @@ background: none; } + &-activated { + background-color: rgb(var(--bg-color-hover)); + } + &-content { display: inline-flex; gap: var(--margin-xs); diff --git a/packages/ui/src/services/menu/menu.ts b/packages/ui/src/services/menu/menu.ts index 7375d4b6ed49..0c4a364ead5c 100644 --- a/packages/ui/src/services/menu/menu.ts +++ b/packages/ui/src/services/menu/menu.ts @@ -19,7 +19,7 @@ import type { Observable } from 'rxjs'; export type OneOrMany = T | T[]; -export const enum MenuPosition { +export enum MenuPosition { VOID = 'void', TOOLBAR_START = 'uiToolbar.start', TOOLBAR_INSERT = 'uiToolbar.insert', @@ -30,7 +30,7 @@ export const enum MenuPosition { CONTEXT_MENU = 'contextMenu', } -export const enum MenuGroup { +export enum MenuGroup { TOOLBAR_HISTORY, TOOLBAR_FORMAT, TOOLBAR_LAYOUT, @@ -45,7 +45,7 @@ export const enum MenuGroup { CONTEXT_MENU_OTHERS, } -export const enum MenuItemType { +export enum MenuItemType { /** Button style menu item. */ BUTTON, /** Menu item with submenus. Submenus could be other IMenuItem or an ID of a registered component. */ @@ -71,8 +71,8 @@ interface IMenuItemBase { /** In what menu should the item display. */ positions: OneOrMany; - /** @deprecated this type seems unnecessary */ type: MenuItemType; + /** * Custom label component id. */ @@ -127,6 +127,9 @@ export interface IMenuSelectorItem // 一个是其他 menu 的 id,直接渲染成其他的 menu /** Options or IDs of registered components. */ selections?: Array> | Observable>>; + + /** If `type` is `MenuItemType.BUTTON_SELECTOR`, this determines if the button is activated. */ + activated$?: Observable; } export function isMenuSelectorItem(v: IMenuItem): v is IMenuSelectorItem { diff --git a/packages/ui/src/services/message/__testing__/mock-message.service.ts b/packages/ui/src/services/message/__testing__/mock-message.service.ts new file mode 100644 index 000000000000..edc1bdb89a9b --- /dev/null +++ b/packages/ui/src/services/message/__testing__/mock-message.service.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IMessageMethodOptions, IMessageProps } from '@univerjs/design'; +import type { IDisposable } from '@wendellhu/redi'; + +import { toDisposable } from '@univerjs/core'; +import type { IMessageService } from '../message.service'; + +/** + * This is a mocked message service for testing purposes. + */ +export class MockMessageService implements IMessageService { + show(_options: IMessageMethodOptions & Omit): IDisposable { + return toDisposable(() => {}); + } + + setContainer(): void {} +} diff --git a/packages/ui/src/services/popup/canvas-popup.service.ts b/packages/ui/src/services/popup/canvas-popup.service.ts index 9fa8c7ce0c56..22362c5ab0f4 100644 --- a/packages/ui/src/services/popup/canvas-popup.service.ts +++ b/packages/ui/src/services/popup/canvas-popup.service.ts @@ -25,6 +25,7 @@ export interface IPopup { anchorRect$: Observable; componentKey: string; onClickOutside?: (e: MouseEvent) => void; + closeOnSelfTarget?: boolean; excludeOutside?: HTMLElement[]; unitId: string; subUnitId: string; diff --git a/packages/ui/src/views/components/doc-bars/Button/index.module.less b/packages/ui/src/views/components/doc-bars/Button/index.module.less index 4632543d3804..8237777c14a6 100644 --- a/packages/ui/src/views/components/doc-bars/Button/index.module.less +++ b/packages/ui/src/views/components/doc-bars/Button/index.module.less @@ -30,5 +30,8 @@ &-active { background-color: rgb(var(--grey-100)); + &[disabled] { + color: rgba(var(--text-color), 0.25); + } } } diff --git a/packages/ui/src/views/components/doc-bars/ToolbarItem.tsx b/packages/ui/src/views/components/doc-bars/ToolbarItem.tsx index 5de44abb7ef4..1f06f631ac93 100644 --- a/packages/ui/src/views/components/doc-bars/ToolbarItem.tsx +++ b/packages/ui/src/views/components/doc-bars/ToolbarItem.tsx @@ -24,6 +24,7 @@ import React, { forwardRef, useEffect, useMemo, useState } from 'react'; import type { Subscription } from 'rxjs'; import { isObservable, Observable } from 'rxjs'; +import clsx from 'clsx'; import { ComponentManager } from '../../../common/component-manager'; import { CustomLabel } from '../../../components/custom-label/CustomLabel'; import { useObservable } from '../../../components/hooks/observable'; @@ -80,14 +81,12 @@ export const ToolbarItem = forwardRef((props: IDisplayMenuItem, ref: }) ); - if (props.type === MenuItemType.BUTTON) { - props.activated$ && - subscriptions.push( - props.activated$.subscribe((activated) => { - setActivated(activated); - }) - ); - } + props.activated$ && + subscriptions.push( + props.activated$.subscribe((activated) => { + setActivated(activated); + }) + ); props.value$ && subscriptions.push( @@ -170,9 +169,10 @@ export const ToolbarItem = forwardRef((props: IDisplayMenuItem, ref: return menuType === MenuItemType.BUTTON_SELECTOR ? (

@@ -184,21 +184,34 @@ export const ToolbarItem = forwardRef((props: IDisplayMenuItem, ref: onChange={handleChange} />
- {!disabled && ( - } - onVisibleChange={handleDropdownVisibleChange} - > + {!disabled + ? ( + } + onVisibleChange={handleDropdownVisibleChange} + > +
+ +
+
+ ) + : (
-
- )} + )}
) : !disabled @@ -207,7 +220,12 @@ export const ToolbarItem = forwardRef((props: IDisplayMenuItem, ref: overlay={} onVisibleChange={handleDropdownVisibleChange} > -
+
, ref: onChange={handleChange} />
@@ -226,7 +244,12 @@ export const ToolbarItem = forwardRef((props: IDisplayMenuItem, ref: ) : ( -
+
, ref: onChange={handleChange} />
diff --git a/packages/ui/src/views/components/doc-bars/index.module.less b/packages/ui/src/views/components/doc-bars/index.module.less index 7ae80539695a..35584fd50c81 100644 --- a/packages/ui/src/views/components/doc-bars/index.module.less +++ b/packages/ui/src/views/components/doc-bars/index.module.less @@ -174,6 +174,23 @@ &-disabled { cursor: not-allowed; color: rgb(var(--grey-200)); + &.toolbar-item-select-button-activated { + color: rgba(var(--text-color), 0.25); + .toolbar-item-select-button { + &-label, + &-arrow { + &:hover { + background-color: rgb( + var(--grey-100) + ) !important; + } + } + } + } + } + + &-activated { + background-color: rgb(var(--grey-100)); } &:hover { @@ -247,6 +264,16 @@ &-disabled { cursor: not-allowed; color: rgb(var(--grey-200)); + &.toolbar-item-select-activated { + color: rgba(var(--text-color), 0.25); + &:hover { + background-color: rgb(var(--grey-100)); + } + } + } + + &-activated { + background-color: rgb(var(--grey-100)); } &:hover { @@ -266,6 +293,16 @@ &-disabled { cursor: not-allowed; color: rgb(var(--grey-200)); + &.toolbar-item-select-arrow-activated { + color: rgba(var(--text-color), 0.25); + &:hover { + background-color: rgb(var(--grey-100)); + } + } + } + + &-activated { + background-color: rgb(var(--grey-100)); } } } diff --git a/packages/ui/src/views/components/popup/CanvasPopup.tsx b/packages/ui/src/views/components/popup/CanvasPopup.tsx index 1aba5e8590f8..722173d56165 100644 --- a/packages/ui/src/views/components/popup/CanvasPopup.tsx +++ b/packages/ui/src/views/components/popup/CanvasPopup.tsx @@ -41,8 +41,8 @@ const SingleCanvasPopup = ({ popup, children }: { popup: IPopup; children?: Reac direction={popup.direction} onClickOutside={popup.onClickOutside} excludeOutside={popup.excludeOutside} + closeOnSelfTarget={popup.closeOnSelfTarget} > - {children} ); @@ -50,7 +50,7 @@ const SingleCanvasPopup = ({ popup, children }: { popup: IPopup; children?: Reac export function CanvasPopup() { const popupService = useDependency(ICanvasPopupService); - const popups = useObservable(popupService.popups$, popupService.popups); + const popups = useObservable(popupService.popups$, undefined, true); const componentManager = useDependency(ComponentManager); return popups.map((item) => { diff --git a/packages/uniscript/src/services/command/batch-command.service.ts b/packages/uniscript/src/services/command/batch-command.service.ts deleted file mode 100644 index 33f9e142ca8f..000000000000 --- a/packages/uniscript/src/services/command/batch-command.service.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2023-present DreamNum Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { CommandListener, ICommand, ICommandService, IExecutionOptions } from '@univerjs/core'; -import type { IDisposable } from '@wendellhu/redi'; - -export class BatchCommandService implements ICommandService { - hasCommand(commandId: string): boolean { - throw new Error('Method not implemented.'); - } - - beforeCommandExecuted(listener: CommandListener): IDisposable { - throw new Error('Method not implemented.'); - } - - registerCommand(command: ICommand): IDisposable { - throw new Error('Method not implemented.'); - } - - registerMultipleCommand(command: ICommand): IDisposable { - throw new Error('Method not implemented.'); - } - - executeCommand

( - id: string, - params?: P | undefined, - options?: IExecutionOptions | undefined - ): Promise { - throw new Error('Method not implemented.'); - } - - syncExecuteCommand

( - id: string, - params?: P | undefined, - options?: IExecutionOptions | undefined - ): R { - throw new Error('Method not implemented.'); - } - - onCommandExecuted(listener: CommandListener): IDisposable { - throw new Error('Method not implemented.'); - } -} - -// TODO: @wzhudev: the facade injector should register this command service to implement command batching -// for performance reasons, and undo redo experience. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d9b69853ac6..d18fe59671a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: ^2.15.0 - version: 2.15.0(@eslint-react/eslint-plugin@1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(@vue/compiler-sfc@3.4.21)(eslint-plugin-format@0.1.1(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.0(eslint@8.57.0))(eslint-plugin-react-refresh@0.4.6(eslint@8.57.0))(eslint@8.57.0)(typescript@5.4.5)(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)) + version: 2.15.0(@eslint-react/eslint-plugin@1.5.9)(@vue/compiler-sfc@3.4.25)(eslint-plugin-format@0.1.1)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react-refresh@0.4.6)(eslint@8.57.0)(typescript@5.4.5) '@commitlint/cli': specifier: ^19.3.0 version: 19.3.0(@types/node@20.12.7)(typescript@5.4.5) @@ -19,19 +19,19 @@ importers: version: 19.2.2 '@eslint-react/eslint-plugin': specifier: ^1.5.9 - version: 1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + version: 1.5.9(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5) '@playwright/test': specifier: ^1.43.1 version: 1.43.1 '@release-it-plugins/workspaces': specifier: ^4.2.0 - version: 4.2.0(release-it@17.2.0(typescript@5.4.5)) + version: 4.2.0(release-it@17.2.0) '@release-it/conventional-changelog': specifier: ^8.0.1 - version: 8.0.1(release-it@17.2.0(typescript@5.4.5)) + version: 8.0.1(release-it@17.2.0) '@storybook/react': specifier: 8.0.9 - version: 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.4.5) + version: 8.0.9(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.5) '@types/node': specifier: ^20.12.7 version: 20.12.7 @@ -46,7 +46,7 @@ importers: version: link:common/shared '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.2.1(vite@5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)) + version: 4.2.1(vite@5.2.10) eslint: specifier: 8.57.0 version: 8.57.0 @@ -100,22 +100,22 @@ importers: version: 7.7.1(eslint@8.57.0)(typescript@5.4.5) '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.2.1(vite@5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)) + version: 4.2.1(vite@5.2.10) '@vitest/coverage-istanbul': specifier: ^1.5.0 - version: 1.5.0(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)) + version: 1.5.1(vitest@1.5.1) happy-dom: specifier: 13.3.8 version: 13.3.8 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vite-plugin-dts: specifier: ^3.9.0 - version: 3.9.0(@types/node@20.12.7)(rollup@4.13.2)(typescript@5.4.5)(vite@5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)) + version: 3.9.0(@types/node@20.12.7)(typescript@5.4.5)(vite@5.2.10) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(happy-dom@13.3.8) common/storybook: dependencies: @@ -124,31 +124,31 @@ importers: version: 1.3.3(react@18.2.0) '@storybook/addon-essentials': specifier: 8.0.9 - version: 8.0.9(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 8.0.9(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-interactions': specifier: 8.0.9 - version: 8.0.9(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)) + version: 8.0.9 '@storybook/addon-links': specifier: 8.0.9 version: 8.0.9(react@18.2.0) '@storybook/addon-styling-webpack': specifier: ^1.0.0 - version: 1.0.0(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) + version: 1.0.0(webpack@5.91.0) '@storybook/addon-webpack5-compiler-swc': specifier: ^1.0.2 - version: 1.0.2(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) + version: 1.0.2(webpack@5.91.0) '@storybook/blocks': specifier: 8.0.9 - version: 8.0.9(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 8.0.9(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0) '@storybook/icons': specifier: ^1.2.9 - version: 1.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 1.2.9(react-dom@18.2.0)(react@18.2.0) '@storybook/react': specifier: 8.0.9 - version: 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.4.5) + version: 8.0.9(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.5) '@storybook/react-webpack5': specifier: 8.0.9 - version: 8.0.9(@swc/core@1.4.11)(esbuild@0.20.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.4.5) + version: 8.0.9(@swc/core@1.5.0)(esbuild@0.20.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.5) '@storybook/types': specifier: ^8.0.9 version: 8.0.9 @@ -166,19 +166,19 @@ importers: version: 0.13.3 css-loader: specifier: ^6.10.0 - version: 6.10.0(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) + version: 6.11.0(webpack@5.91.0) less-loader: specifier: ^12.2.0 - version: 12.2.0(less@4.2.0)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) + version: 12.2.0(less@4.2.0)(webpack@5.91.0) storybook: specifier: 8.0.9 - version: 8.0.9(@babel/preset-env@7.24.3(@babel/core@7.24.3))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 8.0.9(react-dom@18.2.0)(react@18.2.0) storybook-addon-swc: specifier: ^1.2.0 - version: 1.2.0(@swc/core@1.4.11)(terser-webpack-plugin@5.3.10(@swc/core@1.4.11)(esbuild@0.20.2)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)))(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) + version: 1.2.0(@swc/core@1.5.0)(webpack@5.91.0) style-loader: specifier: ^3.3.4 - version: 3.3.4(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) + version: 3.3.4(webpack@5.91.0) tsconfig-paths-webpack-plugin: specifier: ^4.1.0 version: 4.1.0 @@ -221,7 +221,7 @@ importers: version: link:../packages/find-replace '@univerjs/icons': specifier: ^0.1.44 - version: 0.1.44(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.1.44(react-dom@18.2.0)(react@18.2.0) '@univerjs/image': specifier: workspace:* version: link:../packages/image @@ -240,6 +240,12 @@ importers: '@univerjs/sheets-data-validation': specifier: workspace:* version: link:../packages/sheets-data-validation + '@univerjs/sheets-filter': + specifier: workspace:* + version: link:../packages/sheets-filter + '@univerjs/sheets-filter-ui': + specifier: workspace:* + version: link:../packages/sheets-filter-ui '@univerjs/sheets-find-replace': specifier: workspace:* version: link:../packages/sheets-find-replace @@ -284,13 +290,13 @@ importers: version: 18.2.0(react@18.2.0) react-mosaic-component: specifier: ^6.1.0 - version: 6.1.0(@types/node@20.12.7)(@types/react@18.2.79)(dnd-core@16.0.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 6.1.0(@types/node@20.12.7)(@types/react@18.2.79)(dnd-core@16.0.1)(react-dom@18.2.0)(react@18.2.0) rxjs: specifier: ^7.8.1 version: 7.8.1 vue: specifier: ^3.4.21 - version: 3.4.21(typescript@5.4.5) + version: 3.4.25(typescript@5.4.5) devDependencies: '@types/react': specifier: ^18.2.79 @@ -312,7 +318,7 @@ importers: version: 2.1.1(esbuild@0.20.2) esbuild-plugin-vue3: specifier: ^0.4.2 - version: 0.4.2(sass@1.72.0)(vue@3.4.21(typescript@5.4.5)) + version: 0.4.2(vue@3.4.25) esbuild-style-plugin: specifier: ^1.6.3 version: 1.6.3 @@ -330,7 +336,7 @@ importers: dependencies: '@univerjs/protocol': specifier: ^0.1.19 - version: 0.1.19(@grpc/grpc-js@1.10.4)(rxjs@7.8.1) + version: 0.1.20(@grpc/grpc-js@1.10.6)(rxjs@7.8.1) nanoid: specifier: 5.0.7 version: 5.0.7 @@ -358,16 +364,16 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) packages/data-validation: dependencies: '@univerjs/protocol': specifier: ^0.1.19 - version: 0.1.19(@grpc/grpc-js@1.10.4)(rxjs@7.8.1) + version: 0.1.20(@grpc/grpc-js@1.10.6)(rxjs@7.8.1) devDependencies: '@univerjs/core': specifier: workspace:* @@ -389,68 +395,68 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) packages/design: dependencies: '@rc-component/color-picker': specifier: ^1.5.3 - version: 1.5.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 1.5.3(react-dom@18.2.0)(react@18.2.0) '@rc-component/trigger': specifier: ^1.18.3 - version: 1.18.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 1.18.3(react-dom@18.2.0)(react@18.2.0) '@univerjs/icons': specifier: ^0.1.44 - version: 0.1.44(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.1.44(react-dom@18.2.0)(react@18.2.0) rc-dialog: specifier: ^9.4.0 - version: 9.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 9.4.0(react-dom@18.2.0)(react@18.2.0) rc-dropdown: specifier: ^4.2.0 - version: 4.2.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 4.2.0(react-dom@18.2.0)(react@18.2.0) rc-input: specifier: ^1.4.5 - version: 1.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 1.4.5(react-dom@18.2.0)(react@18.2.0) rc-input-number: specifier: ^9.0.0 - version: 9.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 9.0.0(react-dom@18.2.0)(react@18.2.0) rc-menu: specifier: ^9.13.0 - version: 9.13.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 9.13.0(react-dom@18.2.0)(react@18.2.0) rc-picker: specifier: ^4.3.0 - version: 4.3.0(dayjs@1.11.10)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 4.4.2(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0) rc-segmented: specifier: ^2.3.0 - version: 2.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 2.3.0(react-dom@18.2.0)(react@18.2.0) rc-select: specifier: ^14.13.0 - version: 14.13.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.13.1(react-dom@18.2.0)(react@18.2.0) rc-textarea: specifier: ^1.6.3 - version: 1.6.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 1.6.3(react-dom@18.2.0)(react@18.2.0) rc-tooltip: specifier: ^6.2.0 - version: 6.2.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 6.2.0(react-dom@18.2.0)(react@18.2.0) rc-util: specifier: ^5.39.1 - version: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 5.39.1(react-dom@18.2.0)(react@18.2.0) react-draggable: specifier: ^4.4.6 - version: 4.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 4.4.6(react-dom@18.2.0)(react@18.2.0) react-grid-layout: specifier: ^1.4.4 - version: 1.4.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 1.4.4(react-dom@18.2.0)(react@18.2.0) react-transition-group: specifier: ^4.4.5 - version: 4.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 4.4.5(react-dom@18.2.0)(react@18.2.0) devDependencies: '@testing-library/react': specifier: ^14.2.2 - version: 14.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.3.1(react-dom@18.2.0)(react@18.2.0) '@types/react': specifier: ^18.2.79 version: 18.2.79 @@ -486,10 +492,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages/docs: devDependencies: @@ -513,10 +519,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) packages/docs-ui: devDependencies: @@ -558,10 +564,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages/engine-formula: dependencies: @@ -592,10 +598,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) packages/engine-numfmt: dependencies: @@ -611,10 +617,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) packages/engine-render: dependencies: @@ -645,10 +651,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) packages/facade: devDependencies: @@ -702,19 +708,19 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) vitest-canvas-mock: specifier: ^0.3.3 - version: 0.3.3(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)) + version: 0.3.3(vitest@1.5.1) packages/find-replace: dependencies: '@univerjs/icons': specifier: ^0.1.44 - version: 0.1.44(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.1.44(react-dom@18.2.0)(react@18.2.0) devDependencies: '@types/react': specifier: ^18.2.79 @@ -754,16 +760,16 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages/image: dependencies: '@univerjs/icons': specifier: ^0.1.44 - version: 0.1.44(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.1.44(react-dom@18.2.0)(react@18.2.0) devDependencies: '@univerjs/core': specifier: workspace:* @@ -800,10 +806,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages/network: devDependencies: @@ -824,10 +830,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) packages/rpc: devDependencies: @@ -848,16 +854,16 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) packages/sheets: dependencies: '@univerjs/protocol': specifier: ^0.1.19 - version: 0.1.19(@grpc/grpc-js@1.10.4)(rxjs@7.8.1) + version: 0.1.20(@grpc/grpc-js@1.10.6)(rxjs@7.8.1) devDependencies: '@univerjs/core': specifier: workspace:* @@ -885,16 +891,16 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) packages/sheets-conditional-formatting: dependencies: '@univerjs/protocol': specifier: ^0.1.19 - version: 0.1.19(@grpc/grpc-js@1.10.4)(rxjs@7.8.1) + version: 0.1.20(@grpc/grpc-js@1.10.6)(rxjs@7.8.1) devDependencies: '@univerjs/core': specifier: workspace:* @@ -931,22 +937,22 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) packages/sheets-conditional-formatting-ui: dependencies: '@univerjs/icons': specifier: ^0.1.44 - version: 0.1.44(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.1.44(react-dom@18.2.0)(react@18.2.0) '@univerjs/sheets-conditional-formatting': specifier: workspace:* version: link:../sheets-conditional-formatting lodash.get: specifier: ^4.4.2 - version: https://r2.cnpmjs.org/lodash.get/-/lodash.get-4.4.2.tgz + version: 4.4.2 lodash.set: specifier: ^4.3.2 version: 4.3.2 @@ -955,10 +961,10 @@ importers: version: 18.2.0(react@18.2.0) react-grid-layout: specifier: ^1.4.4 - version: 1.4.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 1.4.4(react-dom@18.2.0)(react@18.2.0) react-resizable: specifier: ^3.0.5 - version: 3.0.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 3.0.5(react-dom@18.2.0)(react@18.2.0) devDependencies: '@types/lodash.get': specifier: ^4.4.9 @@ -1016,16 +1022,16 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages/sheets-data-validation: dependencies: '@univerjs/icons': specifier: ^0.1.44 - version: 0.1.44(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.1.44(react-dom@18.2.0)(react@18.2.0) devDependencies: '@univerjs/core': specifier: workspace:* @@ -1080,10 +1086,95 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) + + packages/sheets-filter: + devDependencies: + '@univerjs/core': + specifier: workspace:* + version: link:../core + '@univerjs/shared': + specifier: workspace:* + version: link:../../common/shared + '@univerjs/sheets': + specifier: workspace:* + version: link:../sheets + '@wendellhu/redi': + specifier: ^0.13.1 + version: 0.13.3 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + typescript: + specifier: ^5.3.3 + version: 5.4.5 + vite: + specifier: ^5.1.4 + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) + vitest: + specifier: ^1.3.1 + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) + + packages/sheets-filter-ui: + dependencies: + '@univerjs/icons': + specifier: ^0.1.42 + version: 0.1.44(react-dom@18.2.0)(react@18.2.0) + rc-virtual-list: + specifier: ^3.11.4 + version: 3.11.5(react-dom@18.2.0)(react@18.2.0) + devDependencies: + '@univerjs/core': + specifier: workspace:* + version: link:../core + '@univerjs/design': + specifier: workspace:* + version: link:../design + '@univerjs/engine-render': + specifier: workspace:* + version: link:../engine-render + '@univerjs/shared': + specifier: workspace:* + version: link:../../common/shared + '@univerjs/sheets': + specifier: workspace:* + version: link:../sheets + '@univerjs/sheets-filter': + specifier: workspace:* + version: link:../sheets-filter + '@univerjs/sheets-ui': + specifier: workspace:* + version: link:../sheets-ui + '@univerjs/ui': + specifier: workspace:* + version: link:../ui + '@wendellhu/redi': + specifier: ^0.13.1 + version: 0.13.3 + clsx: + specifier: ^2.1.0 + version: 2.1.1 + less: + specifier: ^4.2.0 + version: 4.2.0 + react: + specifier: ^18.2.0 + version: 18.2.0 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + typescript: + specifier: ^5.3.3 + version: 5.4.5 + vite: + specifier: ^5.1.4 + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) + vitest: + specifier: ^1.3.1 + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages/sheets-find-replace: devDependencies: @@ -1119,16 +1210,16 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) packages/sheets-formula: dependencies: '@univerjs/icons': specifier: ^0.1.44 - version: 0.1.44(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.1.44(react-dom@18.2.0)(react@18.2.0) devDependencies: '@types/react': specifier: ^18.2.79 @@ -1165,7 +1256,7 @@ importers: version: link:../ui '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.2.1(vite@5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)) + version: 4.2.1(vite@5.2.10) '@wendellhu/redi': specifier: ^0.13.3 version: 0.13.3 @@ -1183,16 +1274,16 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages/sheets-numfmt: dependencies: '@univerjs/icons': specifier: ^0.1.44 - version: 0.1.44(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.1.44(react-dom@18.2.0)(react@18.2.0) devDependencies: '@types/react': specifier: ^18.2.79 @@ -1241,10 +1332,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages/sheets-ui: dependencies: @@ -1253,7 +1344,7 @@ importers: version: link:../docs-ui '@univerjs/icons': specifier: ^0.1.44 - version: 0.1.44(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.1.44(react-dom@18.2.0)(react@18.2.0) devDependencies: '@types/react': specifier: ^18.2.79 @@ -1305,16 +1396,16 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages/sheets-zen-editor: dependencies: '@univerjs/icons': specifier: ^0.1.44 - version: 0.1.44(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.1.44(react-dom@18.2.0)(react@18.2.0) devDependencies: '@univerjs/core': specifier: workspace:* @@ -1357,10 +1448,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages/slides: devDependencies: @@ -1384,10 +1475,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) packages/slides-ui: devDependencies: @@ -1429,36 +1520,36 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages/ui: dependencies: '@univerjs/icons': specifier: ^0.1.44 - version: 0.1.44(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 0.1.44(react-dom@18.2.0)(react@18.2.0) localforage: specifier: ^1.10.0 version: 1.10.0 rc-notification: specifier: ^5.4.0 - version: 5.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 5.4.0(react-dom@18.2.0)(react@18.2.0) rc-util: specifier: ^5.39.1 - version: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 5.39.1(react-dom@18.2.0)(react@18.2.0) optionalDependencies: vue: specifier: '>=3.0.0' - version: 3.4.21(typescript@5.4.5) + version: 3.4.25(typescript@5.4.5) devDependencies: '@testing-library/react': specifier: ^14.2.2 - version: 14.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 14.3.1(react-dom@18.2.0)(react@18.2.0) '@testing-library/react-hooks': specifier: ^8.0.1 - version: 8.0.1(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + version: 8.0.1(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0) '@types/react': specifier: ^18.2.79 version: 18.2.79 @@ -1506,10 +1597,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages/uniscript: dependencies: @@ -1552,10 +1643,10 @@ importers: version: 5.4.5 vite: specifier: ^5.2.10 - version: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 5.2.10(@types/node@20.12.7)(less@4.2.0) vitest: specifier: ^1.5.0 - version: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + version: 1.5.1(@types/node@20.12.7)(less@4.2.0) packages: @@ -1624,16 +1715,16 @@ packages: resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.24.1': - resolution: {integrity: sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==} + '@babel/compat-data@7.24.4': + resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} engines: {node: '>=6.9.0'} - '@babel/core@7.24.3': - resolution: {integrity: sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==} + '@babel/core@7.24.4': + resolution: {integrity: sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==} engines: {node: '>=6.9.0'} - '@babel/generator@7.24.1': - resolution: {integrity: sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==} + '@babel/generator@7.24.4': + resolution: {integrity: sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.22.5': @@ -1648,8 +1739,8 @@ packages: resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.24.1': - resolution: {integrity: sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA==} + '@babel/helper-create-class-features-plugin@7.24.4': + resolution: {integrity: sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1660,8 +1751,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@0.6.1': - resolution: {integrity: sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==} + '@babel/helper-define-polyfill-provider@0.6.2': + resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -1739,19 +1830,25 @@ packages: resolution: {integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.24.1': - resolution: {integrity: sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==} + '@babel/helpers@7.24.4': + resolution: {integrity: sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==} engines: {node: '>=6.9.0'} '@babel/highlight@7.24.2': resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} engines: {node: '>=6.9.0'} - '@babel/parser@7.24.1': - resolution: {integrity: sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==} + '@babel/parser@7.24.4': + resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.4': + resolution: {integrity: sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.1': resolution: {integrity: sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==} engines: {node: '>=6.9.0'} @@ -1914,8 +2011,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.24.1': - resolution: {integrity: sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw==} + '@babel/plugin-transform-block-scoping@7.24.4': + resolution: {integrity: sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1926,8 +2023,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-static-block@7.24.1': - resolution: {integrity: sha512-FUHlKCn6J3ERiu8Dv+4eoz7w8+kFLSyeVG4vDAikwADGjUCoHw/JHokyGtr8OR4UjpwPVivyF+h8Q5iv/JmrtA==} + '@babel/plugin-transform-class-static-block@7.24.4': + resolution: {integrity: sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.12.0 @@ -2172,8 +2269,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.24.1': - resolution: {integrity: sha512-liYSESjX2fZ7JyBFkYG78nfvHlMKE6IpNdTVnxmlYUR+j5ZLsitFbaAE+eJSK2zPPkNWNw4mXL51rQ8WrvdK0w==} + '@babel/plugin-transform-typescript@7.24.4': + resolution: {integrity: sha512-79t3CQ8+oBGk/80SQ8MN3Bs3obf83zJ0YZjDmDaEZN8MqhMI760apl5z6a20kFeMXBwJX99VpKT8CKxEBp5H1g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -2202,8 +2299,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.24.3': - resolution: {integrity: sha512-fSk430k5c2ff8536JcPvPWK4tZDwehWLGlBp0wrsBUjZVdeQV6lePbwKWZaZfK2vnh/1kQX1PzAJWsnBmVgGJA==} + '@babel/preset-env@7.24.4': + resolution: {integrity: sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -2234,8 +2331,8 @@ packages: '@babel/regjsgen@0.8.0': resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} - '@babel/runtime@7.24.1': - resolution: {integrity: sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==} + '@babel/runtime@7.24.4': + resolution: {integrity: sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==} engines: {node: '>=6.9.0'} '@babel/template@7.24.0': @@ -2561,8 +2658,8 @@ packages: '@fal-works/esbuild-plugin-global-externals@2.1.2': resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==} - '@grpc/grpc-js@1.10.4': - resolution: {integrity: sha512-MqBisuxTkYvPFnEiu+dag3xG/NBUDzSbAFAWlzfkGnQkjVZ6by3h4atbBc+Ikqup1z5BfB4BN18gKWR1YyppNw==} + '@grpc/grpc-js@1.10.6': + resolution: {integrity: sha512-xP58G7wDQ4TCmN/cMUHh00DS7SRDv/+lC+xFLrTkMIN8h55X5NhZMLYbvy7dSELP15qlI6hPhNCRWVMtZMwqLA==} engines: {node: '>=12.10.0'} '@grpc/proto-loader@0.7.12': @@ -2617,84 +2714,72 @@ packages: engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.0.2': resolution: {integrity: sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==} engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.0.2': resolution: {integrity: sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==} engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.0.2': resolution: {integrity: sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==} engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.0.2': resolution: {integrity: sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==} engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.0.2': resolution: {integrity: sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==} engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linux-arm64@0.33.3': resolution: {integrity: sha512-Zf+sF1jHZJKA6Gor9hoYG2ljr4wo9cY4twaxgFDvlG0Xz9V7sinsPp8pFd1XtlhTzYo0IhDbl3rK7P6MzHpnYA==} engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.33.3': resolution: {integrity: sha512-Q7Ee3fFSC9P7vUSqVEF0zccJsZ8GiiCJYGWDdhEjdlOeS9/jdkyJ6sUSPj+bL8VuOYFSbofrW0t/86ceVhx32w==} engines: {glibc: '>=2.28', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.33.3': resolution: {integrity: sha512-vFk441DKRFepjhTEH20oBlFrHcLjPfI8B0pMIxGm3+yilKyYeHEVvrZhYFdqIseSclIqbQ3SnZMwEMWonY5XFA==} engines: {glibc: '>=2.28', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.33.3': resolution: {integrity: sha512-Q4I++herIJxJi+qmbySd072oDPRkCg/SClLEIDh5IL9h1zjhqjv82H0Seupd+q2m0yOfD+/fJnjSoDFtKiHu2g==} engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.33.3': resolution: {integrity: sha512-qnDccehRDXadhM9PM5hLvcPRYqyFCBN31kq+ErBSZtZlsAc1U4Z85xf/RXv1qolkdu+ibw64fUDaRdktxTNP9A==} engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.33.3': resolution: {integrity: sha512-Jhchim8kHWIU/GZ+9poHMWRcefeaxFIs9EBqf9KtcC14Ojk6qua7ghKiPs0sbeLbLj/2IGBtDcxHyjCdYWkk2w==} engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-wasm32@0.33.3': resolution: {integrity: sha512-68zivsdJ0koE96stdUfM+gmyaK/NcoSZK5dV5CAjES0FUXS9lchYt8LAB5rTbM7nlWtxaU/2GON0HVN6/ZYJAQ==} @@ -2791,21 +2876,24 @@ packages: resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} engines: {node: '>= 18'} - '@octokit/core@5.1.0': - resolution: {integrity: sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==} + '@octokit/core@5.2.0': + resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==} engines: {node: '>= 18'} - '@octokit/endpoint@9.0.4': - resolution: {integrity: sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==} + '@octokit/endpoint@9.0.5': + resolution: {integrity: sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==} engines: {node: '>= 18'} - '@octokit/graphql@7.0.2': - resolution: {integrity: sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==} + '@octokit/graphql@7.1.0': + resolution: {integrity: sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==} engines: {node: '>= 18'} '@octokit/openapi-types@20.0.0': resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} + '@octokit/openapi-types@22.1.0': + resolution: {integrity: sha512-pGUdSP+eEPfZiQHNkZI0U01HLipxncisdJQB4G//OAmfeO8sqTQ9KRa0KF03TUPCziNsoXUrTg4B2Q1EX++T0Q==} + '@octokit/plugin-paginate-rest@9.2.1': resolution: {integrity: sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==} engines: {node: '>= 18'} @@ -2824,12 +2912,12 @@ packages: peerDependencies: '@octokit/core': '5' - '@octokit/request-error@5.0.1': - resolution: {integrity: sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==} + '@octokit/request-error@5.1.0': + resolution: {integrity: sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==} engines: {node: '>= 18'} - '@octokit/request@8.2.0': - resolution: {integrity: sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==} + '@octokit/request@8.4.0': + resolution: {integrity: sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==} engines: {node: '>= 18'} '@octokit/rest@20.1.0': @@ -2839,6 +2927,9 @@ packages: '@octokit/types@12.6.0': resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} + '@octokit/types@13.4.1': + resolution: {integrity: sha512-Y73oOAzRBAUzR/iRAbGULzpNkX8vaxKCqEtg6K74Ff3w9f5apFnWtE/2nade7dMWWW3bS5Kkd6DJS4HF04xreg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -2936,8 +3027,8 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' - '@rc-component/trigger@2.0.0': - resolution: {integrity: sha512-niwKADPdY5dhdIblV6uwSayVivwo2uUISfJqri+/ovYQcH/omxDYBJKo755QKeoIIsWptxnRpgr7reEnNEZGFg==} + '@rc-component/trigger@2.1.1': + resolution: {integrity: sha512-UjHkedkgtEcgQu87w1VuWug1idoDJV7VUt0swxHXRcmei2uu1AuUzGBPEUlmOmXGJ+YtTgZfVLi7kuAUKoZTMA==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' @@ -2973,78 +3064,83 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.13.2': - resolution: {integrity: sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==} + '@rollup/rollup-android-arm-eabi@4.16.4': + resolution: {integrity: sha512-GkhjAaQ8oUTOKE4g4gsZ0u8K/IHU1+2WQSgS1TwTcYvL+sjbaQjNHFXbOJ6kgqGHIO1DfUhI/Sphi9GkRT9K+Q==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.13.2': - resolution: {integrity: sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==} + '@rollup/rollup-android-arm64@4.16.4': + resolution: {integrity: sha512-Bvm6D+NPbGMQOcxvS1zUl8H7DWlywSXsphAeOnVeiZLQ+0J6Is8T7SrjGTH29KtYkiY9vld8ZnpV3G2EPbom+w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.13.2': - resolution: {integrity: sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==} + '@rollup/rollup-darwin-arm64@4.16.4': + resolution: {integrity: sha512-i5d64MlnYBO9EkCOGe5vPR/EeDwjnKOGGdd7zKFhU5y8haKhQZTN2DgVtpODDMxUr4t2K90wTUJg7ilgND6bXw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.13.2': - resolution: {integrity: sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==} + '@rollup/rollup-darwin-x64@4.16.4': + resolution: {integrity: sha512-WZupV1+CdUYehaZqjaFTClJI72fjJEgTXdf4NbW69I9XyvdmztUExBtcI2yIIU6hJtYvtwS6pkTkHJz+k08mAQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.13.2': - resolution: {integrity: sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.16.4': + resolution: {integrity: sha512-ADm/xt86JUnmAfA9mBqFcRp//RVRt1ohGOYF6yL+IFCYqOBNwy5lbEK05xTsEoJq+/tJzg8ICUtS82WinJRuIw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.16.4': + resolution: {integrity: sha512-tJfJaXPiFAG+Jn3cutp7mCs1ePltuAgRqdDZrzb1aeE3TktWWJ+g7xK9SNlaSUFw6IU4QgOxAY4rA+wZUT5Wfg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.13.2': - resolution: {integrity: sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==} + '@rollup/rollup-linux-arm64-gnu@4.16.4': + resolution: {integrity: sha512-7dy1BzQkgYlUTapDTvK997cgi0Orh5Iu7JlZVBy1MBURk7/HSbHkzRnXZa19ozy+wwD8/SlpJnOOckuNZtJR9w==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.13.2': - resolution: {integrity: sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==} + '@rollup/rollup-linux-arm64-musl@4.16.4': + resolution: {integrity: sha512-zsFwdUw5XLD1gQe0aoU2HVceI6NEW7q7m05wA46eUAyrkeNYExObfRFQcvA6zw8lfRc5BHtan3tBpo+kqEOxmg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.13.2': - resolution: {integrity: sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==} - cpu: [ppc64le] + '@rollup/rollup-linux-powerpc64le-gnu@4.16.4': + resolution: {integrity: sha512-p8C3NnxXooRdNrdv6dBmRTddEapfESEUflpICDNKXpHvTjRRq1J82CbU5G3XfebIZyI3B0s074JHMWD36qOW6w==} + cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.13.2': - resolution: {integrity: sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==} + '@rollup/rollup-linux-riscv64-gnu@4.16.4': + resolution: {integrity: sha512-Lh/8ckoar4s4Id2foY7jNgitTOUQczwMWNYi+Mjt0eQ9LKhr6sK477REqQkmy8YHY3Ca3A2JJVdXnfb3Rrwkng==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.13.2': - resolution: {integrity: sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==} + '@rollup/rollup-linux-s390x-gnu@4.16.4': + resolution: {integrity: sha512-1xwwn9ZCQYuqGmulGsTZoKrrn0z2fAur2ujE60QgyDpHmBbXbxLaQiEvzJWDrscRq43c8DnuHx3QorhMTZgisQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.13.2': - resolution: {integrity: sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==} + '@rollup/rollup-linux-x64-gnu@4.16.4': + resolution: {integrity: sha512-LuOGGKAJ7dfRtxVnO1i3qWc6N9sh0Em/8aZ3CezixSTM+E9Oq3OvTsvC4sm6wWjzpsIlOCnZjdluINKESflJLA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.13.2': - resolution: {integrity: sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==} + '@rollup/rollup-linux-x64-musl@4.16.4': + resolution: {integrity: sha512-ch86i7KkJKkLybDP2AtySFTRi5fM3KXp0PnHocHuJMdZwu7BuyIKi35BE9guMlmTpwwBTB3ljHj9IQXnTCD0vA==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.13.2': - resolution: {integrity: sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==} + '@rollup/rollup-win32-arm64-msvc@4.16.4': + resolution: {integrity: sha512-Ma4PwyLfOWZWayfEsNQzTDBVW8PZ6TUUN1uFTBQbF2Chv/+sjenE86lpiEwj2FiviSmSZ4Ap4MaAfl1ciF4aSA==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.13.2': - resolution: {integrity: sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==} + '@rollup/rollup-win32-ia32-msvc@4.16.4': + resolution: {integrity: sha512-9m/ZDrQsdo/c06uOlP3W9G2ENRVzgzbSXmXHT4hwVaDQhYcRpi9bgBT0FTG9OhESxwK0WjQxYOSfv40cU+T69w==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.13.2': - resolution: {integrity: sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==} + '@rollup/rollup-win32-x64-msvc@4.16.4': + resolution: {integrity: sha512-YunpoOAyGLDseanENHmbFvQSfVL5BxW3k7hhy0eN4rb3gS/ct75dVD0EXOWIqFT/nE8XYW6LP6vz6ctKRi0k9A==} cpu: [x64] os: [win32] @@ -3218,9 +3314,6 @@ packages: '@storybook/manager@8.0.9': resolution: {integrity: sha512-+NnRo+5JQFGNqveKrLtC0b+Z08Tae4m44iq292bPeZMpr9OkFsIkU0PBPsHTHPkrqC/zZXRNsCsTEgvu3p2OIA==} - '@storybook/node-logger@8.0.5': - resolution: {integrity: sha512-ssT8YCcCqgc89ee+EeExCxcOpueOsU05iek2roR+NCZnoCL1DmzcUp8H9t0utLaK/ngPV8zatlzSDVgKTHSIJw==} - '@storybook/node-logger@8.0.9': resolution: {integrity: sha512-5ajMdZFrYrjGLJOVDq7dlEQNFsgeLHymt4dCK9MulL/ciXykmXUZXE3Bye0wFy+I2qqDVvrvR8uzCvSFvm5MAQ==} @@ -3327,72 +3420,68 @@ packages: peerDependencies: eslint: '>=8.40.0' - '@swc/core-darwin-arm64@1.4.11': - resolution: {integrity: sha512-C1j1Qp/IHSelVWdEnT7f0iONWxQz6FAqzjCF2iaL+0vFg4V5f2nlgrueY8vj5pNNzSGhrAlxsMxEIp4dj1MXkg==} + '@swc/core-darwin-arm64@1.5.0': + resolution: {integrity: sha512-dyA25zQjm3xmMFsRPFgBpSqWSW9TITnkndZkZAiPYLjBxH9oTNMa0l09BePsaqEeXySY++tUgAeYu/9onsHLbg==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.4.11': - resolution: {integrity: sha512-0TTy3Ni8ncgaMCchSQ7FK8ZXQLlamy0FXmGWbR58c+pVZWYZltYPTmheJUvVcR0H2+gPAymRKyfC0iLszDALjg==} + '@swc/core-darwin-x64@1.5.0': + resolution: {integrity: sha512-cO7kZMMA/fcQIBT31LBzcVNSk3AZGVYLqvEPnJhFImjPm3mGKUd6kWpARUEGR68MyRU2VsWhE6eCjMcM+G7bxw==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.4.11': - resolution: {integrity: sha512-XJLB71uw0rog4DjYAPxFGAuGCBQpgJDlPZZK6MTmZOvI/1t0+DelJ24IjHIxk500YYM26Yv47xPabqFPD7I2zQ==} + '@swc/core-linux-arm-gnueabihf@1.5.0': + resolution: {integrity: sha512-BXaXytS4y9lBFRO6vwA6ovvy1d2ZIzS02i2R1oegoZzzNu89CJDpkYXYS9bId0GvK2m9Q9y2ofoZzKE2Rp3PqQ==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.4.11': - resolution: {integrity: sha512-vYQwzJvm/iu052d5Iw27UFALIN5xSrGkPZXxLNMHPySVko2QMNNBv35HLatkEQHbQ3X+VKSW9J9SkdtAvAVRAQ==} + '@swc/core-linux-arm64-gnu@1.5.0': + resolution: {integrity: sha512-Bu4/41pGadXKnRsUbox0ig63xImATVH704oPCXcoOvNGkDyMjWgIAhzIi111vrwFNpj9utabgUE4AtlUa2tAOQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [glibc] - '@swc/core-linux-arm64-musl@1.4.11': - resolution: {integrity: sha512-eV+KduiRYUFjPsvbZuJ9aknQH9Tj0U2/G9oIZSzLx/18WsYi+upzHbgxmIIHJ2VJgfd7nN40RI/hMtxNsUzR/g==} + '@swc/core-linux-arm64-musl@1.5.0': + resolution: {integrity: sha512-lUFFvC8tsepNcTnKEHNrePWanVVef6PQ82Rv9wIeebgGHRUqDh6+CyCqodXez+aKz6NyE/PBIfp0r+jPx4hoJA==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [musl] - '@swc/core-linux-x64-gnu@1.4.11': - resolution: {integrity: sha512-WA1iGXZ2HpqM1OR9VCQZJ8sQ1KP2or9O4bO8vWZo6HZJIeoQSo7aa9waaCLRpkZvkng1ct/TF/l6ymqSNFXIzQ==} + '@swc/core-linux-x64-gnu@1.5.0': + resolution: {integrity: sha512-c6LegFU1qdyMfk+GzNIOvrX61+mksm21Q01FBnXSy1nf1ACj/a86jmr3zkPl0zpNVHfPOw3Ry1QIuLQKD+67YA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [glibc] - '@swc/core-linux-x64-musl@1.4.11': - resolution: {integrity: sha512-UkVJToKf0owwQYRnGvjHAeYVDfeimCEcx0VQSbJoN7Iy0ckRZi7YPlmWJU31xtKvikE2bQWCOVe0qbSDqqcWXA==} + '@swc/core-linux-x64-musl@1.5.0': + resolution: {integrity: sha512-I/V8aWBmfDWwjtM1bS8ASG+6PcO/pVFYyPP5g2ok46Vz1o1MnAUd18mHnWX43nqVJokaW+BD/G4ZMZ+gXRl4zQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [musl] - '@swc/core-win32-arm64-msvc@1.4.11': - resolution: {integrity: sha512-35khwkyly7lF5NDSyvIrukBMzxPorgc5iTSDfVO/LvnmN5+fm4lTlrDr4tUfTdOhv3Emy7CsKlsNAeFRJ+Pm+w==} + '@swc/core-win32-arm64-msvc@1.5.0': + resolution: {integrity: sha512-nN685BvI7iM58xabrSOSQHUvIY10pcXh5H9DmS8LeYqG6Dkq7QZ8AwYqqonOitIS5C35MUfhSMLpOTzKoLdUqA==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.4.11': - resolution: {integrity: sha512-Wx8/6f0ufgQF2pbVPsJ2dAmFLwIOW+xBE5fxnb7VnEbGkTgP1qMDWiiAtD9rtvDSuODG3i1AEmAak/2HAc6i6A==} + '@swc/core-win32-ia32-msvc@1.5.0': + resolution: {integrity: sha512-3YjltmEHljI+TvuDOC4lspUzjBUoB3X5BhftRBprSTJx/czuMl0vdoZKs2Snzb5Eqqesp0Rl8q+iQ1E1oJ6dEA==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.4.11': - resolution: {integrity: sha512-0xRFW6K9UZQH2NVC/0pVB0GJXS45lY24f+6XaPBF1YnMHd8A8GoHl7ugyM5yNUTe2AKhSgk5fJV00EJt/XBtdQ==} + '@swc/core-win32-x64-msvc@1.5.0': + resolution: {integrity: sha512-ZairtCwJsaxnUH85DcYCyGpNb9bUoIm9QXYW+VaEoXwbcB95dTIiJwN0aRxPT8B0B2MNw/CXLqjoPo6sDwz5iw==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.4.11': - resolution: {integrity: sha512-WKEakMZxkVwRdgMN4AMJ9K5nysY8g8npgQPczmjBeNK5In7QEAZAJwnyccrWwJZU0XjVeHn2uj+XbOKdDW17rg==} + '@swc/core@1.5.0': + resolution: {integrity: sha512-fjADAC5gOOX54Rpcr1lF9DHLD+nPD5H/zXLtEgK2Ez3esmogT+LfHzCZtUxqetjvaMChKhQ0Pp0ZB6Hpz/tCbw==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': ^0.5.0 @@ -3451,8 +3540,8 @@ packages: react-test-renderer: optional: true - '@testing-library/react@14.2.2': - resolution: {integrity: sha512-SOUuM2ysCvjUWBXTNfQ/ztmnKDmqaiPV3SvoIuyxMUca45rbSWWAT/qB8CUs/JQ/ux/8JFs9DNdFQ3f6jH3crA==} + '@testing-library/react@14.3.1': + resolution: {integrity: sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==} engines: {node: '>=14'} peerDependencies: react: ^18.0.0 @@ -3524,17 +3613,14 @@ packages: '@types/eslint@8.56.10': resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} - '@types/eslint@8.56.7': - resolution: {integrity: sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==} - '@types/estree@0.0.51': resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - '@types/express-serve-static-core@4.17.43': - resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==} + '@types/express-serve-static-core@4.19.0': + resolution: {integrity: sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==} '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} @@ -3569,21 +3655,17 @@ packages: '@types/mdast@3.0.15': resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} - '@types/mdx@2.0.12': - resolution: {integrity: sha512-H9VZ9YqE+H28FQVchC83RCs5xQ2J7mAAv6qdDEaWmXEVl3OpdH+xfrSUzQ1lp7U7oSTRZ0RvW08ASPJsYBi7Cw==} + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - '@types/mime@4.0.0': - resolution: {integrity: sha512-5eEkJZ/BLvTE3vXGKkWlyTSUVZuzj23Wj8PoyOq2lt5I3CYbiLBOPb3XmCW6QcuOibIUE6emHXHt9E/F/rCa6w==} - deprecated: This is a stub types definition. mime provides its own type definitions, so you do not need this installed. - '@types/minimatch@3.0.5': resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} - '@types/node@18.19.28': - resolution: {integrity: sha512-J5cOGD9n4x3YGgVuaND6khm5x07MMdAKkRyXnjVR6KFhLMNh2yONGiP7Z+4+tBOt5mK+GvDTiacTOVGGpqiecw==} + '@types/node@18.19.31': + resolution: {integrity: sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==} '@types/node@20.12.7': resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} @@ -3606,8 +3688,8 @@ packages: '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - '@types/qs@6.9.14': - resolution: {integrity: sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==} + '@types/qs@6.9.15': + resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} @@ -3637,8 +3719,8 @@ packages: '@types/send@0.17.4': resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} - '@types/serve-static@1.15.5': - resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} + '@types/serve-static@1.15.7': + resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} '@types/stylus@0.48.42': resolution: {integrity: sha512-CPGlr5teL4sqdap+EOowMifLuNGeIoLwc0VQ7u/BPxo+ocqiNa5jeVt0H0IVBblEh6ZwX1sGpIQIFnSSr8NBQA==} @@ -3677,10 +3759,6 @@ packages: resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/scope-manager@7.6.0': - resolution: {integrity: sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==} - engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/scope-manager@7.7.0': resolution: {integrity: sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==} engines: {node: ^18.18.0 || >=20.0.0} @@ -3713,10 +3791,6 @@ packages: resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/types@7.6.0': - resolution: {integrity: sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==} - engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/types@7.7.0': resolution: {integrity: sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==} engines: {node: ^18.18.0 || >=20.0.0} @@ -3734,15 +3808,6 @@ packages: typescript: optional: true - '@typescript-eslint/typescript-estree@7.6.0': - resolution: {integrity: sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/typescript-estree@7.7.0': resolution: {integrity: sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==} engines: {node: ^18.18.0 || >=20.0.0} @@ -3767,12 +3832,6 @@ packages: peerDependencies: eslint: ^7.0.0 || ^8.0.0 - '@typescript-eslint/utils@7.6.0': - resolution: {integrity: sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - '@typescript-eslint/utils@7.7.0': resolution: {integrity: sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==} engines: {node: ^18.18.0 || >=20.0.0} @@ -3789,10 +3848,6 @@ packages: resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/visitor-keys@7.6.0': - resolution: {integrity: sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==} - engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/visitor-keys@7.7.0': resolution: {integrity: sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==} engines: {node: ^18.18.0 || >=20.0.0} @@ -3813,8 +3868,8 @@ packages: react: '*' react-dom: '*' - '@univerjs/protocol@0.1.19': - resolution: {integrity: sha512-FmSezbI29MZr9IS8U9cGlWHbxMiM4NO3cCrhG3S5sH+69BGUppHqHI7psoP7CqGyowxaZN5dDDfSNTbrHenYVQ==} + '@univerjs/protocol@0.1.20': + resolution: {integrity: sha512-obplLf02e+x+E/Hezh3ES9H/JsFtHgYla7C1G1ffTVbbrqmRssebQ5birqgMcVx1Sj3WG5vsCJ0forISNRjkLg==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} peerDependencies: '@grpc/grpc-js': ^1.9.14 @@ -3826,40 +3881,34 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 - '@vitest/coverage-istanbul@1.5.0': - resolution: {integrity: sha512-mEbVTIAPKhMkszO0lwOwWiG8Cvkj7rdMgdmCNUDnmcSZYUWGIqM8+4O1bcQ1WMHkejpcwvED5oU6ZFm3syVb6A==} + '@vitest/coverage-istanbul@1.5.1': + resolution: {integrity: sha512-E8H34Ul4djgxyF6yss767qbfqw+4HrXVfUUufolL9MdK6gVH568aWV/VYQCfnh3+AIwZyjZrdZZH9f2L6zYxig==} peerDependencies: - vitest: 1.5.0 + vitest: 1.5.1 '@vitest/expect@1.3.1': resolution: {integrity: sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==} - '@vitest/expect@1.5.0': - resolution: {integrity: sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==} + '@vitest/expect@1.5.1': + resolution: {integrity: sha512-w3Bn+VUMqku+oWmxvPhTE86uMTbfmBl35aGaIPlwVW7Q89ZREC/icfo2HBsEZ3AAW6YR9lObfZKPEzstw9tJOQ==} - '@vitest/runner@1.5.0': - resolution: {integrity: sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==} + '@vitest/runner@1.5.1': + resolution: {integrity: sha512-mt372zsz0vFR7L1xF/ert4t+teD66oSuXoTyaZbl0eJgilvyzCKP1tJ21gVa8cDklkBOM3DLnkE1ljj/BskyEw==} - '@vitest/snapshot@1.5.0': - resolution: {integrity: sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==} + '@vitest/snapshot@1.5.1': + resolution: {integrity: sha512-h/1SGaZYXmjn6hULRBOlqam2z4oTlEe6WwARRzLErAPBqljAs6eX7tfdyN0K+MpipIwSZ5sZsubDWkCPAiVXZQ==} '@vitest/spy@1.3.1': resolution: {integrity: sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==} - '@vitest/spy@1.4.0': - resolution: {integrity: sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==} - - '@vitest/spy@1.5.0': - resolution: {integrity: sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==} + '@vitest/spy@1.5.1': + resolution: {integrity: sha512-vsqczk6uPJjmPLy6AEtqfbFqgLYcGBe9BTY+XL8L6y8vrGOhyE23CJN9P/hPimKXnScbqiZ/r/UtUSOQ2jIDGg==} '@vitest/utils@1.3.1': resolution: {integrity: sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==} - '@vitest/utils@1.4.0': - resolution: {integrity: sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==} - - '@vitest/utils@1.5.0': - resolution: {integrity: sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==} + '@vitest/utils@1.5.1': + resolution: {integrity: sha512-92pE17bBXUxA0Y7goPcvnATMCuq4NQLOmqsG0e2BtzRi7KLwZB5jpiELi/8ybY8IQNWemKjSD5rMoO7xTdv8ug==} '@volar/language-core@1.11.1': resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} @@ -3870,17 +3919,17 @@ packages: '@volar/typescript@1.11.1': resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==} - '@vue/compiler-core@3.4.21': - resolution: {integrity: sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==} + '@vue/compiler-core@3.4.25': + resolution: {integrity: sha512-Y2pLLopaElgWnMNolgG8w3C5nNUVev80L7hdQ5iIKPtMJvhVpG0zhnBG/g3UajJmZdvW0fktyZTotEHD1Srhbg==} - '@vue/compiler-dom@3.4.21': - resolution: {integrity: sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==} + '@vue/compiler-dom@3.4.25': + resolution: {integrity: sha512-Ugz5DusW57+HjllAugLci19NsDK+VyjGvmbB2TXaTcSlQxwL++2PETHx/+Qv6qFwNLzSt7HKepPe4DcTE3pBWg==} - '@vue/compiler-sfc@3.4.21': - resolution: {integrity: sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==} + '@vue/compiler-sfc@3.4.25': + resolution: {integrity: sha512-m7rryuqzIoQpOBZ18wKyq05IwL6qEpZxFZfRxlNYuIPDqywrXQxgUwLXIvoU72gs6cRdY6wHD0WVZIFE4OEaAQ==} - '@vue/compiler-ssr@3.4.21': - resolution: {integrity: sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==} + '@vue/compiler-ssr@3.4.25': + resolution: {integrity: sha512-H2ohvM/Pf6LelGxDBnfbbXFPyM4NE3hrw0e/EpwuSiYu8c819wx+SVGdJ65p/sFrYDd6OnSDxN1MB2mN07hRSQ==} '@vue/language-core@1.8.27': resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==} @@ -3890,22 +3939,22 @@ packages: typescript: optional: true - '@vue/reactivity@3.4.21': - resolution: {integrity: sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==} + '@vue/reactivity@3.4.25': + resolution: {integrity: sha512-mKbEtKr1iTxZkAG3vm3BtKHAOhuI4zzsVcN0epDldU/THsrvfXRKzq+lZnjczZGnTdh3ojd86/WrP+u9M51pWQ==} - '@vue/runtime-core@3.4.21': - resolution: {integrity: sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==} + '@vue/runtime-core@3.4.25': + resolution: {integrity: sha512-3qhsTqbEh8BMH3pXf009epCI5E7bKu28fJLi9O6W+ZGt/6xgSfMuGPqa5HRbUxLoehTNp5uWvzCr60KuiRIL0Q==} - '@vue/runtime-dom@3.4.21': - resolution: {integrity: sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==} + '@vue/runtime-dom@3.4.25': + resolution: {integrity: sha512-ode0sj77kuwXwSc+2Yhk8JMHZh1sZp9F/51wdBiz3KGaWltbKtdihlJFhQG4H6AY+A06zzeMLkq6qu8uDSsaoA==} - '@vue/server-renderer@3.4.21': - resolution: {integrity: sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==} + '@vue/server-renderer@3.4.25': + resolution: {integrity: sha512-8VTwq0Zcu3K4dWV0jOwIVINESE/gha3ifYCOKEhxOj6MEl5K5y8J8clQncTcDhKF+9U765nRw4UdUEXvrGhyVQ==} peerDependencies: - vue: 3.4.21 + vue: 3.4.25 - '@vue/shared@3.4.21': - resolution: {integrity: sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==} + '@vue/shared@3.4.25': + resolution: {integrity: sha512-k0yappJ77g2+KNrIaF0FFnzwLvUBLUYr8VOwz+/6vLsmItFp51AcxLL7Ey3iPd7BIRyWPOcqUjMnm7OkahXllA==} '@webassemblyjs/ast@1.12.1': resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} @@ -4198,8 +4247,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - babel-plugin-polyfill-corejs2@0.4.10: - resolution: {integrity: sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==} + babel-plugin-polyfill-corejs2@0.4.11: + resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -4208,8 +4257,8 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.6.1: - resolution: {integrity: sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==} + babel-plugin-polyfill-regenerator@0.6.2: + resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -4333,8 +4382,8 @@ packages: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} - caniuse-lite@1.0.30001603: - resolution: {integrity: sha512-iL2iSS0eDILMb9n5yKQoTBim9jMZ0Yrk8g0N9K7UzYyWnfIKzXBZD5ngpM37ZcL/cv0Mli8XtVMRYMQAfFpi5Q==} + caniuse-lite@1.0.30001612: + resolution: {integrity: sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==} case-sensitive-paths-webpack-plugin@2.4.0: resolution: {integrity: sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==} @@ -4569,6 +4618,9 @@ packages: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} engines: {'0': node >= 6.0} + confbox@0.1.7: + resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} + config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} @@ -4675,8 +4727,8 @@ packages: copy-anything@2.0.6: resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} - core-js-compat@3.36.1: - resolution: {integrity: sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==} + core-js-compat@3.37.0: + resolution: {integrity: sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -4714,8 +4766,8 @@ packages: resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} engines: {node: '>=12'} - css-loader@6.10.0: - resolution: {integrity: sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==} + css-loader@6.11.0: + resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==} engines: {node: '>= 12.13.0'} peerDependencies: '@rspack/core': 0.x || 1.x @@ -5005,13 +5057,13 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - ejs@3.1.9: - resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==} + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.4.722: - resolution: {integrity: sha512-5nLE0TWFFpZ80Crhtp4pIp8LXCztjYX41yUcV6b+bKR2PqzjskTMOOlBi1VjBHlvHwS+4gar7kNKOrsbsewEZQ==} + electron-to-chromium@1.4.747: + resolution: {integrity: sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==} emoji-regex@10.3.0: resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} @@ -5050,8 +5102,8 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - envinfo@7.11.1: - resolution: {integrity: sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==} + envinfo@7.12.0: + resolution: {integrity: sha512-Iw9rQJBGpJRd3rwXm9ft/JiGoAZmLxxJZELYDQoPRZ4USVhkKtIcNBPw6U+/K2mBpaqM25JSV6Yl4Az9vO2wJg==} engines: {node: '>=4'} hasBin: true @@ -5374,8 +5426,8 @@ packages: peerDependencies: eslint: '>=8' - eslint-plugin-n@17.2.1: - resolution: {integrity: sha512-uW1+df2bo06kR7ix6nB614RUlvjRPrYxlaX832O6e1MCJp4V7YozEdvMgCYuvn4ltnjPu1FVYhQ2KRrmTNoJfg==} + eslint-plugin-n@17.3.1: + resolution: {integrity: sha512-25+HTtKe1F8U/M4ERmdzbz/xkm/gaY0OYC8Fcv1z/WvpLJ8Xfh9LzJ13JV5uj4QyCUD8kOPJrNjn/3y+tc57Vw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=8.23.0' @@ -5479,8 +5531,8 @@ packages: '@typescript-eslint/eslint-plugin': optional: true - eslint-plugin-vitest@0.5.3: - resolution: {integrity: sha512-D0iu6ppP6FmNSZP4cdhEXqyI+fuW6JwwWdECRrNymd1jiVgUmDgSvtryytonNxHQQWhGNmZM3V/qvpXttH1rRQ==} + eslint-plugin-vitest@0.5.4: + resolution: {integrity: sha512-um+odCkccAHU53WdKAw39MY61+1x990uXjSPguUCq3VcEHdqJrOb8OTMrbYlY6f9jAKx7x98kLVlIe3RJeJqoQ==} engines: {node: ^18.0.0 || >= 20.0.0} peerDependencies: '@typescript-eslint/eslint-plugin': '*' @@ -5689,8 +5741,8 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - flow-parser@0.232.0: - resolution: {integrity: sha512-U8vcKyYdM+Kb0tPzfPJ5JyPMU0uXKwHxp0L6BcEc+wBlbTW9qRhOqV5DeGXclgclVvtqQNGEG8Strj/b6c/IxA==} + flow-parser@0.235.1: + resolution: {integrity: sha512-s04193L4JE+ntEcQXbD6jxRRlyj9QXcgEl2W6xSjH4l9x4b0eHoCHfbYHjqf9LdZFUiM5LhgpiqsvLj/AyOyYQ==} engines: {node: '>=0.4.0'} for-each@0.3.3: @@ -5879,10 +5931,6 @@ packages: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - globals@15.0.0: resolution: {integrity: sha512-m/C/yR4mjO6pXDTm9/R/SpYTAIyaUB4EOzcaaMEl7mds7Mshct9GfejiJNQGjHHbdMPey13Kpu4TMbYi9ex1pw==} engines: {node: '>=18'} @@ -6481,10 +6529,6 @@ packages: js-tokens@9.0.0: resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} - js-tokens@https://r2.cnpmjs.org/js-tokens/-/js-tokens-4.0.0.tgz: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, tarball: https://r2.cnpmjs.org/js-tokens/-/js-tokens-4.0.0.tgz} - version: 4.0.0 - js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -6559,9 +6603,6 @@ packages: resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - jsonc-parser@3.2.1: - resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} - jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -6693,10 +6734,6 @@ packages: lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} - lodash.get@https://r2.cnpmjs.org/lodash.get/-/lodash.get-4.4.2.tgz: - resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==, tarball: https://r2.cnpmjs.org/lodash.get/-/lodash.get-4.4.2.tgz} - version: 4.4.2 - lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} @@ -6755,11 +6792,6 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - loose-envify@https://r2.cnpmjs.org/loose-envify/-/loose-envify-1.4.0.tgz: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, tarball: https://r2.cnpmjs.org/loose-envify/-/loose-envify-1.4.0.tgz} - version: 1.4.0 - hasBin: true - loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} @@ -6785,11 +6817,6 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - lru-cache@https://r2.cnpmjs.org/lru-cache/-/lru-cache-6.0.0.tgz: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==, tarball: https://r2.cnpmjs.org/lru-cache/-/lru-cache-6.0.0.tgz} - version: 6.0.0 - engines: {node: '>=10'} - lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -6798,12 +6825,11 @@ packages: resolution: {integrity: sha512-fSErXALFNsnowREYZ49XCdOHF8wOPWuFOGQrAhP7x5J/BqQv+B02cNsTykGpDgRVx43EKg++6ANmTaGTtW+hUA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - magic-string@0.30.8: - resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} - engines: {node: '>=12'} + magic-string@0.30.10: + resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} - magicast@0.3.3: - resolution: {integrity: sha512-ZbrP1Qxnpoes8sz47AM0z08U+jW6TyRgZzcWy3Ma3vDhJttwMwAFDMMQFobwdBxByBD46JYmxRzeF7w2+wJEuw==} + magicast@0.3.4: + resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==} make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} @@ -6896,11 +6922,6 @@ packages: engines: {node: '>=4'} hasBin: true - mime@4.0.1: - resolution: {integrity: sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==} - engines: {node: '>=16'} - hasBin: true - mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -7092,8 +7113,8 @@ packages: numfmt@2.5.2: resolution: {integrity: sha512-VXrB2bpU9Xa0oCHq8IsqE2CcUx5OLupLC3oryFT4DB9e/xe+OnUzBndhXfNHUzxFE4DYI3Sx4OtzS1Sdaf7tEw==} - nwsapi@2.2.7: - resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} + nwsapi@2.2.9: + resolution: {integrity: sha512-2f3F0SEEer8bBu0dsNCFF50N0cTThV1nWFYcEYFZttdW0lDAoybv9cQoK7X7/68Z89S7FoRrVjP1LPX4XRf9vg==} nypm@0.3.8: resolution: {integrity: sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og==} @@ -7365,10 +7386,6 @@ packages: picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - picocolors@https://r2.cnpmjs.org/picocolors/-/picocolors-1.0.0.tgz: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==, tarball: https://r2.cnpmjs.org/picocolors/-/picocolors-1.0.0.tgz} - version: 1.0.0 - picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -7402,8 +7419,8 @@ packages: resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==} engines: {node: '>=10'} - pkg-types@1.0.3: - resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + pkg-types@1.1.0: + resolution: {integrity: sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==} playwright-core@1.43.1: resolution: {integrity: sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==} @@ -7427,20 +7444,20 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} - postcss-modules-extract-imports@3.0.0: - resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} + postcss-modules-extract-imports@3.1.0: + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 - postcss-modules-local-by-default@4.0.4: - resolution: {integrity: sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==} + postcss-modules-local-by-default@4.0.5: + resolution: {integrity: sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 - postcss-modules-scope@3.1.1: - resolution: {integrity: sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==} + postcss-modules-scope@3.2.0: + resolution: {integrity: sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 @@ -7564,8 +7581,8 @@ packages: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} - qs@6.12.0: - resolution: {integrity: sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==} + qs@6.12.1: + resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==} engines: {node: '>=0.6'} querystringify@2.2.0: @@ -7645,8 +7662,8 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' - rc-picker@4.3.0: - resolution: {integrity: sha512-bQNB/+NdW55jlQ5lPnNqF5J90Tq4SihLbAF7tzPBvGDJyoYmDgwLm4FN0ZB3Ot9i1v6vJY/1mgqZZTT9jbYc5w==} + rc-picker@4.4.2: + resolution: {integrity: sha512-MdbAXvwiGyhb+bHe66qPps8xPQivzEgcyCp3/MPK4T+oER0gOmVRCEDxaD4FhYG/7GLH3rDrHpu79BvEn2JFTQ==} engines: {node: '>=8.x'} peerDependencies: date-fns: '>= 2.x' @@ -7677,8 +7694,8 @@ packages: react: '>=16.0.0' react-dom: '>=16.0.0' - rc-select@14.13.0: - resolution: {integrity: sha512-ew34FsaqHokK4dxVrcIxSYrgWJ2XJYlkk32eiOIiEo3GkHUExdCzmozMYaUc2P67c5QJRUvvY0uqCs3QG67h5A==} + rc-select@14.13.1: + resolution: {integrity: sha512-A1VHqjIOemxLnUGRxLGVqXBs8jGcJemI5NXxOJwU5PQc1wigAu1T4PRLgMkTPDOz8gPhlY9dwsPzMgakMc2QjQ==} engines: {node: '>=8.x'} peerDependencies: react: '*' @@ -7702,8 +7719,8 @@ packages: react: '>=16.9.0' react-dom: '>=16.9.0' - rc-virtual-list@3.11.4: - resolution: {integrity: sha512-NbBi0fvyIu26gP69nQBiWgUMTPX3mr4FcuBQiVqagU0BnuX8WQkiivnMs105JROeuUIFczLrlgUhLQwTWV1XDA==} + rc-virtual-list@3.11.5: + resolution: {integrity: sha512-iZRW99m5jAxtwKNPLwUrPryurcnKpXBdTyhuBp6ythf7kg/otKO5cCiIvL55GQwU0QGSlouQS0tnkciRMJUwRQ==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' @@ -7971,10 +7988,6 @@ packages: resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} - resize-observer-polyfill@https://r2.cnpmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz: - resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==, tarball: https://r2.cnpmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz} - version: 1.5.1 - resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} @@ -8035,8 +8048,8 @@ packages: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true - rollup@4.13.2: - resolution: {integrity: sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==} + rollup@4.16.4: + resolution: {integrity: sha512-kuaTJSUbz+Wsb2ATGvEknkI12XV40vIiHmLuFlejoo7HtDok/O5eDDD0UpCVY5bBX5U5RYo8wWP83H7ZsqVEnA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -8074,8 +8087,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.72.0: - resolution: {integrity: sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==} + sass@1.75.0: + resolution: {integrity: sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==} engines: {node: '>=14.0.0'} hasBin: true @@ -8218,8 +8231,8 @@ packages: resolution: {integrity: sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==} engines: {node: '>= 14'} - socks@2.8.1: - resolution: {integrity: sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==} + socks@2.8.3: + resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} source-map-js@1.2.0: @@ -8477,8 +8490,8 @@ packages: uglify-js: optional: true - terser@5.30.0: - resolution: {integrity: sha512-Y/SblUl5kEyEFzhMAQdsxVHh+utAxd4IuRNJzKywY/4uzSogh3G219jqbDDxYu4MXO9CzY3tSEqmZvW6AoEDJw==} + terser@5.30.4: + resolution: {integrity: sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==} engines: {node: '>=10'} hasBin: true @@ -8505,11 +8518,11 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tinybench@2.6.0: - resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} + tinybench@2.8.0: + resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} - tinypool@0.8.3: - resolution: {integrity: sha512-Ud7uepAklqRH1bvwy22ynrliC7Dljz7Tm8M/0RBUW+YRa4YHhZ6e4PpgE+fu1zr/WqB1kbeuVrdfeuyIBpy4tw==} + tinypool@0.8.4: + resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} engines: {node: '>=14.0.0'} tinyspy@2.2.1: @@ -8528,8 +8541,8 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - tocbot@4.25.0: - resolution: {integrity: sha512-kE5wyCQJ40hqUaRVkyQ4z5+4juzYsv/eK+aqD97N62YH0TxFhzJvo22RUQQZdO3YnXAk42ZOfOpjVdy+Z0YokA==} + tocbot@4.27.4: + resolution: {integrity: sha512-RJMmos8JXMztNIaasVRyXc/eGQPE+APSkipNBJaFjLC++cYg6zCcRHQRy4EXncpiKiz1Nlax8RTsaSRJMam8CQ==} toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} @@ -8651,8 +8664,8 @@ packages: resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} engines: {node: '>=14.16'} - type-fest@4.14.0: - resolution: {integrity: sha512-on5/Cw89wwqGZQu+yWO0gGMGu8VNxsaW9SB2HE8yJjllEk7IDTwnSN1dUVldYILhYPN5HzD7WAaw2cc/jBfn0Q==} + type-fest@4.17.0: + resolution: {integrity: sha512-9flrz1zkfLRH3jO3bLflmTxryzKMxVa7841VeMgBaNQGY6vH4RCcpN/sQLB7mQQYh1GZ5utT2deypMuCy4yicw==} engines: {node: '>=16'} type-is@1.6.18: @@ -8841,8 +8854,8 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite-node@1.5.0: - resolution: {integrity: sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==} + vite-node@1.5.1: + resolution: {integrity: sha512-HNpfV7BrAsjkYVNWIcPleJwvJmydJqqJRrRbpoQ/U7QDwJKyEzNa4g5aYg8MjXJyKsk29IUCcMLFRcsEvqUIsA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -8889,15 +8902,15 @@ packages: peerDependencies: vitest: '*' - vitest@1.5.0: - resolution: {integrity: sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==} + vitest@1.5.1: + resolution: {integrity: sha512-3GvBMpoRnUNbZRX1L3mJCv3Ou3NAobb4dM48y8k9ZGwDofePpclTOyO+lqJFKSQpubH1V8tEcAEw/Y3mJKGJQQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.5.0 - '@vitest/ui': 1.5.0 + '@vitest/browser': 1.5.1 + '@vitest/ui': 1.5.1 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -8929,8 +8942,8 @@ packages: peerDependencies: typescript: '*' - vue@3.4.21: - resolution: {integrity: sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==} + vue@3.4.25: + resolution: {integrity: sha512-HWyDqoBHMgav/OKiYA2ZQg+kjfMgLt/T0vg4cbIF7JbXAjDexRf5JRg+PWAfrAkSmTd2I8aPSXtooBFWHB98cg==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -9119,10 +9132,6 @@ packages: yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yallist@https://r2.cnpmjs.org/yallist/-/yallist-4.0.0.tgz: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==, tarball: https://r2.cnpmjs.org/yallist/-/yallist-4.0.0.tgz} - version: 4.0.0 - yaml-eslint-parser@1.2.2: resolution: {integrity: sha512-pEwzfsKbTrB8G3xc/sN7aw1v6A6c/pKxLAkjclnAyo5g5qOh6eL9WGu0o3cSDQZKrTNk4KL4lQSwZW+nBkANEg==} engines: {node: ^14.17.0 || >=16.0.0} @@ -9172,12 +9181,13 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - '@antfu/eslint-config@2.15.0(@eslint-react/eslint-plugin@1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(@vue/compiler-sfc@3.4.21)(eslint-plugin-format@0.1.1(eslint@8.57.0))(eslint-plugin-react-hooks@4.6.0(eslint@8.57.0))(eslint-plugin-react-refresh@0.4.6(eslint@8.57.0))(eslint@8.57.0)(typescript@5.4.5)(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0))': + '@antfu/eslint-config@2.15.0(@eslint-react/eslint-plugin@1.5.9)(@vue/compiler-sfc@3.4.25)(eslint-plugin-format@0.1.1)(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react-refresh@0.4.6)(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@antfu/install-pkg': 0.3.2 '@clack/prompts': 0.7.0 + '@eslint-react/eslint-plugin': 1.5.9(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5) '@stylistic/eslint-plugin': 1.7.2(eslint@8.57.0)(typescript@5.4.5) - '@typescript-eslint/eslint-plugin': 7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/eslint-plugin': 7.7.1(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5) '@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-config-flat-gitignore: 0.1.5 @@ -9185,20 +9195,23 @@ snapshots: eslint-merge-processors: 0.1.0(eslint@8.57.0) eslint-plugin-antfu: 2.1.2(eslint@8.57.0) eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0) + eslint-plugin-format: 0.1.1(eslint@8.57.0) eslint-plugin-import-x: 0.5.0(eslint@8.57.0)(typescript@5.4.5) eslint-plugin-jsdoc: 48.2.3(eslint@8.57.0) eslint-plugin-jsonc: 2.15.1(eslint@8.57.0) eslint-plugin-markdown: 4.0.1(eslint@8.57.0) - eslint-plugin-n: 17.2.1(eslint@8.57.0) + eslint-plugin-n: 17.3.1(eslint@8.57.0) eslint-plugin-no-only-tests: 3.1.0 - eslint-plugin-perfectionist: 2.9.0(eslint@8.57.0)(typescript@5.4.5)(vue-eslint-parser@9.4.2(eslint@8.57.0)) + eslint-plugin-perfectionist: 2.9.0(eslint@8.57.0)(typescript@5.4.5)(vue-eslint-parser@9.4.2) + eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) + eslint-plugin-react-refresh: 0.4.6(eslint@8.57.0) eslint-plugin-toml: 0.11.0(eslint@8.57.0) eslint-plugin-unicorn: 52.0.0(eslint@8.57.0) - eslint-plugin-unused-imports: 3.1.0(@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) - eslint-plugin-vitest: 0.5.3(@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)) + eslint-plugin-unused-imports: 3.1.0(@typescript-eslint/eslint-plugin@7.7.1)(eslint@8.57.0) + eslint-plugin-vitest: 0.5.4(@typescript-eslint/eslint-plugin@7.7.1)(eslint@8.57.0)(typescript@5.4.5) eslint-plugin-vue: 9.25.0(eslint@8.57.0) eslint-plugin-yml: 1.14.0(eslint@8.57.0) - eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.4.21)(eslint@8.57.0) + eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.4.25)(eslint@8.57.0) globals: 15.0.0 jsonc-eslint-parser: 2.4.0 local-pkg: 0.5.0 @@ -9208,11 +9221,6 @@ snapshots: vue-eslint-parser: 9.4.2(eslint@8.57.0) yaml-eslint-parser: 1.2.2 yargs: 17.7.2 - optionalDependencies: - '@eslint-react/eslint-plugin': 1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - eslint-plugin-format: 0.1.1(eslint@8.57.0) - eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) - eslint-plugin-react-refresh: 0.4.6(eslint@8.57.0) transitivePeerDependencies: - '@vue/compiler-sfc' - supports-color @@ -9231,19 +9239,19 @@ snapshots: '@babel/code-frame@7.24.2': dependencies: '@babel/highlight': 7.24.2 - picocolors: https://r2.cnpmjs.org/picocolors/-/picocolors-1.0.0.tgz + picocolors: 1.0.0 - '@babel/compat-data@7.24.1': {} + '@babel/compat-data@7.24.4': {} - '@babel/core@7.24.3': + '@babel/core@7.24.4': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.24.2 - '@babel/generator': 7.24.1 + '@babel/generator': 7.24.4 '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.3) - '@babel/helpers': 7.24.1 - '@babel/parser': 7.24.1 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) + '@babel/helpers': 7.24.4 + '@babel/parser': 7.24.4 '@babel/template': 7.24.0 '@babel/traverse': 7.24.1 '@babel/types': 7.24.0 @@ -9255,7 +9263,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.24.1': + '@babel/generator@7.24.4': dependencies: '@babel/types': 7.24.0 '@jridgewell/gen-mapping': 0.3.5 @@ -9272,35 +9280,35 @@ snapshots: '@babel/helper-compilation-targets@7.23.6': dependencies: - '@babel/compat-data': 7.24.1 + '@babel/compat-data': 7.24.4 '@babel/helper-validator-option': 7.23.5 browserslist: 4.23.0 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.24.1(@babel/core@7.24.3)': + '@babel/helper-create-class-features-plugin@7.24.4(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 '@babel/helper-member-expression-to-functions': 7.23.0 '@babel/helper-optimise-call-expression': 7.22.5 - '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.3) + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.4) '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 semver: 6.3.1 - '@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.24.3)': + '@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.22.5 regexpu-core: 5.3.2 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.1(@babel/core@7.24.3)': + '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-plugin-utils': 7.24.0 debug: 4.3.4 @@ -9328,9 +9336,9 @@ snapshots: dependencies: '@babel/types': 7.24.0 - '@babel/helper-module-transforms@7.23.3(@babel/core@7.24.3)': + '@babel/helper-module-transforms@7.23.3(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-module-imports': 7.24.3 '@babel/helper-simple-access': 7.22.5 @@ -9343,16 +9351,16 @@ snapshots: '@babel/helper-plugin-utils@7.24.0': {} - '@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.24.3)': + '@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-wrap-function': 7.22.20 - '@babel/helper-replace-supers@7.24.1(@babel/core@7.24.3)': + '@babel/helper-replace-supers@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-member-expression-to-functions': 7.23.0 '@babel/helper-optimise-call-expression': 7.22.5 @@ -9381,7 +9389,7 @@ snapshots: '@babel/template': 7.24.0 '@babel/types': 7.24.0 - '@babel/helpers@7.24.1': + '@babel/helpers@7.24.4': dependencies: '@babel/template': 7.24.0 '@babel/traverse': 7.24.1 @@ -9396,565 +9404,572 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.0.0 - '@babel/parser@7.24.1': + '@babel/parser@7.24.4': dependencies: '@babel/types': 7.24.0 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.4(@babel/core@7.24.4)': + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-plugin-utils': 7.24.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-transform-optional-chaining': 7.24.1(@babel/core@7.24.3) + '@babel/plugin-transform-optional-chaining': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.3)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.3)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.3)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.3)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.3)': + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.3)': + '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-flow@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-syntax-flow@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-import-assertions@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-syntax-import-assertions@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-import-attributes@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-syntax-import-attributes@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.3)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.3)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.3)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.3)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.3)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.3)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.3)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.3)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.3)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.3)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.3)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-arrow-functions@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-arrow-functions@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-async-generator-functions@7.24.3(@babel/core@7.24.3)': + '@babel/plugin-transform-async-generator-functions@7.24.3(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.24.0 - '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.3) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.3) + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.4) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.4) - '@babel/plugin-transform-async-to-generator@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-async-to-generator@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-module-imports': 7.24.3 '@babel/helper-plugin-utils': 7.24.0 - '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.3) + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.4) - '@babel/plugin-transform-block-scoped-functions@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-block-scoped-functions@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-block-scoping@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-block-scoping@7.24.4(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-class-properties@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-class-properties@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-class-static-block@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-class-static-block@7.24.4(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.3) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.4) - '@babel/plugin-transform-classes@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-classes@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 '@babel/helper-plugin-utils': 7.24.0 - '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.3) + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.4) '@babel/helper-split-export-declaration': 7.22.6 globals: 11.12.0 - '@babel/plugin-transform-computed-properties@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-computed-properties@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 '@babel/template': 7.24.0 - '@babel/plugin-transform-destructuring@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-destructuring@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-dotall-regex@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-dotall-regex@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-duplicate-keys@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-duplicate-keys@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-dynamic-import@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-dynamic-import@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.3) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-transform-exponentiation-operator@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-exponentiation-operator@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-export-namespace-from@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-export-namespace-from@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.3) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-transform-flow-strip-types@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-flow-strip-types@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.24.3) + '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-for-of@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-for-of@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-transform-function-name@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-function-name@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-function-name': 7.23.0 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-json-strings@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-json-strings@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.3) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-transform-literals@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-literals@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-logical-assignment-operators@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-logical-assignment-operators@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.3) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.4) - '@babel/plugin-transform-member-expression-literals@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-member-expression-literals@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-modules-amd@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-modules-amd@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-modules-commonjs@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-modules-commonjs@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-simple-access': 7.22.5 - '@babel/plugin-transform-modules-systemjs@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-modules-systemjs@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.3) + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-validator-identifier': 7.22.20 - '@babel/plugin-transform-modules-umd@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-modules-umd@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.24.3)': + '@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-new-target@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-new-target@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-nullish-coalescing-operator@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-nullish-coalescing-operator@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.3) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-transform-numeric-separator@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-numeric-separator@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.3) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.4) - '@babel/plugin-transform-object-rest-spread@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-object-rest-spread@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-transform-parameters': 7.24.1(@babel/core@7.24.3) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-transform-parameters': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-object-super@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-object-super@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.3) + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-optional-catch-binding@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-optional-catch-binding@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.3) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-transform-optional-chaining@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-optional-chaining@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.3) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) - '@babel/plugin-transform-parameters@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-parameters@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-private-methods@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-private-methods@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-private-property-in-object@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-private-property-in-object@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.3) + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.3) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.4) - '@babel/plugin-transform-property-literals@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-property-literals@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-react-jsx-self@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-react-jsx-self@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-react-jsx-source@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-react-jsx-source@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-regenerator@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-regenerator@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 regenerator-transform: 0.15.2 - '@babel/plugin-transform-reserved-words@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-reserved-words@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-shorthand-properties@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-shorthand-properties@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-spread@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-spread@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-transform-sticky-regex@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-sticky-regex@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-template-literals@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-template-literals@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-typeof-symbol@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-typeof-symbol@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-typescript@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-typescript@7.24.4(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.24.1(@babel/core@7.24.3) + '@babel/helper-create-class-features-plugin': 7.24.4(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.24.3) + '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.24.4) - '@babel/plugin-transform-unicode-escapes@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-unicode-escapes@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-unicode-property-regex@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-unicode-property-regex@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-unicode-regex@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-unicode-regex@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/plugin-transform-unicode-sets-regex@7.24.1(@babel/core@7.24.3)': + '@babel/plugin-transform-unicode-sets-regex@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.4) '@babel/helper-plugin-utils': 7.24.0 - '@babel/preset-env@7.24.3(@babel/core@7.24.3)': + '@babel/preset-env@7.24.4(@babel/core@7.24.4)': dependencies: - '@babel/compat-data': 7.24.1 - '@babel/core': 7.24.3 + '@babel/compat-data': 7.24.4 + '@babel/core': 7.24.4 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-validator-option': 7.23.5 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.3) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.3) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.3) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.3) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-import-assertions': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-syntax-import-attributes': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.3) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.3) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.3) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.3) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.3) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.3) - '@babel/plugin-transform-arrow-functions': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-async-generator-functions': 7.24.3(@babel/core@7.24.3) - '@babel/plugin-transform-async-to-generator': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-block-scoped-functions': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-block-scoping': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-class-properties': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-class-static-block': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-classes': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-computed-properties': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-destructuring': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-dotall-regex': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-duplicate-keys': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-dynamic-import': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-exponentiation-operator': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-export-namespace-from': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-for-of': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-function-name': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-json-strings': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-literals': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-logical-assignment-operators': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-member-expression-literals': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-modules-amd': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-modules-systemjs': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-modules-umd': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.24.3) - '@babel/plugin-transform-new-target': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-nullish-coalescing-operator': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-numeric-separator': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-object-rest-spread': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-object-super': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-optional-catch-binding': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-optional-chaining': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-parameters': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-private-property-in-object': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-property-literals': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-regenerator': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-reserved-words': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-shorthand-properties': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-spread': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-sticky-regex': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-template-literals': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-typeof-symbol': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-unicode-escapes': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-unicode-property-regex': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-unicode-regex': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-unicode-sets-regex': 7.24.1(@babel/core@7.24.3) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.3) - babel-plugin-polyfill-corejs2: 0.4.10(@babel/core@7.24.3) - babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.3) - babel-plugin-polyfill-regenerator: 0.6.1(@babel/core@7.24.3) - core-js-compat: 3.36.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.4(@babel/core@7.24.4) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.4) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.4) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.4) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.4) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-import-assertions': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-syntax-import-attributes': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.4) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.4) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.4) + '@babel/plugin-transform-arrow-functions': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-async-generator-functions': 7.24.3(@babel/core@7.24.4) + '@babel/plugin-transform-async-to-generator': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-block-scoped-functions': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-block-scoping': 7.24.4(@babel/core@7.24.4) + '@babel/plugin-transform-class-properties': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-class-static-block': 7.24.4(@babel/core@7.24.4) + '@babel/plugin-transform-classes': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-computed-properties': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-destructuring': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-dotall-regex': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-duplicate-keys': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-dynamic-import': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-exponentiation-operator': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-export-namespace-from': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-for-of': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-function-name': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-json-strings': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-literals': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-logical-assignment-operators': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-member-expression-literals': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-modules-amd': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-modules-systemjs': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-modules-umd': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-new-target': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-nullish-coalescing-operator': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-numeric-separator': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-object-rest-spread': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-object-super': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-optional-catch-binding': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-optional-chaining': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-parameters': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-private-property-in-object': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-property-literals': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-regenerator': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-reserved-words': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-shorthand-properties': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-spread': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-sticky-regex': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-template-literals': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-typeof-symbol': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-unicode-escapes': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-unicode-property-regex': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-unicode-regex': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-unicode-sets-regex': 7.24.1(@babel/core@7.24.4) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.4) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.4) + babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.4) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.4) + core-js-compat: 3.37.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-flow@7.24.1(@babel/core@7.24.3)': + '@babel/preset-flow@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-validator-option': 7.23.5 - '@babel/plugin-transform-flow-strip-types': 7.24.1(@babel/core@7.24.3) + '@babel/plugin-transform-flow-strip-types': 7.24.1(@babel/core@7.24.4) - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.3)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 '@babel/types': 7.24.0 esutils: 2.0.3 - '@babel/preset-typescript@7.24.1(@babel/core@7.24.3)': + '@babel/preset-typescript@7.24.1(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.24.0 '@babel/helper-validator-option': 7.23.5 - '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-typescript': 7.24.1(@babel/core@7.24.3) + '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-typescript': 7.24.4(@babel/core@7.24.4) - '@babel/register@7.23.7(@babel/core@7.24.3)': + '@babel/register@7.23.7(@babel/core@7.24.4)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 @@ -9963,25 +9978,25 @@ snapshots: '@babel/regjsgen@0.8.0': {} - '@babel/runtime@7.24.1': + '@babel/runtime@7.24.4': dependencies: regenerator-runtime: 0.14.1 '@babel/template@7.24.0': dependencies: '@babel/code-frame': 7.24.2 - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.4 '@babel/types': 7.24.0 '@babel/traverse@7.24.1': dependencies: '@babel/code-frame': 7.24.2 - '@babel/generator': 7.24.1 + '@babel/generator': 7.24.4 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.4 '@babel/types': 7.24.0 debug: 4.3.4 globals: 11.12.0 @@ -10081,7 +10096,7 @@ snapshots: '@commitlint/types': 19.0.3 chalk: 5.3.0 cosmiconfig: 9.0.0(typescript@5.4.5) - cosmiconfig-typescript-loader: 5.0.0(@types/node@20.12.7)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5) + cosmiconfig-typescript-loader: 5.0.0(@types/node@20.12.7)(cosmiconfig@9.0.0)(typescript@5.4.5) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -10268,7 +10283,7 @@ snapshots: - supports-color - typescript - '@eslint-react/eslint-plugin@1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': + '@eslint-react/eslint-plugin@1.5.9(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-react/shared': 1.5.9(eslint@8.57.0)(typescript@5.4.5) '@eslint-react/tools': 1.5.9 @@ -10279,10 +10294,10 @@ snapshots: '@typescript-eslint/types': 7.7.0 '@typescript-eslint/utils': 7.7.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 - eslint-plugin-react-core: 1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - eslint-plugin-react-dom: 1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - eslint-plugin-react-hooks-extra: 1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - eslint-plugin-react-naming-convention: 1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + eslint-plugin-react-core: 1.5.9(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5) + eslint-plugin-react-dom: 1.5.9(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5) + eslint-plugin-react-hooks-extra: 1.5.9(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5) + eslint-plugin-react-naming-convention: 1.5.9(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5) typescript: 5.4.5 transitivePeerDependencies: - supports-color @@ -10356,7 +10371,7 @@ snapshots: '@fal-works/esbuild-plugin-global-externals@2.1.2': {} - '@grpc/grpc-js@1.10.4': + '@grpc/grpc-js@1.10.6': dependencies: '@grpc/proto-loader': 0.7.12 '@js-sdsl/ordered-map': 4.4.2 @@ -10504,7 +10519,7 @@ snapshots: '@mdx-js/react@3.0.1(@types/react@18.2.79)(react@18.2.0)': dependencies: - '@types/mdx': 2.0.12 + '@types/mdx': 2.0.13 '@types/react': 18.2.79 react: 18.2.0 @@ -10563,67 +10578,73 @@ snapshots: '@octokit/auth-token@4.0.0': {} - '@octokit/core@5.1.0': + '@octokit/core@5.2.0': dependencies: '@octokit/auth-token': 4.0.0 - '@octokit/graphql': 7.0.2 - '@octokit/request': 8.2.0 - '@octokit/request-error': 5.0.1 - '@octokit/types': 12.6.0 + '@octokit/graphql': 7.1.0 + '@octokit/request': 8.4.0 + '@octokit/request-error': 5.1.0 + '@octokit/types': 13.4.1 before-after-hook: 2.2.3 universal-user-agent: 6.0.1 - '@octokit/endpoint@9.0.4': + '@octokit/endpoint@9.0.5': dependencies: - '@octokit/types': 12.6.0 + '@octokit/types': 13.4.1 universal-user-agent: 6.0.1 - '@octokit/graphql@7.0.2': + '@octokit/graphql@7.1.0': dependencies: - '@octokit/request': 8.2.0 - '@octokit/types': 12.6.0 + '@octokit/request': 8.4.0 + '@octokit/types': 13.4.1 universal-user-agent: 6.0.1 '@octokit/openapi-types@20.0.0': {} - '@octokit/plugin-paginate-rest@9.2.1(@octokit/core@5.1.0)': + '@octokit/openapi-types@22.1.0': {} + + '@octokit/plugin-paginate-rest@9.2.1(@octokit/core@5.2.0)': dependencies: - '@octokit/core': 5.1.0 + '@octokit/core': 5.2.0 '@octokit/types': 12.6.0 - '@octokit/plugin-request-log@4.0.1(@octokit/core@5.1.0)': + '@octokit/plugin-request-log@4.0.1(@octokit/core@5.2.0)': dependencies: - '@octokit/core': 5.1.0 + '@octokit/core': 5.2.0 - '@octokit/plugin-rest-endpoint-methods@10.4.1(@octokit/core@5.1.0)': + '@octokit/plugin-rest-endpoint-methods@10.4.1(@octokit/core@5.2.0)': dependencies: - '@octokit/core': 5.1.0 + '@octokit/core': 5.2.0 '@octokit/types': 12.6.0 - '@octokit/request-error@5.0.1': + '@octokit/request-error@5.1.0': dependencies: - '@octokit/types': 12.6.0 + '@octokit/types': 13.4.1 deprecation: 2.3.1 once: 1.4.0 - '@octokit/request@8.2.0': + '@octokit/request@8.4.0': dependencies: - '@octokit/endpoint': 9.0.4 - '@octokit/request-error': 5.0.1 - '@octokit/types': 12.6.0 + '@octokit/endpoint': 9.0.5 + '@octokit/request-error': 5.1.0 + '@octokit/types': 13.4.1 universal-user-agent: 6.0.1 '@octokit/rest@20.1.0': dependencies: - '@octokit/core': 5.1.0 - '@octokit/plugin-paginate-rest': 9.2.1(@octokit/core@5.1.0) - '@octokit/plugin-request-log': 4.0.1(@octokit/core@5.1.0) - '@octokit/plugin-rest-endpoint-methods': 10.4.1(@octokit/core@5.1.0) + '@octokit/core': 5.2.0 + '@octokit/plugin-paginate-rest': 9.2.1(@octokit/core@5.2.0) + '@octokit/plugin-request-log': 4.0.1(@octokit/core@5.2.0) + '@octokit/plugin-rest-endpoint-methods': 10.4.1(@octokit/core@5.2.0) '@octokit/types@12.6.0': dependencies: '@octokit/openapi-types': 20.0.0 + '@octokit/types@13.4.1': + dependencies: + '@octokit/openapi-types': 22.1.0 + '@pkgjs/parseargs@0.11.0': optional: true @@ -10670,59 +10691,57 @@ snapshots: '@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.79)(react@18.2.0)': dependencies: - '@babel/runtime': 7.24.1 - react: 18.2.0 - optionalDependencies: + '@babel/runtime': 7.24.4 '@types/react': 18.2.79 + react: 18.2.0 '@radix-ui/react-slot@1.0.2(@types/react@18.2.79)(react@18.2.0)': dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.79)(react@18.2.0) - react: 18.2.0 - optionalDependencies: '@types/react': 18.2.79 + react: 18.2.0 - '@rc-component/color-picker@1.5.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@rc-component/color-picker@1.5.3(react-dom@18.2.0)(react@18.2.0)': dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 '@ctrl/tinycolor': 3.6.1 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) '@rc-component/mini-decimal@1.1.0': dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 - '@rc-component/portal@1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@rc-component/portal@1.1.2(react-dom@18.2.0)(react@18.2.0)': dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@rc-component/trigger@1.18.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@rc-component/trigger@1.18.3(react-dom@18.2.0)(react@18.2.0)': dependencies: - '@babel/runtime': 7.24.1 - '@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@babel/runtime': 7.24.4 + '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@rc-component/trigger@2.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@rc-component/trigger@2.1.1(react-dom@18.2.0)(react@18.2.0)': dependencies: - '@babel/runtime': 7.24.1 - '@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@babel/runtime': 7.24.4 + '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -10732,7 +10751,7 @@ snapshots: '@react-dnd/shallowequal@4.0.2': {} - '@release-it-plugins/workspaces@4.2.0(release-it@17.2.0(typescript@5.4.5))': + '@release-it-plugins/workspaces@4.2.0(release-it@17.2.0)': dependencies: detect-indent: 6.1.0 detect-newline: 3.1.0 @@ -10743,7 +10762,7 @@ snapshots: walk-sync: 2.2.0 yaml: 2.4.1 - '@release-it/conventional-changelog@8.0.1(release-it@17.2.0(typescript@5.4.5))': + '@release-it/conventional-changelog@8.0.1(release-it@17.2.0)': dependencies: concat-stream: 2.0.0 conventional-changelog: 5.1.0 @@ -10751,69 +10770,69 @@ snapshots: release-it: 17.2.0(typescript@5.4.5) semver: 7.6.0 - '@rollup/pluginutils@5.1.0(rollup@4.13.2)': + '@rollup/pluginutils@5.1.0': dependencies: '@types/estree': 1.0.5 estree-walker: 2.0.2 picomatch: 2.3.1 - optionalDependencies: - rollup: 4.13.2 - '@rollup/rollup-android-arm-eabi@4.13.2': + '@rollup/rollup-android-arm-eabi@4.16.4': + optional: true + + '@rollup/rollup-android-arm64@4.16.4': optional: true - '@rollup/rollup-android-arm64@4.13.2': + '@rollup/rollup-darwin-arm64@4.16.4': optional: true - '@rollup/rollup-darwin-arm64@4.13.2': + '@rollup/rollup-darwin-x64@4.16.4': optional: true - '@rollup/rollup-darwin-x64@4.13.2': + '@rollup/rollup-linux-arm-gnueabihf@4.16.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.13.2': + '@rollup/rollup-linux-arm-musleabihf@4.16.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.13.2': + '@rollup/rollup-linux-arm64-gnu@4.16.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.13.2': + '@rollup/rollup-linux-arm64-musl@4.16.4': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.13.2': + '@rollup/rollup-linux-powerpc64le-gnu@4.16.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.13.2': + '@rollup/rollup-linux-riscv64-gnu@4.16.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.13.2': + '@rollup/rollup-linux-s390x-gnu@4.16.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.13.2': + '@rollup/rollup-linux-x64-gnu@4.16.4': optional: true - '@rollup/rollup-linux-x64-musl@4.13.2': + '@rollup/rollup-linux-x64-musl@4.16.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.13.2': + '@rollup/rollup-win32-arm64-msvc@4.16.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.13.2': + '@rollup/rollup-win32-ia32-msvc@4.16.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.13.2': + '@rollup/rollup-win32-x64-msvc@4.16.4': optional: true '@rushstack/node-core-library@4.0.2(@types/node@20.12.7)': dependencies: + '@types/node': 20.12.7 fs-extra: 7.0.1 import-lazy: 4.0.0 jju: 1.4.0 resolve: 1.22.8 semver: 7.5.4 z-schema: 5.0.5 - optionalDependencies: - '@types/node': 20.12.7 '@rushstack/rig-package@0.5.2': dependencies: @@ -10823,9 +10842,8 @@ snapshots: '@rushstack/terminal@0.10.0(@types/node@20.12.7)': dependencies: '@rushstack/node-core-library': 4.0.2(@types/node@20.12.7) - supports-color: 8.1.1 - optionalDependencies: '@types/node': 20.12.7 + supports-color: 8.1.1 '@rushstack/ts-command-line@4.19.1(@types/node@20.12.7)': dependencies: @@ -10857,9 +10875,9 @@ snapshots: memoizerific: 1.11.3 ts-dedent: 2.2.0 - '@storybook/addon-controls@8.0.9(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@storybook/addon-controls@8.0.9(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)': dependencies: - '@storybook/blocks': 8.0.9(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/blocks': 8.0.9(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0) lodash: 4.17.21 ts-dedent: 2.2.0 transitivePeerDependencies: @@ -10871,18 +10889,18 @@ snapshots: '@storybook/addon-docs@8.0.9': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@mdx-js/react': 3.0.1(@types/react@18.2.79)(react@18.2.0) - '@storybook/blocks': 8.0.9(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/blocks': 8.0.9(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0) '@storybook/client-logger': 8.0.9 - '@storybook/components': 8.0.9(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/components': 8.0.9(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0) '@storybook/csf-plugin': 8.0.9 '@storybook/csf-tools': 8.0.9 '@storybook/global': 5.0.0 '@storybook/node-logger': 8.0.9 '@storybook/preview-api': 8.0.9 - '@storybook/react-dom-shim': 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@storybook/theming': 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/react-dom-shim': 8.0.9(react-dom@18.2.0)(react@18.2.0) + '@storybook/theming': 8.0.9(react-dom@18.2.0)(react@18.2.0) '@storybook/types': 8.0.9 '@types/react': 18.2.79 fs-extra: 11.2.0 @@ -10895,11 +10913,11 @@ snapshots: - encoding - supports-color - '@storybook/addon-essentials@8.0.9(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@storybook/addon-essentials@8.0.9(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)': dependencies: '@storybook/addon-actions': 8.0.9 '@storybook/addon-backgrounds': 8.0.9 - '@storybook/addon-controls': 8.0.9(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/addon-controls': 8.0.9(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-docs': 8.0.9 '@storybook/addon-highlight': 8.0.9 '@storybook/addon-measure': 8.0.9 @@ -10907,7 +10925,7 @@ snapshots: '@storybook/addon-toolbars': 8.0.9 '@storybook/addon-viewport': 8.0.9 '@storybook/core-common': 8.0.9 - '@storybook/manager-api': 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/manager-api': 8.0.9(react-dom@18.2.0)(react@18.2.0) '@storybook/node-logger': 8.0.9 '@storybook/preview-api': 8.0.9 ts-dedent: 2.2.0 @@ -10922,11 +10940,11 @@ snapshots: dependencies: '@storybook/global': 5.0.0 - '@storybook/addon-interactions@8.0.9(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0))': + '@storybook/addon-interactions@8.0.9': dependencies: '@storybook/global': 5.0.0 '@storybook/instrumenter': 8.0.9 - '@storybook/test': 8.0.9(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)) + '@storybook/test': 8.0.9 '@storybook/types': 8.0.9 polished: 4.3.1 ts-dedent: 2.2.0 @@ -10941,9 +10959,8 @@ snapshots: dependencies: '@storybook/csf': 0.1.4 '@storybook/global': 5.0.0 - ts-dedent: 2.2.0 - optionalDependencies: react: 18.2.0 + ts-dedent: 2.2.0 '@storybook/addon-measure@8.0.9': dependencies: @@ -10955,10 +10972,10 @@ snapshots: '@storybook/global': 5.0.0 ts-dedent: 2.2.0 - '@storybook/addon-styling-webpack@1.0.0(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2))': + '@storybook/addon-styling-webpack@1.0.0(webpack@5.91.0)': dependencies: - '@storybook/node-logger': 8.0.5 - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) + '@storybook/node-logger': 8.0.9 + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) '@storybook/addon-toolbars@8.0.9': {} @@ -10966,27 +10983,27 @@ snapshots: dependencies: memoizerific: 1.11.3 - '@storybook/addon-webpack5-compiler-swc@1.0.2(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2))': + '@storybook/addon-webpack5-compiler-swc@1.0.2(webpack@5.91.0)': dependencies: - '@swc/core': 1.4.11 - swc-loader: 0.2.6(@swc/core@1.4.11)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) + '@swc/core': 1.5.0 + swc-loader: 0.2.6(@swc/core@1.5.0)(webpack@5.91.0) transitivePeerDependencies: - '@swc/helpers' - webpack - '@storybook/blocks@8.0.9(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@storybook/blocks@8.0.9(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)': dependencies: '@storybook/channels': 8.0.9 '@storybook/client-logger': 8.0.9 - '@storybook/components': 8.0.9(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/components': 8.0.9(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0) '@storybook/core-events': 8.0.9 '@storybook/csf': 0.1.4 '@storybook/docs-tools': 8.0.9 '@storybook/global': 5.0.0 - '@storybook/icons': 1.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@storybook/manager-api': 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/icons': 1.2.9(react-dom@18.2.0)(react@18.2.0) + '@storybook/manager-api': 8.0.9(react-dom@18.2.0)(react@18.2.0) '@storybook/preview-api': 8.0.9 - '@storybook/theming': 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/theming': 8.0.9(react-dom@18.2.0)(react@18.2.0) '@storybook/types': 8.0.9 '@types/lodash': 4.17.0 color-convert: 2.0.1 @@ -10995,14 +11012,13 @@ snapshots: markdown-to-jsx: 7.3.2(react@18.2.0) memoizerific: 1.11.3 polished: 4.3.1 - react-colorful: 5.6.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-colorful: 5.6.1(react-dom@18.2.0)(react@18.2.0) + react-dom: 18.2.0(react@18.2.0) telejson: 7.2.0 - tocbot: 4.25.0 + tocbot: 4.27.4 ts-dedent: 2.2.0 util-deprecate: 1.0.2 - optionalDependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) transitivePeerDependencies: - '@types/react' - encoding @@ -11017,7 +11033,7 @@ snapshots: '@types/ejs': 3.1.5 '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.20.2) browser-assert: 1.2.1 - ejs: 3.1.9 + ejs: 3.1.10 esbuild: 0.20.2 esbuild-plugin-alias: 0.2.1 express: 4.19.2 @@ -11028,7 +11044,7 @@ snapshots: - encoding - supports-color - '@storybook/builder-webpack5@8.0.9(@swc/core@1.4.11)(esbuild@0.20.2)(typescript@5.4.5)': + '@storybook/builder-webpack5@8.0.9(@swc/core@1.5.0)(esbuild@0.20.2)(typescript@5.4.5)': dependencies: '@storybook/channels': 8.0.9 '@storybook/client-logger': 8.0.9 @@ -11038,34 +11054,33 @@ snapshots: '@storybook/node-logger': 8.0.9 '@storybook/preview': 8.0.9 '@storybook/preview-api': 8.0.9 - '@types/node': 18.19.28 + '@types/node': 18.19.31 '@types/semver': 7.5.8 browser-assert: 1.2.1 case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.2.3 constants-browserify: 1.0.0 - css-loader: 6.10.0(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) + css-loader: 6.11.0(webpack@5.91.0) es-module-lexer: 1.5.0 express: 4.19.2 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.4.5)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.4.5)(webpack@5.91.0) fs-extra: 11.2.0 - html-webpack-plugin: 5.6.0(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) - magic-string: 0.30.8 + html-webpack-plugin: 5.6.0(webpack@5.91.0) + magic-string: 0.30.10 path-browserify: 1.0.1 process: 0.11.10 semver: 7.6.0 - style-loader: 3.3.4(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) - terser-webpack-plugin: 5.3.10(@swc/core@1.4.11)(esbuild@0.20.2)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) + style-loader: 3.3.4(webpack@5.91.0) + terser-webpack-plugin: 5.3.10(@swc/core@1.5.0)(esbuild@0.20.2)(webpack@5.91.0) ts-dedent: 2.2.0 + typescript: 5.4.5 url: 0.11.3 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) - webpack-dev-middleware: 6.1.3(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) + webpack-dev-middleware: 6.1.3(webpack@5.91.0) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.5.0 - optionalDependencies: - typescript: 5.4.5 transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -11083,15 +11098,15 @@ snapshots: telejson: 7.2.0 tiny-invariant: 1.3.3 - '@storybook/cli@8.0.9(@babel/preset-env@7.24.3(@babel/core@7.24.3))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@storybook/cli@8.0.9(react-dom@18.2.0)(react@18.2.0)': dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/types': 7.24.0 '@ndelangen/get-tarball': 3.0.9 '@storybook/codemod': 8.0.9 '@storybook/core-common': 8.0.9 '@storybook/core-events': 8.0.9 - '@storybook/core-server': 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/core-server': 8.0.9(react-dom@18.2.0)(react@18.2.0) '@storybook/csf-tools': 8.0.9 '@storybook/node-logger': 8.0.9 '@storybook/telemetry': 8.0.9 @@ -11103,14 +11118,14 @@ snapshots: commander: 6.2.1 cross-spawn: 7.0.3 detect-indent: 6.1.0 - envinfo: 7.11.1 + envinfo: 7.12.0 execa: 5.1.1 find-up: 5.0.0 fs-extra: 11.2.0 get-npm-tarball-url: 2.1.0 giget: 1.2.3 globby: 11.1.0 - jscodeshift: 0.15.2(@babel/preset-env@7.24.3(@babel/core@7.24.3)) + jscodeshift: 0.15.2(@babel/preset-env@7.24.4) leven: 3.1.0 ora: 5.4.1 prettier: 3.2.5 @@ -11136,8 +11151,8 @@ snapshots: '@storybook/codemod@8.0.9': dependencies: - '@babel/core': 7.24.3 - '@babel/preset-env': 7.24.3(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/preset-env': 7.24.4(@babel/core@7.24.4) '@babel/types': 7.24.0 '@storybook/csf': 0.1.4 '@storybook/csf-tools': 8.0.9 @@ -11146,7 +11161,7 @@ snapshots: '@types/cross-spawn': 6.0.6 cross-spawn: 7.0.3 globby: 11.1.0 - jscodeshift: 0.15.2(@babel/preset-env@7.24.3(@babel/core@7.24.3)) + jscodeshift: 0.15.2(@babel/preset-env@7.24.4) lodash: 4.17.21 prettier: 3.2.5 recast: 0.23.6 @@ -11154,14 +11169,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@storybook/components@8.0.9(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@storybook/components@8.0.9(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)': dependencies: '@radix-ui/react-slot': 1.0.2(@types/react@18.2.79)(react@18.2.0) '@storybook/client-logger': 8.0.9 '@storybook/csf': 0.1.4 '@storybook/global': 5.0.0 - '@storybook/icons': 1.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - '@storybook/theming': 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/icons': 1.2.9(react-dom@18.2.0)(react@18.2.0) + '@storybook/theming': 8.0.9(react-dom@18.2.0)(react@18.2.0) '@storybook/types': 8.0.9 memoizerific: 1.11.3 react: 18.2.0 @@ -11208,10 +11223,10 @@ snapshots: dependencies: ts-dedent: 2.2.0 - '@storybook/core-server@8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@storybook/core-server@8.0.9(react-dom@18.2.0)(react@18.2.0)': dependencies: '@aw-web-design/x-default-browser': 1.4.126 - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@discoveryjs/json-ext': 0.5.7 '@storybook/builder-manager': 8.0.9 '@storybook/channels': 8.0.9 @@ -11222,13 +11237,13 @@ snapshots: '@storybook/docs-mdx': 3.0.0 '@storybook/global': 5.0.0 '@storybook/manager': 8.0.9 - '@storybook/manager-api': 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/manager-api': 8.0.9(react-dom@18.2.0)(react@18.2.0) '@storybook/node-logger': 8.0.9 '@storybook/preview-api': 8.0.9 '@storybook/telemetry': 8.0.9 '@storybook/types': 8.0.9 '@types/detect-port': 1.3.5 - '@types/node': 18.19.28 + '@types/node': 18.19.31 '@types/pretty-hrtime': 1.0.3 '@types/semver': 7.5.8 better-opn: 3.0.2 @@ -11266,7 +11281,7 @@ snapshots: '@storybook/core-common': 8.0.9 '@storybook/node-logger': 8.0.9 '@storybook/types': 8.0.9 - '@types/node': 18.19.28 + '@types/node': 18.19.31 ts-dedent: 2.2.0 transitivePeerDependencies: - encoding @@ -11281,8 +11296,8 @@ snapshots: '@storybook/csf-tools@8.0.9': dependencies: - '@babel/generator': 7.24.1 - '@babel/parser': 7.24.1 + '@babel/generator': 7.24.4 + '@babel/parser': 7.24.4 '@babel/traverse': 7.24.1 '@babel/types': 7.24.0 '@storybook/csf': 0.1.4 @@ -11315,7 +11330,7 @@ snapshots: '@storybook/global@5.0.0': {} - '@storybook/icons@1.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@storybook/icons@1.2.9(react-dom@18.2.0)(react@18.2.0)': dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -11327,19 +11342,19 @@ snapshots: '@storybook/core-events': 8.0.9 '@storybook/global': 5.0.0 '@storybook/preview-api': 8.0.9 - '@vitest/utils': 1.4.0 + '@vitest/utils': 1.5.1 util: 0.12.5 - '@storybook/manager-api@8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@storybook/manager-api@8.0.9(react-dom@18.2.0)(react@18.2.0)': dependencies: '@storybook/channels': 8.0.9 '@storybook/client-logger': 8.0.9 '@storybook/core-events': 8.0.9 '@storybook/csf': 0.1.4 '@storybook/global': 5.0.0 - '@storybook/icons': 1.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/icons': 1.2.9(react-dom@18.2.0)(react@18.2.0) '@storybook/router': 8.0.9 - '@storybook/theming': 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/theming': 8.0.9(react-dom@18.2.0)(react@18.2.0) '@storybook/types': 8.0.9 dequal: 2.0.3 lodash: 4.17.21 @@ -11353,31 +11368,28 @@ snapshots: '@storybook/manager@8.0.9': {} - '@storybook/node-logger@8.0.5': {} - '@storybook/node-logger@8.0.9': {} - '@storybook/preset-react-webpack@8.0.9(@swc/core@1.4.11)(esbuild@0.20.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.4.5)': + '@storybook/preset-react-webpack@8.0.9(@swc/core@1.5.0)(esbuild@0.20.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.5)': dependencies: '@storybook/core-webpack': 8.0.9 '@storybook/docs-tools': 8.0.9 '@storybook/node-logger': 8.0.9 - '@storybook/react': 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.4.5) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.4.5)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) - '@types/node': 18.19.28 + '@storybook/react': 8.0.9(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.5) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.4.5)(webpack@5.91.0) + '@types/node': 18.19.31 '@types/semver': 7.5.8 find-up: 5.0.0 fs-extra: 11.2.0 - magic-string: 0.30.8 + magic-string: 0.30.10 react: 18.2.0 react-docgen: 7.0.3 react-dom: 18.2.0(react@18.2.0) resolve: 1.22.8 semver: 7.6.0 tsconfig-paths: 4.2.0 - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) - optionalDependencies: typescript: 5.4.5 + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) transitivePeerDependencies: - '@swc/core' - encoding @@ -11394,18 +11406,18 @@ snapshots: '@storybook/csf': 0.1.4 '@storybook/global': 5.0.0 '@storybook/types': 8.0.9 - '@types/qs': 6.9.14 + '@types/qs': 6.9.15 dequal: 2.0.3 lodash: 4.17.21 memoizerific: 1.11.3 - qs: 6.12.0 + qs: 6.12.1 tiny-invariant: 1.3.3 ts-dedent: 2.2.0 util-deprecate: 1.0.2 '@storybook/preview@8.0.9': {} - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.4.5)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.4.5)(webpack@5.91.0)': dependencies: debug: 4.3.4 endent: 2.1.0 @@ -11415,24 +11427,23 @@ snapshots: react-docgen-typescript: 2.2.2(typescript@5.4.5) tslib: 2.6.2 typescript: 5.4.5 - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) transitivePeerDependencies: - supports-color - '@storybook/react-dom-shim@8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@storybook/react-dom-shim@8.0.9(react-dom@18.2.0)(react@18.2.0)': dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@storybook/react-webpack5@8.0.9(@swc/core@1.4.11)(esbuild@0.20.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.4.5)': + '@storybook/react-webpack5@8.0.9(@swc/core@1.5.0)(esbuild@0.20.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.5)': dependencies: - '@storybook/builder-webpack5': 8.0.9(@swc/core@1.4.11)(esbuild@0.20.2)(typescript@5.4.5) - '@storybook/preset-react-webpack': 8.0.9(@swc/core@1.4.11)(esbuild@0.20.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.4.5) - '@storybook/react': 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.4.5) - '@types/node': 18.19.28 + '@storybook/builder-webpack5': 8.0.9(@swc/core@1.5.0)(esbuild@0.20.2)(typescript@5.4.5) + '@storybook/preset-react-webpack': 8.0.9(@swc/core@1.5.0)(esbuild@0.20.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.5) + '@storybook/react': 8.0.9(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.5) + '@types/node': 18.19.31 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: - '@rspack/core' @@ -11443,17 +11454,17 @@ snapshots: - uglify-js - webpack-cli - '@storybook/react@8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.4.5)': + '@storybook/react@8.0.9(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.5)': dependencies: '@storybook/client-logger': 8.0.9 '@storybook/docs-tools': 8.0.9 '@storybook/global': 5.0.0 '@storybook/preview-api': 8.0.9 - '@storybook/react-dom-shim': 8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/react-dom-shim': 8.0.9(react-dom@18.2.0)(react@18.2.0) '@storybook/types': 8.0.9 '@types/escodegen': 0.0.6 '@types/estree': 0.0.51 - '@types/node': 18.19.28 + '@types/node': 18.19.31 acorn: 7.4.1 acorn-jsx: 5.3.2(acorn@7.4.1) acorn-walk: 7.2.0 @@ -11463,13 +11474,12 @@ snapshots: prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-element-to-jsx-string: 15.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react-element-to-jsx-string: 15.0.0(react-dom@18.2.0)(react@18.2.0) semver: 7.6.0 ts-dedent: 2.2.0 type-fest: 2.19.0 - util-deprecate: 1.0.2 - optionalDependencies: typescript: 5.4.5 + util-deprecate: 1.0.2 transitivePeerDependencies: - encoding - supports-color @@ -11478,7 +11488,7 @@ snapshots: dependencies: '@storybook/client-logger': 8.0.9 memoizerific: 1.11.3 - qs: 6.12.0 + qs: 6.12.1 '@storybook/telemetry@8.0.9': dependencies: @@ -11494,17 +11504,17 @@ snapshots: - encoding - supports-color - '@storybook/test@8.0.9(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0))': + '@storybook/test@8.0.9': dependencies: '@storybook/client-logger': 8.0.9 '@storybook/core-events': 8.0.9 '@storybook/instrumenter': 8.0.9 '@storybook/preview-api': 8.0.9 '@testing-library/dom': 9.3.4 - '@testing-library/jest-dom': 6.4.2(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)) + '@testing-library/jest-dom': 6.4.2 '@testing-library/user-event': 14.5.2(@testing-library/dom@9.3.4) '@vitest/expect': 1.3.1 - '@vitest/spy': 1.4.0 + '@vitest/spy': 1.5.1 util: 0.12.5 transitivePeerDependencies: - '@jest/globals' @@ -11513,13 +11523,12 @@ snapshots: - jest - vitest - '@storybook/theming@8.0.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@storybook/theming@8.0.9(react-dom@18.2.0)(react@18.2.0)': dependencies: '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) '@storybook/client-logger': 8.0.9 '@storybook/global': 5.0.0 memoizerific: 1.11.3 - optionalDependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -11577,51 +11586,51 @@ snapshots: - supports-color - typescript - '@swc/core-darwin-arm64@1.4.11': + '@swc/core-darwin-arm64@1.5.0': optional: true - '@swc/core-darwin-x64@1.4.11': + '@swc/core-darwin-x64@1.5.0': optional: true - '@swc/core-linux-arm-gnueabihf@1.4.11': + '@swc/core-linux-arm-gnueabihf@1.5.0': optional: true - '@swc/core-linux-arm64-gnu@1.4.11': + '@swc/core-linux-arm64-gnu@1.5.0': optional: true - '@swc/core-linux-arm64-musl@1.4.11': + '@swc/core-linux-arm64-musl@1.5.0': optional: true - '@swc/core-linux-x64-gnu@1.4.11': + '@swc/core-linux-x64-gnu@1.5.0': optional: true - '@swc/core-linux-x64-musl@1.4.11': + '@swc/core-linux-x64-musl@1.5.0': optional: true - '@swc/core-win32-arm64-msvc@1.4.11': + '@swc/core-win32-arm64-msvc@1.5.0': optional: true - '@swc/core-win32-ia32-msvc@1.4.11': + '@swc/core-win32-ia32-msvc@1.5.0': optional: true - '@swc/core-win32-x64-msvc@1.4.11': + '@swc/core-win32-x64-msvc@1.5.0': optional: true - '@swc/core@1.4.11': + '@swc/core@1.5.0': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.6 optionalDependencies: - '@swc/core-darwin-arm64': 1.4.11 - '@swc/core-darwin-x64': 1.4.11 - '@swc/core-linux-arm-gnueabihf': 1.4.11 - '@swc/core-linux-arm64-gnu': 1.4.11 - '@swc/core-linux-arm64-musl': 1.4.11 - '@swc/core-linux-x64-gnu': 1.4.11 - '@swc/core-linux-x64-musl': 1.4.11 - '@swc/core-win32-arm64-msvc': 1.4.11 - '@swc/core-win32-ia32-msvc': 1.4.11 - '@swc/core-win32-x64-msvc': 1.4.11 + '@swc/core-darwin-arm64': 1.5.0 + '@swc/core-darwin-x64': 1.5.0 + '@swc/core-linux-arm-gnueabihf': 1.5.0 + '@swc/core-linux-arm64-gnu': 1.5.0 + '@swc/core-linux-arm64-musl': 1.5.0 + '@swc/core-linux-x64-gnu': 1.5.0 + '@swc/core-linux-x64-musl': 1.5.0 + '@swc/core-win32-arm64-msvc': 1.5.0 + '@swc/core-win32-ia32-msvc': 1.5.0 + '@swc/core-win32-x64-msvc': 1.5.0 '@swc/counter@0.1.3': {} @@ -11636,7 +11645,7 @@ snapshots: '@testing-library/dom@9.3.4': dependencies: '@babel/code-frame': 7.24.2 - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 '@types/aria-query': 5.0.4 aria-query: 5.1.3 chalk: 4.1.2 @@ -11644,31 +11653,28 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.4.2(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0))': + '@testing-library/jest-dom@6.4.2': dependencies: '@adobe/css-tools': 4.3.3 - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 aria-query: 5.3.0 chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 lodash: 4.17.21 redent: 3.0.0 - optionalDependencies: - vitest: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) - '@testing-library/react-hooks@8.0.1(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@testing-library/react-hooks@8.0.1(@types/react@18.2.79)(react-dom@18.2.0)(react@18.2.0)': dependencies: - '@babel/runtime': 7.24.1 - react: 18.2.0 - react-error-boundary: 3.1.4(react@18.2.0) - optionalDependencies: + '@babel/runtime': 7.24.4 '@types/react': 18.2.79 + react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + react-error-boundary: 3.1.4(react@18.2.0) - '@testing-library/react@14.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@testing-library/react@14.3.1(react-dom@18.2.0)(react@18.2.0)': dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 '@testing-library/dom': 9.3.4 '@types/react-dom': 18.2.25 react: 18.2.0 @@ -11686,7 +11692,7 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.4 '@babel/types': 7.24.0 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 @@ -11698,7 +11704,7 @@ snapshots: '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.4 '@babel/types': 7.24.0 '@types/babel__traverse@7.20.5': @@ -11738,7 +11744,7 @@ snapshots: '@types/eslint-scope@3.7.7': dependencies: - '@types/eslint': 8.56.7 + '@types/eslint': 8.56.10 '@types/estree': 1.0.5 '@types/eslint@8.56.10': @@ -11746,28 +11752,23 @@ snapshots: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 - '@types/eslint@8.56.7': - dependencies: - '@types/estree': 1.0.5 - '@types/json-schema': 7.0.15 - '@types/estree@0.0.51': {} '@types/estree@1.0.5': {} - '@types/express-serve-static-core@4.17.43': + '@types/express-serve-static-core@4.19.0': dependencies: '@types/node': 20.12.7 - '@types/qs': 6.9.14 + '@types/qs': 6.9.15 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 '@types/express@4.17.21': dependencies: '@types/body-parser': 1.19.5 - '@types/express-serve-static-core': 4.17.43 - '@types/qs': 6.9.14 - '@types/serve-static': 1.15.5 + '@types/express-serve-static-core': 4.19.0 + '@types/qs': 6.9.15 + '@types/serve-static': 1.15.7 '@types/hast@3.0.4': dependencies: @@ -11797,17 +11798,13 @@ snapshots: dependencies: '@types/unist': 2.0.10 - '@types/mdx@2.0.12': {} + '@types/mdx@2.0.13': {} '@types/mime@1.3.5': {} - '@types/mime@4.0.0': - dependencies: - mime: 4.0.1 - '@types/minimatch@3.0.5': {} - '@types/node@18.19.28': + '@types/node@18.19.31': dependencies: undici-types: 5.26.5 @@ -11827,7 +11824,7 @@ snapshots: '@types/prop-types@15.7.12': {} - '@types/qs@6.9.14': {} + '@types/qs@6.9.15': {} '@types/range-parser@1.2.7': {} @@ -11852,7 +11849,7 @@ snapshots: '@types/sass@1.45.0': dependencies: - sass: 1.72.0 + sass: 1.75.0 '@types/semver@7.5.8': {} @@ -11861,11 +11858,11 @@ snapshots: '@types/mime': 1.3.5 '@types/node': 20.12.7 - '@types/serve-static@1.15.5': + '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/mime': 4.0.0 '@types/node': 20.12.7 + '@types/send': 0.17.4 '@types/stylus@0.48.42': dependencies: @@ -11877,7 +11874,7 @@ snapshots: '@types/uuid@9.0.8': {} - '@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': + '@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/regexpp': 4.10.0 '@typescript-eslint/parser': 7.7.1(eslint@8.57.0)(typescript@5.4.5) @@ -11892,7 +11889,6 @@ snapshots: natural-compare: 1.4.0 semver: 7.6.0 ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: - supports-color @@ -11905,7 +11901,6 @@ snapshots: '@typescript-eslint/visitor-keys': 7.7.1 debug: 4.3.4 eslint: 8.57.0 - optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: - supports-color @@ -11915,11 +11910,6 @@ snapshots: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - '@typescript-eslint/scope-manager@7.6.0': - dependencies: - '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/visitor-keys': 7.6.0 - '@typescript-eslint/scope-manager@7.7.0': dependencies: '@typescript-eslint/types': 7.7.0 @@ -11937,7 +11927,6 @@ snapshots: debug: 4.3.4 eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: - supports-color @@ -11949,15 +11938,12 @@ snapshots: debug: 4.3.4 eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: - supports-color '@typescript-eslint/types@6.21.0': {} - '@typescript-eslint/types@7.6.0': {} - '@typescript-eslint/types@7.7.0': {} '@typescript-eslint/types@7.7.1': {} @@ -11972,22 +11958,6 @@ snapshots: minimatch: 9.0.3 semver: 7.6.0 ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/typescript-estree@7.6.0(typescript@5.4.5)': - dependencies: - '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/visitor-keys': 7.6.0 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.4 - semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: - supports-color @@ -12002,7 +11972,6 @@ snapshots: minimatch: 9.0.4 semver: 7.6.0 ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: - supports-color @@ -12017,7 +11986,6 @@ snapshots: minimatch: 9.0.4 semver: 7.6.0 ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: typescript: 5.4.5 transitivePeerDependencies: - supports-color @@ -12036,20 +12004,6 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@7.6.0(eslint@8.57.0)(typescript@5.4.5)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 7.6.0 - '@typescript-eslint/types': 7.6.0 - '@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.5) - eslint: 8.57.0 - semver: 7.6.0 - transitivePeerDependencies: - - supports-color - - typescript - '@typescript-eslint/utils@7.7.0(eslint@8.57.0)(typescript@5.4.5)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) @@ -12083,11 +12037,6 @@ snapshots: '@typescript-eslint/types': 6.21.0 eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@7.6.0': - dependencies: - '@typescript-eslint/types': 7.6.0 - eslint-visitor-keys: 3.4.3 - '@typescript-eslint/visitor-keys@7.7.0': dependencies: '@typescript-eslint/types': 7.7.0 @@ -12102,28 +12051,28 @@ snapshots: '@univerjs/icons-svg@0.1.44': {} - '@univerjs/icons@0.1.44(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@univerjs/icons@0.1.44(react-dom@18.2.0)(react@18.2.0)': dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@univerjs/protocol@0.1.19(@grpc/grpc-js@1.10.4)(rxjs@7.8.1)': + '@univerjs/protocol@0.1.20(@grpc/grpc-js@1.10.6)(rxjs@7.8.1)': dependencies: - '@grpc/grpc-js': 1.10.4 + '@grpc/grpc-js': 1.10.6 rxjs: 7.8.1 - '@vitejs/plugin-react@4.2.1(vite@5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0))': + '@vitejs/plugin-react@4.2.1(vite@5.2.10)': dependencies: - '@babel/core': 7.24.3 - '@babel/plugin-transform-react-jsx-self': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/plugin-transform-react-jsx-self': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.4) '@types/babel__core': 7.20.5 react-refresh: 0.14.0 - vite: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + vite: 5.2.10(@types/node@20.12.7)(less@4.2.0) transitivePeerDependencies: - supports-color - '@vitest/coverage-istanbul@1.5.0(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0))': + '@vitest/coverage-istanbul@1.5.1(vitest@1.5.1)': dependencies: debug: 4.3.4 istanbul-lib-coverage: 3.2.2 @@ -12131,10 +12080,10 @@ snapshots: istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.4 istanbul-reports: 3.1.7 - magicast: 0.3.3 + magicast: 0.3.4 picocolors: 1.0.0 test-exclude: 6.0.0 - vitest: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + vitest: 1.5.1(@types/node@20.12.7)(happy-dom@13.3.8) transitivePeerDependencies: - supports-color @@ -12144,21 +12093,21 @@ snapshots: '@vitest/utils': 1.3.1 chai: 4.4.1 - '@vitest/expect@1.5.0': + '@vitest/expect@1.5.1': dependencies: - '@vitest/spy': 1.5.0 - '@vitest/utils': 1.5.0 + '@vitest/spy': 1.5.1 + '@vitest/utils': 1.5.1 chai: 4.4.1 - '@vitest/runner@1.5.0': + '@vitest/runner@1.5.1': dependencies: - '@vitest/utils': 1.5.0 + '@vitest/utils': 1.5.1 p-limit: 5.0.0 pathe: 1.1.2 - '@vitest/snapshot@1.5.0': + '@vitest/snapshot@1.5.1': dependencies: - magic-string: 0.30.8 + magic-string: 0.30.10 pathe: 1.1.2 pretty-format: 29.7.0 @@ -12166,11 +12115,7 @@ snapshots: dependencies: tinyspy: 2.2.1 - '@vitest/spy@1.4.0': - dependencies: - tinyspy: 2.2.1 - - '@vitest/spy@1.5.0': + '@vitest/spy@1.5.1': dependencies: tinyspy: 2.2.1 @@ -12181,14 +12126,7 @@ snapshots: loupe: 2.3.7 pretty-format: 29.7.0 - '@vitest/utils@1.4.0': - dependencies: - diff-sequences: 29.6.3 - estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 - - '@vitest/utils@1.5.0': + '@vitest/utils@1.5.1': dependencies: diff-sequences: 29.6.3 estree-walker: 3.0.3 @@ -12208,72 +12146,71 @@ snapshots: '@volar/language-core': 1.11.1 path-browserify: 1.0.1 - '@vue/compiler-core@3.4.21': + '@vue/compiler-core@3.4.25': dependencies: - '@babel/parser': 7.24.1 - '@vue/shared': 3.4.21 + '@babel/parser': 7.24.4 + '@vue/shared': 3.4.25 entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.2.0 - '@vue/compiler-dom@3.4.21': + '@vue/compiler-dom@3.4.25': dependencies: - '@vue/compiler-core': 3.4.21 - '@vue/shared': 3.4.21 + '@vue/compiler-core': 3.4.25 + '@vue/shared': 3.4.25 - '@vue/compiler-sfc@3.4.21': + '@vue/compiler-sfc@3.4.25': dependencies: - '@babel/parser': 7.24.1 - '@vue/compiler-core': 3.4.21 - '@vue/compiler-dom': 3.4.21 - '@vue/compiler-ssr': 3.4.21 - '@vue/shared': 3.4.21 + '@babel/parser': 7.24.4 + '@vue/compiler-core': 3.4.25 + '@vue/compiler-dom': 3.4.25 + '@vue/compiler-ssr': 3.4.25 + '@vue/shared': 3.4.25 estree-walker: 2.0.2 - magic-string: 0.30.8 + magic-string: 0.30.10 postcss: 8.4.38 source-map-js: 1.2.0 - '@vue/compiler-ssr@3.4.21': + '@vue/compiler-ssr@3.4.25': dependencies: - '@vue/compiler-dom': 3.4.21 - '@vue/shared': 3.4.21 + '@vue/compiler-dom': 3.4.25 + '@vue/shared': 3.4.25 '@vue/language-core@1.8.27(typescript@5.4.5)': dependencies: '@volar/language-core': 1.11.1 '@volar/source-map': 1.11.1 - '@vue/compiler-dom': 3.4.21 - '@vue/shared': 3.4.21 + '@vue/compiler-dom': 3.4.25 + '@vue/shared': 3.4.25 computeds: 0.0.1 minimatch: 9.0.4 muggle-string: 0.3.1 path-browserify: 1.0.1 - vue-template-compiler: 2.7.16 - optionalDependencies: typescript: 5.4.5 + vue-template-compiler: 2.7.16 - '@vue/reactivity@3.4.21': + '@vue/reactivity@3.4.25': dependencies: - '@vue/shared': 3.4.21 + '@vue/shared': 3.4.25 - '@vue/runtime-core@3.4.21': + '@vue/runtime-core@3.4.25': dependencies: - '@vue/reactivity': 3.4.21 - '@vue/shared': 3.4.21 + '@vue/reactivity': 3.4.25 + '@vue/shared': 3.4.25 - '@vue/runtime-dom@3.4.21': + '@vue/runtime-dom@3.4.25': dependencies: - '@vue/runtime-core': 3.4.21 - '@vue/shared': 3.4.21 + '@vue/runtime-core': 3.4.25 + '@vue/shared': 3.4.25 csstype: 3.1.3 - '@vue/server-renderer@3.4.21(vue@3.4.21(typescript@5.4.5))': + '@vue/server-renderer@3.4.25(vue@3.4.25)': dependencies: - '@vue/compiler-ssr': 3.4.21 - '@vue/shared': 3.4.21 - vue: 3.4.21(typescript@5.4.5) + '@vue/compiler-ssr': 3.4.25 + '@vue/shared': 3.4.25 + vue: 3.4.25(typescript@5.4.5) - '@vue/shared@3.4.21': {} + '@vue/shared@3.4.25': {} '@webassemblyjs/ast@1.12.1': dependencies: @@ -12420,7 +12357,7 @@ snapshots: indent-string: 4.0.0 ajv-formats@2.1.1(ajv@8.12.0): - optionalDependencies: + dependencies: ajv: 8.12.0 ajv-keywords@3.5.2(ajv@6.12.6): @@ -12609,31 +12546,31 @@ snapshots: dependencies: possible-typed-array-names: 1.0.0 - babel-core@7.0.0-bridge.0(@babel/core@7.24.3): + babel-core@7.0.0-bridge.0(@babel/core@7.24.4): dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 - babel-plugin-polyfill-corejs2@0.4.10(@babel/core@7.24.3): + babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.24.4): dependencies: - '@babel/compat-data': 7.24.1 - '@babel/core': 7.24.3 - '@babel/helper-define-polyfill-provider': 0.6.1(@babel/core@7.24.3) + '@babel/compat-data': 7.24.4 + '@babel/core': 7.24.4 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.4) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.24.3): + babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.24.4): dependencies: - '@babel/core': 7.24.3 - '@babel/helper-define-polyfill-provider': 0.6.1(@babel/core@7.24.3) - core-js-compat: 3.36.1 + '@babel/core': 7.24.4 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.4) + core-js-compat: 3.37.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.1(@babel/core@7.24.3): + babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.24.4): dependencies: - '@babel/core': 7.24.3 - '@babel/helper-define-polyfill-provider': 0.6.1(@babel/core@7.24.3) + '@babel/core': 7.24.4 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.4) transitivePeerDependencies: - supports-color @@ -12684,7 +12621,7 @@ snapshots: dependencies: ansi-align: 3.0.1 camelcase: 7.0.1 - chalk: 5.3.0 + chalk: 5.0.1 cli-boxes: 3.0.0 string-width: 5.1.2 type-fest: 2.19.0 @@ -12727,8 +12664,8 @@ snapshots: browserslist@4.23.0: dependencies: - caniuse-lite: 1.0.30001603 - electron-to-chromium: 1.4.722 + caniuse-lite: 1.0.30001612 + electron-to-chromium: 1.4.747 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.23.0) @@ -12780,7 +12717,7 @@ snapshots: camelcase@7.0.1: {} - caniuse-lite@1.0.30001603: {} + caniuse-lite@1.0.30001612: {} case-sensitive-paths-webpack-plugin@2.4.0: {} @@ -13001,6 +12938,8 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 + confbox@0.1.7: {} + config-chain@1.1.13: dependencies: ini: 1.3.8 @@ -13116,13 +13055,13 @@ snapshots: dependencies: is-what: 3.14.1 - core-js-compat@3.36.1: + core-js-compat@3.37.0: dependencies: browserslist: 4.23.0 core-util-is@1.0.3: {} - cosmiconfig-typescript-loader@5.0.0(@types/node@20.12.7)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5): + cosmiconfig-typescript-loader@5.0.0(@types/node@20.12.7)(cosmiconfig@9.0.0)(typescript@5.4.5): dependencies: '@types/node': 20.12.7 cosmiconfig: 9.0.0(typescript@5.4.5) @@ -13143,7 +13082,6 @@ snapshots: import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 - optionalDependencies: typescript: 5.4.5 cross-spawn@7.0.3: @@ -13158,18 +13096,17 @@ snapshots: dependencies: type-fest: 1.4.0 - css-loader@6.10.0(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)): + css-loader@6.11.0(webpack@5.91.0): dependencies: icss-utils: 5.1.0(postcss@8.4.38) postcss: 8.4.38 - postcss-modules-extract-imports: 3.0.0(postcss@8.4.38) - postcss-modules-local-by-default: 4.0.4(postcss@8.4.38) - postcss-modules-scope: 3.1.1(postcss@8.4.38) + postcss-modules-extract-imports: 3.1.0(postcss@8.4.38) + postcss-modules-local-by-default: 4.0.5(postcss@8.4.38) + postcss-modules-scope: 3.2.0(postcss@8.4.38) postcss-modules-values: 4.0.0(postcss@8.4.38) postcss-value-parser: 4.2.0 semver: 7.6.0 - optionalDependencies: - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) css-select@4.3.0: dependencies: @@ -13393,7 +13330,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 csstype: 3.1.3 dom-serializer@1.4.1: @@ -13442,11 +13379,11 @@ snapshots: ee-first@1.1.1: {} - ejs@3.1.9: + ejs@3.1.10: dependencies: jake: 10.8.7 - electron-to-chromium@1.4.722: {} + electron-to-chromium@1.4.747: {} emoji-regex@10.3.0: {} @@ -13479,7 +13416,7 @@ snapshots: env-paths@2.2.1: {} - envinfo@7.11.1: {} + envinfo@7.12.0: {} errno@0.1.8: dependencies: @@ -13662,13 +13599,11 @@ snapshots: fs-extra: 10.1.0 globby: 11.1.0 - esbuild-plugin-vue3@0.4.2(sass@1.72.0)(vue@3.4.21(typescript@5.4.5)): + esbuild-plugin-vue3@0.4.2(vue@3.4.25): dependencies: esbuild: 0.14.54 typescript: 4.9.5 - vue: 3.4.21(typescript@5.4.5) - optionalDependencies: - sass: 1.72.0 + vue: 3.4.25(typescript@5.4.5) esbuild-register@3.5.0(esbuild@0.20.2): dependencies: @@ -13834,7 +13769,7 @@ snapshots: eslint-plugin-import-x@0.5.0(eslint@8.57.0)(typescript@5.4.5): dependencies: - '@typescript-eslint/utils': 7.6.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5) debug: 4.3.4 doctrine: 3.0.0 eslint: 8.57.0 @@ -13880,14 +13815,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-n@17.2.1(eslint@8.57.0): + eslint-plugin-n@17.3.1(eslint@8.57.0): dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) enhanced-resolve: 5.16.0 eslint: 8.57.0 eslint-plugin-es-x: 7.6.0(eslint@8.57.0) get-tsconfig: 4.7.3 - globals: 14.0.0 + globals: 15.0.0 ignore: 5.3.1 minimatch: 9.0.4 semver: 7.6.0 @@ -13899,19 +13834,18 @@ snapshots: eslint-plugin-no-only-tests@3.1.0: {} - eslint-plugin-perfectionist@2.9.0(eslint@8.57.0)(typescript@5.4.5)(vue-eslint-parser@9.4.2(eslint@8.57.0)): + eslint-plugin-perfectionist@2.9.0(eslint@8.57.0)(typescript@5.4.5)(vue-eslint-parser@9.4.2): dependencies: '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 minimatch: 9.0.4 natural-compare-lite: 1.4.0 - optionalDependencies: vue-eslint-parser: 9.4.2(eslint@8.57.0) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-react-core@1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5): + eslint-plugin-react-core@1.5.9(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5): dependencies: '@eslint-react/ast': 1.5.9(eslint@8.57.0)(typescript@5.4.5) '@eslint-react/core': 1.5.9(eslint@8.57.0)(typescript@5.4.5) @@ -13932,7 +13866,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-dom@1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5): + eslint-plugin-react-dom@1.5.9(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5): dependencies: '@eslint-react/ast': 1.5.9(eslint@8.57.0)(typescript@5.4.5) '@eslint-react/core': 1.5.9(eslint@8.57.0)(typescript@5.4.5) @@ -13951,7 +13885,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-react-hooks-extra@1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5): + eslint-plugin-react-hooks-extra@1.5.9(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5): dependencies: '@eslint-react/ast': 1.5.9(eslint@8.57.0)(typescript@5.4.5) '@eslint-react/core': 1.5.9(eslint@8.57.0)(typescript@5.4.5) @@ -13975,7 +13909,7 @@ snapshots: dependencies: eslint: 8.57.0 - eslint-plugin-react-naming-convention@1.5.9(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5): + eslint-plugin-react-naming-convention@1.5.9(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5): dependencies: '@eslint-react/ast': 1.5.9(eslint@8.57.0)(typescript@5.4.5) '@eslint-react/core': 1.5.9(eslint@8.57.0)(typescript@5.4.5) @@ -14037,7 +13971,7 @@ snapshots: '@eslint/eslintrc': 2.1.4 ci-info: 4.0.0 clean-regexp: 1.0.0 - core-js-compat: 3.36.1 + core-js-compat: 3.37.0 eslint: 8.57.0 esquery: 1.5.0 indent-string: 4.0.0 @@ -14052,20 +13986,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-unused-imports@3.1.0(@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0): + eslint-plugin-unused-imports@3.1.0(@typescript-eslint/eslint-plugin@7.7.1)(eslint@8.57.0): dependencies: + '@typescript-eslint/eslint-plugin': 7.7.1(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 eslint-rule-composer: 0.3.0 - optionalDependencies: - '@typescript-eslint/eslint-plugin': 7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - eslint-plugin-vitest@0.5.3(@typescript-eslint/eslint-plugin@7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)): + eslint-plugin-vitest@0.5.4(@typescript-eslint/eslint-plugin@7.7.1)(eslint@8.57.0)(typescript@5.4.5): dependencies: - '@typescript-eslint/utils': 7.6.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/eslint-plugin': 7.7.1(@typescript-eslint/parser@7.7.1)(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.7.1(eslint@8.57.0)(typescript@5.4.5) eslint: 8.57.0 - optionalDependencies: - '@typescript-eslint/eslint-plugin': 7.7.1(@typescript-eslint/parser@7.7.1(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) - vitest: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) transitivePeerDependencies: - supports-color - typescript @@ -14095,9 +14026,9 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.4.21)(eslint@8.57.0): + eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.4.25)(eslint@8.57.0): dependencies: - '@vue/compiler-sfc': 3.4.21 + '@vue/compiler-sfc': 3.4.25 eslint: 8.57.0 eslint-rule-composer@0.3.0: {} @@ -14372,7 +14303,7 @@ snapshots: flatted@3.3.1: {} - flow-parser@0.232.0: {} + flow-parser@0.235.1: {} for-each@0.3.3: dependencies: @@ -14383,7 +14314,7 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.4.5)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.4.5)(webpack@5.91.0): dependencies: '@babel/code-frame': 7.24.2 chalk: 4.1.2 @@ -14398,7 +14329,7 @@ snapshots: semver: 7.6.0 tapable: 2.2.1 typescript: 5.4.5 - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) form-data-encoder@2.1.4: {} @@ -14586,8 +14517,6 @@ snapshots: dependencies: type-fest: 0.20.2 - globals@14.0.0: {} - globals@15.0.0: {} globalthis@1.0.3: @@ -14736,19 +14665,18 @@ snapshots: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.30.0 + terser: 5.30.4 html-tags@3.3.1: {} - html-webpack-plugin@5.6.0(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)): + html-webpack-plugin@5.6.0(webpack@5.91.0): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 lodash: 4.17.21 pretty-error: 4.0.0 tapable: 2.2.1 - optionalDependencies: - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) htmlparser2@6.1.0: dependencies: @@ -15106,8 +15034,8 @@ snapshots: istanbul-lib-instrument@6.0.2: dependencies: - '@babel/core': 7.24.3 - '@babel/parser': 7.24.1 + '@babel/core': 7.24.4 + '@babel/parser': 7.24.4 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.6.0 @@ -15180,29 +15108,28 @@ snapshots: js-tokens@9.0.0: {} - js-tokens@https://r2.cnpmjs.org/js-tokens/-/js-tokens-4.0.0.tgz: {} - js-yaml@4.1.0: dependencies: argparse: 2.0.1 jsbn@1.1.0: {} - jscodeshift@0.15.2(@babel/preset-env@7.24.3(@babel/core@7.24.3)): - dependencies: - '@babel/core': 7.24.3 - '@babel/parser': 7.24.1 - '@babel/plugin-transform-class-properties': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-nullish-coalescing-operator': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-optional-chaining': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.24.3) - '@babel/preset-flow': 7.24.1(@babel/core@7.24.3) - '@babel/preset-typescript': 7.24.1(@babel/core@7.24.3) - '@babel/register': 7.23.7(@babel/core@7.24.3) - babel-core: 7.0.0-bridge.0(@babel/core@7.24.3) + jscodeshift@0.15.2(@babel/preset-env@7.24.4): + dependencies: + '@babel/core': 7.24.4 + '@babel/parser': 7.24.4 + '@babel/plugin-transform-class-properties': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-nullish-coalescing-operator': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-optional-chaining': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.24.4) + '@babel/preset-env': 7.24.4(@babel/core@7.24.4) + '@babel/preset-flow': 7.24.1(@babel/core@7.24.4) + '@babel/preset-typescript': 7.24.1(@babel/core@7.24.4) + '@babel/register': 7.23.7(@babel/core@7.24.4) + babel-core: 7.0.0-bridge.0(@babel/core@7.24.4) chalk: 4.1.2 - flow-parser: 0.232.0 + flow-parser: 0.235.1 graceful-fs: 4.2.11 micromatch: 4.0.5 neo-async: 2.6.2 @@ -15210,8 +15137,6 @@ snapshots: recast: 0.23.6 temp: 0.8.4 write-file-atomic: 2.4.3 - optionalDependencies: - '@babel/preset-env': 7.24.3(@babel/core@7.24.3) transitivePeerDependencies: - supports-color @@ -15227,7 +15152,7 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.4 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.7 + nwsapi: 2.2.9 parse5: 7.1.2 rrweb-cssom: 0.6.0 saxes: 6.0.0 @@ -15274,8 +15199,6 @@ snapshots: espree: 9.6.1 semver: 7.6.0 - jsonc-parser@3.2.1: {} - jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -15315,11 +15238,10 @@ snapshots: dotenv: 16.4.5 dotenv-expand: 10.0.0 - less-loader@12.2.0(less@4.2.0)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)): + less-loader@12.2.0(less@4.2.0)(webpack@5.91.0): dependencies: less: 4.2.0 - optionalDependencies: - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) less@4.2.0: dependencies: @@ -15383,7 +15305,7 @@ snapshots: local-pkg@0.5.0: dependencies: mlly: 1.6.1 - pkg-types: 1.0.3 + pkg-types: 1.1.0 localforage@1.10.0: dependencies: @@ -15416,8 +15338,6 @@ snapshots: lodash.get@4.4.2: {} - lodash.get@https://r2.cnpmjs.org/lodash.get/-/lodash.get-4.4.2.tgz: {} - lodash.isequal@4.5.0: {} lodash.isplainobject@4.0.6: {} @@ -15468,10 +15388,6 @@ snapshots: dependencies: js-tokens: 4.0.0 - loose-envify@https://r2.cnpmjs.org/loose-envify/-/loose-envify-1.4.0.tgz: - dependencies: - js-tokens: https://r2.cnpmjs.org/js-tokens/-/js-tokens-4.0.0.tgz - loupe@2.3.7: dependencies: get-func-name: 2.0.2 @@ -15494,21 +15410,17 @@ snapshots: lru-cache@7.18.3: {} - lru-cache@https://r2.cnpmjs.org/lru-cache/-/lru-cache-6.0.0.tgz: - dependencies: - yallist: https://r2.cnpmjs.org/yallist/-/yallist-4.0.0.tgz - lz-string@1.5.0: {} macos-release@3.2.0: {} - magic-string@0.30.8: + magic-string@0.30.10: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - magicast@0.3.3: + magicast@0.3.4: dependencies: - '@babel/parser': 7.24.1 + '@babel/parser': 7.24.4 '@babel/types': 7.24.0 source-map-js: 1.2.0 @@ -15596,8 +15508,6 @@ snapshots: mime@1.6.0: {} - mime@4.0.1: {} - mimic-fn@2.1.0: {} mimic-fn@4.0.0: {} @@ -15651,7 +15561,7 @@ snapshots: dependencies: acorn: 8.11.3 pathe: 1.1.2 - pkg-types: 1.0.3 + pkg-types: 1.1.0 ufo: 1.5.3 monaco-editor@0.47.0: {} @@ -15755,7 +15665,7 @@ snapshots: numfmt@2.5.2: {} - nwsapi@2.2.7: {} + nwsapi@2.2.9: {} nypm@0.3.8: dependencies: @@ -16060,8 +15970,6 @@ snapshots: picocolors@1.0.0: {} - picocolors@https://r2.cnpmjs.org/picocolors/-/picocolors-1.0.0.tgz: {} - picomatch@2.3.1: {} picomatch@4.0.2: {} @@ -16084,9 +15992,9 @@ snapshots: dependencies: find-up: 5.0.0 - pkg-types@1.0.3: + pkg-types@1.1.0: dependencies: - jsonc-parser: 3.2.1 + confbox: 0.1.7 mlly: 1.6.1 pathe: 1.1.2 @@ -16102,22 +16010,22 @@ snapshots: polished@4.3.1: dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 possible-typed-array-names@1.0.0: {} - postcss-modules-extract-imports@3.0.0(postcss@8.4.38): + postcss-modules-extract-imports@3.1.0(postcss@8.4.38): dependencies: postcss: 8.4.38 - postcss-modules-local-by-default@4.0.4(postcss@8.4.38): + postcss-modules-local-by-default@4.0.5(postcss@8.4.38): dependencies: icss-utils: 5.1.0(postcss@8.4.38) postcss: 8.4.38 postcss-selector-parser: 6.0.16 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.1.1(postcss@8.4.38): + postcss-modules-scope@3.2.0(postcss@8.4.38): dependencies: postcss: 8.4.38 postcss-selector-parser: 6.0.16 @@ -16133,9 +16041,9 @@ snapshots: icss-utils: 5.1.0(postcss@8.4.38) lodash.camelcase: 4.3.0 postcss: 8.4.38 - postcss-modules-extract-imports: 3.0.0(postcss@8.4.38) - postcss-modules-local-by-default: 4.0.4(postcss@8.4.38) - postcss-modules-scope: 3.1.1(postcss@8.4.38) + postcss-modules-extract-imports: 3.1.0(postcss@8.4.38) + postcss-modules-local-by-default: 4.0.5(postcss@8.4.38) + postcss-modules-scope: 3.2.0(postcss@8.4.38) postcss-modules-values: 4.0.0(postcss@8.4.38) string-hash: 1.1.3 @@ -16199,7 +16107,7 @@ snapshots: prop-types@15.8.1: dependencies: - loose-envify: https://r2.cnpmjs.org/loose-envify/-/loose-envify-1.4.0.tgz + loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 @@ -16275,7 +16183,7 @@ snapshots: dependencies: side-channel: 1.0.6 - qs@6.12.0: + qs@6.12.1: dependencies: side-channel: 1.0.6 @@ -16302,154 +16210,153 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - rc-dialog@9.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-dialog@9.4.0(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 - '@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@babel/runtime': 7.24.4 + '@rc-component/portal': 1.1.2(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rc-dropdown@4.2.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-dropdown@4.2.0(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 - '@rc-component/trigger': 2.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@babel/runtime': 7.24.4 + '@rc-component/trigger': 2.1.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rc-input-number@9.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-input-number@9.0.0(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 '@rc-component/mini-decimal': 1.1.0 classnames: 2.5.1 - rc-input: 1.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-input: 1.4.5(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rc-input@1.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-input@1.4.5(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rc-menu@9.13.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-menu@9.13.0(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 - '@rc-component/trigger': 2.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@babel/runtime': 7.24.4 + '@rc-component/trigger': 2.1.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-overflow: 1.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-overflow: 1.3.2(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rc-motion@2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-motion@2.9.0(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rc-notification@5.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-notification@5.4.0(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rc-overflow@1.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-overflow@1.3.2(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 classnames: 2.5.1 - rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rc-picker@4.3.0(dayjs@1.11.10)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-picker@4.4.2(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 - '@rc-component/trigger': 2.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@babel/runtime': 7.24.4 + '@rc-component/trigger': 2.1.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-overflow: 1.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + dayjs: 1.11.10 + rc-overflow: 1.3.2(react-dom@18.2.0)(react@18.2.0) + rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - optionalDependencies: - dayjs: 1.11.10 - rc-resize-observer@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-resize-observer@1.4.0(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 classnames: 2.5.1 - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) resize-observer-polyfill: 1.5.1 - rc-segmented@2.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-segmented@2.3.0(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rc-select@14.13.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-select@14.13.1(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 - '@rc-component/trigger': 2.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@babel/runtime': 7.24.4 + '@rc-component/trigger': 2.1.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 - rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-overflow: 1.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-virtual-list: 3.11.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0) + rc-overflow: 1.3.2(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) + rc-virtual-list: 3.11.5(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rc-textarea@1.6.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-textarea@1.6.3(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 classnames: 2.5.1 - rc-input: 1.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-input: 1.4.5(react-dom@18.2.0)(react@18.2.0) + rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rc-tooltip@6.2.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-tooltip@6.2.0(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 - '@rc-component/trigger': 2.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@babel/runtime': 7.24.4 + '@rc-component/trigger': 2.1.1(react-dom@18.2.0)(react@18.2.0) classnames: 2.5.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - rc-util@5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-util@5.39.1(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-is: 18.2.0 - rc-virtual-list@3.11.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + rc-virtual-list@3.11.5(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 classnames: 2.5.1 - rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - rc-util: 5.39.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rc-resize-observer: 1.4.0(react-dom@18.2.0)(react@18.2.0) + rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -16468,7 +16375,7 @@ snapshots: transitivePeerDependencies: - dnd-core - react-colorful@5.6.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + react-colorful@5.6.1(react-dom@18.2.0)(react@18.2.0): dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -16482,16 +16389,16 @@ snapshots: dependencies: dnd-core: 16.0.1 - react-dnd-multi-backend@8.0.3(dnd-core@16.0.1)(react-dnd@16.0.1(@types/node@20.12.7)(@types/react@18.2.79)(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + react-dnd-multi-backend@8.0.3(dnd-core@16.0.1)(react-dnd@16.0.1)(react-dom@18.2.0)(react@18.2.0): dependencies: dnd-core: 16.0.1 dnd-multi-backend: 8.0.3(dnd-core@16.0.1) react: 18.2.0 react-dnd: 16.0.1(@types/node@20.12.7)(@types/react@18.2.79)(react@18.2.0) - react-dnd-preview: 8.0.3(react-dnd@16.0.1(@types/node@20.12.7)(@types/react@18.2.79)(react@18.2.0))(react@18.2.0) + react-dnd-preview: 8.0.3(react-dnd@16.0.1)(react@18.2.0) react-dom: 18.2.0(react@18.2.0) - react-dnd-preview@8.0.3(react-dnd@16.0.1(@types/node@20.12.7)(@types/react@18.2.79)(react@18.2.0))(react@18.2.0): + react-dnd-preview@8.0.3(react-dnd@16.0.1)(react@18.2.0): dependencies: react: 18.2.0 react-dnd: 16.0.1(@types/node@20.12.7)(@types/react@18.2.79)(react@18.2.0) @@ -16505,13 +16412,12 @@ snapshots: dependencies: '@react-dnd/invariant': 4.0.2 '@react-dnd/shallowequal': 4.0.2 + '@types/node': 20.12.7 + '@types/react': 18.2.79 dnd-core: 16.0.1 fast-deep-equal: 3.1.3 hoist-non-react-statics: 3.3.2 react: 18.2.0 - optionalDependencies: - '@types/node': 20.12.7 - '@types/react': 18.2.79 react-docgen-typescript@2.2.2(typescript@5.4.5): dependencies: @@ -16519,7 +16425,7 @@ snapshots: react-docgen@7.0.3: dependencies: - '@babel/core': 7.24.3 + '@babel/core': 7.24.4 '@babel/traverse': 7.24.1 '@babel/types': 7.24.0 '@types/babel__core': 7.20.5 @@ -16538,14 +16444,14 @@ snapshots: react: 18.2.0 scheduler: 0.23.0 - react-draggable@4.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + react-draggable@4.4.6(react-dom@18.2.0)(react@18.2.0): dependencies: clsx: 1.2.1 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-element-to-jsx-string@15.0.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + react-element-to-jsx-string@15.0.0(react-dom@18.2.0)(react@18.2.0): dependencies: '@base2/pretty-print-object': 1.0.1 is-plain-object: 5.0.0 @@ -16555,19 +16461,19 @@ snapshots: react-error-boundary@3.1.4(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 react: 18.2.0 - react-grid-layout@1.4.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + react-grid-layout@1.4.4(react-dom@18.2.0)(react@18.2.0): dependencies: clsx: 2.1.1 fast-equals: 4.0.3 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-draggable: 4.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - react-resizable: 3.0.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - resize-observer-polyfill: https://r2.cnpmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz + react-draggable: 4.4.6(react-dom@18.2.0)(react@18.2.0) + react-resizable: 3.0.5(react-dom@18.2.0)(react@18.2.0) + resize-observer-polyfill: 1.5.1 react-is@16.13.1: {} @@ -16577,7 +16483,7 @@ snapshots: react-is@18.2.0: {} - react-mosaic-component@6.1.0(@types/node@20.12.7)(@types/react@18.2.79)(dnd-core@16.0.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + react-mosaic-component@6.1.0(@types/node@20.12.7)(@types/react@18.2.79)(dnd-core@16.0.1)(react-dom@18.2.0)(react@18.2.0): dependencies: classnames: 2.5.1 immutability-helper: 3.1.1 @@ -16587,7 +16493,7 @@ snapshots: react: 18.2.0 react-dnd: 16.0.1(@types/node@20.12.7)(@types/react@18.2.79)(react@18.2.0) react-dnd-html5-backend: 16.0.1 - react-dnd-multi-backend: 8.0.3(dnd-core@16.0.1)(react-dnd@16.0.1(@types/node@20.12.7)(@types/react@18.2.79)(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react-dnd-multi-backend: 8.0.3(dnd-core@16.0.1)(react-dnd@16.0.1)(react-dom@18.2.0)(react@18.2.0) react-dnd-touch-backend: 16.0.1 uuid: 9.0.1 transitivePeerDependencies: @@ -16599,17 +16505,17 @@ snapshots: react-refresh@0.14.0: {} - react-resizable@3.0.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + react-resizable@3.0.5(react-dom@18.2.0)(react@18.2.0): dependencies: prop-types: 15.8.1 react: 18.2.0 - react-draggable: 4.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react-draggable: 4.4.6(react-dom@18.2.0)(react@18.2.0) transitivePeerDependencies: - react-dom - react-transition-group@4.4.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -16624,7 +16530,7 @@ snapshots: dependencies: find-up: 6.3.0 read-pkg: 8.1.0 - type-fest: 4.14.0 + type-fest: 4.17.0 read-pkg-up@7.0.1: dependencies: @@ -16644,7 +16550,7 @@ snapshots: '@types/normalize-package-data': 2.4.4 normalize-package-data: 6.0.0 parse-json: 7.1.1 - type-fest: 4.14.0 + type-fest: 4.17.0 readable-stream@2.3.8: dependencies: @@ -16685,7 +16591,7 @@ snapshots: redux@4.2.1: dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 reflect.getprototypeof@1.0.6: dependencies: @@ -16707,7 +16613,7 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.24.1 + '@babel/runtime': 7.24.4 regexp-tree@0.1.27: {} @@ -16826,8 +16732,6 @@ snapshots: resize-observer-polyfill@1.5.1: {} - resize-observer-polyfill@https://r2.cnpmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz: {} - resolve-alpn@1.2.1: {} resolve-from@4.0.0: {} @@ -16886,25 +16790,26 @@ snapshots: dependencies: glob: 7.2.3 - rollup@4.13.2: + rollup@4.16.4: dependencies: '@types/estree': 1.0.5 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.13.2 - '@rollup/rollup-android-arm64': 4.13.2 - '@rollup/rollup-darwin-arm64': 4.13.2 - '@rollup/rollup-darwin-x64': 4.13.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.13.2 - '@rollup/rollup-linux-arm64-gnu': 4.13.2 - '@rollup/rollup-linux-arm64-musl': 4.13.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.13.2 - '@rollup/rollup-linux-riscv64-gnu': 4.13.2 - '@rollup/rollup-linux-s390x-gnu': 4.13.2 - '@rollup/rollup-linux-x64-gnu': 4.13.2 - '@rollup/rollup-linux-x64-musl': 4.13.2 - '@rollup/rollup-win32-arm64-msvc': 4.13.2 - '@rollup/rollup-win32-ia32-msvc': 4.13.2 - '@rollup/rollup-win32-x64-msvc': 4.13.2 + '@rollup/rollup-android-arm-eabi': 4.16.4 + '@rollup/rollup-android-arm64': 4.16.4 + '@rollup/rollup-darwin-arm64': 4.16.4 + '@rollup/rollup-darwin-x64': 4.16.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.16.4 + '@rollup/rollup-linux-arm-musleabihf': 4.16.4 + '@rollup/rollup-linux-arm64-gnu': 4.16.4 + '@rollup/rollup-linux-arm64-musl': 4.16.4 + '@rollup/rollup-linux-powerpc64le-gnu': 4.16.4 + '@rollup/rollup-linux-riscv64-gnu': 4.16.4 + '@rollup/rollup-linux-s390x-gnu': 4.16.4 + '@rollup/rollup-linux-x64-gnu': 4.16.4 + '@rollup/rollup-linux-x64-musl': 4.16.4 + '@rollup/rollup-win32-arm64-msvc': 4.16.4 + '@rollup/rollup-win32-ia32-msvc': 4.16.4 + '@rollup/rollup-win32-x64-msvc': 4.16.4 fsevents: 2.3.3 rrweb-cssom@0.6.0: {} @@ -16940,7 +16845,7 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.72.0: + sass@1.75.0: dependencies: chokidar: 3.6.0 immutable: 4.3.5 @@ -16984,7 +16889,7 @@ snapshots: semver@7.6.0: dependencies: - lru-cache: https://r2.cnpmjs.org/lru-cache/-/lru-cache-6.0.0.tgz + lru-cache: 6.0.0 send@0.18.0: dependencies: @@ -17145,11 +17050,11 @@ snapshots: dependencies: agent-base: 7.1.1 debug: 4.3.4 - socks: 2.8.1 + socks: 2.8.3 transitivePeerDependencies: - supports-color - socks@2.8.1: + socks@2.8.3: dependencies: ip-address: 9.0.5 smart-buffer: 4.2.0 @@ -17204,19 +17109,17 @@ snapshots: store2@2.14.3: {} - storybook-addon-swc@1.2.0(@swc/core@1.4.11)(terser-webpack-plugin@5.3.10(@swc/core@1.4.11)(esbuild@0.20.2)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)))(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)): + storybook-addon-swc@1.2.0(@swc/core@1.5.0)(webpack@5.91.0): dependencies: - '@babel/runtime': 7.24.1 - '@swc/core': 1.4.11 + '@babel/runtime': 7.24.4 + '@swc/core': 1.5.0 deepmerge: 4.3.1 - swc-loader: 0.1.16(@swc/core@1.4.11)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) - optionalDependencies: - terser-webpack-plugin: 5.3.10(@swc/core@1.4.11)(esbuild@0.20.2)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) + swc-loader: 0.1.16(@swc/core@1.5.0)(webpack@5.91.0) + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) - storybook@8.0.9(@babel/preset-env@7.24.3(@babel/core@7.24.3))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): + storybook@8.0.9(react-dom@18.2.0)(react@18.2.0): dependencies: - '@storybook/cli': 8.0.9(@babel/preset-env@7.24.3(@babel/core@7.24.3))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/cli': 8.0.9(react-dom@18.2.0)(react@18.2.0) transitivePeerDependencies: - '@babel/preset-env' - bufferutil @@ -17326,9 +17229,9 @@ snapshots: dependencies: js-tokens: 9.0.0 - style-loader@3.3.4(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)): + style-loader@3.3.4(webpack@5.91.0): dependencies: - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) supports-color@5.5.0: dependencies: @@ -17344,16 +17247,16 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - swc-loader@0.1.16(@swc/core@1.4.11)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)): + swc-loader@0.1.16(@swc/core@1.5.0)(webpack@5.91.0): dependencies: - '@swc/core': 1.4.11 - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) + '@swc/core': 1.5.0 + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) - swc-loader@0.2.6(@swc/core@1.4.11)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)): + swc-loader@0.2.6(@swc/core@1.5.0)(webpack@5.91.0): dependencies: - '@swc/core': 1.4.11 + '@swc/core': 1.5.0 '@swc/counter': 0.1.3 - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) symbol-tree@3.2.4: {} @@ -17410,19 +17313,18 @@ snapshots: type-fest: 0.16.0 unique-string: 2.0.0 - terser-webpack-plugin@5.3.10(@swc/core@1.4.11)(esbuild@0.20.2)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)): + terser-webpack-plugin@5.3.10(@swc/core@1.5.0)(esbuild@0.20.2)(webpack@5.91.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 + '@swc/core': 1.5.0 + esbuild: 0.20.2 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 - terser: 5.30.0 - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) - optionalDependencies: - '@swc/core': 1.4.11 - esbuild: 0.20.2 + terser: 5.30.4 + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) - terser@5.30.0: + terser@5.30.4: dependencies: '@jridgewell/source-map': 0.3.6 acorn: 8.11.3 @@ -17450,9 +17352,9 @@ snapshots: tiny-invariant@1.3.3: {} - tinybench@2.6.0: {} + tinybench@2.8.0: {} - tinypool@0.8.3: {} + tinypool@0.8.4: {} tinyspy@2.2.1: {} @@ -17466,7 +17368,7 @@ snapshots: dependencies: is-number: 7.0.0 - tocbot@4.25.0: {} + tocbot@4.27.4: {} toidentifier@1.0.1: {} @@ -17560,7 +17462,7 @@ snapshots: type-fest@3.13.1: {} - type-fest@4.14.0: {} + type-fest@4.17.0: {} type-is@1.6.18: dependencies: @@ -17730,7 +17632,7 @@ snapshots: url@0.11.3: dependencies: punycode: 1.4.1 - qs: 6.12.0 + qs: 6.12.1 util-deprecate@1.0.2: {} @@ -17762,13 +17664,13 @@ snapshots: vary@1.1.2: {} - vite-node@1.5.0(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0): + vite-node@1.5.1(@types/node@20.12.7)(less@4.2.0): dependencies: cac: 6.7.14 debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + vite: 5.2.10(@types/node@20.12.7)(less@4.2.0) transitivePeerDependencies: - '@types/node' - less @@ -17779,66 +17681,126 @@ snapshots: - supports-color - terser - vite-plugin-dts@3.9.0(@types/node@20.12.7)(rollup@4.13.2)(typescript@5.4.5)(vite@5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)): + vite-plugin-dts@3.9.0(@types/node@20.12.7)(typescript@5.4.5)(vite@5.2.10): dependencies: '@microsoft/api-extractor': 7.43.0(@types/node@20.12.7) - '@rollup/pluginutils': 5.1.0(rollup@4.13.2) + '@rollup/pluginutils': 5.1.0 '@vue/language-core': 1.8.27(typescript@5.4.5) debug: 4.3.4 kolorist: 1.8.0 - magic-string: 0.30.8 + magic-string: 0.30.10 typescript: 5.4.5 + vite: 5.2.10(@types/node@20.12.7)(less@4.2.0) vue-tsc: 1.8.27(typescript@5.4.5) - optionalDependencies: - vite: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite@5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0): + vite@5.2.10(@types/node@20.12.7)(less@4.2.0): dependencies: + '@types/node': 20.12.7 esbuild: 0.20.2 + less: 4.2.0 postcss: 8.4.38 - rollup: 4.13.2 + rollup: 4.16.4 optionalDependencies: - '@types/node': 20.12.7 fsevents: 2.3.3 - less: 4.2.0 - sass: 1.72.0 - terser: 5.30.0 - vitest-canvas-mock@0.3.3(vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0)): + vitest-canvas-mock@0.3.3(vitest@1.5.1): dependencies: jest-canvas-mock: 2.5.2 - vitest: 1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + vitest: 1.5.1(@types/node@20.12.7)(jsdom@24.0.0) - vitest@1.5.0(@types/node@20.12.7)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)(sass@1.72.0)(terser@5.30.0): + vitest@1.5.1(@types/node@20.12.7)(happy-dom@13.3.8): dependencies: - '@vitest/expect': 1.5.0 - '@vitest/runner': 1.5.0 - '@vitest/snapshot': 1.5.0 - '@vitest/spy': 1.5.0 - '@vitest/utils': 1.5.0 + '@types/node': 20.12.7 + '@vitest/expect': 1.5.1 + '@vitest/runner': 1.5.1 + '@vitest/snapshot': 1.5.1 + '@vitest/spy': 1.5.1 + '@vitest/utils': 1.5.1 acorn-walk: 8.3.2 chai: 4.4.1 debug: 4.3.4 execa: 8.0.1 + happy-dom: 13.3.8 local-pkg: 0.5.0 - magic-string: 0.30.8 + magic-string: 0.30.10 pathe: 1.1.2 picocolors: 1.0.0 std-env: 3.7.0 strip-literal: 2.1.0 - tinybench: 2.6.0 - tinypool: 0.8.3 - vite: 5.2.10(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) - vite-node: 1.5.0(@types/node@20.12.7)(less@4.2.0)(sass@1.72.0)(terser@5.30.0) + tinybench: 2.8.0 + tinypool: 0.8.4 + vite: 5.2.10(@types/node@20.12.7)(less@4.2.0) + vite-node: 1.5.1(@types/node@20.12.7)(less@4.2.0) why-is-node-running: 2.2.2 - optionalDependencies: + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + + vitest@1.5.1(@types/node@20.12.7)(jsdom@24.0.0): + dependencies: '@types/node': 20.12.7 - happy-dom: 13.3.8 + '@vitest/expect': 1.5.1 + '@vitest/runner': 1.5.1 + '@vitest/snapshot': 1.5.1 + '@vitest/spy': 1.5.1 + '@vitest/utils': 1.5.1 + acorn-walk: 8.3.2 + chai: 4.4.1 + debug: 4.3.4 + execa: 8.0.1 jsdom: 24.0.0 + local-pkg: 0.5.0 + magic-string: 0.30.10 + pathe: 1.1.2 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 2.1.0 + tinybench: 2.8.0 + tinypool: 0.8.4 + vite: 5.2.10(@types/node@20.12.7)(less@4.2.0) + vite-node: 1.5.1(@types/node@20.12.7)(less@4.2.0) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + + vitest@1.5.1(@types/node@20.12.7)(less@4.2.0): + dependencies: + '@types/node': 20.12.7 + '@vitest/expect': 1.5.1 + '@vitest/runner': 1.5.1 + '@vitest/snapshot': 1.5.1 + '@vitest/spy': 1.5.1 + '@vitest/utils': 1.5.1 + acorn-walk: 8.3.2 + chai: 4.4.1 + debug: 4.3.4 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.10 + pathe: 1.1.2 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 2.1.0 + tinybench: 2.8.0 + tinypool: 0.8.4 + vite: 5.2.10(@types/node@20.12.7)(less@4.2.0) + vite-node: 1.5.1(@types/node@20.12.7)(less@4.2.0) + why-is-node-running: 2.2.2 transitivePeerDependencies: - less - lightningcss @@ -17873,14 +17835,13 @@ snapshots: semver: 7.6.0 typescript: 5.4.5 - vue@3.4.21(typescript@5.4.5): + vue@3.4.25(typescript@5.4.5): dependencies: - '@vue/compiler-dom': 3.4.21 - '@vue/compiler-sfc': 3.4.21 - '@vue/runtime-dom': 3.4.21 - '@vue/server-renderer': 3.4.21(vue@3.4.21(typescript@5.4.5)) - '@vue/shared': 3.4.21 - optionalDependencies: + '@vue/compiler-dom': 3.4.25 + '@vue/compiler-sfc': 3.4.25 + '@vue/runtime-dom': 3.4.25 + '@vue/server-renderer': 3.4.25(vue@3.4.25) + '@vue/shared': 3.4.25 typescript: 5.4.5 w3c-xmlserializer@5.0.0: @@ -17909,15 +17870,14 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-dev-middleware@6.1.3(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)): + webpack-dev-middleware@6.1.3(webpack@5.91.0): dependencies: colorette: 2.0.20 memfs: 3.5.3 mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.2.0 - optionalDependencies: - webpack: 5.91.0(@swc/core@1.4.11)(esbuild@0.20.2) + webpack: 5.91.0(@swc/core@1.5.0)(esbuild@0.20.2) webpack-hot-middleware@2.26.1: dependencies: @@ -17931,7 +17891,7 @@ snapshots: webpack-virtual-modules@0.6.1: {} - webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2): + webpack@5.91.0(@swc/core@1.5.0)(esbuild@0.20.2): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.5 @@ -17954,7 +17914,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.4.11)(esbuild@0.20.2)(webpack@5.91.0(@swc/core@1.4.11)(esbuild@0.20.2)) + terser-webpack-plugin: 5.3.10(@swc/core@1.5.0)(esbuild@0.20.2)(webpack@5.91.0) watchpack: 2.4.1 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -18096,8 +18056,6 @@ snapshots: yallist@4.0.0: {} - yallist@https://r2.cnpmjs.org/yallist/-/yallist-4.0.0.tgz: {} - yaml-eslint-parser@1.2.2: dependencies: eslint-visitor-keys: 3.4.3