diff --git a/examples/package.json b/examples/package.json index 2e81a2079e..479540c794 100644 --- a/examples/package.json +++ b/examples/package.json @@ -52,6 +52,7 @@ "@univerjs/sheets-numfmt-ui": "workspace:*", "@univerjs/sheets-sort": "workspace:*", "@univerjs/sheets-sort-ui": "workspace:*", + "@univerjs/sheets-source-binding": "workspace:*", "@univerjs/sheets-thread-comment": "workspace:*", "@univerjs/sheets-thread-comment-ui": "workspace:*", "@univerjs/sheets-ui": "workspace:*", diff --git a/examples/src/sheets/main.ts b/examples/src/sheets/main.ts index f7a5f7e89e..af9560c486 100644 --- a/examples/src/sheets/main.ts +++ b/examples/src/sheets/main.ts @@ -35,6 +35,7 @@ import { UniverSheetsHyperLinkPlugin } from '@univerjs/sheets-hyper-link'; import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt'; import { UniverSheetsNumfmtUIPlugin } from '@univerjs/sheets-numfmt-ui'; import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort'; +import { UniverSheetsBindingSourcePlugin } from '@univerjs/sheets-source-binding'; import { UniverSheetsThreadCommentPlugin } from '@univerjs/sheets-thread-comment'; import { UniverSheetsThreadCommentUIPlugin } from '@univerjs/sheets-thread-comment-ui'; import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui'; @@ -59,6 +60,7 @@ import '@univerjs/sheets-conditional-formatting/facade'; import '@univerjs/sheets-find-replace/facade'; import '@univerjs/sheets-drawing-ui/facade'; import '@univerjs/sheets-zen-editor/facade'; +import '@univerjs/sheets-source-binding/facade'; import '../global.css'; import './styles'; @@ -118,6 +120,7 @@ univer.registerPlugin(UniverSheetsHyperLinkPlugin); univer.registerPlugin(UniverThreadCommentUIPlugin); univer.registerPlugin(UniverSheetsThreadCommentPlugin); univer.registerPlugin(UniverSheetsThreadCommentUIPlugin); +univer.registerPlugin(UniverSheetsBindingSourcePlugin); // If we are running in e2e platform, we should immediately register the debugger plugin. if (IS_E2E) { diff --git a/packages-experimental/sheets-source-binding/README.md b/packages-experimental/sheets-source-binding/README.md new file mode 100644 index 0000000000..6d80471b21 --- /dev/null +++ b/packages-experimental/sheets-source-binding/README.md @@ -0,0 +1,16 @@ +# @univerjs/sheets-source-binding + +[![npm version](https://img.shields.io/npm/v/@univerjs/sheets-source-binding)](https://npmjs.org/packages/@univerjs/sheets-source-binding) +[![license](https://img.shields.io/npm/l/@univerjs/sheets-source-binding)](https://img.shields.io/npm/l/@univerjs/sheets-source-binding) + +## Introduction + +> TODO: Introduction + +## Usage + +### Installation + +```shell +npm i @univerjs/sheets-source-binding +``` \ No newline at end of file diff --git a/packages-experimental/sheets-source-binding/package.json b/packages-experimental/sheets-source-binding/package.json new file mode 100644 index 0000000000..3d1c59b8bf --- /dev/null +++ b/packages-experimental/sheets-source-binding/package.json @@ -0,0 +1,78 @@ +{ + "name": "@univerjs/sheets-source-binding", + "version": "0.5.3", + "private": false, + "description": "A library for connect and bind data from other sources to Univer Sheets", + "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/*", + "./facade": "./src/facade/index.ts" + }, + "main": "./src/index.ts", + "types": "./lib/types/index.d.ts", + "publishConfig": { + "access": "public", + "main": "./lib/es/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" + }, + "./facade": { + "import": "./lib/es/facade.js", + "require": "./lib/cjs/facade.js", + "types": "./lib/types/facade/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": "univer-cli build" + }, + "peerDependencies": { + "rxjs": ">=7.0.0" + }, + "dependencies": { + "@univerjs/core": "workspace:*", + "@univerjs/sheets": "workspace:*" + }, + "devDependencies": { + "@univerjs-infra/shared": "workspace:*", + "rxjs": "^7.8.1", + "typescript": "^5.7.2", + "vite": "^6.0.7", + "vitest": "^2.1.8" + } +} diff --git a/packages-experimental/sheets-source-binding/src/controllers/binding-manager.ts b/packages-experimental/sheets-source-binding/src/controllers/binding-manager.ts new file mode 100644 index 0000000000..55b9d24509 --- /dev/null +++ b/packages-experimental/sheets-source-binding/src/controllers/binding-manager.ts @@ -0,0 +1,139 @@ +/** + * 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 { ICellBindingNode, ICellBindingNodeParam } from '../types'; +import { Disposable, generateRandomId } from '@univerjs/core'; +import { Subject } from 'rxjs'; +import { SheetBindingModel } from '../model/binding-model'; +import { BindingSourceChangeTypeEnum } from '../types'; + +interface IBindingNodeInfo { + unitId: string; + subunitId: string; + sourceId: string; + nodeId: string; + row: number; + column: number; +} + +export class SheetsBindingManager extends Disposable { + modelMap: Map> = new Map(); + + private _cellBindInfoUpdate$ = new Subject(); + cellBindInfoUpdate$ = this._cellBindInfoUpdate$.asObservable(); + + constructor( + ) { + super(); + } + + getBindingModelBySourceId(sourceId: string) { + const rs: IBindingNodeInfo[] = []; + this.modelMap.forEach((subMap, unitId) => { + subMap.forEach((model, subunitId) => { + const nodes = model.getBindingNodesBySourceId(sourceId); + if (nodes) { + for (const node of nodes) { + rs.push({ + unitId, + subunitId, + sourceId, + nodeId: node.nodeId!, + row: node.row, + column: node.column, + }); + } + } + }); + }); + return rs; + } + + addModel(unitId: string, subunitId: string, model: SheetBindingModel): void { + if (!this.modelMap.has(unitId)) { + this.modelMap.set(unitId, new Map()); + } + this.modelMap.get(unitId)?.set(subunitId, model); + } + + getModel(unitId: string, subunitId: string): SheetBindingModel | undefined { + return this.modelMap.get(unitId)?.get(subunitId); + } + + setBindingNode(unitId: string, subunitId: string, node: ICellBindingNodeParam): void { + let model = this.getModel(unitId, subunitId); + if (!model) { + model = new SheetBindingModel(); + this.addModel(unitId, subunitId, model); + } + if (!node.nodeId) { + node.nodeId = generateRandomId(); + } + const { row, column } = node; + if (row === undefined || column === undefined) { + throw new Error('row and column is required'); + } + const oldNode = model.getBindingNode(row, column); + model.setBindingNode(row, column, { ...node, row, column } as ICellBindingNode); + this._cellBindInfoUpdate$.next({ + unitId, + subunitId, + sourceId: node.sourceId, + nodeId: node.nodeId, + row, + column, + changeType: oldNode ? BindingSourceChangeTypeEnum.Update : BindingSourceChangeTypeEnum.Add, + oldSourceId: oldNode?.sourceId, + }); + } + + removeBindingNode(unitId: string, subunitId: string, row: number, column: number): void { + const model = this.getModel(unitId, subunitId); + if (model) { + const node = model.getBindingNode(row, column); + if (node) { + model.removeBindingNode(row, column); + this._cellBindInfoUpdate$.next({ + unitId, + subunitId, + sourceId: node.sourceId, + nodeId: node.nodeId!, + row, + column, + changeType: BindingSourceChangeTypeEnum.Remove, + }); + } + } + } + + getBindingNode(unitId: string, subunitId: string, row: number, column: number): ICellBindingNode | undefined { + const model = this.getModel(unitId, subunitId); + if (model) { + return model.getBindingNode(row, column); + } + return undefined; + } + + createModel(unitId: string, subunitId: string, json?: any): SheetBindingModel { + const model = new SheetBindingModel(json); + this.addModel(unitId, subunitId, model); + return model; + } + + override dispose(): void { + this.modelMap.clear(); + } +} diff --git a/packages-experimental/sheets-source-binding/src/controllers/source-manager.ts b/packages-experimental/sheets-source-binding/src/controllers/source-manager.ts new file mode 100644 index 0000000000..eefb7423bf --- /dev/null +++ b/packages-experimental/sheets-source-binding/src/controllers/source-manager.ts @@ -0,0 +1,88 @@ +/** + * 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 { ISourceEvent } from '../types'; +import { Disposable, generateRandomId } from '@univerjs/core'; +import { Subject } from 'rxjs'; +import { ListSourceModel, ObjectSourceModel, SourceModelBase } from '../model/source-model'; +import { BindingSourceChangeTypeEnum, DataBindingNodeTypeEnum } from '../types'; + +export class SheetsSourceManager extends Disposable { + sourceMap: Map > = new Map(); + private _sourceDataUpdate$ = new Subject(); + sourceDataUpdate$ = this._sourceDataUpdate$.asObservable(); + constructor() { + super(); + } + + private _ensureUnitMap(unitId: string): Map { + let unit = this.sourceMap.get(unitId); + if (!unit) { + unit = new Map(); + this.sourceMap.set(unitId, unit); + } + return unit; + } + + private _getUnitMap(unitId: string): Map | undefined { + return this.sourceMap.get(unitId); + } + + public getSource(unitId: string, id: string): SourceModelBase | undefined { + const unitMap = this._getUnitMap(unitId); + return unitMap?.get(id); + } + + createSource(unitId: string, type: DataBindingNodeTypeEnum, isListObject?: boolean, id?: string): SourceModelBase { + const sourceId = id === undefined ? generateRandomId() : id; + let source; + switch (type) { + case DataBindingNodeTypeEnum.List: + source = new ListSourceModel(sourceId, isListObject); + break; + case DataBindingNodeTypeEnum.Object: + source = new ObjectSourceModel(sourceId); + break; + + default: + throw new Error(`Invalid source type: ${type}`); + } + const unitMap = this._ensureUnitMap(unitId); + unitMap.set(sourceId, source); + return source; + } + + updateSourceData(unitId: string, idOrInstance: string | SourceModelBase, data: any): void { + const unitMap = this._getUnitMap(unitId); + const sourceId = idOrInstance instanceof SourceModelBase ? idOrInstance.getId() : idOrInstance; + const source = unitMap?.get(sourceId); + if (source) { + source.setSourceData(data); + this._sourceDataUpdate$.next({ ...source.getSourceInfo(), unitId, changeType: BindingSourceChangeTypeEnum.Add }); + } else { + throw new Error(`Source not found: ${sourceId}`); + } + } + + removeSource(unitId: string, id: string): void { + const unitMap = this._getUnitMap(unitId); + const source = unitMap?.get(id); + if (source) { + unitMap?.delete(id); + this._sourceDataUpdate$.next({ ...source.getSourceInfo(), unitId, changeType: BindingSourceChangeTypeEnum.Remove }); + } + } +} diff --git a/packages-experimental/sheets-source-binding/src/facade/f-enum.ts b/packages-experimental/sheets-source-binding/src/facade/f-enum.ts new file mode 100644 index 0000000000..96c13eda0c --- /dev/null +++ b/packages-experimental/sheets-source-binding/src/facade/f-enum.ts @@ -0,0 +1,45 @@ +/** + * 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 { FEnum } from '@univerjs/core'; +import { BindModeEnum, DataBindingNodeTypeEnum } from '@univerjs/sheets-source-binding'; + +export interface ISourceBindingEnumMixin { + /** + * Represents the type of data binding node.{@link DataBindingNodeTypeEnum} + */ + DataBindingNodeTypeEnum: typeof DataBindingNodeTypeEnum; + /** + * Represents the type of data binding node. {@link BindModeEnum} + */ + BindModeEnum: typeof BindModeEnum; +} +class FSourceBindingEnum extends FEnum implements ISourceBindingEnumMixin { + override get DataBindingNodeTypeEnum(): typeof DataBindingNodeTypeEnum { + return DataBindingNodeTypeEnum; + } + + override get BindModeEnum(): typeof BindModeEnum { + return BindModeEnum; + } +} + +FEnum.extend(FSourceBindingEnum); + +declare module '@univerjs/core' { + // eslint-disable-next-line ts/naming-convention + interface FEnum extends ISourceBindingEnumMixin { } +} diff --git a/packages-experimental/sheets-source-binding/src/facade/f-workbook.ts b/packages-experimental/sheets-source-binding/src/facade/f-workbook.ts new file mode 100644 index 0000000000..41e8bf62bc --- /dev/null +++ b/packages-experimental/sheets-source-binding/src/facade/f-workbook.ts @@ -0,0 +1,75 @@ +/** + * 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 { DataBindingNodeTypeEnum, SourceModelBase } from '@univerjs/sheets-source-binding'; +import { SheetsSourceBindService } from '@univerjs/sheets-source-binding'; +import { FWorkbook } from '@univerjs/sheets/facade'; + +export interface IFWorkbookSourceBindingMixin { + /** + * Create a source model with the specified type. + * @param {DataBindingNodeTypeEnum} type The source type. + * @param {boolean} [isListObject] Whether the source is a list object. + * @returns {SourceModelBase} The source data of sheet. + */ + createSource(type: DataBindingNodeTypeEnum, isListObject?: boolean): SourceModelBase; + /** + * Switch to path mode.In this mode, the path will show in cell. + */ + usePathMode(): void; + /** + * Switch to value mode.This is the default mode. In this mode, the cell value will fulfill by source values. + */ + useValueMode(): void; + /** + * Get the source model by the specified source id. + * @param {string} sourceId The source id. + */ + getSource(sourceId: string): SourceModelBase | undefined; +} + +export class FWorkbookSourceBinding extends FWorkbook implements IFWorkbookSourceBindingMixin { + override createSource(type: DataBindingNodeTypeEnum, isListObject?: boolean): SourceModelBase { + const injector = this._injector; + const sheetsSourceBindService = injector.get(SheetsSourceBindService); + return sheetsSourceBindService.createSource(this.getId(), type, isListObject); + } + + override getSource(sourceId: string): SourceModelBase | undefined { + const injector = this._injector; + const sheetsSourceBindService = injector.get(SheetsSourceBindService); + return sheetsSourceBindService.getSource(this.getId(), sourceId); + } + + override usePathMode(): void { + const injector = this._injector; + const sheetsSourceBindService = injector.get(SheetsSourceBindService); + sheetsSourceBindService.usePathMode(); + } + + override useValueMode(): void { + const injector = this._injector; + const sheetsSourceBindService = injector.get(SheetsSourceBindService); + sheetsSourceBindService.useValueMode(); + } +} + +FWorkbook.extend(FWorkbookSourceBinding); + +declare module '@univerjs/sheets/facade' { + // eslint-disable-next-line ts/naming-convention + interface FWorkbook extends IFWorkbookSourceBindingMixin { } +} diff --git a/packages-experimental/sheets-source-binding/src/facade/f-worksheet.ts b/packages-experimental/sheets-source-binding/src/facade/f-worksheet.ts new file mode 100644 index 0000000000..7e8b8ef84d --- /dev/null +++ b/packages-experimental/sheets-source-binding/src/facade/f-worksheet.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 { ICellBindingNode, ICellBindingNodeParam } from '@univerjs/sheets-source-binding'; +import { SheetsSourceBindService } from '@univerjs/sheets-source-binding'; +import { FWorksheet } from '@univerjs/sheets/facade'; + +export interface IFworksheetSourceBindingMixin { + /** + * Set the binding node for the specified cell. + * @param bindingNode The binding node to set. {@link ICellBindingNodeParam} + */ + setBindingNode(bindingNode: ICellBindingNodeParam): void; + /** + * Remove the binding node for the specified cell. + * @param {number} row The row index of the cell. + * @param {number} column The column index of the cell. + */ + removeBindingNode(row: number, column: number): void; + /** + * Get the binding node for the specified cell. + * @param {number} row The row index of the cell. + * @param {number} column The column index of the cell. + * @returns The binding node of the cell. {@link ICellBindingNode} or undefined if not found. + */ + getBindingNode(row: number, column: number): ICellBindingNode | undefined; +} + +export class FWorksheetSourceBinding extends FWorksheet implements IFworksheetSourceBindingMixin { + override setBindingNode(bindingNode: ICellBindingNodeParam): void { + const injector = this._injector; + const sheetsSourceBindService = injector.get(SheetsSourceBindService); + const unitId = this._workbook.getUnitId(); + sheetsSourceBindService.setBindingNode(unitId, this.getSheetId(), bindingNode); + } + + override removeBindingNode(row: number, column: number): void { + const injector = this._injector; + const sheetsSourceBindService = injector.get(SheetsSourceBindService); + const unitId = this._workbook.getUnitId(); + sheetsSourceBindService.removeBindingNode(unitId, this.getSheetId(), row, column); + } + + override getBindingNode(row: number, column: number): ICellBindingNode | undefined { + const injector = this._injector; + const sheetsSourceBindService = injector.get(SheetsSourceBindService); + const unitId = this._workbook.getUnitId(); + return sheetsSourceBindService.getBindingNode(unitId, this.getSheetId(), row, column); + } +} + +FWorksheet.extend(FWorksheetSourceBinding); + +declare module '@univerjs/sheets/facade' { + // eslint-disable-next-line ts/naming-convention + interface FWorksheet extends IFworksheetSourceBindingMixin { } +} diff --git a/packages-experimental/sheets-source-binding/src/facade/index.ts b/packages-experimental/sheets-source-binding/src/facade/index.ts new file mode 100644 index 0000000000..1d949d1bab --- /dev/null +++ b/packages-experimental/sheets-source-binding/src/facade/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. + */ + +import './f-enum'; +import './f-workbook'; +import './f-worksheet'; diff --git a/packages-experimental/sheets-source-binding/src/index.ts b/packages-experimental/sheets-source-binding/src/index.ts new file mode 100644 index 0000000000..7777889b29 --- /dev/null +++ b/packages-experimental/sheets-source-binding/src/index.ts @@ -0,0 +1,22 @@ +/** + * 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 { SheetBindingModel as BindingModel } from './model/binding-model'; +export { SourceModelBase } from './model/source-model'; +export { UniverSheetsBindingSourcePlugin } from './plugin'; +export { BindModeEnum, DataBindingNodeTypeEnum } from './types'; +export { SheetsSourceBindService } from './services/source-binding-service'; +export type { ICellBindingNode, ICellBindingNodeParam } from './types'; diff --git a/packages-experimental/sheets-source-binding/src/model/binding-model.ts b/packages-experimental/sheets-source-binding/src/model/binding-model.ts new file mode 100644 index 0000000000..b00dc30bda --- /dev/null +++ b/packages-experimental/sheets-source-binding/src/model/binding-model.ts @@ -0,0 +1,97 @@ +/** + * 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 { ICellBindingNode, IDataBindingModelMatrix } from '../types'; + +export class SheetBindingModel { + private _matrix: IDataBindingModelMatrix = {}; + + private _nodeMap: Map = new Map(); + + private _sourceIdMap: Map = new Map(); + + constructor(json?: any) { + if (json) { + this._init(json); + } + } + + _init(json: any): void { + this.fromJSON(); + } + + getBindingNodesBySourceId(sourceId: string): ICellBindingNode[] | undefined { + const nodeIds = this._sourceIdMap.get(sourceId); + if (nodeIds) { + return nodeIds.map((nodeId) => this._nodeMap.get(nodeId)) as ICellBindingNode[]; + } + return undefined; + } + + public setBindingNode(row: number, column: number, node: ICellBindingNode): void { + if (!this._matrix[row]) { + this._matrix[row] = {}; + } + + if (!this._matrix[row][column]) { + this._matrix[row][column] = node; + } + this._nodeMap.set(node.nodeId, node); + // update sourceId map, keep the sourceId and nodeId mapping + const nodeIds = this._sourceIdMap.get(node.sourceId); + if (nodeIds) { + nodeIds.push(node.nodeId); + } else { + this._sourceIdMap.set(node.sourceId, [node.nodeId]); + } + } + + public getBindingNode(row: number, column: number): ICellBindingNode { + return this._matrix[row]?.[column]; + } + + public removeBindingNode(row: number, column: number): void { + const node = this._matrix[row]?.[column]; + if (node) { + // @ts-ignore + this._matrix[row][column] = undefined; + this._nodeMap.delete(node.nodeId); + const nodeIds = this._sourceIdMap.get(node.sourceId); + if (nodeIds) { + const index = nodeIds.indexOf(node.nodeId); + if (index >= 0) { + nodeIds.splice(index, 1); + } + if (nodeIds.length === 0) { + this._sourceIdMap.delete(node.sourceId); + } + } + } + } + + public getBindingNodeById(nodeId: string): ICellBindingNode | undefined { + return this._nodeMap.get(nodeId); + } + + fromJSON(): void { + + } + + toJSON() { + + } +} + diff --git a/packages-experimental/sheets-source-binding/src/model/source-model.ts b/packages-experimental/sheets-source-binding/src/model/source-model.ts new file mode 100644 index 0000000000..c633379a3b --- /dev/null +++ b/packages-experimental/sheets-source-binding/src/model/source-model.ts @@ -0,0 +1,127 @@ +/** + * 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 { ICellBindingNode, IListDataBindingNode, IListSourceData, IListSourceInfo, IObjectSourceInfo } from '../types'; +import { DataBindingNodeTypeEnum } from '../types'; + +export abstract class SourceModelBase { + protected _data: any; + readonly id: string; + private _hasData: boolean = false; + readonly type: DataBindingNodeTypeEnum; + constructor(id: string) { + this.id = id; + } + + getId() { + return this.id; + } + + getType(): DataBindingNodeTypeEnum { + return this.type; + } + + hasData(): boolean { + return this._hasData; + } + + setSourceData(data: any): void { + this._data = data; + this._hasData = true; + } + + abstract getSourceInfo(): any; + + abstract getData(node: ICellBindingNode, row: number, col: number): string | number | boolean | null; +} + +export class ListSourceModel extends SourceModelBase { + override readonly type = DataBindingNodeTypeEnum.List; + private _isListObject: boolean; + private _fieldIndexMap: Map = new Map(); + protected override _data: IListSourceData = { fields: [], records: [] }; + constructor(id: string, isListObject?: boolean) { + super(id); + this._isListObject = isListObject ?? true; + } + + /** + * Toggle the list object mode. The default value is true. + * In the list object mode, the records is an array of objects. Such as [{name: 'Tom', age: 20}, {name: 'Jerry', age: 18}]. + * In the list array mode, the records is an array of arrays. Such as [['Tom', 20], ['Jerry', 18]]. + */ + toggleListObject(isListObject: boolean) { + this._isListObject = isListObject; + } + + getData(node: IListDataBindingNode, row: number): string | number | boolean | null { + const { path, row: baseRow } = node; + const colIndex = this._fieldIndexMap.get(path)!; + const rowIndex = row - baseRow; + if (rowIndex === 0) { + return this._data.fields[colIndex]; + } + if (this._isListObject) { + return (this._data.records as Record)[rowIndex - 1][path]; + } + return this._data.records[rowIndex - 1][colIndex]; + } + + override setSourceData(data: IListSourceData): void { + super.setSourceData(data); + const { fields } = data; + this._fieldIndexMap.clear(); + fields.forEach((field, index) => { + this._fieldIndexMap.set(field, index); + }); + } + + override getSourceInfo(): IListSourceInfo { + return { + sourceId: this.id, + sourceType: this.type, + fields: this._data.fields, + recordCount: this._data.records.length, + }; + } +} + +export class ObjectSourceModel extends SourceModelBase { + override readonly type = DataBindingNodeTypeEnum.Object; + constructor(id: string) { + super(id); + } + + getData(node: ICellBindingNode): string | number | boolean | null { + const path = node.path; + const paths = path.split('.'); + let data = this._data; + for (const p of paths) { + data = data[p]; + if (data === undefined) { + return null; + } + } + return data; + } + + override getSourceInfo(): IObjectSourceInfo { + return { + sourceId: this.id, + sourceType: DataBindingNodeTypeEnum.Object, + }; + } +} diff --git a/packages-experimental/sheets-source-binding/src/plugin.ts b/packages-experimental/sheets-source-binding/src/plugin.ts new file mode 100644 index 0000000000..0f3f0fb527 --- /dev/null +++ b/packages-experimental/sheets-source-binding/src/plugin.ts @@ -0,0 +1,52 @@ +/** + * 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 { Dependency } from '@univerjs/core'; +import { IConfigService, Inject, Injector, Plugin, touchDependencies, UniverInstanceType } from '@univerjs/core'; +import { SheetsBindingManager } from './controllers/binding-manager'; +import { SheetsSourceManager } from './controllers/source-manager'; +import { SheetsSourceBindService } from './services/source-binding-service'; + +interface IUniverSheetsBindingSourceConfig {} + +export class UniverSheetsBindingSourcePlugin extends Plugin { + static override type = UniverInstanceType.UNIVER_SHEET; + static override pluginName = 'SHEET_BINDING_SOURCE_PLUGIN'; + + constructor( + private readonly _config: Partial = {}, + @Inject(Injector) override readonly _injector: Injector, + @IConfigService private readonly _configService: IConfigService + ) { + super(); + } + + override onStarting(): void { + ([ + [SheetsBindingManager], + [SheetsSourceManager], + [SheetsSourceBindService], + ] as Dependency[]).forEach((d) => this._injector.add(d)); + } + + override onReady(): void { + touchDependencies(this._injector, [ + [SheetsBindingManager], + [SheetsSourceManager], + [SheetsSourceBindService], + ]); + } +} diff --git a/packages-experimental/sheets-source-binding/src/services/source-binding-service.ts b/packages-experimental/sheets-source-binding/src/services/source-binding-service.ts new file mode 100644 index 0000000000..8ea6fb1fe5 --- /dev/null +++ b/packages-experimental/sheets-source-binding/src/services/source-binding-service.ts @@ -0,0 +1,250 @@ +/** + * 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 { ICellBindingNodeParam, IListSourceInfo } from '../types'; +import { Disposable, Inject, InterceptorEffectEnum, RTree } from '@univerjs/core'; +import { INTERCEPTOR_POINT, SheetInterceptorService } from '@univerjs/sheets'; + +import { SheetsBindingManager } from '../controllers/binding-manager'; +import { SheetsSourceManager } from '../controllers/source-manager'; +import { BindingSourceChangeTypeEnum, BindModeEnum, DataBindingNodeTypeEnum } from '../types'; + +export class SheetsSourceBindService extends Disposable { + private _bindingModel: BindModeEnum = BindModeEnum.Value; + private _bindModelRTreeCollection = new Map(); + constructor( + @Inject(SheetInterceptorService) private _sheetInterceptorService: SheetInterceptorService, + @Inject(SheetsBindingManager) private _sheetsBindingManager: SheetsBindingManager, + @Inject(SheetsSourceManager) private _sheetsSourceManager: SheetsSourceManager + ) { + super(); + this._registerInterceptor(); + this._registerSourceChange(); + } + + /** + * Set the binding model to path mode, in this mode, the binding path will show in the cell. + */ + usePathMode() { + this._bindingModel = BindModeEnum.Path; + } + + /** + * Set the binding model to value mode, in this mode, the value of source will show in the cell. + */ + useValueMode() { + this._bindingModel = BindModeEnum.Value; + } + + /** + * Get the current binding model. + * @returns the current binding model + */ + getBindingModel() { + return this._bindingModel; + } + + createBindModel(unitId: string, subUnitId: string) { + return this._sheetsBindingManager.createModel(unitId, subUnitId); + } + + setBindingNode(unitId: string, subUnitId: string, node: ICellBindingNodeParam) { + this._sheetsBindingManager.setBindingNode(unitId, subUnitId, node); + } + + removeBindingNode(unitId: string, subUnitId: string, row: number, column: number) { + this._sheetsBindingManager.removeBindingNode(unitId, subUnitId, row, column); + } + + getBindingNode(unitId: string, subUnitId: string, row: number, column: number) { + return this._sheetsBindingManager.getBindingNode(unitId, subUnitId, row, column); + } + + getSource(unitId: string, id: string) { + return this._sheetsSourceManager.getSource(unitId, id); + } + + createSource(unitId: string, type: DataBindingNodeTypeEnum, isListObject?: boolean, id?: string) { + return this._sheetsSourceManager.createSource(unitId, type, isListObject, id); + } + + private _ensureRTreeCollection(unitId: string) { + if (!this._bindModelRTreeCollection.has(unitId)) { + this._bindModelRTreeCollection.set(unitId, new RTree()); + } + return this._bindModelRTreeCollection.get(unitId) as RTree; + } + + private _getRTeeCollection(unitId: string) { + return this._bindModelRTreeCollection.get(unitId); + } + + // eslint-disable-next-line max-lines-per-function + private _registerSourceChange() { + // Update the RTree collection when the source data updated + this.disposeWithMe(this._sheetsSourceManager.sourceDataUpdate$.subscribe((sourceInfo) => { + const { sourceId, sourceType, unitId: sourceUnitId, changeType } = sourceInfo; + + if (sourceType === DataBindingNodeTypeEnum.List) { + // Remove the old data + if (changeType === BindingSourceChangeTypeEnum.Remove) { + const nodeInfo = this._sheetsBindingManager.getBindingModelBySourceId(sourceId); + const recordCount = sourceInfo.recordCount; + for (const { unitId, subunitId, nodeId, row, column } of nodeInfo) { + const rTreeCollection = this._getRTeeCollection(sourceUnitId); + if (rTreeCollection) { + const range = { startRow: row, startColumn: column, endRow: row + recordCount, endColumn: column }; + rTreeCollection.remove({ unitId, sheetId: subunitId, id: nodeId, range }); + } + } + return; + } + if (changeType === BindingSourceChangeTypeEnum.Update) { + const oldRecordCount = sourceInfo.oldRecordCount!; + const nodeInfo = this._sheetsBindingManager.getBindingModelBySourceId(sourceId); + for (const { unitId, subunitId, nodeId, row, column } of nodeInfo) { + const rTreeCollection = this._getRTeeCollection(sourceUnitId); + if (rTreeCollection) { + const oldRange = { startRow: row, startColumn: column, endRow: row + oldRecordCount, endColumn: column }; + const range = { startRow: row, startColumn: column, endRow: row + sourceInfo.recordCount, endColumn: column }; + rTreeCollection.remove({ unitId, sheetId: subunitId, id: nodeId, range: oldRange }); + rTreeCollection.insert({ unitId, sheetId: subunitId, id: nodeId, range }); + } + } + return; + } + + // Add the new data + const source = this._sheetsSourceManager.getSource(sourceUnitId, sourceId); + if (source && source.hasData()) { + const sourceInfo = source.getSourceInfo() as IListSourceInfo; + const recordCount = sourceInfo.recordCount; + const nodeInfo = this._sheetsBindingManager.getBindingModelBySourceId(sourceId); + + for (const { unitId, subunitId, nodeId, row, column } of nodeInfo) { + const rTreeCollection = this._ensureRTreeCollection(unitId); + const range = { startRow: row, startColumn: column, endRow: row + recordCount, endColumn: column }; + rTreeCollection.insert({ unitId, sheetId: subunitId, id: nodeId, range }); + } + } + } + })); + + // Update the RTree collection when the binding node updated + this.disposeWithMe(this._sheetsBindingManager.cellBindInfoUpdate$.subscribe((nodeInfo) => { + const { unitId, subunitId, sourceId, nodeId, row, column, changeType } = nodeInfo; + const rTreeCollection = this._ensureRTreeCollection(unitId); + const source = this._sheetsSourceManager.getSource(unitId, sourceId); + + if (source && source.hasData()) { + const sourceInfo = source.getSourceInfo() as IListSourceInfo; + if (sourceInfo.sourceType === DataBindingNodeTypeEnum.List) { + const recordCount = sourceInfo.recordCount; + const range = { startRow: row, startColumn: column, endRow: row + recordCount, endColumn: column }; + if (changeType === BindingSourceChangeTypeEnum.Add) { + rTreeCollection.insert({ unitId, sheetId: subunitId, id: nodeId, range }); + } else if (changeType === BindingSourceChangeTypeEnum.Remove) { + rTreeCollection.remove({ unitId, sheetId: subunitId, id: nodeId, range }); + } else if (changeType === BindingSourceChangeTypeEnum.Update) { + const oldSourceId = nodeInfo.oldSourceId!; + // remove the old range + const oldSource = this._sheetsSourceManager.getSource(unitId, oldSourceId); + if (oldSource && oldSource.hasData()) { + const oldSourceInfo = oldSource.getSourceInfo() as IListSourceInfo; + const oldRecordCount = oldSourceInfo.recordCount; + const oldRange = { startRow: row, startColumn: column, endRow: row + oldRecordCount, endColumn: column }; + rTreeCollection.remove({ unitId, sheetId: subunitId, id: nodeId, range: oldRange }); + } + rTreeCollection.insert({ unitId, sheetId: subunitId, id: nodeId, range }); + } + } + } + })); + } + + private _getPathModeCellValue(unitId: string, subUnitId: string, row: number, col: number) { + const model = this._sheetsBindingManager.getModel(unitId, subUnitId); + const node = model?.getBindingNode(row, col); + if (node) { + const nodeType = node.type; + if (nodeType === DataBindingNodeTypeEnum.List) { + return { + v: `#{${node.path}}`, + s: { cl: { rgb: 'blue' } }, + }; + } else if (nodeType === DataBindingNodeTypeEnum.Object) { + return { + v: `[${node.path}]`, + s: { cl: { rgb: 'blue' } }, + }; + } + } + } + + private _getValueModeCellValue(unitId: string, subUnitId: string, row: number, col: number) { + const model = this._sheetsBindingManager.getModel(unitId, subUnitId); + // object mode + if (model) { + const node = model.getBindingNode(row, col); + if (node) { + const { sourceId } = node; + const source = this._sheetsSourceManager.getSource(unitId, sourceId); + if (source && source.hasData()) { + return { + v: source?.getData(node, row, col) || '', + }; + } + } + } + const rTreeCollection = this._getRTeeCollection(unitId); + if (model && rTreeCollection) { + const range = { startRow: row, startColumn: col, endRow: row, endColumn: col }; + const nodeIds = Array.from(rTreeCollection.bulkSearch([{ unitId, sheetId: subUnitId, range }])); + if (nodeIds.length > 0) { + const node = model.getBindingNodeById(nodeIds[0] as string); + if (node) { + const { sourceId } = node; + const source = this._sheetsSourceManager.getSource(unitId, sourceId); + if (source && source.hasData()) { + return { + v: source?.getData(node, row, col) || '', + }; + } + } + } + } + } + + private _registerInterceptor() { + this.disposeWithMe(this._sheetInterceptorService.intercept(INTERCEPTOR_POINT.CELL_CONTENT, { + effect: InterceptorEffectEnum.Value, + handler: (cell, context, next) => { + const { row, col, unitId, subUnitId } = context; + let value = null; + if (this._bindingModel === BindModeEnum.Path) { + value = this._getPathModeCellValue(unitId, subUnitId, row, col); + } else { + value = this._getValueModeCellValue(unitId, subUnitId, row, col); + } + if (value !== null) { + return next({ ...cell, ...value }); + } + + return next(cell); + }, + })); + } +} diff --git a/packages-experimental/sheets-source-binding/src/types.ts b/packages-experimental/sheets-source-binding/src/types.ts new file mode 100644 index 0000000000..c41f68c773 --- /dev/null +++ b/packages-experimental/sheets-source-binding/src/types.ts @@ -0,0 +1,164 @@ +/** + * 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. + */ + +/** + * Represents the type of data binding node.Which config the array or object like data source or path. + */ +export enum DataBindingNodeTypeEnum { + /** + * The array like data source. + */ + List = 'list', + + // Cell = 'cell', + /** + * The object like data source. + */ + Object = 'object', +} + +export enum ListDataBindingNodeDirectionEnum { + Horizontal = 'horizontal', + Vertical = 'vertical', +} +/** + * Represents the type of data binding node.Which config show Binding path or value. + */ +export enum BindModeEnum { + /** + * The path mode, show the binding path. + */ + Path = 'path', + /** + * The value mode, show the binding value. + */ + Value = 'value', +} + +export type sourceDataType = string | number | boolean | object | null; + +export interface IDataSource { + id: string; + getData(row: number, column: number): sourceDataType; + +} + +export interface IListDataSource extends IDataSource { + getLength(): number; + getColumns(): string[]; +} +export interface IDataBindingModelMatrix { + [row: number]: { + [column: number]: ICellBindingNode; + }; +} + +export interface IDataBindingModel { + themeName?: string; + matrix: IDataBindingModelMatrix; + setBindingNode(row: number, column: number, node: ICellBindingNode): void; + +} + +/** + * The binding node of cell, which config the source id, path, row, column. + */ +export interface ICellBindingNodeParam { + /** + * The binding node type, the node type should be same as provide source type. + */ + type: DataBindingNodeTypeEnum; + /** + * The path of the binding node, the path should be same as provide source path. + * @example + * for object type : the source is :{user: {name :'Tom'}}, we can set path 'user.name' to represent the Tom. + * for list type : the source is :{fields:['name', 'age'], records:[['Tom', 18],['Jerry', 20]]}, we can set path 'name' to represent the all names in data. + */ + path: string; + /** + * The source id of the binding node, the source id should be same as provide source id. + */ + sourceId: string; + /** + * The target row of the binding node. + */ + row: number; + /** + * The target column of the binding node. + */ + column: number; + nodeId?: string; // optional in ICellBindingNodeParam +} + +export interface ICellBindingNode extends ICellBindingNodeParam { + /** + * The node id of ICellBindingNode, if not provide, will generate a random id. + */ + nodeId: string; +} +export interface IListDataBindingNode extends ICellBindingNode { + type: DataBindingNodeTypeEnum.List; + limit?: number; + direction?: ListDataBindingNodeDirectionEnum; + range?: { startRow: number; startColumn: number; endRow: number; endColumn: number }; + displayName?: string; +} + +export interface IDataBindingService { + /** + * A flag to control show path or show value + */ + isBindingMode: boolean; + /** + * Change the binding mode + * @param {BindModeEnum} mode Update the binding mode + */ + setBindingMode(mode: BindModeEnum): void; + +} + +export interface IListSourceInfo { + sourceId: string; + sourceType: DataBindingNodeTypeEnum.List; + fields: string[]; + recordCount: number; + + // range: null|{startRow:number, startColumn:number, endRow:number, endColumn:number}; +} + +export interface IObjectSourceInfo { + sourceId: string; + sourceType: DataBindingNodeTypeEnum.Object; + +} + +export type ISourceEvent = (IListSourceInfo | IObjectSourceInfo) & { + unitId: string; + changeType: BindingSourceChangeTypeEnum; + oldRecordCount?: number; +}; + +export interface IListSourceData { + fields: string[]; + records: any[][] | Record[]; +} + +export enum BindingSourceChangeTypeEnum { + Add = 'add', + Remove = 'remove', + Update = 'update', +} + diff --git a/packages-experimental/sheets-source-binding/tsconfig.json b/packages-experimental/sheets-source-binding/tsconfig.json new file mode 100644 index 0000000000..c62210cbad --- /dev/null +++ b/packages-experimental/sheets-source-binding/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@univerjs-infra/shared/tsconfigs/base", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "references": [{ "path": "./tsconfig.node.json" }], + "include": ["src"] +} diff --git a/packages-experimental/sheets-source-binding/tsconfig.node.json b/packages-experimental/sheets-source-binding/tsconfig.node.json new file mode 100644 index 0000000000..4947a09903 --- /dev/null +++ b/packages-experimental/sheets-source-binding/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": "@univerjs-infra/shared/tsconfigs/node", + "include": ["vitest.config.ts"] +} diff --git a/packages-experimental/sheets-source-binding/vitest.config.ts b/packages-experimental/sheets-source-binding/vitest.config.ts new file mode 100644 index 0000000000..af97c6d7a2 --- /dev/null +++ b/packages-experimental/sheets-source-binding/vitest.config.ts @@ -0,0 +1,3 @@ +import createConfig from '@univerjs-infra/shared/vitest'; + +export default createConfig(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8533d4f60c..b841e394d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -376,6 +376,9 @@ importers: '@univerjs/sheets-sort-ui': specifier: workspace:* version: link:../packages/sheets-sort-ui + '@univerjs/sheets-source-binding': + specifier: workspace:* + version: link:../packages-experimental/sheets-source-binding '@univerjs/sheets-thread-comment': specifier: workspace:* version: link:../packages/sheets-thread-comment @@ -749,6 +752,31 @@ importers: specifier: ^2.1.8 version: 2.1.8(@types/node@22.10.5)(happy-dom@15.0.0)(jsdom@24.1.1)(less@4.2.1)(sass@1.77.5)(terser@5.37.0) + packages-experimental/sheets-source-binding: + dependencies: + '@univerjs/core': + specifier: workspace:* + version: link:../../packages/core + '@univerjs/sheets': + specifier: workspace:* + version: link:../../packages/sheets + devDependencies: + '@univerjs-infra/shared': + specifier: workspace:* + version: link:../../common/shared + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + typescript: + specifier: ^5.7.2 + version: 5.7.2 + vite: + specifier: ^6.0.7 + version: 6.0.7(@types/node@22.10.5)(jiti@2.4.2)(less@4.2.1)(sass@1.77.5)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0) + vitest: + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.5)(happy-dom@15.0.0)(jsdom@24.1.1)(less@4.2.1)(sass@1.77.5)(terser@5.37.0) + packages-experimental/uni-docs-ui: dependencies: '@univerjs/core': @@ -10423,9 +10451,6 @@ packages: tslib@2.5.0: resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} - tslib@2.6.3: - resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -18188,7 +18213,7 @@ snapshots: regexp-util@2.0.0: dependencies: - tslib: 2.6.3 + tslib: 2.8.1 regexp.prototype.flags@1.5.3: dependencies: @@ -18381,7 +18406,7 @@ snapshots: rxjs@7.8.1: dependencies: - tslib: 2.6.3 + tslib: 2.8.1 safe-array-concat@1.1.2: dependencies: @@ -19004,8 +19029,6 @@ snapshots: tslib@2.5.0: {} - tslib@2.6.3: {} - tslib@2.8.1: {} tsx@4.19.2: